@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
@@ -1,301 +1,347 @@
1
1
  /**
2
- * Query scope terms (PRD-08): typed variables and parameters, declared
3
- * inside a `query()` scope callback so identity is lexical. A `Var` is
4
- * typed by the field it is declared from and joins are brand-equal by
5
- * construction (the nominal join discipline); var identity is OBJECT
6
- * identity two `$.var` calls are two variables even from the same field,
7
- * and no name-collision machinery exists. Params are query-global, carry a
8
- * mandatory literal name (the key of `execute`'s params object), and lower
9
- * to dense positional `ParamId`s in declaration order. This module also
10
- * owns the type-level params extraction: every atom and condition value
11
- * carries its contributed params object in a phantom, and the query's
12
- * `Params` type is the flattened intersection over the returned rules
13
- * inference rides the return value, never a mutable type accumulator.
2
+ * Query scope terms, LAW-TYPED edition: string-named variables and
3
+ * parameters as plain frozen values. A `Var` is a NAME it is typed by the
4
+ * field slot it first binds (the descriptor AND the slot's law-computed
5
+ * CLASS, read off the schema's class map through the rule builder's
6
+ * environment), reuse of the name within one rule IS the join, and a
7
+ * class-mismatched reuse is a compile error: a var joins only class-equal
8
+ * slots, and BARE PAIRS ONLY WITH BARE (ruling 3 a slot in no law has no
9
+ * class and never joins a classed slot; the deliberate sum-domain pointer
10
+ * stays legal against other bare slots). Params are query-global by name
11
+ * and typed BY USE: the field position or comparison sibling that anchors
12
+ * a param types it, the query's inferred `Params` object is exactly the
13
+ * params the rules use, and a param value that no rule uses simply never
14
+ * registers — the query executes under its own inferred type (the
15
+ * bug-hunt law). This module also owns the environment/typing utilities
16
+ * the whole surface shares: the env shape (var name → classed slot), the
17
+ * class-equality judgment {@link JoinOk} with its runtime twin
18
+ * {@link fieldJoins}, and the record-folding helpers `Params` and `Row`
19
+ * inference ride.
14
20
  */
15
21
 
16
22
  import * as errors from "@superbuilders/errors"
17
- import { phantom } from "#brand.ts"
18
- import type { AnyClosed } from "#closed.ts"
19
- import type { FieldData } from "#fields.ts"
20
- import type { PredicateData } from "#query/predicate.ts"
21
- import type { AnySchema, SchemaRelation } from "#schema.ts"
23
+ import type { AnyField, Infer } from "#fields.ts"
22
24
 
23
25
  /**
24
26
  * The runtime discriminant of query term values. Host literals (bigints,
25
- * strings, interval objects, `oneOf` sets) never carry it, so "is this
26
- * binding a term or a literal" is one property probe, never a guess.
27
+ * strings, interval objects) never carry it, so "is this position a term
28
+ * or a literal" is one property probe, never a guess.
27
29
  */
28
30
  const term: unique symbol = Symbol("bumbledb.query.term")
29
31
 
30
32
  /**
31
- * A query variable, typed by the field it was declared from
32
- * (`$.var(Holder.fields.id)` `Var<Brand<bigint, "HolderId">>`). Usable in
33
- * any atom position whose field carries the same brand; a brand-mismatched
34
- * placement is a TYPE error. Identity is object identity.
33
+ * The carrier of a value's INFERRED types (a rule's row/params, a query's
34
+ * row/params, a rec's params). The property is never present at runtime —
35
+ * it exists so inference rides plain values without any brand on any
36
+ * field value.
35
37
  */
