@secondlayer/subgraphs 3.21.0 → 3.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/src/index.d.ts +238 -17
  2. package/dist/src/index.js +389 -52
  3. package/dist/src/index.js.map +12 -11
  4. package/dist/src/runtime/block-processor.d.ts +111 -10
  5. package/dist/src/runtime/block-processor.js +263 -31
  6. package/dist/src/runtime/block-processor.js.map +9 -8
  7. package/dist/src/runtime/catchup.d.ts +111 -10
  8. package/dist/src/runtime/catchup.js +263 -31
  9. package/dist/src/runtime/catchup.js.map +9 -8
  10. package/dist/src/runtime/context.d.ts +111 -11
  11. package/dist/src/runtime/context.js +166 -12
  12. package/dist/src/runtime/context.js.map +7 -6
  13. package/dist/src/runtime/processor.js +272 -35
  14. package/dist/src/runtime/processor.js.map +10 -9
  15. package/dist/src/runtime/reindex.d.ts +111 -10
  16. package/dist/src/runtime/reindex.js +263 -31
  17. package/dist/src/runtime/reindex.js.map +9 -8
  18. package/dist/src/runtime/reorg.d.ts +111 -10
  19. package/dist/src/runtime/reorg.js +268 -31
  20. package/dist/src/runtime/reorg.js.map +10 -9
  21. package/dist/src/runtime/replay.d.ts +3 -3
  22. package/dist/src/runtime/replay.js +30 -6
  23. package/dist/src/runtime/replay.js.map +4 -4
  24. package/dist/src/runtime/runner.d.ts +191 -17
  25. package/dist/src/runtime/source-matcher.d.ts +68 -8
  26. package/dist/src/runtime/source-matcher.js +30 -6
  27. package/dist/src/runtime/source-matcher.js.map +3 -3
  28. package/dist/src/runtime/stats.d.ts +3 -3
  29. package/dist/src/schema/index.d.ts +114 -13
  30. package/dist/src/schema/index.js +133 -20
  31. package/dist/src/schema/index.js.map +5 -5
  32. package/dist/src/service.js +272 -35
  33. package/dist/src/service.js.map +10 -9
  34. package/dist/src/testing/index.d.ts +667 -0
  35. package/dist/src/testing/index.js +1463 -0
  36. package/dist/src/testing/index.js.map +17 -0
  37. package/dist/src/triggers/index.d.ts +57 -6
  38. package/dist/src/types.d.ts +112 -11
  39. package/dist/src/validate.d.ts +129 -11
  40. package/dist/src/validate.js +117 -18
  41. package/dist/src/validate.js.map +3 -3
  42. package/package.json +9 -3
