@secondlayer/subgraphs 3.20.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.
- package/dist/src/index.d.ts +188 -16
- package/dist/src/index.js +369 -41
- package/dist/src/index.js.map +12 -11
- package/dist/src/runtime/block-processor.d.ts +100 -13
- package/dist/src/runtime/block-processor.js +208 -22
- package/dist/src/runtime/block-processor.js.map +9 -8
- package/dist/src/runtime/catchup.d.ts +100 -13
- package/dist/src/runtime/catchup.js +208 -22
- package/dist/src/runtime/catchup.js.map +9 -8
- package/dist/src/runtime/context.d.ts +34 -8
- package/dist/src/runtime/context.js +25 -12
- package/dist/src/runtime/context.js.map +4 -4
- package/dist/src/runtime/processor.js +213 -22
- package/dist/src/runtime/processor.js.map +10 -9
- package/dist/src/runtime/reindex.d.ts +100 -13
- package/dist/src/runtime/reindex.js +208 -22
- package/dist/src/runtime/reindex.js.map +9 -8
- package/dist/src/runtime/reorg.d.ts +100 -13
- package/dist/src/runtime/reorg.js +213 -22
- package/dist/src/runtime/reorg.js.map +10 -9
- package/dist/src/runtime/replay.js +29 -6
- package/dist/src/runtime/replay.js.map +3 -3
- package/dist/src/runtime/runner.d.ts +125 -17
- package/dist/src/runtime/runner.js +100 -3
- package/dist/src/runtime/runner.js.map +5 -4
- package/dist/src/runtime/source-matcher.d.ts +98 -11
- package/dist/src/runtime/source-matcher.js +30 -6
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/schema/index.d.ts +100 -13
- package/dist/src/schema/index.js +171 -20
- package/dist/src/schema/index.js.map +5 -5
- package/dist/src/service.js +213 -22
- package/dist/src/service.js.map +10 -9
- package/dist/src/testing/index.d.ts +620 -0
- package/dist/src/testing/index.js +1297 -0
- package/dist/src/testing/index.js.map +16 -0
- package/dist/src/triggers/index.d.ts +87 -9
- package/dist/src/types.d.ts +101 -14
- package/dist/src/validate.d.ts +136 -14
- package/dist/src/validate.js +158 -19
- package/dist/src/validate.js.map +3 -3
- package/package.json +7 -5
|
@@ -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 };
|