@secondlayer/subgraphs 0.7.2 → 0.8.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/dist/src/index.d.ts +15 -1
- package/dist/src/index.js +134 -15
- package/dist/src/index.js.map +5 -5
- package/dist/src/runtime/block-processor.js +12 -6
- package/dist/src/runtime/block-processor.js.map +4 -4
- package/dist/src/runtime/catchup.js +12 -6
- package/dist/src/runtime/catchup.js.map +4 -4
- package/dist/src/runtime/processor.js +12 -6
- package/dist/src/runtime/processor.js.map +4 -4
- package/dist/src/runtime/reindex.d.ts +15 -1
- package/dist/src/runtime/reindex.js +134 -15
- package/dist/src/runtime/reindex.js.map +5 -5
- package/dist/src/runtime/reorg.js +12 -6
- package/dist/src/runtime/reorg.js.map +4 -4
- package/dist/src/runtime/source-matcher.js +9 -3
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/service.js +12 -6
- package/dist/src/service.js.map +4 -4
- package/package.json +2 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -87,15 +87,28 @@ interface ReindexOptions {
|
|
|
87
87
|
fromBlock?: number;
|
|
88
88
|
toBlock?: number;
|
|
89
89
|
schemaName?: string;
|
|
90
|
+
signal?: AbortSignal;
|
|
90
91
|
}
|
|
91
92
|
/**
|
|
92
93
|
* Reindex a subgraph by dropping its tables, recreating them, and reprocessing
|
|
93
94
|
* all historical blocks through the handler pipeline.
|
|
95
|
+
* Supports cancellation via AbortSignal — on shutdown abort, status stays
|
|
96
|
+
* "reindexing" for auto-resume; on user cancel, status resets to "active".
|
|
94
97
|
*/
|
|
95
98
|
declare function reindexSubgraph(def: SubgraphDefinition, opts?: ReindexOptions): Promise<{
|
|
96
99
|
processed: number
|
|
97
100
|
}>;
|
|
98
101
|
/**
|
|
102
|
+
* Resume a previously interrupted reindex. Skips schema drop (already done)
|
|
103
|
+
* and continues from last_processed_block + 1.
|
|
104
|
+
*/
|
|
105
|
+
declare function resumeReindex(def: SubgraphDefinition, opts: {
|
|
106
|
+
schemaName: string
|
|
107
|
+
signal?: AbortSignal
|
|
108
|
+
}): Promise<{
|
|
109
|
+
processed: number
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
99
112
|
* Backfill a subgraph by re-processing a block range WITHOUT dropping the schema.
|
|
100
113
|
* Uses upserts so existing data is updated, not duplicated. Safe to run while
|
|
101
114
|
* the subgraph is actively syncing.
|
|
@@ -104,6 +117,7 @@ declare function backfillSubgraph(def: SubgraphDefinition, opts: {
|
|
|
104
117
|
fromBlock: number
|
|
105
118
|
toBlock: number
|
|
106
119
|
schemaName?: string
|
|
120
|
+
signal?: AbortSignal
|
|
107
121
|
}): Promise<{
|
|
108
122
|
processed: number
|
|
109
123
|
}>;
|
|
@@ -230,4 +244,4 @@ interface SubgraphTableClient<TRow> {
|
|
|
230
244
|
type InferSubgraphClient<T> = T extends {
|
|
231
245
|
schema: infer S
|
|
232
246
|
} ? { [K in keyof S] : S[K] extends SubgraphTable ? SubgraphTableClient<InferTableRow<S[K]>> : never } : never;
|
|
233
|
-
export { validateSubgraphDefinition, sourceKey, reindexSubgraph, pgSchemaName, generateSubgraphSQL, diffSchema, deploySchema, defineSubgraph, backfillSubgraph, WhereInput, TableDiff, SystemRow, SubgraphTableClient, SubgraphTable, SubgraphSource, SubgraphSchema, SubgraphHandler, SubgraphDefinition, SubgraphContext, SubgraphColumn, ReindexOptions, InferTableRow, InferSubgraphClient, InferColumnType, GeneratedSQL, FindManyOptions, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff };
|
|
247
|
+
export { validateSubgraphDefinition, sourceKey, resumeReindex, reindexSubgraph, pgSchemaName, generateSubgraphSQL, diffSchema, deploySchema, defineSubgraph, backfillSubgraph, WhereInput, TableDiff, SystemRow, SubgraphTableClient, SubgraphTable, SubgraphSource, SubgraphSchema, SubgraphHandler, SubgraphDefinition, SubgraphContext, SubgraphColumn, ReindexOptions, InferTableRow, InferSubgraphClient, InferColumnType, GeneratedSQL, FindManyOptions, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff };
|
package/dist/src/index.js
CHANGED
|
@@ -411,11 +411,17 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
// src/runtime/source-matcher.ts
|
|
414
|
+
var patternCache = new Map;
|
|
414
415
|
function matchPattern(value, pattern) {
|
|
415
416
|
if (!pattern.includes("*"))
|
|
416
417
|
return value === pattern;
|
|
417
|
-
|
|
418
|
-
|
|
418
|
+
let re = patternCache.get(pattern);
|
|
419
|
+
if (!re) {
|
|
420
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
421
|
+
re = new RegExp(`^${regex}$`);
|
|
422
|
+
patternCache.set(pattern, re);
|
|
423
|
+
}
|
|
424
|
+
return re.test(value);
|
|
419
425
|
}
|
|
420
426
|
function matchSource(source, transactions, eventsByTx) {
|
|
421
427
|
const results = [];
|
|
@@ -608,10 +614,10 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
608
614
|
try {
|
|
609
615
|
const tables = Object.keys(subgraph.schema);
|
|
610
616
|
for (const table of tables) {
|
|
611
|
-
const { rows } = await sql2.raw(`SELECT
|
|
612
|
-
const count = Number(rows[0]
|
|
617
|
+
const { rows } = await sql2.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(db);
|
|
618
|
+
const count = Number(rows[0]?.count ?? 0);
|
|
613
619
|
if (count >= 1e7) {
|
|
614
|
-
logger3.warn("Subgraph table exceeds 10M rows", {
|
|
620
|
+
logger3.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
615
621
|
subgraph: subgraphName,
|
|
616
622
|
table,
|
|
617
623
|
count
|
|
@@ -865,8 +871,18 @@ async function processBlockRange(def, opts) {
|
|
|
865
871
|
let totalErrors = 0;
|
|
866
872
|
let batchSize = DEFAULT_BATCH_SIZE;
|
|
867
873
|
let currentHeight = fromBlock;
|
|
874
|
+
let aborted = false;
|
|
868
875
|
let nextBatchPromise = loadBlockRange(db, currentHeight, Math.min(currentHeight + batchSize - 1, toBlock));
|
|
869
876
|
while (currentHeight <= toBlock) {
|
|
877
|
+
if (opts.signal?.aborted) {
|
|
878
|
+
aborted = true;
|
|
879
|
+
logger4.info("Block processing aborted", {
|
|
880
|
+
subgraph: subgraphName,
|
|
881
|
+
currentBlock: currentHeight,
|
|
882
|
+
reason: String(opts.signal.reason ?? "unknown")
|
|
883
|
+
});
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
870
886
|
const batch = await nextBatchPromise;
|
|
871
887
|
const batchEnd = Math.min(currentHeight + batchSize - 1, toBlock);
|
|
872
888
|
const nextStart = batchEnd + 1;
|
|
@@ -939,7 +955,7 @@ async function processBlockRange(def, opts) {
|
|
|
939
955
|
currentHeight = batchEnd + 1;
|
|
940
956
|
}
|
|
941
957
|
await stats.flush(db);
|
|
942
|
-
return { blocksProcessed, totalEventsProcessed, totalErrors };
|
|
958
|
+
return { blocksProcessed, totalEventsProcessed, totalErrors, aborted };
|
|
943
959
|
}
|
|
944
960
|
async function resolveBlockRange(db, def, opts) {
|
|
945
961
|
const fromBlock = opts?.fromBlock ?? def.startBlock ?? 1;
|
|
@@ -952,6 +968,9 @@ async function resolveBlockRange(db, def, opts) {
|
|
|
952
968
|
}
|
|
953
969
|
return { fromBlock, toBlock };
|
|
954
970
|
}
|
|
971
|
+
async function clearReindexMetadata(db, subgraphName) {
|
|
972
|
+
await db.updateTable("subgraphs").set({ reindex_from_block: null, reindex_to_block: null }).where("name", "=", subgraphName).execute();
|
|
973
|
+
}
|
|
955
974
|
async function reindexSubgraph(def, opts) {
|
|
956
975
|
const db = getDb2();
|
|
957
976
|
const client = getRawClient();
|
|
@@ -960,12 +979,6 @@ async function reindexSubgraph(def, opts) {
|
|
|
960
979
|
await updateSubgraphStatus2(db, subgraphName, "reindexing");
|
|
961
980
|
logger4.info("Reindex starting", { subgraph: subgraphName });
|
|
962
981
|
try {
|
|
963
|
-
await client.unsafe(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`);
|
|
964
|
-
const { statements } = generateSubgraphSQL(def, schemaName);
|
|
965
|
-
for (const stmt of statements) {
|
|
966
|
-
await client.unsafe(stmt);
|
|
967
|
-
}
|
|
968
|
-
logger4.info("Schema recreated for reindex", { subgraph: subgraphName });
|
|
969
982
|
const { fromBlock, toBlock } = await resolveBlockRange(db, def, opts);
|
|
970
983
|
if (fromBlock > toBlock) {
|
|
971
984
|
logger4.info("No blocks to reindex", {
|
|
@@ -976,6 +989,13 @@ async function reindexSubgraph(def, opts) {
|
|
|
976
989
|
await updateSubgraphStatus2(db, subgraphName, "active", 0);
|
|
977
990
|
return { processed: 0 };
|
|
978
991
|
}
|
|
992
|
+
await db.updateTable("subgraphs").set({ reindex_from_block: fromBlock, reindex_to_block: toBlock }).where("name", "=", subgraphName).execute();
|
|
993
|
+
await client.unsafe(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`);
|
|
994
|
+
const { statements } = generateSubgraphSQL(def, schemaName);
|
|
995
|
+
for (const stmt of statements) {
|
|
996
|
+
await client.unsafe(stmt);
|
|
997
|
+
}
|
|
998
|
+
logger4.info("Schema recreated for reindex", { subgraph: subgraphName });
|
|
979
999
|
logger4.info("Reindexing blocks", {
|
|
980
1000
|
subgraph: subgraphName,
|
|
981
1001
|
fromBlock,
|
|
@@ -989,13 +1009,28 @@ async function reindexSubgraph(def, opts) {
|
|
|
989
1009
|
status: "reindexing",
|
|
990
1010
|
isCatchup: false,
|
|
991
1011
|
apiKeyId: subgraphRow?.api_key_id ?? null,
|
|
992
|
-
subgraphId: subgraphRow?.id
|
|
1012
|
+
subgraphId: subgraphRow?.id,
|
|
1013
|
+
signal: opts?.signal
|
|
993
1014
|
});
|
|
1015
|
+
if (result.aborted) {
|
|
1016
|
+
const reason = String(opts?.signal?.reason ?? "unknown");
|
|
1017
|
+
if (reason === "user-cancelled") {
|
|
1018
|
+
await updateSubgraphStatus2(db, subgraphName, "active");
|
|
1019
|
+
await clearReindexMetadata(db, subgraphName);
|
|
1020
|
+
logger4.info("Reindex cancelled by user", { subgraph: subgraphName });
|
|
1021
|
+
} else {
|
|
1022
|
+
logger4.info("Reindex interrupted by shutdown, will resume", {
|
|
1023
|
+
subgraph: subgraphName
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
return { processed: result.blocksProcessed };
|
|
1027
|
+
}
|
|
994
1028
|
const { recordSubgraphProcessed: recordSubgraphProcessed2 } = await import("@secondlayer/shared/db/queries/subgraphs");
|
|
995
1029
|
if (result.totalEventsProcessed > 0 || result.totalErrors > 0) {
|
|
996
1030
|
await recordSubgraphProcessed2(db, subgraphName, result.totalEventsProcessed, result.totalErrors, result.totalErrors > 0 ? `${result.totalErrors} error(s) during reindex` : undefined);
|
|
997
1031
|
}
|
|
998
1032
|
await updateSubgraphStatus2(db, subgraphName, "active", toBlock);
|
|
1033
|
+
await clearReindexMetadata(db, subgraphName);
|
|
999
1034
|
logger4.info("Reindex complete", {
|
|
1000
1035
|
subgraph: subgraphName,
|
|
1001
1036
|
blocks: result.blocksProcessed,
|
|
@@ -1012,6 +1047,84 @@ async function reindexSubgraph(def, opts) {
|
|
|
1012
1047
|
throw err;
|
|
1013
1048
|
}
|
|
1014
1049
|
}
|
|
1050
|
+
async function resumeReindex(def, opts) {
|
|
1051
|
+
const db = getDb2();
|
|
1052
|
+
const subgraphName = def.name;
|
|
1053
|
+
const row = await db.selectFrom("subgraphs").select([
|
|
1054
|
+
"id",
|
|
1055
|
+
"api_key_id",
|
|
1056
|
+
"last_processed_block",
|
|
1057
|
+
"reindex_from_block",
|
|
1058
|
+
"reindex_to_block"
|
|
1059
|
+
]).where("name", "=", subgraphName).executeTakeFirst();
|
|
1060
|
+
if (!row)
|
|
1061
|
+
throw new Error(`Subgraph "${subgraphName}" not found`);
|
|
1062
|
+
if (row.reindex_from_block == null || row.reindex_to_block == null) {
|
|
1063
|
+
logger4.info("No reindex metadata, starting fresh reindex", {
|
|
1064
|
+
subgraph: subgraphName
|
|
1065
|
+
});
|
|
1066
|
+
return reindexSubgraph(def, {
|
|
1067
|
+
schemaName: opts.schemaName,
|
|
1068
|
+
signal: opts.signal
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
const fromBlock = Math.max(row.last_processed_block + 1, row.reindex_from_block);
|
|
1072
|
+
const toBlock = row.reindex_to_block;
|
|
1073
|
+
if (fromBlock > toBlock) {
|
|
1074
|
+
logger4.info("Resume: no remaining blocks", { subgraph: subgraphName });
|
|
1075
|
+
await updateSubgraphStatus2(db, subgraphName, "active", toBlock);
|
|
1076
|
+
await clearReindexMetadata(db, subgraphName);
|
|
1077
|
+
return { processed: 0 };
|
|
1078
|
+
}
|
|
1079
|
+
logger4.info("Resuming reindex", {
|
|
1080
|
+
subgraph: subgraphName,
|
|
1081
|
+
fromBlock,
|
|
1082
|
+
toBlock,
|
|
1083
|
+
remaining: toBlock - fromBlock + 1
|
|
1084
|
+
});
|
|
1085
|
+
try {
|
|
1086
|
+
const result = await processBlockRange(def, {
|
|
1087
|
+
fromBlock,
|
|
1088
|
+
toBlock,
|
|
1089
|
+
status: "reindexing",
|
|
1090
|
+
isCatchup: false,
|
|
1091
|
+
apiKeyId: row.api_key_id ?? null,
|
|
1092
|
+
subgraphId: row.id,
|
|
1093
|
+
signal: opts.signal
|
|
1094
|
+
});
|
|
1095
|
+
if (result.aborted) {
|
|
1096
|
+
const reason = String(opts.signal?.reason ?? "unknown");
|
|
1097
|
+
if (reason === "user-cancelled") {
|
|
1098
|
+
await updateSubgraphStatus2(db, subgraphName, "active");
|
|
1099
|
+
await clearReindexMetadata(db, subgraphName);
|
|
1100
|
+
logger4.info("Resume cancelled by user", { subgraph: subgraphName });
|
|
1101
|
+
} else {
|
|
1102
|
+
logger4.info("Resume interrupted by shutdown, will resume again", {
|
|
1103
|
+
subgraph: subgraphName
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
return { processed: result.blocksProcessed };
|
|
1107
|
+
}
|
|
1108
|
+
const { recordSubgraphProcessed: recordSubgraphProcessed2 } = await import("@secondlayer/shared/db/queries/subgraphs");
|
|
1109
|
+
if (result.totalEventsProcessed > 0 || result.totalErrors > 0) {
|
|
1110
|
+
await recordSubgraphProcessed2(db, subgraphName, result.totalEventsProcessed, result.totalErrors, result.totalErrors > 0 ? `${result.totalErrors} error(s) during resumed reindex` : undefined);
|
|
1111
|
+
}
|
|
1112
|
+
await updateSubgraphStatus2(db, subgraphName, "active", toBlock);
|
|
1113
|
+
await clearReindexMetadata(db, subgraphName);
|
|
1114
|
+
logger4.info("Resumed reindex complete", {
|
|
1115
|
+
subgraph: subgraphName,
|
|
1116
|
+
blocks: result.blocksProcessed
|
|
1117
|
+
});
|
|
1118
|
+
return { processed: result.blocksProcessed };
|
|
1119
|
+
} catch (err) {
|
|
1120
|
+
logger4.error("Resumed reindex failed", {
|
|
1121
|
+
subgraph: subgraphName,
|
|
1122
|
+
error: getErrorMessage2(err)
|
|
1123
|
+
});
|
|
1124
|
+
await updateSubgraphStatus2(db, subgraphName, "error");
|
|
1125
|
+
throw err;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1015
1128
|
async function backfillSubgraph(def, opts) {
|
|
1016
1129
|
const db = getDb2();
|
|
1017
1130
|
const subgraphName = def.name;
|
|
@@ -1028,8 +1141,13 @@ async function backfillSubgraph(def, opts) {
|
|
|
1028
1141
|
status: "active",
|
|
1029
1142
|
isCatchup: false,
|
|
1030
1143
|
apiKeyId: subgraphRow?.api_key_id ?? null,
|
|
1031
|
-
subgraphId: subgraphRow?.id
|
|
1144
|
+
subgraphId: subgraphRow?.id,
|
|
1145
|
+
signal: opts.signal
|
|
1032
1146
|
});
|
|
1147
|
+
if (result.aborted) {
|
|
1148
|
+
logger4.info("Backfill aborted", { subgraph: subgraphName });
|
|
1149
|
+
return { processed: result.blocksProcessed };
|
|
1150
|
+
}
|
|
1033
1151
|
const resolved = await resolveGaps(db, subgraphName, opts.fromBlock, opts.toBlock).catch(() => 0);
|
|
1034
1152
|
if (resolved > 0) {
|
|
1035
1153
|
logger4.info("Resolved subgraph gaps via backfill", {
|
|
@@ -1233,6 +1351,7 @@ function getDefault(type) {
|
|
|
1233
1351
|
export {
|
|
1234
1352
|
validateSubgraphDefinition,
|
|
1235
1353
|
sourceKey,
|
|
1354
|
+
resumeReindex,
|
|
1236
1355
|
reindexSubgraph,
|
|
1237
1356
|
pgSchemaName,
|
|
1238
1357
|
generateSubgraphSQL,
|
|
@@ -1242,5 +1361,5 @@ export {
|
|
|
1242
1361
|
backfillSubgraph
|
|
1243
1362
|
};
|
|
1244
1363
|
|
|
1245
|
-
//# debugId=
|
|
1364
|
+
//# debugId=7A65C6186191C40964756E2164756E21
|
|
1246
1365
|
//# sourceMappingURL=index.js.map
|