@bjornpagen/bumbledb 0.1.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 (108) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +84 -0
  3. package/dist/brand.d.ts +59 -0
  4. package/dist/brand.d.ts.map +1 -0
  5. package/dist/brand.js +47 -0
  6. package/dist/brand.js.map +1 -0
  7. package/dist/closed.d.ts +97 -0
  8. package/dist/closed.d.ts.map +1 -0
  9. package/dist/closed.js +107 -0
  10. package/dist/closed.js.map +1 -0
  11. package/dist/count.d.ts +55 -0
  12. package/dist/count.d.ts.map +1 -0
  13. package/dist/count.js +92 -0
  14. package/dist/count.js.map +1 -0
  15. package/dist/db.d.ts +341 -0
  16. package/dist/db.d.ts.map +1 -0
  17. package/dist/db.js +1016 -0
  18. package/dist/db.js.map +1 -0
  19. package/dist/exhume.d.ts +130 -0
  20. package/dist/exhume.d.ts.map +1 -0
  21. package/dist/exhume.js +145 -0
  22. package/dist/exhume.js.map +1 -0
  23. package/dist/face.d.ts +91 -0
  24. package/dist/face.d.ts.map +1 -0
  25. package/dist/face.js +69 -0
  26. package/dist/face.js.map +1 -0
  27. package/dist/fields.d.ts +187 -0
  28. package/dist/fields.d.ts.map +1 -0
  29. package/dist/fields.js +204 -0
  30. package/dist/fields.js.map +1 -0
  31. package/dist/index.d.ts +49 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +31 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lower.d.ts +34 -0
  36. package/dist/lower.d.ts.map +1 -0
  37. package/dist/lower.js +105 -0
  38. package/dist/lower.js.map +1 -0
  39. package/dist/marshal.d.ts +79 -0
  40. package/dist/marshal.d.ts.map +1 -0
  41. package/dist/marshal.js +167 -0
  42. package/dist/marshal.js.map +1 -0
  43. package/dist/native.d.ts +532 -0
  44. package/dist/native.d.ts.map +1 -0
  45. package/dist/native.js +64 -0
  46. package/dist/native.js.map +1 -0
  47. package/dist/query/atom.d.ts +285 -0
  48. package/dist/query/atom.d.ts.map +1 -0
  49. package/dist/query/atom.js +281 -0
  50. package/dist/query/atom.js.map +1 -0
  51. package/dist/query/lower.d.ts +145 -0
  52. package/dist/query/lower.d.ts.map +1 -0
  53. package/dist/query/lower.js +604 -0
  54. package/dist/query/lower.js.map +1 -0
  55. package/dist/query/predicate.d.ts +101 -0
  56. package/dist/query/predicate.d.ts.map +1 -0
  57. package/dist/query/predicate.js +85 -0
  58. package/dist/query/predicate.js.map +1 -0
  59. package/dist/query/run.d.ts +30 -0
  60. package/dist/query/run.d.ts.map +1 -0
  61. package/dist/query/run.js +94 -0
  62. package/dist/query/run.js.map +1 -0
  63. package/dist/query/scope.d.ts +168 -0
  64. package/dist/query/scope.d.ts.map +1 -0
  65. package/dist/query/scope.js +134 -0
  66. package/dist/query/scope.js.map +1 -0
  67. package/dist/query/select.d.ts +106 -0
  68. package/dist/query/select.d.ts.map +1 -0
  69. package/dist/query/select.js +69 -0
  70. package/dist/query/select.js.map +1 -0
  71. package/dist/relation.d.ts +120 -0
  72. package/dist/relation.d.ts.map +1 -0
  73. package/dist/relation.js +108 -0
  74. package/dist/relation.js.map +1 -0
  75. package/dist/schema.d.ts +50 -0
  76. package/dist/schema.d.ts.map +1 -0
  77. package/dist/schema.js +235 -0
  78. package/dist/schema.js.map +1 -0
  79. package/dist/spec.d.ts +218 -0
  80. package/dist/spec.d.ts.map +1 -0
  81. package/dist/spec.js +154 -0
  82. package/dist/spec.js.map +1 -0
  83. package/dist/statements.d.ts +91 -0
  84. package/dist/statements.d.ts.map +1 -0
  85. package/dist/statements.js +101 -0
  86. package/dist/statements.js.map +1 -0
  87. package/package.json +66 -0
  88. package/src/brand.ts +82 -0
  89. package/src/closed.ts +228 -0
  90. package/src/count.ts +117 -0
  91. package/src/db.ts +1519 -0
  92. package/src/exhume.ts +243 -0
  93. package/src/face.ts +161 -0
  94. package/src/fields.ts +385 -0
  95. package/src/index.ts +185 -0
  96. package/src/lower.ts +118 -0
  97. package/src/marshal.ts +220 -0
  98. package/src/native.ts +576 -0
  99. package/src/query/atom.ts +556 -0
  100. package/src/query/lower.ts +855 -0
  101. package/src/query/predicate.ts +195 -0
  102. package/src/query/run.ts +106 -0
  103. package/src/query/scope.ts +301 -0
  104. package/src/query/select.ts +140 -0
  105. package/src/relation.ts +252 -0
  106. package/src/schema.ts +297 -0
  107. package/src/spec.ts +325 -0
  108. package/src/statements.ts +148 -0
