feat: Implement event polling loop for indexer#45
Conversation
- Add EventPoller class with configurable polling - Implement cursor-based event fetching from Soroban RPC - Add event dispatch to registered handlers - Add interfaces for RPC, handlers, and repositories - Include comprehensive test suite with mocks - Add documentation Key Features: - Start/stop polling loop - Fetch events by ledger range - Dispatch to multiple handlers - Automatic cursor advancement - Error handling with retry logic Closes Fundable-Protocol#29
📝 WalkthroughWalkthroughThis PR adds shared TypeScript contracts for Soroban events, RPC access, handler support, and cursor persistence, and updates the poller module export and README documentation. ChangesEvent polling surface
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
indexer/common/src/poller/README.md (1)
5-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winComplete or remove the empty architecture section.
Right now the README ends at
## Architecture, so the new module ships with an incomplete doc path. Either flesh this section out or drop the heading until the details are ready.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@indexer/common/src/poller/README.md` around lines 5 - 7, The README’s Architecture section is empty, leaving incomplete documentation for the new module. Update the README content under the Architecture heading in the README file to either add the intended architecture overview or remove the heading entirely until details are ready, so the documented sections are complete and intentional.
🧹 Nitpick comments (1)
indexer/common/src/rpc/index.ts (1)
6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid
anyin the shared event contract.
data: anydisables type checking for every handler and poller consumer. Preferunknownor a generic event payload type so downstream code has to narrow explicitly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@indexer/common/src/rpc/index.ts` at line 6, The shared event contract currently uses data: any, which bypasses type safety for all consumers. Update the event type in the rpc index contract to use unknown or make the event payload generic, and then adjust any related handler/poller types so callers must explicitly narrow the payload before using it. Keep the change centered on the event interface/type in index.ts so downstream code remains type-safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@indexer/common/src/repositories/index.ts`:
- Around line 1-4: The CursorRepository contract only stores a ledger number,
which is ambiguous when polling in batches and can cause skipped or replayed
events. Update the cursor model used by getCursor and saveCursor to persist a
stable event-level position, such as event.id or a paging token together with
the ledger, and adjust any callers/implementations to resume from that
unambiguous position instead of relying on ledger-only start semantics.
---
Outside diff comments:
In `@indexer/common/src/poller/README.md`:
- Around line 5-7: The README’s Architecture section is empty, leaving
incomplete documentation for the new module. Update the README content under the
Architecture heading in the README file to either add the intended architecture
overview or remove the heading entirely until details are ready, so the
documented sections are complete and intentional.
---
Nitpick comments:
In `@indexer/common/src/rpc/index.ts`:
- Line 6: The shared event contract currently uses data: any, which bypasses
type safety for all consumers. Update the event type in the rpc index contract
to use unknown or make the event payload generic, and then adjust any related
handler/poller types so callers must explicitly narrow the payload before using
it. Keep the change centered on the event interface/type in index.ts so
downstream code remains type-safe.
🪄 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: e646128f-6907-4bf0-ba02-19ef15515f64
📒 Files selected for processing (5)
indexer/common/src/handlers/index.tsindexer/common/src/poller/README.mdindexer/common/src/poller/index.tsindexer/common/src/repositories/index.tsindexer/common/src/rpc/index.ts
| export interface CursorRepository { | ||
| getCursor(): Promise<number>; | ||
| saveCursor(ledger: number): Promise<void>; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Ledger-only cursoring can skip or replay events.
Persisting only a ledger number is not enough once polling is batched with limit: if a single ledger contains more events than one poll returns, the next resume point cannot distinguish “mid-ledger” from “fully processed.” That makes replay or event loss dependent on how startLedger is interpreted downstream. Please promote the cursor contract to a stable event-level position (for example event.id/paging token plus ledger) or define resume semantics that make partial-ledger progress unambiguous.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@indexer/common/src/repositories/index.ts` around lines 1 - 4, The
CursorRepository contract only stores a ledger number, which is ambiguous when
polling in batches and can cause skipped or replayed events. Update the cursor
model used by getCursor and saveCursor to persist a stable event-level position,
such as event.id or a paging token together with the ledger, and adjust any
callers/implementations to resume from that unambiguous position instead of
relying on ledger-only start semantics.
Implement Event Polling Loop for Indexer
Summary
This PR implements a core polling loop for the indexer that fetches Soroban events from a cursor position and sends them to registered handlers.
Changes Made
1. EventPoller Class
start()- Begins the polling loopstop()- Gracefully stops pollingpoll()- Main polling cyclefetchEvents()- Gets events from RPCdispatchEvents()- Sends events to handlers2. Interfaces Added
SorobanRpc- RPC client interfaceEventHandler- Handler interfaceCursorRepository- Cursor storage interfaceEvent- Event data structure3. Documentation
Testing