@bjornpagen/bumbledb 0.1.0 → 0.3.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 (112) hide show
  1. package/COOKBOOK.md +1450 -0
  2. package/README.md +69 -30
  3. package/dist/closed.d.ts +157 -38
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +232 -47
  6. package/dist/closed.js.map +1 -1
  7. package/dist/count.d.ts +64 -17
  8. package/dist/count.d.ts.map +1 -1
  9. package/dist/count.js +37 -14
  10. package/dist/count.js.map +1 -1
  11. package/dist/db.d.ts +27 -8
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +33 -10
  14. package/dist/db.js.map +1 -1
  15. package/dist/exhume.d.ts +12 -11
  16. package/dist/exhume.d.ts.map +1 -1
  17. package/dist/exhume.js +7 -6
  18. package/dist/exhume.js.map +1 -1
  19. package/dist/face.d.ts +135 -30
  20. package/dist/face.d.ts.map +1 -1
  21. package/dist/face.js +44 -19
  22. package/dist/face.js.map +1 -1
  23. package/dist/fields.d.ts +130 -130
  24. package/dist/fields.d.ts.map +1 -1
  25. package/dist/fields.js +60 -94
  26. package/dist/fields.js.map +1 -1
  27. package/dist/index.d.ts +29 -24
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +19 -14
  30. package/dist/index.js.map +1 -1
  31. package/dist/law.d.ts +224 -0
  32. package/dist/law.d.ts.map +1 -0
  33. package/dist/law.js +224 -0
  34. package/dist/law.js.map +1 -0
  35. package/dist/lower.d.ts +17 -7
  36. package/dist/lower.d.ts.map +1 -1
  37. package/dist/lower.js +66 -24
  38. package/dist/lower.js.map +1 -1
  39. package/dist/marshal.d.ts +36 -21
  40. package/dist/marshal.d.ts.map +1 -1
  41. package/dist/marshal.js +41 -25
  42. package/dist/marshal.js.map +1 -1
  43. package/dist/native.d.ts +6 -2
  44. package/dist/native.d.ts.map +1 -1
  45. package/dist/native.js +2 -2
  46. package/dist/native.js.map +1 -1
  47. package/dist/query/atom.d.ts +370 -174
  48. package/dist/query/atom.d.ts.map +1 -1
  49. package/dist/query/atom.js +75 -172
  50. package/dist/query/atom.js.map +1 -1
  51. package/dist/query/lower.d.ts +312 -109
  52. package/dist/query/lower.d.ts.map +1 -1
  53. package/dist/query/lower.js +906 -387
  54. package/dist/query/lower.js.map +1 -1
  55. package/dist/query/predicate.d.ts +70 -88
  56. package/dist/query/predicate.d.ts.map +1 -1
  57. package/dist/query/predicate.js +118 -72
  58. package/dist/query/predicate.js.map +1 -1
  59. package/dist/query/run.d.ts +19 -16
  60. package/dist/query/run.d.ts.map +1 -1
  61. package/dist/query/run.js +31 -24
  62. package/dist/query/run.js.map +1 -1
  63. package/dist/query/scope.d.ts +178 -127
  64. package/dist/query/scope.d.ts.map +1 -1
  65. package/dist/query/scope.js +111 -108
  66. package/dist/query/scope.js.map +1 -1
  67. package/dist/query/select.d.ts +102 -80
  68. package/dist/query/select.d.ts.map +1 -1
  69. package/dist/query/select.js +39 -34
  70. package/dist/query/select.js.map +1 -1
  71. package/dist/relation.d.ts +48 -38
  72. package/dist/relation.d.ts.map +1 -1
  73. package/dist/relation.js +24 -19
  74. package/dist/relation.js.map +1 -1
  75. package/dist/schema.d.ts +41 -3
  76. package/dist/schema.d.ts.map +1 -1
  77. package/dist/schema.js +34 -9
  78. package/dist/schema.js.map +1 -1
  79. package/dist/spec.d.ts +7 -5
  80. package/dist/spec.d.ts.map +1 -1
  81. package/dist/spec.js.map +1 -1
  82. package/dist/statements.d.ts +91 -36
  83. package/dist/statements.d.ts.map +1 -1
  84. package/dist/statements.js +42 -12
  85. package/dist/statements.js.map +1 -1
  86. package/package.json +4 -3
  87. package/src/closed.ts +448 -95
  88. package/src/count.ts +112 -18
  89. package/src/db.ts +45 -17
  90. package/src/exhume.ts +12 -11
  91. package/src/face.ts +211 -41
  92. package/src/fields.ts +199 -234
  93. package/src/index.ts +78 -61
  94. package/src/law.ts +519 -0
  95. package/src/lower.ts +82 -27
  96. package/src/marshal.ts +48 -30
  97. package/src/native.ts +8 -4
  98. package/src/query/atom.ts +525 -374
  99. package/src/query/lower.ts +1469 -538
  100. package/src/query/predicate.ts +208 -161
  101. package/src/query/run.ts +35 -25
  102. package/src/query/scope.ts +264 -218
  103. package/src/query/select.ts +168 -93
  104. package/src/relation.ts +47 -47
  105. package/src/schema.ts +69 -18
  106. package/src/spec.ts +7 -5
  107. package/src/statements.ts +124 -41
  108. package/dist/brand.d.ts +0 -59
  109. package/dist/brand.d.ts.map +0 -1
  110. package/dist/brand.js +0 -47
  111. package/dist/brand.js.map +0 -1
  112. package/src/brand.ts +0 -82
