@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/errors.ts
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* causes carried as data properties read back with the `*Of` accessors —
|
|
5
5
|
* never by message-string matching. There is deliberately no
|
|
6
6
|
* `ErrAlreadyApplied`: the state it would name is absorbed by idempotent
|
|
7
|
-
* replay (L10) and never surfaces. ManifestMissing,
|
|
8
|
-
* Exhausted, and SlotRetired are named sums of their own: a
|
|
9
|
-
* without a manifest,
|
|
10
|
-
*
|
|
7
|
+
* replay (L10) and never surfaces. ManifestMissing, OverWidth,
|
|
8
|
+
* RefillNeeded, Exhausted, and SlotRetired are named sums of their own: a
|
|
9
|
+
* replica without a manifest, a draw past the lease width, a cached id
|
|
10
|
+
* block too short for the draw, the u64 ceiling, and a below-floor
|
|
11
|
+
* create. Exhaustion means the id space is spent — a refill is never an
|
|
12
|
+
* exhaustion. An unproved conditional write is the store verbs' own
|
|
13
|
+
* `ambiguous` outcome arm, never an error identity.
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import * as errors from "@superbuilders/errors"
|
|
@@ -26,9 +29,6 @@ const ErrSpanningCommit = errors.new(
|
|
|
26
29
|
"bumbledb-log spanningCommit: the recorded ops span braids — commitSplit is the explicit verb"
|
|
27
30
|
)
|
|
28
31
|
|
|
29
|
-
/** 404 at or below the current checkpoint's vector: the tail was gc'd. */
|
|
30
|
-
const ErrGapDetected = errors.new("bumbledb-log gapDetected: the log tail below the checkpoint vector was collected")
|
|
31
|
-
|
|
32
32
|
/** A rejected replay on a store that passed the wholeness check. */
|
|
33
33
|
const ErrReplayDiverged = errors.new(
|
|
34
34
|
"bumbledb-log replayDiverged: a published batch rejected during steady-state replay"
|
|
@@ -43,16 +43,16 @@ const ErrContention = errors.new("bumbledb-log contention: consecutive live-tip
|
|
|
43
43
|
/** A replica (or any read-role handle) found no manifest; only the writer births a store. */
|
|
44
44
|
export const ErrManifestMissing = errors.new("bumbledb-log manifestMissing: the store has no manifest")
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* A conditional write the transport cannot prove (S3 409, timeout, a
|
|
48
|
-
* retried PUT). The machine GET-verifies; this identity is the unproved
|
|
49
|
-
* arm, never a proved Exists or Moved.
|
|
50
|
-
*/
|
|
51
|
-
const ErrAmbiguous = errors.new("bumbledb-log ambiguous: the conditional write is unproved")
|
|
52
|
-
|
|
53
46
|
/** A single id draw larger than one lease width; the width is the one block size. */
|
|
54
47
|
const ErrOverWidth = errors.new("bumbledb-log overWidth: the draw exceeds the lease width")
|
|
55
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The cached id block is too short for the draw — the writer's signal
|
|
51
|
+
* to lease a fresh block. A refill is not an exhaustion: the id space
|
|
52
|
+
* still has room; only the cache is short.
|
|
53
|
+
*/
|
|
54
|
+
const ErrRefillNeeded = errors.new("bumbledb-log refillNeeded: the cached id block cannot cover the draw")
|
|
55
|
+
|
|
56
56
|
/** The next lease would leave u64 — the id space is spent. */
|
|
57
57
|
const ErrExhausted = errors.new("bumbledb-log exhausted: the id space is spent")
|
|
58
58
|
|
|
@@ -63,34 +63,61 @@ const ErrSlotRetired = errors.new("bumbledb-log slotRetired: the slot is retired
|
|
|
63
63
|
const ErrStore = errors.new("bumbledb-log store failure")
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* The typed causes behind `ErrRefused`, one arm per identity. Every
|
|
67
|
+
* `kind` is either a row of the generated identity table
|
|
68
|
+
* (`crates/bumbledb-log/conformance/v3/identities.json`, spelled by the
|
|
69
|
+
* core's one speller per family) or one of the three host-side kinds at
|
|
70
|
+
* the tail, which cross no bridge. The boundary carries
|
|
71
|
+
* `{ kind, message }` only, so a bridge-minted cause is the bare kind —
|
|
72
|
+
* the detail rides the message — and a payload field exists only where
|
|
73
|
+
* the minting seat owns the datum: a held document's version byte
|
|
74
|
+
* (`version`, byte 0 of every v:3 document) and length (`at`), the
|
|
75
|
+
* codec seat's own arity coordinates, the manifest audit's
|
|
76
|
+
* fingerprints, the counter object's key. The raw id of an
|
|
77
|
+
* `UnknownBraid` rides the message.
|
|
70
78
|
*/
|
|
71
79
|
type RefusalCause =
|
|
72
|
-
|
|
80
|
+
// The batch grammar's rows (`batchDecode` | `batchEncode`), bridge
|
|
81
|
+
// order. `Version` carries the version byte where a document seat
|
|
82
|
+
// holds the bytes; `FingerprintMismatch` carries both digests where
|
|
83
|
+
// the replica's manifest audit owns them; `Arity` carries its
|
|
84
|
+
// coordinates where the codec seat's pre-bridge cell gate owns them.
|
|
85
|
+
| { readonly kind: "Truncated" }
|
|
73
86
|
| { readonly kind: "BadMagic" }
|
|
74
|
-
| { readonly kind: "Version"; readonly version
|
|
75
|
-
| { readonly kind: "Flags"
|
|
76
|
-
| { readonly kind: "FingerprintMismatch"; readonly carried
|
|
77
|
-
| { readonly kind: "UnknownBraid"
|
|
78
|
-
| { readonly kind: "UnknownOpKind"
|
|
79
|
-
| { readonly kind: "UnknownRelation"
|
|
80
|
-
| { readonly kind: "ClosedRelation"
|
|
81
|
-
| { readonly kind: "OpRelationOutsideBraid"
|
|
82
|
-
| { readonly kind: "TagMismatch"
|
|
83
|
-
| { readonly kind: "BoolByte"
|
|
84
|
-
| { readonly kind: "InvalidUtf8"
|
|
85
|
-
| { readonly kind: "EmptyInterval"
|
|
86
|
-
| { readonly kind: "IntervalOverflow"
|
|
87
|
-
| { readonly kind: "TrailingBytes"
|
|
88
|
-
| { readonly kind: "
|
|
89
|
-
| { readonly kind: "
|
|
87
|
+
| { readonly kind: "Version"; readonly version?: number }
|
|
88
|
+
| { readonly kind: "Flags" }
|
|
89
|
+
| { readonly kind: "FingerprintMismatch"; readonly carried?: string; readonly expected?: string }
|
|
90
|
+
| { readonly kind: "UnknownBraid" }
|
|
91
|
+
| { readonly kind: "UnknownOpKind" }
|
|
92
|
+
| { readonly kind: "UnknownRelation" }
|
|
93
|
+
| { readonly kind: "ClosedRelation" }
|
|
94
|
+
| { readonly kind: "OpRelationOutsideBraid" }
|
|
95
|
+
| { readonly kind: "TagMismatch" }
|
|
96
|
+
| { readonly kind: "BoolByte" }
|
|
97
|
+
| { readonly kind: "InvalidUtf8" }
|
|
98
|
+
| { readonly kind: "EmptyInterval" }
|
|
99
|
+
| { readonly kind: "IntervalOverflow" }
|
|
100
|
+
| { readonly kind: "TrailingBytes" }
|
|
101
|
+
| { readonly kind: "Arity"; readonly op?: number; readonly relation?: string; readonly row?: number }
|
|
102
|
+
| { readonly kind: "Value" }
|
|
103
|
+
| { readonly kind: "TooManyOps" }
|
|
104
|
+
| { readonly kind: "TooManyRows" }
|
|
105
|
+
// The document grammars' own rows (`manifest` | `checkpoint` |
|
|
106
|
+
// `sidecar`): the seat holds the refused bytes, so `Malformed`
|
|
107
|
+
// carries the document's length.
|
|
90
108
|
| { readonly kind: "Malformed"; readonly at: number }
|
|
91
109
|
| { readonly kind: "Overflow" }
|
|
92
|
-
| { readonly kind: "
|
|
93
|
-
|
|
110
|
+
| { readonly kind: "BraidSet" }
|
|
111
|
+
// The id-lease counter's row (`counter`): the writer's parse gate
|
|
112
|
+
// owns the counter object's key. `Exhausted` and `OverWidth` are
|
|
113
|
+
// `LeaseRefusal` arms with sentinels of their own.
|
|
114
|
+
| { readonly kind: "Counter"; readonly key: StoreKey }
|
|
115
|
+
// Host-side kinds with no table row: `DigestWidth` refuses a short
|
|
116
|
+
// digest at the seat before a value crosses corrupted (the corpus
|
|
117
|
+
// pins the name); `CheckpointDigest` and `NoOpSlot` are the replica
|
|
118
|
+
// machine's own refusals — the machines keep two executors and no
|
|
119
|
+
// bridge family.
|
|
120
|
+
| { readonly kind: "DigestWidth" }
|
|
94
121
|
| { readonly kind: "CheckpointDigest"; readonly expected: string; readonly computed: string }
|
|
95
122
|
| { readonly kind: "NoOpSlot"; readonly braid: Braid; readonly slot: Generation; readonly writer: bigint }
|
|
96
123
|
|
|
@@ -116,14 +143,13 @@ interface ContentionData {
|
|
|
116
143
|
readonly cause: ContentionCause
|
|
117
144
|
}
|
|
118
145
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
interface AmbiguousData {
|
|
122
|
-
readonly verb: AmbiguousVerb
|
|
123
|
-
readonly key: StoreKey
|
|
146
|
+
interface OverWidthData {
|
|
147
|
+
readonly requested: bigint
|
|
124
148
|
}
|
|
125
149
|
|
|
126
|
-
interface
|
|
150
|
+
interface RefillNeededData {
|
|
151
|
+
readonly relation: number
|
|
152
|
+
readonly field: number
|
|
127
153
|
readonly requested: bigint
|
|
128
154
|
}
|
|
129
155
|
|
|
@@ -137,16 +163,21 @@ interface SlotRetiredData {
|
|
|
137
163
|
readonly slot: Generation
|
|
138
164
|
}
|
|
139
165
|
|
|
140
|
-
/** The id-lease algebra's refusal
|
|
166
|
+
/** The id-lease algebra's refusal sum — the identity table's `counter`
|
|
167
|
+
* family, arm for arm: Counter | Exhausted | OverWidth. `Counter`
|
|
168
|
+
* carries the counter object's key, the datum this driver's parse gate
|
|
169
|
+
* owns (the core's twin carries relation and field). A cache refill is
|
|
170
|
+
* no arm: the shared algebra has none. */
|
|
141
171
|
type LeaseRefusal =
|
|
142
|
-
| { readonly kind: "
|
|
172
|
+
| { readonly kind: "Counter"; readonly key: StoreKey }
|
|
143
173
|
| { readonly kind: "Exhausted"; readonly relation: number; readonly field: number }
|
|
174
|
+
| { readonly kind: "OverWidth"; readonly requested: bigint }
|
|
144
175
|
|
|
145
176
|
const refusalData = new WeakMap<Error, RefusalCause>()
|
|
146
177
|
const chainData = new WeakMap<Error, ChainMismatchData>()
|
|
147
178
|
const contentionData = new WeakMap<Error, ContentionData>()
|
|
148
|
-
const ambiguousData = new WeakMap<Error, AmbiguousData>()
|
|
149
179
|
const overWidthData = new WeakMap<Error, OverWidthData>()
|
|
180
|
+
const refillNeededData = new WeakMap<Error, RefillNeededData>()
|
|
150
181
|
const exhaustedData = new WeakMap<Error, ExhaustedData>()
|
|
151
182
|
const slotRetiredData = new WeakMap<Error, SlotRetiredData>()
|
|
152
183
|
|
|
@@ -176,18 +207,18 @@ function isManifestMissing(error: unknown): boolean {
|
|
|
176
207
|
return error instanceof Error && errors.is(error, ErrManifestMissing)
|
|
177
208
|
}
|
|
178
209
|
|
|
179
|
-
function throwAmbiguous(data: AmbiguousData, detail: string): never {
|
|
180
|
-
const error = errors.wrap(ErrAmbiguous, detail)
|
|
181
|
-
ambiguousData.set(error, data)
|
|
182
|
-
throw error
|
|
183
|
-
}
|
|
184
|
-
|
|
185
210
|
function refuseOverWidth(data: OverWidthData, detail: string): never {
|
|
186
211
|
const error = errors.wrap(ErrOverWidth, detail)
|
|
187
212
|
overWidthData.set(error, data)
|
|
188
213
|
throw error
|
|
189
214
|
}
|
|
190
215
|
|
|
216
|
+
function throwRefillNeeded(data: RefillNeededData, detail: string): never {
|
|
217
|
+
const error = errors.wrap(ErrRefillNeeded, detail)
|
|
218
|
+
refillNeededData.set(error, data)
|
|
219
|
+
throw error
|
|
220
|
+
}
|
|
221
|
+
|
|
191
222
|
function refuseExhausted(data: ExhaustedData, detail: string): never {
|
|
192
223
|
const error = errors.wrap(ErrExhausted, detail)
|
|
193
224
|
exhaustedData.set(error, data)
|
|
@@ -212,14 +243,14 @@ function contentionOf(error: Error): ContentionData | undefined {
|
|
|
212
243
|
return contentionData.get(error)
|
|
213
244
|
}
|
|
214
245
|
|
|
215
|
-
function ambiguousOf(error: Error): AmbiguousData | undefined {
|
|
216
|
-
return ambiguousData.get(error)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
246
|
function overWidthOf(error: Error): OverWidthData | undefined {
|
|
220
247
|
return overWidthData.get(error)
|
|
221
248
|
}
|
|
222
249
|
|
|
250
|
+
function refillNeededOf(error: Error): RefillNeededData | undefined {
|
|
251
|
+
return refillNeededData.get(error)
|
|
252
|
+
}
|
|
253
|
+
|
|
223
254
|
function exhaustedOf(error: Error): ExhaustedData | undefined {
|
|
224
255
|
return exhaustedData.get(error)
|
|
225
256
|
}
|
|
@@ -236,8 +267,6 @@ function wrapStore(inner: Error, detail: string): Error {
|
|
|
236
267
|
}
|
|
237
268
|
|
|
238
269
|
export type {
|
|
239
|
-
AmbiguousData,
|
|
240
|
-
AmbiguousVerb,
|
|
241
270
|
ChainCause,
|
|
242
271
|
ChainMismatchData,
|
|
243
272
|
ContentionCause,
|
|
@@ -245,19 +274,18 @@ export type {
|
|
|
245
274
|
ExhaustedData,
|
|
246
275
|
LeaseRefusal,
|
|
247
276
|
OverWidthData,
|
|
277
|
+
RefillNeededData,
|
|
248
278
|
RefusalCause,
|
|
249
279
|
SlotRetiredData
|
|
250
280
|
}
|
|
251
281
|
export {
|
|
252
|
-
ambiguousOf,
|
|
253
282
|
chainMismatchOf,
|
|
254
283
|
contentionOf,
|
|
255
|
-
ErrAmbiguous,
|
|
256
284
|
ErrChainMismatch,
|
|
257
285
|
ErrContention,
|
|
258
286
|
ErrExhausted,
|
|
259
|
-
ErrGapDetected,
|
|
260
287
|
ErrOverWidth,
|
|
288
|
+
ErrRefillNeeded,
|
|
261
289
|
ErrRefused,
|
|
262
290
|
ErrReplayDiverged,
|
|
263
291
|
ErrSlotRetired,
|
|
@@ -266,6 +294,7 @@ export {
|
|
|
266
294
|
exhaustedOf,
|
|
267
295
|
isManifestMissing,
|
|
268
296
|
overWidthOf,
|
|
297
|
+
refillNeededOf,
|
|
269
298
|
refusalOf,
|
|
270
299
|
refuse,
|
|
271
300
|
refuseChain,
|
|
@@ -274,7 +303,7 @@ export {
|
|
|
274
303
|
refuseOverWidth,
|
|
275
304
|
refuseSlotRetired,
|
|
276
305
|
slotRetiredOf,
|
|
277
|
-
throwAmbiguous,
|
|
278
306
|
throwContention,
|
|
307
|
+
throwRefillNeeded,
|
|
279
308
|
wrapStore
|
|
280
309
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @bjornpagen/bumbledb-log — braided object-store replication for
|
|
3
|
-
* bumbledb, a thin peer of the engine SDK.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* with its tier-1 `fsStore`,
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
3
|
+
* bumbledb, a thin peer of the engine SDK. The protocol grammar has one
|
|
4
|
+
* implementation, `crates/bumbledb-log`, reached through the engine
|
|
5
|
+
* bridge; this package is typed payload construction, the replica and
|
|
6
|
+
* writer machines, the five-verb object store with its tier-1 `fsStore`,
|
|
7
|
+
* in-process `memStore`, and the `s3Store` AWS S3 client, and
|
|
8
|
+
* `openReplica`/`openWriter`/`openTenants` composed from the engine
|
|
9
|
+
* SDK's own verbs — the replica hands out the SDK's `Db`, and no engine
|
|
10
|
+
* surface is duplicated. Engine types (`FactValue`, `IntervalValue`, …)
|
|
11
|
+
* are the peer `@bjornpagen/bumbledb`'s own exports, never re-exported
|
|
12
|
+
* here.
|
|
9
13
|
*/
|
|
10
14
|
|
|
11
15
|
export type { Braid } from "#braids.ts"
|
|
@@ -20,7 +24,6 @@ export {
|
|
|
20
24
|
contentionOf,
|
|
21
25
|
ErrChainMismatch,
|
|
22
26
|
ErrContention,
|
|
23
|
-
ErrGapDetected,
|
|
24
27
|
ErrManifestMissing,
|
|
25
28
|
ErrRefused,
|
|
26
29
|
ErrReplayDiverged,
|
|
@@ -30,24 +33,25 @@ export {
|
|
|
30
33
|
refusalOf
|
|
31
34
|
} from "#errors.ts"
|
|
32
35
|
export type { Generation, StoreKey } from "#keys.ts"
|
|
33
|
-
export {
|
|
34
|
-
|
|
35
|
-
encodeCkptScratch,
|
|
36
|
-
generation,
|
|
37
|
-
LEASE_NAMESPACE,
|
|
38
|
-
parseCkptScratch,
|
|
39
|
-
scratchCkptDigest,
|
|
40
|
-
scratchCkptName,
|
|
41
|
-
storeKey
|
|
42
|
-
} from "#keys.ts"
|
|
43
|
-
export type { OpenReplicaOptions, Replica } from "#replica.ts"
|
|
36
|
+
export { generation, storeKey } from "#keys.ts"
|
|
37
|
+
export type { OpenReplicaOptions, Replica, Waited } from "#replica.ts"
|
|
44
38
|
export { openReplica } from "#replica.ts"
|
|
45
39
|
export type { Create, Etag, Fetched, ObjectStore, Poll, S3Config, S3Credentials, Swap } from "#store.ts"
|
|
46
40
|
export { etag, fsStore, memStore, s3Store } from "#store.ts"
|
|
47
41
|
export type { OpenTenantsOptions, Tenants } from "#tenants.ts"
|
|
48
42
|
export { openTenants } from "#tenants.ts"
|
|
49
|
-
export type {
|
|
50
|
-
export type { CheckpointOrder, VectorParse } from "#vector.ts"
|
|
43
|
+
export type { CheckpointOrder } from "#vector.ts"
|
|
51
44
|
export { Overflow, Vector } from "#vector.ts"
|
|
52
|
-
export type {
|
|
45
|
+
export type {
|
|
46
|
+
Batch,
|
|
47
|
+
BraidOutcome,
|
|
48
|
+
Commit,
|
|
49
|
+
CommitReceipt,
|
|
50
|
+
CommitSplit,
|
|
51
|
+
Deposition,
|
|
52
|
+
Durability,
|
|
53
|
+
EmptyCommit,
|
|
54
|
+
Landing,
|
|
55
|
+
Writer
|
|
56
|
+
} from "#writer.ts"
|
|
53
57
|
export { openWriter } from "#writer.ts"
|
package/src/keys.ts
CHANGED
|
@@ -4,46 +4,61 @@
|
|
|
4
4
|
* StoreKey is parsed here, once — the verbs take the proof and never
|
|
5
5
|
* re-check. An empty prefix joins as the rest alone, so a leading
|
|
6
6
|
* slash is unrepresentable. Temps and leases live under a reserved
|
|
7
|
-
* tilde-family first segment no StoreKey can spell —
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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.
|
|
11
13
|
*/
|
|
12
14
|
|
|
15
|
+
import * as fs from "node:fs"
|
|
16
|
+
import { internalLogParseCkptScratch, internalLogRenderCkptScratch } from "@bjornpagen/bumbledb"
|
|
13
17
|
import * as errors from "@superbuilders/errors"
|
|
14
18
|
import { regex } from "arkregex"
|
|
15
19
|
import type { Digest32 } from "#bytes.ts"
|
|
16
20
|
import { digest32, hex32, U64_MAX } from "#bytes.ts"
|
|
17
21
|
import type { Braid } from "#descriptor.ts"
|
|
18
22
|
|
|
19
|
-
/** A segment wearing this suffix
|
|
23
|
+
/** A segment wearing this suffix is not a key. */
|
|
20
24
|
const LOCK_SUFFIX = ".lock"
|
|
21
25
|
|
|
22
26
|
/** Reserved first-segment names no StoreKey can spell. Temps and leases live here. */
|
|
23
27
|
const TEMP_NAMESPACE = "~tmp"
|
|
24
28
|
const LEASE_NAMESPACE = "~lease"
|
|
25
29
|
|
|
26
|
-
/**
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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()
|
|
44
60
|
|
|
45
61
|
const FORMAT_OR_SEPARATOR = regex("[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Zs}]", "u")
|
|
46
|
-
const FORMAT_CHARS = regex("\\p{Cf}", "gu")
|
|
47
62
|
const SLASH_TRIM = regex("^/+|/+$", "g")
|
|
48
63
|
|
|
49
64
|
declare const storeKeyBrand: unique symbol
|
|
@@ -64,11 +79,7 @@ function tildeFamilyPrefix(seg: string): boolean {
|
|
|
64
79
|
if (first === undefined) {
|
|
65
80
|
return false
|
|
66
81
|
}
|
|
67
|
-
return TILDE_FAMILY.has(first)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function stripFormat(seg: string): string {
|
|
71
|
-
return seg.replace(FORMAT_CHARS, "")
|
|
82
|
+
return TILDE_FAMILY.has(first)
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
/** One path segment of a key or a tenant id: the same grammar. */
|
|
@@ -79,11 +90,7 @@ function segmentOk(seg: string): boolean {
|
|
|
79
90
|
if (FORMAT_OR_SEPARATOR.test(seg)) {
|
|
80
91
|
return false
|
|
81
92
|
}
|
|
82
|
-
|
|
83
|
-
if (stripped.length === 0 || stripped === "." || stripped === "..") {
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
86
|
-
return !tildeFamilyPrefix(stripped) && !stripped.endsWith(LOCK_SUFFIX)
|
|
93
|
+
return !tildeFamilyPrefix(seg) && !seg.endsWith(LOCK_SUFFIX)
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
function storeKey(raw: string): StoreKey {
|
|
@@ -102,10 +109,6 @@ function parsePrefix(raw: string): string {
|
|
|
102
109
|
return storeKey(raw.replace(SLASH_TRIM, ""))
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
function isReservedName(name: string): boolean {
|
|
106
|
-
return tildeFamilyPrefix(stripFormat(name))
|
|
107
|
-
}
|
|
108
|
-
|
|
109
112
|
function reservedTemp(pid: number, seq: number): string {
|
|
110
113
|
return `${TEMP_NAMESPACE}/${String(pid)}.${String(seq)}`
|
|
111
114
|
}
|
|
@@ -135,30 +138,19 @@ function scratchCkptName(): string {
|
|
|
135
138
|
return reservedName(`${LEASE_NAMESPACE}/${CKPT_SCRATCH_LEASE}`)
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
/** The scratch-lease body: version byte 3, then the 32-byte digest. */
|
|
141
|
+
/** The scratch-lease body, spelled by the one grammar (`crates/bumbledb-log`). */
|
|
141
142
|
function encodeCkptScratch(digest: Digest32): Uint8Array {
|
|
142
|
-
|
|
143
|
-
out[0] = SCRATCH_VERSION
|
|
144
|
-
out.set(digest, 1)
|
|
145
|
-
return out
|
|
143
|
+
return internalLogRenderCkptScratch(digest)
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
/** The digest a scratch-lease body names, or null
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const version = bytes[0]
|
|
154
|
-
if (version !== SCRATCH_VERSION) {
|
|
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) {
|
|
155
151
|
return null
|
|
156
152
|
}
|
|
157
|
-
return digest32(
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function parseCkptScratch(bytes: Uint8Array): Digest32 | null {
|
|
161
|
-
return scratchCkptDigest(bytes)
|
|
153
|
+
return digest32(named)
|
|
162
154
|
}
|
|
163
155
|
|
|
164
156
|
function generation(raw: bigint): Generation {
|
|
@@ -210,11 +202,8 @@ export {
|
|
|
210
202
|
ckptDocKey,
|
|
211
203
|
encodeCkptScratch,
|
|
212
204
|
generation,
|
|
213
|
-
hex16,
|
|
214
205
|
idsKey,
|
|
215
|
-
isReservedName,
|
|
216
206
|
LEASE_NAMESPACE,
|
|
217
|
-
LOCK_SUFFIX,
|
|
218
207
|
logKey,
|
|
219
208
|
manifestKey,
|
|
220
209
|
parseCkptScratch,
|
|
@@ -222,9 +211,7 @@ export {
|
|
|
222
211
|
reservedLease,
|
|
223
212
|
reservedName,
|
|
224
213
|
reservedTemp,
|
|
225
|
-
scratchCkptDigest,
|
|
226
214
|
scratchCkptName,
|
|
227
|
-
segmentOk,
|
|
228
215
|
storeKey,
|
|
229
216
|
TEMP_NAMESPACE,
|
|
230
217
|
tenantPrefix
|