@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/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 the protocol derives from that truth. Cached per theory value.
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
- LiteralSetSpec,
11
- LiteralSpec,
13
+ FactValue,
14
+ LogCodecHandle,
12
15
  SchemaSpec,
13
16
  SealedDescriptor,
14
- SealedSide,
15
17
  SealedStatement,
16
- SideSpec,
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: SchemaSpec["relations"][number]["fields"][number]["valueType"]
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 Value[]>
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
- function serialAtOf(
143
- byId: ReadonlyMap<number, RelationInfo>,
144
- statements: readonly StatementInfo[],
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 serialAt: SerialStatement[] = []
72
+ const statementById = new Map<number, SealedStatement>()
148
73
  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
- }
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 === "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
- }
81
+ if (statement.kind === "containment") {
82
+ refuseShape(`serial-at statement ${id} is a containment`)
167
83
  }
168
- }
169
- return serialAt
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 StatementInfo[]
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
- 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}`)
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
- specByName.set(relation.name, relation)
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: specField?.fresh === true,
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, asValue(cell.value)]
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.map(statementOf)
352
- const byId = indexRelations(relations)
353
- const { braidOfRelation, braidMembers } = deriveBraids(byId, statements)
354
- const serialAtStatements = serialAtOf(byId, statements, braidOfRelation)
355
-
356
- const fingerprint = sealed.fingerprint
357
- return {
358
- relations,
359
- relationByName: byName,
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 { braidOfRelation, braidMembers } = deriveBraids(byId, statements)
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
- function deriveBraids(
709
- byId: ReadonlyMap<number, RelationInfo>,
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 }