@secondlayer/shared 7.6.0 → 7.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/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/key-registry.d.ts +52 -0
- package/dist/src/archive/key-registry.js +168 -0
- package/dist/src/archive/key-registry.js.map +11 -0
- package/dist/src/archive/progress.d.ts +36 -0
- package/dist/src/archive/progress.js +75 -0
- package/dist/src/archive/progress.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/archive/status.d.ts +92 -0
- package/dist/src/archive/status.js +106 -0
- package/dist/src/archive/status.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 +22 -2
|
@@ -0,0 +1,142 @@
|
|
|
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/range-digest.ts
|
|
18
|
+
import { sql } from "kysely";
|
|
19
|
+
var RANGE_DIGEST_SPEC = "md5:sql-identity-v1";
|
|
20
|
+
function identityExpression(dataset) {
|
|
21
|
+
if (dataset === "blocks") {
|
|
22
|
+
return sql`md5(
|
|
23
|
+
height::text || '|' ||
|
|
24
|
+
hash || '|' ||
|
|
25
|
+
parent_hash || '|' ||
|
|
26
|
+
burn_block_height::text || '|' ||
|
|
27
|
+
coalesce(burn_block_hash, '~') || '|' ||
|
|
28
|
+
coalesce(index_block_hash, '~')
|
|
29
|
+
)`;
|
|
30
|
+
}
|
|
31
|
+
if (dataset === "transactions") {
|
|
32
|
+
return sql`md5(
|
|
33
|
+
tx_id || '|' ||
|
|
34
|
+
block_height::text || '|' ||
|
|
35
|
+
tx_index::text || '|' ||
|
|
36
|
+
type || '|' ||
|
|
37
|
+
sender || '|' ||
|
|
38
|
+
status || '|' ||
|
|
39
|
+
coalesce(contract_id, '~') || '|' ||
|
|
40
|
+
coalesce(function_name, '~')
|
|
41
|
+
)`;
|
|
42
|
+
}
|
|
43
|
+
return sql`md5(
|
|
44
|
+
tx_id || '|' ||
|
|
45
|
+
block_height::text || '|' ||
|
|
46
|
+
event_index::text || '|' ||
|
|
47
|
+
type
|
|
48
|
+
)`;
|
|
49
|
+
}
|
|
50
|
+
function orderExpression(dataset) {
|
|
51
|
+
if (dataset === "blocks")
|
|
52
|
+
return sql`height`;
|
|
53
|
+
if (dataset === "transactions")
|
|
54
|
+
return sql`block_height, tx_index, tx_id`;
|
|
55
|
+
return sql`block_height, event_index, tx_id`;
|
|
56
|
+
}
|
|
57
|
+
function fromExpression(dataset) {
|
|
58
|
+
if (dataset === "blocks") {
|
|
59
|
+
return sql`FROM blocks WHERE canonical = true`;
|
|
60
|
+
}
|
|
61
|
+
const table = dataset === "transactions" ? sql`transactions` : sql`events`;
|
|
62
|
+
return sql`FROM ${table} AS child
|
|
63
|
+
JOIN blocks AS b ON b.height = child.block_height AND b.canonical = true
|
|
64
|
+
WHERE true`;
|
|
65
|
+
}
|
|
66
|
+
function heightColumn(dataset) {
|
|
67
|
+
return dataset === "blocks" ? sql`height` : sql`child.block_height`;
|
|
68
|
+
}
|
|
69
|
+
async function computeRangeDigest(db, dataset, fromBlock, toBlock) {
|
|
70
|
+
const { rows } = await sql`
|
|
71
|
+
SELECT
|
|
72
|
+
md5(string_agg(row_digest, '' ORDER BY ord)) AS digest,
|
|
73
|
+
COUNT(*)::text AS row_count
|
|
74
|
+
FROM (
|
|
75
|
+
SELECT
|
|
76
|
+
${identityExpression(dataset)} AS row_digest,
|
|
77
|
+
row_number() OVER (ORDER BY ${orderExpression(dataset)}) AS ord
|
|
78
|
+
${fromExpression(dataset)}
|
|
79
|
+
AND ${heightColumn(dataset)} >= ${fromBlock}
|
|
80
|
+
AND ${heightColumn(dataset)} <= ${toBlock}
|
|
81
|
+
) AS identity_rows
|
|
82
|
+
`.execute(db);
|
|
83
|
+
const row = rows[0];
|
|
84
|
+
const rowCount = Number(row?.row_count ?? 0);
|
|
85
|
+
return {
|
|
86
|
+
dataset,
|
|
87
|
+
from_block: fromBlock,
|
|
88
|
+
to_block: toBlock,
|
|
89
|
+
row_count: rowCount,
|
|
90
|
+
digest: rowCount === 0 ? null : row?.digest ?? null,
|
|
91
|
+
digest_spec: RANGE_DIGEST_SPEC
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function computeRangeDigests(db, fromBlock, toBlock, datasets = [
|
|
95
|
+
"blocks",
|
|
96
|
+
"transactions",
|
|
97
|
+
"events"
|
|
98
|
+
]) {
|
|
99
|
+
const digests = [];
|
|
100
|
+
for (const dataset of datasets) {
|
|
101
|
+
digests.push(await computeRangeDigest(db, dataset, fromBlock, toBlock));
|
|
102
|
+
}
|
|
103
|
+
return digests;
|
|
104
|
+
}
|
|
105
|
+
function compareRangeDigests(local, reference) {
|
|
106
|
+
const localByKey = new Map(local.map((d) => [`${d.dataset}:${d.from_block}-${d.to_block}`, d]));
|
|
107
|
+
return reference.map((expected) => {
|
|
108
|
+
const key = `${expected.dataset}:${expected.from_block}-${expected.to_block}`;
|
|
109
|
+
const actual = localByKey.get(key);
|
|
110
|
+
const base = {
|
|
111
|
+
dataset: expected.dataset,
|
|
112
|
+
from_block: expected.from_block,
|
|
113
|
+
to_block: expected.to_block,
|
|
114
|
+
expected_digest: expected.digest,
|
|
115
|
+
expected_rows: expected.row_count
|
|
116
|
+
};
|
|
117
|
+
if (!actual) {
|
|
118
|
+
return {
|
|
119
|
+
...base,
|
|
120
|
+
status: "missing-locally",
|
|
121
|
+
actual_digest: null,
|
|
122
|
+
actual_rows: 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const status = actual.row_count !== expected.row_count ? "count-mismatch" : actual.digest === expected.digest ? "match" : "digest-mismatch";
|
|
126
|
+
return {
|
|
127
|
+
...base,
|
|
128
|
+
status,
|
|
129
|
+
actual_digest: actual.digest,
|
|
130
|
+
actual_rows: actual.row_count
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
computeRangeDigests,
|
|
136
|
+
computeRangeDigest,
|
|
137
|
+
compareRangeDigests,
|
|
138
|
+
RANGE_DIGEST_SPEC
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
//# debugId=D76FA402AE6DA40A64756E2164756E21
|
|
142
|
+
//# sourceMappingURL=range-digest.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/archive/range-digest.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { sql } from \"kysely\";\nimport type { Kysely } from \"kysely\";\nimport type { Database } from \"../db/schema.ts\";\n\n/**\n * Cheap, SQL-computed digests over a height range — the basis of a verify that\n * runs in seconds instead of hours.\n *\n * The archive's per-object `sha256:parquet-object` digests can only be checked\n * by regenerating the Parquet locally, which costs a full export. That is a\n * reasonable paranoid check and a terrible first-run experience: nobody will\n * spend two hours to discover whether they are broken. These digests are\n * computed by the database itself, over the columns that actually carry chain\n * identity, so an operator gets an answer before they lose interest.\n *\n * What this catches is not a subset chosen for convenience — it is precisely\n * the defect class seen in production: missing heights, a canonical child whose\n * `parent_hash` names a block we no longer hold, duplicate canonical rows at one\n * height, and any altered block/transaction identity. What it deliberately does\n * NOT catch is a difference in bulky payload columns (`raw_tx`, event `data`)\n * that leaves identity intact; `--deep` re-export exists for that.\n *\n * Determinism rules, so two independent databases agree:\n * - Aggregate over an explicit ORDER BY, never table order.\n * - Hash each row to a fixed-width digest FIRST, then aggregate those, so the\n * intermediate string is 32 bytes/row rather than the whole row.\n * - Normalize NULL to a sentinel that cannot collide with a real value.\n * - Never include a column whose representation is environment-dependent\n * (timestamps with timezone rendering, generated ids, `created_at`).\n */\n\nexport const RANGE_DIGEST_SPEC = \"md5:sql-identity-v1\" as const;\n\nexport type RangeDigestDataset = \"blocks\" | \"transactions\" | \"events\";\n\nexport type RangeDigest = {\n\tdataset: RangeDigestDataset;\n\tfrom_block: number;\n\tto_block: number;\n\trow_count: number;\n\t/** null when the range holds no rows — an empty range has no digest, and\n\t * that is materially different from \"a digest that happens to be of zero\n\t * rows\", which would compare equal across datasets. */\n\tdigest: string | null;\n\tdigest_spec: typeof RANGE_DIGEST_SPEC;\n};\n\n/**\n * Identity columns per dataset. `blocks` carries the whole corruption surface\n * we care about, which is why quick mode leans on it.\n */\nfunction identityExpression(dataset: RangeDigestDataset) {\n\tif (dataset === \"blocks\") {\n\t\treturn sql`md5(\n\t\t\theight::text || '|' ||\n\t\t\thash || '|' ||\n\t\t\tparent_hash || '|' ||\n\t\t\tburn_block_height::text || '|' ||\n\t\t\tcoalesce(burn_block_hash, '~') || '|' ||\n\t\t\tcoalesce(index_block_hash, '~')\n\t\t)`;\n\t}\n\tif (dataset === \"transactions\") {\n\t\treturn sql`md5(\n\t\t\ttx_id || '|' ||\n\t\t\tblock_height::text || '|' ||\n\t\t\ttx_index::text || '|' ||\n\t\t\ttype || '|' ||\n\t\t\tsender || '|' ||\n\t\t\tstatus || '|' ||\n\t\t\tcoalesce(contract_id, '~') || '|' ||\n\t\t\tcoalesce(function_name, '~')\n\t\t)`;\n\t}\n\treturn sql`md5(\n\t\ttx_id || '|' ||\n\t\tblock_height::text || '|' ||\n\t\tevent_index::text || '|' ||\n\t\ttype\n\t)`;\n}\n\nfunction orderExpression(dataset: RangeDigestDataset) {\n\tif (dataset === \"blocks\") return sql`height`;\n\tif (dataset === \"transactions\") return sql`block_height, tx_index, tx_id`;\n\treturn sql`block_height, event_index, tx_id`;\n}\n\n/**\n * Rows are restricted to CANONICAL blocks in every dataset — a non-canonical\n * row at a height must not change the digest, or a database that merely retains\n * more reorg history than another would report as diverging.\n */\nfunction fromExpression(dataset: RangeDigestDataset) {\n\tif (dataset === \"blocks\") {\n\t\treturn sql`FROM blocks WHERE canonical = true`;\n\t}\n\tconst table = dataset === \"transactions\" ? sql`transactions` : sql`events`;\n\treturn sql`FROM ${table} AS child\n\t\tJOIN blocks AS b ON b.height = child.block_height AND b.canonical = true\n\t\tWHERE true`;\n}\n\nfunction heightColumn(dataset: RangeDigestDataset) {\n\treturn dataset === \"blocks\" ? sql`height` : sql`child.block_height`;\n}\n\nexport async function computeRangeDigest(\n\tdb: Kysely<Database>,\n\tdataset: RangeDigestDataset,\n\tfromBlock: number,\n\ttoBlock: number,\n): Promise<RangeDigest> {\n\tconst { rows } = await sql<{ digest: string | null; row_count: string }>`\n\t\tSELECT\n\t\t\tmd5(string_agg(row_digest, '' ORDER BY ord)) AS digest,\n\t\t\tCOUNT(*)::text AS row_count\n\t\tFROM (\n\t\t\tSELECT\n\t\t\t\t${identityExpression(dataset)} AS row_digest,\n\t\t\t\trow_number() OVER (ORDER BY ${orderExpression(dataset)}) AS ord\n\t\t\t${fromExpression(dataset)}\n\t\t\t\tAND ${heightColumn(dataset)} >= ${fromBlock}\n\t\t\t\tAND ${heightColumn(dataset)} <= ${toBlock}\n\t\t) AS identity_rows\n\t`.execute(db);\n\n\tconst row = rows[0];\n\tconst rowCount = Number(row?.row_count ?? 0);\n\treturn {\n\t\tdataset,\n\t\tfrom_block: fromBlock,\n\t\tto_block: toBlock,\n\t\trow_count: rowCount,\n\t\tdigest: rowCount === 0 ? null : (row?.digest ?? null),\n\t\tdigest_spec: RANGE_DIGEST_SPEC,\n\t};\n}\n\nexport async function computeRangeDigests(\n\tdb: Kysely<Database>,\n\tfromBlock: number,\n\ttoBlock: number,\n\tdatasets: readonly RangeDigestDataset[] = [\n\t\t\"blocks\",\n\t\t\"transactions\",\n\t\t\"events\",\n\t],\n): Promise<RangeDigest[]> {\n\tconst digests: RangeDigest[] = [];\n\tfor (const dataset of datasets) {\n\t\tdigests.push(await computeRangeDigest(db, dataset, fromBlock, toBlock));\n\t}\n\treturn digests;\n}\n\nexport type RangeComparison = {\n\tdataset: RangeDigestDataset;\n\tfrom_block: number;\n\tto_block: number;\n\tstatus: \"match\" | \"digest-mismatch\" | \"count-mismatch\" | \"missing-locally\";\n\texpected_digest: string | null;\n\tactual_digest: string | null;\n\texpected_rows: number;\n\tactual_rows: number;\n};\n\n/**\n * Compare locally computed digests against a reference set.\n *\n * Row count is reported separately from digest because the two failures mean\n * different things to an operator: a count difference is missing or extra data\n * (usually a gap or an un-swept reorg), while an equal count with a different\n * digest means the same number of rows carry different identities — the\n * fork-point corruption signature.\n */\nexport function compareRangeDigests(\n\tlocal: readonly RangeDigest[],\n\treference: readonly RangeDigest[],\n): RangeComparison[] {\n\tconst localByKey = new Map(\n\t\tlocal.map((d) => [`${d.dataset}:${d.from_block}-${d.to_block}`, d]),\n\t);\n\n\treturn reference.map((expected) => {\n\t\tconst key = `${expected.dataset}:${expected.from_block}-${expected.to_block}`;\n\t\tconst actual = localByKey.get(key);\n\t\tconst base = {\n\t\t\tdataset: expected.dataset,\n\t\t\tfrom_block: expected.from_block,\n\t\t\tto_block: expected.to_block,\n\t\t\texpected_digest: expected.digest,\n\t\t\texpected_rows: expected.row_count,\n\t\t};\n\t\tif (!actual) {\n\t\t\treturn {\n\t\t\t\t...base,\n\t\t\t\tstatus: \"missing-locally\" as const,\n\t\t\t\tactual_digest: null,\n\t\t\t\tactual_rows: 0,\n\t\t\t};\n\t\t}\n\t\tconst status =\n\t\t\tactual.row_count !== expected.row_count\n\t\t\t\t? (\"count-mismatch\" as const)\n\t\t\t\t: actual.digest === expected.digest\n\t\t\t\t\t? (\"match\" as const)\n\t\t\t\t\t: (\"digest-mismatch\" as const);\n\t\treturn {\n\t\t\t...base,\n\t\t\tstatus,\n\t\t\tactual_digest: actual.digest,\n\t\t\tactual_rows: actual.row_count,\n\t\t};\n\t});\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "D76FA402AE6DA40A64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `status.json` — the archive's operational truth, published short-cache from
|
|
3
|
+
* verified state only.
|
|
4
|
+
*
|
|
5
|
+
* Everything else in the tree is immutable and signed, which makes it durable
|
|
6
|
+
* but silent: a consumer holding a valid manifest cannot tell whether it is
|
|
7
|
+
* current, three days behind, or the last thing published before an incident.
|
|
8
|
+
* This object answers that, and it is the only object allowed to say something
|
|
9
|
+
* unflattering.
|
|
10
|
+
*
|
|
11
|
+
* The distinction this file exists to protect:
|
|
12
|
+
*
|
|
13
|
+
* lagging → the archive trails the chain tip because it only publishes below
|
|
14
|
+
* the finality boundary. This is CORRECT and permanent. An archive
|
|
15
|
+
* that reported `stale` for its own finality lag would be crying
|
|
16
|
+
* wolf forever, and consumers would learn to ignore it.
|
|
17
|
+
* stale → publishing stopped. The lag exceeds what the finality rule can
|
|
18
|
+
* explain, so something is broken.
|
|
19
|
+
*
|
|
20
|
+
* `state` is derived from measurements, never asserted by the publisher, and
|
|
21
|
+
* every unhealthy state is reachable — an archive that can only report health
|
|
22
|
+
* is decoration.
|
|
23
|
+
*/
|
|
24
|
+
declare const ARCHIVE_STATUS_SCHEMA_VERSION = 1;
|
|
25
|
+
type ArchiveState = "fresh" | "lagging" | "stale" | "gap" | "failed-audit" | "source-unavailable";
|
|
26
|
+
type ArchiveStatus = {
|
|
27
|
+
schema_version: typeof ARCHIVE_STATUS_SCHEMA_VERSION
|
|
28
|
+
network: string
|
|
29
|
+
state: ArchiveState
|
|
30
|
+
/** Human-facing one-liner explaining the state, including WHY. */
|
|
31
|
+
detail: string
|
|
32
|
+
generated_at: string
|
|
33
|
+
archive: {
|
|
34
|
+
snapshot_digest: string | null
|
|
35
|
+
coverage_to_block: number | null
|
|
36
|
+
promoted_at: string | null
|
|
37
|
+
signing_key_id: string | null
|
|
38
|
+
}
|
|
39
|
+
source: {
|
|
40
|
+
/** Chain tip as the publisher last observed it. Null when unreachable. */
|
|
41
|
+
tip_height: number | null
|
|
42
|
+
/** Highest height eligible to publish under the finality rule. */
|
|
43
|
+
finalized_height: number | null
|
|
44
|
+
}
|
|
45
|
+
lag: {
|
|
46
|
+
/** Behind the FINALIZED height — the number that indicates a problem. */
|
|
47
|
+
blocks_behind_finalized: number | null
|
|
48
|
+
/** Behind the raw chain tip — expected to be non-zero forever. */
|
|
49
|
+
blocks_behind_tip: number | null
|
|
50
|
+
seconds_since_promotion: number | null
|
|
51
|
+
}
|
|
52
|
+
audit: {
|
|
53
|
+
complete: boolean
|
|
54
|
+
checked_at: string
|
|
55
|
+
} | null
|
|
56
|
+
signature?: string
|
|
57
|
+
key_id?: string
|
|
58
|
+
};
|
|
59
|
+
type StatusInputs = {
|
|
60
|
+
network: string
|
|
61
|
+
snapshotDigest: string | null
|
|
62
|
+
coverageToBlock: number | null
|
|
63
|
+
promotedAt: string | null
|
|
64
|
+
signingKeyId: string | null
|
|
65
|
+
/** Null when the source could not be reached. */
|
|
66
|
+
sourceTipHeight: number | null
|
|
67
|
+
finalizedHeight: number | null
|
|
68
|
+
audit: {
|
|
69
|
+
complete: boolean
|
|
70
|
+
checkedAt: string
|
|
71
|
+
} | null
|
|
72
|
+
now: Date
|
|
73
|
+
/**
|
|
74
|
+
* How far behind the FINALIZED height the archive may fall before it is
|
|
75
|
+
* stale rather than merely lagging. Publishing one partition covers 50k
|
|
76
|
+
* blocks, so a healthy archive sits well inside this.
|
|
77
|
+
*/
|
|
78
|
+
maxBlocksBehindFinalized?: number
|
|
79
|
+
/** How long since the last promotion before staleness, regardless of height. */
|
|
80
|
+
maxSecondsSincePromotion?: number
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Derive status from measurements. Pure, so every state below is reachable in
|
|
84
|
+
* a test rather than only in production at 3am.
|
|
85
|
+
*
|
|
86
|
+
* Order matters: a failing audit outranks freshness, because an archive that
|
|
87
|
+
* is current and wrong is worse than one that is behind and correct.
|
|
88
|
+
*/
|
|
89
|
+
declare function deriveArchiveStatus(inputs: StatusInputs): ArchiveStatus;
|
|
90
|
+
/** States a consumer should refuse to restore from. */
|
|
91
|
+
declare function isRestorable(state: ArchiveState): boolean;
|
|
92
|
+
export { isRestorable, deriveArchiveStatus, StatusInputs, ArchiveStatus, ArchiveState, ARCHIVE_STATUS_SCHEMA_VERSION };
|
|
@@ -0,0 +1,106 @@
|
|
|
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/status.ts
|
|
18
|
+
var ARCHIVE_STATUS_SCHEMA_VERSION = 1;
|
|
19
|
+
var DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED = 60000;
|
|
20
|
+
var DEFAULT_MAX_SECONDS_SINCE_PROMOTION = 48 * 3600;
|
|
21
|
+
function deriveArchiveStatus(inputs) {
|
|
22
|
+
const maxBehind = inputs.maxBlocksBehindFinalized ?? DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED;
|
|
23
|
+
const maxAge = inputs.maxSecondsSincePromotion ?? DEFAULT_MAX_SECONDS_SINCE_PROMOTION;
|
|
24
|
+
const secondsSincePromotion = inputs.promotedAt ? Math.max(0, Math.round((inputs.now.getTime() - Date.parse(inputs.promotedAt)) / 1000)) : null;
|
|
25
|
+
const behindFinalized = inputs.finalizedHeight !== null && inputs.coverageToBlock !== null ? Math.max(0, inputs.finalizedHeight - inputs.coverageToBlock) : null;
|
|
26
|
+
const behindTip = inputs.sourceTipHeight !== null && inputs.coverageToBlock !== null ? Math.max(0, inputs.sourceTipHeight - inputs.coverageToBlock) : null;
|
|
27
|
+
const base = {
|
|
28
|
+
schema_version: ARCHIVE_STATUS_SCHEMA_VERSION,
|
|
29
|
+
network: inputs.network,
|
|
30
|
+
generated_at: inputs.now.toISOString(),
|
|
31
|
+
archive: {
|
|
32
|
+
snapshot_digest: inputs.snapshotDigest,
|
|
33
|
+
coverage_to_block: inputs.coverageToBlock,
|
|
34
|
+
promoted_at: inputs.promotedAt,
|
|
35
|
+
signing_key_id: inputs.signingKeyId
|
|
36
|
+
},
|
|
37
|
+
source: {
|
|
38
|
+
tip_height: inputs.sourceTipHeight,
|
|
39
|
+
finalized_height: inputs.finalizedHeight
|
|
40
|
+
},
|
|
41
|
+
lag: {
|
|
42
|
+
blocks_behind_finalized: behindFinalized,
|
|
43
|
+
blocks_behind_tip: behindTip,
|
|
44
|
+
seconds_since_promotion: secondsSincePromotion
|
|
45
|
+
},
|
|
46
|
+
audit: inputs.audit ? { complete: inputs.audit.complete, checked_at: inputs.audit.checkedAt } : null
|
|
47
|
+
};
|
|
48
|
+
if (inputs.audit && !inputs.audit.complete) {
|
|
49
|
+
return {
|
|
50
|
+
...base,
|
|
51
|
+
state: "failed-audit",
|
|
52
|
+
detail: `the canonical audit did not pass at ${inputs.audit.checkedAt}; this archive is not safe to restore from`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (!inputs.snapshotDigest || inputs.coverageToBlock === null) {
|
|
56
|
+
return {
|
|
57
|
+
...base,
|
|
58
|
+
state: "gap",
|
|
59
|
+
detail: "no snapshot is currently promoted"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (inputs.sourceTipHeight === null || inputs.finalizedHeight === null) {
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
state: "source-unavailable",
|
|
66
|
+
detail: "the chain source could not be reached, so freshness cannot be determined"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (secondsSincePromotion !== null && secondsSincePromotion > maxAge) {
|
|
70
|
+
return {
|
|
71
|
+
...base,
|
|
72
|
+
state: "stale",
|
|
73
|
+
detail: `last promotion was ${Math.round(secondsSincePromotion / 3600)}h ago, beyond the ${Math.round(maxAge / 3600)}h freshness objective`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (behindFinalized !== null && behindFinalized > maxBehind) {
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
state: "stale",
|
|
80
|
+
detail: `${behindFinalized} blocks behind the finalized height, beyond the ${maxBehind} threshold`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (behindTip !== null && behindTip > 0) {
|
|
84
|
+
return {
|
|
85
|
+
...base,
|
|
86
|
+
state: "lagging",
|
|
87
|
+
detail: `${behindTip} blocks behind the chain tip, which is expected: only heights below the finality boundary are published`
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
...base,
|
|
92
|
+
state: "fresh",
|
|
93
|
+
detail: "published through the finalized height"
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function isRestorable(state) {
|
|
97
|
+
return state === "fresh" || state === "lagging";
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
isRestorable,
|
|
101
|
+
deriveArchiveStatus,
|
|
102
|
+
ARCHIVE_STATUS_SCHEMA_VERSION
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
//# debugId=D9102A12541A66EA64756E2164756E21
|
|
106
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/archive/status.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * `status.json` — the archive's operational truth, published short-cache from\n * verified state only.\n *\n * Everything else in the tree is immutable and signed, which makes it durable\n * but silent: a consumer holding a valid manifest cannot tell whether it is\n * current, three days behind, or the last thing published before an incident.\n * This object answers that, and it is the only object allowed to say something\n * unflattering.\n *\n * The distinction this file exists to protect:\n *\n * lagging → the archive trails the chain tip because it only publishes below\n * the finality boundary. This is CORRECT and permanent. An archive\n * that reported `stale` for its own finality lag would be crying\n * wolf forever, and consumers would learn to ignore it.\n * stale → publishing stopped. The lag exceeds what the finality rule can\n * explain, so something is broken.\n *\n * `state` is derived from measurements, never asserted by the publisher, and\n * every unhealthy state is reachable — an archive that can only report health\n * is decoration.\n */\n\nexport const ARCHIVE_STATUS_SCHEMA_VERSION = 1;\n\nexport type ArchiveState =\n\t| \"fresh\"\n\t| \"lagging\"\n\t| \"stale\"\n\t| \"gap\"\n\t| \"failed-audit\"\n\t| \"source-unavailable\";\n\nexport type ArchiveStatus = {\n\tschema_version: typeof ARCHIVE_STATUS_SCHEMA_VERSION;\n\tnetwork: string;\n\tstate: ArchiveState;\n\t/** Human-facing one-liner explaining the state, including WHY. */\n\tdetail: string;\n\tgenerated_at: string;\n\tarchive: {\n\t\tsnapshot_digest: string | null;\n\t\tcoverage_to_block: number | null;\n\t\tpromoted_at: string | null;\n\t\tsigning_key_id: string | null;\n\t};\n\tsource: {\n\t\t/** Chain tip as the publisher last observed it. Null when unreachable. */\n\t\ttip_height: number | null;\n\t\t/** Highest height eligible to publish under the finality rule. */\n\t\tfinalized_height: number | null;\n\t};\n\tlag: {\n\t\t/** Behind the FINALIZED height — the number that indicates a problem. */\n\t\tblocks_behind_finalized: number | null;\n\t\t/** Behind the raw chain tip — expected to be non-zero forever. */\n\t\tblocks_behind_tip: number | null;\n\t\tseconds_since_promotion: number | null;\n\t};\n\taudit: {\n\t\tcomplete: boolean;\n\t\tchecked_at: string;\n\t} | null;\n\tsignature?: string;\n\tkey_id?: string;\n};\n\nexport type StatusInputs = {\n\tnetwork: string;\n\tsnapshotDigest: string | null;\n\tcoverageToBlock: number | null;\n\tpromotedAt: string | null;\n\tsigningKeyId: string | null;\n\t/** Null when the source could not be reached. */\n\tsourceTipHeight: number | null;\n\tfinalizedHeight: number | null;\n\taudit: { complete: boolean; checkedAt: string } | null;\n\tnow: Date;\n\t/**\n\t * How far behind the FINALIZED height the archive may fall before it is\n\t * stale rather than merely lagging. Publishing one partition covers 50k\n\t * blocks, so a healthy archive sits well inside this.\n\t */\n\tmaxBlocksBehindFinalized?: number;\n\t/** How long since the last promotion before staleness, regardless of height. */\n\tmaxSecondsSincePromotion?: number;\n};\n\nconst DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED = 60_000;\nconst DEFAULT_MAX_SECONDS_SINCE_PROMOTION = 48 * 3_600;\n\n/**\n * Derive status from measurements. Pure, so every state below is reachable in\n * a test rather than only in production at 3am.\n *\n * Order matters: a failing audit outranks freshness, because an archive that\n * is current and wrong is worse than one that is behind and correct.\n */\nexport function deriveArchiveStatus(inputs: StatusInputs): ArchiveStatus {\n\tconst maxBehind =\n\t\tinputs.maxBlocksBehindFinalized ?? DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED;\n\tconst maxAge =\n\t\tinputs.maxSecondsSincePromotion ?? DEFAULT_MAX_SECONDS_SINCE_PROMOTION;\n\n\tconst secondsSincePromotion = inputs.promotedAt\n\t\t? Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.round(\n\t\t\t\t\t(inputs.now.getTime() - Date.parse(inputs.promotedAt)) / 1000,\n\t\t\t\t),\n\t\t\t)\n\t\t: null;\n\n\tconst behindFinalized =\n\t\tinputs.finalizedHeight !== null && inputs.coverageToBlock !== null\n\t\t\t? Math.max(0, inputs.finalizedHeight - inputs.coverageToBlock)\n\t\t\t: null;\n\tconst behindTip =\n\t\tinputs.sourceTipHeight !== null && inputs.coverageToBlock !== null\n\t\t\t? Math.max(0, inputs.sourceTipHeight - inputs.coverageToBlock)\n\t\t\t: null;\n\n\tconst base = {\n\t\tschema_version:\n\t\t\tARCHIVE_STATUS_SCHEMA_VERSION as typeof ARCHIVE_STATUS_SCHEMA_VERSION,\n\t\tnetwork: inputs.network,\n\t\tgenerated_at: inputs.now.toISOString(),\n\t\tarchive: {\n\t\t\tsnapshot_digest: inputs.snapshotDigest,\n\t\t\tcoverage_to_block: inputs.coverageToBlock,\n\t\t\tpromoted_at: inputs.promotedAt,\n\t\t\tsigning_key_id: inputs.signingKeyId,\n\t\t},\n\t\tsource: {\n\t\t\ttip_height: inputs.sourceTipHeight,\n\t\t\tfinalized_height: inputs.finalizedHeight,\n\t\t},\n\t\tlag: {\n\t\t\tblocks_behind_finalized: behindFinalized,\n\t\t\tblocks_behind_tip: behindTip,\n\t\t\tseconds_since_promotion: secondsSincePromotion,\n\t\t},\n\t\taudit: inputs.audit\n\t\t\t? { complete: inputs.audit.complete, checked_at: inputs.audit.checkedAt }\n\t\t\t: null,\n\t};\n\n\t// A failed audit is the loudest signal: current-and-wrong beats behind-and-right.\n\tif (inputs.audit && !inputs.audit.complete) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"failed-audit\",\n\t\t\tdetail: `the canonical audit did not pass at ${inputs.audit.checkedAt}; this archive is not safe to restore from`,\n\t\t};\n\t}\n\n\tif (!inputs.snapshotDigest || inputs.coverageToBlock === null) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"gap\",\n\t\t\tdetail: \"no snapshot is currently promoted\",\n\t\t};\n\t}\n\n\t// Source unreachable: report the gap in KNOWLEDGE rather than guessing. The\n\t// archive may be perfectly healthy; we cannot currently say so.\n\tif (inputs.sourceTipHeight === null || inputs.finalizedHeight === null) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"source-unavailable\",\n\t\t\tdetail:\n\t\t\t\t\"the chain source could not be reached, so freshness cannot be determined\",\n\t\t};\n\t}\n\n\tif (secondsSincePromotion !== null && secondsSincePromotion > maxAge) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"stale\",\n\t\t\tdetail: `last promotion was ${Math.round(secondsSincePromotion / 3_600)}h ago, beyond the ${Math.round(maxAge / 3_600)}h freshness objective`,\n\t\t};\n\t}\n\n\tif (behindFinalized !== null && behindFinalized > maxBehind) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"stale\",\n\t\t\tdetail: `${behindFinalized} blocks behind the finalized height, beyond the ${maxBehind} threshold`,\n\t\t};\n\t}\n\n\t// Behind the tip but within the finality boundary — correct, permanent, and\n\t// explicitly NOT staleness.\n\tif (behindTip !== null && behindTip > 0) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"lagging\",\n\t\t\tdetail: `${behindTip} blocks behind the chain tip, which is expected: only heights below the finality boundary are published`,\n\t\t};\n\t}\n\n\treturn {\n\t\t...base,\n\t\tstate: \"fresh\",\n\t\tdetail: \"published through the finalized height\",\n\t};\n}\n\n/** States a consumer should refuse to restore from. */\nexport function isRestorable(state: ArchiveState): boolean {\n\treturn state === \"fresh\" || state === \"lagging\";\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAwBO,IAAM,gCAAgC;AAiE7C,IAAM,sCAAsC;AAC5C,IAAM,sCAAsC,KAAK;AAS1C,SAAS,mBAAmB,CAAC,QAAqC;AAAA,EACxE,MAAM,YACL,OAAO,4BAA4B;AAAA,EACpC,MAAM,SACL,OAAO,4BAA4B;AAAA,EAEpC,MAAM,wBAAwB,OAAO,aAClC,KAAK,IACL,GACA,KAAK,OACH,OAAO,IAAI,QAAQ,IAAI,KAAK,MAAM,OAAO,UAAU,KAAK,IAC1D,CACD,IACC;AAAA,EAEH,MAAM,kBACL,OAAO,oBAAoB,QAAQ,OAAO,oBAAoB,OAC3D,KAAK,IAAI,GAAG,OAAO,kBAAkB,OAAO,eAAe,IAC3D;AAAA,EACJ,MAAM,YACL,OAAO,oBAAoB,QAAQ,OAAO,oBAAoB,OAC3D,KAAK,IAAI,GAAG,OAAO,kBAAkB,OAAO,eAAe,IAC3D;AAAA,EAEJ,MAAM,OAAO;AAAA,IACZ,gBACC;AAAA,IACD,SAAS,OAAO;AAAA,IAChB,cAAc,OAAO,IAAI,YAAY;AAAA,IACrC,SAAS;AAAA,MACR,iBAAiB,OAAO;AAAA,MACxB,mBAAmB,OAAO;AAAA,MAC1B,aAAa,OAAO;AAAA,MACpB,gBAAgB,OAAO;AAAA,IACxB;AAAA,IACA,QAAQ;AAAA,MACP,YAAY,OAAO;AAAA,MACnB,kBAAkB,OAAO;AAAA,IAC1B;AAAA,IACA,KAAK;AAAA,MACJ,yBAAyB;AAAA,MACzB,mBAAmB;AAAA,MACnB,yBAAyB;AAAA,IAC1B;AAAA,IACA,OAAO,OAAO,QACX,EAAE,UAAU,OAAO,MAAM,UAAU,YAAY,OAAO,MAAM,UAAU,IACtE;AAAA,EACJ;AAAA,EAGA,IAAI,OAAO,SAAS,CAAC,OAAO,MAAM,UAAU;AAAA,IAC3C,OAAO;AAAA,SACH;AAAA,MACH,OAAO;AAAA,MACP,QAAQ,uCAAuC,OAAO,MAAM;AAAA,IAC7D;AAAA,EACD;AAAA,EAEA,IAAI,CAAC,OAAO,kBAAkB,OAAO,oBAAoB,MAAM;AAAA,IAC9D,OAAO;AAAA,SACH;AAAA,MACH,OAAO;AAAA,MACP,QAAQ;AAAA,IACT;AAAA,EACD;AAAA,EAIA,IAAI,OAAO,oBAAoB,QAAQ,OAAO,oBAAoB,MAAM;AAAA,IACvE,OAAO;AAAA,SACH;AAAA,MACH,OAAO;AAAA,MACP,QACC;AAAA,IACF;AAAA,EACD;AAAA,EAEA,IAAI,0BAA0B,QAAQ,wBAAwB,QAAQ;AAAA,IACrE,OAAO;AAAA,SACH;AAAA,MACH,OAAO;AAAA,MACP,QAAQ,sBAAsB,KAAK,MAAM,wBAAwB,IAAK,sBAAsB,KAAK,MAAM,SAAS,IAAK;AAAA,IACtH;AAAA,EACD;AAAA,EAEA,IAAI,oBAAoB,QAAQ,kBAAkB,WAAW;AAAA,IAC5D,OAAO;AAAA,SACH;AAAA,MACH,OAAO;AAAA,MACP,QAAQ,GAAG,kEAAkE;AAAA,IAC9E;AAAA,EACD;AAAA,EAIA,IAAI,cAAc,QAAQ,YAAY,GAAG;AAAA,IACxC,OAAO;AAAA,SACH;AAAA,MACH,OAAO;AAAA,MACP,QAAQ,GAAG;AAAA,IACZ;AAAA,EACD;AAAA,EAEA,OAAO;AAAA,OACH;AAAA,IACH,OAAO;AAAA,IACP,QAAQ;AAAA,EACT;AAAA;AAIM,SAAS,YAAY,CAAC,OAA8B;AAAA,EAC1D,OAAO,UAAU,WAAW,UAAU;AAAA;",
|
|
8
|
+
"debugId": "D9102A12541A66EA64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/src/db/index.d.ts
CHANGED
|
@@ -715,6 +715,30 @@ interface PendingForkBlocksTable {
|
|
|
715
715
|
payload: ColumnType<unknown, unknown, unknown>;
|
|
716
716
|
received_at: Generated<Date>;
|
|
717
717
|
}
|
|
718
|
+
type ObserverJournalStatus = "received" | "processed" | "failed";
|
|
719
|
+
/**
|
|
720
|
+
* Exact observer callback receipt. `raw_body` is immutable evidence; the
|
|
721
|
+
* processing columns are updated only after the current derived-state write
|
|
722
|
+
* succeeds or fails.
|
|
723
|
+
*/
|
|
724
|
+
interface ObserverJournalTable {
|
|
725
|
+
sequence: Generated<string>;
|
|
726
|
+
network: string;
|
|
727
|
+
path: string;
|
|
728
|
+
source: string | null;
|
|
729
|
+
received_at: Generated<Date>;
|
|
730
|
+
raw_body: ColumnType<Buffer, Buffer, Buffer>;
|
|
731
|
+
raw_body_sha256: string;
|
|
732
|
+
status: ColumnType<ObserverJournalStatus, ObserverJournalStatus | undefined, ObserverJournalStatus>;
|
|
733
|
+
semantic_sha256: string | null;
|
|
734
|
+
block_height: number | null;
|
|
735
|
+
block_hash: string | null;
|
|
736
|
+
burn_block_height: number | null;
|
|
737
|
+
burn_block_hash: string | null;
|
|
738
|
+
result: unknown | null;
|
|
739
|
+
error: string | null;
|
|
740
|
+
processed_at: Date | null;
|
|
741
|
+
}
|
|
718
742
|
interface Database {
|
|
719
743
|
blocks: BlocksTable;
|
|
720
744
|
transactions: TransactionsTable;
|
|
@@ -778,6 +802,7 @@ interface Database {
|
|
|
778
802
|
x402_balances: X402BalancesTable;
|
|
779
803
|
chain_read_cache: ChainReadCacheTable;
|
|
780
804
|
pending_fork_blocks: PendingForkBlocksTable;
|
|
805
|
+
observer_journal: ObserverJournalTable;
|
|
781
806
|
}
|
|
782
807
|
/** Prepaid x402 credit — one running USD-micros balance per payer principal. */
|
|
783
808
|
interface X402BalancesTable {
|
|
@@ -1965,6 +1990,7 @@ declare const TABLE_TO_DB: {
|
|
|
1965
1990
|
chain_reorgs: string
|
|
1966
1991
|
chain_read_cache: string
|
|
1967
1992
|
pending_fork_blocks: string
|
|
1993
|
+
observer_journal: string
|
|
1968
1994
|
decoded_events: string
|
|
1969
1995
|
decoder_checkpoints: string
|
|
1970
1996
|
pox4_calls: string
|
|
@@ -2083,4 +2109,4 @@ declare function getRawClient(role?: "source" | "target"): ReturnType<typeof pos
|
|
|
2083
2109
|
declare function getRawClientFor(url: string): ReturnType<typeof postgres>;
|
|
2084
2110
|
/** Close all DB connection pools. Call in CLI commands to allow process exit. */
|
|
2085
2111
|
declare function closeDb(): Promise<void>;
|
|
2086
|
-
export { sql, setMigrationRole, parseJsonb, onControlPlane, onChainPlane, jsonb, getTargetDb, getSourceDb, getRawClientFor, getRawClient, getMigrationRole, getDbSplitStatus, getDb, closeDb, assertDbSplit, X402PaymentsTable, X402BalancesTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateContract, UpdateBlock, UpdateApiKey, UpdateAccountSpendCap, UpdateAccountCredits, TriggerEvaluatorStateTable, TriggerEvaluatorState, TransactionsTable, TransactionsArchiveTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantStatus, TenantComputeAddonsTable, TenantComputeAddon, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, TABLE_TO_DB, SubscriptionsTable, SubscriptionStatus, SubscriptionRuntime, SubscriptionOutboxTable, SubscriptionOutbox, SubscriptionKind, SubscriptionFormat, SubscriptionDelivery, SubscriptionDeliveriesTable, Subscription, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ServiceHeartbeatsTable, SbtcTokenEventsTable, SbtcTokenEventType, SbtcSupplySnapshotsTable, SbtcSettlementsTable, SbtcEventsTable, SbtcEventTopic, SOURCE_READ_TYPES, SOURCE_READ_PKS, SOURCE_READ_COLUMNS, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, ProcessedStripeEventsTable, Pox5EventsTable, Pox5EventTopic, Pox4SignersDailyTable, Pox4FunctionName, Pox4CyclesDailyTable, Pox4CallsTable, PendingForkBlocksTable, OutboxStatus, NumericAsText, MigrationRole, MempoolTransactionsTable, MempoolTransaction, MagicLinksTable, MagicLink, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMempoolTransaction, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertContract, InsertClaimToken, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountCredits, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, IndexPrimaryKey, IndexColumnType, IndexColumn, EventsTable, EventsArchiveTable, Event, DecoderCheckpointsTable, DecodedEventsTable, DeadLetterEventsTable, DbSplitStatus, DbReadRow, DbPlane, Database, ContractsTable, Contract, ClaimTokensTable, ClaimToken, ChainReorgsTable, ChainReadCacheTable, BurnBlockRewardsTable, BurnBlockRewardSlotsTable, BnsNamespacesTable, BnsNamespaceEventsTable, BnsNamespaceEventStatus, BnsNamesTable, BnsNameEventsTable, BnsNameEventTopic, BnsMarketplaceEventsTable, BnsMarketplaceAction, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountCreditsTable, AccountCredits, AccountAgentRunsTable, AccountAgentRun, Account };
|
|
2112
|
+
export { sql, setMigrationRole, parseJsonb, onControlPlane, onChainPlane, jsonb, getTargetDb, getSourceDb, getRawClientFor, getRawClient, getMigrationRole, getDbSplitStatus, getDb, closeDb, assertDbSplit, X402PaymentsTable, X402BalancesTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateContract, UpdateBlock, UpdateApiKey, UpdateAccountSpendCap, UpdateAccountCredits, TriggerEvaluatorStateTable, TriggerEvaluatorState, TransactionsTable, TransactionsArchiveTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantStatus, TenantComputeAddonsTable, TenantComputeAddon, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, TABLE_TO_DB, SubscriptionsTable, SubscriptionStatus, SubscriptionRuntime, SubscriptionOutboxTable, SubscriptionOutbox, SubscriptionKind, SubscriptionFormat, SubscriptionDelivery, SubscriptionDeliveriesTable, Subscription, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ServiceHeartbeatsTable, SbtcTokenEventsTable, SbtcTokenEventType, SbtcSupplySnapshotsTable, SbtcSettlementsTable, SbtcEventsTable, SbtcEventTopic, SOURCE_READ_TYPES, SOURCE_READ_PKS, SOURCE_READ_COLUMNS, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, ProcessedStripeEventsTable, Pox5EventsTable, Pox5EventTopic, Pox4SignersDailyTable, Pox4FunctionName, Pox4CyclesDailyTable, Pox4CallsTable, PendingForkBlocksTable, OutboxStatus, ObserverJournalTable, ObserverJournalStatus, NumericAsText, MigrationRole, MempoolTransactionsTable, MempoolTransaction, MagicLinksTable, MagicLink, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMempoolTransaction, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertContract, InsertClaimToken, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountCredits, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, IndexPrimaryKey, IndexColumnType, IndexColumn, EventsTable, EventsArchiveTable, Event, DecoderCheckpointsTable, DecodedEventsTable, DeadLetterEventsTable, DbSplitStatus, DbReadRow, DbPlane, Database, ContractsTable, Contract, ClaimTokensTable, ClaimToken, ChainReorgsTable, ChainReadCacheTable, BurnBlockRewardsTable, BurnBlockRewardSlotsTable, BnsNamespacesTable, BnsNamespaceEventsTable, BnsNamespaceEventStatus, BnsNamesTable, BnsNameEventsTable, BnsNameEventTopic, BnsMarketplaceEventsTable, BnsMarketplaceAction, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountCreditsTable, AccountCredits, AccountAgentRunsTable, AccountAgentRun, Account };
|
package/dist/src/db/index.js
CHANGED
|
@@ -705,6 +705,7 @@ var TABLE_TO_DB = {
|
|
|
705
705
|
chain_reorgs: "source",
|
|
706
706
|
chain_read_cache: "source",
|
|
707
707
|
pending_fork_blocks: "source",
|
|
708
|
+
observer_journal: "source",
|
|
708
709
|
decoded_events: "source",
|
|
709
710
|
decoder_checkpoints: "source",
|
|
710
711
|
pox4_calls: "source",
|
|
@@ -948,5 +949,5 @@ export {
|
|
|
948
949
|
SOURCE_READ_COLUMNS
|
|
949
950
|
};
|
|
950
951
|
|
|
951
|
-
//# debugId=
|
|
952
|
+
//# debugId=37AABDB3C4D0230C64756E2164756E21
|
|
952
953
|
//# sourceMappingURL=index.js.map
|