@secondlayer/subgraphs 0.6.0 → 0.7.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.
Files changed (39) hide show
  1. package/dist/src/index.d.ts +3 -1
  2. package/dist/src/index.js +233 -119
  3. package/dist/src/index.js.map +16 -16
  4. package/dist/src/runtime/block-processor.d.ts +2 -0
  5. package/dist/src/runtime/block-processor.js +130 -100
  6. package/dist/src/runtime/block-processor.js.map +9 -9
  7. package/dist/src/runtime/catchup.d.ts +2 -0
  8. package/dist/src/runtime/catchup.js +180 -104
  9. package/dist/src/runtime/catchup.js.map +12 -12
  10. package/dist/src/runtime/clarity.js.map +2 -2
  11. package/dist/src/runtime/context.d.ts +1 -1
  12. package/dist/src/runtime/context.js +17 -4
  13. package/dist/src/runtime/context.js.map +3 -3
  14. package/dist/src/runtime/processor.js +198 -110
  15. package/dist/src/runtime/processor.js.map +14 -14
  16. package/dist/src/runtime/reindex.d.ts +2 -0
  17. package/dist/src/runtime/reindex.js +223 -117
  18. package/dist/src/runtime/reindex.js.map +13 -13
  19. package/dist/src/runtime/reorg.d.ts +2 -0
  20. package/dist/src/runtime/reorg.js +143 -104
  21. package/dist/src/runtime/reorg.js.map +10 -10
  22. package/dist/src/runtime/runner.d.ts +3 -1
  23. package/dist/src/runtime/runner.js +7 -3
  24. package/dist/src/runtime/runner.js.map +4 -4
  25. package/dist/src/runtime/source-matcher.js.map +3 -3
  26. package/dist/src/runtime/stats.d.ts +1 -1
  27. package/dist/src/runtime/stats.js +2 -2
  28. package/dist/src/runtime/stats.js.map +3 -3
  29. package/dist/src/schema/index.d.ts +3 -1
  30. package/dist/src/schema/index.js +11 -3
  31. package/dist/src/schema/index.js.map +5 -5
  32. package/dist/src/service.js +199 -111
  33. package/dist/src/service.js.map +15 -15
  34. package/dist/src/templates.js.map +2 -2
  35. package/dist/src/types.d.ts +2 -0
  36. package/dist/src/types.js.map +2 -2
  37. package/dist/src/validate.d.ts +2 -0
  38. package/dist/src/validate.js.map +2 -2
  39. package/package.json +51 -51
