@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,16 +1,18 @@
1
- import { membersAgree, sealedFieldsOf } from "#closed.ts";
2
- import { AuthoringError, SdkInvariantError } from "#errors.ts";
3
- import { assertDeclarationOrderKey, f64 as f64Field, i64 as i64Field, isFloatIntervalValue, isIntervalValue, literalShapeError, rosterOf, rostersAgree, u64 as u64Field } from "#fields.ts";
4
- import { snapshotData } from "#immutable.ts";
5
- import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts";
6
- import { computeFieldOf, computeVarsOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "#query/compute.ts";
7
- import { count, max, mean, min, pack, sum } from "#query/find.ts";
8
- import { parseQueryIr } from "#query/parse-ir.ts";
9
- import { fieldAntiJoins, fieldJoins, headFieldJoins, importFacadeOf, importFacadeOfOwner, inferred, isImportedSource, isTerm, labelImport, makeParam, makeSetParam, renderFieldKind, term } from "#query/scope.ts";
10
- import { difference, intersection, isSegments, segmentField } from "#query/segments.ts";
11
- import { scalarWire } from "#scalar.ts";
12
- import { schemaDescriptor, schemasAgree } from "#schema.ts";
13
- import { arrayValue, recordValue, taggedValueOf } from "#values.ts";
1
+ import { membersAgree, sealedFieldsOf } from "../closed.js";
2
+ import { compiledOf } from "../compile.js";
3
+ import { AuthoringError, internalError } from "../errors.js";
4
+ import { assertDeclarationOrderKey, f64 as f64Field, i64 as i64Field, isFloatIntervalValue, isIntervalValue, literalShapeError, rosterOf, rostersAgree, u64 as u64Field } from "../fields.js";
5
+ import { snapshotData } from "../immutable.js";
6
+ import { addon } from "../native/addon.js";
7
+ import { valueIn } from "../native/json.js";
8
+ import { scalarWire } from "../scalar.js";
9
+ import { schemaDescriptor, schemasAgree } from "../schema.js";
10
+ import { arrayValue, recordValue, taggedValueOf } from "../values.js";
11
+ import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "./atom.js";
12
+ import { computeFieldOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "./compute.js";
13
+ import { count, max, mean, min, pack, sum } from "./find.js";
14
+ import { fieldAntiJoins, fieldJoins, headFieldJoins, importFacadeOf, importFacadeOfOwner, inferred, isImportedSource, isTerm, labelImport, makeParam, makeSetParam, renderFieldKind, term } from "./scope.js";
15
+ import { difference, intersection, isSegments, segmentField } from "./segments.js";
14
16
  const termOps = Object.freeze({
15
17
  param: makeParam,
16
18
  inSet: makeSetParam,
@@ -384,45 +386,6 @@ function findColumnOf(name, entry) {
384
386
  message: `find ${name}: not a find entry — find takes variables, aggregates or Compute expressions`
385
387
  });
386
388
  }
387
- /**
388
- * The orderable ban's pointed refusal
389
- * § orderability): a closed reference is equality-and-membership only.
390
- */
391
- function closedOrderError(context, position, vocabulary) {
392
- return new AuthoringError({
393
- message: `${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
394
- });
395
- }
396
- function isOrderOp(op) {
397
- return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn";
398
- }
399
- function assertBound(where, bound, ref) {
400
- if (!bound.has(ref)) {
401
- throw new AuthoringError({
402
- message: `${where}: the variable ${ref.label} is not bound by a relation atom of the rule`
403
- });
404
- }
405
- }
406
- function assertInterval(where, ref) {
407
- if (ref.field.kind !== "interval") {
408
- throw new AuthoringError({
409
- message: `${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
410
- });
411
- }
412
- }
413
- function assertNotClosed(where, position, ref) {
414
- const roster = rosterOf(ref.field);
415
- if (roster !== undefined) {
416
- throw closedOrderError(where, `${position} ${ref.label}`, roster.name);
417
- }
418
- }
419
- function assertNumeric(where, position, ref) {
420
- if (ref.field.kind !== "u64" && ref.field.kind !== "i64" && ref.field.kind !== "f64") {
421
- throw new AuthoringError({
422
- message: `${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64/f64 only`
423
- });
424
- }
425
- }
426
389
  /**
427
390
  * The head column's slot: a projected variable keeps its mint slot; an
428
391
  * aggregate output is a NEW derived scalar — its descriptor follows the
@@ -457,76 +420,24 @@ function findColumnSlotOf(context, column) {
457
420
  };
458
421
  }
459
422
  }
460
- function validateColumn(context, bound, column) {
461
- const where = `${contextLabel(context)} find ${column.name}`;
462
- const entry = column.entry;
463
- if (entry.kind === "segments") {
464
- assertBound(where, bound, entry.left);
465
- assertBound(where, bound, entry.right);
466
- return;
467
- }
468
- if (entry.kind === "var") {
469
- assertBound(where, bound, entry.over);
470
- return;
471
- }
472
- if (entry.kind === "compute") {
473
- // Kind agreement, closed-reference and depth walls hold at
474
- // construction (#query/compute.ts); boundness is a RULE property,
475
- // judged here like every other term position.
476
- for (const ref of computeVarsOf(entry.expr)) {
477
- assertBound(where, bound, ref);
478
- }
423
+ /** An equality between two variables joins them, so their slots must join under the class law. */
424
+ function validateCond(context, cond) {
425
+ if (cond.kind !== "cmp") {
426
+ for (const child of cond.children)
427
+ validateCond(context, child);
479
428
  return;
480
429
  }
481
- const agg = entry.agg;
482
- switch (agg.op) {
483
- case "count":
484
- return;
485
- case "fold": {
486
- assertBound(where, bound, agg.over);
487
- assertNotClosed(where, `the ${agg.fold} input`, agg.over);
488
- assertNumeric(where, `the ${agg.fold} input`, agg.over);
489
- if (agg.fold === "mean" && agg.over.field.kind !== "f64") {
490
- throw new AuthoringError({ message: `${where}: mean requires an f64 input; cast integers explicitly` });
491
- }
492
- return;
493
- }
494
- case "pack":
495
- assertBound(where, bound, agg.over);
496
- assertInterval(where, agg.over);
497
- return;
498
- }
499
- }
500
- function validateCond(context, bound, cond) {
501
- const label = contextLabel(context);
502
- if (cond.kind === "cmp") {
503
- for (const side of [cond.lhs, cond.rhs]) {
504
- if (side.kind === "var") {
505
- assertBound(label, bound, side.ref);
506
- const roster = rosterOf(side.ref.field);
507
- if (isOrderOp(cond.op.kind) && roster !== undefined) {
508
- throw closedOrderError(label, `the ${cond.op.kind} side ${side.ref.label}`, roster.name);
509
- }
510
- }
511
- }
512
- if ((cond.op.kind === "eq" || cond.op.kind === "ne") && cond.lhs.kind === "var" && cond.rhs.kind === "var") {
513
- assertBound(label, bound, cond.lhs.ref);
514
- assertBound(label, bound, cond.rhs.ref);
515
- const lhs = mintSlotOf(context, cond.lhs.ref);
516
- const rhs = mintSlotOf(context, cond.rhs.ref);
517
- if (!fieldJoins(lhs, rhs)) {
518
- throw new AuthoringError({
519
- 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)`
520
- });
521
- }
522
- }
430
+ if ((cond.op.kind !== "eq" && cond.op.kind !== "ne") || cond.lhs.kind !== "var" || cond.rhs.kind !== "var")
523
431
  return;
524
- }
525
- for (const child of cond.children) {
526
- validateCond(context, bound, child);
432
+ const lhs = mintSlotOf(context, cond.lhs.ref);
433
+ const rhs = mintSlotOf(context, cond.rhs.ref);
434
+ if (!fieldJoins(lhs, rhs)) {
435
+ throw new AuthoringError({
436
+ 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)`
437
+ });
527
438
  }
528
439
  }
529
- function validateInterior(context, bound, item, columns) {
440
+ function validateInterior(context, item, columns) {
530
441
  const label = contextLabel(context);
531
442
  const headColumns = item.target.finds.length > 0 ? item.target.finds : columns;
532
443
  const headNames = headColumns.map(function nameOf(column) {
@@ -550,11 +461,6 @@ function validateInterior(context, bound, item, columns) {
550
461
  }
551
462
  }
552
463
  for (const binding of item.bindings) {
553
- if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
554
- throw new AuthoringError({
555
- 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)`
556
- });
557
- }
558
464
  const headColumn = headColumns.find(function byName(column) {
559
465
  return column.name === binding.key;
560
466
  });
@@ -571,39 +477,16 @@ function validateInterior(context, bound, item, columns) {
571
477
  }
572
478
  function completeRule(context, state, rawColumns) {
573
479
  const label = contextLabel(context);
574
- if (rawColumns.length === 0) {
575
- throw new AuthoringError({ message: `${label}: a find needs at least one entry` });
576
- }
577
- const aggregates = rawColumns.flatMap((column) => (column.entry.kind === "aggregate" ? [column.entry.agg] : []));
578
- const packs = aggregates.filter((agg) => agg.op === "pack").length;
579
- if (packs > 1)
580
- throw new AuthoringError({ message: "a query stage can pack one interval column" });
581
- if (packs !== 0 && aggregates.length !== packs)
582
- throw new AuthoringError({ message: "pack intervals and compute numeric aggregates in separate query stages" });
583
- if (rawColumns.some((c) => c.entry.kind === "segments") && aggregates.length !== 0)
584
- throw new AuthoringError({ message: "aggregate generated segments in a following query stage" });
585
480
  const columns = rawColumns.map(function enrichColumn(column) {
586
481
  assertDeclarationOrderKey(`${label} find column`, column.name);
587
- validateColumn(context, state.bound, column);
588
482
  const slot = findColumnSlotOf(context, column);
589
483
  return Object.freeze({ name: column.name, entry: column.entry, slot, closed: rosterOf(slot?.field) });
590
484
  });
591
485
  for (const item of state.items) {
592
- if (item.kind === "negated") {
593
- for (const binding of item.atom.bindings) {
594
- if (binding.term.kind === "var" && !state.bound.has(binding.term.ref)) {
595
- throw new AuthoringError({
596
- 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)`
597
- });
598
- }
599
- }
600
- }
601
- if (item.kind === "interior" || item.kind === "negatedInterior") {
602
- validateInterior(context, state.bound, item, columns);
603
- }
604
- if (item.kind === "cond") {
605
- validateCond(context, state.bound, item.cond);
606
- }
486
+ if (item.kind === "interior" || item.kind === "negatedInterior")
487
+ validateInterior(context, item, columns);
488
+ if (item.kind === "cond")
489
+ validateCond(context, item.cond);
607
490
  }
608
491
  return Object.freeze({ items: state.items, finds: Object.freeze(columns), paramUses: state.paramUses });
609
492
  }
@@ -693,7 +576,7 @@ function importEntryOf(context, imported) {
693
576
  const data = imported.data;
694
577
  if (data.kind === "reach") {
695
578
  throw new AuthoringError({
696
- message: `${contextLabel(context)}: a recursive query is not importable as a relation expression yet — declare the recursion on the consuming query (C05 boundary, recorded)`
579
+ message: `${contextLabel(context)}: a recursive query is not importable as a relation expression — declare the recursion on the consuming query`
697
580
  });
698
581
  }
699
582
  const userParams = data.params.filter(function userSupplied(entry) {
@@ -808,8 +691,7 @@ function findColumns(context, entries) {
808
691
  const columns = [];
809
692
  queryRecord("query find", entries);
810
693
  // Nonrecursive derived stages (interiors) may emit aggregate/computed
811
- // outputs (C05: "do not leave Interior.rules projection only"); ONLY the
812
- // recursive feedback cycle stays projection-only — no aggregate,
694
+ // outputs; ONLY the recursive feedback cycle stays projection-only — no aggregate,
813
695
  // arithmetic-created value or negation flows through it.
814
696
  const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined;
815
697
  for (const [name, entry] of Object.entries(entries)) {
@@ -874,14 +756,14 @@ function isTypedScope(scope) {
874
756
  function makeQueryRuleScope(theory, env) {
875
757
  const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env });
876
758
  if (!isTypedScope(raw)) {
877
- throw new SdkInvariantError({ message: "query rule builder construction incomplete" });
759
+ throw internalError("query rule builder construction incomplete");
878
760
  }
879
761
  return raw;
880
762
  }
881
763
  function makeInteriorRuleScope(theory, env, self) {
882
764
  const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env });
883
765
  if (!isTypedScope(raw)) {
884
- throw new SdkInvariantError({ message: "interior rule builder construction incomplete" });
766
+ throw internalError("interior rule builder construction incomplete");
885
767
  }
886
768
  return raw;
887
769
  }
@@ -902,7 +784,7 @@ function makeRecRuleScope(theory, env, self, kind) {
902
784
  ...env
903
785
  });
904
786
  if (!isTypedScope(raw)) {
905
- throw new SdkInvariantError({ message: "rec rule builder construction incomplete" });
787
+ throw internalError("rec rule builder construction incomplete");
906
788
  }
907
789
  return raw;
908
790
  }
@@ -910,22 +792,19 @@ function renderClosedSlice(closed) {
910
792
  return closed === undefined ? "a bare value" : `a ${closed.name} reference`;
911
793
  }
912
794
  function headOperation(column) {
913
- return column.entry.kind === "aggregate" ? headOpOf(column.entry.agg) : undefined;
795
+ if (column.entry.kind !== "aggregate")
796
+ return undefined;
797
+ const agg = column.entry.agg;
798
+ return agg.op === "fold" ? agg.fold : agg.op;
914
799
  }
915
800
  function renderParamAnchor(roster) {
916
801
  return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`;
917
802
  }
918
803
  /**
919
- * Folds every rule's param uses (interiors in declaration order, then rec
920
- * base, then rec arms, then main — exactly the lowering walk) into the
921
- * query's registry: first use mints the dense `ParamId`, the first
922
- * FIELD-ANCHORED use types the wire, and one name keeps one shape AND one
923
- * closedness. The orderable ban needs no registry arm: an order use always
924
- * anchors its SIBLING's domain (the no-variable-side spelling is refused
925
- * at the comparison constructor), so a closed-anchored param under an
926
- * order op dies at the one-domain wall here — and an order use whose
927
- * sibling is itself closed-bound dies at the comparison's own var-side
928
- * wall first.
804
+ * Folds every rule's param uses, in lowering order, into the query's registry: the first use mints
805
+ * the dense `ParamId`, the first field-anchored use types it, and one name keeps one shape and one
806
+ * closedness. An order use always anchors its sibling's domain, so a closed-anchored param under an
807
+ * order op fails the one-domain check here.
929
808
  */
930
809
  function paramRegistryOf(interiors, rec, rules) {
931
810
  const order = [];
@@ -1089,6 +968,49 @@ function afterMainError(what) {
1089
968
  message: `query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`
1090
969
  });
1091
970
  }
971
+ /** Names the parts of `data` an engine query diagnostic cites. */
972
+ function explainQuery(data, labels, diagnostic) {
973
+ const tables = [...data.interiors, ...(data.kind === "reach" ? [data.rec] : [])];
974
+ const table = diagnostic.interior === undefined ? undefined : tables[diagnostic.interior];
975
+ const ruleLabels = diagnostic.rule === undefined
976
+ ? undefined
977
+ : (diagnostic.interior === undefined ? labels.main : labels.interiors[diagnostic.interior])?.[diagnostic.rule];
978
+ const finds = table?.finds ?? data.finds;
979
+ const cited = [
980
+ table === undefined ? undefined : `in ${table.name}`,
981
+ diagnostic.rule === undefined ? undefined : `rule ${diagnostic.rule}`,
982
+ diagnostic.find === undefined ? undefined : `find ${finds[diagnostic.find]?.name ?? diagnostic.find}`,
983
+ diagnostic.atom === undefined ? undefined : `atom ${diagnostic.atom}`,
984
+ diagnostic.var === undefined ? undefined : `variable ${ruleLabels?.[diagnostic.var] ?? diagnostic.var}`,
985
+ diagnostic.param === undefined ? undefined : `param ${data.params[diagnostic.param]?.name ?? diagnostic.param}`,
986
+ diagnostic.comparison === undefined ? undefined : `comparison ${diagnostic.comparison}`
987
+ ].filter((part) => part !== undefined);
988
+ return cited.length === 0 ? diagnostic.message : `${diagnostic.message} (${cited.join(", ")})`;
989
+ }
990
+ /** Validates a complete query with the engine and returns its compiled handle. */
991
+ function validateQueryData(theory, data) {
992
+ const lowered = lowerQueryLabelled({ schema: theory, data });
993
+ const validated = addon.validateQuery(compiledOf(schemaDescriptor(theory)).handle, JSON.stringify(lowered.query));
994
+ switch (validated._tag) {
995
+ case "Valid":
996
+ return validated.query;
997
+ case "Invalid":
998
+ throw new AuthoringError({
999
+ message: `query: ${explainQuery(data, lowered.labels, validated.diagnostic)}`,
1000
+ diagnostic: { code: "InvalidQuery", context: validated.diagnostic.code, expected: validated.diagnostic.message }
1001
+ });
1002
+ case "Malformed":
1003
+ throw internalError(`validateQuery: ${validated.path}: ${validated.message}`);
1004
+ }
1005
+ }
1006
+ const validatedQueries = new WeakMap();
1007
+ /** The engine's validated form of a query value `query()` built. */
1008
+ function queryHandleOf(query) {
1009
+ const handle = validatedQueries.get(query);
1010
+ if (handle === undefined)
1011
+ throw internalError("queryHandleOf: the query was not built by query()");
1012
+ return handle;
1013
+ }
1092
1014
  function makeRawQuery(inputTheory, inputInteriors, inputRec, inputRules) {
1093
1015
  const { theory, interiors, rec, rules } = snapshotQueryData({
1094
1016
  theory: inputTheory,
@@ -1122,6 +1044,7 @@ function makeRawQuery(inputTheory, inputInteriors, inputRec, inputRules) {
1122
1044
  finds: mergedFinds,
1123
1045
  params
1124
1046
  });
1047
+ const handle = validateQueryData(theory, data);
1125
1048
  const value = {
1126
1049
  get schema() {
1127
1050
  return snapshotData(theory);
@@ -1147,6 +1070,7 @@ function makeRawQuery(inputTheory, inputInteriors, inputRec, inputRules) {
1147
1070
  }
1148
1071
  };
1149
1072
  Object.freeze(value);
1073
+ validatedQueries.set(value, handle);
1150
1074
  return value;
1151
1075
  }
1152
1076
  function makeQuery(theory, interiors, rec, rules) {
@@ -1317,6 +1241,9 @@ function freshVarIds() {
1317
1241
  const id = assigned.size;
1318
1242
  assigned.set(ref, id);
1319
1243
  return id;
1244
+ },
1245
+ labels() {
1246
+ return [...assigned.keys()].map((ref) => ref.label);
1320
1247
  }
1321
1248
  };
1322
1249
  }
@@ -1328,238 +1255,203 @@ function paramIdOf(ctx, name) {
1328
1255
  return id;
1329
1256
  }
1330
1257
  function lowerAtom(ctx, atom, ids) {
1331
- const member = ctx.theory.relations[atom.relation.name];
1332
- if (!membersAgree(member, atom.relation)) {
1258
+ const relationId = ctx.relationIds.get(atom.relation.name);
1259
+ if (!membersAgree(ctx.theory.relations[atom.relation.name], atom.relation) || relationId === undefined) {
1333
1260
  throw new AuthoringError({
1334
- message: `query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
1261
+ message: `query: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
1335
1262
  });
1336
1263
  }
1337
- const relationId = ctx.relationIds.get(atom.relation.name);
1338
- if (relationId === undefined) {
1339
- throw new AuthoringError({ message: `query lowering: relation ${atom.relation.name} has no ordinal` });
1340
- }
1341
1264
  const ordered = sealedFieldsOf(atom.relation);
1342
- const bindings = atom.bindings.map(function lowerBinding(binding) {
1343
- const ordinal = ordered.findIndex(function byName(candidate) {
1344
- return candidate.name === binding.field;
1345
- });
1346
- if (ordinal < 0) {
1347
- throw new AuthoringError({
1348
- message: `query lowering: relation ${atom.relation.name} has no field ${binding.field}`
1349
- });
1350
- }
1351
- return [ordinal, lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids)];
1265
+ const bindings = atom.bindings.map((binding) => {
1266
+ const field = ordered.findIndex((candidate) => candidate.name === binding.field);
1267
+ if (field < 0)
1268
+ throw new AuthoringError({ message: `query: relation ${atom.relation.name} has no field ${binding.field}` });
1269
+ return { field, term: lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids) };
1352
1270
  });
1353
- return { source: { kind: "edb", relation: relationId }, bindings };
1271
+ return { source: { kind: "Edb", relation: relationId }, bindings };
1354
1272
  }
1355
1273
  function lowerBindingTerm(ctx, context, binding, ids) {
1356
1274
  const bound = binding.term;
1357
1275
  switch (bound.kind) {
1358
1276
  case "var":
1359
- return { kind: "var", var: ids.of(bound.ref) };
1277
+ return { kind: "Var", var: ids.of(bound.ref) };
1360
1278
  case "param":
1361
- return { kind: "param", param: paramIdOf(ctx, bound.name) };
1279
+ return { kind: "Param", param: paramIdOf(ctx, bound.name) };
1362
1280
  case "setParam":
1363
- return { kind: "paramSet", param: paramIdOf(ctx, bound.name) };
1364
1281
  case "literalSet":
1365
- return { kind: "paramSet", param: paramIdOf(ctx, bound.name) };
1282
+ return { kind: "ParamSet", param: paramIdOf(ctx, bound.name) };
1366
1283
  case "literal":
1367
- return { kind: "literal", value: taggedLiteral(context, binding.data, bound.value) };
1284
+ return { kind: "Literal", value: valueIn(taggedLiteral(context, binding.data, bound.value)) };
1368
1285
  }
1369
1286
  }
1370
- /**
1371
- * Lowers one interior atom: named bindings placed by HEAD order, `FieldId(i)` =
1372
- * head position i. Every head column of the table must be bound (a missing key
1373
- * is refused pointed); the var-id assignment order is head order, so the
1374
- * first-use numbering matches the name-keyed edition exactly.
1375
- */
1287
+ /** An interior atom binds every head column of the table, by head position. */
1376
1288
  function lowerInteriorAtom(ctx, target, bindings, ids) {
1377
1289
  const interior = ctx.interiorIds.get(target.name);
1378
1290
  if (interior === undefined) {
1379
- throw new AuthoringError({ message: `query lowering: derived table ${target.name} was not declared on this query` });
1380
- }
1381
- if (target.finds.length === 0) {
1382
- throw new AuthoringError({ message: `query lowering: derived table ${target.name} has no head` });
1291
+ throw new AuthoringError({ message: `query: derived table ${target.name} was not declared on this query` });
1383
1292
  }
1384
- const irBindings = target.finds.map(function lowerPosition(column, position) {
1385
- const binding = bindings.find(function byKey(candidate) {
1386
- return candidate.key === column.name;
1387
- });
1388
- if (binding === undefined) {
1389
- throw new AuthoringError({ message: `query lowering: interior ${target.name} omits head column ${column.name}` });
1390
- }
1391
- return [position, { kind: "var", var: ids.of(binding.ref) }];
1392
- });
1393
- return { source: { kind: "interior", interior }, bindings: irBindings };
1293
+ return {
1294
+ source: { kind: "Interior", interior },
1295
+ bindings: target.finds.map((column, field) => {
1296
+ const binding = bindings.find((candidate) => candidate.key === column.name);
1297
+ if (binding === undefined) {
1298
+ throw new AuthoringError({ message: `query: interior ${target.name} omits head column ${column.name}` });
1299
+ }
1300
+ return { field, term: { kind: "Var", var: ids.of(binding.ref) } };
1301
+ })
1302
+ };
1394
1303
  }
1395
1304
  function lowerCmpTerm(ctx, side, sibling, ids, op) {
1396
1305
  switch (side.kind) {
1397
1306
  case "var":
1398
- return { kind: "var", var: ids.of(side.ref) };
1307
+ return { kind: "Var", var: ids.of(side.ref) };
1399
1308
  case "param":
1400
- return { kind: "param", param: paramIdOf(ctx, side.name) };
1309
+ return { kind: "Param", param: paramIdOf(ctx, side.name) };
1401
1310
  case "setParam":
1402
- return { kind: "paramSet", param: paramIdOf(ctx, side.name) };
1311
+ return { kind: "ParamSet", param: paramIdOf(ctx, side.name) };
1403
1312
  case "literal": {
1404
1313
  const anchor = cmpAnchorOf(ctx, sibling);
1405
1314
  if (anchor === undefined) {
1406
1315
  throw new AuthoringError({
1407
- message: "query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it"
1316
+ message: "query: a comparison literal needs a bound-variable or anchored-param sibling to type it"
1408
1317
  });
1409
1318
  }
1410
- return { kind: "literal", value: taggedCmpLiteral("comparison literal", anchor, side.value, op) };
1319
+ return { kind: "Literal", value: valueIn(taggedCmpLiteral("comparison literal", anchor, side.value, op)) };
1411
1320
  }
1412
1321
  }
1413
1322
  }
1414
1323
  function cmpAnchorOf(ctx, sibling) {
1415
- if (sibling.kind === "var") {
1324
+ if (sibling.kind === "var")
1416
1325
  return sibling.ref.field;
1417
- }
1418
- if (sibling.kind === "param" || sibling.kind === "setParam") {
1326
+ if (sibling.kind === "param" || sibling.kind === "setParam")
1419
1327
  return ctx.params.get(sibling.name)?.anchor;
1420
- }
1421
1328
  return undefined;
1422
1329
  }
1423
- function lowerComparison(ctx, cmp, ids) {
1424
- if (cmp.op.kind === "allen") {
1330
+ const CMP = { eq: "Eq", ne: "Ne", lt: "Lt", le: "Le", gt: "Gt", ge: "Ge", pointIn: "PointIn" };
1331
+ function lowerCondition(ctx, cond, ids) {
1332
+ if (cond.kind !== "cmp") {
1425
1333
  return {
1426
- op: { kind: "allen", mask: cmp.op.mask },
1427
- lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, "allen"),
1428
- rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, "allen")
1334
+ kind: cond.op === "and" ? "And" : "Or",
1335
+ children: cond.children.map((child) => lowerCondition(ctx, child, ids))
1429
1336
  };
1430
1337
  }
1338
+ const kind = cond.op.kind;
1431
1339
  return {
1432
- op: { kind: cmp.op.kind },
1433
- lhs: lowerCmpTerm(ctx, cmp.lhs, cmp.rhs, ids, cmp.op.kind),
1434
- rhs: lowerCmpTerm(ctx, cmp.rhs, cmp.lhs, ids, cmp.op.kind)
1435
- };
1436
- }
1437
- function lowerCondition(ctx, cond, ids) {
1438
- if (cond.kind === "cmp") {
1439
- return { kind: "leaf", cmp: lowerComparison(ctx, cond, ids) };
1440
- }
1441
- return {
1442
- kind: cond.op,
1443
- children: cond.children.map(function lowerChild(child) {
1444
- return lowerCondition(ctx, child, ids);
1445
- })
1340
+ kind: "Leaf",
1341
+ op: cond.op.kind === "allen" ? { kind: "Allen", mask: cond.op.mask } : { kind: CMP[cond.op.kind] },
1342
+ lhs: lowerCmpTerm(ctx, cond.lhs, cond.rhs, ids, kind),
1343
+ rhs: lowerCmpTerm(ctx, cond.rhs, cond.lhs, ids, kind)
1446
1344
  };
1447
1345
  }
1448
- /**
1449
- * Lowers one computed scalar node to the C05 wire (`#native.ts`'s
1450
- * `ScalarExprIr`): the shared grammar arms pass through 1:1. Cached
1451
- * construction depth is the bound; this walk emits wire only and does
1452
- * not re-judge kinds (L14/native compile owns binding).
1453
- */
1454
- function lowerComputeNode(node, ids) {
1455
- if (node.depth > MAX_COMPUTE_DEPTH) {
1456
- throw new AuthoringError({
1457
- message: `query lowering: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes (the engine's scalar depth bound)`
1458
- });
1459
- }
1460
- return lowerComputeGrammar(node, ids);
1461
- }
1462
- function lowerComputeGrammar(node, ids) {
1463
- return scalarWire(node, (leaf) => {
1464
- return ids.of(leaf.ref);
1465
- });
1466
- }
1346
+ const FOLD = { sum: "Sum", mean: "Mean", min: "Min", max: "Max" };
1467
1347
  function lowerFind(entry, ids) {
1468
- if (entry.kind === "segments")
1469
- return { kind: entry.kind, op: entry.op, left: ids.of(entry.left), right: ids.of(entry.right) };
1470
- if (entry.kind === "var") {
1471
- return { kind: "var", var: ids.of(entry.over) };
1472
- }
1473
- if (entry.kind === "compute") {
1474
- return { kind: "compute", expr: lowerComputeNode(entry.expr, ids) };
1348
+ switch (entry.kind) {
1349
+ case "segments":
1350
+ return {
1351
+ kind: "Segments",
1352
+ op: entry.op === "intersection" ? "Intersection" : "Difference",
1353
+ left: ids.of(entry.left),
1354
+ right: ids.of(entry.right)
1355
+ };
1356
+ case "var":
1357
+ return { kind: "Var", var: ids.of(entry.over) };
1358
+ case "compute":
1359
+ if (entry.expr.depth > MAX_COMPUTE_DEPTH) {
1360
+ throw new AuthoringError({
1361
+ message: `query: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes`
1362
+ });
1363
+ }
1364
+ return { kind: "Compute", expr: scalarWire(entry.expr, (leaf) => ids.of(leaf.ref)) };
1475
1365
  }
1476
1366
  const agg = entry.agg;
1477
1367
  switch (agg.op) {
1478
1368
  case "count":
1479
- return { kind: "count" };
1369
+ return { kind: "Count" };
1480
1370
  case "fold":
1481
- return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) };
1371
+ return { kind: "Aggregate", op: FOLD[agg.fold], over: ids.of(agg.over) };
1482
1372
  case "pack":
1483
- return { kind: "pack", over: ids.of(agg.over) };
1484
- }
1485
- }
1486
- function headOpOf(agg) {
1487
- switch (agg.op) {
1488
- case "count":
1489
- return "count";
1490
- case "fold":
1491
- return agg.fold;
1492
- case "pack":
1493
- return "pack";
1373
+ return { kind: "Pack", over: ids.of(agg.over) };
1494
1374
  }
1495
1375
  }
