@secondlayer/subgraphs 7.3.0 → 7.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.
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@secondlayer/subgraphs",
6
- version: "7.3.0",
6
+ version: "7.5.0",
7
7
  description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
8
8
  license: "MIT",
9
9
  repository: {
@@ -63,7 +63,7 @@ var package_default = {
63
63
  prepublishOnly: "bun run build"
64
64
  },
65
65
  dependencies: {
66
- "@secondlayer/shared": "^13.2.0",
66
+ "@secondlayer/shared": "^13.4.0",
67
67
  "@secondlayer/stacks": "^6.4.0",
68
68
  "@secondlayer/verify": "^0.6.0",
69
69
  esbuild: "^0.28.2",
@@ -279,5 +279,5 @@ export {
279
279
  pinPreimage
280
280
  };
281
281
 
282
- //# debugId=B00215F36B8986F364756E2164756E21
282
+ //# debugId=77C06EADF44962D564756E2164756E21
283
283
  //# sourceMappingURL=verification.js.map
@@ -5,6 +5,6 @@
5
5
  "/**\n * Verifiable subgraphs: the determinism contract, the derived level and the\n * pin. Pure (node:crypto only) so the bundler's deploy scan, the API and the\n * runtime realm all read the same rules.\n *\n * A subgraph is never configured as verifiable. Its level is derived from\n * its sources plus a scan of the bundled handler, and only a subgraph whose\n * every input is provable today (`state`: named state writes) has the contract\n * enforced. Everything else gets the same findings as advice.\n */\nimport { createHash } from \"node:crypto\";\nimport type { SubgraphVerification } from \"@secondlayer/shared/schemas/subgraphs\";\nimport pkg from \"../package.json\" with { type: \"json\" };\nimport type { SubgraphDefinition, SubgraphFilter } from \"./types.ts\";\n\nexport type { SubgraphVerification };\n\n/** Runtime identity baked into every pin: matcher, decode and flush semantics. */\nexport const SUBGRAPHS_RUNTIME: string = `@secondlayer/subgraphs@${pkg.version}`;\n\n// ── Determinism contract ────────────────────────────────────────────────\n\n/**\n * Globals a handler may not touch, with the reason shown to the developer.\n * The deploy scan reports them; the realm replaces each with a getter that\n * throws `NondeterminismError`.\n */\nexport const FORBIDDEN_GLOBALS: Readonly<Record<string, string>> = {\n\tDate: \"wall-clock time differs per run\",\n\tperformance: \"timers differ per run\",\n\tprocess: \"host environment differs per run\",\n\tfetch: \"network reads are not chain inputs\",\n\tcrypto: \"randomness differs per run\",\n\tsetTimeout: \"scheduling order differs per run\",\n\tsetInterval: \"scheduling order differs per run\",\n\tsetImmediate: \"scheduling order differs per run\",\n\tclearTimeout: \"scheduling order differs per run\",\n\tclearInterval: \"scheduling order differs per run\",\n\tqueueMicrotask: \"scheduling order differs per run\",\n\tWeakRef: \"garbage collection timing differs per run\",\n\tFinalizationRegistry: \"garbage collection timing differs per run\",\n\tAtomics: \"shared memory is not single-threaded\",\n\tSharedArrayBuffer: \"shared memory is not single-threaded\",\n\tIntl: \"locale and time zone data differ per host\",\n\tTemporal: \"locale and time zone data differ per host\",\n\teval: \"generated code hides inputs from the scan\",\n\tWebAssembly: \"compiled code hides inputs from the scan\",\n\tShadowRealm: \"a nested realm escapes the allow-list\",\n\trequire: \"imports must be bundled into the handler\",\n};\n\n/**\n * Globals a handler may use: ECMAScript built-ins whose results are fully\n * specified, plus deterministic host encoders. `Math` is narrowed separately\n * ({@link ALLOWED_MATH}).\n */\nexport const ALLOWED_GLOBALS: readonly string[] = [\n\t\"undefined\",\n\t\"NaN\",\n\t\"Infinity\",\n\t\"globalThis\",\n\t\"isNaN\",\n\t\"isFinite\",\n\t\"parseInt\",\n\t\"parseFloat\",\n\t\"decodeURI\",\n\t\"decodeURIComponent\",\n\t\"encodeURI\",\n\t\"encodeURIComponent\",\n\t\"escape\",\n\t\"unescape\",\n\t\"Object\",\n\t\"Function\",\n\t\"Array\",\n\t\"Number\",\n\t\"Boolean\",\n\t\"String\",\n\t\"Symbol\",\n\t\"BigInt\",\n\t\"Math\",\n\t\"JSON\",\n\t\"Reflect\",\n\t\"Proxy\",\n\t\"Promise\",\n\t\"Map\",\n\t\"Set\",\n\t\"WeakMap\",\n\t\"WeakSet\",\n\t\"RegExp\",\n\t\"Iterator\",\n\t\"DisposableStack\",\n\t\"AsyncDisposableStack\",\n\t\"Error\",\n\t\"AggregateError\",\n\t\"SuppressedError\",\n\t\"EvalError\",\n\t\"RangeError\",\n\t\"ReferenceError\",\n\t\"SyntaxError\",\n\t\"TypeError\",\n\t\"URIError\",\n\t\"ArrayBuffer\",\n\t\"DataView\",\n\t\"Int8Array\",\n\t\"Uint8Array\",\n\t\"Uint8ClampedArray\",\n\t\"Int16Array\",\n\t\"Uint16Array\",\n\t\"Int32Array\",\n\t\"Uint32Array\",\n\t\"Float16Array\",\n\t\"Float32Array\",\n\t\"Float64Array\",\n\t\"BigInt64Array\",\n\t\"BigUint64Array\",\n\t\"TextEncoder\",\n\t\"TextDecoder\",\n\t\"atob\",\n\t\"btoa\",\n\t\"console\",\n];\n\n/** `Math` members whose results are exact on every engine. The rest\n * (`sin`, `pow`, `log`, …) are implementation-approximated. */\nexport const ALLOWED_MATH: readonly string[] = [\n\t\"abs\",\n\t\"min\",\n\t\"max\",\n\t\"floor\",\n\t\"ceil\",\n\t\"trunc\",\n\t\"sign\",\n\t\"round\",\n\t\"E\",\n\t\"LN10\",\n\t\"LN2\",\n\t\"LOG10E\",\n\t\"LOG2E\",\n\t\"PI\",\n\t\"SQRT1_2\",\n\t\"SQRT2\",\n];\n\n/** Methods whose output depends on the host's locale data. */\nexport const LOCALE_METHODS: readonly string[] = [\n\t\"toLocaleString\",\n\t\"toLocaleDateString\",\n\t\"toLocaleTimeString\",\n\t\"toLocaleUpperCase\",\n\t\"toLocaleLowerCase\",\n\t\"localeCompare\",\n];\n\n/**\n * One thing the deploy scan found in a bundled handler.\n *\n * - `nondeterministic`: breaks the determinism contract. Fails the deploy of\n * a state subgraph; advice for everything else.\n * - `needs-events`: deterministic, but reads something only re-execution proofs cover\n * (`ctx.client`). Raises the level instead of failing.\n */\nexport interface HandlerFinding {\n\tkind: \"nondeterministic\" | \"needs-events\";\n\t/** What was used, e.g. `Date`, `Math.random`, `import()`, `ctx.client`. */\n\tname: string;\n\treason: string;\n\t/** Original source file when a sourcemap was available, else the bundle. */\n\tfile: string;\n\t/** 1-based. */\n\tline: number;\n\t/** 1-based. */\n\tcolumn: number;\n}\n\n/** Formats a handler finding as `file:line:column name: reason`. */\nexport function formatFinding(f: HandlerFinding): string {\n\treturn `${f.file}:${f.line}:${f.column} ${f.name}: ${f.reason}`;\n}\n\n// ── Derived level ───────────────────────────────────────────────────────\n\n/**\n * Sources whose every field is a named state write: provable per block from\n * the header-backed witness plus `state_writes`. Not `map_insert`: storage\n * holds `(some v)` for an insert and a set alike, so telling them apart takes\n * re-execution (event proofs).\n */\nconst STATE_SOURCE_TYPES: ReadonlySet<string> = new Set([\n\t\"var_set\",\n\t\"map_set\",\n\t\"map_delete\",\n]);\n\nconst LEVEL_RANK = { state: 0, events: 1, none: 2 } as const;\n\n/** Handler findings listed in `reasons`; the rest are counted. */\nconst MAX_LISTED_FINDINGS = 10;\n\n/**\n * Derive how far a subgraph's rows can be checked. Level = the highest any\n * source or handler read needs. `reasons` lists everything standing between\n * the subgraph and checkable rows; a `state` subgraph with any reason is\n * refused at deploy, so every stored `state` row has none. Never configured,\n * never a flag.\n */\nexport function deriveVerification(\n\tdef: Pick<SubgraphDefinition, \"sources\" | \"backfillMode\">,\n\tfindings: readonly HandlerFinding[] = [],\n): SubgraphVerification {\n\tlet level: keyof typeof LEVEL_RANK = \"state\";\n\tconst raise = (to: keyof typeof LEVEL_RANK) => {\n\t\tif (LEVEL_RANK[to] > LEVEL_RANK[level]) level = to;\n\t};\n\tconst reasons: string[] = [];\n\n\tfor (const [name, source] of Object.entries(def.sources ?? {})) {\n\t\tconst filter = source as SubgraphFilter & { trait?: string };\n\t\tif (filter.trait) {\n\t\t\traise(\"none\");\n\t\t\treasons.push(\n\t\t\t\t`source \"${name}\": trait scope needs a proven contract registry`,\n\t\t\t);\n\t\t} else if (filter.type === \"map_insert\") {\n\t\t\traise(\"events\");\n\t\t\treasons.push(\n\t\t\t\t`map_insert source \"${name}\": map_insert vs map_set needs event proofs`,\n\t\t\t);\n\t\t} else if (!STATE_SOURCE_TYPES.has(filter.type)) {\n\t\t\traise(\"events\");\n\t\t\treasons.push(`${filter.type} source \"${name}\" needs event proofs`);\n\t\t}\n\t}\n\tif (def.backfillMode === \"concurrent\") {\n\t\traise(\"none\");\n\t\treasons.push(\"backfillMode concurrent: tip-first order is not chain order\");\n\t}\n\tif (findings.some((f) => f.kind === \"needs-events\")) raise(\"events\");\n\t// A large bundled library can trip dozens of findings; list a few.\n\tfor (const f of findings.slice(0, MAX_LISTED_FINDINGS)) {\n\t\treasons.push(\n\t\t\tf.kind === \"needs-events\"\n\t\t\t\t? `${formatFinding(f)} (needs event proofs)`\n\t\t\t\t: formatFinding(f),\n\t\t);\n\t}\n\tif (findings.length > MAX_LISTED_FINDINGS) {\n\t\treasons.push(\n\t\t\t`…and ${findings.length - MAX_LISTED_FINDINGS} more handler findings`,\n\t\t);\n\t}\n\n\treturn {\n\t\tlevel,\n\t\treasons,\n\t\tunproven:\n\t\t\treasons.length === 0\n\t\t\t\t? [\n\t\t\t\t\t\t\"tx attribution of writes: event.tx, _tx_id (needs event proofs)\",\n\t\t\t\t\t\t\"intermediate writes within a block (needs event proofs)\",\n\t\t\t\t\t]\n\t\t\t\t: [],\n\t};\n}\n\n/**\n * Tells whether a deploy must be refused for breaking determinism.\n *\n * It is refused when the sources are provable (`state`), so the determinism\n * contract is enforced, and the scan broke it.\n */\nexport function isDeterminismViolation(v: SubgraphVerification): boolean {\n\treturn v.level === \"state\" && v.reasons.length > 0;\n}\n\n// ── Pin ─────────────────────────────────────────────────────────────────\n\n/** What a pin is computed from: schema hash, handler code, network. */\nexport interface PinInput {\n\t/** Existing `schema_hash` (name + schema + sources). */\n\tschemaHash: string;\n\t/** Bundled ESM exactly as stored in `subgraphs.handler_code`. */\n\thandlerCode: string;\n\tstartBlock?: number | null;\n\tnetwork: string;\n\t/** Defaults to {@link SUBGRAPHS_RUNTIME}. */\n\truntime?: string;\n}\n\nfunction sha256(input: string): string {\n\treturn createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\n/** What a pin commits to, as stored in `pin_preimage`. */\nexport interface PinFields {\n\t/** sha256 of the bundled handler, hex. */\n\thandlerHash: string;\n\tnetwork: string;\n\truntime: string;\n\tschemaHash: string;\n\tstartBlock: number | null;\n}\n\n/**\n * The canonical JSON a pin is the sha256 of: fixed key order, primitives\n * only. Stored beside the pin so anyone can recompute it from the served\n * handler code.\n */\nexport function pinPreimage(input: PinInput): string {\n\tconst fields: PinFields = {\n\t\thandlerHash: sha256(input.handlerCode),\n\t\tnetwork: input.network,\n\t\truntime: input.runtime ?? SUBGRAPHS_RUNTIME,\n\t\tschemaHash: input.schemaHash,\n\t\tstartBlock: input.startBlock ?? null,\n\t};\n\treturn JSON.stringify(fields);\n}\n\n/**\n * Content hash over everything that shapes a subgraph's rows. Unlike\n * `schema_hash`, it changes with the handler, `startBlock` and runtime, so\n * two deploys with the same pin produce the same rows from the same chain.\n */\nexport function computePin(input: PinInput): string {\n\treturn sha256(pinPreimage(input));\n}\n"
6
6
  ],
7
7
  "mappings": "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",
8
- "debugId": "B00215F36B8986F364756E2164756E21",
8
+ "debugId": "77C06EADF44962D564756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  // src/verify/replay.ts
2
- import { createHash as createHash3 } from "node:crypto";
2
+ import { createHash as createHash4 } from "node:crypto";
3
3
  import { decodeRawTx } from "@secondlayer/shared/node/tx-summary";
4
4
  import {
5
5
  BlockVerifier,
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/runtime/runner.ts
11
11
  import { getErrorMessage } from "@secondlayer/shared";
12
- import { logger as logger2 } from "@secondlayer/shared/logger";
12
+ import { logger as logger3 } from "@secondlayer/shared/logger";
13
13
  import {
14
14
  clarityValueToJS,
15
15
  deserializeCV as deserializeCV3,
@@ -388,6 +388,158 @@ function decodeEventData(data) {
388
388
  return data;
389
389
  }
390
390
 
391
+ // src/runtime/error-groups.ts
392
+ import { createHash } from "node:crypto";
393
+ import { getTargetDb } from "@secondlayer/shared/db";
394
+ import { logger } from "@secondlayer/shared/logger";
395
+ import { sql } from "kysely";
396
+ var ERROR_GROUPS_CAP_PER_SUBGRAPH = 50;
397
+ var MESSAGE_MAX_CHARS = 500;
398
+ function normalizeMessage(message) {
399
+ return message.replace(/0x[0-9a-f]+/gi, "<hex>").replace(/(["'`])(?:(?!\1).)*\1/g, "<str>").replace(/\d+/g, "<n>");
400
+ }
401
+ function errorFingerprint(input) {
402
+ return createHash("sha1").update([
403
+ input.sourceName,
404
+ input.eventType ?? "",
405
+ normalizeMessage(input.message),
406
+ input.frame ?? ""
407
+ ].join("|")).digest("hex");
408
+ }
409
+ function topFrame(err) {
410
+ if (!(err instanceof Error) || !err.stack)
411
+ return null;
412
+ for (const line of err.stack.split(`
413
+ `).slice(1)) {
414
+ const match = /([^/\\\s()]+):(\d+):\d+\)?\s*$/.exec(line);
415
+ if (match)
416
+ return `${match[1]}:${match[2]}`;
417
+ }
418
+ return null;
419
+ }
420
+ var FLUSH_INTERVAL_MS = 5000;
421
+ var MAX_PENDING_KEYS = 1000;
422
+ var pending = new Map;
423
+ var droppedKeys = 0;
424
+ var flushTimer = null;
425
+ function startFlusher() {
426
+ if (flushTimer)
427
+ return;
428
+ flushTimer = setInterval(() => {
429
+ flushHandlerErrors();
430
+ }, FLUSH_INTERVAL_MS);
431
+ flushTimer.unref();
432
+ }
433
+ async function flushHandlerErrors() {
434
+ if (pending.size === 0 && droppedKeys === 0)
435
+ return;
436
+ const batch = pending;
437
+ const dropped = droppedKeys;
438
+ pending = new Map;
439
+ droppedKeys = 0;
440
+ if (dropped > 0) {
441
+ logger.warn("Handler error groups dropped: too many distinct errors", {
442
+ dropped,
443
+ cap: MAX_PENDING_KEYS
444
+ });
445
+ }
446
+ if (batch.size === 0)
447
+ return;
448
+ try {
449
+ const db = getTargetDb();
450
+ const subgraphs = new Set;
451
+ for (const group of batch.values()) {
452
+ subgraphs.add(group.subgraphName);
453
+ await db.insertInto("subgraph_error_groups").values({
454
+ subgraph_name: group.subgraphName,
455
+ fingerprint: group.fingerprint,
456
+ source_name: group.sourceName,
457
+ event_type: group.eventType,
458
+ message: group.message,
459
+ frame: group.frame,
460
+ count: group.count,
461
+ sample_tx_id: group.sampleTxId,
462
+ sample_block_height: group.sampleBlockHeight,
463
+ first_seen: group.firstSeenAt,
464
+ last_seen: group.lastSeenAt
465
+ }).onConflict((oc) => oc.columns(["subgraph_name", "fingerprint"]).doUpdateSet({
466
+ count: sql`subgraph_error_groups.count + ${group.count}`,
467
+ last_seen: group.lastSeenAt,
468
+ sample_tx_id: group.sampleTxId,
469
+ sample_block_height: group.sampleBlockHeight
470
+ })).execute();
471
+ }
472
+ for (const subgraphName of subgraphs) {
473
+ await sql`
474
+ DELETE FROM subgraph_error_groups
475
+ WHERE id IN (
476
+ SELECT id FROM subgraph_error_groups
477
+ WHERE subgraph_name = ${subgraphName}
478
+ ORDER BY last_seen DESC, id
479
+ OFFSET ${ERROR_GROUPS_CAP_PER_SUBGRAPH}
480
+ )
481
+ `.execute(db);
482
+ }
483
+ } catch (err) {
484
+ logger.warn("Failed to flush handler error groups", {
485
+ error: err instanceof Error ? err.message : String(err)
486
+ });
487
+ }
488
+ }
489
+ var recorderOverride = null;
490
+ function recordHandlerError(input) {
491
+ try {
492
+ if (recorderOverride) {
493
+ recorderOverride(input).catch((err) => {
494
+ logger.warn("Failed to record handler error group", {
495
+ subgraph: input.subgraphName,
496
+ error: err instanceof Error ? err.message : String(err)
497
+ });
498
+ });
499
+ return;
500
+ }
501
+ const message = (input.err instanceof Error ? input.err.message : String(input.err)).slice(0, MESSAGE_MAX_CHARS);
502
+ const frame = topFrame(input.err);
503
+ const fingerprint = errorFingerprint({
504
+ sourceName: input.sourceName,
505
+ eventType: input.eventType,
506
+ message,
507
+ frame
508
+ });
509
+ const key = `${input.subgraphName}|${fingerprint}`;
510
+ const now = new Date;
511
+ const existing = pending.get(key);
512
+ if (existing) {
513
+ existing.count++;
514
+ existing.sampleTxId = input.txId;
515
+ existing.sampleBlockHeight = input.blockHeight;
516
+ existing.lastSeenAt = now;
517
+ } else if (pending.size >= MAX_PENDING_KEYS) {
518
+ droppedKeys++;
519
+ } else {
520
+ pending.set(key, {
521
+ subgraphName: input.subgraphName,
522
+ fingerprint,
523
+ sourceName: input.sourceName,
524
+ eventType: input.eventType,
525
+ message,
526
+ frame,
527
+ count: 1,
528
+ sampleTxId: input.txId,
529
+ sampleBlockHeight: input.blockHeight,
530
+ firstSeenAt: now,
531
+ lastSeenAt: now
532
+ });
533
+ }
534
+ startFlusher();
535
+ } catch (err) {
536
+ logger.warn("Failed to record handler error group", {
537
+ subgraph: input.subgraphName,
538
+ error: err instanceof Error ? err.message : String(err)
539
+ });
540
+ }
541
+ }
542
+
391
543
  // src/runtime/print-validate.ts
392
544
  var COLUMN_TYPES = new Set([
393
545
  "text",
@@ -471,11 +623,11 @@ function validatePrintPayload(prints, topic, data) {
471
623
  import vm from "node:vm";
472
624
 
473
625
  // src/verification.ts
474
- import { createHash } from "node:crypto";
626
+ import { createHash as createHash2 } from "node:crypto";
475
627
  // package.json
476
628
  var package_default = {
477
629
  name: "@secondlayer/subgraphs",
478
- version: "7.3.0",
630
+ version: "7.5.0",
479
631
  description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
480
632
  license: "MIT",
481
633
  repository: {
@@ -535,7 +687,7 @@ var package_default = {
535
687
  prepublishOnly: "bun run build"
536
688
  },
537
689
  dependencies: {
538
- "@secondlayer/shared": "^13.2.0",
690
+ "@secondlayer/shared": "^13.4.0",
539
691
  "@secondlayer/stacks": "^6.4.0",
540
692
  "@secondlayer/verify": "^0.6.0",
541
693
  esbuild: "^0.28.2",
@@ -723,7 +875,7 @@ function isDeterminismViolation(v) {
723
875
  return v.level === "state" && v.reasons.length > 0;
724
876
  }
725
877
  function sha256(input) {
726
- return createHash("sha256").update(input).digest("hex");
878
+ return createHash2("sha256").update(input).digest("hex");
727
879
  }
728
880
  function pinPreimage(input) {
729
881
  const fields = {
@@ -1307,9 +1459,9 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
1307
1459
  }
1308
1460
 
1309
1461
  // src/runtime/violations.ts
1310
- import { getTargetDb } from "@secondlayer/shared/db";
1311
- import { logger } from "@secondlayer/shared/logger";
1312
- import { sql } from "kysely";
1462
+ import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
1463
+ import { logger as logger2 } from "@secondlayer/shared/logger";
1464
+ import { sql as sql2 } from "kysely";
1313
1465
  var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
1314
1466
  function jsonSafe(value) {
1315
1467
  if (typeof value === "bigint")
@@ -1329,7 +1481,7 @@ function jsonSafe(value) {
1329
1481
  }
1330
1482
  async function defaultRecordPrintViolation(input) {
1331
1483
  try {
1332
- const db = getTargetDb();
1484
+ const db = getTargetDb2();
1333
1485
  const sample = jsonSafe(input.samplePayload);
1334
1486
  await db.insertInto("subgraph_violations").values({
1335
1487
  subgraph_name: input.subgraphName,
@@ -1339,7 +1491,7 @@ async function defaultRecordPrintViolation(input) {
1339
1491
  reason: input.reason,
1340
1492
  sample_payload: sample
1341
1493
  }).execute();
1342
- await sql`
1494
+ await sql2`
1343
1495
  DELETE FROM subgraph_violations
1344
1496
  WHERE id IN (
1345
1497
  SELECT id FROM subgraph_violations
@@ -1349,7 +1501,7 @@ async function defaultRecordPrintViolation(input) {
1349
1501
  )
1350
1502
  `.execute(db);
1351
1503
  } catch (err) {
1352
- logger.warn("Failed to record print violation", {
1504
+ logger2.warn("Failed to record print violation", {
1353
1505
  subgraph: input.subgraphName,
1354
1506
  error: err instanceof Error ? err.message : String(err)
1355
1507
  });
@@ -1691,7 +1843,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1691
1843
  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));
1692
1844
  for (const { tx, event, sourceName } of units) {
1693
1845
  if (errors >= threshold) {
1694
- logger2.error("Subgraph error threshold reached, skipping remaining events", {
1846
+ logger3.error("Subgraph error threshold reached, skipping remaining events", {
1695
1847
  subgraph: subgraph.name,
1696
1848
  errors,
1697
1849
  threshold
@@ -1706,7 +1858,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1706
1858
  const filter = filterLookup.get(sourceName);
1707
1859
  const handler = subgraph.handlers[sourceName] ?? null;
1708
1860
  if (!handler) {
1709
- logger2.warn("No handler found for source", {
1861
+ logger3.warn("No handler found for source", {
1710
1862
  subgraph: subgraph.name,
1711
1863
  sourceName,
1712
1864
  txId: tx.tx_id
@@ -1763,7 +1915,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1763
1915
  const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
1764
1916
  if (!verdict.ok) {
1765
1917
  skipped++;
1766
- logger2.warn("Print payload does not match declared prints schema", {
1918
+ logger3.warn("Print payload does not match declared prints schema", {
1767
1919
  subgraph: subgraph.name,
1768
1920
  sourceName,
1769
1921
  topic: printPayload.topic,
@@ -1791,7 +1943,15 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1791
1943
  throw err;
1792
1944
  ctx.rollbackTo(checkpoint);
1793
1945
  errors++;
1794
- logger2.error("Subgraph handler error", {
1946
+ recordHandlerError({
1947
+ subgraphName: subgraph.name,
1948
+ sourceName,
1949
+ eventType: event?.type ?? null,
1950
+ txId: tx.tx_id,
1951
+ blockHeight: ctx.block.height,
1952
+ err
1953
+ });
1954
+ logger3.error("Subgraph handler error", {
1795
1955
  subgraph: subgraph.name,
1796
1956
  sourceName,
1797
1957
  txId: tx.tx_id,
@@ -1819,11 +1979,11 @@ async function applyBlock(def, data, ctx, scopes = {}) {
1819
1979
  import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
1820
1980
 
1821
1981
  // src/runtime/context.ts
1822
- import { logger as logger4 } from "@secondlayer/shared/logger";
1823
- import { sql as sql2 } from "kysely";
1982
+ import { logger as logger5 } from "@secondlayer/shared/logger";
1983
+ import { sql as sql3 } from "kysely";
1824
1984
 
1825
1985
  // src/schema/generator.ts
1826
- import { createHash as createHash2 } from "node:crypto";
1986
+ import { createHash as createHash3 } from "node:crypto";
1827
1987
 
1828
1988
  // src/schema/utils.ts
1829
1989
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
@@ -1854,7 +2014,7 @@ var MAX_IDENT_BYTES = 63;
1854
2014
  function safeIndexName(name) {
1855
2015
  if (name.length <= MAX_IDENT_BYTES)
1856
2016
  return name;
1857
- const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
2017
+ const hash = createHash3("sha256").update(name).digest("hex").slice(0, 8);
1858
2018
  return `${name.slice(0, MAX_IDENT_BYTES - hash.length - 1)}_${hash}`;
1859
2019
  }
1860
2020
  function emitIndexedColumnIndexDDL(schemaName, tableName, colName, opts) {
@@ -1985,7 +2145,7 @@ function generateSubgraphSQL(def, schemaNameOverride) {
1985
2145
  schema: def.schema,
1986
2146
  sources: def.sources
1987
2147
  }, (_key, value) => typeof value === "bigint" ? value.toString() : value);
1988
- const hash = createHash2("sha256").update(hashInput).digest("hex");
2148
+ const hash = createHash3("sha256").update(hashInput).digest("hex");
1989
2149
  return { statements, hash };
1990
2150
  }
1991
2151
 
@@ -1994,7 +2154,7 @@ import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
1994
2154
  import { chainReadArgsHash } from "@secondlayer/shared/chain-read";
1995
2155
  import { getSourceDb } from "@secondlayer/shared/db";
1996
2156
  import { hasNoLocalChain } from "@secondlayer/shared/db/queries/instance";
1997
- import { logger as logger3 } from "@secondlayer/shared/logger";
2157
+ import { logger as logger4 } from "@secondlayer/shared/logger";
1998
2158
  import {
1999
2159
  http,
2000
2160
  HttpRequestError,
@@ -2130,7 +2290,7 @@ function createChainReadClient(ctx) {
2130
2290
  throw new HandlerReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a hosted contract read cannot be pinned to it. Reindex the subgraph from this block once the block is re-ingested.`);
2131
2291
  }
2132
2292
  if (!ctx.indexBlockHash) {
2133
- logger3.warn("Chain read is not pinned to a block (no index_block_hash)", {
2293
+ logger4.warn("Chain read is not pinned to a block (no index_block_hash)", {
2134
2294
  contractId,
2135
2295
  functionName: fn.name,
2136
2296
  blockHeight: ctx.blockHeight,
@@ -2258,7 +2418,7 @@ class SubgraphContext {
2258
2418
  data: { ...key, ...row, ...meta, _upsert_keys: keyColumns }
2259
2419
  });
2260
2420
  } else {
2261
- logger4.warn("upsert called without matching uniqueKeys constraint, using fallback", {
2421
+ logger5.warn("upsert called without matching uniqueKeys constraint, using fallback", {
2262
2422
  table,
2263
2423
  keys: keyColumns
2264
2424
  });
@@ -2320,7 +2480,7 @@ class SubgraphContext {
2320
2480
  const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
2321
2481
  const { clause } = buildWhereClause(where);
2322
2482
  const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} ORDER BY "_id"${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
2323
- const { rows } = await sql2.raw(query).execute(this.db);
2483
+ const { rows } = await sql3.raw(query).execute(this.db);
2324
2484
  return rows.map((r) => this.coerceRow(table, r));
2325
2485
  }
2326
2486
  async findOne(table, where) {
@@ -2466,16 +2626,16 @@ class SubgraphContext {
2466
2626
  const affected = (r) => r.numAffectedRows ?? 0n;
2467
2627
  for (const stmt of statements) {
2468
2628
  if (typeof stmt === "string") {
2469
- await sql2.raw(stmt).execute(executor);
2629
+ await sql3.raw(stmt).execute(executor);
2470
2630
  continue;
2471
2631
  }
2472
- const updated = await sql2.raw(stmt.update).execute(executor);
2632
+ const updated = await sql3.raw(stmt.update).execute(executor);
2473
2633
  if (affected(updated) > 0n)
2474
2634
  continue;
2475
- const inserted = await sql2.raw(stmt.insert).execute(executor);
2635
+ const inserted = await sql3.raw(stmt.insert).execute(executor);
2476
2636
  if (affected(inserted) > 0n)
2477
2637
  continue;
2478
- const retried = await sql2.raw(stmt.update).execute(executor);
2638
+ const retried = await sql3.raw(stmt.update).execute(executor);
2479
2639
  if (affected(retried) === 0n) {
2480
2640
  throw new Error(`increment could not be applied to ${this.pgSchemaName}.${stmt.table} where ${stmt.keyPred}: row neither updated nor inserted`);
2481
2641
  }
@@ -2501,13 +2661,13 @@ class SubgraphContext {
2501
2661
  async ensureJournalTable() {
2502
2662
  if (!this.journal || journalEnsured.has(this.pgSchemaName))
2503
2663
  return;
2504
- const { rows } = await sql2.raw(`SELECT to_regclass('"${this.pgSchemaName}"."_journal"') AS r`).execute(this.db);
2664
+ const { rows } = await sql3.raw(`SELECT to_regclass('"${this.pgSchemaName}"."_journal"') AS r`).execute(this.db);
2505
2665
  if (rows[0]?.r) {
2506
2666
  journalEnsured.add(this.pgSchemaName);
2507
2667
  return;
2508
2668
  }
2509
2669
  for (const stmt of emitJournalDDL(this.pgSchemaName)) {
2510
- await sql2.raw(stmt).execute(this.db);
2670
+ await sql3.raw(stmt).execute(this.db);
2511
2671
  }
2512
2672
  }
2513
2673
  columnSqlType(table, col) {
@@ -2863,7 +3023,7 @@ function sameValue(a, b) {
2863
3023
  }
2864
3024
 
2865
3025
  // src/runtime/batch-loader.ts
2866
- import { sql as sql3 } from "kysely";
3026
+ import { sql as sql4 } from "kysely";
2867
3027
  function stateWriteBlocks(writesByHeight, txsByHeight) {
2868
3028
  const out = new Map;
2869
3029
  for (const [height, writes] of writesByHeight) {
@@ -2945,8 +3105,8 @@ async function loadBlockRange(db, fromHeight, toHeight, opts = {}) {
2945
3105
  return result;
2946
3106
  }
2947
3107
  async function loadStateWrites(db, fromHeight, toHeight, feed) {
2948
- const scope = feed.contracts ? sql3`AND (${sql3.join(feed.contracts.map((c) => sql3`starts_with(key, ${`vm::${c}::`})`), sql3` OR `)})` : sql3``;
2949
- const { rows } = await sql3`
3108
+ const scope = feed.contracts ? sql4`AND (${sql4.join(feed.contracts.map((c) => sql4`starts_with(key, ${`vm::${c}::`})`), sql4` OR `)})` : sql4``;
3109
+ const { rows } = await sql4`
2950
3110
  SELECT block_height, ordinal, tx_index, key, value_hex
2951
3111
  FROM state_writes
2952
3112
  WHERE block_height >= ${fromHeight} AND block_height <= ${toHeight} ${scope}
@@ -3087,7 +3247,7 @@ function replayContracts(def) {
3087
3247
  }
3088
3248
  return [...out];
3089
3249
  }
3090
- var sha2562 = (s) => createHash3("sha256").update(s).digest("hex");
3250
+ var sha2562 = (s) => createHash4("sha256").update(s).digest("hex");
3091
3251
  function checkPin(served, def) {
3092
3252
  if (!served.pin || !served.pinPreimage)
3093
3253
  return { status: "unchecked", notes: [] };
@@ -3329,7 +3489,7 @@ async function replaySubgraph(opts) {
3329
3489
  }, { ...opts, bitcoinCheckpoint: checkpoint.bitcoin.height });
3330
3490
  }
3331
3491
  // src/verify/compare.ts
3332
- import { createHash as createHash4 } from "node:crypto";
3492
+ import { createHash as createHash5 } from "node:crypto";
3333
3493
  function canonicalJson(value) {
3334
3494
  if (typeof value === "bigint")
3335
3495
  return value.toString();
@@ -3372,7 +3532,7 @@ function canonicalRow(def, row, columns = Object.keys(def.columns).sort()) {
3372
3532
  }
3373
3533
  var line = (r) => JSON.stringify(r);
3374
3534
  function digest(lines) {
3375
- return createHash4("sha256").update([...lines].sort().join(`
3535
+ return createHash5("sha256").update([...lines].sort().join(`
3376
3536
  `)).digest("hex");
3377
3537
  }
3378
3538
  var heightOf = (row) => Number(row._block_height);
@@ -3465,5 +3625,5 @@ export {
3465
3625
  replaySubgraph
3466
3626
  };
3467
3627
 
3468
- //# debugId=4966C31FE93CC61864756E2164756E21
3628
+ //# debugId=D8DAF87FB8AF0A3A64756E2164756E21
3469
3629
  //# sourceMappingURL=index.js.map