package/src/query/atom.ts CHANGED
@@ -1,303 +1,346 @@
1
1
  /**
2
- * Query atoms and conditions (PRD-08) — the body vocabulary of a rule,
3
- * mirroring the engine IR variant for variant
2
+ * Atoms and conditions, STRUCTURAL edition — the body vocabulary of a
3
+ * rule, mirroring the engine IR variant for variant
4
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.
5
+ * `docs/architecture/20-query-ir.md` normative). A `match` binding record
6
+ * binds fields to vars, params, ∈-set params, or bare structural literals
7
+ * (unmentioned fields ARE the wildcard — no wildcard value exists);
8
+ * `not(Rel, {...})` is negation-as-position (anti-join); `eq`/`ne` and the
9
+ * order roster, `pointIn`/`covers` (both spellings of `ir::CmpOp::PointIn`,
10
+ * always lowered interval-left), `allen` (the 13-bit mask pair
11
+ * comparison), and `and`/`or` (the input condition-tree grammar) complete
12
+ * the roster. Nothing beyond the IR exists here — and the walls the engine
13
+ * enforces at prepare are TYPES first: a var joins only domain-equal
14
+ * fields (`JoinOk`, checked against the rule environment AND against the
15
+ * binding record's own same-named siblings — two first occurrences of one
16
+ * name inside one record are a join too), an
17
+ * interval-typed var under a non-`pointIn` comparison is unwritable, and a
18
+ * negated atom's variables must be positively bound (env membership IS the
19
+ * safety rule). Every condition value carries its operands raw — the
20
+ * runtime representation is the type-inference carrier, no phantoms.
21
+ *
22
+ * This module also owns the plain runtime DATA a built rule is made of
23
+ * (`RuleData`/`RecData` and friends): frozen values the lowering walks —
24
+ * pure data, so lowering stays a pure, stable function of the query value.
13
25
  */
14
26
 
15
27
  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"
28
+ import type { AnyClosed } from "#closed.ts"
29
+ import type { AnyField, ClosedIdField, Infer, IntervalValue } from "#fields.ts"
30
+ import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
21
31
  import type {
22
- AnyTerm,
23
- AnyVar,
24
- ItemParams,
32
+ ClassedField,
33
+ Duration,
34
+ EnvShape,
35
+ JoinOk,
25
36
  MaskParam,
26
37
  Param,
27
- ParamSet,
28
- ParamsRecord,
29
- ParamsShape,
30
- TermContribution,
38
+ ParamValueAt,
39
+ SetParam,
40
+ ShapeOf,
31
41
  Var
32
42
  } from "#query/scope.ts"
33
- import { isTerm, term } from "#query/scope.ts"
34
- import type { AnyRelation, FieldsShape, Relation } from "#relation.ts"
43
+ import { isTerm } from "#query/scope.ts"
44
+ import type { AnyRelation, FieldsShape, RelationFields } from "#relation.ts"
45
+
46
+ /**
47
+ * What a query atom matches over: an ordinary relation or a CLOSED
48
+ * vocabulary (ψ query atoms — the engine folds a resolvable closed atom
49
+ * into a plan-constant member set at prepare, or joins the L1-resident
50
+ * virtual image when the shape does not fold; the SDK never pre-folds and
51
+ * never knows which — transparency is the contract).
52
+ */
53
+ type MatchOwner = AnyRelation | AnyClosed
35
54
 
36
55
  /**
37
- * One atom-binding position as runtime data: a scope term, a host literal
38
- * (tagged at lowering by the FIELD's structural typea 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).
56
+ * The matchable field block of an atom owner: a relation's declared
57
+ * fields; a closed relation's SEALED shapethe synthetic `id` (the
58
+ * roster-carrying descriptor) first, then the declared payload columns
59
+ * read through the typed `columns` carrier (the one source of payload
60
+ * typingno parallel column table exists). The runtime twin is
61
+ * `matchFieldsOf` in `#query/lower.ts`; the id-first ordinal shift the two
62
+ * tiers share is pinned by the lowering golden.
43
63
  */
44
- type BindingTerm =
45
- | { readonly kind: "term"; readonly value: AnyTerm }
64
+ type MatchFields<R extends MatchOwner> = R extends AnyClosed
65
+ ? { readonly id: ClosedIdField } & R["columns"]
66
+ : R extends AnyRelation
67
+ ? RelationFields<R>
68
+ : never
69
+
70
+ /** One atom-binding position as runtime data. */
71
+ type BindingTermData =
72
+ | { readonly kind: "var"; readonly name: string }
73
+ | { readonly kind: "param"; readonly name: string }
74
+ | { readonly kind: "setParam"; readonly name: string }
46
75
  | { readonly kind: "literal"; readonly value: unknown }
47
- | { readonly kind: "oneOf"; readonly values: readonly unknown[] }
48
76
 
