From 8af0edcf379db23238ff1ee00bb2371c19846738 Mon Sep 17 00:00:00 2001 From: Jamie Barton Date: Tue, 24 Feb 2026 16:28:10 +0000 Subject: [PATCH 1/4] refactor: use zod-openapi for ensnode-api --- .../ensapi/src/handlers/ensnode-api.routes.ts | 34 ++++++ apps/ensapi/src/handlers/ensnode-api.ts | 101 +++++------------- apps/ensapi/src/stub-routes.ts | 3 +- 3 files changed, 62 insertions(+), 76 deletions(-) create mode 100644 apps/ensapi/src/handlers/ensnode-api.routes.ts diff --git a/apps/ensapi/src/handlers/ensnode-api.routes.ts b/apps/ensapi/src/handlers/ensnode-api.routes.ts new file mode 100644 index 000000000..2d2fa3b01 --- /dev/null +++ b/apps/ensapi/src/handlers/ensnode-api.routes.ts @@ -0,0 +1,34 @@ +import { createRoute } from "@hono/zod-openapi"; + +export const basePath = "/api"; + +export const getConfigRoute = createRoute({ + method: "get", + path: "/config", + tags: ["Meta"], + summary: "Get ENSApi Public Config", + description: "Gets the public config of the ENSApi instance", + responses: { + 200: { + description: "Successfully retrieved ENSApi public config", + }, + }, +}); + +export const getIndexingStatusRoute = createRoute({ + method: "get", + path: "/indexing-status", + tags: ["Meta"], + summary: "Get ENSIndexer Indexing Status", + description: "Returns the indexing status snapshot most recently captured from ENSIndexer", + responses: { + 200: { + description: "Successfully retrieved indexing status", + }, + 503: { + description: "Indexing status snapshot unavailable", + }, + }, +}); + +export const routes = [getConfigRoute, getIndexingStatusRoute]; diff --git a/apps/ensapi/src/handlers/ensnode-api.ts b/apps/ensapi/src/handlers/ensnode-api.ts index 46d458dad..e4ae95369 100644 --- a/apps/ensapi/src/handlers/ensnode-api.ts +++ b/apps/ensapi/src/handlers/ensnode-api.ts @@ -1,7 +1,5 @@ import config from "@/config"; -import { describeRoute, resolver as validationResolver } from "hono-openapi"; - import { IndexingStatusResponseCodes, type IndexingStatusResponseError, @@ -9,93 +7,46 @@ import { serializeENSApiPublicConfig, serializeIndexingStatusResponse, } from "@ensnode/ensnode-sdk"; -import { - makeENSApiPublicConfigSchema, - makeIndexingStatusResponseSchema, -} from "@ensnode/ensnode-sdk/internal"; import { buildEnsApiPublicConfig } from "@/config/config.schema"; -import { factory } from "@/lib/hono-factory"; +import { createApp } from "@/lib/hono-factory"; +import { getConfigRoute, getIndexingStatusRoute } from "./ensnode-api.routes"; import ensnodeGraphQLApi from "./ensnode-graphql-api"; import nameTokensApi from "./name-tokens-api"; import registrarActionsApi from "./registrar-actions-api"; import resolutionApi from "./resolution-api"; -const app = factory.createApp(); - -app.get( - "/config", - describeRoute({ - tags: ["Meta"], - summary: "Get ENSApi Public Config", - description: "Gets the public config of the ENSApi instance", - responses: { - 200: { - description: "Successfully retrieved ENSApi public config", - content: { - "application/json": { - schema: validationResolver(makeENSApiPublicConfigSchema()), - }, - }, - }, - }, - }), - async (c) => { - const ensApiPublicConfig = buildEnsApiPublicConfig(config); - return c.json(serializeENSApiPublicConfig(ensApiPublicConfig)); - }, -); +const app = createApp(); -app.get( - "/indexing-status", - describeRoute({ - tags: ["Meta"], - summary: "Get ENSIndexer Indexing Status", - description: "Returns the indexing status snapshot most recently captured from ENSIndexer", - responses: { - 200: { - description: "Successfully retrieved indexing status", - content: { - "application/json": { - schema: validationResolver(makeIndexingStatusResponseSchema()), - }, - }, - }, - 503: { - description: "Indexing status snapshot unavailable", - content: { - "application/json": { - schema: validationResolver(makeIndexingStatusResponseSchema()), - }, - }, - }, - }, - }), - async (c) => { - // context must be set by the required middleware - if (c.var.indexingStatus === undefined) { - throw new Error(`Invariant(indexing-status): indexingStatusMiddleware required`); - } +app.openapi(getConfigRoute, async (c) => { + const ensApiPublicConfig = buildEnsApiPublicConfig(config); + return c.json(serializeENSApiPublicConfig(ensApiPublicConfig)); +}); - if (c.var.indexingStatus instanceof Error) { - return c.json( - serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Error, - } satisfies IndexingStatusResponseError), - 503, - ); - } +app.openapi(getIndexingStatusRoute, async (c) => { + // context must be set by the required middleware + if (c.var.indexingStatus === undefined) { + throw new Error(`Invariant(indexing-status): indexingStatusMiddleware required`); + } - // return successful response using the indexing status projection from the middleware context + if (c.var.indexingStatus instanceof Error) { return c.json( serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Ok, - realtimeProjection: c.var.indexingStatus, - } satisfies IndexingStatusResponseOk), + responseCode: IndexingStatusResponseCodes.Error, + } satisfies IndexingStatusResponseError), + 503, ); - }, -); + } + + // return successful response using the indexing status projection from the middleware context + return c.json( + serializeIndexingStatusResponse({ + responseCode: IndexingStatusResponseCodes.Ok, + realtimeProjection: c.var.indexingStatus, + } satisfies IndexingStatusResponseOk), + ); +}); // Name Tokens API app.route("/name-tokens", nameTokensApi); diff --git a/apps/ensapi/src/stub-routes.ts b/apps/ensapi/src/stub-routes.ts index 3a36ddf32..73a0372a9 100644 --- a/apps/ensapi/src/stub-routes.ts +++ b/apps/ensapi/src/stub-routes.ts @@ -1,6 +1,7 @@ import { OpenAPIHono } from "@hono/zod-openapi"; import * as amIRealtimeRoutes from "./handlers/amirealtime-api.routes"; +import * as ensnodeRoutes from "./handlers/ensnode-api.routes"; import * as resolutionRoutes from "./handlers/resolution-api.routes"; /** @@ -11,7 +12,7 @@ import * as resolutionRoutes from "./handlers/resolution-api.routes"; export function createStubRoutesForSpec() { const app = new OpenAPIHono(); - const routeGroups = [amIRealtimeRoutes, resolutionRoutes]; + const routeGroups = [amIRealtimeRoutes, ensnodeRoutes, resolutionRoutes]; for (const group of routeGroups) { for (const route of group.routes) { From 0367b9eb925704fc052448312da0a8f8a46dde1a Mon Sep 17 00:00:00 2001 From: Jamie Barton Date: Tue, 24 Feb 2026 16:40:24 +0000 Subject: [PATCH 2/4] add back missing response schemas --- .../ensapi/src/handlers/ensnode-api.routes.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/ensapi/src/handlers/ensnode-api.routes.ts b/apps/ensapi/src/handlers/ensnode-api.routes.ts index 2d2fa3b01..4a6860d80 100644 --- a/apps/ensapi/src/handlers/ensnode-api.routes.ts +++ b/apps/ensapi/src/handlers/ensnode-api.routes.ts @@ -1,5 +1,10 @@ import { createRoute } from "@hono/zod-openapi"; +import { + makeSerializedEnsApiIndexingStatusResponseSchema, + makeSerializedEnsApiPublicConfigSchema, +} from "@ensnode/ensnode-sdk/internal"; + export const basePath = "/api"; export const getConfigRoute = createRoute({ @@ -11,6 +16,11 @@ export const getConfigRoute = createRoute({ responses: { 200: { description: "Successfully retrieved ENSApi public config", + content: { + "application/json": { + schema: makeSerializedEnsApiPublicConfigSchema(), + }, + }, }, }, }); @@ -24,9 +34,19 @@ export const getIndexingStatusRoute = createRoute({ responses: { 200: { description: "Successfully retrieved indexing status", + content: { + "application/json": { + schema: makeSerializedEnsApiIndexingStatusResponseSchema(), + }, + }, }, 503: { description: "Indexing status snapshot unavailable", + content: { + "application/json": { + schema: makeSerializedEnsApiIndexingStatusResponseSchema(), + }, + }, }, }, }); From b8b9bc9e1692bec4be68e79cfe90daeebced9f91 Mon Sep 17 00:00:00 2001 From: Jamie Barton Date: Tue, 24 Feb 2026 17:02:42 +0000 Subject: [PATCH 3/4] fix: response code error --- apps/ensapi/src/handlers/ensnode-api.routes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ensapi/src/handlers/ensnode-api.routes.ts b/apps/ensapi/src/handlers/ensnode-api.routes.ts index 4a6860d80..ae9078e30 100644 --- a/apps/ensapi/src/handlers/ensnode-api.routes.ts +++ b/apps/ensapi/src/handlers/ensnode-api.routes.ts @@ -1,6 +1,7 @@ import { createRoute } from "@hono/zod-openapi"; import { + makeEnsApiIndexingStatusResponseErrorSchema, makeSerializedEnsApiIndexingStatusResponseSchema, makeSerializedEnsApiPublicConfigSchema, } from "@ensnode/ensnode-sdk/internal"; @@ -44,7 +45,7 @@ export const getIndexingStatusRoute = createRoute({ description: "Indexing status snapshot unavailable", content: { "application/json": { - schema: makeSerializedEnsApiIndexingStatusResponseSchema(), + schema: makeEnsApiIndexingStatusResponseErrorSchema(), }, }, }, From 20d1ba32d10f0ad793793d66ba14973a87181e22 Mon Sep 17 00:00:00 2001 From: Jamie Barton Date: Tue, 24 Feb 2026 17:35:54 +0000 Subject: [PATCH 4/4] fix: indexing-status types for 200 OK --- .../ensapi/src/handlers/ensnode-api.routes.ts | 4 ++-- apps/ensapi/src/handlers/ensnode-api.ts | 21 ++++++++++--------- .../ensapi/api/indexing-status/serialize.ts | 17 ++++++++++++++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/apps/ensapi/src/handlers/ensnode-api.routes.ts b/apps/ensapi/src/handlers/ensnode-api.routes.ts index ae9078e30..be00913b6 100644 --- a/apps/ensapi/src/handlers/ensnode-api.routes.ts +++ b/apps/ensapi/src/handlers/ensnode-api.routes.ts @@ -2,7 +2,7 @@ import { createRoute } from "@hono/zod-openapi"; import { makeEnsApiIndexingStatusResponseErrorSchema, - makeSerializedEnsApiIndexingStatusResponseSchema, + makeSerializedEnsApiIndexingStatusResponseOkSchema, makeSerializedEnsApiPublicConfigSchema, } from "@ensnode/ensnode-sdk/internal"; @@ -37,7 +37,7 @@ export const getIndexingStatusRoute = createRoute({ description: "Successfully retrieved indexing status", content: { "application/json": { - schema: makeSerializedEnsApiIndexingStatusResponseSchema(), + schema: makeSerializedEnsApiIndexingStatusResponseOkSchema(), }, }, }, diff --git a/apps/ensapi/src/handlers/ensnode-api.ts b/apps/ensapi/src/handlers/ensnode-api.ts index e4ae95369..96e65d0c9 100644 --- a/apps/ensapi/src/handlers/ensnode-api.ts +++ b/apps/ensapi/src/handlers/ensnode-api.ts @@ -1,11 +1,11 @@ import config from "@/config"; import { - IndexingStatusResponseCodes, - type IndexingStatusResponseError, - type IndexingStatusResponseOk, + EnsApiIndexingStatusResponseCodes, + type EnsApiIndexingStatusResponseError, + type EnsApiIndexingStatusResponseOk, serializeENSApiPublicConfig, - serializeIndexingStatusResponse, + serializeEnsApiIndexingStatusResponse, } from "@ensnode/ensnode-sdk"; import { buildEnsApiPublicConfig } from "@/config/config.schema"; @@ -32,19 +32,20 @@ app.openapi(getIndexingStatusRoute, async (c) => { if (c.var.indexingStatus instanceof Error) { return c.json( - serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Error, - } satisfies IndexingStatusResponseError), + serializeEnsApiIndexingStatusResponse({ + responseCode: EnsApiIndexingStatusResponseCodes.Error, + } satisfies EnsApiIndexingStatusResponseError), 503, ); } // return successful response using the indexing status projection from the middleware context return c.json( - serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Ok, + serializeEnsApiIndexingStatusResponse({ + responseCode: EnsApiIndexingStatusResponseCodes.Ok, realtimeProjection: c.var.indexingStatus, - } satisfies IndexingStatusResponseOk), + } satisfies EnsApiIndexingStatusResponseOk), + 200, ); }); diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts index 9cc13d5d6..5dcf85b87 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts @@ -1,13 +1,28 @@ import { serializeRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection"; -import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes } from "./response"; +import { + type EnsApiIndexingStatusResponse, + EnsApiIndexingStatusResponseCodes, + type EnsApiIndexingStatusResponseError, + type EnsApiIndexingStatusResponseOk, +} from "./response"; import type { SerializedEnsApiIndexingStatusResponse, + SerializedEnsApiIndexingStatusResponseError, SerializedEnsApiIndexingStatusResponseOk, } from "./serialized-response"; /** * Serialize a {@link EnsApiIndexingStatusResponse} object. */ +export function serializeEnsApiIndexingStatusResponse( + response: EnsApiIndexingStatusResponseOk, +): SerializedEnsApiIndexingStatusResponseOk; +export function serializeEnsApiIndexingStatusResponse( + response: EnsApiIndexingStatusResponseError, +): SerializedEnsApiIndexingStatusResponseError; +export function serializeEnsApiIndexingStatusResponse( + response: EnsApiIndexingStatusResponse, +): SerializedEnsApiIndexingStatusResponse; export function serializeEnsApiIndexingStatusResponse( response: EnsApiIndexingStatusResponse, ): SerializedEnsApiIndexingStatusResponse {