From 2fcac8313725afb544336eb05e25ce1de5e481e2 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 09:56:19 +0100 Subject: [PATCH 01/14] Extract OmnichainIndexingStatus business-layer from generic types.ts file --- .../omnichain-indexing-status-snapshot.ts | 198 ++++++++++++++++++ .../src/ensindexer/indexing-status/types.ts | 197 +---------------- 2 files changed, 200 insertions(+), 195 deletions(-) create mode 100644 packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts new file mode 100644 index 000000000..2943e591c --- /dev/null +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts @@ -0,0 +1,198 @@ +import type { ChainId, UnixTimestamp } from "../../shared/types"; +import { + ChainIndexingStatusIds, + type ChainIndexingStatusSnapshot, + type ChainIndexingStatusSnapshotBackfill, + type ChainIndexingStatusSnapshotCompleted, + type ChainIndexingStatusSnapshotQueued, +} from "./chain-indexing-status-snapshot"; + +/** + * The status of omnichain indexing at the time an omnichain indexing status + * snapshot is captured. + */ +export const OmnichainIndexingStatusIds = { + /** + * Represents that omnichain indexing is not ready to begin yet because + * ENSIndexer is in its initialization phase and the data to build a "true" + * {@link OmnichainIndexingStatusSnapshot} is still being loaded. + */ + Unstarted: "omnichain-unstarted", + + /** + * Represents that omnichain indexing is in an overall "backfill" status because + * - At least one indexed chain has a `chainStatus` of + * {@link ChainIndexingStatusIds.Backfill}; and + * - No indexed chain has a `chainStatus` of {@link ChainIndexingStatusIds.Following}. + */ + Backfill: "omnichain-backfill", + + /** + * Represents that omnichain indexing is in an overall "following" status because + * at least one indexed chain has a `chainStatus` of + * {@link ChainIndexingStatusIds.Following}. + */ + Following: "omnichain-following", + + /** + * Represents that omnichain indexing has completed because all indexed chains have + * a `chainStatus` of {@link ChainIndexingStatusIds.Completed}. + */ + Completed: "omnichain-completed", +} as const; + +/** + * The derived string union of possible {@link OmnichainIndexingStatusIds}. + */ +export type OmnichainIndexingStatusId = + (typeof OmnichainIndexingStatusIds)[keyof typeof OmnichainIndexingStatusIds]; + +/** + * Omnichain indexing status snapshot when the overall `omnichainStatus` is + * {@link OmnichainIndexingStatusIds.Unstarted}. + * + * Invariants: + * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Unstarted}. + * - `chains` is always a map to {@link ChainIndexingStatusSnapshotQueued} values exclusively. + * - `omnichainIndexingCursor` is always < the `config.startBlock.timestamp` for all + * chains with `chainStatus` of {@link ChainIndexingStatusIds.Queued}. + */ +export interface OmnichainIndexingStatusSnapshotUnstarted { + /** + * The status of omnichain indexing. + */ + omnichainStatus: typeof OmnichainIndexingStatusIds.Unstarted; + + /** + * The indexing status snapshot for each indexed chain. + */ + chains: Map; + + /** + * The timestamp of omnichain indexing progress across all indexed chains. + */ + omnichainIndexingCursor: UnixTimestamp; +} + +/** + * The range of {@link ChainIndexingSnapshot} types allowed when the + * overall omnichain indexing status is {@link OmnichainIndexingStatusIds.Backfill}. + * + * Note that this is all of the {@link ChainIndexingSnapshot} types with the exception + * of {@link ChainIndexingStatusSnapshotFollowing}. + */ +export type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill = + | ChainIndexingStatusSnapshotQueued + | ChainIndexingStatusSnapshotBackfill + | ChainIndexingStatusSnapshotCompleted; + +/** + * Omnichain indexing status snapshot when the `omnichainStatus` is + * {@link OmnichainIndexingStatusIds.Backfill}. + * + * Invariants: + * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Backfill}. + * - `chains` is guaranteed to contain at least one chain with a `chainStatus` of + * {@link ChainIndexingStatusIds.Backfill}. + * - `chains` is guaranteed to not to contain any chain with a `chainStatus` of + * {@link ChainIndexingStatusIds.Following} + * - `omnichainIndexingCursor` is always < the `config.startBlock.timestamp` for all + * chains with `chainStatus` of {@link ChainIndexingStatusIds.Queued}. + * - `omnichainIndexingCursor` is always <= the `backfillEndBlock.timestamp` for all + * chains with `chainStatus` of {@link ChainIndexingStatusIds.Backfill}. + * - `omnichainIndexingCursor` is always >= the `latestIndexedBlock.timestamp` for all + * chains with `chainStatus` of {@link ChainIndexingStatusIds.Completed}. + * - `omnichainIndexingCursor` is always equal to the timestamp of the highest + * `latestIndexedBlock` across all chains that have started indexing + * (`chainStatus` is not {@link ChainIndexingStatusIds.Queued}). + */ +export interface OmnichainIndexingStatusSnapshotBackfill { + /** + * The status of omnichain indexing. + */ + omnichainStatus: typeof OmnichainIndexingStatusIds.Backfill; + + /** + * The indexing status snapshot for each indexed chain. + */ + chains: Map; + + /** + * The timestamp of omnichain indexing progress across all indexed chains. + */ + omnichainIndexingCursor: UnixTimestamp; +} + +/** + * Omnichain indexing status snapshot when the overall `omnichainStatus` is + * {@link OmnichainIndexingStatusIds.Following}. + * + * Invariants: + * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Following}. + * - `chains` is guaranteed to contain at least one chain with a `status` of + * {@link ChainIndexingStatusIds.Following}. + * - `omnichainIndexingCursor` is always < the `config.startBlock.timestamp` for all + * chains with `chainStatus` of {@link ChainIndexingStatusIds.Queued}. + * - `omnichainIndexingCursor` is always <= the `backfillEndBlock.timestamp` for all + * chains with `chainStatus` of {@link ChainIndexingStatusIds.Backfill}. + * - `omnichainIndexingCursor` is always >= the `latestIndexedBlock.timestamp` for all + * chains with `chainStatus` of {@link ChainIndexingStatusIds.Completed}. + * - `omnichainIndexingCursor` is always equal to the timestamp of the highest + * `latestIndexedBlock` across all chains that have started indexing + * (`chainStatus` is not {@link ChainIndexingStatusIds.Queued}). + */ +export interface OmnichainIndexingStatusSnapshotFollowing { + /** + * The status of omnichain indexing. + */ + omnichainStatus: typeof OmnichainIndexingStatusIds.Following; + + /** + * The indexing status snapshot for each indexed chain. + */ + chains: Map; + + /** + * The timestamp of omnichain indexing progress across all indexed chains. + */ + omnichainIndexingCursor: UnixTimestamp; +} + +/** + * Omnichain indexing status snapshot when the overall `omnichainStatus` is + * {@link OmnichainIndexingStatusIds.Completed}. + * + * Invariants: + * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Completed}. + * - `chains` is always a map to {@link ChainIndexingStatusSnapshotCompleted} values exclusively. + * - `omnichainIndexingCursor` is always equal to the highest + * `latestIndexedBlock.timestamp` for all chains. + */ +export interface OmnichainIndexingStatusSnapshotCompleted { + /** + * The status of omnichain indexing. + */ + omnichainStatus: typeof OmnichainIndexingStatusIds.Completed; + + /** + * The indexing status snapshot for each indexed chain. + */ + chains: Map; + + /** + * The timestamp of omnichain indexing progress across all indexed chains. + */ + omnichainIndexingCursor: UnixTimestamp; +} + +/** + * Omnichain indexing status snapshot for one or more chains. + * + * Use the `omnichainStatus` field to determine the specific type interpretation + * at runtime. + */ +export type OmnichainIndexingStatusSnapshot = + | OmnichainIndexingStatusSnapshotUnstarted + | OmnichainIndexingStatusSnapshotBackfill + | OmnichainIndexingStatusSnapshotCompleted + | OmnichainIndexingStatusSnapshotFollowing; diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/types.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/types.ts index c22be0e05..8165b0732 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/types.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/types.ts @@ -1,202 +1,9 @@ -import type { ChainId, Duration, UnixTimestamp } from "../../shared/types"; +import type { Duration, UnixTimestamp } from "../../shared/types"; import type { ChainIndexingConfigTypeIds, ChainIndexingStatusIds, - ChainIndexingStatusSnapshot, - ChainIndexingStatusSnapshotBackfill, - ChainIndexingStatusSnapshotCompleted, - ChainIndexingStatusSnapshotQueued, } from "./chain-indexing-status-snapshot"; - -/** - * The status of omnichain indexing at the time an omnichain indexing status - * snapshot is captured. - */ -export const OmnichainIndexingStatusIds = { - /** - * Represents that omnichain indexing is not ready to begin yet because - * ENSIndexer is in its initialization phase and the data to build a "true" - * {@link OmnichainIndexingStatusSnapshot} is still being loaded. - */ - Unstarted: "omnichain-unstarted", - - /** - * Represents that omnichain indexing is in an overall "backfill" status because - * - At least one indexed chain has a `chainStatus` of - * {@link ChainIndexingStatusIds.Backfill}; and - * - No indexed chain has a `chainStatus` of {@link ChainIndexingStatusIds.Following}. - */ - Backfill: "omnichain-backfill", - - /** - * Represents that omnichain indexing is in an overall "following" status because - * at least one indexed chain has a `chainStatus` of - * {@link ChainIndexingStatusIds.Following}. - */ - Following: "omnichain-following", - - /** - * Represents that omnichain indexing has completed because all indexed chains have - * a `chainStatus` of {@link ChainIndexingStatusIds.Completed}. - */ - Completed: "omnichain-completed", -} as const; - -/** - * The derived string union of possible {@link OmnichainIndexingStatusIds}. - */ -export type OmnichainIndexingStatusId = - (typeof OmnichainIndexingStatusIds)[keyof typeof OmnichainIndexingStatusIds]; - -/** - * Omnichain indexing status snapshot when the overall `omnichainStatus` is - * {@link OmnichainIndexingStatusIds.Unstarted}. - * - * Invariants: - * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Unstarted}. - * - `chains` is always a map to {@link ChainIndexingStatusSnapshotQueued} values exclusively. - * - `omnichainIndexingCursor` is always < the `config.startBlock.timestamp` for all - * chains with `chainStatus` of {@link ChainIndexingStatusIds.Queued}. - */ -export interface OmnichainIndexingStatusSnapshotUnstarted { - /** - * The status of omnichain indexing. - */ - omnichainStatus: typeof OmnichainIndexingStatusIds.Unstarted; - - /** - * The indexing status snapshot for each indexed chain. - */ - chains: Map; - - /** - * The timestamp of omnichain indexing progress across all indexed chains. - */ - omnichainIndexingCursor: UnixTimestamp; -} - -/** - * The range of {@link ChainIndexingSnapshot} types allowed when the - * overall omnichain indexing status is {@link OmnichainIndexingStatusIds.Backfill}. - * - * Note that this is all of the {@link ChainIndexingSnapshot} types with the exception - * of {@link ChainIndexingStatusSnapshotFollowing}. - */ -export type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill = - | ChainIndexingStatusSnapshotQueued - | ChainIndexingStatusSnapshotBackfill - | ChainIndexingStatusSnapshotCompleted; - -/** - * Omnichain indexing status snapshot when the `omnichainStatus` is - * {@link OmnichainIndexingStatusIds.Backfill}. - * - * Invariants: - * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Backfill}. - * - `chains` is guaranteed to contain at least one chain with a `chainStatus` of - * {@link ChainIndexingStatusIds.Backfill}. - * - `chains` is guaranteed to not to contain any chain with a `chainStatus` of - * {@link ChainIndexingStatusIds.Following} - * - `omnichainIndexingCursor` is always < the `config.startBlock.timestamp` for all - * chains with `chainStatus` of {@link ChainIndexingStatusIds.Queued}. - * - `omnichainIndexingCursor` is always <= the `backfillEndBlock.timestamp` for all - * chains with `chainStatus` of {@link ChainIndexingStatusIds.Backfill}. - * - `omnichainIndexingCursor` is always >= the `latestIndexedBlock.timestamp` for all - * chains with `chainStatus` of {@link ChainIndexingStatusIds.Completed}. - * - `omnichainIndexingCursor` is always equal to the timestamp of the highest - * `latestIndexedBlock` across all chains that have started indexing - * (`chainStatus` is not {@link ChainIndexingStatusIds.Queued}). - */ -export interface OmnichainIndexingStatusSnapshotBackfill { - /** - * The status of omnichain indexing. - */ - omnichainStatus: typeof OmnichainIndexingStatusIds.Backfill; - - /** - * The indexing status snapshot for each indexed chain. - */ - chains: Map; - - /** - * The timestamp of omnichain indexing progress across all indexed chains. - */ - omnichainIndexingCursor: UnixTimestamp; -} - -/** - * Omnichain indexing status snapshot when the overall `omnichainStatus` is - * {@link OmnichainIndexingStatusIds.Following}. - * - * Invariants: - * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Following}. - * - `chains` is guaranteed to contain at least one chain with a `status` of - * {@link ChainIndexingStatusIds.Following}. - * - `omnichainIndexingCursor` is always < the `config.startBlock.timestamp` for all - * chains with `chainStatus` of {@link ChainIndexingStatusIds.Queued}. - * - `omnichainIndexingCursor` is always <= the `backfillEndBlock.timestamp` for all - * chains with `chainStatus` of {@link ChainIndexingStatusIds.Backfill}. - * - `omnichainIndexingCursor` is always >= the `latestIndexedBlock.timestamp` for all - * chains with `chainStatus` of {@link ChainIndexingStatusIds.Completed}. - * - `omnichainIndexingCursor` is always equal to the timestamp of the highest - * `latestIndexedBlock` across all chains that have started indexing - * (`chainStatus` is not {@link ChainIndexingStatusIds.Queued}). - */ -export interface OmnichainIndexingStatusSnapshotFollowing { - /** - * The status of omnichain indexing. - */ - omnichainStatus: typeof OmnichainIndexingStatusIds.Following; - - /** - * The indexing status snapshot for each indexed chain. - */ - chains: Map; - - /** - * The timestamp of omnichain indexing progress across all indexed chains. - */ - omnichainIndexingCursor: UnixTimestamp; -} - -/** - * Omnichain indexing status snapshot when the overall `omnichainStatus` is - * {@link OmnichainIndexingStatusIds.Completed}. - * - * Invariants: - * - `omnichainStatus` is always {@link OmnichainIndexingStatusIds.Completed}. - * - `chains` is always a map to {@link ChainIndexingStatusSnapshotCompleted} values exclusively. - * - `omnichainIndexingCursor` is always equal to the highest - * `latestIndexedBlock.timestamp` for all chains. - */ -export interface OmnichainIndexingStatusSnapshotCompleted { - /** - * The status of omnichain indexing. - */ - omnichainStatus: typeof OmnichainIndexingStatusIds.Completed; - - /** - * The indexing status snapshot for each indexed chain. - */ - chains: Map; - - /** - * The timestamp of omnichain indexing progress across all indexed chains. - */ - omnichainIndexingCursor: UnixTimestamp; -} - -/** - * Omnichain indexing status snapshot for one or more chains. - * - * Use the `omnichainStatus` field to determine the specific type interpretation - * at runtime. - */ -export type OmnichainIndexingStatusSnapshot = - | OmnichainIndexingStatusSnapshotUnstarted - | OmnichainIndexingStatusSnapshotBackfill - | OmnichainIndexingStatusSnapshotCompleted - | OmnichainIndexingStatusSnapshotFollowing; +import type { OmnichainIndexingStatusSnapshot } from "./omnichain-indexing-status-snapshot"; /** * The strategy used for indexing one or more chains. From c8bef20f0f460f34411b1c89b58e511718e5023a Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 09:57:15 +0100 Subject: [PATCH 02/14] Update import paths Replace `./types` with `./omnichain-indexing-status-snapshot` --- .../ensindexer/indexing-status/conversions.test.ts | 5 ++++- .../src/ensindexer/indexing-status/deserialize.ts | 7 ++----- .../src/ensindexer/indexing-status/helpers.test.ts | 2 +- .../src/ensindexer/indexing-status/helpers.ts | 4 ++-- .../src/ensindexer/indexing-status/index.ts | 1 + .../src/ensindexer/indexing-status/projection.test.ts | 7 ++----- .../src/ensindexer/indexing-status/serialize.ts | 11 +++++------ .../ensindexer/indexing-status/serialized-types.ts | 6 ++++-- .../src/ensindexer/indexing-status/validations.ts | 4 +++- .../src/ensindexer/indexing-status/zod-schemas.ts | 6 ++---- 10 files changed, 26 insertions(+), 27 deletions(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts index 8e65b716a..edf1e9483 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts @@ -14,9 +14,12 @@ import { type ChainIndexingStatusSnapshotQueued, } from "./chain-indexing-status-snapshot"; import { deserializeOmnichainIndexingStatusSnapshot } from "./deserialize"; +import { + OmnichainIndexingStatusIds, + type OmnichainIndexingStatusSnapshot, +} from "./omnichain-indexing-status-snapshot"; import { serializeOmnichainIndexingStatusSnapshot } from "./serialize"; import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialized-types"; -import { OmnichainIndexingStatusIds, type OmnichainIndexingStatusSnapshot } from "./types"; describe("ENSIndexer: Indexing Status", () => { describe("Omnichain Indexing Status Snapshot", () => { diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts index 5864fecd5..ca3917bf8 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts @@ -1,15 +1,12 @@ import { prettifyError } from "zod/v4"; +import type { OmnichainIndexingStatusSnapshot } from "./omnichain-indexing-status-snapshot"; import type { SerializedCrossChainIndexingStatusSnapshot, SerializedOmnichainIndexingStatusSnapshot, SerializedRealtimeIndexingStatusProjection, } from "./serialized-types"; -import type { - CrossChainIndexingStatusSnapshot, - OmnichainIndexingStatusSnapshot, - RealtimeIndexingStatusProjection, -} from "./types"; +import type { CrossChainIndexingStatusSnapshot, RealtimeIndexingStatusProjection } from "./types"; import { makeCrossChainIndexingStatusSnapshotSchema, makeOmnichainIndexingStatusSnapshotSchema, diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.test.ts index f0d0e9c63..fed26322e 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.test.ts @@ -17,7 +17,7 @@ import { type ChainIndexingStatusSnapshotQueued, } from "./chain-indexing-status-snapshot"; import { getOmnichainIndexingCursor, getOmnichainIndexingStatus } from "./helpers"; -import { OmnichainIndexingStatusIds } from "./types"; +import { OmnichainIndexingStatusIds } from "./omnichain-indexing-status-snapshot"; describe("ENSIndexer: Indexing Snapshot helpers", () => { describe("getOmnichainIndexingStatus", () => { diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts index 7df076fed..0dcc86d49 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts @@ -7,10 +7,10 @@ import { } from "./chain-indexing-status-snapshot"; import { type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, - type CrossChainIndexingStatusSnapshot, type OmnichainIndexingStatusId, OmnichainIndexingStatusIds, -} from "./types"; +} from "./omnichain-indexing-status-snapshot"; +import type { CrossChainIndexingStatusSnapshot } from "./types"; /** * Get {@link OmnichainIndexingStatusId} based on indexed chains' statuses. diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts index dc02d1811..f92230ed4 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts @@ -2,6 +2,7 @@ export * from "./chain-indexing-status-snapshot"; export * from "./deserialize"; export * from "./deserialize/chain-indexing-status-snapshot"; export * from "./helpers"; +export * from "./omnichain-indexing-status-snapshot"; export * from "./projection"; export * from "./serialize"; export * from "./serialize/chain-indexing-status-snapshot"; diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/projection.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/projection.test.ts index ace15062f..e8b2c33fd 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/projection.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/projection.test.ts @@ -6,12 +6,9 @@ import { ChainIndexingStatusIds, } from "./chain-indexing-status-snapshot"; import { deserializeCrossChainIndexingStatusSnapshot } from "./deserialize"; +import { OmnichainIndexingStatusIds } from "./omnichain-indexing-status-snapshot"; import { createRealtimeIndexingStatusProjection } from "./projection"; -import { - CrossChainIndexingStrategyIds, - OmnichainIndexingStatusIds, - type RealtimeIndexingStatusProjection, -} from "./types"; +import { CrossChainIndexingStrategyIds, type RealtimeIndexingStatusProjection } from "./types"; describe("Realtime Indexing Status Projection", () => { it("can be created from existing omnichain snapshot", () => { diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts index e797d88c3..93e3ccbd7 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts @@ -1,3 +1,7 @@ +import { + OmnichainIndexingStatusIds, + type OmnichainIndexingStatusSnapshot, +} from "./omnichain-indexing-status-snapshot"; import { serializeChainIndexingSnapshots } from "./serialize/chain-indexing-status-snapshot"; import type { SerializedCrossChainIndexingStatusSnapshot, @@ -8,12 +12,7 @@ import type { SerializedOmnichainIndexingStatusSnapshotUnstarted, SerializedRealtimeIndexingStatusProjection, } from "./serialized-types"; -import { - type CrossChainIndexingStatusSnapshot, - OmnichainIndexingStatusIds, - type OmnichainIndexingStatusSnapshot, - type RealtimeIndexingStatusProjection, -} from "./types"; +import type { CrossChainIndexingStatusSnapshot, RealtimeIndexingStatusProjection } from "./types"; export function serializeCrossChainIndexingStatusSnapshotOmnichain({ strategy, diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts index 9ad205592..c94191108 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts @@ -6,13 +6,15 @@ import type { } from "./chain-indexing-status-snapshot"; import type { ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, - CrossChainIndexingStatusSnapshot, - CrossChainIndexingStatusSnapshotOmnichain, OmnichainIndexingStatusSnapshot, OmnichainIndexingStatusSnapshotBackfill, OmnichainIndexingStatusSnapshotCompleted, OmnichainIndexingStatusSnapshotFollowing, OmnichainIndexingStatusSnapshotUnstarted, +} from "./omnichain-indexing-status-snapshot"; +import type { + CrossChainIndexingStatusSnapshot, + CrossChainIndexingStatusSnapshotOmnichain, RealtimeIndexingStatusProjection, } from "./types"; diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts index 42f97e8b7..6960dd954 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts @@ -14,9 +14,11 @@ import { getOmnichainIndexingStatus, } from "./helpers"; import type { - CrossChainIndexingStatusSnapshotOmnichain, OmnichainIndexingStatusSnapshot, OmnichainIndexingStatusSnapshotFollowing, +} from "./omnichain-indexing-status-snapshot"; +import type { + CrossChainIndexingStatusSnapshotOmnichain, RealtimeIndexingStatusProjection, } from "./types"; diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts index a19f2cc1f..19edad6da 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts @@ -22,15 +22,13 @@ import type { } from "./chain-indexing-status-snapshot"; import { type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, - CrossChainIndexingStatusSnapshotOmnichain, - CrossChainIndexingStrategyIds, OmnichainIndexingStatusIds, OmnichainIndexingStatusSnapshotBackfill, OmnichainIndexingStatusSnapshotCompleted, OmnichainIndexingStatusSnapshotFollowing, OmnichainIndexingStatusSnapshotUnstarted, - RealtimeIndexingStatusProjection, -} from "./types"; +} from "./omnichain-indexing-status-snapshot"; +import { CrossChainIndexingStatusSnapshotOmnichain, CrossChainIndexingStrategyIds } from "./types"; import { invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain, invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains, From 63f28eaaf2b84d67aff3dbe4636dd75d6cf81231 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:01:01 +0100 Subject: [PATCH 03/14] Extract OmnichainIndexingStatus business-layer from generic helpers.ts file --- .../src/ensindexer/indexing-status/helpers.ts | 157 +----------------- .../omnichain-indexing-status-snapshot.ts | 143 ++++++++++++++++ 2 files changed, 145 insertions(+), 155 deletions(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts index 0dcc86d49..911676896 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts @@ -1,160 +1,7 @@ -import type { BlockRef, ChainId, UnixTimestamp } from "../../shared/types"; -import { - ChainIndexingStatusIds, - type ChainIndexingStatusSnapshot, - type ChainIndexingStatusSnapshotCompleted, - type ChainIndexingStatusSnapshotQueued, -} from "./chain-indexing-status-snapshot"; -import { - type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, - type OmnichainIndexingStatusId, - OmnichainIndexingStatusIds, -} from "./omnichain-indexing-status-snapshot"; +import type { BlockRef, ChainId } from "../../shared/types"; +import { ChainIndexingStatusIds } from "./chain-indexing-status-snapshot"; import type { CrossChainIndexingStatusSnapshot } from "./types"; -/** - * Get {@link OmnichainIndexingStatusId} based on indexed chains' statuses. - * - * This function decides what is the `OmnichainIndexingStatusId` is, - * based on provided chain indexing statuses. - * - * @throws an error if unable to determine overall indexing status - */ -export function getOmnichainIndexingStatus( - chains: ChainIndexingStatusSnapshot[], -): OmnichainIndexingStatusId { - if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing(chains)) { - return OmnichainIndexingStatusIds.Following; - } - - if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill(chains)) { - return OmnichainIndexingStatusIds.Backfill; - } - - if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted(chains)) { - return OmnichainIndexingStatusIds.Unstarted; - } - - if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted(chains)) { - return OmnichainIndexingStatusIds.Completed; - } - - // if none of the chain statuses matched, throw an error - throw new Error(`Unable to determine omnichain indexing status for provided chains.`); -} - -/** - * Get Omnichain Indexing Cursor - * - * The cursor tracks the "highest" latest indexed block timestamp across - * all indexed chains. If all chains are queued, the cursor tracks the moment - * just before the earliest start block timestamp across those chains. - * - * @throws an error if no chains are provided - */ -export function getOmnichainIndexingCursor(chains: ChainIndexingStatusSnapshot[]): UnixTimestamp { - if (chains.length === 0) { - throw new Error(`Unable to determine omnichain indexing cursor when no chains were provided.`); - } - - // for omnichain indexing status snapshot 'unstarted', the cursor tracks - // the moment just before the indexing would start from. - if (getOmnichainIndexingStatus(chains) === OmnichainIndexingStatusIds.Unstarted) { - const earliestStartBlockTimestamps = chains.map((chain) => chain.config.startBlock.timestamp); - - return Math.min(...earliestStartBlockTimestamps) - 1; - } - - // otherwise, the cursor tracks the "highest" latest indexed block timestamp - // across all indexed chains - const latestIndexedBlockTimestamps = chains - .filter((chain) => chain.chainStatus !== ChainIndexingStatusIds.Queued) - .map((chain) => chain.latestIndexedBlock.timestamp); - - // Invariant: there's at least one element in `latestIndexedBlockTimestamps` array - // This is theoretically impossible based on the 2 checks above, - // but the invariant is explicitly added here as a formality. - if (latestIndexedBlockTimestamps.length < 1) { - throw new Error("latestIndexedBlockTimestamps array must include at least one element"); - } - - return Math.max(...latestIndexedBlockTimestamps); -} - -/** - * Check if Chain Indexing Status Snapshots fit the 'unstarted' overall status - * snapshot requirements: - * - All chains are guaranteed to have a status of "queued". - * - * Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to - * {@link ChainIndexingStatusSnapshotQueued}. - */ -export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted( - chains: ChainIndexingStatusSnapshot[], -): chains is ChainIndexingStatusSnapshotQueued[] { - return chains.every((chain) => chain.chainStatus === ChainIndexingStatusIds.Queued); -} - -/** - * Check if Chain Indexing Status Snapshots fit the 'backfill' overall status - * snapshot requirements: - * - At least one chain is guaranteed to be in the "backfill" status. - * - Each chain is guaranteed to have a status of either "queued", - * "backfill" or "completed". - * - * Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to - * {@link ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill}. - */ -export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill( - chains: ChainIndexingStatusSnapshot[], -): chains is ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill[] { - const atLeastOneChainInTargetStatus = chains.some( - (chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill, - ); - const otherChainsHaveValidStatuses = chains.every( - (chain) => - chain.chainStatus === ChainIndexingStatusIds.Queued || - chain.chainStatus === ChainIndexingStatusIds.Backfill || - chain.chainStatus === ChainIndexingStatusIds.Completed, - ); - - return atLeastOneChainInTargetStatus && otherChainsHaveValidStatuses; -} - -/** - * Checks if Chain Indexing Status Snapshots fit the 'completed' overall status - * snapshot requirements: - * - All chains are guaranteed to have a status of "completed". - * - * Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to - * {@link ChainIndexingStatusSnapshotCompleted}. - */ -export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted( - chains: ChainIndexingStatusSnapshot[], -): chains is ChainIndexingStatusSnapshotCompleted[] { - const allChainsHaveValidStatuses = chains.every( - (chain) => chain.chainStatus === ChainIndexingStatusIds.Completed, - ); - - return allChainsHaveValidStatuses; -} - -/** - * Checks Chain Indexing Status Snapshots fit the 'following' overall status - * snapshot requirements: - * - At least one chain is guaranteed to be in the "following" status. - * - Any other chain can have any status. - */ -export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing( - chains: ChainIndexingStatusSnapshot[], -): chains is ChainIndexingStatusSnapshot[] { - const allChainsHaveValidStatuses = chains.some( - (chain) => chain.chainStatus === ChainIndexingStatusIds.Following, - ); - - return allChainsHaveValidStatuses; -} - /** * Gets the latest indexed {@link BlockRef} for the given {@link ChainId}. * diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts index 2943e591c..a5a1c308f 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.ts @@ -196,3 +196,146 @@ export type OmnichainIndexingStatusSnapshot = | OmnichainIndexingStatusSnapshotBackfill | OmnichainIndexingStatusSnapshotCompleted | OmnichainIndexingStatusSnapshotFollowing; + +/** + * Check if Chain Indexing Status Snapshots fit the 'unstarted' overall status + * snapshot requirements: + * - All chains are guaranteed to have a status of "queued". + * + * Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to + * {@link ChainIndexingStatusSnapshotQueued}. + */ +export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted( + chains: ChainIndexingStatusSnapshot[], +): chains is ChainIndexingStatusSnapshotQueued[] { + return chains.every((chain) => chain.chainStatus === ChainIndexingStatusIds.Queued); +} + +/** + * Check if Chain Indexing Status Snapshots fit the 'backfill' overall status + * snapshot requirements: + * - At least one chain is guaranteed to be in the "backfill" status. + * - Each chain is guaranteed to have a status of either "queued", + * "backfill" or "completed". + * + * Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to + * {@link ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill}. + */ +export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill( + chains: ChainIndexingStatusSnapshot[], +): chains is ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill[] { + const atLeastOneChainInTargetStatus = chains.some( + (chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill, + ); + const otherChainsHaveValidStatuses = chains.every( + (chain) => + chain.chainStatus === ChainIndexingStatusIds.Queued || + chain.chainStatus === ChainIndexingStatusIds.Backfill || + chain.chainStatus === ChainIndexingStatusIds.Completed, + ); + + return atLeastOneChainInTargetStatus && otherChainsHaveValidStatuses; +} + +/** + * Checks if Chain Indexing Status Snapshots fit the 'completed' overall status + * snapshot requirements: + * - All chains are guaranteed to have a status of "completed". + * + * Note: This function narrows the {@link ChainIndexingStatusSnapshot} type to + * {@link ChainIndexingStatusSnapshotCompleted}. + */ +export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted( + chains: ChainIndexingStatusSnapshot[], +): chains is ChainIndexingStatusSnapshotCompleted[] { + const allChainsHaveValidStatuses = chains.every( + (chain) => chain.chainStatus === ChainIndexingStatusIds.Completed, + ); + + return allChainsHaveValidStatuses; +} + +/** + * Checks Chain Indexing Status Snapshots fit the 'following' overall status + * snapshot requirements: + * - At least one chain is guaranteed to be in the "following" status. + * - Any other chain can have any status. + */ +export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing( + chains: ChainIndexingStatusSnapshot[], +): boolean { + const allChainsHaveValidStatuses = chains.some( + (chain) => chain.chainStatus === ChainIndexingStatusIds.Following, + ); + + return allChainsHaveValidStatuses; +} + +/** + * Get {@link OmnichainIndexingStatusId} based on indexed chains' statuses. + * + * This function decides what is the `OmnichainIndexingStatusId` is, + * based on provided chain indexing statuses. + * + * @throws an error if unable to determine overall indexing status + */ +export function getOmnichainIndexingStatus( + chains: ChainIndexingStatusSnapshot[], +): OmnichainIndexingStatusId { + if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing(chains)) { + return OmnichainIndexingStatusIds.Following; + } + + if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill(chains)) { + return OmnichainIndexingStatusIds.Backfill; + } + + if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted(chains)) { + return OmnichainIndexingStatusIds.Unstarted; + } + + if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted(chains)) { + return OmnichainIndexingStatusIds.Completed; + } + + // if none of the chain statuses matched, throw an error + throw new Error(`Unable to determine omnichain indexing status for provided chains.`); +} + +/** + * Get Omnichain Indexing Cursor + * + * The cursor tracks the "highest" latest indexed block timestamp across + * all indexed chains. If all chains are queued, the cursor tracks the moment + * just before the earliest start block timestamp across those chains. + * + * @throws an error if no chains are provided + */ +export function getOmnichainIndexingCursor(chains: ChainIndexingStatusSnapshot[]): UnixTimestamp { + if (chains.length === 0) { + throw new Error(`Unable to determine omnichain indexing cursor when no chains were provided.`); + } + + // for omnichain indexing status snapshot 'unstarted', the cursor tracks + // the moment just before the indexing would start from. + if (getOmnichainIndexingStatus(chains) === OmnichainIndexingStatusIds.Unstarted) { + const earliestStartBlockTimestamps = chains.map((chain) => chain.config.startBlock.timestamp); + + return Math.min(...earliestStartBlockTimestamps) - 1; + } + + // otherwise, the cursor tracks the "highest" latest indexed block timestamp + // across all indexed chains + const latestIndexedBlockTimestamps = chains + .filter((chain) => chain.chainStatus !== ChainIndexingStatusIds.Queued) + .map((chain) => chain.latestIndexedBlock.timestamp); + + // Invariant: there's at least one element in `latestIndexedBlockTimestamps` array + // This is theoretically impossible based on the 2 checks above, + // but the invariant is explicitly added here as a formality. + if (latestIndexedBlockTimestamps.length < 1) { + throw new Error("latestIndexedBlockTimestamps array must include at least one element"); + } + + return Math.max(...latestIndexedBlockTimestamps); +} From b88a823b5594b70df620b4661e11e1d78bff7836 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:03:52 +0100 Subject: [PATCH 04/14] Rename test file for OmnichainIndexingStatusSnapshot --- ...helpers.test.ts => omnichain-indexing-status-snapshot.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/ensnode-sdk/src/ensindexer/indexing-status/{helpers.test.ts => omnichain-indexing-status-snapshot.test.ts} (100%) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.test.ts similarity index 100% rename from packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.test.ts rename to packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.test.ts From 9f15ed3a828020352f9064a17298b6148c033d8c Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:04:53 +0100 Subject: [PATCH 05/14] Update import paths Replace `./helpers` with `./omnichain-indexing-status-snapshot` --- .../omnichain-indexing-status-snapshot.test.ts | 7 +++++-- .../src/ensindexer/indexing-status/validations.ts | 6 ++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.test.ts index fed26322e..4086e33ec 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/omnichain-indexing-status-snapshot.test.ts @@ -16,8 +16,11 @@ import { type ChainIndexingStatusSnapshotFollowing, type ChainIndexingStatusSnapshotQueued, } from "./chain-indexing-status-snapshot"; -import { getOmnichainIndexingCursor, getOmnichainIndexingStatus } from "./helpers"; -import { OmnichainIndexingStatusIds } from "./omnichain-indexing-status-snapshot"; +import { + getOmnichainIndexingCursor, + getOmnichainIndexingStatus, + OmnichainIndexingStatusIds, +} from "./omnichain-indexing-status-snapshot"; describe("ENSIndexer: Indexing Snapshot helpers", () => { describe("getOmnichainIndexingStatus", () => { diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts index 6960dd954..3e3922594 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts @@ -12,10 +12,8 @@ import { checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing, checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted, getOmnichainIndexingStatus, -} from "./helpers"; -import type { - OmnichainIndexingStatusSnapshot, - OmnichainIndexingStatusSnapshotFollowing, + type OmnichainIndexingStatusSnapshot, + type OmnichainIndexingStatusSnapshotFollowing, } from "./omnichain-indexing-status-snapshot"; import type { CrossChainIndexingStatusSnapshotOmnichain, From 3cb5f4c0d25a9ca2f92bdf4a60b0b7b3d4b47ea9 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:10:37 +0100 Subject: [PATCH 06/14] Extract SerializedOmnichainIndexingStatus serialize-layer from generic serialized-types.ts file --- .../omnichain-indexing-status-snapshot.ts | 57 ++++++++++++++++++ .../indexing-status/serialized-types.ts | 59 +------------------ 2 files changed, 58 insertions(+), 58 deletions(-) create mode 100644 packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts new file mode 100644 index 000000000..646fdcd9a --- /dev/null +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts @@ -0,0 +1,57 @@ +import type { ChainIdString } from "../../../shared/serialized-types"; +import type { + ChainIndexingStatusSnapshot, + ChainIndexingStatusSnapshotCompleted, + ChainIndexingStatusSnapshotQueued, +} from "../chain-indexing-status-snapshot"; +import type { + ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, + OmnichainIndexingStatusSnapshotBackfill, + OmnichainIndexingStatusSnapshotCompleted, + OmnichainIndexingStatusSnapshotFollowing, + OmnichainIndexingStatusSnapshotUnstarted, +} from "../omnichain-indexing-status-snapshot"; + +/** + * Serialized representation of {@link OmnichainIndexingStatusSnapshotUnstarted} + */ +export interface SerializedOmnichainIndexingStatusSnapshotUnstarted + extends Omit { + chains: Record; +} + +/** + * Serialized representation of {@link OmnichainIndexingStatusSnapshotBackfill} + */ +export interface SerializedOmnichainIndexingStatusSnapshotBackfill + extends Omit { + chains: Record< + ChainIdString, + ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill + >; +} + +/** + * Serialized representation of {@link OmnichainIndexingStatusSnapshotCompleted} + */ +export interface SerializedOmnichainIndexingStatusSnapshotCompleted + extends Omit { + chains: Record; +} + +/** + * Serialized representation of {@link OmnichainIndexingStatusSnapshotFollowing} + */ +export interface SerializedOmnichainIndexingStatusSnapshotFollowing + extends Omit { + chains: Record; +} + +/** + * Serialized representation of {@link OmnichainIndexingStatusSnapshot} + */ +export type SerializedOmnichainIndexingStatusSnapshot = + | SerializedOmnichainIndexingStatusSnapshotUnstarted + | SerializedOmnichainIndexingStatusSnapshotBackfill + | SerializedOmnichainIndexingStatusSnapshotCompleted + | SerializedOmnichainIndexingStatusSnapshotFollowing; diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts index c94191108..e866d06df 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts @@ -1,67 +1,10 @@ -import type { ChainIdString } from "../../shared/serialized-types"; -import type { - ChainIndexingStatusSnapshot, - ChainIndexingStatusSnapshotCompleted, - ChainIndexingStatusSnapshotQueued, -} from "./chain-indexing-status-snapshot"; -import type { - ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, - OmnichainIndexingStatusSnapshot, - OmnichainIndexingStatusSnapshotBackfill, - OmnichainIndexingStatusSnapshotCompleted, - OmnichainIndexingStatusSnapshotFollowing, - OmnichainIndexingStatusSnapshotUnstarted, -} from "./omnichain-indexing-status-snapshot"; +import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialize/omnichain-indexing-status-snapshot"; import type { CrossChainIndexingStatusSnapshot, CrossChainIndexingStatusSnapshotOmnichain, RealtimeIndexingStatusProjection, } from "./types"; -/** - * Serialized representation of {@link OmnichainIndexingStatusSnapshotUnstarted} - */ -export interface SerializedOmnichainIndexingStatusSnapshotUnstarted - extends Omit { - chains: Record; -} - -/** - * Serialized representation of {@link OmnichainIndexingStatusSnapshotBackfill} - */ -export interface SerializedOmnichainIndexingStatusSnapshotBackfill - extends Omit { - chains: Record< - ChainIdString, - ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill - >; -} - -/** - * Serialized representation of {@link OmnichainIndexingStatusSnapshotCompleted} - */ -export interface SerializedOmnichainIndexingStatusSnapshotCompleted - extends Omit { - chains: Record; -} - -/** - * Serialized representation of {@link OmnichainIndexingStatusSnapshotFollowing} - */ -export interface SerializedOmnichainIndexingStatusSnapshotFollowing - extends Omit { - chains: Record; -} - -/** - * Serialized representation of {@link OmnichainIndexingStatusSnapshot} - */ -export type SerializedOmnichainIndexingStatusSnapshot = - | SerializedOmnichainIndexingStatusSnapshotUnstarted - | SerializedOmnichainIndexingStatusSnapshotBackfill - | SerializedOmnichainIndexingStatusSnapshotCompleted - | SerializedOmnichainIndexingStatusSnapshotFollowing; - /** * Serialized representation of {@link CrossChainIndexingStatusSnapshotOmnichain} */ From 086531c4a3aec25195b4dbe6ca36985fcdaa786a Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:11:11 +0100 Subject: [PATCH 07/14] Update import paths Replace `./serialized-types` with `./serialize/omnichain-indexing-status-snapshot` --- .../src/ensindexer/indexing-status/conversions.test.ts | 2 +- .../ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts | 2 +- packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts | 1 + .../ensnode-sdk/src/ensindexer/indexing-status/serialize.ts | 4 +++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts index edf1e9483..689e975b3 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts @@ -19,7 +19,7 @@ import { type OmnichainIndexingStatusSnapshot, } from "./omnichain-indexing-status-snapshot"; import { serializeOmnichainIndexingStatusSnapshot } from "./serialize"; -import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialized-types"; +import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialize/omnichain-indexing-status-snapshot"; describe("ENSIndexer: Indexing Status", () => { describe("Omnichain Indexing Status Snapshot", () => { diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts index ca3917bf8..eef8adbd1 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts @@ -1,9 +1,9 @@ import { prettifyError } from "zod/v4"; import type { OmnichainIndexingStatusSnapshot } from "./omnichain-indexing-status-snapshot"; +import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialize/omnichain-indexing-status-snapshot"; import type { SerializedCrossChainIndexingStatusSnapshot, - SerializedOmnichainIndexingStatusSnapshot, SerializedRealtimeIndexingStatusProjection, } from "./serialized-types"; import type { CrossChainIndexingStatusSnapshot, RealtimeIndexingStatusProjection } from "./types"; diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts index f92230ed4..73b67009c 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts @@ -6,6 +6,7 @@ export * from "./omnichain-indexing-status-snapshot"; export * from "./projection"; export * from "./serialize"; export * from "./serialize/chain-indexing-status-snapshot"; +export * from "./serialize/omnichain-indexing-status-snapshot"; export * from "./serialized-types"; export * from "./types"; export * from "./validate/chain-indexing-status-snapshot"; diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts index 93e3ccbd7..108f1d17f 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts @@ -4,12 +4,14 @@ import { } from "./omnichain-indexing-status-snapshot"; import { serializeChainIndexingSnapshots } from "./serialize/chain-indexing-status-snapshot"; import type { - SerializedCrossChainIndexingStatusSnapshot, SerializedOmnichainIndexingStatusSnapshot, SerializedOmnichainIndexingStatusSnapshotBackfill, SerializedOmnichainIndexingStatusSnapshotCompleted, SerializedOmnichainIndexingStatusSnapshotFollowing, SerializedOmnichainIndexingStatusSnapshotUnstarted, +} from "./serialize/omnichain-indexing-status-snapshot"; +import type { + SerializedCrossChainIndexingStatusSnapshot, SerializedRealtimeIndexingStatusProjection, } from "./serialized-types"; import type { CrossChainIndexingStatusSnapshot, RealtimeIndexingStatusProjection } from "./types"; From 1ef73bc28f65bed92303a8b4eb1670b8f6b7be0d Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:15:51 +0100 Subject: [PATCH 08/14] Extract OmnichainIndexingStatus serialize-layer from generic serialize.ts file --- .../ensindexer/indexing-status/serialize.ts | 51 +----------------- .../omnichain-indexing-status-snapshot.ts | 53 ++++++++++++++++--- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts index 108f1d17f..fd7b82448 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts @@ -1,15 +1,4 @@ -import { - OmnichainIndexingStatusIds, - type OmnichainIndexingStatusSnapshot, -} from "./omnichain-indexing-status-snapshot"; -import { serializeChainIndexingSnapshots } from "./serialize/chain-indexing-status-snapshot"; -import type { - SerializedOmnichainIndexingStatusSnapshot, - SerializedOmnichainIndexingStatusSnapshotBackfill, - SerializedOmnichainIndexingStatusSnapshotCompleted, - SerializedOmnichainIndexingStatusSnapshotFollowing, - SerializedOmnichainIndexingStatusSnapshotUnstarted, -} from "./serialize/omnichain-indexing-status-snapshot"; +import { serializeOmnichainIndexingStatusSnapshot } from "./serialize/omnichain-indexing-status-snapshot"; import type { SerializedCrossChainIndexingStatusSnapshot, SerializedRealtimeIndexingStatusProjection, @@ -39,41 +28,3 @@ export function serializeRealtimeIndexingStatusProjection( snapshot: serializeCrossChainIndexingStatusSnapshotOmnichain(indexingProjection.snapshot), } satisfies SerializedRealtimeIndexingStatusProjection; } - -/** - * Serialize a {@link OmnichainIndexingStatusSnapshot} object. - */ -export function serializeOmnichainIndexingStatusSnapshot( - indexingStatus: OmnichainIndexingStatusSnapshot, -): SerializedOmnichainIndexingStatusSnapshot { - switch (indexingStatus.omnichainStatus) { - case OmnichainIndexingStatusIds.Unstarted: - return { - omnichainStatus: OmnichainIndexingStatusIds.Unstarted, - chains: serializeChainIndexingSnapshots(indexingStatus.chains), - omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, - } satisfies SerializedOmnichainIndexingStatusSnapshotUnstarted; - - case OmnichainIndexingStatusIds.Backfill: - return { - omnichainStatus: OmnichainIndexingStatusIds.Backfill, - chains: serializeChainIndexingSnapshots(indexingStatus.chains), - omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, - } satisfies SerializedOmnichainIndexingStatusSnapshotBackfill; - - case OmnichainIndexingStatusIds.Completed: { - return { - omnichainStatus: OmnichainIndexingStatusIds.Completed, - chains: serializeChainIndexingSnapshots(indexingStatus.chains), - omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, - } satisfies SerializedOmnichainIndexingStatusSnapshotCompleted; - } - - case OmnichainIndexingStatusIds.Following: - return { - omnichainStatus: OmnichainIndexingStatusIds.Following, - chains: serializeChainIndexingSnapshots(indexingStatus.chains), - omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, - } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing; - } -} diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts index 646fdcd9a..e3668eef2 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/omnichain-indexing-status-snapshot.ts @@ -4,13 +4,16 @@ import type { ChainIndexingStatusSnapshotCompleted, ChainIndexingStatusSnapshotQueued, } from "../chain-indexing-status-snapshot"; -import type { - ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, - OmnichainIndexingStatusSnapshotBackfill, - OmnichainIndexingStatusSnapshotCompleted, - OmnichainIndexingStatusSnapshotFollowing, - OmnichainIndexingStatusSnapshotUnstarted, +import { + type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, + OmnichainIndexingStatusIds, + type OmnichainIndexingStatusSnapshot, + type OmnichainIndexingStatusSnapshotBackfill, + type OmnichainIndexingStatusSnapshotCompleted, + type OmnichainIndexingStatusSnapshotFollowing, + type OmnichainIndexingStatusSnapshotUnstarted, } from "../omnichain-indexing-status-snapshot"; +import { serializeChainIndexingSnapshots } from "./chain-indexing-status-snapshot"; /** * Serialized representation of {@link OmnichainIndexingStatusSnapshotUnstarted} @@ -55,3 +58,41 @@ export type SerializedOmnichainIndexingStatusSnapshot = | SerializedOmnichainIndexingStatusSnapshotBackfill | SerializedOmnichainIndexingStatusSnapshotCompleted | SerializedOmnichainIndexingStatusSnapshotFollowing; + +/** + * Serialize a {@link OmnichainIndexingStatusSnapshot} object. + */ +export function serializeOmnichainIndexingStatusSnapshot( + indexingStatus: OmnichainIndexingStatusSnapshot, +): SerializedOmnichainIndexingStatusSnapshot { + switch (indexingStatus.omnichainStatus) { + case OmnichainIndexingStatusIds.Unstarted: + return { + omnichainStatus: OmnichainIndexingStatusIds.Unstarted, + chains: serializeChainIndexingSnapshots(indexingStatus.chains), + omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, + } satisfies SerializedOmnichainIndexingStatusSnapshotUnstarted; + + case OmnichainIndexingStatusIds.Backfill: + return { + omnichainStatus: OmnichainIndexingStatusIds.Backfill, + chains: serializeChainIndexingSnapshots(indexingStatus.chains), + omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, + } satisfies SerializedOmnichainIndexingStatusSnapshotBackfill; + + case OmnichainIndexingStatusIds.Completed: { + return { + omnichainStatus: OmnichainIndexingStatusIds.Completed, + chains: serializeChainIndexingSnapshots(indexingStatus.chains), + omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, + } satisfies SerializedOmnichainIndexingStatusSnapshotCompleted; + } + + case OmnichainIndexingStatusIds.Following: + return { + omnichainStatus: OmnichainIndexingStatusIds.Following, + chains: serializeChainIndexingSnapshots(indexingStatus.chains), + omnichainIndexingCursor: indexingStatus.omnichainIndexingCursor, + } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing; + } +} From 9e3abbf332e71444f4ac5b10f048e95208cb3710 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:16:07 +0100 Subject: [PATCH 09/14] Update import paths Replace `./serialize` with `./serialize/omnichain-indexing-status-snapshot` --- .../src/ensindexer/indexing-status/conversions.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts index 689e975b3..d4d9ad403 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts @@ -18,8 +18,10 @@ import { OmnichainIndexingStatusIds, type OmnichainIndexingStatusSnapshot, } from "./omnichain-indexing-status-snapshot"; -import { serializeOmnichainIndexingStatusSnapshot } from "./serialize"; -import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialize/omnichain-indexing-status-snapshot"; +import { + type SerializedOmnichainIndexingStatusSnapshot, + serializeOmnichainIndexingStatusSnapshot, +} from "./serialize/omnichain-indexing-status-snapshot"; describe("ENSIndexer: Indexing Status", () => { describe("Omnichain Indexing Status Snapshot", () => { From b42c04d800ceabb6ddcf6d87f89c5a2fd70ad2dc Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:50:21 +0100 Subject: [PATCH 10/14] Extract OmnichainIndexingStatus zod-schema-layer from generic zod-schemas.ts file --- .../omnichain-indexing-status-snapshot.ts | 118 +++++++++++++++++ .../ensindexer/indexing-status/zod-schemas.ts | 124 +----------------- 2 files changed, 120 insertions(+), 122 deletions(-) create mode 100644 packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts new file mode 100644 index 000000000..5c4fe61b5 --- /dev/null +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts @@ -0,0 +1,118 @@ +import { z } from "zod/v4"; + +import { deserializeChainId } from "../../../shared/deserialize"; +import type { ChainId } from "../../../shared/types"; +import { makeChainIdStringSchema, makeUnixTimestampSchema } from "../../../shared/zod-schemas"; +import type { + ChainIndexingStatusSnapshot, + ChainIndexingStatusSnapshotCompleted, + ChainIndexingStatusSnapshotQueued, +} from "../chain-indexing-status-snapshot"; +import { + type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, + OmnichainIndexingStatusIds, +} from "../omnichain-indexing-status-snapshot"; +import { + invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain, + invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains, + invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains, + invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot, + invariant_omnichainSnapshotUnstartedHasValidChains, + invariant_omnichainStatusSnapshotBackfillHasValidChains, + invariant_omnichainStatusSnapshotCompletedHasValidChains, +} from "../validations"; +import { makeChainIndexingStatusSnapshotSchema } from "./chain-indexing-status-snapshot"; + +/** + * Makes Zod schema for {@link ChainIndexingStatusSnapshot} per chain. + */ +export const makeChainIndexingStatusesSchema = (valueLabel: string = "Value") => + z + .record(makeChainIdStringSchema(), makeChainIndexingStatusSnapshotSchema(valueLabel), { + error: + "Chains indexing statuses must be an object mapping valid chain IDs to their indexing status snapshots.", + }) + .transform((serializedChainsIndexingStatus) => { + const chainsIndexingStatus = new Map(); + + for (const [chainIdString, chainStatus] of Object.entries(serializedChainsIndexingStatus)) { + chainsIndexingStatus.set(deserializeChainId(chainIdString), chainStatus); + } + + return chainsIndexingStatus; + }); + +/** + * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotUnstarted} + */ +const makeOmnichainIndexingStatusSnapshotUnstartedSchema = (valueLabel?: string) => + z.strictObject({ + omnichainStatus: z.literal(OmnichainIndexingStatusIds.Unstarted), + chains: makeChainIndexingStatusesSchema(valueLabel) + .check(invariant_omnichainSnapshotUnstartedHasValidChains) + .transform((chains) => chains as Map), + omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), + }); + +/** + * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotBackfill} + */ +const makeOmnichainIndexingStatusSnapshotBackfillSchema = (valueLabel?: string) => + z.strictObject({ + omnichainStatus: z.literal(OmnichainIndexingStatusIds.Backfill), + chains: makeChainIndexingStatusesSchema(valueLabel) + .check(invariant_omnichainStatusSnapshotBackfillHasValidChains) + .transform( + (chains) => + chains as Map< + ChainId, + ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill + >, + ), + omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), + }); + +/** + * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotCompleted} + */ +const makeOmnichainIndexingStatusSnapshotCompletedSchema = (valueLabel?: string) => + z.strictObject({ + omnichainStatus: z.literal(OmnichainIndexingStatusIds.Completed), + chains: makeChainIndexingStatusesSchema(valueLabel) + .check(invariant_omnichainStatusSnapshotCompletedHasValidChains) + .transform((chains) => chains as Map), + omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), + }); + +/** + * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotFollowing} + */ +const makeOmnichainIndexingStatusSnapshotFollowingSchema = (valueLabel?: string) => + z.strictObject({ + omnichainStatus: z.literal(OmnichainIndexingStatusIds.Following), + chains: makeChainIndexingStatusesSchema(valueLabel), + omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), + }); + +/** + * Omnichain Indexing Snapshot Schema + * + * Makes a Zod schema definition for validating indexing snapshot + * across all chains indexed by ENSIndexer instance. + */ +export const makeOmnichainIndexingStatusSnapshotSchema = ( + valueLabel: string = "Omnichain Indexing Snapshot", +) => + z + .discriminatedUnion("omnichainStatus", [ + makeOmnichainIndexingStatusSnapshotUnstartedSchema(valueLabel), + makeOmnichainIndexingStatusSnapshotBackfillSchema(valueLabel), + makeOmnichainIndexingStatusSnapshotCompletedSchema(valueLabel), + makeOmnichainIndexingStatusSnapshotFollowingSchema(valueLabel), + ]) + .check(invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot) + .check(invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains) + .check( + invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains, + ) + .check(invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain); diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts index 19edad6da..a93463abb 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts @@ -8,135 +8,15 @@ */ import { z } from "zod/v4"; -import { deserializeChainId } from "../../shared/deserialize"; -import type { ChainId } from "../../shared/types"; -import { - makeChainIdStringSchema, - makeDurationSchema, - makeUnixTimestampSchema, -} from "../../shared/zod-schemas"; -import type { - ChainIndexingStatusSnapshot, - ChainIndexingStatusSnapshotCompleted, - ChainIndexingStatusSnapshotQueued, -} from "./chain-indexing-status-snapshot"; -import { - type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, - OmnichainIndexingStatusIds, - OmnichainIndexingStatusSnapshotBackfill, - OmnichainIndexingStatusSnapshotCompleted, - OmnichainIndexingStatusSnapshotFollowing, - OmnichainIndexingStatusSnapshotUnstarted, -} from "./omnichain-indexing-status-snapshot"; +import { makeDurationSchema, makeUnixTimestampSchema } from "../../shared/zod-schemas"; import { CrossChainIndexingStatusSnapshotOmnichain, CrossChainIndexingStrategyIds } from "./types"; import { - invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain, - invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains, - invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains, - invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot, - invariant_omnichainSnapshotUnstartedHasValidChains, - invariant_omnichainStatusSnapshotBackfillHasValidChains, - invariant_omnichainStatusSnapshotCompletedHasValidChains, invariant_realtimeIndexingStatusProjectionProjectedAtIsAfterOrEqualToSnapshotTime, invariant_realtimeIndexingStatusProjectionWorstCaseDistanceIsCorrect, invariant_slowestChainEqualsToOmnichainSnapshotTime, invariant_snapshotTimeIsTheHighestKnownBlockTimestamp, } from "./validations"; -import { makeChainIndexingStatusSnapshotSchema } from "./zod-schema/chain-indexing-status-snapshot"; - -/** - * Makes Zod schema for {@link ChainIndexingStatusSnapshot} per chain. - */ -export const makeChainIndexingStatusesSchema = (valueLabel: string = "Value") => - z - .record(makeChainIdStringSchema(), makeChainIndexingStatusSnapshotSchema(valueLabel), { - error: - "Chains indexing statuses must be an object mapping valid chain IDs to their indexing status snapshots.", - }) - .transform((serializedChainsIndexingStatus) => { - const chainsIndexingStatus = new Map(); - - for (const [chainIdString, chainStatus] of Object.entries(serializedChainsIndexingStatus)) { - chainsIndexingStatus.set(deserializeChainId(chainIdString), chainStatus); - } - - return chainsIndexingStatus; - }); - -/** - * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotUnstarted} - */ -const makeOmnichainIndexingStatusSnapshotUnstartedSchema = (valueLabel?: string) => - z.strictObject({ - omnichainStatus: z.literal(OmnichainIndexingStatusIds.Unstarted), - chains: makeChainIndexingStatusesSchema(valueLabel) - .check(invariant_omnichainSnapshotUnstartedHasValidChains) - .transform((chains) => chains as Map), - omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), - }); - -/** - * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotBackfill} - */ -const makeOmnichainIndexingStatusSnapshotBackfillSchema = (valueLabel?: string) => - z.strictObject({ - omnichainStatus: z.literal(OmnichainIndexingStatusIds.Backfill), - chains: makeChainIndexingStatusesSchema(valueLabel) - .check(invariant_omnichainStatusSnapshotBackfillHasValidChains) - .transform( - (chains) => - chains as Map< - ChainId, - ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill - >, - ), - omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), - }); - -/** - * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotCompleted} - */ -const makeOmnichainIndexingStatusSnapshotCompletedSchema = (valueLabel?: string) => - z.strictObject({ - omnichainStatus: z.literal(OmnichainIndexingStatusIds.Completed), - chains: makeChainIndexingStatusesSchema(valueLabel) - .check(invariant_omnichainStatusSnapshotCompletedHasValidChains) - .transform((chains) => chains as Map), - omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), - }); - -/** - * Makes Zod schema for {@link OmnichainIndexingStatusSnapshotFollowing} - */ -const makeOmnichainIndexingStatusSnapshotFollowingSchema = (valueLabel?: string) => - z.strictObject({ - omnichainStatus: z.literal(OmnichainIndexingStatusIds.Following), - chains: makeChainIndexingStatusesSchema(valueLabel), - omnichainIndexingCursor: makeUnixTimestampSchema(valueLabel), - }); - -/** - * Omnichain Indexing Snapshot Schema - * - * Makes a Zod schema definition for validating indexing snapshot - * across all chains indexed by ENSIndexer instance. - */ -export const makeOmnichainIndexingStatusSnapshotSchema = ( - valueLabel: string = "Omnichain Indexing Snapshot", -) => - z - .discriminatedUnion("omnichainStatus", [ - makeOmnichainIndexingStatusSnapshotUnstartedSchema(valueLabel), - makeOmnichainIndexingStatusSnapshotBackfillSchema(valueLabel), - makeOmnichainIndexingStatusSnapshotCompletedSchema(valueLabel), - makeOmnichainIndexingStatusSnapshotFollowingSchema(valueLabel), - ]) - .check(invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot) - .check(invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains) - .check( - invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains, - ) - .check(invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain); +import { makeOmnichainIndexingStatusSnapshotSchema } from "./zod-schema/omnichain-indexing-status-snapshot"; /** * Makes Zod schema for {@link CrossChainIndexingStatusSnapshotOmnichain} From 0391d8513cf8748c0aad20b46797a366d8f3645b Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 10:51:04 +0100 Subject: [PATCH 11/14] Update import paths Replace `./zod-schemas` with `./zod-schema/omnichain-indexing-status-snapshot` --- .../ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts index eef8adbd1..657ae017f 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts @@ -7,9 +7,9 @@ import type { SerializedRealtimeIndexingStatusProjection, } from "./serialized-types"; import type { CrossChainIndexingStatusSnapshot, RealtimeIndexingStatusProjection } from "./types"; +import { makeOmnichainIndexingStatusSnapshotSchema } from "./zod-schema/omnichain-indexing-status-snapshot"; import { makeCrossChainIndexingStatusSnapshotSchema, - makeOmnichainIndexingStatusSnapshotSchema, makeRealtimeIndexingStatusProjectionSchema, } from "./zod-schemas"; From 9242c3b5c2f315b6b99e9da01cba05b5fa57f627 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 11:27:40 +0100 Subject: [PATCH 12/14] Extract OmnichainIndexingStatus zod-schema-layer from generic validations.ts file --- .../ensindexer/indexing-status/validations.ts | 234 ---------------- .../omnichain-indexing-status-snapshot.ts | 249 +++++++++++++++++- 2 files changed, 236 insertions(+), 247 deletions(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts index 3e3922594..94f86b126 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts @@ -1,20 +1,9 @@ import type { ParsePayload } from "zod/v4/core"; -import type { ChainId } from "../../shared/types"; import { ChainIndexingConfigTypeIds, ChainIndexingStatusIds, - type ChainIndexingStatusSnapshot, } from "./chain-indexing-status-snapshot"; -import { - checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill, - checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted, - checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing, - checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted, - getOmnichainIndexingStatus, - type OmnichainIndexingStatusSnapshot, - type OmnichainIndexingStatusSnapshotFollowing, -} from "./omnichain-indexing-status-snapshot"; import type { CrossChainIndexingStatusSnapshotOmnichain, RealtimeIndexingStatusProjection, @@ -24,229 +13,6 @@ import type { * Invariants for {@link OmnichainIndexingSnapshot}. */ -/** - * Invariant: For omnichain snapshot, - * `omnichainStatus` is set based on the snapshots of individual chains. - */ -export function invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot( - ctx: ParsePayload, -) { - const snapshot = ctx.value; - const chains = Array.from(snapshot.chains.values()); - const expectedOmnichainStatus = getOmnichainIndexingStatus(chains); - const actualOmnichainStatus = snapshot.omnichainStatus; - - if (expectedOmnichainStatus !== actualOmnichainStatus) { - ctx.issues.push({ - code: "custom", - input: snapshot, - message: `'${actualOmnichainStatus}' is an invalid omnichainStatus. Expected '${expectedOmnichainStatus}' based on the statuses of individual chains.`, - }); - } -} - -/** - * Invariant: For omnichain status snapshot, - * `omnichainIndexingCursor` is lower than the earliest start block - * across all queued chains. - * - * Note: if there are no queued chains, the invariant holds. - */ -export function invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains( - ctx: ParsePayload, -) { - const snapshot = ctx.value; - const queuedChains = Array.from(snapshot.chains.values()).filter( - (chain) => chain.chainStatus === ChainIndexingStatusIds.Queued, - ); - - // there are no queued chains - if (queuedChains.length === 0) { - // the invariant holds - return; - } - - const queuedChainStartBlocks = queuedChains.map((chain) => chain.config.startBlock.timestamp); - const queuedChainEarliestStartBlock = Math.min(...queuedChainStartBlocks); - - // there are queued chains - // the invariant holds if the omnichain indexing cursor is lower than - // the earliest start block across all queued chains - if (snapshot.omnichainIndexingCursor >= queuedChainEarliestStartBlock) { - ctx.issues.push({ - code: "custom", - input: snapshot, - message: - "`omnichainIndexingCursor` must be lower than the earliest start block across all queued chains.", - }); - } -} - -/** - * Invariant: For omnichain status snapshot, - * `omnichainIndexingCursor` is lower than or equal to - * the highest `backfillEndBlock` across all backfill chains. - * - * Note: if there are no backfill chains, the invariant holds. - */ -export function invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains( - ctx: ParsePayload, -) { - const snapshot = ctx.value; - const backfillChains = Array.from(snapshot.chains.values()).filter( - (chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill, - ); - - // there are no backfill chains - if (backfillChains.length === 0) { - // the invariant holds - return; - } - - const backfillEndBlocks = backfillChains.map((chain) => chain.backfillEndBlock.timestamp); - const highestBackfillEndBlock = Math.max(...backfillEndBlocks); - - // there are backfill chains - // the invariant holds if the omnichainIndexingCursor is lower than or - // equal to the highest backfillEndBlock across all backfill chains. - if (snapshot.omnichainIndexingCursor > highestBackfillEndBlock) { - ctx.issues.push({ - code: "custom", - input: snapshot, - message: - "`omnichainIndexingCursor` must be lower than or equal to the highest `backfillEndBlock` across all backfill chains.", - }); - } -} - -/** - * Invariant: For omnichain status snapshot, - * `omnichainIndexingCursor` is same as the highest latestIndexedBlock - * across all indexed chains. - * - * Note: if there are no indexed chains, the invariant holds. - */ -export function invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain( - ctx: ParsePayload, -) { - const snapshot = ctx.value; - const indexedChains = Array.from(snapshot.chains.values()).filter( - (chain) => - chain.chainStatus === ChainIndexingStatusIds.Backfill || - chain.chainStatus === ChainIndexingStatusIds.Completed || - chain.chainStatus === ChainIndexingStatusIds.Following, - ); - - // there are no indexed chains - if (indexedChains.length === 0) { - // the invariant holds - return; - } - - const indexedChainLatestIndexedBlocks = indexedChains.map( - (chain) => chain.latestIndexedBlock.timestamp, - ); - const indexedChainHighestLatestIndexedBlock = Math.max(...indexedChainLatestIndexedBlocks); - - // there are indexed chains - // the invariant holds if the omnichain indexing cursor is same as - // the highest latestIndexedBlock across all indexed chains - if (snapshot.omnichainIndexingCursor !== indexedChainHighestLatestIndexedBlock) { - ctx.issues.push({ - code: "custom", - input: snapshot, - message: - "`omnichainIndexingCursor` must be same as the highest `latestIndexedBlock` across all indexed chains.", - }); - } -} - -/** - * Invariant: For omnichain status snapshot 'unstarted', - * all chains must have "queued" status. - */ -export function invariant_omnichainSnapshotUnstartedHasValidChains( - ctx: ParsePayload>, -) { - const chains = ctx.value; - const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted( - Array.from(chains.values()), - ); - - if (hasValidChains === false) { - ctx.issues.push({ - code: "custom", - input: chains, - message: `For omnichain status snapshot 'unstarted', all chains must have "queued" status.`, - }); - } -} - -/** - * Invariant: For omnichain status snapshot 'backfill', - * at least one chain must be in "backfill" status and - * each chain has to have a status of either "queued", "backfill" - * or "completed". - */ -export function invariant_omnichainStatusSnapshotBackfillHasValidChains( - ctx: ParsePayload>, -) { - const chains = ctx.value; - const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill( - Array.from(chains.values()), - ); - - if (hasValidChains === false) { - ctx.issues.push({ - code: "custom", - input: chains, - message: `For omnichain status snapshot 'backfill', at least one chain must be in "backfill" status and each chain has to have a status of either "queued", "backfill" or "completed".`, - }); - } -} - -/** - * Invariant: For omnichain status snapshot 'completed', - * all chains must have "completed" status. - */ -export function invariant_omnichainStatusSnapshotCompletedHasValidChains( - ctx: ParsePayload>, -) { - const chains = ctx.value; - const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted( - Array.from(chains.values()), - ); - - if (hasValidChains === false) { - ctx.issues.push({ - code: "custom", - input: chains, - message: `For omnichain status snapshot 'completed', all chains must have "completed" status.`, - }); - } -} - -/** - * Invariant: For omnichain status snapshot 'following', - * at least one chain must be in 'following' status. - */ -export function invariant_omnichainStatusSnapshotFollowingHasValidChains( - ctx: ParsePayload, -) { - const snapshot = ctx.value; - const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing( - Array.from(snapshot.chains.values()), - ); - - if (hasValidChains === false) { - ctx.issues.push({ - code: "custom", - input: snapshot, - message: "For omnichainStatus 'following', at least one chain must be in 'following' status.", - }); - } -} - /** * Invariants for {@link CrossChainIndexingStatusSnapshotOmnichain}. */ diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts index 5c4fe61b5..543e3b0e2 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/omnichain-indexing-status-snapshot.ts @@ -1,28 +1,251 @@ import { z } from "zod/v4"; +import type { ParsePayload } from "zod/v4/core"; import { deserializeChainId } from "../../../shared/deserialize"; import type { ChainId } from "../../../shared/types"; import { makeChainIdStringSchema, makeUnixTimestampSchema } from "../../../shared/zod-schemas"; -import type { - ChainIndexingStatusSnapshot, - ChainIndexingStatusSnapshotCompleted, - ChainIndexingStatusSnapshotQueued, +import { + ChainIndexingStatusIds, + type ChainIndexingStatusSnapshot, + type ChainIndexingStatusSnapshotCompleted, + type ChainIndexingStatusSnapshotQueued, } from "../chain-indexing-status-snapshot"; import { type ChainIndexingStatusSnapshotForOmnichainIndexingStatusSnapshotBackfill, + checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill, + checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted, + checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing, + checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted, + getOmnichainIndexingStatus, OmnichainIndexingStatusIds, + type OmnichainIndexingStatusSnapshot, + type OmnichainIndexingStatusSnapshotFollowing, } from "../omnichain-indexing-status-snapshot"; -import { - invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain, - invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains, - invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains, - invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot, - invariant_omnichainSnapshotUnstartedHasValidChains, - invariant_omnichainStatusSnapshotBackfillHasValidChains, - invariant_omnichainStatusSnapshotCompletedHasValidChains, -} from "../validations"; import { makeChainIndexingStatusSnapshotSchema } from "./chain-indexing-status-snapshot"; +/** + * Invariant: For omnichain snapshot, + * `omnichainStatus` is set based on the snapshots of individual chains. + */ +export function invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot( + ctx: ParsePayload, +) { + const snapshot = ctx.value; + const chains = Array.from(snapshot.chains.values()); + const expectedOmnichainStatus = getOmnichainIndexingStatus(chains); + const actualOmnichainStatus = snapshot.omnichainStatus; + + if (expectedOmnichainStatus !== actualOmnichainStatus) { + ctx.issues.push({ + code: "custom", + input: snapshot, + message: `'${actualOmnichainStatus}' is an invalid omnichainStatus. Expected '${expectedOmnichainStatus}' based on the statuses of individual chains.`, + }); + } +} + +/** + * Invariant: For omnichain status snapshot, + * `omnichainIndexingCursor` is lower than the earliest start block + * across all queued chains. + * + * Note: if there are no queued chains, the invariant holds. + */ +export function invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains( + ctx: ParsePayload, +) { + const snapshot = ctx.value; + const queuedChains = Array.from(snapshot.chains.values()).filter( + (chain) => chain.chainStatus === ChainIndexingStatusIds.Queued, + ); + + // there are no queued chains + if (queuedChains.length === 0) { + // the invariant holds + return; + } + + const queuedChainStartBlocks = queuedChains.map((chain) => chain.config.startBlock.timestamp); + const queuedChainEarliestStartBlock = Math.min(...queuedChainStartBlocks); + + // there are queued chains + // the invariant holds if the omnichain indexing cursor is lower than + // the earliest start block across all queued chains + if (snapshot.omnichainIndexingCursor >= queuedChainEarliestStartBlock) { + ctx.issues.push({ + code: "custom", + input: snapshot, + message: + "`omnichainIndexingCursor` must be lower than the earliest start block across all queued chains.", + }); + } +} + +/** + * Invariant: For omnichain status snapshot, + * `omnichainIndexingCursor` is lower than or equal to + * the highest `backfillEndBlock` across all backfill chains. + * + * Note: if there are no backfill chains, the invariant holds. + */ +export function invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains( + ctx: ParsePayload, +) { + const snapshot = ctx.value; + const backfillChains = Array.from(snapshot.chains.values()).filter( + (chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill, + ); + + // there are no backfill chains + if (backfillChains.length === 0) { + // the invariant holds + return; + } + + const backfillEndBlocks = backfillChains.map((chain) => chain.backfillEndBlock.timestamp); + const highestBackfillEndBlock = Math.max(...backfillEndBlocks); + + // there are backfill chains + // the invariant holds if the omnichainIndexingCursor is lower than or + // equal to the highest backfillEndBlock across all backfill chains. + if (snapshot.omnichainIndexingCursor > highestBackfillEndBlock) { + ctx.issues.push({ + code: "custom", + input: snapshot, + message: + "`omnichainIndexingCursor` must be lower than or equal to the highest `backfillEndBlock` across all backfill chains.", + }); + } +} + +/** + * Invariant: For omnichain status snapshot, + * `omnichainIndexingCursor` is same as the highest latestIndexedBlock + * across all indexed chains. + * + * Note: if there are no indexed chains, the invariant holds. + */ +export function invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain( + ctx: ParsePayload, +) { + const snapshot = ctx.value; + const indexedChains = Array.from(snapshot.chains.values()).filter( + (chain) => + chain.chainStatus === ChainIndexingStatusIds.Backfill || + chain.chainStatus === ChainIndexingStatusIds.Completed || + chain.chainStatus === ChainIndexingStatusIds.Following, + ); + + // there are no indexed chains + if (indexedChains.length === 0) { + // the invariant holds + return; + } + + const indexedChainLatestIndexedBlocks = indexedChains.map( + (chain) => chain.latestIndexedBlock.timestamp, + ); + const indexedChainHighestLatestIndexedBlock = Math.max(...indexedChainLatestIndexedBlocks); + + // there are indexed chains + // the invariant holds if the omnichain indexing cursor is same as + // the highest latestIndexedBlock across all indexed chains + if (snapshot.omnichainIndexingCursor !== indexedChainHighestLatestIndexedBlock) { + ctx.issues.push({ + code: "custom", + input: snapshot, + message: + "`omnichainIndexingCursor` must be same as the highest `latestIndexedBlock` across all indexed chains.", + }); + } +} + +/** + * Invariant: For omnichain status snapshot 'unstarted', + * all chains must have "queued" status. + */ +export function invariant_omnichainSnapshotUnstartedHasValidChains( + ctx: ParsePayload>, +) { + const chains = ctx.value; + const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted( + Array.from(chains.values()), + ); + + if (hasValidChains === false) { + ctx.issues.push({ + code: "custom", + input: chains, + message: `For omnichain status snapshot 'unstarted', all chains must have "queued" status.`, + }); + } +} + +/** + * Invariant: For omnichain status snapshot 'backfill', + * at least one chain must be in "backfill" status and + * each chain has to have a status of either "queued", "backfill" + * or "completed". + */ +export function invariant_omnichainStatusSnapshotBackfillHasValidChains( + ctx: ParsePayload>, +) { + const chains = ctx.value; + const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill( + Array.from(chains.values()), + ); + + if (hasValidChains === false) { + ctx.issues.push({ + code: "custom", + input: chains, + message: `For omnichain status snapshot 'backfill', at least one chain must be in "backfill" status and each chain has to have a status of either "queued", "backfill" or "completed".`, + }); + } +} + +/** + * Invariant: For omnichain status snapshot 'completed', + * all chains must have "completed" status. + */ +export function invariant_omnichainStatusSnapshotCompletedHasValidChains( + ctx: ParsePayload>, +) { + const chains = ctx.value; + const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted( + Array.from(chains.values()), + ); + + if (hasValidChains === false) { + ctx.issues.push({ + code: "custom", + input: chains, + message: `For omnichain status snapshot 'completed', all chains must have "completed" status.`, + }); + } +} + +/** + * Invariant: For omnichain status snapshot 'following', + * at least one chain must be in 'following' status. + */ +export function invariant_omnichainStatusSnapshotFollowingHasValidChains( + ctx: ParsePayload, +) { + const snapshot = ctx.value; + const hasValidChains = checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing( + Array.from(snapshot.chains.values()), + ); + + if (hasValidChains === false) { + ctx.issues.push({ + code: "custom", + input: snapshot, + message: "For omnichainStatus 'following', at least one chain must be in 'following' status.", + }); + } +} + /** * Makes Zod schema for {@link ChainIndexingStatusSnapshot} per chain. */ From 2202a9ee60b07c66a3f235c0b4a8b101f86765f7 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 13:12:07 +0100 Subject: [PATCH 13/14] Extract OmnichainIndexingStatus deserialize-layer from generic deserialize.ts file --- .../ensindexer/indexing-status/deserialize.ts | 22 ----------------- .../omnichain-indexing-status-snapshot.ts | 24 +++++++++++++++++++ 2 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize/omnichain-indexing-status-snapshot.ts diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts index 657ae017f..ea49239ff 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts @@ -1,37 +1,15 @@ import { prettifyError } from "zod/v4"; -import type { OmnichainIndexingStatusSnapshot } from "./omnichain-indexing-status-snapshot"; -import type { SerializedOmnichainIndexingStatusSnapshot } from "./serialize/omnichain-indexing-status-snapshot"; import type { SerializedCrossChainIndexingStatusSnapshot, SerializedRealtimeIndexingStatusProjection, } from "./serialized-types"; import type { CrossChainIndexingStatusSnapshot, RealtimeIndexingStatusProjection } from "./types"; -import { makeOmnichainIndexingStatusSnapshotSchema } from "./zod-schema/omnichain-indexing-status-snapshot"; import { makeCrossChainIndexingStatusSnapshotSchema, makeRealtimeIndexingStatusProjectionSchema, } from "./zod-schemas"; -/** - * Deserialize an {@link OmnichainIndexingStatusSnapshot} object. - */ -export function deserializeOmnichainIndexingStatusSnapshot( - maybeSnapshot: SerializedOmnichainIndexingStatusSnapshot, - valueLabel?: string, -): OmnichainIndexingStatusSnapshot { - const schema = makeOmnichainIndexingStatusSnapshotSchema(valueLabel); - const parsed = schema.safeParse(maybeSnapshot); - - if (parsed.error) { - throw new Error( - `Cannot deserialize into OmnichainIndexingStatusSnapshot:\n${prettifyError(parsed.error)}\n`, - ); - } - - return parsed.data; -} - /** * Deserialize an {@link CrossChainIndexingStatusSnapshot} object. */ diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize/omnichain-indexing-status-snapshot.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize/omnichain-indexing-status-snapshot.ts new file mode 100644 index 000000000..d29601add --- /dev/null +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize/omnichain-indexing-status-snapshot.ts @@ -0,0 +1,24 @@ +import { prettifyError } from "zod/v4"; + +import type { OmnichainIndexingStatusSnapshot } from "../omnichain-indexing-status-snapshot"; +import type { SerializedOmnichainIndexingStatusSnapshot } from "../serialize/omnichain-indexing-status-snapshot"; +import { makeOmnichainIndexingStatusSnapshotSchema } from "../zod-schema/omnichain-indexing-status-snapshot"; + +/** + * Deserialize an {@link OmnichainIndexingStatusSnapshot} object. + */ +export function deserializeOmnichainIndexingStatusSnapshot( + maybeSnapshot: SerializedOmnichainIndexingStatusSnapshot, + valueLabel?: string, +): OmnichainIndexingStatusSnapshot { + const schema = makeOmnichainIndexingStatusSnapshotSchema(valueLabel); + const parsed = schema.safeParse(maybeSnapshot); + + if (parsed.error) { + throw new Error( + `Cannot deserialize into OmnichainIndexingStatusSnapshot:\n${prettifyError(parsed.error)}\n`, + ); + } + + return parsed.data; +} From f5f18486d539bc13f43537eb9d4910047a390551 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 11 Feb 2026 13:13:20 +0100 Subject: [PATCH 14/14] Update import paths Replace `./deserialize` with `./deserialize/omnichain-indexing-status-snapshot` --- .../src/ensindexer/indexing-status/conversions.test.ts | 2 +- packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts index d4d9ad403..6f731860e 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts @@ -13,7 +13,7 @@ import { type ChainIndexingStatusSnapshotFollowing, type ChainIndexingStatusSnapshotQueued, } from "./chain-indexing-status-snapshot"; -import { deserializeOmnichainIndexingStatusSnapshot } from "./deserialize"; +import { deserializeOmnichainIndexingStatusSnapshot } from "./deserialize/omnichain-indexing-status-snapshot"; import { OmnichainIndexingStatusIds, type OmnichainIndexingStatusSnapshot, diff --git a/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts b/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts index 73b67009c..d9ba4f01a 100644 --- a/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts +++ b/packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts @@ -1,6 +1,7 @@ export * from "./chain-indexing-status-snapshot"; export * from "./deserialize"; export * from "./deserialize/chain-indexing-status-snapshot"; +export * from "./deserialize/omnichain-indexing-status-snapshot"; export * from "./helpers"; export * from "./omnichain-indexing-status-snapshot"; export * from "./projection";