@secondlayer/subgraphs 4.1.4 → 4.2.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 (41) hide show
  1. package/dist/src/index.d.ts +91 -31
  2. package/dist/src/index.js +3319 -3067
  3. package/dist/src/index.js.map +11 -9
  4. package/dist/src/runtime/block-processor.d.ts +72 -23
  5. package/dist/src/runtime/block-processor.js +867 -400
  6. package/dist/src/runtime/block-processor.js.map +9 -6
  7. package/dist/src/runtime/catchup.d.ts +72 -23
  8. package/dist/src/runtime/catchup.js +878 -411
  9. package/dist/src/runtime/catchup.js.map +9 -6
  10. package/dist/src/runtime/processor.js +943 -475
  11. package/dist/src/runtime/processor.js.map +10 -7
  12. package/dist/src/runtime/reindex.d.ts +72 -23
  13. package/dist/src/runtime/reindex.js +911 -444
  14. package/dist/src/runtime/reindex.js.map +9 -6
  15. package/dist/src/runtime/reorg.d.ts +72 -23
  16. package/dist/src/runtime/reorg.js +890 -422
  17. package/dist/src/runtime/reorg.js.map +10 -7
  18. package/dist/src/runtime/replay.js +12 -6
  19. package/dist/src/runtime/replay.js.map +4 -4
  20. package/dist/src/runtime/runner.d.ts +72 -23
  21. package/dist/src/runtime/runner.js +757 -28
  22. package/dist/src/runtime/runner.js.map +9 -5
  23. package/dist/src/runtime/source-matcher.d.ts +80 -24
  24. package/dist/src/runtime/source-matcher.js +12 -5
  25. package/dist/src/runtime/source-matcher.js.map +3 -3
  26. package/dist/src/schema/index.d.ts +72 -23
  27. package/dist/src/schema/index.js +115 -1
  28. package/dist/src/schema/index.js.map +3 -3
  29. package/dist/src/service.js +983 -515
  30. package/dist/src/service.js.map +10 -7
  31. package/dist/src/testing/index.d.ts +195 -24
  32. package/dist/src/testing/index.js +1049 -30
  33. package/dist/src/testing/index.js.map +10 -5
  34. package/dist/src/triggers/index.d.ts +83 -24
  35. package/dist/src/triggers/index.js +5 -2
  36. package/dist/src/triggers/index.js.map +3 -3
  37. package/dist/src/types.d.ts +73 -24
  38. package/dist/src/validate.d.ts +72 -23
  39. package/dist/src/validate.js +115 -1
  40. package/dist/src/validate.js.map +3 -3
  41. package/package.json +2 -2
@@ -942,15 +942,679 @@ function decodeFunctionArgs(args) {
942
942
  return args.map(decodeClarityValue);
943
943
  }
944
944
 
