Skip to content

Conversation

@Segfaultd
Copy link
Contributor

@Segfaultd Segfaultd commented Oct 7, 2025

Add per-chain state tracking to handler context

Introduction

Add per-chain state tracking to handler contexts, allowing event handlers to distinguish between chains that are still syncing historical data versus chains that have caught up to head.

Problem / Issue

Event handlers need visibility into which chains are actively syncing versus which chains have caught up and are processing live events. This is critical for:

  • Conditional logic based on chain indexing phase (e.g., skip expensive operations during sync)
  • Metrics and monitoring (separate historical vs live processing stats per chain)
  • Debugging and logging (identify which chains are syncing vs live)
  • Multichain scenarios where different chains progress at different rates

Previously, handlers had no way to determine the indexing state of individual chains.

Solution

Introduced a chains field in handler and loader contexts that provides per-chain state tracking:

  • Key: Chain ID as string (e.g., "1" for Ethereum mainnet)
  • Value: chainInfo object with isReady: boolean
    • isReady: false - Chain is syncing historical data
    • isReady: true - Chain has reached its end block and is processing live events

This allows handlers to check individual chain states and make granular decisions in multichain scenarios.

Key changes:

  1. Core Type Definitions (Internal.res)

    • Added: type chainInfo = {isReady: bool} and type chains = dict<chainInfo>
    • Extended handlerContext to include chains: chains field
  2. Event Processing Logic (EventProcessing.res)

    • Added: computeChainsState function that returns dict of per-chain states
    • Automatically computes chains state by iterating over all chain fetchers
    • Checks hasProcessedToEndblock for each chain independently
    • Threads ~chains parameter through all processing functions
  3. User Context (UserContext.res)

    • Added chains to contextParams for proper context initialization
    • Exposed chains field via proxy handlers for user access in context.chains
  4. Code Generation Templates

    • Types.res.hbs: Updated all context types (handler, loader) to include chains: Internal.chains
    • TestHelpers_MockDb.res.hbs: Creates mock chains dict with processing chain marked as ready
    • GlobalState.res: Computes chains state and checks if all chains ready for exit logic
  5. TypeScript Integration

    • Auto-generated TypeScript types export chainInfo as { readonly isReady: boolean }

Handler usage examples:

// Example 1: Check if current chain is ready (live indexing)
Handlers.MyContract.MyEvent.handler(async ({event, context}) => {
  let currentChainId = event.chainId->Int.toString

  switch context.chains->Js.Dict.get(currentChainId) {
  | Some({isReady: true}) =>
      // Chain is live - send notifications
      context.log.info("Processing live event")
  | Some({isReady: false}) =>
      // Chain is syncing - skip expensive operations
      context.log.debug("Skipping notifications during sync")
  | None =>
      context.log.error("Chain not found in chains state")
  }
})

// Example 2: Check if all chains are ready
Handlers.MyContract.MyEvent.handler(async ({context}) => {
  let allChainsReady = context.chains
    ->Js.Dict.values
    ->Belt.Array.every(chainInfo => chainInfo.isReady)

  if allChainsReady {
    context.log.info("All chains caught up - enabling advanced features")
  }
})

// Example 3: Log status of all chains
Handlers.MyContract.MyEvent.handler(async ({context}) => {
  context.chains
  ->Js.Dict.entries
  ->Belt.Array.forEach(((chainId, chainInfo)) => {
    let status = chainInfo.isReady ? "live" : "syncing"
    context.log.debug(`Chain ${chainId}: ${status}`)
  })
})

TypeScript example:

  Handlers.MyContract.MyEvent.handler(async ({event, context}) => {
    const currentChainId = event.chainId.toString();
    const chainInfo = context.chains[currentChainId];

    if (chainInfo?.isReady) {
      // Chain is live - send notifications
      console.log("Processing live event");
    } else {
      // Chain is syncing - skip expensive operations
      console.log("Skipping notifications during sync");
    }
  });

Test Coverage

Unit Tests:

  • EventOrigin_test.res: Tests chainInfo type, chains dict structure, and context accessibility
  • EventOriginDetection_test.res: 10 comprehensive tests for computeChainsState logic
    • Single chain scenarios (reached end, not reached, no end block)
    • Multichain scenarios (all ready, some ready, mixed states)
    • Edge cases (empty chain fetchers, progress beyond end block)

