@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/src/descriptor.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  /**
2
- * The parsed protocol descriptor: the SDK's lowered `SchemaSpec` resolved
3
- * once — names to dense ids, handles to row ids, statements materialized
4
- * in the engine's own order (fresh auto-keys first, closed auto-keys,
5
- * then declared statements with `==` split into two containments) — so
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 { internalBlake3, lower } from "@bjornpagen/bumbledb"
20
+ import { internalDescriptor, lower } from "@bjornpagen/bumbledb"
22
21
  import * as errors from "@superbuilders/errors"
23
- import { ByteWriter, fromHex, toHex, utf8Encoder } from "#bytes.ts"
24
- import type { LogValue } from "#value.ts"
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: ValueTypeSpec
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 LogValue[]>
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 LogValue[] }>
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: string
91
+ readonly braid: Braid
84
92
  }
85
93
 
86
- interface LogDescriptor {
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 string (`c{smallest:08x}`). */
91
- readonly braidOfRelation: ReadonlyMap<number, string>
92
- /** Braid id string → member relation ids, ascending. */
93
- readonly braidMembers: ReadonlyMap<string, readonly number[]>
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 LogTheory = AnySchema | SchemaSpec | LogDescriptor
108
+ type Theory = AnySchema | SchemaSpec | Descriptor
101
109
 
102
- function isDescriptor(theory: LogTheory): theory is LogDescriptor {
110
+ function isDescriptor(theory: Theory): theory is Descriptor {
103
111
  return "braidMembers" in theory
104
112
  }
105
113
 
106
- function isSpec(theory: LogTheory): theory is SchemaSpec {
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, LogDescriptor>()
118
+ const cache = new WeakMap<object, Descriptor>()
111
119
 
112
- function descriptorOf(theory: LogTheory): LogDescriptor {
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 ? parseSpec(spec) : (cache.get(spec) ?? parseSpec(spec))
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): string {
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
- function logValueOf(value: ValueSpec): LogValue {
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): LogValue {
376
+ function resolveLiteral(tables: SpecTables, relation: RelationInfo, field: FieldInfo, literal: LiteralSpec): Value {
182
377
  if (literal.kind === "value") {
183
- return logValueOf(literal.value)
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): LogValue[] {
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 sideOf(tables: SpecTables, side: SideSpec): SideInfo {
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 = sideOf(tables, statement.target)
252
- const source = sideOf(tables, statement.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
- /** The `exact {n}` window: lo = hi = n; a non-literal n is grammar-refused upstream. */
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: LogValue[] = [BigInt(rowId)]
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 = sideOf(tables, statement.source)
392
- const target = sideOf(tables, statement.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
- let hashed: { readonly hex: string; readonly bytes: Uint8Array } | undefined
428
- function fingerprintLazily(): { readonly hex: string; readonly bytes: Uint8Array } {
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, string>; braidMembers: Map<string, readonly 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, string>()
521
- const braidMembers = new Map<string, readonly number[]>()
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 }