Skip to content

@thru/indexer

View as Markdown

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

Terminal window
npm install @thru/indexer @thru/replay @thru/sdk postgres drizzle-orm

You will also need a database driver and a Drizzle database/schema setup in the application that runs the indexer.

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
AreaExportWhat it does
Schema buildert, columnBuilderDefine typed columns for stream-backed Drizzle tables.
ValidationgenerateZodSchema, validateParsedDataValidate parsed rows during development or debugging.
Stream definitionsdefineEventStream, defineAccountStreamCompile event and account stream definitions into runnable stream objects with .table exports.
CheckpointscheckpointTable, getCheckpoint, updateCheckpoint, deleteCheckpoint, getAllCheckpoints, getSchemaExportsStore resumable progress and collect tables for migrations.
RuntimeIndexerRun configured event and account streams against a database and replay client factory.
Shared typesApiConfig, StreamBatch, HookContext, IndexerConfig, IndexerResultType stream hooks, config, and runtime results.
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.

@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);