@bjornpagen/bumbledb 0.2.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 +462 -406
- package/README.md +66 -31
- package/dist/closed.d.ts +121 -25
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +108 -42
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +12 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -5
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +100 -55
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +36 -10
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +50 -79
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +20 -53
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +17 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- 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 -10
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +34 -23
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +6 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +88 -50
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +4 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +71 -56
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +90 -43
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +10 -9
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +2 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/scope.d.ts +76 -41
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +77 -30
- package/dist/query/scope.js.map +1 -1
- package/dist/query/select.d.ts +5 -5
- package/dist/query/select.d.ts.map +1 -1
- package/dist/relation.d.ts +21 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +13 -7
- 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 +16 -2
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +7 -6
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +61 -31
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +22 -17
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/closed.ts +243 -68
- package/src/db.ts +23 -5
- package/src/face.ts +162 -84
- package/src/fields.ts +57 -136
- package/src/index.ts +42 -16
- package/src/law.ts +519 -0
- package/src/lower.ts +36 -23
- package/src/native.ts +6 -2
- package/src/query/atom.ts +105 -58
- package/src/query/lower.ts +271 -139
- package/src/query/predicate.ts +43 -33
- package/src/query/scope.ts +125 -49
- package/src/query/select.ts +5 -5
- package/src/relation.ts +15 -9
- package/src/schema.ts +48 -7
- package/src/spec.ts +7 -6
- package/src/statements.ts +83 -43
package/src/query/predicate.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import * as errors from "@superbuilders/errors"
|
|
31
|
+
import type { SchemaClasses } from "#law.ts"
|
|
31
32
|
import type { RecData } from "#query/atom.ts"
|
|
32
33
|
import type {
|
|
33
34
|
AnyRuleValue,
|
|
@@ -61,42 +62,47 @@ interface Rec<
|
|
|
61
62
|
Rels extends SchemaRelations,
|
|
62
63
|
Name extends string,
|
|
63
64
|
P extends ParamsRecord,
|
|
64
|
-
Head extends HeadShape = undefined
|
|
65
|
+
Head extends HeadShape = undefined,
|
|
66
|
+
Classes extends SchemaClasses = SchemaClasses
|
|
65
67
|
> extends RecRef<Name, P, Head> {
|
|
66
68
|
rule<RV extends AnyRuleValue>(
|
|
67
|
-
build: (r: RecRuleScope<Rels, Name>) => RV
|
|
68
|
-
): Rec<Rels, Name, Flatten<P & ParamsOf<RV>>, Head extends undefined ? HeadOf<RV> : Head>
|
|
69
|
+
build: (r: RecRuleScope<Rels, Name, Classes>) => RV
|
|
70
|
+
): Rec<Rels, Name, Flatten<P & ParamsOf<RV>>, Head extends undefined ? HeadOf<RV> : Head, Classes>
|
|
69
71
|
readonly [inferred]?: { readonly params: P; readonly head: Head }
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/** One output-rule builder function. */
|
|
73
|
-
type OutputBuild<Rels extends SchemaRelations
|
|
75
|
+
type OutputBuild<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> = (
|
|
76
|
+
r: OutputRuleScope<Rels, Classes>
|
|
77
|
+
) => AnyRuleValue
|
|
74
78
|
|
|
75
79
|
/** A build function's rule value. */
|
|
76
80
|
type BuiltRule<F> = F extends (r: never) => infer RV ? RV : never
|
|
77
81
|
|
|
78
82
|
/** The union row of a tuple of output builds. */
|
|
79
|
-
type OutputRow<Builds extends readonly OutputBuild<SchemaRelations>[]> = RowOf<BuiltRule<Builds[number]>>
|
|
83
|
+
type OutputRow<Builds extends readonly OutputBuild<SchemaRelations, SchemaClasses>[]> = RowOf<BuiltRule<Builds[number]>>
|
|
80
84
|
|
|
81
85
|
/** The intersected params record of a tuple of output builds. */
|
|
82
|
-
type OutputParams<Builds extends readonly OutputBuild<SchemaRelations>[]> = ShapeOf<
|
|
86
|
+
type OutputParams<Builds extends readonly OutputBuild<SchemaRelations, SchemaClasses>[]> = ShapeOf<
|
|
87
|
+
ParamsOf<BuiltRule<Builds[number]>>
|
|
88
|
+
>
|
|
83
89
|
|
|
84
90
|
/**
|
|
85
91
|
* The program scope: declare recs, attach their rules, then declare the
|
|
86
92
|
* output — which seals the recs (a later `rec`/`rule` is a construction
|
|
87
93
|
* error) and returns the program as an ordinary query value.
|
|
88
94
|
*/
|
|
89
|
-
interface ProgramScope<Rels extends SchemaRelations> {
|
|
95
|
+
interface ProgramScope<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> {
|
|
90
96
|
/** Declares one recursive predicate; declaration order = its dense `PredId`. */
|
|
91
|
-
rec<const Name extends string>(name: Name): Rec<Rels, Name, Record<never, never
|
|
97
|
+
rec<const Name extends string>(name: Name): Rec<Rels, Name, Record<never, never>, undefined, Classes>
|
|
92
98
|
/**
|
|
93
99
|
* Declares the output predicate (one rule per build; multiple rules =
|
|
94
100
|
* set union) and seals the program. Must be what the `program()`
|
|
95
101
|
* callback returns.
|
|
96
102
|
*/
|
|
97
|
-
output<const Builds extends readonly OutputBuild<Rels>[]>(
|
|
103
|
+
output<const Builds extends readonly OutputBuild<Rels, Classes>[]>(
|
|
98
104
|
...builds: Builds
|
|
99
|
-
): Query<Rels, OutputRow<Builds>, OutputParams<Builds
|
|
105
|
+
): Query<Rels, OutputRow<Builds>, OutputParams<Builds>, Classes>
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/** The runtime rec-handle shape beneath the typed `Rec` face. */
|
|
@@ -117,7 +123,7 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
|
|
|
117
123
|
`rec ${name}: the program's output is already declared — recursive rules attach before p.output`
|
|
118
124
|
)
|
|
119
125
|
}
|
|
120
|
-
const built = build(makeRawScope({ kind: "rec", self: data }))
|
|
126
|
+
const built = build(makeRawScope({ kind: "rec", self: data, classes: state.classes }))
|
|
121
127
|
const head = data.rules[0]
|
|
122
128
|
if (head !== undefined) {
|
|
123
129
|
const declared = head.select.map(function columnName(column) {
|
|
@@ -145,21 +151,24 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
|
|
|
145
151
|
* the checkable fact — the handle owns exactly the rec data it names — is
|
|
146
152
|
* verified before the raw handle is admitted at its typed face.
|
|
147
153
|
*/
|
|
148
|
-
function isRecHandle<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
function isRecHandle<
|
|
155
|
+
Rels extends SchemaRelations,
|
|
156
|
+
Name extends string,
|
|
157
|
+
P extends ParamsRecord,
|
|
158
|
+
Classes extends SchemaClasses
|
|
159
|
+
>(data: RecData, rec: RawRec<Name>): rec is RawRec<Name> & Rec<Rels, Name, P, undefined, Classes> {
|
|
152
160
|
return rec.data === data
|
|
153
161
|
}
|
|
154
162
|
|
|
155
163
|
/** Builds one typed rec handle over shared rec data. */
|
|
156
|
-
function makeRec<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
function makeRec<
|
|
165
|
+
Rels extends SchemaRelations,
|
|
166
|
+
Name extends string,
|
|
167
|
+
P extends ParamsRecord,
|
|
168
|
+
Classes extends SchemaClasses
|
|
169
|
+
>(state: ProgramState, name: Name, data: RecData): Rec<Rels, Name, P, undefined, Classes> {
|
|
161
170
|
const raw = makeRawRec<Name>(state, name, data)
|
|
162
|
-
if (!isRecHandle<Rels, Name, P>(data, raw)) {
|
|
171
|
+
if (!isRecHandle<Rels, Name, P, Classes>(data, raw)) {
|
|
163
172
|
throw errors.new(`rec ${name}: handle construction incomplete`)
|
|
164
173
|
}
|
|
165
174
|
return raw
|
|
@@ -172,15 +181,16 @@ function makeRec<Rels extends SchemaRelations, Name extends string, P extends Pa
|
|
|
172
181
|
* the one `ProgramIr` shape the engine executes under the per-stratum
|
|
173
182
|
* fixpoint driver.
|
|
174
183
|
*/
|
|
175
|
-
function program<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
function program<
|
|
185
|
+
Rels extends SchemaRelations,
|
|
186
|
+
Classes extends SchemaClasses,
|
|
187
|
+
Q extends Query<Rels, unknown, ParamsRecord, Classes>
|
|
188
|
+
>(theory: Schema<Rels, Classes>, build: (p: ProgramScope<Rels, Classes>) => Q): Q {
|
|
189
|
+
const state: ProgramState = { recs: [], classes: theory.classes, sealed: false }
|
|
180
190
|
const names = new Set<string>()
|
|
181
191
|
const made: { query: unknown } = { query: undefined }
|
|
182
|
-
const scope: ProgramScope<Rels> = {
|
|
183
|
-
rec<const Name extends string>(name: Name): Rec<Rels, Name, Record<never, never
|
|
192
|
+
const scope: ProgramScope<Rels, Classes> = {
|
|
193
|
+
rec<const Name extends string>(name: Name): Rec<Rels, Name, Record<never, never>, undefined, Classes> {
|
|
184
194
|
if (state.sealed) {
|
|
185
195
|
throw errors.new(`program: the output is already declared — rec ${name} would be unreachable`)
|
|
186
196
|
}
|
|
@@ -192,11 +202,11 @@ function program<Rels extends SchemaRelations, Q extends Query<Rels, unknown, Pa
|
|
|
192
202
|
names.add(name)
|
|
193
203
|
const data: RecData = { name, rules: [] }
|
|
194
204
|
state.recs.push(data)
|
|
195
|
-
return makeRec<Rels, Name, Record<never, never
|
|
205
|
+
return makeRec<Rels, Name, Record<never, never>, Classes>(state, name, data)
|
|
196
206
|
},
|
|
197
|
-
output<const Builds extends readonly OutputBuild<Rels>[]>(
|
|
207
|
+
output<const Builds extends readonly OutputBuild<Rels, Classes>[]>(
|
|
198
208
|
...builds: Builds
|
|
199
|
-
): Query<Rels, OutputRow<Builds>, OutputParams<Builds
|
|
209
|
+
): Query<Rels, OutputRow<Builds>, OutputParams<Builds>, Classes> {
|
|
200
210
|
if (state.sealed) {
|
|
201
211
|
throw errors.new("program: output is declared once — multiple rules are multiple builds of the one output")
|
|
202
212
|
}
|
|
@@ -213,9 +223,9 @@ function program<Rels extends SchemaRelations, Q extends Query<Rels, unknown, Pa
|
|
|
213
223
|
throw errors.new("program: the output needs at least one rule")
|
|
214
224
|
}
|
|
215
225
|
const rules = builds.map(function buildRule(buildOne) {
|
|
216
|
-
return buildOne(makeOutputRuleScope<Rels>(state)).rule
|
|
226
|
+
return buildOne(makeOutputRuleScope<Rels, Classes>(state)).rule
|
|
217
227
|
})
|
|
218
|
-
const q = makeQuery<Rels, OutputRow<Builds>, OutputParams<Builds
|
|
228
|
+
const q = makeQuery<Rels, OutputRow<Builds>, OutputParams<Builds>, Classes>(theory, state.recs, rules)
|
|
219
229
|
made.query = q
|
|
220
230
|
return q
|
|
221
231
|
}
|
package/src/query/scope.ts
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Query scope terms,
|
|
2
|
+
* Query scope terms, LAW-TYPED edition: string-named variables and
|
|
3
3
|
* parameters as plain frozen values. A `Var` is a NAME — it is typed by the
|
|
4
|
-
* field it first binds (
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* and
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* the
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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.
|
|
17
20
|
*/
|
|
18
21
|
|
|
22
|
+
import * as errors from "@superbuilders/errors"
|
|
19
23
|
import type { AnyField, Infer } from "#fields.ts"
|
|
20
24
|
|
|
21
25
|
/**
|
|
@@ -106,6 +110,57 @@ function makeVar<const Name extends string>(name: Name): Var<Name> {
|
|
|
106
110
|
return Object.freeze(value)
|
|
107
111
|
}
|
|
108
112
|
|
|
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> }
|
|
115
|
+
|
|
116
|
+
/**
|
|
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).
|
|
122
|
+
*/
|
|
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
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
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.
|
|
144
|
+
*/
|
|
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
|
+
}
|
|
163
|
+
|
|
109
164
|
/** Builds one scalar-parameter term. */
|
|
110
165
|
function makeParam<const Name extends string>(name: Name): Param<Name> {
|
|
111
166
|
const value: Param<Name> = { [term]: "param", name }
|
|
@@ -131,11 +186,24 @@ function makeDuration<const Name extends string>(name: Name): Duration<Name> {
|
|
|
131
186
|
}
|
|
132
187
|
|
|
133
188
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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.
|
|
195
|
+
*/
|
|
196
|
+
interface ClassedField {
|
|
197
|
+
readonly field: AnyField
|
|
198
|
+
readonly class: string | undefined
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
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`
|
|
136
204
|
* record, and the two are built by the same walk.
|
|
137
205
|
*/
|
|
138
|
-
type EnvShape = Record<string,
|
|
206
|
+
type EnvShape = Record<string, ClassedField>
|
|
139
207
|
|
|
140
208
|
/** A params object type — what `execute` takes and inference carries. */
|
|
141
209
|
type ParamsRecord = Readonly<Record<string, unknown>>
|
|
@@ -154,9 +222,6 @@ type UnionToIntersection<U> = (U extends unknown ? (member: U) => void : never)
|
|
|
154
222
|
*/
|
|
155
223
|
type ShapeOf<U> = [U] extends [never] ? Record<never, never> : Flatten<UnionToIntersection<U>>
|
|
156
224
|
|
|
157
|
-
/** Reads a field descriptor's domain label (S1: the label IS the domain check). */
|
|
158
|
-
type DomainOf<F extends AnyField> = F["domain"]
|
|
159
|
-
|
|
160
225
|
/** Reads a field descriptor's width label (`bytes<N>`, `interval<E, W>`); `undefined` when the kind carries none. */
|
|
161
226
|
type WidthOf<F extends AnyField> = F extends { readonly width: infer W } ? W : undefined
|
|
162
227
|
|
|
@@ -164,43 +229,52 @@ type WidthOf<F extends AnyField> = F extends { readonly width: infer W } ? W : u
|
|
|
164
229
|
type ElementOf<F extends AnyField> = F extends { readonly element: infer E } ? E : undefined
|
|
165
230
|
|
|
166
231
|
/**
|
|
167
|
-
* The
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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.
|
|
171
238
|
*/
|
|
172
|
-
type JoinOk<A extends
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
WidthOf<
|
|
176
|
-
ElementOf<
|
|
177
|
-
]
|
|
178
|
-
? [B["kind"],
|
|
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
|
+
]
|
|
179
251
|
? true
|
|
180
252
|
: false
|
|
181
253
|
: false
|
|
182
254
|
|
|
183
255
|
/**
|
|
184
|
-
* The runtime twin of {@link JoinOk}: two
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
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.
|
|
190
262
|
*/
|
|
191
|
-
function fieldJoins(a:
|
|
192
|
-
const widthA = "width" in a ? a.width : undefined
|
|
193
|
-
const widthB = "width" in b ? b.width : undefined
|
|
194
|
-
const elementA = "element" in a ? a.element : undefined
|
|
195
|
-
const elementB = "element" in b ? b.element : undefined
|
|
196
|
-
return a.kind === b.kind && a.
|
|
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
|
|
197
269
|
}
|
|
198
270
|
|
|
199
271
|
/**
|
|
200
|
-
* Renders one
|
|
201
|
-
* grammar's
|
|
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`).
|
|
202
275
|
*/
|
|
203
|
-
function renderFieldKind(
|
|
276
|
+
function renderFieldKind(slot: ClassedField): string {
|
|
277
|
+
const field = slot.field
|
|
204
278
|
let base: string = field.kind
|
|
205
279
|
if (field.kind === "bytes") {
|
|
206
280
|
base = `bytes<${field.width}>`
|
|
@@ -208,7 +282,7 @@ function renderFieldKind(field: AnyField): string {
|
|
|
208
282
|
if (field.kind === "interval") {
|
|
209
283
|
base = field.width === undefined ? `interval<${field.element}>` : `interval<${field.element}, ${field.width}>`
|
|
210
284
|
}
|
|
211
|
-
return
|
|
285
|
+
return slot.class === undefined ? `${base} (bare)` : `${base} in class ${slot.class}`
|
|
212
286
|
}
|
|
213
287
|
|
|
214
288
|
/**
|
|
@@ -241,7 +315,7 @@ interface ParamEntry {
|
|
|
241
315
|
|
|
242
316
|
export type {
|
|
243
317
|
AnyTerm,
|
|
244
|
-
|
|
318
|
+
ClassedField,
|
|
245
319
|
Duration,
|
|
246
320
|
EnvShape,
|
|
247
321
|
Flatten,
|
|
@@ -255,7 +329,8 @@ export type {
|
|
|
255
329
|
SetParam,
|
|
256
330
|
ShapeOf,
|
|
257
331
|
UnionToIntersection,
|
|
258
|
-
Var
|
|
332
|
+
Var,
|
|
333
|
+
VarsRecord
|
|
259
334
|
}
|
|
260
335
|
export {
|
|
261
336
|
fieldJoins,
|
|
@@ -266,6 +341,7 @@ export {
|
|
|
266
341
|
makeParam,
|
|
267
342
|
makeSetParam,
|
|
268
343
|
makeVar,
|
|
344
|
+
makeVars,
|
|
269
345
|
renderFieldKind,
|
|
270
346
|
term
|
|
271
347
|
}
|
package/src/query/select.ts
CHANGED
|
@@ -166,7 +166,7 @@ type CheckNameSelect<Env extends EnvShape, S> = {
|
|
|
166
166
|
* type.
|
|
167
167
|
*/
|
|
168
168
|
type SelectEntryRow<Env extends EnvShape, E> = E extends string
|
|
169
|
-
? { readonly [K in E]: Infer<Env[E & keyof Env]> }
|
|
169
|
+
? { readonly [K in E]: Infer<Env[E & keyof Env]["field"]> }
|
|
170
170
|
: E extends Duration<infer N extends string>
|
|
171
171
|
? { readonly [K in N]: bigint }
|
|
172
172
|
: E extends Agg<"count", undefined>
|
|
@@ -175,14 +175,14 @@ type SelectEntryRow<Env extends EnvShape, E> = E extends string
|
|
|
175
175
|
? { readonly [K in O]: bigint }
|
|
176
176
|
: E extends Agg<"sum" | "min" | "max", infer O>
|
|
177
177
|
? O extends string
|
|
178
|
-
? { readonly [K in O]: Infer<Env[O & keyof Env]> }
|
|
178
|
+
? { readonly [K in O]: Infer<Env[O & keyof Env]["field"]> }
|
|
179
179
|
: O extends Duration<infer N extends string>
|
|
180
180
|
? { readonly [K in N]: bigint }
|
|
181
181
|
: never
|
|
182
182
|
: E extends Agg<"argMax" | "argMin", infer O extends string, string>
|
|
183
|
-
? { readonly [K in O]: Infer<Env[O & keyof Env]> }
|
|
183
|
+
? { readonly [K in O]: Infer<Env[O & keyof Env]["field"]> }
|
|
184
184
|
: E extends Agg<"pack", infer O extends string>
|
|
185
|
-
? { readonly [K in O]: Infer<Env[O & keyof Env]> }
|
|
185
|
+
? { readonly [K in O]: Infer<Env[O & keyof Env]["field"]> }
|
|
186
186
|
: never
|
|
187
187
|
|
|
188
188
|
/** The inferred answer-row object type of a select tuple. */
|
|
@@ -194,7 +194,7 @@ type RowOfSelect<Env extends EnvShape, S extends readonly SelectEntry[]> = Shape
|
|
|
194
194
|
* smears into one column's type), mirroring {@link SelectEntryRow}.
|
|
195
195
|
*/
|
|
196
196
|
type NameSelectRow<Env extends EnvShape, N> = N extends string
|
|
197
|
-
? { readonly [K in N]: Infer<Env[K & keyof Env]> }
|
|
197
|
+
? { readonly [K in N]: Infer<Env[K & keyof Env]["field"]> }
|
|
198
198
|
: never
|
|
199
199
|
|
|
200
200
|
/** The inferred answer-row object type of a names-only (recursive-rule) select tuple. */
|
package/src/relation.ts
CHANGED
|
@@ -57,7 +57,10 @@ function resolveEntry(field: AnyField, entry: unknown): LiteralSetSpec {
|
|
|
57
57
|
* Resolves a whole `where()` selection against the declared fields, in the
|
|
58
58
|
* selection's written order (macro parity: σ is spelled, not sorted). An
|
|
59
59
|
* empty selection is the bare relation respelled and rejected (the
|
|
60
|
-
* canonical-utterance law).
|
|
60
|
+
* canonical-utterance law). THE one selection resolver — `closed()`'s
|
|
61
|
+
* `where()` resolves its payload columns through this same machine (a
|
|
62
|
+
* `ClosedColumn` is structurally a {@link RelationField}), so both surfaces
|
|
63
|
+
* share one vocabulary and one error voice.
|
|
61
64
|
*/
|
|
62
65
|
function resolveSelection(
|
|
63
66
|
name: string,
|
|
@@ -91,8 +94,8 @@ type FieldsShape = Record<string, AnyField>
|
|
|
91
94
|
/**
|
|
92
95
|
* A typed field reference (`Account.fields.holder`) — the value statements,
|
|
93
96
|
* selections, and queries address a field through. Purely positional
|
|
94
|
-
* (relation name + field name); the field's descriptor
|
|
95
|
-
*
|
|
97
|
+
* (relation name + field name); the field's descriptor is read off the
|
|
98
|
+
* relation's schema type structurally.
|
|
96
99
|
*/
|
|
97
100
|
interface FieldRef<Rel extends string, Name extends string> {
|
|
98
101
|
readonly relation: Rel
|
|
@@ -186,11 +189,14 @@ type FreshKeys<R extends AnyRelation> = {
|
|
|
186
189
|
type InsertFact<R extends AnyRelation> = Flatten<Omit<Fact<R>, FreshKeys<R>> & Partial<Pick<Fact<R>, FreshKeys<R>>>>
|
|
187
190
|
|
|
188
191
|
/**
|
|
189
|
-
* Declares one relation: `relation("Account", { id:
|
|
190
|
-
* holder:
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
192
|
+
* Declares one relation: `relation("Account", { id: u64.fresh,
|
|
193
|
+
* holder: u64, kind: Kind.id, ... })` — every field is a pure-structure
|
|
194
|
+
* descriptor (the constructor values themselves; domains are never
|
|
195
|
+
* declared: `schema()` computes them from the statements). Field
|
|
196
|
+
* declaration order is ordinal-id order (macro parity), carried at BOTH
|
|
197
|
+
* levels: the type level by the fields object, the value level by the
|
|
198
|
+
* frozen `data.fields` list — the law the schema-level class naming leans
|
|
199
|
+
* on. The returned value is frozen and side-effect free.
|
|
194
200
|
*/
|
|
195
201
|
function relation<const Name extends string, Fields extends FieldsShape>(
|
|
196
202
|
name: Name,
|
|
@@ -243,4 +249,4 @@ export type {
|
|
|
243
249
|
SelectionBinding,
|
|
244
250
|
SelectionInput
|
|
245
251
|
}
|
|
246
|
-
export { relation }
|
|
252
|
+
export { relation, resolveSelection }
|
package/src/schema.ts
CHANGED
|
@@ -13,6 +13,7 @@ import * as errors from "@superbuilders/errors"
|
|
|
13
13
|
import type { AnyClosed } from "#closed.ts"
|
|
14
14
|
import type { FaceData } from "#face.ts"
|
|
15
15
|
import { type AnyField, assertDeclarationOrderKey, type ClosedRoster } from "#fields.ts"
|
|
16
|
+
import { type ClassesOf, classesComplete, computeClasses, type LawfulStatements, type SchemaClasses } from "#law.ts"
|
|
16
17
|
import type { AnyRelation } from "#relation.ts"
|
|
17
18
|
import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
|
|
18
19
|
import { renderStatement, type Statement } from "#statements.ts"
|
|
@@ -128,7 +129,7 @@ function verifyBindingHandles(
|
|
|
128
129
|
}
|
|
129
130
|
if (roster === undefined) {
|
|
130
131
|
throw errors.new(
|
|
131
|
-
`schema ${name}: ${face.owner.name}.${binding.field} is not a closed-relation reference — the handle literal ${literal.handle} is legal only on a field
|
|
132
|
+
`schema ${name}: ${face.owner.name}.${binding.field} is not a closed-relation reference — the handle literal ${literal.handle} is legal only on a field carrying a closed relation's roster — ${rendered}`
|
|
132
133
|
)
|
|
133
134
|
}
|
|
134
135
|
if (!roster.handles.includes(literal.handle)) {
|
|
@@ -247,16 +248,43 @@ type SchemaRelation = AnyRelation | AnyClosed
|
|
|
247
248
|
/** The relation record a schema is generic over — what `Db` and queries key on. */
|
|
248
249
|
type SchemaRelations = Record<string, SchemaRelation>
|
|
249
250
|
|
|
250
|
-
/**
|
|
251
|
-
|
|
251
|
+
/**
|
|
252
|
+
* A theory value: named relations, the DECLARED dependency statements, and
|
|
253
|
+
* the LAW-COMPUTED class map (`classes` — relation → field → class name,
|
|
254
|
+
* `undefined` = bare). The class map is THE domain authority: `schema()`
|
|
255
|
+
* computes it FROM the statement list at both tiers (the type through
|
|
256
|
+
* {@link ClassesOf}, the value through the union-find twin), queries
|
|
257
|
+
* compare class names off it, and the wire lowering emits it as the spec
|
|
258
|
+
* `newtype` labels. Nothing is ever synthesized: `statements` is exactly
|
|
259
|
+
* the declared list, in written order.
|
|
260
|
+
*/
|
|
261
|
+
interface Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> {
|
|
252
262
|
readonly name: string
|
|
253
263
|
readonly relations: Rels
|
|
254
264
|
readonly statements: readonly Statement[]
|
|
265
|
+
readonly classes: Classes
|
|
255
266
|
}
|
|
256
267
|
|
|
257
268
|
/** Any schema value, whatever its relation record. */
|
|
258
269
|
type AnySchema = Schema<SchemaRelations>
|
|
259
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Forces the class map to EVALUATE at the `schema()` boundary: a two-level
|
|
273
|
+
* mapped copy, type-identical to `C` — but instantiation resolves it to
|
|
274
|
+
* the finished relation → field → class record, so hovering a schema value
|
|
275
|
+
* (or anything carrying its `Classes` parameter — queries, `Db`) shows the
|
|
276
|
+
* computed record instead of the unevaluated `ClassesOf<...>` application
|
|
277
|
+
* dragging the whole statement-tuple type along. The conditional wrapper
|
|
278
|
+
* is the display mechanism, not a judgment: resolving it drops the alias
|
|
279
|
+
* reference, so tsc renders the finished record (measured against tsc's
|
|
280
|
+
* own type rendering; a bare mapped alias still displays by name).
|
|
281
|
+
* Display-only by construction; `classesComplete` guards the same type at
|
|
282
|
+
* the value tier.
|
|
283
|
+
*/
|
|
284
|
+
type EvaluatedClasses<C extends SchemaClasses> = C extends SchemaClasses
|
|
285
|
+
? { readonly [N in keyof C]: { readonly [F in keyof C[N]]: C[N][F] } }
|
|
286
|
+
: never
|
|
287
|
+
|
|
260
288
|
/**
|
|
261
289
|
* Assembles a theory:
|
|
262
290
|
* `schema("Ledger", { Kind, Account, Holder }, [ ...statements ])`.
|
|
@@ -277,12 +305,21 @@ type AnySchema = Schema<SchemaRelations>
|
|
|
277
305
|
* list: the engine materializes them itself, in its own pinned order
|
|
278
306
|
* (`SchemaDescriptor::materialized_statements`), and restating them would
|
|
279
307
|
* double them.
|
|
308
|
+
*
|
|
309
|
+
* THE LAW-TYPING happens here too (rulings 2/3 — the laws type the
|
|
310
|
+
* columns): the statement list induces the equivalence classes over field
|
|
311
|
+
* slots, at the TYPE level ({@link ClassesOf} — spell the statement list
|
|
312
|
+
* inline so the tuple type stays precise) and at runtime (the union-find
|
|
313
|
+
* twin), and the one-generator-per-class wall holds at both tiers — the
|
|
314
|
+
* {@link LawfulStatements} verdict lands the compile error on the
|
|
315
|
+
* statements argument; `computeClasses` throws the same content naming the
|
|
316
|
+
* exact statement.
|
|
280
317
|
*/
|
|
281
|
-
function schema<const Rels extends SchemaRelations>(
|
|
318
|
+
function schema<const Rels extends SchemaRelations, const Stmts extends readonly Statement[]>(
|
|
282
319
|
name: string,
|
|
283
320
|
relations: Rels,
|
|
284
|
-
statements:
|
|
285
|
-
): Schema<Rels
|
|
321
|
+
statements: Stmts & LawfulStatements<Rels, Stmts>
|
|
322
|
+
): Schema<Rels, EvaluatedClasses<ClassesOf<Rels, Stmts>>> {
|
|
286
323
|
const implied = collectImplied(name, relations)
|
|
287
324
|
const seen = new Set<string>()
|
|
288
325
|
for (const statement of statements) {
|
|
@@ -300,7 +337,11 @@ function schema<const Rels extends SchemaRelations>(
|
|
|
300
337
|
verifyHandles(name, statement, rendered)
|
|
301
338
|
}
|
|
302
339
|
verifyClosedReferences(name, statements)
|
|
303
|
-
|
|
340
|
+
const classes = computeClasses(name, relations, statements)
|
|
341
|
+
if (!classesComplete<EvaluatedClasses<ClassesOf<Rels, Stmts>>>(classes, relations)) {
|
|
342
|
+
throw errors.new(`schema ${name}: class-map construction incomplete`)
|
|
343
|
+
}
|
|
344
|
+
return Object.freeze({ name, relations, statements: Object.freeze([...statements]), classes })
|
|
304
345
|
}
|
|
305
346
|
|
|
306
347
|
export type { AnySchema, Schema, SchemaRelation, SchemaRelations }
|
package/src/spec.ts
CHANGED
|
@@ -95,10 +95,10 @@ type WindowSpec =
|
|
|
95
95
|
| { readonly kind: "floor"; readonly lo: bigint }
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
* One field: name, structural type, host newtype name — the
|
|
99
|
-
* DOMAIN
|
|
100
|
-
* carried for handle resolution only, dropped by the engine at
|
|
101
|
-
* lowering and never fingerprinted — and the `fresh` mint mark.
|
|
98
|
+
* One field: name, structural type, host newtype name — the field's
|
|
99
|
+
* DOMAIN (the macro's declared `as NewType`; the SDK's law-computed class
|
|
100
|
+
* name), carried for handle resolution only, dropped by the engine at
|
|
101
|
+
* descriptor lowering and never fingerprinted — and the `fresh` mint mark.
|
|
102
102
|
*/
|
|
103
103
|
interface FieldSpec {
|
|
104
104
|
readonly name: string
|
|
@@ -121,8 +121,9 @@ interface RowSpec {
|
|
|
121
121
|
* kind); a closed relation's `fields` are its declared intrinsic columns
|
|
122
122
|
* only — the synthetic (`id`, u64) handle field is materialized by the
|
|
123
123
|
* engine's schema validation. `newtype` is the handle newtype of a closed
|
|
124
|
-
* relation (the SDK emits
|
|
125
|
-
* label every referencing field carries
|
|
124
|
+
* relation (the SDK emits the id's law-computed generator class,
|
|
125
|
+
* `` `${name}.id` `` — the same label every referencing field carries by
|
|
126
|
+
* law), undefined on an ordinary one.
|
|
126
127
|
*/
|
|
127
128
|
interface RelationSpec {
|
|
128
129
|
readonly name: string
|