@bjornpagen/bumbledb 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/COOKBOOK.md +153 -77
  2. package/README.md +107 -41
  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 -40
  16. package/dist/changes.d.ts.map +1 -1
  17. package/dist/changes.js +86 -104
  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 +13 -27
  24. package/dist/codec.d.ts.map +1 -1
  25. package/dist/codec.js +20 -21
  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 -57
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +27 -28
  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 +4 -4
  123. package/dist/query/compute.d.ts.map +1 -1
  124. package/dist/query/compute.js +4 -4
  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 +22 -17
  130. package/dist/query/lower.d.ts.map +1 -1
  131. package/dist/query/lower.js +230 -343
  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 +7 -7
  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 -20
  158. package/dist/runtime.d.ts.map +1 -1
  159. package/dist/runtime.js +41 -205
  160. package/dist/runtime.js.map +1 -1
  161. package/dist/scalar.d.ts +4 -4
  162. package/dist/scalar.d.ts.map +1 -1
  163. package/dist/scalar.js +24 -8
  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 -191
  195. package/src/closed.ts +5 -5
  196. package/src/codec.ts +35 -49
  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 +92 -105
  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 +7 -7
  223. package/src/query/find.ts +8 -8
  224. package/src/query/lower.ts +270 -392
  225. package/src/query/run.ts +42 -21
  226. package/src/query/scope.ts +29 -37
  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 -277
  232. package/src/scalar.ts +32 -12
  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 -189
  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/native.d.ts +0 -562
  253. package/dist/native.d.ts.map +0 -1
  254. package/dist/native.js +0 -123
  255. package/dist/native.js.map +0 -1
  256. package/dist/query/description.d.ts +0 -41
  257. package/dist/query/description.d.ts.map +0 -1
  258. package/dist/query/description.js +0 -311
  259. package/dist/query/description.js.map +0 -1
  260. package/dist/query/parse-ir.d.ts +0 -10
  261. package/dist/query/parse-ir.d.ts.map +0 -1
  262. package/dist/query/parse-ir.js +0 -280
  263. package/dist/query/parse-ir.js.map +0 -1
  264. package/dist/runtime-codes.d.ts +0 -2
  265. package/dist/runtime-codes.d.ts.map +0 -1
  266. package/dist/runtime-codes.js +0 -19
  267. package/dist/runtime-codes.js.map +0 -1
  268. package/dist/runtime-errors.d.ts +0 -138
  269. package/dist/runtime-errors.d.ts.map +0 -1
  270. package/dist/runtime-errors.js +0 -111
  271. package/dist/runtime-errors.js.map +0 -1
  272. package/dist/runtime-native.d.ts +0 -136
  273. package/dist/runtime-native.d.ts.map +0 -1
  274. package/dist/runtime-native.js +0 -5
  275. package/dist/runtime-native.js.map +0 -1
  276. package/dist/schema-file.d.ts +0 -7
  277. package/dist/schema-file.d.ts.map +0 -1
  278. package/dist/schema-file.js +0 -24
  279. package/dist/schema-file.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 -235
  283. package/src/internal/log.ts +0 -46
  284. package/src/native.ts +0 -736
  285. package/src/query/description.ts +0 -424
  286. package/src/query/parse-ir.ts +0 -293
  287. package/src/runtime-codes.ts +0 -18
  288. package/src/runtime-errors.ts +0 -122
  289. package/src/runtime-native.ts +0 -181
  290. package/src/schema-file.ts +0 -38
