@bjornpagen/bumbledb-log 0.19.2 → 0.20.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/package.json +2 -2
- package/src/braids.ts +8 -6
- package/src/bytes.ts +5 -194
- package/src/chain.ts +67 -111
- package/src/codec.ts +150 -233
- package/src/descriptor.ts +67 -612
- package/src/errors.ts +91 -62
- package/src/index.ts +25 -21
- package/src/keys.ts +49 -62
- package/src/manifest.ts +81 -129
- package/src/replica.ts +82 -128
- package/src/store-s3.ts +1 -1
- package/src/store.ts +245 -229
- package/src/tenants.ts +19 -41
- package/src/vector.ts +5 -75
- package/src/writer.ts +275 -168
- package/src/value.ts +0 -336
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bjornpagen/bumbledb-log",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Braided object-store replication for bumbledb: the command codec, theory-derived braids, and replica/writer over five store verbs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"arkregex": "^0.0.8"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@bjornpagen/bumbledb": "^0.
|
|
40
|
+
"@bjornpagen/bumbledb": "^0.20.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@biomejs/biome": "2.5.4",
|
package/src/braids.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Braid derivation as data (10): connected components of the
|
|
3
|
-
* graph over ordinary relations, the braid id the smallest
|
|
4
|
-
* the component rendered `c{id:08x}`.
|
|
5
|
-
*
|
|
2
|
+
* Braid derivation as data (10/20): connected components of the
|
|
3
|
+
* statement graph over ordinary relations, the braid id the smallest
|
|
4
|
+
* RelationId in the component rendered `c{id:08x}`. The one derivation
|
|
5
|
+
* lives in `crates/bumbledb-log` and reaches the descriptor parse
|
|
6
|
+
* through the engine bridge (`internalLogBraidsOf`); this façade names
|
|
7
|
+
* that derivation in the driver's vocabulary.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import type { Braid, SerialStatement, Theory } from "#descriptor.ts"
|
|
@@ -26,8 +28,8 @@ function braidsOf(theory: Theory): ReadonlyMap<string, Braid> {
|
|
|
26
28
|
/**
|
|
27
29
|
* The degenerate-serial roster (15): key or capacity statements whose
|
|
28
30
|
* determinant projection is empty name one global group, so their braid
|
|
29
|
-
* serializes at that statement.
|
|
30
|
-
*
|
|
31
|
+
* serializes at that statement. The statement ids are the log core's
|
|
32
|
+
* own roster, read off the descriptor's derivation.
|
|
31
33
|
*/
|
|
32
34
|
function serialAtStatementsOf(theory: Theory): readonly SerialStatement[] {
|
|
33
35
|
return descriptorOf(theory).serialAtStatements
|
package/src/bytes.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 64 lowercase hex characters. Integer order and hex width live here
|
|
8
|
-
* so no third spelling can appear.
|
|
2
|
+
* Byte primitives beside the one grammar: the hex spelling every
|
|
3
|
+
* document digest walks (32 bytes, 64 lowercase hex characters), the
|
|
4
|
+
* `Digest32` brand, byte equality, and the u64 arithmetic the vector
|
|
5
|
+
* and lease algebras share. The wire's own cursors live in
|
|
6
|
+
* `crates/bumbledb-log`; nothing here reads or writes protocol bytes.
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
9
|
import * as errors from "@superbuilders/errors"
|
|
12
10
|
|
|
13
11
|
const U64_MAX = 0xffffffffffffffffn
|
|
14
|
-
const I64_MIN = -0x8000000000000000n
|
|
15
|
-
const I64_MAX = 0x7fffffffffffffffn
|
|
16
|
-
const I64_SIGN_BIT = 0x8000000000000000n
|
|
17
12
|
|
|
18
13
|
const utf8Encoder = new TextEncoder()
|
|
19
14
|
/** Fatal UTF-8. ignoreBOM is true: a leading U+FEFF is a character, not a stripped BOM. */
|
|
@@ -22,174 +17,6 @@ const utf8StrictDecoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: tru
|
|
|
22
17
|
declare const digest32Brand: unique symbol
|
|
23
18
|
type Digest32 = Uint8Array & { readonly [digest32Brand]: typeof digest32Brand }
|
|
24
19
|
|
|
25
|
-
class ByteWriter {
|
|
26
|
-
private buf: Uint8Array
|
|
27
|
-
private len = 0
|
|
28
|
-
|
|
29
|
-
constructor(capacity = 256) {
|
|
30
|
-
this.buf = new Uint8Array(capacity)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private grow(need: number): void {
|
|
34
|
-
if (this.len + need <= this.buf.length) {
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
let capacity = this.buf.length * 2
|
|
38
|
-
while (capacity < this.len + need) {
|
|
39
|
-
capacity *= 2
|
|
40
|
-
}
|
|
41
|
-
const next = new Uint8Array(capacity)
|
|
42
|
-
next.set(this.buf.subarray(0, this.len))
|
|
43
|
-
this.buf = next
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
u8(value: number): void {
|
|
47
|
-
this.grow(1)
|
|
48
|
-
this.buf[this.len] = value
|
|
49
|
-
this.len += 1
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
bytes(raw: Uint8Array): void {
|
|
53
|
-
this.grow(raw.length)
|
|
54
|
-
this.buf.set(raw, this.len)
|
|
55
|
-
this.len += raw.length
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
array32(value: Digest32): void {
|
|
59
|
-
this.bytes(value)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
u16le(value: number): void {
|
|
63
|
-
this.grow(2)
|
|
64
|
-
this.buf[this.len] = value & 0xff
|
|
65
|
-
this.buf[this.len + 1] = (value >>> 8) & 0xff
|
|
66
|
-
this.len += 2
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
u32le(value: number): void {
|
|
70
|
-
this.grow(4)
|
|
71
|
-
this.buf[this.len] = value & 0xff
|
|
72
|
-
this.buf[this.len + 1] = (value >>> 8) & 0xff
|
|
73
|
-
this.buf[this.len + 2] = (value >>> 16) & 0xff
|
|
74
|
-
this.buf[this.len + 3] = (value >>> 24) & 0xff
|
|
75
|
-
this.len += 4
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
u64le(value: bigint): void {
|
|
79
|
-
if (value < 0n || value > U64_MAX) {
|
|
80
|
-
throw errors.new(`u64 out of range: ${value}`)
|
|
81
|
-
}
|
|
82
|
-
this.grow(8)
|
|
83
|
-
let v = value
|
|
84
|
-
for (let i = 0; i < 8; i++) {
|
|
85
|
-
this.buf[this.len + i] = Number(v & 0xffn)
|
|
86
|
-
v >>= 8n
|
|
87
|
-
}
|
|
88
|
-
this.len += 8
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
i64le(value: bigint): void {
|
|
92
|
-
if (value < I64_MIN || value > I64_MAX) {
|
|
93
|
-
throw errors.new(`i64 out of range: ${value}`)
|
|
94
|
-
}
|
|
95
|
-
this.u64le(value & U64_MAX)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
u64be(value: bigint): void {
|
|
99
|
-
if (value < 0n || value > U64_MAX) {
|
|
100
|
-
throw errors.new(`u64 out of range: ${value}`)
|
|
101
|
-
}
|
|
102
|
-
this.grow(8)
|
|
103
|
-
let v = value
|
|
104
|
-
for (let i = 7; i >= 0; i--) {
|
|
105
|
-
this.buf[this.len + i] = Number(v & 0xffn)
|
|
106
|
-
v >>= 8n
|
|
107
|
-
}
|
|
108
|
-
this.len += 8
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** The engine's sign-flipped big-endian i64 (lexicographic = numeric). */
|
|
112
|
-
i64beFlipped(value: bigint): void {
|
|
113
|
-
if (value < I64_MIN || value > I64_MAX) {
|
|
114
|
-
throw errors.new(`i64 out of range: ${value}`)
|
|
115
|
-
}
|
|
116
|
-
this.u64be((value & U64_MAX) ^ I64_SIGN_BIT)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
finish(): Uint8Array {
|
|
120
|
-
return this.buf.slice(0, this.len)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
interface ReadFailure {
|
|
125
|
-
fail(what: string): never
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
class ByteReader {
|
|
129
|
-
private readonly buf: Uint8Array
|
|
130
|
-
private pos = 0
|
|
131
|
-
private readonly refusal: ReadFailure
|
|
132
|
-
|
|
133
|
-
constructor(buf: Uint8Array, refusal: ReadFailure) {
|
|
134
|
-
this.buf = buf
|
|
135
|
-
this.refusal = refusal
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
remaining(): number {
|
|
139
|
-
return this.buf.length - this.pos
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private take(count: number, what: string): Uint8Array {
|
|
143
|
-
if (this.pos + count > this.buf.length) {
|
|
144
|
-
this.refusal.fail(what)
|
|
145
|
-
}
|
|
146
|
-
const out = this.buf.subarray(this.pos, this.pos + count)
|
|
147
|
-
this.pos += count
|
|
148
|
-
return out
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
u8(what: string): number {
|
|
152
|
-
const raw = this.take(1, what)
|
|
153
|
-
const byte = raw[0]
|
|
154
|
-
if (byte === undefined) {
|
|
155
|
-
this.refusal.fail(what)
|
|
156
|
-
}
|
|
157
|
-
return byte
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
bytes(count: number, what: string): Uint8Array {
|
|
161
|
-
return new Uint8Array(this.take(count, what))
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
array32(what: string): Digest32 {
|
|
165
|
-
return digest32(this.take(32, what))
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
u16le(what: string): number {
|
|
169
|
-
const raw = this.take(2, what)
|
|
170
|
-
return (raw[0] ?? 0) | ((raw[1] ?? 0) << 8)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
u32le(what: string): number {
|
|
174
|
-
const raw = this.take(4, what)
|
|
175
|
-
return (((raw[0] ?? 0) | ((raw[1] ?? 0) << 8) | ((raw[2] ?? 0) << 16)) + (raw[3] ?? 0) * 0x1000000) >>> 0
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
u64le(what: string): bigint {
|
|
179
|
-
const raw = this.take(8, what)
|
|
180
|
-
let value = 0n
|
|
181
|
-
for (let i = 7; i >= 0; i--) {
|
|
182
|
-
value = (value << 8n) | BigInt(raw[i] ?? 0)
|
|
183
|
-
}
|
|
184
|
-
return value
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
i64le(what: string): bigint {
|
|
188
|
-
const unsigned = this.u64le(what)
|
|
189
|
-
return unsigned > I64_MAX ? unsigned - (U64_MAX + 1n) : unsigned
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
20
|
function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
194
21
|
if (a.length !== b.length) {
|
|
195
22
|
return false
|
|
@@ -202,17 +29,6 @@ function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
|
202
29
|
return true
|
|
203
30
|
}
|
|
204
31
|
|
|
205
|
-
function bytesCompare(a: Uint8Array, b: Uint8Array): number {
|
|
206
|
-
const shared = Math.min(a.length, b.length)
|
|
207
|
-
for (let i = 0; i < shared; i++) {
|
|
208
|
-
const delta = (a[i] ?? 0) - (b[i] ?? 0)
|
|
209
|
-
if (delta !== 0) {
|
|
210
|
-
return delta
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return a.length - b.length
|
|
214
|
-
}
|
|
215
|
-
|
|
216
32
|
const HEX_DIGITS = "0123456789abcdef"
|
|
217
33
|
|
|
218
34
|
function hexNibble(byte: number): number | undefined {
|
|
@@ -285,17 +101,12 @@ function checkedAddU64(a: bigint, b: bigint): bigint | undefined {
|
|
|
285
101
|
|
|
286
102
|
export type { Digest32 }
|
|
287
103
|
export {
|
|
288
|
-
ByteReader,
|
|
289
|
-
ByteWriter,
|
|
290
|
-
bytesCompare,
|
|
291
104
|
bytesEqual,
|
|
292
105
|
checkedAddU64,
|
|
293
106
|
digest32,
|
|
294
107
|
digest32FromHex,
|
|
295
108
|
fromHex,
|
|
296
109
|
hex32,
|
|
297
|
-
I64_MAX,
|
|
298
|
-
I64_MIN,
|
|
299
110
|
saturatingAddU64,
|
|
300
111
|
toHex,
|
|
301
112
|
U64_MAX,
|
package/src/chain.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The chain sidecar in `dir/chain`: a floor cache of chain
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* The chain sidecar in `dir/chain`: a floor cache of chain position,
|
|
3
|
+
* written atomically (temp + rename, fsync). The chain is Settled or
|
|
4
|
+
* Pending — generation is the vector sum, plus one exactly when the
|
|
5
|
+
* value is Pending. The byte grammar has one implementation —
|
|
6
|
+
* `crates/bumbledb-log` behind the napi bridge — so parse and render
|
|
7
|
+
* are marshal walks over the sealed codec handle; the file IO half
|
|
8
|
+
* lives here. The content address is blake3 of the rendered bytes.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import * as crypto from "node:crypto"
|
|
12
12
|
import * as fs from "node:fs/promises"
|
|
13
13
|
import * as path from "node:path"
|
|
14
|
+
import type { LogChain, LogCodecHandle, LogSidecarKind } from "@bjornpagen/bumbledb"
|
|
15
|
+
import { internalLogParseSidecar, internalLogRenderSidecar } from "@bjornpagen/bumbledb"
|
|
14
16
|
import * as errors from "@superbuilders/errors"
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
+
import type { Digest32 } from "#bytes.ts"
|
|
18
|
+
import { digest32, saturatingAddU64, U64_MAX } from "#bytes.ts"
|
|
17
19
|
import type { Braid } from "#descriptor.ts"
|
|
18
20
|
import { braidHex } from "#descriptor.ts"
|
|
19
21
|
import { refuse } from "#errors.ts"
|
|
@@ -24,15 +26,17 @@ import { Vector } from "#vector.ts"
|
|
|
24
26
|
/** The sidecar's file name inside a replica directory. */
|
|
25
27
|
const CHAIN_FILE = "chain"
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/** One braid's chain coordinate: the applied count, the applied
|
|
30
|
+
* batch's content address, its timestamp. */
|
|
31
|
+
interface ChainEntry {
|
|
32
|
+
readonly g: Generation
|
|
33
|
+
readonly prev: Digest32
|
|
34
|
+
readonly ts: bigint
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
interface Pending {
|
|
34
38
|
readonly braid: Braid
|
|
35
|
-
readonly
|
|
39
|
+
readonly slot: Generation
|
|
36
40
|
readonly bytes: Uint8Array
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -72,114 +76,66 @@ function chainGeneration(chain: Chain): bigint {
|
|
|
72
76
|
return chain.tag === "settled" ? sum : saturatingAddU64(sum, 1n)
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Remints a bridge refusal row as the driver's typed refusal. The
|
|
81
|
+
* boundary carries `{ kind, message }` only: the kind is the log
|
|
82
|
+
* core's own identity string, so the cause payload holds the data this
|
|
83
|
+
* side owns — the document's length, its version byte (byte 0 of every
|
|
84
|
+
* v:3 document). The raw braid id of an `UnknownBraid` rides the
|
|
85
|
+
* message.
|
|
86
|
+
*/
|
|
87
|
+
function refuseBridged(kind: LogSidecarKind, message: string, bytes: Uint8Array): never {
|
|
88
|
+
switch (kind) {
|
|
89
|
+
case "Version":
|
|
90
|
+
return refuse({ kind: "Version", version: bytes[0] ?? 0 }, message)
|
|
91
|
+
case "Overflow":
|
|
92
|
+
return refuse({ kind: "Overflow" }, message)
|
|
93
|
+
case "UnknownBraid":
|
|
94
|
+
return refuse({ kind: "UnknownBraid" }, message)
|
|
95
|
+
case "Malformed":
|
|
96
|
+
return refuse({ kind: "Malformed", at: bytes.length }, message)
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
99
|
|
|
86
|
-
function renderSidecar(chain: Chain): Uint8Array {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
out.u64le(entry.ts)
|
|
103
|
-
}
|
|
104
|
-
if (chain.tag === "settled") {
|
|
105
|
-
out.u8(SETTLED)
|
|
106
|
-
return out.finish()
|
|
107
|
-
}
|
|
108
|
-
out.u8(PENDING)
|
|
109
|
-
out.u32le(braidIdOf(chain.batch.braid))
|
|
110
|
-
out.u64le(chain.batch.gen)
|
|
111
|
-
if (chain.batch.bytes.length > 0xffffffff) {
|
|
112
|
-
throw errors.new("sidecar pending exceeds u32 length")
|
|
113
|
-
}
|
|
114
|
-
out.u32le(chain.batch.bytes.length)
|
|
115
|
-
out.bytes(chain.batch.bytes)
|
|
116
|
-
return out.finish()
|
|
100
|
+
function renderSidecar(codec: LogCodecHandle, chain: Chain): Uint8Array {
|
|
101
|
+
const entries = [...chain.entries.entries()]
|
|
102
|
+
.sort(function ascending(a, b) {
|
|
103
|
+
return braidIdOf(a[0]) - braidIdOf(b[0])
|
|
104
|
+
})
|
|
105
|
+
.map(function entryOf([id, entry]) {
|
|
106
|
+
return { braid: braidIdOf(id), g: entry.g, prev: entry.prev, ts: entry.ts }
|
|
107
|
+
})
|
|
108
|
+
const doc: LogChain =
|
|
109
|
+
chain.tag === "settled"
|
|
110
|
+
? { entries }
|
|
111
|
+
: {
|
|
112
|
+
entries,
|
|
113
|
+
pending: { braid: braidIdOf(chain.batch.braid), slot: chain.batch.slot, bytes: chain.batch.bytes }
|
|
114
|
+
}
|
|
115
|
+
return internalLogRenderSidecar(codec, doc)
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
function parseSidecar(
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
const at = function offset(): number {
|
|
126
|
-
return bytes.length - reader.remaining()
|
|
127
|
-
}
|
|
128
|
-
const version = reader.u8("version")
|
|
129
|
-
if (version !== VERSION) {
|
|
130
|
-
refuse({ kind: "Version", version }, `sidecar version ${version}, consumers refuse ≠ ${VERSION}`)
|
|
118
|
+
function parseSidecar(codec: LogCodecHandle, bytes: Uint8Array): Chain {
|
|
119
|
+
const parsed = internalLogParseSidecar(codec, bytes)
|
|
120
|
+
if (!parsed.ok) {
|
|
121
|
+
refuseBridged(parsed.kind, parsed.message, bytes)
|
|
131
122
|
}
|
|
132
|
-
const count = BigInt(reader.u32le("chain count"))
|
|
133
|
-
refuseUnbacked(count, reader.remaining(), ENTRY_BYTES, "chain count")
|
|
134
123
|
const entries = new Map<Braid, ChainEntry>()
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const raw = reader.u32le("braid")
|
|
138
|
-
const name = braidHex(raw)
|
|
139
|
-
if (known !== undefined && !known.has(name)) {
|
|
140
|
-
refuse({ kind: "UnknownBraid", braid: raw }, `sidecar cites unknown braid ${name}`)
|
|
141
|
-
}
|
|
142
|
-
const g = reader.u64le("g")
|
|
143
|
-
const prev = reader.array32("prev")
|
|
144
|
-
const ts = reader.u64le("ts")
|
|
145
|
-
if (last !== undefined && last >= name) {
|
|
146
|
-
refuse({ kind: "Malformed", at: at() }, "sidecar chain is not strictly ascending")
|
|
147
|
-
}
|
|
148
|
-
entries.set(name, { g: generation(g), prev, ts })
|
|
149
|
-
last = name
|
|
150
|
-
}
|
|
151
|
-
if (typeof vectorOf(entries).sum() !== "bigint") {
|
|
152
|
-
refuse({ kind: "Overflow" }, "sidecar chain sum overflows u64")
|
|
124
|
+
for (const entry of parsed.value.entries) {
|
|
125
|
+
entries.set(braidHex(entry.braid), { g: generation(entry.g), prev: digest32(entry.prev), ts: entry.ts })
|
|
153
126
|
}
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
if (reader.remaining() !== 0) {
|
|
157
|
-
refuse({ kind: "Malformed", at: reader.remaining() }, `${reader.remaining()} trailing bytes after the sidecar`)
|
|
158
|
-
}
|
|
127
|
+
const pending = parsed.value.pending
|
|
128
|
+
if (pending === undefined) {
|
|
159
129
|
return { tag: "settled", entries }
|
|
160
130
|
}
|
|
161
|
-
if (tag !== PENDING) {
|
|
162
|
-
refuse({ kind: "Malformed", at: at() - 1 }, `sidecar pending tag ${tag}`)
|
|
163
|
-
}
|
|
164
|
-
const raw = reader.u32le("pending braid")
|
|
165
|
-
const name = braidHex(raw)
|
|
166
|
-
if (known !== undefined && !known.has(name)) {
|
|
167
|
-
refuse({ kind: "UnknownBraid", braid: raw }, `sidecar pending cites unknown braid ${name}`)
|
|
168
|
-
}
|
|
169
|
-
const slot = reader.u64le("pending generation")
|
|
170
|
-
const length = reader.u32le("pending length")
|
|
171
|
-
const body = reader.bytes(length, "pending bytes")
|
|
172
|
-
if (reader.remaining() !== 0) {
|
|
173
|
-
refuse({ kind: "Malformed", at: reader.remaining() }, `${reader.remaining()} trailing bytes after the sidecar`)
|
|
174
|
-
}
|
|
175
131
|
return {
|
|
176
132
|
tag: "pending",
|
|
177
133
|
entries,
|
|
178
|
-
batch: { braid:
|
|
134
|
+
batch: { braid: braidHex(pending.braid), slot: generation(pending.slot), bytes: pending.bytes }
|
|
179
135
|
}
|
|
180
136
|
}
|
|
181
137
|
|
|
182
|
-
async function readSidecar(
|
|
138
|
+
async function readSidecar(codec: LogCodecHandle, file: string): Promise<SidecarRead> {
|
|
183
139
|
const read = await errors.try(fs.readFile(file))
|
|
184
140
|
if (read.error) {
|
|
185
141
|
if (codeOf(read.error) === "ENOENT") {
|
|
@@ -188,7 +144,7 @@ async function readSidecar(file: string, known?: ReadonlySet<Braid>): Promise<Si
|
|
|
188
144
|
return { tag: "fault", io: read.error }
|
|
189
145
|
}
|
|
190
146
|
const parsed = errors.trySync(function parse() {
|
|
191
|
-
return parseSidecar(read.data
|
|
147
|
+
return parseSidecar(codec, read.data)
|
|
192
148
|
})
|
|
193
149
|
if (parsed.error) {
|
|
194
150
|
return { tag: "corrupt", parse: parsed.error }
|
|
@@ -196,14 +152,14 @@ async function readSidecar(file: string, known?: ReadonlySet<Braid>): Promise<Si
|
|
|
196
152
|
return { tag: "read", chain: parsed.data }
|
|
197
153
|
}
|
|
198
154
|
|
|
199
|
-
async function writeSidecar(file: string, chain: Chain): Promise<void> {
|
|
155
|
+
async function writeSidecar(codec: LogCodecHandle, file: string, chain: Chain): Promise<void> {
|
|
200
156
|
const dir = path.dirname(file)
|
|
201
157
|
await fs.mkdir(dir, { recursive: true })
|
|
202
158
|
const temp = path.join(dir, `.chain-${process.pid}-${crypto.randomBytes(4).toString("hex")}`)
|
|
203
159
|
const handle = await fs.open(temp, "wx")
|
|
204
160
|
const written = await errors.try(
|
|
205
161
|
(async function writeAll() {
|
|
206
|
-
await handle.writeFile(renderSidecar(chain))
|
|
162
|
+
await handle.writeFile(renderSidecar(codec, chain))
|
|
207
163
|
await handle.sync()
|
|
208
164
|
})()
|
|
209
165
|
)
|
|
@@ -221,5 +177,5 @@ async function writeSidecar(file: string, chain: Chain): Promise<void> {
|
|
|
221
177
|
}
|
|
222
178
|
}
|
|
223
179
|
|
|
224
|
-
export type { Chain, ChainEntry, Pending
|
|
180
|
+
export type { Chain, ChainEntry, Pending }
|
|
225
181
|
export { CHAIN_FILE, chainGeneration, chainSum, parseSidecar, readSidecar, renderSidecar, writeSidecar }
|