Skip to content
Draft
5 changes: 5 additions & 0 deletions .changeset/petite-rings-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": minor
---

Introduces `EnsIndexerClient` class, which allows interacting with ENSIndexer APIs.
13 changes: 13 additions & 0 deletions packages/ensnode-sdk/src/ensindexer/api/config/deserialize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Unvalidated } from "../../../shared/types";
import { deserializeEnsIndexerPublicConfig } from "../../config/deserialize";
import type { EnsIndexerConfigResponse } from "./response";
import type { SerializedEnsIndexerConfigResponse } from "./serialized-response";

/**
* Deserialize value into {@link EnsIndexerConfigResponse} object.
*/
export function deserializeEnsIndexerConfigResponse(
maybeResponse: Unvalidated<SerializedEnsIndexerConfigResponse>,
): EnsIndexerConfigResponse {
return deserializeEnsIndexerPublicConfig(maybeResponse, "EnsIndexerConfigResponse");
}
4 changes: 4 additions & 0 deletions packages/ensnode-sdk/src/ensindexer/api/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./deserialize";
export * from "./response";
export * from "./serialize";
export * from "./serialized-response";
6 changes: 6 additions & 0 deletions packages/ensnode-sdk/src/ensindexer/api/config/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { EnsIndexerPublicConfig } from "../../config/types";

/**
* ENSIndexer Public Config Response
*/
export type EnsIndexerConfigResponse = EnsIndexerPublicConfig;
9 changes: 9 additions & 0 deletions packages/ensnode-sdk/src/ensindexer/api/config/serialize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { serializeEnsIndexerPublicConfig } from "../../config/serialize";
import type { EnsIndexerConfigResponse } from "./response";
import type { SerializedEnsIndexerConfigResponse } from "./serialized-response";