Integration Tests:

  • EventHandlers.res: Handler that accesses context.chains and logs per-chain states
  • Tests verify correct chains state passed to handlers during batch processing
  • Mock tests updated to create proper chains dict structures

Benefits

  1. Per-chain granularity: Handlers can check individual chain states in multichain scenarios
  2. Async chain support: Different chains can be at different stages simultaneously
  3. Type-safe: Dict structure with proper chainInfo records
  4. Extensible: Easy to add more fields to chainInfo in future (e.g., lastProcessedBlock)
  5. Consistent: Chain IDs as strings match JavaScript/TypeScript dict semantics

References

Summary by CodeRabbit

  • New Features

    • Exposes per-chain readiness via a new chains map on handler and loader contexts; contexts and user-facing traps now include chains.
    • Event processing now computes and threads chain readiness into preload, batch, and handler flows.
  • Tests

    • Added unit and integration tests covering chain readiness computation, propagation, edge cases, and runtime accessibility in handlers.
  • Chores

    • Mock and test harnesses updated to supply chains state for testing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Adds a new per-chain readiness type and threads a chains map through contexts and event processing. Computes chains from chainFetchers, augments handler/loader contexts and traps, updates processing functions to accept/pass chains, adapts templates and mocks, and adds tests for chains computation and accessibility.

Changes

Cohort / File(s) Summary
Internal types
codegenerator/cli/npm/envio/src/Internal.gen.ts, codegenerator/cli/npm/envio/src/Internal.res
Add exported type chainInfo = { isReady: bool }, introduce chains as a dict of chainInfo, and extend handlerContext to include chains. Export corresponding TS type.
Dynamic templates: Types
codegenerator/cli/templates/dynamic/codegen/src/Types.res.hbs, codegenerator/cli/templates/dynamic/codegen/src/Types.ts.hbs
Add chains: Internal.chains to handlerContext and loaderContext (ReScript and TypeScript templates).
Dynamic templates: Mock DB
codegenerator/cli/templates/dynamic/codegen/src/TestHelpers_MockDb.res.hbs
Initialize mock chains state and pass ~chains into preloadBatchOrThrow and runBatchHandlersOrThrow.
Static templates: Event processing pipeline
codegenerator/cli/templates/static/codegen/src/EventProcessing.res
Add computeChainsState(chainFetchers); add ~chains argument to runEventHandlerOrThrow, runHandlerOrThrow, preloadBatchOrThrow, runBatchHandlersOrThrow; add ~chainFetchers to processEventBatch; thread chains through processing.
Static templates: User context
codegenerator/cli/templates/static/codegen/src/UserContext.res
Extend contextParams and handler context with chains; expose chains via handlerTraps and include in entity trap payloads.
Static templates: Global state
codegenerator/cli/templates/static/codegen/src/globalState/GlobalState.res
Pass state.chainManager.chainFetchers into EventProcessing.processEventBatch.
Scenario handlers (ReScript)
scenarios/test_codegen/src/EventHandlers.res
Record and log context.chains in EmptyEvent handler for tests; store last observed chains in lastEmptyEventChains.
Scenario handlers (TypeScript)
scenarios/test_codegen/src/EventHandlers.ts
Add compile-time assertion verifying context.chains shape in a handler.
Tests: chain accessibility & origin
scenarios/test_codegen/test/EventOrigin_test.res
Add tests validating chainInfo.isReady, chains dictionary behavior, and runtime accessibility of chains from handler contexts.
Tests: mocks & batch runs
scenarios/test_codegen/test/Mock_test.res
Create mock chains and pass them to EventProcessing.runBatchHandlersOrThrow.
Tests: schema handler contexts
scenarios/test_codegen/test/schema_types/BigDecimal_test.res, scenarios/test_codegen/test/schema_types/Timestamp_test.res
Construct chains dict and pass into UserContext.getHandlerContext.
Tests: computeChainsState & propagation
scenarios/test_codegen/test/ChainsStateComputation_test.res
Comprehensive unit tests for computeChainsState (various chain endBlock/progress scenarios) and async tests verifying chains propagation through processEventBatch; helper allChainsReady added.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant GS as GlobalState
  participant EP as EventProcessing
  participant CM as ChainMap/Fetchers
  participant UC as UserContext
  participant H as Handler

  GS->>EP: processEventBatch(~items, ~chainFetchers, ...)
  EP->>CM: computeChainsState(chainFetchers)
  CM-->>EP: chains (map chainId -> {isReady})
  note right of EP: New — per-chain readiness derived

  EP->>EP: preloadBatchOrThrow(..., ~chains)
  EP->>EP: runBatchHandlersOrThrow(..., ~chains)
  EP->>EP: runEventHandlerOrThrow(item, ..., ~chains)

  EP->>UC: build handler context(..., chains)
  UC-->>H: handler receives context with chains
  H->>H: read context.chains[chainId].isReady
  H-->>EP: handler result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • JonoPrest
  • JasoonS

