Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Comment thread
tk-o marked this conversation as resolved.
Outdated
6 changes: 3 additions & 3 deletions apps/ensapi/src/config/config.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import pRetry from "p-retry";
import { parse as parseConnectionString } from "pg-connection-string";
import { prettifyError, ZodError, z } from "zod/v4";

import { type ENSApiPublicConfig, serializeENSIndexerPublicConfig } from "@ensnode/ensnode-sdk";
import type { EnsApiPublicConfig } from "@ensnode/ensnode-sdk";
import {
buildRpcConfigsFromEnv,
canFallbackToTheGraph,
Expand Down Expand Up @@ -103,7 +103,7 @@ export async function buildConfigFromEnvironment(env: EnsApiEnvironment): Promis
databaseUrl: env.DATABASE_URL,
ensIndexerUrl: env.ENSINDEXER_URL,
theGraphApiKey: env.THEGRAPH_API_KEY,
ensIndexerPublicConfig: serializeENSIndexerPublicConfig(ensIndexerPublicConfig),
ensIndexerPublicConfig,
namespace: ensIndexerPublicConfig.namespace,
databaseSchemaName: ensIndexerPublicConfig.databaseSchemaName,
rpcConfigs,
Expand All @@ -128,7 +128,7 @@ export async function buildConfigFromEnvironment(env: EnsApiEnvironment): Promis
* @param config - The validated EnsApiConfig object
* @returns A complete ENSApiPublicConfig object
*/
export function buildEnsApiPublicConfig(config: EnsApiConfig): ENSApiPublicConfig {
export function buildEnsApiPublicConfig(config: EnsApiConfig): EnsApiPublicConfig {
return {
version: packageJson.version,
theGraphFallback: canFallbackToTheGraph({
Expand Down
5 changes: 3 additions & 2 deletions packages/ensnode-sdk/src/ensapi/api/config/deserialize.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Unvalidated } from "../../../shared/types";
import { deserializeEnsApiPublicConfig } from "../../config/deserialize";
import type { EnsApiConfigResponse } from "./response";
import type { SerializedEnsApiConfigResponse } from "./serialized-response";
Expand All @@ -6,9 +7,9 @@ import type { SerializedEnsApiConfigResponse } from "./serialized-response";
* Deserialize a {@link EnsApiConfigResponse} object.
*/
export function deserializeEnsApiConfigResponse(
serializedResponse: SerializedEnsApiConfigResponse,
maybeResponse: Unvalidated<SerializedEnsApiConfigResponse>,
): EnsApiConfigResponse {
return deserializeEnsApiPublicConfig(serializedResponse);
return deserializeEnsApiPublicConfig(maybeResponse);
}

/**
Expand Down
47 changes: 35 additions & 12 deletions packages/ensnode-sdk/src/ensapi/config/deserialize.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
import { prettifyError, ZodError } from "zod/v4";
import { prettifyError } from "zod/v4";

import { buildUnvalidatedEnsIndexerPublicConfig } from "../../ensindexer/config/deserialize";
import type { Unvalidated } from "../../shared/types";
import type { SerializedEnsApiPublicConfig } from "./serialized-types";
import type { EnsApiPublicConfig } from "./types";
import { makeEnsApiPublicConfigSchema } from "./zod-schemas";
import {
makeEnsApiPublicConfigSchema,
makeSerializedEnsApiPublicConfigSchema,
} from "./zod-schemas";

/**
* Deserialize a {@link EnsApiPublicConfig} object.
* Builds an unvalidated {@link EnsApiPublicConfig} object to be
* validated with {@link makeEnsApiPublicConfigSchema}.
*
* @param serializedPublicConfig - The serialized public config to build from.
* @return An unvalidated {@link EnsApiPublicConfig} object.
*/
function buildUnvalidatedEnsApiPublicConfig(
serializedPublicConfig: SerializedEnsApiPublicConfig,
): Unvalidated<EnsApiPublicConfig> {
return {
...serializedPublicConfig,
ensIndexerPublicConfig: buildUnvalidatedEnsIndexerPublicConfig(
serializedPublicConfig.ensIndexerPublicConfig,
),
};
}

/**
* Deserialize value into {@link EnsApiPublicConfig} object.
*/
export function deserializeEnsApiPublicConfig(
maybeConfig: SerializedEnsApiPublicConfig,
maybePublicConfig: Unvalidated<SerializedEnsApiPublicConfig>,
valueLabel?: string,
): EnsApiPublicConfig {
const schema = makeEnsApiPublicConfigSchema(valueLabel);
try {
return schema.parse(maybeConfig);
} catch (error) {
if (error instanceof ZodError) {
throw new Error(`Cannot deserialize EnsApiPublicConfig:\n${prettifyError(error)}\n`);
}
const parsed = makeSerializedEnsApiPublicConfigSchema(valueLabel)
.transform(buildUnvalidatedEnsApiPublicConfig)
.pipe(makeEnsApiPublicConfigSchema(valueLabel))
.safeParse(maybePublicConfig);

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

return parsed.data;
}

/**
Expand Down
21 changes: 18 additions & 3 deletions packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { z } from "zod/v4";

import { makeEnsIndexerPublicConfigSchema } from "../../ensindexer/config/zod-schemas";
import {
makeEnsIndexerPublicConfigSchema,
makeSerializedEnsIndexerPublicConfigSchema,
} from "../../ensindexer/config/zod-schemas";
import {
TheGraphCannotFallbackReasonSchema,
TheGraphFallbackSchema,
Expand All @@ -9,14 +12,14 @@ import {
export { TheGraphCannotFallbackReasonSchema, TheGraphFallbackSchema };

/**
* Create a Zod schema for validating a serialized ENSApiPublicConfig.
* Create a Zod schema for validating ENSApiPublicConfig.
*
* @param valueLabel - Optional label for the value being validated (used in error messages)
*/
export function makeEnsApiPublicConfigSchema(valueLabel?: string) {
const label = valueLabel ?? "ENSApiPublicConfig";

return z.strictObject({
return z.object({
version: z.string().min(1, `${label}.version must be a non-empty string`),
theGraphFallback: TheGraphFallbackSchema,
ensIndexerPublicConfig: makeEnsIndexerPublicConfigSchema(`${label}.ensIndexerPublicConfig`),
Expand All @@ -29,3 +32,15 @@ export function makeEnsApiPublicConfigSchema(valueLabel?: string) {
* @deprecated Use {@link makeEnsApiPublicConfigSchema} instead.
*/
export const makeENSApiPublicConfigSchema = makeEnsApiPublicConfigSchema;

export function makeSerializedEnsApiPublicConfigSchema(valueLabel?: string) {
const label = valueLabel ?? "ENSApiPublicConfig";

return z.object({
version: z.string().min(1, `${label}.version must be a non-empty string`),
theGraphFallback: TheGraphFallbackSchema,
ensIndexerPublicConfig: makeSerializedEnsIndexerPublicConfigSchema(
`${label}.ensIndexerPublicConfig`,
),
});
}
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 = {};
Comment thread
tk-o marked this conversation as resolved.
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;
Loading
Loading