@secondlayer/subgraphs 3.10.0 → 3.12.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.
@@ -1119,6 +1119,28 @@ function reconstructEvent(e) {
1119
1119
  }
1120
1120
 
1121
1121
  // src/runtime/block-source.ts
1122
+ function canSparseScan(subgraph) {
1123
+ if (Array.isArray(subgraph.sources))
1124
+ return false;
1125
+ const filters = sourceFilters(subgraph);
1126
+ if (filters.length === 0)
1127
+ return false;
1128
+ return filters.every((f) => Boolean(EVENT_FILTER_TO_INDEX_TYPE[f.type]));
1129
+ }
1130
+ function sparseProbeTargets(subgraph) {
1131
+ const targets = new Map;
1132
+ for (const f of sourceFilters(subgraph)) {
1133
+ const eventType = EVENT_FILTER_TO_INDEX_TYPE[f.type];
1134
+ if (!eventType)
1135
+ continue;
1136
+ const scoped = f;
1137
+ const contractId = scoped.contractId ?? scoped.assetIdentifier?.split("::")[0];
1138
+ const key = `${eventType}|${contractId ?? ""}`;
1139
+ targets.set(key, { eventType, ...contractId ? { contractId } : {} });
1140
+ }
1141
+ return [...targets.values()];
1142
+ }
1143
+
1122
1144
  class PostgresBlockSource {
1123
1145
  async getTip() {
1124
1146
  const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
@@ -1181,9 +1203,18 @@ function isStreamsIndexEligible(subgraph) {
1181
1203
  class PublicApiBlockSource {
1182
1204
  http;
1183
1205
  eventTypes;
1184
- constructor(http, eventTypes) {
1206
+ probeTargets;
1207
+ constructor(http, eventTypes, probeTargets) {
1185
1208
  this.http = http;
1186
1209
  this.eventTypes = eventTypes;
1210
+ this.probeTargets = probeTargets;
1211
+ }
1212
+ async nextDataHeight(afterHeight, untilHeight) {
1213
+ if (!this.probeTargets?.length)
1214
+ return afterHeight + 1;
1215
+ const hits = await Promise.all(this.probeTargets.map((t) => this.http.firstEventHeight(t.eventType, afterHeight + 1, untilHeight, t.contractId)));
1216
+ const found = hits.filter((h) => h !== null);
1217
+ return found.length ? Math.min(...found) : null;
1187
1218
  }
1188
1219
  getTip() {
1189
1220
  return this.http.getIndexTip();
@@ -1247,6 +1278,15 @@ class FallbackBlockSource {
1247
1278
  return this.fallback.loadBlockRange(fromHeight, toHeight);
1248
1279
  }
1249
1280
  }
1281
+ async nextDataHeight(afterHeight, untilHeight) {
1282
+ if (!this.primary.nextDataHeight)
1283
+ return afterHeight + 1;
1284
+ try {
1285
+ return await this.primary.nextDataHeight(afterHeight, untilHeight);
1286
+ } catch {
1287
+ return afterHeight + 1;
1288
+ }
1289
+ }
1250
1290
  }
1251
1291
  var postgresBlockSource = new PostgresBlockSource;
1252
1292
  function buildHttpClient() {
@@ -1259,7 +1299,7 @@ function buildHttpClient() {
1259
1299
  }
1260
1300
  function resolveBlockSource(subgraph) {
1261
1301
  if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph && isStreamsIndexEligible(subgraph)) {
1262
- return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph)), postgresBlockSource);
1302
+ return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined), postgresBlockSource);
1263
1303
  }
1264
1304
  if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
