@secondlayer/subgraphs 1.0.0 → 1.1.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.
@@ -927,7 +927,7 @@ async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block
927
927
  subgraph_name: subgraphName,
928
928
  table_name: write.table,
929
929
  block_height: blockHeight,
930
- tx_id: write.pk.txId || null,
930
+ tx_id: write.pk.txId ?? null,
931
931
  row_pk: write.pk,
932
932
  event_type: eventType,
933
933
  payload: write.row,
@@ -950,7 +950,26 @@ function isPrimitive(v) {
950
950
  const t = typeof v;
951
951
  return t === "string" || t === "number" || t === "boolean";
952
952
  }
953
- function coerceNumeric(v) {
953
+ function coerceBigInt(v) {
954
+ if (typeof v === "bigint")
955
+ return v;
956
+ if (typeof v === "number") {
957
+ if (!Number.isFinite(v))
958
+ return null;
959
+ if (!Number.isInteger(v))
960
+ return null;
961
+ return BigInt(v);
962
+ }
963
+ if (typeof v === "string" && /^-?\d+$/.test(v)) {
964
+ try {
965
+ return BigInt(v);
966
+ } catch {
967
+ return null;
968
+ }
969
+ }
970
+ return null;
971
+ }
972
+ function coerceFloat(v) {
954
973
  if (typeof v === "number" && Number.isFinite(v))
955
974
  return v;
956
975
  if (typeof v === "bigint")
@@ -960,13 +979,31 @@ function coerceNumeric(v) {
960
979
  }
961
980
  return null;
962
981
  }
982
+ function compareNumeric(a, b) {
983
+ const ba = coerceBigInt(a);
984
+ const bb = coerceBigInt(b);
985
+ if (ba !== null && bb !== null) {
986
+ if (ba === bb)
987
+ return 0;
988
+ return ba > bb ? 1 : -1;
989
+ }
990
+ const fa = coerceFloat(a);
991
+ const fb = coerceFloat(b);
992
+ if (fa === null || fb === null)
993
+ return null;
994
+ if (fa === fb)
995
+ return 0;
996
+ return fa > fb ? 1 : -1;
997
+ }
963
998
  function matchClause(rowValue, clause) {
999
+ const rowIsPrimitive = isPrimitive(rowValue) || typeof rowValue === "bigint";
964
1000
  if (isPrimitive(clause)) {
965
- if (typeof clause === "number" || typeof rowValue === "number") {
966
- const a = coerceNumeric(rowValue);
967
- const b = coerceNumeric(clause);
968
- if (a !== null && b !== null)
969
- return a === b;
1001
+ if (!rowIsPrimitive)
1002
+ return false;
1003
+ if (typeof clause === "number" || typeof rowValue === "number" || typeof rowValue === "bigint") {
1004
+ const cmp = compareNumeric(rowValue, clause);
1005
+ if (cmp !== null)
1006
+ return cmp === 0;
970
1007
  }
971
1008
  return rowValue === clause || String(rowValue) === String(clause);
972
1009
  }
@@ -987,22 +1024,25 @@ function matchClause(rowValue, clause) {
987
1024
  case "gte":
988
1025
  case "lt":
989
1026
  case "lte": {
990
- const a = coerceNumeric(rowValue);
991
- const b = coerceNumeric(c[op]);
992
- if (a === null || b === null)
1027
+ if (!rowIsPrimitive)
1028
+ return false;
1029
+ const cmp = compareNumeric(rowValue, c[op]);
1030
+ if (cmp === null)
993
1031
  return false;
994
1032
  if (op === "gt")
995
- return a > b;
1033
+ return cmp > 0;
996
1034
  if (op === "gte")
997
- return a >= b;
1035
+ return cmp >= 0;
998
1036
  if (op === "lt")
999
- return a < b;
1000
- return a <= b;
1037
+ return cmp < 0;
1038
+ return cmp <= 0;
1001
1039
  }
1002
1040
  case "in": {
1003
1041
  const list = c.in;
1004
1042
  if (!Array.isArray(list))
1005
1043
  return false;
1044
+ if (!rowIsPrimitive)
1045
+ return false;
1006
1046
  return list.some((item) => isPrimitive(item) && matchClause(rowValue, item));
1007
1047
  }
1008
1048
  default:
@@ -1185,7 +1225,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
1185
1225
  });
1186
1226
  }
1187
1227
  }
1188
- } catch {}
1228
+ } catch (err) {
1229
+ logger4.debug("Row count sample failed", {
1230
+ subgraph: subgraphName,
1231
+ error: err instanceof Error ? err.message : String(err)
1232
+ });
1233
+ }
1189
1234
  }
1190
1235
  return result;
1191
1236
  }