1496
1376
  function headTermOf(column) {
1497
1377
  const entry = column.entry;
1498
- if (entry.kind === "segments")
1499
- return { kind: "compute" };
1500
- if (entry.kind === "var") {
1501
- return { kind: "var" };
1502
- }
1503
- if (entry.kind === "compute") {
1504
- return { kind: "compute" };
1505
- }
1506
- return { kind: "aggregate", op: headOpOf(entry.agg) };
1378
+ if (entry.kind === "segments" || entry.kind === "compute")
1379
+ return { kind: "Compute" };
1380
+ if (entry.kind === "var")
1381
+ return { kind: "Var" };
1382
+ const agg = entry.agg;
1383
+ if (agg.op === "count")
1384
+ return { kind: "Aggregate", op: "Count" };
1385
+ if (agg.op === "pack")
1386
+ return { kind: "Aggregate", op: "Pack" };
1387
+ return { kind: "Aggregate", op: FOLD[agg.fold] };
1507
1388
  }
1508
- function lowerRule(ctx, rule) {
1389
+ function lowerRule(ctx, rule, labels) {
1509
1390
  const ids = freshVarIds();
1510
1391
  const atoms = [];
1511
1392
  const negated = [];
1512
1393
  const conditions = [];
1513
1394
  for (const item of rule.items) {
1514
1395
  switch (item.kind) {
1515
- case "atom": {
1396
+ case "atom":
1516
1397
  atoms.push(lowerAtom(ctx, item.atom, ids));
1517
1398
  break;
1518
- }
1519
- case "negated": {
1399
+ case "negated":
1520
1400
  negated.push(lowerAtom(ctx, item.atom, ids));
1521
1401
  break;
1522
- }
1523
- case "interior": {
1402
+ case "interior":
1524
1403
  atoms.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids));
1525
1404
  break;
1526
- }
1527
- case "negatedInterior": {
1405
+ case "negatedInterior":
1528
1406
  negated.push(lowerInteriorAtom(ctx, item.target, item.bindings, ids));
1529
1407
  break;
1530
- }
1531
- case "cond": {
1408
+ case "cond":
1532
1409
  conditions.push(lowerCondition(ctx, item.cond, ids));
1533
1410
  break;
1534
- }
1535
1411
  }
1536
1412
  }
