@secondlayer/subgraphs 3.23.1 → 3.23.2
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 +11 -7
- package/dist/src/index.js +59 -50
- package/dist/src/index.js.map +4 -4
- package/dist/src/runtime/block-processor.d.ts +62 -9
- package/dist/src/runtime/block-processor.js +30 -20
- package/dist/src/runtime/block-processor.js.map +4 -4
- package/dist/src/runtime/catchup.d.ts +11 -7
- package/dist/src/runtime/catchup.js +37 -28
- package/dist/src/runtime/catchup.js.map +4 -4
- package/dist/src/runtime/context.js +12 -3
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +98 -89
- package/dist/src/runtime/processor.js.map +4 -4
- package/dist/src/runtime/reindex.d.ts +11 -7
- package/dist/src/runtime/reindex.js +59 -50
- package/dist/src/runtime/reindex.js.map +4 -4
- package/dist/src/runtime/reorg.d.ts +11 -7
- package/dist/src/runtime/reorg.js +44 -35
- package/dist/src/runtime/reorg.js.map +4 -4
- package/dist/src/runtime/runner.d.ts +36 -32
- package/dist/src/runtime/source-matcher.d.ts +11 -7
- package/dist/src/runtime/stats.d.ts +3 -3
- package/dist/src/schema/index.d.ts +11 -7
- package/dist/src/service.js +101 -92
- package/dist/src/service.js.map +4 -4
- package/dist/src/testing/index.d.ts +32 -28
- package/dist/src/testing/index.js +17 -8
- package/dist/src/testing/index.js.map +3 -3
- package/dist/src/triggers/index.d.ts +11 -7
- package/dist/src/types.d.ts +11 -7
- package/dist/src/validate.d.ts +11 -7
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AbiContract as AbiContract2 } from "@secondlayer/stacks/clarity";
|
|
2
|
+
import { AssetIdentifier } from "@secondlayer/stacks/filters";
|
|
2
3
|
/**
|
|
3
4
|
* How long a read stays valid.
|
|
4
5
|
*
|
|
@@ -147,38 +148,38 @@ interface FactoryScope {
|
|
|
147
148
|
/** FT event filters */
|
|
148
149
|
interface FtTransferFilter extends TraitScope {
|
|
149
150
|
type: "ft_transfer";
|
|
150
|
-
assetIdentifier?:
|
|
151
|
+
assetIdentifier?: AssetIdentifier;
|
|
151
152
|
sender?: string;
|
|
152
153
|
recipient?: string;
|
|
153
154
|
minAmount?: bigint;
|
|
154
155
|
}
|
|
155
156
|
interface FtMintFilter extends TraitScope {
|
|
156
157
|
type: "ft_mint";
|
|
157
|
-
assetIdentifier?:
|
|
158
|
+
assetIdentifier?: AssetIdentifier;
|
|
158
159
|
recipient?: string;
|
|
159
160
|
minAmount?: bigint;
|
|
160
161
|
}
|
|
161
162
|
interface FtBurnFilter extends TraitScope {
|
|
162
163
|
type: "ft_burn";
|
|
163
|
-
assetIdentifier?:
|
|
164
|
+
assetIdentifier?: AssetIdentifier;
|
|
164
165
|
sender?: string;
|
|
165
166
|
minAmount?: bigint;
|
|
166
167
|
}
|
|
167
168
|
/** NFT event filters */
|
|
168
169
|
interface NftTransferFilter extends TraitScope {
|
|
169
170
|
type: "nft_transfer";
|
|
170
|
-
assetIdentifier?:
|
|
171
|
+
assetIdentifier?: AssetIdentifier;
|
|
171
172
|
sender?: string;
|
|
172
173
|
recipient?: string;
|
|
173
174
|
}
|
|
174
175
|
interface NftMintFilter extends TraitScope {
|
|
175
176
|
type: "nft_mint";
|
|
176
|
-
assetIdentifier?:
|
|
177
|
+
assetIdentifier?: AssetIdentifier;
|
|
177
178
|
recipient?: string;
|
|
178
179
|
}
|
|
179
180
|
interface NftBurnFilter extends TraitScope {
|
|
180
181
|
type: "nft_burn";
|
|
181
|
-
assetIdentifier?:
|
|
182
|
+
assetIdentifier?: AssetIdentifier;
|
|
182
183
|
sender?: string;
|
|
183
184
|
}
|
|
184
185
|
/** Contract event filters */
|
|
@@ -235,7 +236,10 @@ type PrintField = ColumnType | {
|
|
|
235
236
|
interface PrintEventFilter extends TraitScope, FactoryScope {
|
|
236
237
|
type: "print_event";
|
|
237
238
|
/** One contract id, or a set of them (max 20). Supports `*` wildcards.
|
|
238
|
-
*
|
|
239
|
+
* COMPOSES with `trait` — the matcher ANDs them, so the pair means
|
|
240
|
+
* "contracts conforming to this trait, narrowed to these ids". (The Index
|
|
241
|
+
* read API refuses the combination, but that is its query planner's
|
|
242
|
+
* constraint, not a semantic one.) */
|
|
239
243
|
contractId?: string | readonly string[];
|
|
240
244
|
topic?: string;
|
|
241
245
|
/**
|
|
@@ -341,9 +345,58 @@ interface SubgraphDefinition {
|
|
|
341
345
|
/** Handler functions — keys must match source names (or "*" for catch-all) */
|
|
342
346
|
handlers: Record<string, SubgraphHandler>;
|
|
343
347
|
}
|
|
344
|
-
|
|
348
|
+
type TxRecord = {
|
|
349
|
+
tx_id: string
|
|
350
|
+
type: string
|
|
351
|
+
sender: string
|
|
352
|
+
status: string
|
|
353
|
+
/** Position within the block — the runner sorts dispatch into chain order. */
|
|
354
|
+
tx_index?: number
|
|
355
|
+
contract_id?: string | null
|
|
356
|
+
function_name?: string | null
|
|
357
|
+
function_args?: unknown | null
|
|
358
|
+
raw_result?: string | null
|
|
359
|
+
};
|
|
360
|
+
type EventRecord = {
|
|
361
|
+
id: string
|
|
362
|
+
tx_id: string
|
|
363
|
+
type: string
|
|
364
|
+
event_index: number
|
|
365
|
+
data: unknown
|
|
366
|
+
};
|
|
367
|
+
import { Database as Database2 } from "@secondlayer/shared/db";
|
|
368
|
+
import { Kysely as Kysely2, Transaction } from "kysely";
|
|
345
369
|
/** Drop a subgraph's cached route — call on redeploy/delete (conn may change). */
|
|
346
370
|
declare function invalidateSubgraphRoute(subgraphName: string): void;
|
|
371
|
+
/**
|
|
372
|
+
* Resolve each distinct trait used by a subgraph's sources to its conforming
|
|
373
|
+
* contract-id set, as of `blockHeight`, from the contract registry. Empty map
|
|
374
|
+
* when no source is trait-scoped (the common case → no DB work).
|
|
375
|
+
*/
|
|
376
|
+
/**
|
|
377
|
+
* Resolve each factory-scoped source's address set for this block.
|
|
378
|
+
*
|
|
379
|
+
* Two parts, and the ORDER is the guarantee:
|
|
380
|
+
* 1. addresses persisted by earlier blocks (`block_height <= blockHeight`,
|
|
381
|
+
* so a reindex sees exactly what the live walk saw), then
|
|
382
|
+
* 2. addresses revealed by THIS block's events — computed before matching,
|
|
383
|
+
* so a contract discovered in block N receives its own block-N events.
|
|
384
|
+
* Without this pass the first event from every discovered contract is
|
|
385
|
+
* silently lost.
|
|
386
|
+
*/
|
|
387
|
+
/**
|
|
388
|
+
* Exported as a test seam. Both guarantees this function carries — a contract
|
|
389
|
+
* discovered in block N receiving its OWN block-N events, and the persisted set
|
|
390
|
+
* being height-stamped so a reorg can roll it back — were asserted only by
|
|
391
|
+
* comment until now.
|
|
392
|
+
*/
|
|
393
|
+
declare function resolveFactoryContracts(subgraph: SubgraphDefinition, blockHeight: number, schemaName: string, db: Kysely2<Database2>, txs: TxRecord[], evts: EventRecord[]): Promise<{
|
|
394
|
+
resolved: Map<string, ReadonlySet<string>>
|
|
395
|
+
discovered: Array<{
|
|
396
|
+
sourceName: string
|
|
397
|
+
address: string
|
|
398
|
+
}>
|
|
399
|
+
}>;
|
|
347
400
|
interface ProcessBlockTiming {
|
|
348
401
|
totalMs: number;
|
|
349
402
|
handlerMs: number;
|
|
@@ -407,4 +460,4 @@ declare const BLOCK_RETRY_DELAYS_MS: number[];
|
|
|
407
460
|
*/
|
|
408
461
|
declare function processBlockWithRetry(subgraph: SubgraphDefinition, subgraphName: string, blockHeight: number, opts?: ProcessBlockOptions, retryDelaysMs?: number[]): Promise<ProcessBlockResult>;
|
|
409
462
|
declare function processBlock(subgraph: SubgraphDefinition, subgraphName: string, blockHeight: number, opts?: ProcessBlockOptions): Promise<ProcessBlockResult>;
|
|
410
|
-
export { processBlockWithRetry, processBlock, invalidateSubgraphRoute, ProcessBlockTiming, ProcessBlockResult, ProcessBlockOptions, PreloadedBlockData, BLOCK_RETRY_DELAYS_MS };
|
|
463
|
+
export { resolveFactoryContracts, processBlockWithRetry, processBlock, invalidateSubgraphRoute, ProcessBlockTiming, ProcessBlockResult, ProcessBlockOptions, PreloadedBlockData, BLOCK_RETRY_DELAYS_MS };
|
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/runtime/context.ts
|
|
5
|
-
import { logger } from "@secondlayer/shared/logger";
|
|
5
|
+
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
6
6
|
import { formatUnits } from "@secondlayer/stacks/utils";
|
|
7
7
|
import { sql } from "kysely";
|
|
8
8
|
|
|
@@ -147,6 +147,7 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
147
147
|
|
|
148
148
|
// src/runtime/chain-read.ts
|
|
149
149
|
import { getSourceDb } from "@secondlayer/shared/db";
|
|
150
|
+
import { logger } from "@secondlayer/shared/logger";
|
|
150
151
|
import { http, createPublicClient } from "@secondlayer/stacks";
|
|
151
152
|
import {
|
|
152
153
|
ContractResponseError,
|
|
@@ -232,6 +233,14 @@ function createChainReadClient(ctx) {
|
|
|
232
233
|
const pinnedTo = constant ? null : ctx.indexBlockHash;
|
|
233
234
|
const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
|
|
234
235
|
const resultHex = cached?.result_hex ?? await gate.run(async () => {
|
|
236
|
+
if (!ctx.indexBlockHash) {
|
|
237
|
+
logger.warn("Chain read is not pinned to a block (no index_block_hash)", {
|
|
238
|
+
contractId,
|
|
239
|
+
functionName: fn.name,
|
|
240
|
+
blockHeight: ctx.blockHeight,
|
|
241
|
+
note: 'cache: "contract-constant" asserts this value cannot change; the node answered at its own tip'
|
|
242
|
+
});
|
|
243
|
+
}
|
|
235
244
|
const value2 = await readContract(nodeClient(rpcUrl), {
|
|
236
245
|
contract: contractId,
|
|
237
246
|
functionName: fn.name,
|
|
@@ -346,7 +355,7 @@ class SubgraphContext {
|
|
|
346
355
|
data: { ...key, ...row, ...meta, _upsert_keys: keyColumns }
|
|
347
356
|
});
|
|
348
357
|
} else {
|
|
349
|
-
|
|
358
|
+
logger2.warn("upsert called without matching uniqueKeys constraint, using fallback", {
|
|
350
359
|
table,
|
|
351
360
|
keys: keyColumns
|
|
352
361
|
});
|
|
@@ -906,7 +915,7 @@ function decodeFunctionArgs(args) {
|
|
|
906
915
|
|
|
907
916
|
// src/runtime/runner.ts
|
|
908
917
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
909
|
-
import { logger as
|
|
918
|
+
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
910
919
|
import {
|
|
911
920
|
clarityValueToJS,
|
|
912
921
|
deserializeCV as deserializeCV3,
|
|
@@ -1257,7 +1266,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1257
1266
|
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1258
1267
|
for (const { tx, event, sourceName } of units) {
|
|
1259
1268
|
if (errors >= threshold) {
|
|
1260
|
-
|
|
1269
|
+
logger3.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1261
1270
|
subgraph: subgraph.name,
|
|
1262
1271
|
errors,
|
|
1263
1272
|
threshold
|
|
@@ -1266,7 +1275,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1266
1275
|
}
|
|
1267
1276
|
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1268
1277
|
if (!handler) {
|
|
1269
|
-
|
|
1278
|
+
logger3.warn("No handler found for source", {
|
|
1270
1279
|
subgraph: subgraph.name,
|
|
1271
1280
|
sourceName,
|
|
1272
1281
|
txId: tx.tx_id
|
|
@@ -1323,7 +1332,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1323
1332
|
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1324
1333
|
if (!verdict.ok) {
|
|
1325
1334
|
skipped++;
|
|
1326
|
-
|
|
1335
|
+
logger3.warn("Print payload does not match declared prints schema", {
|
|
1327
1336
|
subgraph: subgraph.name,
|
|
1328
1337
|
sourceName,
|
|
1329
1338
|
topic: printPayload.topic,
|
|
@@ -1338,7 +1347,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1338
1347
|
} catch (err) {
|
|
1339
1348
|
ctx.rollbackTo(checkpoint);
|
|
1340
1349
|
errors++;
|
|
1341
|
-
|
|
1350
|
+
logger3.error("Subgraph handler error", {
|
|
1342
1351
|
subgraph: subgraph.name,
|
|
1343
1352
|
sourceName,
|
|
1344
1353
|
txId: tx.tx_id,
|
|
@@ -1623,7 +1632,7 @@ import {
|
|
|
1623
1632
|
resolveSubgraphDb,
|
|
1624
1633
|
updateSubgraphStatus
|
|
1625
1634
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
1626
|
-
import { logger as
|
|
1635
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
1627
1636
|
import { sql as sql3 } from "kysely";
|
|
1628
1637
|
|
|
1629
1638
|
// src/runtime/block-source.ts
|
|
@@ -1631,7 +1640,7 @@ import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
|
1631
1640
|
import {
|
|
1632
1641
|
IndexHttpClient
|
|
1633
1642
|
} from "@secondlayer/shared/index-http";
|
|
1634
|
-
import { logger as
|
|
1643
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
1635
1644
|
|
|
1636
1645
|
// src/runtime/batch-loader.ts
|
|
1637
1646
|
async function loadBlockRange(db, fromHeight, toHeight) {
|
|
@@ -1955,7 +1964,7 @@ class FallbackBlockSource {
|
|
|
1955
1964
|
try {
|
|
1956
1965
|
return await this.primary.getTip();
|
|
1957
1966
|
} catch (err) {
|
|
1958
|
-
|
|
1967
|
+
logger4.warn("block source primary getTip failed — using DB tap", {
|
|
1959
1968
|
error: err instanceof Error ? err.message : String(err)
|
|
1960
1969
|
});
|
|
1961
1970
|
return this.fallback.getTip();
|
|
@@ -1965,7 +1974,7 @@ class FallbackBlockSource {
|
|
|
1965
1974
|
try {
|
|
1966
1975
|
return await this.primary.loadBlockRange(fromHeight, toHeight);
|
|
1967
1976
|
} catch (err) {
|
|
1968
|
-
|
|
1977
|
+
logger4.warn("block source primary loadBlockRange failed — using DB tap", {
|
|
1969
1978
|
from: fromHeight,
|
|
1970
1979
|
to: toHeight,
|
|
1971
1980
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -1997,7 +2006,7 @@ function resolveBlockSource(subgraph) {
|
|
|
1997
2006
|
return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined, needsTransactionData(subgraph)), postgresBlockSource);
|
|
1998
2007
|
}
|
|
1999
2008
|
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
|
|
2000
|
-
|
|
2009
|
+
logger4.debug("Subgraph not streams-index eligible, using DB tap", {
|
|
2001
2010
|
subgraph: subgraph.name
|
|
2002
2011
|
});
|
|
2003
2012
|
}
|
|
@@ -2006,7 +2015,7 @@ function resolveBlockSource(subgraph) {
|
|
|
2006
2015
|
|
|
2007
2016
|
// src/runtime/outbox-emit.ts
|
|
2008
2017
|
import { createHash as createHash2 } from "node:crypto";
|
|
2009
|
-
import { logger as
|
|
2018
|
+
import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
2010
2019
|
var loggedKillSwitch = false;
|
|
2011
2020
|
var OP_VERB = {
|
|
2012
2021
|
insert: "created",
|
|
@@ -2030,7 +2039,7 @@ function stableStringify(obj) {
|
|
|
2030
2039
|
async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, blockHeight) {
|
|
2031
2040
|
if (!isEmitOutboxEnabled()) {
|
|
2032
2041
|
if (!loggedKillSwitch) {
|
|
2033
|
-
|
|
2042
|
+
logger5.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
|
|
2034
2043
|
loggedKillSwitch = true;
|
|
2035
2044
|
}
|
|
2036
2045
|
return 0;
|
|
@@ -2350,7 +2359,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
2350
2359
|
const delay = retryDelaysMs[attempt];
|
|
2351
2360
|
if (delay === undefined)
|
|
2352
2361
|
break;
|
|
2353
|
-
|
|
2362
|
+
logger6.warn("Block processing failed, retrying", {
|
|
2354
2363
|
subgraph: subgraphName,
|
|
2355
2364
|
blockHeight,
|
|
2356
2365
|
attempt: attempt + 1,
|
|
@@ -2382,7 +2391,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2382
2391
|
} else {
|
|
2383
2392
|
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
2384
2393
|
if (!data) {
|
|
2385
|
-
|
|
2394
|
+
logger6.debug("Block not found or non-canonical for subgraph processing", {
|
|
2386
2395
|
subgraph: subgraphName,
|
|
2387
2396
|
blockHeight
|
|
2388
2397
|
});
|
|
@@ -2523,7 +2532,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2523
2532
|
});
|
|
2524
2533
|
} catch (err) {
|
|
2525
2534
|
if (err instanceof CursorRaceLostError) {
|
|
2526
|
-
|
|
2535
|
+
logger6.warn("cursor race lost — block already covered", {
|
|
2527
2536
|
subgraph: subgraphName,
|
|
2528
2537
|
blockHeight,
|
|
2529
2538
|
error: err.message
|
|
@@ -2547,7 +2556,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2547
2556
|
const { rows } = await sql3.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
|
|
2548
2557
|
const count = Number(rows[0]?.count ?? 0);
|
|
2549
2558
|
if (count >= 1e7) {
|
|
2550
|
-
|
|
2559
|
+
logger6.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
2551
2560
|
subgraph: subgraphName,
|
|
2552
2561
|
table,
|
|
2553
2562
|
count
|
|
@@ -2555,7 +2564,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2555
2564
|
}
|
|
2556
2565
|
}
|
|
2557
2566
|
} catch (err) {
|
|
2558
|
-
|
|
2567
|
+
logger6.debug("Row count sample failed", {
|
|
2559
2568
|
subgraph: subgraphName,
|
|
2560
2569
|
error: err instanceof Error ? err.message : String(err)
|
|
2561
2570
|
});
|
|
@@ -2567,11 +2576,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2567
2576
|
return result;
|
|
2568
2577
|
}
|
|
2569
2578
|
export {
|
|
2579
|
+
resolveFactoryContracts,
|
|
2570
2580
|
processBlockWithRetry,
|
|
2571
2581
|
processBlock,
|
|
2572
2582
|
invalidateSubgraphRoute,
|
|
2573
2583
|
BLOCK_RETRY_DELAYS_MS
|
|
2574
2584
|
};
|
|
2575
2585
|
|
|
2576
|
-
//# debugId=
|
|
2586
|
+
//# debugId=ED4E61C22DF18FC964756E2164756E21
|
|
2577
2587
|
//# sourceMappingURL=block-processor.js.map
|