@bjornpagen/bumbledb 0.7.0 → 0.8.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 +7 -7
- 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/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 +2 -2
- package/src/capacity.ts +454 -0
- package/src/db.ts +13 -11
- package/src/face.ts +1 -0
- package/src/index.ts +19 -8
- package/src/law.ts +3 -2
- package/src/lower.ts +3 -2
- package/src/native.ts +6 -3
- 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/db.ts
CHANGED
|
@@ -117,8 +117,10 @@ interface OffendingFact<Rels extends SchemaRelations> {
|
|
|
117
117
|
* orientation (identical strings; the engine's `render.rs` renders each
|
|
118
118
|
* partner of a mirrored pair as the `==` spelling, never a bare `<=`
|
|
119
119
|
* direction). `direction` (`sourceUnsatisfied` | `targetRequired`) and
|
|
120
|
-
* `
|
|
121
|
-
* the engine VERBATIM
|
|
120
|
+
* `measure` are the containment/capacity form payloads, passed through
|
|
121
|
+
* from the engine VERBATIM (`measure` the capacity form's witnessed group
|
|
122
|
+
* total — u128-wide, crossing whole as bigint, C3) — `direction` is
|
|
123
|
+
* relative to the violated SLOT's
|
|
122
124
|
* own orientation, so for a `mirrors` statement it alone cannot say which
|
|
123
125
|
* side of the `==` was violated: the slot identity is carried by
|
|
124
126
|
* `orientation`, present exactly for `mirrors` slots — `written` is the
|
|
@@ -131,7 +133,7 @@ interface Violation<Rels extends SchemaRelations> {
|
|
|
131
133
|
readonly canonical: string
|
|
132
134
|
readonly direction?: "sourceUnsatisfied" | "targetRequired"
|
|
133
135
|
readonly orientation?: "written" | "mirrored"
|
|
134
|
-
readonly
|
|
136
|
+
readonly measure?: bigint
|
|
135
137
|
readonly facts: readonly OffendingFact<Rels>[]
|
|
136
138
|
}
|
|
137
139
|
|
|
@@ -536,10 +538,10 @@ function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
|
|
|
536
538
|
}
|
|
537
539
|
|
|
538
540
|
/**
|
|
539
|
-
* One declared statement's materialized slots: a key or
|
|
540
|
-
* one, a `mirrors` occupies two adjacent slots (the engine lowers
|
|
541
|
-
* two containments, `source <= target` first), both owned by the
|
|
542
|
-
* value.
|
|
541
|
+
* One declared statement's materialized slots: a key or capacity statement
|
|
542
|
+
* occupies one, a `mirrors` occupies two adjacent slots (the engine lowers
|
|
543
|
+
* `==` to two containments, `source <= target` first), both owned by the
|
|
544
|
+
* one SDK value.
|
|
543
545
|
*/
|
|
544
546
|
function declaredEntries(statement: Statement): StatementEntry[] {
|
|
545
547
|
const data = statement.data
|
|
@@ -562,8 +564,8 @@ function declaredEntries(statement: Statement): StatementEntry[] {
|
|
|
562
564
|
}
|
|
563
565
|
return [{ kind: "containment", statement, key: undefined }]
|
|
564
566
|
}
|
|
565
|
-
case "
|
|
566
|
-
return [{ kind: "
|
|
567
|
+
case "capacity": {
|
|
568
|
+
return [{ kind: "capacity", statement, key: undefined }]
|
|
567
569
|
}
|
|
568
570
|
}
|
|
569
571
|
}
|
|
@@ -889,7 +891,7 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
889
891
|
canonical: wire.canonical,
|
|
890
892
|
direction: wire.direction,
|
|
891
893
|
orientation: orientationOf(entry.reversed),
|
|
892
|
-
|
|
894
|
+
measure: wire.measure,
|
|
893
895
|
facts: Object.freeze(wire.facts.map(offendingFactOf))
|
|
894
896
|
})
|
|
895
897
|
}
|
|
@@ -912,7 +914,7 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
912
914
|
)
|
|
913
915
|
}
|
|
914
916
|
if (entry.kind !== "functionality" || entry.key === undefined) {
|
|
915
|
-
throw errors.new("keyed get takes a key() statement — containments and
|
|
917
|
+
throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing")
|
|
916
918
|
}
|
|
917
919
|
if (entry.key.owner !== relation.name) {
|
|
918
920
|
throw errors.new(
|
package/src/face.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
* `.slice`). The raw native bridge is not exported.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
export type {
|
|
29
|
+
BoundsOnTarget,
|
|
30
|
+
CapacityWeight,
|
|
31
|
+
CapacityWindow,
|
|
32
|
+
DurationRef,
|
|
33
|
+
FieldRef,
|
|
34
|
+
UnitWindowBan,
|
|
35
|
+
WeightOnSource
|
|
36
|
+
} from "#capacity.ts"
|
|
37
|
+
export { duration, ref, weigh, within } from "#capacity.ts"
|
|
28
38
|
export type {
|
|
29
39
|
AnyClosed,
|
|
30
40
|
AnySelectedClosed,
|
|
@@ -40,8 +50,6 @@ export type {
|
|
|
40
50
|
SelectedClosed
|
|
41
51
|
} from "#closed.ts"
|
|
42
52
|
export { closed } from "#closed.ts"
|
|
43
|
-
export type { Count } from "#count.ts"
|
|
44
|
-
export { atLeast, atMost, between, exactly, none } from "#count.ts"
|
|
45
53
|
export type {
|
|
46
54
|
Abandon,
|
|
47
55
|
AbandonedArm,
|
|
@@ -81,6 +89,7 @@ export type {
|
|
|
81
89
|
FaceShapes,
|
|
82
90
|
FaceSource,
|
|
83
91
|
OwnerOf,
|
|
92
|
+
ProjectedShape,
|
|
84
93
|
SameArity,
|
|
85
94
|
SameShapes
|
|
86
95
|
} from "#face.ts"
|
|
@@ -181,6 +190,8 @@ export { relation } from "#relation.ts"
|
|
|
181
190
|
export type { AnySchema, Schema, SchemaRelation, SchemaRelations } from "#schema.ts"
|
|
182
191
|
export { schema } from "#schema.ts"
|
|
183
192
|
export type {
|
|
193
|
+
CapacityBoundSpec,
|
|
194
|
+
CapacityWindowSpec,
|
|
184
195
|
FieldSpec,
|
|
185
196
|
LiteralSetSpec,
|
|
186
197
|
LiteralSpec,
|
|
@@ -191,17 +202,17 @@ export type {
|
|
|
191
202
|
StatementSpec,
|
|
192
203
|
ValueSpec,
|
|
193
204
|
ValueTypeSpec,
|
|
194
|
-
|
|
205
|
+
WeightSpec
|
|
195
206
|
} from "#spec.ts"
|
|
196
|
-
export { renderLiteral, renderLiteralSet,
|
|
207
|
+
export { renderCapacityBound, renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight } from "#spec.ts"
|
|
197
208
|
export type {
|
|
209
|
+
CapacityData,
|
|
210
|
+
CapacityStatement,
|
|
198
211
|
ContainedStatement,
|
|
199
212
|
ContainmentData,
|
|
200
213
|
KeyData,
|
|
201
214
|
KeyStatement,
|
|
202
215
|
Statement,
|
|
203
|
-
StatementData
|
|
204
|
-
WindowData,
|
|
205
|
-
WindowStatement
|
|
216
|
+
StatementData
|
|
206
217
|
} from "#statements.ts"
|
|
207
|
-
export { contained, key, mirrors, renderStatement
|
|
218
|
+
export { capacity, contained, key, mirrors, renderStatement } from "#statements.ts"
|
package/src/law.ts
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
*
|
|
34
34
|
* Every paired face of the statement tuple unions its positionwise field
|
|
35
35
|
* slots: containment (ψ-selected targets included — a selection changes
|
|
36
|
-
* pairing not at all), the `==` bijection, and
|
|
36
|
+
* pairing not at all), the `==` bijection, and capacity source/target pairs.
|
|
37
37
|
* `key()` statements pair nothing (an FD constrains one relation's own
|
|
38
38
|
* rows; it identifies no carriers).
|
|
39
39
|
*
|
|
@@ -141,7 +141,8 @@ type ZipCoords<
|
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
143
|
* One statement's slot pairs: containments (bidirectional included — pair
|
|
144
|
-
* unions are symmetric) and
|
|
144
|
+
* unions are symmetric) and capacity statements pair their two faces
|
|
145
|
+
* positionwise;
|
|
145
146
|
* `key()` pairs nothing. A widened face (owner name or projection no
|
|
146
147
|
* longer literal) contributes nothing — the runtime map stays complete.
|
|
147
148
|
*/
|
package/src/lower.ts
CHANGED
|
@@ -98,10 +98,11 @@ function lowerStatement(statement: Statement): StatementSpec {
|
|
|
98
98
|
target: lowerFace(data.target),
|
|
99
99
|
bidirectional: data.bidirectional
|
|
100
100
|
}
|
|
101
|
-
case "
|
|
101
|
+
case "capacity":
|
|
102
102
|
return {
|
|
103
|
-
kind: "
|
|
103
|
+
kind: "capacity",
|
|
104
104
|
target: lowerFace(data.target),
|
|
105
|
+
weight: data.weight,
|
|
105
106
|
window: data.window,
|
|
106
107
|
source: lowerFace(data.source)
|
|
107
108
|
}
|
package/src/native.ts
CHANGED
|
@@ -175,7 +175,7 @@ type ConditionTreeIr =
|
|
|
175
175
|
| { readonly kind: "or"; readonly children: readonly ConditionTreeIr[] }
|
|
176
176
|
|
|
177
177
|
/** A statement's form tag. */
|
|
178
|
-
type StatementKindTag = "functionality" | "containment" | "
|
|
178
|
+
type StatementKindTag = "functionality" | "containment" | "capacity"
|
|
179
179
|
|
|
180
180
|
/** One field's name, dense id, and structural type. */
|
|
181
181
|
interface ManifestField {
|
|
@@ -231,14 +231,17 @@ interface ViolationFact {
|
|
|
231
231
|
* One violated statement of a rejected commit, rendered to plain data: the
|
|
232
232
|
* statement id (materialized order), form tag, CANONICAL spelling (the
|
|
233
233
|
* engine's one renderer — a bijection on legal statements, paste-back-able),
|
|
234
|
-
* the form's direction/
|
|
234
|
+
* the form's direction/measure payloads, and the decoded offending facts.
|
|
235
|
+
* `measure` is the capacity form's witnessed group total — the engine
|
|
236
|
+
* accumulates in u128 and the value crosses WHOLE as bigint (C3:
|
|
237
|
+
* truncation is unrepresentable).
|
|
235
238
|
*/
|
|
236
239
|
interface Violation {
|
|
237
240
|
readonly statementId: number
|
|
238
241
|
readonly kind: StatementKindTag
|
|
239
242
|
readonly canonical: string
|
|
240
243
|
readonly direction?: "sourceUnsatisfied" | "targetRequired"
|
|
241
|
-
readonly
|
|
244
|
+
readonly measure?: bigint
|
|
242
245
|
readonly facts: readonly ViolationFact[]
|
|
243
246
|
}
|
|
244
247
|
|
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 }
|
package/src/statements.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Dependency statements as typed values (`docs/architecture/30-dependencies.md`
|
|
3
3
|
* owns the semantics; `docs/architecture/70-api.md` the surface): the FD key
|
|
4
4
|
* form, conditional containment, the bidirectional `==` abbreviation, and
|
|
5
|
-
* the
|
|
5
|
+
* the capacity statement. A statement value is opaque and inert — no
|
|
6
6
|
* methods, no fluent continuation: a fact about the theory, not a builder.
|
|
7
7
|
*
|
|
8
8
|
* Every field reference is checked against the relation it names in the
|
|
@@ -35,12 +35,21 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import * as errors from "@superbuilders/errors"
|
|
38
|
+
import {
|
|
39
|
+
type BoundsOnTarget,
|
|
40
|
+
type CapacityWeight,
|
|
41
|
+
type CapacityWindow,
|
|
42
|
+
isCapacityWeight,
|
|
43
|
+
isCapacityWindow,
|
|
44
|
+
type UnitWindowBan,
|
|
45
|
+
unitWeight,
|
|
46
|
+
type WeightOnSource
|
|
47
|
+
} from "#capacity.ts"
|
|
38
48
|
import { isClosedMember, sealedFieldOf } from "#closed.ts"
|
|
39
|
-
import type { Count } from "#count.ts"
|
|
40
49
|
import { type AnyFace, type FaceData, renderFace, type SameArity, type SameShapes } from "#face.ts"
|
|
41
50
|
import { type ClosedRoster, rosterOf } from "#fields.ts"
|
|
42
51
|
import type { AnyRelation, RelationFields } from "#relation.ts"
|
|
43
|
-
import {
|
|
52
|
+
import { type CapacityWindowSpec, renderCapacityWindow, renderWeight, type WeightSpec } from "#spec.ts"
|
|
44
53
|
|
|
45
54
|
/** A `key()` statement's runtime description — owner and projection carried at exact types. */
|
|
46
55
|
interface KeyData<R extends AnyRelation, Projection extends readonly string[]> {
|
|
@@ -63,20 +72,26 @@ interface ContainmentData<Src extends FaceData = FaceData, Tgt extends FaceData
|
|
|
63
72
|
readonly bidirectional: boolean
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
/**
|
|
76
|
+
* A capacity statement's runtime description — target-left, in the
|
|
77
|
+
* operator's own order (C2: target, weight, window, source), faces at
|
|
78
|
+
* exact types like {@link ContainmentData}. The weight is ALWAYS present
|
|
79
|
+
* (C4 — `unit` is a case, not an absence).
|
|
80
|
+
*/
|
|
81
|
+
interface CapacityData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
|
|
82
|
+
readonly kind: "capacity"
|
|
69
83
|
readonly target: Tgt
|
|
70
|
-
readonly
|
|
84
|
+
readonly weight: WeightSpec
|
|
85
|
+
readonly window: CapacityWindowSpec
|
|
71
86
|
readonly source: Src
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
/** One statement's runtime description, tagged by form. */
|
|
75
|
-
type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData |
|
|
90
|
+
type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | CapacityData
|
|
76
91
|
|
|
77
92
|
/**
|
|
78
93
|
* The admission brand — a module-private symbol, deliberately unexported
|
|
79
|
-
* (the `
|
|
94
|
+
* (the `capacity.ts` pattern): `Statement` is a public structural type, so
|
|
80
95
|
* without this brand a forged plain object of the right shape would walk
|
|
81
96
|
* past the construction-time arity and roster walls into `schema()` — and
|
|
82
97
|
* the roster wall is the one the engine cannot backstop (the wire carries
|
|
@@ -115,9 +130,9 @@ interface ContainedStatement<Src extends FaceData, Tgt extends FaceData> extends
|
|
|
115
130
|
readonly data: ContainmentData<Src, Tgt>
|
|
116
131
|
}
|
|
117
132
|
|
|
118
|
-
/** A
|
|
119
|
-
interface
|
|
120
|
-
readonly data:
|
|
133
|
+
/** A capacity statement as a TYPED value — the {@link ContainedStatement} of the capacity form. */
|
|
134
|
+
interface CapacityStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
|
|
135
|
+
readonly data: CapacityData<Tgt, Src>
|
|
121
136
|
}
|
|
122
137
|
|
|
123
138
|
/**
|
|
@@ -266,29 +281,138 @@ function mirrors<A extends AnyFace, B extends AnyFace>(
|
|
|
266
281
|
}
|
|
267
282
|
|
|
268
283
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
|
|
276
|
-
|
|
284
|
+
* The runtime twin of the weight source wall ({@link WeightOnSource}): the
|
|
285
|
+
* weighed field must be a u64-encoded position of the SOURCE's own row
|
|
286
|
+
* (a signed weight would break the polarity scheduler — the illegal weight
|
|
287
|
+
* is unrepresentable, not checked), an interval position for the
|
|
288
|
+
* `Duration(...)` form. Judged at CONSTRUCTION for untyped callers; the
|
|
289
|
+
* engine's `validate_capacity` stays the final authority.
|
|
290
|
+
*/
|
|
291
|
+
function assertWeightOnSource(weight: WeightSpec, source: FaceData, statement: Statement): void {
|
|
292
|
+
if (weight.kind === "unit") {
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
const field = sealedFieldOf(source.owner, weight.field)
|
|
296
|
+
if (field === undefined) {
|
|
297
|
+
throw errors.new(
|
|
298
|
+
`${source.owner.name} has no field ${weight.field} — a weight names a field of the SOURCE's own row (the weight vocabulary is closed at the row) — ${renderStatement(statement)}`
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
if (weight.kind === "field" && field.kind !== "u64") {
|
|
302
|
+
throw errors.new(
|
|
303
|
+
`${source.owner.name}.${weight.field} is ${field.kind}, not u64 — a weight is u64-encoded (a signed weight would break the polarity scheduler: an insert could lower a sum) — ${renderStatement(statement)}`
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
if (weight.kind === "durationField" && field.kind !== "interval") {
|
|
307
|
+
throw errors.new(
|
|
308
|
+
`${source.owner.name}.${weight.field} is ${field.kind}, not an interval — Duration(...) weighs an interval field's measure — ${renderStatement(statement)}`
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* The runtime twin of the dependent-bound target wall
|
|
315
|
+
* ({@link BoundsOnTarget}): a `ref()` bound must name a u64 field of the
|
|
316
|
+
* TARGET's own row, a `duration()` bound an interval field — bound names
|
|
317
|
+
* resolve against the target's FULL roster (C1), never the projection.
|
|
318
|
+
* `within()` mints dependent bounds in the hi slot only (C6), but the walk
|
|
319
|
+
* here is total over the window's bound slots.
|
|
277
320
|
*/
|
|
278
|
-
function window
|
|
321
|
+
function assertBoundsOnTarget(window: CapacityWindowSpec, target: FaceData, statement: Statement): void {
|
|
322
|
+
const bounds = window.kind === "range" ? [window.lo, window.hi] : [window.kind === "exact" ? window.n : window.lo]
|
|
323
|
+
for (const bound of bounds) {
|
|
324
|
+
if (bound.kind === "lit") {
|
|
325
|
+
continue
|
|
326
|
+
}
|
|
327
|
+
const field = sealedFieldOf(target.owner, bound.field)
|
|
328
|
+
if (field === undefined) {
|
|
329
|
+
throw errors.new(
|
|
330
|
+
`${target.owner.name} has no field ${bound.field} — a dependent bound names a field of the TARGET's own row (bound names resolve against the target's full roster) — ${renderStatement(statement)}`
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
if (bound.kind === "field" && field.kind !== "u64") {
|
|
334
|
+
throw errors.new(
|
|
335
|
+
`${target.owner.name}.${bound.field} is ${field.kind}, not u64 — a dependent bound reads a u64 field of the TARGET row (Duration(...) is the interval-measure spelling) — ${renderStatement(statement)}`
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
if (bound.kind === "durationField" && field.kind !== "interval") {
|
|
339
|
+
throw errors.new(
|
|
340
|
+
`${target.owner.name}.${bound.field} is ${field.kind}, not an interval — Duration(...) bounds by an interval field's measure — ${renderStatement(statement)}`
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* `B(Y|ψ) <=[w]{window} A(X|φ)` — the capacity statement, the one
|
|
348
|
+
* extension form: per ψ-selected target fact, the group of φ-selected
|
|
349
|
+
* source facts sharing its key tuple must have its MEASURE (Σ weight; the
|
|
350
|
+
* unit weight IS the count instance) inside the window. READ CAREFULLY:
|
|
351
|
+
* the LEFT face is the TARGET, the per-group parent (B-family, target-left
|
|
352
|
+
* — macro parity), and the RIGHT face is the weighed source. Two
|
|
353
|
+
* overloads mirror the operator positionally (target, weight?, window,
|
|
354
|
+
* source): `capacity(on(Holder, "id"), within(0n, 3n), on(Account,
|
|
355
|
+
* "holder"))` says each Holder id groups at most three Account rows;
|
|
356
|
+
* `capacity(on(Pool, "id"), weigh("watts"), within(0n, ref("supply")),
|
|
357
|
+
* on(Device, "pool"))` bounds each pool's summed draw by the pool's own
|
|
358
|
+
* row. The two faces pair by arity AND structural shape
|
|
359
|
+
* ({@link SameShapes}), exactly as containment — the grouping join reads
|
|
360
|
+
* the same positionwise field pairing. The weight-sensitive `{1..*}` ban
|
|
361
|
+
* rides the UNIT overload only ({@link UnitWindowBan} — on a weighted
|
|
362
|
+
* statement "positive total" is a different, weaker law than containment).
|
|
363
|
+
*/
|
|
364
|
+
function capacity<B extends AnyFace, W extends CapacityWindow, A extends AnyFace>(
|
|
365
|
+
target: B,
|
|
366
|
+
window: W & UnitWindowBan<W> & BoundsOnTarget<W, B>,
|
|
367
|
+
source: A & SameArity<B, A> & SameShapes<B, A>
|
|
368
|
+
): CapacityStatement<B["data"], A["data"]>
|
|
369
|
+
function capacity<B extends AnyFace, M extends CapacityWeight, W extends CapacityWindow, A extends AnyFace>(
|
|
279
370
|
target: B,
|
|
280
|
-
|
|
371
|
+
weight: M & WeightOnSource<M, A>,
|
|
372
|
+
window: W & BoundsOnTarget<W, B>,
|
|
281
373
|
source: A & SameArity<B, A> & SameShapes<B, A>
|
|
282
|
-
):
|
|
283
|
-
|
|
284
|
-
|
|
374
|
+
): CapacityStatement<B["data"], A["data"]>
|
|
375
|
+
function capacity(
|
|
376
|
+
target: AnyFace,
|
|
377
|
+
second: unknown,
|
|
378
|
+
third: unknown,
|
|
379
|
+
fourth?: AnyFace
|
|
380
|
+
): CapacityStatement<FaceData, FaceData> {
|
|
381
|
+
const weighted = fourth !== undefined
|
|
382
|
+
const windowValue = weighted ? third : second
|
|
383
|
+
const source = weighted ? fourth : (third as AnyFace)
|
|
384
|
+
if (!isCapacityWindow(windowValue)) {
|
|
385
|
+
throw errors.new(
|
|
386
|
+
"a capacity window is minted only by within() — a structural literal skips the ban table (the canonical-utterance law)"
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
let weight: WeightSpec = unitWeight
|
|
390
|
+
if (weighted) {
|
|
391
|
+
if (!isCapacityWeight(second)) {
|
|
392
|
+
throw errors.new(
|
|
393
|
+
"a capacity weight is minted only by weigh() — a structural literal skips the row-local weight wall"
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
weight = second.weight
|
|
397
|
+
}
|
|
398
|
+
const window = windowValue.window
|
|
399
|
+
if (weight.kind === "unit" && window.kind === "floor" && window.lo.kind === "lit" && window.lo.value === 1n) {
|
|
400
|
+
throw errors.new(
|
|
401
|
+
"`{1..*}` on the unit instance says only what the bare containment says — drop the annotation and write the containment: contained(source, target)"
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
const data: CapacityData = Object.freeze({
|
|
405
|
+
kind: "capacity",
|
|
285
406
|
target: target.data,
|
|
286
|
-
|
|
407
|
+
weight,
|
|
408
|
+
window,
|
|
287
409
|
source: source.data
|
|
288
410
|
})
|
|
289
411
|
const statement = Object.freeze({ data, [admitted]: true as const })
|
|
290
412
|
assertArityAgreement(data.source, data.target, statement)
|
|
291
413
|
assertRosterAgreement(data.source, data.target, statement)
|
|
414
|
+
assertWeightOnSource(weight, data.source, statement)
|
|
415
|
+
assertBoundsOnTarget(window, data.target, statement)
|
|
292
416
|
return statement
|
|
293
417
|
}
|
|
294
418
|
|
|
@@ -298,9 +422,11 @@ function window<B extends AnyFace, A extends AnyFace>(
|
|
|
298
422
|
* same shapes for violations) — `Account(id) -> Account`,
|
|
299
423
|
* `Account(holder) <= Holder(id)`,
|
|
300
424
|
* `Account(id | kind == Savings) == SavingsTerms(account)`,
|
|
301
|
-
* `Holder(id) <={0..3} Account(holder)
|
|
425
|
+
* `Holder(id) <={0..3} Account(holder)`,
|
|
426
|
+
* `Pool(id) <=[watts]{0..supply} Device(pool)` — so TS-side errors and
|
|
302
427
|
* engine-side diagnostics read identically. A renderer, never a parser:
|
|
303
|
-
* strings are output-only.
|
|
428
|
+
* strings are output-only. The unit weight renders nothing — the count
|
|
429
|
+
* utterance falls out of the one printer.
|
|
304
430
|
*/
|
|
305
431
|
function renderStatement(statement: Statement): string {
|
|
306
432
|
const data = statement.data
|
|
@@ -311,19 +437,19 @@ function renderStatement(statement: Statement): string {
|
|
|
311
437
|
const operator = data.bidirectional ? "==" : "<="
|
|
312
438
|
return `${renderFace(data.source)} ${operator} ${renderFace(data.target)}`
|
|
313
439
|
}
|
|
314
|
-
case "
|
|
315
|
-
return `${renderFace(data.target)} <=${
|
|
440
|
+
case "capacity":
|
|
441
|
+
return `${renderFace(data.target)} <=${renderWeight(data.weight)}${renderCapacityWindow(data.window)} ${renderFace(data.source)}`
|
|
316
442
|
}
|
|
317
443
|
}
|
|
318
444
|
|
|
319
445
|
export type {
|
|
446
|
+
CapacityData,
|
|
447
|
+
CapacityStatement,
|
|
320
448
|
ContainedStatement,
|
|
321
449
|
ContainmentData,
|
|
322
450
|
KeyData,
|
|
323
451
|
KeyStatement,
|
|
324
452
|
Statement,
|
|
325
|
-
StatementData
|
|
326
|
-
WindowData,
|
|
327
|
-
WindowStatement
|
|
453
|
+
StatementData
|
|
328
454
|
}
|
|
329
|
-
export { contained, isStatement, key, mirrors, renderStatement
|
|
455
|
+
export { capacity, contained, isStatement, key, mirrors, renderStatement }
|