@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.
Files changed (42) hide show
  1. package/dist/src/index.d.ts +188 -16
  2. package/dist/src/index.js +369 -41
  3. package/dist/src/index.js.map +12 -11
  4. package/dist/src/runtime/block-processor.d.ts +100 -13
  5. package/dist/src/runtime/block-processor.js +208 -22
  6. package/dist/src/runtime/block-processor.js.map +9 -8
  7. package/dist/src/runtime/catchup.d.ts +100 -13
  8. package/dist/src/runtime/catchup.js +208 -22
  9. package/dist/src/runtime/catchup.js.map +9 -8
  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 +213 -22
  14. package/dist/src/runtime/processor.js.map +10 -9
  15. package/dist/src/runtime/reindex.d.ts +100 -13
  16. package/dist/src/runtime/reindex.js +208 -22
  17. package/dist/src/runtime/reindex.js.map +9 -8
  18. package/dist/src/runtime/reorg.d.ts +100 -13
  19. package/dist/src/runtime/reorg.js +213 -22
  20. package/dist/src/runtime/reorg.js.map +10 -9
  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 +125 -17
  24. package/dist/src/runtime/runner.js +100 -3
  25. package/dist/src/runtime/runner.js.map +5 -4
  26. package/dist/src/runtime/source-matcher.d.ts +98 -11
  27. package/dist/src/runtime/source-matcher.js +30 -6
  28. package/dist/src/runtime/source-matcher.js.map +3 -3
  29. package/dist/src/schema/index.d.ts +100 -13
  30. package/dist/src/schema/index.js +171 -20
  31. package/dist/src/schema/index.js.map +5 -5
  32. package/dist/src/service.js +213 -22
  33. package/dist/src/service.js.map +10 -9
  34. package/dist/src/testing/index.d.ts +620 -0
  35. package/dist/src/testing/index.js +1297 -0
  36. package/dist/src/testing/index.js.map +16 -0
  37. package/dist/src/triggers/index.d.ts +87 -9
  38. package/dist/src/types.d.ts +101 -14
  39. package/dist/src/validate.d.ts +136 -14
  40. package/dist/src/validate.js +158 -19
  41. package/dist/src/validate.js.map +3 -3
  42. package/package.json +7 -5
package/dist/src/index.js CHANGED
@@ -14,6 +14,44 @@ var ColumnTypeSchema = z.enum([
14
14
  "timestamp",
15
15
  "jsonb"
16
16
  ]);
