@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.
- package/COOKBOOK.md +1450 -0
- package/README.md +69 -30
- package/dist/closed.d.ts +157 -38
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +232 -47
- package/dist/closed.js.map +1 -1
- package/dist/count.d.ts +64 -17
- package/dist/count.d.ts.map +1 -1
- package/dist/count.js +37 -14
- package/dist/count.js.map +1 -1
- package/dist/db.d.ts +27 -8
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +33 -10
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts +12 -11
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +7 -6
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +135 -30
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +44 -19
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +130 -130
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +60 -94
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +29 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -14
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +224 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +224 -0
- package/dist/law.js.map +1 -0
- package/dist/lower.d.ts +17 -7
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +66 -24
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +36 -21
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +41 -25
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +6 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +2 -2
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +370 -174
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +75 -172
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +312 -109
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +906 -387
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +70 -88
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +118 -72
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +19 -16
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +31 -24
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +178 -127
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +111 -108
- package/dist/query/scope.js.map +1 -1
- package/dist/query/select.d.ts +102 -80
- package/dist/query/select.d.ts.map +1 -1
- package/dist/query/select.js +39 -34
- package/dist/query/select.js.map +1 -1
- package/dist/relation.d.ts +48 -38
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +24 -19
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +41 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +34 -9
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +7 -5
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +91 -36
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +42 -12
- package/dist/statements.js.map +1 -1
- package/package.json +4 -3
- package/src/closed.ts +448 -95
- package/src/count.ts +112 -18
- package/src/db.ts +45 -17
- package/src/exhume.ts +12 -11
- package/src/face.ts +211 -41
- package/src/fields.ts +199 -234
- package/src/index.ts +78 -61
- package/src/law.ts +519 -0
- package/src/lower.ts +82 -27
- package/src/marshal.ts +48 -30
- package/src/native.ts +8 -4
- package/src/query/atom.ts +525 -374
- package/src/query/lower.ts +1469 -538
- package/src/query/predicate.ts +208 -161
- package/src/query/run.ts +35 -25
- package/src/query/scope.ts +264 -218
- package/src/query/select.ts +168 -93
- package/src/relation.ts +47 -47
- package/src/schema.ts +69 -18
- package/src/spec.ts +7 -5
- package/src/statements.ts +124 -41
- package/dist/brand.d.ts +0 -59
- package/dist/brand.d.ts.map +0 -1
- package/dist/brand.js +0 -47
- package/dist/brand.js.map +0 -1
- package/src/brand.ts +0 -82
package/src/query/scope.ts
CHANGED
|
@@ -1,301 +1,347 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Query scope terms
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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 {
|
|
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
|
|
26
|
-
*
|
|
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
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
-
|
|
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
|
|
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 —
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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
|
|
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)
|
|
60
|
-
* execution to
|
|
61
|
-
* position matches iff the field value is in the set. Legal
|
|
62
|
-
* bindings (positive and negated) and as the right side of `
|
|
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
|
|
66
|
-
readonly [term]: "
|
|
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`)
|
|
76
|
-
* relation as a bind-time argument
|
|
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
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 =
|
|
100
|
+
type AnyTerm = Var | Param | SetParam | MaskParam | Duration
|
|
93
101
|
|
|
94
|
-
/** Narrows an unknown
|
|
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
|
-
/**
|
|
100
|
-
|
|
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
|
-
/**
|
|
103
|
-
type
|
|
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
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
*
|
|
134
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
139
|
-
|
|
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
|
-
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
/**
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
*
|
|
175
|
-
*
|
|
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
|
-
|
|
178
|
-
|
|
196
|
+
interface ClassedField {
|
|
197
|
+
readonly field: AnyField
|
|
198
|
+
readonly class: string | undefined
|
|
179
199
|
}
|
|
180
200
|
|
|
181
201
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
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
|
-
|
|
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
|
|
214
|
-
|
|
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
|
-
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
/**
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
/**
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
/**
|
|
260
|
-
|
|
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
|
-
/**
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
318
|
+
ClassedField,
|
|
319
|
+
Duration,
|
|
320
|
+
EnvShape,
|
|
288
321
|
Flatten,
|
|
289
|
-
|
|
322
|
+
InferredOf,
|
|
323
|
+
JoinOk,
|
|
290
324
|
MaskParam,
|
|
291
325
|
Param,
|
|
292
326
|
ParamEntry,
|
|
293
|
-
ParamSet,
|
|
294
327
|
ParamsRecord,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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 }
|