@@ -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,24 +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 { Same } from "#judgment.ts"
17
- 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"
18
21
  import type {
19
- AtomIr,
20
- ComparisonIr,
21
- ConditionTreeIr,
22
- FindTermIr,
23
- HeadOpIr,
24
- HeadTermIr,
25
- ParsedQuery,
26
- QueryParam,
27
- RuleIr,
28
- ScalarExprIr,
29
- TaggedValue,
30
- TermIr
31
- } 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"
32
39
  import type {
33
40
  AggData,
34
41
  AnyCond,
@@ -37,7 +44,6 @@ import type {
37
44
  BindParamsShape,
38
45
  CheckBindings,
39
46
  CheckCond,
40
- CmpData,
41
47
  CmpKind,
42
48
  CmpTermData,
43
49
  CondData,
@@ -55,13 +61,11 @@ import type {
55
61
  RecHead,
56
62
  RuleData,
57
63
  RuleItem
58
- } from "#query/atom.ts"
59
- import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
60
- import type { QueryNode } from "#query/compute.ts"
61
- import { computeFieldOf, computeVarsOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "#query/compute.ts"
62
- import type { CheckFind, CheckRecFind, FindShape, HeadRecordOf, RowOfFind } from "#query/find.ts"
63
- import { count, max, mean, min, pack, sum } from "#query/find.ts"
64
- 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"
65
69
  import type {
66
70
  AnyVar,
67
71
  ClassedField,
@@ -72,7 +76,7 @@ import type {
72
76
  ParamsRecord,
73
77
  ShapeOf,
74
78
  VarsOf
75
- } from "#query/scope.ts"
79
+ } from "./scope.ts"
76
80
  import {
77
81
  fieldAntiJoins,
78
82
  fieldJoins,
@@ -87,12 +91,8 @@ import {
87
91
  makeSetParam,
88
92
  renderFieldKind,
89
93
  term
90
- } from "#query/scope.ts"
91
- import { difference, intersection, isSegments, segmentField } from "#query/segments.ts"
92
- import { scalarWire } from "#scalar.ts"
93
- import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
94
- import { schemaDescriptor, schemasAgree } from "#schema.ts"
95
- import { arrayValue, recordValue, taggedValueOf } from "#values.ts"
94
+ } from "./scope.ts"
95
+ import { difference, intersection, isSegments, segmentField } from "./segments.ts"
96
96
 
97
97
  type QueryRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], MatchOwner>
98
98
 
@@ -319,8 +319,8 @@ interface InteriorRuleChain<
319
319
  bindings: B & CheckInteriorBindings<B>
320
320
  ): InteriorRuleChain<Rels, P, Classes>
321
321
  /**
322
- * Nonrecursive derived stages emit aggregate/computed outputs too
323
- * (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.
324
324
  */
325
325
  find<const F extends Readonly<Record<string, AnyVar>>>(
326
326
  entries: F
@@ -345,7 +345,7 @@ interface RecRuleScope<Rels extends SchemaRelations, Classes extends SchemaClass
345
345
  relation: R,
346
346
  bindings: B & CheckBindings<Classes, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
347
347
  ): RecRuleChain<Rels, BindParamsShape<MatchFields<R>, B>, Classes>
348
- /** Frozen finite nonrecursive imports may feed base/step (chapter 34). */
348
+ /** Frozen finite nonrecursive imports may feed base/step. */
349
349
  match<Q extends AnyQuery, const B extends ImportMatchShape<Q>>(
350
350
  imported: Q,
351
351
  bindings: B
@@ -913,51 +913,6 @@ function findColumnOf(name: string, entry: unknown): FindColumn {
913
913
  })
914
914
  }
915
915
 
