@bjornpagen/bumbledb 0.1.0 → 0.2.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 +1394 -0
- package/README.md +4 -0
- package/dist/closed.d.ts +60 -37
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +159 -40
- 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 +15 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +13 -5
- 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 +79 -19
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +14 -15
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +158 -129
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +80 -81
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +21 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/lower.d.ts +6 -3
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +48 -17
- 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.js +2 -2
- package/dist/query/atom.d.ts +332 -174
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +72 -172
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +295 -107
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +860 -388
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +69 -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 +139 -123
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +71 -115
- 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 +33 -36
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +15 -16
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +19 -8
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +6 -5
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +56 -31
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +38 -13
- package/dist/statements.js.map +1 -1
- package/package.json +4 -3
- package/src/closed.ts +271 -93
- package/src/count.ts +112 -18
- package/src/db.ts +22 -12
- package/src/exhume.ts +12 -11
- package/src/face.ts +114 -22
- package/src/fields.ts +261 -217
- package/src/index.ts +50 -59
- package/src/lower.ts +62 -20
- package/src/marshal.ts +48 -30
- package/src/native.ts +2 -2
- package/src/query/atom.ts +480 -376
- package/src/query/lower.ts +1341 -542
- package/src/query/predicate.ts +198 -161
- package/src/query/run.ts +35 -25
- package/src/query/scope.ts +188 -218
- package/src/query/select.ts +168 -93
- package/src/relation.ts +38 -44
- package/src/schema.ts +22 -12
- package/src/spec.ts +6 -5
- package/src/statements.ts +69 -26
- 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/select.ts
CHANGED
|
@@ -1,106 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Select entries and aggregates
|
|
3
|
-
* the IR's aggregate roster exactly
|
|
4
|
-
* `
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* maximal segment), the result
|
|
2
|
+
* Select entries and aggregates, STRUCTURAL edition — the head vocabulary,
|
|
3
|
+
* mirroring the IR's aggregate roster exactly
|
|
4
|
+
* (`bumbledb/crates/bumbledb/src/ir.rs` `AggOp`/`FindTerm`;
|
|
5
|
+
* `docs/architecture/20-query-ir.md` § aggregation): `count` (nullary),
|
|
6
|
+
* `countDistinct`, `sum`/`min`/`max` (over an orderable variable or the
|
|
7
|
+
* measure), `argMax`/`argMin` (arg-restriction: carried value + orderable
|
|
8
|
+
* key; a tie yields every attaining row), and `pack` (the coalescing fold —
|
|
9
|
+
* RELATION-SHAPED: one answer row per (group, maximal segment), the result
|
|
10
|
+
* position interval-typed). Aggregates name their variables — `r.sum("m")`
|
|
11
|
+
* — and are typed by the rule environment at `.select`. Grouping is
|
|
10
12
|
* implicit: the non-aggregate select entries are the group key; over empty
|
|
11
13
|
* input an all-aggregate select yields the EMPTY SET, never a zero row.
|
|
14
|
+
* The creation quarantine is representational: a head position is a var
|
|
15
|
+
* name, the measure, or an aggregate — no minting or arithmetic term
|
|
16
|
+
* exists to spell (permanent law).
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
16
|
-
import type { Duration } from "#query/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/** The three folds the measure admits (and the plain-variable folds). */
|
|
21
|
-
type FoldOp = "sum" | "min" | "max"
|
|
22
|
-
|
|
23
|
-
/** One aggregate's runtime description. */
|
|
24
|
-
type AggregateData =
|
|
25
|
-
| { readonly op: "count" }
|
|
26
|
-
| { readonly op: "countDistinct"; readonly over: AnyVar }
|
|
27
|
-
| { readonly op: "fold"; readonly fold: FoldOp; readonly over: AnyVar | Duration }
|
|
28
|
-
| {
|
|
29
|
-
readonly op: "arg"
|
|
30
|
-
readonly direction: "argMax" | "argMin"
|
|
31
|
-
readonly key: AnyVar
|
|
32
|
-
readonly over: AnyVar
|
|
33
|
-
}
|
|
34
|
-
| { readonly op: "pack"; readonly over: AnyVar }
|
|
19
|
+
import type { Infer } from "#fields.ts"
|
|
20
|
+
import type { IntervalVarOk, OrderVarOk } from "#query/atom.ts"
|
|
21
|
+
import type { Duration, EnvShape, ShapeOf } from "#query/scope.ts"
|
|
22
|
+
|
|
23
|
+
/** One aggregate operator name of the select vocabulary. */
|
|
24
|
+
type AggOpName = "count" | "countDistinct" | "sum" | "min" | "max" | "argMax" | "argMin" | "pack"
|
|
35
25
|
|
|
36
26
|
/**
|
|
37
|
-
* One aggregate select
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
27
|
+
* One aggregate select VALUE: the op, the variable name (or measure) it
|
|
28
|
+
* folds, and — for the Arg forms — the orderable key's variable name. The
|
|
29
|
+
* runtime representation carries the types; the rule environment types the
|
|
30
|
+
* result at `.select`.
|
|
41
31
|
*/
|
|
42
|
-
interface
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
interface Agg<
|
|
33
|
+
Op extends AggOpName,
|
|
34
|
+
Over extends string | Duration<string> | undefined,
|
|
35
|
+
Key extends string | undefined = undefined
|
|
36
|
+
> {
|
|
37
|
+
readonly agg: Op
|
|
38
|
+
readonly over: Over
|
|
39
|
+
readonly key: Key
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
/**
|
|
48
|
-
type
|
|
49
|
-
|
|
50
|
-
/** The select record: answer column name to entry, written order = column order. */
|
|
51
|
-
type SelectShape = Readonly<Record<string, SelectEntryInput>>
|
|
42
|
+
/** Any aggregate select value. */
|
|
43
|
+
type AnyAgg = Agg<AggOpName, string | Duration<string> | undefined, string | undefined>
|
|
52
44
|
|
|
53
|
-
/** One select entry
|
|
54
|
-
type
|
|
55
|
-
? Exclude<R, undefined>
|
|
56
|
-
: T extends { readonly measure: AnyVar }
|
|
57
|
-
? bigint
|
|
58
|
-
: T extends { readonly [term]: "var"; readonly [phantom]?: infer V }
|
|
59
|
-
? Exclude<V, undefined>
|
|
60
|
-
: never
|
|
45
|
+
/** One select entry: a projected var name, the measure, or an aggregate. */
|
|
46
|
+
type SelectEntry = string | Duration<string> | AnyAgg
|
|
61
47
|
|
|
62
|
-
/**
|
|
63
|
-
|
|
48
|
+
/** Builds one aggregate value. */
|
|
49
|
+
function aggregate<
|
|
50
|
+
Op extends AggOpName,
|
|
51
|
+
Over extends string | Duration<string> | undefined,
|
|
52
|
+
Key extends string | undefined
|
|
53
|
+
>(op: Op, over: Over, key: Key): Agg<Op, Over, Key> {
|
|
54
|
+
return Object.freeze({ agg: op, over, key })
|
|
55
|
+
}
|
|
64
56
|
|
|
65
|
-
/** Nullary count: |the group's set of distinct full bindings|, `bigint`. */
|
|
66
|
-
function count():
|
|
67
|
-
return
|
|
57
|
+
/** Nullary count: |the group's set of distinct full bindings|, `bigint`; the answer column is named `count`. */
|
|
58
|
+
function count(): Agg<"count", undefined> {
|
|
59
|
+
return aggregate("count", undefined, undefined)
|
|
68
60
|
}
|
|
69
61
|
|
|
70
|
-
/** |the distinct values of
|
|
71
|
-
function countDistinct<
|
|
72
|
-
return
|
|
62
|
+
/** |the distinct values of the named variable across the group|, `bigint`; legal over every type. */
|
|
63
|
+
function countDistinct<const N extends string>(over: N): Agg<"countDistinct", N> {
|
|
64
|
+
return aggregate("countDistinct", over, undefined)
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
/**
|
|
76
|
-
* Exact checked sum over
|
|
77
|
-
* finalize range check
|
|
78
|
-
* never a wrap
|
|
68
|
+
* Exact checked sum over an orderable (u64/i64) variable — wide
|
|
69
|
+
* accumulator, one finalize range check; overflow is the engine's typed
|
|
70
|
+
* runtime error, never a wrap — or over the measure
|
|
71
|
+
* (`r.sum(r.duration("w"))`).
|
|
79
72
|
*/
|
|
80
|
-
function sum<
|
|
81
|
-
|
|
82
|
-
function sum(over: Var<bigint> | Duration): Aggregate<bigint> {
|
|
83
|
-
return Object.freeze({
|
|
84
|
-
aggregate: Object.freeze({ op: "fold" as const, fold: "sum" as const, over })
|
|
85
|
-
})
|
|
73
|
+
function sum<const N extends string | Duration<string>>(over: N): Agg<"sum", N> {
|
|
74
|
+
return aggregate("sum", over, undefined)
|
|
86
75
|
}
|
|
87
76
|
|
|
88
|
-
/** Minimum over
|
|
89
|
-
function min<
|
|
90
|
-
|
|
91
|
-
function min(over: Var<bigint> | Duration): Aggregate<bigint> {
|
|
92
|
-
return Object.freeze({
|
|
93
|
-
aggregate: Object.freeze({ op: "fold" as const, fold: "min" as const, over })
|
|
94
|
-
})
|
|
77
|
+
/** Minimum over an orderable variable or the measure (orderable types only). */
|
|
78
|
+
function min<const N extends string | Duration<string>>(over: N): Agg<"min", N> {
|
|
79
|
+
return aggregate("min", over, undefined)
|
|
95
80
|
}
|
|
96
81
|
|
|
97
|
-
/** Maximum over
|
|
98
|
-
function max<
|
|
99
|
-
|
|
100
|
-
function max(over: Var<bigint> | Duration): Aggregate<bigint> {
|
|
101
|
-
return Object.freeze({
|
|
102
|
-
aggregate: Object.freeze({ op: "fold" as const, fold: "max" as const, over })
|
|
103
|
-
})
|
|
82
|
+
/** Maximum over an orderable variable or the measure (orderable types only). */
|
|
83
|
+
function max<const N extends string | Duration<string>>(over: N): Agg<"max", N> {
|
|
84
|
+
return aggregate("max", over, undefined)
|
|
104
85
|
}
|
|
105
86
|
|
|
106
87
|
/**
|
|
@@ -111,17 +92,13 @@ function max(over: Var<bigint> | Duration): Aggregate<bigint> {
|
|
|
111
92
|
* direction; Arg and fold aggregates never mix (both the engine's typed
|
|
112
93
|
* rules).
|
|
113
94
|
*/
|
|
114
|
-
function
|
|
115
|
-
return
|
|
116
|
-
aggregate: Object.freeze({ op: "arg" as const, direction: "argMax" as const, key, over: value })
|
|
117
|
-
})
|
|
95
|
+
function argMax<const V extends string, const K extends string>(value: V, key: K): Agg<"argMax", V, K> {
|
|
96
|
+
return aggregate("argMax", value, key)
|
|
118
97
|
}
|
|
119
98
|
|
|
120
|
-
/** Arg-restriction toward the minimum of `key`; rules as {@link
|
|
121
|
-
function
|
|
122
|
-
return
|
|
123
|
-
aggregate: Object.freeze({ op: "arg" as const, direction: "argMin" as const, key, over: value })
|
|
124
|
-
})
|
|
99
|
+
/** Arg-restriction toward the minimum of `key`; rules as {@link argMax}. */
|
|
100
|
+
function argMin<const V extends string, const K extends string>(value: V, key: K): Agg<"argMin", V, K> {
|
|
101
|
+
return aggregate("argMin", value, key)
|
|
125
102
|
}
|
|
126
103
|
|
|
127
104
|
/**
|
|
@@ -132,9 +109,107 @@ function argmin<K extends bigint, V>(key: Var<K>, value: Var<V>): Aggregate<V> {
|
|
|
132
109
|
* input's element type. At most one `pack` per select, never beside a
|
|
133
110
|
* fold or an Arg entry (the engine's typed rules).
|
|
134
111
|
*/
|
|
135
|
-
function pack<
|
|
136
|
-
return
|
|
112
|
+
function pack<const N extends string>(over: N): Agg<"pack", N> {
|
|
113
|
+
return aggregate("pack", over, undefined)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A fold input's judgment: an orderable variable, or the measure of an interval variable. */
|
|
117
|
+
type FoldOverOk<Env extends EnvShape, O> = O extends string
|
|
118
|
+
? OrderVarOk<Env, O>
|
|
119
|
+
: O extends Duration<infer N extends string>
|
|
120
|
+
? IntervalVarOk<Env, N>
|
|
121
|
+
: false
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One select entry's judgment against the rule environment: projected
|
|
125
|
+
* names must be bound, the measure and `pack` demand interval-typed
|
|
126
|
+
* variables, folds and Arg keys demand orderable ones.
|
|
127
|
+
*/
|
|
128
|
+
type SelectEntryOk<Env extends EnvShape, E> = E extends string
|
|
129
|
+
? E extends keyof Env
|
|
130
|
+
? true
|
|
131
|
+
: false
|
|
132
|
+
: E extends Duration<infer N extends string>
|
|
133
|
+
? IntervalVarOk<Env, N>
|
|
134
|
+
: E extends Agg<"count", undefined>
|
|
135
|
+
? true
|
|
136
|
+
: E extends Agg<"countDistinct", infer O extends string>
|
|
137
|
+
? O extends keyof Env
|
|
138
|
+
? true
|
|
139
|
+
: false
|
|
140
|
+
: E extends Agg<"sum" | "min" | "max", infer O>
|
|
141
|
+
? FoldOverOk<Env, O>
|
|
142
|
+
: E extends Agg<"argMax" | "argMin", infer O extends string, infer K extends string>
|
|
143
|
+
? [O extends keyof Env ? true : false, OrderVarOk<Env, K>] extends [true, true]
|
|
144
|
+
? true
|
|
145
|
+
: false
|
|
146
|
+
: E extends Agg<"pack", infer O extends string>
|
|
147
|
+
? IntervalVarOk<Env, O>
|
|
148
|
+
: false
|
|
149
|
+
|
|
150
|
+
/** The validated select tuple (intersect with the inferred entries — errors land on the offending argument). */
|
|
151
|
+
type CheckSelect<Env extends EnvShape, S> = {
|
|
152
|
+
readonly [I in keyof S]: SelectEntryOk<Env, S[I]> extends true ? S[I] : never
|
|
137
153
|
}
|
|
138
154
|
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
/** The validated names-only select tuple of a RECURSIVE rule (aggregates and the measure are unwritable there). */
|
|
156
|
+
type CheckNameSelect<Env extends EnvShape, S> = {
|
|
157
|
+
readonly [I in keyof S]: S[I] extends keyof Env ? S[I] : never
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* One select entry's answer-column fragment: the column is named by the
|
|
162
|
+
* variable it projects or folds (`count` names its column `count`), and
|
|
163
|
+
* its type reflects the entry — `count`/`countDistinct` are `bigint`
|
|
164
|
+
* whatever they counted, folds carry their input's type, the measure is
|
|
165
|
+
* `bigint`, the Arg forms carry the payload's type, `pack` its interval
|
|
166
|
+
* type.
|
|
167
|
+
*/
|
|
168
|
+
type SelectEntryRow<Env extends EnvShape, E> = E extends string
|
|
169
|
+
? { readonly [K in E]: Infer<Env[E & keyof Env]> }
|
|
170
|
+
: E extends Duration<infer N extends string>
|
|
171
|
+
? { readonly [K in N]: bigint }
|
|
172
|
+
: E extends Agg<"count", undefined>
|
|
173
|
+
? { readonly count: bigint }
|
|
174
|
+
: E extends Agg<"countDistinct", infer O extends string>
|
|
175
|
+
? { readonly [K in O]: bigint }
|
|
176
|
+
: E extends Agg<"sum" | "min" | "max", infer O>
|
|
177
|
+
? O extends string
|
|
178
|
+
? { readonly [K in O]: Infer<Env[O & keyof Env]> }
|
|
179
|
+
: O extends Duration<infer N extends string>
|
|
180
|
+
? { readonly [K in N]: bigint }
|
|
181
|
+
: never
|
|
182
|
+
: E extends Agg<"argMax" | "argMin", infer O extends string, string>
|
|
183
|
+
? { readonly [K in O]: Infer<Env[O & keyof Env]> }
|
|
184
|
+
: E extends Agg<"pack", infer O extends string>
|
|
185
|
+
? { readonly [K in O]: Infer<Env[O & keyof Env]> }
|
|
186
|
+
: never
|
|
187
|
+
|
|
188
|
+
/** The inferred answer-row object type of a select tuple. */
|
|
189
|
+
type RowOfSelect<Env extends EnvShape, S extends readonly SelectEntry[]> = ShapeOf<SelectEntryRow<Env, S[number]>>
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* One projected name's answer-column fragment — a NAKED parameter, so the
|
|
193
|
+
* judgment distributes per name (the union of a multi-name select never
|
|
194
|
+
* smears into one column's type), mirroring {@link SelectEntryRow}.
|
|
195
|
+
*/
|
|
196
|
+
type NameSelectRow<Env extends EnvShape, N> = N extends string
|
|
197
|
+
? { readonly [K in N]: Infer<Env[K & keyof Env]> }
|
|
198
|
+
: never
|
|
199
|
+
|
|
200
|
+
/** The inferred answer-row object type of a names-only (recursive-rule) select tuple. */
|
|
201
|
+
type RowOfNameSelect<Env extends EnvShape, S extends readonly string[]> = ShapeOf<NameSelectRow<Env, S[number]>>
|
|
202
|
+
|
|
203
|
+
export type {
|
|
204
|
+
Agg,
|
|
205
|
+
AggOpName,
|
|
206
|
+
AnyAgg,
|
|
207
|
+
CheckNameSelect,
|
|
208
|
+
CheckSelect,
|
|
209
|
+
RowOfNameSelect,
|
|
210
|
+
RowOfSelect,
|
|
211
|
+
SelectEntry,
|
|
212
|
+
SelectEntryOk,
|
|
213
|
+
SelectEntryRow
|
|
214
|
+
}
|
|
215
|
+
export { argMax, argMin, count, countDistinct, max, min, pack, sum }
|
package/src/relation.ts
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `relation()` — the ordinary-relation half of the theory's signature. A
|
|
3
3
|
* relation value is a frozen plain object carrying its name, its ordered
|
|
4
|
-
* field
|
|
5
|
-
* field references (`R.fields.holder`), and — since selections are
|
|
6
|
-
* relation's own vocabulary — `where()`, which resolves a selection
|
|
7
|
-
* lowered bindings eagerly (handles
|
|
4
|
+
* field descriptors (declaration order = ordinal ids, the macro's law),
|
|
5
|
+
* typed field references (`R.fields.holder`), and — since selections are
|
|
6
|
+
* the relation's own vocabulary — `where()`, which resolves a selection
|
|
7
|
+
* into lowered bindings eagerly (handles verified against their roster at
|
|
8
8
|
* construction). `Fact<>`/`InsertFact<>` are the inferred row object
|
|
9
|
-
* types
|
|
10
|
-
*
|
|
9
|
+
* types at BARE structural value types (no brands): fresh fields are
|
|
10
|
+
* optional on insert input (omit-to-mint) and present on read
|
|
11
|
+
* (resupply-to-preserve-identity), typed exactly.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import * as errors from "@superbuilders/errors"
|
|
14
|
-
import { phantom } from "#brand.ts"
|
|
15
15
|
import type { OneOf } from "#face.ts"
|
|
16
|
-
import { type AnyField, assertDeclarationOrderKey, type
|
|
16
|
+
import { type AnyField, assertDeclarationOrderKey, type Infer, literalOf } from "#fields.ts"
|
|
17
17
|
import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
|
|
18
18
|
|
|
19
19
|
/** Flattens an intersection into one displayed object type (hover legibility). */
|
|
20
20
|
type Flatten<T> = { [K in keyof T]: T[K] }
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* The one
|
|
23
|
+
* The one trusted seam of `relation()`: the reference record is built by
|
|
24
24
|
* iterating the declared fields, and this guard verifies the checkable
|
|
25
25
|
* facts — one reference per declared field, each carrying its own name —
|
|
26
|
-
* before the record is admitted as the typed {@link FieldRefs}
|
|
27
|
-
*
|
|
28
|
-
* module's single trusted seam, the macro-emission analog.
|
|
26
|
+
* before the record is admitted as the typed {@link FieldRefs} (the
|
|
27
|
+
* macro-emission analog).
|
|
29
28
|
*/
|
|
30
29
|
function refsComplete<RName extends string, Fields extends FieldsShape>(
|
|
31
30
|
refs: Record<string, unknown>,
|
|
@@ -44,7 +43,7 @@ function refsComplete<RName extends string, Fields extends FieldsShape>(
|
|
|
44
43
|
* `oneOf` signature — the one-element set is unwritable); anything else is
|
|
45
44
|
* the bare literal.
|
|
46
45
|
*/
|
|
47
|
-
function resolveEntry(field:
|
|
46
|
+
function resolveEntry(field: AnyField, entry: unknown): LiteralSetSpec {
|
|
48
47
|
if (typeof entry === "object" && entry !== null && "literals" in entry && Array.isArray(entry.literals)) {
|
|
49
48
|
const literals: LiteralSpec[] = entry.literals.map(function lowerSetLiteral(literal: unknown) {
|
|
50
49
|
return Object.freeze(literalOf(field, literal))
|
|
@@ -86,29 +85,29 @@ function resolveSelection(
|
|
|
86
85
|
return Object.freeze(bindings)
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
/** The field block of a relation: field name to field
|
|
88
|
+
/** The field block of a relation: field name to field descriptor. */
|
|
90
89
|
type FieldsShape = Record<string, AnyField>
|
|
91
90
|
|
|
92
91
|
/**
|
|
93
92
|
* A typed field reference (`Account.fields.holder`) — the value statements,
|
|
94
|
-
* selections, and queries address a field through
|
|
95
|
-
* field's
|
|
93
|
+
* selections, and queries address a field through. Purely positional
|
|
94
|
+
* (relation name + field name); the field's descriptor (domain label
|
|
95
|
+
* included) is read off the relation's schema type structurally.
|
|
96
96
|
*/
|
|
97
|
-
interface FieldRef<Rel extends string, Name extends string
|
|
97
|
+
interface FieldRef<Rel extends string, Name extends string> {
|
|
98
98
|
readonly relation: Rel
|
|
99
99
|
readonly field: Name
|
|
100
|
-
readonly [phantom]?: V
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
/** The typed field-reference record of a relation. */
|
|
104
103
|
type FieldRefs<RName extends string, Fields extends FieldsShape> = {
|
|
105
|
-
readonly [K in keyof Fields & string]: FieldRef<RName, K
|
|
104
|
+
readonly [K in keyof Fields & string]: FieldRef<RName, K>
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
/** One declared field: name plus its
|
|
107
|
+
/** One declared field: name plus its descriptor, in declaration order. */
|
|
109
108
|
interface RelationField {
|
|
110
109
|
readonly name: string
|
|
111
|
-
readonly field:
|
|
110
|
+
readonly field: AnyField
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
/** A relation's runtime description. */
|
|
@@ -128,15 +127,14 @@ interface SelectionBinding {
|
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
/**
|
|
131
|
-
* The `where()` argument: per field, a
|
|
132
|
-
* type (a closed handle constant IS such a literal —
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* parameter exists anywhere.
|
|
130
|
+
* The `where()` argument: per field, a bare structural literal of that
|
|
131
|
+
* field's value type (a closed handle constant IS such a literal — a
|
|
132
|
+
* bigint verified against the roster at construction), an `oneOf(a, b,
|
|
133
|
+
* ...)` literal set, or a `span(start, end)` interval literal.
|
|
134
|
+
* Equality-only by construction: no operator parameter exists anywhere.
|
|
137
135
|
*/
|
|
138
136
|
type SelectionInput<Fields extends FieldsShape> = {
|
|
139
|
-
readonly [K in keyof Fields]?:
|
|
137
|
+
readonly [K in keyof Fields]?: Infer<Fields[K]> | OneOf<Infer<Fields[K]>>
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
/** A relation with a selection applied — what `on()` consumes as a σ-carrying source. */
|
|
@@ -163,25 +161,21 @@ interface AnySelected {
|
|
|
163
161
|
}
|
|
164
162
|
|
|
165
163
|
/** Extracts a relation's field block type. */
|
|
166
|
-
type RelationFields<R extends AnyRelation> = R extends Relation<string, infer F> ? F : never
|
|
164
|
+
type RelationFields<R extends AnyRelation> = R extends Relation<string, infer F extends FieldsShape> ? F : never
|
|
167
165
|
|
|
168
166
|
/**
|
|
169
167
|
* The inferred row object type of a relation as READ: every field present,
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* shape.
|
|
168
|
+
* at its BARE structural value type ({@link Infer}). Closed relations have
|
|
169
|
+
* no `Fact` — they are unwritable, and the type constraint refuses them
|
|
170
|
+
* because a closed value lacks the relation shape.
|
|
173
171
|
*/
|
|
174
172
|
type Fact<R extends AnyRelation> = {
|
|
175
|
-
[K in keyof RelationFields<R>]:
|
|
173
|
+
[K in keyof RelationFields<R>]: Infer<RelationFields<R>[K]>
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
/** The field names of `R`
|
|
176
|
+
/** The field names of `R` whose descriptor type carries the fresh mint mark. */
|
|
179
177
|
type FreshKeys<R extends AnyRelation> = {
|
|
180
|
-
[K in keyof RelationFields<R>]: RelationFields<R>[K] extends {
|
|
181
|
-
readonly data: { readonly minted: true }
|
|
182
|
-
}
|
|
183
|
-
? K
|
|
184
|
-
: never
|
|
178
|
+
[K in keyof RelationFields<R>]: RelationFields<R>[K] extends { readonly fresh: true } ? K : never
|
|
185
179
|
}[keyof RelationFields<R>]
|
|
186
180
|
|
|
187
181
|
/**
|
|
@@ -193,10 +187,10 @@ type InsertFact<R extends AnyRelation> = Flatten<Omit<Fact<R>, FreshKeys<R>> & P
|
|
|
193
187
|
|
|
194
188
|
/**
|
|
195
189
|
* Declares one relation: `relation("Account", { id: AccountId.fresh,
|
|
196
|
-
* holder: HolderId, ... })` — every field references a declared
|
|
197
|
-
* (`const AccountId = u64.
|
|
198
|
-
* Field declaration order is ordinal-id order (macro
|
|
199
|
-
* value is frozen and side-effect free.
|
|
190
|
+
* holder: HolderId, ... })` — every field references a declared
|
|
191
|
+
* domain-labeled descriptor (`const AccountId = u64.as("AccountId")`) or a
|
|
192
|
+
* bare constructor. Field declaration order is ordinal-id order (macro
|
|
193
|
+
* parity); the returned value is frozen and side-effect free.
|
|
200
194
|
*/
|
|
201
195
|
function relation<const Name extends string, Fields extends FieldsShape>(
|
|
202
196
|
name: Name,
|
|
@@ -205,7 +199,7 @@ function relation<const Name extends string, Fields extends FieldsShape>(
|
|
|
205
199
|
const ordered: RelationField[] = []
|
|
206
200
|
for (const [fieldName, field] of Object.entries(fields)) {
|
|
207
201
|
assertDeclarationOrderKey(`relation ${name} field`, fieldName)
|
|
208
|
-
ordered.push(Object.freeze({ name: fieldName, field
|
|
202
|
+
ordered.push(Object.freeze({ name: fieldName, field }))
|
|
209
203
|
}
|
|
210
204
|
const data: RelationData = Object.freeze({ name, fields: Object.freeze(ordered) })
|
|
211
205
|
const refs: Record<string, unknown> = {}
|
package/src/schema.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import * as errors from "@superbuilders/errors"
|
|
13
13
|
import type { AnyClosed } from "#closed.ts"
|
|
14
14
|
import type { FaceData } from "#face.ts"
|
|
15
|
-
import { assertDeclarationOrderKey, type
|
|
15
|
+
import { type AnyField, assertDeclarationOrderKey, type ClosedRoster } from "#fields.ts"
|
|
16
16
|
import type { AnyRelation } from "#relation.ts"
|
|
17
17
|
import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
|
|
18
18
|
import { renderStatement, type Statement } from "#statements.ts"
|
|
@@ -38,7 +38,7 @@ function collectImplied(name: string, relations: SchemaRelations): Set<string> {
|
|
|
38
38
|
continue
|
|
39
39
|
}
|
|
40
40
|
for (const declared of member.data.fields) {
|
|
41
|
-
if (declared.field.
|
|
41
|
+
if ("fresh" in declared.field && declared.field.fresh === true) {
|
|
42
42
|
implied.add(`${member.name}(${declared.name}) -> ${member.name}`)
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -74,8 +74,8 @@ function verifyMembership(name: string, relations: SchemaRelations, statement: S
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/** Finds a face's field
|
|
78
|
-
function faceField(face: FaceData, fieldName: string):
|
|
77
|
+
/** Finds a face's field descriptor by name, across both relation kinds. */
|
|
78
|
+
function faceField(face: FaceData, fieldName: string): AnyField | undefined {
|
|
79
79
|
const data = face.owner.data
|
|
80
80
|
if ("handles" in data) {
|
|
81
81
|
const column = data.columns.find(function byName(candidate) {
|
|
@@ -89,6 +89,18 @@ function faceField(face: FaceData, fieldName: string): FieldData | undefined {
|
|
|
89
89
|
return declared?.field
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The roster a field resolves handles through: present exactly on a closed
|
|
94
|
+
* reference descriptor (the structural `closed` property — S1's
|
|
95
|
+
* `ClosedIdField`), absent on every other field kind.
|
|
96
|
+
*/
|
|
97
|
+
function rosterOf(field: AnyField | undefined): ClosedRoster | undefined {
|
|
98
|
+
if (field !== undefined && "closed" in field) {
|
|
99
|
+
return field.closed
|
|
100
|
+
}
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
/** Flattens one binding's literal set into its literals. */
|
|
93
105
|
function bindingLiterals(set: LiteralSetSpec): readonly LiteralSpec[] {
|
|
94
106
|
if (set.kind === "one") {
|
|
@@ -109,20 +121,18 @@ function verifyBindingHandles(
|
|
|
109
121
|
binding: { readonly field: string; readonly set: LiteralSetSpec },
|
|
110
122
|
rendered: string
|
|
111
123
|
): void {
|
|
112
|
-
const
|
|
124
|
+
const roster = rosterOf(faceField(face, binding.field))
|
|
113
125
|
for (const literal of bindingLiterals(binding.set)) {
|
|
114
126
|
if (literal.kind !== "handle") {
|
|
115
127
|
continue
|
|
116
128
|
}
|
|
117
|
-
if (
|
|
129
|
+
if (roster === undefined) {
|
|
118
130
|
throw errors.new(
|
|
119
|
-
`schema ${name}: ${face.owner.name}.${binding.field} is not a closed-relation reference — the handle literal ${literal.handle} is legal only on a field whose
|
|
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 whose domain is a closed relation's handle domain — ${rendered}`
|
|
120
132
|
)
|
|
121
133
|
}
|
|
122
|
-
if (!
|
|
123
|
-
throw errors.new(
|
|
124
|
-
`schema ${name}: closed relation ${field.closed.name} has no handle ${literal.handle} — ${rendered}`
|
|
125
|
-
)
|
|
134
|
+
if (!roster.handles.includes(literal.handle)) {
|
|
135
|
+
throw errors.new(`schema ${name}: closed relation ${roster.name} has no handle ${literal.handle} — ${rendered}`)
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
}
|
|
@@ -216,7 +226,7 @@ function verifyClosedReferenceBinding(
|
|
|
216
226
|
if (!spellsHandle) {
|
|
217
227
|
return
|
|
218
228
|
}
|
|
219
|
-
const roster = faceField(face, binding.field)
|
|
229
|
+
const roster = rosterOf(faceField(face, binding.field))
|
|
220
230
|
if (roster === undefined) {
|
|
221
231
|
return
|
|
222
232
|
}
|
package/src/spec.ts
CHANGED
|
@@ -95,9 +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
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* One field: name, structural type, host newtype name — the descriptor's
|
|
99
|
+
* DOMAIN label (the macro's `as NewType`; the SDK's `.as("Domain")`),
|
|
100
|
+
* carried for handle resolution only, dropped by the engine at descriptor
|
|
101
|
+
* lowering and never fingerprinted — and the `fresh` mint mark.
|
|
101
102
|
*/
|
|
102
103
|
interface FieldSpec {
|
|
103
104
|
readonly name: string
|
|
@@ -120,8 +121,8 @@ interface RowSpec {
|
|
|
120
121
|
* kind); a closed relation's `fields` are its declared intrinsic columns
|
|
121
122
|
* only — the synthetic (`id`, u64) handle field is materialized by the
|
|
122
123
|
* engine's schema validation. `newtype` is the handle newtype of a closed
|
|
123
|
-
* relation (the SDK
|
|
124
|
-
* ordinary one.
|
|
124
|
+
* relation (the SDK emits its handle DOMAIN, `` `${name}Id` `` — the same
|
|
125
|
+
* label every referencing field carries), undefined on an ordinary one.
|
|
125
126
|
*/
|
|
126
127
|
interface RelationSpec {
|
|
127
128
|
readonly name: string
|