49
- /** One resolved binding: the field's name, its description, and the term. */
77
+ /** One resolved binding: the field's name, its descriptor, its law-computed class, and the term. */
50
78
  interface BindingEntry {
51
79
  readonly field: string
52
- readonly data: FieldData
53
- readonly term: BindingTerm
80
+ readonly data: AnyField
81
+ readonly class: string | undefined
82
+ readonly term: BindingTermData
54
83
  }
55
84
 
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
85
+ /** One EDB atom as runtime data (either polarity — polarity is the rule item's; a closed owner is a ψ atom). */
86
+ interface AtomData {
87
+ readonly relation: MatchOwner
70
88
  readonly bindings: readonly BindingEntry[]
71
- readonly [phantom]?: P
72
89
  }
73
90
 
91
+ /** One comparison operator name (mirrors `ir::CmpOp`). */
92
+ type CmpKind = "eq" | "ne" | "lt" | "le" | "gt" | "ge" | "pointIn" | "allen"
93
+
74
94
  /** One comparison side as runtime data. */
75
- type CmpTerm =
76
- | { readonly kind: "term"; readonly value: AnyTerm }
95
+ type CmpTermData =
96
+ | { readonly kind: "var"; readonly name: string }
97
+ | { readonly kind: "param"; readonly name: string }
98
+ | { readonly kind: "setParam"; readonly name: string }
99
+ | { readonly kind: "measure"; readonly name: string }
77
100
  | { 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
101
+
102
+ /** The `allen` mask position as runtime data. */
103
+ type MaskData = { readonly kind: "literal"; readonly mask: number } | { readonly kind: "param"; readonly name: string }
104
+
105
+ /** One comparison condition as runtime data (`mask` present exactly for `allen`). */
106
+ interface CmpData {
107
+ readonly kind: "cmp"
108
+ readonly op: CmpKind
109
+ readonly mask: MaskData | undefined
110
+ readonly lhs: CmpTermData
111
+ readonly rhs: CmpTermData
103
112
  }
104
113
 
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"
114
+ /** One condition-tree node as runtime data (`ir::ConditionTree`). */
115
+ interface TreeData {
116
+ readonly kind: "tree"
112
117
  readonly op: "and" | "or"
113
- readonly children: readonly AnyCondition[]
114
- readonly [phantom]?: P
118
+ readonly children: readonly CondData[]
115
119
  }
116
120
 
117
- /** Any condition value, whatever its params. */
118
- type AnyCondition = ComparisonItem<ParamsRecord> | ConditionTreeItem<ParamsRecord>
121
+ /** Any condition node as runtime data. */
122
+ type CondData = CmpData | TreeData
123
+
124
+ /** One aggregate's runtime description (select vocabulary, over var NAMES). */
125
+ type AggData =
126
+ | { readonly op: "count" }
127
+ | { readonly op: "countDistinct"; readonly over: string }
128
+ | { readonly op: "fold"; readonly fold: "sum" | "min" | "max"; readonly over: string | { readonly duration: string } }
129
+ | { readonly op: "arg"; readonly direction: "argMax" | "argMin"; readonly over: string; readonly key: string }
130
+ | { readonly op: "pack"; readonly over: string }
131
+
132
+ /** One classified select entry as runtime data. */
133
+ type SelectEntryData =
134
+ | { readonly kind: "var"; readonly over: string }
135
+ | { readonly kind: "measure"; readonly over: string }
136
+ | { readonly kind: "aggregate"; readonly agg: AggData }
137
+
138
+ /** One answer column: its name (the row object key) and its entry. */
139
+ interface SelectColumn {
140
+ readonly name: string
141
+ readonly entry: SelectEntryData
142
+ }
119
143
 
120
- /** Any rule body item: an atom (either polarity) or a condition. */
121
- type AnyBodyItem = MatchAtom<ParamsRecord> | AnyCondition
144
+ /** One body item of a rule, in written order. */
145
+ type RuleItem =
146
+ | { readonly kind: "atom"; readonly atom: AtomData }
147
+ | { readonly kind: "negated"; readonly atom: AtomData }
148
+ | { readonly kind: "idb"; readonly rec: RecData; readonly vars: readonly string[] }
149
+ | { readonly kind: "cond"; readonly cond: CondData }
122
150
 
123
151
  /**
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).
152
+ * One use of a parameter inside a rule, in written order: the census the
153
+ * query-level registry folds (first use mints the dense `ParamId`, first
154
+ * FIELD-ANCHORED use types the wire).
131
155
  */
132
- interface Duration {
133
- readonly measure: AnyVar
134
- readonly [phantom]?: bigint
156
+ interface ParamUse {
157
+ readonly name: string
158
+ readonly shape: "value" | "set" | "mask"
159
+ readonly anchor: AnyField | "measure" | undefined
160
+ readonly op: "binding" | CmpKind
161
+ }
162
+
163
+ /** One complete rule as runtime data. */
164
+ interface RuleData {
165
+ readonly items: readonly RuleItem[]
166
+ readonly select: readonly SelectColumn[]
167
+ /** Variable name → the classed slot its FIRST positive binding carries (the runtime env — descriptor + class). */
168
+ readonly varFields: Readonly<Record<string, ClassedField>>
169
+ readonly paramUses: readonly ParamUse[]
135
170
  }
136
171
 
137
172
  /**
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).
173
+ * One recursive predicate's runtime description identity keys the dense
174
+ * `PredId` at lowering. `rules` is appended by `rec.rule(...)` and sealed
175
+ * (frozen) when the program's output is declared.
143
176
  */
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)
177
+ interface RecData {
178
+ readonly name: string
179
+ readonly rules: RuleData[]
180
+ }
151
181
 
152
- /** The point-typed terms an interval field position additionally accepts. */
153
- type PointTermInput = bigint | Var<bigint> | Param<string, bigint> | ParamSet<string, bigint>
182
+ /**
183
+ * What a binding position of field `F` accepts: a bare structural literal
184
+ * of the field's value type, a var/param/∈-set-param term — and, when the
185
+ * field is interval-typed, a bare point literal (the IR's membership
186
+ * typing rule: an element-typed term at an interval field is point
187
+ * membership; an interval-typed term is value equality).
188
+ */
189
+ type BindingInput<F extends AnyField> =
190
+ | Infer<F>
191
+ | (F extends { readonly kind: "interval" } ? bigint : never)
192
+ | Var<string>
193
+ | Param<string>
194
+ | SetParam<string>
154
195
 
155
196
  /**
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).
197
+ * The `match`/`not` bindings record: per field, a term or literal of that
198
+ * field's structural type; unmentioned fields are wildcards (absence IS
199
+ * the wildcard — the IR has no wildcard variant to spell).
159
200
  */
160
- type MatchInput<Fields extends FieldsShape> = {
161
- readonly [K in keyof Fields]?: TermInput<FieldValue<Fields[K]>>
201
+ type MatchShape<F extends FieldsShape> = {
202
+ readonly [K in keyof F]?: BindingInput<F[K]>
162
203
  }
163
204
 
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)
205
+ /**
206
+ * One field position of a bindings record as a classed slot: the declared
207
+ * descriptor plus the slot's law-computed class, read off the relation's
208
+ * class record (`CR` the schema class map's entry for the atom's
209
+ * relation). The one shape every join judgment compares.
210
+ */
211
+ type SlotAt<F extends FieldsShape, CR, K> = {
212
+ readonly field: F[K & keyof F]
213
+ readonly class: ClassLookup<CR, K>
170
214
  }
171
215
 
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
- }
216
+ /**
217
+ * The var binding's judgment against the incoming rule environment: a name
218
+ * already bound must land on a class-equal slot (bare pairs only with bare).
219
+ */
220
+ type EnvJoinOk<Env extends EnvShape, F extends FieldsShape, CR, K, N extends string> = N extends keyof Env
221
+ ? JoinOk<Env[N], SlotAt<F, CR, K>>
222
+ : true
187
223
 
