<!-- source: https://secondlayer.tools/docs/subgraphs-reference -->

# `@secondlayer/subgraphs`

Everything the subgraphs package exports: `defineSubgraph`, the handler `ctx`, contract reads, schema codegen, replay and the test harness. The [Subgraphs guide](/docs/subgraphs) shows how they fit together.

## Define a subgraph

Declare sources, tables and handlers, and check them before a deploy.

Import from `@secondlayer/subgraphs`.

### defineSchema(schema)

Declares a schema you can hoist out of `defineSubgraph()` and reuse.

It keeps the schema's literal types, so table names and row columns stay
checked in helpers typed `TypedSubgraphContext<typeof schema>`. `as const`
alone does not work: it produces `readonly` arrays that a mutable
`uniqueKeys` rejects, and a bare literal widens `type` to `string`.

```ts
defineSchema<const S extends SubgraphSchema>(schema: S): S
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `schema` | `S` | yes |  |

### defineSubgraph(def)

Declares a subgraph and keeps its source and schema literal types.

Handlers are typed per source, and the schema drives query-client inference.
It returns its argument unchanged.

```ts
defineSubgraph<const Sources extends Record<string, SubgraphFilter>, const S extends SubgraphSchema>(
  def: TypedSubgraphDefinition<Sources, S>,
): TypedSubgraphDefinition<Sources, S>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `description` | `string` | no | Human description |
| `startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `sources` | `Sources` | yes |  |
| `schema` | `S` | yes |  |
| `handlers` | `TypedHandlers<Sources, S>` | yes |  |

### filterFieldsByType()

Lists the fields each source filter type accepts.

The list is derived from `SubgraphFilterSchema`. Agent-facing surfaces
such as the MCP filters reference used to keep a parallel copy by hand;
deriving it means adding a field to a filter can never leave the
documentation behind.

```ts
filterFieldsByType(): { type: string; fields: string[] }[]
```

### validateSubgraphDefinition(def)

Validates a subgraph definition and returns the parsed result.

It throws when the definition is invalid.

```ts
validateSubgraphDefinition(def: unknown): SubgraphDefinition
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def` | `unknown` | yes |  |

### camelizeKeys(obj)

Camelizes object keys recursively, the way print payloads are.

It uses the print-payload helper, not ABI `toCamelCase`, which strips `-STX`
and prefixes leading digits.

```ts
camelizeKeys(obj: unknown): unknown
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `obj` | `unknown` | yes |  |

### inferPrintTopics(samples)

Infers per-topic field schemas from sampled print events. Counts and
height bounds cover ALL rows of a topic (cheap); only a bounded subset is
deserialized for typing. Rows with missing/undecodable hex still count but
contribute nothing to typing.

```ts
inferPrintTopics(samples: PrintSample[]): InferredTopicSchema[]
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `samples` | `PrintSample[]` | yes |  |

## Handler context

The `ctx` every handler receives: write rows, read them back, call contracts.

Import from `@secondlayer/subgraphs`.

### ctx.client.contract(contractId, abi, options)

Reads a contract's read-only functions at the block being processed.

Results are untyped here. Call it through `readContractAt` to get typed
results from the contract's ABI.

```ts
ctx.client.contract(
  contractId: string,
  abi: unknown,
  options?: ChainReadOptions,
): { read: Record<string, (args?: Record<string, unknown>) => Promise<unknown>> }
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `contractId` | `string` | yes |  |
| `abi` | `unknown` | yes |  |
| `options.cache` | `ChainReadCacheMode` | no |  |

### ctx.insert(table, row)

Queues a new row for a table.

Writes are batched and applied together when the block commits.
`_block_height` and `_tx_id` are filled in for you.

```ts
ctx.insert<T extends TableName<S>>(table: T, row: WriteRow<S[T]>): void
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `table` | `T` | yes |  |
| `row` | `WriteRow<S[T]>` | yes |  |

### ctx.update(table, where, set)

Queues an update to the rows matching `where`.

`set` holds only the columns to change.

```ts
ctx.update<T extends TableName<S>>(
  table: T,
  where: RowWhere<S, T>,
  set: Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>,
): void
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `table` | `T` | yes |  |
| `where` | `RowWhere<S, T>` | yes |  |
| `set` | `Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>` | yes |  |

### ctx.upsert(table, key, row)

Inserts a row, or updates the one with the same key.

`key` should match a `uniqueKeys` constraint of the table. Without one the
write falls back to a lookup first and logs a warning.

```ts
ctx.upsert<T extends TableName<S>, K extends Partial<WriteRow<S[T]>>>(
  table: T,
  key: K,
  row: Omit<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }, keyof K>
  & Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>,
): void
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `table` | `T` | yes |  |
| `key` | `K` | yes |  |
| `row` | `Omit<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }, keyof K> & Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>` | yes |  |

### ctx.delete(table, where)

Queues deletion of the rows matching `where`.

```ts
ctx.delete<T extends TableName<S>>(table: T, where: RowWhere<S, T>): void
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `table` | `T` | yes |  |
| `where` | `RowWhere<S, T>` | yes |  |

### ctx.increment(table, key, deltas)

Adds signed deltas to a counter row, creating it if missing.

It compiles to `INSERT … ON CONFLICT (key) DO UPDATE SET col =
COALESCE(col,0) + delta`. Deltas commute, so it is the reorg-safe way to
keep running totals such as per-cycle aggregates and balances. It needs a
`uniqueKeys` constraint matching `key`, and deltas may be `number` or
`bigint`.

```ts
ctx.increment<T extends TableName<S>>(
  table: T,
  key: Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>,
  deltas: Record<string, Record<ColumnName<S, T>, number | bigint>[string] | undefined>,
): void
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `table` | `T` | yes |  |
| `key` | `Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>` | yes |  |
| `deltas` | `Record<string, Record<ColumnName<S, T>, number \| bigint>[string] \| undefined>` | yes |  |

### ctx.findOne(table, where)

Returns the first row matching `where`, or `null`.

Reads see the writes queued earlier in the same block, and rows come back
in insertion order.

```ts
ctx.findOne<T extends TableName<S>>(
  table: T,
  where: RowWhere<S, T>,
): Promise<SystemRow & { [K in keyof T["columns"]]: InferColumnType<T["columns"][K]>; } | null>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `table` | `T` | yes |  |
| `where` | `RowWhere<S, T>` | yes |  |

### ctx.findMany(table, where)

Returns every row matching `where`, in insertion order.

Reads see the writes queued earlier in the same block.

```ts
ctx.findMany<T extends TableName<S>>(
  table: T,
  where: RowWhere<S, T>,
): Promise<SystemRow & { [K in keyof T["columns"]]: InferColumnType<T["columns"][K]>; }[]>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `table` | `T` | yes |  |
| `where` | `RowWhere<S, T>` | yes |  |

## Contract reads

Read contract state from a handler, pinned to the block being processed.

Import from `@secondlayer/subgraphs`.

### readContractAt(ctx, contractId, abi, options)

Reads a contract's read-only functions from a handler, with typed results.

Pass `ctx`, the contract id and its ABI. Every call is pinned to the block
being processed. Set `options.cache` to `"contract-constant"` to fetch a
value once per contract instead of once per block.

```ts
readContractAt<const C extends AbiContract>(
  ctx: { client: ErasedChainReadClient },
  contractId: string,
  abi: C,
  options?: ChainReadOptions,
): { read: ChainReadMethods<C> }
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ctx.client` | `ErasedChainReadClient` | yes |  |
| `contractId` | `string` | yes |  |
| `abi` | `C` | yes |  |
| `options.cache` | `ChainReadCacheMode` | no |  |

## Schema and SQL

The SQL and typed clients a subgraph's tables turn into, and the diff a deploy runs.

Import from `@secondlayer/subgraphs`.

### generateSubgraphSQL(def, schemaNameOverride)

Generates PostgreSQL DDL statements for a subgraph definition.
Creates a dedicated schema `subgraph_<name>` with one table per schema entry,
each with auto-columns and indexes.

```ts
generateSubgraphSQL(def: SubgraphDefinition, schemaNameOverride?: string): GeneratedSQL
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def.name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `def.description` | `string` | no | Human description |
| `def.startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `def.sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `def.schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `def.handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |
| `schemaNameOverride` | `string` | no |  |

### generatePrismaSchema(def, opts)

Generates a Prisma schema for a subgraph's tables.