17
+ var PrintFieldSchema = z.lazy(() => z.union([
18
+ ColumnTypeSchema,
19
+ z.object({ type: PrintFieldSchema, optional: z.literal(true) }),
20
+ z.object({ tuple: z.record(z.string(), PrintFieldSchema) }),
21
+ z.object({ list: PrintFieldSchema })
22
+ ]));
23
+ var AbiTypeSchema = z.lazy(() => z.union([
24
+ z.string(),
25
+ z.object({ buff: z.object({ length: z.number() }) }),
26
+ z.object({ "string-ascii": z.object({ length: z.number() }) }),
27
+ z.object({ "string-utf8": z.object({ length: z.number() }) }),
28
+ z.object({ optional: AbiTypeSchema }),
29
+ z.object({
30
+ list: z.object({ type: AbiTypeSchema, length: z.number() })
31
+ }),
32
+ z.object({
33
+ tuple: z.array(z.object({ name: z.string(), type: AbiTypeSchema }))
34
+ }),
35
+ z.object({
36
+ response: z.object({ ok: AbiTypeSchema, error: AbiTypeSchema })
37
+ })
38
+ ]));
39
+ var AbiContractSchema = z.object({
40
+ functions: z.array(z.object({
41
+ name: z.string(),
42
+ access: z.enum(["public", "read-only", "private"], {
43
+ message: "ABI access must be public | read-only | private — a raw Hiro/Clarinet ABI uses `read_only`. Normalize it first: normalizeAbi() from @secondlayer/stacks/clarity."
44
+ }),
45
+ args: z.array(z.object({ name: z.string(), type: AbiTypeSchema })),
46
+ outputs: AbiTypeSchema
47
+ })),
48
+ maps: z.array(z.unknown()).optional(),
49
+ variables: z.array(z.unknown()).optional(),
50
+ fungible_tokens: z.array(z.unknown()).optional(),
51
+ non_fungible_tokens: z.array(z.unknown()).optional(),
52
+ implemented_traits: z.array(z.unknown()).optional(),
53
+ defined_traits: z.array(z.unknown()).optional()
54
+ });
17
55
  var SubgraphColumnSchema = z.object({
18
56
  type: ColumnTypeSchema,
19
57
  nullable: z.boolean().optional(),
@@ -48,24 +86,105 @@ var VALID_FILTER_TYPES = [
48
86
  "contract_deploy",
49
87
  "print_event"
50
88
  ];
51
- var SubgraphFilterSchema = z.object({
52
- type: z.enum(VALID_FILTER_TYPES),
53
- sender: z.string().optional(),
54
- 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 = {
55
98
  minAmount: z.bigint().optional(),
56
- maxAmount: z.bigint().optional(),
57
- assetIdentifier: z.string().optional(),
58
- contractId: z.string().optional(),
59
- functionName: z.string().optional(),
60
- caller: z.string().optional(),
61
- deployer: z.string().optional(),
62
- contractName: z.string().optional(),
63
- topic: z.string().optional(),
64
- lockedAddress: z.string().optional(),
65
- abi: z.record(z.string(), z.any()).optional(),
66
- trait: z.string().optional(),
67
- prints: z.record(z.string(), z.record(z.string(), ColumnTypeSchema)).optional()
68
- }).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;
69
188
  var SubgraphDefinitionSchema = z.object({
70
189
  name: SubgraphNameSchema,
71
190
  version: z.string().optional(),
@@ -79,6 +198,23 @@ var SubgraphDefinitionSchema = z.object({
79
198
  function validateSubgraphDefinition(def) {
80
199
  return SubgraphDefinitionSchema.parse(def);
81
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
+ }
82
218
 
83
219
  // src/runtime/context.ts
84
220
  import { logger } from "@secondlayer/shared/logger";
@@ -179,6 +315,18 @@ function emitJournalDDL(schemaName) {
179
315
  `CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
180
316
  ];
181
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
+ }
182
330
  function emitForeignKeyDDL(schemaName, tableName, tableDef) {
183
331
  return (tableDef.relations ?? []).map((rel) => {
184
332
  const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
@@ -197,6 +345,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
197
345
  statements.push(...emitTableDDL(schemaName, tableName, tableDef));
198
346
  }
199
347
  statements.push(...emitJournalDDL(schemaName));
348
+ if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
349
+ statements.push(...emitFactoryDDL(schemaName));
350
+ }
200
351
  for (const [tableName, tableDef] of Object.entries(def.schema)) {
201
352
  statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
202
353
  }
@@ -341,23 +492,21 @@ class SubgraphContext {
341
492
  formatUnits(value, decimals) {
342
493
  return formatUnits(value, decimals);
343
494
  }
344
- async findOne(table, where) {
345
- this.validateTable(table);
495
+ async readRows(table, where, limit) {
346
496
  const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
347
497
  const { clause } = buildWhereClause(where);
348
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} LIMIT 1`;
498
+ const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
349
499
  const { rows } = await sql.raw(query).execute(this.db);
350
- const row = rows[0] ?? null;
351
- 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);
352
506
  }
353
507
  async findMany(table, where) {
354
508
  this.validateTable(table);
355
- const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
356
- const { clause } = buildWhereClause(where);
357
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}`;
358
- const { rows } = await sql.raw(query).execute(this.db);
359
- const dbRows = rows.map((r) => this.coerceRow(table, r));
360
- return this.overlayMany(table, where, dbRows);
509
+ return this.overlayMany(table, where, await this.readRows(table, where));
361
510
  }
362
511
  overlayOne(table, where, dbRow) {
363
512
  if (this.ops.length === 0)
@@ -837,6 +986,87 @@ import {
837
986
  deserializeCV as deserializeCV2,
838
987
  toCamelCase
839
988
  } from "@secondlayer/stacks/clarity";
989
+
990
+ // src/runtime/print-validate.ts
991
+ var COLUMN_TYPES = new Set([
992
+ "text",
993
+ "uint",
994
+ "int",
995
+ "principal",
996
+ "boolean",
997
+ "timestamp",
998
+ "jsonb"
999
+ ]);
1000
+ function isColumnType(field) {
1001
+ return typeof field === "string" && COLUMN_TYPES.has(field);
1002
+ }
1003
+ function matchesField(field, value) {
1004
+ if (isColumnType(field)) {
1005
+ switch (field) {
1006
+ case "uint":
1007
+ case "int":
1008
+ return typeof value === "bigint" || typeof value === "number";
1009
+ case "boolean":
1010
+ return typeof value === "boolean";
1011
+ case "text":
1012
+ case "principal":
1013
+ case "timestamp":
1014
+ return typeof value === "string" || typeof value === "number" || typeof value === "bigint";
1015
+ case "jsonb":
1016
+ return value !== undefined;
1017
+ }
1018
+ }
1019
+ if (typeof field !== "object" || field === null)
1020
+ return false;
1021
+ if ("optional" in field) {
1022
+ return value === undefined || value === null || matchesField(field.type, value);
1023
+ }
1024
+ if ("tuple" in field) {
1025
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1026
+ return false;
1027
+ }
1028
+ return matchesTopic(field.tuple, value).ok;
1029
+ }
1030
+ if ("list" in field) {
1031
+ return Array.isArray(value) && value.every((item) => matchesField(field.list, item));
1032
+ }
1033
+ return false;
1034
+ }
1035
+ function matchesTopic(declared, data) {
1036
+ for (const [name, field] of Object.entries(declared)) {
1037
+ const present = name in data;
1038
+ const optional = typeof field === "object" && field !== null && "optional" in field;
1039
+ if (!present) {
1040
+ if (optional)
1041
+ continue;
1042
+ return { ok: false, reason: `missing declared field "${name}"` };
1043
+ }
1044
+ if (!matchesField(field, data[name])) {
1045
+ return {
1046
+ ok: false,
1047
+ reason: `field "${name}" does not match its declared type (got ${describe(data[name])})`
1048
+ };
1049
+ }
1050
+ }
1051
+ return { ok: true };
1052
+ }
1053
+ function describe(value) {
1054
+ if (value === null)
1055
+ return "null";
1056
+ if (Array.isArray(value))
1057
+ return "array";
1058
+ return typeof value;
1059
+ }
1060
+ function validatePrintPayload(prints, topic, data) {
1061
+ if (!prints)
1062
+ return { ok: true };
1063
+ const declared = prints[topic];
1064
+ if (!declared)
1065
+ return { ok: true };
1066
+ return matchesTopic(declared, data);
1067
+ }
1068
+
1069
+ // src/runtime/runner.ts
840
1070
  var DEFAULT_ERROR_THRESHOLD = 50;
841
1071
  function camelCase(str) {
842
1072
  return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
@@ -1081,6 +1311,7 @@ function buildEventPayload(filter, tx, event) {
1081
1311
  async function runHandlers(subgraph, matched, ctx, opts) {
1082
1312
  let processed = 0;
1083
1313
  let errors = 0;
1314
+ let skipped = 0;
1084
1315
  const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
1085
1316
  const filterLookup = new Map;
1086
1317
  if (!Array.isArray(subgraph.sources)) {
@@ -1105,7 +1336,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1105
1336
  errors,
1106
1337
  threshold
1107
1338
  });
1108
- return { processed, errors };
1339
+ return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1109
1340
  }
1110
1341
  const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
1111
1342
  if (!handler) {
@@ -1161,6 +1392,21 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1161
1392
  if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
1162
1393
  continue;
1163
1394
  }
1395
+ if (event !== null && filter?.type === "print_event" && filter.prints) {
1396
+ const printPayload = payload;
1397
+ const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
1398
+ if (!verdict.ok) {
1399
+ skipped++;
1400
+ logger2.warn("Print payload does not match declared prints schema", {
1401
+ subgraph: subgraph.name,
1402
+ sourceName,
1403
+ topic: printPayload.topic,
1404
+ txId: tx.tx_id,
1405
+ reason: verdict.reason
1406
+ });
1407
+ continue;
1408
+ }
1409
+ }
1164
1410
  await handler(payload, ctx);
1165
1411
  processed++;
1166
1412
  } catch (err) {
@@ -1175,11 +1421,14 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1175
1421
  });
1176
1422
  }
1177
1423
  }
1178
- return { processed, errors };
1424
+ return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1179
1425
  }
1180
1426
 
1181
1427
  // src/runtime/source-matcher.ts
1182
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
+ }
1183
1432
  function matchPattern(value, pattern) {
1184
1433
  if (!pattern.includes("*"))
1185
1434
  return value === pattern;
@@ -1192,6 +1441,22 @@ function matchPattern(value, pattern) {
1192
1441
  return re.test(value);
1193
1442
  }
1194
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
+ }
1195
1460
  function traitAllows(filter, contractId, traitContracts) {
1196
1461
  const trait = filter.trait;
1197
1462
  if (!trait)
@@ -1203,7 +1468,7 @@ function traitAllows(filter, contractId, traitContracts) {
1203
1468
  function assetContract(assetId) {
1204
1469
  return assetId?.split("::")[0];
1205
1470
  }
1206
- function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1471
+ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
1207
1472
  const results = [];
1208
1473
  switch (filter.type) {
1209
1474
  case "stx_transfer":
@@ -1330,9 +1595,11 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1330
1595
  if (tx.type !== "contract_call")
1331
1596
  continue;
1332
1597
  if (filter.contractId) {
1333
- if (!tx.contract_id || !matchPattern(tx.contract_id, filter.contractId))
1598
+ if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
1334
1599
  continue;
1335
1600
  }
1601
+ if (!factoryAllows(filter, tx.contract_id, factoryContracts))
1602
+ continue;
1336
1603
  if (filter.functionName) {
1337
1604
  if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
1338
1605
  continue;
@@ -1379,11 +1646,13 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1379
1646
  return false;
1380
1647
  const printContractId = data.contract_identifier ?? data.contract_id;
1381
1648
  if (filter.contractId) {
1382
- if (!printContractId || !matchPattern(printContractId, filter.contractId))
1649
+ if (!printContractId || !matchAny(printContractId, filter.contractId))
1383
1650
  return false;
1384
1651
  }
1385
1652
  if (!traitAllows(filter, printContractId, traitContracts))
1386
1653
  return false;
1654
+ if (!factoryAllows(filter, printContractId, factoryContracts))
1655
+ return false;
1387
1656
  return true;
1388
1657
  });
1389
1658
  if (matched.length > 0) {
@@ -1395,7 +1664,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1395
1664
  }
1396
1665
  return results;
1397
1666
  }
1398
- function matchSources(sources, transactions, events, traitContracts = new Map) {
1667
+ function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
1399
1668
  const eventsByTx = new Map;
1400
1669
  for (const event of events) {
1401
1670
  const list = eventsByTx.get(event.tx_id) ?? [];
@@ -1405,7 +1674,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
1405
1674
  const seen = new Set;
1406
1675
  const results = [];
1407
1676
  for (const [sourceName, filter] of Object.entries(sources)) {
1408
- const matches = matchFilter(filter, transactions, eventsByTx, traitContracts);
1677
+ const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
1409
1678
  for (const match of matches) {
1410
1679
  const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
1411
1680
  if (!seen.has(dedupeKey)) {
@@ -2060,6 +2329,57 @@ async function resolveRoute(subgraphName, targetDb) {
2060
2329
  function invalidateSubgraphRoute(subgraphName) {
2061
2330
  routeCache.delete(subgraphName);
2062
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
+ }
2063
2383
  async function resolveTraitContracts(subgraph, blockHeight, db) {
2064
2384
  const traits = new Set;
2065
2385
  for (const source of Object.values(subgraph.sources)) {
@@ -2146,8 +2466,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2146
2466
  txs = data.txs;
2147
2467
  evts = data.events;
2148
2468
  }
2469
+ const route = await resolveRoute(subgraphName, targetDb);
2470
+ const schemaName = route.schemaName;
2149
2471
  const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
2150
- 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);
2151
2474
  result.matched = matched.length;
2152
2475
  if (matched.length === 0) {
2153
2476
  if (!opts?.skipProgressUpdate) {
@@ -2155,8 +2478,6 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2155
2478
  }
2156
2479
  return result;
2157
2480
  }
2158
- const route = await resolveRoute(subgraphName, targetDb);
2159
- const schemaName = route.schemaName;
2160
2481
  const blockMeta = {
2161
2482
  height: block.height,
2162
2483
  hash: block.hash,
@@ -2214,6 +2535,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2214
2535
  });
2215
2536
  result.processed = runResult.processed;
2216
2537
  result.errors = runResult.errors;
2538
+ await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
2217
2539
  await targetDb.transaction().execute(async (tx) => {
2218
2540
  if (manifest && manifest.count > 0) {
2219
2541
  await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
@@ -2933,6 +3255,9 @@ async function backfillSubgraph(def, opts) {
2933
3255
  function defineSubgraph(def) {
2934
3256
  return def;
2935
3257
  }
3258
+ function defineSchema(schema) {
3259
+ return schema;
3260
+ }
2936
3261
  // src/print-schema.ts
2937
3262
  import {
2938
3263
  deserializeCV as deserializeCV3
@@ -3852,8 +4177,8 @@ function diffKeyLists(existing, incoming) {
3852
4177
  const existingSet = new Set((existing ?? []).map(key2));
3853
4178
  const incomingSet = new Set((incoming ?? []).map(key2));
3854
4179
  return {
3855
- added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))),
3856
- 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])
3857
4182
  };
3858
4183
  }
3859
4184
  function diffSchema(existing, incoming) {
@@ -4197,6 +4522,7 @@ function getDefault(type) {
4197
4522
  }
4198
4523
  export {
4199
4524
  validateSubgraphDefinition,
4525
+ validatePrintPayload,
4200
4526
  sparseProbeTargets,
4201
4527
  resumeReindex,
4202
4528
  renderDeployPlan,
@@ -4209,9 +4535,11 @@ export {
4209
4535
  generateKyselySchema,
4210
4536
  generateIndexSchema,
4211
4537
  generateDrizzleSchema,
4538
+ filterFieldsByType,
4212
4539
  diffSchema,
4213
4540
  deploySchema,
4214
4541
  defineSubgraph,
4542
+ defineSchema,
4215
4543
  canSparseScan,
4216
4544
  camelizeDataKey,
4217
4545
  backfillSubgraph,
@@ -4219,5 +4547,5 @@ export {
4219
4547
  ByoBreakingChangeError
4220
4548
  };
4221
4549
 
4222
- //# debugId=E040C325764492FC64756E2164756E21
4550
+ //# debugId=8BECEC189376FB2B64756E2164756E21
4223
4551
  //# sourceMappingURL=index.js.map