@secondlayer/shared 7.6.0 → 7.7.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/archive/copy-loader.d.ts +49 -0
- package/dist/src/archive/copy-loader.js +111 -0
- package/dist/src/archive/copy-loader.js.map +10 -0
- package/dist/src/archive/range-digest.d.ts +1062 -0
- package/dist/src/archive/range-digest.js +142 -0
- package/dist/src/archive/range-digest.js.map +10 -0
- package/dist/src/db/index.d.ts +27 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/index.js.map +3 -3
- package/dist/src/db/queries/chain-reorgs.d.ts +25 -0
- package/dist/src/db/queries/chain-reorgs.js +2 -1
- package/dist/src/db/queries/chain-reorgs.js.map +3 -3
- package/dist/src/db/queries/contracts.d.ts +25 -0
- package/dist/src/db/queries/integrity.d.ts +25 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +25 -0
- package/dist/src/db/queries/subgraph-operations.d.ts +25 -0
- package/dist/src/db/queries/subgraphs.d.ts +25 -0
- package/dist/src/db/queries/subgraphs.js +2 -1
- package/dist/src/db/queries/subgraphs.js.map +3 -3
- package/dist/src/db/queries/subscriptions.d.ts +25 -0
- package/dist/src/db/schema.d.ts +26 -1
- package/dist/src/index.d.ts +27 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +3 -3
- package/dist/src/node/local-client.d.ts +25 -0
- package/migrations/0114_observer_journal.ts +54 -0
- package/package.json +9 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Writable } from "node:stream";
|
|
2
|
+
/**
|
|
3
|
+
* Streaming `COPY ... FROM STDIN` loader for canonical archive rows.
|
|
4
|
+
*
|
|
5
|
+
* Restoring history row-by-row is not viable at chain scale: batched INSERTs
|
|
6
|
+
* measured ~2k rows/s on this schema, which is days for a full genesis restore.
|
|
7
|
+
* COPY moves the same data at ~64k rows/s because it is one statement per
|
|
8
|
+
* partition with no per-row round-trip.
|
|
9
|
+
*
|
|
10
|
+
* This module deliberately knows nothing about Parquet or about where rows come
|
|
11
|
+
* from — it accepts the archive's row shape as an async iterable. That keeps the
|
|
12
|
+
* heavy file-format dependency out of every consumer that only needs to write
|
|
13
|
+
* rows, and lets the CLI and the indexer share one loader rather than growing
|
|
14
|
+
* two subtly different CSV encoders.
|
|
15
|
+
*/
|
|
16
|
+
type ArchiveDataset = "blocks" | "transactions" | "events";
|
|
17
|
+
/** Row shapes exactly as the archive's Parquet columns are named. */
|
|
18
|
+
type ArchiveRow = Record<string, unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* CSV encoding for `WITH (FORMAT csv, NULL '\N')`: every present value is
|
|
21
|
+
* quoted with embedded quotes doubled, so a value that happens to equal the
|
|
22
|
+
* NULL marker cannot be misread as SQL NULL. Only a genuinely absent value is
|
|
23
|
+
* written unquoted as `\N`.
|
|
24
|
+
*/
|
|
25
|
+
declare function csvField(value: string | number | null | undefined): string;
|
|
26
|
+
declare function toCsvLine(dataset: ArchiveDataset, row: ArchiveRow): string;
|
|
27
|
+
/**
|
|
28
|
+
* `canonical` is omitted from the blocks column list: every archived block is
|
|
29
|
+
* canonical by definition, and the schema defaults it to true, so carrying it
|
|
30
|
+
* through every row would be dead weight.
|
|
31
|
+
*/
|
|
32
|
+
declare function copyStatement(dataset: ArchiveDataset): string;
|
|
33
|
+
/**
|
|
34
|
+
* Stream rows onto an open COPY writable. The caller owns opening it (drivers
|
|
35
|
+
* differ in how a COPY stream is obtained) and this owns the wire format,
|
|
36
|
+
* backpressure, and failure semantics.
|
|
37
|
+
*
|
|
38
|
+
* On error the stream is destroyed rather than ended, so Postgres aborts the
|
|
39
|
+
* COPY instead of committing a partial one — an interrupted load must leave
|
|
40
|
+
* nothing behind, not half a partition.
|
|
41
|
+
*/
|
|
42
|
+
declare function writeRowsToCopyStream(params: {
|
|
43
|
+
writable: Writable
|
|
44
|
+
dataset: ArchiveDataset
|
|
45
|
+
rows: AsyncIterable<ArchiveRow>
|
|
46
|
+
onProgress?: (rowsWritten: number) => void
|
|
47
|
+
progressEvery?: number
|
|
48
|
+
}): Promise<number>;
|
|
49
|
+
export { writeRowsToCopyStream, toCsvLine, csvField, copyStatement, ArchiveRow, ArchiveDataset };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/archive/copy-loader.ts
|
|
18
|
+
import { once } from "node:events";
|
|
19
|
+
function csvField(value) {
|
|
20
|
+
if (value === null || value === undefined)
|
|
21
|
+
return "\\N";
|
|
22
|
+
const text = typeof value === "number" ? String(value) : value;
|
|
23
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
24
|
+
}
|
|
25
|
+
function asNumber(value) {
|
|
26
|
+
return typeof value === "bigint" ? Number(value) : Number(value);
|
|
27
|
+
}
|
|
28
|
+
function asText(value) {
|
|
29
|
+
return Buffer.isBuffer(value) ? value.toString("utf8") : String(value);
|
|
30
|
+
}
|
|
31
|
+
function asOptionalText(value) {
|
|
32
|
+
return value === null || value === undefined ? null : asText(value);
|
|
33
|
+
}
|
|
34
|
+
function toCsvLine(dataset, row) {
|
|
35
|
+
if (dataset === "blocks") {
|
|
36
|
+
return [
|
|
37
|
+
csvField(asNumber(row.height)),
|
|
38
|
+
csvField(asText(row.hash)),
|
|
39
|
+
csvField(asText(row.parent_hash)),
|
|
40
|
+
csvField(asNumber(row.burn_block_height)),
|
|
41
|
+
csvField(asOptionalText(row.burn_block_hash)),
|
|
42
|
+
csvField(asOptionalText(row.index_block_hash)),
|
|
43
|
+
csvField(asNumber(row.timestamp))
|
|
44
|
+
].join(",");
|
|
45
|
+
}
|
|
46
|
+
if (dataset === "transactions") {
|
|
47
|
+
return [
|
|
48
|
+
csvField(asText(row.tx_id)),
|
|
49
|
+
csvField(asNumber(row.block_height)),
|
|
50
|
+
csvField(asNumber(row.tx_index)),
|
|
51
|
+
csvField(asText(row.type)),
|
|
52
|
+
csvField(asText(row.sender)),
|
|
53
|
+
csvField(asText(row.status)),
|
|
54
|
+
csvField(asOptionalText(row.contract_id)),
|
|
55
|
+
csvField(asOptionalText(row.function_name)),
|
|
56
|
+
csvField(asOptionalText(row.function_args_json)),
|
|
57
|
+
csvField(asOptionalText(row.raw_result)),
|
|
58
|
+
csvField(asText(row.raw_tx))
|
|
59
|
+
].join(",");
|
|
60
|
+
}
|
|
61
|
+
return [
|
|
62
|
+
csvField(asText(row.tx_id)),
|
|
63
|
+
csvField(asNumber(row.block_height)),
|
|
64
|
+
csvField(asNumber(row.event_index)),
|
|
65
|
+
csvField(asText(row.event_type)),
|
|
66
|
+
csvField(asText(row.data_json))
|
|
67
|
+
].join(",");
|
|
68
|
+
}
|
|
69
|
+
function copyStatement(dataset) {
|
|
70
|
+
if (dataset === "blocks") {
|
|
71
|
+
return "COPY blocks (height, hash, parent_hash, burn_block_height, burn_block_hash, index_block_hash, timestamp) FROM STDIN WITH (FORMAT csv, NULL '\\N')";
|
|
72
|
+
}
|
|
73
|
+
if (dataset === "transactions") {
|
|
74
|
+
return "COPY transactions (tx_id, block_height, tx_index, type, sender, status, contract_id, function_name, function_args, raw_result, raw_tx) FROM STDIN WITH (FORMAT csv, NULL '\\N')";
|
|
75
|
+
}
|
|
76
|
+
return "COPY events (tx_id, block_height, event_index, type, data) FROM STDIN WITH (FORMAT csv, NULL '\\N')";
|
|
77
|
+
}
|
|
78
|
+
async function writeRowsToCopyStream(params) {
|
|
79
|
+
const { writable, dataset, rows } = params;
|
|
80
|
+
const progressEvery = params.progressEvery ?? 500000;
|
|
81
|
+
let count = 0;
|
|
82
|
+
try {
|
|
83
|
+
for await (const row of rows) {
|
|
84
|
+
const line = `${toCsvLine(dataset, row)}
|
|
85
|
+
`;
|
|
86
|
+
if (!writable.write(line)) {
|
|
87
|
+
await once(writable, "drain");
|
|
88
|
+
}
|
|
89
|
+
count++;
|
|
90
|
+
if (params.onProgress && count % progressEvery === 0) {
|
|
91
|
+
params.onProgress(count);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
await new Promise((resolve, reject) => {
|
|
95
|
+
writable.end((error) => error ? reject(error) : resolve());
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
writable.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
return count;
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
writeRowsToCopyStream,
|
|
105
|
+
toCsvLine,
|
|
106
|
+
csvField,
|
|
107
|
+
copyStatement
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
//# debugId=2B81DFB47D25D63C64756E2164756E21
|
|
111
|
+
//# sourceMappingURL=copy-loader.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/archive/copy-loader.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { once } from \"node:events\";\nimport type { Writable } from \"node:stream\";\n\n/**\n * Streaming `COPY ... FROM STDIN` loader for canonical archive rows.\n *\n * Restoring history row-by-row is not viable at chain scale: batched INSERTs\n * measured ~2k rows/s on this schema, which is days for a full genesis restore.\n * COPY moves the same data at ~64k rows/s because it is one statement per\n * partition with no per-row round-trip.\n *\n * This module deliberately knows nothing about Parquet or about where rows come\n * from — it accepts the archive's row shape as an async iterable. That keeps the\n * heavy file-format dependency out of every consumer that only needs to write\n * rows, and lets the CLI and the indexer share one loader rather than growing\n * two subtly different CSV encoders.\n */\n\nexport type ArchiveDataset = \"blocks\" | \"transactions\" | \"events\";\n\n/** Row shapes exactly as the archive's Parquet columns are named. */\nexport type ArchiveRow = Record<string, unknown>;\n\n/**\n * CSV encoding for `WITH (FORMAT csv, NULL '\\N')`: every present value is\n * quoted with embedded quotes doubled, so a value that happens to equal the\n * NULL marker cannot be misread as SQL NULL. Only a genuinely absent value is\n * written unquoted as `\\N`.\n */\nexport function csvField(value: string | number | null | undefined): string {\n\tif (value === null || value === undefined) return \"\\\\N\";\n\tconst text = typeof value === \"number\" ? String(value) : value;\n\treturn `\"${text.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction asNumber(value: unknown): number {\n\treturn typeof value === \"bigint\" ? Number(value) : Number(value as number);\n}\n\nfunction asText(value: unknown): string {\n\treturn Buffer.isBuffer(value) ? value.toString(\"utf8\") : String(value);\n}\n\nfunction asOptionalText(value: unknown): string | null {\n\treturn value === null || value === undefined ? null : asText(value);\n}\n\nexport function toCsvLine(dataset: ArchiveDataset, row: ArchiveRow): string {\n\tif (dataset === \"blocks\") {\n\t\treturn [\n\t\t\tcsvField(asNumber(row.height)),\n\t\t\tcsvField(asText(row.hash)),\n\t\t\tcsvField(asText(row.parent_hash)),\n\t\t\tcsvField(asNumber(row.burn_block_height)),\n\t\t\tcsvField(asOptionalText(row.burn_block_hash)),\n\t\t\tcsvField(asOptionalText(row.index_block_hash)),\n\t\t\tcsvField(asNumber(row.timestamp)),\n\t\t].join(\",\");\n\t}\n\tif (dataset === \"transactions\") {\n\t\treturn [\n\t\t\tcsvField(asText(row.tx_id)),\n\t\t\tcsvField(asNumber(row.block_height)),\n\t\t\tcsvField(asNumber(row.tx_index)),\n\t\t\tcsvField(asText(row.type)),\n\t\t\tcsvField(asText(row.sender)),\n\t\t\tcsvField(asText(row.status)),\n\t\t\tcsvField(asOptionalText(row.contract_id)),\n\t\t\tcsvField(asOptionalText(row.function_name)),\n\t\t\tcsvField(asOptionalText(row.function_args_json)),\n\t\t\tcsvField(asOptionalText(row.raw_result)),\n\t\t\tcsvField(asText(row.raw_tx)),\n\t\t].join(\",\");\n\t}\n\treturn [\n\t\tcsvField(asText(row.tx_id)),\n\t\tcsvField(asNumber(row.block_height)),\n\t\tcsvField(asNumber(row.event_index)),\n\t\tcsvField(asText(row.event_type)),\n\t\tcsvField(asText(row.data_json)),\n\t].join(\",\");\n}\n\n/**\n * `canonical` is omitted from the blocks column list: every archived block is\n * canonical by definition, and the schema defaults it to true, so carrying it\n * through every row would be dead weight.\n */\nexport function copyStatement(dataset: ArchiveDataset): string {\n\tif (dataset === \"blocks\") {\n\t\treturn \"COPY blocks (height, hash, parent_hash, burn_block_height, burn_block_hash, index_block_hash, timestamp) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n\t}\n\tif (dataset === \"transactions\") {\n\t\treturn \"COPY transactions (tx_id, block_height, tx_index, type, sender, status, contract_id, function_name, function_args, raw_result, raw_tx) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n\t}\n\treturn \"COPY events (tx_id, block_height, event_index, type, data) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n}\n\n/**\n * Stream rows onto an open COPY writable. The caller owns opening it (drivers\n * differ in how a COPY stream is obtained) and this owns the wire format,\n * backpressure, and failure semantics.\n *\n * On error the stream is destroyed rather than ended, so Postgres aborts the\n * COPY instead of committing a partial one — an interrupted load must leave\n * nothing behind, not half a partition.\n */\nexport async function writeRowsToCopyStream(params: {\n\twritable: Writable;\n\tdataset: ArchiveDataset;\n\trows: AsyncIterable<ArchiveRow>;\n\tonProgress?: (rowsWritten: number) => void;\n\tprogressEvery?: number;\n}): Promise<number> {\n\tconst { writable, dataset, rows } = params;\n\tconst progressEvery = params.progressEvery ?? 500_000;\n\tlet count = 0;\n\ttry {\n\t\tfor await (const row of rows) {\n\t\t\tconst line = `${toCsvLine(dataset, row)}\\n`;\n\t\t\tif (!writable.write(line)) {\n\t\t\t\tawait once(writable, \"drain\");\n\t\t\t}\n\t\t\tcount++;\n\t\t\tif (params.onProgress && count % progressEvery === 0) {\n\t\t\t\tparams.onProgress(count);\n\t\t\t}\n\t\t}\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\twritable.end((error?: Error | null) =>\n\t\t\t\terror ? reject(error) : resolve(),\n\t\t\t);\n\t\t});\n\t} catch (error) {\n\t\twritable.destroy(error instanceof Error ? error : new Error(String(error)));\n\t\tthrow error;\n\t}\n\treturn count;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "2B81DFB47D25D63C64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|