@secondlayer/subgraphs 3.25.1 → 3.26.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.
@@ -345,6 +345,29 @@ interface SubgraphDefinition {
345
345
  /** Handler functions — keys must match source names (or "*" for catch-all) */
346
346
  handlers: Record<string, SubgraphHandler>;
347
347
  }
348
+ import { Block, Event, Transaction } from "@secondlayer/shared/db";
349
+ interface BlockData {
350
+ block: Block;
351
+ txs: Transaction[];
352
+ events: Event[];
353
+ }
354
+ /**
355
+ * Where the subgraph runtime reads canonical chain data. Today it taps the
356
+ * indexer Postgres directly (`PostgresBlockSource`); the re-point adds a
357
+ * `PublicApiBlockSource` that consumes the Streams clock + Index data over
358
+ * HTTP. `matchSources` / handlers / flush / outbox are unchanged — only the
359
+ * loader + tip swap behind this seam.
360
+ */
361
+ interface BlockSource {
362
+ /** Highest canonical block height available to process. */
363
+ getTip(): Promise<number>;
364
+ /** Canonical block data for [fromHeight, toHeight], keyed by height. */
365
+ loadBlockRange(fromHeight: number, toHeight: number): Promise<Map<number, BlockData>>;
366
+ /** Sparse-scan probe: lowest height in (afterHeight, untilHeight] holding
367
+ * an event this source's subgraph could match, or null when the rest of
368
+ * the range is empty. Optional — only event-scoped sources support it. */
369
+ nextDataHeight?(afterHeight: number, untilHeight: number): Promise<number | null>;
370
+ }
348
371
  type ReindexBatchConfig = {
349
372
  defaultBatchSize: number
350
373
  minBatchSize: number
@@ -364,16 +387,49 @@ declare function initialReindexProgressBlock(fromBlock: number): number;
364
387
  declare function resolveReindexResumeBlock(row: ReindexResumeRow): number | null;
365
388
  declare function resolveReindexBatchConfig(env?: ReindexBatchEnv): ReindexBatchConfig;
366
389
  interface ReindexOptions {
367
- fromBlock?: number;
368
- toBlock?: number;
390
+ /**
391
+ * Policy floor on the walk's first block — NOT a caller-supplied range.
392
+ * Free-tier accounts may not reindex below their registered `start_block`
393
+ * (see `resolveGenesisPolicy` on the API side); this raises the walk start
394
+ * and can never lower it. There is deliberately no `toBlock` counterpart:
395
+ * a reindex always runs to chain tip. See {@link resolveBlockRange}.
396
+ */
397
+ startBlockFloor?: number;
369
398
  schemaName?: string;
370
399
  /** Op row to receive processed_events on each progress flush. */
371
400
  operationId?: string;
372
401
  signal?: AbortSignal;
373
402
  }
374
403
  /**
404
+ * Resolve the reindex walk range. Always the whole subgraph:
405
+ * `[def.startBlock, chain tip]`. Chain tip reads from `index_progress` —
406
+ * lives in the source DB.
407
+ *
408
+ * Reindex drops the schema unconditionally, so the walk range is also the
409
+ * ONLY data that survives. A narrower walk therefore does not "reindex a
410
+ * range" — it silently destroys every row outside it while leaving the
411
+ * subgraph reporting `active` at chain tip (f079: a single-block
412
+ * `--from-block N --to-block N` emptied sbtc-flows, and the first recovery
413
+ * attempt rebuilt only 21% of its history). Ranged, additive work is
414
+ * `backfillSubgraph`'s job.
415
+ *
416
+ * `startBlockFloor` is a plan-policy floor, not a range: it can only raise
417
+ * the start block, never lower it, and never bounds the end.
418
+ */
419
+ declare function resolveBlockRange(source: BlockSource, def: SubgraphDefinition, opts?: ReindexOptions): Promise<{
420
+ fromBlock: number
421
+ toBlock: number
422
+ }>;
423
+ /**
375
424
  * Reindex a subgraph by dropping its tables, recreating them, and reprocessing
376
425
  * all historical blocks through the handler pipeline.
426
+ *
427
+ * Always whole-subgraph: `[start_block, chain tip]`. There is no ranged
428
+ * reindex — the drop cannot be scoped to a range without re-running delta
429
+ * handlers (`ctx.increment` / `ctx.update` / `ctx.patchOrInsert`) over rows
430
+ * that were never removed, which is the exact double-count hazard `backfill`
431
+ * already refuses. Use `backfillSubgraph` for ranged, additive work.
432
+ *
377
433
  * Supports cancellation via AbortSignal — on shutdown abort, status stays
378
434
  * "reindexing" for auto-resume; on user cancel, status resets to "active".
379
435
  */
@@ -405,4 +461,4 @@ declare function backfillSubgraph(def: SubgraphDefinition, opts: {
405
461
  }): Promise<{
406
462
  processed: number
407
463
  }>;
408
- export { resumeReindex, resolveReindexResumeBlock, resolveReindexBatchConfig, reindexSubgraph, initialReindexProgressBlock, backfillSubgraph, ReindexOptions };
464
+ export { resumeReindex, resolveReindexResumeBlock, resolveReindexBatchConfig, resolveBlockRange, reindexSubgraph, initialReindexProgressBlock, backfillSubgraph, ReindexOptions };
@@ -605,14 +605,10 @@ class SubgraphContext {
605
605
  this.ops.length = 0;
606
606
  const statements = this.buildStatements(opsToFlush);
607
607
  if ("isTransaction" in this.db) {
608
- for (const stmt of statements) {
609
- await sql.raw(stmt).execute(this.db);
610
- }
608
+ await this.execStatements(this.db, statements);
611
609
  } else {
612
610
  await this.db.transaction().execute(async (tx) => {
613
- for (const stmt of statements) {
614
- await sql.raw(stmt).execute(tx);
615
- }
611
+ await this.execStatements(tx, statements);
616
612
  });
617
613
  }
618
614
  const writes = opsToFlush.map((op, rowIndex) => {
@@ -631,6 +627,25 @@ class SubgraphContext {
631
627
  });
632
628
  return { count: opsToFlush.length, writes };
633
629
  }
630
+ async execStatements(executor, statements) {
631
+ const affected = (r) => r.numAffectedRows ?? 0n;
632
+ for (const stmt of statements) {
633
+ if (typeof stmt === "string") {
634
+ await sql.raw(stmt).execute(executor);
635
+ continue;
636
+ }
637
+ const updated = await sql.raw(stmt.update).execute(executor);
638
+ if (affected(updated) > 0n)
639
+ continue;
640
+ const inserted = await sql.raw(stmt.insert).execute(executor);
641
+ if (affected(inserted) > 0n)
642
+ continue;
643
+ const retried = await sql.raw(stmt.update).execute(executor);
644
+ if (affected(retried) === 0n) {
645
+ throw new Error(`increment could not be applied to ${this.pgSchemaName}.${stmt.table} where ${stmt.keyPred}: row neither updated nor inserted`);
646
+ }
647
+ }
648
+ }
634
649
  prepareInsert(op) {
635
650
  const upsertKeys = op.data._upsert_keys;
636
651
  const data = { ...op.data };
@@ -710,7 +725,6 @@ class SubgraphContext {
710
725
  for (const r of batch.rows.values()) {
711
726
  const keyPred = batch.keyCols.map((k) => `"${k}" = ${escapeLiteral(r.keys[k])}`).join(" AND ");
712
727
  const updSet = batch.deltaCols.map((c) => `"${c}" = COALESCE("${c}", 0) + (${String(r.deltas[c] ?? 0n)})`).join(", ");
713
- statements.push(`UPDATE ${qualifiedTable} SET ${updSet} WHERE ${keyPred}`);
714
728
  const insertVals = [
715
729
  ...batch.keyCols.map((k) => escapeLiteral(r.keys[k])),
716
730
  ...batch.deltaCols.map((c) => String(r.deltas[c] ?? 0n)),
@@ -718,7 +732,13 @@ class SubgraphContext {
718
732
  escapeLiteral(r.meta.txId),
719
733
  "NOW()"
720
734
  ];
721
- statements.push(`INSERT INTO ${qualifiedTable} (${cols.map((c) => `"${c}"`).join(", ")}) ` + `SELECT ${insertVals.join(", ")} ` + `WHERE NOT EXISTS (SELECT 1 FROM ${qualifiedTable} WHERE ${keyPred})`);
735
+ statements.push({
736
+ kind: "increment",
737
+ table: batch.table,
738
+ keyPred,
739
+ update: `UPDATE ${qualifiedTable} SET ${updSet} WHERE ${keyPred}`,
740
+ insert: `INSERT INTO ${qualifiedTable} (${cols.map((c) => `"${c}"`).join(", ")}) ` + `SELECT ${insertVals.join(", ")} ` + `WHERE NOT EXISTS (SELECT 1 FROM ${qualifiedTable} WHERE ${keyPred})`
741
+ });
722
742
  }
723
743
  incBatch = null;
724
744
  incBatchKey = "";
@@ -3353,9 +3373,9 @@ async function processBlockRange(def, opts) {
3353
3373
  return { blocksProcessed, totalEventsProcessed, totalErrors, aborted };
3354
3374
  }
3355
3375
  async function resolveBlockRange(source, def, opts) {
3356
- const fromBlock = opts?.fromBlock ?? def.startBlock ?? 1;
3357
- const toBlock = opts?.toBlock != null ? opts.toBlock : await source.getTip();
3358
- return { fromBlock, toBlock };
3376
+ const definitionStart = def.startBlock ?? 1;
3377
+ const fromBlock = opts?.startBlockFloor != null ? Math.max(definitionStart, opts.startBlockFloor) : definitionStart;
3378
+ return { fromBlock, toBlock: await source.getTip() };
3359
3379
  }
3360
3380
  async function clearReindexMetadata(db, subgraphName) {
3361
3381
  await db.updateTable("subgraphs").set({ reindex_from_block: null, reindex_to_block: null }).where("name", "=", subgraphName).execute();
@@ -3576,10 +3596,11 @@ export {
3576
3596
  resumeReindex,
3577
3597
  resolveReindexResumeBlock,
3578
3598
  resolveReindexBatchConfig,
3599
+ resolveBlockRange,
3579
3600
  reindexSubgraph,
3580
3601
  initialReindexProgressBlock,
3581
3602
  backfillSubgraph
3582
3603
  };
3583
3604
 
3584
- //# debugId=9F4CFC4A2A15675F64756E2164756E21
3605
+ //# debugId=E95B0AF0DF971D5864756E2164756E21
3585
3606
  //# sourceMappingURL=reindex.js.map