@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/descriptor.ts
CHANGED
|
@@ -2,30 +2,29 @@
|
|
|
2
2
|
* The parsed protocol descriptor: one thin parse of the engine's sealed
|
|
3
3
|
* truth — relation ids, sealed field order, resolved closed rows,
|
|
4
4
|
* materialized statements, and the real fingerprint — plus the braid
|
|
5
|
-
* map
|
|
5
|
+
* map and serial-at roster read off the log core's braids seat
|
|
6
|
+
* (`internalLogBraidsOf`): one derivation of the braid partition,
|
|
7
|
+
* projected here into the driver's branded shapes. Cached per theory
|
|
8
|
+
* value.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import type {
|
|
9
12
|
AnySchema,
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
FactValue,
|
|
14
|
+
LogCodecHandle,
|
|
12
15
|
SchemaSpec,
|
|
13
16
|
SealedDescriptor,
|
|
14
|
-
SealedSide,
|
|
15
17
|
SealedStatement,
|
|
16
|
-
|
|
17
|
-
StatementSpec,
|
|
18
|
-
ValueSpec
|
|
18
|
+
ValueTypeSpec
|
|
19
19
|
} from "@bjornpagen/bumbledb"
|
|
20
|
-
import { internalDescriptor, lower } from "@bjornpagen/bumbledb"
|
|
20
|
+
import { internalDescriptor, internalLogBraidsOf, internalLogCodec, lower } from "@bjornpagen/bumbledb"
|
|
21
21
|
import * as errors from "@superbuilders/errors"
|
|
22
22
|
import { regex } from "arkregex"
|
|
23
23
|
import { fromHex } from "#bytes.ts"
|
|
24
|
-
import type { Value } from "#value.ts"
|
|
25
24
|
|
|
26
25
|
interface FieldInfo {
|
|
27
26
|
readonly name: string
|
|
28
|
-
readonly type:
|
|
27
|
+
readonly type: ValueTypeSpec
|
|
29
28
|
readonly fresh: boolean
|
|
30
29
|
/** Set when the field's newtype names a closed relation's id class. */
|
|
31
30
|
readonly closedRef: string | undefined
|
|
@@ -39,49 +38,13 @@ interface RelationInfo {
|
|
|
39
38
|
/** Sealed order: a closed relation's synthetic u64 `id` at ordinal 0. */
|
|
40
39
|
readonly fields: readonly FieldInfo[]
|
|
41
40
|
/** Closed ground axioms in sealed order (id first), resolved values. */
|
|
42
|
-
readonly rows: ReadonlyArray<readonly
|
|
41
|
+
readonly rows: ReadonlyArray<readonly FactValue[]>
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
interface SideInfo {
|
|
46
|
-
readonly relation: number
|
|
47
|
-
readonly projection: readonly number[]
|
|
48
|
-
readonly selection: ReadonlyArray<{ readonly field: number; readonly values: readonly Value[] }>
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type WeightInfo =
|
|
52
|
-
| { readonly kind: "unit" }
|
|
53
|
-
| { readonly kind: "field"; readonly field: number }
|
|
54
|
-
| { readonly kind: "duration"; readonly field: number }
|
|
55
|
-
|
|
56
|
-
type HiInfo =
|
|
57
|
-
| { readonly kind: "unbounded" }
|
|
58
|
-
| { readonly kind: "lit"; readonly value: bigint }
|
|
59
|
-
| { readonly kind: "targetField"; readonly field: number }
|
|
60
|
-
| { readonly kind: "targetDuration"; readonly field: number }
|
|
61
|
-
|
|
62
|
-
type StatementInfo =
|
|
63
|
-
| {
|
|
64
|
-
readonly id: number
|
|
65
|
-
readonly kind: "functionality"
|
|
66
|
-
readonly relation: number
|
|
67
|
-
readonly projection: readonly number[]
|
|
68
|
-
}
|
|
69
|
-
| { readonly id: number; readonly kind: "containment"; readonly source: SideInfo; readonly target: SideInfo }
|
|
70
|
-
| {
|
|
71
|
-
readonly id: number
|
|
72
|
-
readonly kind: "capacity"
|
|
73
|
-
readonly target: SideInfo
|
|
74
|
-
readonly weight: WeightInfo
|
|
75
|
-
readonly lo: bigint
|
|
76
|
-
readonly hi: HiInfo
|
|
77
|
-
readonly source: SideInfo
|
|
78
|
-
}
|
|
79
|
-
|
|
80
44
|
declare const braidBrand: unique symbol
|
|
81
45
|
type Braid = string & { readonly [braidBrand]: typeof braidBrand }
|
|
82
46
|
|
|
83
47
|
const BRAID_ID = regex("^c[0-9a-f]{8}$")
|
|
84
|
-
const ID_CLASS = regex("^(.*)\\.id$")
|
|
85
48
|
|
|
86
49
|
function braid(raw: string): Braid {
|
|
87
50
|
if (!BRAID_ID.test(raw)) {
|
|
@@ -94,90 +57,51 @@ function refuseShape(why: string): never {
|
|
|
94
57
|
throw errors.new(`theory: ${why}`)
|
|
95
58
|
}
|
|
96
59
|
|
|
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
|
-
|
|
137
60
|
interface SerialStatement {
|
|
138
61
|
readonly statement: number
|
|
139
62
|
readonly braid: Braid
|
|
140
63
|
}
|
|
141
64
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
65
|
+
/** The braids seat names serial-at statements by id; the braid tag is a
|
|
66
|
+
* projection join through the statement's own relation. */
|
|
67
|
+
function serialAtFrom(
|
|
68
|
+
ids: readonly number[],
|
|
69
|
+
statements: readonly SealedStatement[],
|
|
145
70
|
braidOfRelation: ReadonlyMap<number, Braid>
|
|
146
71
|
): SerialStatement[] {
|
|
147
|
-
const
|
|
72
|
+
const statementById = new Map<number, SealedStatement>()
|
|
148
73
|
for (const statement of statements) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
}
|
|
74
|
+
statementById.set(statement.id, statement)
|
|
75
|
+
}
|
|
76
|
+
return ids.map(function joinBraid(id) {
|
|
77
|
+
const statement = statementById.get(id)
|
|
78
|
+
if (statement === undefined) {
|
|
79
|
+
refuseShape(`serial-at statement ${id} is not in the sealed statements`)
|
|
157
80
|
}
|
|
158
|
-
if (statement.kind === "
|
|
159
|
-
|
|
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
|
-
}
|
|
81
|
+
if (statement.kind === "containment") {
|
|
82
|
+
refuseShape(`serial-at statement ${id} is a containment`)
|
|
167
83
|
}
|
|
168
|
-
|
|
169
|
-
|
|
84
|
+
const relation = statement.kind === "functionality" ? statement.relation : statement.target.relation
|
|
85
|
+
const braid = braidOfRelation.get(relation)
|
|
86
|
+
if (braid === undefined) {
|
|
87
|
+
refuseShape(`serial-at statement ${id} relation ${relation} is in no braid`)
|
|
88
|
+
}
|
|
89
|
+
return { statement: id, braid }
|
|
90
|
+
})
|
|
170
91
|
}
|
|
171
92
|
|
|
172
93
|
interface Descriptor {
|
|
173
94
|
readonly relations: readonly RelationInfo[]
|
|
174
95
|
readonly relationByName: ReadonlyMap<string, RelationInfo>
|
|
175
|
-
readonly statements: readonly
|
|
96
|
+
readonly statements: readonly SealedStatement[]
|
|
176
97
|
/** Ordinary relation id → braid id (`c{smallest:08x}`). */
|
|
177
98
|
readonly braidOfRelation: ReadonlyMap<number, Braid>
|
|
178
99
|
/** Braid id → member relation ids, ascending. */
|
|
179
100
|
readonly braidMembers: ReadonlyMap<Braid, readonly number[]>
|
|
180
101
|
readonly serialAtStatements: readonly SerialStatement[]
|
|
102
|
+
/** The sealed per-theory codec handle — the grammar's one reader,
|
|
103
|
+
* minted once and cached with the parse. */
|
|
104
|
+
readonly codec: LogCodecHandle
|
|
181
105
|
readonly fingerprint: string
|
|
182
106
|
readonly fingerprintBytes: Uint8Array
|
|
183
107
|
}
|
|
@@ -214,95 +138,32 @@ function braidHex(relationId: number): Braid {
|
|
|
214
138
|
return braid(`c${relationId.toString(16).padStart(8, "0")}`)
|
|
215
139
|
}
|
|
216
140
|
|
|
217
|
-
function asValue(raw: unknown): Value {
|
|
218
|
-
if (typeof raw === "boolean" || typeof raw === "bigint" || typeof raw === "string" || raw instanceof Uint8Array) {
|
|
219
|
-
return raw
|
|
220
|
-
}
|
|
221
|
-
if (typeof raw === "object" && raw !== null && "start" in raw && "end" in raw) {
|
|
222
|
-
const interval = raw as { start: unknown; end: unknown }
|
|
223
|
-
if (typeof interval.start === "bigint" && typeof interval.end === "bigint") {
|
|
224
|
-
return { start: interval.start, end: interval.end }
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
refuseShape("sealed value is not a raw fact value")
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function sideOf(side: SealedSide): SideInfo {
|
|
231
|
-
return {
|
|
232
|
-
relation: side.relation,
|
|
233
|
-
projection: side.projection,
|
|
234
|
-
selection: side.selection.map(function valuesOf(binding) {
|
|
235
|
-
return { field: binding.field, values: binding.values.map(asValue) }
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function statementOf(statement: SealedStatement): StatementInfo {
|
|
241
|
-
switch (statement.kind) {
|
|
242
|
-
case "functionality":
|
|
243
|
-
return statement
|
|
244
|
-
case "containment":
|
|
245
|
-
return {
|
|
246
|
-
id: statement.id,
|
|
247
|
-
kind: "containment",
|
|
248
|
-
source: sideOf(statement.source),
|
|
249
|
-
target: sideOf(statement.target)
|
|
250
|
-
}
|
|
251
|
-
case "capacity":
|
|
252
|
-
return {
|
|
253
|
-
id: statement.id,
|
|
254
|
-
kind: "capacity",
|
|
255
|
-
target: sideOf(statement.target),
|
|
256
|
-
weight: statement.weight,
|
|
257
|
-
lo: statement.lo,
|
|
258
|
-
hi: statement.hi,
|
|
259
|
-
source: sideOf(statement.source)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
141
|
function fromSealed(spec: SchemaSpec): Descriptor {
|
|
265
142
|
const sealed: SealedDescriptor = internalDescriptor(spec)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
143
|
+
|
|
144
|
+
/** Handle newtype (`{name}.id`, off each closed relation's sealed id slot) → roster name. */
|
|
145
|
+
const ownerOfIdClass = new Map<string, string>()
|
|
146
|
+
for (const relation of sealed.relations) {
|
|
147
|
+
if (relation.extension === undefined) {
|
|
148
|
+
continue
|
|
270
149
|
}
|
|
271
|
-
|
|
150
|
+
const idSlot = relation.fields.find(function idOf(field) {
|
|
151
|
+
return field.name === "id"
|
|
152
|
+
})
|
|
153
|
+
const handleClass = idSlot?.newtype
|
|
154
|
+
if (handleClass === undefined) {
|
|
155
|
+
refuseShape(`closed relation ${relation.name} has no handle class on its sealed id slot`)
|
|
156
|
+
}
|
|
157
|
+
ownerOfIdClass.set(handleClass, relation.name)
|
|
272
158
|
}
|
|
273
|
-
const closedOwners = new Set(
|
|
274
|
-
spec.relations
|
|
275
|
-
.filter(function closedOf(relation) {
|
|
276
|
-
return relation.closed !== undefined
|
|
277
|
-
})
|
|
278
|
-
.map(function nameOf(relation) {
|
|
279
|
-
return relation.name
|
|
280
|
-
})
|
|
281
|
-
)
|
|
282
159
|
|
|
283
160
|
const relations: RelationInfo[] = sealed.relations.map(function relationOf(relation) {
|
|
284
|
-
const declared = specByName.get(relation.name)
|
|
285
|
-
if (declared === undefined) {
|
|
286
|
-
refuseShape(`sealed relation ${relation.name} is not in the spec`)
|
|
287
|
-
}
|
|
288
|
-
const closed = relation.extension !== undefined
|
|
289
161
|
const fields: FieldInfo[] = relation.fields.map(function fieldOf(field) {
|
|
290
|
-
if (field.name === "id" && closed) {
|
|
291
|
-
return { name: field.name, type: field.valueType, fresh: false, closedRef: relation.name }
|
|
292
|
-
}
|
|
293
|
-
const specField = declared.fields.find(function named(candidate) {
|
|
294
|
-
return candidate.name === field.name
|
|
295
|
-
})
|
|
296
|
-
let closedRef: string | undefined
|
|
297
|
-
const owner = specField?.newtype === undefined ? undefined : idClassOwner(specField.newtype)
|
|
298
|
-
if (owner !== undefined && closedOwners.has(owner)) {
|
|
299
|
-
closedRef = owner
|
|
300
|
-
}
|
|
301
162
|
return {
|
|
302
163
|
name: field.name,
|
|
303
164
|
type: field.valueType,
|
|
304
|
-
fresh:
|
|
305
|
-
closedRef
|
|
165
|
+
fresh: field.fresh,
|
|
166
|
+
closedRef: field.newtype === undefined ? undefined : ownerOfIdClass.get(field.newtype)
|
|
306
167
|
}
|
|
307
168
|
})
|
|
308
169
|
const extension = relation.extension ?? []
|
|
@@ -314,7 +175,7 @@ function fromSealed(spec: SchemaSpec): Descriptor {
|
|
|
314
175
|
const rows = extension.map(function rowOf(row) {
|
|
315
176
|
const cells = new Map(
|
|
316
177
|
row.values.map(function cellOf(cell) {
|
|
317
|
-
return [cell.name,
|
|
178
|
+
return [cell.name, cell.value]
|
|
318
179
|
})
|
|
319
180
|
)
|
|
320
181
|
return relation.fields.map(function cellValue(field) {
|
|
@@ -331,7 +192,7 @@ function fromSealed(spec: SchemaSpec): Descriptor {
|
|
|
331
192
|
return {
|
|
332
193
|
id: relation.id,
|
|
333
194
|
name: relation.name,
|
|
334
|
-
closed,
|
|
195
|
+
closed: relation.extension !== undefined,
|
|
335
196
|
handles: extension.map(function handleOf(row) {
|
|
336
197
|
return row.handle
|
|
337
198
|
}),
|
|
@@ -348,351 +209,20 @@ function fromSealed(spec: SchemaSpec): Descriptor {
|
|
|
348
209
|
byName.set(relation.name, relation)
|
|
349
210
|
}
|
|
350
211
|
|
|
351
|
-
const statements = sealed.statements
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
relations
|
|
359
|
-
|
|
360
|
-
statements,
|
|
361
|
-
braidOfRelation,
|
|
362
|
-
braidMembers,
|
|
363
|
-
serialAtStatements,
|
|
364
|
-
fingerprint,
|
|
365
|
-
fingerprintBytes: fromHex(fingerprint)
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* The same descriptor under a pinned fingerprint — for stores whose
|
|
371
|
-
* identity is carried (a manifest, a conformance sidecar) rather than
|
|
372
|
-
* recomputed.
|
|
373
|
-
*/
|
|
374
|
-
function withFingerprint(descriptor: Descriptor, fingerprint: string): Descriptor {
|
|
375
|
-
return {
|
|
376
|
-
relations: descriptor.relations,
|
|
377
|
-
relationByName: descriptor.relationByName,
|
|
378
|
-
statements: descriptor.statements,
|
|
379
|
-
braidOfRelation: descriptor.braidOfRelation,
|
|
380
|
-
braidMembers: descriptor.braidMembers,
|
|
381
|
-
serialAtStatements: descriptor.serialAtStatements,
|
|
382
|
-
fingerprint,
|
|
383
|
-
fingerprintBytes: fromHex(fingerprint)
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Assemble a descriptor from a SchemaSpec that is not a theory — the
|
|
389
|
-
* conformance corpus pins the codec and the braid map on shapes the
|
|
390
|
-
* engine seal refuses. Theories enter through descriptorOf.
|
|
391
|
-
*/
|
|
392
|
-
function rawValue(value: ValueSpec): Value {
|
|
393
|
-
switch (value.kind) {
|
|
394
|
-
case "bool":
|
|
395
|
-
return value.value
|
|
396
|
-
case "u64":
|
|
397
|
-
case "i64":
|
|
398
|
-
return value.value
|
|
399
|
-
case "string":
|
|
400
|
-
return value.value
|
|
401
|
-
case "fixedBytes":
|
|
402
|
-
return value.value
|
|
403
|
-
case "intervalU64":
|
|
404
|
-
case "intervalI64":
|
|
405
|
-
return { start: value.start, end: value.end }
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
interface SpecTables {
|
|
410
|
-
readonly spec: SchemaSpec
|
|
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
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function fieldsOf(
|
|
447
|
-
tables: Map<string, { closed: boolean; handles: readonly string[] }>,
|
|
448
|
-
relation: SchemaSpec["relations"][number]
|
|
449
|
-
): FieldInfo[] {
|
|
450
|
-
const fields: FieldInfo[] = []
|
|
451
|
-
if (relation.closed !== undefined) {
|
|
452
|
-
fields.push({
|
|
453
|
-
name: "id",
|
|
454
|
-
type: { kind: "u64" },
|
|
455
|
-
fresh: false,
|
|
456
|
-
closedRef: relation.name
|
|
457
|
-
})
|
|
458
|
-
}
|
|
459
|
-
for (const field of relation.fields) {
|
|
460
|
-
let closedRef: string | undefined
|
|
461
|
-
const owner = field.newtype === undefined ? undefined : idClassOwner(field.newtype)
|
|
462
|
-
if (owner !== undefined) {
|
|
463
|
-
const target = tables.get(owner)
|
|
464
|
-
if (target?.closed === true) {
|
|
465
|
-
closedRef = owner
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
fields.push({ name: field.name, type: field.valueType, fresh: field.fresh, closedRef })
|
|
469
|
-
}
|
|
470
|
-
return fields
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function resolveLiteral(tables: SpecTables, relation: RelationInfo, field: FieldInfo, literal: LiteralSpec): Value {
|
|
474
|
-
if (literal.kind === "value") {
|
|
475
|
-
return rawValue(literal.value)
|
|
476
|
-
}
|
|
477
|
-
if (field.closedRef === undefined) {
|
|
478
|
-
refuseShape(`handle literal ${literal.handle} on ${relation.name}.${field.name}, which references no closed roster`)
|
|
479
|
-
}
|
|
480
|
-
const target = tables.byName.get(field.closedRef)
|
|
481
|
-
if (target === undefined) {
|
|
482
|
-
refuseShape(`handle literal ${literal.handle}: unknown closed relation ${field.closedRef}`)
|
|
483
|
-
}
|
|
484
|
-
const id = target.handles.indexOf(literal.handle)
|
|
485
|
-
if (id === -1) {
|
|
486
|
-
refuseShape(`handle literal ${literal.handle} is not in the ${target.name} roster`)
|
|
487
|
-
}
|
|
488
|
-
return BigInt(id)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function literalSetOf(tables: SpecTables, relation: RelationInfo, field: FieldInfo, set: LiteralSetSpec): Value[] {
|
|
492
|
-
if (set.kind === "one") {
|
|
493
|
-
return [resolveLiteral(tables, relation, field, set.literal)]
|
|
494
|
-
}
|
|
495
|
-
return set.literals.map(function resolveEach(literal) {
|
|
496
|
-
return resolveLiteral(tables, relation, field, literal)
|
|
497
|
-
})
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function fieldOrdinal(relation: RelationInfo, name: string): number {
|
|
501
|
-
return fieldNamed(relation, name).ordinal
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function specSideOf(tables: SpecTables, side: SideSpec): SideInfo {
|
|
505
|
-
const relation = tables.byName.get(side.relation)
|
|
506
|
-
if (relation === undefined) {
|
|
507
|
-
refuseShape(`statement cites unknown relation ${side.relation}`)
|
|
508
|
-
}
|
|
509
|
-
const projection = side.projection.map(function ordinalOf(name) {
|
|
510
|
-
return fieldOrdinal(relation, name)
|
|
511
|
-
})
|
|
512
|
-
const selection = side.selection.map(function bindingOf(binding) {
|
|
513
|
-
const { ordinal, field } = fieldNamed(relation, binding[0])
|
|
514
|
-
return { field: ordinal, values: literalSetOf(tables, relation, field, binding[1]) }
|
|
515
|
-
})
|
|
516
|
-
return { relation: relation.id, projection, selection }
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function boundValue(context: string, bound: { readonly kind: string }): bigint {
|
|
520
|
-
if (bound.kind !== "lit") {
|
|
521
|
-
refuseShape(`${context}: dependent floors are refused by the schema grammar`)
|
|
522
|
-
}
|
|
523
|
-
return (bound as { readonly kind: "lit"; readonly value: bigint }).value
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function capacityOf(
|
|
527
|
-
tables: SpecTables,
|
|
528
|
-
id: number,
|
|
529
|
-
statement: Extract<StatementSpec, { kind: "capacity" }>
|
|
530
|
-
): StatementInfo {
|
|
531
|
-
const target = specSideOf(tables, statement.target)
|
|
532
|
-
const source = specSideOf(tables, statement.source)
|
|
533
|
-
const sourceRelation = tables.byName.get(statement.source.relation)
|
|
534
|
-
const targetRelation = tables.byName.get(statement.target.relation)
|
|
535
|
-
if (sourceRelation === undefined || targetRelation === undefined) {
|
|
536
|
-
refuseShape("capacity statement cites unknown relations")
|
|
537
|
-
}
|
|
538
|
-
let weight: WeightInfo
|
|
539
|
-
switch (statement.weight.kind) {
|
|
540
|
-
case "unit": {
|
|
541
|
-
weight = { kind: "unit" }
|
|
542
|
-
break
|
|
543
|
-
}
|
|
544
|
-
case "field": {
|
|
545
|
-
weight = { kind: "field", field: fieldOrdinal(sourceRelation, statement.weight.field) }
|
|
546
|
-
break
|
|
547
|
-
}
|
|
548
|
-
case "durationField": {
|
|
549
|
-
weight = { kind: "duration", field: fieldOrdinal(sourceRelation, statement.weight.field) }
|
|
550
|
-
break
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
let lo: bigint
|
|
554
|
-
let hi: HiInfo
|
|
555
|
-
const window = statement.window
|
|
556
|
-
switch (window.kind) {
|
|
557
|
-
case "exact": {
|
|
558
|
-
lo = boundValue("capacity exact bound", window.n)
|
|
559
|
-
hi = { kind: "lit", value: lo }
|
|
560
|
-
break
|
|
561
|
-
}
|
|
562
|
-
case "floor": {
|
|
563
|
-
lo = boundValue("capacity floor", window.lo)
|
|
564
|
-
hi = { kind: "unbounded" }
|
|
565
|
-
break
|
|
566
|
-
}
|
|
567
|
-
case "range": {
|
|
568
|
-
lo = boundValue("capacity floor", window.lo)
|
|
569
|
-
switch (window.hi.kind) {
|
|
570
|
-
case "lit": {
|
|
571
|
-
hi = { kind: "lit", value: window.hi.value }
|
|
572
|
-
break
|
|
573
|
-
}
|
|
574
|
-
case "field": {
|
|
575
|
-
hi = { kind: "targetField", field: fieldOrdinal(targetRelation, window.hi.field) }
|
|
576
|
-
break
|
|
577
|
-
}
|
|
578
|
-
case "durationField": {
|
|
579
|
-
hi = { kind: "targetDuration", field: fieldOrdinal(targetRelation, window.hi.field) }
|
|
580
|
-
break
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
break
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return { id, kind: "capacity", target, weight, lo, hi, source }
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function assembleFromSpec(spec: SchemaSpec): Descriptor {
|
|
590
|
-
const prepass = new Map<string, { closed: boolean; handles: readonly string[] }>()
|
|
591
|
-
for (const relation of spec.relations) {
|
|
592
|
-
if (prepass.has(relation.name)) {
|
|
593
|
-
refuseShape(`duplicate relation ${relation.name}`)
|
|
594
|
-
}
|
|
595
|
-
prepass.set(relation.name, {
|
|
596
|
-
closed: relation.closed !== undefined,
|
|
597
|
-
handles: relation.closed === undefined ? [] : relation.closed.rows.map((row) => row.handle)
|
|
598
|
-
})
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const byName = new Map<string, RelationInfo>()
|
|
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
|
-
}
|
|
609
|
-
const info: RelationInfo = {
|
|
610
|
-
id: nextId,
|
|
611
|
-
name: relation.name,
|
|
612
|
-
closed: table.closed,
|
|
613
|
-
handles: table.handles,
|
|
614
|
-
fields: fieldsOf(prepass, relation),
|
|
615
|
-
rows: []
|
|
616
|
-
}
|
|
617
|
-
nextId += 1
|
|
618
|
-
byName.set(relation.name, info)
|
|
619
|
-
byId.set(info.id, info)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
const tables: SpecTables = { spec, byName, byId }
|
|
623
|
-
|
|
624
|
-
for (const relation of spec.relations) {
|
|
625
|
-
if (relation.closed === undefined) {
|
|
626
|
-
continue
|
|
627
|
-
}
|
|
628
|
-
const info = byName.get(relation.name)
|
|
629
|
-
if (info === undefined) {
|
|
630
|
-
refuseShape(`closed relation ${relation.name} missing`)
|
|
631
|
-
}
|
|
632
|
-
const rows = relation.closed.rows.map(function resolveRow(row, rowId) {
|
|
633
|
-
const values: Value[] = [BigInt(rowId)]
|
|
634
|
-
for (const [field, literal] of zipClosedPayload(info, row.handle, row.values)) {
|
|
635
|
-
values.push(resolveLiteral(tables, info, field, literal))
|
|
636
|
-
}
|
|
637
|
-
return values
|
|
638
|
-
})
|
|
639
|
-
const updated = { ...info, rows }
|
|
640
|
-
byName.set(relation.name, updated)
|
|
641
|
-
byId.set(info.id, updated)
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const relations = [...byId.values()]
|
|
645
|
-
|
|
646
|
-
const statements: StatementInfo[] = []
|
|
647
|
-
relations.forEach(function freshKeys(relation) {
|
|
648
|
-
relation.fields.forEach(function freshKey(field, ordinal) {
|
|
649
|
-
if (field.fresh) {
|
|
650
|
-
statements.push({ id: statements.length, kind: "functionality", relation: relation.id, projection: [ordinal] })
|
|
651
|
-
}
|
|
652
|
-
})
|
|
653
|
-
})
|
|
654
|
-
relations.forEach(function closedKeys(relation) {
|
|
655
|
-
if (relation.closed) {
|
|
656
|
-
statements.push({ id: statements.length, kind: "functionality", relation: relation.id, projection: [0] })
|
|
657
|
-
}
|
|
658
|
-
})
|
|
659
|
-
for (const statement of spec.statements) {
|
|
660
|
-
switch (statement.kind) {
|
|
661
|
-
case "fd": {
|
|
662
|
-
const relation = byName.get(statement.relation)
|
|
663
|
-
if (relation === undefined) {
|
|
664
|
-
refuseShape(`key statement cites unknown relation ${statement.relation}`)
|
|
665
|
-
}
|
|
666
|
-
statements.push({
|
|
667
|
-
id: statements.length,
|
|
668
|
-
kind: "functionality",
|
|
669
|
-
relation: relation.id,
|
|
670
|
-
projection: statement.projection.map(function ordinalOf(name) {
|
|
671
|
-
return fieldOrdinal(relation, name)
|
|
672
|
-
})
|
|
673
|
-
})
|
|
674
|
-
break
|
|
675
|
-
}
|
|
676
|
-
case "containment": {
|
|
677
|
-
const source = specSideOf(tables, statement.source)
|
|
678
|
-
const target = specSideOf(tables, statement.target)
|
|
679
|
-
statements.push({ id: statements.length, kind: "containment", source, target })
|
|
680
|
-
if (statement.bidirectional) {
|
|
681
|
-
statements.push({ id: statements.length, kind: "containment", source: target, target: source })
|
|
682
|
-
}
|
|
683
|
-
break
|
|
684
|
-
}
|
|
685
|
-
case "capacity": {
|
|
686
|
-
statements.push(capacityOf(tables, statements.length, statement))
|
|
687
|
-
break
|
|
688
|
-
}
|
|
212
|
+
const statements = sealed.statements
|
|
213
|
+
const braids = internalLogBraidsOf(sealed)
|
|
214
|
+
const braidOfRelation = new Map<number, Braid>()
|
|
215
|
+
const braidMembers = new Map<Braid, readonly number[]>()
|
|
216
|
+
for (const component of braids.components) {
|
|
217
|
+
const id = braidHex(component.braid)
|
|
218
|
+
braidMembers.set(id, component.relations)
|
|
219
|
+
for (const member of component.relations) {
|
|
220
|
+
braidOfRelation.set(member, id)
|
|
689
221
|
}
|
|
690
222
|
}
|
|
223
|
+
const serialAtStatements = serialAtFrom(braids.serialAt, statements, braidOfRelation)
|
|
691
224
|
|
|
692
|
-
const
|
|
693
|
-
const serialAtStatements = serialAtOf(byId, statements, braidOfRelation)
|
|
694
|
-
|
|
695
|
-
const fingerprint = "00".repeat(32)
|
|
225
|
+
const fingerprint = sealed.fingerprint
|
|
696
226
|
return {
|
|
697
227
|
relations,
|
|
698
228
|
relationByName: byName,
|
|
@@ -700,86 +230,11 @@ function assembleFromSpec(spec: SchemaSpec): Descriptor {
|
|
|
700
230
|
braidOfRelation,
|
|
701
231
|
braidMembers,
|
|
702
232
|
serialAtStatements,
|
|
233
|
+
codec: internalLogCodec(sealed),
|
|
703
234
|
fingerprint,
|
|
704
235
|
fingerprintBytes: fromHex(fingerprint)
|
|
705
236
|
}
|
|
706
237
|
}
|
|
707
238
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
statements: readonly StatementInfo[]
|
|
711
|
-
): { braidOfRelation: Map<number, Braid>; braidMembers: Map<Braid, readonly number[]> } {
|
|
712
|
-
const parent = new Map<number, number>()
|
|
713
|
-
for (const relation of byId.values()) {
|
|
714
|
-
if (!relation.closed) {
|
|
715
|
-
parent.set(relation.id, relation.id)
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
function rootOf(id: number): number {
|
|
719
|
-
let cursor = id
|
|
720
|
-
for (;;) {
|
|
721
|
-
const up = parent.get(cursor)
|
|
722
|
-
if (up === undefined || up === cursor) {
|
|
723
|
-
return cursor
|
|
724
|
-
}
|
|
725
|
-
cursor = up
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
function union(a: number, b: number): void {
|
|
729
|
-
const ra = rootOf(a)
|
|
730
|
-
const rb = rootOf(b)
|
|
731
|
-
if (ra !== rb) {
|
|
732
|
-
parent.set(Math.max(ra, rb), Math.min(ra, rb))
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
for (const statement of statements) {
|
|
736
|
-
if (statement.kind === "functionality") {
|
|
737
|
-
continue
|
|
738
|
-
}
|
|
739
|
-
const source = relationOfId(byId, statement.source.relation)
|
|
740
|
-
const target = relationOfId(byId, statement.target.relation)
|
|
741
|
-
if (source.closed || target.closed) {
|
|
742
|
-
continue
|
|
743
|
-
}
|
|
744
|
-
union(source.id, target.id)
|
|
745
|
-
}
|
|
746
|
-
const members = new Map<number, number[]>()
|
|
747
|
-
for (const id of parent.keys()) {
|
|
748
|
-
const root = rootOf(id)
|
|
749
|
-
const list = members.get(root)
|
|
750
|
-
if (list === undefined) {
|
|
751
|
-
members.set(root, [id])
|
|
752
|
-
} else {
|
|
753
|
-
list.push(id)
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
const braidOfRelation = new Map<number, Braid>()
|
|
757
|
-
const braidMembers = new Map<Braid, readonly number[]>()
|
|
758
|
-
for (const [root, ids] of [...members.entries()].sort(function byRoot(a, b) {
|
|
759
|
-
return a[0] - b[0]
|
|
760
|
-
})) {
|
|
761
|
-
const braid = braidHex(root)
|
|
762
|
-
ids.sort(function ascending(a, b) {
|
|
763
|
-
return a - b
|
|
764
|
-
})
|
|
765
|
-
braidMembers.set(braid, ids)
|
|
766
|
-
for (const id of ids) {
|
|
767
|
-
braidOfRelation.set(id, braid)
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
return { braidOfRelation, braidMembers }
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
export type {
|
|
774
|
-
Braid,
|
|
775
|
-
Descriptor,
|
|
776
|
-
FieldInfo,
|
|
777
|
-
HiInfo,
|
|
778
|
-
RelationInfo,
|
|
779
|
-
SerialStatement,
|
|
780
|
-
SideInfo,
|
|
781
|
-
StatementInfo,
|
|
782
|
-
Theory,
|
|
783
|
-
WeightInfo
|
|
784
|
-
}
|
|
785
|
-
export { assembleFromSpec, braid, braidHex, descriptorOf, withFingerprint }
|
|
239
|
+
export type { Braid, Descriptor, FieldInfo, RelationInfo, SerialStatement, Theory }
|
|
240
|
+
export { braid, braidHex, descriptorOf, serialAtFrom }
|