@secondlayer/subgraphs 3.13.0 → 3.14.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.
@@ -1332,6 +1332,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
1332
1332
  // src/runtime/block-processor.ts
1333
1333
  import { getTargetDb } from "@secondlayer/shared/db";
1334
1334
  import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
1335
+ import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
1335
1336
  import {
1336
1337
  isByoSubgraph,
1337
1338
  recordSubgraphProcessed,
@@ -1951,6 +1952,21 @@ async function resolveTraitContracts(subgraph, blockHeight, db) {
1951
1952
  }
1952
1953
  return resolved;
1953
1954
  }
1955
+
1956
+ class CursorRaceLostError extends Error {
1957
+ constructor(operationId, height) {
1958
+ super(`op ${operationId} lost cursor race at block ${height}`);
1959
+ this.name = "CursorRaceLostError";
1960
+ }
1961
+ }
1962
+ function opCursorMode(opts) {
1963
+ const ap = opts?.atomicProgress;
1964
+ return ap && "operationId" in ap ? ap : undefined;
1965
+ }
1966
+ function statusMode(opts) {
1967
+ const ap = opts?.atomicProgress;
1968
+ return ap && "status" in ap ? ap : undefined;
1969
+ }
1954
1970
  var BLOCK_RETRY_DELAYS_MS = [500, 2000, 5000];
1955
1971
  function journalEnabled(opts) {
1956
1972
  return !opts?.skipProgressUpdate;
@@ -2044,12 +2060,19 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2044
2060
  }
2045
2061
  };
