@secondlayer/subgraphs 3.10.0 → 3.12.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 +5 -0
- package/dist/src/index.d.ts +29 -1
- package/dist/src/index.js +78 -3
- package/dist/src/index.js.map +6 -6
- package/dist/src/runtime/block-processor.d.ts +3 -0
- package/dist/src/runtime/block-processor.js +43 -3
- package/dist/src/runtime/block-processor.js.map +3 -3
- package/dist/src/runtime/catchup.d.ts +3 -0
- package/dist/src/runtime/catchup.js +43 -3
- package/dist/src/runtime/catchup.js.map +3 -3
- package/dist/src/runtime/processor.js +102 -4
- package/dist/src/runtime/processor.js.map +5 -5
- package/dist/src/runtime/reindex.d.ts +7 -0
- package/dist/src/runtime/reindex.js +74 -3
- package/dist/src/runtime/reindex.js.map +4 -4
- package/dist/src/runtime/reorg.d.ts +3 -0
- package/dist/src/runtime/reorg.js +43 -3
- package/dist/src/runtime/reorg.js.map +3 -3
- package/dist/src/runtime/replay.js +43 -3
- package/dist/src/runtime/replay.js.map +3 -3
- package/dist/src/runtime/runner.d.ts +3 -0
- package/dist/src/schema/index.d.ts +3 -0
- package/dist/src/schema/index.js +2 -1
- package/dist/src/schema/index.js.map +4 -4
- package/dist/src/service.js +102 -4
- package/dist/src/service.js.map +5 -5
- package/dist/src/types.d.ts +3 -0
- package/dist/src/validate.d.ts +3 -0
- package/dist/src/validate.js +2 -1
- package/dist/src/validate.js.map +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Typed on-chain indexing for Stacks. Declare event filters + column schema with `defineSubgraph()`; the runtime decodes blocks, matches filters, runs your handlers inside a transactional context, and exposes the result as a Postgres schema you query over REST or SQL.
|
|
4
4
|
|
|
5
|
+
Subgraphs have a visibility: managed deploys default `visibility: public` — anon-readable at `/v1/subgraphs/<name>/<table>`, in a single global public namespace claimed on publish (409 `PUBLIC_NAME_TAKEN` if the name is taken). BYO-database deploys default private. Override with `--visibility public|private` at deploy, or flip later with `sl subgraphs publish|unpublish <name>`.
|
|
6
|
+
|
|
5
7
|
Subgraph rows fan out to HTTP subscribers through a post-flush outbox emitter — signed Standard Webhooks POSTs with retries, circuit breaker, and replay.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
@@ -114,6 +116,9 @@ reads from there. Deploy with a connection string:
|
|
|
114
116
|
sl subgraphs deploy subgraphs/my.ts --database-url "postgres://user:pass@your-host:5432/db"
|
|
115
117
|
```
|
|
116
118
|
|
|
119
|
+
BYO deploys default `visibility: private` (managed deploys default public);
|
|
120
|
+
pass `--visibility public` or `sl subgraphs publish <name>` to open reads.
|
|
121
|
+
|
|
117
122
|
The connection string is stored encrypted at rest (AES-GCM, keyed by
|
|
118
123
|
`SECONDLAYER_SECRETS_KEY`) and never returned in API responses. The server
|
|
119
124
|
verifies the connection before deploying. Once deployed, query the
|
package/dist/src/index.d.ts
CHANGED
|
@@ -237,6 +237,9 @@ interface SubgraphDefinition {
|
|
|
237
237
|
description?: string;
|
|
238
238
|
/** Block height to start indexing from (default: 1) */
|
|
239
239
|
startBlock?: number;
|
|
240
|
+
/** 'concurrent' = tip-first: live at tip now, history backfills behind.
|
|
241
|
+
* Requires order-tolerant handlers. Default 'blocking'. */
|
|
242
|
+
backfillMode?: "blocking" | "concurrent";
|
|
240
243
|
/** Named source filters — keys become handler keys */
|
|
241
244
|
sources: Record<string, SubgraphFilter>;
|
|
242
245
|
/** Tables in this subgraph */
|
|
@@ -252,6 +255,8 @@ interface ReindexOptions {
|
|
|
252
255
|
fromBlock?: number;
|
|
253
256
|
toBlock?: number;
|
|
254
257
|
schemaName?: string;
|
|
258
|
+
/** Op row to receive processed_events on each progress flush. */
|
|
259
|
+
operationId?: string;
|
|
255
260
|
signal?: AbortSignal;
|
|
256
261
|
}
|
|
257
262
|
/**
|
|
@@ -269,6 +274,7 @@ declare function reindexSubgraph(def: SubgraphDefinition, opts?: ReindexOptions)
|
|
|
269
274
|
*/
|
|
270
275
|
declare function resumeReindex(def: SubgraphDefinition, opts: {
|
|
271
276
|
schemaName: string
|
|
277
|
+
operationId?: string
|
|
272
278
|
signal?: AbortSignal
|
|
273
279
|
}): Promise<{
|
|
274
280
|
processed: number
|
|
@@ -282,6 +288,7 @@ declare function backfillSubgraph(def: SubgraphDefinition, opts: {
|
|
|
282
288
|
fromBlock: number
|
|
283
289
|
toBlock: number
|
|
284
290
|
schemaName?: string
|
|
291
|
+
operationId?: string
|
|
285
292
|
signal?: AbortSignal
|
|
286
293
|
}): Promise<{
|
|
287
294
|
processed: number
|
|
@@ -727,6 +734,14 @@ interface ColumnDiff {
|
|
|
727
734
|
* Compare two multi-table subgraph schemas and return differences.
|
|
728
735
|
*/
|
|
729
736
|
declare function diffSchema(existing: SubgraphSchema, incoming: SubgraphSchema): TableDiff;
|
|
737
|
+
/**
|
|
738
|
+
* Returns true if the diff contains any breaking changes
|
|
739
|
+
* (removed tables, removed columns, or changed column types).
|
|
740
|
+
*/
|
|
741
|
+
declare function hasBreakingChanges(diff: TableDiff): {
|
|
742
|
+
breaking: boolean
|
|
743
|
+
reasons: string[]
|
|
744
|
+
};
|
|
730
745
|
interface DeployDiff {
|
|
731
746
|
addedTables: string[];
|
|
732
747
|
removedTables: string[];
|
|
@@ -794,4 +809,17 @@ declare function deploySchema(db: AnyDb, def: SubgraphDefinition, handlerPath: s
|
|
|
794
809
|
version: string
|
|
795
810
|
diff?: DeployDiff
|
|
796
811
|
}>;
|
|
797
|
-
|
|
812
|
+
/** A (decoded event type, optional contract scope) pair the sparse probe
|
|
813
|
+
* checks. Contract scoping is what makes token-subgraph reindexes leap over
|
|
814
|
+
* everything that isn't their token. */
|
|
815
|
+
type SparseProbeTarget = {
|
|
816
|
+
eventType: string
|
|
817
|
+
contractId?: string
|
|
818
|
+
};
|
|
819
|
+
/** Sparse scanning is sound only when EVERY source is an event-type filter —
|
|
820
|
+
* a contract_call/contract_deploy source matches transactions, which the
|
|
821
|
+
* event probe can't see. */
|
|
822
|
+
declare function canSparseScan(subgraph: SubgraphDefinition): boolean;
|
|
823
|
+
/** Probe targets for a subgraph's filters: decoded type + contract scope when\\n* the filter pins one (assetIdentifier "SP….contract::asset" or contractId). */
|
|
824
|
+
declare function sparseProbeTargets(subgraph: SubgraphDefinition): SparseProbeTarget[];
|
|
825
|
+
export { validateSubgraphDefinition, sparseProbeTargets, resumeReindex, renderDeployPlan, reindexSubgraph, pgSchemaName, hasBreakingChanges, generateSubgraphSQL, generatePrismaSchema, generateKyselySchema, generateIndexSchema, generateDrizzleSchema, diffSchema, deploySchema, defineSubgraph, canSparseScan, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubscribeOptions, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, SparseProbeTarget, RowValue, ReindexOptions, PrismaGenOptions, PrintEventPayload, PrintEventFor, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, KyselyGenOptions, InferTableRow, InferSubgraphClient, InferColumnType, IndexCodegenTarget, IndexCodegenOptions, INDEX_CODEGEN_TABLES, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, DrizzleGenOptions, DeployPlan, DeployDiff, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, ByoMigrationPlan, ByoBreakingChangeError, AnyEvent, AggregateSpec, AggregateResult };
|
package/dist/src/index.js
CHANGED
|
@@ -69,6 +69,7 @@ var SubgraphDefinitionSchema = z.object({
|
|
|
69
69
|
version: z.string().optional(),
|
|
70
70
|
description: z.string().optional(),
|
|
71
71
|
startBlock: z.number().int().nonnegative().optional(),
|
|
72
|
+
backfillMode: z.enum(["blocking", "concurrent"]).optional(),
|
|
72
73
|
sources: z.record(z.string(), SubgraphFilterSchema).refine((s) => Object.keys(s).length > 0, "Must have at least one source"),
|
|
73
74
|
schema: SubgraphSchemaSchema,
|
|
74
75
|
handlers: z.record(z.string(), z.any())
|
|
@@ -1195,6 +1196,28 @@ function reconstructEvent(e) {
|
|
|
1195
1196
|
}
|
|
1196
1197
|
|
|
1197
1198
|
// src/runtime/block-source.ts
|
|
1199
|
+
function canSparseScan(subgraph) {
|
|
1200
|
+
if (Array.isArray(subgraph.sources))
|
|
1201
|
+
return false;
|
|
1202
|
+
const filters = sourceFilters(subgraph);
|
|
1203
|
+
if (filters.length === 0)
|
|
1204
|
+
return false;
|
|
1205
|
+
return filters.every((f) => Boolean(EVENT_FILTER_TO_INDEX_TYPE[f.type]));
|
|
1206
|
+
}
|
|
1207
|
+
function sparseProbeTargets(subgraph) {
|
|
1208
|
+
const targets = new Map;
|
|
1209
|
+
for (const f of sourceFilters(subgraph)) {
|
|
1210
|
+
const eventType = EVENT_FILTER_TO_INDEX_TYPE[f.type];
|
|
1211
|
+
if (!eventType)
|
|
1212
|
+
continue;
|
|
1213
|
+
const scoped = f;
|
|
1214
|
+
const contractId = scoped.contractId ?? scoped.assetIdentifier?.split("::")[0];
|
|
1215
|
+
const key = `${eventType}|${contractId ?? ""}`;
|
|
1216
|
+
targets.set(key, { eventType, ...contractId ? { contractId } : {} });
|
|
1217
|
+
}
|
|
1218
|
+
return [...targets.values()];
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1198
1221
|
class PostgresBlockSource {
|
|
1199
1222
|
async getTip() {
|
|
1200
1223
|
const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
|
|
@@ -1257,9 +1280,18 @@ function isStreamsIndexEligible(subgraph) {
|
|
|
1257
1280
|
class PublicApiBlockSource {
|
|
1258
1281
|
http;
|
|
1259
1282
|
eventTypes;
|
|
1260
|
-
|
|
1283
|
+
probeTargets;
|
|
1284
|
+
constructor(http, eventTypes, probeTargets) {
|
|
1261
1285
|
this.http = http;
|
|
1262
1286
|
this.eventTypes = eventTypes;
|
|
1287
|
+
this.probeTargets = probeTargets;
|
|
1288
|
+
}
|
|
1289
|
+
async nextDataHeight(afterHeight, untilHeight) {
|
|
1290
|
+
if (!this.probeTargets?.length)
|
|
1291
|
+
return afterHeight + 1;
|
|
1292
|
+
const hits = await Promise.all(this.probeTargets.map((t) => this.http.firstEventHeight(t.eventType, afterHeight + 1, untilHeight, t.contractId)));
|
|
1293
|
+
const found = hits.filter((h) => h !== null);
|
|
1294
|
+
return found.length ? Math.min(...found) : null;
|
|
1263
1295
|
}
|
|
1264
1296
|
getTip() {
|
|
1265
1297
|
return this.http.getIndexTip();
|
|
@@ -1323,6 +1355,15 @@ class FallbackBlockSource {
|
|
|
1323
1355
|
return this.fallback.loadBlockRange(fromHeight, toHeight);
|
|
1324
1356
|
}
|
|
1325
1357
|
}
|
|
1358
|
+
async nextDataHeight(afterHeight, untilHeight) {
|
|
1359
|
+
if (!this.primary.nextDataHeight)
|
|
1360
|
+
return afterHeight + 1;
|
|
1361
|
+
try {
|
|
1362
|
+
return await this.primary.nextDataHeight(afterHeight, untilHeight);
|
|
1363
|
+
} catch {
|
|
1364
|
+
return afterHeight + 1;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1326
1367
|
}
|
|
1327
1368
|
var postgresBlockSource = new PostgresBlockSource;
|
|
1328
1369
|
function buildHttpClient() {
|
|
@@ -1335,7 +1376,7 @@ function buildHttpClient() {
|
|
|
1335
1376
|
}
|
|
1336
1377
|
function resolveBlockSource(subgraph) {
|
|
1337
1378
|
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph && isStreamsIndexEligible(subgraph)) {
|
|
1338
|
-
return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph)), postgresBlockSource);
|
|
1379
|
+
return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined), postgresBlockSource);
|
|
1339
1380
|
}
|
|
1340
1381
|
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
|
|
1341
1382
|
logger3.debug("Subgraph not streams-index eligible, using DB tap", {
|
|
@@ -1818,6 +1859,7 @@ import {
|
|
|
1818
1859
|
recordGapBatch,
|
|
1819
1860
|
resolveGaps
|
|
1820
1861
|
} from "@secondlayer/shared/db/queries/subgraph-gaps";
|
|
1862
|
+
import { updateOperationProcessedEvents } from "@secondlayer/shared/db/queries/subgraph-operations";
|
|
1821
1863
|
import {
|
|
1822
1864
|
recordSubgraphProcessed as recordSubgraphProcessed2,
|
|
1823
1865
|
updateSubgraphStatus as updateSubgraphStatus2
|
|
@@ -2005,6 +2047,7 @@ async function processBlockRange(def, opts) {
|
|
|
2005
2047
|
let batchSize = batchConfig.defaultBatchSize;
|
|
2006
2048
|
let currentHeight = fromBlock;
|
|
2007
2049
|
let aborted = false;
|
|
2050
|
+
const sparse = Boolean(source.nextDataHeight && canSparseScan(def));
|
|
2008
2051
|
const flushHealth = async () => {
|
|
2009
2052
|
if (pendingEventsProcessed === 0 && pendingErrors === 0)
|
|
2010
2053
|
return;
|
|
@@ -2035,6 +2078,7 @@ async function processBlockRange(def, opts) {
|
|
|
2035
2078
|
nextBatchPromise = source.loadBlockRange(nextStart, nextBatchEnd);
|
|
2036
2079
|
}
|
|
2037
2080
|
const batchFailedBlocks = [];
|
|
2081
|
+
let batchMatched = 0;
|
|
2038
2082
|
for (let height = currentHeight;height <= batchEnd; height++) {
|
|
2039
2083
|
const blockData = batch.get(height);
|
|
2040
2084
|
if (!blockData) {
|
|
@@ -2064,6 +2108,7 @@ async function processBlockRange(def, opts) {
|
|
|
2064
2108
|
continue;
|
|
2065
2109
|
}
|
|
2066
2110
|
blocksProcessed++;
|
|
2111
|
+
batchMatched += result.matched;
|
|
2067
2112
|
totalEventsProcessed += result.processed;
|
|
2068
2113
|
totalErrors += result.errors;
|
|
2069
2114
|
pendingEventsProcessed += result.processed;
|
|
@@ -2081,6 +2126,9 @@ async function processBlockRange(def, opts) {
|
|
|
2081
2126
|
const shouldFlushProgress = blocksProcessed % 100 === 0 || now - lastProgressFlushAt >= PROGRESS_FLUSH_INTERVAL_MS;
|
|
2082
2127
|
if (shouldFlushProgress) {
|
|
2083
2128
|
await updateSubgraphStatus2(targetDb, subgraphName, status, height);
|
|
2129
|
+
if (opts.operationId) {
|
|
2130
|
+
await updateOperationProcessedEvents(targetDb, opts.operationId, totalEventsProcessed).catch(() => {});
|
|
2131
|
+
}
|
|
2084
2132
|
lastProgressFlushAt = now;
|
|
2085
2133
|
}
|
|
2086
2134
|
if (blocksProcessed % LOG_INTERVAL === 0) {
|
|
@@ -2109,6 +2157,27 @@ async function processBlockRange(def, opts) {
|
|
|
2109
2157
|
});
|
|
2110
2158
|
});
|
|
2111
2159
|
}
|
|
2160
|
+
if (sparse && batchMatched === 0 && batchEnd < toBlock && source.nextDataHeight) {
|
|
2161
|
+
const next = await source.nextDataHeight(batchEnd, toBlock);
|
|
2162
|
+
const jumpTo = next === null ? toBlock + 1 : Math.max(next, batchEnd + 1);
|
|
2163
|
+
if (jumpTo > batchEnd + 1) {
|
|
2164
|
+
const skipped = Math.min(jumpTo, toBlock + 1) - (batchEnd + 1);
|
|
2165
|
+
blocksProcessed += skipped;
|
|
2166
|
+
await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
|
|
2167
|
+
logger6.info("Sparse skip", {
|
|
2168
|
+
subgraph: subgraphName,
|
|
2169
|
+
from: batchEnd + 1,
|
|
2170
|
+
to: jumpTo - 1,
|
|
2171
|
+
skipped
|
|
2172
|
+
});
|
|
2173
|
+
currentHeight = jumpTo;
|
|
2174
|
+
if (currentHeight <= toBlock) {
|
|
2175
|
+
nextBatchEnd = Math.min(currentHeight + batchSize - 1, toBlock);
|
|
2176
|
+
nextBatchPromise = source.loadBlockRange(currentHeight, nextBatchEnd);
|
|
2177
|
+
}
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2112
2181
|
const avg = avgEventsPerBlock(batch);
|
|
2113
2182
|
if (avg > 50)
|
|
2114
2183
|
batchSize = Math.max(Math.round(batchSize * 0.5), batchConfig.minBatchSize);
|
|
@@ -2177,6 +2246,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
2177
2246
|
isCatchup: false,
|
|
2178
2247
|
apiKeyId: null,
|
|
2179
2248
|
subgraphId: subgraphRow?.id,
|
|
2249
|
+
operationId: opts?.operationId,
|
|
2180
2250
|
signal: opts?.signal
|
|
2181
2251
|
});
|
|
2182
2252
|
if (result.aborted) {
|
|
@@ -2252,6 +2322,7 @@ async function resumeReindex(def, opts) {
|
|
|
2252
2322
|
isCatchup: false,
|
|
2253
2323
|
apiKeyId: null,
|
|
2254
2324
|
subgraphId: row.id,
|
|
2325
|
+
operationId: opts.operationId,
|
|
2255
2326
|
signal: opts.signal
|
|
2256
2327
|
});
|
|
2257
2328
|
if (result.aborted) {
|
|
@@ -2300,6 +2371,7 @@ async function backfillSubgraph(def, opts) {
|
|
|
2300
2371
|
isCatchup: false,
|
|
2301
2372
|
apiKeyId: null,
|
|
2302
2373
|
subgraphId: subgraphRow?.id,
|
|
2374
|
+
operationId: opts.operationId,
|
|
2303
2375
|
signal: opts.signal
|
|
2304
2376
|
});
|
|
2305
2377
|
if (result.aborted) {
|
|
@@ -3174,10 +3246,12 @@ function getDefault(type) {
|
|
|
3174
3246
|
}
|
|
3175
3247
|
export {
|
|
3176
3248
|
validateSubgraphDefinition,
|
|
3249
|
+
sparseProbeTargets,
|
|
3177
3250
|
resumeReindex,
|
|
3178
3251
|
renderDeployPlan,
|
|
3179
3252
|
reindexSubgraph,
|
|
3180
3253
|
pgSchemaName,
|
|
3254
|
+
hasBreakingChanges,
|
|
3181
3255
|
generateSubgraphSQL,
|
|
3182
3256
|
generatePrismaSchema,
|
|
3183
3257
|
generateKyselySchema,
|
|
@@ -3186,10 +3260,11 @@ export {
|
|
|
3186
3260
|
diffSchema,
|
|
3187
3261
|
deploySchema,
|
|
3188
3262
|
defineSubgraph,
|
|
3263
|
+
canSparseScan,
|
|
3189
3264
|
backfillSubgraph,
|
|
3190
3265
|
INDEX_CODEGEN_TABLES,
|
|
3191
3266
|
ByoBreakingChangeError
|
|
3192
3267
|
};
|
|
3193
3268
|
|
|
3194
|
-
//# debugId=
|
|
3269
|
+
//# debugId=415665ECDA2A436C64756E2164756E21
|
|
3195
3270
|
//# sourceMappingURL=index.js.map
|