@bjornpagen/bumbledb 1.3.0 → 2.0.0-rc.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 (290) hide show
  1. package/COOKBOOK.md +224 -95
  2. package/README.md +114 -45
  3. package/dist/alternatives.d.ts +5 -6
  4. package/dist/alternatives.d.ts.map +1 -1
  5. package/dist/alternatives.js +5 -6
  6. package/dist/alternatives.js.map +1 -1
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +85 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/capacity.d.ts +14 -17
  12. package/dist/capacity.d.ts.map +1 -1
  13. package/dist/capacity.js +7 -7
  14. package/dist/capacity.js.map +1 -1
  15. package/dist/changes.d.ts +32 -41
  16. package/dist/changes.d.ts.map +1 -1
  17. package/dist/changes.js +86 -110
  18. package/dist/changes.js.map +1 -1
  19. package/dist/closed.d.ts +2 -2
  20. package/dist/closed.d.ts.map +1 -1
  21. package/dist/closed.js +5 -5
  22. package/dist/closed.js.map +1 -1
  23. package/dist/codec.d.ts +20 -27
  24. package/dist/codec.d.ts.map +1 -1
  25. package/dist/codec.js +48 -22
  26. package/dist/codec.js.map +1 -1
  27. package/dist/compile.d.ts +26 -50
  28. package/dist/compile.d.ts.map +1 -1
  29. package/dist/compile.js +75 -64
  30. package/dist/compile.js.map +1 -1
  31. package/dist/database/database.d.ts +98 -0
  32. package/dist/database/database.d.ts.map +1 -0
  33. package/dist/database/database.js +249 -0
  34. package/dist/database/database.js.map +1 -0
  35. package/dist/database/driver.d.ts +40 -0
  36. package/dist/database/driver.d.ts.map +1 -0
  37. package/dist/database/driver.js +68 -0
  38. package/dist/database/driver.js.map +1 -0
  39. package/dist/database/fs.d.ts +12 -0
  40. package/dist/database/fs.d.ts.map +1 -0
  41. package/dist/database/fs.js +120 -0
  42. package/dist/database/fs.js.map +1 -0
  43. package/dist/database/generate.d.ts +26 -0
  44. package/dist/database/generate.d.ts.map +1 -0
  45. package/dist/database/generate.js +102 -0
  46. package/dist/database/generate.js.map +1 -0
  47. package/dist/database/hosted.d.ts +32 -0
  48. package/dist/database/hosted.d.ts.map +1 -0
  49. package/dist/database/hosted.js +44 -0
  50. package/dist/database/hosted.js.map +1 -0
  51. package/dist/database/io.d.ts +94 -0
  52. package/dist/database/io.d.ts.map +1 -0
  53. package/dist/database/io.js +35 -0
  54. package/dist/database/io.js.map +1 -0
  55. package/dist/database/mem.d.ts +26 -0
  56. package/dist/database/mem.d.ts.map +1 -0
  57. package/dist/database/mem.js +67 -0
  58. package/dist/database/mem.js.map +1 -0
  59. package/dist/database/migration.d.ts +55 -0
  60. package/dist/database/migration.d.ts.map +1 -0
  61. package/dist/database/migration.js +46 -0
  62. package/dist/database/migration.js.map +1 -0
  63. package/dist/database/s3.d.ts +36 -0
  64. package/dist/database/s3.d.ts.map +1 -0
  65. package/dist/database/s3.js +141 -0
  66. package/dist/database/s3.js.map +1 -0
  67. package/dist/db.d.ts +44 -128
  68. package/dist/db.d.ts.map +1 -1
  69. package/dist/db.js +163 -287
  70. package/dist/db.js.map +1 -1
  71. package/dist/engine.d.ts +7 -0
  72. package/dist/engine.d.ts.map +1 -0
  73. package/dist/engine.js +2 -0
  74. package/dist/engine.js.map +1 -0
  75. package/dist/errors.d.ts +152 -46
  76. package/dist/errors.d.ts.map +1 -1
  77. package/dist/errors.js +107 -19
  78. package/dist/errors.js.map +1 -1
  79. package/dist/face.d.ts +5 -5
  80. package/dist/face.d.ts.map +1 -1
  81. package/dist/face.js +6 -6
  82. package/dist/face.js.map +1 -1
  83. package/dist/fields.d.ts +4 -5
  84. package/dist/fields.d.ts.map +1 -1
  85. package/dist/fields.js +3 -3
  86. package/dist/fields.js.map +1 -1
  87. package/dist/index.d.ts +60 -58
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +27 -29
  90. package/dist/index.js.map +1 -1
  91. package/dist/judgment.d.ts +1 -1
  92. package/dist/judgment.js +1 -1
  93. package/dist/law.d.ts +16 -68
  94. package/dist/law.d.ts.map +1 -1
  95. package/dist/law.js +3 -3
  96. package/dist/law.js.map +1 -1
  97. package/dist/lower.d.ts +3 -3
  98. package/dist/lower.d.ts.map +1 -1
  99. package/dist/lower.js +35 -39
  100. package/dist/lower.js.map +1 -1
  101. package/dist/native/addon.d.ts +23 -0
  102. package/dist/native/addon.d.ts.map +1 -0
  103. package/dist/native/addon.js +15 -0
  104. package/dist/native/addon.js.map +1 -0
  105. package/dist/native/binding.d.ts +996 -0
  106. package/dist/native/json.d.ts +14 -0
  107. package/dist/native/json.d.ts.map +1 -0
  108. package/dist/native/json.js +73 -0
  109. package/dist/native/json.js.map +1 -0
  110. package/dist/native/load.d.ts +9 -0
  111. package/dist/native/load.d.ts.map +1 -0
  112. package/dist/native/load.js +30 -0
  113. package/dist/native/load.js.map +1 -0
  114. package/dist/native/op.d.ts +29 -0
  115. package/dist/native/op.d.ts.map +1 -0
  116. package/dist/native/op.js +50 -0
  117. package/dist/native/op.js.map +1 -0
  118. package/dist/query/atom.d.ts +13 -14
  119. package/dist/query/atom.d.ts.map +1 -1
  120. package/dist/query/atom.js +2 -2
  121. package/dist/query/atom.js.map +1 -1
  122. package/dist/query/compute.d.ts +7 -7
  123. package/dist/query/compute.d.ts.map +1 -1
  124. package/dist/query/compute.js +7 -16
  125. package/dist/query/compute.js.map +1 -1
  126. package/dist/query/find.d.ts +8 -8
  127. package/dist/query/find.d.ts.map +1 -1
  128. package/dist/query/find.js +1 -1
  129. package/dist/query/lower.d.ts +31 -17
  130. package/dist/query/lower.d.ts.map +1 -1
  131. package/dist/query/lower.js +260 -368
  132. package/dist/query/lower.js.map +1 -1
  133. package/dist/query/run.d.ts +5 -5
  134. package/dist/query/run.d.ts.map +1 -1
  135. package/dist/query/run.js +32 -15
  136. package/dist/query/run.js.map +1 -1
  137. package/dist/query/scope.d.ts +20 -28
  138. package/dist/query/scope.d.ts.map +1 -1
  139. package/dist/query/scope.js +13 -8
  140. package/dist/query/scope.js.map +1 -1
  141. package/dist/query/segments.d.ts +2 -2
  142. package/dist/query/segments.d.ts.map +1 -1
  143. package/dist/query/segments.js +3 -3
  144. package/dist/query/segments.js.map +1 -1
  145. package/dist/relation.d.ts +1 -1
  146. package/dist/relation.d.ts.map +1 -1
  147. package/dist/relation.js +4 -4
  148. package/dist/relation.js.map +1 -1
  149. package/dist/result.d.ts +15 -22
  150. package/dist/result.d.ts.map +1 -1
  151. package/dist/result.js +30 -39
  152. package/dist/result.js.map +1 -1
  153. package/dist/rows.d.ts +9 -33
  154. package/dist/rows.d.ts.map +1 -1
  155. package/dist/rows.js +8 -12
  156. package/dist/rows.js.map +1 -1
  157. package/dist/runtime.d.ts +17 -43
  158. package/dist/runtime.d.ts.map +1 -1
  159. package/dist/runtime.js +41 -241
  160. package/dist/runtime.js.map +1 -1
  161. package/dist/scalar.d.ts +36 -106
  162. package/dist/scalar.d.ts.map +1 -1
  163. package/dist/scalar.js +36 -185
  164. package/dist/scalar.js.map +1 -1
  165. package/dist/schema.d.ts +7 -18
  166. package/dist/schema.d.ts.map +1 -1
  167. package/dist/schema.js +18 -228
  168. package/dist/schema.js.map +1 -1
  169. package/dist/selection.d.ts +4 -4
  170. package/dist/selection.d.ts.map +1 -1
  171. package/dist/selection.js +5 -5
  172. package/dist/selection.js.map +1 -1
  173. package/dist/shape.d.ts +5 -5
  174. package/dist/shape.d.ts.map +1 -1
  175. package/dist/spec.d.ts +7 -79
  176. package/dist/spec.d.ts.map +1 -1
  177. package/dist/spec.js.map +1 -1
  178. package/dist/statements.d.ts +3 -3
  179. package/dist/statements.d.ts.map +1 -1
  180. package/dist/statements.js +10 -10
  181. package/dist/statements.js.map +1 -1
  182. package/dist/uuid.d.ts +1 -1
  183. package/dist/uuid.d.ts.map +1 -1
  184. package/dist/uuid.js +1 -1
  185. package/dist/uuid.js.map +1 -1
  186. package/dist/values.d.ts +2 -2
  187. package/dist/values.d.ts.map +1 -1
  188. package/dist/values.js +2 -2
  189. package/dist/values.js.map +1 -1
  190. package/package.json +18 -18
  191. package/src/alternatives.ts +7 -8
  192. package/src/bin.ts +82 -0
  193. package/src/capacity.ts +20 -23
  194. package/src/changes.ts +134 -197
  195. package/src/closed.ts +5 -5
  196. package/src/codec.ts +77 -51
  197. package/src/compile.ts +91 -113
  198. package/src/database/database.ts +413 -0
  199. package/src/database/driver.ts +134 -0
  200. package/src/database/fs.ts +119 -0
  201. package/src/database/generate.ts +116 -0
  202. package/src/database/hosted.ts +102 -0
  203. package/src/database/io.ts +134 -0
  204. package/src/database/mem.ts +113 -0
  205. package/src/database/migration.ts +107 -0
  206. package/src/database/s3.ts +231 -0
  207. package/src/db.ts +290 -547
  208. package/src/engine.ts +14 -0
  209. package/src/errors.ts +118 -39
  210. package/src/face.ts +9 -9
  211. package/src/fields.ts +6 -7
  212. package/src/index.ts +102 -109
  213. package/src/judgment.ts +1 -1
  214. package/src/law.ts +18 -70
  215. package/src/lower.ts +61 -72
  216. package/src/native/addon.ts +54 -0
  217. package/src/native/binding.d.ts +996 -0
  218. package/src/native/json.ts +100 -0
  219. package/src/native/load.ts +31 -0
  220. package/src/native/op.ts +73 -0
  221. package/src/query/atom.ts +15 -16
  222. package/src/query/compute.ts +13 -22
  223. package/src/query/find.ts +8 -8
  224. package/src/query/lower.ts +323 -414
  225. package/src/query/run.ts +42 -21
  226. package/src/query/scope.ts +37 -38
  227. package/src/query/segments.ts +4 -4
  228. package/src/relation.ts +4 -4
  229. package/src/result.ts +53 -80
  230. package/src/rows.ts +18 -46
  231. package/src/runtime.ts +50 -346
  232. package/src/scalar.ts +76 -336
  233. package/src/schema.ts +23 -284
  234. package/src/selection.ts +6 -6
  235. package/src/shape.ts +5 -5
  236. package/src/spec.ts +7 -85
  237. package/src/statements.ts +11 -11
  238. package/src/uuid.ts +1 -1
  239. package/src/values.ts +4 -4
  240. package/dist/close.d.ts +0 -24
  241. package/dist/close.d.ts.map +0 -1
  242. package/dist/close.js +0 -34
  243. package/dist/close.js.map +0 -1
  244. package/dist/db-native.d.ts +0 -196
  245. package/dist/db-native.d.ts.map +0 -1
  246. package/dist/db-native.js +0 -13
  247. package/dist/db-native.js.map +0 -1
  248. package/dist/internal/log.d.ts +0 -26
  249. package/dist/internal/log.d.ts.map +0 -1
  250. package/dist/internal/log.js +0 -12
  251. package/dist/internal/log.js.map +0 -1
  252. package/dist/migration.d.ts +0 -31
  253. package/dist/migration.d.ts.map +0 -1
  254. package/dist/migration.js +0 -47
  255. package/dist/migration.js.map +0 -1
  256. package/dist/native.d.ts +0 -562
  257. package/dist/native.d.ts.map +0 -1
  258. package/dist/native.js +0 -123
  259. package/dist/native.js.map +0 -1
  260. package/dist/query/description.d.ts +0 -41
  261. package/dist/query/description.d.ts.map +0 -1
  262. package/dist/query/description.js +0 -311
  263. package/dist/query/description.js.map +0 -1
  264. package/dist/query/parse-ir.d.ts +0 -10
  265. package/dist/query/parse-ir.d.ts.map +0 -1
  266. package/dist/query/parse-ir.js +0 -280
  267. package/dist/query/parse-ir.js.map +0 -1
  268. package/dist/runtime-codes.d.ts +0 -2
  269. package/dist/runtime-codes.d.ts.map +0 -1
  270. package/dist/runtime-codes.js +0 -18
  271. package/dist/runtime-codes.js.map +0 -1
  272. package/dist/runtime-errors.d.ts +0 -118
  273. package/dist/runtime-errors.d.ts.map +0 -1
  274. package/dist/runtime-errors.js +0 -98
  275. package/dist/runtime-errors.js.map +0 -1
  276. package/dist/runtime-native.d.ts +0 -141
  277. package/dist/runtime-native.d.ts.map +0 -1
  278. package/dist/runtime-native.js +0 -5
  279. package/dist/runtime-native.js.map +0 -1
  280. package/pack-provenance.json +0 -7
  281. package/src/close.ts +0 -53
  282. package/src/db-native.ts +0 -243
  283. package/src/internal/log.ts +0 -48
  284. package/src/migration.ts +0 -61
  285. package/src/native.ts +0 -736
  286. package/src/query/description.ts +0 -424
  287. package/src/query/parse-ir.ts +0 -291
  288. package/src/runtime-codes.ts +0 -17
  289. package/src/runtime-errors.ts +0 -109
  290. package/src/runtime-native.ts +0 -189
