@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
@@ -25,15 +25,24 @@ interface SubgraphRelation {
25
25
  /** Target column(s) the fields point at (a uniqueKeys entry on the target). */
26
26
  referencedColumns: string[];
27
27
  }
28
- /** Table definition within a subgraph schema */
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
+ */
29
38
  interface SubgraphTable {
30
39
  columns: Record<string, SubgraphColumn>;
31
40
  /** Composite indexes (each entry is an array of column names) */
32
- indexes?: string[][];
41
+ indexes?: readonly (readonly string[])[];
33
42
  /** Unique key constraints (each entry is an array of column names). Required for upsert. */
34
- uniqueKeys?: string[][];
43
+ uniqueKeys?: readonly (readonly string[])[];
35
44
  /** Foreign-key relations to other tables (for typed ORM joins). */
36
- relations?: SubgraphRelation[];
45
+ relations?: readonly SubgraphRelation[];
37
46
  }
38
47
  /** Subgraph schema — maps table names to table definitions */
39
48
  type SubgraphSchema = Record<string, SubgraphTable>;
@@ -68,6 +77,45 @@ interface StxLockFilter {
68
77
  type TraitScope = {
69
78
  trait?: string
70
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
+ }
71
119
  /** FT event filters */
72
120
  interface FtTransferFilter extends TraitScope {
73
121
  type: "ft_transfer";
@@ -147,9 +195,19 @@ interface ContractCallEvent {
147
195
  };
148
196
  }
149
197
  /** Contract event filters */
150
- interface ContractCallFilter extends TraitScope {
198
+ interface ContractCallFilter extends TraitScope, FactoryScope {
151
199
  type: "contract_call";
152
- contractId?: string;
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[];
153
211
  functionName?: string;
154
212
  caller?: string;
155
213
  /**
@@ -187,9 +245,11 @@ type PrintField = ColumnType | {
187
245
  } | {
188
246
  list: PrintField
189
247
  };
190
- interface PrintEventFilter extends TraitScope {
248
+ interface PrintEventFilter extends TraitScope, FactoryScope {
191
249
  type: "print_event";
192
- contractId?: string;
250
+ /** One contract id, or a set of them (max 20). Supports `*` wildcards.
251
+ * Mutually exclusive with `trait`. */
252
+ contractId?: string | readonly string[];
193
253
  topic?: string;
194
254
  /**
195
255
  * Per-topic field schema. When declared, the handler's `event` is a
@@ -285,6 +345,16 @@ interface SubgraphDefinition {
285
345
  * Validates a subgraph definition, returning the parsed result or throwing on failure.
286
346
  */
287
347
  declare function validateSubgraphDefinition(def: unknown): SubgraphDefinition;
348
+ /**
349
+ * Per-source-type field vocabulary, DERIVED from
350
+ * {@link SubgraphFilterSchema}. Agent-facing surfaces (the MCP filters
351
+ * reference) used to hand-maintain a parallel copy of this list; deriving it
352
+ * means adding a field to a filter can never leave the documentation behind.
353
+ */
354
+ declare function filterFieldsByType(): Array<{
355
+ type: string
356
+ fields: string[]
357
+ }>;
288
358
  interface ReindexOptions {
289
359
  fromBlock?: number;
290
360
  toBlock?: number;
@@ -737,6 +807,48 @@ declare function defineSubgraph<
737
807
  const Sources extends Record<string, SubgraphFilter>,
738
808
  const S extends SubgraphSchema
739
809
  >(def: TypedSubgraphDefinition<Sources, S>): TypedSubgraphDefinition<Sources, S>;
810
+ /**
811
+ * Identity helper that preserves a schema literal's exact types so it can be
812
+ * HOISTED out of the `defineSubgraph()` call and reused:
813
+ *
814
+ * ```ts
815
+ * const schema = defineSchema({
816
+ * balances: {
817
+ * columns: { holder: { type: "principal" }, amount: { type: "uint" } },
818
+ * uniqueKeys: [["holder"]],
819
+ * },
820
+ * });
821
+ *
822
+ * // An extracted helper keeps the full typed surface — table names and row
823
+ * // columns are still checked:
824
+ * function credit(ctx: TypedSubgraphContext<typeof schema>, holder: string) {
825
+ * ctx.increment("balances", { holder }, { amount: 1n });
826
+ * }
827
+ *
828
+ * defineSubgraph({ name: "balances", schema, sources, handlers });
829
+ * ```
830
+ *
831
+ * `as const` alone does not work here (it produces `readonly` arrays that a
832
+ * mutable `uniqueKeys` rejected) and a bare literal widens `type` to `string`.
833
+ * That gap is why the shipped starter typed its helper `ctx: any` — and in
834
+ * doing so modelled a non-commutative read-modify-write the docs warn against.
835
+ */
836
+ declare function defineSchema<const S extends SubgraphSchema>(schema: S): S;
837
+ /**
838
+ * The typed handler context for a subgraph definition — for annotating
839
+ * helpers extracted out of a handler:
840
+ *
841
+ * ```ts
842
+ * const def = defineSubgraph({ … });
843
+ * function credit(ctx: InferContext<typeof def>) { … }
844
+ * ```
845
+ *
846
+ * Prefer `TypedSubgraphContext<typeof schema>` when the schema is hoisted;
847
+ * this is the form for when only the definition is in scope.
848
+ */
849
+ type InferContext<D> = D extends {
850
+ schema: infer S extends SubgraphSchema
851
+ } ? TypedSubgraphContext<S> : never;
740
852
  type PrintValidationResult = {
741
853
  ok: true
742
854
  } | {
@@ -988,4 +1100,4 @@ type SparseProbeTarget = {
988
1100
  declare function canSparseScan(subgraph: SubgraphDefinition): boolean;
989
1101
  /** Probe targets for a subgraph's filters: decoded type + contract scope when\\n* the filter pins one (assetIdentifier "SP….contract::asset" or contractId). */
990
1102
  declare function sparseProbeTargets(subgraph: SubgraphDefinition): SparseProbeTarget[];
991
- export { validateSubgraphDefinition, validatePrintPayload, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, inferPrintTopics, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, diffSchema, deploySchema, defineSubgraph, canSparseScan, camelizeDataKey, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubscribeOptions, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, SparseProbeTarget, RowValue, ReindexOptions, PrismaGenOptions, PrintValidationResult, PrintSample, PrintFieldToTS, PrintField, PrintEventPayload, PrintEventFor, PrintEventFilter, PrintDataOf, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferredTopicSchema, InferredPrintField, InferTableRow, InferSubgraphClient, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
1103
+ export { validateSubgraphDefinition, validatePrintPayload, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, inferPrintTopics, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, filterFieldsByType, diffSchema, deploySchema, defineSubgraph, defineSchema, canSparseScan, camelizeDataKey, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubscribeOptions, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, SparseProbeTarget, RowValue, ReindexOptions, PrismaGenOptions, PrintValidationResult, PrintSample, PrintFieldToTS, PrintField, PrintEventPayload, PrintEventFor, PrintEventFilter, PrintDataOf, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferredTopicSchema, InferredPrintField, InferTableRow, InferSubgraphClient, InferContext, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, FactoryScope, EventForFilter, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
package/dist/src/index.js CHANGED
@@ -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/runtime/context.ts
122
220
  import { logger } from "@secondlayer/shared/logger";
@@ -217,6 +315,18 @@ function emitJournalDDL(schemaName) {
217
315
  `CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
218
316
  ];
219
317
  }
318
+ function emitFactoryDDL(schemaName) {
319
+ return [
320
+ `CREATE TABLE IF NOT EXISTS ${schemaName}._factory_addresses (
321
+ source_name TEXT NOT NULL,
322
+ address TEXT NOT NULL,
323
+ block_height BIGINT NOT NULL,
324
+ _created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
325
+ PRIMARY KEY (source_name, address)
326
+ )`,
327
+ `CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
328
+ ];
329
+ }
220
330
  function emitForeignKeyDDL(schemaName, tableName, tableDef) {
221
331
  return (tableDef.relations ?? []).map((rel) => {
222
332
  const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
@@ -235,6 +345,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
235
345
  statements.push(...emitTableDDL(schemaName, tableName, tableDef));
236
346
  }
237
347
  statements.push(...emitJournalDDL(schemaName));
348
+ if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
349
+ statements.push(...emitFactoryDDL(schemaName));
350
+ }
238
351
  for (const [tableName, tableDef] of Object.entries(def.schema)) {
239
352
  statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
240
353
  }
@@ -379,23 +492,21 @@ class SubgraphContext {
379
492
  formatUnits(value, decimals) {
380
493
  return formatUnits(value, decimals);
381
494
  }
382
- async findOne(table, where) {
383
- this.validateTable(table);
495
+ async readRows(table, where, limit) {
384
496
  const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
385
497
  const { clause } = buildWhereClause(where);
386
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} LIMIT 1`;
498
+ const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
387
499
  const { rows } = await sql.raw(query).execute(this.db);
388
- const row = rows[0] ?? null;
389
- return this.overlayOne(table, where, row ? this.coerceRow(table, row) : null);
500
+ return rows.map((r) => this.coerceRow(table, r));
501
+ }
502
+ async findOne(table, where) {
503
+ this.validateTable(table);
504
+ const row = (await this.readRows(table, where, 1))[0] ?? null;
505
+ return this.overlayOne(table, where, row);
390
506
  }
391
507
  async findMany(table, where) {
392
508
  this.validateTable(table);
393
- const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
394
- const { clause } = buildWhereClause(where);
395
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}`;
396
- const { rows } = await sql.raw(query).execute(this.db);
397
- const dbRows = rows.map((r) => this.coerceRow(table, r));
398
- return this.overlayMany(table, where, dbRows);
509
+ return this.overlayMany(table, where, await this.readRows(table, where));
399
510
  }
400
511
  overlayOne(table, where, dbRow) {
401
512
  if (this.ops.length === 0)
@@ -1315,6 +1426,9 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1315
1426
 
1316
1427
  // src/runtime/source-matcher.ts
1317
1428
  var patternCache = new Map;
1429
+ function matchAny(value, pattern) {
1430
+ return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
1431
+ }
1318
1432
  function matchPattern(value, pattern) {
1319
1433
  if (!pattern.includes("*"))
1320
1434
  return value === pattern;
@@ -1327,6 +1441,22 @@ function matchPattern(value, pattern) {
1327
1441
  return re.test(value);
1328
1442
  }
1329
1443
  var EMPTY_SET = new Set;
1444
+ function factoryAllows(filter, contractId, factoryContracts) {
1445
+ if (!filter.factory)
1446
+ return true;
1447
+ if (!contractId)
1448
+ return false;
1449
+ return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
1450
+ }
1451
+ function readPath(payload, path) {
1452
+ let cur = payload;
1453
+ for (const key of path.split(".")) {
1454
+ if (cur === null || typeof cur !== "object")
1455
+ return;
1456
+ cur = cur[key];
1457
+ }
1458
+ return cur;
1459
+ }
1330
1460
  function traitAllows(filter, contractId, traitContracts) {
1331
1461
  const trait = filter.trait;
1332
1462
  if (!trait)
@@ -1338,7 +1468,7 @@ function traitAllows(filter, contractId, traitContracts) {
1338
1468
  function assetContract(assetId) {
1339
1469
  return assetId?.split("::")[0];
1340
1470
  }
1341
- function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1471
+ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
1342
1472
  const results = [];
1343
1473
  switch (filter.type) {
1344
1474
  case "stx_transfer":
@@ -1465,9 +1595,11 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1465
1595
  if (tx.type !== "contract_call")
1466
1596
  continue;
1467
1597
  if (filter.contractId) {
1468
- if (!tx.contract_id || !matchPattern(tx.contract_id, filter.contractId))
1598
+ if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
1469
1599
  continue;
1470
1600
  }
1601
+ if (!factoryAllows(filter, tx.contract_id, factoryContracts))
1602
+ continue;
1471
1603
  if (filter.functionName) {
1472
1604
  if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
1473
1605
  continue;
@@ -1514,11 +1646,13 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1514
1646
  return false;
1515
1647
  const printContractId = data.contract_identifier ?? data.contract_id;
1516
1648
  if (filter.contractId) {
1517
- if (!printContractId || !matchPattern(printContractId, filter.contractId))
1649
+ if (!printContractId || !matchAny(printContractId, filter.contractId))
1518
1650
  return false;
1519
1651
  }
1520
1652
  if (!traitAllows(filter, printContractId, traitContracts))
1521
1653
  return false;
1654
+ if (!factoryAllows(filter, printContractId, factoryContracts))
1655
+ return false;
1522
1656
  return true;
1523
1657
  });
1524
1658
  if (matched.length > 0) {
@@ -1530,7 +1664,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1530
1664
  }
1531
1665
  return results;
1532
1666
  }
1533
- function matchSources(sources, transactions, events, traitContracts = new Map) {
1667
+ function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
1534
1668
  const eventsByTx = new Map;
1535
1669
  for (const event of events) {
1536
1670
  const list = eventsByTx.get(event.tx_id) ?? [];
@@ -1540,7 +1674,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
1540
1674
  const seen = new Set;
1541
1675
  const results = [];
1542
1676
  for (const [sourceName, filter] of Object.entries(sources)) {
1543
- const matches = matchFilter(filter, transactions, eventsByTx, traitContracts);
1677
+ const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
1544
1678
  for (const match of matches) {
1545
1679
  const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
1546
1680
  if (!seen.has(dedupeKey)) {
@@ -2195,6 +2329,57 @@ async function resolveRoute(subgraphName, targetDb) {
2195
2329
  function invalidateSubgraphRoute(subgraphName) {
2196
2330
  routeCache.delete(subgraphName);
2197
2331
  }
2332
+ async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
2333
+ const factories = new Map;
2334
+ for (const source of Object.values(subgraph.sources)) {
2335
+ const factory = source.factory;
2336
+ if (factory)
2337
+ factories.set(factory.from, factory);
2338
+ }
2339
+ const resolved = new Map;
2340
+ const discovered = [];
2341
+ if (factories.size === 0)
2342
+ return { resolved, discovered };
2343
+ for (const [discoveringSource, factory] of factories) {
2344
+ const known = new Set;
2345
+ try {
2346
+ const rows = await sql3`
2347
+ SELECT address FROM ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
2348
+ WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
2349
+ `.execute(db);
2350
+ for (const row of rows.rows)
2351
+ known.add(row.address);
2352
+ } catch {}
2353
+ const discovering = subgraph.sources[factory.from];
2354
+ if (discovering) {
2355
+ const matches = matchSources({ [factory.from]: discovering }, txs, evts, new Map, new Map);
2356
+ for (const match of matches) {
2357
+ for (const event of match.events ?? []) {
2358
+ const payload = buildEventPayload(discovering, match.tx, event);
2359
+ const value = readPath(payload, factory.field);
2360
+ if (typeof value === "string" && value.length > 0 && !known.has(value)) {
2361
+ known.add(value);
2362
+ discovered.push({ sourceName: discoveringSource, address: value });
2363
+ }
2364
+ }
2365
+ }
2366
+ }
2367
+ resolved.set(discoveringSource, known);
2368
+ }
2369
+ return { resolved, discovered };
2370
+ }
2371
+ async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered) {
2372
+ if (discovered.length === 0)
2373
+ return;
2374
+ for (const { sourceName, address } of discovered) {
2375
+ await sql3`
2376
+ INSERT INTO ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
2377
+ (source_name, address, block_height)
2378
+ VALUES (${sourceName}, ${address}, ${blockHeight})
2379
+ ON CONFLICT (source_name, address) DO NOTHING
2380
+ `.execute(db);
2381
+ }
2382
+ }
2198
2383
  async function resolveTraitContracts(subgraph, blockHeight, db) {
2199
2384
  const traits = new Set;
2200
2385
  for (const source of Object.values(subgraph.sources)) {
@@ -2281,8 +2466,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2281
2466
  txs = data.txs;
2282
2467
  evts = data.events;
2283
2468
  }
2469
+ const route = await resolveRoute(subgraphName, targetDb);
2470
+ const schemaName = route.schemaName;
2284
2471
  const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
2285
- const matched = matchSources(subgraph.sources, txs, evts, traitContracts);
2472
+ const { resolved: factoryContracts, discovered } = await resolveFactoryContracts(subgraph, blockHeight, schemaName, targetDb, txs, evts);
2473
+ const matched = matchSources(subgraph.sources, txs, evts, traitContracts, factoryContracts);
2286
2474
  result.matched = matched.length;
2287
2475
  if (matched.length === 0) {
2288
2476
  if (!opts?.skipProgressUpdate) {
@@ -2290,8 +2478,6 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2290
2478
  }
2291
2479
  return result;
2292
2480
  }
2293
- const route = await resolveRoute(subgraphName, targetDb);
2294
- const schemaName = route.schemaName;
2295
2481
  const blockMeta = {
2296
2482
  height: block.height,
2297
2483
  hash: block.hash,
@@ -2349,6 +2535,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2349
2535
  });
2350
2536
  result.processed = runResult.processed;
2351
2537
  result.errors = runResult.errors;
2538
+ await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
2352
2539
  await targetDb.transaction().execute(async (tx) => {
2353
2540
  if (manifest && manifest.count > 0) {
2354
2541
  await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
@@ -3068,6 +3255,9 @@ async function backfillSubgraph(def, opts) {
3068
3255
  function defineSubgraph(def) {
3069
3256
  return def;
3070
3257
  }
3258
+ function defineSchema(schema) {
3259
+ return schema;
3260
+ }
3071
3261
  // src/print-schema.ts
3072
3262
  import {
3073
3263
  deserializeCV as deserializeCV3
@@ -3987,8 +4177,8 @@ function diffKeyLists(existing, incoming) {
3987
4177
  const existingSet = new Set((existing ?? []).map(key2));
3988
4178
  const incomingSet = new Set((incoming ?? []).map(key2));
3989
4179
  return {
3990
- added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))),
3991
- removed: (existing ?? []).filter((cols) => !incomingSet.has(key2(cols)))
4180
+ added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))).map((cols) => [...cols]),
4181
+ removed: (existing ?? []).filter((cols) => !incomingSet.has(key2(cols))).map((cols) => [...cols])
3992
4182
  };
3993
4183
  }
3994
4184
  function diffSchema(existing, incoming) {
@@ -4345,9 +4535,11 @@ export {
4345
4535
  generateKyselySchema,
4346
4536
  generateIndexSchema,
4347
4537
  generateDrizzleSchema,
4538
+ filterFieldsByType,
4348
4539
  diffSchema,
4349
4540
  deploySchema,
4350
4541
  defineSubgraph,
4542
+ defineSchema,
4351
4543
  canSparseScan,
4352
4544
  camelizeDataKey,
4353
4545
  backfillSubgraph,
@@ -4355,5 +4547,5 @@ export {
4355
4547
  ByoBreakingChangeError
4356
4548
  };
4357
4549
 
4358
- //# debugId=1014943AF0D5A26B64756E2164756E21
4550
+ //# debugId=8BECEC189376FB2B64756E2164756E21
4359
4551
  //# sourceMappingURL=index.js.map