@secondlayer/subgraphs 4.1.5 → 4.2.1
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/dist/src/index.d.ts +87 -29
- package/dist/src/index.js +376 -128
- package/dist/src/index.js.map +9 -7
- package/dist/src/runtime/block-processor.d.ts +72 -23
- package/dist/src/runtime/block-processor.js +186 -53
- package/dist/src/runtime/block-processor.js.map +7 -5
- package/dist/src/runtime/catchup.d.ts +72 -23
- package/dist/src/runtime/catchup.js +201 -68
- package/dist/src/runtime/catchup.js.map +7 -5
- package/dist/src/runtime/emitter.js +24 -1
- package/dist/src/runtime/emitter.js.map +5 -4
- package/dist/src/runtime/processor.js +328 -166
- package/dist/src/runtime/processor.js.map +10 -7
- package/dist/src/runtime/reindex.d.ts +72 -23
- package/dist/src/runtime/reindex.js +238 -105
- package/dist/src/runtime/reindex.js.map +7 -5
- package/dist/src/runtime/reorg.d.ts +72 -23
- package/dist/src/runtime/reorg.js +215 -81
- package/dist/src/runtime/reorg.js.map +8 -6
- package/dist/src/runtime/runner.d.ts +72 -23
- package/dist/src/runtime/runner.js +156 -23
- package/dist/src/runtime/runner.js.map +8 -6
- package/dist/src/runtime/source-matcher.d.ts +72 -23
- package/dist/src/schema/index.d.ts +72 -23
- package/dist/src/schema/index.js +115 -1
- package/dist/src/schema/index.js.map +3 -3
- package/dist/src/service.js +348 -171
- package/dist/src/service.js.map +11 -8
- package/dist/src/testing/index.d.ts +195 -24
- package/dist/src/testing/index.js +448 -25
- package/dist/src/testing/index.js.map +9 -6
- package/dist/src/triggers/index.d.ts +83 -24
- package/dist/src/triggers/index.js +5 -2
- package/dist/src/triggers/index.js.map +3 -3
- package/dist/src/types.d.ts +73 -24
- package/dist/src/validate.d.ts +72 -23
- package/dist/src/validate.js +115 -1
- package/dist/src/validate.js.map +3 -3
- package/package.json +2 -2
|
@@ -1212,7 +1212,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
|
|
|
1212
1212
|
|
|
1213
1213
|
// src/runtime/runner.ts
|
|
1214
1214
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
1215
|
-
import { logger as
|
|
1215
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
1216
1216
|
import {
|
|
1217
1217
|
clarityValueToJS,
|
|
1218
1218
|
deserializeCV as deserializeCV4,
|
|
@@ -1226,6 +1226,19 @@ import {
|
|
|
1226
1226
|
function camelizeDataKey(str) {
|
|
1227
1227
|
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1228
1228
|
}
|
|
1229
|
+
function camelizeKeys(obj) {
|
|
1230
|
+
if (obj === null || obj === undefined)
|
|
1231
|
+
return obj;
|
|
1232
|
+
if (typeof obj !== "object")
|
|
1233
|
+
return obj;
|
|
1234
|
+
if (Array.isArray(obj))
|
|
1235
|
+
return obj.map(camelizeKeys);
|
|
1236
|
+
const result = {};
|
|
1237
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1238
|
+
result[camelizeDataKey(k)] = camelizeKeys(v);
|
|
1239
|
+
}
|
|
1240
|
+
return result;
|
|
1241
|
+
}
|
|
1229
1242
|
function cvToTree(cv) {
|
|
1230
1243
|
switch (cv.type) {
|
|
1231
1244
|
case "uint":
|
|
@@ -1550,6 +1563,58 @@ function inferPrintTopics(samples) {
|
|
|
1550
1563
|
return out.sort((a, b) => b.count - a.count);
|
|
1551
1564
|
}
|
|
1552
1565
|
|
|
1566
|
+
// src/runtime/materialize.ts
|
|
1567
|
+
function resolveColumn(col, event, tx, block) {
|
|
1568
|
+
if ("fromTx" in col) {
|
|
1569
|
+
return { ok: true, value: tx[col.fromTx] };
|
|
1570
|
+
}
|
|
1571
|
+
if ("fromBlock" in col) {
|
|
1572
|
+
return { ok: true, value: block[col.fromBlock] };
|
|
1573
|
+
}
|
|
1574
|
+
const data = event.data;
|
|
1575
|
+
const dataObj = data !== null && typeof data === "object" && !Array.isArray(data) ? data : undefined;
|
|
1576
|
+
if (dataObj && Object.prototype.hasOwnProperty.call(dataObj, col.from)) {
|
|
1577
|
+
return { ok: true, value: dataObj[col.from] };
|
|
1578
|
+
}
|
|
1579
|
+
if (col.from === "topic" && "topic" in event) {
|
|
1580
|
+
return { ok: true, value: event.topic };
|
|
1581
|
+
}
|
|
1582
|
+
if (Object.prototype.hasOwnProperty.call(event, col.from)) {
|
|
1583
|
+
return { ok: true, value: event[col.from] };
|
|
1584
|
+
}
|
|
1585
|
+
return { ok: false, missing: col.from };
|
|
1586
|
+
}
|
|
1587
|
+
function buildMaterializeRow(spec, event, ctx, tableColumns) {
|
|
1588
|
+
const row = {};
|
|
1589
|
+
for (const [colName, mapping] of Object.entries(spec.columns)) {
|
|
1590
|
+
const resolved = resolveColumn(mapping, event, ctx.tx, ctx.block);
|
|
1591
|
+
if (!resolved.ok) {
|
|
1592
|
+
const colDef = tableColumns[colName];
|
|
1593
|
+
if (colDef?.nullable) {
|
|
1594
|
+
row[colName] = null;
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
ok: false,
|
|
1599
|
+
reason: `required materialize field "${resolved.missing}" missing for column "${colName}"`
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
row[colName] = resolved.value ?? null;
|
|
1603
|
+
}
|
|
1604
|
+
return { ok: true, row };
|
|
1605
|
+
}
|
|
1606
|
+
function applyMaterializeInsert(spec, event, ctx, schema) {
|
|
1607
|
+
const table = schema[spec.table];
|
|
1608
|
+
if (!table) {
|
|
1609
|
+
return { ok: false, reason: `materialize table "${spec.table}" missing` };
|
|
1610
|
+
}
|
|
1611
|
+
const built = buildMaterializeRow(spec, event, ctx, table.columns);
|
|
1612
|
+
if (!built.ok)
|
|
1613
|
+
return built;
|
|
1614
|
+
ctx.insert(spec.table, built.row);
|
|
1615
|
+
return { ok: true };
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1553
1618
|
// src/runtime/print-validate.ts
|
|
1554
1619
|
var COLUMN_TYPES = new Set([
|
|
1555
1620
|
"text",
|
|
@@ -1629,21 +1694,62 @@ function validatePrintPayload(prints, topic, data) {
|
|
|
1629
1694
|
return matchesTopic(declared, data);
|
|
1630
1695
|
}
|
|
1631
1696
|
|
|
1632
|
-
// src/runtime/
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1697
|
+
// src/runtime/violations.ts
|
|
1698
|
+
import { getTargetDb } from "@secondlayer/shared/db";
|
|
1699
|
+
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
1700
|
+
import { sql as sql2 } from "kysely";
|
|
1701
|
+
var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
|
|
1702
|
+
function jsonSafe2(value) {
|
|
1703
|
+
if (typeof value === "bigint")
|
|
1704
|
+
return value.toString();
|
|
1705
|
+
if (value === null || value === undefined)
|
|
1706
|
+
return value;
|
|
1707
|
+
if (Array.isArray(value))
|
|
1708
|
+
return value.map(jsonSafe2);
|
|
1709
|
+
if (typeof value === "object") {
|
|
1710
|
+
const out = {};
|
|
1711
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1712
|
+
out[k] = jsonSafe2(v);
|
|
1713
|
+
}
|
|
1714
|
+
return out;
|
|
1715
|
+
}
|
|
1716
|
+
return value;
|
|
1717
|
+
}
|
|
1718
|
+
async function defaultRecordPrintViolation(input) {
|
|
1719
|
+
try {
|
|
1720
|
+
const db = getTargetDb();
|
|
1721
|
+
const sample = jsonSafe2(input.samplePayload);
|
|
1722
|
+
await db.insertInto("subgraph_violations").values({
|
|
1723
|
+
subgraph_name: input.subgraphName,
|
|
1724
|
+
source_name: input.sourceName,
|
|
1725
|
+
block_height: input.blockHeight,
|
|
1726
|
+
tx_id: input.txId,
|
|
1727
|
+
reason: input.reason,
|
|
1728
|
+
sample_payload: sample
|
|
1729
|
+
}).execute();
|
|
1730
|
+
await sql2`
|
|
1731
|
+
DELETE FROM subgraph_violations
|
|
1732
|
+
WHERE id IN (
|
|
1733
|
+
SELECT id FROM subgraph_violations
|
|
1734
|
+
WHERE subgraph_name = ${input.subgraphName}
|
|
1735
|
+
ORDER BY seen_at DESC
|
|
1736
|
+
OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
|
|
1737
|
+
)
|
|
1738
|
+
`.execute(db);
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
logger3.warn("Failed to record print violation", {
|
|
1741
|
+
subgraph: input.subgraphName,
|
|
1742
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1743
|
+
});
|
|
1644
1744
|
}
|
|
1645
|
-
return result;
|
|
1646
1745
|
}
|
|
1746
|
+
var recorder = defaultRecordPrintViolation;
|
|
1747
|
+
async function recordPrintViolation(input) {
|
|
1748
|
+
await recorder(input);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// src/runtime/runner.ts
|
|
1752
|
+
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
1647
1753
|
function decodeFunctionArgs2(args) {
|
|
1648
1754
|
let parsed = args;
|
|
1649
1755
|
if (typeof parsed === "string") {
|
|
@@ -1891,16 +1997,18 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1891
1997
|
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1892
1998
|
for (const { tx, event, sourceName } of units) {
|
|
1893
1999
|
if (errors >= threshold) {
|
|
1894
|
-
|
|
2000
|
+
logger4.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1895
2001
|
subgraph: subgraph.name,
|
|
1896
2002
|
errors,
|
|
1897
2003
|
threshold
|
|
1898
2004
|
});
|
|
1899
2005
|
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1900
2006
|
}
|
|
2007
|
+
const filter = filterLookup.get(sourceName);
|
|
2008
|
+
const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
|
|
1901
2009
|
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1902
|
-
if (!handler) {
|
|
1903
|
-
|
|
2010
|
+
if (!handler && !materialize) {
|
|
2011
|
+
logger4.warn("No handler found for source", {
|
|
1904
2012
|
subgraph: subgraph.name,
|
|
1905
2013
|
sourceName,
|
|
1906
2014
|
txId: tx.tx_id
|
|
@@ -1915,7 +2023,6 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1915
2023
|
contractId: tx.contract_id ?? null,
|
|
1916
2024
|
functionName: tx.function_name ?? null
|
|
1917
2025
|
});
|
|
1918
|
-
const filter = filterLookup.get(sourceName);
|
|
1919
2026
|
const checkpoint = ctx.opsCheckpoint();
|
|
1920
2027
|
try {
|
|
1921
2028
|
let payload;
|
|
@@ -1957,22 +2064,48 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1957
2064
|
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1958
2065
|
if (!verdict.ok) {
|
|
1959
2066
|
skipped++;
|
|
1960
|
-
|
|
2067
|
+
logger4.warn("Print payload does not match declared prints schema", {
|
|
1961
2068
|
subgraph: subgraph.name,
|
|
1962
2069
|
sourceName,
|
|
1963
2070
|
topic: printPayload.topic,
|
|
1964
2071
|
txId: tx.tx_id,
|
|
1965
2072
|
reason: verdict.reason
|
|
1966
2073
|
});
|
|
2074
|
+
recordPrintViolation({
|
|
2075
|
+
subgraphName: subgraph.name,
|
|
2076
|
+
sourceName,
|
|
2077
|
+
blockHeight: ctx.block.height,
|
|
2078
|
+
txId: tx.tx_id,
|
|
2079
|
+
reason: verdict.reason,
|
|
2080
|
+
samplePayload: {
|
|
2081
|
+
topic: printPayload.topic ?? "",
|
|
2082
|
+
data: printPayload.data ?? {}
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
1967
2085
|
continue;
|
|
1968
2086
|
}
|
|
1969
2087
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
2088
|
+
if (materialize) {
|
|
2089
|
+
const result = applyMaterializeInsert(materialize, payload, ctx, subgraph.schema);
|
|
2090
|
+
if (!result.ok) {
|
|
2091
|
+
skipped++;
|
|
2092
|
+
logger4.warn("Materialize skipped: required field missing", {
|
|
2093
|
+
subgraph: subgraph.name,
|
|
2094
|
+
sourceName,
|
|
2095
|
+
txId: tx.tx_id,
|
|
2096
|
+
reason: result.reason
|
|
2097
|
+
});
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
processed++;
|
|
2101
|
+
} else if (handler) {
|
|
2102
|
+
await handler(payload, ctx);
|
|
2103
|
+
processed++;
|
|
2104
|
+
}
|
|
1972
2105
|
} catch (err) {
|
|
1973
2106
|
ctx.rollbackTo(checkpoint);
|
|
1974
2107
|
errors++;
|
|
1975
|
-
|
|
2108
|
+
logger4.error("Subgraph handler error", {
|
|
1976
2109
|
subgraph: subgraph.name,
|
|
1977
2110
|
sourceName,
|
|
1978
2111
|
txId: tx.tx_id,
|
|
@@ -1984,7 +2117,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1984
2117
|
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1985
2118
|
}
|
|
1986
2119
|
|
|
1987
|
-
// src/testing/
|
|
2120
|
+
// src/testing/harness.ts
|
|
1988
2121
|
import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
|
|
1989
2122
|
var TEST_SCHEMA_NAME = "subgraph_test";
|
|
1990
2123
|
function defaultBlock(overrides = {}) {
|
|
@@ -2052,6 +2185,20 @@ class InMemorySubgraphContext extends SubgraphContext {
|
|
|
2052
2185
|
}
|
|
2053
2186
|
this.ops.length = 0;
|
|
2054
2187
|
}
|
|
2188
|
+
insertsSince(checkpoint) {
|
|
2189
|
+
const out = [];
|
|
2190
|
+
for (const op of this.ops.slice(Math.max(0, checkpoint))) {
|
|
2191
|
+
if (op.kind !== "insert")
|
|
2192
|
+
continue;
|
|
2193
|
+
const row = { ...op.data };
|
|
2194
|
+
out.push({
|
|
2195
|
+
table: op.table,
|
|
2196
|
+
keys: Object.keys(row).filter((k) => !k.startsWith("_")),
|
|
2197
|
+
row
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
return out;
|
|
2201
|
+
}
|
|
2055
2202
|
}
|
|
2056
2203
|
function sameValue(a, b) {
|
|
2057
2204
|
if (a === b)
|
|
@@ -2067,7 +2214,10 @@ function createTestContext(schema, options = {}) {
|
|
|
2067
2214
|
const ctx = impl;
|
|
2068
2215
|
ctx.rows = (table) => impl.rowsOf(table);
|
|
2069
2216
|
ctx.commit = () => impl.commitOps();
|
|
2070
|
-
ctx.setTx = (tx) =>
|
|
2217
|
+
ctx.setTx = (tx) => {
|
|
2218
|
+
SubgraphContext.prototype.setTx.call(impl, defaultTx(tx));
|
|
2219
|
+
};
|
|
2220
|
+
ctx.pendingInsertsSince = (checkpoint) => impl.insertsSince(checkpoint);
|
|
2071
2221
|
return ctx;
|
|
2072
2222
|
}
|
|
2073
2223
|
function buildEvent(source, payload, options = {}) {
|
|
@@ -2087,11 +2237,284 @@ function buildEvent(source, payload, options = {}) {
|
|
|
2087
2237
|
}
|
|
2088
2238
|
return { ...base, ...payload };
|
|
2089
2239
|
}
|
|
2240
|
+
// src/testing/run.ts
|
|
2241
|
+
function toHandlerPayload(_filter, row) {
|
|
2242
|
+
if (row.event_type === "print") {
|
|
2243
|
+
const payload = row.payload;
|
|
2244
|
+
return {
|
|
2245
|
+
contractId: row.contract_id ?? "",
|
|
2246
|
+
topic: payload?.topic ?? "",
|
|
2247
|
+
data: camelizeKeys(payload?.value) ?? {}
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
const r = row;
|
|
2251
|
+
return {
|
|
2252
|
+
...r.sender !== undefined ? { sender: r.sender } : {},
|
|
2253
|
+
...r.recipient !== undefined ? { recipient: r.recipient } : {},
|
|
2254
|
+
...r.amount !== undefined ? { amount: BigInt(String(r.amount)) } : {},
|
|
2255
|
+
...r.asset_identifier !== undefined ? { assetIdentifier: r.asset_identifier } : {},
|
|
2256
|
+
...r.value !== undefined ? { tokenId: r.value } : {}
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
function toContractCallPayload(row) {
|
|
2260
|
+
return {
|
|
2261
|
+
type: "contract_call",
|
|
2262
|
+
sender: row.sender,
|
|
2263
|
+
contractId: row.contract_id ?? "",
|
|
2264
|
+
functionName: row.function_name ?? "",
|
|
2265
|
+
args: Array.isArray(row.args) ? row.args : [],
|
|
2266
|
+
result: row.result ?? null,
|
|
2267
|
+
resultHex: row.result_hex ?? null,
|
|
2268
|
+
tx: {
|
|
2269
|
+
txId: row.tx_id,
|
|
2270
|
+
sender: row.sender,
|
|
2271
|
+
type: "contract_call",
|
|
2272
|
+
status: row.status,
|
|
2273
|
+
contractId: row.contract_id ?? null,
|
|
2274
|
+
functionName: row.function_name ?? null
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
function jsonSafe3(value) {
|
|
2279
|
+
if (typeof value === "bigint")
|
|
2280
|
+
return value.toString();
|
|
2281
|
+
if (value === null || value === undefined)
|
|
2282
|
+
return value;
|
|
2283
|
+
if (Array.isArray(value))
|
|
2284
|
+
return value.map(jsonSafe3);
|
|
2285
|
+
if (typeof value === "object") {
|
|
2286
|
+
const out = {};
|
|
2287
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2288
|
+
out[k] = jsonSafe3(v);
|
|
2289
|
+
}
|
|
2290
|
+
return out;
|
|
2291
|
+
}
|
|
2292
|
+
return value;
|
|
2293
|
+
}
|
|
2294
|
+
function dataKeysOf(data) {
|
|
2295
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
2296
|
+
return Object.keys(data);
|
|
2297
|
+
}
|
|
2298
|
+
return [];
|
|
2299
|
+
}
|
|
2300
|
+
function rowMeta(row) {
|
|
2301
|
+
const r = row;
|
|
2302
|
+
return {
|
|
2303
|
+
...typeof r.block_height === "number" ? { blockHeight: r.block_height } : {},
|
|
2304
|
+
...typeof r.tx_id === "string" ? { txId: r.tx_id } : {}
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
function inKeysOf(filter, payload) {
|
|
2308
|
+
if (filter?.type === "contract_call") {
|
|
2309
|
+
return ["functionName", "args", "sender", "contractId"].filter((k) => payload[k] !== undefined);
|
|
2310
|
+
}
|
|
2311
|
+
return dataKeysOf(payload.data);
|
|
2312
|
+
}
|
|
2313
|
+
function unusedInKeysAcross(traces) {
|
|
2314
|
+
if (traces.length === 0)
|
|
2315
|
+
return;
|
|
2316
|
+
const alwaysIn = new Set(traces[0]?.inKeys ?? []);
|
|
2317
|
+
for (const t of traces.slice(1)) {
|
|
2318
|
+
const keys = new Set(t.inKeys);
|
|
2319
|
+
for (const k of [...alwaysIn]) {
|
|
2320
|
+
if (!keys.has(k))
|
|
2321
|
+
alwaysIn.delete(k);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
const outKeys = new Set;
|
|
2325
|
+
for (const t of traces) {
|
|
2326
|
+
for (const o of t.outs) {
|
|
2327
|
+
for (const k of o.keys)
|
|
2328
|
+
outKeys.add(k);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
const outNormalized = new Set([...outKeys].map((k) => k.replace(/_/g, "").toLowerCase()));
|
|
2332
|
+
const unused = [...alwaysIn].filter((k) => !outNormalized.has(k.replace(/_/g, "").toLowerCase()));
|
|
2333
|
+
return unused.length > 0 ? unused : undefined;
|
|
2334
|
+
}
|
|
2335
|
+
async function runSubgraphTest(input) {
|
|
2336
|
+
const { schema, handlers, sources, events, trace } = input;
|
|
2337
|
+
const sourceNames = Object.keys(sources);
|
|
2338
|
+
if (sourceNames.length === 0) {
|
|
2339
|
+
return {
|
|
2340
|
+
ok: false,
|
|
2341
|
+
code: "NO_SOURCES",
|
|
2342
|
+
matched: 0,
|
|
2343
|
+
written: 0,
|
|
2344
|
+
tables: [],
|
|
2345
|
+
hint: "No sources to test."
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
const ctx = createTestContext(schema);
|
|
2349
|
+
let matched = 0;
|
|
2350
|
+
let firstEvent;
|
|
2351
|
+
const traces = [];
|
|
2352
|
+
for (const [name, rows] of Object.entries(events)) {
|
|
2353
|
+
const filter = sources[name];
|
|
2354
|
+
const handler = handlers[name] ?? handlers["*"];
|
|
2355
|
+
const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
|
|
2356
|
+
if (typeof handler !== "function" && !materialize)
|
|
2357
|
+
continue;
|
|
2358
|
+
for (const row of rows) {
|
|
2359
|
+
const payload = filter?.type === "contract_call" ? toContractCallPayload(row) : toHandlerPayload(filter, row);
|
|
2360
|
+
if (filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
2363
|
+
if (!firstEvent) {
|
|
2364
|
+
firstEvent = {
|
|
2365
|
+
source: name,
|
|
2366
|
+
data: filter?.type === "contract_call" ? {
|
|
2367
|
+
functionName: payload.functionName,
|
|
2368
|
+
args: payload.args
|
|
2369
|
+
} : payload.data
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
matched++;
|
|
2373
|
+
const checkpoint = ctx.opsCheckpoint();
|
|
2374
|
+
try {
|
|
2375
|
+
const event = buildEvent(filter, payload);
|
|
2376
|
+
if (materialize) {
|
|
2377
|
+
applyMaterializeInsert(materialize, event, {
|
|
2378
|
+
tx: ctx.tx,
|
|
2379
|
+
block: ctx.block,
|
|
2380
|
+
insert: (table, insertRow) => {
|
|
2381
|
+
ctx.insert(table, insertRow);
|
|
2382
|
+
}
|
|
2383
|
+
}, schema);
|
|
2384
|
+
} else {
|
|
2385
|
+
await handler(event, ctx);
|
|
2386
|
+
}
|
|
2387
|
+
} catch {}
|
|
2388
|
+
if (trace) {
|
|
2389
|
+
const inserts = ctx.pendingInsertsSince(checkpoint);
|
|
2390
|
+
traces.push({
|
|
2391
|
+
source: name,
|
|
2392
|
+
...rowMeta(row),
|
|
2393
|
+
inKeys: inKeysOf(filter, payload),
|
|
2394
|
+
outs: inserts.map((i) => ({ table: i.table, keys: i.keys }))
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
const tableNames = Object.keys(schema);
|
|
2400
|
+
const tables = [];
|
|
2401
|
+
let written = 0;
|
|
2402
|
+
for (const table of tableNames) {
|
|
2403
|
+
const tableRows = await ctx.rows(table);
|
|
2404
|
+
written += tableRows.length;
|
|
2405
|
+
const sample = tableRows[0];
|
|
2406
|
+
tables.push({
|
|
2407
|
+
name: table,
|
|
2408
|
+
rows: tableRows.length,
|
|
2409
|
+
...sample ? { sampleRow: jsonSafe3(sample) } : {}
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
const fetched = Object.values(events).reduce((n, r) => n + r.length, 0);
|
|
2413
|
+
const unusedInKeys = trace ? unusedInKeysAcross(traces) : undefined;
|
|
2414
|
+
const traceFields = {
|
|
2415
|
+
...trace ? { traces } : {},
|
|
2416
|
+
...unusedInKeys ? { unusedInKeys } : {}
|
|
2417
|
+
};
|
|
2418
|
+
if (fetched === 0) {
|
|
2419
|
+
return {
|
|
2420
|
+
ok: false,
|
|
2421
|
+
code: "NO_EVENTS",
|
|
2422
|
+
matched: 0,
|
|
2423
|
+
written,
|
|
2424
|
+
tables,
|
|
2425
|
+
hint: "No events matched these sources in the given range. Widen the range or check the source filters.",
|
|
2426
|
+
...traceFields
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
if (written === 0) {
|
|
2430
|
+
const keys = dataKeysOf(firstEvent?.data);
|
|
2431
|
+
const keyPart = keys.length > 0 ? ` Observed event.data keys on first event (${firstEvent?.source}): ${keys.join(", ")}.` : firstEvent ? ` First event source: ${firstEvent.source}.` : "";
|
|
2432
|
+
return {
|
|
2433
|
+
ok: false,
|
|
2434
|
+
code: "EMPTY_MAPPING",
|
|
2435
|
+
matched,
|
|
2436
|
+
written: 0,
|
|
2437
|
+
tables,
|
|
2438
|
+
firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
|
|
2439
|
+
hint: `${matched} event${matched === 1 ? "" : "s"} matched, but NO rows were written — the shape of the field-mapping bug that ships a 0-row subgraph.${keyPart} Map only observed keys; do not invent fields.`,
|
|
2440
|
+
...traceFields
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
if (trace && traces.some((t) => t.outs.length === 0)) {
|
|
2444
|
+
const empty = traces.find((t) => t.outs.length === 0);
|
|
2445
|
+
const keyPart = empty && empty.inKeys.length > 0 ? ` Empty event (${empty.source}) IN keys: ${empty.inKeys.join(", ")}.` : "";
|
|
2446
|
+
return {
|
|
2447
|
+
ok: false,
|
|
2448
|
+
code: "EMPTY_MAPPING",
|
|
2449
|
+
matched,
|
|
2450
|
+
written,
|
|
2451
|
+
tables,
|
|
2452
|
+
firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
|
|
2453
|
+
hint: `At least one matched event wrote 0 rows.${keyPart}`,
|
|
2454
|
+
...traceFields
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
return {
|
|
2458
|
+
ok: true,
|
|
2459
|
+
matched,
|
|
2460
|
+
written,
|
|
2461
|
+
tables,
|
|
2462
|
+
firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe3(firstEvent.data) } : undefined,
|
|
2463
|
+
...traceFields
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
async function probeHandlers(def, samples) {
|
|
2467
|
+
const ctx = createTestContext(def.schema);
|
|
2468
|
+
let matched = 0;
|
|
2469
|
+
let firstEventKeys;
|
|
2470
|
+
for (const sample of samples) {
|
|
2471
|
+
const filter = def.sources[sample.source];
|
|
2472
|
+
if (!filter)
|
|
2473
|
+
continue;
|
|
2474
|
+
const handler = def.handlers[sample.source] ?? def.handlers["*"];
|
|
2475
|
+
const materialize = "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
|
|
2476
|
+
if (typeof handler !== "function" && !materialize)
|
|
2477
|
+
continue;
|
|
2478
|
+
if (firstEventKeys === undefined) {
|
|
2479
|
+
firstEventKeys = dataKeysOf(sample.event.data);
|
|
2480
|
+
}
|
|
2481
|
+
matched++;
|
|
2482
|
+
try {
|
|
2483
|
+
const event = buildEvent(filter, sample.event);
|
|
2484
|
+
if (materialize) {
|
|
2485
|
+
applyMaterializeInsert(materialize, event, {
|
|
2486
|
+
tx: ctx.tx,
|
|
2487
|
+
block: ctx.block,
|
|
2488
|
+
insert: (table, row) => {
|
|
2489
|
+
ctx.insert(table, row);
|
|
2490
|
+
}
|
|
2491
|
+
}, def.schema);
|
|
2492
|
+
} else {
|
|
2493
|
+
await handler(event, ctx);
|
|
2494
|
+
}
|
|
2495
|
+
} catch {}
|
|
2496
|
+
}
|
|
2497
|
+
const tables = Object.keys(def.schema);
|
|
2498
|
+
let written = 0;
|
|
2499
|
+
for (const table of tables) {
|
|
2500
|
+
written += (await ctx.rows(table)).length;
|
|
2501
|
+
}
|
|
2502
|
+
return {
|
|
2503
|
+
matched,
|
|
2504
|
+
written,
|
|
2505
|
+
tables,
|
|
2506
|
+
...firstEventKeys !== undefined ? { firstEventKeys } : {}
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2090
2509
|
export {
|
|
2510
|
+
toHandlerPayload,
|
|
2511
|
+
toContractCallPayload,
|
|
2512
|
+
runSubgraphTest,
|
|
2513
|
+
probeHandlers,
|
|
2091
2514
|
createTestContext,
|
|
2092
2515
|
buildEventPayload,
|
|
2093
2516
|
buildEvent
|
|
2094
2517
|
};
|
|
2095
2518
|
|
|
2096
|
-
//# debugId=
|
|
2519
|
+
//# debugId=ABB2AE2A4DA0D8AC64756E2164756E21
|
|
2097
2520
|
//# sourceMappingURL=index.js.map
|