export function serializeEnsIndexerConfigResponse(
response: EnsIndexerConfigResponse,
): SerializedEnsIndexerConfigResponse {
return serializeEnsIndexerPublicConfig(response);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { SerializedEnsIndexerPublicConfig } from "../../config/serialized-types";

export type SerializedEnsIndexerConfigResponse = SerializedEnsIndexerPublicConfig;
2 changes: 2 additions & 0 deletions packages/ensnode-sdk/src/ensindexer/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./config";
export * from "./indexing-status";
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { prettifyError } from "zod/v4";

import { buildUnvalidatedRealtimeIndexingStatusProjection } from "../../../indexing-status/deserialize/realtime-indexing-status-projection";
import type { Unvalidated } from "../../../shared/types";
import {
type EnsIndexerIndexingStatusResponse,
EnsIndexerIndexingStatusResponseCodes,
} from "./response";
import type { SerializedEnsIndexerIndexingStatusResponse } from "./serialized-response";
import {
makeEnsIndexerIndexingStatusResponseSchema,
makeSerializedEnsIndexerIndexingStatusResponseSchema,
} from "./zod-schemas";

/**
* Builds an unvalidated {@link EnsIndexerIndexingStatusResponse} object to be
* validated with {@link makeEnsIndexerIndexingStatusResponseSchema}.
*
* @param serializedResponse - The serialized response to build from.
* @return An unvalidated {@link EnsIndexerIndexingStatusResponse} object.
*/
function buildUnvalidatedEnsIndexerIndexingStatusResponse(
serializedResponse: SerializedEnsIndexerIndexingStatusResponse,
): Unvalidated<EnsIndexerIndexingStatusResponse> {
if (serializedResponse.responseCode !== EnsIndexerIndexingStatusResponseCodes.Ok) {
return serializedResponse;
}

return {
...serializedResponse,
realtimeProjection: buildUnvalidatedRealtimeIndexingStatusProjection(
serializedResponse.realtimeProjection,
),
};
}

/**
* Deserialize a {@link EnsIndexerIndexingStatusResponse} object.
*/
export function deserializeEnsIndexerIndexingStatusResponse(
maybeResponse: Unvalidated<SerializedEnsIndexerIndexingStatusResponse>,
): EnsIndexerIndexingStatusResponse {
const parsed = makeSerializedEnsIndexerIndexingStatusResponseSchema()
.transform(buildUnvalidatedEnsIndexerIndexingStatusResponse)
.pipe(makeEnsIndexerIndexingStatusResponseSchema())
.safeParse(maybeResponse);

if (parsed.error) {
throw new Error(
`Cannot deserialize EnsIndexerIndexingStatusResponse:\n${prettifyError(parsed.error)}\n`,
);
}

return parsed.data;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./deserialize";
export * from "./request";
export * from "./response";
export * from "./serialize";
export * from "./serialized-response";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Represents a request to ENSIndexer Indexing Status API.
*/
export type EnsIndexerIndexingStatusRequest = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { RealtimeIndexingStatusProjection } from "../../../indexing-status/realtime-indexing-status-projection";

/**
* A status code for ENSIndexer indexing status responses.
*/
export const EnsIndexerIndexingStatusResponseCodes = {
/**
* Represents that the indexing status is available.
*/
Ok: "ok",

/**
* Represents that the indexing status is unavailable.
*/
Error: "error",
} as const;

/**
* The derived string union of possible {@link EnsIndexerIndexingStatusResponseCodes}.
*/
export type EnsIndexerIndexingStatusResponseCode =
(typeof EnsIndexerIndexingStatusResponseCodes)[keyof typeof EnsIndexerIndexingStatusResponseCodes];

/**
* An ENSIndexer indexing status response when the indexing status is available.
*/
export type EnsIndexerIndexingStatusResponseOk = {
responseCode: typeof EnsIndexerIndexingStatusResponseCodes.Ok;
realtimeProjection: RealtimeIndexingStatusProjection;
};

/**
* An ENSIndexer indexing status response when the indexing status is unavailable.
*/
export type EnsIndexerIndexingStatusResponseError = {
responseCode: typeof EnsIndexerIndexingStatusResponseCodes.Error;
};

/**
* ENSIndexer indexing status response.
*
* Use the `responseCode` field to determine the specific type interpretation
* at runtime.
*/
export type EnsIndexerIndexingStatusResponse =
| EnsIndexerIndexingStatusResponseOk
| EnsIndexerIndexingStatusResponseError;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { serializeRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection";
import {
type EnsIndexerIndexingStatusResponse,
EnsIndexerIndexingStatusResponseCodes,
} from "./response";
import type {
SerializedEnsIndexerIndexingStatusResponse,
SerializedEnsIndexerIndexingStatusResponseOk,
} from "./serialized-response";

export function serializeEnsIndexerIndexingStatusResponse(
response: EnsIndexerIndexingStatusResponse,
): SerializedEnsIndexerIndexingStatusResponse {
switch (response.responseCode) {
case EnsIndexerIndexingStatusResponseCodes.Ok:
return {
responseCode: response.responseCode,
realtimeProjection: serializeRealtimeIndexingStatusProjection(response.realtimeProjection),
} satisfies SerializedEnsIndexerIndexingStatusResponseOk;

case EnsIndexerIndexingStatusResponseCodes.Error:
return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { SerializedRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection";
import type {
EnsIndexerIndexingStatusResponseError,
EnsIndexerIndexingStatusResponseOk,
} from "./response";

/**
* Serialized representation of {@link EnsIndexerIndexingStatusResponseError}.
*/
export type SerializedEnsIndexerIndexingStatusResponseError = EnsIndexerIndexingStatusResponseError;

/**
* Serialized representation of {@link EnsIndexerIndexingStatusResponseOk}.
*/
export interface SerializedEnsIndexerIndexingStatusResponseOk
extends Omit<EnsIndexerIndexingStatusResponseOk, "realtimeProjection"> {
realtimeProjection: SerializedRealtimeIndexingStatusProjection;
}

/**
* Serialized representation of {@link EnsIndexerIndexingStatusResponse}.
*/
export type SerializedEnsIndexerIndexingStatusResponse =
| SerializedEnsIndexerIndexingStatusResponseOk
| SerializedEnsIndexerIndexingStatusResponseError;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { z } from "zod/v4";

import {
makeRealtimeIndexingStatusProjectionSchema,
makeSerializedRealtimeIndexingStatusProjectionSchema,
} from "../../../indexing-status/zod-schema/realtime-indexing-status-projection";
import {
type EnsIndexerIndexingStatusResponse,
EnsIndexerIndexingStatusResponseCodes,
type EnsIndexerIndexingStatusResponseError,
type EnsIndexerIndexingStatusResponseOk,
} from "./response";
import {
SerializedEnsIndexerIndexingStatusResponse,
SerializedEnsIndexerIndexingStatusResponseOk,
} from "./serialized-response";

/**
* Schema for {@link EnsIndexerIndexingStatusResponseOk}
**/
export const makeEnsIndexerIndexingStatusResponseOkSchema = (
valueLabel: string = "Indexing Status Response OK",
) =>
z.strictObject({
responseCode: z.literal(EnsIndexerIndexingStatusResponseCodes.Ok),
realtimeProjection: makeRealtimeIndexingStatusProjectionSchema(valueLabel),
});

/**
* Schema for {@link EnsIndexerIndexingStatusResponseError}
**/
export const makeEnsIndexerIndexingStatusResponseErrorSchema = (
_valueLabel: string = "Indexing Status Response Error",
) =>
z.strictObject({
responseCode: z.literal(EnsIndexerIndexingStatusResponseCodes.Error),
});

/**
* Schema for {@link EnsIndexerIndexingStatusResponse}
**/
export const makeEnsIndexerIndexingStatusResponseSchema = (
valueLabel: string = "Indexing Status Response",
) =>
z.discriminatedUnion("responseCode", [
makeEnsIndexerIndexingStatusResponseOkSchema(valueLabel),
makeEnsIndexerIndexingStatusResponseErrorSchema(valueLabel),
]);

/**
* Schema for {@link SerializedEnsIndexerIndexingStatusResponseOk}
**/
export const makeSerializedEnsIndexerIndexingStatusResponseOkSchema = (
valueLabel: string = "Serialized Indexing Status Response OK",
) =>
z.strictObject({
responseCode: z.literal(EnsIndexerIndexingStatusResponseCodes.Ok),
realtimeProjection: makeSerializedRealtimeIndexingStatusProjectionSchema(valueLabel),
});

/**
* Schema for {@link SerializedEnsIndexerIndexingStatusResponse}
**/
export const makeSerializedEnsIndexerIndexingStatusResponseSchema = (
valueLabel: string = "Serialized Indexing Status Response",
) =>
z.discriminatedUnion("responseCode", [
makeSerializedEnsIndexerIndexingStatusResponseOkSchema(valueLabel),
makeEnsIndexerIndexingStatusResponseErrorSchema(valueLabel),
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { prettifyError } from "zod/v4";

import type { ErrorResponse } from "./response";
import { ErrorResponseSchema } from "./zod-schemas";

/**
* Deserialize a {@link ErrorResponse} object.
*/
export function deserializeErrorResponse(maybeErrorResponse: unknown): ErrorResponse {
const parsed = ErrorResponseSchema.safeParse(maybeErrorResponse);

if (parsed.error) {
throw new Error(`Cannot deserialize ErrorResponse:\n${prettifyError(parsed.error)}\n`);
}

return parsed.data;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./deserialize";
export * from "./response";
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { z } from "zod/v4";

import type { ErrorResponseSchema } from "./zod-schemas";

/**
* API Error Response Type
*/
export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { z } from "zod/v4";

// biome-ignore lint/correctness/noUnusedImports: ErrorResponse is used in JSDoc @link
import type { ErrorResponse } from "./response";

/**
* Schema for {@link ErrorResponse}.
*/
export const ErrorResponseSchema = z.object({
message: z.string(),
details: z.optional(z.unknown()),
});
Loading