@secondlayer/subgraphs 6.3.6 → 6.5.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.
@@ -1,6 +1,3 @@
1
- // src/testing/harness.ts
2
- import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
3
-
4
1
  // src/runtime/context.ts
5
2
  import { logger as logger2 } from "@secondlayer/shared/logger";
6
3
  import { sql } from "kysely";
@@ -440,7 +437,7 @@ class SubgraphContext {
440
437
  async readRows(table, where, limit) {
441
438
  const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
442
439
  const { clause } = buildWhereClause(where);
443
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
440
+ const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} ORDER BY "_id"${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
444
441
  const { rows } = await sql.raw(query).execute(this.db);
445
442
  return rows.map((r) => this.coerceRow(table, r));
446
443
  }
@@ -862,13 +859,127 @@ function buildWhereClause(where) {
862
859
  return { clause: parts.join(" AND "), values: [] };
863
860
  }
864
861
 
862
+ // src/runtime/memory-store.ts
863
+ import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase2 } from "@secondlayer/stacks/clarity";
864
+ class MemoryStore {
865
+ tables = new Map;
866
+ }
867
+
868
+ class MemorySubgraphContext extends SubgraphContext {
869
+ store;
870
+ tables;
871
+ constructor(store, schema, block, tx) {
872
+ super(undefined, "memory", schema, block, tx, false);
873
+ this.store = store;
874
+ this.tables = schema;
875
+ }
876
+ async readRows(table, where, limit) {
877
+ const rows = (this.store.tables.get(table) ?? []).filter((row) => Object.entries(where).every(([k, v]) => sameValue(row[k], v)));
878
+ return limit === undefined ? rows : rows.slice(0, limit);
879
+ }
880
+ readDependent = null;
881
+ dependsOnPrior(what) {
882
+ this.readDependent ??= what;
883
+ }
884
+ findOne(table, where) {
885
+ this.dependsOnPrior(`findOne("${table}")`);
886
+ return super.findOne(table, where);
887
+ }
888
+ findMany(table, where) {
889
+ this.dependsOnPrior(`findMany("${table}")`);
890
+ return super.findMany(table, where);
891
+ }
892
+ update(table, where, set) {
893
+ this.dependsOnPrior(`update("${table}")`);
894
+ super.update(table, where, set);
895
+ }
896
+ delete(table, where) {
897
+ this.dependsOnPrior(`delete("${table}")`);
898
+ super.delete(table, where);
899
+ }
900
+ increment(table, key, deltas) {
901
+ this.dependsOnPrior(`increment("${table}")`);
902
+ super.increment(table, key, deltas);
903
+ }
904
+ upsert(table, key, row) {
905
+ const def = this.tables[table];
906
+ if (def) {
907
+ const keyCols = Object.keys(key);
908
+ const keyed = def.uniqueKeys?.some((uk) => uk.length === keyCols.length && uk.every((c) => keyCols.includes(c)));
909
+ const omitted = Object.keys(def.columns).find((c) => !(c in key) && !(c in row));
910
+ if (!keyed)
911
+ this.dependsOnPrior(`upsert("${table}") without a unique key`);
912
+ else if (omitted)
913
+ this.dependsOnPrior(`upsert("${table}") leaves "${omitted}" as it was`);
914
+ }
915
+ super.upsert(table, key, row);
916
+ }
917
+ async rowsOf(table) {
918
+ return this.overlayMany(table, {}, await this.readRows(table, {}));
919
+ }
920
+ setReads(reads) {
921
+ this._client = {
922
+ contract(contractId, abi) {
923
+ const camelToKebab = new Map;
924
+ for (const fn of normalizeAbi2(abi).functions) {
925
+ camelToKebab.set(toCamelCase2(fn.name), fn.name);
926
+ }
927
+ const read = new Proxy({}, {
928
+ get(_target, prop) {
929
+ const fnName = camelToKebab.get(prop) ?? prop;
930
+ const key = `${contractId}.${fnName}`;
931
+ return async () => {
932
+ if (!(key in reads)) {
933
+ throw new Error(`No stubbed chain read for "${key}" — pass it via createTestContext(schema, { reads: { "${key}": … } }).`);
934
+ }
935
+ return reads[key];
936
+ };
937
+ }
938
+ });
939
+ return { read };
940
+ }
941
+ };
942
+ }
943
+ async commitOps() {
944
+ const tables = new Set;
945
+ for (const op of this.ops)
946
+ tables.add(op.table);
947
+ for (const table of tables) {
948
+ this.store.tables.set(table, await this.rowsOf(table));
949
+ }
950
+ this.ops.length = 0;
951
+ }
952
+ insertsSince(checkpoint) {
953
+ const out = [];
954
+ for (const op of this.ops.slice(Math.max(0, checkpoint))) {
955
+ if (op.kind !== "insert")
956
+ continue;
957
+ const row = { ...op.data };
958
+ out.push({
959
+ table: op.table,
960
+ keys: Object.keys(row).filter((k) => !k.startsWith("_")),
961
+ row
962
+ });
963
+ }
964
+ return out;
965
+ }
966
+ }
967
+ function sameValue(a, b) {
968
+ if (a === b)
969
+ return true;
970
+ if ((typeof a === "bigint" || typeof a === "number") && (typeof b === "bigint" || typeof b === "number")) {
971
+ return BigInt(a) === BigInt(b);
972
+ }
973
+ return String(a) === String(b);
974
+ }
975
+
865
976
  // src/runtime/runner.ts