Poem

I mapped the chains, one hop, two three,
A carrot compass for readiness at sea.
Is it live or still en route?
I twitch my nose, then execute.
With tiny paws I pass the state — batch to handler, we propagate. 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title concisely and accurately describes the primary change, which is adding a chains mapping to event contexts for per-chain readiness tracking, making it clear to reviewers what the PR implements.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Segfaultd Segfaultd marked this pull request as ready for review October 7, 2025 20:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df605a2 and 6888b4a.

📒 Files selected for processing (1)
  • scenarios/test_codegen/test/EventOriginDetection_test.res (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Never use [| item |] to create an array. Use [ item ] instead.
Must always use = for setting value to a field. Use := only for ref values created using ref function.
ReScript has record types which require a type definition before hand. You can access record fields by dot like foo.myField.
It's also possible to define an inline object, it'll have quoted fields in this case.
Use records when working with structured data, and objects to conveniently pass payload data between functions.
Never use %raw to access object fields if you know the type.

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Prefer reading ReScript .res modules directly; compiled .js artifacts can be ignored

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
scenarios/test_codegen/test/EventOriginDetection_test.res (1)

180-357: Consider extracting a test helper to reduce duplication.

The three async tests share substantial setup code (creating config, stores, managers, event logs, and items). Extracting a helper function would improve maintainability and make the test intent clearer.

Example helper:

let makeTestEventBatch = (~chainId, ~progressBlockNumber, ~isProgressAtHead) => {
  let config = RegisterHandlers.registerAllHandlers()
  let inMemoryStore = InMemoryStore.make()
  let loadManager = LoadManager.make()
  
  let emptyEventLog: Types.eventLog<Types.Gravatar.EmptyEvent.eventArgs> = {
    params: (),
    chainId: chainId,
    srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow,
    logIndex: 1,
    transaction: MockEvents.tx1,
    block: MockEvents.block1,
  }
  
  let item = Internal.Event({
    timestamp: emptyEventLog.block.timestamp,
    chain: ChainMap.Chain.makeUnsafe(~chainId),
    blockNumber: emptyEventLog.block.number,
    logIndex: emptyEventLog.logIndex,
    eventConfig: (Types.Gravatar.EmptyEvent.register() :> Internal.eventConfig),
    event: emptyEventLog->Internal.fromGenericEvent,
  })
  
  let progressedChains = [{
    chainId: chainId,
    batchSize: 1,
    progressBlockNumber: progressBlockNumber,
    isProgressAtHead: isProgressAtHead,
    totalEventsProcessed: 1,
  }]
  
  (config, inMemoryStore, loadManager, item, progressedChains)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6888b4a and ea08064.

📒 Files selected for processing (2)
  • scenarios/test_codegen/src/EventHandlers.res (1 hunks)
  • scenarios/test_codegen/test/EventOriginDetection_test.res (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • scenarios/test_codegen/src/EventHandlers.res
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Never use [| item |] to create an array. Use [ item ] instead.
Must always use = for setting value to a field. Use := only for ref values created using ref function.
ReScript has record types which require a type definition before hand. You can access record fields by dot like foo.myField.
It's also possible to define an inline object, it'll have quoted fields in this case.
Use records when working with structured data, and objects to conveniently pass payload data between functions.
Never use %raw to access object fields if you know the type.

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Prefer reading ReScript .res modules directly; compiled .js artifacts can be ignored

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
🔇 Additional comments (2)
scenarios/test_codegen/test/EventOriginDetection_test.res (2)

4-174: LGTM! Comprehensive coverage of allChainsEventsProcessedToEndblock.

The test suite thoroughly validates the function across single-chain, multi-chain, edge cases (empty map, exceeded end block), and various progress states. The tests are clear and well-structured.


180-293: LGTM! These tests now exercise the real processEventBatch code path.

Unlike the previous version that duplicated the conditional logic, these tests actually invoke EventProcessing.processEventBatch and verify that it correctly passes eventOrigin to handlers based on chainFetchers state. This gives us confidence that the wiring is correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea08064 and 9945070.

📒 Files selected for processing (1)
  • scenarios/test_codegen/test/EventOriginDetection_test.res (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Never use [| item |] to create an array. Use [ item ] instead.
Must always use = for setting value to a field. Use := only for ref values created using ref function.
ReScript has record types which require a type definition before hand. You can access record fields by dot like foo.myField.
It's also possible to define an inline object, it'll have quoted fields in this case.
Use records when working with structured data, and objects to conveniently pass payload data between functions.
Never use %raw to access object fields if you know the type.

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Prefer reading ReScript .res modules directly; compiled .js artifacts can be ignored

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
🧠 Learnings (1)
📚 Learning: 2025-09-04T14:42:53.500Z
Learnt from: CR
PR: enviodev/hyperindex#0
File: .cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-09-04T14:42:53.500Z
Learning: Applies to src/**/*.ts : For multichain support, prefix all entity IDs with event.chainId (e.g., `${event.chainId}-${originalId}`), never hardcode chainId=1, pass chainId into helpers, and use chain-specific Bundle IDs like `${chainId}-1`

Applied to files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
scenarios/test_codegen/test/EventOriginDetection_test.res (1)

180-357: Previous review concerns have been addressed.

All chain ID mismatches flagged in prior reviews have been corrected:

  • Lines 180-236: chainId=54321 used consistently across chainFetchers, event, item.chain, and progressedChains
  • Lines 238-293: chainId=54321 used consistently
  • Lines 295-357: chainId=1 used consistently in multi-chain scenario

Additionally, tests now call the real EventProcessing.processEventBatch and assert on the actual eventOrigin captured via EventHandlers.lastEmptyEventOrigin, rather than duplicating the conditional logic.

Optional: Consider adding multi-chain Live test for completeness.

Current coverage includes single-chain Historical, single-chain Live, and multi-chain Historical scenarios. Adding a test where all chains in a multi-chain setup have reached their end blocks (expecting Live) would provide more comprehensive coverage.

Example test structure:

Async.it("should pass Live in multi-chain when all chains reached end", async () => {
  EventHandlers.lastEmptyEventOrigin := None
  
  // Setup: both chains have reached their end blocks
  let chainFetcher1 = {
    "committedProgressBlockNumber": 1000,
    "fetchState": {"endBlock": Some(1000)},
  }->Utils.magic
  let chainFetcher2 = {
    "committedProgressBlockNumber": 2000,
    "fetchState": {"endBlock": Some(2000)},
  }->Utils.magic
  let chainFetchers = ChainMap.fromArrayUnsafe([
    (ChainMap.Chain.makeUnsafe(~chainId=1), chainFetcher1),
    (ChainMap.Chain.makeUnsafe(~chainId=2), chainFetcher2),
  ])
  
  // ... rest of test setup and assertions expecting Live
})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9945070 and 63fa750.

📒 Files selected for processing (1)
  • scenarios/test_codegen/test/EventOriginDetection_test.res (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Never use [| item |] to create an array. Use [ item ] instead.
Must always use = for setting value to a field. Use := only for ref values created using ref function.
ReScript has record types which require a type definition before hand. You can access record fields by dot like foo.myField.
It's also possible to define an inline object, it'll have quoted fields in this case.
Use records when working with structured data, and objects to conveniently pass payload data between functions.
Never use %raw to access object fields if you know the type.

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Prefer reading ReScript .res modules directly; compiled .js artifacts can be ignored

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
🧠 Learnings (1)
📚 Learning: 2025-09-04T14:42:53.500Z
Learnt from: CR
PR: enviodev/hyperindex#0
File: .cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-09-04T14:42:53.500Z
Learning: Applies to src/**/*.ts : For multichain support, prefix all entity IDs with event.chainId (e.g., `${event.chainId}-${originalId}`), never hardcode chainId=1, pass chainId into helpers, and use chain-specific Bundle IDs like `${chainId}-1`

Applied to files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
🔇 Additional comments (1)
scenarios/test_codegen/test/EventOriginDetection_test.res (1)

3-174: Comprehensive test coverage for allChainsEventsProcessedToEndblock.

The test suite thoroughly validates edge cases including:

  • All chains reached end block
  • Partial completion in multi-chain scenarios
  • Live mode (no end block set)
  • Progress below/at/above end block
  • Empty chainFetchers map

Test logic is correct and assertions align with expected behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
scenarios/test_codegen/test/EventOriginDetection_test.res (2)

176-357: Consider adding test coverage for additional scenarios.

While the current tests cover the core cases, consider adding tests for:

  1. Live mode with no endBlock: Verify handlers receive Live when a chain has fetchState.endBlock = None (this case is tested in the helper function tests at lines 57-70 but not in the processEventBatch integration tests).

  2. Progress exceeds endBlock: Verify handlers receive Live when committedProgressBlockNumber exceeds endBlock (covered in helper tests at lines 86-99 but not in processEventBatch tests).

  3. Multi-chain all complete → Live: Add the inverse of the test at lines 295-357 where ALL chains have reached their end block in a multi-chain setup, verifying handlers receive Live.

These additions would ensure the integration tests have the same coverage as the unit tests for the helper function.


180-357: Optional: Extract shared test setup into a helper function.

The three processEventBatch tests duplicate setup code for config, inMemoryStore, loadManager, and event/item construction. Consider extracting a helper function like:

let makeTestContext = () => {
  let config = RegisterHandlers.registerAllHandlers()
  let inMemoryStore = InMemoryStore.make()
  let loadManager = LoadManager.make()
  (config, inMemoryStore, loadManager)
}

let makeEmptyEvent = (~chainId) => {
  let emptyEventLog: Types.eventLog<Types.Gravatar.EmptyEvent.eventArgs> = {
    params: (),
    chainId,
    srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow,
    logIndex: 1,
    transaction: MockEvents.tx1,
    block: MockEvents.block1,
  }
  Internal.Event({
    timestamp: emptyEventLog.block.timestamp,
    chain: ChainMap.Chain.makeUnsafe(~chainId),
    blockNumber: emptyEventLog.block.number,
    logIndex: emptyEventLog.logIndex,
    eventConfig: (Types.Gravatar.EmptyEvent.register() :> Internal.eventConfig),
    event: emptyEventLog->Internal.fromGenericEvent,
  })
}

This would reduce duplication and make the tests more maintainable, though the current approach is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9945070 and 63fa750.

📒 Files selected for processing (1)
  • scenarios/test_codegen/test/EventOriginDetection_test.res (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Never use [| item |] to create an array. Use [ item ] instead.
Must always use = for setting value to a field. Use := only for ref values created using ref function.
ReScript has record types which require a type definition before hand. You can access record fields by dot like foo.myField.
It's also possible to define an inline object, it'll have quoted fields in this case.
Use records when working with structured data, and objects to conveniently pass payload data between functions.
Never use %raw to access object fields if you know the type.

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Prefer reading ReScript .res modules directly; compiled .js artifacts can be ignored

Files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
🔇 Additional comments (2)
scenarios/test_codegen/test/EventOriginDetection_test.res (2)

4-174: Comprehensive coverage of the helper function.

The test suite thoroughly exercises allChainsEventsProcessedToEndblock across all relevant scenarios: single-chain (complete, incomplete, no endBlock, progress below/at/exceeds endBlock), multi-chain (partial/full completion), and edge cases (empty map). The logic and assertions are correct.


180-357: Past chain ID mismatches have been fixed.

All the chain ID mismatches flagged in previous reviews have been resolved:

  • Lines 180-236: item.chain now uses chainId=54321, matching chainFetchers
  • Lines 238-293: item.chain now uses chainId=54321, matching chainFetchers
  • Lines 295-357: event.chainId, item.chain, and progressedChains[0].chainId all use chainId=1, matching chainFetchers

The tests now correctly exercise the real processEventBatch code path and verify handlers receive the expected eventOrigin.

@DZakh
Copy link
Member

DZakh commented Oct 8, 2025

I think it should be done similarly to _meta but exposed in indexer handlers:

console.log(context.chains[event.chainId])
// {
//    id: number,
//    isReady: boolean,
//    readyAt: Date | undefined,
//    progressBlock: number,
//    processingBlock: number,
//    sourceBlock: number,
//    + Add built-in rpc client effects
//    readContract: fn,
//    getBallance: fn,
//    ...
// }

@JonoPrest What do you think?

@DZakh
Copy link
Member

DZakh commented Oct 8, 2025

Even though the solution with eventOrigin it conflicts with the future changes we planned.

@JonoPrest
Copy link
Collaborator

Well done @Segfaultd on navigating the ReSscript code 👏🏼

Generally with API level features like this we first discuss the API before implementing, because people have different ideas.

I tend to agree with Dmitry that it would be nice to have a unified API to expose more metadata than just whether it is syncing at the head for a given chain.

If we just want the single field so long though, I would suggest using a boolean rather than enum/string union. Since there's only 2 options and its nicer to just say if (context.isLive)... than string checks.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
codegenerator/cli/npm/envio/src/Internal.res (1)

57-62: Consider adding inline documentation for isReady.

The types are correctly defined with appropriate @genType annotation. However, consider adding a doc comment explaining when isReady is true (live indexing after sync completes) vs false (historical sync in progress).

Example:

 @genType
 type chainInfo = {
+  // true when the chain has completed initial sync and is processing live events
+  // false during historical synchronization
   isReady: bool,
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63fa750 and c4b414b.

⛔ Files ignored due to path filters (1)
  • scenarios/test_codegen/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • codegenerator/cli/npm/envio/src/Internal.gen.ts (1 hunks)
  • codegenerator/cli/npm/envio/src/Internal.res (1 hunks)
  • codegenerator/cli/templates/dynamic/codegen/src/TestHelpers_MockDb.res.hbs (1 hunks)
  • codegenerator/cli/templates/dynamic/codegen/src/Types.res.hbs (3 hunks)
  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res (14 hunks)
  • codegenerator/cli/templates/static/codegen/src/UserContext.res (3 hunks)
  • codegenerator/cli/templates/static/codegen/src/globalState/GlobalState.res (2 hunks)
  • scenarios/test_codegen/src/EventHandlers.res (1 hunks)
  • scenarios/test_codegen/test/EventOriginDetection_test.res (1 hunks)
  • scenarios/test_codegen/test/EventOrigin_test.res (1 hunks)
  • scenarios/test_codegen/test/Mock_test.res (1 hunks)
  • scenarios/test_codegen/test/schema_types/BigDecimal_test.res (1 hunks)
  • scenarios/test_codegen/test/schema_types/Timestamp_test.res (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • codegenerator/cli/templates/static/codegen/src/UserContext.res
🧰 Additional context used
📓 Path-based instructions (3)
codegenerator/cli/templates/{static,dynamic}/codegen/src/**/*

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Edit template versions under codegenerator/cli/templates/static/codegen/src or codegenerator/cli/templates/dynamic/codegen/src instead of editing generated/src

Files:

  • codegenerator/cli/templates/dynamic/codegen/src/TestHelpers_MockDb.res.hbs
  • codegenerator/cli/templates/dynamic/codegen/src/Types.res.hbs
  • codegenerator/cli/templates/static/codegen/src/globalState/GlobalState.res
  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Never use [| item |] to create an array. Use [ item ] instead.
Must always use = for setting value to a field. Use := only for ref values created using ref function.
ReScript has record types which require a type definition before hand. You can access record fields by dot like foo.myField.
It's also possible to define an inline object, it'll have quoted fields in this case.
Use records when working with structured data, and objects to conveniently pass payload data between functions.
Never use %raw to access object fields if you know the type.

Files:

  • codegenerator/cli/templates/static/codegen/src/globalState/GlobalState.res
  • scenarios/test_codegen/test/EventOriginDetection_test.res
  • codegenerator/cli/npm/envio/src/Internal.res
  • scenarios/test_codegen/src/EventHandlers.res
  • scenarios/test_codegen/test/schema_types/Timestamp_test.res
  • scenarios/test_codegen/test/schema_types/BigDecimal_test.res
  • scenarios/test_codegen/test/EventOrigin_test.res
  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res
  • scenarios/test_codegen/test/Mock_test.res
**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Prefer reading ReScript .res modules directly; compiled .js artifacts can be ignored

Files:

  • codegenerator/cli/templates/static/codegen/src/globalState/GlobalState.res
  • scenarios/test_codegen/test/EventOriginDetection_test.res
  • codegenerator/cli/npm/envio/src/Internal.res
  • scenarios/test_codegen/src/EventHandlers.res
  • scenarios/test_codegen/test/schema_types/Timestamp_test.res
  • scenarios/test_codegen/test/schema_types/BigDecimal_test.res
  • scenarios/test_codegen/test/EventOrigin_test.res
  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res
  • scenarios/test_codegen/test/Mock_test.res
🧠 Learnings (1)
📚 Learning: 2025-09-04T14:42:53.500Z
Learnt from: CR
PR: enviodev/hyperindex#0
File: .cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-09-04T14:42:53.500Z
Learning: Applies to src/**/*.ts : For multichain support, prefix all entity IDs with event.chainId (e.g., `${event.chainId}-${originalId}`), never hardcode chainId=1, pass chainId into helpers, and use chain-specific Bundle IDs like `${chainId}-1`

Applied to files:

  • scenarios/test_codegen/test/EventOriginDetection_test.res
🔇 Additional comments (8)
scenarios/test_codegen/test/schema_types/BigDecimal_test.res (1)

42-43: LGTM!

The chains initialization and propagation through UserContext.getHandlerContext is correct. Setting isReady: false appropriately simulates historical indexing mode for this test scenario.

Also applies to: 52-52

codegenerator/cli/npm/envio/src/Internal.gen.ts (1)

45-45: LGTM!

The generated TypeScript type correctly mirrors the ReScript chainInfo definition with appropriate readonly modifier for immutability.

scenarios/test_codegen/test/schema_types/Timestamp_test.res (1)

34-35: LGTM!

Chains initialization follows the same correct pattern as other test files, appropriately simulating historical indexing mode.

Also applies to: 44-44

scenarios/test_codegen/test/Mock_test.res (1)

13-15: LGTM!

Chains state correctly simulates historical indexing with clear documentation. The propagation to runBatchHandlersOrThrow is correct.

Also applies to: 24-24

codegenerator/cli/templates/dynamic/codegen/src/TestHelpers_MockDb.res.hbs (1)

408-410: LGTM!

Chains state correctly simulates live mode (isReady: true) for processEvents. The chain ID is properly converted to string for the dictionary key, and chains are consistently passed through both preload and handler phases.

Also applies to: 417-417, 425-425

scenarios/test_codegen/test/EventOrigin_test.res (1)

1-53: Excellent test coverage!

This test suite comprehensively validates the chains functionality:

  • Unit tests for chainInfo type with both boolean states
  • Multi-chain dictionary behavior
  • Integration test confirming handler context accessibility

The tests properly cover type validation, dictionary operations, and end-to-end integration.

scenarios/test_codegen/src/EventHandlers.res (1)

144-160: LGTM!

The handler correctly accesses and logs the chains state. The mutable reference enables test verification, and the logging provides clear visibility into chain readiness during processing.

codegenerator/cli/npm/envio/src/Internal.res (1)

65-68: LGTM!

The handlerContext extension correctly includes the chains field while preserving the private modifier to prevent unsafe manual construction. The structure properly supports per-chain readiness tracking.

@Segfaultd Segfaultd changed the title Introduce EventOrigin in block & event handler Add chains mapping in event context Oct 12, 2025
Copy link
Member

@DZakh DZakh left a comment

Choose a reason for hiding this comment

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

Nice, this API looks good to me. I left a few comments, keen to merge after they are fixed 🫡

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
codegenerator/cli/templates/dynamic/codegen/src/Types.ts.hbs (1)

82-92: Template duplication is necessary but creates maintenance burden.

The chains property definition is duplicated here (identical to lines 27-37) due to the template's conditional structure. While this duplication is unavoidable given the {{#if preload_handlers}} and {{#unless preload_handlers}} blocks, be aware that any future changes to the chains type must be synchronized across both locations.

Consider adding a code comment above both blocks referencing each other to remind maintainers to keep them in sync:

+  // Note: This chains property is duplicated at line 27 (or line 82) - keep both in sync
   /**
    * Per-chain state information accessible in event handlers and block handlers.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85819df and ee82275.

📒 Files selected for processing (4)
  • codegenerator/cli/templates/dynamic/codegen/src/Types.ts.hbs (2 hunks)
  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res (14 hunks)
  • codegenerator/cli/templates/static/codegen/src/globalState/GlobalState.res (1 hunks)
  • scenarios/test_codegen/src/EventHandlers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res
  • codegenerator/cli/templates/static/codegen/src/globalState/GlobalState.res
🧰 Additional context used
📓 Path-based instructions (2)
scenarios/test_codegen/**/*.ts

📄 CodeRabbit inference engine (scenarios/test_codegen/.cursor/rules/hyperindex.mdc)

scenarios/test_codegen/**/*.ts: After changing any TypeScript files, run: pnpm tsc --noEmit to ensure successful compilation
When updating existing entities in handlers, always use the spread operator to create updated objects before persisting
For any external call (e.g., fetch), wrap it in an Effect via experimental_createEffect and consume via context.effect
Use !context.isPreload to skip logic that should not run during preload
In TypeScript, set relationship fields using *_id properties (e.g., token_id) rather than object references
Always cast timestamps from events to BigInt (e.g., BigInt(event.block.timestamp))
When matching addresses in configuration objects within code, ensure keys are lowercase and compare using address.toLowerCase()
Use string | undefined for optional string fields instead of string | null
Always normalize token amounts to a standard decimal (e.g., 18) before addition across tokens; use helpers like normalizeAmountToUSD()

Files:

  • scenarios/test_codegen/src/EventHandlers.ts
codegenerator/cli/templates/{static,dynamic}/codegen/src/**/*

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Edit template versions under codegenerator/cli/templates/static/codegen/src or codegenerator/cli/templates/dynamic/codegen/src instead of editing generated/src

Files:

  • codegenerator/cli/templates/dynamic/codegen/src/Types.ts.hbs
🔇 Additional comments (1)
scenarios/test_codegen/src/EventHandlers.ts (1)

174-185: LGTM! Type assertion correctly validates chains field structure.

The compile-time type check confirms that context.chains has the expected shape ({ [chainId: string]: { readonly isReady: boolean } }), which aligns with the chainInfo type introduced in this PR. The assertion follows the established pattern used for other type validations in this test file.

Copy link
Member

@DZakh DZakh left a comment

Choose a reason for hiding this comment

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

Nice, let's wait for the tests.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
codegenerator/cli/templates/static/codegen/src/EventProcessing.res (2)

9-24: Chains readiness computation looks good; consider centralizing readiness and keying helpers

  • Logic aligns with intended semantics and prior feedback.
  • Suggest exposing a ChainFetcher.isReady (or similar) helper to avoid direct field access to timestampCaughtUpToHeadOrEndblock and keep invariants local.
  • Minor: use Utils.Dict.setByInt for consistent int-to-string dict keying across the codebase.

337-341: No missing ~chains propagation; computeChainsState usage is isolated

  • All call sites for preloadBatchOrThrow, runBatchHandlersOrThrow, and processEventBatch include ~chains.
  • computeChainsState is only invoked in processEventBatch; no duplication in GlobalState.
  • Optional: add a comment clarifying that chains is a per-batch snapshot and not live-updating mid-batch.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1529e2f and bc93c9e.

📒 Files selected for processing (1)
  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res (14 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Never use [| item |] to create an array. Use [ item ] instead.
Must always use = for setting value to a field. Use := only for ref values created using ref function.
ReScript has record types which require a type definition before hand. You can access record fields by dot like foo.myField.
It's also possible to define an inline object, it'll have quoted fields in this case.
Use records when working with structured data, and objects to conveniently pass payload data between functions.
Never use %raw to access object fields if you know the type.

Files:

  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res
**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Prefer reading ReScript .res modules directly; compiled .js artifacts can be ignored

Files:

  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res
codegenerator/cli/templates/{static,dynamic}/codegen/src/**/*

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Edit template versions under codegenerator/cli/templates/static/codegen/src or codegenerator/cli/templates/dynamic/codegen/src instead of editing generated/src

Files:

  • codegenerator/cli/templates/static/codegen/src/EventProcessing.res
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (4)
codegenerator/cli/templates/static/codegen/src/EventProcessing.res (4)

107-130: Threading chains into handler context is correct

Parameter added and passed to UserContext.getHandlerContext consistently.


159-204: runHandlerOrThrow: correct propagation of chains for both Block and Event paths

Looks consistent and safe.


220-272: Preload path correctly includes chains in preloader contexts

Both Event and Block preload contexts carry chains. Good.


289-301: Batch handlers: chains correctly threaded

Consistent with single-item path.

@DZakh DZakh merged commit d906825 into enviodev:main Oct 13, 2025
2 checks passed
@Segfaultd Segfaultd deleted the feature/event-origin branch October 13, 2025 18:01
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.

3 participants