916
- /**
917
- * The orderable ban's pointed refusal
918
- * § orderability): a closed reference is equality-and-membership only.
919
- */
920
- function closedOrderError(context: string, position: string, vocabulary: string): Error {
921
- return new AuthoringError({
922
- message: `${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
923
- })
924
- }
925
-
926
- function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
927
- return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn"
928
- }
929
-
930
- function assertBound(where: string, bound: ReadonlySet<AnyVar>, ref: AnyVar): void {
931
- if (!bound.has(ref)) {
932
- throw new AuthoringError({
933
- message: `${where}: the variable ${ref.label} is not bound by a relation atom of the rule`
934
- })
935
- }
936
- }
937
-
938
- function assertInterval(where: string, ref: AnyVar): void {
939
- if (ref.field.kind !== "interval") {
940
- throw new AuthoringError({
941
- message: `${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
942
- })
943
- }
944
- }
945
-
946
- function assertNotClosed(where: string, position: string, ref: AnyVar): void {
947
- const roster = rosterOf(ref.field)
948
- if (roster !== undefined) {
949
- throw closedOrderError(where, `${position} ${ref.label}`, roster.name)
950
- }
951
- }
952
-
953
- function assertNumeric(where: string, position: string, ref: AnyVar): void {
954
- if (ref.field.kind !== "u64" && ref.field.kind !== "i64" && ref.field.kind !== "f64") {
955
- throw new AuthoringError({
956
- message: `${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64/f64 only`
957
- })
958
- }
959
- }
960
-
961
916
  /**
962
917
  * The head column's slot: a projected variable keeps its mint slot; an
963
918
  * aggregate output is a NEW derived scalar — its descriptor follows the
@@ -992,80 +947,24 @@ function findColumnSlotOf(context: ChainContext, column: FindColumn): ClassedFie
992
947
  }
993
948
  }
994
949
 
995
- function validateColumn(context: ChainContext, bound: ReadonlySet<AnyVar>, column: FindColumn): void {
996
- const where = `${contextLabel(context)} find ${column.name}`
997
- const entry = column.entry
998
- if (entry.kind === "segments") {
999
- assertBound(where, bound, entry.left)
1000
- assertBound(where, bound, entry.right)
1001
- return
1002
- }
1003
- if (entry.kind === "var") {
1004
- assertBound(where, bound, entry.over)
1005
- return
1006
- }
1007
- if (entry.kind === "compute") {
1008
- // Kind agreement, closed-reference and depth walls hold at
1009
- // construction (#query/compute.ts); boundness is a RULE property,
1010
- // judged here like every other term position.
1011
- for (const ref of computeVarsOf(entry.expr)) {
1012
- assertBound(where, bound, ref)
1013
- }
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)
1014
954
  return
1015
955
  }
1016
- const agg = entry.agg
1017
- switch (agg.op) {
1018
- case "count":
1019
- return
1020
- case "fold": {
1021
- assertBound(where, bound, agg.over)
1022
- assertNotClosed(where, `the ${agg.fold} input`, agg.over)
1023
- assertNumeric(where, `the ${agg.fold} input`, agg.over)
1024
- if (agg.fold === "mean" && agg.over.field.kind !== "f64") {
1025
- throw new AuthoringError({ message: `${where}: mean requires an f64 input; cast integers explicitly` })
1026
- }
1027
- return
1028
- }
1029
- case "pack":
1030
- assertBound(where, bound, agg.over)
1031
- assertInterval(where, agg.over)
1032
- return
1033
- }
1034
- }
1035
-
1036
- function validateCond(context: ChainContext, bound: ReadonlySet<AnyVar>, cond: CondData): void {
1037
- const label = contextLabel(context)
1038
- if (cond.kind === "cmp") {
1039
- for (const side of [cond.lhs, cond.rhs]) {
1040
- if (side.kind === "var") {
1041
- assertBound(label, bound, side.ref)
1042
- const roster = rosterOf(side.ref.field)
1043
- if (isOrderOp(cond.op.kind) && roster !== undefined) {
1044
- throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name)
1045
- }
1046
- }
1047
- }
1048
- if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
1049
- assertBound(label, bound, cond.lhs.ref)
1050
- assertBound(label, bound, cond.rhs.ref)
1051
- const lhs = mintSlotOf(context, cond.lhs.ref)
1052
- const rhs = mintSlotOf(context, cond.rhs.ref)
1053
- if (!fieldJoins(lhs, rhs)) {
1054
- throw new AuthoringError({
1055
- 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)`
1056
- })
1057
- }
1058
- }
1059
- return
1060
- }
1061
- for (const child of cond.children) {
1062
- 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
+ })
1063
963
  }
1064
964
  }
1065
965
 
1066
966
  function validateInterior(
1067
967
  context: ChainContext,
1068
- bound: ReadonlySet<AnyVar>,
1069
968
  item: {
1070
969
  readonly kind: "interior" | "negatedInterior"
1071
970
  readonly target: DerivedTable
@@ -1096,11 +995,6 @@ function validateInterior(
1096
995
  }
1097
996
  }
1098
997
  for (const binding of item.bindings) {
1099
- if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
1100
- throw new AuthoringError({
1101
- 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)`
1102
- })
1103
- }
1104
998
  const headColumn = headColumns.find(function byName(column) {
1105
999
  return column.name === binding.key
1106
1000
  })
@@ -1118,38 +1012,14 @@ function validateInterior(
1118
1012
 
1119
1013
  function completeRule(context: ChainContext, state: RuleBuildState, rawColumns: readonly FindColumn[]): RuleData {
1120
1014
  const label = contextLabel(context)
1121
- if (rawColumns.length === 0) {
1122
- throw new AuthoringError({ message: `${label}: a find needs at least one entry` })
1123
- }
1124
- const aggregates = rawColumns.flatMap((column) => (column.entry.kind === "aggregate" ? [column.entry.agg] : []))
1125
- const packs = aggregates.filter((agg) => agg.op === "pack").length
1126
- if (packs > 1) throw new AuthoringError({ message: "a query stage can pack one interval column" })
1127
- if (packs !== 0 && aggregates.length !== packs)
1128
- throw new AuthoringError({ message: "pack intervals and compute numeric aggregates in separate query stages" })
1129
- if (rawColumns.some((c) => c.entry.kind === "segments") && aggregates.length !== 0)
1130
- throw new AuthoringError({ message: "aggregate generated segments in a following query stage" })
1131
1015
  const columns = rawColumns.map(function enrichColumn(column): FindColumn {
1132
1016
  assertDeclarationOrderKey(`${label} find column`, column.name)
1133
- validateColumn(context, state.bound, column)
1134
1017
  const slot = findColumnSlotOf(context, column)
1135
1018
  return Object.freeze({ name: column.name, entry: column.entry, slot, closed: rosterOf(slot?.field) })
1136
1019
  })
1137
1020
  for (const item of state.items) {
1138
- if (item.kind === "negated") {
1139
- for (const binding of item.atom.bindings) {
1140
- if (binding.term.kind === "var" && !state.bound.has(binding.term.ref)) {
1141
- throw new AuthoringError({
1142
- 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)`
1143
- })
1144
- }
1145
- }
1146
- }
1147
- if (item.kind === "interior" || item.kind === "negatedInterior") {
1148
- validateInterior(context, state.bound, item, columns)
1149
- }
1150
- if (item.kind === "cond") {
1151
- validateCond(context, state.bound, item.cond)
1152
- }
1021
+ if (item.kind === "interior" || item.kind === "negatedInterior") validateInterior(context, item, columns)
1022
+ if (item.kind === "cond") validateCond(context, item.cond)
1153
1023
  }
1154
1024
  return Object.freeze({ items: state.items, finds: Object.freeze(columns), paramUses: state.paramUses })
1155
1025
  }
@@ -1241,8 +1111,8 @@ function interiorAdvance(
1241
1111
  }
1242
1112
 
1243
1113
  /**
1244
- * Imported query templates as relation-expression sources (chapter 34's
1245
- * 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
1246
1116
  * interiors plus its main rules as one derived table — is SPLICED into the
1247
1117
  * consuming query's interior roster at assembly ({@link makeRawQuery});
1248
1118
  * naming does not materialize anything, and the imported template keeps its
@@ -1289,7 +1159,7 @@ function importEntryOf(context: ChainContext, imported: AnyQuery): ImportEntry {
1289
1159
  const data = imported.data
1290
1160
  if (data.kind === "reach") {
1291
1161
  throw new AuthoringError({
1292
- 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`
1293
1163
  })
1294
1164
  }
1295
1165
  const userParams = data.params.filter(function userSupplied(entry) {
@@ -1428,8 +1298,7 @@ function findColumns(context: ChainContext, entries: Readonly<Record<string, unk
1428
1298
  const columns: FindColumn[] = []
1429
1299
  queryRecord("query find", entries)
1430
1300
  // Nonrecursive derived stages (interiors) may emit aggregate/computed
1431
- // outputs (C05: "do not leave Interior.rules projection only"); ONLY the
1432
- // recursive feedback cycle stays projection-only — no aggregate,
1301
+ // outputs; ONLY the recursive feedback cycle stays projection-only — no aggregate,
1433
1302
  // arithmetic-created value or negation flows through it.
1434
1303
  const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined
1435
1304
  for (const [name, entry] of Object.entries(entries)) {
@@ -1501,7 +1370,7 @@ function makeQueryRuleScope<Rels extends SchemaRelations, Classes extends Schema
1501
1370
  ): QueryRuleScope<Rels, Classes> {
1502
1371
  const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env })
1503
1372
  if (!isTypedScope<QueryRuleScope<Rels, Classes>>(raw)) {
1504
- throw new SdkInvariantError({ message: "query rule builder construction incomplete" })
1373
+ throw internalError("query rule builder construction incomplete")
1505
1374
  }
1506
1375
  return raw
1507
1376
  }
@@ -1513,7 +1382,7 @@ function makeInteriorRuleScope<Rels extends SchemaRelations, Classes extends Sch
1513
1382
  ): InteriorRuleScope<Rels, Classes> {
1514
1383
  const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env })
1515
1384
  if (!isTypedScope<InteriorRuleScope<Rels, Classes>>(raw)) {
1516
- throw new SdkInvariantError({ message: "interior rule builder construction incomplete" })
1385
+ throw internalError("interior rule builder construction incomplete")
1517
1386
  }
1518
1387
  return raw
1519
1388
  }
@@ -1553,7 +1422,7 @@ function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaCl
1553
1422
  ...env
1554
1423
  })
1555
1424
  if (!isTypedScope<RecRuleScope<Rels, Classes>>(raw)) {
1556
- throw new SdkInvariantError({ message: "rec rule builder construction incomplete" })
1425
+ throw internalError("rec rule builder construction incomplete")
1557
1426
  }
1558
1427
  return raw
1559
1428
  }
@@ -1562,8 +1431,10 @@ function renderClosedSlice(closed: AnyClosedRoster | undefined): string {
1562
1431
  return closed === undefined ? "a bare value" : `a ${closed.name} reference`
1563
1432
  }
1564
1433
 
1565
- function headOperation(column: FindColumn): HeadOpIr | undefined {
1566
- return column.entry.kind === "aggregate" ? headOpOf(column.entry.agg) : undefined
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
1567
1438
  }
1568
1439
 
1569
1440
  function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
@@ -1571,16 +1442,10 @@ function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
1571
1442
  }
1572
1443
 
1573
1444
  /**
1574
- * Folds every rule's param uses (interiors in declaration order, then rec
1575
- * base, then rec arms, then main — exactly the lowering walk) into the
1576
- * query's registry: first use mints the dense `ParamId`, the first
1577
- * FIELD-ANCHORED use types the wire, and one name keeps one shape AND one
1578
- * closedness. The orderable ban needs no registry arm: an order use always
1579
- * anchors its SIBLING's domain (the no-variable-side spelling is refused
1580
- * at the comparison constructor), so a closed-anchored param under an
1581
- * order op dies at the one-domain wall here — and an order use whose
1582
- * sibling is itself closed-bound dies at the comparison's own var-side
1583
- * 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.
1584
1449
  */