36
- interface Var<V> {
38
+ const inferred: unique symbol = Symbol("bumbledb.query.inferred")
39
+
40
+ /**
41
+ * A query variable — a NAME. Its type comes from the field it first binds
42
+ * in the rule (the builder's environment); reusing the name joins, and a
43
+ * cross-domain reuse is a compile error. Identity is the name, strictly
44
+ * rule-scoped: the same name in two rules names two unrelated variables
45
+ * (exactly as the IR scopes `VarId`).
46
+ */
47
+ interface Var<Name extends string = string> {
37
48
  readonly [term]: "var"
38
- readonly relation: string
39
- readonly field: string
40
- readonly data: FieldData
41
- readonly [phantom]?: V
49
+ readonly name: Name
42
50
  }
43
51
 
44
52
  /**
45
- * A scalar query parameter — `$.param("root", Holder.fields.id)`. The name
46
- * literal is the key of the typed params object `execute` takes; the value
47
- * is marshaled by the declaring field's structural type at bind.
53
+ * A scalar query parameter — `r.param("root")`. The name is the key of the
54
+ * typed params object `execute` takes; the type is the element type of the
55
+ * position that anchors it (a field binding, or the bound-variable side of
56
+ * a comparison).
48
57
  */
49
- interface Param<Name extends string, V> {
58
+ interface Param<Name extends string = string> {
50
59
  readonly [term]: "param"
51
60
  readonly name: Name
52
- readonly relation: string
53
- readonly field: string
54
- readonly data: FieldData
55
- readonly [phantom]?: V
56
61
  }
57
62
 
58
63
  /**
59
- * A set-valued query parameter (the IR's `ParamSet` term): bound at
60
- * execution to an ARRAY of values of the declaring field's type; a binding
61
- * position matches iff the field value is in the set. Legal in atom
62
- * bindings (positive and negated) and as the right side of `is` — nowhere
63
- * else, exactly as the IR rules it.
64
+ * A set-valued query parameter (the IR's `ParamSet` term) `r.inSet("frontier")`:
65
+ * bound at execution to a readonly ARRAY of values of the anchoring field's
66
+ * type; a binding position matches iff the field value is in the set. Legal
67
+ * in atom bindings (positive and negated) and as the right side of `eq` —
68
+ * nowhere else, exactly as the IR rules it.
64
69
  */
65
- interface ParamSet<Name extends string, V> {
66
- readonly [term]: "paramSet"
70
+ interface SetParam<Name extends string = string> {
71
+ readonly [term]: "setParam"
67
72
  readonly name: Name
68
- readonly relation: string
69
- readonly field: string
70
- readonly data: FieldData
71
- readonly [phantom]?: V
72
73
  }
73
74
 
74
75
  /**
75
- * An Allen-mask parameter (the IR's `MaskTerm::Param`): the temporal
76
- * relation as a bind-time argument one prepared query answers any of the
76
+ * An Allen-mask parameter (the IR's `MaskTerm::Param`) the temporal
77
+ * relation as a bind-time argument: one prepared query answers any of the
77
78
  * mask questions per execution. Bound to a 13-bit mask number built from
78
79
  * the `ALLEN` constants.
79
80
  */
80
- interface MaskParam<Name extends string> {
81
+ interface MaskParam<Name extends string = string> {
81
82
  readonly [term]: "maskParam"
82
83
  readonly name: Name
83
84
  }
84
85
 
85
- /** Any query variable, whatever its brand. */
86
- type AnyVar = Var<unknown>
87
-
88
- /** Any parameter term of the scope, whatever its name and brand. */
89
- type AnyParamTerm = Param<string, unknown> | ParamSet<string, unknown> | MaskParam<string>
86
+ /**
87
+ * The measure of an interval-typed variable (`ir::Term::Measure`):
88
+ * `|[s, e)| = e − s`, u64 — legal as one side of an order comparison, as a
89
+ * select entry, and as the input of `sum`/`min`/`max`; every other position
90
+ * is unwritable, exactly as the IR rejects it typed. A ray has no finite
91
+ * measure — the engine's `MeasureOfRay` execution error; exclude rays first
92
+ * (`allen` against a bounded window).
93
+ */
94
+ interface Duration<Name extends string = string> {
95
+ readonly [term]: "duration"
96
+ readonly name: Name
97
+ }
90
98
 
91
99
  /** Any scope term value. */
92
- type AnyTerm = AnyVar | AnyParamTerm
100
+ type AnyTerm = Var | Param | SetParam | MaskParam | Duration
93
101
 
94
- /** Narrows an unknown binding value to a scope term (vs a host literal). */
102
+ /** Narrows an unknown position value to a scope term (vs a host literal). */
95
103
  function isTerm(value: unknown): value is AnyTerm {
96
104
  return typeof value === "object" && value !== null && term in value
97
105
  }
98
106
 
99
- /** A params object type — what `execute` takes and the phantoms carry. */
100
- type ParamsRecord = Readonly<Record<string, unknown>>
107
+ /** Builds one variable term. */
108
+ function makeVar<const Name extends string>(name: Name): Var<Name> {
109
+ const value: Var<Name> = { [term]: "var", name }
110
+ return Object.freeze(value)
111
+ }
101
112
 
102
- /** Flattens an intersection into one displayed object type (hover legibility). */
103
- type Flatten<T> = { [K in keyof T]: T[K] }
104
-
105
- /** The standard union-to-intersection fold (distributes over `U`). */
106
- type UnionToIntersection<U> = (U extends unknown ? (member: U) => void : never) extends (member: infer I) => void
107
- ? I
108
- : never
113
+ /** The record `makeVars` mints: one own frozen `Var<Name>` per requested name, each typed exactly. */
114
+ type VarsRecord<Names extends string> = { readonly [N in Names]: Var<N> }
109
115
 
110
116
  /**
111
- * One term's contribution to the query's params object type: a `Param`
112
- * contributes its value type under its name, a `ParamSet` the readonly
113
- * array of it, a `MaskParam` a mask number; everything else contributes
114
- * nothing.
117
+ * The trusted seam of the vars mint: every requested name reads back as an
118
+ * own var term of exactly that name verified before the record is
119
+ * admitted at the {@link VarsRecord} type (a name riding the
120
+ * object-protocol accessor instead of an own definition would fail exactly
121
+ * this check).
115
122
  */
116
- type TermContribution<T> = T extends {
117
- readonly [term]: "param"
118
- readonly name: infer N extends string
119
- readonly [phantom]?: infer V
123
+ function varsMinted<Names extends string>(
124
+ record: Readonly<Record<string, unknown>>,
125
+ names: readonly Names[]
126
+ ): record is Readonly<Record<string, unknown>> & VarsRecord<Names> {
127
+ return names.every(function varMinted(name) {
128
+ if (!Object.hasOwn(record, name)) {
129
+ return false
130
+ }
131
+ const value = record[name]
132
+ return isTerm(value) && value[term] === "var" && value.name === name
133
+ })
120
134
  }
121
- ? { readonly [K in N]: Exclude<V, undefined> }
122
- : T extends {
123
- readonly [term]: "paramSet"
124
- readonly name: infer N extends string
125
- readonly [phantom]?: infer V
126
- }
127
- ? { readonly [K in N]: readonly Exclude<V, undefined>[] }
128
- : T extends { readonly [term]: "maskParam"; readonly name: infer N extends string }
129
- ? { readonly [K in N]: number }
130
- : Record<never, never>
131
135
 
132
136
  /**
133
- * Folds a union of per-term/per-item params objects into the one flattened
134
- * params record (the query's `Params` type).
137
+ * Mints several variables at once `const { service, w } = r.vars("service",
138
+ * "w")`: tuple-to-object, each name typed exactly (`Var<"service">`),
139
+ * inference identical to the one-at-a-time `r.var` spelling (one lowering,
140
+ * two entry flavors). Each key is defined as an OWN property (a name like
141
+ * `"__proto__"` is a record key like any other, never a prototype write),
142
+ * and a duplicate name in one call is a construction error: each name mints
143
+ * one variable — write it once and reuse the binding.
135
144
  */
136
- type ParamsShape<U> = [U] extends [never] ? Record<never, never> : Flatten<UnionToIntersection<U>>
145
+ function makeVars<const Names extends readonly string[]>(...names: Names): VarsRecord<Names[number]> {
146
+ const out: Record<string, unknown> = {}
147
+ const seen = new Set<string>()
148
+ for (const name of names) {
149
+ if (seen.has(name)) {
150
+ throw errors.new(
151
+ `vars: duplicate name ${name} — each name mints one variable; write it once and reuse the binding`
152
+ )
153
+ }
154
+ seen.add(name)
155
+ Object.defineProperty(out, name, { value: makeVar(name), enumerable: true })
156
+ }
157
+ Object.freeze(out)
158
+ if (!varsMinted<Names[number]>(out, names)) {
159
+ throw errors.new("vars: variable minting incomplete")
160
+ }
161
+ return out
162
+ }
137
163
 
138
- /** Reads an atom's/condition's contributed params object off its phantom. */
139
- type ItemParams<T> = T extends { readonly [phantom]?: infer P } ? Exclude<P, undefined> : Record<never, never>
164
+ /** Builds one scalar-parameter term. */
165
+ function makeParam<const Name extends string>(name: Name): Param<Name> {
166
+ const value: Param<Name> = { [term]: "param", name }
167
+ return Object.freeze(value)
168
+ }
140
169
 
141
- /** One registered parameter: name, wire shape, and the declaring field. */
142
- interface ParamEntry {
143
- readonly name: string
144
- readonly shape: "value" | "set" | "mask"
145
- readonly data: FieldData | undefined
170
+ /** Builds one set-parameter term. */
171
+ function makeSetParam<const Name extends string>(name: Name): SetParam<Name> {
172
+ const value: SetParam<Name> = { [term]: "setParam", name }
173
+ return Object.freeze(value)
146
174
  }
147
175
 
148
- /**
149
- * The mutable build-time registry one `query()` scope owns: declared vars
150
- * (membership polices cross-scope smuggling), params in declaration order
151
- * (= dense `ParamId`s), and declared predicates in declaration order
152
- * (= dense `PredId`s; the output predicate is appended by lowering).
153
- */
154
- interface QueryRegistry {
155
- readonly theory: AnySchema
156
- readonly vars: Set<AnyVar>
157
- readonly params: ParamEntry[]
158
- readonly paramIndex: Map<AnyTerm, number>
159
- readonly predicates: PredicateData[]
176
+ /** Builds one Allen-mask-parameter term. */
177
+ function makeMaskParam<const Name extends string>(name: Name): MaskParam<Name> {
178
+ const value: MaskParam<Name> = { [term]: "maskParam", name }
179
+ return Object.freeze(value)
160
180
  }
161
181
 
162
- /** Creates one empty scope registry over the query's theory. */
163
- function createRegistry(theory: AnySchema): QueryRegistry {
164
- return {
165
- theory,
166
- vars: new Set(),
167
- params: [],
168
- paramIndex: new Map(),
169
- predicates: []
170
- }
182
+ /** Builds one measure term over an interval-typed variable's name. */
183
+ function makeDuration<const Name extends string>(name: Name): Duration<Name> {
184
+ const value: Duration<Name> = { [term]: "duration", name }
185
+ return Object.freeze(value)
171
186
  }
172
187
 
173
188
  /**
174
- * The relation-kind discriminant: a closed relation's runtime description
175
- * carries its handle roster, an ordinary relation's never does.
189
+ * One bound field slot: the field's descriptor plus the slot's
190
+ * law-computed CLASS (`undefined` = bare the slot is in no law). The one
191
+ * shape the rule environment carries per variable, at the TYPE level (env
192
+ * entries hold the schema type's class-map lookups) and at RUNTIME alike
193
+ * (the rule's `varFields` record holds exactly this shape, read off the
194
+ * schema value's frozen class map) — one shape, two tiers, one walk.
176
195
  */
177
- function isClosedMember(member: SchemaRelation): member is AnyClosed {
178
- return "handles" in member.data
196
+ interface ClassedField {
197
+ readonly field: AnyField
198
+ readonly class: string | undefined
179
199
  }
180
200
 
181
201
  /**
182
- * Resolves a field reference's runtime description through the schema
183
- * the seam that types `$.var`/`$.param` declarations at runtime (the type
184
- * level already carries the brand; this recovers the structural type the
185
- * lowering and the param marshaler direct by).
202
+ * A rule's typing environment: variable name → the classed slot it first
203
+ * bound. Purely a TYPE the runtime twin is the rule's `varFields`
204
+ * record, and the two are built by the same walk.
186
205
  */
187
- function resolveFieldData(theory: AnySchema, relationName: string, fieldName: string): FieldData {
188
- const member: SchemaRelation | undefined = theory.relations[relationName]
189
- if (member === undefined) {
190
- throw errors.new(`schema ${theory.name} has no relation ${relationName}`)
191
- }
192
- if (isClosedMember(member)) {
193
- if (fieldName === "id") {
194
- return member.id.data
195
- }
196
- const column = member.data.columns.find(function byName(candidate) {
197
- return candidate.name === fieldName
198
- })
199
- if (column === undefined) {
200
- throw errors.new(`closed relation ${relationName} has no column ${fieldName}`)
201
- }
202
- return column.field
203
- }
204
- const declared = member.data.fields.find(function byName(candidate) {
205
- return candidate.name === fieldName
206
- })
207
- if (declared === undefined) {
208
- throw errors.new(`relation ${relationName} has no field ${fieldName}`)
209
- }
210
- return declared.field
211
- }
206
+ type EnvShape = Record<string, ClassedField>
212
207
 
213
- /** A field reference's runtime half, as the scope factories consume it. */
214
- interface RefNames {
215
- readonly relation: string
216
- readonly field: string
217
- }
208
+ /** A params object type what `execute` takes and inference carries. */
209
+ type ParamsRecord = Readonly<Record<string, unknown>>
218
210
 
219
- /** Declares one variable in the scope (the `$.var` implementation). */
220
- function scopeVar<V>(registry: QueryRegistry, ref: RefNames): Var<V> {
221
- const value: Var<V> = Object.freeze({
222
- [term]: "var" as const,
223
- relation: ref.relation,
224
- field: ref.field,
225
- data: resolveFieldData(registry.theory, ref.relation, ref.field)
226
- })
227
- registry.vars.add(value)
228
- return value
211
+ /** Flattens an intersection into one displayed object type (hover legibility). */
212
+ type Flatten<T> = { [K in keyof T]: T[K] }
213
+
214
+ /** The standard union-to-intersection fold (distributes over `U`). */
215
+ type UnionToIntersection<U> = (U extends unknown ? (member: U) => void : never) extends (member: infer I) => void
216
+ ? I
217
+ : never
218
+
219
+ /**
220
+ * Folds a union of per-position record fragments into one flattened record
221
+ * (the machinery both `Params` and `Row` inference ride).
222
+ */
223
+ type ShapeOf<U> = [U] extends [never] ? Record<never, never> : Flatten<UnionToIntersection<U>>
224
+
225
+ /** Reads a field descriptor's width label (`bytes<N>`, `interval<E, W>`); `undefined` when the kind carries none. */
226
+ type WidthOf<F extends AnyField> = F extends { readonly width: infer W } ? W : undefined
227
+
228
+ /** Reads an interval descriptor's element kind; `undefined` on scalar kinds. */
229
+ type ElementOf<F extends AnyField> = F extends { readonly element: infer E } ? E : undefined
230
+
231
+ /**
232
+ * The join judgment: two bound slots join iff their descriptors' structure
233
+ * agrees (kind, width label, interval element) AND their law-computed
234
+ * classes agree — same class name joins, and bare (`undefined`) pairs only
235
+ * with bare (ruling 3: a field in no law has no class; a bare↔classed
236
+ * pairing refuses). The class names come off the SCHEMA type's class map —
237
+ * the statements are the typing; no descriptor label exists to compare.
238
+ */
239
+ type JoinOk<A extends ClassedField, B extends ClassedField> = [
240
+ A["field"]["kind"],
241
+ A["class"],
242
+ WidthOf<A["field"]>,
243
+ ElementOf<A["field"]>
244
+ ] extends [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>]
245
+ ? [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>] extends [
246
+ A["field"]["kind"],
247
+ A["class"],
248
+ WidthOf<A["field"]>,
249
+ ElementOf<A["field"]>
250
+ ]
251
+ ? true
252
+ : false
253
+ : false
254
+
255
+ /**
256
+ * The runtime twin of {@link JoinOk}: two bound slots join iff descriptor
257
+ * structure and class agree — the same comparison the type tier makes,
258
+ * judged on the honest runtime values (the descriptor and the schema
259
+ * value's frozen class map). The rule builders throw through this on a
260
+ * class-unequal variable reuse, so the wall holds for untyped callers too,
261
+ * not only where the compiler can see.
262
+ */
263
+ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
264
+ const widthA = "width" in a.field ? a.field.width : undefined
265
+ const widthB = "width" in b.field ? b.field.width : undefined
266
+ const elementA = "element" in a.field ? a.field.element : undefined
267
+ const elementB = "element" in b.field ? b.field.element : undefined
268
+ return a.field.kind === b.field.kind && a.class === b.class && widthA === widthB && elementA === elementB
229
269
  }
230
270
 
231
- /** Rejects a second parameter under an already-taken name. */
232
- function assertFreshParamName(registry: QueryRegistry, name: string): void {
233
- const taken = registry.params.some(function byName(entry) {
234
- return entry.name === name
235
- })
236
- if (taken) {
237
- throw errors.new(
238
- `query scope already declares a param named ${name} — param names key the execute params object, one declaration each`
239
- )
271
+ /**
272
+ * Renders one bound slot for join-mismatch diagnostics — the structural
273
+ * kind in the schema grammar's spelling plus the slot's law-computed class
274
+ * (`u64 in class Holder.id`; a lawless slot renders `bare`).
275
+ */
276
+ function renderFieldKind(slot: ClassedField): string {
277
+ const field = slot.field
278
+ let base: string = field.kind
279
+ if (field.kind === "bytes") {
280
+ base = `bytes<${field.width}>`
281
+ }
282
+ if (field.kind === "interval") {
283
+ base = field.width === undefined ? `interval<${field.element}>` : `interval<${field.element}, ${field.width}>`
240
284
  }
285
+ return slot.class === undefined ? `${base} (bare)` : `${base} in class ${slot.class}`
241
286
  }
242
287
 
243
- /** Declares one scalar parameter (the `$.param` implementation). */
244
- function scopeParam<Name extends string, V>(registry: QueryRegistry, name: Name, ref: RefNames): Param<Name, V> {
245
- assertFreshParamName(registry, name)
246
- const data = resolveFieldData(registry.theory, ref.relation, ref.field)
247
- const value: Param<Name, V> = Object.freeze({
248
- [term]: "param" as const,
249
- name,
250
- relation: ref.relation,
251
- field: ref.field,
252
- data
253
- })
254
- registry.paramIndex.set(value, registry.params.length)
255
- registry.params.push(Object.freeze({ name, shape: "value" as const, data }))
256
- return value
257
- }
288
+ /**
289
+ * What a PARAM anchored at field `F` accepts at execution: the field's
290
+ * bare value type, exactly. At an interval field the engine resolves the
291
+ * bivalent anchor to the INTERVAL reading (value equality) — the point
292
+ * reading of a param is spelled `pointIn(r.param(...), w)`, whose sibling
293
+ * anchors it element-typed.
294
+ */
295
+ type ParamValueAt<F extends AnyField> = Infer<F>
258
296
 
259
- /** Declares one set parameter (the `$.paramSet` implementation). */
260
- function scopeParamSet<Name extends string, V>(registry: QueryRegistry, name: Name, ref: RefNames): ParamSet<Name, V> {
261
- assertFreshParamName(registry, name)
262
- const data = resolveFieldData(registry.theory, ref.relation, ref.field)
263
- const value: ParamSet<Name, V> = Object.freeze({
264
- [term]: "paramSet" as const,
265
- name,
266
- relation: ref.relation,
267
- field: ref.field,
268
- data
269
- })
270
- registry.paramIndex.set(value, registry.params.length)
271
- registry.params.push(Object.freeze({ name, shape: "set" as const, data }))
272
- return value
273
- }
297
+ /** Reads a value's inferred-types carrier (rules, queries, recs). */
298
+ type InferredOf<T> = T extends { readonly [inferred]?: infer S } ? Exclude<S, undefined> : never
274
299
 
275
- /** Declares one Allen-mask parameter (the `$.allenParam` implementation). */
276
- function scopeAllenParam<Name extends string>(registry: QueryRegistry, name: Name): MaskParam<Name> {
277
- assertFreshParamName(registry, name)
278
- const value: MaskParam<Name> = Object.freeze({ [term]: "maskParam" as const, name })
279
- registry.paramIndex.set(value, registry.params.length)
280
- registry.params.push(Object.freeze({ name, shape: "mask" as const, data: undefined }))
281
- return value
300
+ /**
301
+ * One registered parameter of a query, as the wire marshal reads it: the
302
+ * name, the wire shape, the field descriptor (or the measure) that anchored
303
+ * it, and the comparison op the anchor came from (`"binding"` for atom
304
+ * positions) — the op keeps literal tagging op-aware at `pointIn`
305
+ * (the bug-hunt fix, preserved). `anchor` is `undefined` only on a query
306
+ * built but not yet anchored by any rule; lowering and the wire both refuse
307
+ * that state typed.
308
+ */
309
+ interface ParamEntry {
310
+ readonly name: string
311
+ readonly shape: "value" | "set" | "mask"
312
+ readonly anchor: AnyField | "measure" | undefined
313
+ readonly op: "binding" | "eq" | "ne" | "lt" | "le" | "gt" | "ge" | "pointIn" | "allen"
282
314
  }
283
315
 
284
316
  export type {
285
- AnyParamTerm,
286
317
  AnyTerm,
287
- AnyVar,
318
+ ClassedField,
319
+ Duration,
320
+ EnvShape,
288
321
  Flatten,
289
- ItemParams,
322
+ InferredOf,
323
+ JoinOk,
290
324
  MaskParam,
291
325
  Param,
292
326
  ParamEntry,
293
- ParamSet,
294
327
  ParamsRecord,
295
- ParamsShape,
296
- QueryRegistry,
297
- TermContribution,
328
+ ParamValueAt,
329
+ SetParam,
330
+ ShapeOf,
298
331
  UnionToIntersection,
299
- Var
332
+ Var,
333
+ VarsRecord
334
+ }
335
+ export {
336
+ fieldJoins,
337
+ inferred,
338
+ isTerm,
339
+ makeDuration,
340
+ makeMaskParam,
341
+ makeParam,
342
+ makeSetParam,
343
+ makeVar,
344
+ makeVars,
345
+ renderFieldKind,
346
+ term
300
347
  }
301
- export { createRegistry, isTerm, resolveFieldData, scopeAllenParam, scopeParam, scopeParamSet, scopeVar, term }