188
224
  /**
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.
225
+ * The var binding's judgment against its OWN record's siblings: two
226
+ * bindings of one var name inside a single bindings record are the same
227
+ * join the environment check judges across atoms, so every same-named
228
+ * sibling must be class-equal too. Without this arm two FIRST occurrences
229
+ * of one name (a record the environment has not seen yet) would meet no
230
+ * check at all — the intra-atom join would silently cross classes.
192
231
  */
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
- }
232
+ type SiblingJoinOk<F extends FieldsShape, CR, B, K extends keyof B, N extends string> = false extends {
233
+ [K2 in Exclude<keyof B & keyof F, K>]: B[K2] extends Var<N> ? JoinOk<SlotAt<F, CR, K2>, SlotAt<F, CR, K>> : true
234
+ }[Exclude<keyof B & keyof F, K>]
235
+ ? false
236
+ : true
219
237
 
220
238
  /**
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).
239
+ * The per-property join judgment of a bindings record: a var binding must
240
+ * be class-equal to the rule environment's binding of the name AND to
241
+ * every same-named sibling of its own record (a cross-class reuse maps
242
+ * the property to `never` the compile error the old value brand carried,
243
+ * now law-born off the schema type's class map).
225
244
  */
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
- })
245
+ type BindingOk<Env extends EnvShape, F extends FieldsShape, CR, B, K extends keyof B> =
246
+ B[K] extends Var<infer N extends string>
247
+ ? [EnvJoinOk<Env, F, CR, K, N>, SiblingJoinOk<F, CR, B, K, N>] extends [true, true]
248
+ ? true
249
+ : false
250
+ : true
251
+
252
+ /** The validated bindings record (intersect with the inferred `B` errors land on the offending property). */
253
+ type CheckBindings<Env extends EnvShape, F extends FieldsShape, CR, B> = {
254
+ readonly [K in keyof B]: K extends keyof F ? (BindingOk<Env, F, CR, B, K> extends true ? B[K] : never) : never
240
255
  }
241
256
 