1585
1450
  function paramRegistryOf(
1586
1451
  interiors: readonly InteriorData[],
@@ -1777,6 +1642,53 @@ function afterMainError(what: string): Error {
1777
1642
  })
1778
1643
  }
1779
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
+
1780
1692
  function makeRawQuery(
1781
1693
  inputTheory: AnySchema,
1782
1694
  inputInteriors: readonly InteriorData[],
@@ -1816,6 +1728,7 @@ function makeRawQuery(
1816
1728
  finds: mergedFinds,
1817
1729
  params
1818
1730
  })
1731
+ const handle = validateQueryData(theory, data)
1819
1732
  const value: RawQuery = {
1820
1733
  get schema() {
1821
1734
  return snapshotData(theory)
@@ -1841,6 +1754,7 @@ function makeRawQuery(
1841
1754
  }
1842
1755
  }
1843
1756
  Object.freeze(value)
1757
+ validatedQueries.set(value, handle)
1844
1758
  return value
1845
1759
  }
1846
1760
 
@@ -2071,6 +1985,8 @@ interface LowerContext {
2071
1985
 
2072
1986
  interface VarIds {
2073
1987
  of(ref: AnyVar): number
1988
+ /** Each variable's label, by variable id. */
1989
+ labels(): readonly string[]
2074
1990
  }
2075
1991
 
2076
1992
  function freshVarIds(): VarIds {
@@ -2084,6 +2000,9 @@ function freshVarIds(): VarIds {
2084
2000
  const id = assigned.size
2085
2001
  assigned.set(ref, id)
2086
2002
  return id
2003
+ },
2004
+ labels() {
2005
+ return [...assigned.keys()].map((ref) => ref.label)
2087
2006
  }
2088
2007
  }
2089
2008
  }
