@secondlayer/subgraphs 6.4.0 → 6.6.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";
@@ -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,74 @@ 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
+ }
1256
1435
 
1257
1436
  // src/runtime/realm.ts
1258
1437
  import vm from "node:vm";
@@ -1262,7 +1441,7 @@ import { createHash as createHash2 } from "node:crypto";
1262
1441
  // package.json
1263
1442
  var package_default = {
1264
1443
  name: "@secondlayer/subgraphs",
1265
- version: "6.4.0",
1444
+ version: "6.6.0",
1266
1445
  description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
1267
1446
  license: "MIT",
1268
1447
  repository: {
@@ -1306,6 +1485,10 @@ var package_default = {
1306
1485
  "./testing": {
1307
1486
  types: "./dist/testing/index.d.ts",
1308
1487
  import: "./dist/testing/index.js"
1488
+ },
1489
+ "./verify": {
1490
+ types: "./dist/verify/index.d.ts",
1491
+ import: "./dist/verify/index.js"
1309
1492
  }
1310
1493
  },
1311
1494
  files: ["dist"],
@@ -1317,14 +1500,16 @@ var package_default = {
1317
1500
  prepublishOnly: "bun run build"
1318
1501
  },
1319
1502
  dependencies: {
1320
- "@secondlayer/shared": "^11.22.0",
1321
- "@secondlayer/stacks": "^6.1.0",
1503
+ "@secondlayer/shared": "^11.24.0",
1504
+ "@secondlayer/stacks": "^6.2.0",
1505
+ "@secondlayer/verify": "^0.3.0",
1322
1506
  esbuild: "^0.28.2",
1323
1507
  kysely: "0.28.17",
1324
1508
  zod: "^4.3.6"
1325
1509
  },
1326
1510
  devDependencies: {
1327
1511
  "@types/bun": "latest",
1512
+ bunup: "0.16.32",
1328
1513
  typescript: "^5"
1329
1514
  },
1330
1515
  publishConfig: {
@@ -1454,7 +1639,6 @@ function formatFinding(f) {
1454
1639
  var STATE_SOURCE_TYPES = new Set([
1455
1640
  "var_set",
1456
1641
  "map_set",
1457
- "map_insert",
1458
1642
  "map_delete"
1459
1643
  ]);
1460
1644
  var LEVEL_RANK = { state: 0, events: 1, none: 2 };
@@ -1471,6 +1655,9 @@ function deriveVerification(def, findings = []) {
1471
1655
  if (filter.trait) {
1472
1656
  raise("none");
1473
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`);
1474
1661
  } else if (!STATE_SOURCE_TYPES.has(filter.type)) {
1475
1662
  raise("events");
1476
1663
  reasons.push(`${filter.type} source "${name}" needs event proofs`);
@@ -1488,14 +1675,12 @@ function deriveVerification(def, findings = []) {
1488
1675
  if (findings.length > MAX_LISTED_FINDINGS) {
1489
1676
  reasons.push(`…and ${findings.length - MAX_LISTED_FINDINGS} more handler findings`);
1490
1677
  }
1491
- const writesMapInsert = Object.values(def.sources ?? {}).some((s) => s.type === "map_insert");
1492
1678
  return {
1493
1679
  level,
1494
1680
  reasons,
1495
1681
  unproven: reasons.length === 0 ? [
1496
1682
  "tx attribution of writes: event.tx, _tx_id (needs event proofs)",
1497
- "intermediate writes within a block (needs event proofs)",
1498
- ...writesMapInsert ? ["map_insert vs map_set (needs event proofs)"] : []
1683
+ "intermediate writes within a block (needs event proofs)"
1499
1684
  ] : []
1500
1685
  };
1501
1686
  }
@@ -1505,15 +1690,18 @@ function isDeterminismViolation(v) {
1505
1690
  function sha256(input) {
1506
1691
  return createHash2("sha256").update(input).digest("hex");
1507
1692
  }
1508
- function computePin(input) {
1509
- const canonical = JSON.stringify({
1693
+ function pinPreimage(input) {
1694
+ const fields = {
1510
1695
  handlerHash: sha256(input.handlerCode),
1511
1696
  network: input.network,
1512
1697
  runtime: input.runtime ?? SUBGRAPHS_RUNTIME,
1513
1698
  schemaHash: input.schemaHash,
1514
1699
  startBlock: input.startBlock ?? null
1515
- });
1516
- return sha256(canonical);
1700
+ };
1701
+ return JSON.stringify(fields);
1702
+ }
1703
+ function computePin(input) {
1704
+ return sha256(pinPreimage(input));
1517
1705
  }
1518
1706
 
1519
1707
  // src/runtime/realm.ts
@@ -1524,6 +1712,9 @@ class NondeterminismError extends Error {
1524
1712
  class HandlerReadError extends Error {
1525
1713
  name = "HandlerReadError";
1526
1714
  }
1715
+ function abortsBlock(err) {
1716
+ return err instanceof NondeterminismError || err instanceof HandlerReadError;
1717
+ }
1527
1718
  var HOST_GLOBALS = [
1528
1719
  "ArrayBuffer",
1529
1720
  "DataView",
@@ -1695,6 +1886,7 @@ function deterministicContext(ctx, schema) {
1695
1886
  }
1696
1887
  };
1697
1888
  }
1889
+ var realmDefinitions = new WeakSet;
1698
1890
  async function loadDeterministicDefinition(handlerCode) {
1699
1891
  const { transform } = await import("esbuild");
1700
1892
  const { code } = await transform(handlerCode, {
@@ -1723,7 +1915,9 @@ ${code}
1723
1915
  return handler(event, facade);
1724
1916
  };
1725
1917
  }
1726
- return { ...def, sources, schema, handlers };
1918
+ const loaded = { ...def, sources, schema, handlers };
1919
+ realmDefinitions.add(loaded);
1920
+ return loaded;
1727
1921
  }
1728
1922
 
1729
1923
  // src/runtime/source-matcher.ts
@@ -2046,8 +2240,58 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
2046
2240
  import { getTargetDb } from "@secondlayer/shared/db";
2047
2241
  import { logger as logger3 } from "@secondlayer/shared/logger";
2048
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
+ }
2049
2292
 
2050
2293
  // src/runtime/runner.ts
2294
+ var DEFAULT_ERROR_THRESHOLD = 50;
2051
2295
  function decodeFunctionArgs(args) {
2052
2296
  let parsed = args;
2053
2297
  if (typeof parsed === "string") {
@@ -2117,7 +2361,7 @@ function buildContractCallInput(filter, tx) {
2117
2361
  return;
2118
2362
  try {
2119
2363
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2120
- input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV4(clean));
2364
+ input[toCamelCase3(arg.name)] = decodeArg(arg.type, deserializeCV4(clean));
2121
2365
  } catch {}
2122
2366
  });
2123
2367
  return input;
@@ -2330,9 +2574,144 @@ function buildEventPayload(filter, tx, event) {
2330
2574
  };
2331
2575
  }
2332
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
+ }
2333
2713
 
2334
2714
  // src/testing/harness.ts
2335
- var TEST_SCHEMA_NAME = "subgraph_test";
2336
2715
  function defaultBlock(overrides = {}) {
2337
2716
  return {
2338
2717
  height: 1,
@@ -2353,76 +2732,8 @@ function defaultTx(overrides = {}) {
2353
2732
  ...overrides
2354
2733
  };
2355
2734
  }
2356
-
2357
- class InMemorySubgraphContext extends SubgraphContext {
2358
- store = new Map;
2359
- constructor(schema, block, tx) {
2360
- super(undefined, TEST_SCHEMA_NAME, schema, block, tx, false);
2361
- }
2362
- async readRows(table, where, limit) {
2363
- const rows = (this.store.get(table) ?? []).filter((row) => Object.entries(where).every(([k, v]) => sameValue(row[k], v)));
2364
- return limit === undefined ? rows : rows.slice(0, limit);
2365
- }
2366
- async rowsOf(table) {
2367
- return this.overlayMany(table, {}, await this.readRows(table, {}));
2368
- }
2369
- setReads(reads) {
2370
- this._client = {
2371
- contract(contractId, abi) {
2372
- const camelToKebab = new Map;
2373
- for (const fn of normalizeAbi2(abi).functions) {
2374
- camelToKebab.set(toCamelCase3(fn.name), fn.name);
2375
- }
2376
- const read = new Proxy({}, {
2377
- get(_target, prop) {
2378
- const fnName = camelToKebab.get(prop) ?? prop;
2379
- const key = `${contractId}.${fnName}`;
2380
- return async () => {
2381
- if (!(key in reads)) {
2382
- throw new Error(`No stubbed chain read for "${key}" — pass it via createTestContext(schema, { reads: { "${key}": … } }).`);
2383
- }
2384
- return reads[key];
2385
- };
2386
- }
2387
- });
2388
- return { read };
2389
- }
2390
- };
2391
- }
2392
- async commitOps() {
2393
- const tables = new Set;
2394
- for (const op of this.ops)
2395
- tables.add(op.table);
2396
- for (const table of tables) {
2397
- this.store.set(table, await this.rowsOf(table));
2398
- }
2399
- this.ops.length = 0;
2400
- }
2401
- insertsSince(checkpoint) {
2402
- const out = [];
2403
- for (const op of this.ops.slice(Math.max(0, checkpoint))) {
2404
- if (op.kind !== "insert")
2405
- continue;
2406
- const row = { ...op.data };
2407
- out.push({
2408
- table: op.table,
2409
- keys: Object.keys(row).filter((k) => !k.startsWith("_")),
2410
- row
2411
- });
2412
- }
2413
- return out;
2414
- }
2415
- }
2416
- function sameValue(a, b) {
2417
- if (a === b)
2418
- return true;
2419
- if ((typeof a === "bigint" || typeof a === "number") && (typeof b === "bigint" || typeof b === "number")) {
2420
- return BigInt(a) === BigInt(b);
2421
- }
2422
- return String(a) === String(b);
2423
- }
2424
2735
  function createTestContext(schema, options = {}) {
2425
- 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));
2426
2737
  impl.setReads(options.reads ?? {});
2427
2738
  const ctx = impl;
2428
2739
  ctx.rows = (table) => impl.rowsOf(table);
@@ -2537,17 +2848,17 @@ function toContractCallPayload(row) {
2537
2848
  }
2538
2849
  };
2539
2850
  }
2540
- function jsonSafe2(value) {
2851
+ function jsonSafe3(value) {
2541
2852
  if (typeof value === "bigint")
2542
2853
  return value.toString();
2543
2854
  if (value === null || value === undefined)
2544
2855
  return value;
2545
2856
  if (Array.isArray(value))
2546
- return value.map(jsonSafe2);
2857
+ return value.map(jsonSafe3);
2547
2858
  if (typeof value === "object") {
2548
2859
  const out = {};
2549
2860
  for (const [k, v] of Object.entries(value)) {
2550
- out[k] = jsonSafe2(v);
2861
+ out[k] = jsonSafe3(v);
2551
2862
  }
2552
2863
  return out;
2553
2864
  }
@@ -2658,7 +2969,7 @@ async function runSubgraphTest(input) {
2658
2969
  tables.push({
2659
2970
  name: table,
2660
2971
  rows: tableRows.length,
2661
- ...sample ? { sampleRow: jsonSafe2(sample) } : {}
2972
+ ...sample ? { sampleRow: jsonSafe3(sample) } : {}
2662
2973
  });
2663
2974
  }
2664
2975
  const fetched = Object.values(events).reduce((n, r) => n + r.length, 0);
@@ -2687,7 +2998,7 @@ async function runSubgraphTest(input) {
2687
2998
  matched,
2688
2999
  written: 0,
2689
3000
  tables,
2690
- firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
3001
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2691
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.`,
2692
3003
  ...traceFields
2693
3004
  };
@@ -2701,7 +3012,7 @@ async function runSubgraphTest(input) {
2701
3012
  matched,
2702
3013
  written,
2703
3014
  tables,
2704
- firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
3015
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2705
3016
  hint: `At least one matched event wrote 0 rows.${keyPart}`,
2706
3017
  ...traceFields
2707
3018
  };
@@ -2711,7 +3022,7 @@ async function runSubgraphTest(input) {
2711
3022
  matched,
2712
3023
  written,
2713
3024
  tables,
2714
- firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
3025
+ firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
2715
3026
  ...traceFields
2716
3027
  };
2717
3028
  }
@@ -2757,5 +3068,5 @@ export {
2757
3068
  toHandlerPayload
2758
3069
  };
2759
3070
 
2760
- //# debugId=124EC2B77481A62F64756E2164756E21
3071
+ //# debugId=C0C7992E00C29F7064756E2164756E21
2761
3072
  //# sourceMappingURL=index.js.map