Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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