@@ -2096,256 +2015,221 @@ function paramIdOf(ctx: LowerContext, name: string): number {
2096
2015
  return id
2097
2016
  }
2098
2017
 
2099
- function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
2100
- const member = ctx.theory.relations[atom.relation.name]
2101
- 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) {
2102
2021
  throw new AuthoringError({
2103
- 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`
2104
2023
  })
2105
2024
  }
2106
- const relationId = ctx.relationIds.get(atom.relation.name)
2107
- if (relationId === undefined) {
2108
- throw new AuthoringError({ message: `query lowering: relation ${atom.relation.name} has no ordinal` })
2109
- }
2110
2025
  const ordered = sealedFieldsOf(atom.relation)
2111
- const bindings: Array<readonly [number, TermIr]> = atom.bindings.map(function lowerBinding(binding) {
2112
- const ordinal = ordered.findIndex(function byName(candidate) {
2113
- return candidate.name === binding.field
2114
- })
2115
- if (ordinal < 0) {
2116
- throw new AuthoringError({
2117
- message: `query lowering: relation ${atom.relation.name} has no field ${binding.field}`
2118
- })
2119
- }
2120
- 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) }
2121
2031
  })
2122
- return { source: { kind: "edb", relation: relationId }, bindings }
2032
+ return { source: { kind: "Edb", relation: relationId }, bindings }
2123
2033
  }
2124
2034
 
2125
- function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds): TermIr {
2035
+ function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds): TermIn {
2126
2036
  const bound = binding.term
2127
2037
  switch (bound.kind) {
2128
2038
  case "var":
2129
- return { kind: "var", var: ids.of(bound.ref) }
2039
+ return { kind: "Var", var: ids.of(bound.ref) }
2130
2040
  case "param":
2131
- return { kind: "param", param: paramIdOf(ctx, bound.name) }
2041
+ return { kind: "Param", param: paramIdOf(ctx, bound.name) }
2132
2042
  case "setParam":
2133
- return { kind: "paramSet", param: paramIdOf(ctx, bound.name) }
2134
2043
  case "literalSet":
2135
- return { kind: "paramSet", param: paramIdOf(ctx, bound.name) }
2044
+ return { kind: "ParamSet", param: paramIdOf(ctx, bound.name) }
2136
2045
  case "literal":
2137
- return { kind: "literal", value: taggedLiteral(context, binding.data, bound.value) }
2046
+ return { kind: "Literal", value: valueIn(taggedLiteral(context, binding.data, bound.value)) }
2138
2047
  }
2139
2048
  }
2140
2049
 
2141
- /**
2142
- * Lowers one interior atom: named bindings placed by HEAD order, `FieldId(i)` =
2143
- * head position i. Every head column of the table must be bound (a missing key
2144
- * is refused pointed); the var-id assignment order is head order, so the
2145
- * first-use numbering matches the name-keyed edition exactly.
2146
- */
2050
+ /** An interior atom binds every head column of the table, by head position. */
2147
2051
  function lowerInteriorAtom(
2148
2052
  ctx: LowerContext,
2149
2053
  target: DerivedTable,
2150
2054
  bindings: ReadonlyArray<{ readonly key: string; readonly ref: AnyVar }>,
2151
2055
  ids: VarIds
2152
- ): AtomIr {
2056
+ ): AtomIn {
2153
2057
  const interior = ctx.interiorIds.get(target.name)
2154
2058
  if (interior === undefined) {
2155
- throw new AuthoringError({ message: `query lowering: derived table ${target.name} was not declared on this query` })
2156
- }
2157
- if (target.finds.length === 0) {
2158
- 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` })
2159
2060
  }
