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