1413
+ const finds = rule.finds.map((column) => lowerFind(column.entry, ids));
1414
+ labels?.push(ids.labels());
1415
+ return { finds, atoms, negated, conditions };
1416
+ }
1417
+ /** A recursive rule projects variables only and negates nothing. */
1418
+ function recFinds(label, rule) {
1419
+ if (rule.negated.length > 0)
1420
+ throw new AuthoringError({ message: `query ${label}: a recursive rule cannot negate` });
1421
+ return rule.finds.map((find) => {
1422
+ if (find.kind !== "Var")
1423
+ throw new AuthoringError({ message: `query ${label}: a recursive rule finds variables only` });
1424
+ return find.var;
1425
+ });
1426
+ }
1427
+ /** A recursive step reads its own previous output through exactly one self-atom. */
1428
+ function recStep(label, rule, self) {
1429
+ const selfAtoms = rule.atoms.filter((atom) => atom.source.kind === "Interior" && atom.source.interior === self);
1430
+ const [selfAtom] = selfAtoms;
1431
+ if (selfAtom === undefined || selfAtoms.length > 1) {
1432
+ throw new AuthoringError({ message: `query ${label}: a recursive step reads the recursion exactly once` });
1433
+ }
1537
1434
  return {
1538
- finds: rule.finds.map(function findOf(column) {
1539
- return lowerFind(column.entry, ids);
1540
- }),
1541
- atoms,
1542
- negated,
1543
- conditions
1435
+ finds: recFinds(label, rule),
1436
+ selfBindings: selfAtom.bindings,
1437
+ atoms: rule.atoms.filter((atom) => atom !== selfAtom),
1438
+ conditions: rule.conditions
1544
1439
  };
1545
1440
  }