2160
- const irBindings: Array<readonly [number, TermIr]> = target.finds.map(function lowerPosition(column, position) {
2161
- const binding = bindings.find(function byKey(candidate) {
2162
- 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) } }
2163
2069
  })
2164
- if (binding === undefined) {
2165
- throw new AuthoringError({ message: `query lowering: interior ${target.name} omits head column ${column.name}` })
2166
- }
2167
- return [position, { kind: "var", var: ids.of(binding.ref) } as const] as const
2168
- })
2169
- return { source: { kind: "interior", interior }, bindings: irBindings }
2070
+ }
2170
2071
  }
2171
2072
 
2172
- 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 {
2173
2074
  switch (side.kind) {
2174
2075
  case "var":
2175
- return { kind: "var", var: ids.of(side.ref) }
2076
+ return { kind: "Var", var: ids.of(side.ref) }
2176
2077
  case "param":
2177
- return { kind: "param", param: paramIdOf(ctx, side.name) }
2078
+ return { kind: "Param", param: paramIdOf(ctx, side.name) }
2178
2079
  case "setParam":
2179
- return { kind: "paramSet", param: paramIdOf(ctx, side.name) }
2080
+ return { kind: "ParamSet", param: paramIdOf(ctx, side.name) }
2180
2081
  case "literal": {
2181
2082
  const anchor = cmpAnchorOf(ctx, sibling)
2182
2083
  if (anchor === undefined) {
2183
2084
  throw new AuthoringError({
2184
- 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"
2185
2086
  })
2186
2087
  }
2187
- return { kind: "literal", value: taggedCmpLiteral("comparison literal", anchor, side.value, op) }
2088
+ return { kind: "Literal", value: valueIn(taggedCmpLiteral("comparison literal", anchor, side.value, op)) }
2188
2089
  }
2189
2090
  }
2190
2091
  }
2191
2092
 
2192
2093
  function cmpAnchorOf(ctx: LowerContext, sibling: CmpTermData): AnyField | undefined {
2193
- if (sibling.kind === "var") {
2194
- return sibling.ref.field
2195
- }
2196
- if (sibling.kind === "param" || sibling.kind === "setParam") {
2197
- return ctx.params.get(sibling.name)?.anchor
2198
- }
2094
+ if (sibling.kind === "var") return sibling.ref.field
2095
+ if (sibling.kind === "param" || sibling.kind === "setParam") return ctx.params.get(sibling.name)?.anchor
2199
2096
  return undefined
2200
2097
  }
