@secondlayer/subgraphs
Everything the subgraphs package exports: defineSubgraph, the handler ctx, contract reads, schema codegen, replay and the test harness. The Subgraphs guide shows how they fit together.
| Define a subgraph | ||
| defineSchema(schema) | S | Declares a schema you can hoist out of defineSubgraph() and reuse. |
| defineSubgraph(def) | TypedSubgraphDefinition<Sources, S> | Declares a subgraph and keeps its source and schema literal types. |
| filterFieldsByType() | { type: string; fields: string[] }[] | Lists the fields each source filter type accepts. |
| validateSubgraphDefinition(def) | SubgraphDefinition | Validates a subgraph definition and returns the parsed result. |
| camelizeKeys(obj) | unknown | Camelizes object keys recursively, the way print payloads are. |
| inferPrintTopics(samples) | InferredTopicSchema[] | Infers per-topic field schemas from sampled print events. |
| Handler context | ||
| ctx.client.contract(contractId, abi, options) | { read: Record<string, (args?: Reco… | Reads a contract's read-only functions at the block being processed. |
| ctx.insert(table, row) | void | Queues a new row for a table. |
| ctx.update(table, where, set) | void | Queues an update to the rows matching where. |
| ctx.upsert(table, key, row) | void | Inserts a row, or updates the one with the same key. |
| ctx.delete(table, where) | void | Queues deletion of the rows matching where. |
| ctx.increment(table, key, deltas) | void | Adds signed deltas to a counter row, creating it if missing. |
| ctx.findOne(table, where) | SystemRow & { [K in keyof T["column… | Returns the first row matching where, or null. |
| ctx.findMany(table, where) | SystemRow & { [K in keyof T["column… | Returns every row matching where, in insertion order. |
| Contract reads | ||
| readContractAt(ctx, contractId, abi, options) | { read: ChainReadMethods<C> } | Reads a contract's read-only functions from a handler, with typed results. |
| Schema and SQL | ||
| generateSubgraphSQL(def, schemaNameOverride) | GeneratedSQL | Generates PostgreSQL DDL statements for a subgraph definition. |
| generatePrismaSchema(def, opts) | string | Generates a Prisma schema for a subgraph's tables. |
| generateDrizzleSchema(def, opts) | string | Generates Drizzle table definitions for a subgraph's tables. |
| generateKyselySchema(def, opts) | string | Generates Kysely table interfaces for a subgraph's tables. |
| generateIndexSchema(target, opts) | string | Generates a typed schema for Index tables. |
| pgSchemaName(subgraphName) | string | Convert a subgraph name to its PostgreSQL schema name. |
| deploySchema(db, def, handlerPath, opts) | { action: "created" | "unchanged" |… | Deploys a subgraph schema to the database. |
| diffSchema(existing, incoming) | TableDiff | Compare two multi-table subgraph schemas and return differences. |
| hasBreakingChanges(diff) | { breaking: boolean; reasons: strin… | Tells whether a schema diff is breaking, with the reasons. |
| renderDeployPlan(def, schemaName) | DeployPlan | Render the DDL a deploy would run, without executing. |
| Block sources | ||
| canSparseScan(subgraph) | boolean | Tells whether a subgraph can skip blocks without its events. |
| isStreamsIndexEligible(subgraph) | boolean | Tells whether a subgraph's sources can be read from the Streams index. |
| sparseProbeTargets(subgraph) | SparseProbeTarget[] | Lists the event types and contract scopes a subgraph's filters match. |
| Verification | ||
| computePin(input) | string | Content hash over everything that shapes a subgraph's rows. |
| deriveVerification(def, findings) | SubgraphVerification | Derive how far a subgraph's rows can be checked. |
| formatFinding(f) | string | Formats a handler finding as file:line:column name: reason. |
| isDeterminismViolation(v) | boolean | Tells whether a deploy must be refused for breaking determinism. |
| loadDeterministicDefinition(handlerCode) | SubgraphDefinition | Loads a bundled handler into a fresh deterministic realm. |
| Replay | ||
| checkPin(served, def) | PinCheck | Checks a served pin against the served bundle. |
| replayBlocks(deps, opts) | ReplayResult | The replay loop over an injected verifier. |
| replayContracts(def) | string[] | { error: string } | Lists the contracts a state subgraph's writes come from. |
| replaySubgraph(opts) | ReplayResult | Verifies every block in a range and recomputes the subgraph's tables. |
| compareRows(schema, replayed, served) | RowComparison | Compares replayed tables with the served ones over a block range. |
| Testing | ||
| buildEvent(source, payload, options) | EventForFilter<F> | Builds a handler-shaped event for a source, typed by that source's filter. |
| buildEventPayload(filter, tx, event) | Record<string, unknown> | Build a typed event payload based on the source filter type. |
| createTestContext(schema, options) | TestSubgraphContext<S> | Builds an in-memory context for a subgraph's schema. |
| probeHandlers(def, samples) | ProbeHandlersResult | Run handlers against pre-built sample payloads in memory (no Index, no Postgres). |
| runSubgraphTest(input) | SubgraphTestResult | Apply local handlers to Index rows in memory. |
| toContractCallPayload(row) | Record<string, unknown> | Map an Index contract-call row onto the ContractCallEvent handler shape. |
| toHandlerPayload(_filter, row) | Record<string, unknown> | Map an Index event row onto the payload shape a handler expects. |
Define a subgraph
Declare sources, tables and handlers, and check them before a deploy.
import from @secondlayer/subgraphs
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.
Parameters
hover a row to find it in the codeRequired
schemarequiredS |
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.
Parameters
hover a row to find it in the codeRequired
namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
sourcesrequiredSources | |
schemarequiredS | |
handlersrequiredTypedHandlers<Sources, S> |
Options
descriptionstring | Human description |
startBlocknumber | 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'. |
Returns TypedSubgraphDefinition<Sources, S>
name- Unique subgraph name (lowercase, alphanumeric + hyphens)
description- Human description
startBlock- Block height to start indexing from (default: 1)
backfillMode- 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'.
sources- Sources
schema- S
handlers- TypedHandlers<Sources, S>
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.
Validates a subgraph definition and returns the parsed result.
It throws when the definition is invalid.
Parameters
hover a row to find it in the codeRequired
defrequiredunknown |
Returns SubgraphDefinition
name- Unique subgraph name (lowercase, alphanumeric + hyphens)
description- Human description
startBlock- Block height to start indexing from (default: 1)
backfillMode- 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'.
sources- Named source filters — keys become handler keys
schema- Tables in this subgraph
handlers- Handler functions, one per source, keyed by source name
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.
Parameters
hover a row to find it in the codeRequired
objrequiredunknown |
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.
Parameters
hover a row to find it in the codeRequired
samplesrequiredPrintSample[] |
Returns InferredTopicSchema[]
topic- string
count- number
first_height- number
last_height- number
non_tuple- boolean
fields- InferredPrintField[]
Handler context
The ctx every handler receives: write rows, read them back, call contracts.
import from @secondlayer/subgraphs
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.
Parameters
hover a row to find it in the codeRequired
contractIdrequiredstring | |
abirequiredunknown |
Options
options.cacheChainReadCacheMode |
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.
Parameters
hover a row to find it in the codeRequired
tablerequiredT | |
rowrequiredWriteRow<S[T]> |
Queues an update to the rows matching where.
set holds only the columns to change.
Parameters
hover a row to find it in the codeRequired
tablerequiredT | |
whererequiredRowWhere<S, T> | |
setrequiredPartial<{ [K in keyof T["columns"] as IsOptionalColumn<… |
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.
Parameters
hover a row to find it in the codeRequired
tablerequiredT | |
keyrequiredK | |
rowrequiredOmit<{ [K in keyof T["columns"] as IsOptionalColumn<T["… |
Queues deletion of the rows matching where.
Parameters
hover a row to find it in the codeRequired
tablerequiredT | |
whererequiredRowWhere<S, T> |
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.
Parameters
hover a row to find it in the codeRequired
tablerequiredT | |
keyrequiredPartial<{ [K in keyof T["columns"] as IsOptionalColumn<… | |
deltasrequiredRecord<string, Record<ColumnName<S, T>, number | bigint… |
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.
Parameters
hover a row to find it in the codeRequired
tablerequiredT | |
whererequiredRowWhere<S, T> |
Returns Promise<SystemRow & { [K in keyof T["columns"]]: InferC…
_id- string
_blockHeight- bigint
_txId- string
_createdAt- string
Returns every row matching where, in insertion order.
Reads see the writes queued earlier in the same block.
Parameters
hover a row to find it in the codeRequired
tablerequiredT | |
whererequiredRowWhere<S, T> |
Returns Promise<SystemRow & { [K in keyof T["columns"]]: InferC…
_id- string
_blockHeight- bigint
_txId- string
_createdAt- string
Contract reads
Read contract state from a handler, pinned to the block being processed.
import from @secondlayer/subgraphs
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.
Parameters
hover a row to find it in the codeRequired
ctx.clientrequiredErasedChainReadClient | |
contractIdrequiredstring | |
abirequiredC |
Options
options.cacheChainReadCacheMode |
Schema and SQL
The SQL and typed clients a subgraph's tables turn into, and the diff a deploy runs.
import from @secondlayer/subgraphs
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.
Parameters
hover a row to find it in the codeRequired
def.namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
def.sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
def.schemarequiredSubgraphSchema | Tables in this subgraph |
def.handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
def.descriptionstring | Human description |
def.startBlocknumber | Block height to start indexing from (default: 1) |
def.backfillMode"blocking" | "concurrent" | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
schemaNameOverridestring |
Returns GeneratedSQL
statements- string[]
hash- string
Generates a Prisma schema for a subgraph's tables.
Set modelsOnly to emit just the model blocks, to compose with your own schema.
Parameters
hover a row to find it in the codeRequired
def.namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
def.sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
def.schemarequiredSubgraphSchema | Tables in this subgraph |
def.handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
def.descriptionstring | Human description |
def.startBlocknumber | Block height to start indexing from (default: 1) |
def.backfillMode"blocking" | "concurrent" | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
opts.schemaNamestring | Postgres schema the tables live in (account-scoped). |
opts.datasourceEnvstring | env var the datasource url reads from. |
opts.modelsOnlyboolean | Emit only the model blocks (no datasource/generator). Lets users compose these models with their own schema via Prisma's prismaSchemaFolder. |
Generates Drizzle table definitions for a subgraph's tables.
Parameters
hover a row to find it in the codeRequired
def.namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
def.sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
def.schemarequiredSubgraphSchema | Tables in this subgraph |
def.handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
def.descriptionstring | Human description |
def.startBlocknumber | Block height to start indexing from (default: 1) |
def.backfillMode"blocking" | "concurrent" | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
opts.schemaNamestring |
Generates Kysely table interfaces for a subgraph's tables.
Parameters
hover a row to find it in the codeRequired
def.namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
def.sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
def.schemarequiredSubgraphSchema | Tables in this subgraph |
def.handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
def.descriptionstring | Human description |
def.startBlocknumber | Block height to start indexing from (default: 1) |
def.backfillMode"blocking" | "concurrent" | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
opts.schemaNamestring | Postgres schema the tables live in (account-scoped). |
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.
Parameters
hover a row to find it in the codeRequired
targetrequiredIndexCodegenTarget |
Options
opts.tablesstring[] | Restrict output to these Index tables (default: all). |
opts.schemaNamestring | Postgres schema to qualify table names with (default: bare names). |
opts.datasourceEnvstring | Prisma only: datasource url env var (default DATABASE_URL). |
Convert a subgraph name to its PostgreSQL schema name.
Parameters
hover a row to find it in the codeRequired
subgraphNamerequiredstring |
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.
Parameters
hover a row to find it in the codeRequired
dbrequiredAnyDb | |
def.namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
def.sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
def.schemarequiredSubgraphSchema | Tables in this subgraph |
def.handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
handlerPathrequiredstring |
Options
def.descriptionstring | Human description |
def.startBlocknumber | Block height to start indexing from (default: 1) |
def.backfillMode"blocking" | "concurrent" | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
opts.forceReindexboolean | |
opts.apiKeyIdstring | |
opts.accountIdstring | |
opts.schemaNamestring | |
opts.handlerCodestring |
3 more options
opts.sourceCodestring | |
opts.verificationSubgraphVerification | |
opts.networkstring |
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.
Parameters
hover a row to find it in the codeRequired
existingrequiredSubgraphSchema | |
incomingrequiredSubgraphSchema |
Returns TableDiff
addedTables- Tables added to the schema
removedTables- Tables removed from the schema
tables- Per-table column diffs (only for tables present in both)
constraints- Per-table uniqueKeys / composite-index diffs (tables present in both)
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.
Parameters
hover a row to find it in the codeRequired
addedTablesrequiredstring[] | Tables added to the schema |
removedTablesrequiredstring[] | Tables removed from the schema |
tablesrequiredRecord<string, ColumnDiff> | Per-table column diffs (only for tables present in both) |
constraintsrequiredRecord<string, ConstraintDiff> | Per-table uniqueKeys / composite-index diffs (tables present in both) |
Render the DDL a deploy would run, without executing. Powers --dry-run: the user reviews exactly what a deploy does first.
Parameters
hover a row to find it in the codeRequired
def.namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
def.sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
def.schemarequiredSubgraphSchema | Tables in this subgraph |
def.handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
def.descriptionstring | Human description |
def.startBlocknumber | Block height to start indexing from (default: 1) |
def.backfillMode"blocking" | "concurrent" | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
schemaNamestring |
Returns DeployPlan
schemaName- string
dropStatementDROP SCHEMA … CASCADEa destructive rebuild would run first.statements- DDL the deploy will run.
Block sources
Which block source a subgraph's filters allow.
import from @secondlayer/subgraphs
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.
Parameters
hover a row to find it in the codeRequired
namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
schemarequiredSubgraphSchema | Tables in this subgraph |
handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
descriptionstring | Human description |
startBlocknumber | 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'. |
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.
Parameters
hover a row to find it in the codeRequired
namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
schemarequiredSubgraphSchema | Tables in this subgraph |
handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
descriptionstring | Human description |
startBlocknumber | 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'. |
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.
Parameters
hover a row to find it in the codeRequired
namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
schemarequiredSubgraphSchema | Tables in this subgraph |
handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
descriptionstring | Human description |
startBlocknumber | 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'. |
Returns SparseProbeTarget[]
eventType- string
contractId- string
Verification
The pin and the findings that say how far a subgraph's rows can be checked.
import from @secondlayer/subgraphs
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.
Parameters
hover a row to find it in the codeRequired
schemaHashrequiredstring | Existing schema_hash (name + schema + sources). |
handlerCoderequiredstring | Bundled ESM exactly as stored in subgraphs.handler_code. |
networkrequiredstring |
Options
startBlocknumber | null | |
runtimestring | Defaults to SUBGRAPHS_RUNTIME. |
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.
Parameters
hover a row to find it in the codeRequired
def.sourcesrequiredRecord<string, SubgraphFilter> |
Options
def.backfillMode"blocking" | "concurrent" | |
findingsreadonly HandlerFinding[] |
Formats a handler finding as file:line:column name: reason.
Parameters
hover a row to find it in the codeRequired
kindrequired"nondeterministic" | "needs-events" | |
namerequiredstring | What was used, e.g. Date, Math.random, import(), ctx.client. |
reasonrequiredstring | |
filerequiredstring | Original source file when a sourcemap was available, else the bundle. |
linerequirednumber | 1-based. |
columnrequirednumber | 1-based. |
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.
Parameters
hover a row to find it in the codeRequired
vrequiredSubgraphVerification |
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.
Parameters
hover a row to find it in the codeRequired
handlerCoderequiredstring |
Returns Promise<SubgraphDefinition>
name- Unique subgraph name (lowercase, alphanumeric + hyphens)
description- Human description
startBlock- Block height to start indexing from (default: 1)
backfillMode- 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'.
sources- Named source filters — keys become handler keys
schema- Tables in this subgraph
handlers- Handler functions, one per source, keyed by source name
Constants
SUBGRAPHS_RUNTIMEstringReplay
Re-run a subgraph from the archive and compare it with what is served.
import from @secondlayer/subgraphs/verify
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.
Parameters
hover a row to find it in the codeRequired
served.pinrequiredstring | null | |
served.pinPreimagerequiredstring | null | |
served.handlerCoderequiredstring | |
def.namerequiredstring | Unique subgraph name (lowercase, alphanumeric + hyphens) |
def.sourcesrequiredRecord<string, SubgraphFilter> | Named source filters — keys become handler keys |
def.schemarequiredSubgraphSchema | Tables in this subgraph |
def.handlersrequiredRecord<string, SubgraphHandler> | Handler functions, one per source, keyed by source name |
Options
def.descriptionstring | Human description |
def.startBlocknumber | Block height to start indexing from (default: 1) |
def.backfillMode"blocking" | "concurrent" | 'concurrent' = tip-first: live at tip now, history backfills behind. Requires order-tolerant handlers. Default 'blocking'. |
The replay loop over an injected verifier.
Parameters
hover a row to find it in the codeRequired
deps.verifyrequired(height: number) => Promise<BlockVerification> | |
deps.burnHeightHintrequired(consensusHash: string) => Promise<number> | The source's (unproven) burn height for a consensus hash. |
opts.handlerCoderequiredstring | |
opts.fromrequirednumber | |
opts.torequirednumber |
Options
opts.startBlocknumber | |
opts.onProgress(height: number) => void | |
opts.bitcoinCheckpointnumber |
Returns Promise<ReplayResult>
ok- boolean
inconclusive- Set when the range starts after startBlock and a handler read or merged earlier rows.
from- number
to- number
blocks- number
txs- number
writes- Named writes to the subgraph's contracts.
events- Events offered to a handler.
handlerErrors- number
marfProofs- MARF proofs the walk took (one per window top below the checkpoint).
tables- The memory store at
to. notes- string[]
failures- The first entry is the first broken link.
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.
Parameters
hover a row to find it in the codeRequired
sourcesrequiredRecord<string, SubgraphFilter> |
Verifies every block in a range and recomputes the subgraph's tables.
The range is [from, to], and blocks are verified from the checkpoint.
Parameters
hover a row to find it in the codeRequired
sourcerequiredProofSource | Needs getStateWrites: the names are what feed the handlers. |
handlerCoderequiredstring | The bundled ESM as stored in handler_code. |
fromrequirednumber | |
torequirednumber |
Options
checkpointVerifyCheckpoint | Defaults to MAINNET_CHECKPOINT. |
startBlocknumber | Defaults to the definition's startBlock. |
onProgress(height: number) => void |
Returns Promise<ReplayResult>
ok- boolean
inconclusive- Set when the range starts after startBlock and a handler read or merged earlier rows.
from- number
to- number
blocks- number
txs- number
writes- Named writes to the subgraph's contracts.
events- Events offered to a handler.
handlerErrors- number
marfProofs- MARF proofs the walk took (one per window top below the checkpoint).
tables- The memory store at
to. notes- string[]
failures- The first entry is the first broken link.
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.
Parameters
hover a row to find it in the codeRequired
schemarequiredSubgraphSchema | |
replayed.fromrequirednumber | |
replayed.torequirednumber | |
replayed.tablesrequiredMap<string, Row[]> | |
servedrequiredMap<string, Row[]> |
Returns RowComparison
failures- ReplayFailure[]
tables- TableComparison[]
Constants
REPLAY_WINDOW512Testing
Run a handler against fixture events without deploying.
import from @secondlayer/subgraphs/testing
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.
Parameters
hover a row to find it in the codeRequired
sourcerequiredF | |
payloadrequiredRecord<string, unknown> |
Options
options.tx{ txId?: string; sender?: string; type?: string; status… |
Build a typed event payload based on the source filter type. Returns the payload the handler will receive.
Parameters
hover a row to find it in the codeRequired
filterrequiredSubgraphFilter | |
tx.tx_idrequiredstring | |
tx.typerequiredstring | |
tx.senderrequiredstring | |
tx.statusrequiredstring | |
eventrequiredEventRecord | null |
Options
tx.tx_indexnumber | Position within the block — the runner sorts dispatch into chain order. |
tx.contract_idstring | null | |
tx.function_namestring | null | |
tx.function_argsunknown | |
tx.raw_resultstring | null |
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.
Parameters
hover a row to find it in the codeRequired
schemarequiredS |
Options
options.block{ height?: number; hash?: string; timestamp?: number; b… | |
options.tx{ txId?: string; sender?: string; type?: string; status… | |
options.readsRecord<string, unknown> |
Returns TestSubgraphContext<S>
rows- Current rows of
table, INCLUDING writes queued by the handler you just ran (the same read-your-writes overlay the runtime uses). Drop straight intotoMatchInlineSnapshot(). commit- Commit pending writes into the in-memory store, as an end-of-block flush would — call between blocks when a test spans several.
setTx- Swap the transaction metadata handlers see (
ctx.tx). pendingInsertsSince- Insert ops queued since
opsCheckpoint(), for preview IN/OUT traces. opsCheckpoint- Length of the pending-ops queue (same as runtime
opsCheckpoint). tx- TxMeta
block- { height: number; hash: string; timestamp: number; burnBlockHeight: number; ind…
client- 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 withreadContractAt(ctx, id, abi)for full ABI typing at the call site. insert- Queues a new row for a table. Writes are batched and applied together when the block commits.
_block_heightand_tx_idare filled in for you. update- Queues an update to the rows matching
where.setholds only the columns to change. upsert- Inserts a row, or updates the one with the same key.
keyshould match auniqueKeysconstraint of the table. Without one the write falls back to a lookup first and logs a warning. delete- Queues deletion of the rows matching
where. increment- 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 auniqueKeysconstraint matchingkey, and deltas may benumberorbigint. findOne- Returns the first row matching
where, ornull. Reads see the writes queued earlier in the same block, and rows come back in insertion order. findMany- Returns every row matching
where, in insertion order. Reads see the writes queued earlier in the same block.
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.
Parameters
hover a row to find it in the codeRequired
def.schemarequiredSubgraphSchema | |
def.sourcesrequiredRecord<string, { type: string; [key: string]: unknown }> | |
def.handlersrequiredRecord<string, unknown> | |
samplesrequired{ source: string; event: Record<string, unknown> }[] |
Returns Promise<ProbeHandlersResult>
matched- number
written- number
tables- string[]
firstEventKeys- string[]
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).
Parameters
hover a row to find it in the codeRequired
schemarequiredSubgraphSchema | |
handlersrequiredRecord<string, unknown> | |
sourcesrequiredRecord<string, SubgraphTestSource> | |
eventsrequiredRecord<string, IndexTestRow[]> |
Options
traceboolean | Collect per-event IN/OUT traces (preview). |
Returns Promise<SubgraphTestResult>
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- Present when
trace: true. unusedInKeys- Keys present on every matched IN and never written to any OUT column.
Map an Index contract-call row onto the ContractCallEvent handler shape.
Parameters
hover a row to find it in the codeRequired
senderrequiredstring | |
tx_idrequiredstring | |
statusrequiredstring |
Options
contract_idstring | null | |
function_namestring | null | |
argsunknown | |
resultunknown | |
result_hexunknown | |
cursorstring |
Map an Index event row onto the payload shape a handler expects.
Parameters
hover a row to find it in the codeRequired
_filterrequired{ type: string } | undefined | |
row.event_typerequiredstring |
Options
row.contract_idstring | null | |
row.payloadunknown | Print events carry { topic, value }; other types vary. |
row.senderunknown | |
row.recipientunknown | |
row.amountunknown | |
row.asset_identifierunknown | |
row.valueunknown | |
row.cursorstring |
AggregateResultAggregateSpec.AnyEventChainReadCacheModeChainReadErrorChainReadMethodsChainReadOptionsColumnDiffadded, removed, changed, indexChangedColumnToTSColumnTypeComparisonFilterConstraintDiffaddedUniqueKeys, removedUniqueKeys, addedIndexes, removedIndexesContractCallEventContractCallFilterContractCallPayloadContractDeployFiltertype, deployer, contractNameContractDeployPayloadcontractId, deployer, txDeployDiffDeployPlanDrizzleGenOptionsErasedChainReadClientChainReadClient with the ABI generic erased.EventForFilterEventRecordEventTracesubgraphs preview.FactoryScopeFindManyOptionswhere, orderBy, limit, cursor, fieldsFindManyPagefindMany, keyset-paginated.FtBurnFiltertype, assetIdentifier, sender, minAmount, traitFtBurnPayloadsender, amount, assetIdentifier, txFtMintFiltertype, assetIdentifier, recipient, minAmount, traitFtMintPayloadrecipient, amount, assetIdentifier, txFtTransferFilterFtTransferPayloadsender, recipient, amount, assetIdentifier, txGeneratedSQLHandlerFindingHandlerReadErrorctx.client) on any subgraph that had no node, a node that didn't answer, or a cache error.IndexCodegenOptionsIndexCodegenTargetIndexContractCallRowIndexEventRowIndexTestRowIndexEventRow | IndexContractCallRowInferColumnTypeInferContextInferredPrintFieldname, camel_name, clarity_type, ts_type, column_type, always_present, …InferredTopicSchemaprint topic, with counts and heights.InferSubgraphClientInferTableRowKyselyGenOptionsMapDeleteEventcontractId, map, rawKey, key, txMapDeletePayloadF extends { abi: infer A extends AbiContract; map: infer N extends string; } ? N extends ExtractMapNames<A> ?…MapWriteEventcontractId, map, rawKey, key, rawValue, value, …MapWriteFiltertype, contractId, map, abi, trait, factoryMapWritePayloadF extends { abi: infer A extends AbiContract; map: infer N extends string; } ? N extends ExtractMapNames<A> ?…NestedContractCallFiltertype, contractId, functionName, caller, sender, trait, …NestedContractCallPayloadcontractId, sender, caller, functionName, args, arguments, …NftBurnFiltertype, assetIdentifier, sender, traitNftBurnPayloadsender, tokenId, assetIdentifier, txNftMintFiltertype, assetIdentifier, recipient, traitNftMintPayloadrecipient, tokenId, assetIdentifier, txNftTransferFilterNftTransferPayloadsender, recipient, tokenId, assetIdentifier, txNondeterminismErrorPinCheckPinInputPinnedPrintEventFilterprints is required so event.data is typed and empty-table handlers cannot typecheck.PrintDataOfevent.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).PrintEventFilterPrintEventForPrintEventPayloadcontractId, topic, data, txPrintEventPrintsPrintFieldPrintFieldToTSPrintSamplePrintValidationResult{ ok: true; } | { ok: false; reason: string; }PrismaGenOptionsProbeHandlersDefProbeHandlersResultReplayDepsReplayFailurestep, message, height, table, key, unavailableReplayOptionsReplayResultReplayStep"pin" | "blocks" | "txs" | "inputs" | "handlers" | "rows"RowRowComparisonRowValueRunSubgraphTestInputSparseProbeTargetStxBurnFiltertype, sender, minAmountStxBurnPayloadsender, amount, txStxLockFiltertype, lockedAddress, minAmountStxLockPayloadlockedAddress, lockedAmount, unlockHeight, txStxMintFiltertype, recipient, minAmountStxMintPayloadrecipient, amount, txStxTransferFilterStxTransferPayloadsender, recipient, amount, memo, txSubgraphColumnSubgraphContextSubgraphDefinitionSubgraphFiltertype.SubgraphHandlerSubgraphSchemaSubgraphTableSubgraphTestResultSubgraphTestSourcetype, contractId, topic, where, functionNameSubgraphTip/v1 row/page response.SubscribeOptionsSystemOrderByAliasesblockHeight, txId, createdAt, idSystemRowSystemWhereAliasesTableComparisontable, equal, superseded, digest, servedDigestTableDiffTestSubgraphContextTraitPrintEventFilterTxMetaTxRecordTypedHandlersTypedSubgraphContextTypedSubgraphDefinitionsources and schema literals typed.UnpinnedPrintEventFilterVarSetEventcontractId, varName, rawValue, value, txVarSetFiltertype, contractId, varName, abi, trait, factoryVarSetPayloadWhereInputWriteRowctx.insert.