@bjornpagen/bumbledb-log 0.18.0 → 0.19.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/README.md +5 -4
- package/package.json +4 -3
- package/src/braids.ts +17 -2
- package/src/bytes.ts +78 -10
- package/src/chain.ts +173 -56
- package/src/codec.ts +122 -41
- package/src/descriptor.ts +182 -107
- package/src/errors.ts +133 -5
- package/src/index.ts +15 -2
- package/src/keys.ts +161 -18
- package/src/manifest.ts +165 -73
- package/src/replica.ts +577 -264
- package/src/store-s3.ts +191 -36
- package/src/store.ts +459 -85
- package/src/tenants.ts +322 -26
- package/src/value.ts +65 -14
- package/src/vector.ts +173 -0
- package/src/writer.ts +453 -92
package/src/descriptor.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
} from "@bjornpagen/bumbledb"
|
|
20
20
|
import { internalDescriptor, lower } from "@bjornpagen/bumbledb"
|
|
21
21
|
import * as errors from "@superbuilders/errors"
|
|
22
|
+
import { regex } from "arkregex"
|
|
22
23
|
import { fromHex } from "#bytes.ts"
|
|
23
24
|
import type { Value } from "#value.ts"
|
|
24
25
|
|
|
@@ -79,18 +80,95 @@ type StatementInfo =
|
|
|
79
80
|
declare const braidBrand: unique symbol
|
|
80
81
|
type Braid = string & { readonly [braidBrand]: typeof braidBrand }
|
|
81
82
|
|
|
83
|
+
const BRAID_ID = regex("^c[0-9a-f]{8}$")
|
|
84
|
+
const ID_CLASS = regex("^(.*)\\.id$")
|
|
85
|
+
|
|
82
86
|
function braid(raw: string): Braid {
|
|
83
|
-
if (
|
|
87
|
+
if (!BRAID_ID.test(raw)) {
|
|
84
88
|
throw errors.new(`not a braid id: ${raw}`)
|
|
85
89
|
}
|
|
86
90
|
return raw as Braid
|
|
87
91
|
}
|
|
88
92
|
|
|
93
|
+
function refuseShape(why: string): never {
|
|
94
|
+
throw errors.new(`theory: ${why}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `{name}.id` — the closed relation's generator class. */
|
|
98
|
+
function idClassOwner(newtype: string): string | undefined {
|
|
99
|
+
const match = ID_CLASS.exec(newtype)
|
|
100
|
+
const owner = match?.[1]
|
|
101
|
+
if (owner === undefined || owner.length === 0) {
|
|
102
|
+
return undefined
|
|
103
|
+
}
|
|
104
|
+
return owner
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function indexRelations(relations: readonly RelationInfo[]): Map<number, RelationInfo> {
|
|
108
|
+
const byId = new Map<number, RelationInfo>()
|
|
109
|
+
for (const relation of relations) {
|
|
110
|
+
if (byId.has(relation.id)) {
|
|
111
|
+
refuseShape(`duplicate relation id ${relation.id}`)
|
|
112
|
+
}
|
|
113
|
+
byId.set(relation.id, relation)
|
|
114
|
+
}
|
|
115
|
+
return byId
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function relationOfId(byId: ReadonlyMap<number, RelationInfo>, id: number): RelationInfo {
|
|
119
|
+
const relation = byId.get(id)
|
|
120
|
+
if (relation === undefined) {
|
|
121
|
+
refuseShape(`unknown relation id ${id}`)
|
|
122
|
+
}
|
|
123
|
+
return relation
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function fieldNamed(relation: RelationInfo, name: string): { readonly ordinal: number; readonly field: FieldInfo } {
|
|
127
|
+
let ordinal = 0
|
|
128
|
+
for (const field of relation.fields) {
|
|
129
|
+
if (field.name === name) {
|
|
130
|
+
return { ordinal, field }
|
|
131
|
+
}
|
|
132
|
+
ordinal += 1
|
|
133
|
+
}
|
|
134
|
+
refuseShape(`relation ${relation.name} has no field ${name}`)
|
|
135
|
+
}
|
|
136
|
+
|
|
89
137
|
interface SerialStatement {
|
|
90
138
|
readonly statement: number
|
|
91
139
|
readonly braid: Braid
|
|
92
140
|
}
|
|
93
141
|
|
|
142
|
+
function serialAtOf(
|
|
143
|
+
byId: ReadonlyMap<number, RelationInfo>,
|
|
144
|
+
statements: readonly StatementInfo[],
|
|
145
|
+
braidOfRelation: ReadonlyMap<number, Braid>
|
|
146
|
+
): SerialStatement[] {
|
|
147
|
+
const serialAt: SerialStatement[] = []
|
|
148
|
+
for (const statement of statements) {
|
|
149
|
+
if (statement.kind === "functionality" && statement.projection.length === 0) {
|
|
150
|
+
const relation = relationOfId(byId, statement.relation)
|
|
151
|
+
if (relation.fields.length > 0) {
|
|
152
|
+
const braid = braidOfRelation.get(statement.relation)
|
|
153
|
+
if (braid !== undefined) {
|
|
154
|
+
serialAt.push({ statement: statement.id, braid })
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (statement.kind === "capacity" && statement.target.projection.length === 0) {
|
|
159
|
+
const targetRelation = relationOfId(byId, statement.target.relation)
|
|
160
|
+
if (!targetRelation.closed) {
|
|
161
|
+
const braid = braidOfRelation.get(statement.target.relation)
|
|
162
|
+
if (braid === undefined) {
|
|
163
|
+
refuseShape(`capacity ${statement.id} target ${targetRelation.name} is not in the derived braid set`)
|
|
164
|
+
}
|
|
165
|
+
serialAt.push({ statement: statement.id, braid })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return serialAt
|
|
170
|
+
}
|
|
171
|
+
|
|
94
172
|
interface Descriptor {
|
|
95
173
|
readonly relations: readonly RelationInfo[]
|
|
96
174
|
readonly relationByName: ReadonlyMap<string, RelationInfo>
|
|
@@ -146,7 +224,7 @@ function asValue(raw: unknown): Value {
|
|
|
146
224
|
return { start: interval.start, end: interval.end }
|
|
147
225
|
}
|
|
148
226
|
}
|
|
149
|
-
|
|
227
|
+
refuseShape("sealed value is not a raw fact value")
|
|
150
228
|
}
|
|
151
229
|
|
|
152
230
|
function sideOf(side: SealedSide): SideInfo {
|
|
@@ -185,11 +263,13 @@ function statementOf(statement: SealedStatement): StatementInfo {
|
|
|
185
263
|
|
|
186
264
|
function fromSealed(spec: SchemaSpec): Descriptor {
|
|
187
265
|
const sealed: SealedDescriptor = internalDescriptor(spec)
|
|
188
|
-
const specByName = new Map(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
266
|
+
const specByName = new Map<string, SchemaSpec["relations"][number]>()
|
|
267
|
+
for (const relation of spec.relations) {
|
|
268
|
+
if (specByName.has(relation.name)) {
|
|
269
|
+
refuseShape(`duplicate relation ${relation.name}`)
|
|
270
|
+
}
|
|
271
|
+
specByName.set(relation.name, relation)
|
|
272
|
+
}
|
|
193
273
|
const closedOwners = new Set(
|
|
194
274
|
spec.relations
|
|
195
275
|
.filter(function closedOf(relation) {
|
|
@@ -202,21 +282,21 @@ function fromSealed(spec: SchemaSpec): Descriptor {
|
|
|
202
282
|
|
|
203
283
|
const relations: RelationInfo[] = sealed.relations.map(function relationOf(relation) {
|
|
204
284
|
const declared = specByName.get(relation.name)
|
|
285
|
+
if (declared === undefined) {
|
|
286
|
+
refuseShape(`sealed relation ${relation.name} is not in the spec`)
|
|
287
|
+
}
|
|
205
288
|
const closed = relation.extension !== undefined
|
|
206
289
|
const fields: FieldInfo[] = relation.fields.map(function fieldOf(field) {
|
|
207
290
|
if (field.name === "id" && closed) {
|
|
208
291
|
return { name: field.name, type: field.valueType, fresh: false, closedRef: relation.name }
|
|
209
292
|
}
|
|
210
|
-
const specField = declared
|
|
293
|
+
const specField = declared.fields.find(function named(candidate) {
|
|
211
294
|
return candidate.name === field.name
|
|
212
295
|
})
|
|
213
296
|
let closedRef: string | undefined
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
if (closedOwners.has(owner)) {
|
|
218
|
-
closedRef = owner
|
|
219
|
-
}
|
|
297
|
+
const owner = specField?.newtype === undefined ? undefined : idClassOwner(specField.newtype)
|
|
298
|
+
if (owner !== undefined && closedOwners.has(owner)) {
|
|
299
|
+
closedRef = owner
|
|
220
300
|
}
|
|
221
301
|
return {
|
|
222
302
|
name: field.name,
|
|
@@ -245,7 +325,7 @@ function fromSealed(spec: SchemaSpec): Descriptor {
|
|
|
245
325
|
if (field.name === "id" && named.has("id")) {
|
|
246
326
|
return row.id
|
|
247
327
|
}
|
|
248
|
-
|
|
328
|
+
return refuseShape(`closed relation ${relation.name}: sealed row ${row.handle} missing field ${field.name}`)
|
|
249
329
|
})
|
|
250
330
|
})
|
|
251
331
|
return {
|
|
@@ -262,30 +342,16 @@ function fromSealed(spec: SchemaSpec): Descriptor {
|
|
|
262
342
|
|
|
263
343
|
const byName = new Map<string, RelationInfo>()
|
|
264
344
|
for (const relation of relations) {
|
|
345
|
+
if (byName.has(relation.name)) {
|
|
346
|
+
refuseShape(`duplicate relation ${relation.name}`)
|
|
347
|
+
}
|
|
265
348
|
byName.set(relation.name, relation)
|
|
266
349
|
}
|
|
267
350
|
|
|
268
351
|
const statements = sealed.statements.map(statementOf)
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
const serialAtStatements
|
|
272
|
-
for (const statement of statements) {
|
|
273
|
-
if (statement.kind === "functionality" && statement.projection.length === 0) {
|
|
274
|
-
const braid = braidOfRelation.get(statement.relation)
|
|
275
|
-
if (braid !== undefined) {
|
|
276
|
-
serialAtStatements.push({ statement: statement.id, braid })
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (statement.kind === "capacity" && statement.target.projection.length === 0) {
|
|
280
|
-
const targetRelation = relations[statement.target.relation]
|
|
281
|
-
if (targetRelation !== undefined && !targetRelation.closed) {
|
|
282
|
-
const braid = braidOfRelation.get(statement.target.relation)
|
|
283
|
-
if (braid !== undefined) {
|
|
284
|
-
serialAtStatements.push({ statement: statement.id, braid })
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
352
|
+
const byId = indexRelations(relations)
|
|
353
|
+
const { braidOfRelation, braidMembers } = deriveBraids(byId, statements)
|
|
354
|
+
const serialAtStatements = serialAtOf(byId, statements, braidOfRelation)
|
|
289
355
|
|
|
290
356
|
const fingerprint = sealed.fingerprint
|
|
291
357
|
return {
|
|
@@ -342,8 +408,39 @@ function rawValue(value: ValueSpec): Value {
|
|
|
342
408
|
|
|
343
409
|
interface SpecTables {
|
|
344
410
|
readonly spec: SchemaSpec
|
|
345
|
-
readonly relations: RelationInfo[]
|
|
346
411
|
readonly byName: Map<string, RelationInfo>
|
|
412
|
+
readonly byId: Map<number, RelationInfo>
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function zipClosedPayload(
|
|
416
|
+
relation: RelationInfo,
|
|
417
|
+
handle: string,
|
|
418
|
+
literals: readonly LiteralSpec[]
|
|
419
|
+
): Array<readonly [FieldInfo, LiteralSpec]> {
|
|
420
|
+
const pairs: Array<readonly [FieldInfo, LiteralSpec]> = []
|
|
421
|
+
const pending = literals[Symbol.iterator]()
|
|
422
|
+
let seenId = false
|
|
423
|
+
for (const field of relation.fields) {
|
|
424
|
+
if (!seenId) {
|
|
425
|
+
if (field.name !== "id") {
|
|
426
|
+
refuseShape(`closed relation ${relation.name} has no sealed id field`)
|
|
427
|
+
}
|
|
428
|
+
seenId = true
|
|
429
|
+
continue
|
|
430
|
+
}
|
|
431
|
+
const next = pending.next()
|
|
432
|
+
if (next.done) {
|
|
433
|
+
refuseShape(`closed row ${handle} of ${relation.name}: fewer cells than payload fields`)
|
|
434
|
+
}
|
|
435
|
+
pairs.push([field, next.value])
|
|
436
|
+
}
|
|
437
|
+
if (!seenId) {
|
|
438
|
+
refuseShape(`closed relation ${relation.name} has no sealed id field`)
|
|
439
|
+
}
|
|
440
|
+
if (!pending.next().done) {
|
|
441
|
+
refuseShape(`closed row ${handle} of ${relation.name}: more cells than payload fields`)
|
|
442
|
+
}
|
|
443
|
+
return pairs
|
|
347
444
|
}
|
|
348
445
|
|
|
349
446
|
function fieldsOf(
|
|
@@ -361,8 +458,8 @@ function fieldsOf(
|
|
|
361
458
|
}
|
|
362
459
|
for (const field of relation.fields) {
|
|
363
460
|
let closedRef: string | undefined
|
|
364
|
-
|
|
365
|
-
|
|
461
|
+
const owner = field.newtype === undefined ? undefined : idClassOwner(field.newtype)
|
|
462
|
+
if (owner !== undefined) {
|
|
366
463
|
const target = tables.get(owner)
|
|
367
464
|
if (target?.closed === true) {
|
|
368
465
|
closedRef = owner
|
|
@@ -378,17 +475,15 @@ function resolveLiteral(tables: SpecTables, relation: RelationInfo, field: Field
|
|
|
378
475
|
return rawValue(literal.value)
|
|
379
476
|
}
|
|
380
477
|
if (field.closedRef === undefined) {
|
|
381
|
-
|
|
382
|
-
`handle literal ${literal.handle} on ${relation.name}.${field.name}, which references no closed roster`
|
|
383
|
-
)
|
|
478
|
+
refuseShape(`handle literal ${literal.handle} on ${relation.name}.${field.name}, which references no closed roster`)
|
|
384
479
|
}
|
|
385
480
|
const target = tables.byName.get(field.closedRef)
|
|
386
481
|
if (target === undefined) {
|
|
387
|
-
|
|
482
|
+
refuseShape(`handle literal ${literal.handle}: unknown closed relation ${field.closedRef}`)
|
|
388
483
|
}
|
|
389
484
|
const id = target.handles.indexOf(literal.handle)
|
|
390
485
|
if (id === -1) {
|
|
391
|
-
|
|
486
|
+
refuseShape(`handle literal ${literal.handle} is not in the ${target.name} roster`)
|
|
392
487
|
}
|
|
393
488
|
return BigInt(id)
|
|
394
489
|
}
|
|
@@ -403,29 +498,19 @@ function literalSetOf(tables: SpecTables, relation: RelationInfo, field: FieldIn
|
|
|
403
498
|
}
|
|
404
499
|
|
|
405
500
|
function fieldOrdinal(relation: RelationInfo, name: string): number {
|
|
406
|
-
|
|
407
|
-
return field.name === name
|
|
408
|
-
})
|
|
409
|
-
if (ordinal === -1) {
|
|
410
|
-
throw errors.new(`relation ${relation.name} has no field ${name}`)
|
|
411
|
-
}
|
|
412
|
-
return ordinal
|
|
501
|
+
return fieldNamed(relation, name).ordinal
|
|
413
502
|
}
|
|
414
503
|
|
|
415
504
|
function specSideOf(tables: SpecTables, side: SideSpec): SideInfo {
|
|
416
505
|
const relation = tables.byName.get(side.relation)
|
|
417
506
|
if (relation === undefined) {
|
|
418
|
-
|
|
507
|
+
refuseShape(`statement cites unknown relation ${side.relation}`)
|
|
419
508
|
}
|
|
420
509
|
const projection = side.projection.map(function ordinalOf(name) {
|
|
421
510
|
return fieldOrdinal(relation, name)
|
|
422
511
|
})
|
|
423
512
|
const selection = side.selection.map(function bindingOf(binding) {
|
|
424
|
-
const ordinal =
|
|
425
|
-
const field = relation.fields[ordinal]
|
|
426
|
-
if (field === undefined) {
|
|
427
|
-
throw errors.new(`relation ${relation.name} has no field ordinal ${ordinal}`)
|
|
428
|
-
}
|
|
513
|
+
const { ordinal, field } = fieldNamed(relation, binding[0])
|
|
429
514
|
return { field: ordinal, values: literalSetOf(tables, relation, field, binding[1]) }
|
|
430
515
|
})
|
|
431
516
|
return { relation: relation.id, projection, selection }
|
|
@@ -433,7 +518,7 @@ function specSideOf(tables: SpecTables, side: SideSpec): SideInfo {
|
|
|
433
518
|
|
|
434
519
|
function boundValue(context: string, bound: { readonly kind: string }): bigint {
|
|
435
520
|
if (bound.kind !== "lit") {
|
|
436
|
-
|
|
521
|
+
refuseShape(`${context}: dependent floors are refused by the schema grammar`)
|
|
437
522
|
}
|
|
438
523
|
return (bound as { readonly kind: "lit"; readonly value: bigint }).value
|
|
439
524
|
}
|
|
@@ -448,7 +533,7 @@ function capacityOf(
|
|
|
448
533
|
const sourceRelation = tables.byName.get(statement.source.relation)
|
|
449
534
|
const targetRelation = tables.byName.get(statement.target.relation)
|
|
450
535
|
if (sourceRelation === undefined || targetRelation === undefined) {
|
|
451
|
-
|
|
536
|
+
refuseShape("capacity statement cites unknown relations")
|
|
452
537
|
}
|
|
453
538
|
let weight: WeightInfo
|
|
454
539
|
switch (statement.weight.kind) {
|
|
@@ -504,51 +589,59 @@ function capacityOf(
|
|
|
504
589
|
function assembleFromSpec(spec: SchemaSpec): Descriptor {
|
|
505
590
|
const prepass = new Map<string, { closed: boolean; handles: readonly string[] }>()
|
|
506
591
|
for (const relation of spec.relations) {
|
|
592
|
+
if (prepass.has(relation.name)) {
|
|
593
|
+
refuseShape(`duplicate relation ${relation.name}`)
|
|
594
|
+
}
|
|
507
595
|
prepass.set(relation.name, {
|
|
508
596
|
closed: relation.closed !== undefined,
|
|
509
597
|
handles: relation.closed === undefined ? [] : relation.closed.rows.map((row) => row.handle)
|
|
510
598
|
})
|
|
511
599
|
}
|
|
512
600
|
|
|
513
|
-
const relations: RelationInfo[] = []
|
|
514
601
|
const byName = new Map<string, RelationInfo>()
|
|
515
|
-
|
|
516
|
-
|
|
602
|
+
const byId = new Map<number, RelationInfo>()
|
|
603
|
+
let nextId = 0
|
|
604
|
+
for (const relation of spec.relations) {
|
|
605
|
+
const table = prepass.get(relation.name)
|
|
606
|
+
if (table === undefined) {
|
|
607
|
+
refuseShape(`relation ${relation.name} missing from the closedness table`)
|
|
608
|
+
}
|
|
517
609
|
const info: RelationInfo = {
|
|
518
|
-
id,
|
|
610
|
+
id: nextId,
|
|
519
611
|
name: relation.name,
|
|
520
|
-
closed:
|
|
521
|
-
handles:
|
|
522
|
-
fields,
|
|
612
|
+
closed: table.closed,
|
|
613
|
+
handles: table.handles,
|
|
614
|
+
fields: fieldsOf(prepass, relation),
|
|
523
615
|
rows: []
|
|
524
616
|
}
|
|
525
|
-
|
|
617
|
+
nextId += 1
|
|
526
618
|
byName.set(relation.name, info)
|
|
527
|
-
|
|
528
|
-
|
|
619
|
+
byId.set(info.id, info)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const tables: SpecTables = { spec, byName, byId }
|
|
529
623
|
|
|
530
|
-
|
|
624
|
+
for (const relation of spec.relations) {
|
|
531
625
|
if (relation.closed === undefined) {
|
|
532
|
-
|
|
626
|
+
continue
|
|
533
627
|
}
|
|
534
|
-
const info =
|
|
628
|
+
const info = byName.get(relation.name)
|
|
535
629
|
if (info === undefined) {
|
|
536
|
-
|
|
630
|
+
refuseShape(`closed relation ${relation.name} missing`)
|
|
537
631
|
}
|
|
538
632
|
const rows = relation.closed.rows.map(function resolveRow(row, rowId) {
|
|
539
633
|
const values: Value[] = [BigInt(rowId)]
|
|
540
|
-
|
|
541
|
-
const field = info.fields[column + 1]
|
|
542
|
-
if (field === undefined) {
|
|
543
|
-
throw errors.new(`closed row ${row.handle} of ${relation.name}: no field at column ${column}`)
|
|
544
|
-
}
|
|
634
|
+
for (const [field, literal] of zipClosedPayload(info, row.handle, row.values)) {
|
|
545
635
|
values.push(resolveLiteral(tables, info, field, literal))
|
|
546
|
-
}
|
|
636
|
+
}
|
|
547
637
|
return values
|
|
548
638
|
})
|
|
549
|
-
|
|
550
|
-
byName.set(relation.name,
|
|
551
|
-
|
|
639
|
+
const updated = { ...info, rows }
|
|
640
|
+
byName.set(relation.name, updated)
|
|
641
|
+
byId.set(info.id, updated)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const relations = [...byId.values()]
|
|
552
645
|
|
|
553
646
|
const statements: StatementInfo[] = []
|
|
554
647
|
relations.forEach(function freshKeys(relation) {
|
|
@@ -568,7 +661,7 @@ function assembleFromSpec(spec: SchemaSpec): Descriptor {
|
|
|
568
661
|
case "fd": {
|
|
569
662
|
const relation = byName.get(statement.relation)
|
|
570
663
|
if (relation === undefined) {
|
|
571
|
-
|
|
664
|
+
refuseShape(`key statement cites unknown relation ${statement.relation}`)
|
|
572
665
|
}
|
|
573
666
|
statements.push({
|
|
574
667
|
id: statements.length,
|
|
@@ -596,26 +689,8 @@ function assembleFromSpec(spec: SchemaSpec): Descriptor {
|
|
|
596
689
|
}
|
|
597
690
|
}
|
|
598
691
|
|
|
599
|
-
const { braidOfRelation, braidMembers } = deriveBraids(
|
|
600
|
-
|
|
601
|
-
const serialAtStatements: SerialStatement[] = []
|
|
602
|
-
for (const statement of statements) {
|
|
603
|
-
if (statement.kind === "functionality" && statement.projection.length === 0) {
|
|
604
|
-
const braid = braidOfRelation.get(statement.relation)
|
|
605
|
-
if (braid !== undefined) {
|
|
606
|
-
serialAtStatements.push({ statement: statement.id, braid })
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
if (statement.kind === "capacity" && statement.target.projection.length === 0) {
|
|
610
|
-
const targetRelation = relations[statement.target.relation]
|
|
611
|
-
if (targetRelation !== undefined && !targetRelation.closed) {
|
|
612
|
-
const braid = braidOfRelation.get(statement.target.relation)
|
|
613
|
-
if (braid !== undefined) {
|
|
614
|
-
serialAtStatements.push({ statement: statement.id, braid })
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
692
|
+
const { braidOfRelation, braidMembers } = deriveBraids(byId, statements)
|
|
693
|
+
const serialAtStatements = serialAtOf(byId, statements, braidOfRelation)
|
|
619
694
|
|
|
620
695
|
const fingerprint = "00".repeat(32)
|
|
621
696
|
return {
|
|
@@ -631,11 +706,11 @@ function assembleFromSpec(spec: SchemaSpec): Descriptor {
|
|
|
631
706
|
}
|
|
632
707
|
|
|
633
708
|
function deriveBraids(
|
|
634
|
-
|
|
709
|
+
byId: ReadonlyMap<number, RelationInfo>,
|
|
635
710
|
statements: readonly StatementInfo[]
|
|
636
711
|
): { braidOfRelation: Map<number, Braid>; braidMembers: Map<Braid, readonly number[]> } {
|
|
637
712
|
const parent = new Map<number, number>()
|
|
638
|
-
for (const relation of
|
|
713
|
+
for (const relation of byId.values()) {
|
|
639
714
|
if (!relation.closed) {
|
|
640
715
|
parent.set(relation.id, relation.id)
|
|
641
716
|
}
|
|
@@ -661,9 +736,9 @@ function deriveBraids(
|
|
|
661
736
|
if (statement.kind === "functionality") {
|
|
662
737
|
continue
|
|
663
738
|
}
|
|
664
|
-
const source =
|
|
665
|
-
const target =
|
|
666
|
-
if (source
|
|
739
|
+
const source = relationOfId(byId, statement.source.relation)
|
|
740
|
+
const target = relationOfId(byId, statement.target.relation)
|
|
741
|
+
if (source.closed || target.closed) {
|
|
667
742
|
continue
|
|
668
743
|
}
|
|
669
744
|
union(source.id, target.id)
|
package/src/errors.ts
CHANGED
|
@@ -4,12 +4,15 @@
|
|
|
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.
|
|
7
|
+
* replay (L10) and never surfaces. ManifestMissing, Ambiguous, OverWidth,
|
|
8
|
+
* Exhausted, and SlotRetired are named sums of their own: a replica
|
|
9
|
+
* without a manifest, an unproved conditional write, a draw past the
|
|
10
|
+
* lease width or the u64 ceiling, and a below-floor create.
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import * as errors from "@superbuilders/errors"
|
|
11
14
|
import type { Braid } from "#descriptor.ts"
|
|
12
|
-
import type { Generation } from "#keys.ts"
|
|
15
|
+
import type { Generation, StoreKey } from "#keys.ts"
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Typed refusal of a protocol object before any apply: batch shape,
|
|
@@ -37,6 +40,25 @@ const ErrChainMismatch = errors.new("bumbledb-log chainMismatch: the batch viola
|
|
|
37
40
|
/** Bounded live-tip losses exhausted — an operational signal, not an outcome arm. */
|
|
38
41
|
const ErrContention = errors.new("bumbledb-log contention: consecutive live-tip losses exhausted the bound")
|
|
39
42
|
|
|
43
|
+
/** A replica (or any read-role handle) found no manifest; only the writer births a store. */
|
|
44
|
+
export const ErrManifestMissing = errors.new("bumbledb-log manifestMissing: the store has no manifest")
|
|
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
|
+
/** A single id draw larger than one lease width; the width is the one block size. */
|
|
54
|
+
const ErrOverWidth = errors.new("bumbledb-log overWidth: the draw exceeds the lease width")
|
|
55
|
+
|
|
56
|
+
/** The next lease would leave u64 — the id space is spent. */
|
|
57
|
+
const ErrExhausted = errors.new("bumbledb-log exhausted: the id space is spent")
|
|
58
|
+
|
|
59
|
+
/** A put_create at a slot below the published checkpoint vector. */
|
|
60
|
+
const ErrSlotRetired = errors.new("bumbledb-log slotRetired: the slot is retired")
|
|
61
|
+
|
|
40
62
|
/** The vendor channel: I/O and store infrastructure failures. */
|
|
41
63
|
const ErrStore = errors.new("bumbledb-log store failure")
|
|
42
64
|
|
|
@@ -63,9 +85,11 @@ type RefusalCause =
|
|
|
63
85
|
| { readonly kind: "EmptyInterval"; readonly relation: string; readonly row: number; readonly field: string }
|
|
64
86
|
| { readonly kind: "IntervalOverflow"; readonly relation: string; readonly row: number; readonly field: string }
|
|
65
87
|
| { readonly kind: "TrailingBytes"; readonly bytes: number }
|
|
66
|
-
| { readonly kind: "
|
|
88
|
+
| { readonly kind: "DigestWidth" }
|
|
89
|
+
| { readonly kind: "Arity"; readonly op: number; readonly relation: string; readonly row: number }
|
|
90
|
+
| { readonly kind: "Malformed"; readonly at: number }
|
|
91
|
+
| { readonly kind: "Overflow" }
|
|
67
92
|
| { readonly kind: "ManifestVersion"; readonly version: number }
|
|
68
|
-
| { readonly kind: "CheckpointShape" }
|
|
69
93
|
| { readonly kind: "CheckpointBraids"; readonly carried: readonly string[]; readonly derived: readonly string[] }
|
|
70
94
|
| { readonly kind: "CheckpointDigest"; readonly expected: string; readonly computed: string }
|
|
71
95
|
| { readonly kind: "NoOpSlot"; readonly braid: Braid; readonly slot: Generation; readonly writer: bigint }
|
|
@@ -92,9 +116,39 @@ interface ContentionData {
|
|
|
92
116
|
readonly cause: ContentionCause
|
|
93
117
|
}
|
|
94
118
|
|
|
119
|
+
type AmbiguousVerb = "create" | "swap"
|
|
120
|
+
|
|
121
|
+
interface AmbiguousData {
|
|
122
|
+
readonly verb: AmbiguousVerb
|
|
123
|
+
readonly key: StoreKey
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface OverWidthData {
|
|
127
|
+
readonly requested: bigint
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface ExhaustedData {
|
|
131
|
+
readonly relation: number
|
|
132
|
+
readonly field: number
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface SlotRetiredData {
|
|
136
|
+
readonly braid: Braid
|
|
137
|
+
readonly slot: Generation
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The id-lease algebra's refusal arms: OverWidth | Exhausted. */
|
|
141
|
+
type LeaseRefusal =
|
|
142
|
+
| { readonly kind: "OverWidth"; readonly requested: bigint }
|
|
143
|
+
| { readonly kind: "Exhausted"; readonly relation: number; readonly field: number }
|
|
144
|
+
|
|
95
145
|
const refusalData = new WeakMap<Error, RefusalCause>()
|
|
96
146
|
const chainData = new WeakMap<Error, ChainMismatchData>()
|
|
97
147
|
const contentionData = new WeakMap<Error, ContentionData>()
|
|
148
|
+
const ambiguousData = new WeakMap<Error, AmbiguousData>()
|
|
149
|
+
const overWidthData = new WeakMap<Error, OverWidthData>()
|
|
150
|
+
const exhaustedData = new WeakMap<Error, ExhaustedData>()
|
|
151
|
+
const slotRetiredData = new WeakMap<Error, SlotRetiredData>()
|
|
98
152
|
|
|
99
153
|
function refuse(cause: RefusalCause, detail: string): never {
|
|
100
154
|
const error = errors.wrap(ErrRefused, detail)
|
|
@@ -114,6 +168,38 @@ function throwContention(data: ContentionData, detail: string): never {
|
|
|
114
168
|
throw error
|
|
115
169
|
}
|
|
116
170
|
|
|
171
|
+
function refuseManifestMissing(detail: string): never {
|
|
172
|
+
throw errors.wrap(ErrManifestMissing, detail)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isManifestMissing(error: unknown): boolean {
|
|
176
|
+
return error instanceof Error && errors.is(error, ErrManifestMissing)
|
|
177
|
+
}
|
|
178
|
+
|
|
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
|
+
function refuseOverWidth(data: OverWidthData, detail: string): never {
|
|
186
|
+
const error = errors.wrap(ErrOverWidth, detail)
|
|
187
|
+
overWidthData.set(error, data)
|
|
188
|
+
throw error
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function refuseExhausted(data: ExhaustedData, detail: string): never {
|
|
192
|
+
const error = errors.wrap(ErrExhausted, detail)
|
|
193
|
+
exhaustedData.set(error, data)
|
|
194
|
+
throw error
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function refuseSlotRetired(data: SlotRetiredData, detail: string): never {
|
|
198
|
+
const error = errors.wrap(ErrSlotRetired, detail)
|
|
199
|
+
slotRetiredData.set(error, data)
|
|
200
|
+
throw error
|
|
201
|
+
}
|
|
202
|
+
|
|
117
203
|
function refusalOf(error: Error): RefusalCause | undefined {
|
|
118
204
|
return refusalData.get(error)
|
|
119
205
|
}
|
|
@@ -126,6 +212,22 @@ function contentionOf(error: Error): ContentionData | undefined {
|
|
|
126
212
|
return contentionData.get(error)
|
|
127
213
|
}
|
|
128
214
|
|
|
215
|
+
function ambiguousOf(error: Error): AmbiguousData | undefined {
|
|
216
|
+
return ambiguousData.get(error)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function overWidthOf(error: Error): OverWidthData | undefined {
|
|
220
|
+
return overWidthData.get(error)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function exhaustedOf(error: Error): ExhaustedData | undefined {
|
|
224
|
+
return exhaustedData.get(error)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function slotRetiredOf(error: Error): SlotRetiredData | undefined {
|
|
228
|
+
return slotRetiredData.get(error)
|
|
229
|
+
}
|
|
230
|
+
|
|
129
231
|
/** Every store failure wraps the exported sentinel itself, so
|
|
130
232
|
* `errors.is(e, ErrStore)` matches by identity; the vendor error's
|
|
131
233
|
* message rides the detail verbatim. */
|
|
@@ -133,20 +235,46 @@ function wrapStore(inner: Error, detail: string): Error {
|
|
|
133
235
|
return errors.wrap(ErrStore, `${detail}: ${inner.message}`)
|
|
134
236
|
}
|
|
135
237
|
|
|
136
|
-
export type {
|
|
238
|
+
export type {
|
|
239
|
+
AmbiguousData,
|
|
240
|
+
AmbiguousVerb,
|
|
241
|
+
ChainCause,
|
|
242
|
+
ChainMismatchData,
|
|
243
|
+
ContentionCause,
|
|
244
|
+
ContentionData,
|
|
245
|
+
ExhaustedData,
|
|
246
|
+
LeaseRefusal,
|
|
247
|
+
OverWidthData,
|
|
248
|
+
RefusalCause,
|
|
249
|
+
SlotRetiredData
|
|
250
|
+
}
|
|
137
251
|
export {
|
|
252
|
+
ambiguousOf,
|
|
138
253
|
chainMismatchOf,
|
|
139
254
|
contentionOf,
|
|
255
|
+
ErrAmbiguous,
|
|
140
256
|
ErrChainMismatch,
|
|
141
257
|
ErrContention,
|
|
258
|
+
ErrExhausted,
|
|
142
259
|
ErrGapDetected,
|
|
260
|
+
ErrOverWidth,
|
|
143
261
|
ErrRefused,
|
|
144
262
|
ErrReplayDiverged,
|
|
263
|
+
ErrSlotRetired,
|
|
145
264
|
ErrSpanningCommit,
|
|
146
265
|
ErrStore,
|
|
266
|
+
exhaustedOf,
|
|
267
|
+
isManifestMissing,
|
|
268
|
+
overWidthOf,
|
|
147
269
|
refusalOf,
|
|
148
270
|
refuse,
|
|
149
271
|
refuseChain,
|
|
272
|
+
refuseExhausted,
|
|
273
|
+
refuseManifestMissing,
|
|
274
|
+
refuseOverWidth,
|
|
275
|
+
refuseSlotRetired,
|
|
276
|
+
slotRetiredOf,
|
|
277
|
+
throwAmbiguous,
|
|
150
278
|
throwContention,
|
|
151
279
|
wrapStore
|
|
152
280
|
}
|