-
Notifications
You must be signed in to change notification settings - Fork 15
refactor(ensnode-sdk): improve Chain Indexing Status Snapshot data model #1617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(ensnode-sdk): improve Chain Indexing Status Snapshot data model #1617
Conversation
Removes code that was unused anywhere.
…rialized-types.ts file
… from zod-schema-layer
…ch enables validating values against business-layer requirements.
🦋 Changeset detectedLatest commit: 974335e The changes in this PR will be included in the next version bump. This PR includes changesets to release 19 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughA comprehensive refactoring of indexing status infrastructure that reorganizes type definitions and validation logic, introduces new dedicated schema modules, removes legacy omnichaining validators, and modernizes the ponder-sdk type system by replacing zod-inferred types with plain types and Unvalidated utilities. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Tip We've launched Issue Planner and it is currently in beta. Please try it out and share your feedback on Discord! Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
tk-o
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self-review completed.
| // Invariant: earliestKnownBlockTimestamps is guaranteed to have at least one element | ||
| if (earliestKnownBlockTimestamps.length === 0) { | ||
| throw new Error( | ||
| "Invariant violation: at least one chain is required to determine the lowest omnichain start block timestamp", | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is additional change to just moving the code from helpers.ts.
| // Invariant: latestKnownBlockTimestamps is guaranteed to have at least one element | ||
| if (latestKnownBlockTimestamps.length === 0) { | ||
| throw new Error( | ||
| "Invariant violation: at least one chain is required to determine the highest omnichain known block timestamp", | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is additional change to just moving the code from helpers.ts.
| ChainStatusType extends ChainIndexingStatusSnapshot, | ||
| >(chains: [ChainId, ChainStatusType][]): [ChainId, ChainStatusType][] { | ||
| // Sort the chain statuses by the omnichain first block to index timestamp | ||
| return [...chains].sort( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is additional change to just moving the code from helpers.ts.
Before it was modifying the input object:
chains.sort(There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts (1)
148-156: 🧹 Nitpick | 🔵 TrivialMisleading variable name:
allChainsHaveValidStatusesuses.some(), not.every().The variable name suggests all chains are checked, but
.some()only checks for at least one match. Consider renaming to something likeatLeastOneChainFollowingfor consistency with the naming pattern used incheckChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill(line 111).♻️ Suggested rename
export function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing( chains: ChainIndexingStatusSnapshot[], ): chains is ChainIndexingStatusSnapshot[] { - const allChainsHaveValidStatuses = chains.some( + const atLeastOneChainInTargetStatus = chains.some( (chain) => chain.chainStatus === ChainIndexingStatusIds.Following, ); - return allChainsHaveValidStatuses; + return atLeastOneChainInTargetStatus; }packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts (1)
264-268:⚠️ Potential issue | 🟡 MinorRemove debug
console.logfrom invariant function.This
console.logininvariant_slowestChainEqualsToOmnichainSnapshotTimeappears to be a leftover debug statement. It will emit internal state to the console on every validation failure in production.🧹 Proposed fix
if (slowestChainIndexingCursor !== omnichainIndexingCursor) { - console.log("invariant_slowestChainEqualsToOmnichainSnapshotTime", { - slowestChainIndexingCursor, - omnichainIndexingCursor, - }); ctx.issues.push({
🤖 Fix all issues with AI agents
In
`@packages/ensnode-sdk/src/ensindexer/indexing-status/chain-indexing-status-snapshot.ts`:
- Around line 344-383: Update the documentation and error message to reflect
that queued chains only contribute a timestamp when their config is Definite: in
getTimestampForHighestOmnichainKnownBlock, clarify in the JSDoc that
ChainIndexingStatusIds.Queued contributes config.endBlock.timestamp only for
ChainIndexingConfigTypeIds.Definite and that Queued+Indefinite chains are
intentionally skipped (they do not add to latestKnownBlockTimestamps); also
change the thrown Error text to state the actual precondition — e.g., "Invariant
violation: at least one chain must contribute a known block timestamp" — so the
invariant refers to contributing chains rather than the input chains array.
In `@packages/ensnode-sdk/src/ensindexer/indexing-status/types.ts`:
- Around line 1-7: Add imports for the missing types ChainIndexingStatusIds and
ChainIndexingConfigTypeIds to the top of this file (in the same import block
that currently brings in ChainIndexingStatusSnapshot,
ChainIndexingStatusSnapshotBackfill, ChainIndexingStatusSnapshotCompleted,
ChainIndexingStatusSnapshotQueued) so JSDoc {`@link` ...} references resolve;
import them from the module where those IDs are defined and ensure they are
exported as types (i.e., use "import type { ChainIndexingStatusIds,
ChainIndexingConfigTypeIds } from '...'").
In
`@packages/ensnode-sdk/src/ensindexer/indexing-status/validate/chain-indexing-status-snapshot.ts`:
- Around line 7-12: The JSDoc for validateChainIndexingStatusSnapshot is
misleading: it says it validates a "maybe ChainIndexingStatusSnapshot" while the
function parameter unvalidatedSnapshot is already typed as
ChainIndexingStatusSnapshot; either update the JSDoc to remove "maybe" and state
it performs runtime invariant checks on an already-typed object, or change the
parameter type to a looser type (e.g., unknown) so the function performs full
structural validation before asserting/returning
ChainIndexingStatusSnapshot—update the function signature or the comment
accordingly and ensure references to unvalidatedSnapshot and
validateChainIndexingStatusSnapshot remain consistent.
In
`@packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts`:
- Around line 21-39: Remove the redundant truthiness check for config.endBlock
after the Indefinite guard in invariant_chainSnapshotQueuedBlocks: once you
return when config.configType === ChainIndexingConfigTypeIds.Indefinite the type
is narrowed to the definite variant (ChainIndexingConfigDefinite) so drop the
`config.endBlock &&` condition and directly call
blockRef.isBeforeOrEqualTo(config.startBlock, config.endBlock); apply the same
cleanup to the similar check around line 74 in the other invariant where the
Indefinite guard already narrows the type.
packages/ensnode-sdk/src/ensindexer/indexing-status/chain-indexing-status-snapshot.ts
Show resolved
Hide resolved
packages/ensnode-sdk/src/ensindexer/indexing-status/validate/chain-indexing-status-snapshot.ts
Show resolved
Hide resolved
...ages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Refactors the ENSNode SDK’s Chain Indexing Status Snapshot model into clearer layers (business types, schema/validation, serialization/deserialization) so consumers can validate/deserialise snapshots more flexibly in preparation for upcoming indexing-status API divergence.
Changes:
- Extracts
ChainIndexingStatusSnapshotbusiness types + related helpers into a dedicated module. - Moves chain snapshot Zod schemas/invariants into
zod-schema/and adds a dedicated runtime validator (validateChainIndexingStatusSnapshot). - Introduces a dedicated serialize/deserialize entry for chain snapshots and updates tests/imports accordingly.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.ts | Removes inline chain snapshot schema definitions and reuses new zod-schema/ module. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schemas.test.ts | Updates test imports to use new chain snapshot module and schema location. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts | New home for chain snapshot schemas + invariants (and “serialized schema” alias). |
| packages/ensnode-sdk/src/ensindexer/indexing-status/validations.ts | Removes chain-level invariants from the shared validations module. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/validate/chain-indexing-status-snapshot.ts | Adds a runtime validator for the business-layer chain snapshot type. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/types.ts | Removes chain snapshot business types from types.ts (now imported from new module). |
| packages/ensnode-sdk/src/ensindexer/indexing-status/serialized-types.ts | Adjusts serialized omnichain/cross-chain types to depend on new chain snapshot module. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/serialize/chain-indexing-status-snapshot.ts | New serialize helper + serialized chain snapshot type aliases. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/serialize.ts | Uses extracted serializeChainIndexingSnapshots helper. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/projection.test.ts | Updates imports for chain snapshot constants/types. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/index.ts | Re-exports new modules (business types, serialize/deserialize, validator). |
| packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.ts | Moves chain snapshot–specific helpers out of general helpers module. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/helpers.test.ts | Removes tests for helpers moved to chain snapshot module. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize/chain-indexing-status.ts | Adds dedicated deserializer for a chain snapshot. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/deserialize.ts | Removes chain snapshot deserializer from the aggregate deserialize module. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/conversions.test.ts | Updates imports due to type/module refactor. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/chain-indexing-status-snapshot.ts | New module defining chain snapshot business types + moved helpers. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/chain-indexing-status-snapshot.test.ts | Adds tests for createIndexingConfig in its new location. |
| packages/ensnode-sdk/src/ensindexer/indexing-status/block-refs.mock.ts | Adds block ref fixtures used by new chain snapshot tests. |
| apps/ensindexer/src/lib/indexing-status/ponder-metadata/validations.ts | Removes now-unneeded invariant and simplifies imports. |
| .changeset/quick-paws-attend.md | Declares a minor bump for the new validator API. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/ensnode-sdk/src/ensindexer/indexing-status/validate/chain-indexing-status-snapshot.ts
Outdated
Show resolved
Hide resolved
...ages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts
Show resolved
Hide resolved
...ages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts
Show resolved
Hide resolved
...ages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts
Outdated
Show resolved
Hide resolved
...ages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts
Outdated
Show resolved
Hide resolved
...ages/ensnode-sdk/src/ensindexer/indexing-status/zod-schema/chain-indexing-status-snapshot.ts
Show resolved
Hide resolved
packages/ensnode-sdk/src/ensindexer/indexing-status/validate/chain-indexing-status-snapshot.ts
Outdated
Show resolved
Hide resolved
lightwalker-eth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tk-o Thanks for updates 👍 Shared a few suggestions please take the lead to merge when ready.
| * Validates a maybe {@link ChainIndexingStatusSnapshot} object. | ||
| */ | ||
| export function validateChainIndexingStatusSnapshot( | ||
| unvalidatedSnapshot: ChainIndexingStatusSnapshot, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's find a different way to type unvalidated objects so that it's always explicit that they might not be what you think they are. I don't feel comfortable ever typing something as an X that we cannot rely on as being an X. Let's support the type system so that it can support us.
Appreciate your advice on how we solve this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lightwalker-eth What if our buildInvalidatedX functions would all return respective Unvalidated<X> value so that it could be validated as X?
The Unvalidated wrapper type would signal that data is of a business-level data model shape, but lacks guarantees of that model. For example:
unvalidatedSnapshot: Unvalidated<ChainIndexingStatusSnapshot>,where Unvalidated is defined as:
type Unvalidated<T> = DeepPartial<T>;We have DeepPartial defined in both ENSNode SDK and can use it the same way in Ponder SDK:
ensnode/packages/ensnode-sdk/src/shared/types.ts
Lines 131 to 166 in 4b43203
| /** | |
| * A utility type that makes all properties of a type optional recursively, | |
| * including nested objects and arrays. | |
| * | |
| * @example | |
| * ```typescript | |
| * type Config = { | |
| * a: string; | |
| * b: { | |
| * x: number; | |
| * y: { z: boolean }; | |
| * }; | |
| * c: { id: string }[]; | |
| * } | |
| * | |
| * type PartialConfig = DeepPartial<Config>; | |
| * // Results in: | |
| * // { | |
| * // a?: string; | |
| * // b?: { | |
| * // x?: number; | |
| * // y?: { z?: boolean }; | |
| * // }; | |
| * // c?: { id?: string }[]; | |
| * // } | |
| * | |
| * // Usage: | |
| * const update: PartialConfig = { b: { y: { z: true } } }; | |
| * ``` | |
| */ | |
| export type DeepPartial<T> = { | |
| [P in keyof T]?: T[P] extends (infer U)[] | |
| ? DeepPartial<U>[] | |
| : T[P] extends object | |
| ? DeepPartial<T[P]> | |
| : T[P]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tk-o I like this idea 👍 Thanks!
| unvalidatedSnapshot: ChainIndexingStatusSnapshot, | ||
| unvalidatedSnapshot: ChainIndexingStatusSnapshot | unknown, | ||
| valueLabel?: string, | ||
| ): ChainIndexingStatusSnapshot { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSDoc should make it explicit what happens if it's not valid.
Additionally, what is the motivation to have this return the parsed object? If this is purely about validation then there should be zero transformations, right?
And assuming zero transformations, and considering how this function can also take an input an unknown, shouldn't this return the type hint to the type system identifying that unvalidatedSnapshot truly is a ChainIndexingStatusSnapshot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the final suggestion that I'll apply is with using the Unvalidated<T> type to signal that validation happens on object that is shaped like T but has not been validated against business rules required for T.
unvalidatedSnapshot: Unvalidated<ChainIndexingStatusSnapshot>,
shrugs
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
…-chain-indexing-status-snapshot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 36 out of 38 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (2)
packages/ponder-sdk/src/schema/chains.ts:22
schemaChainIdStringcurrently only checks thatString(Number(value)) === value. This allows invalid inputs like "NaN", "Infinity", "-1", or "0" to pass, which can let invalid chain ID strings through (e.g., as record keys) without being caught. Tighten the check to require a finite, safe integer > 0 (and still reject formats like "01"), e.g., parse to a number and validateNumber.isSafeInteger(n) && n > 0 && String(n) === value.
packages/ponder-sdk/src/deserialize/indexing-status.ts:33deserializePonderIndexingStatusbuilds thechainsMap usingchainData.idand ignores the record key. This means inconsistent payloads like{ "1": { id: 10, ... } }will be accepted and silently remapped to chain 10. Consider enforcing that the record key matcheschainData.id(e.g., via a.checkon the schema or by comparing insidebuildUnvalidatedPonderIndexingStatusand surfacing a validation issue), or simplify the serialized shape toRecord<ChainIdString, BlockRef>to avoid duplicated IDs.
const chains = new Map<ChainId, BlockRef>();
for (const [, chainData] of Object.entries(data)) {
chains.set(chainData.id, chainData.block);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/ponder-sdk/src/client.test.ts (1)
142-160:⚠️ Potential issue | 🟡 MinorTest silently passes if no error is thrown.
The
try/catchblock has no guard ensuring the error path is actually reached. IfponderClient.metrics()resolves successfully, no assertions execute and the test passes silently. Addexpect.assertions(n)at the top or use therejectspattern (as other tests in this file already do).Proposed fix
// Act + expect.assertions(5); try { await ponderClient.metrics(); } catch (error) {Or alternatively, refactor to use
rejects.toThrowErrorlike the other test cases.packages/ponder-sdk/src/schema/chains.ts (1)
8-22:⚠️ Potential issue | 🟠 Major
schemaChainIdStringnow accepts invalid chain-ID strings (e.g."0","-1","1.5","Infinity","NaN").The removed preprocessing/cross-check previously validated that the parsed numeric value was a positive integer. The remaining invariant only checks that the string round-trips through
Number()→ template literal, which passes for any string thatNumber()can parse — including"0","-1","1.5","Infinity", and"NaN".Consider adding an integer + positive check inside the invariant:
Proposed fix
function invariant_chainIdStringRepresentsValidChainId(ctx: ParsePayload<string>) { const maybeChainId = ctx.value; - if (`${Number(maybeChainId)}` !== maybeChainId) { + const num = Number(maybeChainId); + if (!Number.isInteger(num) || num <= 0 || `${num}` !== maybeChainId) { ctx.issues.push({ code: "custom", input: ctx.value, message: `'${maybeChainId}' must be a string representing a chain ID.`, }); } }
🤖 Fix all issues with AI agents
In `@packages/ponder-sdk/src/deserialize/indexing-metrics.ts`:
- Around line 100-106: The function deserializePonderIndexingMetrics narrows its
parameter to SerializedPonderIndexingMetrics while the sibling
deserializePonderIndexingStatus accepts SerializedPonderIndexingStatus |
unknown; change the parameter type of deserializePonderIndexingMetrics to
SerializedPonderIndexingMetrics | unknown so it matches the sibling and accepts
untyped external input, leaving the existing
schemaDecodedPonderIndexingMetrics.transform(...).pipe(...).safeParse(data)
logic intact; update the function signature for deserializePonderIndexingMetrics
to use the union type to avoid breaking callers and ensure consistent behavior
with deserializePonderIndexingStatus.
- Around line 28-37: The JSDoc for buildUnvalidatedPonderIndexingMetrics
incorrectly references schemaSerializedPonderIndexingMetrics; update the comment
to state the returned Unvalidated<PonderIndexingMetrics> should be validated
with schemaPonderIndexingMetrics (the schema that validates the builder's
output), i.e., replace the {`@link` schemaSerializedPonderIndexingMetrics}
reference with {`@link` schemaPonderIndexingMetrics} in the function's docblock so
it matches the actual validation used in the code.
In `@packages/ponder-sdk/src/schema/chain-indexing-metrics.ts`:
- Around line 80-124: The function invariant_includesRequiredMetrics currently
always runs label validation for requiredChainMetricNames even if the metric was
already reported missing; update invariant_includesRequiredMetrics to
short-circuit per-metric label checks by first verifying the metric exists in
metricNames before calling prometheusMetrics.getLabels or validating chain label
values. Specifically, after computing requiredChainMetricNames and metricNames,
skip the whole getLabels / schemaChainIdString.safeParse block for any
requiredChainMetricName that is not present (i.e., only call
prometheusMetrics.getLabels and validate maybeChainId when
metricNames.includes(requiredChainMetricName) is true) so you don't emit
redundant "must include a 'chain' label" or parsing errors when the metric was
already flagged missing.
In `@packages/ponder-sdk/src/schema/chain-indexing-status.ts`:
- Around line 8-11: The schemaChainBlockRef constant is defined but is used only
internally by schemaSerializedPonderIndexingStatus; add a short inline comment
above schemaChainBlockRef (or rename it with an internal prefix like
_schemaChainBlockRef) to indicate it is intentionally module-private and not
exported, so future contributors know it's for internal use only and avoid
exporting or reusing it elsewhere; update any nearby JSDoc or comment to
reference schemaSerializedPonderIndexingStatus as its sole consumer.
- Line 2: Remove the import of the internal ParsePayload type from zod/v4/core
and stop using it in the .check() callback signatures; instead, have the
callback accept a safe public type (e.g. use unknown or any) or declare a local
typedef for the payload parameter, and keep using Zod's public APIs (.check(),
.refine()) for validation. Specifically, in chain-indexing-status.ts (and the
other affected files chains.ts and chain-indexing-metrics.ts) remove "import
type { ParsePayload } ..." and update the .check(...) / callback parameter types
in the schema definitions (look for the schema variable/function names in those
files) to use a locally defined type or unknown/any so you no longer rely on the
undocumented ParsePayload symbol.
In `@packages/ponder-sdk/src/utils.ts`:
- Around line 1-68: DeepPartial and Unvalidated are duplicated across packages
(DeepPartial and Unvalidated types); extract these types into a shared module
(e.g., a new types/shared file or an existing common utils package) and have
both packages import/re-export from that single location. Update all references
to use the shared export (replace local DeepPartial and Unvalidated definitions
with imports) and add a tests/build check to ensure both packages compile
against the shared type to prevent future divergence.
- Around line 31-37: DeepPartial<T> currently recurses into all non-primitives
(the "extends object" branch), which breaks built-in types like Date/Map/Set and
affects places such as PonderIndexingStatus.chains; change the type guard so
only plain record-like objects are deep-partial'd (for example use T[P] extends
Record<string, unknown> or explicitly exclude built-ins/Function types) and
leave built-ins (Date, Map, Set, WeakMap, WeakSet, Function, etc.) as-is; update
the DeepPartial<T> definition accordingly so arrays still recurse but built-in
objects do not get their methods turned into optionals.
| @@ -0,0 +1,48 @@ | |||
| import { z } from "zod/v4"; | |||
| import type { ParsePayload } from "zod/v4/core"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Zod v4 ParsePayload type public API documentation
💡 Result:
ParsePayload in Zod v4 is not part of the documented public API.
- In the v4 architecture it’s an internal parsing “carrier” object (holds the current
value, accumulatedissues, and an optionalabortedflag) used by the core parser pipeline; it shows up in the generated type declarations asinterface ParsePayload<T = unknown> { aborted?: boolean; issues: ...; value: T }and is defined innode_modules/zod/v4/core/schemas.d.cts. [3] - Zod’s own docs describe
zod/v4/coreas internals-oriented and “not intended to be used directly”, designed for other packages to build on. [1] - The v4 migration guide explicitly warns that undocumented quasi-internal types/functions are not considered public API and changes to them may be undocumented. [2]
So: there isn’t official “public API documentation” for ParsePayload; if you depend on it, treat it as internal/unstable, and prefer the supported parse APIs (parse, safeParse, etc.) and their return types instead. [1][2]
Sources: [1] [2] [3]
🏁 Script executed:
# First, let's examine the file to understand how ParsePayload is being used
cat -n packages/ponder-sdk/src/schema/chain-indexing-status.tsRepository: namehash/ensnode
Length of output: 1890
🏁 Script executed:
# Check if there are any other uses of ParsePayload in the codebase
rg "ParsePayload" --type ts --type tsxRepository: namehash/ensnode
Length of output: 87
🏁 Script executed:
# Check the package.json to see the Zod version being used
cat packages/ponder-sdk/package.json | grep -A 2 -B 2 "zod"Repository: namehash/ensnode
Length of output: 194
🌐 Web query:
Zod v4 .check() method vs .refine() .superRefine() public API validation
💡 Result:
Zod v4: .check() vs .refine() vs .superRefine() (and what to use for public API validation)
.refine(predicate, options?) — “simple custom rule”
- Best for one boolean condition (“valid / invalid”).
- Produces a single issue with code
"custom". - Still the most readable choice for typical business rules on public inputs. [3]
- In v4, type predicates no longer narrow the inferred type, and some older overloads were removed. [4]
.superRefine((value, ctx) => …) — legacy “multi-issue with ctx”
- Lets you add multiple issues and use non-
"custom"issue codes. [2] - Deprecated in Zod v4 in favor of
.check(). [1] - Also, in v4 the refinement ctx no longer exposes
ctx.path. [4]
.check(checkOrFn) — v4’s low-level “checks” API (preferred over superRefine)
- This is the more general primitive underneath refinements in Zod v4; schemas hold an array of “checks” that run post-parse. [5]
- Use it when you need:
- multiple issues in one pass
- full control over issue objects (codes like
"too_big", etc., not just"custom") - advanced behavior like continuable issues (
continue: true) [3]
- Zod docs note it’s more complex/verbose than
superRefine, but can be faster in performance-sensitive paths. [2]
Practical guidance for “public API validation”
- Prefer built-in validators (
.min,.email, etc.) for standard constraints. - Add
.refine()for single-rule business logic (clean error message + optional path/conditional execution). [6] - Use
.check()when you need multiple field-specific errors, precise issue codes, or you’re building reusable validation “rules” at scale. [3] - Avoid starting new code with
.superRefine()(deprecated). [1]
Sources:
[1] Zod package docs (superRefine deprecated; check exists) https://zod.dev/packages/zod
[2] Zod API docs (superRefine + note on check) https://zod.dev/api?id=preprocess
[3] Zod API docs (check() is the versatile API; example with multiple issues and issue objects) https://zod.dev/api?id=sets
[4] Zod v4 changelog/migration notes (refine changes; ctx.path removed) https://zod.dev/v4/changelog
[5] Zod Core docs (checks concept; .check(...) composition) https://zod.dev/packages/core
[6] Zod docs (refine usage, including path/conditional execution example) https://zod.dev/?id=dates-1
🏁 Script executed:
# Check what Zod types/methods are imported elsewhere in the codebase
rg "from [\"']zod" packages/ponder-sdk/src --type ts -A 1 | head -30Repository: namehash/ensnode
Length of output: 1301
Remove import of ParsePayload from internal Zod module.
ParsePayload is not a public API in Zod v4—it's an internal implementation detail defined in zod/v4/core and explicitly marked as "not intended to be used directly" per Zod's v4 migration guide. Zod warns that changes to undocumented types may be made without notice.
While .check() is the correct public method for this type of validation (replacing deprecated .superRefine()), the ParsePayload type should not be imported from internals. Either define the check function's parameter type locally, use the public .refine() method if possible, or find the documented way to type check callbacks in Zod v4.
This pattern appears in multiple schema files (chains.ts, chain-indexing-status.ts, chain-indexing-metrics.ts) and should be fixed across all of them.
🤖 Prompt for AI Agents
In `@packages/ponder-sdk/src/schema/chain-indexing-status.ts` at line 2, Remove
the import of the internal ParsePayload type from zod/v4/core and stop using it
in the .check() callback signatures; instead, have the callback accept a safe
public type (e.g. use unknown or any) or declare a local typedef for the payload
parameter, and keep using Zod's public APIs (.check(), .refine()) for
validation. Specifically, in chain-indexing-status.ts (and the other affected
files chains.ts and chain-indexing-metrics.ts) remove "import type {
ParsePayload } ..." and update the .check(...) / callback parameter types in the
schema definitions (look for the schema variable/function names in those files)
to use a locally defined type or unknown/any so you no longer rely on the
undocumented ParsePayload symbol.
8758c1d to
974335e
Compare
|
All changes affecting files in |
Lite PR
Tip: Review docs on the ENSNode PR process
Summary
a. Deserialize a serialized representation of business-layer data model, including validation.
b. Validate an unvalidated object with shape matching business-layer data model.
Why
/api/configinto/api/indexing-status#1405, Indexing Status API will soon be different for ENSApi and ENSIndexer. Changes in this PR enable re-using ENSIndexer data model in the future ENSApi data model for Indexing Status.Testing
/api/indexing-statuson local ENSIndexer instance./api/indexing-statuson local ENSApi instance connected to ENSIndexer Alpha in the Green env.pn
Notes for Reviewer (Optional)
OmnichainIndexingStatusSnapshotCrossChainIndexingStatusSnapshotRealtimeIndexingStatusProjectionPre-Review Checklist (Blocking)