@bjornpagen/bumbledb 1.1.0 → 1.2.1
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 +114 -11
- package/README.md +52 -9
- package/dist/capacity.d.ts +22 -59
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +66 -13
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +26 -3
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +89 -44
- package/dist/changes.js.map +1 -1
- package/dist/closed.d.ts +23 -51
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +110 -168
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +4 -9
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +108 -183
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +16 -16
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +36 -15
- package/dist/compile.js.map +1 -1
- package/dist/db-native.d.ts +39 -0
- package/dist/db-native.d.ts.map +1 -1
- package/dist/db-native.js +8 -0
- package/dist/db-native.js.map +1 -1
- package/dist/db.d.ts +28 -8
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +91 -38
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +11 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -1
- package/dist/errors.js.map +1 -1
- package/dist/face.d.ts +19 -25
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +30 -24
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +13 -26
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +89 -116
- package/dist/fields.js.map +1 -1
- package/dist/immutable.d.ts +10 -0
- package/dist/immutable.d.ts.map +1 -0
- package/dist/immutable.js +75 -0
- package/dist/immutable.js.map +1 -0
- package/dist/index.d.ts +16 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +2 -0
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +2 -0
- package/dist/internal/log.js.map +1 -1
- package/dist/law.d.ts +13 -11
- 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 +11 -6
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +1 -1
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/description.d.ts +33 -0
- package/dist/query/description.d.ts.map +1 -0
- package/dist/query/description.js +302 -0
- package/dist/query/description.js.map +1 -0
- package/dist/query/find.d.ts +21 -6
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +75 -10
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +132 -163
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +7 -2
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +240 -53
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +8 -10
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +24 -16
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +5 -10
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +22 -48
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +30 -82
- package/dist/relation.js.map +1 -1
- package/dist/rows.d.ts +7 -4
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +44 -198
- package/dist/rows.js.map +1 -1
- package/dist/runtime-errors.d.ts +26 -2
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +43 -3
- package/dist/runtime-errors.js.map +1 -1
- package/dist/schema.d.ts +9 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +85 -24
- package/dist/schema.js.map +1 -1
- package/dist/selection.d.ts +27 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +104 -0
- package/dist/selection.js.map +1 -0
- package/dist/shape.d.ts +12 -12
- package/dist/shape.d.ts.map +1 -1
- package/dist/statements.d.ts +17 -52
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +92 -111
- package/dist/statements.js.map +1 -1
- package/dist/values.d.ts +25 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +203 -0
- package/dist/values.js.map +1 -0
- package/pack-provenance.json +3 -3
- package/package.json +4 -4
- package/src/capacity.ts +93 -80
- package/src/changes.ts +183 -54
- package/src/closed.ts +161 -314
- package/src/codec.ts +116 -190
- package/src/compile.ts +56 -32
- package/src/db-native.ts +42 -0
- package/src/db.ts +134 -52
- package/src/errors.ts +16 -1
- package/src/face.ts +44 -74
- package/src/fields.ts +100 -146
- package/src/immutable.ts +76 -0
- package/src/index.ts +36 -29
- package/src/internal/log.ts +2 -0
- package/src/law.ts +21 -18
- package/src/lower.ts +13 -10
- package/src/native.ts +2 -0
- package/src/query/atom.ts +1 -0
- package/src/query/description.ts +394 -0
- package/src/query/find.ts +20 -7
- package/src/query/lower.ts +177 -187
- package/src/query/parse-ir.ts +247 -54
- package/src/query/run.ts +15 -9
- package/src/query/scope.ts +42 -33
- package/src/relation.ts +58 -142
- package/src/rows.ts +62 -205
- package/src/runtime-errors.ts +47 -3
- package/src/schema.ts +99 -29
- package/src/selection.ts +124 -0
- package/src/shape.ts +18 -12
- package/src/statements.ts +138 -189
- package/src/values.ts +223 -0
package/src/capacity.ts
CHANGED
|
@@ -1,55 +1,15 @@
|
|
|
1
1
|
import { AuthoringError } from "#errors.ts"
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
* the
|
|
5
|
-
* § the canonical-utterance law, restated
|
|
6
|
-
* around the aggregate form). `within` is the ONE window spelling —
|
|
7
|
-
* `within(n)` exact (`within(0n)` IS the exclusion's one spelling),
|
|
8
|
-
* `within(lo, hi)` range (`within(0n, hi)` the canonical ceiling),
|
|
9
|
-
* `within(lo, "*")` floor — `weigh` names the measure on the SOURCE row
|
|
10
|
-
* (`weigh("watts")` a u64 field, `weigh(duration("booked"))` an interval's
|
|
11
|
-
* measure), and `ref`/`duration` read a dependent bound from the
|
|
12
|
-
* TARGET row (hi slot only — ruled 2026-07-24, C6). The ban table is
|
|
13
|
-
* enforced REPRESENTATIONALLY in two tiers, split per-aggregate where
|
|
14
|
-
* weight-sensitive (design § 6: a ban is canonical-utterance policing when
|
|
15
|
-
* it is weight-independent, semantic deduplication when it is not):
|
|
16
|
-
*
|
|
17
|
-
* - **The type tier**: a banned spelling written as a LITERAL does not
|
|
18
|
-
* compile — every negative bound, `within(n, n)`, `within(0n, 0n)`, and
|
|
19
|
-
* `within(0n, "*")` are type errors naming the canonical form. The
|
|
20
|
-
* weight-SENSITIVE row rides the `capacity` overloads themselves:
|
|
21
|
-
* `within(1n, "*")` is banned on the unit overload only (`{1..*}` on the
|
|
22
|
-
* count instance is the bare containment respelled —
|
|
23
|
-
* `window_floor_containment`), and LEGAL on the weighted one ("positive
|
|
24
|
-
* total" is not an existence claim over rows).
|
|
25
|
-
* - **The construction tier**: a bound the type level cannot judge — a
|
|
26
|
-
* COMPUTED `bigint`, or an inverted `within(lo, hi)` order — is judged at
|
|
27
|
-
* construction with the same canonical-naming errors; and past both
|
|
28
|
-
* tiers the engine's own spec validation remains the law for a hostile
|
|
29
|
-
* FFI caller (the standing two-tier ban enforcement).
|
|
30
|
-
*
|
|
31
|
-
* The weight vocabulary is closed at the row (ruled 2026-07-24, ruling 6):
|
|
32
|
-
* a path weight (`weigh("model.watts")`) is a typed refusal at BOTH tiers
|
|
33
|
-
* whose diagnostic names the pinned-column idiom — the two-column
|
|
34
|
-
* containment IS the join, stated as a law. Bounds are `bigint` (u64
|
|
35
|
-
* crosses as bigint always, PRD-04's law); the witnessed measure comes
|
|
36
|
-
* back as `bigint` too (u128-wide engine accumulator, C3).
|
|
37
|
-
*/
|
|
2
|
+
/** Structural capacity measures and windows. Constructors and generated
|
|
3
|
+
* descriptions share the same checked representation. Bounds are u64;
|
|
4
|
+
* dependent bounds read the target row, measures read the source row. */
|
|
38
5
|
|
|
39
6
|
import type { AnyFace, FaceFields, FaceSource, ProjectedShape } from "#face.ts"
|
|
40
7
|
import type { CapacityBoundSpec, CapacityWindowSpec, WeightSpec } from "#spec.ts"
|
|
8
|
+
import { integerValue, recordValue } from "#values.ts"
|
|
41
9
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
interface CapacityWindow<S extends CapacityWindowSpec = CapacityWindowSpec> {
|
|
45
|
-
readonly window: S
|
|
46
|
-
readonly [admitted]: true
|
|
47
|
-
}
|
|
10
|
+
type CapacityWindow<S extends CapacityWindowSpec = CapacityWindowSpec> = S
|
|
48
11
|
|
|
49
|
-
|
|
50
|
-
readonly weight: S
|
|
51
|
-
readonly [admitted]: true
|
|
52
|
-
}
|
|
12
|
+
type CapacityWeight<S extends WeightSpec = WeightSpec> = S
|
|
53
13
|
|
|
54
14
|
interface FieldRef<F extends string = string> {
|
|
55
15
|
readonly kind: "field"
|
|
@@ -61,14 +21,6 @@ interface DurationRef<F extends string = string> {
|
|
|
61
21
|
readonly field: F
|
|
62
22
|
}
|
|
63
23
|
|
|
64
|
-
function isCapacityWindow(value: unknown): value is CapacityWindow {
|
|
65
|
-
return typeof value === "object" && value !== null && admitted in value && "window" in value
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isCapacityWeight(value: unknown): value is CapacityWeight {
|
|
69
|
-
return typeof value === "object" && value !== null && admitted in value && "weight" in value
|
|
70
|
-
}
|
|
71
|
-
|
|
72
24
|
interface BannedWindow<Canonical extends string> {
|
|
73
25
|
readonly "banned window spelling — the canonical-utterance law names the one legal form": Canonical
|
|
74
26
|
}
|
|
@@ -125,11 +77,12 @@ type RangeBan<Lo extends bigint, Hi extends bigint> = bigint extends Lo
|
|
|
125
77
|
* The C18 dimension gate's ban row, unit instance (the engine's
|
|
126
78
|
* `CapacityDimensionMixing` twin, ruled 2026-07-24): a unit (count)
|
|
127
79
|
* window against a `duration` bound counts facts against a span of
|
|
128
|
-
*
|
|
129
|
-
* only
|
|
130
|
-
*
|
|
80
|
+
* interval coordinates — a dimension error. Judged on the `capacity`
|
|
81
|
+
* UNIT overload only. Explicit scalar and Duration measures may use
|
|
82
|
+
* either scalar or Duration bounds; the application supplies their
|
|
83
|
+
* common unit (for example, cents or calendar days).
|
|
131
84
|
*/
|
|
132
|
-
type UnitDimensionBan<W extends CapacityWindow> = W
|
|
85
|
+
type UnitDimensionBan<W extends CapacityWindow> = W extends {
|
|
133
86
|
readonly hi: { readonly kind: "durationField" }
|
|
134
87
|
}
|
|
135
88
|
? BannedWindow<"a count of facts bounded by a span of time mixes dimensions (C18) — weigh the source with weigh(duration(field)), or bound by a u64 field or literal">
|
|
@@ -154,17 +107,17 @@ interface BoundKindMismatch<K, Want> {
|
|
|
154
107
|
|
|
155
108
|
type BoundOnTarget<K extends string, Want extends "u64" | "interval", B extends AnyFace> = string extends K
|
|
156
109
|
? unknown
|
|
157
|
-
: K extends FaceFields<B["
|
|
158
|
-
? KindAt<B["
|
|
110
|
+
: K extends FaceFields<B["owner"]>
|
|
111
|
+
? KindAt<B["owner"], K> extends Want
|
|
159
112
|
? unknown
|
|
160
113
|
: BoundKindMismatch<K, Want>
|
|
161
|
-
: BoundOffTargetRoster<K, FaceFields<B["
|
|
114
|
+
: BoundOffTargetRoster<K, FaceFields<B["owner"]>>
|
|
162
115
|
|
|
163
|
-
type BoundsOnTarget<W extends CapacityWindow, B extends AnyFace> = W
|
|
116
|
+
type BoundsOnTarget<W extends CapacityWindow, B extends AnyFace> = W extends {
|
|
164
117
|
readonly hi: FieldRef<infer K>
|
|
165
118
|
}
|
|
166
119
|
? BoundOnTarget<K, "u64", B>
|
|
167
|
-
: W
|
|
120
|
+
: W extends { readonly hi: DurationRef<infer K> }
|
|
168
121
|
? BoundOnTarget<K, "interval", B>
|
|
169
122
|
: unknown
|
|
170
123
|
|
|
@@ -189,30 +142,32 @@ interface WeightKindMismatch<K, Want> {
|
|
|
189
142
|
* position. Checked at the `capacity` call where the source face is
|
|
190
143
|
* inferred — the proven constrain-after-inference pattern.
|
|
191
144
|
*/
|
|
192
|
-
type WeightOnSource<M extends CapacityWeight, A extends AnyFace> = M
|
|
145
|
+
type WeightOnSource<M extends CapacityWeight, A extends AnyFace> = M extends {
|
|
193
146
|
readonly kind: "field"
|
|
194
147
|
readonly field: infer K extends string
|
|
195
148
|
}
|
|
196
149
|
? string extends K
|
|
197
150
|
? unknown
|
|
198
|
-
: K extends FaceFields<A["
|
|
199
|
-
? KindAt<A["
|
|
151
|
+
: K extends FaceFields<A["owner"]>
|
|
152
|
+
? KindAt<A["owner"], K> extends "u64"
|
|
200
153
|
? unknown
|
|
201
154
|
: WeightKindMismatch<K, "u64">
|
|
202
|
-
: WeightOffSourceRoster<K, FaceFields<A["
|
|
203
|
-
: M
|
|
155
|
+
: WeightOffSourceRoster<K, FaceFields<A["owner"]>>
|
|
156
|
+
: M extends { readonly kind: "durationField"; readonly field: infer K extends string }
|
|
204
157
|
? string extends K
|
|
205
158
|
? unknown
|
|
206
|
-
: K extends FaceFields<A["
|
|
207
|
-
? KindAt<A["
|
|
159
|
+
: K extends FaceFields<A["owner"]>
|
|
160
|
+
? KindAt<A["owner"], K> extends "interval"
|
|
208
161
|
? unknown
|
|
209
162
|
: WeightKindMismatch<K, "interval">
|
|
210
|
-
: WeightOffSourceRoster<K, FaceFields<A["
|
|
163
|
+
: WeightOffSourceRoster<K, FaceFields<A["owner"]>>
|
|
211
164
|
: unknown
|
|
212
165
|
|
|
213
166
|
const unitWeight: WeightSpec = Object.freeze({ kind: "unit" })
|
|
214
167
|
|
|
215
|
-
function assertRowLocal(field:
|
|
168
|
+
function assertRowLocal<F extends string>(field: F, role: string): F {
|
|
169
|
+
if (typeof field !== "string" || !field.isWellFormed())
|
|
170
|
+
throw new AuthoringError({ message: `${role}: expected a well-formed field name` })
|
|
216
171
|
if (field.includes(".")) {
|
|
217
172
|
throw new AuthoringError({
|
|
218
173
|
message: `${role} \`${field}\` walks a reference — the vocabulary is closed at the row (ruling 6): pin the column with a two-column containment (Source(ref, f) <= Catalog(id, f)) and name the local field`
|
|
@@ -222,8 +177,8 @@ function assertRowLocal(field: string, role: string): string {
|
|
|
222
177
|
}
|
|
223
178
|
|
|
224
179
|
function admitWindow<S extends CapacityWindowSpec>(window: S): CapacityWindow<S> {
|
|
225
|
-
|
|
226
|
-
return Object.freeze(
|
|
180
|
+
capacityWindow(window)
|
|
181
|
+
return Object.freeze(window)
|
|
227
182
|
}
|
|
228
183
|
|
|
229
184
|
function lit(value: bigint): CapacityBoundSpec {
|
|
@@ -265,6 +220,8 @@ function within<const Lo extends bigint, const Hi extends bigint>(
|
|
|
265
220
|
| { readonly kind: "exact"; readonly n: { readonly kind: "lit"; readonly value: Lo } }
|
|
266
221
|
>
|
|
267
222
|
function within(lo: bigint, hi?: bigint | "*" | FieldRef | DurationRef): CapacityWindow {
|
|
223
|
+
integerValue("capacity lower bound", "u64", lo)
|
|
224
|
+
if (typeof hi === "bigint") integerValue("capacity upper bound", "u64", hi)
|
|
268
225
|
if (lo < 0n) {
|
|
269
226
|
throw new AuthoringError({
|
|
270
227
|
message: `capacity bounds are u64: within(${lo}${hi === undefined ? "" : ", …"}) is out of domain`
|
|
@@ -302,8 +259,8 @@ function within(lo: bigint, hi?: bigint | "*" | FieldRef | DurationRef): Capacit
|
|
|
302
259
|
}
|
|
303
260
|
|
|
304
261
|
function admitWeight<S extends WeightSpec>(weight: S): CapacityWeight<S> {
|
|
305
|
-
|
|
306
|
-
return Object.freeze(
|
|
262
|
+
capacityWeight(weight)
|
|
263
|
+
return Object.freeze(weight)
|
|
307
264
|
}
|
|
308
265
|
|
|
309
266
|
/**
|
|
@@ -326,18 +283,74 @@ function weigh(measure: string | DurationRef): CapacityWeight {
|
|
|
326
283
|
}
|
|
327
284
|
|
|
328
285
|
function ref<const F extends string>(field: F & PathBan<F>): FieldRef<F> {
|
|
329
|
-
return Object.freeze({ kind: "field", field: assertRowLocal(field, "dependent bound") })
|
|
286
|
+
return Object.freeze({ kind: "field", field: assertRowLocal(field, "dependent bound") })
|
|
330
287
|
}
|
|
331
288
|
|
|
332
289
|
/**
|
|
333
290
|
* `Duration(field)` — the interval-measure spelling, one mint for both
|
|
334
291
|
* slots: handed to `weigh` it is the SOURCE row's interval measure;
|
|
335
292
|
* in `within`'s hi slot it is the TARGET row's interval-measure bound
|
|
336
|
-
*
|
|
293
|
+
* in the interval element's unit, not necessarily a clock unit.
|
|
294
|
+
* Explicit u64 measures/bounds may carry that same unit; an unweighted
|
|
295
|
+
* row count cannot be bounded by an interval measure (C18).
|
|
337
296
|
*/
|
|
338
297
|
function duration<const F extends string>(field: F & PathBan<F>): DurationRef<F> {
|
|
339
|
-
return Object.freeze({ kind: "durationField", field: assertRowLocal(field, "Duration measure") })
|
|
298
|
+
return Object.freeze({ kind: "durationField", field: assertRowLocal(field, "Duration measure") })
|
|
340
299
|
}
|
|
341
300
|
|
|
342
301
|
export type { BoundsOnTarget, CapacityWeight, CapacityWindow, DurationRef, FieldRef, UnitDimensionBan, WeightOnSource }
|
|
343
|
-
|
|
302
|
+
|
|
303
|
+
/** Read a checked bound without retaining a caller-owned record. */
|
|
304
|
+
function capacityBound(input: unknown): CapacityBoundSpec {
|
|
305
|
+
const kind = typeof input === "object" && input !== null && "kind" in input ? input.kind : undefined
|
|
306
|
+
if (kind === "lit") {
|
|
307
|
+
const bound = recordValue("capacity bound", input, ["kind", "value"])
|
|
308
|
+
return Object.freeze({ kind, value: integerValue("capacity bound", "u64", bound.value) })
|
|
309
|
+
}
|
|
310
|
+
if (kind === "field" || kind === "durationField") {
|
|
311
|
+
const bound = recordValue("capacity bound", input, ["kind", "field"])
|
|
312
|
+
if (typeof bound.field !== "string") throw new AuthoringError({ message: "capacity bound: expected a field name" })
|
|
313
|
+
return Object.freeze({ kind, field: assertRowLocal(bound.field, "dependent bound") })
|
|
314
|
+
}
|
|
315
|
+
throw new AuthoringError({ message: "capacity bound: unknown kind" })
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function literalBound(input: unknown): CapacityBoundSpec & { readonly kind: "lit" } {
|
|
319
|
+
const bound = capacityBound(input)
|
|
320
|
+
if (bound.kind !== "lit")
|
|
321
|
+
throw new AuthoringError({ message: "dependent bounds are allowed only at a range's upper bound" })
|
|
322
|
+
return bound
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function capacityWindow(input: unknown): CapacityWindow {
|
|
326
|
+
const kind = typeof input === "object" && input !== null && "kind" in input ? input.kind : undefined
|
|
327
|
+
if (kind === "exact") {
|
|
328
|
+
const window = recordValue("capacity window", input, ["kind", "n"])
|
|
329
|
+
return Object.freeze({ kind, n: literalBound(window.n) })
|
|
330
|
+
}
|
|
331
|
+
if (kind === "floor") {
|
|
332
|
+
const window = recordValue("capacity window", input, ["kind", "lo"])
|
|
333
|
+
return Object.freeze({ kind, lo: literalBound(window.lo) })
|
|
334
|
+
}
|
|
335
|
+
if (kind === "range") {
|
|
336
|
+
const window = recordValue("capacity window", input, ["kind", "lo", "hi"])
|
|
337
|
+
const lo = literalBound(window.lo)
|
|
338
|
+
const hi = capacityBound(window.hi)
|
|
339
|
+
if (hi.kind === "lit" && hi.value < lo.value) throw new AuthoringError({ message: "capacity window is inverted" })
|
|
340
|
+
return Object.freeze({ kind, lo, hi })
|
|
341
|
+
}
|
|
342
|
+
throw new AuthoringError({ message: "capacity window: unknown kind" })
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function capacityWeight(input: unknown): CapacityWeight {
|
|
346
|
+
const kind = typeof input === "object" && input !== null && "kind" in input ? input.kind : undefined
|
|
347
|
+
if (kind === "unit") {
|
|
348
|
+
recordValue("capacity weight", input, ["kind"])
|
|
349
|
+
return unitWeight
|
|
350
|
+
}
|
|
351
|
+
const weight = capacityBound(input)
|
|
352
|
+
if (weight.kind === "lit") throw new AuthoringError({ message: "capacity weight: expected unit or a source field" })
|
|
353
|
+
return weight
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export { capacityWeight, capacityWindow, duration, ref, unitWeight, weigh, within }
|
package/src/changes.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import type { Scope } from "effect"
|
|
2
|
-
import { Effect, Exit } from "effect"
|
|
2
|
+
import { Effect, Exit, Option, Stream } from "effect"
|
|
3
3
|
import { drainClose, releaseOwner } from "#close.ts"
|
|
4
|
+
import { isClosedMember, membersAgree } from "#closed.ts"
|
|
4
5
|
import type { SchemaId } from "#compile.ts"
|
|
5
6
|
import { Schema as CoreSchema, schemaTables } from "#compile.ts"
|
|
6
|
-
import type { ChangesHandle, DraftHandle } from "#db-native.ts"
|
|
7
|
+
import type { ChangeRecordWire, ChangesHandle, ChangesWire, DraftHandle } from "#db-native.ts"
|
|
7
8
|
import { dbNative } from "#db-native.ts"
|
|
9
|
+
import { SdkInvariantError } from "#errors.ts"
|
|
8
10
|
import { lower } from "#lower.ts"
|
|
9
|
-
import type
|
|
11
|
+
import { type AnyRelation, type Fact, relationFields } from "#relation.ts"
|
|
10
12
|
import type { CellValue } from "#rows.ts"
|
|
11
|
-
import {
|
|
13
|
+
import { factCellsOf, factOfCells, hostCellCharge } from "#rows.ts"
|
|
12
14
|
import { nativeOperationWith, runtimeHandle } from "#runtime.ts"
|
|
13
15
|
import type { CloseReport } from "#runtime-errors.ts"
|
|
14
|
-
import { DbError } from "#runtime-errors.ts"
|
|
15
|
-
import type { AnySchema } from "#schema.ts"
|
|
16
|
+
import { argumentError, DbError } from "#runtime-errors.ts"
|
|
17
|
+
import type { AnySchema, SchemaRelation } from "#schema.ts"
|
|
16
18
|
import type { Rel } from "#shape.ts"
|
|
19
|
+
import { bytesValue, recordValue } from "#values.ts"
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* `ChangeSet` — the engine's checked immutable delta:
|
|
@@ -22,10 +25,32 @@ import type { Rel } from "#shape.ts"
|
|
|
22
25
|
* add-wins. Immutable and reusable while open; sealing/submitting it later
|
|
23
26
|
* retains the SAME native value — no second JS row walk ever happens.
|
|
24
27
|
*/
|
|
28
|
+
/** Counts of distinct fact additions and removals, never input events. */
|
|
29
|
+
interface ChangeCounts {
|
|
30
|
+
readonly added: bigint
|
|
31
|
+
readonly removed: bigint
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A relation-name-discriminated union of plain, fully typed facts. */
|
|
35
|
+
type ChangeRecord<S extends AnySchema> = {
|
|
36
|
+
[N in keyof S["relations"]]: S["relations"][N] extends AnyRelation
|
|
37
|
+
? { readonly relation: N; readonly kind: "add" | "remove"; readonly fact: Fact<S["relations"][N]> }
|
|
38
|
+
: never
|
|
39
|
+
}[keyof S["relations"]]
|
|
40
|
+
|
|
25
41
|
interface ChangeSet<S extends AnySchema> {
|
|
26
42
|
readonly schemaId: SchemaId
|
|
43
|
+
/** Distinct requested actions; use judge for effective counts against a store. */
|
|
44
|
+
readonly counts: ChangeCounts
|
|
45
|
+
readonly byteLength: bigint
|
|
27
46
|
/** Phantom schema brand: a ChangeSet is only ever applied to its own S. */
|
|
28
47
|
readonly schema?: S
|
|
48
|
+
/** Independent scoped traversal, delivered in bounded native batches. Reusable. */
|
|
49
|
+
records(): Stream.Stream<ChangeRecord<S>, DbError>
|
|
50
|
+
/** Explicit full byte materialization. Mutating returned bytes cannot alter this value. */
|
|
51
|
+
toBytes(): Effect.Effect<Uint8Array, DbError>
|
|
52
|
+
/** Commutative add-wins composition into one command, not sequential replay. */
|
|
53
|
+
compose(other: ChangeSet<S>): Effect.Effect<ChangeSet<S>, DbError, Scope.Scope>
|
|
29
54
|
close(): Effect.Effect<CloseReport>
|
|
30
55
|
}
|
|
31
56
|
|
|
@@ -97,31 +122,15 @@ function eventLoopTurn(): Effect.Effect<void> {
|
|
|
97
122
|
}
|
|
98
123
|
|
|
99
124
|
function hostFactCharge(relation: AnyRelation, record: Readonly<Record<string, unknown>>): bigint {
|
|
100
|
-
const
|
|
125
|
+
const fields = relationFields(relation)
|
|
101
126
|
let bytes = 0n
|
|
102
|
-
for (const declared of
|
|
127
|
+
for (const declared of fields) {
|
|
103
128
|
const value = record[declared.name]
|
|
104
|
-
if (value === undefined) {
|
|
105
|
-
throw refusal("ChangeDraft.ingest", "InvalidArgument")
|
|
106
|
-
}
|
|
107
129
|
bytes += hostCellCharge(value)
|
|
108
130
|
}
|
|
109
131
|
return bytes
|
|
110
132
|
}
|
|
111
133
|
|
|
112
|
-
function projectFact(relation: AnyRelation, record: Readonly<Record<string, unknown>>): readonly CellValue[] {
|
|
113
|
-
const data = relation.data
|
|
114
|
-
const cells: CellValue[] = []
|
|
115
|
-
for (const declared of data.fields) {
|
|
116
|
-
const value = record[declared.name]
|
|
117
|
-
if (value === undefined) {
|
|
118
|
-
throw refusal("ChangeDraft.ingest", "InvalidArgument")
|
|
119
|
-
}
|
|
120
|
-
cells.push(cellOf(`relation ${data.name} field ${declared.name}`, declared.field, value))
|
|
121
|
-
}
|
|
122
|
-
return cells
|
|
123
|
-
}
|
|
124
|
-
|
|
125
134
|
/**
|
|
126
135
|
* Pulls one bounded chunk off the caller's iterator. Host length is judged
|
|
127
136
|
* before any string scan or byte copy. A leftover fact that does not fit
|
|
@@ -141,13 +150,17 @@ function pullChunk(relation: AnyRelation, iterator: Iterator<object>, pending: o
|
|
|
141
150
|
}
|
|
142
151
|
current = next.value
|
|
143
152
|
}
|
|
144
|
-
const record =
|
|
153
|
+
const record = recordValue(
|
|
154
|
+
`relation ${relation.name}`,
|
|
155
|
+
current,
|
|
156
|
+
relationFields(relation).map((field) => field.name)
|
|
157
|
+
)
|
|
145
158
|
const charge = hostFactCharge(relation, record)
|
|
146
159
|
if (rows > 0n && bytes + charge > CHUNK_BYTES) {
|
|
147
160
|
leftover = current
|
|
148
161
|
break
|
|
149
162
|
}
|
|
150
|
-
for (const cell of
|
|
163
|
+
for (const cell of factCellsOf(relation, record)) cells.push(cell)
|
|
151
164
|
bytes += charge
|
|
152
165
|
rows += 1n
|
|
153
166
|
current = undefined
|
|
@@ -185,7 +198,7 @@ function ingest(
|
|
|
185
198
|
yield* spendAndDrain(state, operation)
|
|
186
199
|
return yield* Effect.fail(refusal(operation, "SpentHandle"))
|
|
187
200
|
}
|
|
188
|
-
if (state.theory.relations[relation.name]
|
|
201
|
+
if (!membersAgree(state.theory.relations[relation.name], relation)) {
|
|
189
202
|
return yield* Effect.fail(refusal(operation, "InvalidArgument"))
|
|
190
203
|
}
|
|
191
204
|
const tables = schemaTables(state.theory)
|
|
@@ -197,7 +210,7 @@ function ingest(
|
|
|
197
210
|
const body = Effect.gen(function* () {
|
|
198
211
|
const iterator = yield* Effect.try({
|
|
199
212
|
try: () => rows[Symbol.iterator](),
|
|
200
|
-
catch: () =>
|
|
213
|
+
catch: (cause) => argumentError(operation, cause)
|
|
201
214
|
})
|
|
202
215
|
let leftover: object | undefined
|
|
203
216
|
let done = false
|
|
@@ -205,7 +218,7 @@ function ingest(
|
|
|
205
218
|
while (!done) {
|
|
206
219
|
const chunk = yield* Effect.try({
|
|
207
220
|
try: () => pullChunk(relation, iterator, leftover),
|
|
208
|
-
catch: (cause) => (
|
|
221
|
+
catch: (cause) => argumentError(operation, cause)
|
|
209
222
|
}).pipe(Effect.catch((error) => spendAndDrain(state, operation).pipe(Effect.andThen(Effect.fail(error)))))
|
|
210
223
|
leftover = chunk.leftover
|
|
211
224
|
done = chunk.done && leftover === undefined
|
|
@@ -244,19 +257,126 @@ function ingest(
|
|
|
244
257
|
})
|
|
245
258
|
}
|
|
246
259
|
|
|
247
|
-
function
|
|
260
|
+
function decodeChangeRecord<S extends AnySchema>(
|
|
261
|
+
relations: readonly SchemaRelation[],
|
|
262
|
+
record: ChangeRecordWire
|
|
263
|
+
): ChangeRecord<S> {
|
|
264
|
+
const relation = relations[record.relation]
|
|
265
|
+
if (relation === undefined || isClosedMember(relation) || (record.kind !== "add" && record.kind !== "remove")) {
|
|
266
|
+
throw new SdkInvariantError({ message: "ChangeSet.records: invalid native record descriptor" })
|
|
267
|
+
}
|
|
268
|
+
// Relation id resolves through this schema's own ordered roster. The
|
|
269
|
+
// shared projector validates all field values before this union seam.
|
|
270
|
+
return Object.freeze({
|
|
271
|
+
relation: relation.name,
|
|
272
|
+
kind: record.kind,
|
|
273
|
+
fact: factOfCells(relation, record.values)
|
|
274
|
+
}) as ChangeRecord<S>
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function changeRecords<S extends AnySchema>(
|
|
278
|
+
handle: ChangesHandle,
|
|
279
|
+
relations: readonly SchemaRelation[]
|
|
280
|
+
): Stream.Stream<ChangeRecord<S>, DbError> {
|
|
281
|
+
return Stream.unwrap(
|
|
282
|
+
Effect.gen(function* () {
|
|
283
|
+
const cursor = yield* Effect.acquireRelease(
|
|
284
|
+
nativeOperationWith(
|
|
285
|
+
"ChangeSet.records",
|
|
286
|
+
(callback) => dbNative.runtimeChangesCursor(handle, callback),
|
|
287
|
+
dbNative.runtimeChangesCursorTake,
|
|
288
|
+
(value) => value
|
|
289
|
+
),
|
|
290
|
+
(value) =>
|
|
291
|
+
releaseOwner("ChangeCursor.close", (callback) => dbNative.runtimeChangesCursorClose(value, callback)),
|
|
292
|
+
{ interruptible: true }
|
|
293
|
+
)
|
|
294
|
+
return Stream.paginate(undefined, () =>
|
|
295
|
+
nativeOperationWith(
|
|
296
|
+
"ChangeSet.records",
|
|
297
|
+
(callback) => dbNative.runtimeChangesCursorNext(cursor, callback),
|
|
298
|
+
dbNative.runtimeChangePageTake,
|
|
299
|
+
(page) => page
|
|
300
|
+
).pipe(
|
|
301
|
+
Effect.map((page) =>
|
|
302
|
+
page === null
|
|
303
|
+
? ([[], Option.none<undefined>()] as const)
|
|
304
|
+
: ([page.map((record) => decodeChangeRecord<S>(relations, record)), Option.some(undefined)] as const)
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
})
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function acquireChanges<S extends AnySchema>(
|
|
313
|
+
theory: S,
|
|
314
|
+
schemaId: SchemaId,
|
|
315
|
+
acquire: Effect.Effect<ChangesWire, DbError>
|
|
316
|
+
): Effect.Effect<ChangeSet<S>, DbError, Scope.Scope> {
|
|
317
|
+
return Effect.acquireRelease(
|
|
318
|
+
acquire.pipe(Effect.map((wire) => makeChangeSet(theory, wire, schemaId))),
|
|
319
|
+
(changes) =>
|
|
320
|
+
Effect.suspend(() => {
|
|
321
|
+
const internal = changesInternals.get(changes)
|
|
322
|
+
if (internal === undefined) return Effect.void
|
|
323
|
+
internal.closed = true
|
|
324
|
+
return releaseOwner("ChangeSet.close", (callback) => dbNative.runtimeChangesClose(internal.handle, callback))
|
|
325
|
+
}),
|
|
326
|
+
{ interruptible: true }
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function makeChangeSet<S extends AnySchema>(theory: S, wire: ChangesWire, schemaId: SchemaId): ChangeSet<S> {
|
|
331
|
+
const handle = wire.changes
|
|
332
|
+
const relations = Object.values(theory.relations)
|
|
333
|
+
const internal: ChangesInternal = { handle, schemaId, closed: false }
|
|
248
334
|
const value: ChangeSet<S> = {
|
|
249
335
|
schemaId,
|
|
336
|
+
counts: Object.freeze({ ...wire.counts }),
|
|
337
|
+
byteLength: wire.byteLength,
|
|
338
|
+
records() {
|
|
339
|
+
return changeRecords<S>(handle, relations)
|
|
340
|
+
},
|
|
341
|
+
toBytes() {
|
|
342
|
+
return nativeOperationWith(
|
|
343
|
+
"ChangeSet.toBytes",
|
|
344
|
+
(callback) => dbNative.runtimeChangesBytes(handle, callback),
|
|
345
|
+
dbNative.runtimeBytesTake,
|
|
346
|
+
(bytes) => bytes
|
|
347
|
+
)
|
|
348
|
+
},
|
|
349
|
+
compose(other) {
|
|
350
|
+
return Effect.suspend(() => {
|
|
351
|
+
const right = internalChanges(other)
|
|
352
|
+
if (right === undefined || right.schemaId !== schemaId)
|
|
353
|
+
return Effect.fail(refusal("ChangeSet.compose", "InvalidArgument"))
|
|
354
|
+
if (internal.closed || right.closed) return Effect.fail(refusal("ChangeSet.compose", "ClosedHandle"))
|
|
355
|
+
return acquireChanges(
|
|
356
|
+
theory,
|
|
357
|
+
schemaId,
|
|
358
|
+
nativeOperationWith(
|
|
359
|
+
"ChangeSet.compose",
|
|
360
|
+
(callback) => dbNative.runtimeChangesCompose(handle, right.handle, callback),
|
|
361
|
+
dbNative.runtimeChangesTake,
|
|
362
|
+
(result) => result
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
})
|
|
366
|
+
},
|
|
250
367
|
close() {
|
|
251
|
-
return
|
|
368
|
+
return Effect.suspend(() => {
|
|
369
|
+
internal.closed = true
|
|
370
|
+
return drainClose("ChangeSet.close", (callback) => dbNative.runtimeChangesClose(handle, callback))
|
|
371
|
+
})
|
|
252
372
|
}
|
|
253
373
|
}
|
|
254
374
|
Object.freeze(value)
|
|
255
|
-
changesInternals.set(value,
|
|
375
|
+
changesInternals.set(value, internal)
|
|
256
376
|
return value
|
|
257
377
|
}
|
|
258
378
|
|
|
259
|
-
function makeDraft<S extends AnySchema>(state: DraftState, schemaId: SchemaId): ChangeDraft<S> {
|
|
379
|
+
function makeDraft<S extends AnySchema>(theory: S, state: DraftState, schemaId: SchemaId): ChangeDraft<S> {
|
|
260
380
|
const draft: ChangeDraft<S> = {
|
|
261
381
|
insert(relation, rows) {
|
|
262
382
|
return ingest(state, "ChangeDraft.insert", relation, rows)
|
|
@@ -265,7 +385,9 @@ function makeDraft<S extends AnySchema>(state: DraftState, schemaId: SchemaId):
|
|
|
265
385
|
return ingest(state, "ChangeDraft.delete", relation, rows)
|
|
266
386
|
},
|
|
267
387
|
finish() {
|
|
268
|
-
return
|
|
388
|
+
return acquireChanges(
|
|
389
|
+
theory,
|
|
390
|
+
schemaId,
|
|
269
391
|
Effect.gen(function* () {
|
|
270
392
|
if (state.spent) {
|
|
271
393
|
return yield* Effect.fail(refusal("ChangeDraft.finish", "SpentHandle"))
|
|
@@ -276,26 +398,13 @@ function makeDraft<S extends AnySchema>(state: DraftState, schemaId: SchemaId):
|
|
|
276
398
|
}
|
|
277
399
|
// Finish CONSUMES the draft, success or failure.
|
|
278
400
|
state.spent = true
|
|
279
|
-
|
|
401
|
+
return yield* nativeOperationWith(
|
|
280
402
|
"ChangeDraft.finish",
|
|
281
403
|
(callback) => dbNative.runtimeDraftFinish(state.handle, callback),
|
|
282
404
|
dbNative.runtimeChangesTake,
|
|
283
405
|
(value) => value
|
|
284
406
|
)
|
|
285
|
-
|
|
286
|
-
}),
|
|
287
|
-
(changes) =>
|
|
288
|
-
Effect.suspend(() => {
|
|
289
|
-
const internal = changesInternals.get(changes)
|
|
290
|
-
if (internal === undefined || internal.closed) {
|
|
291
|
-
return Effect.void
|
|
292
|
-
}
|
|
293
|
-
internal.closed = true
|
|
294
|
-
return releaseOwner("ChangeSet.close", (callback) =>
|
|
295
|
-
dbNative.runtimeChangesClose(internal.handle, callback)
|
|
296
|
-
)
|
|
297
|
-
}),
|
|
298
|
-
{ interruptible: true }
|
|
407
|
+
})
|
|
299
408
|
)
|
|
300
409
|
},
|
|
301
410
|
close() {
|
|
@@ -318,7 +427,7 @@ function makeDraft<S extends AnySchema>(state: DraftState, schemaId: SchemaId):
|
|
|
318
427
|
const builder = Effect.fn("ChangeSet.builder")(function* <S extends AnySchema>(schema: S) {
|
|
319
428
|
const handle = yield* runtimeHandle()
|
|
320
429
|
const compiled = yield* CoreSchema.compile(schema)
|
|
321
|
-
const spec = lower(schema)
|
|
430
|
+
const spec = lower(compiled.schema)
|
|
322
431
|
return yield* Effect.acquireRelease(
|
|
323
432
|
Effect.gen(function* () {
|
|
324
433
|
const draftHandle = yield* nativeOperationWith(
|
|
@@ -329,11 +438,11 @@ const builder = Effect.fn("ChangeSet.builder")(function* <S extends AnySchema>(s
|
|
|
329
438
|
)
|
|
330
439
|
const state: DraftState = {
|
|
331
440
|
handle: draftHandle,
|
|
332
|
-
theory: schema,
|
|
441
|
+
theory: compiled.schema,
|
|
333
442
|
spent: false,
|
|
334
443
|
inFlight: false
|
|
335
444
|
}
|
|
336
|
-
const draft = makeDraft
|
|
445
|
+
const draft = makeDraft(compiled.schema, state, compiled.schemaId)
|
|
337
446
|
draftStates.set(draft, state)
|
|
338
447
|
return draft
|
|
339
448
|
}),
|
|
@@ -357,7 +466,27 @@ const builder = Effect.fn("ChangeSet.builder")(function* <S extends AnySchema>(s
|
|
|
357
466
|
// native handle without exposing it on the public capability.
|
|
358
467
|
const draftStates = new WeakMap<object, DraftState>()
|
|
359
468
|
|
|
360
|
-
|
|
469
|
+
/** Parse canonical native bytes, owning the input when the Effect starts. */
|
|
470
|
+
const fromBytes = Effect.fn("ChangeSet.fromBytes")(function* <S extends AnySchema>(schema: S, bytes: Uint8Array) {
|
|
471
|
+
const ownedBytes = yield* Effect.try({
|
|
472
|
+
try: () => bytesValue("ChangeSet.fromBytes", bytes),
|
|
473
|
+
catch: (cause) => argumentError("ChangeSet.fromBytes", cause)
|
|
474
|
+
})
|
|
475
|
+
const handle = yield* runtimeHandle()
|
|
476
|
+
const compiled = yield* CoreSchema.compile(schema)
|
|
477
|
+
return yield* acquireChanges(
|
|
478
|
+
compiled.schema,
|
|
479
|
+
compiled.schemaId,
|
|
480
|
+
nativeOperationWith(
|
|
481
|
+
"ChangeSet.fromBytes",
|
|
482
|
+
(callback) => dbNative.runtimeChangesParse(handle, lower(compiled.schema), ownedBytes, callback),
|
|
483
|
+
dbNative.runtimeChangesTake,
|
|
484
|
+
(wire) => wire
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
const ChangeSet = Object.freeze({ builder, fromBytes })
|
|
361
490
|
|
|
362
|
-
export type { ChangeDraft }
|
|
491
|
+
export type { ChangeCounts, ChangeDraft, ChangeRecord }
|
|
363
492
|
export { ChangeSet, internalChanges }
|