257
+ /** The environment a bindings record contributes: var name → the bound slot (descriptor + class). */
258
+ type BindEnv<F extends FieldsShape, CR, B> = {
259
+ readonly [K in keyof B & keyof F as B[K] extends Var<infer N extends string> ? N : never]: SlotAt<F, CR, K>
260
+ }
261
+
262
+ /** The params-object fragments a bindings record contributes (one union member per param use). */
263
+ type BindParams<F extends FieldsShape, B> = {
264
+ [K in keyof B & keyof F]: B[K] extends Param<infer P extends string>
265
+ ? { readonly [Q in P]: ParamValueAt<F[K]> }
266
+ : B[K] extends SetParam<infer P extends string>
267
+ ? { readonly [Q in P]: readonly ParamValueAt<F[K]>[] }
268
+ : never
269
+ }[keyof B & keyof F]
270
+
242
271
  /**
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).
272
+ * One comparison VALUE: op plus its operands, raw the runtime
273
+ * representation carries the operand types, so `.where`'s environment
274
+ * check and the params inference both read the value itself (no phantom).
275
+ * `mask` is populated exactly for `allen`.
252
276
  */
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
- })
277
+ interface Cmp<Op extends CmpKind, L, R, M = undefined> {
278
+ readonly cond: "cmp"
279
+ readonly op: Op
280
+ readonly lhs: L
281
+ readonly rhs: R
282
+ readonly mask: M
271
283
  }
272
284
 
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 })
285
+ /** One condition-tree VALUE (`and`/`or` over comparisons and nested trees). */
286
+ interface Tree<Ch extends readonly AnyTreeChild[]> {
287
+ readonly cond: "tree"
288
+ readonly op: "and" | "or"
289
+ readonly children: Ch
276
290
  }
277
291
 
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)
292
+ /**
293
+ * One negated-atom VALUE — negation is a position in the rule (anti-join
294
+ * over sets, no null trick): a binding satisfies it iff NO fact matches.
295
+ * Its variables must be positively bound in the rule — environment
296
+ * membership at `.where` IS the safety rule, a compile error before it is
297
+ * the engine's refusal.
298
+ */
299
+ interface NotAtom<R extends MatchOwner, B> {
300
+ readonly cond: "not"
301
+ readonly relation: R
302
+ readonly bindings: B
281
303
  }
282
304
 
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 })
305
+ /** Any comparison value. */
306
+ type AnyCmp = Cmp<CmpKind, unknown, unknown, unknown>
307
+
308
+ /** Any condition-tree child (trees hold comparisons and trees never atoms). */
309
+ type AnyTreeChild = AnyCmp | Tree<readonly AnyTreeChild[]>
310
+
311
+ /** Any negated-atom value. */
312
+ type AnyNotAtom = NotAtom<MatchOwner, unknown>
313
+
314
+ /** Any `.where` input: a comparison, a condition tree, or a negated atom. */
315
+ type AnyCond = AnyCmp | Tree<readonly AnyTreeChild[]> | AnyNotAtom
316
+
317
+ /** What `eq`'s right side accepts (`ParamSet` is `Eq`-only — the IR's rule). */
318
+ type EqRight = Var<string> | Param<string> | SetParam<string> | bigint | string | boolean | Uint8Array | IntervalValue
319
+
320
+ /** What `ne`'s right side accepts. */
321
+ type NeRight = Var<string> | Param<string> | bigint | string | boolean | Uint8Array | IntervalValue
322
+
323
+ /** One side of an order comparison: orderable terms only (the IR's comparison rules). */
324
+ type OrderSide = Var<string> | Param<string> | Duration<string> | bigint
325
+
326
+ /** The point side of `pointIn`/`covers`. */
327
+ type PointSide = Var<string> | Param<string> | bigint
328
+
329
+ /** The interval side of `pointIn`/`covers`/`allen`. */
330
+ type IntervalSide = Var<string> | Param<string> | IntervalValue
331
+
332
+ /** Builds one comparison value. */
333
+ function comparison<Op extends CmpKind, L, R, M>(op: Op, lhs: L, rhs: R, mask: M): Cmp<Op, L, R, M> {
334
+ return Object.freeze({ cond: "cmp", op, lhs, rhs, mask })
292
335
  }
293
336
 
294
337
  /**
295
338
  * 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
339
+ * engine's own validation refuses it, and the lowering has no anchored
297
340
  * position to type the literals by — fail here with the same verdict.
298
341
  */