@@ -1,6 +1,7 @@
1
- import { membersAgree, sealedFieldsOf } from "#closed.ts"
2
- import { AuthoringError, SdkInvariantError } from "#errors.ts"
3
- import type { AnyClosedRoster, AnyField, IntervalField } from "#fields.ts"
1
+ import { membersAgree, sealedFieldsOf } from "../closed.ts"
2
+ import { compiledOf } from "../compile.ts"
3
+ import { AuthoringError, internalError } from "../errors.ts"
4
+ import type { AnyClosedRoster, AnyField, IntervalField } from "../fields.ts"
4
5
  import {
5
6
  assertDeclarationOrderKey,
6
7
  f64 as f64Field,
@@ -11,23 +12,30 @@ import {
11
12
  rosterOf,
12
13
  rostersAgree,
13
14
  u64 as u64Field
14
- } from "#fields.ts"
15
- import { snapshotData } from "#immutable.ts"
16
- import type { ClassRecordOf, SchemaClasses } from "#law.ts"
15
+ } from "../fields.ts"
16
+ import { snapshotData } from "../immutable.ts"
17
+ import type { Same } from "../judgment.ts"
18
+ import type { ClassRecordOf, SchemaClasses } from "../law.ts"
19
+ import type { QueryRef } from "../native/addon.ts"
20
+ import { addon } from "../native/addon.ts"
17
21
  import type {
18
- AtomIr,
19
- ComparisonIr,
20
- ConditionTreeIr,
21
- FindTermIr,
22
- HeadOpIr,
23
- HeadTermIr,
24
- ParsedQuery,
25
- QueryParam,
26
- RuleIr,
27
- ScalarExprIr,
28
- TaggedValue,
29
- TermIr
30
- } from "#native.ts"
22
+ AtomIn,
23
+ BindingIn,
24
+ ConditionIn,
25
+ FindTermIn,
26
+ HeadTermIn,
27
+ QueryDiagnostic,
28
+ QueryIn,
29
+ RecStepIn,
30
+ RuleIn,
31
+ TermIn
32
+ } from "../native/binding.d.ts"
33
+ import { valueIn } from "../native/json.ts"
34
+ import { scalarWire } from "../scalar.ts"
35
+ import type { AnySchema, Schema, SchemaRelations } from "../schema.ts"
36
+ import { schemaDescriptor, schemasAgree } from "../schema.ts"
37
+ import type { QueryParam, TaggedValue } from "../spec.ts"
38
+ import { arrayValue, recordValue, taggedValueOf } from "../values.ts"
31
39
  import type {
32
40
  AggData,
33
41
  AnyCond,
@@ -36,7 +44,6 @@ import type {
36
44
  BindParamsShape,
37
45
  CheckBindings,
38
46
  CheckCond,
39
- CmpData,
40
47
  CmpKind,
41
48
  CmpTermData,
42
49
  CondData,
@@ -54,13 +61,11 @@ import type {
54
61
  RecHead,
55
62
  RuleData,
56
63
  RuleItem
57
- } from "#query/atom.ts"
58
- import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
59
- import type { QueryNode } from "#query/compute.ts"
60
- import { computeFieldOf, computeVarsOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "#query/compute.ts"
61
- import type { CheckFind, CheckRecFind, FindShape, HeadRecordOf, RowOfFind } from "#query/find.ts"
62
- import { count, max, mean, min, pack, sum } from "#query/find.ts"
63
- import { parseQueryIr } from "#query/parse-ir.ts"
64
+ } from "./atom.ts"
65
+ import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "./atom.ts"
66
+ import { computeFieldOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "./compute.ts"
67
+ import type { CheckFind, CheckRecFind, FindShape, HeadRecordOf, RowOfFind } from "./find.ts"
68
+ import { count, max, mean, min, pack, sum } from "./find.ts"
64
69
  import type {
65
70
  AnyVar,
66
71
  ClassedField,
@@ -71,7 +76,7 @@ import type {
71
76
  ParamsRecord,
72
77
  ShapeOf,
73
78
  VarsOf
74
- } from "#query/scope.ts"
79
+ } from "./scope.ts"
75
80
  import {
76
81
  fieldAntiJoins,
77
82
  fieldJoins,
@@ -86,12 +91,8 @@ import {
86
91
  makeSetParam,
87
92
  renderFieldKind,
88
93
  term
89
- } from "#query/scope.ts"
90
- import { difference, intersection, isSegments, segmentField } from "#query/segments.ts"
91
- import { scalarWire } from "#scalar.ts"
92
- import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
93
- import { schemaDescriptor, schemasAgree } from "#schema.ts"
94
- import { arrayValue, recordValue, taggedValueOf } from "#values.ts"
94
+ } from "./scope.ts"
95
+ import { difference, intersection, isSegments, segmentField } from "./segments.ts"
95
96
 
96
97
  type QueryRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], MatchOwner>
97
98
 
@@ -103,6 +104,25 @@ type HeadShape = Readonly<Record<string, ClassedField>> | undefined
103
104
 
104
105
  type HeadOf<T> = InferredOf<T> extends { readonly head: infer H extends HeadShape } ? H : HeadShape
105
106
 
107
+ type HeadFieldMeet<A extends AnyField, B extends AnyField> = A extends IntervalField
108
+ ? B extends IntervalField
109
+ ? IntervalField<A["element"], Same<A["width"], B["width"]> extends true ? A["width"] : undefined>
110
+ : A
111
+ : A
112
+
113
+ /** The common refinements of the same output column across union arms. */
114
+ type HeadMeet<A extends HeadShape, B extends HeadShape> =
115
+ A extends Readonly<Record<string, ClassedField>>
116
+ ? B extends Readonly<Record<string, ClassedField>>
117
+ ? {
118
+ readonly [K in keyof A & keyof B]: {
119
+ readonly field: HeadFieldMeet<A[K]["field"], B[K]["field"]>
120
+ readonly class: Same<A[K]["class"], B[K]["class"]> extends true ? A[K]["class"] : undefined
121
+ }
122
+ }
123
+ : HeadShape
124
+ : HeadShape
125
+
106
126
  interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefined> {
107
127
  readonly rule: RuleData
108
128
  readonly [inferred]?: { readonly row: Row; readonly params: P; readonly head: Head }
@@ -299,8 +319,8 @@ interface InteriorRuleChain<
299
319
  bindings: B & CheckInteriorBindings<B>
300
320
  ): InteriorRuleChain<Rels, P, Classes>
301
321
  /**
302
- * Nonrecursive derived stages emit aggregate/computed outputs too
303
- * (C05): only the RECURSIVE head stays projection-only.
322
+ * Nonrecursive derived stages emit aggregate/computed outputs too; only
323
+ * the RECURSIVE head stays projection-only.
304
324
  */
305
325
  find<const F extends Readonly<Record<string, AnyVar>>>(
306
326
  entries: F
@@ -325,7 +345,7 @@ interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClass
325
345
  relation: R,
326
346
  bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
327
347
  ): RecRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
328
- /** Frozen finite nonrecursive imports may feed base/step (chapter 34). */
348
+ /** Frozen finite nonrecursive imports may feed base/step. */
329
349
  match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
330
350
  imported: Q,
331
351
  bindings: B
@@ -412,7 +432,7 @@ interface Query<
412
432
 
413
433
  rule<RV extends AnyRuleValue>(
414
434
  build: (r: QueryRuleScope<Rels, Classes>) => RV
415
- ): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, Head | HeadOf<RV>>
435
+ ): Query<Rels, Row | RowOf<RV>, Flatten<Params & ParamsOf<RV>>, Classes, HeadMeet<Head, HeadOf<RV>>>
416
436
 
417
437
  /** A diagnostic name for composition/tracing — never a schema relation. */
418
438
  named(label: string): Query<Rels, Row, Params, Classes, Head>
@@ -893,51 +913,6 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
893
913
  })