@@ -0,0 +1,667 @@
1
+ import { AbiContract as AbiContract2 } from "@secondlayer/stacks/clarity";
2
+ /**
3
+ * How long a read stays valid.
4
+ *
5
+ * - `per-block` (default): pinned to the block being processed, cached under
6
+ * its `index_block_hash`. Sound for any read-only function — reindexing the
7
+ * same block always returns the same value, and a reorg-replaced block has a
8
+ * different id so it can't inherit the orphaned answer.
9
+ * - `contract-constant`: the caller asserts the value cannot change for this
10
+ * contract and these args (SIP-010 `get-decimals`/`get-symbol`), so it is
11
+ * resolved ONCE instead of once per block. This is the mode that makes a
12
+ * backfill affordable — under `per-block` every block is a distinct key and
13
+ * therefore a distinct RPC. Declaring it on a value that does change pins the
14
+ * first answer forever, so it is opt-in and never inferred.
15
+ */
16
+ type ChainReadCacheMode = "per-block" | "contract-constant";
17
+ type ChainReadOptions = {
18
+ cache?: ChainReadCacheMode
19
+ };
20
+ /**
21
+ * `ChainReadClient` with the ABI generic erased. Structurally cheap, so the
22
+ * base `SubgraphContext` can carry it; `TypedSubgraphContext` uses the real
23
+ * one and handlers get full inference.
24
+ */
25
+ interface ErasedChainReadClient {
26
+ contract(contractId: string, abi: unknown, options?: ChainReadOptions): {
27
+ read: Record<string, (args?: Record<string, unknown>) => Promise<unknown>>
28
+ };
29
+ }
30
+ /** Supported column types for subgraph schemas */
31
+ type ColumnType = "text" | "uint" | "int" | "principal" | "boolean" | "timestamp" | "jsonb";
32
+ /** Column definition in a subgraph table */
33
+ interface SubgraphColumn {
34
+ type: ColumnType;
35
+ nullable?: boolean;
36
+ indexed?: boolean;
37
+ search?: boolean;
38
+ default?: string | number | boolean;
39
+ }
40
+ /**
41
+ * A foreign-key relation to another table in the same subgraph. Drives DDL FK
42
+ * constraints and ORM codegen (`@relation` in Prisma, `relations()` in Drizzle)
43
+ * so generated clients get typed joins. The referenced columns must form a
44
+ * `uniqueKeys` entry on the target table.
45
+ */
46
+ interface SubgraphRelation {
47
+ /** Relation field name on this table's generated model (e.g. "pool"). */
48
+ name: string;
49
+ /** Target table name in this subgraph. */
50
+ references: string;
51
+ /** Local column(s) holding the foreign key. */
52
+ fields: string[];
53
+ /** Target column(s) the fields point at (a uniqueKeys entry on the target). */
54
+ referencedColumns: string[];
55
+ }
56
+ /**
57
+ * Table definition within a subgraph schema.
58
+ *
59
+ * The array fields are `readonly` so a schema literal can be hoisted out of
60
+ * the `defineSubgraph()` call with `as const` (or {@link defineSchema}) and
61
+ * still typecheck — `as const` produces `readonly [readonly ["holder"]]`,
62
+ * which a mutable `string[][]` rejects. Without that, extracting a helper
63
+ * that takes `ctx` meant losing the entire typed surface, which is why the
64
+ * shipped starter reached for `ctx: any`.
65
+ */
66
+ interface SubgraphTable {
67
+ columns: Record<string, SubgraphColumn>;
68
+ /** Composite indexes (each entry is an array of column names) */
69
+ indexes?: readonly (readonly string[])[];
70
+ /** Unique key constraints (each entry is an array of column names). Required for upsert. */
71
+ uniqueKeys?: readonly (readonly string[])[];
72
+ /** Foreign-key relations to other tables (for typed ORM joins). */
73
+ relations?: readonly SubgraphRelation[];
74
+ }
75
+ /** Subgraph schema — maps table names to table definitions */
76
+ type SubgraphSchema = Record<string, SubgraphTable>;
77
+ /** STX event filters */
78
+ interface StxTransferFilter {
79
+ type: "stx_transfer";
80
+ sender?: string;
81
+ recipient?: string;
82
+ minAmount?: bigint;
83
+ maxAmount?: bigint;
84
+ }
85
+ interface StxMintFilter {
86
+ type: "stx_mint";
87
+ recipient?: string;
88
+ minAmount?: bigint;
89
+ }
90
+ interface StxBurnFilter {
91
+ type: "stx_burn";
92
+ sender?: string;
93
+ minAmount?: bigint;
94
+ }
95
+ interface StxLockFilter {
96
+ type: "stx_lock";
97
+ lockedAddress?: string;
98
+ minAmount?: bigint;
99
+ }
100
+ /**
101
+ * Restrict a source to contracts conforming to a trait/standard (e.g. "sip-010")
102
+ * instead of a fixed contract — resolved from the contract registry at match time,
103
+ * as-of each processed block. Lets a source index "all SIP-010 tokens" etc.
104
+ */
105
+ type TraitScope = {
106
+ trait?: string
107
+ };
108
+ /**
109
+ * Scope a source to a DYNAMIC address set discovered from another source's
110
+ * events, instead of a fixed contract list.
111
+ *
112
+ * The standard shape it unlocks: a router plus pools created after you
113
+ * deploy. `contractId` arrays cover a set you know at authoring time; a
114
+ * factory covers the set that grows. It is deliberately NOT tied to contract
115
+ * deployment — the address can come from any field of any event, so it also
116
+ * covers launchpad-minted tokens, DAO extension allowlists, and registry
117
+ * entries.
118
+ *
119
+ * ```ts
120
+ * sources: {
121
+ * registry: { type: "print_event", contractId: REGISTRY, topic: "pool-created" },
122
+ * swaps: {
123
+ * type: "print_event",
124
+ * topic: "swap",
125
+ * // every pool the registry has ever announced, including new ones
126
+ * factory: { from: "registry", field: "data.pool" },
127
+ * },
128
+ * }
129
+ * ```
130
+ *
131
+ * Two guarantees the runtime provides:
132
+ * - **Same-block ordering** — a contract discovered in block N receives its
133
+ * own block-N events. Discovery runs before matching, within the block.
134
+ * - **Reorg-scoped** — the discovered set is stamped with the block that
135
+ * revealed it and rolled back with everything else on a fork, so an address
136
+ * announced on an orphaned chain does not linger forever.
137
+ */
138
+ interface FactoryScope {
139
+ factory?: {
140
+ /** Source name whose events reveal the addresses. */
141
+ from: string
142
+ /** Dotted path to the address on that source's payload
143
+ * (e.g. `"data.pool"`, `"contractId"`, `"data.token.id"`). */
144
+ field: string
145
+ };
146
+ }
147
+ /** FT event filters */
148
+ interface FtTransferFilter extends TraitScope {
149
+ type: "ft_transfer";
150
+ assetIdentifier?: string;
151
+ sender?: string;
152
+ recipient?: string;
153
+ minAmount?: bigint;
154
+ }
155
+ interface FtMintFilter extends TraitScope {
156
+ type: "ft_mint";
157
+ assetIdentifier?: string;
158
+ recipient?: string;
159
+ minAmount?: bigint;
160
+ }
161
+ interface FtBurnFilter extends TraitScope {
162
+ type: "ft_burn";
163
+ assetIdentifier?: string;
164
+ sender?: string;
165
+ minAmount?: bigint;
166
+ }
167
+ /** NFT event filters */
168
+ interface NftTransferFilter extends TraitScope {
169
+ type: "nft_transfer";
170
+ assetIdentifier?: string;
171
+ sender?: string;
172
+ recipient?: string;
173
+ }
174
+ interface NftMintFilter extends TraitScope {
175
+ type: "nft_mint";
176
+ assetIdentifier?: string;
177
+ recipient?: string;
178
+ }
179
+ interface NftBurnFilter extends TraitScope {
180
+ type: "nft_burn";
181
+ assetIdentifier?: string;
182
+ sender?: string;
183
+ }
184
+ /**
185
+ * Event shape passed to contract_call handlers.
186
+ *
187
+ * `args` is a **positional array** of decoded Clarity values matching the
188
+ * contract function's parameter list in declaration order. Use the ABI
189
+ * (or Clarity contract source) to map positions to names:
190
+ *
191
+ * ```ts
192
+ * // pox-4 stack-stx args: (amount-ustx uint) (pox-addr tuple) (start-burn-ht uint) (lock-period uint)
193
+ * const [amountUstx, , , lockPeriod] = event.args;
194
+ * ```
195
+ *
196
+ * Bigints come out as `bigint`. Buffers as `Uint8Array`. Principals as
197
+ * strings (`"SP..."`). Tuples as `Record<string, unknown>`.
198
+ *
199
+ * Historical transactions indexed before the function_args column was added
200
+ * will have `args = []` — always guard with `args.length > 0` when reading
201
+ * args from pre-Nakamoto history.
202
+ */
203
+ interface ContractCallEvent {
204
+ type: "contract_call";
205
+ /** Transaction sender (the principal who signed the tx). Always non-null. */
206
+ sender: string;
207
+ contractId: string;
208
+ functionName: string;
209
+ /** Positional decoded Clarity values — order matches the ABI parameter list. */
210
+ args: unknown[];
211
+ /** Decoded return value from the contract function, or null. */
212
+ result: unknown;
213
+ /** Raw hex-encoded result value. */
214
+ resultHex: string | null;
215
+ /** Transaction metadata. */
216
+ tx: {
217
+ txId: string
218
+ sender: string
219
+ type: string
220
+ status: string
221
+ contractId: string | null
222
+ functionName: string | null
223
+ };
224
+ }
225
+ /** Contract event filters */
226
+ interface ContractCallFilter extends TraitScope, FactoryScope {
227
+ type: "contract_call";
228
+ /**
229
+ * One contract id, or a SET of them (max 20) — the standard DeFi shape is
230
+ * a router plus N pools, which used to cost N near-identical sources and
231
+ * handlers, or the `"*"` catch-all that erases per-source typing. Payload
232
+ * types are identical per `type`, so one handler serves the set and
233
+ * `event.contractId` disambiguates.
234
+ *
235
+ * Supports `*` wildcards (e.g. `"SP1ABC….pool-*"`). Mutually exclusive
236
+ * with `trait`, which resolves its own set per block.
237
+ */
238
+ contractId?: string | readonly string[];
239
+ functionName?: string;
240
+ caller?: string;
241
+ /**
242
+ * Contract ABI (pass it `as const`) used to type `event.input` — the named,
243
+ * decoded function arguments. Dev-provided; serialized into the deployed
244
+ * definition and used at runtime to decode args by name. Omit to keep
245
+ * `event.args` as a positional `unknown[]`.
246
+ */
247
+ abi?: AbiContract2;
248
+ }
249
+ interface ContractDeployFilter {
250
+ type: "contract_deploy";
251
+ deployer?: string;
252
+ contractName?: string;
253
+ }
254
+ /**
255
+ * One declared field of a print payload.
256
+ *
257
+ * A bare {@link ColumnType} is a required scalar (`amount: "uint"`). The
258
+ * composite forms exist because real print payloads are not flat: BNS-V2
259
+ * emits `name` as a NESTED tuple, and a `prints` vocabulary that could only
260
+ * say `"jsonb"` is why a handler reading flat `data.name` type-checked,
261
+ * deployed, and then decoded to null on every event.
262
+ *
263
+ * - `{ type: F, optional: true }` — the KEY becomes optional on `event.data`
264
+ * (mirrors `always_present: false` in the inferred schema).
265
+ * - `{ tuple: { … } }` — a nested tuple, typed all the way down.
266
+ * - `{ list: F }` — a list of `F`.
267
+ */
268
+ type PrintField = ColumnType | {
269
+ type: PrintField
270
+ optional: true
271
+ } | {
272
+ tuple: Record<string, PrintField>
273
+ } | {
274
+ list: PrintField
275
+ };
276
+ interface PrintEventFilter extends TraitScope, FactoryScope {
277
+ type: "print_event";
278
+ /** One contract id, or a set of them (max 20). Supports `*` wildcards.
279
+ * Mutually exclusive with `trait`. */
280
+ contractId?: string | readonly string[];
281
+ topic?: string;
282
+ /**
283
+ * Per-topic field schema. When declared, the handler's `event` is a
284
+ * discriminated union keyed by `topic` and `event.data` is typed per topic
285
+ * (e.g. `{ "completed-deposit": { amount: "uint", sender: "principal" } }`).
286
+ * Nested tuples, lists, and optional fields are expressible — see
287
+ * {@link PrintField}.
288
+ *
289
+ * Declaring `prints` also opts the source into RUNTIME validation: an
290
+ * event whose decoded payload does not match is skipped and logged rather
291
+ * than written as nulls (and the deploy-time field lint is promoted from
292
+ * a warning to an error).
293
+ */
294
+ prints?: Record<string, Record<string, PrintField>>;
295
+ }
296
+ /** All subgraph filter types — discriminated on `type` */
297
+ type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter;
298
+ /** Transaction metadata available in handlers */
299
+ interface TxMeta {
300
+ txId: string;
301
+ sender: string;
302
+ type: string;
303
+ status: string;
304
+ contractId?: string | null;
305
+ functionName?: string | null;
306
+ }
307
+ /** Scalar types that can appear as row values */
308
+ type RowValue = string | number | bigint | boolean | null | undefined | Record<string, unknown> | unknown[];
309
+ /** Value or computed function that receives existing row */
310
+ type ComputedValue = RowValue | ((existing: Record<string, unknown> | null) => unknown);
311
+ interface BlockMeta {
312
+ height: number;
313
+ hash: string;
314
+ timestamp: number;
315
+ burnBlockHeight: number;
316
+ /**
317
+ * Nakamoto StacksBlockId — the identifier `?tip=` accepts, and the only way
318
+ * to pin a read-only call to THIS block. Null on rows ingested before it
319
+ * was persisted; `ctx.client.readOnly` refuses to run rather than silently
320
+ * reading at the node's tip.
321
+ */
322
+ indexBlockHash?: string | null;
323
+ }
324
+ interface TxMeta2 {
325
+ txId: string;
326
+ sender: string;
327
+ type: string;
328
+ status: string;
329
+ contractId?: string | null;
330
+ functionName?: string | null;
331
+ }
332
+ interface MatchedTx {
333
+ tx: TxRecord;
334
+ events: EventRecord[];
335
+ /** Source object key — used for handler dispatch */
336
+ sourceName: string;
337
+ }
338
+ type TxRecord = {
339
+ tx_id: string
340
+ type: string
341
+ sender: string
342
+ status: string
343
+ /** Position within the block — the runner sorts dispatch into chain order. */
344
+ tx_index?: number
345
+ contract_id?: string | null
346
+ function_name?: string | null
347
+ function_args?: unknown | null
348
+ raw_result?: string | null
349
+ };
350
+ type EventRecord = {
351
+ id: string
352
+ tx_id: string
353
+ type: string
354
+ event_index: number
355
+ data: unknown
356
+ };
357
+ /**
358
+ * Build a typed event payload based on the source filter type.
359
+ * Returns the payload the handler will receive.
360
+ */
361
+ declare function buildEventPayload(filter: SubgraphFilter, tx: MatchedTx["tx"], event: MatchedTx["events"][0] | null): Record<string, unknown>;
362
+ import { AbiContract as AbiContract3, ExtractFunctionArgs as ExtractFunctionArgs2, ExtractFunctionNames } from "@secondlayer/stacks/clarity";
363
+ /** Maps a ColumnType string literal to its TypeScript equivalent */
364
+ type ColumnToTS<T extends string> = T extends "uint" | "int" ? bigint : T extends "text" | "principal" | "timestamp" ? string : T extends "boolean" ? boolean : T extends "jsonb" ? Record<string, unknown> : unknown;
365
+ /** Infer TS type for a single column definition, respecting nullable */
366
+ type InferColumnType<C extends SubgraphColumn> = C["type"] extends ColumnType ? C["nullable"] extends true ? ColumnToTS<C["type"]> | null : ColumnToTS<C["type"]> : unknown;
367
+ /** Shape of system columns on every returned row (camelCase, underscore-prefixed) */
368
+ interface SystemRow {
369
+ _id: string;
370
+ _blockHeight: bigint;
371
+ _txId: string;
372
+ _createdAt: string;
373
+ }
374
+ /** Infer the row shape for a single table definition */
375
+ type InferTableRow<T extends SubgraphTable> = SystemRow & { [K in keyof T["columns"]] : InferColumnType<T["columns"][K]> };
376
+ /** Comparison operators for a scalar value */
377
+ type ComparisonFilter<T> = {
378
+ eq?: T
379
+ neq?: T
380
+ gt?: T
381
+ gte?: T
382
+ lt?: T
383
+ lte?: T
384
+ /** Match any value in the set. */
385
+ in?: T[]
386
+ /** Match none of the values in the set. */
387
+ notIn?: T[]
388
+ /** SQL ILIKE pattern (case-insensitive); `%`/`_` wildcards. Strings only. */
389
+ like?: string
390
+ };
391
+ /** Where clause — each column accepts a scalar (eq) or comparison object */
392
+ type WhereInput<TRow> = { [K in keyof TRow]? : TRow[K] | ComparisonFilter<TRow[K]> };
393
+ /** A column is optional on insert when it's nullable or has a SQL default. */
394
+ type IsOptionalColumn<C> = C extends {
395
+ nullable: true
396
+ } ? true : C extends {
397
+ default: string | number | boolean
398
+ } ? true : false;
399
+ /**
400
+ * Like `InferColumnType` but for WRITE inputs: numeric (`uint`/`int`) columns
401
+ * also accept a plain `number` (the runtime coerces to bigint). Reads stay
402
+ * strict `bigint` via `InferColumnType`, so handlers still get exact types back.
403
+ */
404
+ type WriteColumnType<C extends SubgraphColumn> = C["type"] extends "uint" | "int" ? C["nullable"] extends true ? bigint | number | null : bigint | number : InferColumnType<C>;
405
+ /**
406
+ * Row shape accepted by `ctx.insert`. System columns (`_id`, `_blockHeight`,
407
+ * `_txId`, `_createdAt`) are omitted — the runtime adds them. Nullable and
408
+ * defaulted columns are optional; all others required.
409
+ */
410
+ type WriteRow<T extends SubgraphTable> = { [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]> };
411
+ /** Computed-or-value row for `patchOrInsert` — each field may be a function of the existing row. */
412
+ type PatchRow<T extends SubgraphTable> = { [K in keyof WriteRow<T>]? : WriteRow<T>[K] | ((existing: InferTableRow<T> | null) => unknown) };
413
+ type TableName<S extends SubgraphSchema> = keyof S & string;
414
+ type ColumnName<
415
+ S extends SubgraphSchema,
416
+ T extends TableName<S>
417
+ > = keyof S[T]["columns"] & string;
418
+ type RowWhere<
419
+ S extends SubgraphSchema,
420
+ T extends TableName<S>
421
+ > = WhereInput<InferTableRow<S[T]>>;
422
+ /**
423
+ * Schema-typed handler context. Mirrors the runtime `SubgraphContext` but
424
+ * checks table names against `S` and row/where shapes against each table's
425
+ * columns. The runtime passes the concrete (untyped) context — this facade is
426
+ * type-level only.
427
+ */
428
+ interface TypedSubgraphContext<S extends SubgraphSchema> {
429
+ block: {
430
+ height: number
431
+ hash: string
432
+ timestamp: number
433
+ burnBlockHeight: number
434
+ /** Nakamoto StacksBlockId, when the block row carries one. */
435
+ indexBlockHash?: string | null
436
+ };
437
+ tx: TxMeta;
438
+ /**
439
+ * Read-only contract calls pinned to this block. Erased here for the same
440
+ * reason as on `SubgraphContext`: an ABI-generic method on a structurally
441
+ * compared interface exceeds TS's instantiation budget. Wrap it with
442
+ * `readContractAt(ctx, id, abi)` for full ABI typing at the call site.
443
+ */
444
+ client: ErasedChainReadClient;
445
+ insert<T extends TableName<S>>(table: T, row: WriteRow<S[T]>): void;
446
+ update<T extends TableName<S>>(table: T, where: RowWhere<S, T>, set: Partial<WriteRow<S[T]>>): void;
447
+ upsert<
448
+ T extends TableName<S>,
449
+ K extends Partial<WriteRow<S[T]>>
450
+ >(table: T, key: K, row: Omit<WriteRow<S[T]>, keyof K> & Partial<WriteRow<S[T]>>): void;
451
+ delete<T extends TableName<S>>(table: T, where: RowWhere<S, T>): void;
452
+ patch<T extends TableName<S>>(table: T, where: RowWhere<S, T>, set: Partial<WriteRow<S[T]>>): void;
453
+ patchOrInsert<T extends TableName<S>>(table: T, key: Partial<WriteRow<S[T]>>, row: PatchRow<S[T]> & Record<string, ComputedValue>): Promise<void>;
454
+ /**
455
+ * Atomic accumulator — `INSERT … ON CONFLICT (key) DO UPDATE SET col =
456
+ * COALESCE(col,0) + delta`. Deltas commute, so it's the reorg-safe primitive
457
+ * for running totals (per-cycle aggregates, balances). Requires a `uniqueKeys`
458
+ * constraint matching `key`; deltas may be `number` or `bigint`.
459
+ */
460
+ increment<T extends TableName<S>>(table: T, key: Partial<WriteRow<S[T]>>, deltas: Partial<Record<ColumnName<S, T>, bigint | number>>): void;
461
+ findOne<T extends TableName<S>>(table: T, where: RowWhere<S, T>): Promise<InferTableRow<S[T]> | null>;
462
+ findMany<T extends TableName<S>>(table: T, where: RowWhere<S, T>): Promise<InferTableRow<S[T]>[]>;
463
+ formatUnits(value: bigint, decimals: number): string;
464
+ count<T extends TableName<S>>(table: T, where?: RowWhere<S, T>): Promise<number>;
465
+ sum<T extends TableName<S>>(table: T, column: ColumnName<S, T>, where?: RowWhere<S, T>): Promise<bigint>;
466
+ min<T extends TableName<S>>(table: T, column: ColumnName<S, T>, where?: RowWhere<S, T>): Promise<bigint | null>;
467
+ max<T extends TableName<S>>(table: T, column: ColumnName<S, T>, where?: RowWhere<S, T>): Promise<bigint | null>;
468
+ countDistinct<T extends TableName<S>>(table: T, column: ColumnName<S, T>, where?: RowWhere<S, T>): Promise<number>;
469
+ }
470
+ interface FtTransferPayload {
471
+ sender: string;
472
+ recipient: string;
473
+ amount: bigint;
474
+ assetIdentifier: string;
475
+ tx: TxMeta;
476
+ }
477
+ interface FtMintPayload {
478
+ recipient: string;
479
+ amount: bigint;
480
+ assetIdentifier: string;
481
+ tx: TxMeta;
482
+ }
483
+ interface FtBurnPayload {
484
+ sender: string;
485
+ amount: bigint;
486
+ assetIdentifier: string;
487
+ tx: TxMeta;
488
+ }
489
+ interface NftTransferPayload {
490
+ sender: string;
491
+ recipient: string;
492
+ tokenId: unknown;
493
+ assetIdentifier: string;
494
+ tx: TxMeta;
495
+ }
496
+ interface NftMintPayload {
497
+ recipient: string;
498
+ tokenId: unknown;
499
+ assetIdentifier: string;
500
+ tx: TxMeta;
501
+ }
502
+ interface NftBurnPayload {
503
+ sender: string;
504
+ tokenId: unknown;
505
+ assetIdentifier: string;
506
+ tx: TxMeta;
507
+ }
508
+ interface StxTransferPayload {
509
+ sender: string;
510
+ recipient: string;
511
+ amount: bigint;
512
+ /** Memo string, or "" when none was attached. */
513
+ memo: string;
514
+ tx: TxMeta;
515
+ }
516
+ interface StxMintPayload {
517
+ recipient: string;
518
+ amount: bigint;
519
+ tx: TxMeta;
520
+ }
521
+ interface StxBurnPayload {
522
+ sender: string;
523
+ amount: bigint;
524
+ tx: TxMeta;
525
+ }
526
+ interface StxLockPayload {
527
+ lockedAddress: string;
528
+ lockedAmount: bigint;
529
+ unlockHeight: bigint;
530
+ tx: TxMeta;
531
+ }
532
+ interface PrintEventPayload {
533
+ contractId: string;
534
+ /** Decoded print topic (the Clarity tuple's `topic` field), or "". */
535
+ topic: string;
536
+ /**
537
+ * Remaining decoded tuple fields, camelCased. Empty object when the print
538
+ * value isn't a tuple. Narrow per `topic` to access typed fields.
539
+ */
540
+ data: Record<string, unknown>;
541
+ tx: TxMeta;
542
+ }
543
+ interface ContractDeployPayload {
544
+ contractId: string;
545
+ deployer: string;
546
+ tx: TxMeta;
547
+ }
548
+ /**
549
+ * Contract-call payload. When the source carries a `const` `abi` and a known
550
+ * `functionName`, `event.input` is the named, typed, decoded arguments
551
+ * (camelCased, via the contract ABI). The positional `event.args` is always
552
+ * present for back-compat. Without an `abi`, the payload is {@link ContractCallEvent}.
553
+ */
554
+ type ContractCallPayload<F> = F extends {
555
+ abi: infer A extends AbiContract3
556
+ functionName: infer N extends string
557
+ } ? N extends ExtractFunctionNames<A> ? ContractCallEvent & {
558
+ input: ExtractFunctionArgs2<A, N>
559
+ } : ContractCallEvent : ContractCallEvent;
560
+ /**
561
+ * Print event typed per topic. When a `print_event` source declares a `prints`
562
+ * map, the payload is a discriminated union keyed by `topic` with `data` typed
563
+ * per topic; otherwise it falls back to the untyped {@link PrintEventPayload}.
564
+ */
565
+ type PrintEventFor<F> = F extends {
566
+ prints: infer P extends Record<string, Record<string, PrintField>>
567
+ } ? { [K in keyof P] : {
568
+ contractId: string
569
+ topic: K
570
+ data: PrintDataOf<P[K]>
571
+ tx: TxMeta
572
+ } }[keyof P] : PrintEventPayload;
573
+ /** TS type of one declared print field (nested tuples and lists included). */
574
+ type PrintFieldToTS<F> = F extends ColumnType ? ColumnToTS<F> : F extends {
575
+ type: infer Inner
576
+ optional: true
577
+ } ? PrintFieldToTS<Inner> : F extends {
578
+ tuple: infer T extends Record<string, PrintField>
579
+ } ? PrintDataOf<T> : F extends {
580
+ list: infer Item
581
+ } ? PrintFieldToTS<Item>[] : unknown;
582
+ type OptionalFieldKeys<T> = { [K in keyof T] : T[K] extends {
583
+ optional: true
584
+ } ? K : never }[keyof T];
585
+ /** Collapse an intersection into one object literal, so hovering `event.data`
586
+ * shows the fields rather than `A & B`. */
587
+ type Flatten<T> = { [K in keyof T] : T[K] } & {};
588
+ /**
589
+ * `event.data` for one topic: declared fields typed, with
590
+ * `{ optional: true }` fields made optional KEYS (a field observed on only
591
+ * some events must not be typed as always present).
592
+ */
593
+ type PrintDataOf<T extends Record<string, PrintField>> = Flatten<{ [K in Exclude<keyof T, OptionalFieldKeys<T>>] : PrintFieldToTS<T[K]> } & { [K in OptionalFieldKeys<T>]? : PrintFieldToTS<T[K]> }>;
594
+ /** The event payload a handler receives for a given source filter. */
595
+ type EventForFilter<F extends SubgraphFilter> = F extends {
596
+ type: "print_event"
597
+ } ? PrintEventFor<F> : F extends {
598
+ type: "ft_transfer"
599
+ } ? FtTransferPayload : F extends {
600
+ type: "ft_mint"
601
+ } ? FtMintPayload : F extends {
602
+ type: "ft_burn"
603
+ } ? FtBurnPayload : F extends {
604
+ type: "nft_transfer"
605
+ } ? NftTransferPayload : F extends {
606
+ type: "nft_mint"
607
+ } ? NftMintPayload : F extends {
608
+ type: "nft_burn"
609
+ } ? NftBurnPayload : F extends {
610
+ type: "stx_transfer"
611
+ } ? StxTransferPayload : F extends {
612
+ type: "stx_mint"
613
+ } ? StxMintPayload : F extends {
614
+ type: "stx_burn"
615
+ } ? StxBurnPayload : F extends {
616
+ type: "stx_lock"
617
+ } ? StxLockPayload : F extends {
618
+ type: "contract_call"
619
+ } ? ContractCallPayload<F> : F extends {
620
+ type: "contract_deploy"
621
+ } ? ContractDeployPayload : never;
622
+ /** An in-memory subgraph context for tests. */
623
+ interface TestSubgraphContext<S extends SubgraphSchema> extends Omit<TypedSubgraphContext<S>, never> {
624
+ /**
625
+ * Current rows of `table`, INCLUDING writes queued by the handler you just
626
+ * ran (the same read-your-writes overlay the runtime uses). Drop straight
627
+ * into `toMatchInlineSnapshot()`.
628
+ */
629
+ rows(table: keyof S & string): Promise<Record<string, unknown>[]>;
630
+ /** Commit pending writes into the in-memory store, as an end-of-block flush
631
+ * would — call between blocks when a test spans several. */
632
+ commit(): Promise<void>;
633
+ /** Swap the transaction metadata handlers see (`ctx.tx`). */
634
+ setTx(tx: Partial<TxMeta2>): void;
635
+ }
636
+ /**
637
+ * Build an in-memory context for a subgraph's schema.
638
+ *
639
+ * ```ts
640
+ * const ctx = createTestContext(bns.schema, { block: { height: 167_484 } });
641
+ * await bns.handlers.bns!(buildEvent(bns.sources.bns, { … }), ctx);
642
+ * expect(await ctx.rows("names")).toMatchInlineSnapshot();
643
+ * ```
644
+ */
645
+ declare function createTestContext<const S extends SubgraphSchema>(schema: S, options?: {
646
+ block?: Partial<BlockMeta>
647
+ tx?: Partial<TxMeta2>
648
+ /** Stubbed `ctx.client.readOnly` results, keyed
649
+ * `"<contractId>.<function-name>"`. */
650
+ reads?: Record<string, unknown>
651
+ }): TestSubgraphContext<S>;
652
+ /**
653
+ * Build a handler-shaped event payload for a source, typed by that source's
654
+ * filter. `data` supplies the decoded, camelCased fields the runtime would
655
+ * have produced; block/tx metadata is filled in.
656
+ *
657
+ * ```ts
658
+ * buildEvent(bns.sources.bns, {
659
+ * topic: "name-register",
660
+ * data: { name: { name: "0x616c696365", namespace: "0x627463" } },
661
+ * })
662
+ * ```
663
+ */
664
+ declare function buildEvent<const F extends SubgraphFilter>(source: F, payload: Record<string, unknown>, options?: {
665
+ tx?: Partial<TxMeta2>
666
+ }): EventForFilter<F>;
667
+ export { createTestContext, buildEventPayload, buildEvent, TxMeta2 as TxMeta, TestSubgraphContext, BlockMeta };