@bjornpagen/bumbledb-log 0.17.0 → 0.18.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 +22 -15
- package/package.json +3 -2
- package/src/braids.ts +5 -8
- package/src/bytes.ts +2 -2
- package/src/chain.ts +16 -17
- package/src/codec.ts +21 -23
- package/src/descriptor.ts +245 -276
- package/src/errors.ts +8 -6
- package/src/index.ts +10 -8
- package/src/keys.ts +69 -15
- package/src/manifest.ts +11 -7
- package/src/replica.ts +42 -44
- package/src/store-s3.ts +222 -0
- package/src/store.ts +76 -31
- package/src/value.ts +16 -16
- package/src/writer.ts +37 -34
package/src/descriptor.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The parsed protocol descriptor: the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* every pure function downstream reads ids, never names. Braid
|
|
7
|
-
* derivation and the schema fingerprint mirror live on the same parse:
|
|
8
|
-
* one boundary, parsed in full, cached per theory value.
|
|
2
|
+
* The parsed protocol descriptor: one thin parse of the engine's sealed
|
|
3
|
+
* truth — relation ids, sealed field order, resolved closed rows,
|
|
4
|
+
* materialized statements, and the real fingerprint — plus the braid
|
|
5
|
+
* map the protocol derives from that truth. Cached per theory value.
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
8
|
import type {
|
|
@@ -13,20 +10,21 @@ import type {
|
|
|
13
10
|
LiteralSetSpec,
|
|
14
11
|
LiteralSpec,
|
|
15
12
|
SchemaSpec,
|
|
13
|
+
SealedDescriptor,
|
|
14
|
+
SealedSide,
|
|
15
|
+
SealedStatement,
|
|
16
16
|
SideSpec,
|
|
17
17
|
StatementSpec,
|
|
18
|
-
ValueSpec
|
|
19
|
-
ValueTypeSpec
|
|
18
|
+
ValueSpec
|
|
20
19
|
} from "@bjornpagen/bumbledb"
|
|
21
|
-
import {
|
|
20
|
+
import { internalDescriptor, lower } from "@bjornpagen/bumbledb"
|
|
22
21
|
import * as errors from "@superbuilders/errors"
|
|
23
|
-
import {
|
|
24
|
-
import type {
|
|
25
|
-
import { writeCanonicalLiteral } from "#value.ts"
|
|
22
|
+
import { fromHex } from "#bytes.ts"
|
|
23
|
+
import type { Value } from "#value.ts"
|
|
26
24
|
|
|
27
25
|
interface FieldInfo {
|
|
28
26
|
readonly name: string
|
|
29
|
-
readonly type:
|
|
27
|
+
readonly type: SchemaSpec["relations"][number]["fields"][number]["valueType"]
|
|
30
28
|
readonly fresh: boolean
|
|
31
29
|
/** Set when the field's newtype names a closed relation's id class. */
|
|
32
30
|
readonly closedRef: string | undefined
|
|
@@ -40,13 +38,13 @@ interface RelationInfo {
|
|
|
40
38
|
/** Sealed order: a closed relation's synthetic u64 `id` at ordinal 0. */
|
|
41
39
|
readonly fields: readonly FieldInfo[]
|
|
42
40
|
/** Closed ground axioms in sealed order (id first), resolved values. */
|
|
43
|
-
readonly rows: ReadonlyArray<readonly
|
|
41
|
+
readonly rows: ReadonlyArray<readonly Value[]>
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
interface SideInfo {
|
|
47
45
|
readonly relation: number
|
|
48
46
|
readonly projection: readonly number[]
|
|
49
|
-
readonly selection: ReadonlyArray<{ readonly field: number; readonly values: readonly
|
|
47
|
+
readonly selection: ReadonlyArray<{ readonly field: number; readonly values: readonly Value[] }>
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
type WeightInfo =
|
|
@@ -78,38 +76,48 @@ type StatementInfo =
|
|
|
78
76
|
readonly source: SideInfo
|
|
79
77
|
}
|
|
80
78
|
|
|
79
|
+
declare const braidBrand: unique symbol
|
|
80
|
+
type Braid = string & { readonly [braidBrand]: typeof braidBrand }
|
|
81
|
+
|
|
82
|
+
function braid(raw: string): Braid {
|
|
83
|
+
if (!/^c[0-9a-f]{8}$/.test(raw)) {
|
|
84
|
+
throw errors.new(`not a braid id: ${raw}`)
|
|
85
|
+
}
|
|
86
|
+
return raw as Braid
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
interface SerialStatement {
|
|
82
90
|
readonly statement: number
|
|
83
|
-
readonly braid:
|
|
91
|
+
readonly braid: Braid
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
interface
|
|
94
|
+
interface Descriptor {
|
|
87
95
|
readonly relations: readonly RelationInfo[]
|
|
88
96
|
readonly relationByName: ReadonlyMap<string, RelationInfo>
|
|
89
97
|
readonly statements: readonly StatementInfo[]
|
|
90
|
-
/** Ordinary relation id → braid id
|
|
91
|
-
readonly braidOfRelation: ReadonlyMap<number,
|
|
92
|
-
/** Braid id
|
|
93
|
-
readonly braidMembers: ReadonlyMap<
|
|
98
|
+
/** Ordinary relation id → braid id (`c{smallest:08x}`). */
|
|
99
|
+
readonly braidOfRelation: ReadonlyMap<number, Braid>
|
|
100
|
+
/** Braid id → member relation ids, ascending. */
|
|
101
|
+
readonly braidMembers: ReadonlyMap<Braid, readonly number[]>
|
|
94
102
|
readonly serialAtStatements: readonly SerialStatement[]
|
|
95
103
|
readonly fingerprint: string
|
|
96
104
|
readonly fingerprintBytes: Uint8Array
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/** The pure trio's input: the theory value, its lowered spec, or an already-parsed descriptor. */
|
|
100
|
-
type
|
|
108
|
+
type Theory = AnySchema | SchemaSpec | Descriptor
|
|
101
109
|
|
|
102
|
-
function isDescriptor(theory:
|
|
110
|
+
function isDescriptor(theory: Theory): theory is Descriptor {
|
|
103
111
|
return "braidMembers" in theory
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
function isSpec(theory:
|
|
114
|
+
function isSpec(theory: Theory): theory is SchemaSpec {
|
|
107
115
|
return Array.isArray((theory as SchemaSpec).relations)
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
const cache = new WeakMap<object,
|
|
118
|
+
const cache = new WeakMap<object, Descriptor>()
|
|
111
119
|
|
|
112
|
-
function descriptorOf(theory:
|
|
120
|
+
function descriptorOf(theory: Theory): Descriptor {
|
|
113
121
|
if (isDescriptor(theory)) {
|
|
114
122
|
return theory
|
|
115
123
|
}
|
|
@@ -118,17 +126,204 @@ function descriptorOf(theory: LogTheory): LogDescriptor {
|
|
|
118
126
|
return hit
|
|
119
127
|
}
|
|
120
128
|
const spec = isSpec(theory) ? theory : lower(theory)
|
|
121
|
-
const parsed = spec === theory ?
|
|
129
|
+
const parsed = spec === theory ? fromSealed(spec) : (cache.get(spec) ?? fromSealed(spec))
|
|
122
130
|
cache.set(theory, parsed)
|
|
123
131
|
cache.set(spec, parsed)
|
|
124
132
|
return parsed
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
function braidHex(relationId: number):
|
|
128
|
-
return `c${relationId.toString(16).padStart(8, "0")}`
|
|
135
|
+
function braidHex(relationId: number): Braid {
|
|
136
|
+
return braid(`c${relationId.toString(16).padStart(8, "0")}`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function asValue(raw: unknown): Value {
|
|
140
|
+
if (typeof raw === "boolean" || typeof raw === "bigint" || typeof raw === "string" || raw instanceof Uint8Array) {
|
|
141
|
+
return raw
|
|
142
|
+
}
|
|
143
|
+
if (typeof raw === "object" && raw !== null && "start" in raw && "end" in raw) {
|
|
144
|
+
const interval = raw as { start: unknown; end: unknown }
|
|
145
|
+
if (typeof interval.start === "bigint" && typeof interval.end === "bigint") {
|
|
146
|
+
return { start: interval.start, end: interval.end }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
throw errors.new("sealed value is not a raw fact value")
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function sideOf(side: SealedSide): SideInfo {
|
|
153
|
+
return {
|
|
154
|
+
relation: side.relation,
|
|
155
|
+
projection: side.projection,
|
|
156
|
+
selection: side.selection.map(function valuesOf(binding) {
|
|
157
|
+
return { field: binding.field, values: binding.values.map(asValue) }
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function statementOf(statement: SealedStatement): StatementInfo {
|
|
163
|
+
switch (statement.kind) {
|
|
164
|
+
case "functionality":
|
|
165
|
+
return statement
|
|
166
|
+
case "containment":
|
|
167
|
+
return {
|
|
168
|
+
id: statement.id,
|
|
169
|
+
kind: "containment",
|
|
170
|
+
source: sideOf(statement.source),
|
|
171
|
+
target: sideOf(statement.target)
|
|
172
|
+
}
|
|
173
|
+
case "capacity":
|
|
174
|
+
return {
|
|
175
|
+
id: statement.id,
|
|
176
|
+
kind: "capacity",
|
|
177
|
+
target: sideOf(statement.target),
|
|
178
|
+
weight: statement.weight,
|
|
179
|
+
lo: statement.lo,
|
|
180
|
+
hi: statement.hi,
|
|
181
|
+
source: sideOf(statement.source)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function fromSealed(spec: SchemaSpec): Descriptor {
|
|
187
|
+
const sealed: SealedDescriptor = internalDescriptor(spec)
|
|
188
|
+
const specByName = new Map(
|
|
189
|
+
spec.relations.map(function byName(relation) {
|
|
190
|
+
return [relation.name, relation]
|
|
191
|
+
})
|
|
192
|
+
)
|
|
193
|
+
const closedOwners = new Set(
|
|
194
|
+
spec.relations
|
|
195
|
+
.filter(function closedOf(relation) {
|
|
196
|
+
return relation.closed !== undefined
|
|
197
|
+
})
|
|
198
|
+
.map(function nameOf(relation) {
|
|
199
|
+
return relation.name
|
|
200
|
+
})
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const relations: RelationInfo[] = sealed.relations.map(function relationOf(relation) {
|
|
204
|
+
const declared = specByName.get(relation.name)
|
|
205
|
+
const closed = relation.extension !== undefined
|
|
206
|
+
const fields: FieldInfo[] = relation.fields.map(function fieldOf(field) {
|
|
207
|
+
if (field.name === "id" && closed) {
|
|
208
|
+
return { name: field.name, type: field.valueType, fresh: false, closedRef: relation.name }
|
|
209
|
+
}
|
|
210
|
+
const specField = declared?.fields.find(function named(candidate) {
|
|
211
|
+
return candidate.name === field.name
|
|
212
|
+
})
|
|
213
|
+
let closedRef: string | undefined
|
|
214
|
+
const newtype = specField?.newtype
|
|
215
|
+
if (newtype?.endsWith(".id")) {
|
|
216
|
+
const owner = newtype.slice(0, -3)
|
|
217
|
+
if (closedOwners.has(owner)) {
|
|
218
|
+
closedRef = owner
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
name: field.name,
|
|
223
|
+
type: field.valueType,
|
|
224
|
+
fresh: specField?.fresh === true,
|
|
225
|
+
closedRef
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
const extension = relation.extension ?? []
|
|
229
|
+
const named = new Map(
|
|
230
|
+
relation.fields.map(function indexOf(field) {
|
|
231
|
+
return [field.name, field]
|
|
232
|
+
})
|
|
233
|
+
)
|
|
234
|
+
const rows = extension.map(function rowOf(row) {
|
|
235
|
+
const cells = new Map(
|
|
236
|
+
row.values.map(function cellOf(cell) {
|
|
237
|
+
return [cell.name, asValue(cell.value)]
|
|
238
|
+
})
|
|
239
|
+
)
|
|
240
|
+
return relation.fields.map(function cellValue(field) {
|
|
241
|
+
const hit = cells.get(field.name)
|
|
242
|
+
if (hit !== undefined) {
|
|
243
|
+
return hit
|
|
244
|
+
}
|
|
245
|
+
if (field.name === "id" && named.has("id")) {
|
|
246
|
+
return row.id
|
|
247
|
+
}
|
|
248
|
+
throw errors.new(`closed relation ${relation.name}: sealed row ${row.handle} missing field ${field.name}`)
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
return {
|
|
252
|
+
id: relation.id,
|
|
253
|
+
name: relation.name,
|
|
254
|
+
closed,
|
|
255
|
+
handles: extension.map(function handleOf(row) {
|
|
256
|
+
return row.handle
|
|
257
|
+
}),
|
|
258
|
+
fields,
|
|
259
|
+
rows
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const byName = new Map<string, RelationInfo>()
|
|
264
|
+
for (const relation of relations) {
|
|
265
|
+
byName.set(relation.name, relation)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const statements = sealed.statements.map(statementOf)
|
|
269
|
+
const { braidOfRelation, braidMembers } = deriveBraids(relations, statements)
|
|
270
|
+
|
|
271
|
+
const serialAtStatements: SerialStatement[] = []
|
|
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
|
+
}
|
|
289
|
+
|
|
290
|
+
const fingerprint = sealed.fingerprint
|
|
291
|
+
return {
|
|
292
|
+
relations,
|
|
293
|
+
relationByName: byName,
|
|
294
|
+
statements,
|
|
295
|
+
braidOfRelation,
|
|
296
|
+
braidMembers,
|
|
297
|
+
serialAtStatements,
|
|
298
|
+
fingerprint,
|
|
299
|
+
fingerprintBytes: fromHex(fingerprint)
|
|
300
|
+
}
|
|
129
301
|
}
|
|
130
302
|
|
|
131
|
-
|
|
303
|
+
/**
|
|
304
|
+
* The same descriptor under a pinned fingerprint — for stores whose
|
|
305
|
+
* identity is carried (a manifest, a conformance sidecar) rather than
|
|
306
|
+
* recomputed.
|
|
307
|
+
*/
|
|
308
|
+
function withFingerprint(descriptor: Descriptor, fingerprint: string): Descriptor {
|
|
309
|
+
return {
|
|
310
|
+
relations: descriptor.relations,
|
|
311
|
+
relationByName: descriptor.relationByName,
|
|
312
|
+
statements: descriptor.statements,
|
|
313
|
+
braidOfRelation: descriptor.braidOfRelation,
|
|
314
|
+
braidMembers: descriptor.braidMembers,
|
|
315
|
+
serialAtStatements: descriptor.serialAtStatements,
|
|
316
|
+
fingerprint,
|
|
317
|
+
fingerprintBytes: fromHex(fingerprint)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Assemble a descriptor from a SchemaSpec that is not a theory — the
|
|
323
|
+
* conformance corpus pins the codec and the braid map on shapes the
|
|
324
|
+
* engine seal refuses. Theories enter through descriptorOf.
|
|
325
|
+
*/
|
|
326
|
+
function rawValue(value: ValueSpec): Value {
|
|
132
327
|
switch (value.kind) {
|
|
133
328
|
case "bool":
|
|
134
329
|
return value.value
|
|
@@ -178,9 +373,9 @@ function fieldsOf(
|
|
|
178
373
|
return fields
|
|
179
374
|
}
|
|
180
375
|
|
|
181
|
-
function resolveLiteral(tables: SpecTables, relation: RelationInfo, field: FieldInfo, literal: LiteralSpec):
|
|
376
|
+
function resolveLiteral(tables: SpecTables, relation: RelationInfo, field: FieldInfo, literal: LiteralSpec): Value {
|
|
182
377
|
if (literal.kind === "value") {
|
|
183
|
-
return
|
|
378
|
+
return rawValue(literal.value)
|
|
184
379
|
}
|
|
185
380
|
if (field.closedRef === undefined) {
|
|
186
381
|
throw errors.new(
|
|
@@ -198,7 +393,7 @@ function resolveLiteral(tables: SpecTables, relation: RelationInfo, field: Field
|
|
|
198
393
|
return BigInt(id)
|
|
199
394
|
}
|
|
200
395
|
|
|
201
|
-
function literalSetOf(tables: SpecTables, relation: RelationInfo, field: FieldInfo, set: LiteralSetSpec):
|
|
396
|
+
function literalSetOf(tables: SpecTables, relation: RelationInfo, field: FieldInfo, set: LiteralSetSpec): Value[] {
|
|
202
397
|
if (set.kind === "one") {
|
|
203
398
|
return [resolveLiteral(tables, relation, field, set.literal)]
|
|
204
399
|
}
|
|
@@ -217,7 +412,7 @@ function fieldOrdinal(relation: RelationInfo, name: string): number {
|
|
|
217
412
|
return ordinal
|
|
218
413
|
}
|
|
219
414
|
|
|
220
|
-
function
|
|
415
|
+
function specSideOf(tables: SpecTables, side: SideSpec): SideInfo {
|
|
221
416
|
const relation = tables.byName.get(side.relation)
|
|
222
417
|
if (relation === undefined) {
|
|
223
418
|
throw errors.new(`statement cites unknown relation ${side.relation}`)
|
|
@@ -248,8 +443,8 @@ function capacityOf(
|
|
|
248
443
|
id: number,
|
|
249
444
|
statement: Extract<StatementSpec, { kind: "capacity" }>
|
|
250
445
|
): StatementInfo {
|
|
251
|
-
const target =
|
|
252
|
-
const source =
|
|
446
|
+
const target = specSideOf(tables, statement.target)
|
|
447
|
+
const source = specSideOf(tables, statement.source)
|
|
253
448
|
const sourceRelation = tables.byName.get(statement.source.relation)
|
|
254
449
|
const targetRelation = tables.byName.get(statement.target.relation)
|
|
255
450
|
if (sourceRelation === undefined || targetRelation === undefined) {
|
|
@@ -306,9 +501,7 @@ function capacityOf(
|
|
|
306
501
|
return { id, kind: "capacity", target, weight, lo, hi, source }
|
|
307
502
|
}
|
|
308
503
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
function parseSpec(spec: SchemaSpec): LogDescriptor {
|
|
504
|
+
function assembleFromSpec(spec: SchemaSpec): Descriptor {
|
|
312
505
|
const prepass = new Map<string, { closed: boolean; handles: readonly string[] }>()
|
|
313
506
|
for (const relation of spec.relations) {
|
|
314
507
|
prepass.set(relation.name, {
|
|
@@ -343,7 +536,7 @@ function parseSpec(spec: SchemaSpec): LogDescriptor {
|
|
|
343
536
|
throw errors.new(`relation ordinal ${id} missing`)
|
|
344
537
|
}
|
|
345
538
|
const rows = relation.closed.rows.map(function resolveRow(row, rowId) {
|
|
346
|
-
const values:
|
|
539
|
+
const values: Value[] = [BigInt(rowId)]
|
|
347
540
|
row.values.forEach(function resolveCell(literal, column) {
|
|
348
541
|
const field = info.fields[column + 1]
|
|
349
542
|
if (field === undefined) {
|
|
@@ -388,8 +581,8 @@ function parseSpec(spec: SchemaSpec): LogDescriptor {
|
|
|
388
581
|
break
|
|
389
582
|
}
|
|
390
583
|
case "containment": {
|
|
391
|
-
const source =
|
|
392
|
-
const target =
|
|
584
|
+
const source = specSideOf(tables, statement.source)
|
|
585
|
+
const target = specSideOf(tables, statement.target)
|
|
393
586
|
statements.push({ id: statements.length, kind: "containment", source, target })
|
|
394
587
|
if (statement.bidirectional) {
|
|
395
588
|
statements.push({ id: statements.length, kind: "containment", source: target, target: source })
|
|
@@ -424,46 +617,14 @@ function parseSpec(spec: SchemaSpec): LogDescriptor {
|
|
|
424
617
|
}
|
|
425
618
|
}
|
|
426
619
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (hashed === undefined) {
|
|
430
|
-
const bytes = fingerprintOf(relations, statements)
|
|
431
|
-
hashed = { hex: toHex(bytes), bytes }
|
|
432
|
-
}
|
|
433
|
-
return hashed
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const descriptor: LogDescriptor = {
|
|
620
|
+
const fingerprint = "00".repeat(32)
|
|
621
|
+
return {
|
|
437
622
|
relations,
|
|
438
623
|
relationByName: byName,
|
|
439
624
|
statements,
|
|
440
625
|
braidOfRelation,
|
|
441
626
|
braidMembers,
|
|
442
627
|
serialAtStatements,
|
|
443
|
-
get fingerprint() {
|
|
444
|
-
return fingerprintLazily().hex
|
|
445
|
-
},
|
|
446
|
-
get fingerprintBytes() {
|
|
447
|
-
return fingerprintLazily().bytes
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return descriptor
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* The same descriptor under a pinned fingerprint — for stores whose
|
|
455
|
-
* identity is carried (a manifest, a conformance sidecar) rather than
|
|
456
|
-
* recomputed, e.g. when the mirror cannot hash a closed relation's
|
|
457
|
-
* interned string axioms.
|
|
458
|
-
*/
|
|
459
|
-
function withFingerprint(descriptor: LogDescriptor, fingerprint: string): LogDescriptor {
|
|
460
|
-
return {
|
|
461
|
-
relations: descriptor.relations,
|
|
462
|
-
relationByName: descriptor.relationByName,
|
|
463
|
-
statements: descriptor.statements,
|
|
464
|
-
braidOfRelation: descriptor.braidOfRelation,
|
|
465
|
-
braidMembers: descriptor.braidMembers,
|
|
466
|
-
serialAtStatements: descriptor.serialAtStatements,
|
|
467
628
|
fingerprint,
|
|
468
629
|
fingerprintBytes: fromHex(fingerprint)
|
|
469
630
|
}
|
|
@@ -472,7 +633,7 @@ function withFingerprint(descriptor: LogDescriptor, fingerprint: string): LogDes
|
|
|
472
633
|
function deriveBraids(
|
|
473
634
|
relations: readonly RelationInfo[],
|
|
474
635
|
statements: readonly StatementInfo[]
|
|
475
|
-
): { braidOfRelation: Map<number,
|
|
636
|
+
): { braidOfRelation: Map<number, Braid>; braidMembers: Map<Braid, readonly number[]> } {
|
|
476
637
|
const parent = new Map<number, number>()
|
|
477
638
|
for (const relation of relations) {
|
|
478
639
|
if (!relation.closed) {
|
|
@@ -517,8 +678,8 @@ function deriveBraids(
|
|
|
517
678
|
list.push(id)
|
|
518
679
|
}
|
|
519
680
|
}
|
|
520
|
-
const braidOfRelation = new Map<number,
|
|
521
|
-
const braidMembers = new Map<
|
|
681
|
+
const braidOfRelation = new Map<number, Braid>()
|
|
682
|
+
const braidMembers = new Map<Braid, readonly number[]>()
|
|
522
683
|
for (const [root, ids] of [...members.entries()].sort(function byRoot(a, b) {
|
|
523
684
|
return a[0] - b[0]
|
|
524
685
|
})) {
|
|
@@ -534,208 +695,16 @@ function deriveBraids(
|
|
|
534
695
|
return { braidOfRelation, braidMembers }
|
|
535
696
|
}
|
|
536
697
|
|
|
537
|
-
/** `bumbledb-schema-v5` canonical bytes, mirrored from the engine's own encoder. */
|
|
538
|
-
const FORMAT_VERSION_LABEL = "bumbledb-schema-v5"
|
|
539
|
-
|
|
540
|
-
const VALUE_TYPE_TAG = { bool: 0, u64: 2, i64: 3, string: 4, fixedBytes: 5, interval: 6, fixedInterval: 7 } as const
|
|
541
|
-
|
|
542
|
-
function putLen(out: ByteWriter, len: number): void {
|
|
543
|
-
out.u32le(len)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function putBytes(out: ByteWriter, raw: Uint8Array): void {
|
|
547
|
-
putLen(out, raw.length)
|
|
548
|
-
out.bytes(raw)
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function putString(out: ByteWriter, text: string): void {
|
|
552
|
-
putBytes(out, utf8Encoder.encode(text))
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function putValueType(out: ByteWriter, type: ValueTypeSpec): void {
|
|
556
|
-
switch (type.kind) {
|
|
557
|
-
case "bool": {
|
|
558
|
-
out.u8(VALUE_TYPE_TAG.bool)
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
|
-
case "u64": {
|
|
562
|
-
out.u8(VALUE_TYPE_TAG.u64)
|
|
563
|
-
return
|
|
564
|
-
}
|
|
565
|
-
case "i64": {
|
|
566
|
-
out.u8(VALUE_TYPE_TAG.i64)
|
|
567
|
-
return
|
|
568
|
-
}
|
|
569
|
-
case "string": {
|
|
570
|
-
out.u8(VALUE_TYPE_TAG.string)
|
|
571
|
-
return
|
|
572
|
-
}
|
|
573
|
-
case "fixedBytes": {
|
|
574
|
-
out.u8(VALUE_TYPE_TAG.fixedBytes)
|
|
575
|
-
out.u16le(type.len)
|
|
576
|
-
return
|
|
577
|
-
}
|
|
578
|
-
case "interval": {
|
|
579
|
-
if (type.width === undefined) {
|
|
580
|
-
out.u8(VALUE_TYPE_TAG.interval)
|
|
581
|
-
out.u8(type.element === "u64" ? 0 : 1)
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
out.u8(VALUE_TYPE_TAG.fixedInterval)
|
|
585
|
-
out.u8(type.element === "u64" ? 0 : 1)
|
|
586
|
-
out.u64le(type.width)
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function putSide(out: ByteWriter, relations: readonly RelationInfo[], side: SideInfo): void {
|
|
593
|
-
out.u32le(side.relation)
|
|
594
|
-
putLen(out, side.projection.length)
|
|
595
|
-
for (const field of side.projection) {
|
|
596
|
-
out.u16le(field)
|
|
597
|
-
}
|
|
598
|
-
putLen(out, side.selection.length)
|
|
599
|
-
const relation = relations[side.relation]
|
|
600
|
-
if (relation === undefined) {
|
|
601
|
-
throw errors.new(`side cites unknown relation id ${side.relation}`)
|
|
602
|
-
}
|
|
603
|
-
for (const binding of side.selection) {
|
|
604
|
-
out.u16le(binding.field)
|
|
605
|
-
const field = relation.fields[binding.field]
|
|
606
|
-
if (field === undefined) {
|
|
607
|
-
throw errors.new(`selection cites unknown field ordinal ${binding.field}`)
|
|
608
|
-
}
|
|
609
|
-
putLen(out, binding.values.length)
|
|
610
|
-
for (const value of binding.values) {
|
|
611
|
-
if (typeof value === "string") {
|
|
612
|
-
putString(out, value)
|
|
613
|
-
} else {
|
|
614
|
-
writeCanonicalLiteral(out, field.type, value)
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function fingerprintOf(relations: readonly RelationInfo[], statements: readonly StatementInfo[]): Uint8Array {
|
|
621
|
-
const out = new ByteWriter(1024)
|
|
622
|
-
putString(out, FORMAT_VERSION_LABEL)
|
|
623
|
-
putLen(out, relations.length)
|
|
624
|
-
for (const relation of relations) {
|
|
625
|
-
putString(out, relation.name)
|
|
626
|
-
putLen(out, relation.fields.length)
|
|
627
|
-
for (const field of relation.fields) {
|
|
628
|
-
putString(out, field.name)
|
|
629
|
-
putValueType(out, field.type)
|
|
630
|
-
out.u8(field.fresh ? 1 : 0)
|
|
631
|
-
}
|
|
632
|
-
if (!relation.closed) {
|
|
633
|
-
out.u8(0)
|
|
634
|
-
} else {
|
|
635
|
-
out.u8(1)
|
|
636
|
-
putLen(out, relation.rows.length)
|
|
637
|
-
relation.rows.forEach(function putRow(row, rowId) {
|
|
638
|
-
const handle = relation.handles[rowId]
|
|
639
|
-
if (handle === undefined) {
|
|
640
|
-
throw errors.new(`closed relation ${relation.name}: no handle for row ${rowId}`)
|
|
641
|
-
}
|
|
642
|
-
putString(out, handle)
|
|
643
|
-
const fact = new ByteWriter(64)
|
|
644
|
-
row.forEach(function putCell(value, ordinal) {
|
|
645
|
-
const field = relation.fields[ordinal]
|
|
646
|
-
if (field === undefined) {
|
|
647
|
-
throw errors.new(`closed relation ${relation.name}: no field at ordinal ${ordinal}`)
|
|
648
|
-
}
|
|
649
|
-
if (field.type.kind === "string") {
|
|
650
|
-
throw errors.new(
|
|
651
|
-
`closed relation ${relation.name} has a string ground axiom — the fingerprint mirror does not carry interned axiom columns`
|
|
652
|
-
)
|
|
653
|
-
}
|
|
654
|
-
writeCanonicalLiteral(fact, field.type, value)
|
|
655
|
-
})
|
|
656
|
-
putBytes(out, fact.finish())
|
|
657
|
-
})
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
putLen(out, statements.length)
|
|
661
|
-
for (const statement of statements) {
|
|
662
|
-
switch (statement.kind) {
|
|
663
|
-
case "functionality": {
|
|
664
|
-
out.u8(0)
|
|
665
|
-
out.u32le(statement.relation)
|
|
666
|
-
putLen(out, statement.projection.length)
|
|
667
|
-
for (const field of statement.projection) {
|
|
668
|
-
out.u16le(field)
|
|
669
|
-
}
|
|
670
|
-
break
|
|
671
|
-
}
|
|
672
|
-
case "containment": {
|
|
673
|
-
out.u8(1)
|
|
674
|
-
putSide(out, relations, statement.source)
|
|
675
|
-
putSide(out, relations, statement.target)
|
|
676
|
-
break
|
|
677
|
-
}
|
|
678
|
-
case "capacity": {
|
|
679
|
-
out.u8(4)
|
|
680
|
-
putSide(out, relations, statement.target)
|
|
681
|
-
switch (statement.weight.kind) {
|
|
682
|
-
case "unit": {
|
|
683
|
-
out.u8(0)
|
|
684
|
-
break
|
|
685
|
-
}
|
|
686
|
-
case "field": {
|
|
687
|
-
out.u8(1)
|
|
688
|
-
out.u16le(statement.weight.field)
|
|
689
|
-
break
|
|
690
|
-
}
|
|
691
|
-
case "duration": {
|
|
692
|
-
out.u8(2)
|
|
693
|
-
out.u16le(statement.weight.field)
|
|
694
|
-
break
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
out.u64le(statement.lo)
|
|
698
|
-
switch (statement.hi.kind) {
|
|
699
|
-
case "unbounded": {
|
|
700
|
-
out.u8(0)
|
|
701
|
-
break
|
|
702
|
-
}
|
|
703
|
-
case "lit": {
|
|
704
|
-
out.u8(1)
|
|
705
|
-
out.u8(0)
|
|
706
|
-
out.u64le(statement.hi.value)
|
|
707
|
-
break
|
|
708
|
-
}
|
|
709
|
-
case "targetField": {
|
|
710
|
-
out.u8(1)
|
|
711
|
-
out.u8(1)
|
|
712
|
-
out.u16le(statement.hi.field)
|
|
713
|
-
break
|
|
714
|
-
}
|
|
715
|
-
case "targetDuration": {
|
|
716
|
-
out.u8(1)
|
|
717
|
-
out.u8(2)
|
|
718
|
-
out.u16le(statement.hi.field)
|
|
719
|
-
break
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
putSide(out, relations, statement.source)
|
|
723
|
-
break
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
return new Uint8Array(internalBlake3(out.finish()))
|
|
728
|
-
}
|
|
729
|
-
|
|
730
698
|
export type {
|
|
699
|
+
Braid,
|
|
700
|
+
Descriptor,
|
|
731
701
|
FieldInfo,
|
|
732
702
|
HiInfo,
|
|
733
|
-
LogDescriptor,
|
|
734
|
-
LogTheory,
|
|
735
703
|
RelationInfo,
|
|
736
704
|
SerialStatement,
|
|
737
705
|
SideInfo,
|
|
738
706
|
StatementInfo,
|
|
707
|
+
Theory,
|
|
739
708
|
WeightInfo
|
|
740
709
|
}
|
|
741
|
-
export { braidHex, descriptorOf, withFingerprint }
|
|
710
|
+
export { assembleFromSpec, braid, braidHex, descriptorOf, withFingerprint }
|