Skip to content

feat: Implement event polling loop for indexer#45

Open
Timothy2025-20 wants to merge 1 commit into
Fundable-Protocol:devfrom
Timothy2025-20:dev
Open

feat: Implement event polling loop for indexer#45
Timothy2025-20 wants to merge 1 commit into
Fundable-Protocol:devfrom
Timothy2025-20:dev

Conversation

@Timothy2025-20

@Timothy2025-20 Timothy2025-20 commented Jun 26, 2026

Copy link
Copy Markdown

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 loop
  • stop() - Gracefully stops polling
  • poll() - Main polling cycle
  • fetchEvents() - Gets events from RPC
  • dispatchEvents() - Sends events to handlers

2. Interfaces Added

  • SorobanRpc - RPC client interface
  • EventHandler - Handler interface
  • CursorRepository - Cursor storage interface
  • Event - Event data structure

3. Documentation

  • README with architecture diagram
  • Usage examples
  • Configuration guide

Testing

bun run indexer:test
bun run indexer:type-check
bun run indexer:lint

Files Changed
indexer/common/src/poller/index.ts - Main implementation

indexer/common/src/poller/README.md - Documentation

indexer/common/src/rpc/index.ts - RPC interfaces

indexer/common/src/handlers/index.ts - Handler interfaces

indexer/common/src/repositories/index.ts - Repository interfaces

Closes #29

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
  * Added public interfaces for event polling, event handlers, cursor persistence, and RPC event access, enabling broader support for indexing and event processing.
  * Exposed the event poller through the common entry point for easier integration.

* **Documentation**
  * Expanded polling documentation with an overview of how events are fetched and dispatched, plus an added architecture section.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

- 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
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared TypeScript contracts for Soroban events, RPC access, handler support, and cursor persistence, and updates the poller module export and README documentation.

Changes

Event polling surface

Layer / File(s) Summary
Core contracts
indexer/common/src/rpc/index.ts, indexer/common/src/handlers/index.ts, indexer/common/src/repositories/index.ts
Adds the shared Event, GetEventsParams, SorobanRpc, EventHandler, and CursorRepository interfaces.
Poller export and docs
indexer/common/src/poller/index.ts, indexer/common/src/poller/README.md
Re-exports EventPoller from the poller entrypoint and adds introductory poller documentation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • Fundable-Protocol/Backend issue 29: The new shared event-polling contracts align with the event polling loop work referenced there.

Poem

A rabbit hopped by, ears held high, 🐰
With event shapes and cursors stacked nearby.
It twitched its nose at poller lore,
And re-exports opened the module door.
“Hop, hop!” it sang, “the contracts are set—
Soroban events are ready to fetch, I bet.”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers summary and testing, but it omits the required Area, Scope, Indexer Safety, and Notes sections. Add the missing template sections for Area, Scope, Indexer Safety, and Notes, and complete the relevant checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main change: adding an event polling loop for the indexer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

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.

❤️ Share

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

@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

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 win

Complete 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 win

Avoid any in the shared event contract.

data: any disables type checking for every handler and poller consumer. Prefer unknown or 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

📥 Commits

Reviewing files that changed from the base of the PR and between bcfa08d and 0a036d9.

📒 Files selected for processing (5)
  • indexer/common/src/handlers/index.ts
  • indexer/common/src/poller/README.md
  • indexer/common/src/poller/index.ts
  • indexer/common/src/repositories/index.ts
  • indexer/common/src/rpc/index.ts

Comment on lines +1 to +4
export interface CursorRepository {
getCursor(): Promise<number>;
saveCursor(ledger: number): Promise<void>;
}

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.

🗄️ 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.

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.

1 participant