@@ -70,6 +70,8 @@ interface SubgraphDefinition {
70
70
  version?: string;
71
71
  /** Human description */
72
72
  description?: string;
73
+ /** Block height to start indexing from (default: 1) */
74
+ startBlock?: number;
73
75
  /** What blockchain data to process — one or more source filters */
74
76
  sources: SubgraphSource[];
75
77
  /** Tables in this subgraph */
@@ -138,8 +140,8 @@ interface GeneratedSQL {
138
140
  */
139
141
  declare function generateSubgraphSQL(def: SubgraphDefinition, schemaNameOverride?: string): GeneratedSQL;
140
142
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
141
- import { Kysely } from "kysely";
142
143
  import { Database } from "@secondlayer/shared/db";
144
+ import { Kysely } from "kysely";
143
145
  type AnyDb = Kysely<Database>;
144
146
  interface TableDiff {
145
147
  /** Tables added to the schema */
package/dist/src/index.js CHANGED
@@ -60,8 +60,8 @@ function validateSubgraphDefinition(def) {
60
60
  }
61
61
 
62
62
  // src/runtime/context.ts
63
- import { sql } from "kysely";
64
63
  import { logger } from "@secondlayer/shared/logger";
64
+ import { sql } from "kysely";
65
65
  function validateColumnName(name) {
66
66
  if (!/^[a-z_][a-z0-9_]*$/i.test(name)) {
67
67
  throw new Error(`Invalid column name: ${name}`);
@@ -102,13 +102,26 @@ class SubgraphContext {
102
102
  const keyColumns = Object.keys(key);
103
103
  const hasUniqueConstraint = tableDef.uniqueKeys?.some((uk) => uk.length === keyColumns.length && uk.every((c) => keyColumns.includes(c)));
104
104
  if (hasUniqueConstraint) {
105
- this.ops.push({ kind: "insert", table, data: { ...key, ...row, _upsert_keys: keyColumns } });
105
+ this.ops.push({
106
+ kind: "insert",
107
+ table,
108
+ data: { ...key, ...row, _upsert_keys: keyColumns }
109
+ });
106
110
  } else {
107
111
  logger.warn("upsert called without matching uniqueKeys constraint, using fallback", {
108
112
  table,
109
113
  keys: keyColumns
110
114
  });
111
- this.ops.push({ kind: "insert", table, data: { ...key, ...row, _upsert_fallback_keys: keyColumns, _upsert_fallback_set: row } });
115
+ this.ops.push({
116
+ kind: "insert",
117
+ table,
118
+ data: {
119
+ ...key,
120
+ ...row,
121
+ _upsert_fallback_keys: keyColumns,
122
+ _upsert_fallback_set: row
123
+ }
124
+ });
112
125
  }
113
126
  }
114
127
  delete(table, where) {
@@ -271,95 +284,6 @@ function buildWhereClause(where) {
271
284
  return { clause: parts.join(" AND "), values: [] };
272
285
  }
273
286
 
274
- // src/runtime/source-matcher.ts
275
- function matchPattern(value, pattern) {
276
- if (!pattern.includes("*"))
277
- return value === pattern;
278
- const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
279
- return new RegExp(`^${regex}$`).test(value);
280
- }
281
- function matchSource(source, transactions, eventsByTx) {
282
- const results = [];
283
- const key = sourceKey(source);
284
- for (const tx of transactions) {
285
- if (source.type) {
286
- if (!matchPattern(tx.type, source.type))
287
- continue;
288
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
289
- let matchedEvents = txEvents;
290
- if (source.minAmount !== undefined) {
291
- const amountEvents = matchedEvents.filter((e) => {
292
- const data = e.data;
293
- const rawAmount = data?.amount;
294
- if (rawAmount === undefined)
295
- return false;
296
- const amount = BigInt(rawAmount);
297
- return amount >= source.minAmount;
298
- });
299
- if (amountEvents.length === 0)
300
- continue;
301
- matchedEvents = amountEvents;
302
- }
303
- results.push({ tx, events: matchedEvents, sourceKey: key });
304
- continue;
305
- }
306
- if (source.contract) {
307
- const txContractMatch = tx.contract_id && matchPattern(tx.contract_id, source.contract);
308
- if (source.function && tx.function_name) {
309
- if (!matchPattern(tx.function_name, source.function))
310
- continue;
311
- } else if (source.function && !tx.function_name) {
312
- continue;
313
- }
314
- const txEvents = eventsByTx.get(tx.tx_id) ?? [];
315
- let matchedEvents = txEvents;
316
- if (!txContractMatch) {
317
- matchedEvents = txEvents.filter((e) => {
318
- const data = e.data;
319
- const contractIdentifier = data?.contract_identifier;
320
- return contractIdentifier && matchPattern(contractIdentifier, source.contract);
321
- });
322
- if (matchedEvents.length === 0)
323
- continue;
324
- }
325
- if (source.event) {
326
- matchedEvents = matchedEvents.filter((e) => {
327
- if (matchPattern(e.type, source.event))
328
- return true;
329
- const data = e.data;
330
- const topic = data?.topic;
331
- return topic ? matchPattern(topic, source.event) : false;
332
- });
333
- }
334
- if (txContractMatch || matchedEvents.length > 0) {
335
- results.push({ tx, events: matchedEvents, sourceKey: key });
336
- }
337
- }
338
- }
339
- return results;
340
- }
341
- function matchSources(sources, transactions, events) {
342
- const eventsByTx = new Map;
343
- for (const event of events) {
344
- const list = eventsByTx.get(event.tx_id) ?? [];
345
- list.push(event);
346
- eventsByTx.set(event.tx_id, list);
347
- }
348
- const seen = new Set;
349
- const results = [];
350
- for (const source of sources) {
351
- const matches = matchSource(source, transactions, eventsByTx);
352
- for (const match of matches) {
353
- const dedupeKey = `${match.tx.tx_id}:${match.sourceKey}`;
354
- if (!seen.has(dedupeKey)) {
355
- seen.add(dedupeKey);
356
- results.push(match);
357
- }
358
- }
359
- }
360
- return results;
361
- }
362
-
363
287
  // src/runtime/clarity.ts
364
288
  import { cvToJSON, deserializeCV } from "@secondlayer/stacks/clarity";
365
289
  function decodeClarityValue(hex) {
@@ -392,8 +316,8 @@ function decodeFunctionArgs(args) {
392
316
  }
393
317
 
394
318
  // src/runtime/runner.ts
395
- import { logger as logger2 } from "@secondlayer/shared/logger";
396
319
  import { getErrorMessage } from "@secondlayer/shared";
320
+ import { logger as logger2 } from "@secondlayer/shared/logger";
397
321
  var DEFAULT_ERROR_THRESHOLD = 50;
398
322
  function resolveHandler(handlers, key) {
399
323
  return handlers[key] ?? handlers["*"] ?? null;
@@ -405,7 +329,11 @@ async function runHandlers(subgraph, matched, ctx, opts) {
405
329
  for (const { tx, events, sourceKey: sourceKey2 } of matched) {
406
330
  const handler = resolveHandler(subgraph.handlers, sourceKey2);
407
331
  if (!handler) {
408
- logger2.warn("No handler found for source key", { subgraph: subgraph.name, sourceKey: sourceKey2, txId: tx.tx_id });
332
+ logger2.warn("No handler found for source key", {
333
+ subgraph: subgraph.name,
334
+ sourceKey: sourceKey2,
335
+ txId: tx.tx_id
336
+ });
409
337
  continue;
410
338
  }
411
339
  ctx.setTx({
@@ -482,11 +410,103 @@ async function runHandlers(subgraph, matched, ctx, opts) {
482
410
  return { processed, errors };
483
411
  }
484
412
 
413
+ // src/runtime/source-matcher.ts
414
+ function matchPattern(value, pattern) {
415
+ if (!pattern.includes("*"))
416
+ return value === pattern;
417
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
418
+ return new RegExp(`^${regex}$`).test(value);
419
+ }
420
+ function matchSource(source, transactions, eventsByTx) {
421
+ const results = [];
422
+ const key = sourceKey(source);
423
+ for (const tx of transactions) {
424
+ if (source.type) {
425
+ if (!matchPattern(tx.type, source.type))
426
+ continue;
427
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
428
+ let matchedEvents = txEvents;
429
+ if (source.minAmount !== undefined) {
430
+ const amountEvents = matchedEvents.filter((e) => {
431
+ const data = e.data;
432
+ const rawAmount = data?.amount;
433
+ if (rawAmount === undefined)
434
+ return false;
435
+ const amount = BigInt(rawAmount);
436
+ return amount >= source.minAmount;
437
+ });
438
+ if (amountEvents.length === 0)
439
+ continue;
440
+ matchedEvents = amountEvents;
441
+ }
442
+ results.push({ tx, events: matchedEvents, sourceKey: key });
443
+ continue;
444
+ }
445
+ if (source.contract) {
446
+ const txContractMatch = tx.contract_id && matchPattern(tx.contract_id, source.contract);
447
+ if (source.function && tx.function_name) {
448
+ if (!matchPattern(tx.function_name, source.function))
449
+ continue;
450
+ } else if (source.function && !tx.function_name) {
451
+ continue;
452
+ }
453
+ const txEvents = eventsByTx.get(tx.tx_id) ?? [];
454
+ let matchedEvents = txEvents;
455
+ if (!txContractMatch) {
456
+ matchedEvents = txEvents.filter((e) => {
457
+ const data = e.data;
458
+ const contractIdentifier = data?.contract_identifier;
459
+ return contractIdentifier && matchPattern(contractIdentifier, source.contract);
460
+ });
461
+ if (matchedEvents.length === 0)
462
+ continue;
463
+ }
464
+ if (source.event) {
465
+ matchedEvents = matchedEvents.filter((e) => {
466
+ if (matchPattern(e.type, source.event))
467
+ return true;
468
+ const data = e.data;
469
+ const topic = data?.topic;
470
+ return topic ? matchPattern(topic, source.event) : false;
471
+ });
472
+ }
473
+ if (txContractMatch || matchedEvents.length > 0) {
474
+ results.push({ tx, events: matchedEvents, sourceKey: key });
475
+ }
476
+ }
477
+ }
478
+ return results;
479
+ }
480
+ function matchSources(sources, transactions, events) {
481
+ const eventsByTx = new Map;
482
+ for (const event of events) {
483
+ const list = eventsByTx.get(event.tx_id) ?? [];
484
+ list.push(event);
485
+ eventsByTx.set(event.tx_id, list);
486
+ }
487
+ const seen = new Set;
488
+ const results = [];
489
+ for (const source of sources) {
490
+ const matches = matchSource(source, transactions, eventsByTx);
491
+ for (const match of matches) {
492
+ const dedupeKey = `${match.tx.tx_id}:${match.sourceKey}`;
493
+ if (!seen.has(dedupeKey)) {
494
+ seen.add(dedupeKey);
495
+ results.push(match);
496
+ }
497
+ }
498
+ }
499
+ return results;
500
+ }
501
+
485
502
  // src/runtime/block-processor.ts
486
- import { sql as sql2 } from "kysely";
487
503
  import { getDb } from "@secondlayer/shared/db";
504
+ import {
505
+ recordSubgraphProcessed,
506
+ updateSubgraphStatus
507
+ } from "@secondlayer/shared/db/queries/subgraphs";
488
508
  import { logger as logger3 } from "@secondlayer/shared/logger";
489
- import { updateSubgraphStatus, recordSubgraphProcessed } from "@secondlayer/shared/db/queries/subgraphs";
509
+ import { sql as sql2 } from "kysely";
490
510
 
491
511
  // src/schema/utils.ts
492
512
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
@@ -511,12 +531,18 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
511
531
  } else {
512
532
  block = await db.selectFrom("blocks").selectAll().where("height", "=", blockHeight).executeTakeFirst();
513
533
  if (!block) {
514
- logger3.warn("Block not found for subgraph processing", { subgraph: subgraphName, blockHeight });
534
+ logger3.warn("Block not found for subgraph processing", {
535
+ subgraph: subgraphName,
536
+ blockHeight
537
+ });
515
538
  result.skipped = true;
516
539
  return result;
517
540
  }
518
541
  if (!block.canonical) {
519
- logger3.debug("Skipping non-canonical block", { subgraph: subgraphName, blockHeight });
542
+ logger3.debug("Skipping non-canonical block", {
543
+ subgraph: subgraphName,
544
+ blockHeight
545
+ });
520
546
  result.skipped = true;
521
547
  return result;
522
548
  }
@@ -585,7 +611,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
585
611
  const { rows } = await sql2.raw(`SELECT COUNT(*) as count FROM "${schemaName}"."${table}"`).execute(db);
586
612
  const count = Number(rows[0].count);
587
613
  if (count >= 1e7) {
588
- logger3.warn("Subgraph table exceeds 10M rows", { subgraph: subgraphName, table, count });
614
+ logger3.warn("Subgraph table exceeds 10M rows", {
615
+ subgraph: subgraphName,
616
+ table,
617
+ count
618
+ });
589
619
  }
590
620
  }
591
621
  } catch {}
@@ -639,7 +669,7 @@ class StatsAccumulator {
639
669
  flush_time_ms: Math.round(entry.flushTimeMs),
640
670
  max_block_time_ms: Math.round(entry.maxBlockTimeMs),
641
671
  max_handler_time_ms: Math.round(entry.maxHandlerTimeMs),
642
- avg_ops_per_block: parseFloat(avgOpsPerBlock.toFixed(2)),
672
+ avg_ops_per_block: Number.parseFloat(avgOpsPerBlock.toFixed(2)),
643
673
  is_catchup: entry.isCatchup
644
674
  }).execute();
645
675
  }
@@ -661,10 +691,14 @@ class StatsAccumulator {
661
691
  }
662
692
 
663
693
  // src/runtime/reindex.ts
694
+ import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
664
695
  import { getDb as getDb2, getRawClient } from "@secondlayer/shared/db";
696
+ import {
697
+ recordGapBatch,
698
+ resolveGaps
699
+ } from "@secondlayer/shared/db/queries/subgraph-gaps";
665
700
  import { updateSubgraphStatus as updateSubgraphStatus2 } from "@secondlayer/shared/db/queries/subgraphs";
666
701
  import { logger as logger4 } from "@secondlayer/shared/logger";
667
- import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
668
702
 
669
703
  // src/schema/generator.ts
670
704
  var TYPE_MAP = {
@@ -798,6 +832,28 @@ var LOG_INTERVAL = 1000;
798
832
  var DEFAULT_BATCH_SIZE = 500;
799
833
  var MIN_BATCH_SIZE = 100;
800
834
  var MAX_BATCH_SIZE = 1000;
835
+ function coalesceFailedBlocks(blocks) {
836
+ if (blocks.length === 0)
837
+ return [];
838
+ blocks.sort((a, b) => a.height - b.height);
839
+ const ranges = [];
840
+ let start = blocks[0].height;
841
+ let end = blocks[0].height;
842
+ let reason = blocks[0].reason;
843
+ for (let i = 1;i < blocks.length; i++) {
844
+ const b = blocks[i];
845
+ if (b.height === end + 1 && b.reason === reason) {
846
+ end = b.height;
847
+ } else {
848
+ ranges.push({ start, end, reason });
849
+ start = b.height;
850
+ end = b.height;
851
+ reason = b.reason;
852
+ }
853
+ }
854
+ ranges.push({ start, end, reason });
855
+ return ranges;
856
+ }
801
857
  async function processBlockRange(def, opts) {
802
858
  const db = getDb2();
803
859
  const subgraphName = def.name;
@@ -818,9 +874,11 @@ async function processBlockRange(def, opts) {
818
874
  const nextEnd = Math.min(nextStart + batchSize - 1, toBlock);
819
875
  nextBatchPromise = loadBlockRange(db, nextStart, nextEnd);
820
876
  }
877
+ const batchFailedBlocks = [];
821
878
  for (let height = currentHeight;height <= batchEnd; height++) {
822
879
  const blockData = batch.get(height);
823
880
  if (!blockData) {
881
+ batchFailedBlocks.push({ height, reason: "block_missing" });
824
882
  blocksProcessed++;
825
883
  continue;
826
884
  }
@@ -836,6 +894,7 @@ async function processBlockRange(def, opts) {
836
894
  blockHeight: height,
837
895
  error: err instanceof Error ? err.message : String(err)
838
896
  });
897
+ batchFailedBlocks.push({ height, reason: "processing_error" });
839
898
  await updateSubgraphStatus2(db, subgraphName, status, height).catch(() => {});
840
899
  blocksProcessed++;
841
900
  totalErrors++;
@@ -863,6 +922,15 @@ async function processBlockRange(def, opts) {
863
922
  });
864
923
  }
865
924
  }
925
+ if (batchFailedBlocks.length > 0 && opts.subgraphId) {
926
+ const gaps = coalesceFailedBlocks(batchFailedBlocks);
927
+ await recordGapBatch(db, opts.subgraphId, subgraphName, gaps).catch((err) => {
928
+ logger4.warn("Failed to record subgraph gaps", {
929
+ subgraph: subgraphName,
930
+ error: err instanceof Error ? err.message : String(err)
931
+ });
932
+ });
933
+ }
866
934
  const avg = avgEventsPerBlock(batch);
867
935
  if (avg > 50)
868
936
  batchSize = Math.max(Math.round(batchSize * 0.5), MIN_BATCH_SIZE);
@@ -873,14 +941,14 @@ async function processBlockRange(def, opts) {
873
941
  await stats.flush(db);
874
942
  return { blocksProcessed, totalEventsProcessed, totalErrors };
875
943
  }
876
- async function resolveBlockRange(db, opts) {
877
- const fromBlock = opts?.fromBlock ?? 1;
944
+ async function resolveBlockRange(db, def, opts) {
945
+ const fromBlock = opts?.fromBlock ?? def.startBlock ?? 1;
878
946
  let toBlock;
879
947
  if (opts?.toBlock != null) {
880
948
  toBlock = opts.toBlock;
881
949
  } else {
882
950
  const progress = await db.selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
883
- toBlock = progress?.last_indexed_block ?? progress?.last_contiguous_block ?? 0;
951
+ toBlock = progress?.highest_seen_block ?? 0;
884
952
  }
885
953
  return { fromBlock, toBlock };
886
954
  }
@@ -898,30 +966,48 @@ async function reindexSubgraph(def, opts) {
898
966
  await client.unsafe(stmt);
899
967
  }
900
968
  logger4.info("Schema recreated for reindex", { subgraph: subgraphName });
901
- const { fromBlock, toBlock } = await resolveBlockRange(db, opts);
969
+ const { fromBlock, toBlock } = await resolveBlockRange(db, def, opts);
902
970
  if (fromBlock > toBlock) {
903
- logger4.info("No blocks to reindex", { subgraph: subgraphName, fromBlock, toBlock });
971
+ logger4.info("No blocks to reindex", {
972
+ subgraph: subgraphName,
973
+ fromBlock,
974
+ toBlock
975
+ });
904
976
  await updateSubgraphStatus2(db, subgraphName, "active", 0);
905
977
  return { processed: 0 };
906
978
  }
907
- logger4.info("Reindexing blocks", { subgraph: subgraphName, fromBlock, toBlock, totalBlocks: toBlock - fromBlock + 1 });
908
- const subgraphRow = await db.selectFrom("subgraphs").select("api_key_id").where("name", "=", subgraphName).executeTakeFirst();
979
+ logger4.info("Reindexing blocks", {
980
+ subgraph: subgraphName,
981
+ fromBlock,
982
+ toBlock,
983
+ totalBlocks: toBlock - fromBlock + 1
984
+ });
985
+ const subgraphRow = await db.selectFrom("subgraphs").select(["id", "api_key_id"]).where("name", "=", subgraphName).executeTakeFirst();
909
986
  const result = await processBlockRange(def, {
910
987
  fromBlock,
911
988
  toBlock,
912
989
  status: "reindexing",
913
990
  isCatchup: false,
914
- apiKeyId: subgraphRow?.api_key_id ?? null
991
+ apiKeyId: subgraphRow?.api_key_id ?? null,
992
+ subgraphId: subgraphRow?.id
915
993
  });
916
994
  const { recordSubgraphProcessed: recordSubgraphProcessed2 } = await import("@secondlayer/shared/db/queries/subgraphs");
917
995
  if (result.totalEventsProcessed > 0 || result.totalErrors > 0) {
918
996
  await recordSubgraphProcessed2(db, subgraphName, result.totalEventsProcessed, result.totalErrors, result.totalErrors > 0 ? `${result.totalErrors} error(s) during reindex` : undefined);
919
997
  }
920
998
  await updateSubgraphStatus2(db, subgraphName, "active", toBlock);
921
- logger4.info("Reindex complete", { subgraph: subgraphName, blocks: result.blocksProcessed, events: result.totalEventsProcessed, errors: result.totalErrors });
999
+ logger4.info("Reindex complete", {
1000
+ subgraph: subgraphName,
1001
+ blocks: result.blocksProcessed,
1002
+ events: result.totalEventsProcessed,
1003
+ errors: result.totalErrors
1004
+ });
922
1005
  return { processed: result.blocksProcessed };
923
1006
  } catch (err) {
924
- logger4.error("Reindex failed", { subgraph: subgraphName, error: getErrorMessage2(err) });
1007
+ logger4.error("Reindex failed", {
1008
+ subgraph: subgraphName,
1009
+ error: getErrorMessage2(err)
1010
+ });
925
1011
  await updateSubgraphStatus2(db, subgraphName, "error");
926
1012
  throw err;
927
1013
  }
@@ -929,24 +1015,44 @@ async function reindexSubgraph(def, opts) {
929
1015
  async function backfillSubgraph(def, opts) {
930
1016
  const db = getDb2();
931
1017
  const subgraphName = def.name;
932
- logger4.info("Backfill starting", { subgraph: subgraphName, from: opts.fromBlock, to: opts.toBlock });
1018
+ logger4.info("Backfill starting", {
1019
+ subgraph: subgraphName,
1020
+ from: opts.fromBlock,
1021
+ to: opts.toBlock
1022
+ });
933
1023
  try {
934
- const subgraphRow = await db.selectFrom("subgraphs").select("api_key_id").where("name", "=", subgraphName).executeTakeFirst();
1024
+ const subgraphRow = await db.selectFrom("subgraphs").select(["id", "api_key_id"]).where("name", "=", subgraphName).executeTakeFirst();
935
1025
  const result = await processBlockRange(def, {
936
1026
  fromBlock: opts.fromBlock,
937
1027
  toBlock: opts.toBlock,
938
1028
  status: "active",
939
1029
  isCatchup: false,
940
- apiKeyId: subgraphRow?.api_key_id ?? null
1030
+ apiKeyId: subgraphRow?.api_key_id ?? null,
1031
+ subgraphId: subgraphRow?.id
941
1032
  });
1033
+ const resolved = await resolveGaps(db, subgraphName, opts.fromBlock, opts.toBlock).catch(() => 0);
1034
+ if (resolved > 0) {
1035
+ logger4.info("Resolved subgraph gaps via backfill", {
1036
+ subgraph: subgraphName,
1037
+ resolved
1038
+ });
1039
+ }
942
1040
  const { recordSubgraphProcessed: recordSubgraphProcessed2 } = await import("@secondlayer/shared/db/queries/subgraphs");
943
1041
  if (result.totalEventsProcessed > 0 || result.totalErrors > 0) {
944
1042
  await recordSubgraphProcessed2(db, subgraphName, result.totalEventsProcessed, result.totalErrors, result.totalErrors > 0 ? `${result.totalErrors} error(s) during backfill` : undefined);
945
1043
  }
946
- logger4.info("Backfill complete", { subgraph: subgraphName, blocks: result.blocksProcessed, events: result.totalEventsProcessed, errors: result.totalErrors });
1044
+ logger4.info("Backfill complete", {
1045
+ subgraph: subgraphName,
1046
+ blocks: result.blocksProcessed,
1047
+ events: result.totalEventsProcessed,
1048
+ errors: result.totalErrors
1049
+ });
947
1050
  return { processed: result.blocksProcessed };
948
1051
  } catch (err) {
949
- logger4.error("Backfill failed", { subgraph: subgraphName, error: getErrorMessage2(err) });
1052
+ logger4.error("Backfill failed", {
1053
+ subgraph: subgraphName,
1054
+ error: getErrorMessage2(err)
1055
+ });
950
1056
  throw err;
951
1057
  }
952
1058
  }
@@ -1008,11 +1114,19 @@ async function deploySchema(db, def, handlerPath, opts) {
1008
1114
  const regData = {
1009
1115
  name: def.name,
1010
1116
  version: def.version || "1.0.0",
1011
- definition: toJsonSafe({ name: def.name, version: def.version, description: def.description, sources: def.sources, schema: def.schema }),
1117
+ definition: toJsonSafe({
1118
+ name: def.name,
1119
+ version: def.version,
1120
+ description: def.description,
1121
+ startBlock: def.startBlock,
1122
+ sources: def.sources,
1123
+ schema: def.schema
1124
+ }),
1012
1125
  schemaHash: hash,
1013
1126
  handlerPath,
1014
1127
  apiKeyId: opts?.apiKeyId,
1015
- schemaName
1128
+ schemaName,
1129
+ startBlock: def.startBlock
1016
1130
  };
1017
1131
  if (existing) {
1018
1132
  if (existing.schema_hash === hash && !opts?.forceReindex) {
@@ -1128,5 +1242,5 @@ export {
1128
1242
  backfillSubgraph
1129
1243
  };
1130
1244
 
1131
- //# debugId=FAC592B26F4F381E64756E2164756E21
1245
+ //# debugId=DED4A221B27D85F264756E2164756E21
1132
1246
  //# sourceMappingURL=index.js.map