299
- function assertTermSide(op: string, lhs: CmpTerm, rhs: CmpTerm): void {
300
- if (lhs.kind === "literal" && rhs.kind === "literal") {
342
+ function assertTermSide(op: string, lhs: unknown, rhs: unknown): void {
343
+ if (!isTerm(lhs) && !isTerm(rhs)) {
301
344
  throw errors.new(
302
345
  `${op}: a comparison without a variable or parameter side is constant-valued — write the query you mean`
303
346
  )
@@ -305,100 +348,73 @@ function assertTermSide(op: string, lhs: CmpTerm, rhs: CmpTerm): void {
305
348
  }
306
349
 
307
350
  /**
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).
351
+ * The equality comparison (`ir::CmpOp::Eq`) — a bound variable against a
352
+ * variable (var-to-var unification, domain-equal by the environment
353
+ * check), a param (typed by the variable), an ∈-set param (`Eq`-only, the
354
+ * IR's set rule), or a bare literal of the variable's own value type.
355
+ * Prefer direct placement in `match` where punning applies.
313
356
  */
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))
357
+ function eq<L extends Var<string>, const R extends EqRight>(left: L, right: R): Cmp<"eq", L, R> {
358
+ return comparison("eq", left, right, undefined)
319
359
  }
320
360
 
321
361
  /** 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))
362
+ function ne<L extends Var<string>, const R extends NeRight>(left: L, right: R): Cmp<"ne", L, R> {
363
+ return comparison("ne", left, right, undefined)
327
364
  }
328
365
 
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
366
  /** The shared order-comparison constructor. */
338
- function order<const L extends OrderInput, const R extends OrderInput>(
339
- op: "lt" | "le" | "gt" | "ge",
367
+ function order<Op extends "lt" | "le" | "gt" | "ge", const L extends OrderSide, const R extends OrderSide>(
368
+ op: Op,
340
369
  left: L,
341
370
  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)
371
+ ): Cmp<Op, L, R> {
372
+ assertTermSide(op, left, right)
373
+ return comparison(op, left, right, undefined)
347
374
  }
348
375
 
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>>> {
376
+ /** Strict less-than (`ir::CmpOp::Lt`) — orderable sides only, never intervals/bytes/strings/bools. */
377
+ function lt<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"lt", L, R> {
354
378
  return order("lt", left, right)
355
379
  }
356
380
 
357
381
  /** 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>>> {
382
+ function le<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"le", L, R> {
362
383
  return order("le", left, right)
363
384
  }
364
385
 
365
386
  /** 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>>> {
387
+ function gt<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"gt", L, R> {
370
388
  return order("gt", left, right)
371
389
  }
372
390
 
373
391
  /** 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>>> {
392
+ function ge<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"ge", L, R> {
378
393
  return order("ge", left, right)
379
394
  }
380
395
 
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
396
+ /**
397
+ * Point membership as a predicate (`ir::CmpOp::PointIn`), membership
398
+ * spelling: `pointIn(t, w)` holds iff `w.start ≤ t < w.end`. The IR
399
+ * orders the operands interval-left, point-right; the value stores them
400
+ * that way whatever the surface spelling.
401
+ */
402
+ function pointIn<const P extends PointSide, const I extends IntervalSide>(point: P, interval: I): Cmp<"pointIn", I, P> {
403
+ assertTermSide("pointIn", point, interval)
404
+ return comparison("pointIn", interval, point, undefined)
405
+ }
386
406
 
387
407
  /**
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.
408
+ * Point membership, coverage spelling `covers(w, t)` is `pointIn(t, w)`
409
+ * with the interval written first (the IR's own operand order; a literal
410
+ * `span(...)` left operand is legal and tags by the point sibling's
411
+ * element domain the bug-hunt fix, now also a type-level guarantee).
412
+ * Interval interval is NOT this operator; that predicate is
413
+ * `allen(a, ALLEN.covers, b)`.
393
414
  */
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)
415
+ function covers<const I extends IntervalSide, const P extends PointSide>(interval: I, point: P): Cmp<"pointIn", I, P> {
416
+ assertTermSide("covers", interval, point)
417
+ return comparison("pointIn", interval, point, undefined)
402
418
  }
403
419
 
404
420
  /**
@@ -444,46 +460,22 @@ const ALLEN = Object.freeze({
444
460
  * THE interval-pair comparison (`ir::CmpOp::Allen`): two interval terms of
445
461
  * one element type, satisfied iff the pair's classification is in the
446
462
  * 13-bit mask — a literal built from the `ALLEN` constants, or a mask
447
- * parameter (`$.allenParam`). Vacuous masks (empty/full) are the engine's
463
+ * parameter (`r.maskParam`). Vacuous masks (empty/full) are the engine's
448
464
  * two distinct typed rejections; nothing is pre-judged here beyond the
449
465
  * representable bit range.
450
466
  */
451
- function allen<
452
- const A extends IntervalInput,
453
- const M extends number | MaskParam<string>,
454
- const B extends IntervalInput
455
- >(
467
+ function allen<const A extends IntervalSide, const M extends number | MaskParam<string>, const B extends IntervalSide>(
456
468
  left: A,
457
469
  mask: M,
458
470
  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
471
+ ): Cmp<"allen", A, B, M> {
472
+ assertTermSide("allen", left, right)
473
+ if (typeof mask === "number" && (!Number.isInteger(mask) || mask < 0 || mask > ALLEN_ALL_BITS)) {
474
+ throw errors.new(
475
+ `allen mask ${mask} is not a 13-bit mask — build masks from the ALLEN constants (bumbledb allen.rs: bits above the low 13 are unrepresentable)`
477
476
  )
478
477
  }
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
- )
478
+ return comparison("allen", left, right, mask)
487
479
  }
488
480
 
489
481
  /**
@@ -492,14 +484,8 @@ function allen<
492
484
  * nesting under `or`, and the empty combination keeps the IR's algebraic
493
485
  * reading (`And([])` is true).
494
486
  */
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
- })
487
+ function and<const C extends readonly AnyTreeChild[]>(...children: C): Tree<C> {
488
+ return Object.freeze({ cond: "tree", op: "and", children: Object.freeze(children) })
503
489
  }
504
490
 
505
491
  /**
@@ -508,49 +494,214 @@ function and<const C extends readonly AnyCondition[]>(
508
494
  * to DNF rules engine-side (OR is data or it is nothing). `Or([])` keeps
509
495
  * its algebraic reading (false: the rule denotes nothing).
510
496
  */
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
- })
497
+ function or<const C extends readonly AnyTreeChild[]>(...children: C): Tree<C> {
498
+ return Object.freeze({ cond: "tree", op: "or", children: Object.freeze(children) })
519
499
  }
520
500
 
