@secondlayer/subgraphs 3.23.2 → 3.24.1

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.
@@ -11,6 +11,11 @@ import { createHash } from "node:crypto";
11
11
 
12
12
  // src/schema/utils.ts
13
13
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
14
+ function quotePgIdent(name) {
15
+ if (/^[a-z_][a-z0-9_]*$/i.test(name))
16
+ return name;
17
+ return `"${name.replace(/"/g, '""')}"`;
18
+ }
14
19
 
15
20
  // src/schema/generator.ts
16
21
  var TYPE_MAP = {
@@ -35,7 +40,7 @@ function tableNeedsTrgm(tableDef) {
35
40
  return Object.values(tableDef.columns).some((col) => col.search);
36
41
  }
37
42
  function emitTableDDL(schemaName, tableName, tableDef) {
38
- const qualifiedName = `${schemaName}.${tableName}`;
43
+ const qualifiedName = `${quotePgIdent(schemaName)}.${quotePgIdent(tableName)}`;
39
44
  const statements = [];
40
45
  const columnDefs = [
41
46
  "_id BIGSERIAL PRIMARY KEY",
@@ -46,12 +51,12 @@ function emitTableDDL(schemaName, tableName, tableDef) {
46
51
  for (const [colName, col] of Object.entries(tableDef.columns)) {
47
52
  const sqlType = TYPE_MAP[col.type];
48
53
  const nullable = col.nullable ? "" : " NOT NULL";
49
- let colDef = `${colName} ${sqlType}${nullable}`;
54
+ let colDef = `${quotePgIdent(colName)} ${sqlType}${nullable}`;
50
55
  if (col.default !== undefined) {
51
56
  colDef += ` DEFAULT ${escapeLiteralDefault(col.default)}`;
52
57
  }
53
58
  if (col.type === "uint") {
54
- colDef += ` CHECK (${colName} >= 0)`;
59
+ colDef += ` CHECK (${quotePgIdent(colName)} >= 0)`;
55
60
  }
56
61
  columnDefs.push(colDef);
57
62
  }
@@ -59,30 +64,30 @@ function emitTableDDL(schemaName, tableName, tableDef) {
59
64
  ${columnDefs.join(`,
60
65
  `)}
61
66
  )`);
62
- statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_block_height ON ${qualifiedName} (_block_height)`);
63
- statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_tx_id ON ${qualifiedName} (_tx_id)`);
67
+ statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_block_height`)} ON ${qualifiedName} (_block_height)`);
68
+ statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_tx_id`)} ON ${qualifiedName} (_tx_id)`);
64
69
  for (const [colName, col] of Object.entries(tableDef.columns)) {
65
70
  if (col.indexed) {
66
- statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName} ON ${qualifiedName} (${colName})`);
71
+ statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_${colName}`)} ON ${qualifiedName} (${quotePgIdent(colName)})`);
67
72
  }
68
73
  }
