@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
package/dist/src/index.js
CHANGED
|
@@ -98,6 +98,15 @@ var amountRange = {
|
|
|
98
98
|
minAmount: z.bigint().optional(),
|
|
99
99
|
maxAmount: z.bigint().optional()
|
|
100
100
|
};
|
|
101
|
+
var MaterializeColumnSchema = z.union([
|
|
102
|
+
z.object({ from: z.string().min(1) }).strict(),
|
|
103
|
+
z.object({ fromTx: z.enum(["sender", "txId"]) }).strict(),
|
|
104
|
+
z.object({ fromBlock: z.enum(["height", "hash", "timestamp"]) }).strict()
|
|
105
|
+
]);
|
|
106
|
+
var MaterializeSpecSchema = z.object({
|
|
107
|
+
table: z.string().min(1),
|
|
108
|
+
columns: z.record(z.string(), MaterializeColumnSchema)
|
|
109
|
+
}).strict();
|
|
101
110
|
var SubgraphFilterUnion = z.discriminatedUnion("type", [
|
|
102
111
|
z.object({
|
|
103
112
|
type: z.literal("stx_transfer"),
|
|
@@ -180,11 +189,114 @@ var SubgraphFilterUnion = z.discriminatedUnion("type", [
|
|
|
180
189
|
contractId: contractIdField.optional(),
|
|
181
190
|
topic: z.string().optional(),
|
|
182
191
|
prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional(),
|
|
192
|
+
materialize: MaterializeSpecSchema.optional(),
|
|
183
193
|
...traitScope,
|
|
184
194
|
...factoryScope
|
|
185
195
|
}).strict()
|
|
186
196
|
]);
|
|
187
197
|
var SubgraphFilterSchema = SubgraphFilterUnion;
|
|
198
|
+
function hasPinnedContractId(contractId) {
|
|
199
|
+
if (typeof contractId === "string")
|
|
200
|
+
return contractId.length > 0;
|
|
201
|
+
if (Array.isArray(contractId))
|
|
202
|
+
return contractId.length > 0;
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
function printMaterializeFromKeys(prints) {
|
|
206
|
+
const keys = new Set(["topic"]);
|
|
207
|
+
if (prints) {
|
|
208
|
+
for (const fields of Object.values(prints)) {
|
|
209
|
+
for (const k of Object.keys(fields))
|
|
210
|
+
keys.add(k);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return [...keys].sort();
|
|
214
|
+
}
|
|
215
|
+
function refineMaterialize(name, filter, schema, ctx) {
|
|
216
|
+
if (!("materialize" in filter) || filter.materialize === undefined)
|
|
217
|
+
return;
|
|
218
|
+
const spec = filter.materialize;
|
|
219
|
+
const table = schema[spec.table];
|
|
220
|
+
if (!table) {
|
|
221
|
+
ctx.addIssue({
|
|
222
|
+
code: "custom",
|
|
223
|
+
path: ["sources", name, "materialize", "table"],
|
|
224
|
+
message: `materialize table "${spec.table}" is not in schema`
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
for (const col of Object.keys(spec.columns)) {
|
|
229
|
+
if (!(col in table.columns)) {
|
|
230
|
+
ctx.addIssue({
|
|
231
|
+
code: "custom",
|
|
232
|
+
path: ["sources", name, "materialize", "columns", col],
|
|
233
|
+
message: `materialize column "${col}" is not on schema table "${spec.table}"`
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (filter.type === "print_event") {
|
|
238
|
+
const known = printMaterializeFromKeys(filter.prints);
|
|
239
|
+
const knownSet = new Set(known);
|
|
240
|
+
for (const [col, mapping] of Object.entries(spec.columns)) {
|
|
241
|
+
if (!("from" in mapping))
|
|
242
|
+
continue;
|
|
243
|
+
if (!knownSet.has(mapping.from)) {
|
|
244
|
+
ctx.addIssue({
|
|
245
|
+
code: "custom",
|
|
246
|
+
path: ["sources", name, "materialize", "columns", col, "from"],
|
|
247
|
+
message: `materialize from "${mapping.from}" is not a known print field — known: ${known.join(", ")}`
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function refineDeployGates(def, ctx) {
|
|
254
|
+
for (const [name, filter] of Object.entries(def.sources)) {
|
|
255
|
+
if (filter.type === "print_event") {
|
|
256
|
+
const pinned = hasPinnedContractId(filter.contractId) && !filter.trait;
|
|
257
|
+
if (pinned) {
|
|
258
|
+
const prints = filter.prints;
|
|
259
|
+
if (prints === undefined || typeof prints !== "object" || Object.keys(prints).length === 0) {
|
|
260
|
+
ctx.addIssue({
|
|
261
|
+
code: "custom",
|
|
262
|
+
path: ["sources", name, "prints"],
|
|
263
|
+
message: `print_event source "${name}" has no prints map — field names would be untyped and empty tables would typecheck. Declare prints from index_print_schema / create --from-contract.`
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (filter.type === "contract_call" && filter.functionName !== undefined) {
|
|
269
|
+
if (filter.abi === undefined) {
|
|
270
|
+
ctx.addIssue({
|
|
271
|
+
code: "custom",
|
|
272
|
+
path: ["sources", name, "abi"],
|
|
273
|
+
message: `contract_call source "${name}" has functionName but no abi — pass a canonical ABI (normalizeAbi() from @secondlayer/stacks/clarity).`
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
refineMaterialize(name, filter, def.schema, ctx);
|
|
278
|
+
}
|
|
279
|
+
const hasCatchAll = def.handlers["*"] != null;
|
|
280
|
+
for (const [name, filter] of Object.entries(def.sources)) {
|
|
281
|
+
const hasMaterialize = "materialize" in filter && filter.materialize !== undefined;
|
|
282
|
+
const hasHandler = def.handlers[name] != null;
|
|
283
|
+
if (hasMaterialize && hasHandler) {
|
|
284
|
+
ctx.addIssue({
|
|
285
|
+
code: "custom",
|
|
286
|
+
path: ["handlers", name],
|
|
287
|
+
message: `source "${name}" has both materialize and a handler — use one or the other`
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (!hasHandler && !hasMaterialize && !hasCatchAll) {
|
|
292
|
+
ctx.addIssue({
|
|
293
|
+
code: "custom",
|
|
294
|
+
path: ["handlers", name],
|
|
295
|
+
message: `source "${name}" has no handler (and no "*") — events will be skipped and the table stay empty.`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
188
300
|
var SubgraphDefinitionSchema = z.object({
|
|
189
301
|
name: SubgraphNameSchema,
|
|
190
302
|
version: z.string().optional(),
|
|
@@ -194,6 +306,8 @@ var SubgraphDefinitionSchema = z.object({
|
|
|
194
306
|
sources: z.record(z.string(), SubgraphFilterSchema).refine((s) => Object.keys(s).length > 0, "Must have at least one source"),
|
|
195
307
|
schema: SubgraphSchemaSchema,
|
|
196
308
|
handlers: z.record(z.string(), z.any())
|
|
309
|
+
}).superRefine((def, ctx) => {
|
|
310
|
+
refineDeployGates(def, ctx);
|
|
197
311
|
});
|
|
198
312
|
function validateSubgraphDefinition(def) {
|
|
199
313
|
return SubgraphDefinitionSchema.parse(def);
|
|
@@ -1427,7 +1541,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map, f
|
|
|
1427
1541
|
|
|
1428
1542
|
// src/runtime/runner.ts
|
|
1429
1543
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
1430
|
-
import { logger as
|
|
1544
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
1431
1545
|
import {
|
|
1432
1546
|
clarityValueToJS,
|
|
1433
1547
|
deserializeCV as deserializeCV4,
|
|
@@ -1441,6 +1555,19 @@ import {
|
|
|
1441
1555
|
function camelizeDataKey(str) {
|
|
1442
1556
|
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1443
1557
|
}
|
|
1558
|
+
function camelizeKeys(obj) {
|
|
1559
|
+
if (obj === null || obj === undefined)
|
|
1560
|
+
return obj;
|
|
1561
|
+
if (typeof obj !== "object")
|
|
1562
|
+
return obj;
|
|
1563
|
+
if (Array.isArray(obj))
|
|
1564
|
+
return obj.map(camelizeKeys);
|
|
1565
|
+
const result = {};
|
|
1566
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1567
|
+
result[camelizeDataKey(k)] = camelizeKeys(v);
|
|
1568
|
+
}
|
|
1569
|
+
return result;
|
|
1570
|
+
}
|
|
1444
1571
|
function cvToTree(cv) {
|
|
1445
1572
|
switch (cv.type) {
|
|
1446
1573
|
case "uint":
|
|
@@ -1765,6 +1892,58 @@ function inferPrintTopics(samples) {
|
|
|
1765
1892
|
return out.sort((a, b) => b.count - a.count);
|
|
1766
1893
|
}
|
|
1767
1894
|
|
|
1895
|
+
// src/runtime/materialize.ts
|
|
1896
|
+
function resolveColumn(col, event, tx, block) {
|
|
1897
|
+
if ("fromTx" in col) {
|
|
1898
|
+
return { ok: true, value: tx[col.fromTx] };
|
|
1899
|
+
}
|
|
1900
|
+
if ("fromBlock" in col) {
|
|
1901
|
+
return { ok: true, value: block[col.fromBlock] };
|
|
1902
|
+
}
|
|
1903
|
+
const data = event.data;
|
|
1904
|
+
const dataObj = data !== null && typeof data === "object" && !Array.isArray(data) ? data : undefined;
|
|
1905
|
+
if (dataObj && Object.prototype.hasOwnProperty.call(dataObj, col.from)) {
|
|
1906
|
+
return { ok: true, value: dataObj[col.from] };
|
|
1907
|
+
}
|
|
1908
|
+
if (col.from === "topic" && "topic" in event) {
|
|
1909
|
+
return { ok: true, value: event.topic };
|
|
1910
|
+
}
|
|
1911
|
+
if (Object.prototype.hasOwnProperty.call(event, col.from)) {
|
|
1912
|
+
return { ok: true, value: event[col.from] };
|
|
1913
|
+
}
|
|
1914
|
+
return { ok: false, missing: col.from };
|
|
1915
|
+
}
|
|
1916
|
+
function buildMaterializeRow(spec, event, ctx, tableColumns) {
|
|
1917
|
+
const row = {};
|
|
1918
|
+
for (const [colName, mapping] of Object.entries(spec.columns)) {
|
|
1919
|
+
const resolved = resolveColumn(mapping, event, ctx.tx, ctx.block);
|
|
1920
|
+
if (!resolved.ok) {
|
|
1921
|
+
const colDef = tableColumns[colName];
|
|
1922
|
+
if (colDef?.nullable) {
|
|
1923
|
+
row[colName] = null;
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
return {
|
|
1927
|
+
ok: false,
|
|
1928
|
+
reason: `required materialize field "${resolved.missing}" missing for column "${colName}"`
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
row[colName] = resolved.value ?? null;
|
|
1932
|
+
}
|
|
1933
|
+
return { ok: true, row };
|
|
1934
|
+
}
|
|
1935
|
+
function applyMaterializeInsert(spec, event, ctx, schema) {
|
|
1936
|
+
const table = schema[spec.table];
|
|
1937
|
+
if (!table) {
|
|
1938
|
+
return { ok: false, reason: `materialize table "${spec.table}" missing` };
|
|
1939
|
+
}
|
|
1940
|
+
const built = buildMaterializeRow(spec, event, ctx, table.columns);
|
|
1941
|
+
if (!built.ok)
|
|
1942
|
+
return built;
|
|
1943
|
+
ctx.insert(spec.table, built.row);
|
|
1944
|
+
return { ok: true };
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1768
1947
|
// src/runtime/print-validate.ts
|
|
1769
1948
|
var COLUMN_TYPES = new Set([
|
|
1770
1949
|
"text",
|
|
@@ -1844,21 +2023,62 @@ function validatePrintPayload(prints, topic, data) {
|
|
|
1844
2023
|
return matchesTopic(declared, data);
|
|
1845
2024
|
}
|
|
1846
2025
|
|
|
1847
|
-
// src/runtime/
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2026
|
+
// src/runtime/violations.ts
|
|
2027
|
+
import { getTargetDb } from "@secondlayer/shared/db";
|
|
2028
|
+
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
2029
|
+
import { sql as sql2 } from "kysely";
|
|
2030
|
+
var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
|
|
2031
|
+
function jsonSafe2(value) {
|
|
2032
|
+
if (typeof value === "bigint")
|
|
2033
|
+
return value.toString();
|
|
2034
|
+
if (value === null || value === undefined)
|
|
2035
|
+
return value;
|
|
2036
|
+
if (Array.isArray(value))
|
|
2037
|
+
return value.map(jsonSafe2);
|
|
2038
|
+
if (typeof value === "object") {
|
|
2039
|
+
const out = {};
|
|
2040
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2041
|
+
out[k] = jsonSafe2(v);
|
|
2042
|
+
}
|
|
2043
|
+
return out;
|
|
1859
2044
|
}
|
|
1860
|
-
return
|
|
2045
|
+
return value;
|
|
1861
2046
|
}
|
|
2047
|
+
async function defaultRecordPrintViolation(input) {
|
|
2048
|
+
try {
|
|
2049
|
+
const db = getTargetDb();
|
|
2050
|
+
const sample = jsonSafe2(input.samplePayload);
|
|
2051
|
+
await db.insertInto("subgraph_violations").values({
|
|
2052
|
+
subgraph_name: input.subgraphName,
|
|
2053
|
+
source_name: input.sourceName,
|
|
2054
|
+
block_height: input.blockHeight,
|
|
2055
|
+
tx_id: input.txId,
|
|
2056
|
+
reason: input.reason,
|
|
2057
|
+
sample_payload: sample
|
|
2058
|
+
}).execute();
|
|
2059
|
+
await sql2`
|
|
2060
|
+
DELETE FROM subgraph_violations
|
|
2061
|
+
WHERE id IN (
|
|
2062
|
+
SELECT id FROM subgraph_violations
|
|
2063
|
+
WHERE subgraph_name = ${input.subgraphName}
|
|
2064
|
+
ORDER BY seen_at DESC
|
|
2065
|
+
OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
|
|
2066
|
+
)
|
|
2067
|
+
`.execute(db);
|
|
2068
|
+
} catch (err) {
|
|
2069
|
+
logger3.warn("Failed to record print violation", {
|
|
2070
|
+
subgraph: input.subgraphName,
|
|
2071
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
var recorder = defaultRecordPrintViolation;
|
|
2076
|
+
async function recordPrintViolation(input) {
|
|
2077
|
+
await recorder(input);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// src/runtime/runner.ts
|
|
2081
|
+
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
1862
2082
|
function decodeFunctionArgs2(args) {
|
|
1863
2083
|
let parsed = args;
|
|
1864
2084
|
if (typeof parsed === "string") {
|
|
@@ -2106,16 +2326,18 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
2106
2326
|
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
2107
2327
|
for (const { tx, event, sourceName } of units) {
|
|
2108
2328
|
if (errors >= threshold) {
|
|
2109
|
-
|
|
2329
|
+
logger4.error("Subgraph error threshold reached, skipping remaining events", {
|
|
2110
2330
|
subgraph: subgraph.name,
|
|
2111
2331
|
errors,
|
|
2112
2332
|
threshold
|
|
2113
2333
|
});
|
|
2114
2334
|
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
2115
2335
|
}
|
|
2336
|
+
const filter = filterLookup.get(sourceName);
|
|
2337
|
+
const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
|
|
2116
2338
|
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
2117
|
-
if (!handler) {
|
|
2118
|
-
|
|
2339
|
+
if (!handler && !materialize) {
|
|
2340
|
+
logger4.warn("No handler found for source", {
|
|
2119
2341
|
subgraph: subgraph.name,
|
|
2120
2342
|
sourceName,
|
|
2121
2343
|
txId: tx.tx_id
|
|
@@ -2130,7 +2352,6 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
2130
2352
|
contractId: tx.contract_id ?? null,
|
|
2131
2353
|
functionName: tx.function_name ?? null
|
|
2132
2354
|
});
|
|
2133
|
-
const filter = filterLookup.get(sourceName);
|
|
2134
2355
|
const checkpoint = ctx.opsCheckpoint();
|
|
2135
2356
|
try {
|
|
2136
2357
|
let payload;
|
|
@@ -2172,22 +2393,48 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
2172
2393
|
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
2173
2394
|
if (!verdict.ok) {
|
|
2174
2395
|
skipped++;
|
|
2175
|
-
|
|
2396
|
+
logger4.warn("Print payload does not match declared prints schema", {
|
|
2176
2397
|
subgraph: subgraph.name,
|
|
2177
2398
|
sourceName,
|
|
2178
2399
|
topic: printPayload.topic,
|
|
2179
2400
|
txId: tx.tx_id,
|
|
2180
2401
|
reason: verdict.reason
|
|
2181
2402
|
});
|
|
2403
|
+
recordPrintViolation({
|
|
2404
|
+
subgraphName: subgraph.name,
|
|
2405
|
+
sourceName,
|
|
2406
|
+
blockHeight: ctx.block.height,
|
|
2407
|
+
txId: tx.tx_id,
|
|
2408
|
+
reason: verdict.reason,
|
|
2409
|
+
samplePayload: {
|
|
2410
|
+
topic: printPayload.topic ?? "",
|
|
2411
|
+
data: printPayload.data ?? {}
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
if (materialize) {
|
|
2418
|
+
const result = applyMaterializeInsert(materialize, payload, ctx, subgraph.schema);
|
|
2419
|
+
if (!result.ok) {
|
|
2420
|
+
skipped++;
|
|
2421
|
+
logger4.warn("Materialize skipped: required field missing", {
|
|
2422
|
+
subgraph: subgraph.name,
|
|
2423
|
+
sourceName,
|
|
2424
|
+
txId: tx.tx_id,
|
|
2425
|
+
reason: result.reason
|
|
2426
|
+
});
|
|
2182
2427
|
continue;
|
|
2183
2428
|
}
|
|
2429
|
+
processed++;
|
|
2430
|
+
} else if (handler) {
|
|
2431
|
+
await handler(payload, ctx);
|
|
2432
|
+
processed++;
|
|
2184
2433
|
}
|
|
2185
|
-
await handler(payload, ctx);
|
|
2186
|
-
processed++;
|
|
2187
2434
|
} catch (err) {
|
|
2188
2435
|
ctx.rollbackTo(checkpoint);
|
|
2189
2436
|
errors++;
|
|
2190
|
-
|
|
2437
|
+
logger4.error("Subgraph handler error", {
|
|
2191
2438
|
subgraph: subgraph.name,
|
|
2192
2439
|
sourceName,
|
|
2193
2440
|
txId: tx.tx_id,
|
|
@@ -2200,7 +2447,7 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
2200
2447
|
}
|
|
2201
2448
|
|
|
2202
2449
|
// src/runtime/block-processor.ts
|
|
2203
|
-
import { getTargetDb } from "@secondlayer/shared/db";
|
|
2450
|
+
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
2204
2451
|
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
2205
2452
|
import { recordGapBatch } from "@secondlayer/shared/db/queries/subgraph-gaps";
|
|
2206
2453
|
import { advanceOperationCursor } from "@secondlayer/shared/db/queries/subgraph-operations";
|
|
@@ -2211,15 +2458,15 @@ import {
|
|
|
2211
2458
|
rewindLiveProgress,
|
|
2212
2459
|
updateSubgraphStatus
|
|
2213
2460
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
2214
|
-
import { logger as
|
|
2215
|
-
import { sql as
|
|
2461
|
+
import { logger as logger8 } from "@secondlayer/shared/logger";
|
|
2462
|
+
import { sql as sql4 } from "kysely";
|
|
2216
2463
|
|
|
2217
2464
|
// src/runtime/block-source.ts
|
|
2218
2465
|
import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
2219
2466
|
import {
|
|
2220
2467
|
createInternalIndexHttpClient
|
|
2221
2468
|
} from "@secondlayer/shared/index-http";
|
|
2222
|
-
import { logger as
|
|
2469
|
+
import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
2223
2470
|
|
|
2224
2471
|
// src/runtime/batch-loader.ts
|
|
2225
2472
|
async function loadBlockRange(db, fromHeight, toHeight) {
|
|
@@ -2706,7 +2953,7 @@ class FallbackBlockSource {
|
|
|
2706
2953
|
try {
|
|
2707
2954
|
return await this.primary.getTip();
|
|
2708
2955
|
} catch (err) {
|
|
2709
|
-
|
|
2956
|
+
logger5.warn("block source primary getTip failed — using DB tap", {
|
|
2710
2957
|
error: err instanceof Error ? err.message : String(err)
|
|
2711
2958
|
});
|
|
2712
2959
|
return this.fallback.getTip();
|
|
@@ -2716,7 +2963,7 @@ class FallbackBlockSource {
|
|
|
2716
2963
|
try {
|
|
2717
2964
|
return await this.primary.loadBlockRange(fromHeight, toHeight);
|
|
2718
2965
|
} catch (err) {
|
|
2719
|
-
|
|
2966
|
+
logger5.warn("block source primary loadBlockRange failed — using DB tap", {
|
|
2720
2967
|
from: fromHeight,
|
|
2721
2968
|
to: toHeight,
|
|
2722
2969
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2753,7 +3000,7 @@ function resolveBlockSource(subgraph) {
|
|
|
2753
3000
|
return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined, needsTransactionData(subgraph)), postgresBlockSource);
|
|
2754
3001
|
}
|
|
2755
3002
|
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
|
|
2756
|
-
|
|
3003
|
+
logger5.debug("Subgraph not streams-index eligible, using DB tap", {
|
|
2757
3004
|
subgraph: subgraph.name
|
|
2758
3005
|
});
|
|
2759
3006
|
}
|
|
@@ -2762,7 +3009,7 @@ function resolveBlockSource(subgraph) {
|
|
|
2762
3009
|
|
|
2763
3010
|
// src/runtime/outbox-emit.ts
|
|
2764
3011
|
import { createHash as createHash2 } from "node:crypto";
|
|
2765
|
-
import { logger as
|
|
3012
|
+
import { logger as logger6 } from "@secondlayer/shared/logger";
|
|
2766
3013
|
var loggedKillSwitch = false;
|
|
2767
3014
|
var OP_VERB = {
|
|
2768
3015
|
insert: "created",
|
|
@@ -2786,7 +3033,7 @@ function stableStringify(obj) {
|
|
|
2786
3033
|
async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, blockHeight) {
|
|
2787
3034
|
if (!isEmitOutboxEnabled()) {
|
|
2788
3035
|
if (!loggedKillSwitch) {
|
|
2789
|
-
|
|
3036
|
+
logger6.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
|
|
2790
3037
|
loggedKillSwitch = true;
|
|
2791
3038
|
}
|
|
2792
3039
|
return 0;
|
|
@@ -2831,7 +3078,7 @@ function sandboxEnabled(subgraph) {
|
|
|
2831
3078
|
// src/runtime/sandbox/host.ts
|
|
2832
3079
|
import { tmpdir } from "node:os";
|
|
2833
3080
|
import { fileURLToPath } from "node:url";
|
|
2834
|
-
import { logger as
|
|
3081
|
+
import { logger as logger7 } from "@secondlayer/shared/logger";
|
|
2835
3082
|
|
|
2836
3083
|
// src/runtime/sandbox/bundle.ts
|
|
2837
3084
|
import esbuild from "esbuild";
|
|
@@ -2911,7 +3158,7 @@ function spawnSandboxProcess() {
|
|
|
2911
3158
|
break;
|
|
2912
3159
|
const text = decoder.decode(value, { stream: true });
|
|
2913
3160
|
if (text.trim()) {
|
|
2914
|
-
|
|
3161
|
+
logger7.warn("Sandbox subprocess stderr", { stderr: text.trim() });
|
|
2915
3162
|
}
|
|
2916
3163
|
}
|
|
2917
3164
|
} catch {}
|
|
@@ -2969,7 +3216,7 @@ async function ensureSandboxProcess(subgraphName, version, handlerCode) {
|
|
|
2969
3216
|
try {
|
|
2970
3217
|
await initSandboxProcess(existing.proc, subgraphName, version, handlerCode);
|
|
2971
3218
|
existing.version = version;
|
|
2972
|
-
|
|
3219
|
+
logger7.info("Sandbox subprocess handler reloaded", {
|
|
2973
3220
|
subgraph: subgraphName,
|
|
2974
3221
|
version
|
|
2975
3222
|
});
|
|
@@ -2977,7 +3224,7 @@ async function ensureSandboxProcess(subgraphName, version, handlerCode) {
|
|
|
2977
3224
|
} catch (err) {
|
|
2978
3225
|
existing.proc.kill();
|
|
2979
3226
|
pool.delete(subgraphName);
|
|
2980
|
-
|
|
3227
|
+
logger7.warn("Sandbox subprocess re-init failed; respawning", {
|
|
2981
3228
|
subgraph: subgraphName,
|
|
2982
3229
|
error: err instanceof Error ? err.message : String(err)
|
|
2983
3230
|
});
|
|
@@ -3060,7 +3307,7 @@ async function runBlockThroughProcess(entry, subgraphName, hostCtx, block, match
|
|
|
3060
3307
|
return result;
|
|
3061
3308
|
} catch (err) {
|
|
3062
3309
|
evictSandboxWorker(subgraphName);
|
|
3063
|
-
|
|
3310
|
+
logger7.warn("Sandbox subprocess run failed; subprocess evicted", {
|
|
3064
3311
|
subgraph: subgraphName,
|
|
3065
3312
|
blockHeight: block.height,
|
|
3066
3313
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3131,7 +3378,7 @@ async function answerRead(hostCtx, msg) {
|
|
|
3131
3378
|
|
|
3132
3379
|
// src/runtime/subscription-state.ts
|
|
3133
3380
|
import { listSubscriptions } from "@secondlayer/shared/db/queries/subscriptions";
|
|
3134
|
-
import { sql as
|
|
3381
|
+
import { sql as sql3 } from "kysely";
|
|
3135
3382
|
|
|
3136
3383
|
// src/runtime/emitter-matcher.ts
|
|
3137
3384
|
function isPrimitive(v) {
|
|
@@ -3295,7 +3542,7 @@ class SubscriptionMatcher {
|
|
|
3295
3542
|
// src/runtime/subscription-state.ts
|
|
3296
3543
|
var matcher = new SubscriptionMatcher;
|
|
3297
3544
|
async function refreshMatcher(db) {
|
|
3298
|
-
const rows = await
|
|
3545
|
+
const rows = await sql3`
|
|
3299
3546
|
SELECT * FROM subscriptions WHERE status = 'active'
|
|
3300
3547
|
`.execute(db);
|
|
3301
3548
|
matcher.setAll(rows.rows);
|
|
@@ -3317,7 +3564,7 @@ async function resolveRoute(subgraphName, targetDb) {
|
|
|
3317
3564
|
version: row?.version ?? ""
|
|
3318
3565
|
};
|
|
3319
3566
|
routeCache.set(subgraphName, route);
|
|
3320
|
-
|
|
3567
|
+
logger8.info("subgraph handler dispatch path resolved", {
|
|
3321
3568
|
subgraph: subgraphName,
|
|
3322
3569
|
path: sandboxEnabled({ sandbox_workers: route.sandboxWorkers }) ? "sandbox" : "in-process"
|
|
3323
3570
|
});
|
|
@@ -3341,8 +3588,8 @@ async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, tx
|
|
|
3341
3588
|
for (const [discoveringSource, factory] of factories) {
|
|
3342
3589
|
const known = new Set;
|
|
3343
3590
|
try {
|
|
3344
|
-
const rows = await
|
|
3345
|
-
SELECT address FROM ${
|
|
3591
|
+
const rows = await sql4`
|
|
3592
|
+
SELECT address FROM ${sql4.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
3346
3593
|
WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
|
|
3347
3594
|
`.execute(db);
|
|
3348
3595
|
for (const row of rows.rows)
|
|
@@ -3370,8 +3617,8 @@ async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered
|
|
|
3370
3617
|
if (discovered.length === 0)
|
|
3371
3618
|
return;
|
|
3372
3619
|
for (const { sourceName, address } of discovered) {
|
|
3373
|
-
await
|
|
3374
|
-
INSERT INTO ${
|
|
3620
|
+
await sql4`
|
|
3621
|
+
INSERT INTO ${sql4.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
3375
3622
|
(source_name, address, block_height)
|
|
3376
3623
|
VALUES (${sourceName}, ${address}, ${blockHeight})
|
|
3377
3624
|
ON CONFLICT (source_name, address) DO NOTHING
|
|
@@ -3431,7 +3678,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
3431
3678
|
const delay = retryDelaysMs[attempt];
|
|
3432
3679
|
if (delay === undefined)
|
|
3433
3680
|
break;
|
|
3434
|
-
|
|
3681
|
+
logger8.warn("Block processing failed, retrying", {
|
|
3435
3682
|
subgraph: subgraphName,
|
|
3436
3683
|
blockHeight,
|
|
3437
3684
|
attempt: attempt + 1,
|
|
@@ -3444,7 +3691,7 @@ async function processBlockWithRetry(subgraph, subgraphName, blockHeight, opts,
|
|
|
3444
3691
|
throw lastError;
|
|
3445
3692
|
}
|
|
3446
3693
|
async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
3447
|
-
const targetDb =
|
|
3694
|
+
const targetDb = getTargetDb2();
|
|
3448
3695
|
const blockStart = performance.now();
|
|
3449
3696
|
const result = {
|
|
3450
3697
|
blockHeight,
|
|
@@ -3463,7 +3710,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3463
3710
|
} else {
|
|
3464
3711
|
const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
|
|
3465
3712
|
if (!data) {
|
|
3466
|
-
|
|
3713
|
+
logger8.debug("Block not found or non-canonical for subgraph processing", {
|
|
3467
3714
|
subgraph: subgraphName,
|
|
3468
3715
|
blockHeight
|
|
3469
3716
|
});
|
|
@@ -3529,7 +3776,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3529
3776
|
{ start: blockHeight, end: blockHeight, reason: "processing_error" }
|
|
3530
3777
|
]);
|
|
3531
3778
|
} else {
|
|
3532
|
-
|
|
3779
|
+
logger8.warn("Block errors not recorded as a gap — no subgraphId", {
|
|
3533
3780
|
subgraph: subgraphName,
|
|
3534
3781
|
blockHeight,
|
|
3535
3782
|
errors: rr.errors
|
|
@@ -3602,7 +3849,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3602
3849
|
});
|
|
3603
3850
|
} catch (err) {
|
|
3604
3851
|
if (err instanceof CursorRaceLostError || err instanceof LiveCursorRaceLostError) {
|
|
3605
|
-
|
|
3852
|
+
logger8.warn("cursor race lost — block already covered", {
|
|
3606
3853
|
subgraph: subgraphName,
|
|
3607
3854
|
blockHeight,
|
|
3608
3855
|
error: err.message
|
|
@@ -3627,10 +3874,10 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3627
3874
|
try {
|
|
3628
3875
|
const tables = Object.keys(subgraph.schema);
|
|
3629
3876
|
for (const table of tables) {
|
|
3630
|
-
const { rows } = await
|
|
3877
|
+
const { rows } = await sql4.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
|
|
3631
3878
|
const count = Number(rows[0]?.count ?? 0);
|
|
3632
3879
|
if (count >= 1e7) {
|
|
3633
|
-
|
|
3880
|
+
logger8.warn("Subgraph table exceeds 10M rows (estimate)", {
|
|
3634
3881
|
subgraph: subgraphName,
|
|
3635
3882
|
table,
|
|
3636
3883
|
count
|
|
@@ -3638,13 +3885,13 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
3638
3885
|
}
|
|
3639
3886
|
}
|
|
3640
3887
|
} catch (err) {
|
|
3641
|
-
|
|
3888
|
+
logger8.debug("Row count sample failed", {
|
|
3642
3889
|
subgraph: subgraphName,
|
|
3643
3890
|
error: err instanceof Error ? err.message : String(err)
|
|
3644
3891
|
});
|
|
3645
3892
|
}
|
|
3646
3893
|
if (journalEnabled(opts)) {
|
|
3647
|
-
await
|
|
3894
|
+
await sql4.raw(`DELETE FROM "${schemaName}"."_journal" WHERE "block_height" < ${blockHeight - JOURNAL_RETENTION_BLOCKS}`).execute(route.dataDb).catch(() => {});
|
|
3648
3895
|
}
|
|
3649
3896
|
}
|
|
3650
3897
|
return result;
|
|
@@ -3714,9 +3961,9 @@ class StatsAccumulator {
|
|
|
3714
3961
|
}
|
|
3715
3962
|
|
|
3716
3963
|
// src/runtime/catchup.ts
|
|
3717
|
-
import { getTargetDb as
|
|
3964
|
+
import { getTargetDb as getTargetDb3, sql as sql5 } from "@secondlayer/shared/db";
|
|
3718
3965
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
3719
|
-
import { logger as
|
|
3966
|
+
import { logger as logger9 } from "@secondlayer/shared/logger";
|
|
3720
3967
|
|
|
3721
3968
|
// src/runtime/catchup-leader.ts
|
|
3722
3969
|
import {
|
|
@@ -3846,8 +4093,8 @@ async function withSubgraphBlockLock(name, fn) {
|
|
|
3846
4093
|
}
|
|
3847
4094
|
}
|
|
3848
4095
|
async function withSubgraphBlockAdvisoryLock(name, fn) {
|
|
3849
|
-
return
|
|
3850
|
-
await
|
|
4096
|
+
return getTargetDb3().transaction().execute(async (lockTx) => {
|
|
4097
|
+
await sql5`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-block:${name}`}))`.execute(lockTx);
|
|
3851
4098
|
return fn();
|
|
3852
4099
|
});
|
|
3853
4100
|
}
|
|
@@ -3908,7 +4155,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3908
4155
|
catchingUp.add(subgraphName);
|
|
3909
4156
|
try {
|
|
3910
4157
|
const source = resolveBlockSource(subgraph);
|
|
3911
|
-
const targetDb =
|
|
4158
|
+
const targetDb = getTargetDb3();
|
|
3912
4159
|
const subgraphRow = await getSubgraph(targetDb, subgraphName);
|
|
3913
4160
|
if (!subgraphRow)
|
|
3914
4161
|
return 0;
|
|
@@ -3920,7 +4167,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3920
4167
|
if (usesDecodedIndexPlane(subgraph)) {
|
|
3921
4168
|
const bound = await boundSourceTip(rawTip, decoderNamesForSubgraph(subgraph));
|
|
3922
4169
|
if (!bound.ok) {
|
|
3923
|
-
|
|
4170
|
+
logger9.warn("Subgraph catch-up stalled: missing decoder checkpoint", {
|
|
3924
4171
|
event: "subgraph_catchup_decoder_stall",
|
|
3925
4172
|
subgraph: subgraphName,
|
|
3926
4173
|
missing: bound.missing
|
|
@@ -3928,7 +4175,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3928
4175
|
return 0;
|
|
3929
4176
|
}
|
|
3930
4177
|
if (bound.floor !== null && bound.floor < rawTip) {
|
|
3931
|
-
|
|
4178
|
+
logger9.debug("Subgraph catch-up tip bounded by decoder progress", {
|
|
3932
4179
|
event: "subgraph_catchup_decoder_floor",
|
|
3933
4180
|
subgraph: subgraphName,
|
|
3934
4181
|
rawTip,
|
|
@@ -3943,7 +4190,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3943
4190
|
const subgraphStart = Number(subgraphRow.start_block) || 1;
|
|
3944
4191
|
const startBlock = Math.max(lastProcessedBlock + 1, subgraphStart);
|
|
3945
4192
|
const totalBlocks = chainTip - lastProcessedBlock;
|
|
3946
|
-
|
|
4193
|
+
logger9.info("Subgraph catch-up starting", {
|
|
3947
4194
|
subgraph: subgraphName,
|
|
3948
4195
|
from: startBlock,
|
|
3949
4196
|
to: chainTip,
|
|
@@ -3959,7 +4206,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3959
4206
|
let nextBatchPromise = batchConfig.prefetch ? source.loadBlockRange(currentHeight, prefetchedBatchEnd) : undefined;
|
|
3960
4207
|
while (currentHeight <= chainTip) {
|
|
3961
4208
|
if (!isCatchUpLeader()) {
|
|
3962
|
-
|
|
4209
|
+
logger9.info("Lost catch-up leadership, stopping catch-up", {
|
|
3963
4210
|
subgraph: subgraphName,
|
|
3964
4211
|
processed
|
|
3965
4212
|
});
|
|
@@ -3967,7 +4214,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3967
4214
|
}
|
|
3968
4215
|
const currentRow = await getSubgraph(targetDb, subgraphName);
|
|
3969
4216
|
if (!currentRow || currentRow.status !== "active") {
|
|
3970
|
-
|
|
4217
|
+
logger9.info("Subgraph status changed, stopping catch-up", {
|
|
3971
4218
|
subgraph: subgraphName,
|
|
3972
4219
|
status: currentRow?.status ?? "deleted"
|
|
3973
4220
|
});
|
|
@@ -3996,7 +4243,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3996
4243
|
blockData = (await source.loadBlockRange(height, height)).get(height);
|
|
3997
4244
|
}
|
|
3998
4245
|
if (!blockData) {
|
|
3999
|
-
|
|
4246
|
+
logger9.warn("Block missing during catch-up, deferring to next tick", {
|
|
4000
4247
|
subgraph: subgraphName,
|
|
4001
4248
|
blockHeight: height
|
|
4002
4249
|
});
|
|
@@ -4017,12 +4264,12 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
4017
4264
|
});
|
|
4018
4265
|
} catch (err) {
|
|
4019
4266
|
if (err instanceof ReorgEpochChangedError) {
|
|
4020
|
-
|
|
4267
|
+
logger9.info("Reorg detected mid catch-up, aborting walk without writing further progress", { subgraph: subgraphName, blockHeight: height });
|
|
4021
4268
|
stopCatchup = true;
|
|
4022
4269
|
break;
|
|
4023
4270
|
}
|
|
4024
4271
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
4025
|
-
|
|
4272
|
+
logger9.error("Block processing failed persistently during catch-up", {
|
|
4026
4273
|
subgraph: subgraphName,
|
|
4027
4274
|
blockHeight: height,
|
|
4028
4275
|
error: errorMsg
|
|
@@ -4041,7 +4288,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
4041
4288
|
}
|
|
4042
4289
|
}
|
|
4043
4290
|
if (processed % LOG_INTERVAL === 0) {
|
|
4044
|
-
|
|
4291
|
+
logger9.info("Subgraph catch-up progress", {
|
|
4045
4292
|
subgraph: subgraphName,
|
|
4046
4293
|
processed,
|
|
4047
4294
|
total: totalBlocks,
|
|
@@ -4057,7 +4304,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
4057
4304
|
currentHeight = batchEnd + 1;
|
|
4058
4305
|
}
|
|
4059
4306
|
await stats.flush(targetDb);
|
|
4060
|
-
|
|
4307
|
+
logger9.info("Subgraph catch-up complete", {
|
|
4061
4308
|
subgraph: subgraphName,
|
|
4062
4309
|
processed
|
|
4063
4310
|
});
|
|
@@ -4069,7 +4316,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
4069
4316
|
|
|
4070
4317
|
// src/runtime/reindex.ts
|
|
4071
4318
|
import { getErrorMessage as getErrorMessage3 } from "@secondlayer/shared";
|
|
4072
|
-
import { getRawClient, getTargetDb as
|
|
4319
|
+
import { getRawClient, getTargetDb as getTargetDb4 } from "@secondlayer/shared/db";
|
|
4073
4320
|
import {
|
|
4074
4321
|
recordGapBatch as recordGapBatch2,
|
|
4075
4322
|
resolveGaps
|
|
@@ -4082,11 +4329,11 @@ import {
|
|
|
4082
4329
|
recordSubgraphProcessed as recordSubgraphProcessed2,
|
|
4083
4330
|
updateSubgraphStatus as updateSubgraphStatus2
|
|
4084
4331
|
} from "@secondlayer/shared/db/queries/subgraphs";
|
|
4085
|
-
import { logger as
|
|
4332
|
+
import { logger as logger11 } from "@secondlayer/shared/logger";
|
|
4086
4333
|
|
|
4087
4334
|
// src/runtime/reindex-notify.ts
|
|
4088
4335
|
import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
|
|
4089
|
-
import { logger as
|
|
4336
|
+
import { logger as logger10 } from "@secondlayer/shared/logger";
|
|
4090
4337
|
async function notifyReindexComplete(db, subgraphName, stats) {
|
|
4091
4338
|
try {
|
|
4092
4339
|
const subgraph = await db.selectFrom("subgraphs").select(["account_id"]).where("name", "=", subgraphName).executeTakeFirst();
|
|
@@ -4097,7 +4344,7 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
4097
4344
|
return;
|
|
4098
4345
|
const resendKey = process.env.RESEND_API_KEY;
|
|
4099
4346
|
if (!resendKey) {
|
|
4100
|
-
|
|
4347
|
+
logger10.warn("RESEND_API_KEY unset — skipping reindex-complete email", {
|
|
4101
4348
|
subgraph: subgraphName
|
|
4102
4349
|
});
|
|
4103
4350
|
return;
|
|
@@ -4119,14 +4366,14 @@ async function notifyReindexComplete(db, subgraphName, stats) {
|
|
|
4119
4366
|
});
|
|
4120
4367
|
if (!res.ok) {
|
|
4121
4368
|
const text = await res.text().catch(() => "");
|
|
4122
|
-
|
|
4369
|
+
logger10.warn("reindex-complete email failed", {
|
|
4123
4370
|
subgraph: subgraphName,
|
|
4124
4371
|
status: res.status,
|
|
4125
4372
|
body: text.slice(0, 200)
|
|
4126
4373
|
});
|
|
4127
4374
|
}
|
|
4128
4375
|
} catch (err) {
|
|
4129
|
-
|
|
4376
|
+
logger10.warn("reindex-complete email threw", {
|
|
4130
4377
|
subgraph: subgraphName,
|
|
4131
4378
|
error: getErrorMessage2(err)
|
|
4132
4379
|
});
|
|
@@ -4195,7 +4442,7 @@ function coalesceFailedBlocks(blocks) {
|
|
|
4195
4442
|
}
|
|
4196
4443
|
async function processBlockRange(def, opts) {
|
|
4197
4444
|
const source = resolveBlockSource(def);
|
|
4198
|
-
const targetDb =
|
|
4445
|
+
const targetDb = getTargetDb4();
|
|
4199
4446
|
const subgraphName = def.name;
|
|
4200
4447
|
const { fromBlock, toBlock, status } = opts;
|
|
4201
4448
|
const totalBlocks = toBlock - fromBlock + 1;
|
|
@@ -4238,7 +4485,7 @@ async function processBlockRange(def, opts) {
|
|
|
4238
4485
|
while (currentHeight <= toBlock) {
|
|
4239
4486
|
if (opts.signal?.aborted) {
|
|
4240
4487
|
aborted = true;
|
|
4241
|
-
|
|
4488
|
+
logger11.info("Block processing aborted", {
|
|
4242
4489
|
subgraph: subgraphName,
|
|
4243
4490
|
currentBlock: currentHeight,
|
|
4244
4491
|
reason: String(opts.signal.reason ?? "unknown")
|
|
@@ -4289,7 +4536,7 @@ async function processBlockRange(def, opts) {
|
|
|
4289
4536
|
result = mayWrite ? await withSubgraphBlockWriteLock(subgraphName, runBlock) : await runBlock();
|
|
4290
4537
|
} catch (err) {
|
|
4291
4538
|
const errorMsg = getErrorMessage3(err);
|
|
4292
|
-
|
|
4539
|
+
logger11.error("Block processing failed persistently", {
|
|
4293
4540
|
subgraph: subgraphName,
|
|
4294
4541
|
blockHeight: height,
|
|
4295
4542
|
error: errorMsg
|
|
@@ -4335,7 +4582,7 @@ async function processBlockRange(def, opts) {
|
|
|
4335
4582
|
lastProgressFlushAt = now;
|
|
4336
4583
|
}
|
|
4337
4584
|
if (blocksProcessed % LOG_INTERVAL2 === 0) {
|
|
4338
|
-
|
|
4585
|
+
logger11.info(`${status === "reindexing" ? "Reindex" : "Backfill"} progress`, {
|
|
4339
4586
|
subgraph: subgraphName,
|
|
4340
4587
|
processed: blocksProcessed,
|
|
4341
4588
|
total: totalBlocks,
|
|
@@ -4355,7 +4602,7 @@ async function processBlockRange(def, opts) {
|
|
|
4355
4602
|
if (batchFailedBlocks.length > 0 && opts.subgraphId) {
|
|
4356
4603
|
const gaps = coalesceFailedBlocks(batchFailedBlocks);
|
|
4357
4604
|
await recordGapBatch2(targetDb, opts.subgraphId, subgraphName, gaps).catch((err) => {
|
|
4358
|
-
|
|
4605
|
+
logger11.warn("Failed to record subgraph gaps", {
|
|
4359
4606
|
subgraph: subgraphName,
|
|
4360
4607
|
error: err instanceof Error ? err.message : String(err)
|
|
4361
4608
|
});
|
|
@@ -4372,7 +4619,7 @@ async function processBlockRange(def, opts) {
|
|
|
4372
4619
|
} else {
|
|
4373
4620
|
await updateSubgraphStatus2(targetDb, subgraphName, status, jumpTo - 1);
|
|
4374
4621
|
}
|
|
4375
|
-
|
|
4622
|
+
logger11.info("Sparse skip", {
|
|
4376
4623
|
subgraph: subgraphName,
|
|
4377
4624
|
from: batchEnd + 1,
|
|
4378
4625
|
to: jumpTo - 1,
|
|
@@ -4406,7 +4653,7 @@ async function resolveBlockRange(source, def, opts) {
|
|
|
4406
4653
|
}
|
|
4407
4654
|
const bound = await boundSourceTip(rawTip, decoderNamesForSubgraph(def));
|
|
4408
4655
|
if (!bound.ok) {
|
|
4409
|
-
|
|
4656
|
+
logger11.warn("Reindex stalled: missing decoder checkpoint", {
|
|
4410
4657
|
event: "subgraph_reindex_decoder_stall",
|
|
4411
4658
|
subgraph: def.name,
|
|
4412
4659
|
missing: bound.missing
|
|
@@ -4414,7 +4661,7 @@ async function resolveBlockRange(source, def, opts) {
|
|
|
4414
4661
|
throw new Error(`Reindex stalled: missing decoder checkpoint (${bound.missing.join(", ")})`);
|
|
4415
4662
|
}
|
|
4416
4663
|
if (bound.floor !== null && bound.floor < rawTip) {
|
|
4417
|
-
|
|
4664
|
+
logger11.debug("Reindex tip bounded by decoder progress", {
|
|
4418
4665
|
event: "subgraph_reindex_decoder_floor",
|
|
4419
4666
|
subgraph: def.name,
|
|
4420
4667
|
rawTip,
|
|
@@ -4429,16 +4676,16 @@ async function clearReindexMetadata(db, subgraphName) {
|
|
|
4429
4676
|
}
|
|
4430
4677
|
async function reindexSubgraph(def, opts) {
|
|
4431
4678
|
const source = resolveBlockSource(def);
|
|
4432
|
-
const targetDb =
|
|
4679
|
+
const targetDb = getTargetDb4();
|
|
4433
4680
|
const client = getRawClient("target");
|
|
4434
4681
|
const subgraphName = def.name;
|
|
4435
4682
|
const schemaName = opts?.schemaName ?? pgSchemaName(subgraphName);
|
|
4436
4683
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing");
|
|
4437
|
-
|
|
4684
|
+
logger11.info("Reindex starting", { subgraph: subgraphName });
|
|
4438
4685
|
try {
|
|
4439
4686
|
const { fromBlock, toBlock } = await resolveBlockRange(source, def, opts);
|
|
4440
4687
|
if (fromBlock > toBlock) {
|
|
4441
|
-
|
|
4688
|
+
logger11.info("No blocks to reindex", {
|
|
4442
4689
|
subgraph: subgraphName,
|
|
4443
4690
|
fromBlock,
|
|
4444
4691
|
toBlock
|
|
@@ -4452,7 +4699,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
4452
4699
|
await client.unsafe(stmt);
|
|
4453
4700
|
}
|
|
4454
4701
|
await updateSubgraphStatus2(targetDb, subgraphName, "reindexing", Math.max(0, fromBlock - 1));
|
|
4455
|
-
|
|
4702
|
+
logger11.info("Schema recreated for reindex", {
|
|
4456
4703
|
subgraph: subgraphName,
|
|
4457
4704
|
cursorResetTo: Math.max(0, fromBlock - 1)
|
|
4458
4705
|
});
|
|
@@ -4466,7 +4713,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
4466
4713
|
last_error_at: null,
|
|
4467
4714
|
updated_at: new Date
|
|
4468
4715
|
}).where("name", "=", subgraphName).execute();
|
|
4469
|
-
|
|
4716
|
+
logger11.info("Reindexing blocks", {
|
|
4470
4717
|
subgraph: subgraphName,
|
|
4471
4718
|
fromBlock,
|
|
4472
4719
|
toBlock,
|
|
@@ -4488,9 +4735,9 @@ async function reindexSubgraph(def, opts) {
|
|
|
4488
4735
|
if (reason === "user-cancelled") {
|
|
4489
4736
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
4490
4737
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
4491
|
-
|
|
4738
|
+
logger11.info("Reindex cancelled by user", { subgraph: subgraphName });
|
|
4492
4739
|
} else {
|
|
4493
|
-
|
|
4740
|
+
logger11.info("Reindex interrupted by shutdown, will resume", {
|
|
4494
4741
|
subgraph: subgraphName
|
|
4495
4742
|
});
|
|
4496
4743
|
}
|
|
@@ -4498,7 +4745,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
4498
4745
|
}
|
|
4499
4746
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
4500
4747
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
4501
|
-
|
|
4748
|
+
logger11.info("Reindex complete", {
|
|
4502
4749
|
subgraph: subgraphName,
|
|
4503
4750
|
blocks: result.blocksProcessed,
|
|
4504
4751
|
events: result.totalEventsProcessed,
|
|
@@ -4511,7 +4758,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
4511
4758
|
});
|
|
4512
4759
|
return { processed: result.blocksProcessed };
|
|
4513
4760
|
} catch (err) {
|
|
4514
|
-
|
|
4761
|
+
logger11.error("Reindex failed", {
|
|
4515
4762
|
subgraph: subgraphName,
|
|
4516
4763
|
error: getErrorMessage3(err)
|
|
4517
4764
|
});
|
|
@@ -4520,7 +4767,7 @@ async function reindexSubgraph(def, opts) {
|
|
|
4520
4767
|
}
|
|
4521
4768
|
}
|
|
4522
4769
|
async function resumeReindex(def, opts) {
|
|
4523
|
-
const targetDb =
|
|
4770
|
+
const targetDb = getTargetDb4();
|
|
4524
4771
|
const subgraphName = def.name;
|
|
4525
4772
|
const row = await targetDb.selectFrom("subgraphs").select([
|
|
4526
4773
|
"id",
|
|
@@ -4533,7 +4780,7 @@ async function resumeReindex(def, opts) {
|
|
|
4533
4780
|
const fromBlock = resolveReindexResumeBlock(row);
|
|
4534
4781
|
const toBlock = Number(row.reindex_to_block);
|
|
4535
4782
|
if (fromBlock == null) {
|
|
4536
|
-
|
|
4783
|
+
logger11.info("No reindex metadata, starting fresh reindex", {
|
|
4537
4784
|
subgraph: subgraphName
|
|
4538
4785
|
});
|
|
4539
4786
|
return reindexSubgraph(def, {
|
|
@@ -4542,12 +4789,12 @@ async function resumeReindex(def, opts) {
|
|
|
4542
4789
|
});
|
|
4543
4790
|
}
|
|
4544
4791
|
if (fromBlock > toBlock) {
|
|
4545
|
-
|
|
4792
|
+
logger11.info("Resume: no remaining blocks", { subgraph: subgraphName });
|
|
4546
4793
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
4547
4794
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
4548
4795
|
return { processed: 0 };
|
|
4549
4796
|
}
|
|
4550
|
-
|
|
4797
|
+
logger11.info("Resuming reindex", {
|
|
4551
4798
|
subgraph: subgraphName,
|
|
4552
4799
|
fromBlock,
|
|
4553
4800
|
toBlock,
|
|
@@ -4569,9 +4816,9 @@ async function resumeReindex(def, opts) {
|
|
|
4569
4816
|
if (reason === "user-cancelled") {
|
|
4570
4817
|
await updateSubgraphStatus2(targetDb, subgraphName, "active");
|
|
4571
4818
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
4572
|
-
|
|
4819
|
+
logger11.info("Resume cancelled by user", { subgraph: subgraphName });
|
|
4573
4820
|
} else {
|
|
4574
|
-
|
|
4821
|
+
logger11.info("Resume interrupted by shutdown, will resume again", {
|
|
4575
4822
|
subgraph: subgraphName
|
|
4576
4823
|
});
|
|
4577
4824
|
}
|
|
@@ -4579,13 +4826,13 @@ async function resumeReindex(def, opts) {
|
|
|
4579
4826
|
}
|
|
4580
4827
|
await updateSubgraphStatus2(targetDb, subgraphName, "active", toBlock);
|
|
4581
4828
|
await clearReindexMetadata(targetDb, subgraphName);
|
|
4582
|
-
|
|
4829
|
+
logger11.info("Resumed reindex complete", {
|
|
4583
4830
|
subgraph: subgraphName,
|
|
4584
4831
|
blocks: result.blocksProcessed
|
|
4585
4832
|
});
|
|
4586
4833
|
return { processed: result.blocksProcessed };
|
|
4587
4834
|
} catch (err) {
|
|
4588
|
-
|
|
4835
|
+
logger11.error("Resumed reindex failed", {
|
|
4589
4836
|
subgraph: subgraphName,
|
|
4590
4837
|
error: getErrorMessage3(err)
|
|
4591
4838
|
});
|
|
@@ -4594,9 +4841,9 @@ async function resumeReindex(def, opts) {
|
|
|
4594
4841
|
}
|
|
4595
4842
|
}
|
|
4596
4843
|
async function backfillSubgraph(def, opts) {
|
|
4597
|
-
const targetDb =
|
|
4844
|
+
const targetDb = getTargetDb4();
|
|
4598
4845
|
const subgraphName = def.name;
|
|
4599
|
-
|
|
4846
|
+
logger11.info("Backfill starting", {
|
|
4600
4847
|
subgraph: subgraphName,
|
|
4601
4848
|
from: opts.fromBlock,
|
|
4602
4849
|
to: opts.toBlock
|
|
@@ -4614,17 +4861,17 @@ async function backfillSubgraph(def, opts) {
|
|
|
4614
4861
|
signal: opts.signal
|
|
4615
4862
|
});
|
|
4616
4863
|
if (result.aborted) {
|
|
4617
|
-
|
|
4864
|
+
logger11.info("Backfill aborted", { subgraph: subgraphName });
|
|
4618
4865
|
return { processed: result.blocksProcessed };
|
|
4619
4866
|
}
|
|
4620
4867
|
const resolved = await resolveGaps(targetDb, subgraphName, opts.fromBlock, opts.toBlock).catch(() => 0);
|
|
4621
4868
|
if (resolved > 0) {
|
|
4622
|
-
|
|
4869
|
+
logger11.info("Resolved subgraph gaps via backfill", {
|
|
4623
4870
|
subgraph: subgraphName,
|
|
4624
4871
|
resolved
|
|
4625
4872
|
});
|
|
4626
4873
|
}
|
|
4627
|
-
|
|
4874
|
+
logger11.info("Backfill complete", {
|
|
4628
4875
|
subgraph: subgraphName,
|
|
4629
4876
|
blocks: result.blocksProcessed,
|
|
4630
4877
|
events: result.totalEventsProcessed,
|
|
@@ -4632,7 +4879,7 @@ async function backfillSubgraph(def, opts) {
|
|
|
4632
4879
|
});
|
|
4633
4880
|
return { processed: result.blocksProcessed };
|
|
4634
4881
|
} catch (err) {
|
|
4635
|
-
|
|
4882
|
+
logger11.error("Backfill failed", {
|
|
4636
4883
|
subgraph: subgraphName,
|
|
4637
4884
|
error: getErrorMessage3(err)
|
|
4638
4885
|
});
|
|
@@ -5200,7 +5447,7 @@ function generateIndexSchema(target, opts = {}) {
|
|
|
5200
5447
|
}
|
|
5201
5448
|
var INDEX_CODEGEN_TABLES = Object.keys(SOURCE_READ_TYPES);
|
|
5202
5449
|
// src/schema/deployer.ts
|
|
5203
|
-
import { sql as
|
|
5450
|
+
import { sql as sql6 } from "kysely";
|
|
5204
5451
|
function toJsonSafe(obj) {
|
|
5205
5452
|
return JSON.parse(JSON.stringify(obj, (_key, value) => typeof value === "bigint" ? value.toString() : value));
|
|
5206
5453
|
}
|
|
@@ -5323,7 +5570,7 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5323
5570
|
startBlock: def.startBlock
|
|
5324
5571
|
};
|
|
5325
5572
|
if (existing) {
|
|
5326
|
-
const schemaExists = await
|
|
5573
|
+
const schemaExists = await sql6`
|
|
5327
5574
|
SELECT EXISTS (
|
|
5328
5575
|
SELECT 1 FROM information_schema.schemata
|
|
5329
5576
|
WHERE schema_name = ${schemaName}
|
|
@@ -5331,7 +5578,7 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5331
5578
|
`.execute(ddlDb).then((r) => r.rows[0]?.exists ?? false);
|
|
5332
5579
|
if (!schemaExists) {
|
|
5333
5580
|
for (const stmt of statements) {
|
|
5334
|
-
await
|
|
5581
|
+
await sql6.raw(stmt).execute(ddlDb);
|
|
5335
5582
|
}
|
|
5336
5583
|
const sg2 = await registerSubgraph(db, regData);
|
|
5337
5584
|
return { action: "reindexed", subgraphId: sg2.id, version: newVersion };
|
|
@@ -5355,9 +5602,9 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5355
5602
|
};
|
|
5356
5603
|
}
|
|
5357
5604
|
if (existing.schema_hash === hash && opts?.forceReindex) {
|
|
5358
|
-
await
|
|
5605
|
+
await sql6.raw(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`).execute(ddlDb);
|
|
5359
5606
|
for (const stmt of statements) {
|
|
5360
|
-
await
|
|
5607
|
+
await sql6.raw(stmt).execute(ddlDb);
|
|
5361
5608
|
}
|
|
5362
5609
|
const sg2 = await registerSubgraph(db, regData);
|
|
5363
5610
|
return { action: "reindexed", subgraphId: sg2.id, version: newVersion };
|
|
@@ -5366,9 +5613,9 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5366
5613
|
const diff = diffSchema(existing.definition.schema, def.schema);
|
|
5367
5614
|
const { breaking, reasons } = hasBreakingChanges(diff);
|
|
5368
5615
|
if (breaking || opts?.forceReindex) {
|
|
5369
|
-
await
|
|
5616
|
+
await sql6.raw(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`).execute(ddlDb);
|
|
5370
5617
|
for (const stmt of statements) {
|
|
5371
|
-
await
|
|
5618
|
+
await sql6.raw(stmt).execute(ddlDb);
|
|
5372
5619
|
}
|
|
5373
5620
|
const sg3 = await registerSubgraph(db, regData);
|
|
5374
5621
|
const deployDiff2 = toDeployDiff(diff, reasons);
|
|
@@ -5381,16 +5628,16 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5381
5628
|
}
|
|
5382
5629
|
const addedDefs = diff.addedTables.map((tableName) => ({ tableName, tableDef: def.schema[tableName] })).filter((t) => t.tableDef !== undefined);
|
|
5383
5630
|
if (addedDefs.some(({ tableDef }) => tableNeedsTrgm(tableDef))) {
|
|
5384
|
-
await
|
|
5631
|
+
await sql6.raw("CREATE EXTENSION IF NOT EXISTS pg_trgm").execute(ddlDb);
|
|
5385
5632
|
}
|
|
5386
5633
|
for (const { tableName, tableDef } of addedDefs) {
|
|
5387
5634
|
for (const stmt of emitTableDDL(schemaName, tableName, tableDef)) {
|
|
5388
|
-
await
|
|
5635
|
+
await sql6.raw(stmt).execute(ddlDb);
|
|
5389
5636
|
}
|
|
5390
5637
|
}
|
|
5391
5638
|
for (const { tableName, tableDef } of addedDefs) {
|
|
5392
5639
|
for (const stmt of emitForeignKeyDDL(schemaName, tableName, tableDef)) {
|
|
5393
|
-
await
|
|
5640
|
+
await sql6.raw(stmt).execute(ddlDb);
|
|
5394
5641
|
}
|
|
5395
5642
|
}
|
|
5396
5643
|
for (const [tableName, colDiff] of Object.entries(diff.tables)) {
|
|
@@ -5408,12 +5655,12 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5408
5655
|
if (!sqlType)
|
|
5409
5656
|
continue;
|
|
5410
5657
|
const nullable = col.nullable ? "" : ` NOT NULL DEFAULT ${getDefault(col.type)}`;
|
|
5411
|
-
await
|
|
5658
|
+
await sql6.raw(`ALTER TABLE ${qualifiedName} ADD COLUMN ${colName} ${sqlType}${nullable}`).execute(ddlDb);
|
|
5412
5659
|
if (col.indexed) {
|
|
5413
|
-
await
|
|
5660
|
+
await sql6.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName} ON ${qualifiedName} (${colName})`).execute(ddlDb);
|
|
5414
5661
|
}
|
|
5415
5662
|
if (col.search) {
|
|
5416
|
-
await
|
|
5663
|
+
await sql6.raw(`CREATE INDEX IF NOT EXISTS idx_${schemaName}_${tableName}_${colName}_trgm ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`).execute(ddlDb);
|
|
5417
5664
|
}
|
|
5418
5665
|
}
|
|
5419
5666
|
}
|
|
@@ -5421,7 +5668,7 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5421
5668
|
const flagFlips = {};
|
|
5422
5669
|
const needsTrgmForFlips = Object.entries(diff.tables).some(([tableName, colDiff]) => colDiff.indexChanged.some((colName) => def.schema[tableName]?.columns[colName]?.search && !existingSchema[tableName]?.columns[colName]?.search));
|
|
5423
5670
|
if (needsTrgmForFlips) {
|
|
5424
|
-
await
|
|
5671
|
+
await sql6.raw("CREATE EXTENSION IF NOT EXISTS pg_trgm").execute(ddlDb);
|
|
5425
5672
|
}
|
|
5426
5673
|
for (const [tableName, colDiff] of Object.entries(diff.tables)) {
|
|
5427
5674
|
if (colDiff.indexChanged.length === 0)
|
|
@@ -5436,14 +5683,14 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5436
5683
|
const plainIdx = `idx_${schemaName}_${tableName}_${colName}`;
|
|
5437
5684
|
const trgmIdx = `idx_${schemaName}_${tableName}_${colName}_trgm`;
|
|
5438
5685
|
if (now.indexed && !was?.indexed) {
|
|
5439
|
-
await
|
|
5686
|
+
await sql6.raw(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${plainIdx} ON ${qualifiedName} (${colName})`).execute(ddlDb);
|
|
5440
5687
|
} else if (!now.indexed && was?.indexed) {
|
|
5441
|
-
await
|
|
5688
|
+
await sql6.raw(`DROP INDEX CONCURRENTLY IF EXISTS ${schemaName}.${plainIdx}`).execute(ddlDb);
|
|
5442
5689
|
}
|
|
5443
5690
|
if (now.search && !was?.search) {
|
|
5444
|
-
await
|
|
5691
|
+
await sql6.raw(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${trgmIdx} ON ${qualifiedName} USING gin (${colName} gin_trgm_ops)`).execute(ddlDb);
|
|
5445
5692
|
} else if (!now.search && was?.search) {
|
|
5446
|
-
await
|
|
5693
|
+
await sql6.raw(`DROP INDEX CONCURRENTLY IF EXISTS ${schemaName}.${trgmIdx}`).execute(ddlDb);
|
|
5447
5694
|
}
|
|
5448
5695
|
}
|
|
5449
5696
|
}
|
|
@@ -5452,28 +5699,28 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5452
5699
|
const qualifiedName = `${schemaName}.${tableName}`;
|
|
5453
5700
|
for (const cols of conDiff.addedUniqueKeys) {
|
|
5454
5701
|
const constraintName = `uq_${schemaName}_${tableName}_${cols.join("_")}`;
|
|
5455
|
-
const exists = await
|
|
5702
|
+
const exists = await sql6`
|
|
5456
5703
|
SELECT EXISTS (
|
|
5457
5704
|
SELECT 1 FROM pg_constraint
|
|
5458
5705
|
WHERE conname = ${constraintName}
|
|
5459
5706
|
) AS "exists"
|
|
5460
5707
|
`.execute(ddlDb).then((r) => r.rows[0]?.exists ?? false);
|
|
5461
5708
|
if (!exists) {
|
|
5462
|
-
await
|
|
5709
|
+
await sql6.raw(`ALTER TABLE ${qualifiedName} ADD CONSTRAINT ${constraintName} UNIQUE (${cols.join(", ")})`).execute(ddlDb);
|
|
5463
5710
|
}
|
|
5464
5711
|
addedUq[tableName] = [...addedUq[tableName] ?? [], cols];
|
|
5465
5712
|
}
|
|
5466
5713
|
for (const cols of conDiff.addedIndexes) {
|
|
5467
5714
|
const i = (def.schema[tableName]?.indexes ?? []).findIndex((entry) => JSON.stringify(entry) === JSON.stringify(cols));
|
|
5468
5715
|
const idxName = `idx_${schemaName}_${tableName}_composite_${i}`;
|
|
5469
|
-
await
|
|
5716
|
+
await sql6.raw(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${idxName} ON ${qualifiedName} (${cols.join(", ")})`).execute(ddlDb);
|
|
5470
5717
|
}
|
|
5471
5718
|
for (const cols of conDiff.removedIndexes) {
|
|
5472
5719
|
const i = (existingSchema[tableName]?.indexes ?? []).findIndex((entry) => JSON.stringify(entry) === JSON.stringify(cols));
|
|
5473
5720
|
if (i < 0)
|
|
5474
5721
|
continue;
|
|
5475
5722
|
const idxName = `idx_${schemaName}_${tableName}_composite_${i}`;
|
|
5476
|
-
await
|
|
5723
|
+
await sql6.raw(`DROP INDEX CONCURRENTLY IF EXISTS ${schemaName}.${idxName}`).execute(ddlDb);
|
|
5477
5724
|
}
|
|
5478
5725
|
}
|
|
5479
5726
|
const sg2 = await registerSubgraph(db, regData);
|
|
@@ -5499,7 +5746,7 @@ async function deploySchema(db, def, handlerPath, opts) {
|
|
|
5499
5746
|
}
|
|
5500
5747
|
}
|
|
5501
5748
|
for (const stmt of statements) {
|
|
5502
|
-
await
|
|
5749
|
+
await sql6.raw(stmt).execute(ddlDb);
|
|
5503
5750
|
}
|
|
5504
5751
|
const sg = await registerSubgraph(db, regData);
|
|
5505
5752
|
return { action: "created", subgraphId: sg.id, version: newVersion };
|
|
@@ -5544,11 +5791,12 @@ export {
|
|
|
5544
5791
|
defineSubgraph,
|
|
5545
5792
|
defineSchema,
|
|
5546
5793
|
canSparseScan,
|
|
5794
|
+
camelizeKeys,
|
|
5547
5795
|
camelizeDataKey,
|
|
5548
5796
|
backfillSubgraph,
|
|
5549
5797
|
INDEX_CODEGEN_TABLES,
|
|
5550
5798
|
ChainReadError
|
|
5551
5799
|
};
|
|
5552
5800
|
|
|
5553
|
-
//# debugId=
|
|
5801
|
+
//# debugId=226325936FFF7D9F64756E2164756E21
|
|
5554
5802
|
//# sourceMappingURL=index.js.map
|