@bjornpagen/bumbledb 1.1.0 → 1.2.1

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.
Files changed (152) hide show
  1. package/COOKBOOK.md +114 -11
  2. package/README.md +52 -9
  3. package/dist/capacity.d.ts +22 -59
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +66 -13
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/changes.d.ts +26 -3
  8. package/dist/changes.d.ts.map +1 -1
  9. package/dist/changes.js +89 -44
  10. package/dist/changes.js.map +1 -1
  11. package/dist/closed.d.ts +23 -51
  12. package/dist/closed.d.ts.map +1 -1
  13. package/dist/closed.js +110 -168
  14. package/dist/closed.js.map +1 -1
  15. package/dist/codec.d.ts +4 -9
  16. package/dist/codec.d.ts.map +1 -1
  17. package/dist/codec.js +108 -183
  18. package/dist/codec.js.map +1 -1
  19. package/dist/compile.d.ts +16 -16
  20. package/dist/compile.d.ts.map +1 -1
  21. package/dist/compile.js +36 -15
  22. package/dist/compile.js.map +1 -1
  23. package/dist/db-native.d.ts +39 -0
  24. package/dist/db-native.d.ts.map +1 -1
  25. package/dist/db-native.js +8 -0
  26. package/dist/db-native.js.map +1 -1
  27. package/dist/db.d.ts +28 -8
  28. package/dist/db.d.ts.map +1 -1
  29. package/dist/db.js +91 -38
  30. package/dist/db.js.map +1 -1
  31. package/dist/errors.d.ts +11 -3
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +13 -1
  34. package/dist/errors.js.map +1 -1
  35. package/dist/face.d.ts +19 -25
  36. package/dist/face.d.ts.map +1 -1
  37. package/dist/face.js +30 -24
  38. package/dist/face.js.map +1 -1
  39. package/dist/fields.d.ts +13 -26
  40. package/dist/fields.d.ts.map +1 -1
  41. package/dist/fields.js +89 -116
  42. package/dist/fields.js.map +1 -1
  43. package/dist/immutable.d.ts +10 -0
  44. package/dist/immutable.d.ts.map +1 -0
  45. package/dist/immutable.js +75 -0
  46. package/dist/immutable.js.map +1 -0
  47. package/dist/index.d.ts +16 -12
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +5 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/internal/log.d.ts +2 -0
  52. package/dist/internal/log.d.ts.map +1 -1
  53. package/dist/internal/log.js +2 -0
  54. package/dist/internal/log.js.map +1 -1
  55. package/dist/law.d.ts +13 -11
  56. package/dist/law.d.ts.map +1 -1
  57. package/dist/law.js +1 -1
  58. package/dist/law.js.map +1 -1
  59. package/dist/lower.d.ts.map +1 -1
  60. package/dist/lower.js +11 -6
  61. package/dist/lower.js.map +1 -1
  62. package/dist/native.d.ts +1 -1
  63. package/dist/native.d.ts.map +1 -1
  64. package/dist/native.js.map +1 -1
  65. package/dist/query/atom.d.ts +1 -1
  66. package/dist/query/atom.d.ts.map +1 -1
  67. package/dist/query/atom.js.map +1 -1
  68. package/dist/query/description.d.ts +33 -0
  69. package/dist/query/description.d.ts.map +1 -0
  70. package/dist/query/description.js +302 -0
  71. package/dist/query/description.js.map +1 -0
  72. package/dist/query/find.d.ts +21 -6
  73. package/dist/query/find.d.ts.map +1 -1
  74. package/dist/query/find.js.map +1 -1
  75. package/dist/query/lower.d.ts +75 -10
  76. package/dist/query/lower.d.ts.map +1 -1
  77. package/dist/query/lower.js +132 -163
  78. package/dist/query/lower.js.map +1 -1
  79. package/dist/query/parse-ir.d.ts +7 -2
  80. package/dist/query/parse-ir.d.ts.map +1 -1
  81. package/dist/query/parse-ir.js +240 -53
  82. package/dist/query/parse-ir.js.map +1 -1
  83. package/dist/query/run.d.ts.map +1 -1
  84. package/dist/query/run.js +8 -10
  85. package/dist/query/run.js.map +1 -1
  86. package/dist/query/scope.d.ts +24 -16
  87. package/dist/query/scope.d.ts.map +1 -1
  88. package/dist/query/scope.js +5 -10
  89. package/dist/query/scope.js.map +1 -1
  90. package/dist/relation.d.ts +22 -48
  91. package/dist/relation.d.ts.map +1 -1
  92. package/dist/relation.js +30 -82
  93. package/dist/relation.js.map +1 -1
  94. package/dist/rows.d.ts +7 -4
  95. package/dist/rows.d.ts.map +1 -1
  96. package/dist/rows.js +44 -198
  97. package/dist/rows.js.map +1 -1
  98. package/dist/runtime-errors.d.ts +26 -2
  99. package/dist/runtime-errors.d.ts.map +1 -1
  100. package/dist/runtime-errors.js +43 -3
  101. package/dist/runtime-errors.js.map +1 -1
  102. package/dist/schema.d.ts +9 -1
  103. package/dist/schema.d.ts.map +1 -1
  104. package/dist/schema.js +85 -24
  105. package/dist/schema.js.map +1 -1
  106. package/dist/selection.d.ts +27 -0
  107. package/dist/selection.d.ts.map +1 -0
  108. package/dist/selection.js +104 -0
  109. package/dist/selection.js.map +1 -0
  110. package/dist/shape.d.ts +12 -12
  111. package/dist/shape.d.ts.map +1 -1
  112. package/dist/statements.d.ts +17 -52
  113. package/dist/statements.d.ts.map +1 -1
  114. package/dist/statements.js +92 -111
  115. package/dist/statements.js.map +1 -1
  116. package/dist/values.d.ts +25 -0
  117. package/dist/values.d.ts.map +1 -0
  118. package/dist/values.js +203 -0
  119. package/dist/values.js.map +1 -0
  120. package/pack-provenance.json +3 -3
  121. package/package.json +4 -4
  122. package/src/capacity.ts +93 -80
  123. package/src/changes.ts +183 -54
  124. package/src/closed.ts +161 -314
  125. package/src/codec.ts +116 -190
  126. package/src/compile.ts +56 -32
  127. package/src/db-native.ts +42 -0
  128. package/src/db.ts +134 -52
  129. package/src/errors.ts +16 -1
  130. package/src/face.ts +44 -74
  131. package/src/fields.ts +100 -146
  132. package/src/immutable.ts +76 -0
  133. package/src/index.ts +36 -29
  134. package/src/internal/log.ts +2 -0
  135. package/src/law.ts +21 -18
  136. package/src/lower.ts +13 -10
  137. package/src/native.ts +2 -0
  138. package/src/query/atom.ts +1 -0
  139. package/src/query/description.ts +394 -0
  140. package/src/query/find.ts +20 -7
  141. package/src/query/lower.ts +177 -187
  142. package/src/query/parse-ir.ts +247 -54
  143. package/src/query/run.ts +15 -9
  144. package/src/query/scope.ts +42 -33
  145. package/src/relation.ts +58 -142
  146. package/src/rows.ts +62 -205
  147. package/src/runtime-errors.ts +47 -3
  148. package/src/schema.ts +99 -29
  149. package/src/selection.ts +124 -0
  150. package/src/shape.ts +18 -12
  151. package/src/statements.ts +138 -189
  152. package/src/values.ts +223 -0