Set `modelsOnly` to emit just the `model` blocks, to compose with your own
schema.

```ts
generatePrismaSchema(def: SubgraphDefinition, opts?: PrismaGenOptions): string
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def.name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `def.description` | `string` | no | Human description |
| `def.startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `def.sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `def.schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `def.handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |
| `opts.schemaName` | `string` | no | Postgres schema the tables live in (account-scoped). |
| `opts.datasourceEnv` | `string` | no | env var the datasource url reads from. |
| `opts.modelsOnly` | `boolean` | no | Emit only the `model` blocks (no datasource/generator). Lets users compose these models with their own schema via Prisma's `prismaSchemaFolder`. |

### generateDrizzleSchema(def, opts)

Generates Drizzle table definitions for a subgraph's tables.

```ts
generateDrizzleSchema(def: SubgraphDefinition, opts?: DrizzleGenOptions): string
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def.name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `def.description` | `string` | no | Human description |
| `def.startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `def.sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `def.schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `def.handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |
| `opts.schemaName` | `string` | no |  |

### generateKyselySchema(def, opts)

Generates Kysely table interfaces for a subgraph's tables.

```ts
generateKyselySchema(def: SubgraphDefinition, opts?: KyselyGenOptions): string
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def.name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `def.description` | `string` | no | Human description |
| `def.startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `def.sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `def.schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `def.handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |
| `opts.schemaName` | `string` | no | Postgres schema the tables live in (account-scoped). |

### generateIndexSchema(target, opts)

Generates a typed schema for Index tables.

`target` picks Prisma, Kysely, Drizzle or JSON Schema. Use `tables` to limit
the output. Prisma omits tables with no read-set key.

```ts
generateIndexSchema(target: IndexCodegenTarget, opts?: IndexCodegenOptions): string
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `target` | `IndexCodegenTarget` | yes |  |
| `opts.tables` | `string[]` | no | Restrict output to these Index tables (default: all). |
| `opts.schemaName` | `string` | no | Postgres schema to qualify table names with (default: bare names). |
| `opts.datasourceEnv` | `string` | no | Prisma only: datasource url env var (default DATABASE_URL). |

### pgSchemaName(subgraphName)

Convert a subgraph name to its PostgreSQL schema name.

```ts
pgSchemaName(subgraphName: string): string
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `subgraphName` | `string` | yes |  |

### deploySchema(db, def, handlerPath, opts)

Deploys a subgraph schema to the database.

A new subgraph gets its schema and tables created and registered. The same
schema hash only updates the handler path. An additive change adds columns
and tables in place, and a breaking change reindexes by dropping and
recreating the tables.

```ts
deploySchema(
  db: AnyDb,
  def: SubgraphDefinition,
  handlerPath: string,
  opts?: {
    forceReindex?: boolean;
    apiKeyId?: string;
    accountId?: string;
    schemaName?: string;
    handlerCode?: string;
    sourceCode?: string;
    verification?: SubgraphVerification;
    network?: string;
  },
): Promise<{ action: "created" | "unchanged" | "handler_updated" | "updated" | "reindexed"; subgraphId: string; version: string; pin?: string; diff?: DeployDiff; warnings?: string[] }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `db` | `AnyDb` | yes |  |
| `def.name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `def.description` | `string` | no | Human description |
| `def.startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `def.sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `def.schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `def.handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |
| `handlerPath` | `string` | yes |  |
| `opts.forceReindex` | `boolean` | no |  |
| `opts.apiKeyId` | `string` | no |  |
| `opts.accountId` | `string` | no |  |
| `opts.schemaName` | `string` | no |  |
| `opts.handlerCode` | `string` | no |  |
| `opts.sourceCode` | `string` | no |  |
| `opts.verification` | `SubgraphVerification` | no |  |
| `opts.network` | `string` | no |  |

### diffSchema(existing, incoming)

Compare two multi-table subgraph schemas and return differences.

Index-only changes (`indexed`/`search` flags, composite `indexes`) are
reported separately from data-shape changes: an index never requires a
rebuild, so it must never land in `changed`/breaking — flipping
`indexed: true` on a populated column used to cost a full reindex.

```ts
diffSchema(existing: SubgraphSchema, incoming: SubgraphSchema): TableDiff
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `existing` | `SubgraphSchema` | yes |  |
| `incoming` | `SubgraphSchema` | yes |  |

### hasBreakingChanges(diff)

Tells whether a schema diff is breaking, with the reasons.

Breaking means removed tables, removed columns, changed column types or
removed `uniqueKeys`, which handler upserts conflict on. Index-flag and
composite-index changes are never breaking.

```ts
hasBreakingChanges(diff: TableDiff): { breaking: boolean; reasons: string[] }
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `addedTables` | `string[]` | yes | Tables added to the schema |
| `removedTables` | `string[]` | yes | Tables removed from the schema |
| `tables` | `Record<string, ColumnDiff>` | yes | Per-table column diffs (only for tables present in both) |
| `constraints` | `Record<string, ConstraintDiff>` | yes | Per-table uniqueKeys / composite-index diffs (tables present in both) |

### renderDeployPlan(def, schemaName)

Render the DDL a deploy would run, without executing.
Powers `--dry-run`: the user reviews exactly what a deploy does first.

```ts
renderDeployPlan(def: SubgraphDefinition, schemaName?: string): DeployPlan
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def.name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `def.description` | `string` | no | Human description |
| `def.startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `def.sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `def.schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `def.handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |
| `schemaName` | `string` | no |  |

## Block sources

Which block source a subgraph's filters allow.

Import from `@secondlayer/subgraphs`.

### canSparseScan(subgraph)

Tells whether a subgraph can skip blocks without its events.

Sparse scanning is sound only when every source is an event-type filter. A
`contract_call` or `contract_deploy` source matches transactions, which the
event probe cannot see.

```ts
canSparseScan(subgraph: SubgraphDefinition): boolean
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `description` | `string` | no | Human description |
| `startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |

### isStreamsIndexEligible(subgraph)

Tells whether a subgraph's sources can be read from the Streams index.

Every source must be a known event-type, `contract_call` or
`contract_deploy` filter, and array-style sources never qualify because they
leak the unreconstructable `_eventId`. Trait scope is allowed: trait
resolution reads the contract registry on the platform database, which the
processor always holds. Everything else stays on the database tap.

```ts
isStreamsIndexEligible(subgraph: SubgraphDefinition): boolean
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `description` | `string` | no | Human description |
| `startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |

### sparseProbeTargets(subgraph)

Lists the event types and contract scopes a subgraph's filters match.

Each target is a decoded event type with an optional contract scope. One
function feeds both the sparse probe and the event walks, so a new
contract-pinning filter field is added here once.

