@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.
@@ -196,6 +196,9 @@ interface SubgraphDefinition {
196
196
  description?: string;
197
197
  /** Block height to start indexing from (default: 1) */
198
198
  startBlock?: number;
199
+ /** 'concurrent' = tip-first: live at tip now, history backfills behind.
200
+ * Requires order-tolerant handlers. Default 'blocking'. */
201
+ backfillMode?: "blocking" | "concurrent";
199
202
  /** Named source filters — keys become handler keys */
200
203
  sources: Record<string, SubgraphFilter>;
201
204
  /** Tables in this subgraph */
@@ -225,6 +228,8 @@ interface ReindexOptions {
225
228
  fromBlock?: number;
226
229
  toBlock?: number;
227
230
  schemaName?: string;
231
+ /** Op row to receive processed_events on each progress flush. */
232
+ operationId?: string;
228
233
  signal?: AbortSignal;
229
234
  }
230
235
  /**
@@ -242,6 +247,7 @@ declare function reindexSubgraph(def: SubgraphDefinition, opts?: ReindexOptions)
242
247
  */
243
248
  declare function resumeReindex(def: SubgraphDefinition, opts: {
244
249
  schemaName: string
250
+ operationId?: string
245
251
  signal?: AbortSignal
246
252
  }): Promise<{
247
253
  processed: number
@@ -255,6 +261,7 @@ declare function backfillSubgraph(def: SubgraphDefinition, opts: {
255
261
  fromBlock: number
256
262
  toBlock: number
257
263
  schemaName?: string
264
+ operationId?: string
258
265
  signal?: AbortSignal
259
266
  }): Promise<{
260
267
  processed: number
@@ -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) {
@@ -2261,5 +2332,5 @@ export {
2261
2332
  backfillSubgraph
2262
2333
  };
2263
2334
 
2264
- //# debugId=DCE205425496103A64756E2164756E21
2335
+ //# debugId=BBB610F8B1974F6264756E2164756E21
2265
2336
  //# sourceMappingURL=reindex.js.map