@@ -1,15 +1,18 @@
1
- import { sealedFieldsOf } from "#closed.ts"
1
+ import { membersAgree, sealedFieldsOf } from "#closed.ts"
2
2
  import { AuthoringError, SdkInvariantError } from "#errors.ts"
3
3
  import type { AnyClosedRoster, AnyField } from "#fields.ts"
4
4
  import {
5
5
  assertDeclarationOrderKey,
6
6
  f64 as f64Field,
7
+ i64 as i64Field,
7
8
  isFloatIntervalValue,
8
9
  isIntervalValue,
9
10
  literalShapeError,
10
11
  rosterOf,
12
+ rostersAgree,
11
13
  u64 as u64Field
12
14
  } from "#fields.ts"
15
+ import { snapshotData } from "#immutable.ts"
13
16
  import type { ClassRecordOf, SchemaClasses } from "#law.ts"
14
17
  import type {
15
18
  AtomIr,
@@ -86,7 +89,8 @@ import {
86
89
  } from "#query/scope.ts"
87
90
  import { literalWireOf } from "#scalar.ts"
88
91
  import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
89
- import { Uuid } from "#uuid.ts"
92
+ import { schemaDescriptor, schemasAgree } from "#schema.ts"
93
+ import { arrayValue, recordValue, taggedValueOf } from "#values.ts"
90
94
 
91
95
  type QueryRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], MatchOwner>
92
96
 
@@ -96,6 +100,8 @@ type RowOf<T> = InferredOf<T> extends { readonly row: infer R } ? R : never
96
100
 
97
101
  type HeadShape = Readonly<Record<string, ClassedField>> | undefined
98
102
 
103
+ type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends HeadShape } ? H : HeadShape
104
+
99
105
  interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
100
106
  readonly rule: RuleData
101
107
  readonly [inferred]?: { readonly row: Row; readonly params: P; readonly head: Head }
@@ -221,7 +227,12 @@ interface QueryRuleChain<
221
227
  bindings: B & CheckInteriorBindings<B>
222
228
  ): QueryRuleChain<Rels, P, Classes>
223
229
 
224
- find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P>
230
+ /** A record of variables is already a valid projection, including v(rel)
231
+ * for a generic relation. Aggregate judgments remain on the general form. */
232
+ find<const F extends Readonly<Record<string, AnyVar>>>(
233
+ entries: F
234
+ ): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
235
+ find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
225
236
  }
226
237
 
227
238
  interface InteriorRuleScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses>
@@ -288,6 +299,9 @@ interface InteriorRuleChain<
288
299
  * Nonrecursive derived stages emit aggregate/computed outputs too
289
300
  * (C05): only the RECURSIVE head stays projection-only.
290
301
  */
302
+ find<const F extends Readonly<Record<string, AnyVar>>>(
303
+ entries: F
304
+ ): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
291
305
  find<const F extends FindShape>(entries: F & CheckFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
292
306
  }
293
307
 
@@ -351,6 +365,9 @@ interface RecRuleChain<
351
365
  name: string,
352
366
  bindings: B & CheckInteriorBindings<B>
353
367
  ): RecRuleChain<Rels, P, Classes>
368
+ find<const F extends Readonly<Record<string, AnyVar>>>(
369
+ entries: F
370
+ ): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
354
371
  find<const F extends FindShape>(entries: F & CheckRecFind<F>): RuleValue<RowOfFind<F>, P, HeadRecordOf<Classes, F>>
355
372
  }
356
373
 
@@ -384,22 +401,23 @@ interface Query<
384
401
  Rels extends SchemaRelations,
385
402
  Row,
386
403
  Params extends ParamsRecord,
