@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
@@ -1,4 +1,32 @@
1
- import { AbiContract } from "@secondlayer/stacks/clarity";
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
+ }
2
30
  /** Supported column types for subgraph schemas */
3
31
  type ColumnType = "text" | "uint" | "int" | "principal" | "boolean" | "timestamp" | "jsonb";
4
32
  /** Column definition in a subgraph table */
@@ -25,15 +53,24 @@ interface SubgraphRelation {
25
53
  /** Target column(s) the fields point at (a uniqueKeys entry on the target). */
26
54
  referencedColumns: string[];
27
55
  }
28
- /** Table definition within a subgraph schema */
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
+ */
29
66
  interface SubgraphTable {
30
67
  columns: Record<string, SubgraphColumn>;
31
68
  /** Composite indexes (each entry is an array of column names) */
32
- indexes?: string[][];
69
+ indexes?: readonly (readonly string[])[];
33
70
  /** Unique key constraints (each entry is an array of column names). Required for upsert. */
34
- uniqueKeys?: string[][];
71
+ uniqueKeys?: readonly (readonly string[])[];
35
72
  /** Foreign-key relations to other tables (for typed ORM joins). */
36
- relations?: SubgraphRelation[];
73
+ relations?: readonly SubgraphRelation[];
37
74
  }
38
75
  /** Subgraph schema — maps table names to table definitions */
39
76
  type SubgraphSchema = Record<string, SubgraphTable>;
