Skip to content

Add block/slot handler support with ecosystem-specific filtering - #1105

Merged
DZakh merged 14 commits into
mainfrom
claude/add-indexer-onblock-method-svWzl
Apr 15, 2026
Merged

Add block/slot handler support with ecosystem-specific filtering#1105
DZakh merged 14 commits into
mainfrom
claude/add-indexer-onblock-method-svWzl

Conversation

@DZakh

@DZakh DZakh commented Apr 14, 2026

Copy link
Copy Markdown
Member

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.onSlot method.

Key Changes

  • Unified handler context: Extracted BaseHandlerContext type 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:

    • EVM: EvmOnBlockFilter, EvmOnBlockHandler, EvmOnBlockOptions with block-number range/stride filtering
    • Fuel: FuelOnBlockFilter, FuelOnBlockHandler, FuelOnBlockOptions with block-height range/stride filtering
    • SVM: SvmOnSlotFilter, SvmOnSlotHandler, SvmOnSlotOptions with slot range/stride filtering
  • Ecosystem-specific method names: EVM and Fuel expose indexer.onBlock(), while SVM exposes indexer.onSlot() via the new Ecosystem.t.onBlockMethodName field.

  • Two-stage filter parsing: Implemented shared blockRangeSchema validation in Main.res that handles the inner {_gte?, _lte?, _every?} fields consistently across ecosystems, with ecosystem-specific outer schemas unwrapping block.number / block.height / slot.

  • Runtime handler registration: Refactored HandlerRegister.registerOnBlock to accept parsed parameters directly (name, chainId, interval, startBlock, endBlock) rather than raw options, with the where predicate evaluation moved to Main.res::onBlockFn.

  • Predicate-based chain filtering: The where predicate is evaluated once per configured chain at registration time, returning false to skip, true to match all blocks/slots, or a filter object for range/stride constraints.

  • SVM test scenario: Added scenarios/svm_test with minimal coverage of indexer.onSlot API and SVM-specific filter decoding.

  • Documentation updates: Updated block handler skill documentation and SVM template README to reflect the new where predicate API and ecosystem-specific method names.

