Skip to main content
Use this page when the stream definitions are ready and you need to wire the runtime, database, and optional API surface.

Architecture

The typical Thru indexing stack looks like this:
Chain RPC -> @thru/replay ChainClient -> @thru/indexer runtime -> Postgres tables
                                                         \
                                                          -> optional generated Hono routes

Tech Stack Assumptions

The current @thru/indexer runtime assumes:
  • PostgreSQL-backed tables generated through Drizzle
  • a Drizzle database client passed as db
  • a clientFactory that returns a ChainClient from @thru/replay
Optional but common:
  • Hono for generated stream routes via mountStreamRoutes(...)
  • Drizzle Kit for migrations
  • a standalone Node service to run the indexer continuously
If you are not using Postgres plus Drizzle, do not start here. Start with the package reference for @thru/indexer and validate whether the current runtime fits your stack.

Runtime Setup

import { Indexer } from "@thru/indexer";
import { ChainClient } from "@thru/replay";
import { db } from "./db";
import tokenTransfers from "./streams/token-transfers";
import tokenAccounts from "./account-streams/token-accounts";

export function createIndexer() {
  return new Indexer({
    db,
    clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }),
    eventStreams: [tokenTransfers],
    accountStreams: [tokenAccounts],
    defaultStartSlot: 0n,
    safetyMargin: 64,
    pageSize: 512,
    logLevel: "info",
  });
}

What Each Runtime Option Does

OptionWhat it controls
dbThe Drizzle client used for inserts, updates, checkpoints, and generated route queries.
clientFactoryFresh replay client creation for backfill and live streaming.
eventStreamsAppend-only streams for event rows.
accountStreamsCurrent-state streams for account rows.
defaultStartSlotStarting slot when no checkpoint exists yet.
safetyMarginHow far behind the live tip replay should stay during backfill-to-live switchover.
pageSizeHow many records to request per backfill page.
logLevelRuntime verbosity.

Checkpoints And Schema

Your Drizzle schema needs the checkpoint table plus every stream table.
export { checkpointTable } from "@thru/indexer";
export { tokenTransferEvents } from "./streams/token-transfers";
export { tokenAccountsTable } from "./account-streams/token-accounts";
Without checkpointTable, the runtime cannot resume safely after restarts.

Process Shape

In practice, most apps run the indexer as its own long-lived service:
  1. load environment and connect to Postgres
  2. run or verify migrations
  3. build the Indexer
  4. call await indexer.start()
  5. stop gracefully on SIGTERM or SIGINT

Generated API Routes

If you want a read API over indexed rows, @thru/indexer can mount generated routes into a Hono app:
import { OpenAPIHono } from "@hono/zod-openapi";
import { mountStreamRoutes } from "@thru/indexer";
import { db } from "./db";
import tokenTransfers from "./streams/token-transfers";
import tokenAccounts from "./account-streams/token-accounts";

const app = new OpenAPIHono();

mountStreamRoutes(app, {
  db,
  pathPrefix: "/api/v1",
  eventStreams: [tokenTransfers],
  accountStreams: [tokenAccounts],
});
Use that when you want a quick query surface without writing every route by hand.

Next Step

Open Querying Indexed Data once rows are landing in Postgres.