@secondlayer/subgraphs 1.0.0-beta.1 → 1.0.0-beta.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.
@@ -1503,7 +1503,7 @@ import {
1503
1503
  listSubgraphs as listSubgraphs2,
1504
1504
  updateSubgraphStatus as updateSubgraphStatus2
1505
1505
  } from "@secondlayer/shared/db/queries/subgraphs";
1506
- import { logger as logger8 } from "@secondlayer/shared/logger";
1506
+ import { logger as logger9 } from "@secondlayer/shared/logger";
1507
1507
  import { listen as listen2 } from "@secondlayer/shared/queue/listener";
1508
1508
 
1509
1509
  // src/runtime/emitter.ts
@@ -1513,10 +1513,98 @@ import {
1513
1513
  import {
1514
1514
  getSubscriptionSigningSecret
1515
1515
  } from "@secondlayer/shared/db/queries/subscriptions";
1516
- import { logger as logger7 } from "@secondlayer/shared/logger";
1516
+ import { logger as logger8 } from "@secondlayer/shared/logger";
1517
1517
  import { listen } from "@secondlayer/shared/queue/listener";
1518
1518
  import { sql as sql4 } from "kysely";
1519
1519
 
1520
+ // src/runtime/formats/index.ts
1521
+ import { logger as logger7 } from "@secondlayer/shared/logger";
1522
+
1523
+ // src/runtime/formats/cloudevents.ts
1524
+ function buildCloudEvents(outboxRow, _sub) {
1525
+ const event = {
1526
+ specversion: "1.0",
1527
+ type: outboxRow.event_type,
1528
+ source: `secondlayer:${outboxRow.subgraph_name}`,
1529
+ id: outboxRow.id,
1530
+ time: new Date(outboxRow.created_at).toISOString(),
1531
+ datacontenttype: "application/json",
1532
+ data: outboxRow.payload
1533
+ };
1534
+ return {
1535
+ body: JSON.stringify(event),
1536
+ headers: {
1537
+ "content-type": "application/cloudevents+json; charset=utf-8"
1538
+ }
1539
+ };
1540
+ }
1541
+
1542
+ // src/runtime/formats/cloudflare.ts
1543
+ import { decryptSecret } from "@secondlayer/shared/crypto/secrets";
1544
+ function resolveBearer(sub) {
1545
+ const cfg = sub.auth_config;
1546
+ if (cfg.tokenEnc) {
1547
+ try {
1548
+ return decryptSecret(Buffer.from(cfg.tokenEnc, "base64"));
1549
+ } catch {
1550
+ return null;
1551
+ }
1552
+ }
1553
+ return cfg.token ?? null;
1554
+ }
1555
+ function buildCloudflare(outboxRow, sub) {
1556
+ const body = JSON.stringify({
1557
+ params: {
1558
+ ...outboxRow.payload,
1559
+ _type: outboxRow.event_type,
1560
+ _outboxId: outboxRow.id
1561
+ }
1562
+ });
1563
+ const headers = {
1564
+ "content-type": "application/json"
1565
+ };
1566
+ const token = resolveBearer(sub);
1567
+ if (token)
1568
+ headers.authorization = `Bearer ${token}`;
1569
+ return { body, headers };
1570
+ }
1571
+
1572
+ // src/runtime/formats/inngest.ts
1573
+ var INNGEST_VERSION = "2026-04-23.v1";
1574
+ function buildInngest(outboxRow) {
1575
+ const event = {
1576
+ name: outboxRow.event_type,
1577
+ data: outboxRow.payload,
1578
+ id: outboxRow.id,
1579
+ ts: new Date(outboxRow.created_at).getTime(),
1580
+ v: INNGEST_VERSION
1581
+ };
1582
+ return {
1583
+ body: JSON.stringify([event]),
1584
+ headers: {
1585
+ "content-type": "application/json"
1586
+ }
1587
+ };
1588
+ }
1589
+
1590
+ // src/runtime/formats/raw.ts
1591
+ function buildRaw(outboxRow, sub) {
1592
+ const cfg = sub.auth_config;
1593
+ const headers = {
1594
+ "content-type": cfg.contentType ?? "application/json",
1595
+ ...cfg.headers ?? {}
1596
+ };
1597
+ if (cfg.authType === "bearer" && cfg.token) {
1598
+ headers.authorization = `Bearer ${cfg.token}`;
1599
+ } else if (cfg.authType === "basic" && cfg.basicAuth) {
1600
+ headers.authorization = `Basic ${cfg.basicAuth}`;
1601
+ }
1602
+ return {
1603
+ body: JSON.stringify(outboxRow.payload),
1604
+ headers
1605
+ };
1606
+ }
1607
+
1520
1608
  // src/runtime/formats/standard-webhooks.ts
1521
1609
  import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
1522
1610
  function buildStandardWebhooks(outboxRow, signingSecret) {
@@ -1539,15 +1627,69 @@ function buildStandardWebhooks(outboxRow, signingSecret) {
1539
1627
  };
1540
1628
  }
1541
1629
 
1630
+ // src/runtime/formats/trigger.ts
1631
+ import { decryptSecret as decryptSecret2 } from "@secondlayer/shared/crypto/secrets";
1632
+ function resolveBearer2(sub) {
1633
+ const cfg = sub.auth_config;
1634
+ if (cfg.tokenEnc) {
1635
+ try {
1636
+ return decryptSecret2(Buffer.from(cfg.tokenEnc, "base64"));
1637
+ } catch {
1638
+ return null;
1639
+ }
1640
+ }
1641
+ return cfg.token ?? null;
1642
+ }
1643
+ function buildTrigger(outboxRow, sub) {
1644
+ const body = JSON.stringify({
1645
+ payload: outboxRow.payload,
1646
+ options: {
1647
+ idempotencyKey: outboxRow.id
1648
+ }
1649
+ });
1650
+ const headers = {
1651
+ "content-type": "application/json"
1652
+ };
1653
+ const token = resolveBearer2(sub);
1654
+ if (token)
1655
+ headers.authorization = `Bearer ${token}`;
1656
+ return { body, headers };
1657
+ }
1658
+
1659
+ // src/runtime/formats/index.ts
1660
+ function buildForFormat(outboxRow, sub, signingSecret) {
1661
+ switch (sub.format) {
1662
+ case "inngest":
1663
+ return buildInngest(outboxRow);
1664
+ case "trigger":
1665
+ return buildTrigger(outboxRow, sub);
1666
+ case "cloudflare":
1667
+ return buildCloudflare(outboxRow, sub);
1668
+ case "cloudevents":
1669
+ return buildCloudEvents(outboxRow, sub);
1670
+ case "raw":
1671
+ return buildRaw(outboxRow, sub);
1672
+ case "standard-webhooks":
1673
+ return buildStandardWebhooks(outboxRow, signingSecret);
1674
+ default:
1675
+ logger7.warn("Unknown subscription format, falling back to standard-webhooks", {
1676
+ format: sub.format,
1677
+ subscriptionId: sub.id
1678
+ });
1679
+ return buildStandardWebhooks(outboxRow, signingSecret);
1680
+ }
1681
+ }
1682
+
1542
1683
  // src/runtime/emitter.ts
1543
1684
  var BATCH_SIZE = 50;
1685
+ var LIVE_SHARE = 0.9;
1544
1686
  var BACKOFF_SECONDS = [30, 120, 600, 3600, 21600, 86400, 259200];
1545
1687
  var CIRCUIT_THRESHOLD = 20;
1546
1688
  function nextDelaySeconds(attempt) {
1547
1689
  return BACKOFF_SECONDS[Math.min(attempt, BACKOFF_SECONDS.length - 1)];
1548
1690
  }
1549
1691
  async function dispatchOne(db, outboxRow, sub) {
1550
- const { body, headers } = buildStandardWebhooks(outboxRow, getSubscriptionSigningSecret(sub));
1692
+ const { body, headers } = buildForFormat(outboxRow, sub, getSubscriptionSigningSecret(sub));
1551
1693
  const start = performance.now();
1552
1694
  let statusCode = null;
1553
1695
  let error = null;
@@ -1625,7 +1767,7 @@ async function settleFailed(db, outboxRow, sub, errText) {
1625
1767
  updated_at: new Date
1626
1768
  }).where("id", "=", outboxRow.subscription_id).execute();
1627
1769
  if (shouldTripCircuit) {
1628
- logger7.warn("Subscription circuit tripped — paused after consecutive failures", {
1770
+ logger8.warn("Subscription circuit tripped — paused after consecutive failures", {
1629
1771
  subscription: sub.name,
1630
1772
  failures: newFailures
1631
1773
  });
@@ -1637,20 +1779,34 @@ async function claimAndDrain(db, state, emitterId) {
1637
1779
  return 0;
1638
1780
  state.claimInFlight = true;
1639
1781
  try {
1782
+ const liveLimit = Math.max(1, Math.round(BATCH_SIZE * LIVE_SHARE));
1783
+ const replayLimit = BATCH_SIZE - liveLimit;
1640
1784
  const claimed = await db.transaction().execute(async (tx) => {
1641
- const rows = await sql4`
1785
+ const live = await sql4`
1642
1786
  SELECT * FROM subscription_outbox
1643
- WHERE status = 'pending' AND next_attempt_at <= NOW()
1787
+ WHERE status = 'pending'
1788
+ AND next_attempt_at <= NOW()
1789
+ AND is_replay = FALSE
1644
1790
  ORDER BY next_attempt_at ASC
1645
1791
  FOR UPDATE SKIP LOCKED
1646
- LIMIT ${sql4.lit(BATCH_SIZE)}
1792
+ LIMIT ${sql4.lit(liveLimit)}
1647
1793
  `.execute(tx);
1648
- if (rows.rows.length === 0)
1794
+ const replay = await sql4`
1795
+ SELECT * FROM subscription_outbox
1796
+ WHERE status = 'pending'
1797
+ AND next_attempt_at <= NOW()
1798
+ AND is_replay = TRUE
1799
+ ORDER BY next_attempt_at ASC
1800
+ FOR UPDATE SKIP LOCKED
1801
+ LIMIT ${sql4.lit(replayLimit)}
1802
+ `.execute(tx);
1803
+ const combined = [...live.rows, ...replay.rows];
1804
+ if (combined.length === 0)
1649
1805
  return [];
1650
1806
  const now = new Date;
1651
1807
  const lockUntil = new Date(now.getTime() + 60000);
1652
- await tx.updateTable("subscription_outbox").set({ locked_by: emitterId, locked_until: lockUntil }).where("id", "in", rows.rows.map((r) => r.id)).execute();
1653
- return rows.rows;
1808
+ await tx.updateTable("subscription_outbox").set({ locked_by: emitterId, locked_until: lockUntil }).where("id", "in", combined.map((r) => r.id)).execute();
1809
+ return combined;
1654
1810
  });
1655
1811
  if (claimed.length === 0)
1656
1812
  return 0;
@@ -1695,7 +1851,7 @@ async function drainForSub(db, state, sub, rows) {
1695
1851
  await settleFailed(db, row, sub, err);
1696
1852
  }
1697
1853
  } catch (err) {
1698
- logger7.error("Emitter dispatch crashed", {
1854
+ logger8.error("Emitter dispatch crashed", {
1699
1855
  outboxId: row.id,
1700
1856
  error: err instanceof Error ? err.message : String(err)
1701
1857
  });
@@ -1732,30 +1888,30 @@ async function startEmitter(opts) {
1732
1888
  };
1733
1889
  const pollIntervalMs = opts?.pollIntervalMs ?? 120000;
1734
1890
  const retentionIntervalMs = opts?.retentionIntervalMs ?? 60 * 60000;
1735
- logger7.info("[emitter] started", { id: emitterId });
1891
+ logger8.info("[emitter] started", { id: emitterId });
1736
1892
  await refreshMatcher(db).catch((err) => {
1737
- logger7.error("[emitter] initial matcher refresh failed", {
1893
+ logger8.error("[emitter] initial matcher refresh failed", {
1738
1894
  error: err instanceof Error ? err.message : String(err)
1739
1895
  });
1740
1896
  });
1741
1897
  const stopNew = await listen("subscriptions:new_outbox", () => {
1742
1898
  if (!state.running)
1743
1899
  return;
1744
- claimAndDrain(db, state, emitterId).catch((err) => logger7.error("[emitter] claim failed", {
1900
+ claimAndDrain(db, state, emitterId).catch((err) => logger8.error("[emitter] claim failed", {
1745
1901
  error: err instanceof Error ? err.message : String(err)
1746
1902
  }));
1747
1903
  });
1748
1904
  const stopChanged = await listen("subscriptions:changed", () => {
1749
1905
  if (!state.running)
1750
1906
  return;
1751
- refreshMatcher(db).catch((err) => logger7.error("[emitter] matcher refresh failed", {
1907
+ refreshMatcher(db).catch((err) => logger8.error("[emitter] matcher refresh failed", {
1752
1908
  error: err instanceof Error ? err.message : String(err)
1753
1909
  }));
1754
1910
  });
1755
1911
  const poll = setInterval(() => {
1756
1912
  if (!state.running)
1757
1913
  return;
1758
- claimAndDrain(db, state, emitterId).catch((err) => logger7.error("[emitter] poll claim failed", {
1914
+ claimAndDrain(db, state, emitterId).catch((err) => logger8.error("[emitter] poll claim failed", {
1759
1915
  error: err instanceof Error ? err.message : String(err)
1760
1916
  }));
1761
1917
  }, pollIntervalMs);
@@ -1763,7 +1919,7 @@ async function startEmitter(opts) {
1763
1919
  const retention = setInterval(() => {
1764
1920
  if (!state.running)
1765
1921
  return;
1766
- runRetention(db).catch((err) => logger7.error("[emitter] retention failed", {
1922
+ runRetention(db).catch((err) => logger8.error("[emitter] retention failed", {
1767
1923
  error: err instanceof Error ? err.message : String(err)
1768
1924
  }));
1769
1925
  }, retentionIntervalMs);
@@ -1773,7 +1929,7 @@ async function startEmitter(opts) {
1773
1929
  clearInterval(retention);
1774
1930
  await stopNew();
1775
1931
  await stopChanged();
1776
- logger7.info("[emitter] stopped", { id: emitterId });
1932
+ logger8.info("[emitter] stopped", { id: emitterId });
1777
1933
  };
1778
1934
  }
1779
1935
 
@@ -1811,7 +1967,7 @@ async function loadSubgraphDefinition(sg) {
1811
1967
  knownVersions.set(sg.name, sg.version);
1812
1968
  definitionCache.set(sg.name, def);
1813
1969
  if (prevVersion && prevVersion !== sg.version) {
1814
- logger8.info("Subgraph handler reloaded", {
1970
+ logger9.info("Subgraph handler reloaded", {
1815
1971
  subgraph: sg.name,
1816
1972
  from: prevVersion,
1817
1973
  to: sg.version
@@ -1831,7 +1987,7 @@ function cleanupCaches(active) {
1831
1987
  async function startSubgraphProcessor(opts) {
1832
1988
  const concurrency = opts?.concurrency ?? DEFAULT_CONCURRENCY;
1833
1989
  let running = true;
1834
- logger8.info("Starting subgraph processor", { concurrency });
1990
+ logger9.info("Starting subgraph processor", { concurrency });
1835
1991
  const targetDb = getTargetDb5();
1836
1992
  const activeSubgraphs = (await listSubgraphs2(targetDb)).filter((v) => v.status === "active");
1837
1993
  for (const sg of activeSubgraphs) {
@@ -1843,7 +1999,7 @@ async function startSubgraphProcessor(opts) {
1843
1999
  if (isHandlerNotFoundError(err)) {
1844
2000
  await updateSubgraphStatus2(targetDb, sg.name, "error");
1845
2001
  }
1846
- logger8.error("Subgraph catch-up failed on startup", {
2002
+ logger9.error("Subgraph catch-up failed on startup", {
1847
2003
  subgraph: sg.name,
1848
2004
  error: msg
1849
2005
  });
@@ -1864,7 +2020,7 @@ async function startSubgraphProcessor(opts) {
1864
2020
  if (isHandlerNotFoundError(err)) {
1865
2021
  await updateSubgraphStatus2(db, sg.name, "error");
1866
2022
  }
1867
- logger8.error("Subgraph processing failed", {
2023
+ logger9.error("Subgraph processing failed", {
1868
2024
  subgraph: sg.name,
1869
2025
  error: msg
1870
2026
  });
@@ -1881,7 +2037,7 @@ async function startSubgraphProcessor(opts) {
1881
2037
  await handleSubgraphReorg(blockHeight, loadSubgraphDefinition);
1882
2038
  }
1883
2039
  } catch (err) {
1884
- logger8.error("Subgraph reorg handling failed", {
2040
+ logger9.error("Subgraph reorg handling failed", {
1885
2041
  error: getErrorMessage3(err)
1886
2042
  });
1887
2043
  }
@@ -1897,7 +2053,7 @@ async function startSubgraphProcessor(opts) {
1897
2053
  const def = await loadSubgraphDefinition(sg);
1898
2054
  await catchUpSubgraph(def, sg.name);
1899
2055
  } catch (err) {
1900
- logger8.error("Subgraph poll processing failed", {
2056
+ logger9.error("Subgraph poll processing failed", {
1901
2057
  subgraph: sg.name,
1902
2058
  error: getErrorMessage3(err)
1903
2059
  });
@@ -1905,29 +2061,29 @@ async function startSubgraphProcessor(opts) {
1905
2061
  }
1906
2062
  }, POLL_INTERVAL_MS);
1907
2063
  const stopEmitter = await startEmitter();
1908
- logger8.info("Subgraph processor ready");
2064
+ logger9.info("Subgraph processor ready");
1909
2065
  return async () => {
1910
2066
  running = false;
1911
2067
  clearInterval(pollInterval);
1912
2068
  await stopListening();
1913
2069
  await stopReorgListening();
1914
2070
  await stopEmitter();
1915
- logger8.info("Subgraph processor stopped");
2071
+ logger9.info("Subgraph processor stopped");
1916
2072
  };
1917
2073
  }
1918
2074
 
1919
2075
  // src/service.ts
1920
- import { logger as logger9 } from "@secondlayer/shared/logger";
2076
+ import { logger as logger10 } from "@secondlayer/shared/logger";
1921
2077
  var processor = await startSubgraphProcessor({
1922
2078
  concurrency: Number.parseInt(process.env.SUBGRAPH_CONCURRENCY ?? "5")
1923
2079
  });
1924
2080
  var shutdown = async () => {
1925
- logger9.info("Shutting down subgraph processor...");
2081
+ logger10.info("Shutting down subgraph processor...");
1926
2082
  await processor();
1927
2083
  process.exit(0);
1928
2084
  };
1929
2085
  process.on("SIGINT", shutdown);
1930
2086
  process.on("SIGTERM", shutdown);
1931
2087
 
1932
- //# debugId=831A226FB3756A4564756E2164756E21
2088
+ //# debugId=2B633BA3D599600D64756E2164756E21
1933
2089
  //# sourceMappingURL=service.js.map