894
914
  }
895
915
 
896
- /**
897
- * The orderable ban's pointed refusal
898
- * § orderability): a closed reference is equality-and-membership only.
899
- */
900
- function closedOrderError(context: string, position: string, vocabulary: string): Error {
901
- return new AuthoringError({
902
- message: `${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
903
- })
904
- }
905
-
906
- function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
907
- return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn"
908
- }
909
-
910
- function assertBound(where: string, bound: ReadonlySet<AnyVar>, ref: AnyVar): void {
911
- if (!bound.has(ref)) {
912
- throw new AuthoringError({
913
- message: `${where}: the variable ${ref.label} is not bound by a relation atom of the rule`
914
- })
915
- }
916
- }
917
-
918
- function assertInterval(where: string, ref: AnyVar): void {
919
- if (ref.field.kind !== "interval") {
920
- throw new AuthoringError({
921
- message: `${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
922
- })
923
- }
924
- }
925
-
926
- function assertNotClosed(where: string, position: string, ref: AnyVar): void {
927
- const roster = rosterOf(ref.field)
928
- if (roster !== undefined) {
929
- throw closedOrderError(where, `${position} ${ref.label}`, roster.name)
930
- }
931
- }
932
-
933
- function assertNumeric(where: string, position: string, ref: AnyVar): void {
934
- if (ref.field.kind !== "u64" && ref.field.kind !== "i64" && ref.field.kind !== "f64") {
935
- throw new AuthoringError({
936
- message: `${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64/f64 only`
937
- })
938
- }
939
- }
940
-
941
916
  /**
942
917
  * The head column's slot: a projected variable keeps its mint slot; an
943
918
  * aggregate output is a NEW derived scalar — its descriptor follows the
@@ -972,80 +947,24 @@ function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedFie
972
947
  }
973
948
  }
974
949
 
975
- function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, column: FindColumn): void {
976
- const where = `${contextLabel(context)} find ${column.name}`
977
- const entry = column.entry
978
- if (entry.kind === "segments") {
979
- assertBound(where, bound, entry.left)
980
- assertBound(where, bound, entry.right)
981
- return
982
- }
983
- if (entry.kind === "var") {
984
- assertBound(where, bound, entry.over)
985
- return
986
- }
987
- if (entry.kind === "compute") {
988
- // Kind agreement, closed-reference and depth walls hold at
989
- // construction (#query/compute.ts); boundness is a RULE property,
990
- // judged here like every other term position.
991
- for (const ref of computeVarsOf(entry.expr)) {
992
- assertBound(where, bound, ref)
993
- }
994
- return
995
- }
996
- const agg = entry.agg
997
- switch (agg.op) {
998
- case "count":
999
- return
1000
- case "fold": {
1001
- assertBound(where, bound, agg.over)
1002
- assertNotClosed(where, `the ${agg.fold} input`, agg.over)
1003
- assertNumeric(where, `the ${agg.fold} input`, agg.over)
1004
- if (agg.fold === "mean" && agg.over.field.kind !== "f64") {
1005
- throw new AuthoringError({ message: `${where}: mean requires an f64 input; cast integers explicitly` })
1006
- }
1007
- return
1008
- }
1009
- case "pack":
1010
- assertBound(where, bound, agg.over)
1011
- assertInterval(where, agg.over)
1012
- return
1013
- }
1014
- }
1015
-
1016
- function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: CondData): void {
1017
- const label = contextLabel(context)
1018
- if (cond.kind === "cmp") {
1019
- for (const side of [cond.lhs, cond.rhs]) {
1020
- if (side.kind === "var") {
1021
- assertBound(label, bound, side.ref)
1022
- const roster = rosterOf(side.ref.field)
1023
- if (isOrderOp(cond.op.kind) && roster !== undefined) {
1024
- throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name)
1025
- }
1026
- }
1027
- }
1028
- if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
1029
- assertBound(label, bound, cond.lhs.ref)
1030
- assertBound(label, bound, cond.rhs.ref)
1031
- const lhs = mintSlotOf(context, cond.lhs.ref)
1032
- const rhs = mintSlotOf(context, cond.rhs.ref)
1033
- if (!fieldJoins(lhs, rhs)) {
1034
- throw new AuthoringError({
1035
- message: `${label}: ${cond.op.kind}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`
1036
- })
1037
- }
1038
- }
950
+ /** An equality between two variables joins them, so their slots must join under the class law. */
951
+ function validateCond(context: ChainContext, cond: CondData): void {
952
+ if (cond.kind !== "cmp") {
953
+ for (const child of cond.children) validateCond(context, child)
1039
954
  return
1040
955
  }
1041
- for (const child of cond.children) {
1042
- validateCond(context, bound, child)
956
+ if ((cond.op.kind !== "eq" && cond.op.kind !== "ne") || cond.lhs.kind !== "var" || cond.rhs.kind !== "var") return
957
+ const lhs = mintSlotOf(context, cond.lhs.ref)
958
+ const rhs = mintSlotOf(context, cond.rhs.ref)
959
+ if (!fieldJoins(lhs, rhs)) {
960
+ throw new AuthoringError({
961
+ message: `${contextLabel(context)}: ${cond.op.kind}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`
962
+ })
1043
963
  }
1044
964
  }
1045
965
 
1046
966
  function validateInterior(
1047
967
  context: ChainContext,
1048
- bound: ReadonlySet<AnyVar>,
1049
968
  item: {
1050
969
  readonly kind: "interior" | "negatedInterior"
1051
970
  readonly target: DerivedTable
@@ -1076,11 +995,6 @@ function validateInterior(
1076
995
  }
1077
996
  }
1078
997
  for (const binding of item.bindings) {
1079
- if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
1080
- throw new AuthoringError({
1081
- message: `${label}: negated interior ${item.target.name} names the variable ${binding.ref.label}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
1082
- })
1083
- }
1084
998
  const headColumn = headColumns.find(function byName(column) {
1085
999
  return column.name === binding.key
1086
1000
  })
@@ -1098,38 +1012,14 @@ function validateInterior(
1098
1012
 
1099
1013
  function completeRule(context: ChainContext, state: RuleBuildState, rawColumns: readonly FindColumn[]): RuleData {
1100
1014
  const label = contextLabel(context)
1101
- if (rawColumns.length === 0) {
1102
- throw new AuthoringError({ message: `${label}: a find needs at least one entry` })
1103
- }
1104
- const aggregates = rawColumns.flatMap((column) => (column.entry.kind === "aggregate" ? [column.entry.agg] : []))
1105
- const packs = aggregates.filter((agg) => agg.op === "pack").length
1106
- if (packs > 1) throw new AuthoringError({ message: "a query stage can pack one interval column" })
1107
- if (packs !== 0 && aggregates.length !== packs)
1108
- throw new AuthoringError({ message: "pack intervals and compute numeric aggregates in separate query stages" })
1109
- if (rawColumns.some((c) => c.entry.kind === "segments") && aggregates.length !== 0)
1110
- throw new AuthoringError({ message: "aggregate generated segments in a following query stage" })
1111
1015
  const columns = rawColumns.map(function enrichColumn(column): FindColumn {
1112
1016
  assertDeclarationOrderKey(`${label} find column`, column.name)
1113
- validateColumn(context, state.bound, column)
1114
1017
  const slot = findColumnSlotOf(context, column)
1115
1018
  return Object.freeze({ name: column.name, entry: column.entry, slot, closed: rosterOf(slot?.field) })
1116
1019
  })
1117
1020
  for (const item of state.items) {
1118
- if (item.kind === "negated") {
1119
- for (const binding of item.atom.bindings) {
1120
- if (binding.term.kind === "var" && !state.bound.has(binding.term.ref)) {
1121
- throw new AuthoringError({
1122
- message: `${label}: negated ${item.atom.relation.name} atom binds the variable ${binding.term.ref.label} at position ${binding.field}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
1123
- })
1124
- }
1125
- }
1126
- }
1127
- if (item.kind === "interior" || item.kind === "negatedInterior") {
1128
- validateInterior(context, state.bound, item, columns)
1129
- }
1130
- if (item.kind === "cond") {
1131
- validateCond(context, state.bound, item.cond)
1132
- }
1021
+ if (item.kind === "interior" || item.kind === "negatedInterior") validateInterior(context, item, columns)
1022
+ if (item.kind === "cond") validateCond(context, item.cond)
1133
1023
  }