866
977
  import { getErrorMessage } from "@secondlayer/shared";
867
978
  import { logger as logger4 } from "@secondlayer/shared/logger";
868
979
  import {
869
980
  clarityValueToJS,
870
981
  deserializeCV as deserializeCV4,
871
- toCamelCase as toCamelCase2
982
+ toCamelCase as toCamelCase3
872
983
  } from "@secondlayer/stacks/clarity";
873
984
 
874
985
  // src/print-schema.ts
@@ -1253,6 +1364,561 @@ var COLUMN_TYPES = new Set([
1253
1364
  "timestamp",
1254
1365
  "jsonb"
1255
1366
  ]);
1367
+ function isColumnType(field) {
1368
+ return typeof field === "string" && COLUMN_TYPES.has(field);
1369
+ }
1370
+ function matchesField(field, value) {
1371
+ if (isColumnType(field)) {
1372
+ switch (field) {
1373
+ case "uint":
1374
+ case "int":
1375
+ return typeof value === "bigint" || typeof value === "number";
1376
+ case "boolean":
1377
+ return typeof value === "boolean";
1378
+ case "text":
1379
+ case "principal":
1380
+ case "timestamp":
1381
+ return typeof value === "string" || typeof value === "number" || typeof value === "bigint";
1382
+ case "jsonb":
1383
+ return value !== undefined;
1384
+ }
1385
+ }
1386
+ if (typeof field !== "object" || field === null)
1387
+ return false;
1388
+ if ("optional" in field) {
1389
+ return value === undefined || value === null || matchesField(field.type, value);
1390
+ }
1391
+ if ("tuple" in field) {
1392
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1393
+ return false;
1394
+ }
1395
+ return matchesTopic(field.tuple, value).ok;
1396
+ }
1397
+ if ("list" in field) {
1398
+ return Array.isArray(value) && value.every((item) => matchesField(field.list, item));
1399
+ }
1400
+ return false;
1401
+ }
1402
+ function matchesTopic(declared, data) {
1403
+ for (const [name, field] of Object.entries(declared)) {
1404
+ const present = name in data;
1405
+ const optional = typeof field === "object" && field !== null && "optional" in field;
1406
+ if (!present) {
1407
+ if (optional)
1408
+ continue;
1409
+ return { ok: false, reason: `missing declared field "${name}"` };
1410
+ }
1411
+ if (!matchesField(field, data[name])) {
1412
+ return {
1413
+ ok: false,
1414
+ reason: `field "${name}" does not match its declared type (got ${describe(data[name])})`
1415
+ };
1416
+ }
1417
+ }
1418
+ return { ok: true };
1419
+ }
1420
+ function describe(value) {
1421
+ if (value === null)
1422
+ return "null";
1423
+ if (Array.isArray(value))
1424
+ return "array";
1425
+ return typeof value;
1426
+ }
1427
+ function validatePrintPayload(prints, topic, data) {
1428
+ if (!prints)
1429
+ return { ok: true };
1430
+ const declared = prints[topic];
1431
+ if (!declared)
1432
+ return { ok: true };
1433
+ return matchesTopic(declared, data);
1434
+ }
1435
+
1436
+ // src/runtime/realm.ts
1437
+ import vm from "node:vm";
1438
+
1439
+ // src/verification.ts
1440
+ import { createHash as createHash2 } from "node:crypto";
1441
+ // package.json
1442
+ var package_default = {
1443
+ name: "@secondlayer/subgraphs",
1444
+ version: "6.5.0",
1445
+ description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
1446
+ license: "MIT",
1447
+ repository: {
1448
+ type: "git",
1449
+ url: "git+https://github.com/ryanwaits/secondlayer.git",
1450
+ directory: "packages/subgraphs"
1451
+ },
1452
+ homepage: "https://github.com/ryanwaits/secondlayer/tree/main/packages/subgraphs#readme",
1453
+ type: "module",
1454
+ main: "./dist/index.js",
1455
+ types: "./dist/index.d.ts",
1456
+ exports: {
1457
+ ".": {
1458
+ types: "./dist/index.d.ts",
1459
+ import: "./dist/index.js"
1460
+ },
1461
+ "./types": {
1462
+ types: "./dist/types.d.ts",
1463
+ import: "./dist/types.js"
1464
+ },
1465
+ "./schema": {
1466
+ types: "./dist/schema/index.d.ts",
1467
+ import: "./dist/schema/index.js"
1468
+ },
1469
+ "./validate": {
1470
+ types: "./dist/validate.d.ts",
1471
+ import: "./dist/validate.js"
1472
+ },
1473
+ "./verification": {
1474
+ types: "./dist/verification.d.ts",
1475
+ import: "./dist/verification.js"
1476
+ },
1477
+ "./runtime/replay": {
1478
+ types: "./dist/runtime/replay.d.ts",
1479
+ import: "./dist/runtime/replay.js"
1480
+ },
1481
+ "./runtime/emitter": {
1482
+ types: "./dist/runtime/emitter.d.ts",
1483
+ import: "./dist/runtime/emitter.js"
1484
+ },
1485
+ "./testing": {
1486
+ types: "./dist/testing/index.d.ts",
1487
+ import: "./dist/testing/index.js"
1488
+ },
1489
+ "./verify": {
1490
+ types: "./dist/verify/index.d.ts",
1491
+ import: "./dist/verify/index.js"
1492
+ }
1493
+ },
1494
+ files: ["dist"],
1495
+ scripts: {
1496
+ build: "bunup",
1497
+ dev: "bunup --watch",
1498
+ test: "bun test",
1499
+ typecheck: "tsc --noEmit",
1500
+ prepublishOnly: "bun run build"
1501
+ },
1502
+ dependencies: {
1503
+ "@secondlayer/shared": "^11.23.0",
1504
+ "@secondlayer/stacks": "^6.2.0",
1505
+ "@secondlayer/verify": "^0.3.0",
1506
+ esbuild: "^0.28.2",
1507
+ kysely: "0.28.17",
1508
+ zod: "^4.3.6"
1509
+ },
1510
+ devDependencies: {
1511
+ "@types/bun": "latest",
1512
+ bunup: "0.16.32",
1513
+ typescript: "^5"
1514
+ },
1515
+ publishConfig: {
1516
+ access: "public"
1517
+ }
1518
+ };
1519
+
1520
+ // src/verification.ts
1521
+ var SUBGRAPHS_RUNTIME = `@secondlayer/subgraphs@${package_default.version}`;
1522
+ var FORBIDDEN_GLOBALS = {
1523
+ Date: "wall-clock time differs per run",
1524
+ performance: "timers differ per run",
1525
+ process: "host environment differs per run",
1526
+ fetch: "network reads are not chain inputs",
1527
+ crypto: "randomness differs per run",
1528
+ setTimeout: "scheduling order differs per run",
1529
+ setInterval: "scheduling order differs per run",
1530
+ setImmediate: "scheduling order differs per run",
1531
+ clearTimeout: "scheduling order differs per run",
1532
+ clearInterval: "scheduling order differs per run",
1533
+ queueMicrotask: "scheduling order differs per run",
1534
+ WeakRef: "garbage collection timing differs per run",
1535
+ FinalizationRegistry: "garbage collection timing differs per run",
1536
+ Atomics: "shared memory is not single-threaded",
1537
+ SharedArrayBuffer: "shared memory is not single-threaded",
1538
+ Intl: "locale and time zone data differ per host",
1539
+ Temporal: "locale and time zone data differ per host",
1540
+ eval: "generated code hides inputs from the scan",
1541
+ WebAssembly: "compiled code hides inputs from the scan",
1542
+ ShadowRealm: "a nested realm escapes the allow-list",
1543
+ require: "imports must be bundled into the handler"
1544
+ };
1545
+ var ALLOWED_GLOBALS = [
1546
+ "undefined",
1547
+ "NaN",
1548
+ "Infinity",
1549
+ "globalThis",
1550
+ "isNaN",
1551
+ "isFinite",
1552
+ "parseInt",
1553
+ "parseFloat",
1554
+ "decodeURI",
1555
+ "decodeURIComponent",
1556
+ "encodeURI",
1557
+ "encodeURIComponent",
1558
+ "escape",
1559
+ "unescape",
1560
+ "Object",
1561
+ "Function",
1562
+ "Array",
1563
+ "Number",
1564
+ "Boolean",
1565
+ "String",
1566
+ "Symbol",
1567
+ "BigInt",
1568
+ "Math",
1569
+ "JSON",
1570
+ "Reflect",
1571
+ "Proxy",
1572
+ "Promise",
1573
+ "Map",
1574
+ "Set",
1575
+ "WeakMap",
1576
+ "WeakSet",
1577
+ "RegExp",
1578
+ "Iterator",
1579
+ "DisposableStack",
1580
+ "AsyncDisposableStack",
1581
+ "Error",
1582
+ "AggregateError",
1583
+ "SuppressedError",
1584
+ "EvalError",
1585
+ "RangeError",
1586
+ "ReferenceError",
1587
+ "SyntaxError",
1588
+ "TypeError",
1589
+ "URIError",
1590
+ "ArrayBuffer",
1591
+ "DataView",
1592
+ "Int8Array",
1593
+ "Uint8Array",
1594
+ "Uint8ClampedArray",
1595
+ "Int16Array",
1596
+ "Uint16Array",
1597
+ "Int32Array",
1598
+ "Uint32Array",
1599
+ "Float16Array",
1600
+ "Float32Array",
1601
+ "Float64Array",
1602
+ "BigInt64Array",
1603
+ "BigUint64Array",
1604
+ "TextEncoder",
1605
+ "TextDecoder",
1606
+ "atob",
1607
+ "btoa",
1608
+ "console"
1609
+ ];
1610
+ var ALLOWED_MATH = [
1611
+ "abs",
1612
+ "min",
1613
+ "max",
1614
+ "floor",
1615
+ "ceil",
1616
+ "trunc",
1617
+ "sign",
1618
+ "round",
1619
+ "E",
1620
+ "LN10",
1621
+ "LN2",
1622
+ "LOG10E",
1623
+ "LOG2E",
1624
+ "PI",
1625
+ "SQRT1_2",
1626
+ "SQRT2"
1627
+ ];
1628
+ var LOCALE_METHODS = [
1629
+ "toLocaleString",
1630
+ "toLocaleDateString",
1631
+ "toLocaleTimeString",
1632
+ "toLocaleUpperCase",
1633
+ "toLocaleLowerCase",
1634
+ "localeCompare"
1635
+ ];
1636
+ function formatFinding(f) {
1637
+ return `${f.file}:${f.line}:${f.column} ${f.name}: ${f.reason}`;
1638
+ }
1639
+ var STATE_SOURCE_TYPES = new Set([
1640
+ "var_set",
1641
+ "map_set",
1642
+ "map_delete"
1643
+ ]);
1644
+ var LEVEL_RANK = { state: 0, events: 1, none: 2 };
1645
+ var MAX_LISTED_FINDINGS = 10;
1646
+ function deriveVerification(def, findings = []) {
1647
+ let level = "state";
1648
+ const raise = (to) => {
1649
+ if (LEVEL_RANK[to] > LEVEL_RANK[level])
1650
+ level = to;
1651
+ };
1652
+ const reasons = [];
1653
+ for (const [name, source] of Object.entries(def.sources ?? {})) {
1654
+ const filter = source;
1655
+ if (filter.trait) {
1656
+ raise("none");
1657
+ reasons.push(`source "${name}": trait scope needs a proven contract registry`);
1658
+ } else if (filter.type === "map_insert") {
1659
+ raise("events");
1660
+ reasons.push(`map_insert source "${name}": map_insert vs map_set needs event proofs`);
1661
+ } else if (!STATE_SOURCE_TYPES.has(filter.type)) {
1662
+ raise("events");
1663
+ reasons.push(`${filter.type} source "${name}" needs event proofs`);
1664
+ }
1665
+ }
1666
+ if (def.backfillMode === "concurrent") {
1667
+ raise("none");
1668
+ reasons.push("backfillMode concurrent: tip-first order is not chain order");
1669
+ }
1670
+ if (findings.some((f) => f.kind === "needs-events"))
1671
+ raise("events");
1672
+ for (const f of findings.slice(0, MAX_LISTED_FINDINGS)) {
1673
+ reasons.push(f.kind === "needs-events" ? `${formatFinding(f)} (needs event proofs)` : formatFinding(f));
1674
+ }
1675
+ if (findings.length > MAX_LISTED_FINDINGS) {
1676
+ reasons.push(`…and ${findings.length - MAX_LISTED_FINDINGS} more handler findings`);
1677
+ }
1678
+ return {
1679
+ level,
1680
+ reasons,
1681
+ unproven: reasons.length === 0 ? [
1682
+ "tx attribution of writes: event.tx, _tx_id (needs event proofs)",
1683
+ "intermediate writes within a block (needs event proofs)"
1684
+ ] : []
1685
+ };
1686
+ }
1687
+ function isDeterminismViolation(v) {
1688
+ return v.level === "state" && v.reasons.length > 0;
1689
+ }
1690
+ function sha256(input) {
1691
+ return createHash2("sha256").update(input).digest("hex");
1692
+ }
1693
+ function pinPreimage(input) {
1694
+ const fields = {
1695
+ handlerHash: sha256(input.handlerCode),
1696
+ network: input.network,
1697
+ runtime: input.runtime ?? SUBGRAPHS_RUNTIME,
1698
+ schemaHash: input.schemaHash,
1699
+ startBlock: input.startBlock ?? null
1700
+ };
1701
+ return JSON.stringify(fields);
1702
+ }
1703
+ function computePin(input) {
1704
+ return sha256(pinPreimage(input));
1705
+ }
1706
+
1707
+ // src/runtime/realm.ts
1708
+ class NondeterminismError extends Error {
1709
+ name = "NondeterminismError";
1710
+ }
1711
+
1712
+ class HandlerReadError extends Error {
1713
+ name = "HandlerReadError";
1714
+ }
1715
+ function abortsBlock(err) {
1716
+ return err instanceof NondeterminismError || err instanceof HandlerReadError;
1717
+ }
1718
+ var HOST_GLOBALS = [
1719
+ "ArrayBuffer",
1720
+ "DataView",
1721
+ "Int8Array",
1722
+ "Uint8Array",
1723
+ "Uint8ClampedArray",
1724
+ "Int16Array",
1725
+ "Uint16Array",
1726
+ "Int32Array",
1727
+ "Uint32Array",
1728
+ "Float16Array",
1729
+ "Float32Array",
1730
+ "Float64Array",
1731
+ "BigInt64Array",
1732
+ "BigUint64Array",
1733
+ "TextEncoder",
1734
+ "TextDecoder",
1735
+ "atob",
1736
+ "btoa"
1737
+ ];
1738
+ function forbid(name, reason) {
1739
+ return () => {
1740
+ throw new NondeterminismError(`${name}: ${reason}`);
1741
+ };
1742
+ }
1743
+ function createRealm() {
1744
+ const host = globalThis;
1745
+ const sandbox = Object.create(null);
1746
+ for (const name of HOST_GLOBALS) {
1747
+ if (host[name] !== undefined)
1748
+ sandbox[name] = host[name];
1749
+ }
1750
+ const context = vm.createContext(sandbox, {
1751
+ name: "subgraph-handler",
1752
+ codeGeneration: { strings: false, wasm: false }
1753
+ });
1754
+ const g = vm.runInContext("globalThis", context);
1755
+ const allowed = new Set(ALLOWED_GLOBALS);
1756
+ for (const name of Object.getOwnPropertyNames(g)) {
1757
+ if (!allowed.has(name))
1758
+ delete g[name];
1759
+ }
1760
+ for (const [name, reason] of Object.entries(FORBIDDEN_GLOBALS)) {
1761
+ Object.defineProperty(g, name, { get: forbid(name, reason) });
1762
+ }
1763
+ const realmMath = g.Math;
1764
+ const safeMath = {};
1765
+ const allowedMath = new Set(ALLOWED_MATH);
1766
+ for (const key of Object.getOwnPropertyNames(realmMath)) {
1767
+ if (allowedMath.has(key))
1768
+ safeMath[key] = realmMath[key];
1769
+ else
1770
+ Object.defineProperty(safeMath, key, {
1771
+ get: forbid(`Math.${key}`, key === "random" ? "randomness differs per run" : "result is engine-approximated")
1772
+ });
1773
+ }
1774
+ Object.defineProperty(g, "Math", { value: Object.freeze(safeMath) });
1775
+ for (const ctor of ["String", "Number", "BigInt", "Array", "Object"]) {
1776
+ const proto = g[ctor].prototype;
1777
+ for (const method of LOCALE_METHODS) {
1778
+ if (Object.prototype.hasOwnProperty.call(proto, method)) {
1779
+ Object.defineProperty(proto, method, {
1780
+ value: forbid(method, "output depends on the host's locale data")
1781
+ });
1782
+ }
1783
+ }
1784
+ }
1785
+ Object.preventExtensions(g);
1786
+ return context;
1787
+ }
1788
+ function checkRowValues(where, values) {
1789
+ for (const [col, value] of Object.entries(values)) {
1790
+ checkValue(value, `${where}.${col}`);
1791
+ }
1792
+ }
1793
+ function checkValue(value, path) {
1794
+ switch (typeof value) {
1795
+ case "number":
1796
+ if (!Number.isSafeInteger(value)) {
1797
+ throw new TypeError(`${path}: ${value} is not a safe integer; verifiable subgraphs store bigint or integers only`);
1798
+ }
1799
+ return;
1800
+ case "function":
1801
+ case "symbol":
1802
+ throw new TypeError(`${path}: a ${typeof value} is not a row value`);
1803
+ case "object":
1804
+ if (value === null || ArrayBuffer.isView(value))
1805
+ return;
1806
+ if (Array.isArray(value)) {
1807
+ value.forEach((v, i) => checkValue(v, `${path}[${i}]`));
1808
+ return;
1809
+ }
1810
+ for (const [k, v] of Object.entries(value))
1811
+ checkValue(v, `${path}.${k}`);
1812
+ return;
1813
+ default:
1814
+ return;
1815
+ }
1816
+ }
1817
+ function deterministicContext(ctx, schema) {
1818
+ let pendingReads = 0;
1819
+ const knownTable = (table) => {
1820
+ if (!Object.prototype.hasOwnProperty.call(schema, table)) {
1821
+ throw new Error(`Table "${table}" not found in subgraph schema`);
1822
+ }
1823
+ };
1824
+ const guard = (op) => {
1825
+ if (pendingReads > 0) {
1826
+ throw new NondeterminismError(`ctx.${op} while a ctx read is pending: concurrent ctx calls order writes by database latency; await each read before the next call`);
1827
+ }
1828
+ };
1829
+ const read = async (op, run) => {
1830
+ guard(op);
1831
+ pendingReads++;
1832
+ try {
1833
+ return await run();
1834
+ } catch (err) {
1835
+ if (err instanceof NondeterminismError)
1836
+ throw err;
1837
+ throw new HandlerReadError(`ctx.${op} failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1838
+ } finally {
1839
+ pendingReads--;
1840
+ }
1841
+ };
1842
+ return {
1843
+ get block() {
1844
+ return ctx.block;
1845
+ },
1846
+ get tx() {
1847
+ return ctx.tx;
1848
+ },
1849
+ get client() {
1850
+ throw new NondeterminismError("ctx.client: contract reads need re-execution proofs");
1851
+ },
1852
+ insert(table, row) {
1853
+ guard("insert");
1854
+ checkRowValues(`insert ${table}`, row);
1855
+ ctx.insert(table, row);
1856
+ },
1857
+ update(table, where, set) {
1858
+ guard("update");
1859
+ checkRowValues(`update ${table}`, where);
1860
+ checkRowValues(`update ${table}`, set);
1861
+ ctx.update(table, where, set);
1862
+ },
1863
+ upsert(table, key, row) {
1864
+ guard("upsert");
1865
+ checkRowValues(`upsert ${table}`, key);
1866
+ checkRowValues(`upsert ${table}`, row);
1867
+ ctx.upsert(table, key, row);
1868
+ },
1869
+ delete(table, where) {
1870
+ guard("delete");
1871
+ ctx.delete(table, where);
1872
+ },
1873
+ increment(table, key, deltas) {
1874
+ guard("increment");
1875
+ checkRowValues(`increment ${table}`, key);
1876
+ checkRowValues(`increment ${table}`, deltas);
1877
+ ctx.increment(table, key, deltas);
1878
+ },
1879
+ findOne: (table, where) => {
1880
+ knownTable(table);
1881
+ return read("findOne", () => ctx.findOne(table, where));
1882
+ },
1883
+ findMany: (table, ...rest) => {
1884
+ knownTable(table);
1885
+ return read("findMany", () => ctx.findMany(table, ...rest));
1886
+ }
1887
+ };
1888
+ }
1889
+ var realmDefinitions = new WeakSet;
1890
+ async function loadDeterministicDefinition(handlerCode) {
1891
+ const { transform } = await import("esbuild");
1892
+ const { code } = await transform(handlerCode, {
1893
+ format: "cjs",
1894
+ loader: "js",
1895
+ logLevel: "silent"
1896
+ });
1897
+ const context = createRealm();
1898
+ const evaluate = vm.runInContext(`(function (module, exports) {
1899
+ ${code}
1900
+ })`, context, { filename: "handler.js" });
1901
+ const mod = { exports: {} };
1902
+ evaluate(mod, mod.exports);
1903
+ const def = mod.exports.default ?? mod.exports;
1904
+ const sources = structuredClone(def.sources);
1905
+ const schema = structuredClone(def.schema);
1906
+ const facades = new WeakMap;
1907
+ const handlers = {};
1908
+ for (const [name, handler] of Object.entries(def.handlers ?? {})) {
1909
+ handlers[name] = (event, ctx) => {
1910
+ let facade = facades.get(ctx);
1911
+ if (!facade) {
1912
+ facade = deterministicContext(ctx, schema);
1913
+ facades.set(ctx, facade);
1914
+ }
1915
+ return handler(event, facade);
1916
+ };
1917
+ }
1918
+ const loaded = { ...def, sources, schema, handlers };
1919
+ realmDefinitions.add(loaded);
1920
+ return loaded;
1921
+ }
1256
1922
 
1257
1923
  // src/runtime/source-matcher.ts
1258
1924
  var patternCache = new Map;
@@ -1574,8 +2240,58 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
1574
2240
  import { getTargetDb } from "@secondlayer/shared/db";
1575
2241
  import { logger as logger3 } from "@secondlayer/shared/logger";
1576
2242
  import { sql as sql2 } from "kysely";
2243
+ var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
2244
+ function jsonSafe2(value) {
2245
+ if (typeof value === "bigint")
2246
+ return value.toString();
2247
+ if (value === null || value === undefined)
2248
+ return value;
2249
+ if (Array.isArray(value))
2250
+ return value.map(jsonSafe2);
2251
+ if (typeof value === "object") {
2252
+ const out = {};
2253
+ for (const [k, v] of Object.entries(value)) {
2254
+ out[k] = jsonSafe2(v);
2255
+ }
2256
+ return out;
2257
+ }
2258
+ return value;
2259
+ }
2260
+ async function defaultRecordPrintViolation(input) {
2261
+ try {
2262
+ const db = getTargetDb();
2263
+ const sample = jsonSafe2(input.samplePayload);
2264
+ await db.insertInto("subgraph_violations").values({
2265
+ subgraph_name: input.subgraphName,
2266
+ source_name: input.sourceName,
2267
+ block_height: input.blockHeight,
2268
+ tx_id: input.txId,
2269
+ reason: input.reason,
2270
+ sample_payload: sample
2271
+ }).execute();
2272
+ await sql2`
2273
+ DELETE FROM subgraph_violations
2274
+ WHERE id IN (
2275
+ SELECT id FROM subgraph_violations
2276
+ WHERE subgraph_name = ${input.subgraphName}
2277
+ ORDER BY seen_at DESC
2278
+ OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
2279
+ )
2280
+ `.execute(db);
2281
+ } catch (err) {
2282
+ logger3.warn("Failed to record print violation", {
2283
+ subgraph: input.subgraphName,
2284
+ error: err instanceof Error ? err.message : String(err)
2285
+ });
2286
+ }
2287
+ }
2288
+ var recorder = defaultRecordPrintViolation;
2289
+ async function recordPrintViolation(input) {
2290
+ await recorder(input);
2291
+ }
1577
2292
 
1578
2293
  // src/runtime/runner.ts
2294
+ var DEFAULT_ERROR_THRESHOLD = 50;
1579
2295
  function decodeFunctionArgs(args) {
1580
2296
  let parsed = args;
1581
2297
  if (typeof parsed === "string") {
@@ -1645,7 +2361,7 @@ function buildContractCallInput(filter, tx) {
1645
2361
  return;
1646
2362
  try {
1647
2363
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1648
- input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV4(clean));
2364
+ input[toCamelCase3(arg.name)] = decodeArg(arg.type, deserializeCV4(clean));
1649
2365
  } catch {}
1650
2366
  });
1651
2367
  return input;
@@ -1858,9 +2574,144 @@ function buildEventPayload(filter, tx, event) {
1858
2574
  };
1859
2575
  }
1860
2576
  }
2577
+ async function runHandlers(subgraph, matched, ctx, opts) {
2578
+ let processed = 0;
2579
+ let errors = 0;
2580
+ let skipped = 0;
2581
+ let delivered = 0;
2582
+ const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
2583
+ const filterLookup = new Map;
2584
+ if (!Array.isArray(subgraph.sources)) {
2585
+ for (const [name, filter] of Object.entries(subgraph.sources)) {
2586
+ filterLookup.set(name, filter);
2587
+ }
2588
+ }
2589
+ const units = [];
2590
+ for (const { tx, events, sourceName } of matched) {
2591
+ if (events.length === 0) {
2592
+ units.push({ tx, sourceName, event: null });
2593
+ } else {
2594
+ for (const event of events)
2595
+ units.push({ tx, sourceName, event });
2596
+ }
2597
+ }
2598
+ const clockRank = (e) => e?.clock === "vm" ? 1 : 0;
2599
+ units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || clockRank(a.event) - clockRank(b.event) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
2600
+ for (const { tx, event, sourceName } of units) {
2601
+ if (errors >= threshold) {
2602
+ logger4.error("Subgraph error threshold reached, skipping remaining events", {
2603
+ subgraph: subgraph.name,
2604
+ errors,
2605
+ threshold
2606
+ });
2607
+ return {
2608
+ processed,
2609
+ errors,
2610
+ delivered,
2611
+ ...skipped > 0 ? { skipped } : {}
2612
+ };
2613
+ }
2614
+ const filter = filterLookup.get(sourceName);
2615
+ const handler = subgraph.handlers[sourceName] ?? null;
2616
+ if (!handler) {
2617
+ logger4.warn("No handler found for source", {
2618
+ subgraph: subgraph.name,
2619
+ sourceName,
2620
+ txId: tx.tx_id
2621
+ });
2622
+ continue;
2623
+ }
2624
+ ctx.setTx({
2625
+ txId: tx.tx_id,
2626
+ sender: tx.sender,
2627
+ type: tx.type,
2628
+ status: tx.status,
2629
+ contractId: tx.contract_id ?? null,
2630
+ functionName: tx.function_name ?? null
2631
+ });
2632
+ const checkpoint = ctx.opsCheckpoint();
2633
+ try {
2634
+ let payload;
2635
+ if (event === null) {
2636
+ payload = filter ? buildEventPayload(filter, tx, null) : {
2637
+ tx: {
2638
+ txId: tx.tx_id,
2639
+ sender: tx.sender,
2640
+ type: tx.type,
2641
+ status: tx.status,
2642
+ contractId: tx.contract_id,
2643
+ functionName: tx.function_name
2644
+ }
2645
+ };
2646
+ } else if (filter) {
2647
+ payload = buildEventPayload(filter, tx, event);
2648
+ } else {
2649
+ const decoded = decodeEventData(event.data);
2650
+ payload = {
2651
+ ...decoded,
2652
+ _eventId: event.id,
2653
+ _eventType: event.type,
2654
+ _eventIndex: event.event_index,
2655
+ tx: {
2656
+ txId: tx.tx_id,
2657
+ sender: tx.sender,
2658
+ type: tx.type,
2659
+ status: tx.status,
2660
+ contractId: tx.contract_id,
2661
+ functionName: tx.function_name
2662
+ }
2663
+ };
2664
+ }
2665
+ if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
2666
+ continue;
2667
+ }
2668
+ delivered++;
2669
+ if (event !== null && filter?.type === "print_event" && filter.prints) {
2670
+ const printPayload = payload;
2671
+ const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
2672
+ if (!verdict.ok) {
2673
+ skipped++;
2674
+ logger4.warn("Print payload does not match declared prints schema", {
2675
+ subgraph: subgraph.name,
2676
+ sourceName,
2677
+ topic: printPayload.topic,
2678
+ txId: tx.tx_id,
2679
+ reason: verdict.reason
2680
+ });
2681
+ recordPrintViolation({
2682
+ subgraphName: subgraph.name,
2683
+ sourceName,
2684
+ blockHeight: ctx.block.height,
2685
+ txId: tx.tx_id,
2686
+ reason: verdict.reason,
2687
+ samplePayload: {
2688
+ topic: printPayload.topic ?? "",
2689
+ data: printPayload.data ?? {}
2690
+ }
2691
+ });
2692
+ continue;
2693
+ }
2694
+ }
2695
+ await handler(payload, ctx);
2696
+ processed++;
2697
+ } catch (err) {
2698
+ if (abortsBlock(err))
2699
+ throw err;
2700
+ ctx.rollbackTo(checkpoint);
2701
+ errors++;
2702
+ logger4.error("Subgraph handler error", {
2703
+ subgraph: subgraph.name,
2704
+ sourceName,
2705
+ txId: tx.tx_id,
2706
+ ...event !== null ? { eventId: event.id, eventType: event.type } : {},
2707
+ error: getErrorMessage(err)
2708
+ });
2709
+ }
2710
+ }
2711
+ return { processed, errors, delivered, ...skipped > 0 ? { skipped } : {} };
2712
+ }
1861
2713
 
1862
2714
  // src/testing/harness.ts
1863
- var TEST_SCHEMA_NAME = "subgraph_test";
1864
2715
  function defaultBlock(overrides = {}) {
1865
2716
  return {
1866
2717
  height: 1,
@@ -1881,76 +2732,8 @@ function defaultTx(overrides = {}) {
1881
2732
  ...overrides
1882
2733
  };
1883
2734
  }
1884
-
1885
- class InMemorySubgraphContext extends SubgraphContext {
1886
- store = new Map;
1887
- constructor(schema, block, tx) {
1888
- super(undefined, TEST_SCHEMA_NAME, schema, block, tx, false);
1889
- }
1890
- async readRows(table, where, limit) {
1891
- const rows = (this.store.get(table) ?? []).filter((row) => Object.entries(where).every(([k, v]) => sameValue(row[k], v)));
1892
- return limit === undefined ? rows : rows.slice(0, limit);
1893
- }
1894
- async rowsOf(table) {
1895
- return this.overlayMany(table, {}, await this.readRows(table, {}));
1896
- }
1897
- setReads(reads) {
1898
- this._client = {
1899
- contract(contractId, abi) {
1900
- const camelToKebab = new Map;
1901
- for (const fn of normalizeAbi2(abi).functions) {
1902
- camelToKebab.set(toCamelCase3(fn.name), fn.name);
1903
- }
1904
- const read = new Proxy({}, {
1905
- get(_target, prop) {
1906
- const fnName = camelToKebab.get(prop) ?? prop;
1907
- const key = `${contractId}.${fnName}`;
1908
- return async () => {
1909
- if (!(key in reads)) {
1910
- throw new Error(`No stubbed chain read for "${key}" — pass it via createTestContext(schema, { reads: { "${key}": … } }).`);
1911
- }
1912
- return reads[key];
1913
- };
1914
- }
1915
- });
1916
- return { read };
1917
- }
1918
- };
1919
- }
1920
- async commitOps() {
1921
- const tables = new Set;
1922
- for (const op of this.ops)
1923
- tables.add(op.table);
1924
- for (const table of tables) {
1925
- this.store.set(table, await this.rowsOf(table));
1926
- }
1927
- this.ops.length = 0;
1928
- }
1929
- insertsSince(checkpoint) {
1930
- const out = [];
1931
- for (const op of this.ops.slice(Math.max(0, checkpoint))) {
1932
- if (op.kind !== "insert")
1933
- continue;
1934
- const row = { ...op.data };
1935
- out.push({
1936
- table: op.table,
1937
- keys: Object.keys(row).filter((k) => !k.startsWith("_")),
1938
- row
1939
- });
1940
- }
1941
- return out;
1942
- }
1943
- }
1944
- function sameValue(a, b) {
1945
- if (a === b)
1946
- return true;
1947
- if ((typeof a === "bigint" || typeof a === "number") && (typeof b === "bigint" || typeof b === "number")) {
1948
- return BigInt(a) === BigInt(b);
1949
- }
1950
- return String(a) === String(b);
1951
- }
1952
2735
  function createTestContext(schema, options = {}) {
1953
- const impl = new InMemorySubgraphContext(schema, defaultBlock(options.block), defaultTx(options.tx));
2736
+ const impl = new MemorySubgraphContext(new MemoryStore, schema, defaultBlock(options.block), defaultTx(options.tx));
1954
2737
  impl.setReads(options.reads ?? {});
1955
2738
  const ctx = impl;
1956
2739
  ctx.rows = (table) => impl.rowsOf(table);
@@ -2065,17 +2848,17 @@ function toContractCallPayload(row) {
2065
2848
  }
2066
2849
  };
2067
2850
  }
2068
- function jsonSafe2(value) {
2851
+ function jsonSafe3(value) {
2069
2852
  if (typeof value === "bigint")
2070
2853
  return value.toString();
2071
2854
  if (value === null || value === undefined)
2072
2855
  return value;
2073
2856
  if (Array.isArray(value))
2074
- return value.map(jsonSafe2);
2857
+ return value.map(jsonSafe3);
2075
2858
  if (typeof value === "object") {
2076
2859
  const out = {};
2077
2860
  for (const [k, v] of Object.entries(value)) {
2078
- out[k] = jsonSafe2(v);
2861
+ out[k] = jsonSafe3(v);
2079
2862
  }
2080
2863
  return out;
2081
2864
  }
@@ -2186,7 +2969,7 @@ async function runSubgraphTest(input) {
2186
2969
  tables.push({
2187
2970
  name: table,
2188
2971
  rows: tableRows.length,
2189
- ...sample ? { sampleRow: jsonSafe2(sample) } : {}
2972
+ ...sample ? { sampleRow: jsonSafe3(sample) } : {}
2190
2973
  });
2191
2974
  }
2192
2975
  const fetched = Object.values(events).reduce((n, r) => n + r.length, 0);
@@ -2215,7 +2998,7 @@ async function runSubgraphTest(input) {
2215
2998
  matched,
2216
2999
  written: 0,
2217
3000
  tables,
2218
- firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
3001
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2219
3002
  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.`,
2220
3003
  ...traceFields
2221
3004
  };
@@ -2229,7 +3012,7 @@ async function runSubgraphTest(input) {
2229
3012
  matched,
2230
3013
  written,
2231
3014
  tables,
2232
- firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
3015
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2233
3016
  hint: `At least one matched event wrote 0 rows.${keyPart}`,
2234
3017
  ...traceFields
2235
3018
  };
@@ -2239,7 +3022,7 @@ async function runSubgraphTest(input) {
2239
3022
  matched,
2240
3023
  written,
2241
3024
  tables,
2242
- firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
3025
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2243
3026
  ...traceFields
2244
3027
  };
2245
3028
  }
@@ -2285,5 +3068,5 @@ export {
2285
3068
  toHandlerPayload
2286
3069
  };
2287
3070
 
2288
- //# debugId=73BD3E2068CDEA0464756E2164756E21
3071
+ //# debugId=13B7A8082FE1831F64756E2164756E21
2289
3072
  //# sourceMappingURL=index.js.map