@@ -1544,11 +1589,7 @@ import { decryptSecret } from "@secondlayer/shared/crypto/secrets";
1544
1589
  function resolveBearer(sub) {
1545
1590
  const cfg = sub.auth_config;
1546
1591
  if (cfg.tokenEnc) {
1547
- try {
1548
- return decryptSecret(Buffer.from(cfg.tokenEnc, "base64"));
1549
- } catch {
1550
- return null;
1551
- }
1592
+ return decryptSecret(Buffer.from(cfg.tokenEnc, "base64"));
1552
1593
  }
1553
1594
  return cfg.token ?? null;
1554
1595
  }
@@ -1608,15 +1649,16 @@ function buildRaw(outboxRow, sub) {
1608
1649
  // src/runtime/formats/standard-webhooks.ts
1609
1650
  import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
1610
1651
  function buildStandardWebhooks(outboxRow, signingSecret) {
1652
+ const nowSeconds = Math.floor(Date.now() / 1000);
1611
1653
  const payload = {
1612
1654
  type: outboxRow.event_type,
1613
- timestamp: new Date(outboxRow.created_at).toISOString(),
1655
+ timestamp: new Date(nowSeconds * 1000).toISOString(),
1614
1656
  data: outboxRow.payload
1615
1657
  };
1616
1658
  const body = JSON.stringify(payload);
1617
1659
  const sigHeaders = sign(body, signingSecret, {
1618
1660
  id: outboxRow.id,
1619
- timestampSeconds: Math.floor(new Date(outboxRow.created_at).getTime() / 1000)
1661
+ timestampSeconds: nowSeconds
1620
1662
  });
1621
1663
  return {
1622
1664
  body,
@@ -1632,11 +1674,7 @@ import { decryptSecret as decryptSecret2 } from "@secondlayer/shared/crypto/secr
1632
1674
  function resolveBearer2(sub) {
1633
1675
  const cfg = sub.auth_config;
1634
1676
  if (cfg.tokenEnc) {
1635
- try {
1636
- return decryptSecret2(Buffer.from(cfg.tokenEnc, "base64"));
1637
- } catch {
1638
- return null;
1639
- }
1677
+ return decryptSecret2(Buffer.from(cfg.tokenEnc, "base64"));
1640
1678
  }
1641
1679
  return cfg.token ?? null;
1642
1680
  }
@@ -1685,6 +1723,7 @@ var BATCH_SIZE = 50;
1685
1723
  var LIVE_SHARE = 0.9;
1686
1724
  var BACKOFF_SECONDS = [30, 120, 600, 3600, 21600, 86400, 259200];
1687
1725
  var CIRCUIT_THRESHOLD = 20;
1726
+ var LOCK_WINDOW_MS = 60000;
1688
1727
  function nextDelaySeconds(attempt) {
1689
1728
  return BACKOFF_SECONDS[Math.min(attempt, BACKOFF_SECONDS.length - 1)];
1690
1729
  }
@@ -1707,13 +1746,34 @@ function isPrivateEgress(url) {
1707
1746
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1708
1747
  return true;
1709
1748
  }
1710
- const host = parsed.hostname.toLowerCase();
1711
- if (host === "localhost")
1749
+ const raw = parsed.hostname.toLowerCase();
1750
+ const host = raw.startsWith("[") && raw.endsWith("]") ? raw.slice(1, -1) : raw;
1751
+ if (host === "localhost" || host === "0.0.0.0")
1752
+ return true;
1753
+ if (host === "::" || host === "::1")
1712
1754
  return true;
1713
- if (host === "::1" || host === "[::1]")
1755
+ if (/^f[cd][0-9a-f]{2}:/.test(host))
1714
1756
  return true;
1715
- if (host.startsWith("fd") || host.startsWith("fe80:"))
1757
+ if (/^fe[89ab][0-9a-f]:/.test(host))
1716
1758
  return true;
1759
+ const mapped = host.match(/^::ffff:(.+)$/);
1760
+ if (mapped) {
1761
+ const inner = mapped[1];
1762
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(inner)) {
1763
+ for (const p of PRIVATE_V4_PATTERNS)
1764
+ if (p.test(inner))
1765
+ return true;
1766
+ }
1767
+ const hex = inner.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
1768
+ if (hex) {
1769
+ const a = Number.parseInt(hex[1], 16);
1770
+ const b = Number.parseInt(hex[2], 16);
1771
+ const dotted = `${a >> 8 & 255}.${a & 255}.${b >> 8 & 255}.${b & 255}`;
1772
+ for (const p of PRIVATE_V4_PATTERNS)
1773
+ if (p.test(dotted))
1774
+ return true;
1775
+ }
1776
+ }
1717
1777
  for (const p of PRIVATE_V4_PATTERNS) {
1718
1778
  if (p.test(host))
1719
1779
  return true;
@@ -1812,16 +1872,23 @@ async function settleFailed(db, outboxRow, sub, errText) {
1812
1872
  locked_by: null,
1813
1873
  locked_until: null
1814
1874
  }).where("id", "=", outboxRow.id).execute();
1815
- const newFailures = sub.circuit_failures + 1;
1875
+ const incResult = await sql4`
1876
+ UPDATE subscriptions
1877
+ SET circuit_failures = circuit_failures + 1,
1878
+ last_delivery_at = NOW(),
1879
+ last_error = ${errText.slice(0, 500)},
1880
+ updated_at = NOW()
1881
+ WHERE id = ${sub.id}
1882
+ RETURNING circuit_failures
1883
+ `.execute(tx);
1884
+ const newFailures = incResult.rows[0]?.circuit_failures ?? sub.circuit_failures + 1;
1816
1885
  const shouldTripCircuit = newFailures >= CIRCUIT_THRESHOLD;
1817
- await tx.updateTable("subscriptions").set({
1818
- last_delivery_at: new Date,
1819
- last_error: errText.slice(0, 500),
1820
- circuit_failures: newFailures,
1821
- ...shouldTripCircuit ? { status: "paused", circuit_opened_at: new Date } : {},
1822
- updated_at: new Date
1823
- }).where("id", "=", outboxRow.subscription_id).execute();
1824
1886
  if (shouldTripCircuit) {
1887
+ await tx.updateTable("subscriptions").set({
1888
+ status: "paused",
1889
+ circuit_opened_at: new Date,
1890
+ updated_at: new Date
1891
+ }).where("id", "=", sub.id).execute();
1825
1892
  logger8.warn("Subscription circuit tripped — paused after consecutive failures", {
1826
1893
  subscription: sub.name,
1827
1894
  failures: newFailures
@@ -1859,8 +1926,12 @@ async function claimAndDrain(db, state, emitterId) {
1859
1926
  if (combined.length === 0)
1860
1927
  return [];
1861
1928
  const now = new Date;
1862
- const lockUntil = new Date(now.getTime() + 60000);
1863
- await tx.updateTable("subscription_outbox").set({ locked_by: emitterId, locked_until: lockUntil }).where("id", "in", combined.map((r) => r.id)).execute();
1929
+ const lockUntil = new Date(now.getTime() + LOCK_WINDOW_MS);
1930
+ await tx.updateTable("subscription_outbox").set({
1931
+ locked_by: emitterId,
1932
+ locked_until: lockUntil,
1933
+ next_attempt_at: lockUntil
1934
+ }).where("id", "in", combined.map((r) => r.id)).execute();
1864
1935
  return combined;
1865
1936
  });
1866
1937
  if (claimed.length === 0)
@@ -1944,11 +2015,27 @@ async function startEmitter(opts) {
1944
2015
  const pollIntervalMs = opts?.pollIntervalMs ?? 120000;
1945
2016
  const retentionIntervalMs = opts?.retentionIntervalMs ?? 60 * 60000;
1946
2017
  logger8.info("[emitter] started", { id: emitterId });
1947
- await refreshMatcher(db).catch((err) => {
1948
- logger8.error("[emitter] initial matcher refresh failed", {
1949
- error: err instanceof Error ? err.message : String(err)
1950
- });
1951
- });
2018
+ const MATCHER_BOOT_ATTEMPTS = 5;
2019
+ let lastErr = null;
2020
+ for (let i = 0;i < MATCHER_BOOT_ATTEMPTS; i++) {
2021
+ try {
2022
+ await refreshMatcher(db);
2023
+ lastErr = null;
2024
+ break;
2025
+ } catch (err) {
2026
+ lastErr = err;
2027
+ const delayMs = 500 * 2 ** i;
2028
+ logger8.warn("[emitter] matcher refresh failed, retrying", {
2029
+ attempt: i + 1,
2030
+ delayMs,
2031
+ error: err instanceof Error ? err.message : String(err)
2032
+ });
2033
+ await new Promise((r) => setTimeout(r, delayMs));
2034
+ }
2035
+ }
2036
+ if (lastErr) {
2037
+ throw new Error(`[emitter] matcher refresh failed ${MATCHER_BOOT_ATTEMPTS}×; aborting boot: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
2038
+ }
1952
2039
  const stopNew = await listen("subscriptions:new_outbox", () => {
1953
2040
  if (!state.running)
1954
2041
  return;
@@ -2140,5 +2227,5 @@ var shutdown = async () => {
2140
2227
  process.on("SIGINT", shutdown);
2141
2228
  process.on("SIGTERM", shutdown);
2142
2229
 
2143
- //# debugId=4DE5FBD02184D64464756E2164756E21
2230
+ //# debugId=C2E636DFAA9174B264756E2164756E21
2144
2231
  //# sourceMappingURL=service.js.map