1134
1024
  return Object.freeze({ items: state.items, finds: Object.freeze(columns), paramUses: state.paramUses })
1135
1025
  }
@@ -1221,8 +1111,8 @@ function interiorAdvance(
1221
1111
  }
1222
1112
 
1223
1113
  /**
1224
- * Imported query templates as relation-expression sources (chapter 34's
1225
- * nonrecursive composition). The imported CQ's whole body — its inner
1114
+ * Imported query templates as relation-expression sources (nonrecursive
1115
+ * composition). The imported CQ's whole body — its inner
1226
1116
  * interiors plus its main rules as one derived table — is SPLICED into the
1227
1117
  * consuming query's interior roster at assembly ({@link makeRawQuery});
1228
1118
  * naming does not materialize anything, and the imported template keeps its
@@ -1269,7 +1159,7 @@ function importEntryOf(context: ChainContext, imported: AnyQuery): ImportEntry {
1269
1159
  const data = imported.data
1270
1160
  if (data.kind === "reach") {
1271
1161
  throw new AuthoringError({
1272
- message: `${contextLabel(context)}: a recursive query is not importable as a relation expression yet — declare the recursion on the consuming query (C05 boundary, recorded)`
1162
+ message: `${contextLabel(context)}: a recursive query is not importable as a relation expression — declare the recursion on the consuming query`
1273
1163
  })
1274
1164
  }
1275
1165
  const userParams = data.params.filter(function userSupplied(entry) {
@@ -1408,8 +1298,7 @@ function findColumns(context: ChainContext, entries: Readonly<Record<string, unk
1408
1298
  const columns: FindColumn[] = []
1409
1299
  queryRecord("query find", entries)
1410
1300
  // Nonrecursive derived stages (interiors) may emit aggregate/computed
1411
- // outputs (C05: "do not leave Interior.rules projection only"); ONLY the
1412
- // recursive feedback cycle stays projection-only — no aggregate,
1301
+ // outputs; ONLY the recursive feedback cycle stays projection-only — no aggregate,
1413
1302
  // arithmetic-created value or negation flows through it.
1414
1303
  const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined
1415
1304
  for (const [name, entry] of Object.entries(entries)) {
@@ -1481,7 +1370,7 @@ function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends Schema
1481
1370
  ): QueryRuleScope<Rels, Classes> {
1482
1371
  const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env })
1483
1372
  if (!isTypedScope<QueryRuleScope<Rels, Classes>>(raw)) {
1484
- throw new SdkInvariantError({ message: "query rule builder construction incomplete" })
1373
+ throw internalError("query rule builder construction incomplete")
1485
1374
  }
1486
1375
  return raw
1487
1376
  }
@@ -1493,7 +1382,7 @@ function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends Sch
1493
1382
  ): InteriorRuleScope<Rels, Classes> {
1494
1383
  const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env })
1495
1384
  if (!isTypedScope<InteriorRuleScope<Rels, Classes>>(raw)) {
1496
- throw new SdkInvariantError({ message: "interior rule builder construction incomplete" })
1385
+ throw internalError("interior rule builder construction incomplete")
1497
1386
  }
1498
1387
  return raw
1499
1388
  }
@@ -1533,7 +1422,7 @@ function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaCl
1533
1422
  ...env
1534
1423
  })
1535
1424
  if (!isTypedScope<RecRuleScope<Rels, Classes>>(raw)) {
1536
- throw new SdkInvariantError({ message: "rec rule builder construction incomplete" })
1425
+ throw internalError("rec rule builder construction incomplete")
1537
1426
  }
1538
1427
  return raw
1539
1428
  }
@@ -1542,20 +1431,10 @@ function renderClosedSlice(closed: AnyClosedRoster | undefined): string {
1542
1431
  return closed === undefined ? "a bare value" : `a ${closed.name} reference`
1543
1432
  }
1544
1433
 
1545
- function headSignature(column: FindColumn): string {
1546
- const entry = column.entry
1547
- if (entry.kind === "segments") return `${column.name}:compute`
1548
- if (entry.kind === "var") {
1549
- return `${column.name}:var`
1550
- }
1551
- if (entry.kind === "compute") {
1552
- return `${column.name}:compute`
1553
- }
1554
- const agg = entry.agg
1555
- if (agg.op === "fold") {
1556
- return `${column.name}:${agg.fold}`
1557
- }
1558
- return `${column.name}:${agg.op}`
1434
+ function headOperation(column: FindColumn): string | undefined {
1435
+ if (column.entry.kind !== "aggregate") return undefined
1436
+ const agg = column.entry.agg
1437
+ return agg.op === "fold" ? agg.fold : agg.op
1559
1438
  }
1560
1439
 
1561
1440
  function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
@@ -1563,16 +1442,10 @@ function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
1563
1442
  }
1564
1443
 
1565
1444
  /**
1566
- * Folds every rule's param uses (interiors in declaration order, then rec
1567
- * base, then rec arms, then main — exactly the lowering walk) into the
1568
- * query's registry: first use mints the dense `ParamId`, the first
1569
- * FIELD-ANCHORED use types the wire, and one name keeps one shape AND one
1570
- * closedness. The orderable ban needs no registry arm: an order use always
1571
- * anchors its SIBLING's domain (the no-variable-side spelling is refused
1572
- * at the comparison constructor), so a closed-anchored param under an
1573
- * order op dies at the one-domain wall here — and an order use whose
1574
- * sibling is itself closed-bound dies at the comparison's own var-side
1575
- * wall first.
1445
+ * Folds every rule's param uses, in lowering order, into the query's registry: the first use mints
1446
+ * the dense `ParamId`, the first field-anchored use types it, and one name keeps one shape and one
1447
+ * closedness. An order use always anchors its sibling's domain, so a closed-anchored param under an
1448
+ * order op fails the one-domain check here.
1576
1449
  */