2201
2098
 
2202
- function lowerComparison(ctx: LowerContext, cmp: CmpData, ids: VarIds): ComparisonIr {
2203
- 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") {
2204
2103
  return {
2205
- op: { kind: "allen", mask: cmp.op.mask },
2206
- lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, "allen"),
2207
- 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))
2208
2106
  }
2209
2107
  }
2108
+ const kind = cond.op.kind
2210
2109
  return {
2211
- op: { kind: cmp.op.kind },
2212
- lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op.kind),
2213
- rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op.kind)
2214
- }
2215
- }
2216
-
2217
- function lowerCondition(ctx: LowerContext, cond: CondData, ids: VarIds): ConditionTreeIr {
2218
- if (cond.kind === "cmp") {
2219
- return { kind: "leaf", cmp: lowerComparison(ctx, cond, ids) }
2220
- }
2221
- return {
2222
- kind: cond.op,
2223
- children: cond.children.map(function lowerChild(child) {
2224
- return lowerCondition(ctx, child, ids)
2225
- })
2226
- }
2227
- }
2228
-
2229
- /**
2230
- * Lowers one computed scalar node to the C05 wire (`#native.ts`'s
2231
- * `ScalarExprIr`): the shared grammar arms pass through 1:1. Cached
2232
- * construction depth is the bound; this walk emits wire only and does
2233
- * not re-judge kinds (L14/native compile owns binding).
2234
- */
2235
- function lowerComputeNode(node: QueryNode, ids: VarIds): ScalarExprIr {
2236
- if (node.depth > MAX_COMPUTE_DEPTH) {
2237
- throw new AuthoringError({
2238
- message: `query lowering: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes (the engine's scalar depth bound)`
2239
- })
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)
2240
2114
  }
2241
- return lowerComputeGrammar(node, ids)
2242
2115
  }
2243
2116
 
2244
- function lowerComputeGrammar(node: QueryNode, ids: VarIds): ScalarExprIr {
2245
- return scalarWire(node, (leaf) => {
2246
- return ids.of(leaf.ref as AnyVar)
2247
- })
2248
- }
2117
+ const FOLD = { sum: "Sum", mean: "Mean", min: "Min", max: "Max" } as const
2249
2118
 
2250
- function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
2251
- if (entry.kind === "segments")
2252
- return { kind: entry.kind, op: entry.op, left: ids.of(entry.left), right: ids.of(entry.right) }
2253
- if (entry.kind === "var") {
2254
- return { kind: "var", var: ids.of(entry.over) }
2255
- }
2256
- if (entry.kind === "compute") {
2257
- 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)) }
2258
2137
  }
2259
2138
  const agg = entry.agg
2260
2139
  switch (agg.op) {
2261
2140
  case "count":
2262
- return { kind: "count" }
2263
- case "fold":
2264
- return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) }
2265
- case "pack":
2266
- return { kind: "pack", over: ids.of(agg.over) }
2267
- }
2268
- }
2269
-
2270
- function headOpOf(agg: AggData): HeadOpIr {
2271
- switch (agg.op) {
2272
- case "count":
2273
- return "count"
2141
+ return { kind: "Count" }
2274
2142
  case "fold":
2275
- return agg.fold
2143
+ return { kind: "Aggregate", op: FOLD[agg.fold], over: ids.of(agg.over) }
2276
2144
  case "pack":
2277
- return "pack"
2145
+ return { kind: "Pack", over: ids.of(agg.over) }
2278
2146
  }
2279
2147
  }
2280
2148
 
2281
- function headTermOf(column: FindColumn): HeadTermIr {
2149
+ function headTermOf(column: FindColumn): HeadTermIn {
2282
2150
  const entry = column.entry
2283
- if (entry.kind === "segments") return { kind: "compute" }
2284
- if (entry.kind === "var") {
2285
- return { kind: "var" }
2286
- }
2287
- if (entry.kind === "compute") {
2288
- return { kind: "compute" }
2289
- }
2290
- 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] }
2291
2157
  }
2292
2158
 