387
- Classes extends SchemaClasses = SchemaClasses
404
+ Classes extends SchemaClasses = SchemaClasses,
405
+ Head extends HeadShape = HeadShape
388
406
  > {
389
407
  readonly schema: Schema<Rels, Classes>
390
408
  readonly data: QueryData
391
409
 
392
410
  rule<RV extends AnyRuleValue>(
393
411
  build: (r: QueryRuleScope<Rels, Classes>) => RV
394
- ): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes>
412
+ ): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, Head | HeadOf<RV>>
395
413
 
396
414
  /** A diagnostic name for composition/tracing — never a schema relation. */
397
- named(label: string): Query<Rels, Row, Params, Classes>
415
+ named(label: string): Query<Rels, Row, Params, Classes, Head>
398
416
 
399
417
  interior(name: string, ...builds: never[]): never
400
418
 
401
419
  reach(name: string, arms: never): never
402
- readonly [inferred]?: { readonly row: Row; readonly params: Params }
420
+ readonly [inferred]?: { readonly row: Row; readonly params: Params; readonly head: Head }
403
421
  }
404
422
 
405
423
  interface AnyQuery {
@@ -418,7 +436,7 @@ type QueryStart<
418
436
  > = {
419
437
  rule<RV extends AnyRuleValue>(
420
438
  build: (r: QueryRuleScope<Rels, Classes>) => RV
421
- ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
439
+ ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>
422
440
  interior<const Builds extends readonly InteriorBuild<Rels, Classes>[]>(
423
441
  name: string,
424
442
  ...builds: Builds
@@ -436,7 +454,7 @@ type QueryReachStart<
436
454
  > = {
437
455
  rule<RV extends AnyRuleValue>(
438
456
  build: (r: QueryRuleScope<Rels, Classes>) => RV
439
- ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>
457
+ ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>
440
458
  }
441
459
 
442
460
  const termOps: TermOps = Object.freeze({
@@ -481,8 +499,8 @@ interface ResolvedBindings {
481
499
 
482
500
  /**
483
501
  * The MINT slot of a variable, the runtime twin of {@link MintSlotOf}: (i)
484
- * verifies the mint owner is the schema's own member value — a variable
485
- * minted from a foreign relation is refused, naming its label — and (ii)
502
+ * verifies the mint owner agrees with the schema's ordered declaration — a
503
+ * conflicting declaration is refused, naming its label — and (ii)
486
504
  * returns the descriptor it was minted at plus the law-computed class read
487
505
  * off the schema's frozen class map. Because {@link fieldJoins} is an
488
506
  * equality, judging every binding position against this one slot makes all
@@ -495,16 +513,16 @@ function mintSlotOf(context: ChainContext, ref: AnyVar): ClassedField {
495
513
  // the descriptor it projected and the carrier class that survived
496
514
  // the projection (aggregate outputs are bare derived scalars).
497
515
  const column = facade.columns.get(ref.column)
498
- if (column === undefined || (facade.source.schema as AnySchema) !== context.theory) {
516
+ if (column === undefined || !schemasAgree(facade.source.schema as AnySchema, context.theory)) {
499
517
  throw new AuthoringError({
500
518
  message: `the variable ${ref.label} was minted from a query template schema ${context.theory.name} does not own`
501
519
  })
502
520
  }
503
521
  return column.slot ?? { field: ref.field, class: undefined }
504
522
  }
505
- if (context.theory.relations[ref.owner.name] !== ref.owner) {
523
+ if (!membersAgree(context.theory.relations[ref.owner.name], ref.owner)) {
506
524
  throw new AuthoringError({
507
- message: `the variable ${ref.label} was minted from a relation schema ${context.theory.name} does not declare — mint variables with v() from the schema's own relations`
525
+ message: `the variable ${ref.label} was minted from a relation schema ${context.theory.name} does not declare — use an equivalent ordered declaration`
508
526
  })
509
527
  }
510
528
  return { field: ref.field, class: context.classes[ref.owner.name]?.[ref.column] }
@@ -532,7 +550,7 @@ function membershipSet(
532
550
  })
533
551
  }
534
552
  const seen = new Set<string>()
535
- const members = value.map(function memberName(member) {
553
+ const members = arrayValue(context, value, function memberName(context, member) {
536
554
  if (typeof member !== "string") {
537
555
  throw literalShapeError(context, `a ${roster.name} handle name (string)`, member)
538
556
  }
@@ -548,22 +566,30 @@ function membershipSet(
548
566
  return { name: `∈ ${roster.name} ${JSON.stringify(key)}`, members: Object.freeze(members) }
549
567
  }
550
568
 
569
+ function queryRecord(context: string, input: unknown): Readonly<Record<string, unknown>> {
570
+ return recordValue(context, input, typeof input === "object" && input !== null ? Object.keys(input) : [])
571
+ }
572
+
551
573
  function resolveBindings(
552
574
  context: ChainContext,
553
575
  label: string,
554
- relation: MatchOwner,
555
- bindings: Readonly<Record<string, unknown>>,
576
+ input: MatchOwner,
577
+ bindings: unknown,
556
578
  joins: (a: ClassedField, b: ClassedField) => boolean = fieldJoins
557
579
  ): ResolvedBindings {
580
+ const relation = context.theory.relations[input.name]
581
+ if (relation === undefined || !membersAgree(relation, input)) {
582
+ throw new AuthoringError({
583
+ message: `${label}: schema ${context.theory.name} does not declare this ordered relation`
584
+ })
585
+ }
586
+ // Retain the schema-owned declaration, not mutable caller metadata.
558
587
  const entries: BindingEntry[] = []
559
588
  const vars: AnyVar[] = []
560
589
  const uses: ParamUse[] = []
561
590
  const relationClasses = context.classes[relation.name]
562
591
  const ordered = sealedFieldsOf(relation)
563
- for (const [fieldName, value] of Object.entries(bindings)) {
564
- if (value === undefined) {
565
- continue
566
- }
592
+ for (const [fieldName, value] of Object.entries(queryRecord(label, bindings))) {
567
593
  const declared = ordered.find(function byName(candidate) {
568
594
  return candidate.name === fieldName
569
595
  })
@@ -627,7 +653,10 @@ function resolveBindings(
627
653
  })
628
654
  )
629
655
  } else {
630
- bound = Object.freeze({ kind: "literal" as const, value })
656
+ bound = Object.freeze({
657
+ kind: "literal" as const,
658
+ value: ownLiteral(`${label}.${fieldName}`, declared.field, value, "binding")
659
+ })
631
660
  }
632
661
  entries.push(Object.freeze({ field: fieldName, data: declared.field, class: fieldClass, term: bound }))
633
662
  }
@@ -683,9 +712,22 @@ function sideUses(op: CmpKind, side: CmpTermData, sibling: CmpTermData, uses: Pa
683
712
  }
684
713
 
685
714
  function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
715
+ queryRecord("query condition", cond)
686
716
  if (cond.cond === "cmp") {
687
- const lhs = cmpTermDataOf(cond.lhs)
688
- const rhs = cmpTermDataOf(cond.rhs)
717
+ let lhs = cmpTermDataOf(cond.lhs)
718
+ let rhs = cmpTermDataOf(cond.rhs)
719
+ if (lhs.kind === "literal" && rhs.kind === "var") {
720
+ lhs = Object.freeze({
721
+ kind: "literal",
722
+ value: ownLiteral("comparison literal", rhs.ref.field, lhs.value, cond.op)
723
+ })
724
+ }
725
+ if (rhs.kind === "literal" && lhs.kind === "var") {
726
+ rhs = Object.freeze({
727
+ kind: "literal",
728
+ value: ownLiteral("comparison literal", lhs.ref.field, rhs.value, cond.op)
729
+ })
730
+ }
689
731
  sideUses(cond.op, lhs, rhs, uses)
690
732
  sideUses(cond.op, rhs, lhs, uses)
691
733
  if (cond.op === "allen") {
@@ -705,8 +747,8 @@ function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
705
747
  return Object.freeze({ kind: "cmp" as const, op: { kind: cond.op }, lhs, rhs })
706
748
  }
707
749
  if (cond.cond === "tree") {
708
- const children = cond.children.map(function lowerChild(child) {
709
- return condDataOf(child, uses)
750
+ const children = arrayValue("query condition children", cond.children, function lowerChild(_context, child) {
751
+ return condDataOf(child as AnyCond, uses)
710
752
  })
711
753
  return Object.freeze({ kind: "tree" as const, op: cond.op, children: Object.freeze(children) })
712
754
  }
@@ -716,25 +758,22 @@ function condDataOf(cond: AnyCond, uses: ParamUse[]): CondData {
716
758
  }
717
759
 
718
760
  function advanceWhere(context: ChainContext, state: RuleBuildState, cond: AnyCond): RuleBuildState {
761
+ queryRecord("query condition", cond)
719
762
  if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
720
763
  throw new AuthoringError({ message: "where() takes a comparison, an and()/or() tree, or a negated atom" })
721
764
  }
722
765
  if (cond.cond === "notInterior") {
723
- const bindings: Readonly<Record<string, unknown>> = Object.fromEntries(
724
- Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
725
- return value !== undefined
726
- })
727
- )
728
- return notInteriorAdvance(context, state, cond.name, bindings)
766
+ return notInteriorAdvance(context, state, cond.name, queryRecord("negated interior bindings", cond.bindings))
729
767
  }
730
768
  if (cond.cond === "not") {
731
769
  const relation: MatchOwner = cond.relation
732
- const bindings: Readonly<Record<string, unknown>> = Object.fromEntries(
733
- Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
734
- return value !== undefined
735
- })
770
+ const resolved = resolveBindings(
771
+ context,
772
+ `negated relation ${relation.name}`,
773
+ relation,
774
+ cond.bindings,
775
+ fieldAntiJoins
736
776
  )
737
- const resolved = resolveBindings(context, `negated relation ${relation.name}`, relation, bindings, fieldAntiJoins)
738
777
  return Object.freeze({
739
778
  items: Object.freeze([...state.items, Object.freeze({ kind: "negated" as const, atom: resolved.atom })]),
740
779
  bound: state.bound,
@@ -757,10 +796,7 @@ function advanceInterior(
757
796
  kind: "interior" | "negatedInterior"
758
797
  ): RuleBuildState {
759
798
  const resolved: Array<{ readonly key: string; readonly ref: AnyVar }> = []
760
- for (const [key, value] of Object.entries(bindings)) {
761
- if (value === undefined) {
762
- continue
763
- }
799
+ for (const [key, value] of Object.entries(queryRecord(`interior ${target.name} bindings`, bindings))) {
764
800
  if (!isTerm(value) || value[term] !== "var") {
765
801
  throw new AuthoringError({
766
802
  message: `interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`
@@ -1177,28 +1213,43 @@ interface ImportEntry {
1177
1213
 
1178
1214
  const importEntries = new WeakMap<object, ImportEntry>()
1179
1215
  const importTables = new WeakSet<InteriorData>()
1216
+ const importOrigins = new WeakMap<InteriorData, InteriorData>()
1217
+
1218
+ function snapshotQueryData<A>(input: A): A {
1219
+ return snapshotData(input, (source, snapshot) => {
1220
+ // Detached imports retain their authoring identity for deduplication.
1221
+ if (importTables.has(source as InteriorData)) {
1222
+ const table = snapshot as InteriorData
1223
+ importTables.add(table)
1224
+ importOrigins.set(table, importOrigins.get(source as InteriorData) ?? (source as InteriorData))
1225
+ const inner = importInner.get(source as InteriorData)
1226
+ if (inner !== undefined) importInner.set(table, inner)
1227
+ }
1228
+ })
1229
+ }
1180
1230
 
1181
1231
  function importEntryOf(context: ChainContext, imported: AnyQuery): ImportEntry {
1182
1232
  const cached = importEntries.get(imported)
1183
1233
  if (cached !== undefined) {
1184
- if (imported.schema !== context.theory) {
1234
+ if (!schemasAgree(imported.schema, context.theory)) {
1185
1235
  throw new AuthoringError({
1186
1236
  message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
1187
1237
  })
1188
1238
  }
1189
1239
  return cached
1190
1240
  }
1191
- if (imported.schema !== context.theory) {
1241
+ if (!schemasAgree(imported.schema, context.theory)) {
1192
1242
  throw new AuthoringError({
1193
1243
  message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
1194
1244
  })
1195
1245
  }
1196
- if (imported.data.kind === "reach") {
1246
+ const data = imported.data
1247
+ if (data.kind === "reach") {
1197
1248
  throw new AuthoringError({
1198
1249
  message: `${contextLabel(context)}: a recursive query is not importable as a relation expression yet — declare the recursion on the consuming query (C05 boundary, recorded)`
1199
1250
  })
1200
1251
  }
1201
- const userParams = imported.data.params.filter(function userSupplied(entry) {
1252
+ const userParams = data.params.filter(function userSupplied(entry) {
1202
1253
  return entry.membership === undefined
1203
1254
  })
1204
1255
  if (userParams.length > 0) {
@@ -1211,10 +1262,10 @@ function importEntryOf(context: ChainContext, imported: AnyQuery): ImportEntry {
1211
1262
  const facade = importFacadeOf(imported)
1212
1263
  const table: InteriorData = Object.freeze({
1213
1264
  name: facade.owner.name,
1214
- finds: imported.data.finds,
1215
- rules: imported.data.rules
1265
+ finds: data.finds,
1266
+ rules: data.rules
1216
1267
  })
1217
- const entry: ImportEntry = Object.freeze({ table, inner: imported.data.interiors })
1268
+ const entry: ImportEntry = Object.freeze({ table, inner: data.interiors })
1218
1269
  importEntries.set(imported, entry)
1219
1270
  importTables.add(table)
1220
1271
  importInner.set(table, entry.inner)
@@ -1242,8 +1293,9 @@ function referencedImports(
1242
1293
  continue
1243
1294
  }
1244
1295
  const target = item.target as InteriorData
1245
- if (importTables.has(target) && !seen.has(target)) {
1246
- seen.add(target)
1296
+ const origin = importOrigins.get(target) ?? target
1297
+ if (importTables.has(target) && !seen.has(origin)) {
1298
+ seen.add(origin)
1247
1299
  out.push(target)
1248
1300
  }
1249
1301
  }
@@ -1280,7 +1332,8 @@ function spliceImports(
1280
1332
  const names = new Set<string>()
1281
1333
  const added = new Set<InteriorData>()
1282
1334
  function add(table: InteriorData): void {
1283
- if (added.has(table)) {
1335
+ const origin = importOrigins.get(table) ?? table
1336
+ if (added.has(origin)) {
1284
1337
  return
1285
1338
  }
1286
1339
  if (names.has(table.name)) {
@@ -1288,7 +1341,7 @@ function spliceImports(
1288
1341
  message: `query: the derived-table name ${table.name} appears twice after import splicing — rename the colliding interior (names are unique)`
1289
1342
  })
1290
1343
  }
1291
- added.add(table)
1344
+ added.add(origin)
1292
1345
  names.add(table.name)
1293
1346
  spliced.push(table)
1294
1347
  }
@@ -1330,15 +1383,13 @@ function notInteriorAdvance(
1330
1383
 
1331
1384
  function findColumns(context: ChainContext, entries: Readonly<Record<string, unknown>>): FindColumn[] {
1332
1385
  const columns: FindColumn[] = []
1386
+ queryRecord("query find", entries)
1333
1387
  // Nonrecursive derived stages (interiors) may emit aggregate/computed
1334
1388
  // outputs (C05: "do not leave Interior.rules projection only"); ONLY the
1335
1389
  // recursive feedback cycle stays projection-only — no aggregate,
1336
1390
  // arithmetic-created value or negation flows through it.
1337
1391
  const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined
1338
1392
  for (const [name, entry] of Object.entries(entries)) {
1339
- if (entry === undefined) {
1340
- continue
1341
- }
1342
1393
  if (recName !== undefined && !(isTerm(entry) && entry[term] === "var")) {
1343
1394
  throw new AuthoringError({
1344
1395
  message: `rec ${recName}: a rec head projects bound variables only — no aggregate or arithmetic-created value flows through the feedback cycle (compute over the finished set in the main rules)`
@@ -1540,7 +1591,7 @@ function paramRegistryOf(
1540
1591
  if (existing.anchor !== undefined && use.anchor !== undefined) {
1541
1592
  const registered = rosterOf(existing.anchor)
1542
1593
  const anchored = rosterOf(use.anchor)
1543
- if (registered !== anchored) {
1594
+ if (!rostersAgree(registered, anchored)) {
1544
1595
  throw new AuthoringError({
1545
1596
  message: `query param ${use.name} is anchored at ${renderParamAnchor(registered)} and at ${renderParamAnchor(anchored)} — a closed-anchored param translates handle names through ONE roster (one name, one domain); name the params differently`
1546
1597
  })
@@ -1627,7 +1678,7 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
1627
1678
  }
1628
1679
  rule.finds.forEach(function verifyClosedSlice(column, position) {
1629
1680
  const lead = first.finds[position]
1630
- if (lead !== undefined && column.closed !== lead.closed) {
1681
+ if (lead !== undefined && !rostersAgree(column.closed, lead.closed)) {
1631
1682
  throw new AuthoringError({
1632
1683
  message: `every rule of ${label} derives the same head — the head column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`
1633
1684
  })
@@ -1671,11 +1722,17 @@ function afterMainError(what: string): Error {
1671
1722
  }
1672
1723
 
1673
1724
  function makeRawQuery(
1674
- theory: AnySchema,
1675
- interiors: readonly InteriorData[],
1676
- rec: RecData | undefined,
1677
- rules: readonly RuleData[]
1725
+ inputTheory: AnySchema,
1726
+ inputInteriors: readonly InteriorData[],
1727
+ inputRec: RecData | undefined,
1728
+ inputRules: readonly RuleData[]
1678
1729
  ): RawQuery {
1730
+ const { theory, interiors, rec, rules } = snapshotQueryData({
1731
+ theory: inputTheory,
1732
+ interiors: inputInteriors,
1733
+ rec: inputRec,
1734
+ rules: inputRules
1735
+ })
1679
1736
  const mergedFinds = alignedHeadOf("a query", rules)
1680
1737
  const first = rules[0]
1681
1738
  if (first === undefined) {
@@ -1704,8 +1761,12 @@ function makeRawQuery(
1704
1761
  params
1705
1762
  })
1706
1763
  const value: RawQuery = {
1707
- schema: theory,
1708
- data,
1764
+ get schema() {
1765
+ return snapshotData(theory)
1766
+ },
1767
+ get data() {
1768
+ return snapshotQueryData(data)
1769
+ },
1709
1770
  rule(build) {
1710
1771
  const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory, ...env }))
1711
1772
  return makeRawQuery(theory, interiors, rec, [...rules, built.rule])
@@ -1727,13 +1788,19 @@ function makeRawQuery(
1727
1788
  return value
1728
1789
  }
1729
1790
 
1730
- function makeQuery<Rels extends SchemaRelations, Row, P extends ParamsRecord, Classes extends SchemaClasses>(
1791
+ function makeQuery<
1792
+ Rels extends SchemaRelations,
1793
+ Row,
1794
+ P extends ParamsRecord,
1795
+ Classes extends SchemaClasses,
1796
+ Head extends HeadShape
1797
+ >(
1731
1798
  theory: Schema<Rels, Classes>,
1732
1799
  interiors: readonly InteriorData[],
1733
1800
  rec: RecData | undefined,
1734
1801
  rules: readonly RuleData[]
1735
- ): Query<Rels, Row, P, Classes> {
1736
- return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes>
1802
+ ): Query<Rels, Row, P, Classes, Head> {
1803
+ return makeRawQuery(theory, interiors, rec, rules) as unknown as Query<Rels, Row, P, Classes, Head>
1737
1804
  }
1738
1805
 
1739
1806
  function collectInterior<Rels extends SchemaRelations, Classes extends SchemaClasses>(
@@ -1799,9 +1866,9 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
1799
1866
  const sealedRec: RecData["rec"] = [firstRec, ...rec.slice(1)]
1800
1867
  const recData: RecData = Object.freeze({
1801
1868
  name,
1802
- finds: sealedFinds,
1803
- base: sealedBase,
1804
- rec: sealedRec
1869
+ finds: Object.freeze(sealedFinds),
1870
+ base: Object.freeze(sealedBase),
1871
+ rec: Object.freeze(sealedRec)
1805
1872
  })
1806
1873
  return recData
1807
1874
  }
@@ -1852,9 +1919,11 @@ function makeQueryStart<Rels extends SchemaRelations, Classes extends SchemaClas
1852
1919
  },
1853
1920
  rule<RV extends AnyRuleValue>(
1854
1921
  build: (r: QueryRuleScope<Rels, Classes>) => RV
1855
- ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes> {
1922
+ ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>> {
1856
1923
  const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
1857
- return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>(theory, interiors, undefined, [built.rule])
1924
+ return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>(theory, interiors, undefined, [
1925
+ built.rule
1926
+ ])
1858
1927
  }
1859
1928
  }
1860
1929
  Object.freeze(start)
@@ -1870,9 +1939,11 @@ function makeQueryReachStart<Rels extends SchemaRelations, Classes extends Schem
1870
1939
  const start = {
1871
1940
  rule<RV extends AnyRuleValue>(
1872
1941
  build: (r: QueryRuleScope<Rels, Classes>) => RV
1873
- ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes> {
1942
+ ): Query<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>> {
1874
1943
  const built = build(makeQueryRuleScope<Rels, Classes>(theory, env))
1875
- return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes>(theory, interiors, rec, [built.rule])
1944
+ return makeQuery<Rels, RowOf<RV>, Flatten<P & ParamsOf<RV>>, Classes, HeadOf<RV>>(theory, interiors, rec, [
1945
+ built.rule
1946
+ ])
1876
1947
  }
1877
1948
  }
1878
1949
  Object.freeze(start)
@@ -1882,112 +1953,21 @@ function makeQueryReachStart<Rels extends SchemaRelations, Classes extends Schem
1882
1953
  function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
1883
1954
  theory: Schema<Rels, Classes>
1884
1955
  ): QueryStart<Rels, Classes> {
1885
- return makeQueryStart<Rels, Classes, Record<never, never>>(theory, [])
1886
- }
1887
-
1888
- function taggedHandleId(
1889
- context: string,
1890
- closed: { readonly name: string; readonly handles: readonly string[] },
1891
- value: unknown
1892
- ): TaggedValue {
1893
- if (typeof value !== "string") {
1894
- throw literalShapeError(context, `a ${closed.name} handle name (string)`, value)
1895
- }
1896
- const id = closed.handles.indexOf(value)
1897
- if (id < 0) {
1898
- throw new AuthoringError({
1899
- message: `${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
1900
- })
1901
- }
1902
- return { kind: "u64", value: BigInt(id) }
1956
+ return makeQueryStart<Rels, Classes, Record<never, never>>(schemaDescriptor(theory), [])
1903
1957
  }
1904
1958
 
1905
1959
  function taggedAtElementDomain(context: string, element: "u64" | "i64" | "f64", value: unknown): TaggedValue {
1906
- if (element === "f64") {
1907
- if (typeof value === "number") {
1908
- return { kind: "f64", value }
1909
- }
1910
- if (isFloatIntervalValue(value)) {
1911
- if (Number.isNaN(value.start) || Number.isNaN(value.end) || !(value.start < value.end)) {
1912
- throw literalShapeError(context, "a nonempty float interval with non-NaN endpoints", value)
1913
- }
1914
- return {
1915
- kind: "intervalF64",
1916
- start: Object.is(value.start, -0) ? 0 : value.start,
1917
- end: Object.is(value.end, -0) ? 0 : value.end
1918
- }
1919
- }
1920
- throw literalShapeError(context, "number (point) or { start, end } numbers (interval)", value)
1921
- }
1922
- if (typeof value === "bigint") {
1923
- if (element === "u64") {
1924
- return { kind: "u64", value }
1925
- }
1926
- return { kind: "i64", value }
1927
- }
1928
- if (isIntervalValue(value)) {
1929
- if (element === "u64") {
1930
- return { kind: "intervalU64", start: value.start, end: value.end }
1931
- }
1932
- return { kind: "intervalI64", start: value.start, end: value.end }
1933
- }
1934
- throw literalShapeError(context, "bigint (point) or { start, end } (interval)", value)
1960
+ const field: AnyField =
1961
+ typeof value === "number" || typeof value === "bigint"
1962
+ ? { u64: u64Field, i64: i64Field, f64: f64Field }[element]
1963
+ : { kind: "interval", element, width: undefined }
1964
+ return taggedValueOf(context, field, value)
1935
1965
  }
1936
1966
 
1937
1967
  function taggedLiteral(context: string, field: AnyField, value: unknown): TaggedValue {
1938
- const roster = rosterOf(field)
1939
- if (roster !== undefined) {
1940
- return taggedHandleId(context, roster, value)
1941
- }
1942
- switch (field.kind) {
1943
- case "bool": {
1944
- if (typeof value !== "boolean") {
1945
- throw literalShapeError(context, "boolean", value)
1946
- }
1947
- return { kind: "bool", value }
1948
- }
1949
- case "u64": {
1950
- if (typeof value !== "bigint") {
1951
- throw literalShapeError(context, "bigint", value)
1952
- }
1953
- return { kind: "u64", value }
1954
- }
1955
- case "i64": {
1956
- if (typeof value !== "bigint") {
1957
- throw literalShapeError(context, "bigint", value)
1958
- }
1959
- return { kind: "i64", value }
1960
- }
1961
- case "f64": {
1962
- if (typeof value !== "number") {
1963
- throw literalShapeError(context, "number", value)
1964
- }
1965
- return { kind: "f64", value }
1966
- }
1967
- case "uuid": {
1968
- if (!Uuid.isUuid(value)) {
1969
- throw literalShapeError(context, "a UUID (canonical UUID text)", value)
1970
- }
1971
- return { kind: "uuid", value }
1972
- }
1973
- case "str": {
1974
- if (typeof value !== "string") {
1975
- throw literalShapeError(context, "string", value)
1976
- }
1977
- if (!value.isWellFormed()) {
1978
- throw literalShapeError(context, "well-formed string", value)
1979
- }
1980
- return { kind: "string", value }
1981
- }
1982
- case "bytes": {
1983
- if (!(value instanceof Uint8Array)) {
1984
- throw literalShapeError(context, "Uint8Array", value)
1985
- }
1986
- return { kind: "fixedBytes", value }
1987
- }
1988
- case "interval":
1989
- return taggedAtElementDomain(context, field.element, value)
1990
- }
1968
+ return field.kind === "interval"
1969
+ ? taggedAtElementDomain(context, field.element, value)
1970
+ : taggedValueOf(context, field, value)
1991
1971
  }
1992
1972
 
1993
1973
  /**
@@ -1996,7 +1976,7 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
1996
1976
  * contributes its element domain, a scalar sibling its own type. At
1997
1977
  * `pointIn` the operand order is interval-left, point-right, so an
1998
1978
  * interval-shaped literal beside a scalar element-typed sibling is the LEGAL
1999
- * interval operand of `pointIn(t, span(...))`; under every other operator an
1979
+ * interval operand of `pointIn(t, { start, end })`; under every other operator an
2000
1980
  * interval shape against a scalar sibling stays refused.
2001
1981
  */
2002
1982
  function taggedCmpLiteral(context: string, sibling: AnyField, value: unknown, op: CmpKind | "binding"): TaggedValue {
@@ -2017,6 +1997,14 @@ function taggedCmpLiteral(context: string, sibling: AnyField, value: unknown, op
2017
1997
  return taggedLiteral(context, sibling, value)
2018
1998
  }
2019
1999
 
2000
+ /** Validate and own a literal when the query is authored, before caller mutation. */
2001
+ function ownLiteral(context: string, field: AnyField, value: unknown, op: CmpKind | "binding"): unknown {
2002
+ const tagged = taggedCmpLiteral(context, field, value, op)
2003
+ if (rosterOf(field) !== undefined) return value
2004
+ if ("value" in tagged) return tagged.value
2005
+ return Object.freeze({ start: tagged.start, end: tagged.end })
2006
+ }
2007
+
2020
2008
  interface LowerContext {
2021
2009
  readonly theory: AnySchema
2022
2010
  readonly relationIds: ReadonlyMap<string, number>
@@ -2054,7 +2042,7 @@ function paramIdOf(ctx: LowerContext, name: string): number {
2054
2042
 
2055
2043
  function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
2056
2044
  const member = ctx.theory.relations[atom.relation.name]
2057
- if (member !== atom.relation) {
2045
+ if (!membersAgree(member, atom.relation)) {
2058
2046
  throw new AuthoringError({
2059
2047
  message: `query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
2060
2048
  })
@@ -2306,20 +2294,21 @@ function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
2306
2294
 
2307
2295
  function lowerQuery(q: AnyQuery): ParsedQuery {
2308
2296
  const theory = q.schema
2297
+ const data = q.data
2309
2298
  const relationIds = new Map<string, number>()
2310
2299
  Object.keys(theory.relations).forEach(function assignOrdinal(name, index) {
2311
2300
  relationIds.set(name, index)
2312
2301
  })
2313
2302
  const interiorIds = new Map<string, number>()
2314
- q.data.interiors.forEach(function assignInteriorId(interior, index) {
2303
+ data.interiors.forEach(function assignInteriorId(interior, index) {
2315
2304
  interiorIds.set(interior.name, index)
2316
2305
  })
2317
- if (q.data.kind === "reach") {
2318
- interiorIds.set(q.data.rec.name, q.data.interiors.length)
2306
+ if (data.kind === "reach") {
2307
+ interiorIds.set(data.rec.name, data.interiors.length)
2319
2308
  }
2320
2309
  const paramIds = new Map<string, number>()
2321
2310
  const params = new Map<string, ParamEntry>()
2322
- q.data.params.forEach(function assignParamId(entry, index) {
2311
+ data.params.forEach(function assignParamId(entry, index) {
2323
2312
  if (entry.anchor === undefined) {
2324
2313
  throw new AuthoringError({
2325
2314
  message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
@@ -2329,7 +2318,7 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
2329
2318
  params.set(entry.name, entry)
2330
2319
  })
2331
2320
  const ctx: LowerContext = { theory, relationIds, interiorIds, paramIds, params }
2332
- const interiors = q.data.interiors.map(function lowerInterior(interior) {
2321
+ const interiors = data.interiors.map(function lowerInterior(interior) {
2333
2322
  return {
2334
2323
  head: interior.finds.map(headTermOf),
2335
2324
  rules: interior.rules.map(function lowerInteriorRule(rule) {
@@ -2337,22 +2326,22 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
2337
2326
  })
2338
2327
  }
2339
2328
  })
2340
- const head = q.data.finds.map(headTermOf)
2341
- const rules = q.data.rules.map(function lowerMainRule(rule) {
2329
+ const head = data.finds.map(headTermOf)
2330
+ const rules = data.rules.map(function lowerMainRule(rule) {
2342
2331
  return lowerRule(ctx, rule)
2343
2332
  })
2344
- if (q.data.kind === "cq") {
2333
+ if (data.kind === "cq") {
2345
2334
  return parseQueryIr({ kind: "cq", interiors, head, rules })
2346
2335
  }
2347
2336
  return parseQueryIr({
2348
2337
  kind: "reach",
2349
2338
  interiors,
2350
2339
  rec: {
2351
- head: q.data.rec.finds.map(headTermOf),
2352
- base: q.data.rec.base.map(function lowerBase(rule) {
2340
+ head: data.rec.finds.map(headTermOf),
2341
+ base: data.rec.base.map(function lowerBase(rule) {
2353
2342
  return lowerRule(ctx, rule)
2354
2343
  }),
2355
- rec: q.data.rec.rec.map(function lowerRecArm(rule) {
2344
+ rec: data.rec.rec.map(function lowerRecArm(rule) {
2356
2345
  return lowerRule(ctx, rule)
2357
2346
  })
2358
2347
  },
@@ -2364,6 +2353,7 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
2364
2353
  export type {
2365
2354
  AnyQuery,
2366
2355
  AnyRuleValue,
2356
+ ChainContext,
2367
2357
  Query,
2368
2358
  QueryData,
2369
2359
  QueryParams,
@@ -2378,4 +2368,4 @@ export type {
2378
2368
  RuleValue,
2379
2369
  TermOps
2380
2370
  }
2381
- export { lowerQuery, query, taggedCmpLiteral }
2371
+ export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, taggedCmpLiteral }