Per event type: if ANY filter for it is unscoped, a single unscoped target
covers everything (today's behavior); otherwise one target per distinct
pinned contract.

```ts
sparseProbeTargets(subgraph: SubgraphDefinition): SparseProbeTarget[]
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `description` | `string` | no | Human description |
| `startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |

## Verification

The pin and the findings that say how far a subgraph's rows can be checked.

Import from `@secondlayer/subgraphs`.

### computePin(input)

Content hash over everything that shapes a subgraph's rows. Unlike
`schema_hash`, it changes with the handler, `startBlock` and runtime, so
two deploys with the same pin produce the same rows from the same chain.

```ts
computePin(input: PinInput): string
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `schemaHash` | `string` | yes | Existing `schema_hash` (name + schema + sources). |
| `handlerCode` | `string` | yes | Bundled ESM exactly as stored in `subgraphs.handler_code`. |
| `startBlock` | `number \| null` | no |  |
| `network` | `string` | yes |  |
| `runtime` | `string` | no | Defaults to [`SUBGRAPHS_RUNTIME`](/docs/subgraphs-reference#c-SUBGRAPHS_RUNTIME). |

### deriveVerification(def, findings)

Derive how far a subgraph's rows can be checked. Level = the highest any
source or handler read needs. `reasons` lists everything standing between
the subgraph and checkable rows; a `state` subgraph with any reason is
refused at deploy, so every stored `state` row has none. Never configured,
never a flag.

```ts
deriveVerification(
  def: {
    sources: Record<string, SubgraphFilter>;
    backfillMode?: "blocking" | "concurrent";
  },
  findings?: readonly HandlerFinding[],
): SubgraphVerification
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def.sources` | `Record<string, SubgraphFilter>` | yes |  |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no |  |
| `findings` | `readonly HandlerFinding[]` | no |  |

### formatFinding(f)

Formats a handler finding as `file:line:column name: reason`.

```ts
formatFinding(f: HandlerFinding): string
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `"nondeterministic" \| "needs-events"` | yes |  |
| `name` | `string` | yes | What was used, e.g. `Date`, `Math.random`, `import()`, `ctx.client`. |
| `reason` | `string` | yes |  |
| `file` | `string` | yes | Original source file when a sourcemap was available, else the bundle. |
| `line` | `number` | yes | 1-based. |
| `column` | `number` | yes | 1-based. |

### isDeterminismViolation(v)

Tells whether a deploy must be refused for breaking determinism.

It is refused when the sources are provable (`state`), so the determinism
contract is enforced, and the scan broke it.

```ts
isDeterminismViolation(v: SubgraphVerification): boolean
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `v` | `SubgraphVerification` | yes |  |

### loadDeterministicDefinition(handlerCode)

Loads a bundled handler into a fresh deterministic realm.

The bundle is ESM, as stored in `handler_code`. The returned definition has
every handler wrapped to receive the `deterministicContext` facade.

```ts
loadDeterministicDefinition(handlerCode: string): Promise<SubgraphDefinition>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `handlerCode` | `string` | yes |  |

### Constants

- `SUBGRAPHS_RUNTIME`: `string`. Runtime identity baked into every pin: matcher, decode and flush semantics.

## Replay

Re-run a subgraph from the archive and compare it with what is served.

Import from `@secondlayer/subgraphs/verify`.

### checkPin(served, def)

Checks a served pin against the served bundle.

The preimage must hash to the pin, and name this handler code, this schema
and this `startBlock`. A runtime other than this package's is only a note,
since rows can still match.

```ts
checkPin(
  served: { pin: string | null; pinPreimage: string | null; handlerCode: string },
  def: SubgraphDefinition,
): PinCheck
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `served.pin` | `string \| null` | yes |  |
| `served.pinPreimage` | `string \| null` | yes |  |
| `served.handlerCode` | `string` | yes |  |
| `def.name` | `string` | yes | Unique subgraph name (lowercase, alphanumeric + hyphens) |
| `def.description` | `string` | no | Human description |
| `def.startBlock` | `number` | no | Block height to start indexing from (default: 1) |
| `def.backfillMode` | `"blocking" \| "concurrent"` | no | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
| `def.sources` | `Record<string, SubgraphFilter>` | yes | Named source filters — keys become handler keys |
| `def.schema` | `SubgraphSchema` | yes | Tables in this subgraph |
| `def.handlers` | `Record<string, SubgraphHandler>` | yes | Handler functions, one per source, keyed by source name |

### replayBlocks(deps, opts)

The replay loop over an injected verifier.

```ts
replayBlocks(
  deps: ReplayDeps,
  opts: {
    handlerCode: string;
    from: number;
    to: number;
    startBlock?: number;
    onProgress?: (height: number) => void;
  }
  & { bitcoinCheckpoint?: number },
): Promise<ReplayResult>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `deps.verify` | `(height: number) => Promise<BlockVerification>` | yes |  |
| `deps.burnHeightHint` | `(consensusHash: string) => Promise<number>` | yes | The source's (unproven) burn height for a consensus hash. |
| `opts.handlerCode` | `string` | yes |  |
| `opts.from` | `number` | yes |  |
| `opts.to` | `number` | yes |  |
| `opts.startBlock` | `number` | no |  |
| `opts.onProgress` | `(height: number) => void` | no |  |
| `opts.bitcoinCheckpoint` | `number` | no |  |

### replayContracts(def)

Lists the contracts a state subgraph's writes come from.

It returns an error when a source does not fix one, because completeness
would then need every contract's writes.

```ts
replayContracts(
  def: { sources: Record<string, SubgraphFilter> },
): string[] | { error: string }
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `sources` | `Record<string, SubgraphFilter>` | yes |  |

### replaySubgraph(opts)

Verifies every block in a range and recomputes the subgraph's tables.

The range is `[from, to]`, and blocks are verified from the checkpoint.

```ts
replaySubgraph(opts: ReplayOptions): Promise<ReplayResult>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | `ProofSource` | yes | Needs `getStateWrites`: the names are what feed the handlers. |
| `handlerCode` | `string` | yes | The bundled ESM as stored in `handler_code`. |
| `from` | `number` | yes |  |
| `to` | `number` | yes |  |
| `checkpoint` | `VerifyCheckpoint` | no | Defaults to `MAINNET_CHECKPOINT`. |
| `startBlock` | `number` | no | Defaults to the definition's `startBlock`. |
| `onProgress` | `(height: number) => void` | no |  |

### compareRows(schema, replayed, served)

Compares replayed tables with the served ones over a block range.

Rows are matched on `_block_height` within `[from, to]`. Keyed tables, by
their first `uniqueKeys` tuple, compare row by row, and unkeyed tables
compare as multisets.

```ts
compareRows(
  schema: SubgraphSchema,
  replayed: { from: number; to: number; tables: Map<string, Row[]> },
  served: Map<string, Row[]>,
): RowComparison
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `schema` | `SubgraphSchema` | yes |  |
| `replayed.from` | `number` | yes |  |
| `replayed.to` | `number` | yes |  |
| `replayed.tables` | `Map<string, Row[]>` | yes |  |
| `served` | `Map<string, Row[]>` | yes |  |

### Constants

- `REPLAY_WINDOW`: `512`. Verify blocks 512 at a time: the top by one MARF jump, the rest by parent links.

## Testing

Run a handler against fixture events without deploying.

Import from `@secondlayer/subgraphs/testing`.

### buildEvent(source, payload, options)

Builds a handler-shaped event for a source, typed by that source's filter.

`data` supplies the decoded, camelCased fields the runtime would have
produced. Transaction metadata is filled in.

```ts
buildEvent<const F extends SubgraphFilter>(
  source: F,
  payload: Record<string, unknown>,
  options?: {
    tx?: {
      txId?: string;
      sender?: string;
      type?: string;
      status?: string;
      contractId?: string | null;
      functionName?: string | null;
    };
  },
): EventForFilter<F>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | `F` | yes |  |
| `payload` | `Record<string, unknown>` | yes |  |
| `options.tx` | `{ txId?: string; sender?: string; type?: string; status?: string; contractId?: string \| null; functionName?: string \| null }` | no |  |

### buildEventPayload(filter, tx, event)

Build a typed event payload based on the source filter type.
Returns the payload the handler will receive.

```ts
buildEventPayload(
  filter: SubgraphFilter,
  tx: TxRecord,
  event: EventRecord | null,
): Record<string, unknown>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `filter` | `SubgraphFilter` | yes |  |
| `tx.tx_id` | `string` | yes |  |
| `tx.type` | `string` | yes |  |
| `tx.sender` | `string` | yes |  |
| `tx.status` | `string` | yes |  |
| `tx.tx_index` | `number` | no | Position within the block — the runner sorts dispatch into chain order. |
| `tx.contract_id` | `string \| null` | no |  |
| `tx.function_name` | `string \| null` | no |  |
| `tx.function_args` | `unknown` | no |  |
| `tx.raw_result` | `string \| null` | no |  |
| `event` | `EventRecord \| null` | yes |  |

### createTestContext(schema, options)

Builds an in-memory context for a subgraph's schema.

It is the real handler context with its row store swapped for memory, so
reads see earlier writes. Use `ctx.rows(table)` to inspect what a handler
wrote.

```ts
createTestContext<const S extends SubgraphSchema>(
  schema: S,
  options?: {
    block?: {
      height?: number;
      hash?: string;
      timestamp?: number;
      burnBlockHeight?: number;
      indexBlockHash?: string | null;
    };
    tx?: {
      txId?: string;
      sender?: string;
      type?: string;
      status?: string;
      contractId?: string | null;
      functionName?: string | null;
    };
    reads?: Record<string, unknown>;
  },
): TestSubgraphContext<S>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `schema` | `S` | yes |  |
| `options.block` | `{ height?: number; hash?: string; timestamp?: number; burnBlockHeight?: number; indexBlockHash?: string \| null }` | no |  |
| `options.tx` | `{ txId?: string; sender?: string; type?: string; status?: string; contractId?: string \| null; functionName?: string \| null }` | no |  |
| `options.reads` | `Record<string, unknown>` | no |  |

### probeHandlers(def, samples)

Run handlers against pre-built sample payloads in memory (no Index, no
Postgres). Deploy uses this with print-schema dummy events to refuse
EMPTY_MAPPING before DDL apply.

```ts
probeHandlers(
  def: ProbeHandlersDef,
  samples: { source: string; event: Record<string, unknown> }[],
): Promise<ProbeHandlersResult>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def.schema` | `SubgraphSchema` | yes |  |
| `def.sources` | `Record<string, { type: string; [key: string]: unknown }>` | yes |  |
| `def.handlers` | `Record<string, unknown>` | yes |  |
| `samples` | `{ source: string; event: Record<string, unknown> }[]` | yes |  |

### runSubgraphTest(input)

Apply local handlers to Index rows in memory. Fail-closed: matched events
with zero written rows is EMPTY_MAPPING (the bns-names field-mapping shape).

```ts
runSubgraphTest(input: RunSubgraphTestInput): Promise<SubgraphTestResult>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `schema` | `SubgraphSchema` | yes |  |
| `handlers` | `Record<string, unknown>` | yes |  |
| `sources` | `Record<string, SubgraphTestSource>` | yes |  |
| `events` | `Record<string, IndexTestRow[]>` | yes |  |
| `trace` | `boolean` | no | Collect per-event IN/OUT traces (preview). |

### toContractCallPayload(row)

Map an Index contract-call row onto the ContractCallEvent handler shape.

```ts
toContractCallPayload(row: IndexContractCallRow): Record<string, unknown>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `sender` | `string` | yes |  |
| `contract_id` | `string \| null` | no |  |
| `function_name` | `string \| null` | no |  |
| `args` | `unknown` | no |  |
| `result` | `unknown` | no |  |
| `result_hex` | `unknown` | no |  |
| `tx_id` | `string` | yes |  |
| `status` | `string` | yes |  |
| `cursor` | `string` | no |  |

### toHandlerPayload(_filter, row)

Map an Index event row onto the payload shape a handler expects.

```ts
toHandlerPayload(
  _filter: { type: string } | undefined,
  row: IndexEventRow,
): Record<string, unknown>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `_filter` | `{ type: string } \| undefined` | yes |  |
| `row.event_type` | `string` | yes |  |
| `row.contract_id` | `string \| null` | no |  |
| `row.payload` | `unknown` | no | Print events carry `{ topic, value }`; other types vary. |
| `row.sender` | `unknown` | no |  |
| `row.recipient` | `unknown` | no |  |
| `row.amount` | `unknown` | no |  |
| `row.asset_identifier` | `unknown` | no |  |
| `row.value` | `unknown` | no |  |
| `row.cursor` | `string` | no |  |

## Types

- `AggregateResult`: Result shape inferred from an `AggregateSpec`.
- `AnyEvent`: Union of every event payload.
- `ChainReadCacheMode`: How long a read stays valid.
- `ChainReadError`: A handler asked for something no node can answer: a function missing from the ABI, or one that isn't read-only.
- `ChainReadMethods`: The camelCased read methods of an ABI.
- `ChainReadOptions`: Options for a contract read, such as cache mode.
  - `cache`?: `ChainReadCacheMode`
- `ColumnDiff`
  - `added`: `string[]`
  - `removed`: `string[]`
  - `changed`: `string[]`. Data-shape changes (type/nullable/default) — breaking.
  - `indexChanged`: `string[]`. Only the `indexed`/`search` flags differ — index DDL, never a rebuild.
- `ColumnToTS`: Maps a ColumnType string literal to its TypeScript equivalent
- `ColumnType`: Supported column types for subgraph schemas
- `ComparisonFilter`: Comparison operators for a scalar value
  - `eq`?: `T`
  - `neq`?: `T`
  - `gt`?: `T`
  - `gte`?: `T`
  - `lt`?: `T`
  - `lte`?: `T`
  - `in`?: `T[]`. Match any value in the set.
  - `notIn`?: `T[]`. Match none of the values in the set.
  - `like`?: `string`. SQL ILIKE pattern (case-insensitive); `%`/`_` wildcards. Strings only.
- `ConstraintDiff`
  - `addedUniqueKeys`: `string[][]`. New uniqueKeys entries → `ALTER TABLE … ADD CONSTRAINT` (additive).
  - `removedUniqueKeys`: `string[][]`. Removed uniqueKeys entries — breaking: handler upserts may target them.
  - `addedIndexes`: `string[][]`. New composite `indexes` entries → CREATE INDEX (additive).
  - `removedIndexes`: `string[][]`. Removed composite `indexes` entries → DROP INDEX (additive).
- `ContractCallEvent`: Event shape passed to contract_call handlers.
  - `type`: `"contract_call"`
  - `sender`: `string`. Transaction sender (the principal who signed the tx). Always non-null.
  - `contractId`: `string`
  - `functionName`: `string`
  - `args`: `unknown[]`. Positional decoded Clarity values — order matches the ABI parameter list.
  - `result`: `unknown`. Decoded return value from the contract function, or null.
  - `resultHex`: `string \| null`. Raw hex-encoded result value.
  - `tx`: `{ txId: string; sender: string; type: string; status: string; contractId: string \| null; functionName: string \| null }`. Transaction metadata.
- `ContractCallFilter`: Contract event filters
  - `type`: `"contract_call"`
  - `contractId`?: `string \| readonly string[]`. One contract id, or a SET of them (max 20) — the standard DeFi shape is a router plus N pools, which used to cost N near-identical sources and handlers. Payload types are identical per `type`, so one handler serves the set and `event.contractId` disambiguates. Supports `*` wildcards (e.g. `"SP1ABC….pool-*"`). Mutually exclusive with `trait`, which resolves its own set per block.
  - `functionName`?: `string`
  - `caller`?: `string`
  - `abi`?: `AbiContract`. Contract ABI (pass it `as const`) used to type `event.input` — the named, decoded function arguments. Dev-provided; serialized into the deployed definition and used at runtime to decode args by name. Omit to keep `event.args` as a positional `unknown[]`. Required whenever `functionName` is set. `secondlayer subgraphs deploy` fills it in from the deployed contract for a single concrete `contractId`; pass it yourself for wildcard, multi-contract or trait sources, or a local deploy (`normalizeAbi()` from `@secondlayer/stacks/clarity`).
  - `trait`?: `string`
  - `factory`?: `{ from: string; field: string }`
- `ContractCallPayload`: Contract-call payload.
- `ContractDeployFilter`
  - `type`: `"contract_deploy"`
  - `deployer`?: `string`
  - `contractName`?: `string`
- `ContractDeployPayload`
  - `contractId`: `string`
  - `deployer`: `string`
  - `tx`: `TxMeta`
- `DeployDiff`: The tables and columns a deploy adds, removes, or breaks.
  - `addedTables`: `string[]`
  - `removedTables`: `string[]`
  - `addedColumns`: `Record<string, string[]>`
  - `breakingChanges`: `string[]`
  - `indexChanges`?: `Record<string, string[]>`. Columns whose only change was the `indexed`/`search` flag (index DDL applied in place).
  - `addedUniqueKeys`?: `Record<string, string[][]>`. uniqueKeys added in place via ALTER TABLE … ADD CONSTRAINT.
- `DeployPlan`: The schema name and SQL a deploy will run.
  - `schemaName`: `string`
  - `dropStatement`: `string`. `DROP SCHEMA … CASCADE` a destructive rebuild would run first.
  - `statements`: `string[]`. DDL the deploy will run.
- `DrizzleGenOptions`: Options for generating Drizzle table definitions.
  - `schemaName`?: `string`
- `ErasedChainReadClient`: `ChainReadClient` with the ABI generic erased.
  - `contract`: `(contractId: string, abi: unknown, options?: ChainReadOptions) => { read: Record<string, (args?: Record<string, unknown>) => Promise<unknown>> }`. Reads a contract's read-only functions at the block being processed. Results are untyped here. Call it through `readContractAt` to get typed results from the contract's ABI.
- `EventForFilter`: The event payload a handler receives for a given source filter.
- `EventRecord`: An event as a subgraph source matcher reads it.
  - `id`: `string`
  - `tx_id`: `string`
  - `type`: `string`
  - `event_index`: `number`. Classic `event_index`, or `ordinal` when `clock === "vm"`.
  - `data`: `unknown`
  - `clock`?: `"vm"`. Set on opt-in `vm_events` rows. Two clocks never sort or dedupe together.
- `EventTrace`: Per-event IN/OUT trace for `subgraphs preview`.
  - `source`: `string`
  - `blockHeight`?: `number`
  - `txId`?: `string`
  - `inKeys`: `string[]`. Camelized event.data keys (or contract-call field names).
  - `outs`: `{ table: string; keys: string[] }[]`. Tables written by this event; empty when the mapping wrote nothing.
- `FactoryScope`: Scope a source to a DYNAMIC address set discovered from another source's events, instead of a fixed contract list.
  - `factory`?: `{ from: string; field: string }`
- `FindManyOptions`
  - `where`?: `{ [K in keyof TRow]?: TRow[K] \| ComparisonFilter<TRow[K]>; } & SystemWhereAliases`
  - `orderBy`?: `unknown & SystemOrderByAliases`. Single-column object, e.g. `{ price: "desc" }`. `/v1` sorts by one column only (the keyset cursor pairs it with `_id` as a tiebreaker) — a second key throws at call time, since a plain object type can't reject "more than one key" at compile time.
  - `limit`?: `number`
  - `cursor`?: `string`. Resume from a previous page's `nextCursor`.
  - `fields`?: `(keyof TRow & string)[]`
- `FindManyPage`: A page of rows from `findMany`, keyset-paginated.
  - `rows`: `TRow[]`
  - `nextCursor`: `string \| null`
  - `tip`: `SubgraphTip`
- `FtBurnFilter`
  - `type`: `"ft_burn"`
  - `assetIdentifier`?: `AssetIdentifier`
  - `sender`?: `string`
  - `minAmount`?: `bigint`
  - `trait`?: `string`
- `FtBurnPayload`
  - `sender`: `string`
  - `amount`: `bigint`
  - `assetIdentifier`: `string`
  - `tx`: `TxMeta`
- `FtMintFilter`
  - `type`: `"ft_mint"`
  - `assetIdentifier`?: `AssetIdentifier`
  - `recipient`?: `string`
  - `minAmount`?: `bigint`
  - `trait`?: `string`
- `FtMintPayload`
  - `recipient`: `string`
  - `amount`: `bigint`
  - `assetIdentifier`: `string`
  - `tx`: `TxMeta`
- `FtTransferFilter`: FT event filters
  - `type`: `"ft_transfer"`
  - `assetIdentifier`?: `AssetIdentifier`
  - `sender`?: `string`
  - `recipient`?: `string`
  - `minAmount`?: `bigint`
  - `trait`?: `string`
- `FtTransferPayload`
  - `sender`: `string`
  - `recipient`: `string`
  - `amount`: `bigint`
  - `assetIdentifier`: `string`
  - `tx`: `TxMeta`
- `GeneratedSQL`: The SQL statements for a subgraph's tables and their hash.
  - `statements`: `string[]`
  - `hash`: `string`
- `HandlerFinding`: One thing the deploy scan found in a bundled handler.
  - `kind`: `"nondeterministic" \| "needs-events"`
  - `name`: `string`. What was used, e.g. `Date`, `Math.random`, `import()`, `ctx.client`.
  - `reason`: `string`
  - `file`: `string`. Original source file when a sourcemap was available, else the bundle.
  - `line`: `number`. 1-based.
  - `column`: `number`. 1-based.
- `HandlerReadError`: A ctx read failed for a reason outside the handler: the database under a verifiable subgraph, or a chain read (`ctx.client`) on any subgraph that had no node, a node that didn't answer, or a cache error.
- `IndexCodegenOptions`: Options for generating the Index schema: tables, schema name.
  - `tables`?: `string[]`. Restrict output to these Index tables (default: all).
  - `schemaName`?: `string`. Postgres schema to qualify table names with (default: bare names).
  - `datasourceEnv`?: `string`. Prisma only: datasource url env var (default DATABASE_URL).
- `IndexCodegenTarget`: A portable, typed schema target for the public Index read domain.
- `IndexContractCallRow`: Structural Index contract-call row.
  - `sender`: `string`
  - `contract_id`?: `string \| null`
  - `function_name`?: `string \| null`
  - `args`?: `unknown`
  - `result`?: `unknown`
  - `result_hex`?: `unknown`
  - `tx_id`: `string`
  - `status`: `string`
  - `cursor`?: `string`
- `IndexEventRow`: Structural Index event row (avoids an SDK dep on this package).
  - `event_type`: `string`
  - `contract_id`?: `string \| null`
  - `payload`?: `unknown`. Print events carry `{ topic, value }`; other types vary.
  - `sender`?: `unknown`
  - `recipient`?: `unknown`
  - `amount`?: `unknown`
  - `asset_identifier`?: `unknown`
  - `value`?: `unknown`
  - `cursor`?: `string`
- `IndexTestRow`: `IndexEventRow \| IndexContractCallRow`
- `InferColumnType`: Infer TS type for a single column definition, respecting nullable
- `InferContext`: The typed handler context for a subgraph definition — for annotating helpers extracted out of a handler:
- `InferredPrintField`
  - `name`: `string`. Original kebab-case tuple key (the `topic` discriminant is excluded)
  - `camel_name`: `string`. What handlers see on `e.data` — exact runner camelization
  - `clarity_type`: `string`. Rendered Clarity type; buff/string lengths are the max observed
  - `ts_type`: `string`. Decoded handler value type (uint→bigint, buffer→string, …)
  - `column_type`: `ColumnType`
  - `always_present`: `boolean`. Present in 100% of this topic's decoded samples
  - `optional_some_rate`?: `number`. Only when optional observed: share of present samples that were some
- `InferredTopicSchema`: The fields seen in one `print` topic, with counts and heights.
  - `topic`: `string`
  - `count`: `number`
  - `first_height`: `number`
  - `last_height`: `number`
  - `non_tuple`: `boolean`
  - `fields`: `InferredPrintField[]`
- `InferSubgraphClient`: Infer a typed client object from a subgraph definition shape
- `InferTableRow`: Infer the row shape for a single table definition
- `KyselyGenOptions`: Options for generating Kysely table interfaces.
  - `schemaName`?: `string`. Postgres schema the tables live in (account-scoped).
- `MapDeleteEvent`
  - `contractId`: `string`
  - `map`: `string`
  - `rawKey`: `string`
  - `key`: `unknown`
  - `tx`: `TxMeta`
- `MapDeletePayload`: `F extends { abi: infer A extends AbiContract; map: infer N extends string; } ? N extends ExtractMapNames<A> ? Omit<MapDeleteEvent, "key"> & { key: ExtractMapKey<A, N> } : MapDeleteEvent : MapDeleteEvent`
- `MapWriteEvent`
  - `contractId`: `string`
  - `map`: `string`
  - `rawKey`: `string`
  - `key`: `unknown`
  - `rawValue`: `string`
  - `value`: `unknown`
  - `tx`: `TxMeta`
- `MapWriteFilter`
  - `type`: `"map_set" \| "map_insert" \| "map_delete"`
  - `contractId`?: `string \| readonly string[]`
  - `map`?: `string`
  - `abi`?: `AbiContract`. Contract ABI. With a `map` the abi defines, `event.key` and `event.value` are typed and decoded from the map's types. Pass it yourself (`normalizeAbi()` from `@secondlayer/stacks/clarity`).
  - `trait`?: `string`
  - `factory`?: `{ from: string; field: string }`
- `MapWritePayload`: `F extends { abi: infer A extends AbiContract; map: infer N extends string; } ? N extends ExtractMapNames<A> ? Omit<MapWriteEvent, "key" \| "value"> & { key: ExtractMapKey<A, N>; value: ExtractMapValue<A, N>; } : MapWriteEvent : MapWriteEvent`
- `NestedContractCallFilter`
  - `type`: `"nested_contract_call"`
  - `contractId`?: `string \| readonly string[]`
  - `functionName`?: `string`
  - `caller`?: `string`
  - `sender`?: `string`
  - `trait`?: `string`
  - `factory`?: `{ from: string; field: string }`
- `NestedContractCallPayload`
  - `contractId`: `string`
  - `sender`: `string \| null`
  - `caller`: `string`
  - `functionName`: `string`
  - `args`: `unknown[]`. Decoded `function_args`.
  - `arguments`: `string[]`. Original hex `function_args`, preserved.
  - `rawResult`: `string`
  - `result`: `unknown`
  - `tx`: `TxMeta`
- `NftBurnFilter`
  - `type`: `"nft_burn"`
  - `assetIdentifier`?: `AssetIdentifier`
  - `sender`?: `string`
  - `trait`?: `string`
- `NftBurnPayload`
  - `sender`: `string`
  - `tokenId`: `unknown`
  - `assetIdentifier`: `string`
  - `tx`: `TxMeta`
- `NftMintFilter`
  - `type`: `"nft_mint"`
  - `assetIdentifier`?: `AssetIdentifier`
  - `recipient`?: `string`
  - `trait`?: `string`
- `NftMintPayload`
  - `recipient`: `string`
  - `tokenId`: `unknown`
  - `assetIdentifier`: `string`
  - `tx`: `TxMeta`
- `NftTransferFilter`: NFT event filters
  - `type`: `"nft_transfer"`
  - `assetIdentifier`?: `AssetIdentifier`
  - `sender`?: `string`
  - `recipient`?: `string`
  - `trait`?: `string`
- `NftTransferPayload`
  - `sender`: `string`
  - `recipient`: `string`
  - `tokenId`: `unknown`
  - `assetIdentifier`: `string`
  - `tx`: `TxMeta`
- `NondeterminismError`: A handler broke the determinism contract at runtime.
- `PinCheck`: Whether a served pin matches the served bundle.
- `PinInput`: What a pin is computed from: schema hash, handler code, network.
  - `schemaHash`: `string`. Existing `schema_hash` (name + schema + sources).
  - `handlerCode`: `string`. Bundled ESM exactly as stored in `subgraphs.handler_code`.
  - `startBlock`?: `number \| null`
  - `network`: `string`
  - `runtime`?: `string`. Defaults to [`SUBGRAPHS_RUNTIME`](/docs/subgraphs-reference#c-SUBGRAPHS_RUNTIME).
- `PinnedPrintEventFilter`: Pinned to one or more contract ids with no trait — `prints` is required so `event.data` is typed and empty-table handlers cannot typecheck.
  - `factory`?: `{ from: string; field: string }`
  - `type`: `"print_event"`
  - `topic`?: `string`
  - `where`?: `Record<string, string \| readonly string[]>`. Equality on top-level keys of the decoded print tuple, spelled as the contract prints them (`signer-manager`). A value matches the field's JSON form: principals `SP…`, uints as decimal strings, bools `"true"`/`"false"`, buffers `0x…`. An array is any-of; keys AND. Nested tuples and lists never match.
  - `contractId`: `string \| readonly string[]`
  - `trait`?: `undefined`
  - `prints`: `PrintEventPrints`
- `PrintDataOf`: `event.data` for one topic: declared fields typed, with `{ optional: true }` fields made optional KEYS (a field observed on only some events must not be typed as always present).
- `PrintEventFilter`: Print-event filter.
- `PrintEventFor`: Print event typed per topic.
- `PrintEventPayload`
  - `contractId`: `string`
  - `topic`: `string`. Decoded print topic (the Clarity tuple's `topic` field), or "".
  - `data`: `Record<string, unknown>`. Remaining decoded tuple fields, camelCased. Empty object when the print value isn't a tuple. Narrow per `topic` to access typed fields.
  - `tx`: `TxMeta`
- `PrintEventPrints`: Per-topic field schema for a print_event source.
- `PrintField`: One declared field of a print payload.
- `PrintFieldToTS`: TS type of one declared print field (nested tuples and lists included).
- `PrintSample`: Empirical print-payload schema inference.
  - `blockHeight`: `number`
  - `topic`: `string`
  - `rawHex`: `string \| null`
- `PrintValidationResult`: `{ ok: true; } \| { ok: false; reason: string; }`
- `PrismaGenOptions`: Options for generating Prisma models.
  - `schemaName`?: `string`. Postgres schema the tables live in (account-scoped).
  - `datasourceEnv`?: `string`. env var the datasource url reads from.
  - `modelsOnly`?: `boolean`. Emit only the `model` blocks (no datasource/generator). Lets users compose these models with their own schema via Prisma's `prismaSchemaFolder`.
- `ProbeHandlersDef`: A subgraph's schema, sources, and handlers, for a probe run.
  - `schema`: `SubgraphSchema`
  - `sources`: `Record<string, { type: string; [key: string]: unknown }>`
  - `handlers`: `Record<string, unknown>`
- `ProbeHandlersResult`: How many events matched and rows a probe wrote.
  - `matched`: `number`
  - `written`: `number`
  - `tables`: `string[]`
  - `firstEventKeys`?: `string[]`
- `ReplayDeps`: Seams for tests: the block verifier and the burn height hint.
  - `verify`: `(height: number) => Promise<BlockVerification>`
  - `burnHeightHint`: `(consensusHash: string) => Promise<number>`. The source's (unproven) burn height for a consensus hash.
- `ReplayFailure`
  - `step`: `ReplayStep`
  - `message`: `string`
  - `height`?: `number`
  - `table`?: `string`
  - `key`?: `Record<string, unknown>`
  - `unavailable`?: `boolean`. The source could not serve the input: unchecked, not disproven.
- `ReplayOptions`: What to replay: proof source, handler bundle, and block range.
  - `source`: `ProofSource`. Needs `getStateWrites`: the names are what feed the handlers.
  - `handlerCode`: `string`. The bundled ESM as stored in `handler_code`.
  - `from`: `number`
  - `to`: `number`
  - `checkpoint`?: `VerifyCheckpoint`. Defaults to `MAINNET_CHECKPOINT`.
  - `startBlock`?: `number`. Defaults to the definition's `startBlock`.
  - `onProgress`?: `(height: number) => void`
- `ReplayResult`: The outcome of a replay: pass or fail, blocks covered, failures.
  - `ok`: `boolean`
  - `inconclusive`?: `string`. Set when the range starts after startBlock and a handler read or merged earlier rows.
  - `from`: `number`
  - `to`: `number`
  - `blocks`: `number`
  - `txs`: `number`
  - `writes`: `number`. Named writes to the subgraph's contracts.
  - `events`: `number`. Events offered to a handler.
  - `handlerErrors`: `number`
  - `marfProofs`: `number`. MARF proofs the walk took (one per window top below the checkpoint).
  - `tables`: `Map<string, Record<string, unknown>[]>`. The memory store at `to`.
  - `notes`: `string[]`
  - `failures`: `ReplayFailure[]`. The first entry is the first broken link.
- `ReplayStep`: `"pin" \| "blocks" \| "txs" \| "inputs" \| "handlers" \| "rows"`
- `Row`: A table row as a plain column-to-value record.
- `RowComparison`: Replayed tables compared with served tables, and any mismatches.
  - `failures`: `ReplayFailure[]`
  - `tables`: `TableComparison[]`
- `RowValue`: Scalar types that can appear as row values
- `RunSubgraphTestInput`: A subgraph's schema, handlers, sources, and the events to run.
  - `schema`: `SubgraphSchema`
  - `handlers`: `Record<string, unknown>`
  - `sources`: `Record<string, SubgraphTestSource>`
  - `events`: `Record<string, IndexTestRow[]>`
  - `trace`?: `boolean`. Collect per-event IN/OUT traces (preview).
- `SparseProbeTarget`: A (decoded event type, optional contract scope) pair the sparse probe checks.
  - `eventType`: `string`
  - `contractId`?: `string`
- `StxBurnFilter`
  - `type`: `"stx_burn"`
  - `sender`?: `string`
  - `minAmount`?: `bigint`
- `StxBurnPayload`
  - `sender`: `string`
  - `amount`: `bigint`
  - `tx`: `TxMeta`
- `StxLockFilter`
  - `type`: `"stx_lock"`
  - `lockedAddress`?: `string`
  - `minAmount`?: `bigint`
- `StxLockPayload`
  - `lockedAddress`: `string`
  - `lockedAmount`: `bigint`
  - `unlockHeight`: `bigint`
  - `tx`: `TxMeta`
- `StxMintFilter`
  - `type`: `"stx_mint"`
  - `recipient`?: `string`
  - `minAmount`?: `bigint`
- `StxMintPayload`
  - `recipient`: `string`
  - `amount`: `bigint`
  - `tx`: `TxMeta`
- `StxTransferFilter`: STX event filters
  - `type`: `"stx_transfer"`
  - `sender`?: `string`
  - `recipient`?: `string`
  - `minAmount`?: `bigint`
  - `maxAmount`?: `bigint`
- `StxTransferPayload`
  - `sender`: `string`
  - `recipient`: `string`
  - `amount`: `bigint`
  - `memo`: `string`. Memo string, or "" when none was attached.
  - `tx`: `TxMeta`
- `SubgraphColumn`: Column definition in a subgraph table
  - `type`: `ColumnType`
  - `nullable`?: `boolean`
  - `indexed`?: `boolean`
  - `search`?: `boolean`
  - `default`?: `string \| number \| boolean`
- `SubgraphContext`: Context passed to subgraph handlers during event processing
  - `block`: `{ height: number; hash: string; timestamp: number; burnBlockHeight: number; indexBlockHash?: string \| null }`
  - `tx`: `TxMeta`
  - `client`: `ErasedChainReadClient`. Read-only contract calls pinned to this block. Cached, so the same (contract, function, args) is fetched once per block rather than once per event. Erased here on purpose: this interface is compared structurally wherever a subgraph definition is passed around, and carrying an ABI-generic method pushes those comparisons past TS's instantiation depth. Handlers receive `TypedSubgraphContext`, which exposes the fully typed version.
  - `insert`: `(table: string, row: Record<string, unknown>) => void`
  - `update`: `(table: string, where: Record<string, unknown>, set: Record<string, unknown>) => void`
  - `upsert`: `(table: string, key: Record<string, unknown>, row: Record<string, unknown>) => void`
  - `delete`: `(table: string, where: Record<string, unknown>) => void`
  - `increment`: `(table: string, key: Record<string, unknown>, deltas: Record<string, number \| bigint>) => void`. Atomic counter update — the blessed accumulator primitive. Applies `col = COALESCE(col, 0) + delta` per column (insert-or-add); deltas may be negative. Requires a uniqueKeys constraint matching `key`. Prefer this over read-modify-write handlers for running totals.
  - `findOne`: `(table: string, where: Record<string, unknown>) => Promise<Record<string, unknown> \| null>`
  - `findMany`: `(table: string, where: Record<string, unknown>) => Promise<Record<string, unknown>[]>`
- `SubgraphDefinition`: Complete subgraph definition
  - `name`: `string`. Unique subgraph name (lowercase, alphanumeric + hyphens)
  - `description`?: `string`. Human description
  - `startBlock`?: `number`. Block height to start indexing from (default: 1)
  - `backfillMode`?: `"blocking" \| "concurrent"`. 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'.
  - `sources`: `Record<string, SubgraphFilter>`. Named source filters — keys become handler keys
  - `schema`: `SubgraphSchema`. Tables in this subgraph
  - `handlers`: `Record<string, SubgraphHandler>`. Handler functions, one per source, keyed by source name
- `SubgraphFilter`: A subgraph filter, discriminated on `type`.
- `SubgraphHandler`: Handler function that processes events and writes to the subgraph
- `SubgraphSchema`: Maps table names to table definitions.
- `SubgraphTable`: Table definition within a subgraph schema.
  - `columns`: `Record<string, SubgraphColumn>`
  - `indexes`?: `readonly readonly string[][]`. Composite indexes (each entry is an array of column names)
  - `uniqueKeys`?: `readonly readonly string[][]`. Unique key constraints (each entry is an array of column names). Required for upsert.
- `SubgraphTestResult`: The outcome of a test run: matches, writes, and table rows.
  - `ok`: `boolean`
  - `code`?: `"EMPTY_MAPPING" \| "NO_EVENTS" \| "NO_SOURCES"`
  - `matched`: `number`
  - `written`: `number`
  - `tables`: `{ name: string; rows: number; sampleRow?: Record<string, unknown> }[]`
  - `firstEvent`?: `{ source: string; data?: unknown }`
  - `hint`?: `string`
  - `traces`?: `EventTrace[]`. Present when `trace: true`.
  - `unusedInKeys`?: `string[]`. Keys present on every matched IN and never written to any OUT column.
- `SubgraphTestSource`
  - `type`: `string`
  - `contractId`?: `string \| string[]`
  - `topic`?: `string`
  - `where`?: `Record<string, string \| readonly string[]>`
  - `functionName`?: `string`
- `SubgraphTip`: Wire tip: chain height, subgraph's processed height, and the gap between them — same object on every `/v1` row/page response.
  - `block_height`: `number`
  - `subgraph_height`: `number`
  - `blocks_behind`: `number`
- `SubscribeOptions`: Options for the realtime row stream (SSE).
  - `where`?: `{ [K in keyof TRow]?: TRow[K] \| ComparisonFilter<TRow[K]>; } & SystemWhereAliases`. Same column filters as `findMany`, applied server-side per row.
  - `since`?: `number`. Replay rows from this block height, then tail live. Omit for go-forward only.
  - `onError`?: `(err: unknown) => void`. Called on stream/connection errors.
- `SystemOrderByAliases`
  - `blockHeight`?: `"asc" \| "desc"`
  - `txId`?: `"asc" \| "desc"`
  - `createdAt`?: `"asc" \| "desc"`
  - `id`?: `"asc" \| "desc"`
- `SystemRow`: Shape of system columns on every returned row (camelCase, underscore-prefixed)
  - `_id`: `string`
  - `_blockHeight`: `bigint`
  - `_txId`: `string`
  - `_createdAt`: `string`
- `SystemWhereAliases`: No-prefix aliases for system columns accepted in where/orderBy inputs.
  - `blockHeight`?: `bigint \| ComparisonFilter<bigint>`
  - `txId`?: `string \| ComparisonFilter<string>`
  - `createdAt`?: `string \| ComparisonFilter<string>`
  - `id`?: `string \| ComparisonFilter<string>`
- `TableComparison`
  - `table`: `string`
  - `equal`: `number`. Replayed rows the served table holds identically.
  - `superseded`: `number`. Replayed keys the served table re-created after `to`: not checked.
  - `digest`: `string`. sha256 over the sorted canonical replayed rows in the window, hex.
  - `servedDigest`: `string`. The same digest over the served rows in the window.
- `TableDiff`: Tables added, removed, or changed between two schemas.
  - `addedTables`: `string[]`. Tables added to the schema
  - `removedTables`: `string[]`. Tables removed from the schema
  - `tables`: `Record<string, ColumnDiff>`. Per-table column diffs (only for tables present in both)
  - `constraints`: `Record<string, ConstraintDiff>`. Per-table uniqueKeys / composite-index diffs (tables present in both)
- `TestSubgraphContext`: An in-memory subgraph context for tests.
  - `rows`: `(table: keyof S & string) => Promise<Record<string, unknown>[]>`. Current rows of `table`, INCLUDING writes queued by the handler you just ran (the same read-your-writes overlay the runtime uses). Drop straight into `toMatchInlineSnapshot()`.
  - `commit`: `() => Promise<void>`. Commit pending writes into the in-memory store, as an end-of-block flush would — call between blocks when a test spans several.
  - `setTx`: `(tx: { txId?: string; sender?: string; type?: string; status?: string; contractId?: string \| null; functionName?: string \| null }) => void`. Swap the transaction metadata handlers see (`ctx.tx`).
  - `pendingInsertsSince`: `(checkpoint: number) => { table: string; keys: string[]; row: Record<string, unknown> }[]`. Insert ops queued since `opsCheckpoint()`, for preview IN/OUT traces.
  - `opsCheckpoint`: `() => number`. Length of the pending-ops queue (same as runtime `opsCheckpoint`).
  - `tx`: `TxMeta`
  - `block`: `{ height: number; hash: string; timestamp: number; burnBlockHeight: number; indexBlockHash?: string \| null }`
  - `client`: `ErasedChainReadClient`. Read-only contract calls pinned to this block. Erased here for the same reason as on `SubgraphContext`: an ABI-generic method on a structurally compared interface exceeds TS's instantiation budget. Wrap it with `readContractAt(ctx, id, abi)` for full ABI typing at the call site.
  - `insert`: `(table: T, row: WriteRow<S[T]>) => void`. Queues a new row for a table. Writes are batched and applied together when the block commits. `_block_height` and `_tx_id` are filled in for you.
  - `update`: `(table: T, where: RowWhere<S, T>, set: Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>) => void`. Queues an update to the rows matching `where`. `set` holds only the columns to change.
  - `upsert`: `(table: T, key: K, row: Omit<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }, keyof K> & Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>) => void`. Inserts a row, or updates the one with the same key. `key` should match a `uniqueKeys` constraint of the table. Without one the write falls back to a lookup first and logs a warning.
  - `delete`: `(table: T, where: RowWhere<S, T>) => void`. Queues deletion of the rows matching `where`.
  - `increment`: `(table: T, key: Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>, deltas: Record<string, Record<ColumnName<S, T>, number \| bigint>[string] \| undefined>) => void`. Adds signed deltas to a counter row, creating it if missing. It compiles to `INSERT … ON CONFLICT (key) DO UPDATE SET col = COALESCE(col,0) + delta`. Deltas commute, so it is the reorg-safe way to keep running totals such as per-cycle aggregates and balances. It needs a `uniqueKeys` constraint matching `key`, and deltas may be `number` or `bigint`.
  - `findOne`: `(table: T, where: RowWhere<S, T>) => Promise<SystemRow & { [K in keyof T["columns"]]: InferColumnType<T["columns"][K]>; } \| null>`. Returns the first row matching `where`, or `null`. Reads see the writes queued earlier in the same block, and rows come back in insertion order.
  - `findMany`: `(table: T, where: RowWhere<S, T>) => Promise<SystemRow & { [K in keyof T["columns"]]: InferColumnType<T["columns"][K]>; }[]>`. Returns every row matching `where`, in insertion order. Reads see the writes queued earlier in the same block.
- `TraitPrintEventFilter`: Trait-scoped print source (optionally narrowed by contractId).
  - `factory`?: `{ from: string; field: string }`
  - `type`: `"print_event"`
  - `topic`?: `string`
  - `where`?: `Record<string, string \| readonly string[]>`. Equality on top-level keys of the decoded print tuple, spelled as the contract prints them (`signer-manager`). A value matches the field's JSON form: principals `SP…`, uints as decimal strings, bools `"true"`/`"false"`, buffers `0x…`. An array is any-of; keys AND. Nested tuples and lists never match.
  - `contractId`?: `string \| readonly string[]`
  - `trait`: `string`
  - `prints`?: `PrintEventPrints`
- `TxMeta`: Transaction metadata available in handlers
  - `txId`: `string`
  - `sender`: `string`
  - `type`: `string`
  - `status`: `string`
  - `contractId`?: `string \| null`
  - `functionName`?: `string \| null`
- `TxRecord`: A transaction as a subgraph source matcher reads it.
  - `tx_id`: `string`
  - `type`: `string`
  - `sender`: `string`
  - `status`: `string`
  - `tx_index`?: `number`. Position within the block — the runner sorts dispatch into chain order.
  - `contract_id`?: `string \| null`
  - `function_name`?: `string \| null`
  - `function_args`?: `unknown`
  - `raw_result`?: `string \| null`
- `TypedHandlers`: Handlers keyed by source name, one per source.
- `TypedSubgraphContext`: Schema-typed handler context.
  - `block`: `{ height: number; hash: string; timestamp: number; burnBlockHeight: number; indexBlockHash?: string \| null }`
  - `tx`: `TxMeta`
  - `client`: `ErasedChainReadClient`. Read-only contract calls pinned to this block. Erased here for the same reason as on `SubgraphContext`: an ABI-generic method on a structurally compared interface exceeds TS's instantiation budget. Wrap it with `readContractAt(ctx, id, abi)` for full ABI typing at the call site.
  - `insert`: `(table: T, row: WriteRow<S[T]>) => void`. Queues a new row for a table. Writes are batched and applied together when the block commits. `_block_height` and `_tx_id` are filled in for you.
  - `update`: `(table: T, where: RowWhere<S, T>, set: Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>) => void`. Queues an update to the rows matching `where`. `set` holds only the columns to change.
  - `upsert`: `(table: T, key: K, row: Omit<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }, keyof K> & Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>) => void`. Inserts a row, or updates the one with the same key. `key` should match a `uniqueKeys` constraint of the table. Without one the write falls back to a lookup first and logs a warning.
  - `delete`: `(table: T, where: RowWhere<S, T>) => void`. Queues deletion of the rows matching `where`.
  - `increment`: `(table: T, key: Partial<{ [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? never : K]: WriteColumnType<T["columns"][K]>; } & { [K in keyof T["columns"] as IsOptionalColumn<T["columns"][K]> extends true ? K : never]?: WriteColumnType<T["columns"][K]>; }>, deltas: Record<string, Record<ColumnName<S, T>, number \| bigint>[string] \| undefined>) => void`. Adds signed deltas to a counter row, creating it if missing. It compiles to `INSERT … ON CONFLICT (key) DO UPDATE SET col = COALESCE(col,0) + delta`. Deltas commute, so it is the reorg-safe way to keep running totals such as per-cycle aggregates and balances. It needs a `uniqueKeys` constraint matching `key`, and deltas may be `number` or `bigint`.
  - `findOne`: `(table: T, where: RowWhere<S, T>) => Promise<SystemRow & { [K in keyof T["columns"]]: InferColumnType<T["columns"][K]>; } \| null>`. Returns the first row matching `where`, or `null`. Reads see the writes queued earlier in the same block, and rows come back in insertion order.
  - `findMany`: `(table: T, where: RowWhere<S, T>) => Promise<SystemRow & { [K in keyof T["columns"]]: InferColumnType<T["columns"][K]>; }[]>`. Returns every row matching `where`, in insertion order. Reads see the writes queued earlier in the same block.
- `TypedSubgraphDefinition`: A subgraph definition that keeps `sources` and `schema` literals typed.
  - `name`: `string`. Unique subgraph name (lowercase, alphanumeric + hyphens)
  - `description`?: `string`. Human description
  - `startBlock`?: `number`. Block height to start indexing from (default: 1)
  - `backfillMode`?: `"blocking" \| "concurrent"`. 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'.
  - `sources`: `Sources`
  - `schema`: `S`
  - `handlers`: `TypedHandlers<Sources, S>`
- `UnpinnedPrintEventFilter`: Unpinned / factory-only print source (no contractId).
  - `factory`?: `{ from: string; field: string }`
  - `type`: `"print_event"`
  - `topic`?: `string`
  - `where`?: `Record<string, string \| readonly string[]>`. Equality on top-level keys of the decoded print tuple, spelled as the contract prints them (`signer-manager`). A value matches the field's JSON form: principals `SP…`, uints as decimal strings, bools `"true"`/`"false"`, buffers `0x…`. An array is any-of; keys AND. Nested tuples and lists never match.
  - `contractId`?: `undefined`
  - `trait`?: `string`
  - `prints`?: `PrintEventPrints`
- `VarSetEvent`
  - `contractId`: `string`
  - `varName`: `string`
  - `rawValue`: `string`
  - `value`: `unknown`
  - `tx`: `TxMeta`
- `VarSetFilter`
  - `type`: `"var_set"`
  - `contractId`?: `string \| readonly string[]`
  - `varName`?: `string`
  - `abi`?: `AbiContract`. Contract ABI. With a `varName` the abi defines, `event.value` is typed and decoded from the variable's type. Pass it yourself (`normalizeAbi()` from `@secondlayer/stacks/clarity`).
  - `trait`?: `string`
  - `factory`?: `{ from: string; field: string }`
- `VarSetPayload`: State-write payloads.
- `WhereInput`: Where clause — each column accepts a scalar (eq) or comparison object
- `WriteRow`: Row shape accepted by `ctx.insert`.

## Notes for agents

- Import from '@secondlayer/subgraphs'; the test harness is '@secondlayer/subgraphs/testing' and replay is '@secondlayer/subgraphs/verify'.
- A subgraph is 'defineSubgraph({ name, sources, schema, handlers })'; each handler gets '(event, ctx)' and writes rows through 'ctx.insert', 'ctx.upsert' and the other 'ctx.*' methods.
- Every name on this page is a real export read from the package source, so a signature here is the one the installed version ships.
- Run handlers locally with 'createTestContext' and 'buildEvent' before a deploy; they use the real context with an in-memory store.

## Setup

Ensure setup once, skipping any step already done:
- Skill: `bunx skills add ryanwaits/secondlayer --skill secondlayer -y`
- CLI: `bun add -g @secondlayer/cli`
- Instance: `secondlayer init` writes `INSTANCE_TOKEN`. Loopback `/v1` reads need no key.
- Hosted/archive: export `SECONDLAYER_API_KEY` (`sk-sl_*`) or buy credits with `secondlayer credits buy` (`balance`, `refill`), then `secondlayer whoami`