1577
1450
  function paramRegistryOf(
1578
1451
  interiors: readonly InteriorData[],
@@ -1692,12 +1565,22 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
1692
1565
  if (first === undefined) {
1693
1566
  throw new AuthoringError({ message: `${label} needs at least one rule` })
1694
1567
  }
1695
- const signature = first.finds.map(headSignature).join(", ")
1568
+ // Bare u64 admits either carrier, but does not make two different
1569
+ // carriers compatible. Retain one original classed slot per column;
1570
+ // checking only the first arm (or its weakened meet) loses that proof.
1571
+ const classed = new Map<number, { readonly slot: ClassedField; readonly rule: number }>()
1696
1572
  rules.forEach(function verifyHead(rule, index) {
1697
- const candidate = rule.finds.map(headSignature).join(", ")
1698
- if (candidate !== signature) {
1573
+ if (
1574
+ rule.finds.length !== first.finds.length ||
1575
+ rule.finds.some((column, position) => {
1576
+ const lead = first.finds[position]
1577
+ return lead === undefined || lead.name !== column.name || headOperation(lead) !== headOperation(column)
1578
+ })
1579
+ ) {
1580
+ const render = (columns: readonly FindColumn[]) =>
1581
+ columns.map((column) => `${column.name}:${headOperation(column) ?? "project"}`).join(", ")
1699
1582
  throw new AuthoringError({
1700
- message: `every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`
1583
+ message: `every rule of ${label} derives the same head — rule 0 finds (${render(first.finds)}), rule ${index} finds (${render(rule.finds)})`
1701
1584
  })
1702
1585
  }
1703
1586
  rule.finds.forEach(function verifyClosedSlice(column, position) {
@@ -1710,30 +1593,44 @@ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly Find
1710
1593
  if (lead === undefined) {
1711
1594
  return
1712
1595
  }
1713
- if (lead.slot !== undefined && column.slot !== undefined && !headFieldJoins(lead.slot, column.slot)) {
1596
+ const prior = classed.get(position) ?? (lead.slot === undefined ? undefined : { slot: lead.slot, rule: 0 })
1597
+ if (prior !== undefined && column.slot !== undefined && !headFieldJoins(prior.slot, column.slot)) {
1714
1598
  throw new AuthoringError({
1715
- message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`
1599
+ message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(prior.slot)} in rule ${prior.rule} but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`
1716
1600
  })
1717
1601
  }
1602
+ if (column.slot?.class !== undefined && !classed.has(position)) {
1603
+ classed.set(position, { slot: column.slot, rule: index })
1604
+ }
1718
1605
  })
1719
1606
  })
1720
1607
  const merged = first.finds.map(function meetColumn(lead, position) {
1721
1608
  const slot = lead.slot
1722
- if (slot === undefined || slot.class === undefined) {
1609
+ if (slot === undefined) {
1723
1610
  return lead
1724
1611
  }
1725
1612
  const demoted = rules.some(function bareElsewhere(rule) {
1726
1613
  const column = rule.finds[position]
1727
1614
  return column !== undefined && column.slot !== undefined && column.slot.class !== slot.class
1728
1615
  })
1729
- if (!demoted) {
1616
+ const field = slot.field
1617
+ const widened =
1618
+ field.kind === "interval" &&
1619
+ rules.some((rule) => {
1620
+ const other = rule.finds[position]?.slot?.field
1621
+ return other?.kind === "interval" && other.width !== field.width
1622
+ })
1623
+ if (!demoted && !widened) {
1730
1624
  return lead
1731
1625
  }
1732
1626
  return Object.freeze({
1733
1627
  name: lead.name,
1734
1628
  entry: lead.entry,
1735
1629
  closed: lead.closed,
1736
- slot: Object.freeze({ field: slot.field, class: undefined })
1630
+ slot: Object.freeze({
1631
+ field: widened ? Object.freeze({ ...field, width: undefined }) : field,
1632
+ class: demoted ? undefined : slot.class
1633
+ })
1737
1634
  })
1738
1635
  })
1739
1636
  return Object.freeze(merged)
@@ -1745,6 +1642,53 @@ function afterMainError(what: string): Error {
1745
1642
  })
1746
1643
  }
1747
1644
 
1645
+ /** Names the parts of `data` an engine query diagnostic cites. */
1646
+ function explainQuery(data: QueryData, labels: RuleLabels, diagnostic: QueryDiagnostic): string {
1647
+ const tables = [...data.interiors, ...(data.kind === "reach" ? [data.rec] : [])]
1648
+ const table = diagnostic.interior === undefined ? undefined : tables[diagnostic.interior]
1649
+ const ruleLabels =
1650
+ diagnostic.rule === undefined
1651
+ ? undefined
1652
+ : (diagnostic.interior === undefined ? labels.main : labels.interiors[diagnostic.interior])?.[diagnostic.rule]
1653
+ const finds = table?.finds ?? data.finds
1654
+ const cited = [
1655
+ table === undefined ? undefined : `in ${table.name}`,
1656
+ diagnostic.rule === undefined ? undefined : `rule ${diagnostic.rule}`,
1657
+ diagnostic.find === undefined ? undefined : `find ${finds[diagnostic.find]?.name ?? diagnostic.find}`,
1658
+ diagnostic.atom === undefined ? undefined : `atom ${diagnostic.atom}`,
1659
+ diagnostic.var === undefined ? undefined : `variable ${ruleLabels?.[diagnostic.var] ?? diagnostic.var}`,
1660
+ diagnostic.param === undefined ? undefined : `param ${data.params[diagnostic.param]?.name ?? diagnostic.param}`,
1661
+ diagnostic.comparison === undefined ? undefined : `comparison ${diagnostic.comparison}`
1662
+ ].filter((part) => part !== undefined)
1663
+ return cited.length === 0 ? diagnostic.message : `${diagnostic.message} (${cited.join(", ")})`
1664
+ }
1665
+
1666
+ /** Validates a complete query with the engine and returns its compiled handle. */
1667
+ function validateQueryData(theory: AnySchema, data: QueryData): QueryRef {
1668
+ const lowered = lowerQueryLabelled({ schema: theory, data })
1669
+ const validated = addon.validateQuery(compiledOf(schemaDescriptor(theory)).handle, JSON.stringify(lowered.query))
1670
+ switch (validated._tag) {
1671
+ case "Valid":
1672
+ return validated.query
1673
+ case "Invalid":
1674
+ throw new AuthoringError({
1675
+ message: `query: ${explainQuery(data, lowered.labels, validated.diagnostic)}`,
1676
+ diagnostic: { code: "InvalidQuery", context: validated.diagnostic.code, expected: validated.diagnostic.message }
1677
+ })
1678
+ case "Malformed":
1679
+ throw internalError(`validateQuery: ${validated.path}: ${validated.message}`)
1680
+ }
1681
+ }
1682
+
1683
+ const validatedQueries = new WeakMap<object, QueryRef>()
1684
+
1685
+ /** The engine's validated form of a query value `query()` built. */
1686
+ function queryHandleOf(query: AnyQuery): QueryRef {
1687
+ const handle = validatedQueries.get(query)
1688
+ if (handle === undefined) throw internalError("queryHandleOf: the query was not built by query()")
1689
+ return handle
1690
+ }
1691
+
1748
1692
  function makeRawQuery(
1749
1693
  inputTheory: AnySchema,
1750
1694
  inputInteriors: readonly InteriorData[],
@@ -1784,6 +1728,7 @@ function makeRawQuery(
1784
1728
  finds: mergedFinds,
1785
1729
  params
1786
1730
  })
1731
+ const handle = validateQueryData(theory, data)
1787
1732
  const value: RawQuery = {
1788
1733
  get schema() {
1789
1734
  return snapshotData(theory)
@@ -1809,6 +1754,7 @@ function makeRawQuery(
1809
1754
  }
1810
1755
  }
1811
1756
  Object.freeze(value)
1757
+ validatedQueries.set(value, handle)
1812
1758
  return value
1813
1759
  }
1814
1760
 
@@ -2039,6 +1985,8 @@ interface LowerContext {
2039
1985
 
2040
1986
  interface VarIds {
2041
1987
  of(ref: AnyVar): number
1988
+ /** Each variable's label, by variable id. */
1989
+ labels(): readonly string[]
2042
1990
  }
2043
1991
 
2044
1992
  function freshVarIds(): VarIds {
@@ -2052,6 +2000,9 @@ function freshVarIds(): VarIds {
2052
2000
  const id = assigned.size
2053
2001
  assigned.set(ref, id)
2054
2002
  return id
2003
+ },
2004
+ labels() {
2005
+ return [...assigned.keys()].map((ref) => ref.label)
2055
2006
  }
2056
2007
  }
2057
2008
  }
@@ -2064,257 +2015,221 @@ function paramIdOf(ctx: LowerContext, name: string): number {
2064
2015
  return id
2065
2016
  }
2066
2017
 
2067
- function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
2068
- const member = ctx.theory.relations[atom.relation.name]
2069
- if (!membersAgree(member, atom.relation)) {
2018
+ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIn {
2019
+ const relationId = ctx.relationIds.get(atom.relation.name)
2020
+ if (!membersAgree(ctx.theory.relations[atom.relation.name], atom.relation) || relationId === undefined) {
2070
2021
  throw new AuthoringError({
2071
- message: `query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
2022
+ message: `query: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
2072
2023
  })
2073
2024
  }
2074
- const relationId = ctx.relationIds.get(atom.relation.name)
2075
- if (relationId === undefined) {
2076
- throw new AuthoringError({ message: `query lowering: relation ${atom.relation.name} has no ordinal` })
2077
- }
2078
2025
  const ordered = sealedFieldsOf(atom.relation)
2079
- const bindings: Array<readonly [number, TermIr]> = atom.bindings.map(function lowerBinding(binding) {
2080
- const ordinal = ordered.findIndex(function byName(candidate) {
2081
- return candidate.name === binding.field
2082
- })
2083
- if (ordinal < 0) {
2084
- throw new AuthoringError({
2085
- message: `query lowering: relation ${atom.relation.name} has no field ${binding.field}`
2086
- })
2087
- }
2088
- return [ordinal, lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids)] as const
2026
+ const bindings = atom.bindings.map((binding): BindingIn => {
2027
+ const field = ordered.findIndex((candidate) => candidate.name === binding.field)
2028
+ if (field < 0)
2029
+ throw new AuthoringError({ message: `query: relation ${atom.relation.name} has no field ${binding.field}` })
2030
+ return { field, term: lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids) }
2089
2031
  })
2090
- return { source: { kind: "edb", relation: relationId }, bindings }
2032
+ return { source: { kind: "Edb", relation: relationId }, bindings }
2091
2033
  }
2092
2034
 
2093
- function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds): TermIr {
2035
+ function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds): TermIn {
2094
2036
  const bound = binding.term
2095
2037
  switch (bound.kind) {
2096
2038
  case "var":
2097
- return { kind: "var", var: ids.of(bound.ref) }
2039
+ return { kind: "Var", var: ids.of(bound.ref) }
2098
2040
  case "param":
2099
- return { kind: "param", param: paramIdOf(ctx, bound.name) }
2041
+ return { kind: "Param", param: paramIdOf(ctx, bound.name) }
2100
2042
  case "setParam":
2101
- return { kind: "paramSet", param: paramIdOf(ctx, bound.name) }
2102
2043
  case "literalSet":
2103
- return { kind: "paramSet", param: paramIdOf(ctx, bound.name) }
2044
+ return { kind: "ParamSet", param: paramIdOf(ctx, bound.name) }
2104
2045
  case "literal":
2105
- return { kind: "literal", value: taggedLiteral(context, binding.data, bound.value) }
2046
+ return { kind: "Literal", value: valueIn(taggedLiteral(context, binding.data, bound.value)) }
2106
2047
  }
2107
2048
  }
2108
2049
 
2109
- /**
2110
- * Lowers one interior atom: named bindings placed by HEAD order, `FieldId(i)` =
2111
- * head position i. Every head column of the table must be bound (a missing key
2112
- * is refused pointed); the var-id assignment order is head order, so the
2113
- * first-use numbering matches the name-keyed edition exactly.
2114
- */
2050
+ /** An interior atom binds every head column of the table, by head position. */
2115
2051
  function lowerInteriorAtom(
2116
2052
  ctx: LowerContext,
2117
2053
  target: DerivedTable,
2118
2054
  bindings: ReadonlyArray<{ readonly key: string; readonly ref: AnyVar }>,
2119
2055
  ids: VarIds
2120
- ): AtomIr {
2056
+ ): AtomIn {
2121
2057
  const interior = ctx.interiorIds.get(target.name)
2122
2058
  if (interior === undefined) {
2123
- throw new AuthoringError({ message: `query lowering: derived table ${target.name} was not declared on this query` })
2124
- }
2125
- if (target.finds.length === 0) {
2126
- throw new AuthoringError({ message: `query lowering: derived table ${target.name} has no head` })
2059
+ throw new AuthoringError({ message: `query: derived table ${target.name} was not declared on this query` })
2127
2060
  }
2128
- const irBindings: Array<readonly [number, TermIr]> = target.finds.map(function lowerPosition(column, position) {
2129
- const binding = bindings.find(function byKey(candidate) {
2130
- return candidate.key === column.name
2061
+ return {
2062
+ source: { kind: "Interior", interior },
2063
+ bindings: target.finds.map((column, field): BindingIn => {
2064
+ const binding = bindings.find((candidate) => candidate.key === column.name)
2065
+ if (binding === undefined) {
2066
+ throw new AuthoringError({ message: `query: interior ${target.name} omits head column ${column.name}` })
2067
+ }
2068
+ return { field, term: { kind: "Var", var: ids.of(binding.ref) } }
2131
2069
  })
2132
- if (binding === undefined) {
2133
- throw new AuthoringError({ message: `query lowering: interior ${target.name} omits head column ${column.name}` })
2134
- }
2135
- return [position, { kind: "var", var: ids.of(binding.ref) } as const] as const
2136
- })
2137
- return { source: { kind: "interior", interior }, bindings: irBindings }
2070
+ }
2138
2071
  }
2139
2072
 
2140
- function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData, ids: VarIds, op: CmpKind): TermIr {
2073
+ function lowerCmpTerm(ctx: LowerContext, side: CmpTermData, sibling: CmpTermData, ids: VarIds, op: CmpKind): TermIn {
2141
2074
  switch (side.kind) {
2142
2075
  case "var":
2143
- return { kind: "var", var: ids.of(side.ref) }
2076
+ return { kind: "Var", var: ids.of(side.ref) }
2144
2077
  case "param":
2145
- return { kind: "param", param: paramIdOf(ctx, side.name) }
2078
+ return { kind: "Param", param: paramIdOf(ctx, side.name) }
2146
2079
  case "setParam":
2147
- return { kind: "paramSet", param: paramIdOf(ctx, side.name) }
2080
+ return { kind: "ParamSet", param: paramIdOf(ctx, side.name) }
2148
2081
  case "literal": {
2149
2082
  const anchor = cmpAnchorOf(ctx, sibling)
2150
2083
  if (anchor === undefined) {
2151
2084
  throw new AuthoringError({
2152
- message: "query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it"
2085
+ message: "query: a comparison literal needs a bound-variable or anchored-param sibling to type it"
2153
2086
  })
2154
2087
  }
2155
- return { kind: "literal", value: taggedCmpLiteral("comparison literal", anchor, side.value, op) }
2088
+ return { kind: "Literal", value: valueIn(taggedCmpLiteral("comparison literal", anchor, side.value, op)) }
2156
2089
  }
2157
2090
  }
2158
2091
  }
2159
2092
 
2160
2093
  function cmpAnchorOf(ctx: LowerContext, sibling: CmpTermData): AnyField | undefined {
2161
- if (sibling.kind === "var") {
2162
- return sibling.ref.field
2163
- }
2164
- if (sibling.kind === "param" || sibling.kind === "setParam") {
2165
- return ctx.params.get(sibling.name)?.anchor
2166
- }
2094
+ if (sibling.kind === "var") return sibling.ref.field
2095
+ if (sibling.kind === "param" || sibling.kind === "setParam") return ctx.params.get(sibling.name)?.anchor
2167
2096
  return undefined
2168
2097
  }
2169
2098
 
2170
- function lowerComparison(ctx: LowerContext, cmp: CmpData, ids: VarIds): ComparisonIr {
2171
- if (cmp.op.kind === "allen") {
2099
+ const CMP = { eq: "Eq", ne: "Ne", lt: "Lt", le: "Le", gt: "Gt", ge: "Ge", pointIn: "PointIn" } as const
2100
+
2101
+ function lowerCondition(ctx: LowerContext, cond: CondData, ids: VarIds): ConditionIn {
2102
+ if (cond.kind !== "cmp") {
2172
2103
  return {
2173
- op: { kind: "allen", mask: cmp.op.mask },
2174
- lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, "allen"),
2175
- rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, "allen")
2104
+ kind: cond.op === "and" ? "And" : "Or",
2105
+ children: cond.children.map((child) => lowerCondition(ctx, child, ids))
2176
2106
  }
2177
2107
  }
2108
+ const kind = cond.op.kind
2178
2109
  return {
2179
- op: { kind: cmp.op.kind },
2180
- lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op.kind),
2181
- rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op.kind)
2110
+ kind: "Leaf",
2111
+ op: cond.op.kind === "allen" ? { kind: "Allen", mask: cond.op.mask } : { kind: CMP[cond.op.kind] },
2112
+ lhs: lowerCmpTerm(ctx, cond.lhs, cond.rhs, ids, kind),
2113
+ rhs: lowerCmpTerm(ctx, cond.rhs, cond.lhs, ids, kind)
2182
2114
  }
2183
2115
  }
2184
2116
 
2185
- function lowerCondition(ctx: LowerContext, cond: CondData, ids: VarIds): ConditionTreeIr {
2186
- if (cond.kind === "cmp") {
2187
- return { kind: "leaf", cmp: lowerComparison(ctx, cond, ids) }
2188
- }
2189
- return {
2190
- kind: cond.op,
2191
- children: cond.children.map(function lowerChild(child) {
2192
- return lowerCondition(ctx, child, ids)
2193
- })
2194
- }
2195
- }
2196
-
2197
- /**
2198
- * Lowers one computed scalar node to the C05 wire (`#native.ts`'s
2199
- * `ScalarExprIr`): the shared grammar arms pass through 1:1. Cached
2200
- * construction depth is the bound; this walk emits wire only and does
2201
- * not re-judge kinds (L14/native compile owns binding).
2202
- */
2203
- function lowerComputeNode(node: QueryNode, ids: VarIds): ScalarExprIr {
2204
- if (node.depth > MAX_COMPUTE_DEPTH) {
2205
- throw new AuthoringError({
2206
- message: `query lowering: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes (the engine's scalar depth bound)`
2207
- })
2208
- }
2209
- return lowerComputeGrammar(node, ids)
2210
- }
2211
-
2212
- function lowerComputeGrammar(node: QueryNode, ids: VarIds): ScalarExprIr {
2213
- return scalarWire(node, (leaf) => {
2214
- if (leaf.kind !== "var") throw new AuthoringError({ message: "query expressions require bound variables" })
2215
- return ids.of(leaf.ref as AnyVar)
2216
- })
2217
- }
2117
+ const FOLD = { sum: "Sum", mean: "Mean", min: "Min", max: "Max" } as const
2218
2118
 
2219
- function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
2220
- if (entry.kind === "segments")
2221
- return { kind: entry.kind, op: entry.op, left: ids.of(entry.left), right: ids.of(entry.right) }
2222
- if (entry.kind === "var") {
2223
- return { kind: "var", var: ids.of(entry.over) }
2224
- }
2225
- if (entry.kind === "compute") {
2226
- return { kind: "compute", expr: lowerComputeNode(entry.expr, ids) }
2119
+ function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIn {
2120
+ switch (entry.kind) {
2121
+ case "segments":
2122
+ return {
2123
+ kind: "Segments",
2124
+ op: entry.op === "intersection" ? "Intersection" : "Difference",
2125
+ left: ids.of(entry.left),
2126
+ right: ids.of(entry.right)
2127
+ }
2128
+ case "var":
2129
+ return { kind: "Var", var: ids.of(entry.over) }
2130
+ case "compute":
2131
+ if (entry.expr.depth > MAX_COMPUTE_DEPTH) {
2132
+ throw new AuthoringError({
2133
+ message: `query: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes`
2134
+ })
2135
+ }
2136
+ return { kind: "Compute", expr: scalarWire(entry.expr, (leaf) => ids.of(leaf.ref as AnyVar)) }
2227
2137
  }
2228
2138
  const agg = entry.agg
2229
2139
  switch (agg.op) {
2230
2140
  case "count":
2231
- return { kind: "count" }
2141
+ return { kind: "Count" }
2232
2142
  case "fold":
2233
- return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) }
2143
+ return { kind: "Aggregate", op: FOLD[agg.fold], over: ids.of(agg.over) }
2234
2144
  case "pack":
2235
- return { kind: "pack", over: ids.of(agg.over) }
2145
+ return { kind: "Pack", over: ids.of(agg.over) }
2236
2146
  }
2237
2147
  }
2238
2148
 
2239
- function headOpOf(agg: AggData): HeadOpIr {
2240
- switch (agg.op) {
2241
- case "count":
2242
- return "count"
2243
- case "fold":
2244
- return agg.fold
2245
- case "pack":
2246
- return "pack"
2247
- }
2248
- }
2249
-
2250
- function headTermOf(column: FindColumn): HeadTermIr {
2149
+ function headTermOf(column: FindColumn): HeadTermIn {
2251
2150
  const entry = column.entry
2252
- if (entry.kind === "segments") return { kind: "compute" }
2253
- if (entry.kind === "var") {
2254
- return { kind: "var" }
2255
- }
2256
- if (entry.kind === "compute") {
2257
- return { kind: "compute" }
2258
- }
2259
- return { kind: "aggregate", op: headOpOf(entry.agg) }
2151
+ if (entry.kind === "segments" || entry.kind === "compute") return { kind: "Compute" }
2152
+ if (entry.kind === "var") return { kind: "Var" }
2153
+ const agg = entry.agg
2154
+ if (agg.op === "count") return { kind: "Aggregate", op: "Count" }
2155
+ if (agg.op === "pack") return { kind: "Aggregate", op: "Pack" }
2156
+ return { kind: "Aggregate", op: FOLD[agg.fold] }
2260
2157
  }
2261
2158
 
2262
- function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
2159
+ function lowerRule(ctx: LowerContext, rule: RuleData, labels?: (readonly string[])[]): RuleIn {
2263
2160
  const ids = freshVarIds()
2264
- const atoms: AtomIr[] = []
2265
- const negated: AtomIr[] = []
2266
- const conditions: ConditionTreeIr[] = []
2161
+ const atoms: AtomIn[] = []
2162
+ const negated: AtomIn[] = []
2163
+ const conditions: ConditionIn[] = []
2267
2164
  for (const item of rule.items) {
2268
2165
  switch (item.kind) {
2269
- case "atom": {
2166
+ case "atom":
2270
2167
  atoms.push(lowerAtom(ctx, item.atom, ids))
2271
2168
  break
2272
- }
2273
- case "negated": {
2169
+ case "negated":
2274
2170
  negated.push(lowerAtom(ctx, item.atom, ids))
2275
2171
  break
2276
- }
2277
- case "interior": {
2172
+ case "interior":
2278
2173
  atoms.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
2279
2174
  break
2280
- }
2281
- case "negatedInterior": {
2175
+ case "negatedInterior":
2282
2176
  negated.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
2283
2177
  break
2284
- }
2285
- case "cond": {
2178
+ case "cond":
2286
2179
  conditions.push(lowerCondition(ctx, item.cond, ids))
2287
2180
  break
2288
- }
2289
2181
  }
2290
2182
  }
2183
+ const finds = rule.finds.map((column) => lowerFind(column.entry, ids))
2184
+ labels?.push(ids.labels())
2185
+ return { finds, atoms, negated, conditions }
2186
+ }
2187
+
2188
+ /** A recursive rule projects variables only and negates nothing. */
2189
+ function recFinds(label: string, rule: RuleIn): number[] {
2190
+ if (rule.negated.length > 0) throw new AuthoringError({ message: `query ${label}: a recursive rule cannot negate` })
2191
+ return rule.finds.map((find) => {
2192
+ if (find.kind !== "Var")
2193
+ throw new AuthoringError({ message: `query ${label}: a recursive rule finds variables only` })
2194
+ return find.var
2195
+ })
2196
+ }
2197
+
2198
+ /** A recursive step reads its own previous output through exactly one self-atom. */
2199
+ function recStep(label: string, rule: RuleIn, self: number): RecStepIn {
2200
+ const selfAtoms = rule.atoms.filter((atom) => atom.source.kind === "Interior" && atom.source.interior === self)
2201
+ const [selfAtom] = selfAtoms
2202
+ if (selfAtom === undefined || selfAtoms.length > 1) {
2203
+ throw new AuthoringError({ message: `query ${label}: a recursive step reads the recursion exactly once` })
2204
+ }
2291
2205
  return {
2292
- finds: rule.finds.map(function findOf(column) {
2293
- return lowerFind(column.entry, ids)
2294
- }),
2295
- atoms,
2296
- negated,
2297
- conditions
2206
+ finds: recFinds(label, rule),
2207
+ selfBindings: selfAtom.bindings,
2208
+ atoms: rule.atoms.filter((atom) => atom !== selfAtom),
2209
+ conditions: rule.conditions
2298
2210
  }
2299
2211
  }
2300
2212
 
2301
- function lowerQuery(q: AnyQuery): ParsedQuery {
2213
+ /** Variable labels by rule: main rules, then each interior's rules. */
2214
+ interface RuleLabels {
2215
+ readonly main: (readonly string[])[]
2216
+ readonly interiors: (readonly string[])[][]
2217
+ }
2218
+
2219
+ /** Lowers a query to the addon's `QueryIn`. The engine validates it; lowering judges nothing. */
2220
+ function lowerQuery(q: AnyQuery): QueryIn {
2221
+ return lowerQueryLabelled(q).query
2222
+ }
2223
+
2224
+ function lowerQueryLabelled(q: AnyQuery): { readonly query: QueryIn; readonly labels: RuleLabels } {
2302
2225
  const theory = q.schema
2303
2226
  const data = q.data
2304
- const relationIds = new Map<string, number>()
2305
- Object.keys(theory.relations).forEach(function assignOrdinal(name, index) {
2306
- relationIds.set(name, index)
2307
- })
2308
- const interiorIds = new Map<string, number>()
2309
- data.interiors.forEach(function assignInteriorId(interior, index) {
2310
- interiorIds.set(interior.name, index)
2311
- })
2312
- if (data.kind === "reach") {
2313
- interiorIds.set(data.rec.name, data.interiors.length)
2314
- }
2227
+ const relationIds = new Map(Object.keys(theory.relations).map((name, index) => [name, index] as const))
2228
+ const interiorIds = new Map(data.interiors.map((interior, index) => [interior.name, index] as const))
2229
+ if (data.kind === "reach") interiorIds.set(data.rec.name, data.interiors.length)
2315
2230
  const paramIds = new Map<string, number>()
2316
2231
  const params = new Map<string, ParamEntry>()
2317
- data.params.forEach(function assignParamId(entry, index) {
2232
+ data.params.forEach((entry, index) => {
2318
2233
  if (entry.anchor === undefined) {
2319
2234
  throw new AuthoringError({
2320
2235
  message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
@@ -2324,36 +2239,30 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
2324
2239
  params.set(entry.name, entry)
2325
2240
  })
2326
2241
  const ctx: LowerContext = { theory, relationIds, interiorIds, paramIds, params }
2327
- const interiors = data.interiors.map(function lowerInterior(interior) {
2328
- return {
2329
- head: interior.finds.map(headTermOf),
2330
- rules: interior.rules.map(function lowerInteriorRule(rule) {
2331
- return lowerRule(ctx, rule)
2332
- })
2333
- }
2334
- })
2335
- const head = data.finds.map(headTermOf)
2336
- const rules = data.rules.map(function lowerMainRule(rule) {
2337
- return lowerRule(ctx, rule)
2338
- })
2339
- if (data.kind === "cq") {
2340
- return parseQueryIr({ kind: "cq", interiors, head, rules })
2341
- }
2342
- return parseQueryIr({
2343
- kind: "reach",
2344
- interiors,
2345
- rec: {
2346
- head: data.rec.finds.map(headTermOf),
2347
- base: data.rec.base.map(function lowerBase(rule) {
2348
- return lowerRule(ctx, rule)
2349
- }),
2350
- rec: data.rec.rec.map(function lowerRecArm(rule) {
2351
- return lowerRule(ctx, rule)
2352
- })
2242
+ const labels: RuleLabels = { main: [], interiors: data.interiors.map(() => []) }
2243
+ const query: QueryIn = {
2244
+ interiors: data.interiors.map((interior, index) => ({
2245
+ rules: interior.rules.map((rule) => lowerRule(ctx, rule, labels.interiors[index]))
2246
+ })),
2247
+ head: data.finds.map(headTermOf),
2248
+ rules: data.rules.map((rule) => lowerRule(ctx, rule, labels.main))
2249
+ }
2250
+ if (data.kind === "cq") return { query, labels }
2251
+ const label = `recursion ${data.rec.name}`
2252
+ const self = data.interiors.length
2253
+ return {
2254
+ query: {
2255
+ ...query,
2256
+ rec: {
2257
+ base: data.rec.base.map((rule) => {
2258
+ const lowered = lowerRule(ctx, rule)
2259
+ return { finds: recFinds(label, lowered), atoms: lowered.atoms, conditions: lowered.conditions }
2260
+ }),
2261
+ rec: data.rec.rec.map((rule) => recStep(label, lowerRule(ctx, rule), self))
2262
+ }
2353
2263
  },
2354
- head,
2355
- rules
2356
- })
2264
+ labels
2265
+ }
2357
2266
  }
2358
2267
 
2359
2268
  export type {
@@ -2374,4 +2283,4 @@ export type {
2374
2283
  RuleValue,
2375
2284
  TermOps
2376
2285
  }
2377
- export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, taggedCmpLiteral }
2286
+ export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, queryHandleOf, taggedCmpLiteral }