@thru/indexer
@thru/indexer is the public root entrypoint for building persistent Thru indexers. The package exposes a single published entrypoint at the package root; there are no public subpath exports.
Install
Section titled “Install”npm install @thru/indexer @thru/replay @thru/sdk postgres drizzle-ormYou will also need a database driver and a Drizzle database/schema setup in the application that runs the indexer.
When To Use It
Section titled “When To Use It”Use @thru/indexer when you want to:
- define event streams for historical chain data
- define account streams for mutable on-chain state
- build Drizzle tables from stream definitions
- persist checkpoints so an indexer can resume after a restart
- run a background indexer that processes event and account streams together
Choose a different package when:
- you only need an ordered feed and do not want to store results yourself: use
@thru/replay - you only need direct chain reads or writes: use
@thru/sdk - you only need low-level encoding helpers: use
@thru/sdk/helpers
Main Exports
Section titled “Main Exports”| Area | Export | What it does |
|---|---|---|
| Schema builder | t, columnBuilder | Define typed columns for stream-backed Drizzle tables. |
| Validation | generateZodSchema, validateParsedData | Validate parsed rows during development or debugging. |
| Stream definitions | defineEventStream, defineAccountStream | Compile event and account stream definitions into runnable stream objects with .table exports. |
| Checkpoints | checkpointTable, getCheckpoint, updateCheckpoint, deleteCheckpoint, getAllCheckpoints, getSchemaExports | Store resumable progress and collect tables for migrations. |
| Runtime | Indexer | Run configured event and account streams against a database and replay client factory. |
| Shared types | ApiConfig, StreamBatch, HookContext, IndexerConfig, IndexerResult | Type stream hooks, config, and runtime results. |
Typical Workflow
Section titled “Typical Workflow”import { Indexer, checkpointTable, defineAccountStream, defineEventStream, t,} from "@thru/indexer";import { ChainClient } from "@thru/replay";
const indexer = new Indexer({ db, clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), eventStreams: [transfers], accountStreams: [tokenAccounts], defaultStartSlot: 0n, safetyMargin: 64, pageSize: 512,});
await indexer.start();defineEventStream is for append-only chain events. defineAccountStream is for current account state that gets updated as chain data changes. Both return compiled stream objects with a Drizzle table on .table, which keeps schema and runtime definitions aligned.
Indexer is the runtime layer. Its config requires a database client and a clientFactory, and it supports event streams, account streams, a default start slot, safety margin, page size, and optional runtime validation.
Additional runtime options include:
| Option | Use it for |
|---|---|
logger | Structured runtime, processor, and replay logs. |
endpointLabel | Including an endpoint name in normalized stream errors. |
supervisorInitialBackoffMs | Tuning the first restart delay after stream failure. |
supervisorMaxBackoffMs | Capping stream supervisor restart backoff. |
streamStaleMs | Marking running streams stale after no activity. |
validateParse | Checking parsed rows against generated Zod schemas. |
Runtime Status
Section titled “Runtime Status”Indexer exposes in-memory status for health checks and observability.
const status = indexer.getStatus();
for (const stream of status.streams) { console.log(stream.name, stream.state, stream.restartCount);}Each stream status includes the stream kind, state, checkpoint slot, last processed slot, stale marker, restart count, normalized last error, and counters for records, commits, parser skips, validation failures, and hook failures.
The runtime supervises streams continuously. A stream that fails or ends
unexpectedly is restarted with backoff unless indexer.stop() has been called.
Reading Indexed Data
Section titled “Reading Indexed Data”@thru/indexer writes ordinary Drizzle tables. Query them directly from your backend.
import { desc, eq } from "drizzle-orm";import { db } from "./db";import { tokenTransferEvents } from "./schema";
const recentTransfers = await db .select() .from(tokenTransferEvents) .where(eq(tokenTransferEvents.dest, "ta...")) .orderBy(desc(tokenTransferEvents.slot)) .limit(20);Related Guides
Section titled “Related Guides”- Indexing Overview for package selection and the full indexing guide structure.
- Build an Indexer for a step-by-step guide with production concerns.
@thru/replayfor the replay primitives that power the indexer runtime.@thru/sdkfor the app-facing RPC SDK.