Skip to content

feat(indexer/common): create core ledger cursor table#41

Open
playmaker410 wants to merge 1 commit into
Fundable-Protocol:devfrom
playmaker410:feat/issue-24-ledger-cursor-table
Open

feat(indexer/common): create core ledger cursor table#41
playmaker410 wants to merge 1 commit into
Fundable-Protocol:devfrom
playmaker410:feat/issue-24-ledger-cursor-table

Conversation

@playmaker410

@playmaker410 playmaker410 commented Jun 25, 2026

Copy link
Copy Markdown

Summary

Implements issue #24 — durable progress tracking for the Soroban event poller.

Changes

Migration — 0001_create_ledger_cursor.sql

  • Creates schema_migrations bookkeeping table (idempotent, guarded run)
  • Creates ledger_cursor table:
    • BIGSERIAL surrogate PK
    • UNIQUE(source, domain) — the natural identity for a cursor
    • CHECK constraints: non-empty source/domain, last_ledger_seq >= 0
    • Index on last_ledger_seq WHERE NOT NULL for monitoring/backfill queries

Cursor repository — ledger-cursor.ts

  • LedgerCursor interface: id, source, domain, last_ledger_seq, created_at, updated_at
  • SqlClient type alias: minimal tagged-template signature; no postgres import needed in callers/tests
  • findCursor(sql, source, domain) — SELECT by (source, domain), returns LedgerCursor | null
  • upsertCursor(sql, source, domain, lastLedgerSeq) — INSERT … ON CONFLICT DO UPDATE with stale-write guard; falls back to findCursor if the WHERE suppresses the update

Migration runner — migrate.ts

  • Reads *.sql files from migrations/ lexicographically
  • Executes each file via sql.unsafe() (idempotency enforced inside SQL)
  • INDEXER_DATABASE_URL env var; db:migrate script in package.json
  • Root indexer:db:migrate convenience script added

Package config

  • @types/node devDependency added to indexer/common
  • tsconfig.json updated with types: ["node", "vitest/globals"]
  • All public API exported from index.ts

Tests

16 tests covering findCursor and upsertCursor using an in-memory SqlClient mock (no database required):

  • null for missing cursor
  • insert on first call
  • advances on higher seq; ignores stale/same seq (idempotent)
  • separate cursors per source and domain
  • input validation (empty strings, negative seq)
  • timestamps present on returned cursor

Acceptance criteria checklist

  • Ledger cursor schema defined
  • Migration for cursor storage added
  • Tracks source/domain and last processed ledger
  • Useful timestamps and indexes added
  • Relevant tests added (16 tests, all green)
  • bun run indexer:type-check passes
  • bun run indexer:test passes
  • bun run indexer:lint passes

Closes #24

Summary by CodeRabbit

  • New Features

    • Added database migration support for the indexer, including a new command to run pending migrations.
    • Introduced persistent ledger cursor tracking so progress can be saved and resumed reliably.
    • Added a top-level command to launch indexer database migrations more easily.
  • Bug Fixes

    • Improved cursor updates so progress only moves forward and does not regress on older values.
    • Added validation and safeguards to prevent invalid cursor data from being stored.
  • Tests

    • Added coverage for cursor lookup, updates, validation, and domain/source isolation.

…col#24)

- Add ledger_cursor SQL migration (0001_create_ledger_cursor.sql)
  - BIGSERIAL primary key, UNIQUE(source, domain) constraint
  - CHECK constraints: non-empty source/domain, seq >= 0
  - Index on last_ledger_seq for monitoring queries
  - schema_migrations bookkeeping table for idempotent runs

- Add LedgerCursor type and SqlClient type alias (ledger-cursor.ts)
  - findCursor(sql, source, domain): returns cursor or null
  - upsertCursor(sql, source, domain, lastLedgerSeq): insert-or-advance
    with stale-write guard (only advances if new seq > existing)

- Add migration runner (migrate.ts)
  - Reads *.sql files from migrations/ sorted lexicographically
  - Executes each file idempotently via sql.unsafe()
  - INDEXER_DATABASE_URL env var; db:migrate npm script

- Add @types/node devDependency and node types in tsconfig
- Export all public API from index.ts
- 16 tests covering findCursor, upsertCursor (in-memory mock)

Closes Fundable-Protocol#24
@drips-wave

drips-wave Bot commented Jun 25, 2026

Copy link
Copy Markdown