945
+ // src/runtime/source-matcher.ts
946
+ var patternCache = new Map;
947
+ function matchAny(value, pattern) {
948
+ return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
949
+ }
950
+ function matchPattern(value, pattern) {
951
+ if (!pattern.includes("*"))
952
+ return value === pattern;
953
+ let re = patternCache.get(pattern);
954
+ if (!re) {
955
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
956
+ re = new RegExp(`^${regex}$`);
957
+ patternCache.set(pattern, re);
958
+ }
959
+ return re.test(value);
960
+ }
961
+ var EMPTY_SET = new Set;
962
+ function factoryAllows(filter, contractId, factoryContracts) {
963
+ if (!filter.factory)
964
+ return true;
965
+ if (!contractId)
966
+ return false;
967
+ return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
968
+ }
969
+ function readPath(payload, path) {
970
+ let cur = payload;
971
+ for (const key of path.split(".")) {
972
+ if (cur === null || typeof cur !== "object")
973
+ return;
974
+ cur = cur[key];
975
+ }
976
+ return cur;
977
+ }
978
+ function traitAllows(filter, contractId, traitContracts) {
979
+ const trait = filter.trait;
980
+ if (!trait)
981
+ return true;
982
+ if (!contractId)
983
+ return false;
984
+ return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
985
+ }
986
+ function assetContract(assetId) {
987
+ return assetId?.split("::")[0];
988
+ }
989
+ function printContractId(data) {
990
+ if (!data)
991
+ return;
992
+ const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
993
+ return id || undefined;
994
+ }
995
+ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
996
+ const results = [];
997
+ switch (filter.type) {
998
+ case "stx_transfer":
999
+ case "stx_mint":
1000
+ case "stx_burn":
1001
+ case "stx_lock": {
1002
+ const eventType = `${filter.type}_event`;
1003
+ for (const tx of transactions) {
1004
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1005
+ const matched = txEvents.filter((e) => e.type === eventType);
1006
+ if (matched.length === 0)
1007
+ continue;
1008
+ const filtered = matched.filter((e) => {
1009
+ const data = e.data;
1010
+ if (!data)
1011
+ return false;
1012
+ if ("sender" in filter && filter.sender) {
1013
+ if (!matchPattern(data.sender, filter.sender))
1014
+ return false;
1015
+ }
1016
+ if ("recipient" in filter && filter.recipient) {
1017
+ if (!matchPattern(data.recipient, filter.recipient))
1018
+ return false;
1019
+ }
1020
+ if ("lockedAddress" in filter && filter.lockedAddress) {
1021
+ if (!matchPattern(data.locked_address, filter.lockedAddress))
1022
+ return false;
1023
+ }
1024
+ if ("minAmount" in filter && filter.minAmount !== undefined) {
1025
+ const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
1026
+ if (amount < filter.minAmount)
1027
+ return false;
1028
+ }
1029
+ if ("maxAmount" in filter && filter.maxAmount !== undefined) {
1030
+ const amount = BigInt(data.amount ?? "0");
1031
+ if (amount > filter.maxAmount)
1032
+ return false;
1033
+ }
1034
+ return true;
1035
+ });
1036
+ if (filtered.length > 0) {
1037
+ results.push({ tx, events: filtered });
1038
+ }
1039
+ }
1040
+ break;
1041
+ }
1042
+ case "ft_transfer":
1043
+ case "ft_mint":
1044
+ case "ft_burn": {
1045
+ const eventType = `${filter.type}_event`;
1046
+ for (const tx of transactions) {
1047
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1048
+ const matched = txEvents.filter((e) => {
1049
+ if (e.type !== eventType)
1050
+ return false;
1051
+ const data = e.data;
1052
+ if (!data)
1053
+ return false;
1054
+ if (filter.assetIdentifier) {
1055
+ const assetId = data.asset_identifier;
1056
+ if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
1057
+ return false;
1058
+ }
1059
+ if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
1060
+ return false;
1061
+ if ("sender" in filter && filter.sender) {
1062
+ if (!matchPattern(data.sender, filter.sender))
1063
+ return false;
1064
+ }
1065
+ if ("recipient" in filter && filter.recipient) {
1066
+ if (!matchPattern(data.recipient, filter.recipient))
1067
+ return false;
1068
+ }
1069
+ if (filter.minAmount !== undefined) {
1070
+ const amount = BigInt(data.amount ?? "0");
1071
+ if (amount < filter.minAmount)
1072
+ return false;
1073
+ }
1074
+ return true;
1075
+ });
1076
+ if (matched.length > 0) {
1077
+ results.push({ tx, events: matched });
1078
+ }
1079
+ }
1080
+ break;
1081
+ }
1082
+ case "nft_transfer":
1083
+ case "nft_mint":
1084
+ case "nft_burn": {
1085
+ const eventType = `${filter.type}_event`;
1086
+ for (const tx of transactions) {
1087
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1088
+ const matched = txEvents.filter((e) => {
1089
+ if (e.type !== eventType)
1090
+ return false;
1091
+ const data = e.data;
1092
+ if (!data)
1093
+ return false;
1094
+ if (filter.assetIdentifier) {
1095
+ const assetId = data.asset_identifier;
1096
+ if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
1097
+ return false;
1098
+ }
1099
+ if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
1100
+ return false;
1101
+ if ("sender" in filter && filter.sender) {
1102
+ if (!matchPattern(data.sender, filter.sender))
1103
+ return false;
1104
+ }
1105
+ if ("recipient" in filter && filter.recipient) {
1106
+ if (!matchPattern(data.recipient, filter.recipient))
1107
+ return false;
1108
+ }
1109
+ return true;
1110
+ });
1111
+ if (matched.length > 0) {
1112
+ results.push({ tx, events: matched });
1113
+ }
1114
+ }
1115
+ break;
1116
+ }
1117
+ case "contract_call": {
1118
+ for (const tx of transactions) {
1119
+ if (tx.type !== "contract_call")
1120
+ continue;
1121
+ if (filter.contractId) {
1122
+ if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
1123
+ continue;
1124
+ }
1125
+ if (!factoryAllows(filter, tx.contract_id, factoryContracts))
1126
+ continue;
1127
+ if (filter.functionName) {
1128
+ if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
1129
+ continue;
1130
+ }
1131
+ if (filter.caller) {
1132
+ if (!matchPattern(tx.sender, filter.caller))
1133
+ continue;
1134
+ }
1135
+ if (!traitAllows(filter, tx.contract_id, traitContracts))
1136
+ continue;
1137
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1138
+ results.push({ tx, events: txEvents });
1139
+ }
1140
+ break;
1141
+ }
1142
+ case "contract_deploy": {
1143
+ for (const tx of transactions) {
1144
+ if (tx.type !== "smart_contract")
1145
+ continue;
1146
+ if (filter.deployer) {
1147
+ if (!matchPattern(tx.sender, filter.deployer))
1148
+ continue;
1149
+ }
1150
+ if (filter.contractName) {
1151
+ const name = tx.contract_id?.split(".")[1] ?? "";
1152
+ if (!matchPattern(name, filter.contractName))
1153
+ continue;
1154
+ }
1155
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1156
+ results.push({ tx, events: txEvents });
1157
+ }
1158
+ break;
1159
+ }
1160
+ case "print_event": {
1161
+ for (const tx of transactions) {
1162
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1163
+ const matched = txEvents.filter((e) => {
1164
+ if (e.type !== "smart_contract_event" && e.type !== "contract_event")
1165
+ return false;
1166
+ const data = e.data;
1167
+ if (!data)
1168
+ return false;
1169
+ if (data.topic !== "print")
1170
+ return false;
1171
+ const contractId = printContractId(data);
1172
+ if (filter.contractId) {
1173
+ if (!contractId || !matchAny(contractId, filter.contractId))
1174
+ return false;
1175
+ }
1176
+ if (!traitAllows(filter, contractId, traitContracts))
1177
+ return false;
1178
+ if (!factoryAllows(filter, contractId, factoryContracts))
1179
+ return false;
1180
+ return true;
1181
+ });
1182
+ if (matched.length > 0) {
1183
+ results.push({ tx, events: matched });
1184
+ }
1185
+ }
1186
+ break;
1187
+ }
1188
+ }
1189
+ return results;
1190
+ }
1191
+ function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
1192
+ const eventsByTx = new Map;
1193
+ for (const event of events) {
1194
+ const list = eventsByTx.get(event.tx_id) ?? [];
1195
+ list.push(event);
1196
+ eventsByTx.set(event.tx_id, list);
1197
+ }
1198
+ const seen = new Set;
1199
+ const results = [];
1200
+ for (const [sourceName, filter] of Object.entries(sources)) {
1201
+ const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
1202
+ for (const match of matches) {
1203
+ const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
1204
+ if (!seen.has(dedupeKey)) {
1205
+ seen.add(dedupeKey);
1206
+ results.push({ ...match, sourceName });
1207
+ }
1208
+ }
1209
+ }
1210
+ return results;
1211
+ }
1212
+
945
1213
  // src/runtime/runner.ts
946
1214
  import { getErrorMessage } from "@secondlayer/shared";
947
- import { logger as logger3 } from "@secondlayer/shared/logger";
1215
+ import { logger as logger4 } from "@secondlayer/shared/logger";
948
1216
  import {
949
1217
  clarityValueToJS,
950
- deserializeCV as deserializeCV3,
1218
+ deserializeCV as deserializeCV4,
951
1219
  toCamelCase as toCamelCase2
952
1220
  } from "@secondlayer/stacks/clarity";