1265
1305
  logger3.debug("Subgraph not streams-index eligible, using DB tap", {
@@ -1742,6 +1782,7 @@ import {
1742
1782
  recordGapBatch,
1743
1783
  resolveGaps
1744
1784
  } from "@secondlayer/shared/db/queries/subgraph-gaps";
1785
+ import { updateOperationProcessedEvents } from "@secondlayer/shared/db/queries/subgraph-operations";
1745
1786
  import {
1746
1787
  recordSubgraphProcessed as recordSubgraphProcessed2,
1747
1788
  updateSubgraphStatus as updateSubgraphStatus2
@@ -1929,6 +1970,7 @@ async function processBlockRange(def, opts) {
1929
1970
  let batchSize = batchConfig.defaultBatchSize;
1930
1971
  let currentHeight = fromBlock;
1931
1972
  let aborted = false;
1973
+ const sparse = Boolean(source.nextDataHeight && canSparseScan(def));
1932
1974
  const flushHealth = async () => {
1933
1975
  if (pendingEventsProcessed === 0 && pendingErrors === 0)
1934
1976
  return;
@@ -1959,6 +2001,7 @@ async function processBlockRange(def, opts) {
1959
2001
  nextBatchPromise = source.loadBlockRange(nextStart, nextBatchEnd);
1960
2002
  }
1961
2003
  const batchFailedBlocks = [];
2004
+ let batchMatched = 0;
1962
2005
  for (let height = currentHeight;height <= batchEnd; height++) {
1963
2006
  const blockData = batch.get(height);
1964
2007
  if (!blockData) {
@@ -1988,6 +2031,7 @@ async function processBlockRange(def, opts) {
1988
2031
  continue;
1989
2032
  }
1990
2033
  blocksProcessed++;
2034
+ batchMatched += result.matched;
1991
2035
  totalEventsProcessed += result.processed;
1992
2036
  totalErrors += result.errors;
1993
2037
  pendingEventsProcessed += result.processed;
@@ -2005,6 +2049,9 @@ async function processBlockRange(def, opts) {
2005
2049
  const shouldFlushProgress = blocksProcessed % 100 === 0 || now - lastProgressFlushAt >= PROGRESS_FLUSH_INTERVAL_MS;
2006
2050
  if (shouldFlushProgress) {
2007
2051
  await updateSubgraphStatus2(targetDb, subgraphName, status, height);
2052
+ if (opts.operationId) {
2053
+ await updateOperationProcessedEvents(targetDb, opts.operationId, totalEventsProcessed).catch(() => {});
2054
+ }
2008
2055
  lastProgressFlushAt = now;
2009
2056
  }
2010
2057
  if (blocksProcessed % LOG_INTERVAL === 0) {
@@ -2033,6 +2080,27 @@ async function processBlockRange(def, opts) {
2033
2080
  });
2034
2081
  });
2035
2082
  }
2083
+ if (sparse && batchMatched === 0 && batchEnd < toBlock && source.nextDataHeight) {
2084
+ const next = await source.nextDataHeight(batchEnd, toBlock);
2085
+ const jumpTo = next === null ? toBlock + 1 : Math.max(next, batchEnd + 1);
2086
+ if (jumpTo > batchEnd + 1) {
2087
+ const skipped = Math.min(jumpTo, toBlock + 1) - (batchEnd + 1);
2088
+ blocksProcessed += skipped;
2089
+ await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
2090
+ logger6.info("Sparse skip", {
2091
+ subgraph: subgraphName,
2092
+ from: batchEnd + 1,
2093
+ to: jumpTo - 1,
2094
+ skipped
2095
+ });
2096
+ currentHeight = jumpTo;
2097
+ if (currentHeight <= toBlock) {
2098
+ nextBatchEnd = Math.min(currentHeight + batchSize - 1, toBlock);
2099
+ nextBatchPromise = source.loadBlockRange(currentHeight, nextBatchEnd);
2100
+ }
2101
+ continue;
2102
+ }
2103
+ }
2036
2104
  const avg = avgEventsPerBlock(batch);
2037
2105
  if (avg > 50)
2038
2106
  batchSize = Math.max(Math.round(batchSize * 0.5), batchConfig.minBatchSize);
@@ -2101,6 +2169,7 @@ async function reindexSubgraph(def, opts) {
2101
2169
  isCatchup: false,
2102
2170
  apiKeyId: null,
2103
2171
  subgraphId: subgraphRow?.id,
2172
+ operationId: opts?.operationId,
2104
2173
  signal: opts?.signal
2105
2174
  });
2106
2175
  if (result.aborted) {
@@ -2176,6 +2245,7 @@ async function resumeReindex(def, opts) {
2176
2245
  isCatchup: false,
2177
2246
  apiKeyId: null,
2178
2247
  subgraphId: row.id,
2248
+ operationId: opts.operationId,
2179
2249
  signal: opts.signal
2180
2250
  });
2181
2251
  if (result.aborted) {
@@ -2224,6 +2294,7 @@ async function backfillSubgraph(def, opts) {
2224
2294
  isCatchup: false,
2225
2295
  apiKeyId: null,
2226
2296
  subgraphId: subgraphRow?.id,
2297
+ operationId: opts.operationId,
2227
2298
  signal: opts.signal
2228
2299
  });
2229
2300
  if (result.aborted) {
@@ -2766,6 +2837,7 @@ async function runSubgraphOperation(operation, signal) {
2766
2837
  fromBlock: Number(operation.from_block),
2767
2838
  toBlock: Number(operation.to_block),
2768
2839
  schemaName,
2840
+ operationId: operation.id,
2769
2841
  signal
2770
2842
  });
2771
2843
  return result2.processed;
@@ -2775,13 +2847,18 @@ async function runSubgraphOperation(operation, signal) {
2775
2847
  }
2776
2848
  const hasResumeMetadata = subgraph.status === "reindexing" && subgraph.reindex_from_block != null && subgraph.reindex_to_block != null;
2777
2849
  if (hasResumeMetadata) {
2778
- const result2 = await resumeReindex(def, { schemaName, signal });
2850
+ const result2 = await resumeReindex(def, {
2851
+ schemaName,
2852
+ operationId: operation.id,
2853
+ signal
2854
+ });
2779
2855
  return result2.processed;
2780
2856
  }
2781
2857
  const result = await reindexSubgraph(def, {
2782
2858
  fromBlock: operation.from_block == null ? undefined : Number(operation.from_block),
2783
2859
  toBlock: operation.to_block == null ? undefined : Number(operation.to_block),
2784
2860
  schemaName,
2861
+ operationId: operation.id,
2785
2862
  signal
2786
2863
  });
2787
2864
  return result.processed;
@@ -2795,6 +2872,27 @@ async function startSubgraphOperationRunner(opts) {
2795
2872
  let running = true;
2796
2873
  let draining = false;
2797
2874
  logger10.info("Starting subgraph operation runner", { concurrency, lockedBy });
2875
+ try {
2876
+ const stranded = await db.selectFrom("subgraphs").select(["id", "name", "account_id"]).where("status", "=", "reindexing").where("reindex_from_block", "is not", null).where("reindex_to_block", "is not", null).where(({ not, exists, selectFrom }) => not(exists(selectFrom("subgraph_operations").select("id").whereRef("subgraph_id", "=", "subgraphs.id").where("status", "in", ["queued", "running"])))).execute();
2877
+ for (const row of stranded) {
2878
+ try {
2879
+ await createSubgraphOperation(db, {
2880
+ subgraphId: row.id,
2881
+ subgraphName: row.name,
2882
+ accountId: row.account_id,
2883
+ kind: "reindex"
2884
+ });
2885
+ logger10.info("Re-enqueued stranded reindex", { subgraph: row.name });
2886
+ } catch (err) {
2887
+ if (!isActiveSubgraphOperationConflict(err))
2888
+ throw err;
2889
+ }
2890
+ }
2891
+ } catch (err) {
2892
+ logger10.warn("Stranded-reindex sweep failed", {
2893
+ error: getErrorMessage5(err)
2894
+ });
2895
+ }
2798
2896
  const startOperation = (operation) => {
2799
2897
  const controller = new AbortController;
2800
2898
  active.set(operation.id, controller);
@@ -2996,5 +3094,5 @@ var shutdown = async () => {
2996
3094
  process.on("SIGINT", shutdown);
2997
3095
  process.on("SIGTERM", shutdown);
2998
3096
 
2999
- //# debugId=9428CDEA9E276AF264756E2164756E21
3097
+ //# debugId=3945FD567AE4CFB864756E2164756E21
3000
3098
  //# sourceMappingURL=service.js.map