521
501
  /**
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.
502
+ * Negation anti-join over sets: `not(Rel, { field: r.var("x"), ... })`
503
+ * rejects every binding some matching fact extends. A negated atom binds
504
+ * nothing, only rejects: every variable it names must be positively bound
505
+ * in the rule, which `.where`'s environment check makes a COMPILE error
506
+ * (the engine's safety refusal stands behind it). A CLOSED owner is legal
507
+ * here too — the engine folds a resolvable negated closed atom to the
508
+ * COMPLEMENT of its member set (domain-witness guarded), and the SDK's
509
+ * negation rules apply to it unchanged.
525
510
  */
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 })
511
+ function not<R extends MatchOwner, const B extends MatchShape<MatchFields<R>>>(
512
+ relation: R,
513
+ bindings: B
514
+ ): NotAtom<R, B> {
515
+ const value: NotAtom<R, B> = { cond: "not", relation, bindings }
516
+ return Object.freeze(value)
533
517
  }
534
518
 
519
+ /** Whether a var name is bound in the environment at an orderable (u64/i64) field. */
520
+ type OrderVarOk<Env extends EnvShape, N extends string> = N extends keyof Env
521
+ ? Env[N]["field"]["kind"] extends "u64" | "i64"
522
+ ? true
523
+ : false
524
+ : false
525
+
526
+ /** Whether a var name is bound at an interval field. */
527
+ type IntervalVarOk<Env extends EnvShape, N extends string> = N extends keyof Env
528
+ ? Env[N]["field"]["kind"] extends "interval"
529
+ ? true
530
+ : false
531
+ : false
532
+
533
+ /** One order-comparison side's judgment against the environment. */
534
+ type OrderSideOk<Env extends EnvShape, T> =
535
+ T extends Var<infer N extends string>
536
+ ? OrderVarOk<Env, N>
537
+ : T extends Duration<infer N extends string>
538
+ ? IntervalVarOk<Env, N>
539
+ : true
540
+
541
+ /** One point side's judgment. */
542
+ type PointSideOk<Env extends EnvShape, T> = T extends Var<infer N extends string> ? OrderVarOk<Env, N> : true
543
+
544
+ /** One interval side's judgment. */
545
+ type IntervalSideOk<Env extends EnvShape, T> = T extends Var<infer N extends string> ? IntervalVarOk<Env, N> : true
546
+
547
+ /** The `eq`/`ne` judgment: left var bound; right joins it (class-equal var, param, or an exact-type literal). */
548
+ type EqOk<Env extends EnvShape, L, R> =
549
+ L extends Var<infer N extends string>
550
+ ? N extends keyof Env
551
+ ? R extends Var<infer M extends string>
552
+ ? M extends keyof Env
553
+ ? JoinOk<Env[N], Env[M]>
554
+ : false
555
+ : R extends Param<string> | SetParam<string>
556
+ ? true
557
+ : [R] extends [Infer<Env[N]["field"]>]
558
+ ? true
559
+ : false
560
+ : false
561
+ : false
562
+
563
+ /** One negated-atom binding's judgment: a var must be positively bound (safety) AND class-equal. */
564
+ type NotBindingOk<Env extends EnvShape, S extends ClassedField, T> =
565
+ T extends Var<infer N extends string> ? (N extends keyof Env ? JoinOk<Env[N], S> : false) : true
566
+
567
+ /** The whole negated atom's judgment (`CR` — the negated relation's class record off the schema class map). */
568
+ type NotOk<Env extends EnvShape, F extends FieldsShape, CR, B> = false extends {
569
+ [K in keyof B]: NotBindingOk<Env, SlotAt<F, CR, K>, B[K]>
570
+ }[keyof B]
571
+ ? false
572
+ : true
573
+
574
+ /**
575
+ * One condition's judgment against the rule environment — the type-level
576
+ * twin of the engine's comparison roster: class-equal joins (off the
577
+ * schema type's class map), orderable order sides (an interval var under a
578
+ * non-`pointIn` op is exactly here refused), kind-correct
579
+ * `pointIn`/`covers`/`allen` sides, and negated-atom safety (the negated
580
+ * relation's class record is resolved through `Classes` by its name). The
581
+ * leading `[AnyTreeChild] extends [C]` arm is the recursion's base case:
582
+ * at an UNRESOLVED constraint (the whole condition union — or a tree's
583
+ * child union, which is the union itself) the judgment is vacuously true —
584
+ * without it the constraint instantiation recurses into itself.
585
+ */
586
+ type CondOkBool<Env extends EnvShape, Classes extends SchemaClasses, C> = [AnyTreeChild] extends [C]
587
+ ? true
588
+ : C extends Cmp<infer Op, infer L, infer R, unknown>
589
+ ? Op extends "eq" | "ne"
590
+ ? EqOk<Env, L, R>
591
+ : Op extends "lt" | "le" | "gt" | "ge"
592
+ ? [OrderSideOk<Env, L>, OrderSideOk<Env, R>] extends [true, true]
593
+ ? true
594
+ : false
595
+ : Op extends "pointIn"
596
+ ? [IntervalSideOk<Env, L>, PointSideOk<Env, R>] extends [true, true]
597
+ ? true
598
+ : false
599
+ : Op extends "allen"
600
+ ? [IntervalSideOk<Env, L>, IntervalSideOk<Env, R>] extends [true, true]
601
+ ? true
602
+ : false
603
+ : false
604
+ : C extends Tree<infer Ch extends readonly AnyTreeChild[]>
605
+ ? false extends CondOkBool<Env, Classes, Ch[number]>
606
+ ? false
607
+ : true
608
+ : C extends NotAtom<infer R extends MatchOwner, infer B>
609
+ ? NotOk<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
610
+ : false
611
+
612
+ /** The validated `.where` argument (intersect with the inferred condition type). */
613
+ type CheckCond<Env extends EnvShape, Classes extends SchemaClasses, C> =
614
+ CondOkBool<Env, Classes, C> extends true ? C : never
615
+
616
+ /** The `eq`/`ne` params contribution: the param typed by the left variable's field. */
617
+ type EqParams<Env extends EnvShape, L, R> =
618
+ L extends Var<infer N extends string>
619
+ ? R extends Param<infer P extends string>
620
+ ? { readonly [Q in P]: Infer<Env[N & keyof Env]["field"]> }
621
+ : R extends SetParam<infer P extends string>
622
+ ? { readonly [Q in P]: readonly Infer<Env[N & keyof Env]["field"]>[] }
623
+ : never
624
+ : never
625
+
626
+ /** An order side's params contribution (order params are always `bigint`). */
627
+ type OrderSideParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: bigint } : never
628
+
629
+ /** An interval side's params contribution. */
630
+ type IntervalSideParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: IntervalValue } : never
631
+
632
+ /** The mask position's params contribution. */
633
+ type MaskParams<M> = M extends MaskParam<infer P extends string> ? { readonly [Q in P]: number } : never
634
+
635
+ /**
636
+ * One condition's params-object fragments (a union; the rule builder folds
637
+ * them into the inferred `Params` record) — every param typed by its use.
638
+ * The leading arm is the same base case as {@link CondOkBool}'s: the
639
+ * unresolved constraint contributes nothing.
640
+ */
641
+ type CondParams<Env extends EnvShape, C> = [AnyTreeChild] extends [C]
642
+ ? never
643
+ : C extends Cmp<infer Op, infer L, infer R, infer M>
644
+ ? Op extends "eq" | "ne"
645
+ ? EqParams<Env, L, R>
646
+ : Op extends "lt" | "le" | "gt" | "ge"
647
+ ? OrderSideParams<L> | OrderSideParams<R>
648
+ : Op extends "pointIn"
649
+ ? IntervalSideParams<L> | OrderSideParams<R>
650
+ : Op extends "allen"
651
+ ? IntervalSideParams<L> | IntervalSideParams<R> | MaskParams<M>
652
+ : never
653
+ : C extends Tree<infer Ch extends readonly AnyTreeChild[]>
654
+ ? CondParams<Env, Ch[number]>
655
+ : C extends NotAtom<infer R extends MatchOwner, infer B>
656
+ ? BindParams<MatchFields<R>, B>
657
+ : never
658
+
659
+ /** The flattened params record one bindings record contributes. */
660
+ type BindParamsShape<F extends FieldsShape, B> = ShapeOf<BindParams<F, B>>
661
+
662
+ /** The flattened params record one condition contributes. */
663
+ type CondParamsShape<Env extends EnvShape, C> = ShapeOf<CondParams<Env, C>>
664
+
535
665
  export type {
536
- AnyBodyItem,
537
- AnyCondition,
538
- AtomSourceData,
666
+ AggData,
667
+ AnyCmp,
668
+ AnyCond,
669
+ AnyNotAtom,
670
+ AnyTreeChild,
671
+ AtomData,
672
+ BindEnv,
539
673
  BindingEntry,
540
- BindingsParams,
541
- BindingTerm,
542
- CmpOpData,
543
- CmpTerm,
544
- ComparisonItem,
545
- ConditionTreeItem,
546
- Duration,
547
- IntervalInput,
674
+ BindingInput,
675
+ BindingTermData,
676
+ BindParams,
677
+ BindParamsShape,
678
+ CheckBindings,
679
+ CheckCond,
680
+ Cmp,
681
+ CmpData,
682
+ CmpKind,
683
+ CmpTermData,
684
+ CondData,
685
+ CondOkBool,
686
+ CondParams,
687
+ CondParamsShape,
688
+ IntervalSide,
689
+ IntervalVarOk,
548
690
  MaskData,
549
- MatchAtom,
550
- MatchInput,
551
- OrderInput,
552
- PointInput,
553
- PointTermInput,
554
- TermInput
691
+ MatchFields,
692
+ MatchOwner,
693
+ MatchShape,
694
+ NotAtom,
695
+ OrderSide,
696
+ OrderVarOk,
697
+ ParamUse,
698
+ PointSide,
699
+ RecData,
700
+ RuleData,
701
+ RuleItem,
702
+ SelectColumn,
703
+ SelectEntryData,
704
+ Tree,
705
+ TreeData
555
706
  }
556
- export { ALLEN, allen, and, covers, duration, ge, gt, is, le, lt, match, ne, not, or, resolveBindings }
707
+ export { ALLEN, allen, and, comparison, covers, eq, ge, gt, le, lt, ne, not, or, pointIn }