@secondlayer/subgraphs 1.3.1 → 1.3.3

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.
@@ -187,9 +187,9 @@ class SubgraphContext {
187
187
  const blockHeight = op.data._block_height ?? this.block.height;
188
188
  const txId = op.data._tx_id ?? this._tx.txId;
189
189
  const baseRow = op.kind === "update" ? { ...op.data, ...op.set ?? {} } : { ...op.data };
190
- delete baseRow._upsert_keys;
191
- delete baseRow._upsert_fallback_keys;
192
- delete baseRow._upsert_fallback_set;
190
+ baseRow._upsert_keys = undefined;
191
+ baseRow._upsert_fallback_keys = undefined;
192
+ baseRow._upsert_fallback_set = undefined;
193
193
  return {
194
194
  op: op.kind,
195
195
  table: op.table,
@@ -1336,16 +1336,16 @@ function generateSubgraphSQL(def, schemaNameOverride) {
1336
1336
  const statements = [];
1337
1337
  const needsTrgm = Object.values(def.schema).some((table) => Object.values(table.columns).some((col) => col.search));
1338
1338
  if (needsTrgm) {
1339
- statements.push(`CREATE EXTENSION IF NOT EXISTS pg_trgm`);
1339
+ statements.push("CREATE EXTENSION IF NOT EXISTS pg_trgm");
1340
1340
  }
1341
1341
  statements.push(`CREATE SCHEMA IF NOT EXISTS ${schemaName}`);
1342
1342
  for (const [tableName, tableDef] of Object.entries(def.schema)) {
1343
1343
  const qualifiedName = `${schemaName}.${tableName}`;
1344
1344
  const columnDefs = [
1345
- `_id BIGSERIAL PRIMARY KEY`,
1346
- `_block_height BIGINT NOT NULL`,
1347
- `_tx_id TEXT NOT NULL`,
1348
- `_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`
1345
+ "_id BIGSERIAL PRIMARY KEY",
1346
+ "_block_height BIGINT NOT NULL",
1347
+ "_tx_id TEXT NOT NULL",
1348
+ "_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()"
1349
1349
  ];
1350
1350
  for (const [colName, col] of Object.entries(tableDef.columns)) {
1351
1351
  const sqlType = TYPE_MAP[col.type];
@@ -1441,6 +1441,7 @@ function avgEventsPerBlock(batch) {
1441
1441
  // src/runtime/reindex.ts
1442
1442
  var LOG_INTERVAL = 1000;
1443
1443
  var HEALTH_FLUSH_INTERVAL = 1000;
1444
+ var PROGRESS_FLUSH_INTERVAL_MS = 5000;
1444
1445
  var HOBBY_REINDEX_BATCH_CONFIG = {
1445
1446
  defaultBatchSize: 50,
1446
1447
  minBatchSize: 25,
@@ -1515,6 +1516,8 @@ async function processBlockRange(def, opts) {
1515
1516
  let pendingErrors = 0;
1516
1517
  let pendingLastError;
1517
1518
  let lastHealthFlushBlock = 0;
1519
+ let lastHealthFlushAt = Date.now();
1520
+ let lastProgressFlushAt = Date.now();
1518
1521
  const batchConfig = resolveReindexBatchConfig();
1519
1522
  let batchSize = batchConfig.defaultBatchSize;
1520
1523
  let currentHeight = fromBlock;
@@ -1527,6 +1530,7 @@ async function processBlockRange(def, opts) {
1527
1530
  pendingErrors = 0;
1528
1531
  pendingLastError = undefined;
1529
1532
  lastHealthFlushBlock = blocksProcessed;
1533
+ lastHealthFlushAt = Date.now();
1530
1534
  };
1531
1535
  let nextBatchEnd = Math.min(currentHeight + batchSize - 1, toBlock);
1532
1536
  let nextBatchPromise = loadBlockRange(sourceDb, currentHeight, nextBatchEnd);
@@ -1590,8 +1594,11 @@ async function processBlockRange(def, opts) {
1590
1594
  await stats.flush(targetDb);
1591
1595
  }
1592
1596
  }
1593
- if (blocksProcessed % 100 === 0) {
1597
+ const now = Date.now();
1598
+ const shouldFlushProgress = blocksProcessed % 100 === 0 || now - lastProgressFlushAt >= PROGRESS_FLUSH_INTERVAL_MS;
1599
+ if (shouldFlushProgress) {
1594
1600
  await updateSubgraphStatus2(targetDb, subgraphName, status, height);
1601
+ lastProgressFlushAt = now;
1595
1602
  }
1596
1603
  if (blocksProcessed % LOG_INTERVAL === 0) {
1597
1604
  logger5.info(`${status === "reindexing" ? "Reindex" : "Backfill"} progress`, {
@@ -1603,7 +1610,11 @@ async function processBlockRange(def, opts) {
1603
1610
  });
1604
1611
  }
1605
1612
  }
1606
- if (blocksProcessed - lastHealthFlushBlock >= HEALTH_FLUSH_INTERVAL) {
1613
+ if (status === "reindexing" && blocksProcessed > 0) {
1614
+ await updateSubgraphStatus2(targetDb, subgraphName, status, batchEnd);
1615
+ }
1616
+ const shouldFlushHealth = blocksProcessed - lastHealthFlushBlock >= HEALTH_FLUSH_INTERVAL || Date.now() - lastHealthFlushAt >= PROGRESS_FLUSH_INTERVAL_MS;
1617
+ if (shouldFlushHealth) {
1607
1618
  await flushHealth();
1608
1619
  }
1609
1620
  if (batchFailedBlocks.length > 0 && opts.subgraphId) {
@@ -1849,10 +1860,47 @@ import {
1849
1860
  import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
1850
1861
  import { logger as logger6 } from "@secondlayer/shared/logger";
1851
1862
  var LOG_INTERVAL2 = 1000;
1852
- var DEFAULT_BATCH_SIZE = 500;
1853
- var MIN_BATCH_SIZE = 100;
1854
- var MAX_BATCH_SIZE = 1000;
1863
+ var HOBBY_CATCHUP_BATCH_CONFIG = {
1864
+ defaultBatchSize: 50,
1865
+ minBatchSize: 25,
1866
+ maxBatchSize: 100,
1867
+ prefetch: false
1868
+ };
1869
+ var STANDARD_CATCHUP_BATCH_CONFIG = {
1870
+ defaultBatchSize: 500,
1871
+ minBatchSize: 100,
1872
+ maxBatchSize: 1000,
1873
+ prefetch: true
1874
+ };
1855
1875
  var catchingUp = new Set;
1876
+ function parsePositiveInt2(value) {
1877
+ if (value == null || value.trim() === "")
1878
+ return;
1879
+ const parsed = Number.parseInt(value, 10);
1880
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
1881
+ }
1882
+ function parseBoolean(value) {
1883
+ if (value == null || value.trim() === "")
1884
+ return;
1885
+ const normalized = value.trim().toLowerCase();
1886
+ if (normalized === "true")
1887
+ return true;
1888
+ if (normalized === "false")
1889
+ return false;
1890
+ return;
1891
+ }
1892
+ function resolveCatchupBatchConfig(env = process.env) {
1893
+ const base = env.TENANT_PLAN?.trim().toLowerCase() === "hobby" ? HOBBY_CATCHUP_BATCH_CONFIG : STANDARD_CATCHUP_BATCH_CONFIG;
1894
+ const minBatchSize = parsePositiveInt2(env.SUBGRAPH_CATCHUP_MIN_BATCH_SIZE) ?? base.minBatchSize;
1895
+ const maxBatchSize = parsePositiveInt2(env.SUBGRAPH_CATCHUP_MAX_BATCH_SIZE) ?? base.maxBatchSize;
1896
+ const defaultBatchSize = parsePositiveInt2(env.SUBGRAPH_CATCHUP_BATCH_SIZE) ?? base.defaultBatchSize;
1897
+ return {
1898
+ minBatchSize,
1899
+ maxBatchSize,
1900
+ defaultBatchSize: Math.min(Math.max(defaultBatchSize, minBatchSize), maxBatchSize),
1901
+ prefetch: parseBoolean(env.SUBGRAPH_CATCHUP_PREFETCH) ?? base.prefetch
1902
+ };
1903
+ }
1856
1904
  function coalesceGaps(blocks) {
1857
1905
  if (blocks.length === 0)
1858
1906
  return [];
@@ -1875,11 +1923,11 @@ function coalesceGaps(blocks) {
1875
1923
  ranges.push({ start, end, reason });
1876
1924
  return ranges;
1877
1925
  }
1878
- function adjustBatchSize(current, avgEvents) {
1926
+ function adjustBatchSize(current, avgEvents, config) {
1879
1927
  if (avgEvents > 50)
1880
- return Math.max(Math.round(current * 0.5), MIN_BATCH_SIZE);
1928
+ return Math.max(Math.round(current * 0.5), config.minBatchSize);
1881
1929
  if (avgEvents < 10)
1882
- return Math.min(Math.round(current * 1.5), MAX_BATCH_SIZE);
1930
+ return Math.min(Math.round(current * 1.5), config.maxBatchSize);
1883
1931
  return current;
1884
1932
  }
1885
1933
  async function catchUpSubgraph(subgraph, subgraphName) {
@@ -1910,10 +1958,11 @@ async function catchUpSubgraph(subgraph, subgraphName) {
1910
1958
  });
1911
1959
  const stats = new StatsAccumulator(subgraphName, true);
1912
1960
  let processed = 0;
1913
- let batchSize = DEFAULT_BATCH_SIZE;
1961
+ const batchConfig = resolveCatchupBatchConfig();
1962
+ let batchSize = batchConfig.defaultBatchSize;
1914
1963
  let currentHeight = startBlock;
1915
- let nextBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
1916
- let nextBatchPromise = loadBlockRange(sourceDb, currentHeight, nextBatchEnd);
1964
+ let prefetchedBatchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
1965
+ let nextBatchPromise = batchConfig.prefetch ? loadBlockRange(sourceDb, currentHeight, prefetchedBatchEnd) : undefined;
1917
1966
  while (currentHeight <= chainTip) {
1918
1967
  const currentRow = await getSubgraph(targetDb, subgraphName);
1919
1968
  if (!currentRow || currentRow.status !== "active") {
@@ -1923,12 +1972,21 @@ async function catchUpSubgraph(subgraph, subgraphName) {
1923
1972
  });
1924
1973
  break;
1925
1974
  }
1926
- const batch = await nextBatchPromise;
1927
- const batchEnd = nextBatchEnd;
1928
- const nextStart = batchEnd + 1;
1929
- if (nextStart <= chainTip) {
1930
- nextBatchEnd = Math.min(nextStart + batchSize - 1, chainTip);
1931
- nextBatchPromise = loadBlockRange(sourceDb, nextStart, nextBatchEnd);
1975
+ let batchEnd;
1976
+ let batch;
1977
+ if (nextBatchPromise) {
1978
+ batch = await nextBatchPromise;
1979
+ batchEnd = prefetchedBatchEnd;
1980
+ const nextStart = batchEnd + 1;
1981
+ if (nextStart <= chainTip) {
1982
+ prefetchedBatchEnd = Math.min(nextStart + batchSize - 1, chainTip);
1983
+ nextBatchPromise = loadBlockRange(sourceDb, nextStart, prefetchedBatchEnd);
1984
+ } else {
1985
+ nextBatchPromise = undefined;
1986
+ }
1987
+ } else {
1988
+ batchEnd = Math.min(currentHeight + batchSize - 1, chainTip);
1989
+ batch = await loadBlockRange(sourceDb, currentHeight, batchEnd);
1932
1990
  }
1933
1991
  const batchFailedBlocks = [];
1934
1992
  for (let height = currentHeight;height <= batchEnd; height++) {
@@ -1982,7 +2040,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
1982
2040
  });
1983
2041
  }
1984
2042
  const avg = avgEventsPerBlock(batch);
1985
- batchSize = adjustBatchSize(batchSize, avg);
2043
+ batchSize = adjustBatchSize(batchSize, avg, batchConfig);
1986
2044
  currentHeight = batchEnd + 1;
1987
2045
  }
1988
2046
  await stats.flush(targetDb);
@@ -2070,9 +2128,7 @@ import { listen as listen2 } from "@secondlayer/shared/queue/listener";
2070
2128
  import {
2071
2129
  getTargetDb as getTargetDb5
2072
2130
  } from "@secondlayer/shared/db";
2073
- import {
2074
- getSubscriptionSigningSecret
2075
- } from "@secondlayer/shared/db/queries/subscriptions";
2131
+ import { getSubscriptionSigningSecret } from "@secondlayer/shared/db/queries/subscriptions";
2076
2132
  import { logger as logger9 } from "@secondlayer/shared/logger";
2077
2133
  import { listen } from "@secondlayer/shared/queue/listener";
2078
2134
  import { sql as sql4 } from "kysely";
@@ -2736,7 +2792,7 @@ async function startSubgraphOperationRunner(opts) {
2736
2792
  }, HEARTBEAT_INTERVAL_MS);
2737
2793
  const cancelPoll = setInterval(() => {
2738
2794
  getSubgraphOperation(db, operation.id).then((row) => {
2739
- if (row?.cancel_requested && !controller.signal.aborted) {
2795
+ if ((!row || row.cancel_requested) && !controller.signal.aborted) {
2740
2796
  controller.abort("user-cancelled");
2741
2797
  }
2742
2798
  }).catch((err) => {
@@ -2946,5 +3002,5 @@ var shutdown = async () => {
2946
3002
  process.on("SIGINT", shutdown);
2947
3003
  process.on("SIGTERM", shutdown);
2948
3004
 
2949
- //# debugId=C4D9D2D38E25E18D64756E2164756E21
3005
+ //# debugId=A1AAE72D48D7A5B964756E2164756E21
2950
3006
  //# sourceMappingURL=service.js.map