@playmaker410 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a ledger cursor table migration, migration runner wiring, and a new cursor repository with lookup/upsert behavior, tests, exports, and TypeScript settings.

Changes

Ledger cursor persistence and migration setup

Layer / File(s) Summary
Migration tooling and schema
package.json, indexer/common/package.json, indexer/common/src/db/migrate.ts, indexer/common/src/db/migrations/0001_create_ledger_cursor.sql
Adds migration scripts and dependencies, a migration runner CLI, and the initial ledger_cursor SQL migration.
Cursor repository and API
indexer/common/src/cursor/ledger-cursor.ts, indexer/common/src/cursor/ledger-cursor.test.ts, indexer/common/src/index.ts, indexer/common/tsconfig.json
Defines the cursor row and SQL client types, implements cursor lookup and upsert behavior, adds tests, and re-exports the API with TypeScript config updates.

Sequence Diagram(s)

sequenceDiagram
  participant upsertCursor
  participant SqlClient
  participant ledger_cursor
  participant findCursor

  upsertCursor->>SqlClient: INSERT ... ON CONFLICT ... last_ledger_seq
  SqlClient->>ledger_cursor: write or advance cursor row
  alt no row returned
    upsertCursor->>findCursor: re-read source and domain
    findCursor->>SqlClient: SELECT cursor row
    SqlClient->>ledger_cursor: fetch row
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Poem

🐰 I hop through ledgers, one soft thump at a time,
past schema stones and migration chime.
A cursor remembers where the burrow has been,
and resumes right there when the lights come in.
Hooray for tables, tests, and tidy trails!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning It includes summary, changes, and tests, but omits the required Area, Scope, Verification, and Indexer Safety template sections. Reformat the description to match the template and add the Area, Scope, Verification, Indexer Safety, and Notes sections with the required checklists.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main change: adding the core ledger cursor table for indexer/common.
Linked Issues check ✅ Passed The PR implements the cursor schema, migration, source/domain tracking, timestamps/indexes, repository helpers, and tests required by #24.
Out of Scope Changes check ✅ Passed No clear out-of-scope changes are present; the package scripts, types, exports, and tests all support the ledger cursor migration work.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ 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

🤖 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/db/migrate.ts`:
- Around line 45-48: The exported runMigrations helper currently looks for the
migrations directory next to the compiled module, so the SQL files must be
available in the built package. Update the build/package flow for the db
migration path so the src/db/migrations/*.sql assets are copied or otherwise
shipped alongside the emitted JS used by runMigrations, or revert the API to
source-only if that packaging is not intended. Use runMigrations,
listMigrationFiles, and the migrationsDir resolution logic as the main places to
adjust.
🪄 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: 736d7011-eca3-4f7d-81ba-47772b98fe5e

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • indexer/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • indexer/common/package.json
  • indexer/common/src/cursor/ledger-cursor.test.ts
  • indexer/common/src/cursor/ledger-cursor.ts
  • indexer/common/src/db/migrate.ts
  • indexer/common/src/db/migrations/0001_create_ledger_cursor.sql
  • indexer/common/src/index.ts
  • indexer/common/tsconfig.json
  • package.json

Comment on lines +45 to +48
export async function runMigrations(databaseUrl: string): Promise<void> {
const migrationsDir = join(fileURLToPath(import.meta.url), "..", "migrations");

const files = await listMigrationFiles(migrationsDir);

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Package the SQL migrations alongside runMigrations.

Line 46 resolves migrations/ next to the executing module, but this PR only shows source-time execution for the CLI. I don't see a matching step that makes src/db/migrations/*.sql available beside the compiled JS, so the newly exported runMigrations API can fail with ENOENT when invoked from the built package. Either ship the SQL assets with the build or keep this runner source-only.

🤖 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/db/migrate.ts` around lines 45 - 48, The exported
runMigrations helper currently looks for the migrations directory next to the
compiled module, so the SQL files must be available in the built package. Update
the build/package flow for the db migration path so the src/db/migrations/*.sql
assets are copied or otherwise shipped alongside the emitted JS used by
runMigrations, or revert the API to source-only if that packaging is not
intended. Use runMigrations, listMigrationFiles, and the migrationsDir
resolution logic as the main places to adjust.

@Franklivania Franklivania mentioned this pull request Jun 26, 2026
13 tasks
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.

Create core ledger cursor table

1 participant