69
74
  for (const [colName, col] of Object.entries(tableDef.columns)) {
70
75
  if (col.search) {
71
- statements.push(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName}_trgm ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`);
76
+ statements.push(`CREATE INDEX IF NOT EXISTS ${quotePgIdent(`idx_${schemaName}_${tableName}_${colName}_trgm`)} ON ${qualifiedName} USING gin (${quotePgIdent(colName)} gin_trgm_ops)`);
72
77
  }
73
78
  }
74
79
  if (tableDef.indexes) {
75
80
  for (let i = 0;i < tableDef.indexes.length; i++) {
76
81
  const cols = tableDef.indexes[i];
77
- const idxName = `idx_${schemaName}_${tableName}_composite_${i}`;
78
- statements.push(`CREATE INDEX IF NOT EXISTS ${idxName} ON ${qualifiedName} (${cols.join(", ")})`);
82
+ const idxName = quotePgIdent(`idx_${schemaName}_${tableName}_composite_${i}`);
83
+ statements.push(`CREATE INDEX IF NOT EXISTS ${idxName} ON ${qualifiedName} (${cols.map(quotePgIdent).join(", ")})`);
79
84
  }
80
85
  }
81
86
  if (tableDef.uniqueKeys) {
82
87
  for (let i = 0;i < tableDef.uniqueKeys.length; i++) {
83
88
  const cols = tableDef.uniqueKeys[i];
84
- const constraintName = `uq_${schemaName}_${tableName}_${cols.join("_")}`;
85
- statements.push(`ALTER TABLE ${qualifiedName} ADD CONSTRAINT ${constraintName} UNIQUE (${cols.join(", ")})`);
89
+ const constraintName = quotePgIdent(`uq_${schemaName}_${tableName}_${cols.join("_")}`);
90
+ statements.push(`ALTER TABLE ${qualifiedName} ADD CONSTRAINT ${constraintName} UNIQUE (${cols.map(quotePgIdent).join(", ")})`);
86
91
  }
87
92
  }
88
93
  return statements;
@@ -114,8 +119,8 @@ function emitFactoryDDL(schemaName) {
114
119
  }
115
120
  function emitForeignKeyDDL(schemaName, tableName, tableDef) {
116
121
  return (tableDef.relations ?? []).map((rel) => {
117
- const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
118
- return `ALTER TABLE ${schemaName}.${tableName} ADD CONSTRAINT ${constraintName} ` + `FOREIGN KEY (${rel.fields.join(", ")}) ` + `REFERENCES ${schemaName}.${rel.references} (${rel.referencedColumns.join(", ")})`;
122
+ const constraintName = quotePgIdent(`fk_${schemaName}_${tableName}_${rel.name}`);
123
+ return `ALTER TABLE ${quotePgIdent(schemaName)}.${quotePgIdent(tableName)} ADD CONSTRAINT ${constraintName} ` + `FOREIGN KEY (${rel.fields.map(quotePgIdent).join(", ")}) ` + `REFERENCES ${quotePgIdent(schemaName)}.${quotePgIdent(rel.references)} (${rel.referencedColumns.map(quotePgIdent).join(", ")})`;
119
124
  });
120
125
  }
121
126
  function generateSubgraphSQL(def, schemaNameOverride) {
@@ -1627,9 +1632,11 @@ import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contract
1627
1632
  import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
1628
1633
  import {
1629
1634
  isByoSubgraph,
1635
+ recordLiveError,
1630
1636
  recordLiveProgress,
1631
1637
  recordSubgraphProcessed,
1632
1638
  resolveSubgraphDb,
1639
+ rewindLiveProgress,
1633
1640
  updateSubgraphStatus
1634
1641
  } from "@secondlayer/shared/db/queries/subgraphs";
1635
1642
  import { logger as logger6 } from "@secondlayer/shared/logger";
@@ -2337,10 +2344,20 @@ class CursorRaceLostError extends Error {
2337
2344
  this.name = "CursorRaceLostError";
2338
2345
  }
2339
2346
  }
2347
+
2348
+ class LiveCursorRaceLostError extends Error {
2349
+ constructor(subgraphName, height) {
2350
+ super(`live cursor race lost for ${subgraphName} at block ${height}`);
2351
+ this.name = "LiveCursorRaceLostError";
2352
+ }
2353
+ }
2340
2354
  function opCursorMode(opts) {
2341
2355
  const ap = opts?.atomicProgress;
2342
2356
  return ap && "operationId" in ap ? ap : undefined;
2343
2357
  }
2358
+ function isReorgRewind(opts) {
2359
+ return opts?.reorgRewind === true;
2360
+ }
2344
2361
  function statusMode(opts) {
2345
2362
  const ap = opts?.atomicProgress;
2346
2363
  return ap && "status" in ap ? ap : undefined;
@@ -2410,7 +2427,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2410
2427
  result.matched = matched.length;
2411
2428
  if (matched.length === 0) {
2412
2429
  if (!opts?.skipProgressUpdate) {
2413
- await recordLiveProgress(targetDb, subgraphName, blockHeight);
2430
+ if (isReorgRewind(opts)) {
2431
+ await rewindLiveProgress(targetDb, subgraphName, blockHeight);
2432
+ } else {
2433
+ await recordLiveProgress(targetDb, subgraphName, blockHeight);
2434
+ }
2414
2435
  }
2415
2436
  return result;
2416
2437
  }
@@ -2429,13 +2450,23 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2429
2450
  };
2430
2451
  let handlerMs = 0;
2431
2452
  let flushMs = 0;
2432
- const applyProgress = async (tx, rr) => {
2453
+ const applyProgress = async (tx, rr, flushedWrites) => {
2433
2454
  if (opts?.skipProgressUpdate)
2434
2455
  return;
2456
+ const rewind = isReorgRewind(opts);
2435
2457
  if (rr.errors > 0 && rr.processed === 0) {
2436
- await updateSubgraphStatus(tx, subgraphName, "error", blockHeight);
2458
+ if (rewind) {
2459
+ await updateSubgraphStatus(tx, subgraphName, "error", blockHeight);
2460
+ } else {
2461
+ await recordLiveError(tx, subgraphName, blockHeight);
2462
+ }
2463
+ } else if (rewind) {
2464
+ await rewindLiveProgress(tx, subgraphName, blockHeight);
2437
2465
  } else {
2438
- await recordLiveProgress(tx, subgraphName, blockHeight);
2466
+ const advanced = await recordLiveProgress(tx, subgraphName, blockHeight);
2467
+ if (!advanced && flushedWrites && !route.byo) {
2468
+ throw new LiveCursorRaceLostError(subgraphName, blockHeight);
2469
+ }
2439
2470
  }
2440
2471
  if (rr.processed > 0 || rr.errors > 0) {
2441
2472
  const lastError = rr.errors > 0 ? `${rr.errors} error(s) at block ${blockHeight}` : undefined;
@@ -2484,13 +2515,15 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2484
2515
  } else if (byoOm && manifest && manifest.count > 0) {
2485
2516
  await advanceOperationCursor(tx, byoOm.operationId, blockHeight);
2486
2517
  }
2487
- await applyProgress(tx, runResult);
2518
+ await applyProgress(tx, runResult, manifest ? manifest.count > 0 : false);
2488
2519
  });
2489
2520
  } else {
2490
2521
  try {
2491
2522
  await targetDb.transaction().execute(async (tx) => {
2492
2523
  const opMode = opCursorMode(opts);
2493
- if (statusMode(opts)) {
2524
+ const sm = statusMode(opts);
2525
+ const rewind = isReorgRewind(opts);
2526
+ if (sm) {
2494
2527
  const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2495
2528
  if (row && Number(row.last_processed_block) >= blockHeight) {
2496
2529
  result.skipped = true;
@@ -2502,6 +2535,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2502
2535
  result.skipped = true;
2503
2536
  return;
2504
2537
  }
2538
+ } else if (!rewind) {
2539
+ const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2540
+ if (row && Number(row.last_processed_block) >= blockHeight) {
2541
+ result.skipped = true;
2542
+ return;
2543
+ }
2505
2544
  }
2506
2545
  const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
2507
2546
  const handlerStart = performance.now();
@@ -2519,7 +2558,6 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2519
2558
  }
2520
2559
  flushMs = performance.now() - flushStart;
2521
2560
  }
2522
- const sm = statusMode(opts);
2523
2561
  if (sm && flushedWrites) {
2524
2562
  await updateSubgraphStatus(tx, subgraphName, sm.status, blockHeight);
2525
2563
  } else if (opMode && flushedWrites) {
@@ -2528,10 +2566,10 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2528
2566
  throw new CursorRaceLostError(opMode.operationId, blockHeight);
2529
2567
  }
2530
2568
  }
2531
- await applyProgress(tx, runResult);
2569
+ await applyProgress(tx, runResult, flushedWrites);
2532
2570
  });
2533
2571
  } catch (err) {
2534
- if (err instanceof CursorRaceLostError) {
2572
+ if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
2535
2573
  logger6.warn("cursor race lost — block already covered", {
2536
2574
  subgraph: subgraphName,
2537
2575
  blockHeight,
@@ -2544,11 +2582,13 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2544
2582
  }
2545
2583
  }
2546
2584
  const totalMs = performance.now() - blockStart;
2547
- result.timing = {
2548
- totalMs: Math.round(totalMs),
2549
- handlerMs: Math.round(handlerMs),
2550
- flushMs: Math.round(flushMs)
2551
- };
2585
+ if (!result.skipped) {
2586
+ result.timing = {
2587
+ totalMs: Math.round(totalMs),
2588
+ handlerMs: Math.round(handlerMs),
2589
+ flushMs: Math.round(flushMs)
2590
+ };
2591
+ }
2552
2592
  if (blockHeight % 1000 === 0) {
2553
2593
  try {
2554
2594
  const tables = Object.keys(subgraph.schema);
@@ -2643,6 +2683,33 @@ class StatsAccumulator {
2643
2683
  import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
2644
2684
  import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
2645
2685
  import { logger as logger7 } from "@secondlayer/shared/logger";
2686
+
2687
+ // src/runtime/catchup-leader.ts
2688
+ import {
2689
+ SUBGRAPH_CATCHUP_LOCK_KEY,
2690
+ createPostgresLeaderBackend,
2691
+ withLeaderLock
2692
+ } from "@secondlayer/shared/leader";
2693
+ import { targetListenerUrl } from "@secondlayer/shared/queue/listener";
2694
+ var catchUpLeader = false;
2695
+ function isCatchUpLeader() {
2696
+ return catchUpLeader;
2697
+ }
2698
+ function startCatchUpLeader(opts = {}) {
2699
+ return withLeaderLock(SUBGRAPH_CATCHUP_LOCK_KEY, async () => {
2700
+ catchUpLeader = true;
2701
+ await opts.onAcquire?.();
2702
+ return () => {
2703
+ catchUpLeader = false;
2704
+ };
2705
+ }, {
2706
+ pollMs: opts.pollMs,
2707
+ heartbeatMs: opts.heartbeatMs,
2708
+ createBackend: opts.createBackend ?? (() => createPostgresLeaderBackend(targetListenerUrl()))
2709
+ });
2710
+ }
2711
+
2712
+ // src/runtime/catchup.ts
2646
2713
  var LOG_INTERVAL = 1000;
2647
2714
  var STANDARD_CATCHUP_BATCH_CONFIG = {
2648
2715
  defaultBatchSize: 500,
@@ -2764,6 +2831,13 @@ async function catchUpSubgraph(subgraph, subgraphName) {
2764
2831
  let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
2765
2832
  let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
2766
2833
  while (currentHeight <= chainTip) {
2834
+ if (!isCatchUpLeader()) {
2835
+ logger7.info("Lost catch-up leadership, stopping catch-up", {
2836
+ subgraph: subgraphName,
2837
+ processed
2838
+ });
2839
+ break;
2840
+ }
2767
2841
  const currentRow = await getSubgraph(targetDb, subgraphName);
2768
2842
  if (!currentRow || currentRow.status !== "active") {
2769
2843
  logger7.info("Subgraph status changed, stopping catch-up", {
@@ -2873,6 +2947,7 @@ import {
2873
2947
  resolveSubgraphRawClient
2874
2948
  } from "@secondlayer/shared/db/queries/subgraphs";
2875
2949
  import { logger as logger8 } from "@secondlayer/shared/logger";
2950
+ import { sql as sql4 } from "kysely";
2876
2951
  async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
2877
2952
  const targetDb = getTargetDb3();
2878
2953
  const activeSubgraphs = (await listSubgraphs(targetDb)).filter((v) => v.status === "active");
@@ -2889,96 +2964,9 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
2889
2964
  continue;
2890
2965
  bumpReorgEpoch(sg.name);
2891
2966
  await withSubgraphBlockLock(sg.name, async () => {
2892
- const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
2893
- const client = resolveSubgraphRawClient(sg);
2894
- const tableNames = Object.keys(schema);
2895
- const revertedByTable = {};
2896
- for (const tableName of tableNames) {
2897
- const rows = await client.unsafe(`SELECT * FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1 LIMIT 1000`, [blockHeight]);
2898
- if (rows.length > 0)
2899
- revertedByTable[tableName] = rows;
2900
- }
2901
- const hasJournal = (await client.unsafe(`SELECT to_regclass('"${schemaName}"."_journal"') AS r`))[0]?.r;
2902
- if (hasJournal) {
2903
- await client.begin(async (tx) => {
2904
- for (const tableName of tableNames) {
2905
- const earliest = `
2906
- SELECT DISTINCT ON (row_key) row_key, prev_row
2907
- FROM "${schemaName}"."_journal"
2908
- WHERE block_height >= $1 AND table_name = $2
2909
- ORDER BY row_key, _jid ASC`;
2910
- await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" t USING (${earliest}) e WHERE to_jsonb(t.*) @> e.row_key`, [blockHeight, tableName]);
2911
- await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
2912
- await tx.unsafe(`INSERT INTO "${schemaName}"."${tableName}"
2913
- SELECT r.* FROM (${earliest}) e
2914
- CROSS JOIN LATERAL jsonb_populate_record(NULL::"${schemaName}"."${tableName}", e.prev_row) r
2915
- WHERE e.prev_row IS NOT NULL`, [blockHeight, tableName]);
2916
- }
2917
- await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
2918
- });
2919
- } else {
2920
- logger8.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
2921
- for (const tableName of tableNames) {
2922
- await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
2923
- }
2924
- }
2925
- await client.unsafe(`DO $$ BEGIN
2926
- IF to_regclass('"${schemaName}"."_factory_addresses"') IS NOT NULL THEN
2927
- DELETE FROM "${schemaName}"."_factory_addresses" WHERE block_height >= ${Number(blockHeight)};
2928
- END IF;
2929
- END $$`);
2930
- for (const [tableName, rows] of Object.entries(revertedByTable)) {
2931
- if (rows.length === 0)
2932
- continue;
2933
- await targetDb.insertInto("subscription_outbox").columns([
2934
- "subscription_id",
2935
- "subgraph_name",
2936
- "table_name",
2937
- "block_height",
2938
- "event_type",
2939
- "payload",
2940
- "dedup_key",
2941
- "row_pk",
2942
- "status"
2943
- ]).expression((eb) => eb.selectFrom("subscriptions").select((eb2) => [
2944
- "id as subscription_id",
2945
- eb2.val(sg.name).as("subgraph_name"),
2946
- eb2.val(tableName).as("table_name"),
2947
- eb2.val(blockHeight).as("block_height"),
2948
- eb2.val(`${sg.name}.${tableName}.reverted`).as("event_type"),
2949
- eb2.val(JSON.stringify({
2950
- subgraph: sg.name,
2951
- table: tableName,
2952
- reorgFromHeight: blockHeight,
2953
- rows
2954
- })).as("payload"),
2955
- eb2.val(`reorg:${sg.name}:${tableName}:${blockHeight}`).as("dedup_key"),
2956
- eb2.val({
2957
- subgraph: sg.name,
2958
- table: tableName,
2959
- reorgRoot: blockHeight
2960
- }).as("row_pk"),
2961
- eb2.val("pending").as("status")
2962
- ]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.columns(["subscription_id", "dedup_key"]).doNothing()).execute().catch((err) => {
2963
- logger8.error("Failed to emit revert event for subscriptions", {
2964
- event: "reorg_revert_emit_dropped",
2965
- subgraph: sg.name,
2966
- table: tableName,
2967
- blockHeight,
2968
- error: getErrorMessage2(err)
2969
- });
2970
- });
2971
- }
2972
- logger8.info("Subgraph reorg cleanup done", {
2973
- subgraph: sg.name,
2974
- blockHeight,
2975
- tablesAffected: Object.keys(revertedByTable).length
2976
- });
2977
- const def = await loadSubgraphDef(sg);
2978
- await processBlock(def, sg.name, blockHeight);
2979
- logger8.info("Subgraph reorg reprocessed", {
2980
- subgraph: sg.name,
2981
- blockHeight
2967
+ await getTargetDb3().transaction().execute(async (lockTx) => {
2968
+ await sql4`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-reorg:${sg.name}`}))`.execute(lockTx);
2969
+ await reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef);
2982
2970
  });
2983
2971
  });
2984
2972
  } catch (err) {
@@ -2990,9 +2978,103 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
2990
2978
  }
2991
2979
  }
2992
2980
  }
2981
+ async function reorgOneSubgraph(sg, schema, blockHeight, loadSubgraphDef) {
2982
+ const targetDb = getTargetDb3();
2983
+ const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
2984
+ const client = resolveSubgraphRawClient(sg);
2985
+ const tableNames = Object.keys(schema);
2986
+ const revertedByTable = {};
2987
+ for (const tableName of tableNames) {
2988
+ const rows = await client.unsafe(`SELECT * FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1 LIMIT 1000`, [blockHeight]);
2989
+ if (rows.length > 0)
2990
+ revertedByTable[tableName] = rows;
2991
+ }
2992
+ const hasJournal = (await client.unsafe(`SELECT to_regclass('"${schemaName}"."_journal"') AS r`))[0]?.r;
2993
+ if (hasJournal) {
2994
+ await client.begin(async (tx) => {
2995
+ for (const tableName of tableNames) {
2996
+ const earliest = `
2997
+ SELECT DISTINCT ON (row_key) row_key, prev_row
2998
+ FROM "${schemaName}"."_journal"
2999
+ WHERE block_height >= $1 AND table_name = $2
3000
+ ORDER BY row_key, _jid ASC`;
3001
+ await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" t USING (${earliest}) e WHERE to_jsonb(t.*) @> e.row_key`, [blockHeight, tableName]);
3002
+ await tx.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3003
+ await tx.unsafe(`INSERT INTO "${schemaName}"."${tableName}"
3004
+ SELECT r.* FROM (${earliest}) e
3005
+ CROSS JOIN LATERAL jsonb_populate_record(NULL::"${schemaName}"."${tableName}", e.prev_row) r
3006
+ WHERE e.prev_row IS NOT NULL`, [blockHeight, tableName]);
3007
+ }
3008
+ await tx.unsafe(`DELETE FROM "${schemaName}"."_journal" WHERE block_height >= $1`, [blockHeight]);
3009
+ });
3010
+ } else {
3011
+ logger8.warn("Subgraph has no revert journal — falling back to height delete (accumulator rows may lose history)", { subgraph: sg.name, blockHeight });
3012
+ for (const tableName of tableNames) {
3013
+ await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3014
+ }
3015
+ }
3016
+ await client.unsafe(`DO $$ BEGIN
3017
+ IF to_regclass('"${schemaName}"."_factory_addresses"') IS NOT NULL THEN
3018
+ DELETE FROM "${schemaName}"."_factory_addresses" WHERE block_height >= ${Number(blockHeight)};
3019
+ END IF;
3020
+ END $$`);
3021
+ for (const [tableName, rows] of Object.entries(revertedByTable)) {
3022
+ if (rows.length === 0)
3023
+ continue;
3024
+ await targetDb.insertInto("subscription_outbox").columns([
3025
+ "subscription_id",
3026
+ "subgraph_name",
3027
+ "table_name",
3028
+ "block_height",
3029
+ "event_type",
3030
+ "payload",
3031
+ "dedup_key",
3032
+ "row_pk",
3033
+ "status"
3034
+ ]).expression((eb) => eb.selectFrom("subscriptions").select((eb2) => [
3035
+ "id as subscription_id",
3036
+ eb2.val(sg.name).as("subgraph_name"),
3037
+ eb2.val(tableName).as("table_name"),
3038
+ eb2.val(blockHeight).as("block_height"),
3039
+ eb2.val(`${sg.name}.${tableName}.reverted`).as("event_type"),
3040
+ eb2.val(JSON.stringify({
3041
+ subgraph: sg.name,
3042
+ table: tableName,
3043
+ reorgFromHeight: blockHeight,
3044
+ rows
3045
+ })).as("payload"),
3046
+ eb2.val(`reorg:${sg.name}:${tableName}:${blockHeight}`).as("dedup_key"),
3047
+ eb2.val({
3048
+ subgraph: sg.name,
3049
+ table: tableName,
3050
+ reorgRoot: blockHeight
3051
+ }).as("row_pk"),
3052
+ eb2.val("pending").as("status")
3053
+ ]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.columns(["subscription_id", "dedup_key"]).doNothing()).execute().catch((err) => {
3054
+ logger8.error("Failed to emit revert event for subscriptions", {
3055
+ event: "reorg_revert_emit_dropped",
3056
+ subgraph: sg.name,
3057
+ table: tableName,
3058
+ blockHeight,
3059
+ error: getErrorMessage2(err)
3060
+ });
3061
+ });
3062
+ }
3063
+ logger8.info("Subgraph reorg cleanup done", {
3064
+ subgraph: sg.name,
3065
+ blockHeight,
3066
+ tablesAffected: Object.keys(revertedByTable).length
3067
+ });
3068
+ const def = await loadSubgraphDef(sg);
3069
+ await processBlock(def, sg.name, blockHeight, { reorgRewind: true });
3070
+ logger8.info("Subgraph reorg reprocessed", {
3071
+ subgraph: sg.name,
3072
+ blockHeight
3073
+ });
3074
+ }
2993
3075
  export {
2994
3076
  handleSubgraphReorg
2995
3077
  };
2996
3078
 
2997
- //# debugId=33E820804CBE066864756E2164756E21
3079
+ //# debugId=3CC5B780D31E790E64756E2164756E21
2998
3080
  //# sourceMappingURL=reorg.js.map