@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
@@ -1292,366 +1994,131 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1292
1994
  units.push({ tx, sourceName, event });
1293
1995
  }
1294
1996
  }
1295
- 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
- for (const { tx, event, sourceName } of units) {
1297
- if (errors >= threshold) {
1298
- logger3.error("Subgraph error threshold reached, skipping remaining events", {
1299
- subgraph: subgraph.name,
1300
- errors,
1301
- threshold
1302
- });
1303
- return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1304
- }
1305
- const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
1306
- if (!handler) {
1307
- logger3.warn("No handler found for source", {
1308
- subgraph: subgraph.name,
1309
- sourceName,
1310
- txId: tx.tx_id
1311
- });
1312
- continue;
1313
- }
1314
- ctx.setTx({
1315
- txId: tx.tx_id,
1316
- sender: tx.sender,
1317
- type: tx.type,
1318
- status: tx.status,
1319
- contractId: tx.contract_id ?? null,
1320
- functionName: tx.function_name ?? null
1321
- });
1322
- const filter = filterLookup.get(sourceName);
1323
- const checkpoint = ctx.opsCheckpoint();
1324
- try {
1325
- let payload;
1326
- if (event === null) {
1327
- payload = filter ? buildEventPayload(filter, tx, null) : {
1328
- tx: {
1329
- txId: tx.tx_id,
1330
- sender: tx.sender,
1331
- type: tx.type,
1332
- status: tx.status,
1333
- contractId: tx.contract_id,
1334
- functionName: tx.function_name
1335
- }
1336
- };
1337
- } else if (filter) {
1338
- payload = buildEventPayload(filter, tx, event);
1339
- } else {
1340
- const decoded = decodeEventData(event.data);
1341
- payload = {
1342
- ...decoded,
1343
- _eventId: event.id,
1344
- _eventType: event.type,
1345
- _eventIndex: event.event_index,
1346
- tx: {
1347
- txId: tx.tx_id,
1348
- sender: tx.sender,
1349
- type: tx.type,
1350
- status: tx.status,
1351
- contractId: tx.contract_id,
1352
- functionName: tx.function_name
1353
- }
1354
- };
1355
- }
1356
- if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
1357
- continue;
1358
- }
1359
- if (event !== null && filter?.type === "print_event" && filter.prints) {
1360
- const printPayload = payload;
1361
- const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
1362
- if (!verdict.ok) {
1363
- skipped++;
1364
- logger3.warn("Print payload does not match declared prints schema", {
1365
- subgraph: subgraph.name,
1366
- sourceName,
1367
- topic: printPayload.topic,
1368
- txId: tx.tx_id,
1369
- reason: verdict.reason
1370
- });
1371
- continue;
1372
- }
1373
- }
1374
- await handler(payload, ctx);
1375
- processed++;
1376
- } catch (err) {
1377
- ctx.rollbackTo(checkpoint);
1378
- errors++;
1379
- logger3.error("Subgraph handler error", {
1380
- subgraph: subgraph.name,
1381
- sourceName,
1382
- txId: tx.tx_id,
1383
- ...event !== null ? { eventId: event.id, eventType: event.type } : {},
1384
- error: getErrorMessage(err)
1385
- });
1386
- }
1387
- }
1388
- return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1389
- }
1390
-
1391
- // src/runtime/source-matcher.ts
1392
- var patternCache = new Map;
1393
- function matchAny(value, pattern) {
1394
- return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
1395
- }
1396
- function matchPattern(value, pattern) {
1397
- if (!pattern.includes("*"))
1398
- return value === pattern;
1399
- let re = patternCache.get(pattern);
1400
- if (!re) {
1401
- const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1402
- re = new RegExp(`^${regex}$`);
1403
- patternCache.set(pattern, re);
1404
- }
1405
- return re.test(value);
1406
- }
1407
- var EMPTY_SET = new Set;
1408
- function factoryAllows(filter, contractId, factoryContracts) {
1409
- if (!filter.factory)
1410
- return true;
1411
- if (!contractId)
1412
- return false;
1413
- return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
1414
- }
1415
- function readPath(payload, path) {
1416
- let cur = payload;
1417
- for (const key of path.split(".")) {
1418
- if (cur === null || typeof cur !== "object")
1419
- return;
1420
- cur = cur[key];
1421
- }
1422
- return cur;
1423
- }
1424
- function traitAllows(filter, contractId, traitContracts) {
1425
- const trait = filter.trait;
1426
- if (!trait)
1427
- return true;
1428
- if (!contractId)
1429
- return false;
1430
- return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
1431
- }
1432
- function assetContract(assetId) {
1433
- return assetId?.split("::")[0];
1434
- }
1435
- function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
1436
- const results = [];
1437
- switch (filter.type) {
1438
- case "stx_transfer":
1439
- case "stx_mint":
1440
- case "stx_burn":
1441
- case "stx_lock": {
1442
- const eventType = `${filter.type}_event`;
1443
- for (const tx of transactions) {
1444
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1445
- const matched = txEvents.filter((e) => e.type === eventType);
1446
- if (matched.length === 0)
1447
- continue;
1448
- const filtered = matched.filter((e) => {
1449
- const data = e.data;
1450
- if (!data)
1451
- return false;
1452
- if ("sender" in filter && filter.sender) {
1453
- if (!matchPattern(data.sender, filter.sender))
1454
- return false;
1455
- }
1456
- if ("recipient" in filter && filter.recipient) {
1457
- if (!matchPattern(data.recipient, filter.recipient))
1458
- return false;
1459
- }
1460
- if ("lockedAddress" in filter && filter.lockedAddress) {
1461
- if (!matchPattern(data.locked_address, filter.lockedAddress))
1462
- return false;
1463
- }
1464
- if ("minAmount" in filter && filter.minAmount !== undefined) {
1465
- const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
1466
- if (amount < filter.minAmount)
1467
- return false;
1468
- }
1469
- if ("maxAmount" in filter && filter.maxAmount !== undefined) {
1470
- const amount = BigInt(data.amount ?? "0");
1471
- if (amount > filter.maxAmount)
1472
- return false;
1473
- }
1474
- return true;
1475
- });
1476
- if (filtered.length > 0) {
1477
- results.push({ tx, events: filtered });
1478
- }
1479
- }
1480
- break;
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));
1998
+ for (const { tx, event, sourceName } of units) {
1999
+ if (errors >= threshold) {
2000
+ logger4.error("Subgraph error threshold reached, skipping remaining events", {
2001
+ subgraph: subgraph.name,
2002
+ errors,
2003
+ threshold
2004
+ });
2005
+ return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1481
2006
  }
1482
- case "ft_transfer":
1483
- case "ft_mint":
1484
- case "ft_burn": {
1485
- const eventType = `${filter.type}_event`;
1486
- for (const tx of transactions) {
1487
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1488
- const matched = txEvents.filter((e) => {
1489
- if (e.type !== eventType)
1490
- return false;
1491
- const data = e.data;
1492
- if (!data)
1493
- return false;
1494
- if (filter.assetIdentifier) {
1495
- const assetId = data.asset_identifier;
1496
- if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
1497
- return false;
1498
- }
1499
- if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
1500
- return false;
1501
- if ("sender" in filter && filter.sender) {
1502
- if (!matchPattern(data.sender, filter.sender))
1503
- return false;
1504
- }
1505
- if ("recipient" in filter && filter.recipient) {
1506
- if (!matchPattern(data.recipient, filter.recipient))
1507
- return false;
1508
- }
1509
- if (filter.minAmount !== undefined) {
1510
- const amount = BigInt(data.amount ?? "0");
1511
- if (amount < filter.minAmount)
1512
- return false;
1513
- }
1514
- return true;
1515
- });
1516
- if (matched.length > 0) {
1517
- results.push({ tx, events: matched });
1518
- }
1519
- }
1520
- break;
2007
+ const filter = filterLookup.get(sourceName);
2008
+ const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
2009
+ const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
2010
+ if (!handler && !materialize) {
2011
+ logger4.warn("No handler found for source", {
2012
+ subgraph: subgraph.name,
2013
+ sourceName,
2014
+ txId: tx.tx_id
2015
+ });
2016
+ continue;
1521
2017
  }
1522
- case "nft_transfer":
1523
- case "nft_mint":
1524
- case "nft_burn": {
1525
- const eventType = `${filter.type}_event`;
1526
- for (const tx of transactions) {
1527
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1528
- const matched = txEvents.filter((e) => {
1529
- if (e.type !== eventType)
1530
- return false;
1531
- const data = e.data;
1532
- if (!data)
1533
- return false;
1534
- if (filter.assetIdentifier) {
1535
- const assetId = data.asset_identifier;
1536
- if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
1537
- return false;
1538
- }
1539
- if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
1540
- return false;
1541
- if ("sender" in filter && filter.sender) {
1542
- if (!matchPattern(data.sender, filter.sender))
1543
- return false;
2018
+ ctx.setTx({
2019
+ txId: tx.tx_id,
2020
+ sender: tx.sender,
2021
+ type: tx.type,
2022
+ status: tx.status,
2023
+ contractId: tx.contract_id ?? null,
2024
+ functionName: tx.function_name ?? null
2025
+ });
2026
+ const checkpoint = ctx.opsCheckpoint();
2027
+ try {
2028
+ let payload;
2029
+ if (event === null) {
2030
+ payload = filter ? buildEventPayload(filter, tx, null) : {
2031
+ tx: {
2032
+ txId: tx.tx_id,
2033
+ sender: tx.sender,
2034
+ type: tx.type,
2035
+ status: tx.status,
2036
+ contractId: tx.contract_id,
2037
+ functionName: tx.function_name
1544
2038
  }
1545
- if ("recipient" in filter && filter.recipient) {
1546
- if (!matchPattern(data.recipient, filter.recipient))
1547
- return false;
2039
+ };
2040
+ } else if (filter) {
2041
+ payload = buildEventPayload(filter, tx, event);
2042
+ } else {
2043
+ const decoded = decodeEventData(event.data);
2044
+ payload = {
2045
+ ...decoded,
2046
+ _eventId: event.id,
2047
+ _eventType: event.type,
2048
+ _eventIndex: event.event_index,
2049
+ tx: {
2050
+ txId: tx.tx_id,
2051
+ sender: tx.sender,
2052
+ type: tx.type,
2053
+ status: tx.status,
2054
+ contractId: tx.contract_id,
2055
+ functionName: tx.function_name
1548
2056
  }
1549
- return true;
1550
- });
1551
- if (matched.length > 0) {
1552
- results.push({ tx, events: matched });
1553
- }
2057
+ };
1554
2058
  }
1555
- break;
1556
- }
1557
- case "contract_call": {
1558
- for (const tx of transactions) {
1559
- if (tx.type !== "contract_call")
1560
- continue;
1561
- if (filter.contractId) {
1562
- if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
1563
- continue;
1564
- }
1565
- if (!factoryAllows(filter, tx.contract_id, factoryContracts))
1566
- continue;
1567
- if (filter.functionName) {
1568
- if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
1569
- continue;
1570
- }
1571
- if (filter.caller) {
1572
- if (!matchPattern(tx.sender, filter.caller))
1573
- continue;
1574
- }
1575
- if (!traitAllows(filter, tx.contract_id, traitContracts))
1576
- continue;
1577
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1578
- results.push({ tx, events: txEvents });
2059
+ if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
2060
+ continue;
1579
2061
  }
1580
- break;
1581
- }
1582
- case "contract_deploy": {
1583
- for (const tx of transactions) {
1584
- if (tx.type !== "smart_contract")
2062
+ if (event !== null && filter?.type === "print_event" && filter.prints) {
2063
+ const printPayload = payload;
2064
+ const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
2065
+ if (!verdict.ok) {
2066
+ skipped++;
2067
+ logger4.warn("Print payload does not match declared prints schema", {
2068
+ subgraph: subgraph.name,
2069
+ sourceName,
2070
+ topic: printPayload.topic,
2071
+ txId: tx.tx_id,
2072
+ reason: verdict.reason
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
+ });
1585
2085
  continue;
1586
- if (filter.deployer) {
1587
- if (!matchPattern(tx.sender, filter.deployer))
1588
- continue;
1589
- }
1590
- if (filter.contractName) {
1591
- const name = tx.contract_id?.split(".")[1] ?? "";
1592
- if (!matchPattern(name, filter.contractName))
1593
- continue;
1594
2086
  }
1595
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1596
- results.push({ tx, events: txEvents });
1597
2087
  }
1598
- break;
1599
- }
1600
- case "print_event": {
1601
- for (const tx of transactions) {
1602
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
1603
- const matched = txEvents.filter((e) => {
1604
- if (e.type !== "smart_contract_event" && e.type !== "contract_event")
1605
- return false;
1606
- const data = e.data;
1607
- if (!data)
1608
- return false;
1609
- if (data.topic !== "print")
1610
- return false;
1611
- const printContractId = data.contract_identifier ?? data.contract_id;
1612
- if (filter.contractId) {
1613
- if (!printContractId || !matchAny(printContractId, filter.contractId))
1614
- return false;
1615
- }
1616
- if (!traitAllows(filter, printContractId, traitContracts))
1617
- return false;
1618
- if (!factoryAllows(filter, printContractId, factoryContracts))
1619
- return false;
1620
- return true;
1621
- });
1622
- if (matched.length > 0) {
1623
- results.push({ tx, events: matched });
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
+ });
2098
+ continue;
1624
2099
  }
2100
+ processed++;
2101
+ } else if (handler) {
2102
+ await handler(payload, ctx);
2103
+ processed++;
1625
2104
  }
1626
- break;
1627
- }
1628
- }
1629
- return results;
1630
- }
1631
- function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
1632
- const eventsByTx = new Map;
1633
- for (const event of events) {
1634
- const list = eventsByTx.get(event.tx_id) ?? [];
1635
- list.push(event);
1636
- eventsByTx.set(event.tx_id, list);
1637
- }
1638
- const seen = new Set;
1639
- const results = [];
1640
- for (const [sourceName, filter] of Object.entries(sources)) {
1641
- const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
1642
- for (const match of matches) {
1643
- const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
1644
- if (!seen.has(dedupeKey)) {
1645
- seen.add(dedupeKey);
1646
- results.push({ ...match, sourceName });
1647
- }
2105
+ } catch (err) {
2106
+ ctx.rollbackTo(checkpoint);
2107
+ errors++;
2108
+ logger4.error("Subgraph handler error", {
2109
+ subgraph: subgraph.name,
2110
+ sourceName,
2111
+ txId: tx.tx_id,
2112
+ ...event !== null ? { eventId: event.id, eventType: event.type } : {},
2113
+ error: getErrorMessage(err)
2114
+ });
1648
2115
  }
1649
2116
  }
1650
- return results;
2117
+ return { processed, errors, ...skipped > 0 ? { skipped } : {} };
1651
2118
  }
1652
2119
 
1653
2120
  // src/runtime/block-processor.ts
1654
- import { getTargetDb } from "@secondlayer/shared/db";
2121
+ import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
1655
2122
  import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
1656
2123
  import { recordGapBatch } from "@secondlayer/shared/db/queries/subgraph-gaps";
1657
2124
  import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
@@ -1662,15 +2129,15 @@ import {
1662
2129
  rewindLiveProgress,
1663
2130
  updateSubgraphStatus
1664
2131
  } from "@secondlayer/shared/db/queries/subgraphs";
1665
- import { logger as logger7 } from "@secondlayer/shared/logger";
1666
- import { sql as sql3 } from "kysely";
2132
+ import { logger as logger8 } from "@secondlayer/shared/logger";
2133
+ import { sql as sql4 } from "kysely";
1667
2134
 
1668
2135
  // src/runtime/block-source.ts
1669
2136
  import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
1670
2137
  import {
1671
2138
  createInternalIndexHttpClient
1672
2139
  } from "@secondlayer/shared/index-http";
1673
- import { logger as logger4 } from "@secondlayer/shared/logger";
2140
+ import { logger as logger5 } from "@secondlayer/shared/logger";
1674
2141
 
1675
2142
  // src/runtime/batch-loader.ts
1676
2143
  async function loadBlockRange(db, fromHeight, toHeight) {
@@ -1990,7 +2457,7 @@ function reconstructEvent(e) {
1990
2457
  type: "contract_event",
1991
2458
  data: {
1992
2459
  topic: e.payload.topic,
1993
- contract_identifier: e.contract_id,
2460
+ contract_id: e.contract_id,
1994
2461
  value: e.payload.value,
1995
2462
  raw_value: e.payload.raw_value
1996
2463
  }
@@ -2157,7 +2624,7 @@ class FallbackBlockSource {
2157
2624
  try {
2158
2625
  return await this.primary.getTip();
2159
2626
  } catch (err) {
2160
- logger4.warn("block source primary getTip failed — using DB tap", {
2627
+ logger5.warn("block source primary getTip failed — using DB tap", {
2161
2628
  error: err instanceof Error ? err.message : String(err)
2162
2629
  });
2163
2630
  return this.fallback.getTip();
@@ -2167,7 +2634,7 @@ class FallbackBlockSource {
2167
2634
  try {
2168
2635
  return await this.primary.loadBlockRange(fromHeight, toHeight);
2169
2636
  } catch (err) {
2170
- logger4.warn("block source primary loadBlockRange failed — using DB tap", {
2637
+ logger5.warn("block source primary loadBlockRange failed — using DB tap", {
2171
2638
  from: fromHeight,
2172
2639
  to: toHeight,
2173
2640
  error: err instanceof Error ? err.message : String(err)
@@ -2204,7 +2671,7 @@ function resolveBlockSource(subgraph) {
2204
2671
  return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined, needsTransactionData(subgraph)), postgresBlockSource);
2205
2672
  }
2206
2673
  if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
2207
- logger4.debug("Subgraph not streams-index eligible, using DB tap", {
2674
+ logger5.debug("Subgraph not streams-index eligible, using DB tap", {
2208
2675
  subgraph: subgraph.name
2209
2676
  });
2210
2677
  }
@@ -2213,7 +2680,7 @@ function resolveBlockSource(subgraph) {
2213
2680
 
2214
2681
  // src/runtime/outbox-emit.ts
2215
2682
  import { createHash as createHash2 } from "node:crypto";
2216
- import { logger as logger5 } from "@secondlayer/shared/logger";
2683
+ import { logger as logger6 } from "@secondlayer/shared/logger";
2217
2684
  var loggedKillSwitch = false;
2218
2685
  var OP_VERB = {
2219
2686
  insert: "created",
@@ -2237,7 +2704,7 @@ function stableStringify(obj) {
2237
2704
  async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, blockHeight) {
2238
2705
  if (!isEmitOutboxEnabled()) {
2239
2706
  if (!loggedKillSwitch) {
2240
- logger5.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
2707
+ logger6.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
2241
2708
  loggedKillSwitch = true;
2242
2709
  }
2243
2710
  return 0;
@@ -2282,7 +2749,7 @@ function sandboxEnabled(subgraph) {
2282
2749
  // src/runtime/sandbox/host.ts
2283
2750
  import { tmpdir } from "node:os";
2284
2751
  import { fileURLToPath } from "node:url";
2285
- import { logger as logger6 } from "@secondlayer/shared/logger";
2752
+ import { logger as logger7 } from "@secondlayer/shared/logger";
2286
2753
 
2287
2754
  // src/runtime/sandbox/bundle.ts
2288
2755
  import esbuild from "esbuild";
@@ -2362,7 +2829,7 @@ function spawnSandboxProcess() {
2362
2829
  break;
2363
2830
  const text = decoder.decode(value, { stream: true });
2364
2831
  if (text.trim()) {
2365
- logger6.warn("Sandbox subprocess stderr", { stderr: text.trim() });
2832
+ logger7.warn("Sandbox subprocess stderr", { stderr: text.trim() });
2366
2833
  }
2367
2834
  }
2368
2835
  } catch {}
@@ -2420,7 +2887,7 @@ async function ensureSandboxProcess(subgraphName, version, handlerCode) {
2420
2887
  try {
2421
2888
  await initSandboxProcess(existing.proc, subgraphName, version, handlerCode);
2422
2889
  existing.version = version;
2423
- logger6.info("Sandbox subprocess handler reloaded", {
2890
+ logger7.info("Sandbox subprocess handler reloaded", {
2424
2891
  subgraph: subgraphName,
2425
2892
  version
2426
2893
  });
@@ -2428,7 +2895,7 @@ async function ensureSandboxProcess(subgraphName, version, handlerCode) {
2428
2895
  } catch (err) {
2429
2896
  existing.proc.kill();
2430
2897
  pool.delete(subgraphName);
2431
- logger6.warn("Sandbox subprocess re-init failed; respawning", {
2898
+ logger7.warn("Sandbox subprocess re-init failed; respawning", {
2432
2899
  subgraph: subgraphName,
2433
2900
  error: err instanceof Error ? err.message : String(err)
2434
2901
  });
@@ -2511,7 +2978,7 @@ async function runBlockThroughProcess(entry, subgraphName, hostCtx, block, match
2511
2978
  return result;
2512
2979
  } catch (err) {
2513
2980
  evictSandboxWorker(subgraphName);
2514
- logger6.warn("Sandbox subprocess run failed; subprocess evicted", {
2981
+ logger7.warn("Sandbox subprocess run failed; subprocess evicted", {
2515
2982
  subgraph: subgraphName,
2516
2983
  blockHeight: block.height,
2517
2984
  error: err instanceof Error ? err.message : String(err)
@@ -2582,7 +3049,7 @@ async function answerRead(hostCtx, msg) {
2582
3049
 
2583
3050
  // src/runtime/subscription-state.ts
2584
3051
  import { listSubscriptions } from "@secondlayer/shared/db/queries/subscriptions";
2585
- import { sql as sql2 } from "kysely";
3052
+ import { sql as sql3 } from "kysely";
2586
3053
 
2587
3054
  // src/runtime/emitter-matcher.ts
2588
3055
  function isPrimitive(v) {
@@ -2746,7 +3213,7 @@ class SubscriptionMatcher {
2746
3213
  // src/runtime/subscription-state.ts
2747
3214
  var matcher = new SubscriptionMatcher;
2748
3215
  async function refreshMatcher(db) {
2749
- const rows = await sql2`
3216
+ const rows = await sql3`
2750
3217
  SELECT * FROM subscriptions WHERE status = 'active'
2751
3218
  `.execute(db);
2752
3219
  matcher.setAll(rows.rows);
@@ -2768,7 +3235,7 @@ async function resolveRoute(subgraphName, targetDb) {
2768
3235
  version: row?.version ?? ""
2769
3236
  };
2770
3237
  routeCache.set(subgraphName, route);
2771
- logger7.info("subgraph handler dispatch path resolved", {
3238
+ logger8.info("subgraph handler dispatch path resolved", {
2772
3239
  subgraph: subgraphName,
2773
3240
  path: sandboxEnabled({ sandbox_workers: route.sandboxWorkers }) ? "sandbox" : "in-process"
2774
3241
  });
@@ -2792,8 +3259,8 @@ async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, tx
2792
3259
  for (const [discoveringSource, factory] of factories) {
2793
3260
  const known = new Set;
2794
3261
  try {
2795
- const rows = await sql3`
2796
- SELECT address FROM ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
3262
+ const rows = await sql4`
3263
+ SELECT address FROM ${sql4.raw(`"${schemaName}"."_factory_addresses"`)}
2797
3264
  WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
2798
3265
  `.execute(db);
2799
3266
  for (const row of rows.rows)
@@ -2821,8 +3288,8 @@ async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered
2821
3288
  if (discovered.length === 0)
2822
3289
  return;
2823
3290
  for (const { sourceName, address } of discovered) {
2824
- await sql3`
2825
- INSERT INTO ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
3291
+ await sql4`
3292
+ INSERT INTO ${sql4.raw(`"${schemaName}"."_factory_addresses"`)}
2826
3293
  (source_name, address, block_height)
2827
3294
  VALUES (${sourceName}, ${address}, ${blockHeight})
2828
3295
  ON CONFLICT (source_name, address) DO NOTHING
@@ -2882,7 +3349,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
2882
3349
  const delay = retryDelaysMs[attempt];
2883
3350
  if (delay === undefined)
2884
3351
  break;
2885
- logger7.warn("Block processing failed, retrying", {
3352
+ logger8.warn("Block processing failed, retrying", {
2886
3353
  subgraph: subgraphName,
2887
3354
  blockHeight,
2888
3355
  attempt: attempt + 1,
@@ -2895,7 +3362,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
2895
3362
  throw lastError;
2896
3363
  }
2897
3364
  async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2898
- const targetDb = getTargetDb();
3365
+ const targetDb = getTargetDb2();
2899
3366
  const blockStart = performance.now();
2900
3367
  const result = {
2901
3368
  blockHeight,
@@ -2914,7 +3381,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2914
3381
  } else {
2915
3382
  const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
2916
3383
  if (!data) {
2917
- logger7.debug("Block not found or non-canonical for subgraph processing", {
3384
+ logger8.debug("Block not found or non-canonical for subgraph processing", {
2918
3385
  subgraph: subgraphName,
2919
3386
  blockHeight
2920
3387
  });
@@ -2980,7 +3447,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2980
3447
  { start: blockHeight, end: blockHeight, reason: "processing_error" }
2981
3448
  ]);
2982
3449
  } else {
2983
- logger7.warn("Block errors not recorded as a gap — no subgraphId", {
3450
+ logger8.warn("Block errors not recorded as a gap — no subgraphId", {
2984
3451
  subgraph: subgraphName,
2985
3452
  blockHeight,
2986
3453
  errors: rr.errors
@@ -3053,7 +3520,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
3053
3520
  });
3054
3521
  } catch (err) {
3055
3522
  if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
3056
- logger7.warn("cursor race lost — block already covered", {
3523
+ logger8.warn("cursor race lost — block already covered", {
3057
3524
  subgraph: subgraphName,
3058
3525
  blockHeight,
3059
3526
  error: err.message
@@ -3078,10 +3545,10 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
3078
3545
  try {
3079
3546
  const tables = Object.keys(subgraph.schema);
3080
3547
  for (const table of tables) {
3081
- const { rows } = await sql3.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
3548
+ const { rows } = await sql4.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
3082
3549
  const count = Number(rows[0]?.count ?? 0);
3083
3550
  if (count >= 1e7) {
3084
- logger7.warn("Subgraph table exceeds 10M rows (estimate)", {
3551
+ logger8.warn("Subgraph table exceeds 10M rows (estimate)", {
3085
3552
  subgraph: subgraphName,
3086
3553
  table,
3087
3554
  count
@@ -3089,13 +3556,13 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
3089
3556
  }
3090
3557
  }
3091
3558
  } catch (err) {
3092
- logger7.debug("Row count sample failed", {
3559
+ logger8.debug("Row count sample failed", {
3093
3560
  subgraph: subgraphName,
3094
3561
  error: err instanceof Error ? err.message : String(err)
3095
3562
  });
3096
3563
  }
3097
3564
  if (journalEnabled(opts)) {
3098
- await sql3.raw(`DELETE FROM "${schemaName}"."_journal" WHERE "block_height" < ${blockHeight - JOURNAL_RETENTION_BLOCKS}`).execute(route.dataDb).catch(() => {});
3565
+ await sql4.raw(`DELETE FROM "${schemaName}"."_journal" WHERE "block_height" < ${blockHeight - JOURNAL_RETENTION_BLOCKS}`).execute(route.dataDb).catch(() => {});
3099
3566
  }
3100
3567
  }
3101
3568
  return result;
@@ -3165,9 +3632,9 @@ class StatsAccumulator {
3165
3632
  }
3166
3633
 
3167
3634
  // src/runtime/catchup.ts
3168
- import { getTargetDb as getTargetDb2, sql as sql4 } from "@secondlayer/shared/db";
3635
+ import { getTargetDb as getTargetDb3, sql as sql5 } from "@secondlayer/shared/db";
3169
3636
  import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
3170
- import { logger as logger8 } from "@secondlayer/shared/logger";
3637
+ import { logger as logger9 } from "@secondlayer/shared/logger";
3171
3638
 
3172
3639
  // src/runtime/catchup-leader.ts
3173
3640
  import {
@@ -3297,8 +3764,8 @@ async function withSubgraphBlockLock(name, fn) {
3297
3764
  }
3298
3765
  }
3299
3766
  async function withSubgraphBlockAdvisoryLock(name, fn) {
3300
- return getTargetDb2().transaction().execute(async (lockTx) => {
3301
- await sql4`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-block:${name}`}))`.execute(lockTx);
3767
+ return getTargetDb3().transaction().execute(async (lockTx) => {
3768
+ await sql5`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-block:${name}`}))`.execute(lockTx);
3302
3769
  return fn();
3303
3770
  });
3304
3771
  }
@@ -3359,7 +3826,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3359
3826
  catchingUp.add(subgraphName);
3360
3827
  try {
3361
3828
  const source = resolveBlockSource(subgraph);
3362
- const targetDb = getTargetDb2();
3829
+ const targetDb = getTargetDb3();
3363
3830
  const subgraphRow = await getSubgraph(targetDb, subgraphName);
3364
3831
  if (!subgraphRow)
3365
3832
  return 0;
@@ -3371,7 +3838,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3371
3838
  if (usesDecodedIndexPlane(subgraph)) {
3372
3839
  const bound = await boundSourceTip(rawTip, decoderNamesForSubgraph(subgraph));
3373
3840
  if (!bound.ok) {
3374
- logger8.warn("Subgraph catch-up stalled: missing decoder checkpoint", {
3841
+ logger9.warn("Subgraph catch-up stalled: missing decoder checkpoint", {
3375
3842
  event: "subgraph_catchup_decoder_stall",
3376
3843
  subgraph: subgraphName,
3377
3844
  missing: bound.missing
@@ -3379,7 +3846,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3379
3846
  return 0;
3380
3847
  }
3381
3848
  if (bound.floor !== null && bound.floor < rawTip) {
3382
- logger8.debug("Subgraph catch-up tip bounded by decoder progress", {
3849
+ logger9.debug("Subgraph catch-up tip bounded by decoder progress", {
3383
3850
  event: "subgraph_catchup_decoder_floor",
3384
3851
  subgraph: subgraphName,
3385
3852
  rawTip,
@@ -3394,7 +3861,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3394
3861
  const subgraphStart = Number(subgraphRow.start_block) || 1;
3395
3862
  const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
3396
3863
  const totalBlocks = chainTip - lastProcessedBlock;
3397
- logger8.info("Subgraph catch-up starting", {
3864
+ logger9.info("Subgraph catch-up starting", {
3398
3865
  subgraph: subgraphName,
3399
3866
  from: startBlock,
3400
3867
  to: chainTip,
@@ -3410,7 +3877,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3410
3877
  let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
3411
3878
  while (currentHeight <= chainTip) {
3412
3879
  if (!isCatchUpLeader()) {
3413
- logger8.info("Lost catch-up leadership, stopping catch-up", {
3880
+ logger9.info("Lost catch-up leadership, stopping catch-up", {
3414
3881
  subgraph: subgraphName,
3415
3882
  processed
3416
3883
  });
@@ -3418,7 +3885,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3418
3885
  }
3419
3886
  const currentRow = await getSubgraph(targetDb, subgraphName);
3420
3887
  if (!currentRow || currentRow.status !== "active") {
3421
- logger8.info("Subgraph status changed, stopping catch-up", {
3888
+ logger9.info("Subgraph status changed, stopping catch-up", {
3422
3889
  subgraph: subgraphName,
3423
3890
  status: currentRow?.status ?? "deleted"
3424
3891
  });
@@ -3447,7 +3914,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3447
3914
  blockData = (await source.loadBlockRange(height, height)).get(height);
3448
3915
  }
3449
3916
  if (!blockData) {
3450
- logger8.warn("Block missing during catch-up, deferring to next tick", {
3917
+ logger9.warn("Block missing during catch-up, deferring to next tick", {
3451
3918
  subgraph: subgraphName,
3452
3919
  blockHeight: height
3453
3920
  });
@@ -3468,12 +3935,12 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3468
3935
  });
3469
3936
  } catch (err) {
3470
3937
  if (err instanceof ReorgEpochChangedError) {
3471
- logger8.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
3938
+ logger9.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
3472
3939
  stopCatchup = true;
3473
3940
  break;
3474
3941
  }
3475
3942
  const errorMsg = err instanceof Error ? err.message : String(err);
3476
- logger8.error("Block processing failed persistently during catch-up", {
3943
+ logger9.error("Block processing failed persistently during catch-up", {
3477
3944
  subgraph: subgraphName,
3478
3945
  blockHeight: height,
3479
3946
  error: errorMsg
@@ -3492,7 +3959,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3492
3959
  }
3493
3960
  }
3494
3961
  if (processed % LOG_INTERVAL === 0) {
3495
- logger8.info("Subgraph catch-up progress", {
3962
+ logger9.info("Subgraph catch-up progress", {
3496
3963
  subgraph: subgraphName,
3497
3964
  processed,
3498
3965
  total: totalBlocks,
@@ -3508,7 +3975,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3508
3975
  currentHeight = batchEnd + 1;
3509
3976
  }
3510
3977
  await stats.flush(targetDb);
3511
- logger8.info("Subgraph catch-up complete", {
3978
+ logger9.info("Subgraph catch-up complete", {
3512
3979
  subgraph: subgraphName,
3513
3980
  processed
3514
3981
  });
@@ -3520,16 +3987,16 @@ async function catchUpSubgraph(subgraph, subgraphName) {
3520
3987
 
3521
3988
  // src/runtime/reorg.ts
3522
3989
  import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
3523
- import { getRawClient, getTargetDb as getTargetDb3 } from "@secondlayer/shared/db";
3990
+ import { getRawClient, getTargetDb as getTargetDb4 } from "@secondlayer/shared/db";
3524
3991
  import { listSubgraphs } from "@secondlayer/shared/db/queries/subgraphs";
3525
- import { logger as logger9 } from "@secondlayer/shared/logger";
3526
- import { sql as sql5 } from "kysely";
3992
+ import { logger as logger10 } from "@secondlayer/shared/logger";
3993
+ import { sql as sql6 } from "kysely";
3527
3994
  async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
3528
- const targetDb = getTargetDb3();
3995
+ const targetDb = getTargetDb4();
3529
3996
  const activeSubgraphs = (await listSubgraphs(targetDb)).filter((v) => v.status === "active");
3530
3997
  if (activeSubgraphs.length === 0)
3531
3998
  return;
3532
- logger9.info("Propagating reorg to subgraphs", {
3999
+ logger10.info("Propagating reorg to subgraphs", {
3533
4000
  blockHeight,
3534
4001
  subgraphCount: activeSubgraphs.length
3535
4002
  });
@@ -3540,13 +4007,13 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
3540
4007
  continue;
3541
4008
  bumpReorgEpoch(sg.name);
3542
4009
  await withSubgraphBlockLock(sg.name, async () => {
3543
- await getTargetDb3().transaction().execute(async (lockTx) => {
3544
- await sql5`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-reorg:${sg.name}`}))`.execute(lockTx);
4010
+ await getTargetDb4().transaction().execute(async (lockTx) => {
4011
+ await sql6`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-reorg:${sg.name}`}))`.execute(lockTx);
3545
4012
  await reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef);
3546
4013
  });
3547
4014
  });
3548
4015
  } catch (err) {
3549
- logger9.error("Subgraph reorg handling failed", {
4016
+ logger10.error("Subgraph reorg handling failed", {
3550
4017
  subgraph: sg.name,
3551
4018
  blockHeight,
3552
4019
  error: getErrorMessage2(err)
@@ -3555,7 +4022,7 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
3555
4022
  }
3556
4023
  }
3557
4024
  async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
3558
- const targetDb = getTargetDb3();
4025
+ const targetDb = getTargetDb4();
3559
4026
  const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
3560
4027
  const client = getRawClient("target");
3561
4028
  const tableNames = Object.keys(schema);
@@ -3584,7 +4051,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
3584
4051
  await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
3585
4052
  });
3586
4053
  } else {
3587
- logger9.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
4054
+ logger10.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
3588
4055
  for (const tableName of tableNames) {
3589
4056
  await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3590
4057
  }
@@ -3594,6 +4061,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
3594
4061
  DELETE FROM "${schemaName}"."_factory_addresses" WHERE block_height >= ${Number(blockHeight)};
3595
4062
  END IF;
3596
4063
  END $$`);
4064
+ await targetDb.deleteFrom("subgraph_violations").where("subgraph_name", "=", sg.name).where("block_height", ">=", blockHeight).execute();
3597
4065
  for (const [tableName, rows] of Object.entries(revertedByTable)) {
3598
4066
  if (rows.length === 0)
3599
4067
  continue;
@@ -3627,7 +4095,7 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
3627
4095
  }).as("row_pk"),
3628
4096
  eb2.val("pending").as("status")
3629
4097
  ]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.columns(["subscription_id", "dedup_key"]).doNothing()).execute().catch((err) => {
3630
- logger9.error("Failed to emit revert event for subscriptions", {
4098
+ logger10.error("Failed to emit revert event for subscriptions", {
3631
4099
  event: "reorg_revert_emit_dropped",
3632
4100
  subgraph: sg.name,
3633
4101
  table: tableName,
@@ -3636,14 +4104,14 @@ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
3636
4104
  });
3637
4105
  });
3638
4106
  }
3639
- logger9.info("Subgraph reorg cleanup done", {
4107
+ logger10.info("Subgraph reorg cleanup done", {
3640
4108
  subgraph: sg.name,
3641
4109
  blockHeight,
3642
4110
  tablesAffected: Object.keys(revertedByTable).length
3643
4111
  });
3644
4112
  const def = await loadSubgraphDef(sg);
3645
4113
  await processBlock(def, sg.name, blockHeight, { reorgRewind: true });
3646
- logger9.info("Subgraph reorg reprocessed", {
4114
+ logger10.info("Subgraph reorg reprocessed", {
3647
4115
  subgraph: sg.name,
3648
4116
  blockHeight
3649
4117
  });
@@ -3652,5 +4120,5 @@ export {
3652
4120
  handleSubgraphReorg
3653
4121
  };
3654
4122
 
3655
- //# debugId=33B13FDE84A36A1564756E2164756E21
4123
+ //# debugId=C83516F4FE20D8A864756E2164756E21
3656
4124
  //# sourceMappingURL=reorg.js.map