@bjornpagen/bumbledb-log 0.20.0 → 0.20.2

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.
Files changed (76) hide show
  1. package/dist/braids.d.ts +28 -0
  2. package/dist/braids.d.ts.map +1 -0
  3. package/{src/braids.ts → dist/braids.js} +22 -29
  4. package/dist/braids.js.map +1 -0
  5. package/dist/bytes.d.ts +26 -0
  6. package/dist/bytes.d.ts.map +1 -0
  7. package/dist/bytes.js +86 -0
  8. package/dist/bytes.js.map +1 -0
  9. package/dist/chain.d.ts +56 -0
  10. package/dist/chain.d.ts.map +1 -0
  11. package/dist/chain.js +137 -0
  12. package/dist/chain.js.map +1 -0
  13. package/dist/codec.d.ts +71 -0
  14. package/dist/codec.d.ts.map +1 -0
  15. package/dist/codec.js +179 -0
  16. package/dist/codec.js.map +1 -0
  17. package/dist/descriptor.d.ts +61 -0
  18. package/dist/descriptor.d.ts.map +1 -0
  19. package/dist/descriptor.js +160 -0
  20. package/dist/descriptor.js.map +1 -0
  21. package/dist/errors.d.ts +202 -0
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/errors.js +122 -0
  24. package/dist/errors.js.map +1 -0
  25. package/dist/index.d.ts +34 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +24 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/keys.d.ts +52 -0
  30. package/dist/keys.d.ts.map +1 -0
  31. package/dist/keys.js +166 -0
  32. package/dist/keys.js.map +1 -0
  33. package/dist/manifest.d.ts +42 -0
  34. package/dist/manifest.d.ts.map +1 -0
  35. package/dist/manifest.js +116 -0
  36. package/dist/manifest.js.map +1 -0
  37. package/dist/replica.d.ts +154 -0
  38. package/dist/replica.d.ts.map +1 -0
  39. package/dist/replica.js +820 -0
  40. package/dist/replica.js.map +1 -0
  41. package/dist/store-s3.d.ts +31 -0
  42. package/dist/store-s3.d.ts.map +1 -0
  43. package/dist/store-s3.js +299 -0
  44. package/dist/store-s3.js.map +1 -0
  45. package/dist/store.d.ts +129 -0
  46. package/dist/store.d.ts.map +1 -0
  47. package/dist/store.js +577 -0
  48. package/dist/store.js.map +1 -0
  49. package/dist/tenants.d.ts +56 -0
  50. package/dist/tenants.d.ts.map +1 -0
  51. package/dist/tenants.js +333 -0
  52. package/dist/tenants.js.map +1 -0
  53. package/dist/tilde-family.json +21 -0
  54. package/dist/vector.d.ts +31 -0
  55. package/dist/vector.d.ts.map +1 -0
  56. package/dist/vector.js +87 -0
  57. package/dist/vector.js.map +1 -0
  58. package/dist/writer.d.ts +106 -0
  59. package/dist/writer.d.ts.map +1 -0
  60. package/dist/writer.js +677 -0
  61. package/dist/writer.js.map +1 -0
  62. package/package.json +12 -7
  63. package/src/bytes.ts +0 -115
  64. package/src/chain.ts +0 -181
  65. package/src/codec.ts +0 -252
  66. package/src/descriptor.ts +0 -240
  67. package/src/errors.ts +0 -309
  68. package/src/index.ts +0 -57
  69. package/src/keys.ts +0 -218
  70. package/src/manifest.ts +0 -161
  71. package/src/replica.ts +0 -1061
  72. package/src/store-s3.ts +0 -377
  73. package/src/store.ts +0 -727
  74. package/src/tenants.ts +0 -414
  75. package/src/vector.ts +0 -103
  76. package/src/writer.ts +0 -977