@@ -68,6 +105,45 @@ interface StxLockFilter {
68
105
  type TraitScope = {
69
106
  trait?: string
70
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
+ }
71
147
  /** FT event filters */
72
148
  interface FtTransferFilter extends TraitScope {
73
149
  type: "ft_transfer";
@@ -106,9 +182,19 @@ interface NftBurnFilter extends TraitScope {
106
182
  sender?: string;
107
183
  }
108
184
  /** Contract event filters */
109
- interface ContractCallFilter extends TraitScope {
185
+ interface ContractCallFilter extends TraitScope, FactoryScope {
110
186
  type: "contract_call";
111
- contractId?: string;
187
+ /**
188
+ * One contract id, or a SET of them (max 20) — the standard DeFi shape is
189
+ * a router plus N pools, which used to cost N near-identical sources and
190
+ * handlers, or the `"*"` catch-all that erases per-source typing. Payload
191
+ * types are identical per `type`, so one handler serves the set and
192
+ * `event.contractId` disambiguates.
193
+ *
194
+ * Supports `*` wildcards (e.g. `"SP1ABC….pool-*"`). Mutually exclusive
195
+ * with `trait`, which resolves its own set per block.
196
+ */
197
+ contractId?: string | readonly string[];
112
198
  functionName?: string;
113
199
  caller?: string;
114
200
  /**
@@ -117,7 +203,7 @@ interface ContractCallFilter extends TraitScope {
117
203
  * definition and used at runtime to decode args by name. Omit to keep
118
204
  * `event.args` as a positional `unknown[]`.
119
205
  */
120
- abi?: AbiContract;
206
+ abi?: AbiContract2;
121
207
  }
122
208
  interface ContractDeployFilter {
123
209
  type: "contract_deploy";
@@ -146,9 +232,11 @@ type PrintField = ColumnType | {
146
232
  } | {
147
233
  list: PrintField
148
234
  };
149
- interface PrintEventFilter extends TraitScope {
235
+ interface PrintEventFilter extends TraitScope, FactoryScope {
150
236
  type: "print_event";
151
- contractId?: string;
237
+ /** One contract id, or a set of them (max 20). Supports `*` wildcards.
238
+ * Mutually exclusive with `trait`. */
239
+ contractId?: string | readonly string[];
152
240
  topic?: string;
153
241
  /**
154
242
  * Per-topic field schema. When declared, the handler's `event` is a
@@ -186,8 +274,21 @@ interface SubgraphContext {
186
274
  hash: string
187
275
  timestamp: number
188
276
  burnBlockHeight: number
277
+ /** Nakamoto StacksBlockId, when the block row carries one. */
278
+ indexBlockHash?: string | null
189
279
  };
190
280
  tx: TxMeta;
281
+ /**
282
+ * Read-only contract calls pinned to this block. Cached, so the same
283
+ * (contract, function, args) is fetched once per block rather than once
284
+ * per event.
285
+ *
286
+ * Erased here on purpose: this interface is compared structurally wherever
287
+ * a subgraph definition is passed around, and carrying an ABI-generic
288
+ * method pushes those comparisons past TS's instantiation depth. Handlers
289
+ * receive `TypedSubgraphContext`, which exposes the fully typed version.
290
+ */
291
+ client: ErasedChainReadClient;
191
292
  insert(table: string, row: Record<string, unknown>): void;
192
293
  update(table: string, where: Record<string, unknown>, set: Record<string, unknown>): void;
193
294
  upsert(table: string, key: Record<string, unknown>, row: Record<string, unknown>): void;
@@ -251,9 +352,9 @@ interface GeneratedSQL {
251
352
  * each with auto-columns and indexes.
252
353
  */
253
354
  declare function generateSubgraphSQL(def: SubgraphDefinition, schemaNameOverride?: string): GeneratedSQL;
254
- import { Database } from "@secondlayer/shared/db";
255
- import { Kysely } from "kysely";
256
- type AnyDb = Kysely<Database>;
355
+ import { Database as Database2 } from "@secondlayer/shared/db";
356
+ import { Kysely as Kysely2 } from "kysely";
357
+ type AnyDb = Kysely2<Database2>;
257
358
  interface TableDiff {
258
359
  /** Tables added to the schema */
259
360
  addedTables: string[];
@@ -86,24 +86,105 @@ var VALID_FILTER_TYPES = [
86
86
  "contract_deploy",
87
87
  "print_event"
88
88
  ];
89
- var SubgraphFilterSchema = z.object({
90
- type: z.enum(VALID_FILTER_TYPES),
91
- sender: z.string().optional(),
92
- recipient: z.string().optional(),
89
+ var contractIdField = z.union([
90
+ z.string(),
91
+ z.array(z.string()).min(1).max(20)
92
+ ]);
93
+ var traitScope = { trait: z.string().optional() };
94
+ var factoryScope = {
95
+ factory: z.object({ from: z.string().min(1), field: z.string().min(1) }).strict().optional()
96
+ };
97
+ var amountRange = {
93
98
  minAmount: z.bigint().optional(),
94
- maxAmount: z.bigint().optional(),
95
- assetIdentifier: z.string().optional(),
96
- contractId: z.string().optional(),
97
- functionName: z.string().optional(),
98
- caller: z.string().optional(),
99
- deployer: z.string().optional(),
100
- contractName: z.string().optional(),
101
- topic: z.string().optional(),
102
- lockedAddress: z.string().optional(),
103
- abi: AbiContractSchema.optional(),
104
- trait: z.string().optional(),
105
- prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional()
106
- }).strict();
99
+ maxAmount: z.bigint().optional()
100
+ };
101
+ var SubgraphFilterUnion = z.discriminatedUnion("type", [
102
+ z.object({
103
+ type: z.literal("stx_transfer"),
104
+ sender: z.string().optional(),
105
+ recipient: z.string().optional(),
106
+ ...amountRange
107
+ }).strict(),
108
+ z.object({
109
+ type: z.literal("stx_mint"),
110
+ recipient: z.string().optional(),
111
+ minAmount: z.bigint().optional()
112
+ }).strict(),
113
+ z.object({
114
+ type: z.literal("stx_burn"),
115
+ sender: z.string().optional(),
116
+ minAmount: z.bigint().optional()
117
+ }).strict(),
118
+ z.object({
119
+ type: z.literal("stx_lock"),
120
+ lockedAddress: z.string().optional(),
121
+ minAmount: z.bigint().optional()
122
+ }).strict(),
123
+ z.object({
124
+ type: z.literal("ft_transfer"),
125
+ assetIdentifier: z.string().optional(),
126
+ sender: z.string().optional(),
127
+ recipient: z.string().optional(),
128
+ minAmount: z.bigint().optional(),
129
+ ...traitScope
130
+ }).strict(),
131
+ z.object({
132
+ type: z.literal("ft_mint"),
133
+ assetIdentifier: z.string().optional(),
134
+ recipient: z.string().optional(),
135
+ minAmount: z.bigint().optional(),
136
+ ...traitScope
137
+ }).strict(),
138
+ z.object({
139
+ type: z.literal("ft_burn"),
140
+ assetIdentifier: z.string().optional(),
141
+ sender: z.string().optional(),
142
+ minAmount: z.bigint().optional(),
143
+ ...traitScope
144
+ }).strict(),
145
+ z.object({
146
+ type: z.literal("nft_transfer"),
147
+ assetIdentifier: z.string().optional(),
148
+ sender: z.string().optional(),
149
+ recipient: z.string().optional(),
150
+ ...traitScope
151
+ }).strict(),
152
+ z.object({
153
+ type: z.literal("nft_mint"),
154
+ assetIdentifier: z.string().optional(),
155
+ recipient: z.string().optional(),
156
+ ...traitScope
157
+ }).strict(),
158
+ z.object({
159
+ type: z.literal("nft_burn"),
160
+ assetIdentifier: z.string().optional(),
161
+ sender: z.string().optional(),
162
+ ...traitScope
163
+ }).strict(),
164
+ z.object({
165
+ type: z.literal("contract_call"),
166
+ contractId: contractIdField.optional(),
167
+ functionName: z.string().optional(),
168
+ caller: z.string().optional(),
169
+ abi: AbiContractSchema.optional(),
170
+ ...traitScope,
171
+ ...factoryScope
172
+ }).strict(),
173
+ z.object({
174
+ type: z.literal("contract_deploy"),
175
+ deployer: z.string().optional(),
176
+ contractName: z.string().optional()
177
+ }).strict(),
178
+ z.object({
179
+ type: z.literal("print_event"),
180
+ contractId: contractIdField.optional(),
181
+ topic: z.string().optional(),
182
+ prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional(),
183
+ ...traitScope,
184
+ ...factoryScope
185
+ }).strict()
186
+ ]);
187
+ var SubgraphFilterSchema = SubgraphFilterUnion;
107
188
  var SubgraphDefinitionSchema = z.object({
108
189
  name: SubgraphNameSchema,
109
190
  version: z.string().optional(),
@@ -117,6 +198,23 @@ var SubgraphDefinitionSchema = z.object({
117
198
  function validateSubgraphDefinition(def) {
118
199
  return SubgraphDefinitionSchema.parse(def);
119
200
  }
201
+ function filterFieldsByType() {
202
+ const options = SubgraphFilterUnion?._def?.options ?? [];
203
+ const out = [];
204
+ for (const option of options) {
205
+ const shape = option?.shape ?? option?._def?.shape;
206
+ if (!shape)
207
+ continue;
208
+ const literal = shape.type?._def?.values?.[0] ?? shape.type?._def?.value ?? undefined;
209
+ if (typeof literal !== "string")
210
+ continue;
211
+ out.push({
212
+ type: literal,
213
+ fields: Object.keys(shape).filter((k) => k !== "type")
214
+ });
215
+ }
216
+ return out;
217
+ }
120
218
 
121
219
  // src/schema/generator.ts
122
220
  import { createHash } from "node:crypto";
@@ -212,6 +310,18 @@ function emitJournalDDL(schemaName) {
212
310
  `CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
213
311
  ];
214
312
  }
313
+ function emitFactoryDDL(schemaName) {
314
+ return [
315
+ `CREATE TABLE IF NOT EXISTS ${schemaName}._factory_addresses (
316
+ source_name TEXT NOT NULL,
317
+ address TEXT NOT NULL,
318
+ block_height BIGINT NOT NULL,
319
+ _created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
320
+ PRIMARY KEY (source_name, address)
321
+ )`,
322
+ `CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
323
+ ];
324
+ }
215
325
  function emitForeignKeyDDL(schemaName, tableName, tableDef) {
216
326
  return (tableDef.relations ?? []).map((rel) => {
217
327
  const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
@@ -230,6 +340,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
230
340
  statements.push(...emitTableDDL(schemaName, tableName, tableDef));
231
341
  }
232
342
  statements.push(...emitJournalDDL(schemaName));
343
+ if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
344
+ statements.push(...emitFactoryDDL(schemaName));
345
+ }
233
346
  for (const [tableName, tableDef] of Object.entries(def.schema)) {
234
347
  statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
235
348
  }
@@ -255,8 +368,8 @@ function diffKeyLists(existing, incoming) {
255
368
  const existingSet = new Set((existing ?? []).map(key));
256
369
  const incomingSet = new Set((incoming ?? []).map(key));
257
370
  return {
258
- added: (incoming ?? []).filter((cols) => !existingSet.has(key(cols))),
259
- removed: (existing ?? []).filter((cols) => !incomingSet.has(key(cols)))
371
+ added: (incoming ?? []).filter((cols) => !existingSet.has(key(cols))).map((cols) => [...cols]),
372
+ removed: (existing ?? []).filter((cols) => !incomingSet.has(key(cols))).map((cols) => [...cols])
260
373
  };
261
374
  }
262
375
  function diffSchema(existing, incoming) {
@@ -605,5 +718,5 @@ export {
605
718
  TYPE_MAP
606
719
  };
607
720
 
608
- //# debugId=9CD49545FF5B17EC64756E2164756E21
721
+ //# debugId=A19E9CF28F38E66064756E2164756E21
609
722
  //# sourceMappingURL=index.js.map