@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/src/value.ts
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The driver's raw-value vocabulary and its two canonical encodings:
|
|
3
|
-
* the command codec's tagged little-endian form (the tag table the
|
|
4
|
-
* batch wire already declared), and the engine's big-endian
|
|
5
|
-
* order-preserving literal form the fingerprint mirror reproduces.
|
|
6
|
-
* Values here are always RAW — strings as UTF-8, never intern ids —
|
|
7
|
-
* so a key hashed from a raw value cannot depend on a catalog.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ValueTypeSpec } from "@bjornpagen/bumbledb"
|
|
11
|
-
import * as errors from "@superbuilders/errors"
|
|
12
|
-
import type { ByteReader, ByteWriter } from "#bytes.ts"
|
|
13
|
-
import { bytesEqual, I64_MAX, I64_MIN, U64_MAX, utf8Encoder, utf8StrictDecoder } from "#bytes.ts"
|
|
14
|
-
|
|
15
|
-
interface Interval {
|
|
16
|
-
readonly start: bigint
|
|
17
|
-
readonly end: bigint
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type Value = boolean | bigint | string | Uint8Array | Interval
|
|
21
|
-
|
|
22
|
-
/** A string cell: well-formed UTF-8 by construction. A lone surrogate cannot enter. */
|
|
23
|
-
declare const wellFormedUtf8Brand: unique symbol
|
|
24
|
-
type WellFormedUtf8 = string & { readonly [wellFormedUtf8Brand]: typeof wellFormedUtf8Brand }
|
|
25
|
-
|
|
26
|
-
/** Fatal encoder: refuses lone surrogates rather than emitting U+FFFD. */
|
|
27
|
-
function wellFormedUtf8(text: string): WellFormedUtf8 {
|
|
28
|
-
if (!text.isWellFormed()) {
|
|
29
|
-
throw errors.new("string cell is not well-formed UTF-8")
|
|
30
|
-
}
|
|
31
|
-
return text as WellFormedUtf8
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function parseWellFormedUtf8(raw: Uint8Array): WellFormedUtf8 | undefined {
|
|
35
|
-
const decoded = errors.trySync(function decodeUtf8() {
|
|
36
|
-
return utf8StrictDecoder.decode(raw)
|
|
37
|
-
})
|
|
38
|
-
if (decoded.error) {
|
|
39
|
-
return undefined
|
|
40
|
-
}
|
|
41
|
-
return decoded.data as WellFormedUtf8
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function domainCeiling(element: "u64" | "i64"): bigint {
|
|
45
|
-
return element === "u64" ? U64_MAX : I64_MAX
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Fixed-width `[start, start + width)`; the domain ceiling is not a value. */
|
|
49
|
-
function fixedInterval(start: bigint, width: bigint, element: "u64" | "i64"): Interval | undefined {
|
|
50
|
-
const end = start + width
|
|
51
|
-
if (end <= start || end >= domainCeiling(element)) {
|
|
52
|
-
return undefined
|
|
53
|
-
}
|
|
54
|
-
return { start, end }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** 20's tag table: the codec's own numbering, normative to the byte. */
|
|
58
|
-
const TAG = {
|
|
59
|
-
bool: 0,
|
|
60
|
-
u64: 1,
|
|
61
|
-
i64: 2,
|
|
62
|
-
string: 3,
|
|
63
|
-
fixedBytes: 4,
|
|
64
|
-
interval: 5,
|
|
65
|
-
fixedInterval: 6
|
|
66
|
-
} as const
|
|
67
|
-
|
|
68
|
-
function isInterval(value: Value): value is Interval {
|
|
69
|
-
return typeof value === "object" && !(value instanceof Uint8Array)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function wireTagOf(type: ValueTypeSpec): number {
|
|
73
|
-
switch (type.kind) {
|
|
74
|
-
case "bool":
|
|
75
|
-
return TAG.bool
|
|
76
|
-
case "u64":
|
|
77
|
-
return TAG.u64
|
|
78
|
-
case "i64":
|
|
79
|
-
return TAG.i64
|
|
80
|
-
case "string":
|
|
81
|
-
return TAG.string
|
|
82
|
-
case "fixedBytes":
|
|
83
|
-
return TAG.fixedBytes
|
|
84
|
-
case "interval":
|
|
85
|
-
return type.width === undefined ? TAG.interval : TAG.fixedInterval
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function checkAgainst(context: string, type: ValueTypeSpec, value: Value): void {
|
|
90
|
-
switch (type.kind) {
|
|
91
|
-
case "bool": {
|
|
92
|
-
if (typeof value !== "boolean") {
|
|
93
|
-
throw errors.new(`${context}: expected boolean`)
|
|
94
|
-
}
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
case "u64": {
|
|
98
|
-
if (typeof value !== "bigint" || value < 0n || value > U64_MAX) {
|
|
99
|
-
throw errors.new(`${context}: expected u64 bigint`)
|
|
100
|
-
}
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
case "i64": {
|
|
104
|
-
if (typeof value !== "bigint" || value < I64_MIN || value > I64_MAX) {
|
|
105
|
-
throw errors.new(`${context}: expected i64 bigint`)
|
|
106
|
-
}
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
case "string": {
|
|
110
|
-
if (typeof value !== "string") {
|
|
111
|
-
throw errors.new(`${context}: expected well-formed string`)
|
|
112
|
-
}
|
|
113
|
-
wellFormedUtf8(value)
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
case "fixedBytes": {
|
|
117
|
-
if (!(value instanceof Uint8Array) || value.length !== type.len) {
|
|
118
|
-
throw errors.new(`${context}: expected ${type.len}-byte Uint8Array`)
|
|
119
|
-
}
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
case "interval": {
|
|
123
|
-
if (!(typeof value === "object") || value instanceof Uint8Array) {
|
|
124
|
-
throw errors.new(`${context}: expected interval value`)
|
|
125
|
-
}
|
|
126
|
-
const lo = type.element === "u64" ? 0n : I64_MIN
|
|
127
|
-
const hi = type.element === "u64" ? U64_MAX : I64_MAX
|
|
128
|
-
if (value.start < lo || value.end > hi || value.start >= value.end) {
|
|
129
|
-
throw errors.new(`${context}: interval bounds out of range or empty`)
|
|
130
|
-
}
|
|
131
|
-
if (type.width !== undefined) {
|
|
132
|
-
const parsed = fixedInterval(value.start, type.width, type.element)
|
|
133
|
-
if (parsed === undefined) {
|
|
134
|
-
throw errors.new(`${context}: fixed interval end is the domain ceiling`)
|
|
135
|
-
}
|
|
136
|
-
if (parsed.end !== value.end) {
|
|
137
|
-
throw errors.new(`${context}: interval width must be ${type.width}`)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** The codec's tagged form: `tag u8` + payload, at the field's layout. */
|
|
146
|
-
function writeTagged(out: ByteWriter, type: ValueTypeSpec, value: Value): void {
|
|
147
|
-
switch (type.kind) {
|
|
148
|
-
case "bool": {
|
|
149
|
-
out.u8(TAG.bool)
|
|
150
|
-
out.u8(value === true ? 1 : 0)
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
case "u64": {
|
|
154
|
-
out.u8(TAG.u64)
|
|
155
|
-
out.u64le(value as bigint)
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
case "i64": {
|
|
159
|
-
out.u8(TAG.i64)
|
|
160
|
-
out.i64le(value as bigint)
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
case "string": {
|
|
164
|
-
const text = wellFormedUtf8(value as string)
|
|
165
|
-
const raw = utf8Encoder.encode(text)
|
|
166
|
-
out.u8(TAG.string)
|
|
167
|
-
out.u32le(raw.length)
|
|
168
|
-
out.bytes(raw)
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
case "fixedBytes": {
|
|
172
|
-
out.u8(TAG.fixedBytes)
|
|
173
|
-
out.bytes(value as Uint8Array)
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
case "interval": {
|
|
177
|
-
const interval = value as Interval
|
|
178
|
-
if (type.width === undefined) {
|
|
179
|
-
out.u8(TAG.interval)
|
|
180
|
-
if (type.element === "u64") {
|
|
181
|
-
out.u64le(interval.start)
|
|
182
|
-
out.u64le(interval.end)
|
|
183
|
-
} else {
|
|
184
|
-
out.i64le(interval.start)
|
|
185
|
-
out.i64le(interval.end)
|
|
186
|
-
}
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
out.u8(TAG.fixedInterval)
|
|
190
|
-
if (type.element === "u64") {
|
|
191
|
-
out.u64le(interval.start)
|
|
192
|
-
} else {
|
|
193
|
-
out.i64le(interval.start)
|
|
194
|
-
}
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** The value parser's refusal channel: one method per proved cause,
|
|
201
|
-
* matching the Rust decoder's cross-implementation identities. */
|
|
202
|
-
interface TaggedRefusal {
|
|
203
|
-
badTag(expected: number, actual: number): never
|
|
204
|
-
boolByte(byte: number): never
|
|
205
|
-
invalidUtf8(): never
|
|
206
|
-
emptyInterval(): never
|
|
207
|
-
intervalOverflow(): never
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** Full parse at the layout's type; every illegal byte is a typed refusal. */
|
|
211
|
-
function readTagged(reader: ByteReader, type: ValueTypeSpec, refusal: TaggedRefusal): Value {
|
|
212
|
-
const expected = wireTagOf(type)
|
|
213
|
-
const tag = reader.u8("value tag")
|
|
214
|
-
if (tag !== expected) {
|
|
215
|
-
refusal.badTag(expected, tag)
|
|
216
|
-
}
|
|
217
|
-
switch (type.kind) {
|
|
218
|
-
case "bool": {
|
|
219
|
-
const byte = reader.u8("bool payload")
|
|
220
|
-
if (byte > 1) {
|
|
221
|
-
refusal.boolByte(byte)
|
|
222
|
-
}
|
|
223
|
-
return byte === 1
|
|
224
|
-
}
|
|
225
|
-
case "u64":
|
|
226
|
-
return reader.u64le("u64 payload")
|
|
227
|
-
case "i64":
|
|
228
|
-
return reader.i64le("i64 payload")
|
|
229
|
-
case "string": {
|
|
230
|
-
const len = reader.u32le("string length")
|
|
231
|
-
const raw = reader.bytes(len, "string payload")
|
|
232
|
-
const text = parseWellFormedUtf8(raw)
|
|
233
|
-
if (text === undefined) {
|
|
234
|
-
refusal.invalidUtf8()
|
|
235
|
-
}
|
|
236
|
-
return text
|
|
237
|
-
}
|
|
238
|
-
case "fixedBytes":
|
|
239
|
-
return reader.bytes(type.len, "fixedBytes payload")
|
|
240
|
-
case "interval": {
|
|
241
|
-
if (type.width === undefined) {
|
|
242
|
-
const start = type.element === "u64" ? reader.u64le("interval start") : reader.i64le("interval start")
|
|
243
|
-
const end = type.element === "u64" ? reader.u64le("interval end") : reader.i64le("interval end")
|
|
244
|
-
if (start >= end) {
|
|
245
|
-
refusal.emptyInterval()
|
|
246
|
-
}
|
|
247
|
-
return { start, end }
|
|
248
|
-
}
|
|
249
|
-
const start = type.element === "u64" ? reader.u64le("interval start") : reader.i64le("interval start")
|
|
250
|
-
const parsed = fixedInterval(start, type.width, type.element)
|
|
251
|
-
if (parsed === undefined) {
|
|
252
|
-
refusal.intervalOverflow()
|
|
253
|
-
}
|
|
254
|
-
return parsed
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function valuesEqual(a: Value, b: Value): boolean {
|
|
260
|
-
if (typeof a === "boolean" || typeof a === "bigint" || typeof a === "string") {
|
|
261
|
-
return a === b
|
|
262
|
-
}
|
|
263
|
-
if (a instanceof Uint8Array) {
|
|
264
|
-
return b instanceof Uint8Array && bytesEqual(a, b)
|
|
265
|
-
}
|
|
266
|
-
if (typeof b !== "object" || b instanceof Uint8Array) {
|
|
267
|
-
return false
|
|
268
|
-
}
|
|
269
|
-
return a.start === b.start && a.end === b.end
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* The engine's canonical big-endian literal form (`encode_literal`) —
|
|
274
|
-
* the fingerprint mirror's alphabet. Strings never reach this encoder:
|
|
275
|
-
* the fingerprint's `put_literal` length-prefixes them separately, and
|
|
276
|
-
* closed ground axioms with string columns are the mirror's recorded gap.
|
|
277
|
-
*/
|
|
278
|
-
function writeCanonicalLiteral(out: ByteWriter, type: ValueTypeSpec, value: Value): void {
|
|
279
|
-
switch (type.kind) {
|
|
280
|
-
case "bool": {
|
|
281
|
-
out.u8(value === true ? 1 : 0)
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
case "u64": {
|
|
285
|
-
out.u64be(value as bigint)
|
|
286
|
-
return
|
|
287
|
-
}
|
|
288
|
-
case "i64": {
|
|
289
|
-
out.i64beFlipped(value as bigint)
|
|
290
|
-
return
|
|
291
|
-
}
|
|
292
|
-
case "string":
|
|
293
|
-
throw errors.new("canonical literal: strings are length-prefixed by the caller, never encoded here")
|
|
294
|
-
case "fixedBytes": {
|
|
295
|
-
const raw = value as Uint8Array
|
|
296
|
-
const padded = Math.ceil(type.len / 8) * 8
|
|
297
|
-
out.bytes(raw)
|
|
298
|
-
for (let i = raw.length; i < padded; i++) {
|
|
299
|
-
out.u8(0)
|
|
300
|
-
}
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
case "interval": {
|
|
304
|
-
const interval = value as Interval
|
|
305
|
-
if (type.width !== undefined) {
|
|
306
|
-
if (type.element === "u64") {
|
|
307
|
-
out.u64be(interval.start)
|
|
308
|
-
} else {
|
|
309
|
-
out.i64beFlipped(interval.start)
|
|
310
|
-
}
|
|
311
|
-
return
|
|
312
|
-
}
|
|
313
|
-
if (type.element === "u64") {
|
|
314
|
-
out.u64be(interval.start)
|
|
315
|
-
out.u64be(interval.end)
|
|
316
|
-
} else {
|
|
317
|
-
out.i64beFlipped(interval.start)
|
|
318
|
-
out.i64beFlipped(interval.end)
|
|
319
|
-
}
|
|
320
|
-
return
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export type { Interval, TaggedRefusal, Value, WellFormedUtf8 }
|
|
326
|
-
export {
|
|
327
|
-
checkAgainst,
|
|
328
|
-
isInterval,
|
|
329
|
-
readTagged,
|
|
330
|
-
TAG,
|
|
331
|
-
valuesEqual,
|
|
332
|
-
wellFormedUtf8,
|
|
333
|
-
wireTagOf,
|
|
334
|
-
writeCanonicalLiteral,
|
|
335
|
-
writeTagged
|
|
336
|
-
}
|