2046
2062
  if (route.byo) {
2047
- if (opts?.atomicProgress) {
2063
+ if (statusMode(opts)) {
2048
2064
  const row = await targetDb.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2049
2065
  if (row && Number(row.last_processed_block) >= blockHeight) {
2050
2066
  result.skipped = true;
2051
2067
  return result;
2052
2068
  }
2069
+ } else if (opCursorMode(opts)) {
2070
+ const om = opCursorMode(opts);
2071
+ const row = await targetDb.selectFrom("subgraph_operations").select("cursor_block").where("id", "=", om.operationId).executeTakeFirst();
2072
+ if (row?.cursor_block != null && Number(row.cursor_block) >= blockHeight) {
2073
+ result.skipped = true;
2074
+ return result;
2075
+ }
2053
2076
  }
2054
2077
  let runResult = { processed: 0, errors: 0 };
2055
2078
  let manifest;
@@ -2070,41 +2093,71 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2070
2093
  if (manifest && manifest.count > 0) {
2071
2094
  await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
2072
2095
  }
2073
- if (opts?.atomicProgress && manifest && manifest.count > 0) {
2074
- await updateSubgraphStatus(tx, subgraphName, opts.atomicProgress.status, blockHeight);
2096
+ const byoSm = statusMode(opts);
2097
+ const byoOm = opCursorMode(opts);
2098
+ if (byoSm && manifest && manifest.count > 0) {
2099
+ await updateSubgraphStatus(tx, subgraphName, byoSm.status, blockHeight);
2100
+ } else if (byoOm && manifest && manifest.count > 0) {
2101
+ await advanceOperationCursor(tx, byoOm.operationId, blockHeight);
2075
2102
  }
2076
2103
  await applyProgress(tx, runResult);
2077
2104
  });
2078
2105
  } else {
2079
- await targetDb.transaction().execute(async (tx) => {
2080
- if (opts?.atomicProgress) {
2081
- const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2082
- if (row && Number(row.last_processed_block) >= blockHeight) {
2083
- result.skipped = true;
2084
- return;
2106
+ try {
2107
+ await targetDb.transaction().execute(async (tx) => {
2108
+ const opMode = opCursorMode(opts);
2109
+ if (statusMode(opts)) {
2110
+ const row = await tx.selectFrom("subgraphs").select("last_processed_block").where("name", "=", subgraphName).executeTakeFirst();
2111
+ if (row && Number(row.last_processed_block) >= blockHeight) {
2112
+ result.skipped = true;
2113
+ return;
2114
+ }
2115
+ } else if (opMode) {
2116
+ const row = await tx.selectFrom("subgraph_operations").select("cursor_block").where("id", "=", opMode.operationId).executeTakeFirst();
2117
+ if (row?.cursor_block != null && Number(row.cursor_block) >= blockHeight) {
2118
+ result.skipped = true;
2119
+ return;
2120
+ }
2085
2121
  }
2086
- }
2087
- const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
2088
- const handlerStart = performance.now();
2089
- const runResult = await runHandlers(subgraph, matched, ctx);
2090
- handlerMs = performance.now() - handlerStart;
2091
- result.processed = runResult.processed;
2092
- result.errors = runResult.errors;
2093
- let flushedWrites = false;
2094
- if (ctx.pendingOps > 0) {
2095
- const flushStart = performance.now();
2096
- const manifest = await ctx.flush();
2097
- flushedWrites = manifest.count > 0;
2098
- if (manifest.count > 0) {
2099
- await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
2122
+ const ctx = new SubgraphContext(tx, schemaName, subgraph.schema, blockMeta, initialTx, false, journalEnabled(opts));
2123
+ const handlerStart = performance.now();
2124
+ const runResult = await runHandlers(subgraph, matched, ctx);
2125
+ handlerMs = performance.now() - handlerStart;
2126
+ result.processed = runResult.processed;
2127
+ result.errors = runResult.errors;
2128
+ let flushedWrites = false;
2129
+ if (ctx.pendingOps > 0) {
2130
+ const flushStart = performance.now();
2131
+ const manifest = await ctx.flush();
2132
+ flushedWrites = manifest.count > 0;
2133
+ if (manifest.count > 0) {
2134
+ await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
2135
+ }
2136
+ flushMs = performance.now() - flushStart;
2100
2137
  }
2101
- flushMs = performance.now() - flushStart;
2102
- }
2103
- if (opts?.atomicProgress && flushedWrites) {
2104
- await updateSubgraphStatus(tx, subgraphName, opts.atomicProgress.status, blockHeight);
2138
+ const sm = statusMode(opts);
2139
+ if (sm && flushedWrites) {
2140
+ await updateSubgraphStatus(tx, subgraphName, sm.status, blockHeight);
2141
+ } else if (opMode && flushedWrites) {
2142
+ const advanced = await advanceOperationCursor(tx, opMode.operationId, blockHeight);
2143
+ if (!advanced) {
2144
+ throw new CursorRaceLostError(opMode.operationId, blockHeight);
2145
+ }
2146
+ }
2147
+ await applyProgress(tx, runResult);
2148
+ });
2149
+ } catch (err) {
2150
+ if (err instanceof CursorRaceLostError) {
2151
+ logger5.warn("cursor race lost — block already covered", {
2152
+ subgraph: subgraphName,
2153
+ blockHeight,
2154
+ error: err.message
2155
+ });
2156
+ result.skipped = true;
2157
+ return result;
2105
2158
  }
2106
- await applyProgress(tx, runResult);
2107
- });
2159
+ throw err;
2160
+ }
2108
2161
  }
2109
2162
  const totalMs = performance.now() - blockStart;
2110
2163
  result.timing = {
@@ -2209,7 +2262,10 @@ import {
2209
2262
  recordGapBatch,
2210
2263
  resolveGaps
2211
2264
  } from "@secondlayer/shared/db/queries/subgraph-gaps";
2212
- import { updateOperationProcessedEvents } from "@secondlayer/shared/db/queries/subgraph-operations";
2265
+ import {
2266
+ advanceOperationCursor as advanceOperationCursor2,
2267
+ updateOperationProcessedEvents
2268
+ } from "@secondlayer/shared/db/queries/subgraph-operations";
2213
2269
  import {
2214
2270
  recordSubgraphProcessed as recordSubgraphProcessed2,
2215
2271
  updateSubgraphStatus as updateSubgraphStatus2
@@ -2282,6 +2338,7 @@ async function processBlockRange(def, opts) {
2282
2338
  const totalBlocks = toBlock - fromBlock + 1;
2283
2339
  const stats = new StatsAccumulator(subgraphName, opts.isCatchup);
2284
2340
  let blocksProcessed = 0;
2341
+ let blocksSkippedByCursor = 0;
2285
2342
  let totalEventsProcessed = 0;
2286
2343
  let totalErrors = 0;
2287
2344
  let pendingEventsProcessed = 0;
@@ -2342,7 +2399,8 @@ async function processBlockRange(def, opts) {
2342
2399
  }
2343
2400
  const batchFailedBlocks = [];
2344
2401
  let batchMatched = 0;
2345
- const atomicProgress = status === "reindexing" ? { status } : undefined;
2402
+ const opCursor = status === "active" && opts.operationId ? { operationId: opts.operationId } : undefined;
2403
+ const atomicProgress = status === "reindexing" ? { status } : opCursor;
2346
2404
  for (let height = currentHeight;height <= batchEnd; height++) {
2347
2405
  let blockData = batch.get(height);
2348
2406
  if (!blockData) {
@@ -2382,6 +2440,8 @@ async function processBlockRange(def, opts) {
2382
2440
  continue;
2383
2441
  }
2384
2442
  blocksProcessed++;
2443
+ if (result.skipped)
2444
+ blocksSkippedByCursor++;
2385
2445
  batchMatched += result.matched;
2386
2446
  totalEventsProcessed += result.processed;
2387
2447
  totalErrors += result.errors;
@@ -2399,7 +2459,11 @@ async function processBlockRange(def, opts) {
2399
2459
  const now = Date.now();
2400
2460
  const shouldFlushProgress = blocksProcessed % 100 === 0 || now - lastProgressFlushAt >= PROGRESS_FLUSH_INTERVAL_MS;
2401
2461
  if (shouldFlushProgress) {
2402
- await updateSubgraphStatus2(targetDb, subgraphName, status, height);
2462
+ if (opCursor) {
2463
+ await advanceOperationCursor2(targetDb, opCursor.operationId, height);
2464
+ } else {
2465
+ await updateSubgraphStatus2(targetDb, subgraphName, status, height);
2466
+ }
2403
2467
  if (opts.operationId) {
2404
2468
  await updateOperationProcessedEvents(targetDb, opts.operationId, totalEventsProcessed).catch(() => {});
2405
2469
  }
@@ -2411,7 +2475,8 @@ async function processBlockRange(def, opts) {
2411
2475
  processed: blocksProcessed,
2412
2476
  total: totalBlocks,
2413
2477
  currentBlock: height,
2414
- pct: Math.round(blocksProcessed / totalBlocks * 100)
2478
+ pct: Math.round(blocksProcessed / totalBlocks * 100),
2479
+ ...blocksSkippedByCursor > 0 ? { skippedByCursor: blocksSkippedByCursor } : {}
2415
2480
  });
2416
2481
  }
2417
2482
  }
@@ -2437,7 +2502,11 @@ async function processBlockRange(def, opts) {
2437
2502
  if (jumpTo > batchEnd + 1) {
2438
2503
  const skipped = Math.min(jumpTo, toBlock + 1) - (batchEnd + 1);
2439
2504
  blocksProcessed += skipped;
2440
- await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
2505
+ if (opCursor) {
2506
+ await advanceOperationCursor2(targetDb, opCursor.operationId, jumpTo - 1);
2507
+ } else {
2508
+ await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
2509
+ }
2441
2510
  logger6.info("Sparse skip", {
2442
2511
  subgraph: subgraphName,
2443
2512
  from: batchEnd + 1,
@@ -2683,5 +2752,5 @@ export {
2683
2752
  backfillSubgraph
2684
2753
  };
2685
2754
 
2686
- //# debugId=6F74BAD96C2E3BCE64756E2164756E21
2755
+ //# debugId=68FA2F82D514BA4764756E2164756E21
2687
2756
  //# sourceMappingURL=reindex.js.map