953
1221
 
1222
+ // src/print-schema.ts
1223
+ import {
1224
+ deserializeCV as deserializeCV3
1225
+ } from "@secondlayer/stacks/clarity";
1226
+ function camelizeDataKey(str) {
1227
+ return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
1228
+ }
1229
+ function camelizeKeys(obj) {
1230
+ if (obj === null || obj === undefined)
1231
+ return obj;
1232
+ if (typeof obj !== "object")
1233
+ return obj;
1234
+ if (Array.isArray(obj))
1235
+ return obj.map(camelizeKeys);
1236
+ const result = {};
1237
+ for (const [k, v] of Object.entries(obj)) {
1238
+ result[camelizeDataKey(k)] = camelizeKeys(v);
1239
+ }
1240
+ return result;
1241
+ }
1242
+ function cvToTree(cv) {
1243
+ switch (cv.type) {
1244
+ case "uint":
1245
+ case "int":
1246
+ return { kind: cv.type };
1247
+ case "true":
1248
+ case "false":
1249
+ return { kind: "bool" };
1250
+ case "address":
1251
+ case "contract":
1252
+ return { kind: "principal" };
1253
+ case "buffer":
1254
+ return { kind: "buffer", len: cv.value.length / 2 };
1255
+ case "ascii":
1256
+ return { kind: "ascii", len: cv.value.length };
1257
+ case "utf8":
1258
+ return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
1259
+ case "none":
1260
+ return { kind: "optional", inner: null };
1261
+ case "some":
1262
+ return { kind: "optional", inner: cvToTree(cv.value) };
1263
+ case "ok":
1264
+ return { kind: "response", ok: cvToTree(cv.value), err: null };
1265
+ case "err":
1266
+ return { kind: "response", ok: null, err: cvToTree(cv.value) };
1267
+ case "list": {
1268
+ let inner = null;
1269
+ for (const el of cv.value) {
1270
+ const t = cvToTree(el);
1271
+ inner = inner ? unify(inner, t) : t;
1272
+ }
1273
+ return { kind: "list", inner };
1274
+ }
1275
+ case "tuple": {
1276
+ const entries = new Map;
1277
+ for (const [k, v] of Object.entries(cv.value)) {
1278
+ entries.set(k, { tree: cvToTree(v), present: 1 });
1279
+ }
1280
+ return { kind: "tuple", count: 1, entries };
1281
+ }
1282
+ }
1283
+ }
1284
+ function unifyNullable(a, b) {
1285
+ if (!a)
1286
+ return b;
1287
+ if (!b)
1288
+ return a;
1289
+ return unify(a, b);
1290
+ }
1291
+ var UNION_KIND_ORDER = {
1292
+ uint: 0,
1293
+ int: 1,
1294
+ bool: 2,
1295
+ principal: 3,
1296
+ buffer: 4,
1297
+ ascii: 5,
1298
+ utf8: 6,
1299
+ list: 7,
1300
+ tuple: 8,
1301
+ response: 9
1302
+ };
1303
+ function makeUnion(members) {
1304
+ const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
1305
+ return { kind: "union", members: sorted };
1306
+ }
1307
+ function unify(a, b) {
1308
+ if (a.kind === "optional" || b.kind === "optional") {
1309
+ const ai = a.kind === "optional" ? a.inner : a;
1310
+ const bi = b.kind === "optional" ? b.inner : b;
1311
+ return { kind: "optional", inner: unifyNullable(ai, bi) };
1312
+ }
1313
+ if (a.kind === "union")
1314
+ return unionAdd(a.members, b);
1315
+ if (b.kind === "union")
1316
+ return unionAdd(b.members, a);
1317
+ if (a.kind !== b.kind)
1318
+ return makeUnion([a, b]);
1319
+ switch (a.kind) {
1320
+ case "uint":
1321
+ case "int":
1322
+ case "bool":
1323
+ case "principal":
1324
+ return a;
1325
+ case "buffer":
1326
+ case "ascii":
1327
+ case "utf8":
1328
+ return { kind: a.kind, len: Math.max(a.len, b.len) };
1329
+ case "list":
1330
+ return {
1331
+ kind: "list",
1332
+ inner: unifyNullable(a.inner, b.inner)
1333
+ };
1334
+ case "response": {
1335
+ const rb = b;
1336
+ return {
1337
+ kind: "response",
1338
+ ok: unifyNullable(a.ok, rb.ok),
1339
+ err: unifyNullable(a.err, rb.err)
1340
+ };
1341
+ }
1342
+ case "tuple": {
1343
+ const tb = b;
1344
+ const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
1345
+ for (const [k, e] of tb.entries) {
1346
+ const existing = entries.get(k);
1347
+ entries.set(k, existing ? {
1348
+ tree: unify(existing.tree, e.tree),
1349
+ present: existing.present + e.present
1350
+ } : { ...e });
1351
+ }
1352
+ return { kind: "tuple", count: a.count + tb.count, entries };
1353
+ }
1354
+ }
1355
+ }
1356
+ function unionAdd(members, t) {
1357
+ if (t.kind === "union") {
1358
+ let acc = { kind: "union", members };
1359
+ for (const m of t.members)
1360
+ acc = unify(acc, m);
1361
+ return acc;
1362
+ }
1363
+ const next = [...members];
1364
+ for (let i = 0;i < next.length; i++) {
1365
+ const member = next[i];
1366
+ if (!member)
1367
+ continue;
1368
+ const merged = unify(member, t);
1369
+ if (merged.kind !== "union") {
1370
+ next[i] = merged;
1371
+ return makeUnion(next);
1372
+ }
1373
+ }
1374
+ next.push(t);
1375
+ return makeUnion(next);
1376
+ }
1377
+ function wrapOptional(t) {
1378
+ return t.kind === "optional" ? t : { kind: "optional", inner: t };
1379
+ }
1380
+ function renderClarity(t) {
1381
+ if (!t)
1382
+ return "?";
1383
+ switch (t.kind) {
1384
+ case "uint":
1385
+ case "int":
1386
+ case "bool":
1387
+ case "principal":
1388
+ return t.kind;
1389
+ case "buffer":
1390
+ return `(buff ${t.len})`;
1391
+ case "ascii":
1392
+ return `(string-ascii ${t.len})`;
1393
+ case "utf8":
1394
+ return `(string-utf8 ${t.len})`;
1395
+ case "optional":
1396
+ return `(optional ${renderClarity(t.inner)})`;
1397
+ case "list":
1398
+ return `(list ${renderClarity(t.inner)})`;
1399
+ case "response":
1400
+ return `(response ${renderClarity(t.ok)} ${renderClarity(t.err)})`;
1401
+ case "tuple": {
1402
+ const parts = [...t.entries].map(([k, e]) => {
1403
+ const tree = e.present < t.count ? wrapOptional(e.tree) : e.tree;
1404
+ return `(${k} ${renderClarity(tree)})`;
1405
+ });
1406
+ return `(tuple ${parts.join(" ")})`;
1407
+ }
1408
+ case "union":
1409
+ return t.members.map(renderClarity).join(" | ");
1410
+ }
1411
+ }
1412
+ function renderTs(t) {
1413
+ if (!t)
1414
+ return "unknown";
1415
+ switch (t.kind) {
1416
+ case "uint":
1417
+ case "int":
1418
+ return "bigint";
1419
+ case "bool":
1420
+ return "boolean";
1421
+ case "principal":
1422
+ case "buffer":
1423
+ case "ascii":
1424
+ case "utf8":
1425
+ return "string";
1426
+ case "optional":
1427
+ return t.inner ? `${renderTs(t.inner)} | null` : "unknown | null";
1428
+ case "list": {
1429
+ const inner = renderTs(t.inner);
1430
+ return inner.includes(" | ") ? `(${inner})[]` : `${inner}[]`;
1431
+ }
1432
+ case "response": {
1433
+ const sides = [...new Set([renderTs(t.ok), renderTs(t.err)])];
1434
+ return sides.join(" | ");
1435
+ }
1436
+ case "tuple": {
1437
+ const parts = [...t.entries].map(([k, e]) => {
1438
+ const opt = e.present < t.count ? "?" : "";
1439
+ return `${camelizeDataKey(k)}${opt}: ${renderTs(e.tree)}`;
1440
+ });
1441
+ return `{ ${parts.join("; ")} }`;
1442
+ }
1443
+ case "union":
1444
+ return [...new Set(t.members.map((m) => renderTs(m)))].join(" | ");
1445
+ }
1446
+ }
1447
+ function toColumnType(t) {
1448
+ if (!t)
1449
+ return "jsonb";
1450
+ switch (t.kind) {
1451
+ case "uint":
1452
+ return "uint";
1453
+ case "int":
1454
+ return "int";
1455
+ case "bool":
1456
+ return "boolean";
1457
+ case "principal":
1458
+ return "principal";
1459
+ case "buffer":
1460
+ case "ascii":
1461
+ case "utf8":
1462
+ return "text";
1463
+ case "list":
1464
+ case "tuple":
1465
+ return "jsonb";
1466
+ case "optional":
1467
+ return toColumnType(t.inner);
1468
+ case "response":
1469
+ return t.ok ? toColumnType(t.ok) : "jsonb";
1470
+ case "union":
1471
+ return "jsonb";
1472
+ }
1473
+ }
1474
+ var MAX_DECODED_PER_TOPIC_NEWEST = 75;
1475
+ var MAX_DECODED_PER_TOPIC_OLDEST = 25;
1476
+ function inferPrintTopics(samples) {
1477
+ const groups = new Map;
1478
+ for (const s of samples) {
1479
+ const group = groups.get(s.topic);
1480
+ if (group)
1481
+ group.push(s);
1482
+ else
1483
+ groups.set(s.topic, [s]);
1484
+ }
1485
+ const out = [];
1486
+ for (const [topic, rows] of groups) {
1487
+ let first = Number.POSITIVE_INFINITY;
1488
+ let last = Number.NEGATIVE_INFINITY;
1489
+ for (const r of rows) {
1490
+ if (r.blockHeight < first)
1491
+ first = r.blockHeight;
1492
+ if (r.blockHeight > last)
1493
+ last = r.blockHeight;
1494
+ }
1495
+ const withHex = [...rows].filter((r) => r.rawHex !== null).sort((a, b) => b.blockHeight - a.blockHeight);
1496
+ const budget = MAX_DECODED_PER_TOPIC_NEWEST + MAX_DECODED_PER_TOPIC_OLDEST;
1497
+ const picked = withHex.length <= budget ? withHex : [
1498
+ ...withHex.slice(0, MAX_DECODED_PER_TOPIC_NEWEST),
1499
+ ...withHex.slice(-MAX_DECODED_PER_TOPIC_OLDEST)
1500
+ ];
1501
+ const tuples = [];
1502
+ let decoded = 0;
1503
+ for (const p of picked) {
1504
+ try {
1505
+ const cv = deserializeCV3(p.rawHex);
1506
+ decoded++;
1507
+ if (cv.type === "tuple")
1508
+ tuples.push(cv);
1509
+ } catch {}
1510
+ }
1511
+ const nonTuple = decoded > 0 && tuples.length === 0;
1512
+ const fields = [];
1513
+ if (!nonTuple) {
1514
+ const stats = new Map;
1515
+ for (const t of tuples) {
1516
+ for (const [key, value] of Object.entries(t.value)) {
1517
+ if (key === "topic")
1518
+ continue;
1519
+ const tree = cvToTree(value);
1520
+ const existing = stats.get(key);
1521
+ if (existing) {
1522
+ existing.present++;
1523
+ existing.tree = unify(existing.tree, tree);
1524
+ if (value.type === "none")
1525
+ existing.noneCount++;
1526
+ if (value.type === "none" || value.type === "some") {
1527
+ existing.optionalSeen = true;
1528
+ }
1529
+ } else {
1530
+ stats.set(key, {
1531
+ tree,
1532
+ present: 1,
1533
+ noneCount: value.type === "none" ? 1 : 0,
1534
+ optionalSeen: value.type === "none" || value.type === "some"
1535
+ });
1536
+ }
1537
+ }
1538
+ }
1539
+ for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
1540
+ const field = {
1541
+ name,
1542
+ camel_name: camelizeDataKey(name),
1543
+ clarity_type: renderClarity(st.tree),
1544
+ ts_type: renderTs(st.tree),
1545
+ column_type: toColumnType(st.tree),
1546
+ always_present: st.present === tuples.length
1547
+ };
1548
+ if (st.optionalSeen) {
1549
+ field.optional_some_rate = (st.present - st.noneCount) / st.present;
1550
+ }
1551
+ fields.push(field);
1552
+ }
1553
+ }
1554
+ out.push({
1555
+ topic,
1556
+ count: rows.length,
1557
+ first_height: first,
1558
+ last_height: last,
1559
+ non_tuple: nonTuple,
1560
+ fields
1561
+ });
1562
+ }
1563
+ return out.sort((a, b) => b.count - a.count);
1564
+ }
1565
+
1566
+ // src/runtime/materialize.ts
1567
+ function resolveColumn(col, event, tx, block) {
1568
+ if ("fromTx" in col) {
1569
+ return { ok: true, value: tx[col.fromTx] };
1570
+ }
1571
+ if ("fromBlock" in col) {
1572
+ return { ok: true, value: block[col.fromBlock] };
1573
+ }
1574
+ const data = event.data;
1575
+ const dataObj = data !== null && typeof data === "object" && !Array.isArray(data) ? data : undefined;
1576
+ if (dataObj && Object.prototype.hasOwnProperty.call(dataObj, col.from)) {
1577
+ return { ok: true, value: dataObj[col.from] };
1578
+ }
1579
+ if (col.from === "topic" && "topic" in event) {
1580
+ return { ok: true, value: event.topic };
1581
+ }
1582
+ if (Object.prototype.hasOwnProperty.call(event, col.from)) {
1583
+ return { ok: true, value: event[col.from] };
1584
+ }
1585
+ return { ok: false, missing: col.from };
1586
+ }
1587
+ function buildMaterializeRow(spec, event, ctx, tableColumns) {
1588
+ const row = {};
1589
+ for (const [colName, mapping] of Object.entries(spec.columns)) {
1590
+ const resolved = resolveColumn(mapping, event, ctx.tx, ctx.block);
1591
+ if (!resolved.ok) {
1592
+ const colDef = tableColumns[colName];
1593
+ if (colDef?.nullable) {
1594
+ row[colName] = null;
1595
+ continue;
1596
+ }
1597
+ return {
1598
+ ok: false,
1599
+ reason: `required materialize field "${resolved.missing}" missing for column "${colName}"`
1600
+ };
1601
+ }
1602
+ row[colName] = resolved.value ?? null;
1603
+ }
1604
+ return { ok: true, row };
1605
+ }
1606
+ function applyMaterializeInsert(spec, event, ctx, schema) {
1607
+ const table = schema[spec.table];
1608
+ if (!table) {
1609
+ return { ok: false, reason: `materialize table "${spec.table}" missing` };
1610
+ }
1611
+ const built = buildMaterializeRow(spec, event, ctx, table.columns);
1612
+ if (!built.ok)
1613
+ return built;
1614
+ ctx.insert(spec.table, built.row);
1615
+ return { ok: true };
1616
+ }
1617
+
954
1618
  // src/runtime/print-validate.ts
