Add block/slot handler support with ecosystem-specific filtering - #1105
Conversation
Drops the top-level `onBlock` export in favor of `indexer.onBlock`,
mirroring `indexer.onEvent` / `contractRegister`. Collapses the
`{chain, startBlock, endBlock, interval}` options into a single
`where: ({chain}) => false | true | {block: {number: {_gte, _lte, _every}}}`
predicate, evaluated once per configured chain at registration time. The
decoded range/stride feeds straight into `registerOnBlock` so the
existing `(n - startBlock) % interval === 0` math at FetchState.res:619
is preserved.
TS surface (packages/envio/index.d.ts):
- Drop the shared OnBlockNumberFilter / OnBlockFilter / OnBlockWhereResult
/ OnBlockMethod helpers. Inline _gte/_lte/_every per ecosystem so the
vocabulary doesn't depend on WhereOperator<number>.
- Add Evm/Fuel/Svm prefixed type families mirroring the *OnEvent*
exports: e.g. EvmOnBlockOptions, EvmOnBlockHandler, EvmOnBlockFilter,
EvmOnBlockNumberFilter, EvmOnBlockWhereArgs, EvmOnBlockWhereResult,
EvmOnBlockHandlerArgs, EvmOnBlockContext (alias of EvmOnEventContext).
Same shape for Fuel (filters on block.height) and SVM.
- Rename SvmOnBlockContext -> SvmOnSlotContext. SVM method on
SvmHandlerMethods is now indexer.onSlot with SvmOnSlot* options /
handler / filter (flat {_gte,_lte,_every} since slot is a single int).
Runtime (packages/envio/src):
- Envio.res: drop the typed onBlockFilter/onBlockNumberFilter records;
the canonical TS types live in index.d.ts and the runtime decoder
reads filter shapes ecosystem-aware. onBlockOptions.where now returns
unknown (cast at the boundary in Main.res).
- Main.res: extractRange branches on config.ecosystem.name -- EVM reads
raw.block.number, Fuel reads raw.block.height, SVM reads raw directly
(flat). Predicate evaluation is unchanged; bool fast paths short-
circuit before the decoder. Method is attached as "onSlot" on SVM and
"onBlock" on EVM/Fuel.
- Warn when a where predicate matches zero configured chains so a
misconfigured filter doesn't silently disable the handler.
Tests + docs:
- scenarios/test_codegen/src/handlers/EventHandlers.ts: extend the
onBlockInHandler case with the filter-object path
(block.number._gte/_lte/_every), the default-omitted path, and the
zero-match path that exercises the new warning.
- svmblock_template: BlockHandler.ts and README updated to indexer.onSlot.
- indexing-blocks SKILL.md: per-ecosystem examples (EVM block.number,
Fuel block.height, SVM flat slot) + zero-match warning note.
Runtime hardening (packages/envio/src/Main.res):
- Reject `where` values that aren't a function or undefined with a
user-friendly error citing the actual call site
(`indexer.onBlock(...)` on EVM/Fuel, `indexer.onSlot(...)` on SVM).
This is intentionally stricter than `onEvent`'s `where`, which also
accepts a static value: a static value would have to be re-evaluated
per chain identically and has no useful semantic for block handlers.
- Reject predicate return values that aren't bool / undefined / null /
plain object. Previously a stray number/string/array silently
registered with no filter -- the user would then wonder why their
handler ran on every block.
- Switch the zero-chain-match warning from the module-level
`Logging.warn` to a child logger with structured params
`{onBlock: name}`, so the handler name is searchable in production
logs.
- Hoist `onBlockMethodName` ahead of the function body so it can be
threaded into both the `definePropertyWithValue` call and the error
messages (single source of truth).
SVM scenario (scenarios/svm_test):
- New minimal SVM scenario: config.yaml, schema.graphql, a SlotHandler
that exercises `indexer.onSlot` with a flat `{_every: 5}` filter, and
a vitest test that asserts:
* `indexer.onSlot` is a function and `indexer.onBlock` is absent
on SVM (covers the SVM-only method-name attachment).
* `createTestIndexer().chainIds` reflects the SVM chain config.
- Wire the scenario into .github/workflows/build_and_verify.yml next
to fuel_test so CI runs `pnpm exec envio codegen && pnpm test`.
- pnpm-lock.yaml picks up the new workspace member.
Envio.gen.ts: regenerated from Envio.res (no source change here).
Earlier commit shipped the SVM filter as a flat
`{_gte, _lte, _every}` triple, but the locked design from the original
shape question was `{slot: {_gte, _lte, _every}}` -- nested under a
top-level `slot` key, parallel to EVM's `{block: {number: ...}}` and
Fuel's `{block: {height: ...}}`. The flat shape was a misimplementation.
- packages/envio/index.d.ts: add SvmOnSlotNumberFilter (the inner
range/stride triple) and make SvmOnSlotFilter = `{slot?: ...}`.
- packages/envio/src/Main.res: extractRange's SVM branch now reads
`filter.slot` instead of using `filter` directly.
- skills/indexing-blocks/SKILL.md: SVM example + options table updated
to the `{slot: {...}}` shape.
- scenarios/svm_test/src/handlers/SlotHandler.ts: scenario filter
switched from `{_every: 5}` to `{slot: {_every: 5}}`.
Polish pass on the onBlock API:
Ecosystem.t carries the config (packages/envio/src):
- Add `onBlockMethodName` and `extractOnBlockNumberFilter` fields to
`Ecosystem.t`. Each ecosystem implementation in `sources/{Evm,Fuel,Svm}.res`
now owns its own method name (`onBlock` for chain-based ecosystems,
`onSlot` for SVM) and the unwrap path for the user-returned filter
(`block.number`, `block.height`, `slot`).
- Main.res::getGlobalIndexer drops the two `switch config.ecosystem.name`
branches and reads `config.ecosystem.onBlockMethodName` /
`extractOnBlockNumberFilter` directly. Adding a new ecosystem now only
requires a new ecosystem record, no new switch arm.
Envio.res internal-only types (no genType):
- `onBlockWhereArgs` / `onBlockOptions` lose `@genType`. The canonical
user-facing TS types live in `packages/envio/index.d.ts`; emitting an
`unknown`-typed stub into `Envio.gen.ts` would shadow the precise
ecosystem-specific declarations.
TS types (packages/envio/index.d.ts):
- Inline the `_gte` / `_lte` / `_every` triple directly into
`EvmOnBlockFilter`, `FuelOnBlockFilter`, and `SvmOnSlotFilter`. The
intermediate `*NumberFilter` / `*HeightFilter` / `SvmOnSlotNumberFilter`
types are dropped.
Docs and tests:
- `indexing-blocks/SKILL.md`: simplified to a single EVM example using
`switch (chain.id)` with a `default: never` exhaustiveness check, so
adding a new chain to config.yaml without updating the handler becomes
a TypeScript compile error. Fuel/SVM kept as one-line notes.
- `svmblock_template`: dropped the `where` clause from both the README
and `BlockHandler.ts`. The default (no `where`) registers on every
configured chain, which is what the starter wants.
- `scenarios/svm_test/test/SlotHandler.test.ts`: dropped the `as unknown
as Record<string, unknown>` cast — `indexer.onSlot` is properly typed
by `SvmHandlerMethods` and a separate runtime check for `onBlock`'s
absence is unnecessary because that access is a TypeScript compile
error on SVM.
Envio.gen.ts: regenerated; `onBlockWhereArgs` / `onBlockOptions` no
longer present.
…stem
- Replace Utils.magic decoders with two-stage S.parseOrThrow: each
ecosystem's onBlockFilterSchema unwraps block.number/height/slot as
option<unknown>; shared blockRangeSchema (S.strict) validates the
inner {_gte?, _lte?, _every?} and surfaces typos with clear errors.
- Rename onBlockHandlerFn -> onBlockFn.
- Drop | void from *OnBlockWhereResult / SvmOnSlotWhereResult; runtime
throws on undefined/null returns instead of silently match-all.
- Add JSDoc to every *OnBlock* / *OnSlot* handler/options/where field.
- Merge *HandlerMethods into *Ecosystem; give the test indexer its
own EvmTestEcosystem / FuelTestEcosystem / SvmTestEcosystem family
so real and test surfaces evolve independently.
https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
|
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:
📝 WalkthroughWalkthroughEmbeds onBlock/onSlot into generated indexer types, removes standalone onBlock export, introduces per-ecosystem onBlockMethodName and filter unwrapping, adds schema-driven where/filter parsing, introduces SVM onSlot runtime/types and tests, and adds an SVM scenario plus CI step to run it. Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer
participant Indexer as Indexer (runtime)
participant Ecosystem as Ecosystem Config
participant Register as HandlerRegister
participant Store as Registry/State
Dev->>Indexer: call indexer.onBlock/onSlot({name, where}, handler)
Indexer->>Ecosystem: read onBlockMethodName & onBlockFilterSchema
Indexer->>Ecosystem: evaluate/unwrap `where` per-chain
Ecosystem-->>Indexer: returns (boolean | filter object | false)
Indexer->>Register: registerOnBlock(~name, ~chainId, ~interval, ~startBlock, ~endBlock, ~handler)
Register->>Store: push registration into onBlockByChainId
Store-->>Register: ack
Register-->>Indexer: registration complete
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 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)
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 |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/src/hbs_templating/codegen_templates.rs (1)
1371-1397:⚠️ Potential issue | 🟠 MajorSVM still gets a generated
onBlockmethod here.Line 1393 hardcodes the field name to
onBlock, but the rest of this PR moves SVM toindexer.onSlot. That leaves the generated ReScript API out of sync with the runtime/docs for SVM projects. This should be emitted from an ecosystem-specific method-name variable, not baked into the template.🛠️ Minimal direction for the fix
+ let on_block_method_name = match cfg.get_ecosystem() { + Ecosystem::Svm => "onSlot", + _ => "onBlock", + }; + let indexer_type = format!( r#"/** Metadata and configuration for the indexer. */ type indexer = {{ ... /** Register a Block Handler. Evaluates `where` once per configured chain at registration time. */ - onBlock: ( + {on_block_method_name}: ( Envio.onBlockOptions<indexerChain>, {on_block_handler_type}, ) => unit, }}"# );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/hbs_templating/codegen_templates.rs` around lines 1371 - 1397, The template emits a hardcoded onBlock field in the generated indexer type (see indexer and the onBlock signature using {on_block_handler_type}), which is incorrect for SVM that uses indexer.onSlot; update the template to use the ecosystem-specific method name variable (e.g., replace the literal "onBlock" with the provided on_block_method_name / on_slot_method_name variable) so the field name is emitted from that variable and the signature remains {on_block_handler_type} (or its ecosystem-specific equivalent) to keep generated ReScript API in sync with runtime/docs.
🧹 Nitpick comments (1)
scenarios/svm_test/package.json (1)
9-12: Makepnpm testself-contained.Line 11 only compiles and runs tests, but this scenario also depends on
generated/being created first. That means a clean local checkout needs an extra manual step that CI currently does out-of-band. Addingcodegentopretestwould make the scenario runnable the same way locally and in CI.♻️ Suggested tweak
"scripts": { + "pretest": "pnpm run codegen", "res:clean": "rescript clean", "res:build": "rescript", "res:watch": "rescript -w", "codegen": "cargo run --manifest-path ../../packages/cli/Cargo.toml -- codegen", "dev": "cargo run --manifest-path ../../packages/cli/Cargo.toml -- dev", "test": "rescript && tsc --noEmit && vitest run", "start": "envio start" },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scenarios/svm_test/package.json` around lines 9 - 12, The test script depends on generated/ but doesn't run codegen; add a pretest script in package.json so that "codegen" runs automatically before "test" (i.e., add a "pretest" entry that invokes the existing "codegen" script) so running "pnpm test" is self-contained and creates generated/ first.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/envio/index.d.ts`:
- Around line 855-908: The onBlock property is incorrectly nested inside the
contracts conditional so it disappears from the type when an ecosystem has
chains but no contracts; move the readonly onBlock: (options:
EvmOnBlockOptions<Config>, handler: EvmOnBlockHandler<Config>) => void
declaration out of the conditional that gates Contracts while leaving readonly
onEvent and readonly contractRegister inside that conditional; update the EVM
(and the analogous Fuel) union branch so onBlock is declared at the same level
as the conditional result (using EvmOnBlockOptions and EvmOnBlockHandler /
FuelOnBlock equivalents) so block-only indexers retain the onBlock type while
event/contract handlers remain gated by Contracts.
In `@packages/envio/src/Main.res`:
- Around line 432-436: The zero-match warning uses a generic message; change it
to reference the actual API method name by using the existing onBlockMethodName
variable instead of the hardcoded "Block handler"; update the branch that checks
matchedAny.contents to call logger->Logging.childWarn with a message like
"{onBlockMethodName} matched 0 chains. Check the `where` predicate." so SVM
registrations and other ecosystems see the correct method name (locate the
matchedAny check and the logger->Logging.childWarn call and replace the literal
string with a formatted message using onBlockMethodName).
- Around line 372-398: The code treats `null`/`undefined` as omitted in the
initial switch but then still reads raw["where"] later and may attempt to call
`null` as a predicate; fix by normalizing raw["where"] into an option once and
using that option for per-chain evaluation instead of raw["where"].
Specifically, introduce a normalized variable (e.g., whereOpt) computed from
raw["where"] that maps undefined/null to None, functions to Some(predicate), and
anything else to an error, then replace the later `switch raw["where"]` with
`switch whereOpt` in the block that calls the predicate (symbols: raw["where"],
whereOpt, predicate, ChainMap.values, chainConfig).
- Around line 76-80: The schema for blockRangeSchema currently allows _every to
be 0 or negative (it uses _every: s.field("_every",
S.option(S.int)->S.Option.getOr(1))), so add a validation step that rejects
non-positive values: after you default with S.Option.getOr(1) or when mapping
the optional int, enforce that the resulting _every > 0 and return a schema
validation error for <= 0; update blockRangeSchema (and the _every field
handling) to perform this positive-int check so invalid inputs fail fast rather
than being accepted.
In `@scenarios/svm_test/test/SlotHandler.test.ts`:
- Around line 18-22: Replace the two separate assertions in the "creates a test
indexer with the SVM chain configured" test with a single whole-object
assertion: call createTestIndexer() (symbol: createTestIndexer), store result in
testIndexer, and assert that testIndexer equals the expected full shape (an
object with chainIds: [0] and chains: [{ id: 0 }]) in one expect call; update
the test in SlotHandler.test.ts accordingly so there is only one expect for the
complete testIndexer value.
In `@scenarios/test_codegen/src/handlers/EventHandlers.ts`:
- Around line 755-778: The onBlock registrations (calls to indexer.onBlock) are
being performed inside an event handler after registration phase, so
HandlerRegister.withRegistration rejects them; move the test registrations (the
calls named "test", "test_filter", "test_default", "test_skip_all") out of the
handler into module initialization or a test fixture that runs during startup
(i.e., module scope or the fixture that sets up the indexer) so they execute
during the registration phase rather than at runtime, ensuring onBlock's
boolean/filter/default/skip-all paths are exercised.
---
Outside diff comments:
In `@packages/cli/src/hbs_templating/codegen_templates.rs`:
- Around line 1371-1397: The template emits a hardcoded onBlock field in the
generated indexer type (see indexer and the onBlock signature using
{on_block_handler_type}), which is incorrect for SVM that uses indexer.onSlot;
update the template to use the ecosystem-specific method name variable (e.g.,
replace the literal "onBlock" with the provided on_block_method_name /
on_slot_method_name variable) so the field name is emitted from that variable
and the signature remains {on_block_handler_type} (or its ecosystem-specific
equivalent) to keep generated ReScript API in sync with runtime/docs.
---
Nitpick comments:
In `@scenarios/svm_test/package.json`:
- Around line 9-12: The test script depends on generated/ but doesn't run
codegen; add a pretest script in package.json so that "codegen" runs
automatically before "test" (i.e., add a "pretest" entry that invokes the
existing "codegen" script) so running "pnpm test" is self-contained and creates
generated/ first.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b6ab1bba-33e3-4e45-9b9c-4a93ef6dbc62
⛔ Files ignored due to path filters (3)
packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generates_correct_types_and_values.snapis excluded by!**/*.snappackages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_multiple_chains.snapis excluded by!**/*.snappnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (28)
.github/workflows/build_and_verify.ymlpackages/cli/src/hbs_templating/codegen_templates.rspackages/cli/templates/dynamic/codegen/index.d.ts.hbspackages/cli/templates/dynamic/codegen/index.js.hbspackages/cli/templates/static/shared/.claude/skills/indexing-blocks/SKILL.mdpackages/cli/templates/static/svmblock_template/typescript/README.mdpackages/cli/templates/static/svmblock_template/typescript/src/handlers/BlockHandler.tspackages/envio/index.d.tspackages/envio/src/Ecosystem.respackages/envio/src/Envio.gen.tspackages/envio/src/Envio.respackages/envio/src/HandlerRegister.respackages/envio/src/HandlerRegister.resipackages/envio/src/Main.respackages/envio/src/sources/Evm.respackages/envio/src/sources/Fuel.respackages/envio/src/sources/Svm.resscenarios/svm_test/.gitignorescenarios/svm_test/README.mdscenarios/svm_test/config.yamlscenarios/svm_test/package.jsonscenarios/svm_test/rescript.jsonscenarios/svm_test/schema.graphqlscenarios/svm_test/src/handlers/SlotHandler.tsscenarios/svm_test/test/SlotHandler.test.tsscenarios/svm_test/tsconfig.jsonscenarios/svm_test/vitest.config.tsscenarios/test_codegen/src/handlers/EventHandlers.ts
💤 Files with no reviewable changes (2)
- packages/cli/templates/dynamic/codegen/index.d.ts.hbs
- packages/envio/src/Envio.gen.ts
Brings in the ReScript 12.2.0 migration (Js.* -> stdlib) and dynamic contract registry refactor. Updated onBlock runtime to match stdlib conventions: Js.Exn.raiseError -> JsError.throwWithMessage, Js.typeof -> typeof (...:>string), Js.Array2.isArray -> Array.isArray, Js.Dict.unsafeGet -> Dict.getUnsafe. Synced scenarios/svm_test/package.json deps to rescript 12.2.0 + rescript-schema 9.5.1 (matches fuel_test / test_codegen on main) and switched its scripts to rescript-legacy. https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
New ReScript test (OnBlockSchema_test.res):
- blockRangeSchema: parse full/partial/empty, _every defaults to 1,
S.strict rejects typos (_gt) and type mismatches.
- Per-ecosystem onBlockFilterSchema (Evm/Fuel/Svm): outer unwrap
returns Some(unknown) for valid input, None when wrapper absent.
- Documents the chained two-stage parse: `{block: {}}` surfaces
Some(undefined) at the outer schema and fails at blockRangeSchema
when Main.extractRange feeds it through.
- Ecosystem.onBlockMethodName: EVM/Fuel -> "onBlock", SVM -> "onSlot".
New TypeScript type tests (EventHandler.test.ts):
- indexer.onBlock value-level smoke check, typed options shape.
- EvmOnBlockWhereResult === boolean | EvmOnBlockFilter (no |void).
- Negative test: implicit-undefined return in a `where` predicate is
a compile error (@ts-expect-error).
- EvmOnBlockFilter accepts partial/empty shapes at the type level;
key strictness lives in the runtime schema (documented split).
https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/envio/index.d.ts (1)
855-908:⚠️ Potential issue | 🟠 Major
onBlockis still incorrectly gated bycontractsin EVM/Fuel ecosystem types.
onBlockremains inside thecontractsconditional in both branches, so on a chains-only config it disappears from types even though runtime still exposes it. This mismatch starts at Line [855] and Line [936].Suggested type-shape fix
type EvmEcosystem<Config extends IndexerConfigTypes> = ... - } & (Config["evm"] extends { + } & { + readonly onBlock: ( + options: EvmOnBlockOptions<Config>, + handler: EvmOnBlockHandler<Config>, + ) => void; + } & (Config["evm"] extends { contracts: infer Contracts extends Record<string, Record<string, any>>; } ? { readonly onEvent: ... readonly contractRegister: ... - readonly onBlock: (...) } : {}) type FuelEcosystem<Config extends IndexerConfigTypes> = ... - } & (Config["fuel"] extends { + } & { + readonly onBlock: ( + options: FuelOnBlockOptions<Config>, + handler: FuelOnBlockHandler<Config>, + ) => void; + } & (Config["fuel"] extends { contracts: infer Contracts extends Record<string, Record<string, any>>; } ? { readonly onEvent: ... readonly contractRegister: ... - readonly onBlock: (...) } : {})Also applies to: 936-984
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/envio/index.d.ts` around lines 855 - 908, The types wrongly nest onBlock inside the contracts-gated branch so onBlock disappears for chains-only configs; move the readonly onBlock: (options: EvmOnBlockOptions<Config>, handler: EvmOnBlockHandler<Config>) => void definition out of the conditional that checks Config["evm"] extends { contracts: ... } so it is declared alongside (not inside) the contracts-dependent onEvent/contractRegister block (reference the onBlock symbol and the types EvmOnBlockOptions and EvmOnBlockHandler to locate where to lift it), ensuring onBlock is always present while leaving onEvent/contractRegister inside the contracts branch.
🧹 Nitpick comments (1)
scenarios/test_codegen/test/OnBlockSchema_test.res (1)
15-124: Consider shifting primary assertions to publicindexer.onBlock/indexer.onSlotbehavior tests.These tests are high-quality, but they are tightly bound to internal schema modules (
Main.blockRangeSchema,*.ecosystem.onBlockFilterSchema). A thin public-API integration test layer would reduce brittleness while preserving intent.As per coding guidelines, "For testing, prefer Public module API".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scenarios/test_codegen/test/OnBlockSchema_test.res` around lines 15 - 124, Tests currently assert behavior directly against internal schemas (Main.blockRangeSchema, Evm.ecosystem.onBlockFilterSchema, Fuel.ecosystem.onBlockFilterSchema, Svm.ecosystem.onBlockFilterSchema), making them brittle; change the test strategy so primary assertions exercise the public API by calling indexer.onBlock and indexer.onSlot (or the public Indexer module methods that invoke those) with representative payloads and asserting observable results, while keeping a small set of focused unit tests for the internal schemas only to validate edge-case parsing; update the current specs to delegate to new public-integration tests and remove/assert less about internal schema wiring so the test intent is preserved but tied to indexer.onBlock/indexer.onSlot behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/envio/index.d.ts`:
- Around line 855-908: The types wrongly nest onBlock inside the contracts-gated
branch so onBlock disappears for chains-only configs; move the readonly onBlock:
(options: EvmOnBlockOptions<Config>, handler: EvmOnBlockHandler<Config>) => void
definition out of the conditional that checks Config["evm"] extends { contracts:
... } so it is declared alongside (not inside) the contracts-dependent
onEvent/contractRegister block (reference the onBlock symbol and the types
EvmOnBlockOptions and EvmOnBlockHandler to locate where to lift it), ensuring
onBlock is always present while leaving onEvent/contractRegister inside the
contracts branch.
---
Nitpick comments:
In `@scenarios/test_codegen/test/OnBlockSchema_test.res`:
- Around line 15-124: Tests currently assert behavior directly against internal
schemas (Main.blockRangeSchema, Evm.ecosystem.onBlockFilterSchema,
Fuel.ecosystem.onBlockFilterSchema, Svm.ecosystem.onBlockFilterSchema), making
them brittle; change the test strategy so primary assertions exercise the public
API by calling indexer.onBlock and indexer.onSlot (or the public Indexer module
methods that invoke those) with representative payloads and asserting observable
results, while keeping a small set of focused unit tests for the internal
schemas only to validate edge-case parsing; update the current specs to delegate
to new public-integration tests and remove/assert less about internal schema
wiring so the test intent is preserved but tied to
indexer.onBlock/indexer.onSlot behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b9f92334-874f-44e3-a031-3e13d356fefb
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
.github/workflows/build_and_verify.ymlpackages/envio/index.d.tspackages/envio/src/Ecosystem.respackages/envio/src/Envio.respackages/envio/src/HandlerRegister.respackages/envio/src/HandlerRegister.resipackages/envio/src/Main.respackages/envio/src/Utils.gen.tspackages/envio/src/sources/Evm.respackages/envio/src/sources/Fuel.respackages/envio/src/sources/Svm.resscenarios/svm_test/package.jsonscenarios/test_codegen/test/EventHandler.test.tsscenarios/test_codegen/test/OnBlockSchema_test.res
✅ Files skipped from review due to trivial changes (2)
- packages/envio/src/Utils.gen.ts
- scenarios/svm_test/package.json
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/envio/src/Ecosystem.res
- packages/envio/src/sources/Fuel.res
- packages/envio/src/sources/Evm.res
- packages/envio/src/Envio.res
- packages/envio/src/Main.res
- packages/envio/src/sources/Svm.res
- TS types: move EVM/Fuel `onBlock` outside the `contracts` conditional so block-only indexers (no contracts configured) still see it on the typed indexer surface, matching what the runtime always exposes. - Main.res: add `S.intMin(1)` to `_every` — rejects 0 and negatives that would crash `(blockNumber - startBlock) % _every` or never match. - Main.res: normalize `where` once into an `option` at entry instead of re-reading `raw["where"]` per chain. Prevents calling JS `null` as a predicate (ReScript treats `null` field values as `Some(null)` when the field is typed `option`). - Main.res: zero-match warning now uses `onBlockMethodName` so SVM users see "indexer.onSlot" instead of a generic "Block handler". - SlotHandler.test.ts: collapse two asserts into one whole-value assert. - OnBlockSchema_test.res: cover _every: 0 / negative rejection + the _every: 1 minimum-accepted boundary. https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/envio/index.d.ts`:
- Around line 697-700: Update the doc comments for FuelOnBlockFilter (and the
related comment block around lines referencing _every/_gte) to state that _every
alignment is relative to _gte if provided, otherwise it aligns to the chain
startBlock (matching the shared range behavior and the EVM docs); specifically
mention the symbols _every, _gte and startBlock in the comment so readers of
FuelOnBlockFilter and the nearby Fuel/SVM doc block understand the exact
alignment semantics when _gte is omitted.
- Around line 1210-1215: The TestIndexerProcessConfig typing for
process(...).chains currently only includes EVM/Fuel chain mappings and must be
extended to support SVM chains; update the generic mapping used in
TestIndexerProcessConfig to add an svm entry that uses the SvmChainIds<Config>
helper and the SVM chain type keys from Config["svm"] (mirror how EVM/Fuel are
typed), so that process(...).chains accepts overrides for SVM chain ids/types
(and allows SVM-specific overrides like start/end ranges); locate the
TestIndexerProcessConfig/type for process(...).chains and add the svm mapping
using SvmChainIds<Config> and keyof Config["svm"] as appropriate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3ccc7eae-48ec-4651-b2b9-c61c9a5ad6a4
📒 Files selected for processing (4)
packages/envio/index.d.tspackages/envio/src/Main.resscenarios/svm_test/test/SlotHandler.test.tsscenarios/test_codegen/test/OnBlockSchema_test.res
✅ Files skipped from review due to trivial changes (1)
- scenarios/svm_test/test/SlotHandler.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- scenarios/test_codegen/test/OnBlockSchema_test.res
- packages/envio/src/Main.res
… fixes
- EventHandlers.ts: move the four `indexer.onBlock` registrations from the
dead `case "onBlockInHandler"` (no test triggered it) up to module scope,
so the boolean / filter / default / skip-all `where` paths run during
indexer init and HandlerRegister.withRegistration is exercised. Pin the
predicates to chain 137 (configured but unused by any simulate test) so
the handlers don't fire on existing test runs and pollute their
`result.changes` deep-equal assertions. Handlers are no-ops; end-to-end
block-firing is already covered by `lib_tests/FetchState_onBlock_test.res`.
- index.d.ts: align Fuel/SVM `_every` doc comments with EVM — the shared
range schema uses `startBlock` as the alignment fallback when `_gte` is
omitted (CodeRabbit minor).
- index.d.ts: add `SvmTestIndexerChainConfig` + `SvmTestChains` and
intersect them into `TestIndexerProcessConfig.chains`. SVM-only
indexers (slot-range overrides for `indexer.onSlot` test runs) now type
correctly in `process({ chains: { ... } })` (CodeRabbit major).
https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scenarios/test_codegen/src/handlers/EventHandlers.ts`:
- Around line 895-903: The test mistakenly still supplies a where predicate for
the "test_onblock_default" registration so it never hits the
no-`where`/undefined branch in onBlockFn; restore a real no-where registration
by adding one indexer.onBlock({ name: "test_onblock_default" }, async () => {})
(or a dedicated fixture/scenario) alongside the existing predicate-based case so
that the onBlockFn pathway that handles undefined `where` is exercised; keep the
predicate-based "test_onblock_skip_all" case as-is to cover the explicit false
branch and ensure both indexer.onBlock and
packages/envio/src/Main.res::onBlockFn behaviors are tested.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5ffabf67-dee7-4971-b29e-c24ff0c7e522
📒 Files selected for processing (2)
packages/envio/index.d.tsscenarios/test_codegen/src/handlers/EventHandlers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/envio/index.d.ts
| // `test_onblock_default` would register on every configured chain — but | ||
| // that fires on every block of every test indexer run, polluting their | ||
| // `result.changes` deep-equal assertions. Pin it to chain 137 too; the | ||
| // "no `where`" code path is exercised by `test_onblock_skip_all` below | ||
| // (which goes through the same `None` branch on chains it doesn't match). | ||
| indexer.onBlock( | ||
| { name: "test_onblock_default", where: ({ chain }) => chain.id === 137 }, | ||
| async () => {}, | ||
| ); |
There was a problem hiding this comment.
This no longer covers the default/no-where branch.
Line 901 still passes a where predicate, so test_onblock_default exercises the boolean path again. test_onblock_skip_all only covers the explicit false case; it never hits the separate no-where/undefined path in packages/envio/src/Main.res::onBlockFn. That leaves one of the new registration modes untested here. Please keep one real indexer.onBlock({ name: ... }, ...) case in a dedicated fixture/scenario if the shared test run cannot tolerate it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scenarios/test_codegen/src/handlers/EventHandlers.ts` around lines 895 - 903,
The test mistakenly still supplies a where predicate for the
"test_onblock_default" registration so it never hits the no-`where`/undefined
branch in onBlockFn; restore a real no-where registration by adding one
indexer.onBlock({ name: "test_onblock_default" }, async () => {}) (or a
dedicated fixture/scenario) alongside the existing predicate-based case so that
the onBlockFn pathway that handles undefined `where` is exercised; keep the
predicate-based "test_onblock_skip_all" case as-is to cover the explicit false
branch and ensure both indexer.onBlock and
packages/envio/src/Main.res::onBlockFn behaviors are tested.
CodeRabbit pointed out that pinning every `test_onblock_*` predicate to
chain 137 in test_codegen leaves the `where: undefined` branch in
`Main.res::onBlockFn` untested (the `None` arm of the per-chain switch).
Added `indexer.onSlot({ name: "SlotPingDefault" }, async () => {})` in
svm_test where it's safe — svm_test has only two simple type/wiring
assertions and no `result.changes` deep-equals, so a default-fires-on-
every-block handler doesn't pollute anything. Updated the comment in
EventHandlers.ts to point at the new coverage location.
https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
Bring back the `case "onBlockInHandler"` branch with a single minimal `indexer.onBlock(...)` call and add a matching vitest test that asserts `assert.rejects` against a simulate run that triggers the case. Mirrors the existing `handlerInHandler` case + test for `indexer.onEvent`. This covers the late-registration guard in `HandlerRegister.withRegistration` for onBlock specifically — previously onEvent was the only path tested. https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
Resolved conflicts in packages/envio/index.d.ts: - Kept my new EvmOnBlockFilter / EvmOnBlockWhereResult types. - Dropped main's standalone EvmHandlerMethods / FuelHandlerMethods types (already consolidated into EvmEcosystem / FuelEcosystem on this branch). - Applied main's OnEventWhere<P> -> EvmOnEventWhere<P, C> / FuelOnEventWhere<P, C> signature bump inside the inline onEvent/contractRegister definitions. - Took origin/main's pnpm-lock.yaml. Updated OnBlockSchema_test.res: Option.getExn -> Option.getOrThrow (stdlib API rename picked up with the merge). https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
Conflicts resolved: - EventConfigBuilder.res (top): main's #1104 dropped `open Belt` and removed the empty-component-type stub. Kept the `eventParamComponent` / `eventParam.components` definitions from this PR (the feature) and dropped `open Belt` to follow main's modern-API migration. Migrated the component helpers (`componentsToSimulateSchema`, `componentsToDefaultValue`, `componentsToRemapper`, `paramsToRemap`) from `Js.String2.*`, `Js.Array2.*`, `Js.Dict.*` to the modern `String.*` / `Array.*` / `Dict.*` APIs to match main's style; flipped `Array.forEachWithIndex` argument order from `(i, c)` to `(c, i)` to match the modern signature. - EventConfigBuilder.res (buildSimulateParamsSchema): kept this PR's component-aware schema/default selection but used main's `Dict.set` (was `Js.Dict.set`). - EventHandlers.ts: both sides added independent registrations near the bottom of the file. Kept both — this PR's `#538` Solidity-struct regression handler and main's #1105 `indexer.onBlock` test registrations. - codegen_templates.rs (test): main's #1106 added a third `&CapitalizedOptions` contract_name argument to `EventTemplate::from_config_event`. Updated the new `event_template_named_struct_rescript_snapshot` test to pass a `SablierLockup` contract name. - snapshot regenerated for `event_template_named_struct_rescript_snapshot` to capture main's #1106 `onEventWhere` API refactor (chain-object callback rather than flat args). https://claude.ai/code/session_01QHeJe9qkDbozn6YP8uxdeg
Summary
This PR introduces block and slot handler support across EVM, Fuel, and SVM ecosystems with a unified, ecosystem-specific filtering API. Block handlers enable processing every block (or every Nth block) for time-series data and periodic snapshots, while SVM gains a dedicated
indexer.onSlotmethod.Key Changes
Unified handler context: Extracted
BaseHandlerContexttype to share logger, effect caller, preload flag, chain state, and entity operations across all handler types and ecosystems.Block/slot handler types: Added comprehensive TypeScript types for:
EvmOnBlockFilter,EvmOnBlockHandler,EvmOnBlockOptionswith block-number range/stride filteringFuelOnBlockFilter,FuelOnBlockHandler,FuelOnBlockOptionswith block-height range/stride filteringSvmOnSlotFilter,SvmOnSlotHandler,SvmOnSlotOptionswith slot range/stride filteringEcosystem-specific method names: EVM and Fuel expose
indexer.onBlock(), while SVM exposesindexer.onSlot()via the newEcosystem.t.onBlockMethodNamefield.Two-stage filter parsing: Implemented shared
blockRangeSchemavalidation inMain.resthat handles the inner{_gte?, _lte?, _every?}fields consistently across ecosystems, with ecosystem-specific outer schemas unwrappingblock.number/block.height/slot.Runtime handler registration: Refactored
HandlerRegister.registerOnBlockto accept parsed parameters directly (name, chainId, interval, startBlock, endBlock) rather than raw options, with thewherepredicate evaluation moved toMain.res::onBlockFn.Predicate-based chain filtering: The
wherepredicate is evaluated once per configured chain at registration time, returningfalseto skip,trueto match all blocks/slots, or a filter object for range/stride constraints.SVM test scenario: Added
scenarios/svm_testwith minimal coverage ofindexer.onSlotAPI and SVM-specific filter decoding.Documentation updates: Updated block handler skill documentation and SVM template README to reflect the new
wherepredicate API and ecosystem-specific method names.Notable Implementation Details
wherepredicate receives achainobject and must explicitly return a value (implicitundefinedis rejected) to catch user errors early._gtinstead of_gte) with readable schema errors pointing at the offending key._everystride defaults to 1 inside the schema, and alignment is relative to_gte(or the chain's configuredstartBlockwhen_gteis omitted).onBlockMethodNameandonBlockFilterSchemato centralize ecosystem-specific behavior, reducing the need for switches inMain.res.indexer.evm.onBlock,indexer.fuel.onBlock,indexer.svm.onSlot), while single-ecosystem indexers flatten them to the root level.https://claude.ai/code/session_019VcJ9Z3XSGnaedzQ6RQh24
Summary by CodeRabbit
New Features
Documentation
Tests & Scenarios
Chores