@@ -0,0 +1,556 @@
1
+ /**
2
+ * Query atoms and conditions (PRD-08) — the body vocabulary of a rule,
3
+ * mirroring the engine IR variant for variant
4
+ * (`bumbledb/crates/bumbledb/src/ir.rs`, the bijection target;
5
+ * `docs/architecture/20-query-ir.md` normative): `match` is the named-field
6
+ * atom (unmentioned fields ARE the wildcard — no wildcard value exists),
7
+ * `not` is negation-as-position (anti-join), `is`/`ne`/`lt`/`le`/`gt`/`ge`
8
+ * are the comparison roster, `covers` is the IR's `PointIn` predicate,
9
+ * `allen` the 13-bit-mask interval-pair comparison, `and`/`or` the input
10
+ * condition-tree grammar (distributed to DNF by the engine's validation),
11
+ * and `duration` the measure term. Nothing beyond the IR exists here: no
12
+ * convenience operator fakes an unsupported comparison.
13
+ */
14
+
15
+ import * as errors from "@superbuilders/errors"
16
+ import type { IntervalValue } from "#brand.ts"
17
+ import { phantom } from "#brand.ts"
18
+ import type { OneOf } from "#face.ts"
19
+ import type { FieldData, FieldValue } from "#fields.ts"
20
+ import type { PredicateData } from "#query/predicate.ts"
21
+ import type {
22
+ AnyTerm,
23
+ AnyVar,
24
+ ItemParams,
25
+ MaskParam,
26
+ Param,
27
+ ParamSet,
28
+ ParamsRecord,
29
+ ParamsShape,
30
+ TermContribution,
31
+ Var
32
+ } from "#query/scope.ts"
33
+ import { isTerm, term } from "#query/scope.ts"
34
+ import type { AnyRelation, FieldsShape, Relation } from "#relation.ts"
35
+
36
+ /**
37
+ * One atom-binding position as runtime data: a scope term, a host literal
38
+ * (tagged at lowering by the FIELD's structural type — a point-typed
39
+ * literal at an interval field is the IR's membership typing rule), or an
40
+ * `oneOf` literal set (lowered to a fresh variable plus a disjunctive
41
+ * equality condition — the three-confinement law's rule-level OR, spelled
42
+ * for the caller as one binding).
43
+ */
44
+ type BindingTerm =
45
+ | { readonly kind: "term"; readonly value: AnyTerm }
46
+ | { readonly kind: "literal"; readonly value: unknown }
47
+ | { readonly kind: "oneOf"; readonly values: readonly unknown[] }
48
+
49
+ /** One resolved binding: the field's name, its description, and the term. */
50
+ interface BindingEntry {
51
+ readonly field: string
52
+ readonly data: FieldData
53
+ readonly term: BindingTerm
54
+ }
55
+
56
+ /** Where an atom draws its facts: a stored relation or a scope predicate. */
57
+ type AtomSourceData =
58
+ | { readonly kind: "relation"; readonly relation: AnyRelation }
59
+ | { readonly kind: "predicate"; readonly pred: PredicateData }
60
+
61
+ /**
62
+ * One atom value (positive or negated — negation is a position in the
63
+ * rule, not a kind of atom, exactly as the IR reuses `Atom` unchanged).
64
+ * The phantom carries the params object the atom's bindings contribute.
65
+ */
66
+ interface MatchAtom<P extends ParamsRecord> {
67
+ readonly item: "atom"
68
+ readonly negated: boolean
69
+ readonly source: AtomSourceData
70
+ readonly bindings: readonly BindingEntry[]
71
+ readonly [phantom]?: P
72
+ }
73
+
74
+ /** One comparison side as runtime data. */
75
+ type CmpTerm =
76
+ | { readonly kind: "term"; readonly value: AnyTerm }
77
+ | { readonly kind: "literal"; readonly value: unknown }
78
+ | { readonly kind: "measure"; readonly over: AnyVar }
79
+
80
+ /** The `allen` mask position: a literal 13-bit mask or a mask parameter. */
81
+ type MaskData =
82
+ | { readonly kind: "literal"; readonly mask: number }
83
+ | { readonly kind: "param"; readonly param: MaskParam<string> }
84
+
85
+ /** One comparison operator as runtime data (mirrors `ir::CmpOp`). */
86
+ type CmpOpData =
87
+ | { readonly kind: "eq" }
88
+ | { readonly kind: "ne" }
89
+ | { readonly kind: "lt" }
90
+ | { readonly kind: "le" }
91
+ | { readonly kind: "gt" }
92
+ | { readonly kind: "ge" }
93
+ | { readonly kind: "allen"; readonly mask: MaskData }
94
+ | { readonly kind: "pointIn" }
95
+
96
+ /** One comparison condition value. */
97
+ interface ComparisonItem<P extends ParamsRecord> {
98
+ readonly item: "cmp"
99
+ readonly op: CmpOpData
100
+ readonly lhs: CmpTerm
101
+ readonly rhs: CmpTerm
102
+ readonly [phantom]?: P
103
+ }
104
+
105
+ /**
106
+ * One condition-tree node (`ir::ConditionTree`): any boolean combination
107
+ * of comparisons — the engine's validation distributes nested OR to DNF
108
+ * rules; the surface admits exactly what the IR admits.
109
+ */
110
+ interface ConditionTreeItem<P extends ParamsRecord> {
111
+ readonly item: "tree"
112
+ readonly op: "and" | "or"
113
+ readonly children: readonly AnyCondition[]
114
+ readonly [phantom]?: P
115
+ }
116
+
117
+ /** Any condition value, whatever its params. */
118
+ type AnyCondition = ComparisonItem<ParamsRecord> | ConditionTreeItem<ParamsRecord>
119
+
120
+ /** Any rule body item: an atom (either polarity) or a condition. */
121
+ type AnyBodyItem = MatchAtom<ParamsRecord> | AnyCondition
122
+
123
+ /**
124
+ * The measure of an interval-typed variable (`ir::Term::Measure`, surface
125
+ * `Duration`): `|[s, e)| = e − s`, u64 — legal as one side of an order
126
+ * comparison, as a projected select entry, and as the input of
127
+ * `sum`/`min`/`max`; every other position is unwritable here exactly as
128
+ * the IR rejects it typed. A ray has no finite measure — the engine's
129
+ * `MeasureOfRay` execution error; exclude rays first (`allen` against a
130
+ * bounded window).
131
+ */
132
+ interface Duration {
133
+ readonly measure: AnyVar
134
+ readonly [phantom]?: bigint
135
+ }
136
+
137
+ /**
138
+ * What a binding position of value type `V` accepts: a branded literal, a
139
+ * disjunctive `oneOf` literal set, a `Var`/`Param`/`ParamSet` of the same
140
+ * brand — and, when the field is interval-typed, a point-typed term (the
141
+ * IR's membership typing rule: an element-typed term at an interval field
142
+ * is point membership; an interval-typed term is value equality).
143
+ */
144
+ type TermInput<V> =
145
+ | V
146
+ | OneOf<V>
147
+ | Var<V>
148
+ | Param<string, V>
149
+ | ParamSet<string, V>
150
+ | (V extends IntervalValue ? PointTermInput : never)
151
+
152
+ /** The point-typed terms an interval field position additionally accepts. */
153
+ type PointTermInput = bigint | Var<bigint> | Param<string, bigint> | ParamSet<string, bigint>
154
+
155
+ /**
156
+ * The `match` bindings record: per field, a term of that field's brand;
157
+ * unmentioned fields are wildcards (absence IS the wildcard — the IR has
158
+ * no wildcard variant to spell).
159
+ */
160
+ type MatchInput<Fields extends FieldsShape> = {
161
+ readonly [K in keyof Fields]?: TermInput<FieldValue<Fields[K]>>
162
+ }
163
+
164
+ /** The params object a bindings record contributes (used by `match`). */
165
+ type BindingsParams<B> = ParamsShape<TermContribution<B[keyof B]>>
166
+
167
+ /** Narrows an `oneOf` literal set (the detection rule `where()` uses). */
168
+ function isOneOf(value: unknown): value is OneOf<unknown> {
169
+ return typeof value === "object" && value !== null && "literals" in value && Array.isArray(value.literals)
170
+ }
171
+
172
+ /** Resolves one binding value to its runtime term. */
173
+ function bindingTermOf(context: string, value: unknown): BindingTerm {
174
+ if (isTerm(value)) {
175
+ if (value[term] === "maskParam") {
176
+ throw errors.new(
177
+ `${context}: an Allen-mask param is not a field-typed value — masks live in allen() conditions only`
178
+ )
179
+ }
180
+ return Object.freeze({ kind: "term" as const, value })
181
+ }
182
+ if (isOneOf(value)) {
183
+ return Object.freeze({ kind: "oneOf" as const, values: Object.freeze([...value.literals]) })
184
+ }
185
+ return Object.freeze({ kind: "literal" as const, value })
186
+ }
187
+
188
+ /**
189
+ * Resolves a bindings record against an ordered field roster (shared by
190
+ * relation atoms here and predicate atoms in `#query/predicate.ts`), in
191
+ * the record's written order.
192
+ */
193
+ function resolveBindings(
194
+ context: string,
195
+ fields: ReadonlyArray<{ readonly name: string; readonly field: FieldData }>,
196
+ bindings: Readonly<Record<string, unknown>>
197
+ ): readonly BindingEntry[] {
198
+ const entries: BindingEntry[] = []
199
+ for (const [fieldName, value] of Object.entries(bindings)) {
200
+ if (value === undefined) {
201
+ continue
202
+ }
203
+ const declared = fields.find(function byName(candidate) {
204
+ return candidate.name === fieldName
205
+ })
206
+ if (declared === undefined) {
207
+ throw errors.new(`${context} has no field ${fieldName}`)
208
+ }
209
+ entries.push(
210
+ Object.freeze({
211
+ field: fieldName,
212
+ data: declared.field,
213
+ term: bindingTermOf(`${context}.${fieldName}`, value)
214
+ })
215
+ )
216
+ }
217
+ return Object.freeze(entries)
218
+ }
219
+
220
+ /**
221
+ * The named-field atom — the semantic twin of `query!`'s `Node(id: c)`:
222
+ * fields bind vars, params, branded literals, `oneOf` sets, or (interval
223
+ * fields) point terms; unmentioned fields are wildcards; a zero-binding
224
+ * atom is a nonemptiness gate on the relation (IR-legal, so writable).
225
+ */
226
+ function match<Name extends string, Fields extends FieldsShape, const B extends MatchInput<Fields>>(
227
+ relation: Relation<Name, Fields>,
228
+ bindings: B
229
+ ): MatchAtom<BindingsParams<B>> {
230
+ return Object.freeze({
231
+ item: "atom" as const,
232
+ negated: false,
233
+ source: Object.freeze({ kind: "relation" as const, relation }),
234
+ bindings: resolveBindings(
235
+ `relation ${relation.name}`,
236
+ relation.data.fields,
237
+ Object.fromEntries(Object.entries(bindings))
238
+ )
239
+ })
240
+ }
241
+
242
+ /**
243
+ * Negation — anti-join over sets, no null trick: a binding satisfies the
244
+ * negated atom iff NO fact matches it. Safety (every negated var bound
245
+ * positively) is validated at `query()` construction with an error naming
246
+ * the variable; the negated atom binds nothing, only rejects. An `oneOf`
247
+ * binding is refused HERE, at construction: it lowers to a synthetic
248
+ * variable bound only inside the atom plus a rule-level OR — which is
249
+ * exactly the shape the safety rule rejects, and semantically wrong for
250
+ * negation anyway (¬∃(f = a ∨ f = b) is a CONJUNCTION of negated atoms,
251
+ * ¬∃(f = a) ∧ ¬∃(f = b), never one negated atom's OR).
252
+ */
253
+ function not<P extends ParamsRecord>(atom: MatchAtom<P>): MatchAtom<P> {
254
+ if (atom.item !== "atom" || atom.negated) {
255
+ throw errors.new("negation is a position in the rule, not an operator — not() takes one positive atom")
256
+ }
257
+ for (const binding of atom.bindings) {
258
+ if (binding.term.kind === "oneOf") {
259
+ const source = atom.source.kind === "relation" ? atom.source.relation.name : `predicate ${atom.source.pred.name}`
260
+ throw errors.new(
261
+ `negated ${source} atom binds ${binding.field} with oneOf(...) — ¬∃(${binding.field} = a ∨ ${binding.field} = b) means ¬∃(${binding.field} = a) ∧ ¬∃(${binding.field} = b): write one not(match(...)) per literal, or bind a paramSet`
262
+ )
263
+ }
264
+ }
265
+ return Object.freeze({
266
+ item: "atom" as const,
267
+ negated: true,
268
+ source: atom.source,
269
+ bindings: atom.bindings
270
+ })
271
+ }
272
+
273
+ /** Builds one comparison value. */
274
+ function comparison(op: CmpOpData, lhs: CmpTerm, rhs: CmpTerm): ComparisonItem<never> {
275
+ return Object.freeze({ item: "cmp" as const, op: Object.freeze(op), lhs, rhs })
276
+ }
277
+
278
+ /** Narrows a `duration()` value. */
279
+ function isDuration(value: unknown): value is Duration {
280
+ return typeof value === "object" && value !== null && "measure" in value && !isTerm(value)
281
+ }
282
+
283
+ /** Resolves one comparison side to its runtime term. */
284
+ function cmpTermOf(value: unknown): CmpTerm {
285
+ if (isTerm(value)) {
286
+ return Object.freeze({ kind: "term" as const, value })
287
+ }
288
+ if (isDuration(value)) {
289
+ return Object.freeze({ kind: "measure" as const, over: value.measure })
290
+ }
291
+ return Object.freeze({ kind: "literal" as const, value })
292
+ }
293
+
294
+ /**
295
+ * Rejects a comparison with no term side: it is constant-valued, the
296
+ * engine's own validation refuses it, and the lowering has no field
297
+ * position to type the literals by — fail here with the same verdict.
298
+ */
299
+ function assertTermSide(op: string, lhs: CmpTerm, rhs: CmpTerm): void {
300
+ if (lhs.kind === "literal" && rhs.kind === "literal") {
301
+ throw errors.new(
302
+ `${op}: a comparison without a variable or parameter side is constant-valued — write the query you mean`
303
+ )
304
+ }
305
+ }
306
+
307
+ /**
308
+ * The equality atom (`ir::CmpOp::Eq`) — for binding a var to a param or
309
+ * literal where punning inside `match` doesn't apply, and for var-to-var
310
+ * unification. Prefer direct placement (`match(Account, { kind:
311
+ * Kind.Savings })`); `is` exists for the var-to-param case. `ParamSet` is
312
+ * legal here and under no other operator (the IR's `Eq`-only set rule).
313
+ */
314
+ function is<V, const R extends Var<V> | Param<string, V> | ParamSet<string, V> | V>(
315
+ left: Var<V>,
316
+ right: R
317
+ ): ComparisonItem<ParamsShape<TermContribution<R>>> {
318
+ return comparison(Object.freeze({ kind: "eq" as const }), cmpTermOf(left), cmpTermOf(right))
319
+ }
320
+
321
+ /** Typed disequality (`ir::CmpOp::Ne`). "Not in set" has no operator — write a negated atom. */
322
+ function ne<V, const R extends Var<V> | Param<string, V> | V>(
323
+ left: Var<V>,
324
+ right: R
325
+ ): ComparisonItem<ParamsShape<TermContribution<R>>> {
326
+ return comparison(Object.freeze({ kind: "ne" as const }), cmpTermOf(left), cmpTermOf(right))
327
+ }
328
+
329
+ /**
330
+ * One side of an order comparison: a u64/i64-typed var or param, a bigint
331
+ * literal, or the measure (`duration(v)`) — order operators are legal for
332
+ * the orderable types only, never intervals/bytes/strings/bools (the IR's
333
+ * comparison rules; each refusal is the engine's own typed diagnostic).
334
+ */
335
+ type OrderInput = Var<bigint> | Param<string, bigint> | bigint | Duration
336
+
337
+ /** The shared order-comparison constructor. */
338
+ function order<const L extends OrderInput, const R extends OrderInput>(
339
+ op: "lt" | "le" | "gt" | "ge",
340
+ left: L,
341
+ right: R
342
+ ): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
343
+ const lhs = cmpTermOf(left)
344
+ const rhs = cmpTermOf(right)
345
+ assertTermSide(op, lhs, rhs)
346
+ return comparison(Object.freeze({ kind: op }), lhs, rhs)
347
+ }
348
+
349
+ /** Strict less-than (`ir::CmpOp::Lt`). */
350
+ function lt<const L extends OrderInput, const R extends OrderInput>(
351
+ left: L,
352
+ right: R
353
+ ): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
354
+ return order("lt", left, right)
355
+ }
356
+
357
+ /** Less-or-equal (`ir::CmpOp::Le`). */
358
+ function le<const L extends OrderInput, const R extends OrderInput>(
359
+ left: L,
360
+ right: R
361
+ ): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
362
+ return order("le", left, right)
363
+ }
364
+
365
+ /** Strict greater-than (`ir::CmpOp::Gt`). */
366
+ function gt<const L extends OrderInput, const R extends OrderInput>(
367
+ left: L,
368
+ right: R
369
+ ): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
370
+ return order("gt", left, right)
371
+ }
372
+
373
+ /** Greater-or-equal (`ir::CmpOp::Ge`). */
374
+ function ge<const L extends OrderInput, const R extends OrderInput>(
375
+ left: L,
376
+ right: R
377
+ ): ComparisonItem<ParamsShape<TermContribution<L> | TermContribution<R>>> {
378
+ return order("ge", left, right)
379
+ }
380
+
381
+ /** An interval-typed comparison side: a var, a param, or a `span` literal. */
382
+ type IntervalInput = Var<IntervalValue> | Param<string, IntervalValue> | IntervalValue
383
+
384
+ /** A point-typed comparison side. */
385
+ type PointInput = Var<bigint> | Param<string, bigint> | bigint
386
+
387
+ /**
388
+ * Point membership as a predicate (`ir::CmpOp::PointIn`): `covers(iv, t)`
389
+ * holds iff `iv.start ≤ t < iv.end` — the predicate form of the membership
390
+ * typing rule, for terms already bound elsewhere. Interval ⊇ interval is
391
+ * NOT this operator; that predicate is `allen(a, ALLEN.covers, b)`. The IR
392
+ * orders the operands interval-left, point-right; so does this surface.
393
+ */
394
+ function covers<const I extends IntervalInput, const T extends PointInput>(
395
+ interval: I,
396
+ point: T
397
+ ): ComparisonItem<ParamsShape<TermContribution<I> | TermContribution<T>>> {
398
+ const lhs = cmpTermOf(interval)
399
+ const rhs = cmpTermOf(point)
400
+ assertTermSide("covers", lhs, rhs)
401
+ return comparison(Object.freeze({ kind: "pointIn" as const }), lhs, rhs)
402
+ }
403
+
404
+ /**
405
+ * The 13-bit mask range: bits above the low 13 are unrepresentable in the
406
+ * engine's `AllenMask` (`bumbledb/crates/bumbledb/src/allen.rs`:
407
+ * `AllenMask::new` refuses them) — the check here is the same boundary,
408
+ * moved to construction where the message can name the constants.
409
+ */
410
+ const ALLEN_ALL_BITS = (1 << 13) - 1
411
+
412
+ /**
413
+ * The Allen coordinate system's named constants — the 13 basics in the
414
+ * engine's palindromic bit order (bit i = basic i:
415
+ * `bumbledb/crates/bumbledb/src/allen.rs`, a specified representation)
416
+ * plus the workload composites, values identical to the engine's. Compose
417
+ * with `|`: `ALLEN.before | ALLEN.meets`.
418
+ */
419
+ const ALLEN = Object.freeze({
420
+ before: 1 << 0,
421
+ meets: 1 << 1,
422
+ overlaps: 1 << 2,
423
+ starts: 1 << 3,
424
+ during: 1 << 4,
425
+ finishes: 1 << 5,
426
+ equals: 1 << 6,
427
+ finishedBy: 1 << 7,
428
+ contains: 1 << 8,
429
+ startedBy: 1 << 9,
430
+ overlappedBy: 1 << 10,
431
+ metBy: 1 << 11,
432
+ after: 1 << 12,
433
+ /** The point-sets share a point (9 bits; under half-open intervals *meets* shares none). */
434
+ intersects: (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10),
435
+ /** Point-set ⊇: equals ∪ contains ∪ started-by ∪ finished-by. */
436
+ covers: (1 << 6) | (1 << 8) | (1 << 9) | (1 << 7),
437
+ /** Point-set ⊆ — `covers`' converse: equals ∪ during ∪ starts ∪ finishes. */
438
+ coveredBy: (1 << 6) | (1 << 4) | (1 << 3) | (1 << 5),
439
+ /** The point-sets share no point: before ∪ meets ∪ met-by ∪ after. */
440
+ disjoint: (1 << 0) | (1 << 1) | (1 << 11) | (1 << 12)
441
+ })
442
+
443
+ /**
444
+ * THE interval-pair comparison (`ir::CmpOp::Allen`): two interval terms of
445
+ * one element type, satisfied iff the pair's classification is in the
446
+ * 13-bit mask — a literal built from the `ALLEN` constants, or a mask
447
+ * parameter (`$.allenParam`). Vacuous masks (empty/full) are the engine's
448
+ * two distinct typed rejections; nothing is pre-judged here beyond the
449
+ * representable bit range.
450
+ */
451
+ function allen<
452
+ const A extends IntervalInput,
453
+ const M extends number | MaskParam<string>,
454
+ const B extends IntervalInput
455
+ >(
456
+ left: A,
457
+ mask: M,
458
+ right: B
459
+ ): ComparisonItem<ParamsShape<TermContribution<A> | TermContribution<M> | TermContribution<B>>> {
460
+ const lhs = cmpTermOf(left)
461
+ const rhs = cmpTermOf(right)
462
+ assertTermSide("allen", lhs, rhs)
463
+ const maskValue: number | MaskParam<string> = mask
464
+ if (typeof maskValue === "number") {
465
+ if (!Number.isInteger(maskValue) || maskValue < 0 || maskValue > ALLEN_ALL_BITS) {
466
+ throw errors.new(
467
+ `allen mask ${maskValue} is not a 13-bit mask — build masks from the ALLEN constants (bumbledb allen.rs: bits above the low 13 are unrepresentable)`
468
+ )
469
+ }
470
+ return comparison(
471
+ Object.freeze({
472
+ kind: "allen" as const,
473
+ mask: Object.freeze({ kind: "literal" as const, mask: maskValue })
474
+ }),
475
+ lhs,
476
+ rhs
477
+ )
478
+ }
479
+ return comparison(
480
+ Object.freeze({
481
+ kind: "allen" as const,
482
+ mask: Object.freeze({ kind: "param" as const, param: maskValue })
483
+ }),
484
+ lhs,
485
+ rhs
486
+ )
487
+ }
488
+
489
+ /**
490
+ * Conjunction node of the input condition grammar (`ConditionTree::And`).
491
+ * The rule's condition list is already a conjunction — `and` exists for
492
+ * nesting under `or`, and the empty combination keeps the IR's algebraic
493
+ * reading (`And([])` is true).
494
+ */
495
+ function and<const C extends readonly AnyCondition[]>(
496
+ ...children: C
497
+ ): ConditionTreeItem<ParamsShape<ItemParams<C[number]>>> {
498
+ return Object.freeze({
499
+ item: "tree" as const,
500
+ op: "and" as const,
501
+ children: Object.freeze([...children])
502
+ })
503
+ }
504
+
505
+ /**
506
+ * Disjunction node of the input condition grammar (`ConditionTree::Or`) —
507
+ * the one place the surface admits a nested OR; validation distributes it
508
+ * to DNF rules engine-side (OR is data or it is nothing). `Or([])` keeps
509
+ * its algebraic reading (false: the rule denotes nothing).
510
+ */
511
+ function or<const C extends readonly AnyCondition[]>(
512
+ ...children: C
513
+ ): ConditionTreeItem<ParamsShape<ItemParams<C[number]>>> {
514
+ return Object.freeze({
515
+ item: "tree" as const,
516
+ op: "or" as const,
517
+ children: Object.freeze([...children])
518
+ })
519
+ }
520
+
521
+ /**
522
+ * The measure term — surface `Duration`, IR `Measure`: the point-set
523
+ * cardinality `end − start` of an interval-typed variable, u64. See
524
+ * {@link Duration} for the legal positions.
525
+ */
526
+ function duration<IV extends IntervalValue>(over: Var<IV>): Duration {
527
+ if (over.data.type.kind !== "interval") {
528
+ throw errors.new(
529
+ `duration(${over.relation}.${over.field}): the measure is defined over interval-typed variables only`
530
+ )
531
+ }
532
+ return Object.freeze({ measure: over })
533
+ }
534
+
535
+ export type {
536
+ AnyBodyItem,
537
+ AnyCondition,
538
+ AtomSourceData,
539
+ BindingEntry,
540
+ BindingsParams,
541
+ BindingTerm,
542
+ CmpOpData,
543
+ CmpTerm,
544
+ ComparisonItem,
545
+ ConditionTreeItem,
546
+ Duration,
547
+ IntervalInput,
548
+ MaskData,
549
+ MatchAtom,
550
+ MatchInput,
551
+ OrderInput,
552
+ PointInput,
553
+ PointTermInput,
554
+ TermInput
555
+ }
556
+ export { ALLEN, allen, and, covers, duration, ge, gt, is, le, lt, match, ne, not, or, resolveBindings }