@bjornpagen/bumbledb 0.7.0 → 0.9.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 +105 -3
- package/README.md +3 -3
- package/dist/capacity.d.ts +310 -0
- package/dist/capacity.d.ts.map +1 -0
- package/dist/capacity.js +138 -0
- package/dist/capacity.js.map +1 -0
- package/dist/db.d.ts +5 -3
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +8 -8
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +1 -1
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +3 -2
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +1 -1
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +3 -2
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +6 -3
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/order.d.ts +53 -2
- package/dist/order.d.ts.map +1 -1
- package/dist/order.js +30 -12
- package/dist/order.js.map +1 -1
- package/dist/query/atom.d.ts +33 -13
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +1 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +19 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +6 -5
- package/dist/query/find.js.map +1 -1
- package/dist/schema.js +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +76 -23
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +39 -7
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +42 -25
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +81 -19
- package/dist/statements.js.map +1 -1
- package/package.json +4 -4
- package/src/capacity.ts +454 -0
- package/src/db.ts +13 -11
- package/src/face.ts +1 -0
- package/src/index.ts +20 -9
- package/src/law.ts +3 -2
- package/src/lower.ts +3 -2
- package/src/native.ts +6 -3
- package/src/order.ts +83 -5
- package/src/query/atom.ts +37 -13
- package/src/query/find.ts +34 -16
- package/src/schema.ts +1 -1
- package/src/spec.ts +91 -25
- package/src/statements.ts +160 -34
- package/dist/count.d.ts +0 -102
- package/dist/count.d.ts.map +0 -1
- package/dist/count.js +0 -115
- package/dist/count.js.map +0 -1
- package/src/count.ts +0 -211
package/src/order.ts
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
* `by(...)` into one row-typed comparator. Cross-type cells cannot arise
|
|
11
11
|
* within one column (one column, one domain), and the cell order is TOTAL
|
|
12
12
|
* anyway (the type-rank wall), so the comparator never throws.
|
|
13
|
+
*
|
|
14
|
+
* ZERO keys is the identity key (ruled 2026-07-25): `by()` / `desc()` are
|
|
15
|
+
* the same two spellings over BARE scalar arrays (`bigint[]` of ids, map
|
|
16
|
+
* keys) — with no column to name, the value itself is the key, there is
|
|
17
|
+
* nothing to fold, and the comparator IS the result. One ordering
|
|
18
|
+
* vocabulary, no sibling names. The identity arms are typed to EXACTLY the
|
|
19
|
+
* engine-orderable roster ({@link EngineOrderable}) because a bare scalar
|
|
20
|
+
* carries no decoded-row provenance — the type wall must carry the
|
|
21
|
+
* orderability law itself, where the row arm's cells arrive under their
|
|
22
|
+
* columns' law-typed proof.
|
|
13
23
|
*/
|
|
14
24
|
|
|
15
25
|
import type { FactValue } from "#native.ts"
|
|
@@ -23,6 +33,23 @@ interface Desc<K extends string> {
|
|
|
23
33
|
/** One sort key: a bare column name (ascending — the punning spelling) or `desc(name)`. */
|
|
24
34
|
type SortKey<K extends string> = K | Desc<K>
|
|
25
35
|
|
|
36
|
+
/**
|
|
37
|
+
* The engine-orderable scalar roster in the SDK's representation — `bigint`
|
|
38
|
+
* (U64/I64) and `boolean` (false < true — the strict 0/1 encoding IS the
|
|
39
|
+
* order), EXACTLY as `docs/architecture/10-data-model.md` § "Orderability,
|
|
40
|
+
* complete" admits it (ruled 2026-07-23, R3/R4), and nothing more:
|
|
41
|
+
* `string` is deliberately absent (intern ids are meaningless to order — a
|
|
42
|
+
* typed refusal, not a gap) and `number` is not an engine scalar at all.
|
|
43
|
+
* The identity comparators `by()`/`desc()` constrain on this alias, so
|
|
44
|
+
* sorting a `string[]` or `number[]` through them is a COMPILE error naming
|
|
45
|
+
* this roster. ONE OWNER for scalar ordering semantics: both identity arms
|
|
46
|
+
* route through the same cell order the row arm uses, whose bigint and
|
|
47
|
+
* boolean arms mirror the engine's U64/I64/Bool order exactly — a host
|
|
48
|
+
* sort over these values can never disagree with an engine order judgment
|
|
49
|
+
* (`Lt`-family, `Min`/`Max`) over the same column.
|
|
50
|
+
*/
|
|
51
|
+
type EngineOrderable = bigint | boolean
|
|
52
|
+
|
|
26
53
|
/**
|
|
27
54
|
* The type-rank wall: boolean 0, bigint 1, string 2, bytes 3, interval 4.
|
|
28
55
|
* One column carries one domain, so a mixed pair never arises from decoded
|
|
@@ -116,8 +143,38 @@ function cellCmp(left: FactValue, right: FactValue): number {
|
|
|
116
143
|
return cellRank(left) - cellRank(right)
|
|
117
144
|
}
|
|
118
145
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The ascending identity comparator `by()` returns — one value, minted
|
|
148
|
+
* once (`by() === by()`), {@link cellCmp} narrowed to the engine-orderable
|
|
149
|
+
* roster (the one owner of the cell order).
|
|
150
|
+
*/
|
|
151
|
+
function identityAscending<T extends EngineOrderable>(left: T, right: T): number {
|
|
152
|
+
return cellCmp(left, right)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The descending identity comparator `desc()` returns — the same owner, sides flipped. */
|
|
156
|
+
function identityDescending<T extends EngineOrderable>(left: T, right: T): number {
|
|
157
|
+
return cellCmp(right, left)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The descending spelling — ONE name, both arities of one ordering
|
|
162
|
+
* vocabulary. `desc(name)` marks one sort key DESCENDING: plain data for
|
|
163
|
+
* {@link by} to fold (a bare name is already ascending). `desc()` — zero
|
|
164
|
+
* keys, the identity key (ruled 2026-07-25) — is the descending comparator
|
|
165
|
+
* over engine-orderable scalars themselves: a bare `bigint[]` or
|
|
166
|
+
* `boolean[]` has no column to name, so the value IS the key, there is
|
|
167
|
+
* nothing to fold, and the comparator is the result directly. The identity
|
|
168
|
+
* arm covers EXACTLY the {@link EngineOrderable} roster — `string` and
|
|
169
|
+
* `number` refuse at compile time, citing the orderability law
|
|
170
|
+
* (`10-data-model.md` § "Orderability, complete").
|
|
171
|
+
*/
|
|
172
|
+
function desc(): <T extends EngineOrderable>(left: T, right: T) => number
|
|
173
|
+
function desc<const K extends string>(key: K): Desc<K>
|
|
174
|
+
function desc<const K extends string>(key?: K): Desc<K> | (<T extends EngineOrderable>(left: T, right: T) => number) {
|
|
175
|
+
if (key === undefined) {
|
|
176
|
+
return identityDescending
|
|
177
|
+
}
|
|
121
178
|
const marker: Desc<K> = { key, desc: true }
|
|
122
179
|
return Object.freeze(marker)
|
|
123
180
|
}
|
|
@@ -130,12 +187,33 @@ function desc<const K extends string>(key: K): Desc<K> {
|
|
|
130
187
|
* carries that proof here (parse-don't-validate). The generic RETURN is the
|
|
131
188
|
* load-bearing trick: `rows.sort(by("rank"))` instantiates `Row` from the
|
|
132
189
|
* array's own element type and checks the key set right there.
|
|
190
|
+
*
|
|
191
|
+
* ZERO keys is the identity key (ruled 2026-07-25): `by()` is the
|
|
192
|
+
* ascending comparator over engine-orderable scalars themselves —
|
|
193
|
+
* `ids.sort(by())` — for the bare arrays (`bigint[]`, map keys) the
|
|
194
|
+
* row-typed arm cannot reach because they have no column to name. One
|
|
195
|
+
* vocabulary, both arities. The identity arm is typed to EXACTLY the
|
|
196
|
+
* {@link EngineOrderable} roster: with no law-typed column standing proof
|
|
197
|
+
* over the values, the type wall carries the orderability law itself —
|
|
198
|
+
* `string` ordering is deliberately refused and `number` is not an engine
|
|
199
|
+
* scalar (`10-data-model.md` § "Orderability, complete"), so both refuse
|
|
200
|
+
* at compile time and a host sort can never disagree with an engine order
|
|
201
|
+
* judgment.
|
|
133
202
|
*/
|
|
203
|
+
function by(): <T extends EngineOrderable>(left: T, right: T) => number
|
|
134
204
|
function by<const K extends string>(
|
|
135
205
|
first: SortKey<K>,
|
|
136
206
|
...rest: ReadonlyArray<SortKey<K>>
|
|
137
|
-
): <Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number
|
|
138
|
-
|
|
207
|
+
): <Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number
|
|
208
|
+
function by<const K extends string>(
|
|
209
|
+
...keys: ReadonlyArray<SortKey<K>>
|
|
210
|
+
):
|
|
211
|
+
| (<T extends EngineOrderable>(left: T, right: T) => number)
|
|
212
|
+
| (<Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number) {
|
|
213
|
+
if (keys.length === 0) {
|
|
214
|
+
return identityAscending
|
|
215
|
+
}
|
|
216
|
+
const entries = keys.map(function normalizeKey(sortKey): { readonly key: K; readonly factor: 1 | -1 } {
|
|
139
217
|
if (typeof sortKey === "string") {
|
|
140
218
|
return { key: sortKey, factor: 1 }
|
|
141
219
|
}
|
|
@@ -152,5 +230,5 @@ function by<const K extends string>(
|
|
|
152
230
|
}
|
|
153
231
|
}
|
|
154
232
|
|
|
155
|
-
export type { Desc, SortKey }
|
|
233
|
+
export type { Desc, EngineOrderable, SortKey }
|
|
156
234
|
export { by, desc }
|
package/src/query/atom.ts
CHANGED
|
@@ -335,8 +335,8 @@ type EqRight = AnyVar | Param<string> | SetParam<string> | bigint | string | boo
|
|
|
335
335
|
/** What `ne`'s right side accepts. */
|
|
336
336
|
type NeRight = AnyVar | Param<string> | bigint | string | boolean | Uint8Array | IntervalValue
|
|
337
337
|
|
|
338
|
-
/** One side of an order comparison: orderable terms only (the IR's comparison rules). */
|
|
339
|
-
type OrderSide = AnyVar | Param<string> | Duration | bigint
|
|
338
|
+
/** One side of an order comparison: orderable terms only (the IR's comparison rules — bool orders, `false < true`, R3). */
|
|
339
|
+
type OrderSide = AnyVar | Param<string> | Duration | bigint | boolean
|
|
340
340
|
|
|
341
341
|
/** The point side of `pointIn`. */
|
|
342
342
|
type PointSide = AnyVar | Param<string> | bigint
|
|
@@ -388,7 +388,7 @@ function order<Op extends "lt" | "le" | "gt" | "ge", const L extends OrderSide,
|
|
|
388
388
|
return comparison(op, left, right, undefined)
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
/** Strict less-than (`ir::CmpOp::Lt`) — orderable sides only, never intervals/bytes/strings
|
|
391
|
+
/** Strict less-than (`ir::CmpOp::Lt`) — orderable sides only (bool included: `false < true`, R3), never intervals/bytes/strings. */
|
|
392
392
|
function lt<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"lt", L, R> {
|
|
393
393
|
return order("lt", left, right)
|
|
394
394
|
}
|
|
@@ -531,19 +531,32 @@ function isRecTarget(value: MatchOwner | RecTarget): value is RecTarget {
|
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
/**
|
|
534
|
-
* Whether a variable's OWN field is
|
|
534
|
+
* Whether a variable's OWN field is NUMERIC (u64/i64) — the judgment the
|
|
535
|
+
* point side of `pointIn` and the `sum` input read: a point lives in the
|
|
536
|
+
* interval's element domain, and a quantifier is not an addition, so bool
|
|
537
|
+
* (orderable, never numeric) is exactly here refused. A CLOSED reference
|
|
535
538
|
* is excluded even though its kind is `u64`: a vocabulary's declaration-id
|
|
536
539
|
* order is an accident, not semantics (`docs/architecture/10-data-model.md`
|
|
537
540
|
* § orderability), so every order-comparison and fold position refuses
|
|
538
|
-
* closed-bound terms —
|
|
539
|
-
*
|
|
541
|
+
* closed-bound terms — the construction-time validations in
|
|
542
|
+
* `#query/lower.ts` are that ban's runtime twin.
|
|
540
543
|
*/
|
|
541
|
-
type
|
|
544
|
+
type NumericVarOk<V extends AnyVar> = V["field"] extends { readonly closed: ClosedRoster }
|
|
542
545
|
? false
|
|
543
546
|
: V["field"]["kind"] extends "u64" | "i64"
|
|
544
547
|
? true
|
|
545
548
|
: false
|
|
546
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Whether a variable's OWN field is ORDERABLE (u64/i64/bool) — the one gate
|
|
552
|
+
* every order-comparison side, `min`/`max` input, and Arg key reads. Bool
|
|
553
|
+
* orders: `false < true`, the strict 0/1 encoding IS the order (ruled R3),
|
|
554
|
+
* exactly the engine's operand screen
|
|
555
|
+
* (`bumbledb/crates/bumbledb/src/ir/validate/context.rs`); bool has no
|
|
556
|
+
* closed variant, so the closed exclusion rides in {@link NumericVarOk}.
|
|
557
|
+
*/
|
|
558
|
+
type OrderVarOk<V extends AnyVar> = V["field"]["kind"] extends "bool" ? true : NumericVarOk<V>
|
|
559
|
+
|
|
547
560
|
/** Whether a variable's OWN field is interval-typed. */
|
|
548
561
|
type IntervalVarOk<V extends AnyVar> = V["field"]["kind"] extends "interval" ? true : false
|
|
549
562
|
|
|
@@ -554,8 +567,8 @@ type OrderSideOk<T> = T extends AnyVar
|
|
|
554
567
|
? IntervalVarOk<V>
|
|
555
568
|
: true
|
|
556
569
|
|
|
557
|
-
/** One point side's judgment. */
|
|
558
|
-
type PointSideOk<T> = T extends AnyVar ?
|
|
570
|
+
/** One point side's judgment (numeric only — a point lives in the interval's element domain, never bool). */
|
|
571
|
+
type PointSideOk<T> = T extends AnyVar ? NumericVarOk<T> : true
|
|
559
572
|
|
|
560
573
|
/** One interval side's judgment. */
|
|
561
574
|
type IntervalSideOk<T> = T extends AnyVar ? IntervalVarOk<T> : true
|
|
@@ -677,8 +690,18 @@ type EqParams<L, R> = L extends AnyVar
|
|
|
677
690
|
: never
|
|
678
691
|
: never
|
|
679
692
|
|
|
680
|
-
/**
|
|
681
|
-
|
|
693
|
+
/**
|
|
694
|
+
* An order side's params contribution, typed by the SIBLING side — the
|
|
695
|
+
* runtime anchors a param to its sibling variable's field, so a bool var
|
|
696
|
+
* orders against a `boolean` param; every var-less pairing stays `bigint`.
|
|
697
|
+
*/
|
|
698
|
+
type OrderSideParams<T, Sib> =
|
|
699
|
+
T extends Param<infer P extends string>
|
|
700
|
+
? { readonly [Q in P]: Sib extends AnyVar ? Infer<Sib["field"]> : bigint }
|
|
701
|
+
: never
|
|
702
|
+
|
|
703
|
+
/** The point side's params contribution (a point is always `bigint`). */
|
|
704
|
+
type PointParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: bigint } : never
|
|
682
705
|
|
|
683
706
|
/** An interval side's params contribution. */
|
|
684
707
|
type IntervalSideParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: IntervalValue } : never
|
|
@@ -697,9 +720,9 @@ type CondParams<C> = [AnyTreeChild] extends [C]
|
|
|
697
720
|
? Op extends "eq" | "ne"
|
|
698
721
|
? EqParams<L, R>
|
|
699
722
|
: Op extends "lt" | "le" | "gt" | "ge"
|
|
700
|
-
? OrderSideParams<L> | OrderSideParams<R>
|
|
723
|
+
? OrderSideParams<L, R> | OrderSideParams<R, L>
|
|
701
724
|
: Op extends "pointIn"
|
|
702
|
-
? IntervalSideParams<L> |
|
|
725
|
+
? IntervalSideParams<L> | PointParams<R>
|
|
703
726
|
: Op extends "allen"
|
|
704
727
|
? IntervalSideParams<L> | IntervalSideParams<R> | MaskParams<M>
|
|
705
728
|
: never
|
|
@@ -749,6 +772,7 @@ export type {
|
|
|
749
772
|
MatchShape,
|
|
750
773
|
NotAtom,
|
|
751
774
|
NotIdbAtom,
|
|
775
|
+
NumericVarOk,
|
|
752
776
|
OrderSide,
|
|
753
777
|
OrderVarOk,
|
|
754
778
|
ParamUse,
|
package/src/query/find.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import type { Infer } from "#fields.ts"
|
|
23
23
|
import type { SchemaClasses } from "#law.ts"
|
|
24
|
-
import type { IntervalVarOk, OrderVarOk } from "#query/atom.ts"
|
|
24
|
+
import type { IntervalVarOk, NumericVarOk, OrderVarOk } from "#query/atom.ts"
|
|
25
25
|
import type { AnyVar, Duration, MintSlotOf } from "#query/scope.ts"
|
|
26
26
|
|
|
27
27
|
/** One aggregate operator name of the find vocabulary. */
|
|
@@ -71,20 +71,21 @@ function countDistinct<const V extends AnyVar>(over: V): Agg<"countDistinct", V>
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Exact checked sum over
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
* Exact checked sum over a NUMERIC (u64/i64) variable — wide accumulator,
|
|
75
|
+
* one finalize range check; overflow is the engine's typed runtime error —
|
|
76
|
+
* or over the measure (`r.sum(r.duration(w))`). Bool stays refused: a
|
|
77
|
+
* quantifier is not an addition (R3).
|
|
77
78
|
*/
|
|
78
79
|
function sum<const O extends AnyVar | Duration>(over: O): Agg<"sum", O> {
|
|
79
80
|
return aggregate("sum", over, undefined)
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
/** Minimum over an orderable variable
|
|
83
|
+
/** Minimum over an orderable variable (u64/i64/bool — over bool, `Min` is the ALL quantifier; R3) or the measure. */
|
|
83
84
|
function min<const O extends AnyVar | Duration>(over: O): Agg<"min", O> {
|
|
84
85
|
return aggregate("min", over, undefined)
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
/** Maximum over an orderable variable
|
|
88
|
+
/** Maximum over an orderable variable (u64/i64/bool — over bool, `Max` is the ANY quantifier; R3) or the measure. */
|
|
88
89
|
function max<const O extends AnyVar | Duration>(over: O): Agg<"max", O> {
|
|
89
90
|
return aggregate("max", over, undefined)
|
|
90
91
|
}
|
|
@@ -116,7 +117,22 @@ function pack<const V extends AnyVar>(over: V): Agg<"pack", V> {
|
|
|
116
117
|
return aggregate("pack", over, undefined)
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* A `sum` input's judgment: a NUMERIC variable — Sum over bool stays
|
|
122
|
+
* refused, a quantifier is not an addition (R3) — or the measure of an
|
|
123
|
+
* interval variable.
|
|
124
|
+
*/
|
|
125
|
+
type SumOverOk<O> = O extends AnyVar
|
|
126
|
+
? NumericVarOk<O>
|
|
127
|
+
: O extends Duration<infer V extends AnyVar>
|
|
128
|
+
? IntervalVarOk<V>
|
|
129
|
+
: false
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A `min`/`max` input's judgment: an ORDERABLE variable — bool folds:
|
|
133
|
+
* `Max` is Any and `Min` is All, the two quantifiers as the 0/1 encoding's
|
|
134
|
+
* extremes (R3) — or the measure of an interval variable.
|
|
135
|
+
*/
|
|
120
136
|
type FoldOverOk<O> = O extends AnyVar
|
|
121
137
|
? OrderVarOk<O>
|
|
122
138
|
: O extends Duration<infer V extends AnyVar>
|
|
@@ -136,15 +152,17 @@ type FindEntryOk<E> = E extends AnyVar
|
|
|
136
152
|
? true
|
|
137
153
|
: E extends Agg<"countDistinct", AnyVar>
|
|
138
154
|
? true
|
|
139
|
-
: E extends Agg<"sum"
|
|
140
|
-
?
|
|
141
|
-
: E extends Agg<"
|
|
142
|
-
?
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
:
|
|
155
|
+
: E extends Agg<"sum", infer O>
|
|
156
|
+
? SumOverOk<O>
|
|
157
|
+
: E extends Agg<"min" | "max", infer O>
|
|
158
|
+
? FoldOverOk<O>
|
|
159
|
+
: E extends Agg<"argMax" | "argMin", AnyVar, infer K extends AnyVar>
|
|
160
|
+
? OrderVarOk<K> extends true
|
|
161
|
+
? true
|
|
162
|
+
: false
|
|
163
|
+
: E extends Agg<"pack", infer V extends AnyVar>
|
|
164
|
+
? IntervalVarOk<V>
|
|
165
|
+
: false
|
|
148
166
|
|
|
149
167
|
/** The validated find record (intersect with the inferred entries — errors land on the offending key). */
|
|
150
168
|
type CheckFind<F extends FindShape> = {
|
package/src/schema.ts
CHANGED
|
@@ -306,7 +306,7 @@ function schema<const Rels extends SchemaRelations, const Stmts extends readonly
|
|
|
306
306
|
*/
|
|
307
307
|
if (!isStatement(statement)) {
|
|
308
308
|
throw errors.new(
|
|
309
|
-
`schema ${name}: a statement is minted only by key/contained/mirrors/
|
|
309
|
+
`schema ${name}: a statement is minted only by key/contained/mirrors/capacity — a structural literal skips the construction-time arity and roster walls`
|
|
310
310
|
)
|
|
311
311
|
}
|
|
312
312
|
const rendered = renderStatement(statement)
|
package/src/spec.ts
CHANGED
|
@@ -72,10 +72,10 @@ type LiteralSetSpec =
|
|
|
72
72
|
| { readonly kind: "many"; readonly literals: readonly LiteralSpec[] }
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* One side of a containment or
|
|
76
|
-
* all names. `projection` is π in the
|
|
77
|
-
* pairing with the other side);
|
|
78
|
-
* pairs, read conjunctively.
|
|
75
|
+
* One side of a containment or capacity statement:
|
|
76
|
+
* `R(fields… | field == literal…)`, all names. `projection` is π in the
|
|
77
|
+
* statement's written order (positional pairing with the other side);
|
|
78
|
+
* `selection` is σ as (field, literal-or-set) pairs, read conjunctively.
|
|
79
79
|
*/
|
|
80
80
|
interface SideSpec {
|
|
81
81
|
readonly relation: string
|
|
@@ -84,16 +84,43 @@ interface SideSpec {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
87
|
+
* One capacity bound: a non-negative literal, a u64 field of the TARGET
|
|
88
|
+
* row (the dependent bound — per-group capacity read at judge time), or
|
|
89
|
+
* the interval-measure of a TARGET-row field (`Duration(span)`). Names,
|
|
90
|
+
* not ids — the spec is the name-level wire; the engine resolves bound
|
|
91
|
+
* names against the target's FULL roster (C1), never the projection.
|
|
92
92
|
*/
|
|
93
|
-
type
|
|
94
|
-
| { readonly kind: "
|
|
95
|
-
| { readonly kind: "
|
|
96
|
-
| { readonly kind: "
|
|
93
|
+
type CapacityBoundSpec =
|
|
94
|
+
| { readonly kind: "lit"; readonly value: bigint }
|
|
95
|
+
| { readonly kind: "field"; readonly field: string }
|
|
96
|
+
| { readonly kind: "durationField"; readonly field: string }
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A capacity statement's weight — a TOTAL sum (C4: `unit` is a case, not
|
|
100
|
+
* an absence): the count instance (`unit`), a u64 field of the SOURCE row
|
|
101
|
+
* (`field`), or a SOURCE-row interval's measure (`durationField`). The
|
|
102
|
+
* wire always carries it — a unit statement crosses as `{ kind: "unit" }`,
|
|
103
|
+
* never by omission.
|
|
104
|
+
*/
|
|
105
|
+
type WeightSpec =
|
|
106
|
+
| { readonly kind: "unit" }
|
|
107
|
+
| { readonly kind: "field"; readonly field: string }
|
|
108
|
+
| { readonly kind: "durationField"; readonly field: string }
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A capacity statement's window — the canonical-utterance law's surviving
|
|
112
|
+
* spellings, per-aggregate where weight-sensitive (design § 6), since the
|
|
113
|
+
* SDK's `within()` mint makes every banned spelling unwritable or a
|
|
114
|
+
* construction error: `exact` is `{n}` (`{0}` the exclusion on the unit
|
|
115
|
+
* instance, "total is zero" on a weighted one), `range` is `{lo..hi}` with
|
|
116
|
+
* lo < hi (`{0..hi}` the canonical ceiling; the hi slot admits a dependent
|
|
117
|
+
* bound — C6: hi only), `floor` is `{lo..*}` (`{1..*}` legal on weighted
|
|
118
|
+
* statements only).
|
|
119
|
+
*/
|
|
120
|
+
type CapacityWindowSpec =
|
|
121
|
+
| { readonly kind: "exact"; readonly n: CapacityBoundSpec }
|
|
122
|
+
| { readonly kind: "range"; readonly lo: CapacityBoundSpec; readonly hi: CapacityBoundSpec }
|
|
123
|
+
| { readonly kind: "floor"; readonly lo: CapacityBoundSpec }
|
|
97
124
|
|
|
98
125
|
/**
|
|
99
126
|
* One field: name, structural type, host newtype name — the field's
|
|
@@ -148,8 +175,10 @@ interface RelationSpec {
|
|
|
148
175
|
* One dependency statement, tagged by form. `==` is not a variant: a
|
|
149
176
|
* bidirectional containment is `containment` with `bidirectional: true`,
|
|
150
177
|
* lowered by the engine to the two adjacent containments (`source <=
|
|
151
|
-
* target` first). `
|
|
152
|
-
* per-group parent, the source
|
|
178
|
+
* target` first). `capacity` reads as the operator does (C2 — target,
|
|
179
|
+
* weight, window, source): the target is the per-group parent, the source
|
|
180
|
+
* is the weighed side, and the weight is ALWAYS present (`unit` the count
|
|
181
|
+
* instance).
|
|
153
182
|
*/
|
|
154
183
|
type StatementSpec =
|
|
155
184
|
| { readonly kind: "fd"; readonly relation: string; readonly projection: readonly string[] }
|
|
@@ -160,9 +189,10 @@ type StatementSpec =
|
|
|
160
189
|
readonly bidirectional: boolean
|
|
161
190
|
}
|
|
162
191
|
| {
|
|
163
|
-
readonly kind: "
|
|
192
|
+
readonly kind: "capacity"
|
|
164
193
|
readonly target: SideSpec
|
|
165
|
-
readonly
|
|
194
|
+
readonly weight: WeightSpec
|
|
195
|
+
readonly window: CapacityWindowSpec
|
|
166
196
|
readonly source: SideSpec
|
|
167
197
|
}
|
|
168
198
|
|
|
@@ -308,22 +338,58 @@ function renderLiteralSet(set: LiteralSetSpec): string {
|
|
|
308
338
|
}
|
|
309
339
|
|
|
310
340
|
/**
|
|
311
|
-
* Renders
|
|
341
|
+
* Renders one capacity bound in its one canonical spelling: a literal as
|
|
342
|
+
* digits, a dependent bound bare by field name, an interval-measure bound
|
|
343
|
+
* as `Duration(field)` — the spellings the engine's renderer emits.
|
|
344
|
+
*/
|
|
345
|
+
function renderCapacityBound(bound: CapacityBoundSpec): string {
|
|
346
|
+
switch (bound.kind) {
|
|
347
|
+
case "lit":
|
|
348
|
+
return bound.value.toString()
|
|
349
|
+
case "field":
|
|
350
|
+
return bound.field
|
|
351
|
+
case "durationField":
|
|
352
|
+
return `Duration(${bound.field})`
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Renders a capacity window in its one canonical spelling: `{n}` exact
|
|
312
358
|
* (`{0}` the exclusion), `{lo..hi}`, `{lo..*}` — the spelling set the
|
|
313
|
-
* engine's renderer emits for sealed statements
|
|
359
|
+
* engine's renderer emits for sealed statements, bounds through
|
|
360
|
+
* {@link renderCapacityBound}.
|
|
314
361
|
*/
|
|
315
|
-
function
|
|
362
|
+
function renderCapacityWindow(window: CapacityWindowSpec): string {
|
|
316
363
|
switch (window.kind) {
|
|
317
364
|
case "exact":
|
|
318
|
-
return `{${window.n}}`
|
|
365
|
+
return `{${renderCapacityBound(window.n)}}`
|
|
319
366
|
case "range":
|
|
320
|
-
return `{${window.lo}..${window.hi}}`
|
|
367
|
+
return `{${renderCapacityBound(window.lo)}..${renderCapacityBound(window.hi)}}`
|
|
321
368
|
case "floor":
|
|
322
|
-
return `{${window.lo}..*}`
|
|
369
|
+
return `{${renderCapacityBound(window.lo)}..*}`
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Renders a capacity weight as the operator's bracket: the unit weight
|
|
375
|
+
* renders NOTHING — the count utterance `<={lo..hi}` falls out of the one
|
|
376
|
+
* printer, never a second "legacy" arm — a field weight as `[field]`, an
|
|
377
|
+
* interval measure as `[Duration(field)]`.
|
|
378
|
+
*/
|
|
379
|
+
function renderWeight(weight: WeightSpec): string {
|
|
380
|
+
switch (weight.kind) {
|
|
381
|
+
case "unit":
|
|
382
|
+
return ""
|
|
383
|
+
case "field":
|
|
384
|
+
return `[${weight.field}]`
|
|
385
|
+
case "durationField":
|
|
386
|
+
return `[Duration(${weight.field})]`
|
|
323
387
|
}
|
|
324
388
|
}
|
|
325
389
|
|
|
326
390
|
export type {
|
|
391
|
+
CapacityBoundSpec,
|
|
392
|
+
CapacityWindowSpec,
|
|
327
393
|
ClosedSpec,
|
|
328
394
|
FieldSpec,
|
|
329
395
|
LiteralSetSpec,
|
|
@@ -335,6 +401,6 @@ export type {
|
|
|
335
401
|
StatementSpec,
|
|
336
402
|
ValueSpec,
|
|
337
403
|
ValueTypeSpec,
|
|
338
|
-
|
|
404
|
+
WeightSpec
|
|
339
405
|
}
|
|
340
|
-
export { renderLiteral, renderLiteralSet,
|
|
406
|
+
export { renderCapacityBound, renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight }
|