Notable Implementation Details

  • The where predicate receives a chain object and must explicitly return a value (implicit undefined is rejected) to catch user errors early.
  • Filter validation surfaces typos (e.g., _gt instead of _gte) with readable schema errors pointing at the offending key.
  • The _every stride defaults to 1 inside the schema, and alignment is relative to _gte (or the chain's configured startBlock when _gte is omitted).
  • Ecosystem records now include onBlockMethodName and onBlockFilterSchema to centralize ecosystem-specific behavior, reducing the need for switches in Main.res.
  • Multi-ecosystem indexers expose handlers under ecosystem namespaces (e.g., 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

    • SVM support: indexer.onSlot added; unified where-based onBlock/onSlot registration and runtime warning when a filter matches no chains.
  • Documentation

    • Guides and templates updated to show indexer.onBlock/onSlot and new where-based filter semantics, plus “Other ecosystems” guidance.
  • Tests & Scenarios

    • New SVM scenario, tests, and example slot handler validating the APIs and test indexer wiring.
  • Chores

    • CI updated to run the new SVM scenario tests.

claude added 6 commits April 8, 2026 15:41
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
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Embeds 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

Cohort / File(s) Summary
CI
\.github/workflows/build_and_verify.yml
Added svm_test job step running pnpm exec envio codegen and pnpm test in scenarios/svm_test.
Generated code & templates
packages/cli/src/hbs_templating/codegen_templates.rs, packages/cli/templates/dynamic/codegen/index.d.ts.hbs, packages/cli/templates/dynamic/codegen/index.js.hbs
Moved onBlock into the generated type indexer (removed standalone onBlock binding) and removed onBlock from re-exports.
Public types
packages/envio/index.d.ts, packages/envio/src/Envio.gen.ts
Added explicit EVM/Fuel onBlock and SVM onSlot type families; introduced BaseHandlerContext; removed old generic onBlockOptions export; updated test-indexer typing.
Runtime handler registration
packages/envio/src/HandlerRegister.res, packages/envio/src/HandlerRegister.resi, packages/envio/src/Main.res
Replaced exported onBlock with registerOnBlock; added dynamic indexer.<onBlockMethodName> (onBlock/onSlot) that unwraps ecosystem filter wrapper, validates block-range schema, evaluates where per-chain, and registers via registerOnBlock.
Ecosystem configs
packages/envio/src/Ecosystem.res, packages/envio/src/sources/Evm.res, packages/envio/src/sources/Fuel.res, packages/envio/src/sources/Svm.res
Ecosystem records now include onBlockMethodName and onBlockFilterSchema (EVM/Fuel: onBlock, SVM: onSlot) to support per-ecosystem filter unwrapping.
Docs & examples
packages/cli/templates/static/.../SKILL.md, packages/cli/templates/static/svmblock_template/...
Updated docs and templates to use indexer.onBlock/indexer.onSlot and where-style predicates; converted SVM examples to slot-based handlers.
SVM scenario & tests
scenarios/svm_test/... (config, package.json, rescript.json, tsconfig, vitest config, schema, handlers, tests)
Added SVM scenario with config, build/test tooling, indexer.onSlot handler, GraphQL type, and tests validating onSlot presence and test-indexer chain ids.
Test updates
scenarios/test_codegen/...
Reworked tests to exercise indexer.onBlock predicate/filter/default/no-match variants and added schema/unit tests for block-range/unwrapping logic.
Misc / internal
packages/envio/src/Envio.res, packages/envio/src/Utils.gen.ts
Internal plumbing for where predicate handling and schema parsing; adjusted a type-only import path and removed the generated onBlockOptions type.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • moose-code
  • JonoPrest

"🐰
I hop through slots and blocks anew,
I bind the handlers, stitch each view;
Predicates choose which chains shall ring,
Slots and ranges make the indexer sing;
Thump-thump—tests pass, and I nibble glue!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add block/slot handler support with ecosystem-specific filtering' clearly and directly summarizes the main change: adding block/slot handlers with filtering capabilities across different ecosystems (EVM, Fuel, SVM).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/add-indexer-onblock-method-svWzl

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

SVM still gets a generated onBlock method here.

Line 1393 hardcodes the field name to onBlock, but the rest of this PR moves SVM to indexer.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: Make pnpm test self-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. Adding codegen to pretest would 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2418466 and f947e15.

⛔ 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.snap is excluded by !**/*.snap
  • packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_multiple_chains.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .github/workflows/build_and_verify.yml
  • packages/cli/src/hbs_templating/codegen_templates.rs
  • packages/cli/templates/dynamic/codegen/index.d.ts.hbs
  • packages/cli/templates/dynamic/codegen/index.js.hbs
  • packages/cli/templates/static/shared/.claude/skills/indexing-blocks/SKILL.md
  • packages/cli/templates/static/svmblock_template/typescript/README.md
  • packages/cli/templates/static/svmblock_template/typescript/src/handlers/BlockHandler.ts
  • packages/envio/index.d.ts
  • packages/envio/src/Ecosystem.res
  • packages/envio/src/Envio.gen.ts
  • packages/envio/src/Envio.res
  • packages/envio/src/HandlerRegister.res
  • packages/envio/src/HandlerRegister.resi
  • packages/envio/src/Main.res
  • packages/envio/src/sources/Evm.res
  • packages/envio/src/sources/Fuel.res
  • packages/envio/src/sources/Svm.res
  • scenarios/svm_test/.gitignore
  • scenarios/svm_test/README.md
  • scenarios/svm_test/config.yaml
  • scenarios/svm_test/package.json
  • scenarios/svm_test/rescript.json
  • scenarios/svm_test/schema.graphql
  • scenarios/svm_test/src/handlers/SlotHandler.ts
  • scenarios/svm_test/test/SlotHandler.test.ts
  • scenarios/svm_test/tsconfig.json
  • scenarios/svm_test/vitest.config.ts
  • scenarios/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

Comment thread packages/envio/index.d.ts
Comment thread packages/envio/src/Main.res
Comment thread packages/envio/src/Main.res Outdated
Comment thread packages/envio/src/Main.res
Comment thread scenarios/svm_test/test/SlotHandler.test.ts
Comment thread scenarios/test_codegen/src/handlers/EventHandlers.ts Outdated
claude added 2 commits April 14, 2026 14:45
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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/envio/index.d.ts (1)

855-908: ⚠️ Potential issue | 🟠 Major

onBlock is still incorrectly gated by contracts in EVM/Fuel ecosystem types.

onBlock remains inside the contracts conditional 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 public indexer.onBlock / indexer.onSlot behavior 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

📥 Commits

Reviewing files that changed from the base of the PR and between f947e15 and fe0f858.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • .github/workflows/build_and_verify.yml
  • packages/envio/index.d.ts
  • packages/envio/src/Ecosystem.res
  • packages/envio/src/Envio.res
  • packages/envio/src/HandlerRegister.res
  • packages/envio/src/HandlerRegister.resi
  • packages/envio/src/Main.res
  • packages/envio/src/Utils.gen.ts
  • packages/envio/src/sources/Evm.res
  • packages/envio/src/sources/Fuel.res
  • packages/envio/src/sources/Svm.res
  • scenarios/svm_test/package.json
  • scenarios/test_codegen/test/EventHandler.test.ts
  • scenarios/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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between fe0f858 and 3b1ce7d.

📒 Files selected for processing (4)
  • packages/envio/index.d.ts
  • packages/envio/src/Main.res
  • scenarios/svm_test/test/SlotHandler.test.ts
  • scenarios/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

Comment thread packages/envio/index.d.ts
Comment thread packages/envio/index.d.ts
… 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3b1ce7d and 6767d81.

📒 Files selected for processing (2)
  • packages/envio/index.d.ts
  • scenarios/test_codegen/src/handlers/EventHandlers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/envio/index.d.ts

Comment on lines +895 to +903
// `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 () => {},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

claude and others added 4 commits April 14, 2026 15:21
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
@DZakh
DZakh enabled auto-merge (squash) April 15, 2026 08:49
@DZakh
DZakh merged commit 071cc0e into main Apr 15, 2026
10 checks passed
@DZakh
DZakh deleted the claude/add-indexer-onblock-method-svWzl branch April 15, 2026 08:51
DZakh pushed a commit that referenced this pull request Apr 15, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants