@secondlayer/subgraphs 0.5.6 → 0.6.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.
@@ -122,53 +122,83 @@ class SubgraphContext {
122
122
  }
123
123
  return opsToFlush.length;
124
124
  }
125
+ prepareInsert(op) {
126
+ const upsertKeys = op.data._upsert_keys;
127
+ const data = { ...op.data };
128
+ delete data._upsert_keys;
129
+ delete data._upsert_fallback_keys;
130
+ delete data._upsert_fallback_set;
131
+ data._block_height = this.block.height;
132
+ data._tx_id = this._tx.txId;
133
+ data._created_at = "NOW()";
134
+ const cols = Object.keys(data);
135
+ cols.forEach(validateColumnName);
136
+ const vals = cols.map((c) => data[c] === "NOW()" ? "NOW()" : escapeLiteral(data[c]));
137
+ const batchKey = `${op.table}:${[...cols].sort().join(",")}:${upsertKeys ? [...upsertKeys].sort().join(",") : ""}`;
138
+ return { data, cols, vals, upsertKeys, batchKey };
139
+ }
125
140
  buildStatements(ops) {
126
141
  const statements = [];
142
+ let currentBatch = null;
143
+ let currentBatchKey = "";
144
+ const flushInsertBatch = () => {
145
+ if (!currentBatch)
146
+ return;
147
+ const qualifiedTable = `"${this.pgSchemaName}"."${currentBatch.table}"`;
148
+ const colList = currentBatch.cols.map((c) => `"${c}"`).join(", ");
149
+ let rows = currentBatch.rows;
150
+ if (currentBatch.upsertKeys && currentBatch.upsertKeys.length > 0) {
151
+ const keyIndices = currentBatch.upsertKeys.map((k) => currentBatch.cols.indexOf(k));
152
+ const seen = new Map;
153
+ for (let i = 0;i < rows.length; i++) {
154
+ const key = keyIndices.map((ki) => rows[i][ki]).join("\x00");
155
+ seen.set(key, i);
156
+ }
157
+ if (seen.size < rows.length) {
158
+ rows = Array.from(seen.values()).map((i) => rows[i]);
159
+ }
160
+ }
161
+ const valuesList = rows.map((r) => `(${r.join(", ")})`).join(", ");
162
+ let stmt = `INSERT INTO ${qualifiedTable} (${colList}) VALUES ${valuesList}`;
163
+ if (currentBatch.upsertKeys && currentBatch.upsertKeys.length > 0) {
164
+ const updateCols = currentBatch.cols.filter((c) => !currentBatch.upsertKeys.includes(c) && !c.startsWith("_"));
165
+ if (updateCols.length > 0) {
166
+ const setClauses = updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`);
167
+ stmt += ` ON CONFLICT (${currentBatch.upsertKeys.map((k) => `"${k}"`).join(", ")}) DO UPDATE SET ${setClauses.join(", ")}`;
168
+ } else {
169
+ stmt += ` ON CONFLICT (${currentBatch.upsertKeys.map((k) => `"${k}"`).join(", ")}) DO NOTHING`;
170
+ }
171
+ }
172
+ statements.push(stmt);
173
+ currentBatch = null;
174
+ currentBatchKey = "";
175
+ };
127
176
  for (const op of ops) {
128
177
  const qualifiedTable = `"${this.pgSchemaName}"."${op.table}"`;
129
- switch (op.kind) {
130
- case "insert": {
131
- const upsertKeys = op.data._upsert_keys;
132
- const fallbackKeys = op.data._upsert_fallback_keys;
133
- const fallbackSet = op.data._upsert_fallback_set;
134
- const data = { ...op.data };
135
- delete data._upsert_keys;
136
- delete data._upsert_fallback_keys;
137
- delete data._upsert_fallback_set;
138
- data._block_height = this.block.height;
139
- data._tx_id = this._tx.txId;
140
- data._created_at = "NOW()";
141
- const cols = Object.keys(data);
142
- cols.forEach(validateColumnName);
143
- const vals = cols.map((c) => data[c] === "NOW()" ? "NOW()" : escapeLiteral(data[c]));
144
- let stmt = `INSERT INTO ${qualifiedTable} (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${vals.join(", ")})`;
145
- if (upsertKeys && upsertKeys.length > 0) {
146
- const updateCols = cols.filter((c) => !upsertKeys.includes(c) && !c.startsWith("_"));
147
- if (updateCols.length > 0) {
148
- const setClauses = updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`);
149
- stmt += ` ON CONFLICT (${upsertKeys.map((k) => `"${k}"`).join(", ")}) DO UPDATE SET ${setClauses.join(", ")}`;
150
- } else {
151
- stmt += ` ON CONFLICT (${upsertKeys.map((k) => `"${k}"`).join(", ")}) DO NOTHING`;
152
- }
153
- } else if (fallbackKeys && fallbackSet) {}
154
- statements.push(stmt);
155
- break;
178
+ if (op.kind === "insert") {
179
+ const { cols, vals, upsertKeys, batchKey } = this.prepareInsert(op);
180
+ if (batchKey === currentBatchKey && currentBatch) {
181
+ currentBatch.rows.push(vals);
182
+ } else {
183
+ flushInsertBatch();
184
+ currentBatch = { table: op.table, cols, rows: [vals], upsertKeys };
185
+ currentBatchKey = batchKey;
156
186
  }
157
- case "update": {
187
+ } else {
188
+ flushInsertBatch();
189
+ if (op.kind === "update") {
158
190
  const setEntries = Object.entries(op.set);
159
191
  setEntries.forEach(([k]) => validateColumnName(k));
160
192
  const setClauses = setEntries.map(([k, v]) => `"${k}" = ${escapeLiteral(v)}`);
161
193
  const { clause } = buildWhereClause(op.data);
162
194
  statements.push(`UPDATE ${qualifiedTable} SET ${setClauses.join(", ")} WHERE ${clause}`);
163
- break;
164
- }
165
- case "delete": {
195
+ } else if (op.kind === "delete") {
166
196
  const { clause } = buildWhereClause(op.data);
167
197
  statements.push(`DELETE FROM ${qualifiedTable} WHERE ${clause}`);
168
- break;
169
198
  }
170
199
  }
171
200
  }
201
+ flushInsertBatch();
172
202
  return statements;
173
203
  }
174
204
  validateTable(table) {
@@ -418,6 +448,7 @@ import { updateSubgraphStatus, recordSubgraphProcessed } from "@secondlayer/shar
418
448
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
419
449
 
420
450
  // src/runtime/block-processor.ts
451
+ var schemaNameCache = new Map;
421
452
  async function processBlock(subgraph, subgraphName, blockHeight, opts) {
422
453
  const db = getDb();
423
454
  const blockStart = performance.now();
@@ -428,19 +459,26 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
428
459
  errors: 0,
429
460
  skipped: false
430
461
  };
431
- const block = await db.selectFrom("blocks").selectAll().where("height", "=", blockHeight).executeTakeFirst();
432
- if (!block) {
433
- logger3.warn("Block not found for subgraph processing", { subgraph: subgraphName, blockHeight });
434
- result.skipped = true;
435
- return result;
436
- }
437
- if (!block.canonical) {
438
- logger3.debug("Skipping non-canonical block", { subgraph: subgraphName, blockHeight });
439
- result.skipped = true;
440
- return result;
462
+ let block, txs, evts;
463
+ if (opts?.preloaded) {
464
+ block = opts.preloaded.block;
465
+ txs = opts.preloaded.txs;
466
+ evts = opts.preloaded.events;
467
+ } else {
468
+ block = await db.selectFrom("blocks").selectAll().where("height", "=", blockHeight).executeTakeFirst();
469
+ if (!block) {
470
+ logger3.warn("Block not found for subgraph processing", { subgraph: subgraphName, blockHeight });
471
+ result.skipped = true;
472
+ return result;
473
+ }
474
+ if (!block.canonical) {
475
+ logger3.debug("Skipping non-canonical block", { subgraph: subgraphName, blockHeight });
476
+ result.skipped = true;
477
+ return result;
478
+ }
479
+ txs = await db.selectFrom("transactions").selectAll().where("block_height", "=", blockHeight).execute();
480
+ evts = await db.selectFrom("events").selectAll().where("block_height", "=", blockHeight).execute();
441
481
  }
442
- const txs = await db.selectFrom("transactions").selectAll().where("block_height", "=", blockHeight).execute();
443
- const evts = await db.selectFrom("events").selectAll().where("block_height", "=", blockHeight).execute();
444
482
  const matched = matchSources(subgraph.sources, txs, evts);
445
483
  result.matched = matched.length;
446
484
  if (matched.length === 0) {
@@ -449,8 +487,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
449
487
  }
450
488
  return result;
451
489
  }
452
- const subgraphRecord = await db.selectFrom("subgraphs").select("schema_name").where("name", "=", subgraphName).executeTakeFirst();
453
- const schemaName = subgraphRecord?.schema_name ?? pgSchemaName(subgraphName);
490
+ let schemaName = schemaNameCache.get(subgraphName);
491
+ if (!schemaName) {
492
+ const subgraphRecord = await db.selectFrom("subgraphs").select("schema_name").where("name", "=", subgraphName).executeTakeFirst();
493
+ schemaName = subgraphRecord?.schema_name ?? pgSchemaName(subgraphName);
494
+ schemaNameCache.set(subgraphName, schemaName);
495
+ }
454
496
  const blockMeta = {
455
497
  height: block.height,
456
498
  hash: block.hash,
@@ -578,8 +620,62 @@ class StatsAccumulator {
578
620
  import { getDb as getDb2 } from "@secondlayer/shared/db";
579
621
  import { logger as logger4 } from "@secondlayer/shared/logger";
580
622
  import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
623
+
624
+ // src/runtime/batch-loader.ts
625
+ async function loadBlockRange(db, fromHeight, toHeight) {
626
+ const [blocks, txs, events] = await Promise.all([
627
+ db.selectFrom("blocks").selectAll().where("height", ">=", fromHeight).where("height", "<=", toHeight).where("canonical", "=", true).execute(),
628
+ db.selectFrom("transactions").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
629
+ db.selectFrom("events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute()
630
+ ]);
631
+ const txsByHeight = new Map;
632
+ for (const tx of txs) {
633
+ const h = Number(tx.block_height);
634
+ const list = txsByHeight.get(h) ?? [];
635
+ list.push(tx);
636
+ txsByHeight.set(h, list);
637
+ }
638
+ const eventsByHeight = new Map;
639
+ for (const evt of events) {
640
+ const h = Number(evt.block_height);
641
+ const list = eventsByHeight.get(h) ?? [];
642
+ list.push(evt);
643
+ eventsByHeight.set(h, list);
644
+ }
645
+ const result = new Map;
646
+ for (const block of blocks) {
647
+ const h = Number(block.height);
648
+ result.set(h, {
649
+ block,
650
+ txs: txsByHeight.get(h) ?? [],
651
+ events: eventsByHeight.get(h) ?? []
652
+ });
653
+ }
654
+ return result;
655
+ }
656
+ function avgEventsPerBlock(batch) {
657
+ if (batch.size === 0)
658
+ return 0;
659
+ let totalEvents = 0;
660
+ for (const data of batch.values()) {
661
+ totalEvents += data.events.length;
662
+ }
663
+ return totalEvents / batch.size;
664
+ }
665
+
666
+ // src/runtime/catchup.ts
581
667
  var LOG_INTERVAL = 1000;
668
+ var DEFAULT_BATCH_SIZE = 500;
669
+ var MIN_BATCH_SIZE = 100;
670
+ var MAX_BATCH_SIZE = 1000;
582
671
  var catchingUp = new Set;
672
+ function adjustBatchSize(current, avgEvents) {
673
+ if (avgEvents > 50)
674
+ return Math.max(Math.round(current * 0.5), MIN_BATCH_SIZE);
675
+ if (avgEvents < 10)
676
+ return Math.min(Math.round(current * 1.5), MAX_BATCH_SIZE);
677
+ return current;
678
+ }
583
679
  async function catchUpSubgraph(subgraph, subgraphName) {
584
680
  if (catchingUp.has(subgraphName))
585
681
  return 0;
@@ -606,24 +702,59 @@ async function catchUpSubgraph(subgraph, subgraphName) {
606
702
  });
607
703
  const stats = new StatsAccumulator(subgraphName, subgraphRow.api_key_id, true);
608
704
  let processed = 0;
609
- for (let height = startBlock;height <= chainTip; height++) {
610
- const result = await processBlock(subgraph, subgraphName, height);
611
- processed++;
612
- if (result.timing) {
613
- stats.record(result.timing, result.processed);
614
- if (stats.shouldFlush()) {
615
- await stats.flush(db);
616
- }
705
+ let batchSize = DEFAULT_BATCH_SIZE;
706
+ let currentHeight = startBlock;
707
+ let nextBatchPromise = loadBlockRange(db, currentHeight, Math.min(currentHeight + batchSize - 1, chainTip));
708
+ while (currentHeight <= chainTip) {
709
+ const batch = await nextBatchPromise;
710
+ const batchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
711
+ const nextStart = batchEnd + 1;
712
+ if (nextStart <= chainTip) {
713
+ const nextEnd = Math.min(nextStart + batchSize - 1, chainTip);
714
+ nextBatchPromise = loadBlockRange(db, nextStart, nextEnd);
617
715
  }
618
- if (processed % LOG_INTERVAL === 0) {
619
- logger4.info("Subgraph catch-up progress", {
620
- subgraph: subgraphName,
621
- processed,
622
- total: totalBlocks,
623
- currentBlock: height,
624
- pct: Math.round(processed / totalBlocks * 100)
625
- });
716
+ for (let height = currentHeight;height <= batchEnd; height++) {
717
+ const blockData = batch.get(height);
718
+ if (!blockData) {
719
+ processed++;
720
+ continue;
721
+ }
722
+ let result;
723
+ try {
724
+ result = await processBlock(subgraph, subgraphName, height, {
725
+ preloaded: blockData
726
+ });
727
+ } catch (err) {
728
+ logger4.error("Block processing error during catch-up", {
729
+ subgraph: subgraphName,
730
+ blockHeight: height,
731
+ error: err instanceof Error ? err.message : String(err)
732
+ });
733
+ const { updateSubgraphStatus: updateSubgraphStatus2 } = await import("@secondlayer/shared/db/queries/subgraphs");
734
+ await updateSubgraphStatus2(db, subgraphName, "active", height).catch(() => {});
735
+ processed++;
736
+ continue;
737
+ }
738
+ processed++;
739
+ if (result.timing) {
740
+ stats.record(result.timing, result.processed);
741
+ if (stats.shouldFlush()) {
742
+ await stats.flush(db);
743
+ }
744
+ }
745
+ if (processed % LOG_INTERVAL === 0) {
746
+ logger4.info("Subgraph catch-up progress", {
747
+ subgraph: subgraphName,
748
+ processed,
749
+ total: totalBlocks,
750
+ currentBlock: height,
751
+ pct: Math.round(processed / totalBlocks * 100)
752
+ });
753
+ }
626
754
  }
755
+ const avg = avgEventsPerBlock(batch);
756
+ batchSize = adjustBatchSize(batchSize, avg);
757
+ currentHeight = batchEnd + 1;
627
758
  }
628
759
  await stats.flush(db);
629
760
  logger4.info("Subgraph catch-up complete", {
@@ -650,7 +781,7 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
650
781
  logger5.info("Propagating reorg to subgraphs", { blockHeight, subgraphCount: activeSubgraphs.length });
651
782
  for (const sg of activeSubgraphs) {
652
783
  try {
653
- const schema = sg.definition?.schema;
784
+ const schema = sg.definition.schema;
654
785
  if (!schema)
655
786
  continue;
656
787
  const schemaName = sg.schema_name ?? pgSchemaName(sg.name);
@@ -789,5 +920,5 @@ var shutdown = async () => {
789
920
  process.on("SIGINT", shutdown);
790
921
  process.on("SIGTERM", shutdown);
791
922
 
792
- //# debugId=3ED765B9AA3142E064756E2164756E21
923
+ //# debugId=4CDF75EDA895AA0964756E2164756E21
793
924
  //# sourceMappingURL=service.js.map