1441
+ /** Lowers a query to the addon's `QueryIn`. The engine validates it; lowering judges nothing. */
1546
1442
  function lowerQuery(q) {
1443
+ return lowerQueryLabelled(q).query;
1444
+ }
1445
+ function lowerQueryLabelled(q) {
1547
1446
  const theory = q.schema;
1548
1447
  const data = q.data;
1549
- const relationIds = new Map();
1550
- Object.keys(theory.relations).forEach(function assignOrdinal(name, index) {
1551
- relationIds.set(name, index);
1552
- });
1553
- const interiorIds = new Map();
1554
- data.interiors.forEach(function assignInteriorId(interior, index) {
1555
- interiorIds.set(interior.name, index);
1556
- });
1557
- if (data.kind === "reach") {
1448
+ const relationIds = new Map(Object.keys(theory.relations).map((name, index) => [name, index]));
1449
+ const interiorIds = new Map(data.interiors.map((interior, index) => [interior.name, index]));
1450
+ if (data.kind === "reach")
1558
1451
  interiorIds.set(data.rec.name, data.interiors.length);
1559
- }
1560
1452
  const paramIds = new Map();
1561
1453
  const params = new Map();
1562
- data.params.forEach(function assignParamId(entry, index) {
1454
+ data.params.forEach((entry, index) => {
1563
1455
  if (entry.anchor === undefined) {
1564
1456
  throw new AuthoringError({
1565
1457
  message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
@@ -1569,36 +1461,31 @@ function lowerQuery(q) {
1569
1461
  params.set(entry.name, entry);
1570
1462
  });
1571
1463
  const ctx = { theory, relationIds, interiorIds, paramIds, params };
1572
- const interiors = data.interiors.map(function lowerInterior(interior) {
1573
- return {
1574
- head: interior.finds.map(headTermOf),
1575
- rules: interior.rules.map(function lowerInteriorRule(rule) {
1576
- return lowerRule(ctx, rule);
1577
- })
1578
- };
1579
- });
1580
- const head = data.finds.map(headTermOf);
1581
- const rules = data.rules.map(function lowerMainRule(rule) {
1582
- return lowerRule(ctx, rule);
1583
- });
1584
- if (data.kind === "cq") {
1585
- return parseQueryIr({ kind: "cq", interiors, head, rules });
1586
- }
1587
- return parseQueryIr({
1588
- kind: "reach",
1589
- interiors,
1590
- rec: {
1591
- head: data.rec.finds.map(headTermOf),
1592
- base: data.rec.base.map(function lowerBase(rule) {
1593
- return lowerRule(ctx, rule);
1594
- }),
1595
- rec: data.rec.rec.map(function lowerRecArm(rule) {
1596
- return lowerRule(ctx, rule);
1597
- })
1464
+ const labels = { main: [], interiors: data.interiors.map(() => []) };
1465
+ const query = {
1466
+ interiors: data.interiors.map((interior, index) => ({
1467
+ rules: interior.rules.map((rule) => lowerRule(ctx, rule, labels.interiors[index]))
1468
+ })),
1469
+ head: data.finds.map(headTermOf),
1470
+ rules: data.rules.map((rule) => lowerRule(ctx, rule, labels.main))
1471
+ };
1472
+ if (data.kind === "cq")
1473
+ return { query, labels };
1474
+ const label = `recursion ${data.rec.name}`;
1475
+ const self = data.interiors.length;
1476
+ return {
1477
+ query: {
1478
+ ...query,
1479
+ rec: {
1480
+ base: data.rec.base.map((rule) => {
1481
+ const lowered = lowerRule(ctx, rule);
1482
+ return { finds: recFinds(label, lowered), atoms: lowered.atoms, conditions: lowered.conditions };
1483
+ }),
1484
+ rec: data.rec.rec.map((rule) => recStep(label, lowerRule(ctx, rule), self))
1485
+ }
1598
1486
  },
1599
- head,
1600
- rules
1601
- });
1487
+ labels
1488
+ };
1602
1489
  }
1603
- export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, taggedCmpLiteral };
1490
+ export { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, query, queryHandleOf, taggedCmpLiteral };
1604
1491
  //# sourceMappingURL=lower.js.map