@bjornpagen/bumbledb-log 0.20.1 → 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/vector.d.ts +31 -0
  54. package/dist/vector.d.ts.map +1 -0
  55. package/dist/vector.js +87 -0
  56. package/dist/vector.js.map +1 -0
  57. package/dist/writer.d.ts +106 -0
  58. package/dist/writer.d.ts.map +1 -0
  59. package/dist/writer.js +677 -0
  60. package/dist/writer.js.map +1 -0
  61. package/package.json +12 -7
  62. package/src/bytes.ts +0 -115
  63. package/src/chain.ts +0 -181
  64. package/src/codec.ts +0 -252
  65. package/src/descriptor.ts +0 -240
  66. package/src/errors.ts +0 -309
  67. package/src/index.ts +0 -57
  68. package/src/keys.ts +0 -224
  69. package/src/manifest.ts +0 -161
  70. package/src/replica.ts +0 -1061
  71. package/src/store-s3.ts +0 -377
  72. package/src/store.ts +0 -727
  73. package/src/tenants.ts +0 -414
  74. package/src/vector.ts +0 -103
  75. package/src/writer.ts +0 -977
  76. /package/{src → dist}/tilde-family.json +0 -0
package/src/keys.ts DELETED
@@ -1,224 +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
- /**
31
- * The one tilde table both drivers consume: ASCII `~`, its lookalikes, and
32
- * the NFKC preimage of U+007E. The packaged copy beside this module is the
33
- * census-pinned byte-identical twin of the generator's output at
34
- * crates/bumbledb-log/conformance/v3/keys/tilde-family.json, so the read
35
- * never leaves the published files roster.
36
- */
37
- const TILDE_TABLE_URL = new URL("./tilde-family.json", import.meta.url)
38
-
39
- const CODE_POINT_SPELLING = regex("^U\\+[0-9A-F]{4,6}$")
40
-
41
- function loadTildeFamily(): Set<string> {
42
- const raw = fs.readFileSync(TILDE_TABLE_URL, "utf8")
43
- const table: unknown = JSON.parse(raw)
44
- if (typeof table !== "object" || table === null || !("codePoints" in table)) {
45
- throw errors.new(`tilde table is not a codePoints document: ${TILDE_TABLE_URL.pathname}`)
46
- }
47
- const { codePoints } = table
48
- if (!Array.isArray(codePoints) || codePoints.length === 0) {
49
- throw errors.new(`tilde table codePoints is not a nonempty array: ${TILDE_TABLE_URL.pathname}`)
50
- }
51
- const family = new Set<string>()
52
- for (const spelling of codePoints) {
53
- if (typeof spelling !== "string" || !CODE_POINT_SPELLING.test(spelling)) {
54
- throw errors.new(`tilde table entry is not a U+XXXX code point: ${String(spelling)}`)
55
- }
56
- family.add(String.fromCodePoint(Number.parseInt(spelling.slice(2), 16)))
57
- }
58
- if (!family.has("~")) {
59
- throw errors.new(`tilde table does not reserve ASCII tilde: ${TILDE_TABLE_URL.pathname}`)
60
- }
61
- return family
62
- }
63
-
64
- /** First-segment code points that spell a reserved name. The table is the set; no local spelling. */
65
- const TILDE_FAMILY = loadTildeFamily()
66
-
67
- const FORMAT_OR_SEPARATOR = regex("[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Zs}]", "u")
68
- const SLASH_TRIM = regex("^/+|/+$", "g")
69
-
70
- declare const storeKeyBrand: unique symbol
71
- type StoreKey = string & { readonly [storeKeyBrand]: typeof storeKeyBrand }
72
-
73
- declare const generationBrand: unique symbol
74
- type Generation = bigint & { readonly [generationBrand]: typeof generationBrand }
75
-
76
- function firstCodePoint(seg: string): string | undefined {
77
- for (const ch of seg) {
78
- return ch
79
- }
80
- return undefined
81
- }
82
-
83
- function tildeFamilyPrefix(seg: string): boolean {
84
- const first = firstCodePoint(seg)
85
- if (first === undefined) {
86
- return false
87
- }
88
- return TILDE_FAMILY.has(first)
89
- }
90
-
91
- /** One path segment of a key or a tenant id: the same grammar. */
92
- function segmentOk(seg: string): boolean {
93
- if (seg.length === 0 || seg.includes("/") || seg === "." || seg === "..") {
94
- return false
95
- }
96
- if (FORMAT_OR_SEPARATOR.test(seg)) {
97
- return false
98
- }
99
- return !tildeFamilyPrefix(seg) && !seg.endsWith(LOCK_SUFFIX)
100
- }
101
-
102
- function storeKey(raw: string): StoreKey {
103
- const wellFormed = raw.length > 0 && !raw.startsWith("/") && !raw.endsWith("/") && raw.split("/").every(segmentOk)
104
- if (!wellFormed) {
105
- throw errors.new(`store key is not a slash path: ${raw}`)
106
- }
107
- return raw as StoreKey
108
- }
109
-
110
- /** Empty, or a StoreKey spelling (the same segment grammar, no leading or trailing slash). */
111
- function parsePrefix(raw: string): string {
112
- if (raw.length === 0) {
113
- return ""
114
- }
115
- return storeKey(raw.replace(SLASH_TRIM, ""))
116
- }
117
-
118
- function reservedTemp(pid: number, seq: number): string {
119
- return `${TEMP_NAMESPACE}/${String(pid)}.${String(seq)}`
120
- }
121
-
122
- function reservedLease(key: string, token: bigint): string {
123
- return `${LEASE_NAMESPACE}/${key}/${String(token)}`
124
- }
125
-
126
- /** A slash path whose first segment is `~tmp` or `~lease`. Not a StoreKey. */
127
- function reservedName(raw: string): string {
128
- const segs = raw.split("/")
129
- if (segs.length === 0 || segs.some((seg) => seg.length === 0 || seg === "." || seg === "..")) {
130
- throw errors.new(`reserved name is not a slash path: ${raw}`)
131
- }
132
- const first = segs[0]
133
- if (first !== TEMP_NAMESPACE && first !== LEASE_NAMESPACE) {
134
- throw errors.new(`reserved name is not under ${TEMP_NAMESPACE} or ${LEASE_NAMESPACE}: ${raw}`)
135
- }
136
- return raw
137
- }
138
-
139
- /** Known scratch-lease document under `~lease`. One path, no LIST. */
140
- const CKPT_SCRATCH_LEASE = "ckpt-scratch"
141
-
142
- /** The reserved scratch name: `~lease/ckpt-scratch`. */
143
- function scratchCkptName(): string {
144
- return reservedName(`${LEASE_NAMESPACE}/${CKPT_SCRATCH_LEASE}`)
145
- }
146
-
147
- /** The scratch-lease body, spelled by the one grammar (`crates/bumbledb-log`). */
148
- function encodeCkptScratch(digest: Digest32): Uint8Array {
149
- return internalLogRenderCkptScratch(digest)
150
- }
151
-
152
- /** The digest a scratch-lease body names, or null — the one grammar's
153
- * parse, branded; the refusal is undifferentiated by law. */
154
- function parseCkptScratch(bytes: Uint8Array): Digest32 | null {
155
- const named = internalLogParseCkptScratch(bytes)
156
- if (named === null) {
157
- return null
158
- }
159
- return digest32(named)
160
- }
161
-
162
- function generation(raw: bigint): Generation {
163
- if (raw < 0n || raw > U64_MAX) {
164
- throw errors.new(`generation is a u64: ${raw}`)
165
- }
166
- return raw as Generation
167
- }
168
-
169
- function hex16(g: Generation): string {
170
- return g.toString(16).padStart(16, "0")
171
- }
172
-
173
- function assemble(prefix: string, rest: string): StoreKey {
174
- return storeKey(prefix.length === 0 ? rest : `${prefix}/${rest}`)
175
- }
176
-
177
- function manifestKey(prefix: string): StoreKey {
178
- return assemble(prefix, "manifest")
179
- }
180
-
181
- function logKey(prefix: string, braid: Braid, g: Generation): StoreKey {
182
- return assemble(prefix, `log/${braid}/${hex16(g)}`)
183
- }
184
-
185
- function checkpointMdbKey(prefix: string, digest: Digest32): StoreKey {
186
- return assemble(prefix, `ckpt/${hex32(digest)}.mdb`)
187
- }
188
-
189
- function ckptDocKey(prefix: string, digest: Digest32): StoreKey {
190
- return assemble(prefix, `ckpt/${hex32(digest)}`)
191
- }
192
-
193
- function idsKey(prefix: string, relation: number, field: number): StoreKey {
194
- return assemble(prefix, `ids/${relation.toString(16).padStart(8, "0")}/${field.toString(16).padStart(4, "0")}`)
195
- }
196
-
197
- function tenantPrefix(root: string, tenant: string): string {
198
- if (!segmentOk(tenant)) {
199
- throw errors.new(`tenant id is not a single path segment: ${tenant}`)
200
- }
201
- return assemble(root, `t/${tenant}`)
202
- }
203
-
204
- export type { Generation, StoreKey }
205
- export {
206
- CKPT_SCRATCH_LEASE,
207
- checkpointMdbKey,
208
- ckptDocKey,
209
- encodeCkptScratch,
210
- generation,
211
- idsKey,
212
- LEASE_NAMESPACE,
213
- logKey,
214
- manifestKey,
215
- parseCkptScratch,
216
- parsePrefix,
217
- reservedLease,
218
- reservedName,
219
- reservedTemp,
220
- scratchCkptName,
221
- storeKey,
222
- TEMP_NAMESPACE,
223
- tenantPrefix
224
- }
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 }