2293
- function lowerRule(ctx: LowerContext, rule: RuleData): RuleIr {
2159
+ function lowerRule(ctx: LowerContext, rule: RuleData, labels?: (readonly string[])[]): RuleIn {
2294
2160
  const ids = freshVarIds()
2295
- const atoms: AtomIr[] = []
2296
- const negated: AtomIr[] = []
2297
- const conditions: ConditionTreeIr[] = []
2161
+ const atoms: AtomIn[] = []
2162
+ const negated: AtomIn[] = []
2163
+ const conditions: ConditionIn[] = []
2298
2164
  for (const item of rule.items) {
2299
2165
  switch (item.kind) {
2300
- case "atom": {
2166
+ case "atom":
2301
2167
  atoms.push(lowerAtom(ctx, item.atom, ids))
2302
2168
  break
2303
- }
2304
- case "negated": {
2169
+ case "negated":
2305
2170
  negated.push(lowerAtom(ctx, item.atom, ids))
2306
2171
  break
2307
- }
2308
- case "interior": {
2172
+ case "interior":
2309
2173
  atoms.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
2310
2174
  break
2311
- }
2312
- case "negatedInterior": {
2175
+ case "negatedInterior":
2313
2176
  negated.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids))
2314
2177
  break
2315
- }
2316
- case "cond": {
2178
+ case "cond":
2317
2179
  conditions.push(lowerCondition(ctx, item.cond, ids))
2318
2180
  break
2319
- }
2320
2181
  }
2321
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
+ }
2322
2205
  return {
2323
- finds: rule.finds.map(function findOf(column) {
2324
- return lowerFind(column.entry, ids)
2325
- }),
2326
- atoms,
2327
- negated,
2328
- conditions
2206
+ finds: recFinds(label, rule),
2207
+ selfBindings: selfAtom.bindings,
2208
+ atoms: rule.atoms.filter((atom) => atom !== selfAtom),
2209
+ conditions: rule.conditions
2329
2210
  }
2330
2211
  }
2331
2212
 
2332
- 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 } {
2333
2225
  const theory = q.schema
2334
2226
  const data = q.data
2335
- const relationIds = new Map<string, number>()
2336
- Object.keys(theory.relations).forEach(function assignOrdinal(name, index) {
2337
- relationIds.set(name, index)
2338
- })
2339
- const interiorIds = new Map<string, number>()
2340
- data.interiors.forEach(function assignInteriorId(interior, index) {
2341
- interiorIds.set(interior.name, index)
2342
- })
2343
- if (data.kind === "reach") {
2344
- interiorIds.set(data.rec.name, data.interiors.length)
2345
- }
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)
2346
2230
  const paramIds = new Map<string, number>()
2347
2231
  const params = new Map<string, ParamEntry>()
2348
- data.params.forEach(function assignParamId(entry, index) {
2232
+ data.params.forEach((entry, index) => {
2349
2233
  if (entry.anchor === undefined) {
2350
2234
  throw new AuthoringError({
2351
2235
  message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
@@ -2355,36 +2239,30 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
2355
2239
  params.set(entry.name, entry)
2356
2240
  })
2357
2241
  const ctx: LowerContext = { theory, relationIds, interiorIds, paramIds, params }
2358
- const interiors = data.interiors.map(function lowerInterior(interior) {
2359
- return {
2360
- head: interior.finds.map(headTermOf),
2361
- rules: interior.rules.map(function lowerInteriorRule(rule) {
2362
- return lowerRule(ctx, rule)
2363
- })
2364
- }
2365
- })
2366
- const head = data.finds.map(headTermOf)
2367
- const rules = data.rules.map(function lowerMainRule(rule) {
2368
- return lowerRule(ctx, rule)
2369
- })
2370
- if (data.kind === "cq") {
2371
- return parseQueryIr({ kind: "cq", interiors, head, rules })
2372
- }
2373
- return parseQueryIr({
2374
- kind: "reach",
2375
- interiors,
2376
- rec: {
2377
- head: data.rec.finds.map(headTermOf),
2378
- base: data.rec.base.map(function lowerBase(rule) {
2379
- return lowerRule(ctx, rule)
2380
- }),
2381
- rec: data.rec.rec.map(function lowerRecArm(rule) {
2382
- return lowerRule(ctx, rule)
2383
- })
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
+ }
2384
2263
  },
2385
- head,
2386
- rules
2387
- })
2264
+ labels
2265
+ }
2388
2266
  }
2389
2267
 
2390
2268
  export type {
@@ -2405,4 +2283,4 @@ export type {
2405
2283
  RuleValue,
2406
2284
  TermOps
2407
2285
  }
2408
- export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, taggedCmpLiteral }
2286
+ export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, queryHandleOf, taggedCmpLiteral }