955
1619
  var COLUMN_TYPES = new Set([
956
1620
  "text",
@@ -1030,24 +1694,62 @@ function validatePrintPayload(prints, topic, data) {
1030
1694
  return matchesTopic(declared, data);
1031
1695
  }
1032
1696
 
1033
- // src/runtime/runner.ts
1034
- var DEFAULT_ERROR_THRESHOLD = 50;
1035
- function camelCase(str) {
1036
- return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
1697
+ // src/runtime/violations.ts
1698
+ import { getTargetDb } from "@secondlayer/shared/db";
1699
+ import { logger as logger3 } from "@secondlayer/shared/logger";
1700
+ import { sql as sql2 } from "kysely";
1701
+ var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
1702
+ function jsonSafe2(value) {
1703
+ if (typeof value === "bigint")
1704
+ return value.toString();
1705
+ if (value === null || value === undefined)
1706
+ return value;
1707
+ if (Array.isArray(value))
1708
+ return value.map(jsonSafe2);
1709
+ if (typeof value === "object") {
1710
+ const out = {};
1711
+ for (const [k, v] of Object.entries(value)) {
1712
+ out[k] = jsonSafe2(v);
1713
+ }
1714
+ return out;
1715
+ }
1716
+ return value;
1037
1717
  }
1038
- function camelizeKeys(obj) {
1039
- if (obj === null || obj === undefined)
1040
- return obj;
1041
- if (typeof obj !== "object")
1042
- return obj;
1043
- if (Array.isArray(obj))
1044
- return obj.map(camelizeKeys);
1045
- const result = {};
1046
- for (const [k, v] of Object.entries(obj)) {
1047
- result[camelCase(k)] = camelizeKeys(v);
1718
+ async function defaultRecordPrintViolation(input) {
1719
+ try {
1720
+ const db = getTargetDb();
1721
+ const sample = jsonSafe2(input.samplePayload);
1722
+ await db.insertInto("subgraph_violations").values({
1723
+ subgraph_name: input.subgraphName,
1724
+ source_name: input.sourceName,
1725
+ block_height: input.blockHeight,
1726
+ tx_id: input.txId,
1727
+ reason: input.reason,
1728
+ sample_payload: sample
1729
+ }).execute();
1730
+ await sql2`
1731
+ DELETE FROM subgraph_violations
1732
+ WHERE id IN (
1733
+ SELECT id FROM subgraph_violations
1734
+ WHERE subgraph_name = ${input.subgraphName}
1735
+ ORDER BY seen_at DESC
1736
+ OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
1737
+ )
1738
+ `.execute(db);
1739
+ } catch (err) {
1740
+ logger3.warn("Failed to record print violation", {
1741
+ subgraph: input.subgraphName,
1742
+ error: err instanceof Error ? err.message : String(err)
1743
+ });
1048
1744
  }
1049
- return result;
1050
1745
  }
1746
+ var recorder = defaultRecordPrintViolation;
1747
+ async function recordPrintViolation(input) {
1748
+ await recorder(input);
1749
+ }
1750
+
1751
+ // src/runtime/runner.ts
1752
+ var DEFAULT_ERROR_THRESHOLD = 50;
1051
1753
  function decodeFunctionArgs2(args) {
1052
1754
  let parsed = args;
1053
1755
  if (typeof parsed === "string") {
@@ -1111,7 +1813,7 @@ function buildContractCallInput(filter, tx) {
1111
1813
  return;
1112
1814
  try {
1113
1815
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1114
- input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV3(clean));
1816
+ input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV4(clean));
1115
1817
  } catch {}
1116
1818
  });
1117
1819
  return input;
@@ -1234,7 +1936,7 @@ function buildEventPayload(filter, tx, event) {
1234
1936
  const { topic: _, ...rest } = clarityObj ?? {};
1235
1937
  const data = Object.keys(rest).length > 0 ? camelizeKeys(rest) : rawValue && typeof rawValue !== "object" ? rawValue : {};
1236
1938
  return {
1237
- contractId: decoded.contract_identifier ?? tx.contract_id ?? "",
1939
+ contractId: printContractId(decoded) ?? tx.contract_id ?? "",
1238
1940
  topic,
1239
1941
  data: data ?? {},
1240
1942
  tx: txMeta
@@ -1295,16 +1997,18 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1295
1997
  units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
1296
1998
  for (const { tx, event, sourceName } of units) {
1297
1999
  if (errors >= threshold) {
1298
- logger3.error("Subgraph error threshold reached, skipping remaining events", {
2000
+ logger4.error("Subgraph error threshold reached, skipping remaining events", {
1299
2001
  subgraph: subgraph.name,
1300
2002
  errors,
1301
2003
  threshold
1302
2004
  });
1303
2005
  return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1304
2006
  }
2007
+ const filter = filterLookup.get(sourceName);
2008
+ const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
1305
2009
  const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
1306
- if (!handler) {
1307
- logger3.warn("No handler found for source", {
2010
+ if (!handler && !materialize) {
2011
+ logger4.warn("No handler found for source", {
1308
2012
  subgraph: subgraph.name,
1309
2013
  sourceName,
1310
2014
  txId: tx.tx_id
@@ -1319,7 +2023,6 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1319
2023
  contractId: tx.contract_id ?? null,
1320
2024
  functionName: tx.function_name ?? null
1321
2025
  });
1322
- const filter = filterLookup.get(sourceName);
1323
2026
  const checkpoint = ctx.opsCheckpoint();
1324
2027
  try {
1325
2028
  let payload;
@@ -1361,22 +2064,48 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1361
2064
  const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
1362
2065
  if (!verdict.ok) {
1363
2066
  skipped++;
1364
- logger3.warn("Print payload does not match declared prints schema", {
2067
+ logger4.warn("Print payload does not match declared prints schema", {
1365
2068
  subgraph: subgraph.name,
1366
2069
  sourceName,
1367
2070
  topic: printPayload.topic,
1368
2071
  txId: tx.tx_id,
1369
2072
  reason: verdict.reason
1370
2073
  });
2074
+ recordPrintViolation({
2075
+ subgraphName: subgraph.name,
2076
+ sourceName,
2077
+ blockHeight: ctx.block.height,
2078
+ txId: tx.tx_id,
2079
+ reason: verdict.reason,
2080
+ samplePayload: {
2081
+ topic: printPayload.topic ?? "",
2082
+ data: printPayload.data ?? {}
2083
+ }
2084
+ });
2085
+ continue;
2086
+ }
2087
+ }
2088
+ if (materialize) {
2089
+ const result = applyMaterializeInsert(materialize, payload, ctx, subgraph.schema);
2090
+ if (!result.ok) {
2091
+ skipped++;
2092
+ logger4.warn("Materialize skipped: required field missing", {
2093
+ subgraph: subgraph.name,
2094
+ sourceName,
2095
+ txId: tx.tx_id,
2096
+ reason: result.reason
2097
+ });
1371
2098
  continue;
1372
2099
  }
2100
+ processed++;
2101
+ } else if (handler) {
2102
+ await handler(payload, ctx);
2103
+ processed++;
1373
2104
  }
1374
- await handler(payload, ctx);
1375
- processed++;
1376
2105
  } catch (err) {
1377
2106
  ctx.rollbackTo(checkpoint);
1378
2107
  errors++;
1379
- logger3.error("Subgraph handler error", {
2108
+ logger4.error("Subgraph handler error", {
1380
2109
  subgraph: subgraph.name,
1381
2110
  sourceName,
1382
2111
  txId: tx.tx_id,
@@ -1388,7 +2117,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1388
2117
  return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1389
2118
  }
1390
2119
 
1391
- // src/testing/index.ts
2120
+ // src/testing/harness.ts
1392
2121
  import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
1393
2122
  var TEST_SCHEMA_NAME = "subgraph_test";
1394
2123
  function defaultBlock(overrides = {}) {
@@ -1456,6 +2185,20 @@ class InMemorySubgraphContext extends SubgraphContext {
1456
2185
  }
1457
2186
  this.ops.length = 0;
1458
2187
  }
2188
+ insertsSince(checkpoint) {
2189
+ const out = [];
2190
+ for (const op of this.ops.slice(Math.max(0, checkpoint))) {
2191
+ if (op.kind !== "insert")
2192
+ continue;
2193
+ const row = { ...op.data };
2194
+ out.push({
2195
+ table: op.table,
2196
+ keys: Object.keys(row).filter((k) => !k.startsWith("_")),
2197
+ row
2198
+ });
2199
+ }
2200
+ return out;
2201
+ }
1459
2202
  }
1460
2203
  function sameValue(a, b) {
1461
2204
  if (a === b)
@@ -1471,7 +2214,10 @@ function createTestContext(schema, options = {}) {
1471
2214
  const ctx = impl;
1472
2215
  ctx.rows = (table) => impl.rowsOf(table);
1473
2216
  ctx.commit = () => impl.commitOps();
1474
- ctx.setTx = (tx) => impl.setTx(defaultTx(tx));
2217
+ ctx.setTx = (tx) => {
2218
+ SubgraphContext.prototype.setTx.call(impl, defaultTx(tx));
2219
+ };
2220
+ ctx.pendingInsertsSince = (checkpoint) => impl.insertsSince(checkpoint);
1475
2221
  return ctx;
1476
2222
  }
1477
2223
  function buildEvent(source, payload, options = {}) {
@@ -1491,11 +2237,284 @@ function buildEvent(source, payload, options = {}) {
1491
2237
  }
1492
2238
  return { ...base, ...payload };
1493
2239
  }
2240
+ // src/testing/run.ts
2241
+ function toHandlerPayload(_filter, row) {
2242
+ if (row.event_type === "print") {
2243
+ const payload = row.payload;
2244
+ return {
2245
+ contractId: row.contract_id ?? "",
2246
+ topic: payload?.topic ?? "",
2247
+ data: camelizeKeys(payload?.value) ?? {}
2248
+ };
2249
+ }
2250
+ const r = row;
2251
+ return {
2252
+ ...r.sender !== undefined ? { sender: r.sender } : {},
2253
+ ...r.recipient !== undefined ? { recipient: r.recipient } : {},
2254
+ ...r.amount !== undefined ? { amount: BigInt(String(r.amount)) } : {},
2255
+ ...r.asset_identifier !== undefined ? { assetIdentifier: r.asset_identifier } : {},
2256
+ ...r.value !== undefined ? { tokenId: r.value } : {}
2257
+ };
2258
+ }
2259
+ function toContractCallPayload(row) {
2260
+ return {
2261
+ type: "contract_call",
2262
+ sender: row.sender,
2263
+ contractId: row.contract_id ?? "",
2264
+ functionName: row.function_name ?? "",
2265
+ args: Array.isArray(row.args) ? row.args : [],
2266
+ result: row.result ?? null,
2267
+ resultHex: row.result_hex ?? null,
2268
+ tx: {
2269
+ txId: row.tx_id,
2270
+ sender: row.sender,
2271
+ type: "contract_call",
2272
+ status: row.status,
2273
+ contractId: row.contract_id ?? null,
2274
+ functionName: row.function_name ?? null
2275
+ }
2276
+ };
2277
+ }
2278
+ function jsonSafe3(value) {
2279
+ if (typeof value === "bigint")
2280
+ return value.toString();
2281
+ if (value === null || value === undefined)
2282
+ return value;
2283
+ if (Array.isArray(value))
2284
+ return value.map(jsonSafe3);
2285
+ if (typeof value === "object") {
2286
+ const out = {};
2287
+ for (const [k, v] of Object.entries(value)) {
2288
+ out[k] = jsonSafe3(v);
2289
+ }
2290
+ return out;
2291
+ }
2292
+ return value;
2293
+ }
2294
+ function dataKeysOf(data) {
2295
+ if (data && typeof data === "object" && !Array.isArray(data)) {
2296
+ return Object.keys(data);
2297
+ }
2298
+ return [];
2299
+ }
2300
+ function rowMeta(row) {
2301
+ const r = row;
2302
+ return {
2303
+ ...typeof r.block_height === "number" ? { blockHeight: r.block_height } : {},
2304
+ ...typeof r.tx_id === "string" ? { txId: r.tx_id } : {}
2305
+ };
2306
+ }
2307
+ function inKeysOf(filter, payload) {
2308
+ if (filter?.type === "contract_call") {
2309
+ return ["functionName", "args", "sender", "contractId"].filter((k) => payload[k] !== undefined);
2310
+ }
2311
+ return dataKeysOf(payload.data);
2312
+ }
2313
+ function unusedInKeysAcross(traces) {
2314
+ if (traces.length === 0)
2315
+ return;
2316
+ const alwaysIn = new Set(traces[0]?.inKeys ?? []);
2317
+ for (const t of traces.slice(1)) {
2318
+ const keys = new Set(t.inKeys);
2319
+ for (const k of [...alwaysIn]) {
2320
+ if (!keys.has(k))
2321
+ alwaysIn.delete(k);
2322
+ }
2323
+ }
2324
+ const outKeys = new Set;
2325
+ for (const t of traces) {
2326
+ for (const o of t.outs) {
2327
+ for (const k of o.keys)
2328
+ outKeys.add(k);
2329
+ }
2330
+ }
2331
+ const outNormalized = new Set([...outKeys].map((k) => k.replace(/_/g, "").toLowerCase()));
2332
+ const unused = [...alwaysIn].filter((k) => !outNormalized.has(k.replace(/_/g, "").toLowerCase()));
2333
+ return unused.length > 0 ? unused : undefined;
2334
+ }
2335
+ async function runSubgraphTest(input) {
2336
+ const { schema, handlers, sources, events, trace } = input;
2337
+ const sourceNames = Object.keys(sources);
2338
+ if (sourceNames.length === 0) {
2339
+ return {
2340
+ ok: false,
2341
+ code: "NO_SOURCES",
2342
+ matched: 0,
2343
+ written: 0,
2344
+ tables: [],
2345
+ hint: "No sources to test."
2346
+ };
2347
+ }
2348
+ const ctx = createTestContext(schema);
2349
+ let matched = 0;
2350
+ let firstEvent;
2351
+ const traces = [];
2352
+ for (const [name, rows] of Object.entries(events)) {
2353
+ const filter = sources[name];
2354
+ const handler = handlers[name] ?? handlers["*"];
2355
+ const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
2356
+ if (typeof handler !== "function" && !materialize)
2357
+ continue;
2358
+ for (const row of rows) {
2359
+ const payload = filter?.type === "contract_call" ? toContractCallPayload(row) : toHandlerPayload(filter, row);
2360
+ if (filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
2361
+ continue;
2362
+ }
2363
+ if (!firstEvent) {
2364
+ firstEvent = {
2365
+ source: name,
2366
+ data: filter?.type === "contract_call" ? {
2367
+ functionName: payload.functionName,
2368
+ args: payload.args
2369
+ } : payload.data
2370
+ };
2371
+ }
2372
+ matched++;
2373
+ const checkpoint = ctx.opsCheckpoint();
2374
+ try {
2375
+ const event = buildEvent(filter, payload);
2376
+ if (materialize) {
2377
+ applyMaterializeInsert(materialize, event, {
2378
+ tx: ctx.tx,
2379
+ block: ctx.block,
2380
+ insert: (table, insertRow) => {
2381
+ ctx.insert(table, insertRow);
2382
+ }
2383
+ }, schema);
2384
+ } else {
2385
+ await handler(event, ctx);
2386
+ }
2387
+ } catch {}
2388
+ if (trace) {
2389
+ const inserts = ctx.pendingInsertsSince(checkpoint);
2390
+ traces.push({
2391
+ source: name,
2392
+ ...rowMeta(row),
2393
+ inKeys: inKeysOf(filter, payload),
2394
+ outs: inserts.map((i) => ({ table: i.table, keys: i.keys }))
2395
+ });
2396
+ }
2397
+ }
2398
+ }
2399
+ const tableNames = Object.keys(schema);
2400
+ const tables = [];
2401
+ let written = 0;
2402
+ for (const table of tableNames) {
2403
+ const tableRows = await ctx.rows(table);
2404
+ written += tableRows.length;
2405
+ const sample = tableRows[0];
2406
+ tables.push({
2407
+ name: table,
2408
+ rows: tableRows.length,
2409
+ ...sample ? { sampleRow: jsonSafe3(sample) } : {}
2410
+ });
2411
+ }
2412
+ const fetched = Object.values(events).reduce((n, r) => n + r.length, 0);
2413
+ const unusedInKeys = trace ? unusedInKeysAcross(traces) : undefined;
2414
+ const traceFields = {
2415
+ ...trace ? { traces } : {},
2416
+ ...unusedInKeys ? { unusedInKeys } : {}
2417
+ };
2418
+ if (fetched === 0) {
2419
+ return {
2420
+ ok: false,
2421
+ code: "NO_EVENTS",
2422
+ matched: 0,
2423
+ written,
2424
+ tables,
2425
+ hint: "No events matched these sources in the given range. Widen the range or check the source filters.",
2426
+ ...traceFields
2427
+ };
2428
+ }
2429
+ if (written === 0) {
2430
+ const keys = dataKeysOf(firstEvent?.data);
2431
+ const keyPart = keys.length > 0 ? ` Observed event.data keys on first event (${firstEvent?.source}): ${keys.join(", ")}.` : firstEvent ? ` First event source: ${firstEvent.source}.` : "";
2432
+ return {
2433
+ ok: false,
2434
+ code: "EMPTY_MAPPING",
2435
+ matched,
2436
+ written: 0,
2437
+ tables,
2438
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2439
+ hint: `${matched} event${matched === 1 ? "" : "s"} matched, but NO rows were written — the shape of the field-mapping bug that ships a 0-row subgraph.${keyPart} Map only observed keys; do not invent fields.`,
2440
+ ...traceFields
2441
+ };
2442
+ }
2443
+ if (trace && traces.some((t) => t.outs.length === 0)) {
2444
+ const empty = traces.find((t) => t.outs.length === 0);
2445
+ const keyPart = empty && empty.inKeys.length > 0 ? ` Empty event (${empty.source}) IN keys: ${empty.inKeys.join(", ")}.` : "";
2446
+ return {
2447
+ ok: false,
2448
+ code: "EMPTY_MAPPING",
2449
+ matched,
2450
+ written,
2451
+ tables,
2452
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2453
+ hint: `At least one matched event wrote 0 rows.${keyPart}`,
2454
+ ...traceFields
2455
+ };
2456
+ }
2457
+ return {
2458
+ ok: true,
2459
+ matched,
2460
+ written,
2461
+ tables,
2462
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2463
+ ...traceFields
2464
+ };
2465
+ }
2466
+ async function probeHandlers(def, samples) {
2467
+ const ctx = createTestContext(def.schema);
2468
+ let matched = 0;
2469
+ let firstEventKeys;
2470
+ for (const sample of samples) {
2471
+ const filter = def.sources[sample.source];
2472
+ if (!filter)
2473
+ continue;
2474
+ const handler = def.handlers[sample.source] ?? def.handlers["*"];
2475
+ const materialize = "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
2476
+ if (typeof handler !== "function" && !materialize)
2477
+ continue;
2478
+ if (firstEventKeys === undefined) {
2479
+ firstEventKeys = dataKeysOf(sample.event.data);
2480
+ }
2481
+ matched++;
2482
+ try {
2483
+ const event = buildEvent(filter, sample.event);
2484
+ if (materialize) {
2485
+ applyMaterializeInsert(materialize, event, {
2486
+ tx: ctx.tx,
2487
+ block: ctx.block,
2488
+ insert: (table, row) => {
2489
+ ctx.insert(table, row);
2490
+ }
2491
+ }, def.schema);
2492
+ } else {
2493
+ await handler(event, ctx);
2494
+ }
2495
+ } catch {}
2496
+ }
2497
+ const tables = Object.keys(def.schema);
2498
+ let written = 0;
2499
+ for (const table of tables) {
2500
+ written += (await ctx.rows(table)).length;
2501
+ }
2502
+ return {
2503
+ matched,
2504
+ written,
2505
+ tables,
2506
+ ...firstEventKeys !== undefined ? { firstEventKeys } : {}
2507
+ };
2508
+ }
1494
2509
  export {
2510
+ toHandlerPayload,
2511
+ toContractCallPayload,
2512
+ runSubgraphTest,
2513
+ probeHandlers,
1495
2514
  createTestContext,
1496
2515
  buildEventPayload,
1497
2516
  buildEvent
1498
2517
  };
1499
2518
 
1500
- //# debugId=F28CFCCFD7CC80FA64756E2164756E21
2519
+ //# debugId=ABB2AE2A4DA0D8AC64756E2164756E21
1501
2520
  //# sourceMappingURL=index.js.map