@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.
- package/README.md +94 -0
- package/dist/src/index.js +61 -16
- package/dist/src/index.js.map +5 -5
- package/dist/src/runtime/block-processor.js +61 -16
- package/dist/src/runtime/block-processor.js.map +5 -5
- package/dist/src/runtime/catchup.js +61 -16
- package/dist/src/runtime/catchup.js.map +5 -5
- package/dist/src/runtime/processor.js +134 -47
- package/dist/src/runtime/processor.js.map +9 -9
- package/dist/src/runtime/reindex.js +61 -16
- package/dist/src/runtime/reindex.js.map +5 -5
- package/dist/src/runtime/reorg.js +61 -16
- package/dist/src/runtime/reorg.js.map +5 -5
- package/dist/src/runtime/replay.d.ts +2 -0
- package/dist/src/runtime/replay.js +11 -4
- package/dist/src/runtime/replay.js.map +3 -3
- package/dist/src/service.js +134 -47
- package/dist/src/service.js.map +9 -9
- package/package.json +3 -4
package/dist/src/service.js
CHANGED
|
@@ -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
|
|
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
|
|
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 (
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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
|
|
1033
|
+
return cmp > 0;
|
|
996
1034
|
if (op === "gte")
|
|
997
|
-
return
|
|
1035
|
+
return cmp >= 0;
|
|
998
1036
|
if (op === "lt")
|
|
999
|
-
return
|
|
1000
|
-
return
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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
|
|
1711
|
-
|
|
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 (
|
|
1755
|
+
if (/^f[cd][0-9a-f]{2}:/.test(host))
|
|
1714
1756
|
return true;
|
|
1715
|
-
if (
|
|
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
|
|
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() +
|
|
1863
|
-
await tx.updateTable("subscription_outbox").set({
|
|
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
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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=
|
|
2230
|
+
//# debugId=C2E636DFAA9174B264756E2164756E21
|
|
2144
2231
|
//# sourceMappingURL=service.js.map
|