package/src/keys.ts DELETED
@@ -1,218 +0,0 @@
1
- /**
2
- * The key layout: generation numbers zero-padded lowercase hex, 16
3
- * chars; a prefix is a store; a tenant is a prefix under `t/`. A
4
- * StoreKey is parsed here, once — the verbs take the proof and never
5
- * re-check. An empty prefix joins as the rest alone, so a leading
6
- * slash is unrepresentable. Temps and leases live under a reserved
7
- * tilde-family first segment no StoreKey can spell — the generated
8
- * table under `conformance/v3/keys/` is the set, read at module init
9
- * — so those namespaces stay disjoint from every honest key. Format
10
- * characters, line and paragraph separators, and space separators
11
- * cannot hide a reserved prefix or a `.lock` suffix: a segment
12
- * containing one is refused outright.
13
- */
14
-
15
- import * as fs from "node:fs"
16
- import { internalLogParseCkptScratch, internalLogRenderCkptScratch } from "@bjornpagen/bumbledb"
17
- import * as errors from "@superbuilders/errors"
18
- import { regex } from "arkregex"
19
- import type { Digest32 } from "#bytes.ts"
20
- import { digest32, hex32, U64_MAX } from "#bytes.ts"
21
- import type { Braid } from "#descriptor.ts"
22
-
23
- /** A segment wearing this suffix is not a key. */
24
- const LOCK_SUFFIX = ".lock"
25
-
26
- /** Reserved first-segment names no StoreKey can spell. Temps and leases live here. */
27
- const TEMP_NAMESPACE = "~tmp"
28
- const LEASE_NAMESPACE = "~lease"
29
-
30
- /** The one tilde table both drivers consume: ASCII `~`, its lookalikes, and the NFKC preimage of U+007E. */
31
- const TILDE_TABLE_URL = new URL("../../crates/bumbledb-log/conformance/v3/keys/tilde-family.json", import.meta.url)
32
-
33
- const CODE_POINT_SPELLING = regex("^U\\+[0-9A-F]{4,6}$")
34
-
35
- function loadTildeFamily(): Set<string> {
36
- const raw = fs.readFileSync(TILDE_TABLE_URL, "utf8")
37
- const table: unknown = JSON.parse(raw)
38
- if (typeof table !== "object" || table === null || !("codePoints" in table)) {
39
- throw errors.new(`tilde table is not a codePoints document: ${TILDE_TABLE_URL.pathname}`)
40
- }
41
- const { codePoints } = table
42
- if (!Array.isArray(codePoints) || codePoints.length === 0) {
43
- throw errors.new(`tilde table codePoints is not a nonempty array: ${TILDE_TABLE_URL.pathname}`)
44
- }
45
- const family = new Set<string>()
46
- for (const spelling of codePoints) {
47
- if (typeof spelling !== "string" || !CODE_POINT_SPELLING.test(spelling)) {
48
- throw errors.new(`tilde table entry is not a U+XXXX code point: ${String(spelling)}`)
49
- }
50
- family.add(String.fromCodePoint(Number.parseInt(spelling.slice(2), 16)))
51
- }
52
- if (!family.has("~")) {
53
- throw errors.new(`tilde table does not reserve ASCII tilde: ${TILDE_TABLE_URL.pathname}`)
54
- }
55
- return family
56
- }
57
-
58
- /** First-segment code points that spell a reserved name. The table is the set; no local spelling. */
59
- const TILDE_FAMILY = loadTildeFamily()
60
-
61
- const FORMAT_OR_SEPARATOR = regex("[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Zs}]", "u")
62
- const SLASH_TRIM = regex("^/+|/+$", "g")
63
-
64
- declare const storeKeyBrand: unique symbol
65
- type StoreKey = string & { readonly [storeKeyBrand]: typeof storeKeyBrand }
66
-
67
- declare const generationBrand: unique symbol
68
- type Generation = bigint & { readonly [generationBrand]: typeof generationBrand }
69
-
70
- function firstCodePoint(seg: string): string | undefined {
71
- for (const ch of seg) {
72
- return ch
73
- }
74
- return undefined
75
- }
76
-
77
- function tildeFamilyPrefix(seg: string): boolean {
78
- const first = firstCodePoint(seg)
79
- if (first === undefined) {
80
- return false
81
- }
82
- return TILDE_FAMILY.has(first)
83
- }
84
-
85
- /** One path segment of a key or a tenant id: the same grammar. */
86
- function segmentOk(seg: string): boolean {
87
- if (seg.length === 0 || seg.includes("/") || seg === "." || seg === "..") {
88
- return false
89
- }
90
- if (FORMAT_OR_SEPARATOR.test(seg)) {
91
- return false
92
- }
93
- return !tildeFamilyPrefix(seg) && !seg.endsWith(LOCK_SUFFIX)
94
- }
95
-
96
- function storeKey(raw: string): StoreKey {
97
- const wellFormed = raw.length > 0 && !raw.startsWith("/") && !raw.endsWith("/") && raw.split("/").every(segmentOk)
98
- if (!wellFormed) {
99
- throw errors.new(`store key is not a slash path: ${raw}`)
100
- }
101
- return raw as StoreKey
102
- }
103
-
104
- /** Empty, or a StoreKey spelling (the same segment grammar, no leading or trailing slash). */
105
- function parsePrefix(raw: string): string {
106
- if (raw.length === 0) {
107
- return ""
108
- }
109
- return storeKey(raw.replace(SLASH_TRIM, ""))
110
- }
111
-
112
- function reservedTemp(pid: number, seq: number): string {
113
- return `${TEMP_NAMESPACE}/${String(pid)}.${String(seq)}`
114
- }
115
-
116
- function reservedLease(key: string, token: bigint): string {
117
- return `${LEASE_NAMESPACE}/${key}/${String(token)}`
118
- }
119
-
120
- /** A slash path whose first segment is `~tmp` or `~lease`. Not a StoreKey. */
121
- function reservedName(raw: string): string {
122
- const segs = raw.split("/")
123
- if (segs.length === 0 || segs.some((seg) => seg.length === 0 || seg === "." || seg === "..")) {
124
- throw errors.new(`reserved name is not a slash path: ${raw}`)
125
- }
126
- const first = segs[0]
127
- if (first !== TEMP_NAMESPACE && first !== LEASE_NAMESPACE) {
128
- throw errors.new(`reserved name is not under ${TEMP_NAMESPACE} or ${LEASE_NAMESPACE}: ${raw}`)
129
- }
130
- return raw
131
- }
132
-
133
- /** Known scratch-lease document under `~lease`. One path, no LIST. */
134
- const CKPT_SCRATCH_LEASE = "ckpt-scratch"
135
-
136
- /** The reserved scratch name: `~lease/ckpt-scratch`. */
137
- function scratchCkptName(): string {
138
- return reservedName(`${LEASE_NAMESPACE}/${CKPT_SCRATCH_LEASE}`)
139
- }
140
-
141
- /** The scratch-lease body, spelled by the one grammar (`crates/bumbledb-log`). */
142
- function encodeCkptScratch(digest: Digest32): Uint8Array {
143
- return internalLogRenderCkptScratch(digest)
144
- }
145
-
146
- /** The digest a scratch-lease body names, or null — the one grammar's
147
- * parse, branded; the refusal is undifferentiated by law. */
148
- function parseCkptScratch(bytes: Uint8Array): Digest32 | null {
149
- const named = internalLogParseCkptScratch(bytes)
150
- if (named === null) {
151
- return null
152
- }
153
- return digest32(named)
154
- }
155
-
156
- function generation(raw: bigint): Generation {
157
- if (raw < 0n || raw > U64_MAX) {
158
- throw errors.new(`generation is a u64: ${raw}`)
159
- }
160
- return raw as Generation
161
- }
162
-
163
- function hex16(g: Generation): string {
164
- return g.toString(16).padStart(16, "0")
165
- }
166
-
167
- function assemble(prefix: string, rest: string): StoreKey {
168
- return storeKey(prefix.length === 0 ? rest : `${prefix}/${rest}`)
169
- }
170
-
171
- function manifestKey(prefix: string): StoreKey {
172
- return assemble(prefix, "manifest")
173
- }
174
-
175
- function logKey(prefix: string, braid: Braid, g: Generation): StoreKey {
176
- return assemble(prefix, `log/${braid}/${hex16(g)}`)
177
- }
178
-
179
- function checkpointMdbKey(prefix: string, digest: Digest32): StoreKey {
180
- return assemble(prefix, `ckpt/${hex32(digest)}.mdb`)
181
- }
182
-
183
- function ckptDocKey(prefix: string, digest: Digest32): StoreKey {
184
- return assemble(prefix, `ckpt/${hex32(digest)}`)
185
- }
186
-
187
- function idsKey(prefix: string, relation: number, field: number): StoreKey {
188
- return assemble(prefix, `ids/${relation.toString(16).padStart(8, "0")}/${field.toString(16).padStart(4, "0")}`)
189
- }
190
-
191
- function tenantPrefix(root: string, tenant: string): string {
192
- if (!segmentOk(tenant)) {
193
- throw errors.new(`tenant id is not a single path segment: ${tenant}`)
194
- }
195
- return assemble(root, `t/${tenant}`)
196
- }
197
-
198
- export type { Generation, StoreKey }
199
- export {
200
- CKPT_SCRATCH_LEASE,
201
- checkpointMdbKey,
202
- ckptDocKey,
203
- encodeCkptScratch,
204
- generation,
205
- idsKey,
206
- LEASE_NAMESPACE,
207
- logKey,
208
- manifestKey,
209
- parseCkptScratch,
210
- parsePrefix,
211
- reservedLease,
212
- reservedName,
213
- reservedTemp,
214
- scratchCkptName,
215
- storeKey,
216
- TEMP_NAMESPACE,
217
- tenantPrefix
218
- }
package/src/manifest.ts DELETED
@@ -1,161 +0,0 @@
1
- /**
2
- * The protocol's one mutable object and its checkpoint, as typed
3
- * payloads. The byte grammar has one implementation —
4
- * `crates/bumbledb-log` behind the napi bridge — so parse and render
5
- * here are marshal walks: branded `Digest32` and `Braid` values in,
6
- * bridge-tagged payloads across, never a second reader of the bytes.
7
- * The vector algebra over checkpoint heads and the seed transition's
8
- * catalog audit are machine logic and stay here.
9
- */
10
-
11
- import type { LogCheckpointDoc, LogCheckpointKind, LogCodecHandle, LogManifestKind } from "@bjornpagen/bumbledb"
12
- import {
13
- internalLogParseCheckpoint,
14
- internalLogParseManifest,
15
- internalLogRenderCheckpoint,
16
- internalLogRenderManifest
17
- } from "@bjornpagen/bumbledb"
18
- import type { Digest32 } from "#bytes.ts"
19
- import { bytesEqual, digest32, hex32 } from "#bytes.ts"
20
- import type { Braid } from "#descriptor.ts"
21
- import { braidHex } from "#descriptor.ts"
22
- import { refuse } from "#errors.ts"
23
- import type { Generation } from "#keys.ts"
24
- import { generation } from "#keys.ts"
25
- import { Vector } from "#vector.ts"
26
-
27
- interface Manifest {
28
- readonly fingerprint: Digest32
29
- readonly checkpoint: Digest32 | null
30
- }
31
-
32
- /**
33
- * Remints a bridge refusal row as the driver's typed refusal. The
34
- * boundary carries `{ kind, message }` only: the kind is the log
35
- * core's own identity string, so the cause payload holds the data this
36
- * side owns — the document's length, its version byte (byte 0 of every
37
- * v:3 document) — and nothing invented. The raw braid id of an
38
- * `UnknownBraid` rides the message; the drifted set of a `BraidSet`
39
- * rides the message the same way.
40
- */
41
- function refuseBridged(kind: LogManifestKind | LogCheckpointKind, message: string, bytes: Uint8Array): never {
42
- switch (kind) {
43
- case "Version":
44
- return refuse({ kind: "Version", version: bytes[0] ?? 0 }, message)
45
- case "Overflow":
46
- return refuse({ kind: "Overflow" }, message)
47
- case "UnknownBraid":
48
- return refuse({ kind: "UnknownBraid" }, message)
49
- case "BraidSet":
50
- return refuse({ kind: "BraidSet" }, message)
51
- case "Malformed":
52
- return refuse({ kind: "Malformed", at: bytes.length }, message)
53
- }
54
- }
55
-
56
- function braidIdOf(id: Braid): number {
57
- return Number.parseInt(id.slice(1), 16)
58
- }
59
-
60
- function renderManifest(manifest: Manifest): Uint8Array {
61
- return internalLogRenderManifest(
62
- manifest.checkpoint === null
63
- ? { fingerprint: manifest.fingerprint }
64
- : { fingerprint: manifest.fingerprint, checkpoint: manifest.checkpoint }
65
- )
66
- }
67
-
68
- function parseManifest(bytes: Uint8Array): Manifest {
69
- const parsed = internalLogParseManifest(bytes)
70
- if (!parsed.ok) {
71
- refuseBridged(parsed.kind, parsed.message, bytes)
72
- }
73
- return {
74
- fingerprint: digest32(parsed.value.fingerprint),
75
- checkpoint: parsed.value.checkpoint === undefined ? null : digest32(parsed.value.checkpoint)
76
- }
77
- }
78
-
79
- interface CheckpointHead {
80
- readonly g: Generation
81
- readonly hash: Digest32
82
- readonly ts: bigint
83
- }
84
-
85
- interface CheckpointFacts {
86
- readonly braids: ReadonlyMap<Braid, CheckpointHead>
87
- readonly catalog: Digest32
88
- readonly writer: bigint
89
- readonly prev: Digest32 | null
90
- readonly sum: bigint
91
- }
92
-
93
- function vectorOfHeads(braids: ReadonlyMap<Braid, CheckpointHead>): Vector {
94
- const counts = new Map<Braid, bigint>()
95
- for (const [id, head] of braids) {
96
- counts.set(id, head.g)
97
- }
98
- return Vector.from(counts)
99
- }
100
-
101
- function checkpointVector(facts: CheckpointFacts): Vector {
102
- return vectorOfHeads(facts.braids)
103
- }
104
-
105
- function renderCheckpoint(codec: LogCodecHandle, facts: CheckpointFacts): Uint8Array {
106
- const braids = [...facts.braids.entries()]
107
- .sort(function ascending(a, b) {
108
- return braidIdOf(a[0]) - braidIdOf(b[0])
109
- })
110
- .map(function headOf([id, head]) {
111
- return { braid: braidIdOf(id), g: head.g, hash: head.hash, ts: head.ts }
112
- })
113
- const doc: LogCheckpointDoc =
114
- facts.prev === null
115
- ? { braids, catalog: facts.catalog, writer: facts.writer }
116
- : { braids, catalog: facts.catalog, writer: facts.writer, prev: facts.prev }
117
- return internalLogRenderCheckpoint(codec, doc)
118
- }
119
-
120
- function parseCheckpoint(codec: LogCodecHandle, bytes: Uint8Array): CheckpointFacts {
121
- const parsed = internalLogParseCheckpoint(codec, bytes)
122
- if (!parsed.ok) {
123
- refuseBridged(parsed.kind, parsed.message, bytes)
124
- }
125
- const braids = new Map<Braid, CheckpointHead>()
126
- for (const head of parsed.value.braids) {
127
- braids.set(braidHex(head.braid), { g: generation(head.g), hash: digest32(head.hash), ts: head.ts })
128
- }
129
- /** The core refuses `Overflow` at parse; the narrowing here is the
130
- * derived sum's own type gate over the same heads. */
131
- const summed = vectorOfHeads(braids).sum()
132
- if (typeof summed !== "bigint") {
133
- refuse({ kind: "Overflow" }, "checkpoint vector sum overflows u64")
134
- }
135
- return {
136
- braids,
137
- catalog: digest32(parsed.value.catalog),
138
- writer: parsed.value.writer,
139
- prev: parsed.value.prev === undefined ? null : digest32(parsed.value.prev),
140
- sum: summed
141
- }
142
- }
143
-
144
- /** The seed transition's catalog claim: a present checkpoint
145
- * compares the opened store's `catalog_digest` against the document.
146
- * Genesis (no checkpoint) has no claim. A mismatch names the publisher. */
147
- function auditCatalog(facts: CheckpointFacts | null, computed: Digest32): void {
148
- if (facts === null) {
149
- return
150
- }
151
- if (bytesEqual(facts.catalog, computed)) {
152
- return
153
- }
154
- refuse(
155
- { kind: "CheckpointDigest", expected: hex32(facts.catalog), computed: hex32(computed) },
156
- `checkpoint catalog ${hex32(facts.catalog)} disagrees with the opened store ${hex32(computed)}; publisher ${facts.writer}`
157
- )
158
- }
159
-
160
- export type { CheckpointFacts, CheckpointHead }
161
- export { auditCatalog, checkpointVector, parseCheckpoint, parseManifest, renderCheckpoint, renderManifest }