@bjornpagen/bumbledb 0.12.0 → 0.14.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 +10 -8
- package/README.md +5 -4
- package/dist/capacity.d.ts +2 -2
- package/dist/closed.d.ts +4 -4
- package/dist/db.d.ts +109 -60
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +90 -71
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts +2 -2
- package/dist/face.d.ts +3 -3
- package/dist/fields.d.ts +1 -1
- package/dist/index.d.ts +32 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +5 -5
- package/dist/lower.d.ts +5 -5
- package/dist/marshal.d.ts +8 -30
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +4 -21
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +38 -21
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +4 -4
- package/dist/query/find.d.ts +18 -13
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +2 -2
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +10 -10
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +5 -4
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +11 -9
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +3 -3
- package/dist/query/scope.d.ts +5 -5
- package/dist/relation.d.ts +6 -17
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +3 -4
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +4 -4
- package/dist/statements.d.ts +4 -4
- package/package.json +4 -3
- package/src/db.ts +248 -168
- package/src/index.ts +8 -2
- package/src/marshal.ts +5 -35
- package/src/native.ts +34 -16
- package/src/query/find.ts +19 -14
- package/src/query/lower.ts +7 -6
- package/src/query/parse-ir.ts +11 -9
- package/src/relation.ts +3 -15
package/src/native.ts
CHANGED
|
@@ -44,6 +44,24 @@ type TxHandle = { readonly __brand: "bumbledb.tx" }
|
|
|
44
44
|
/** One prepared query (plan pinned at prepare). */
|
|
45
45
|
type PreparedHandle = { readonly __brand: "bumbledb.prepared" }
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Engine mutation report as it crosses napi: both counts are engine
|
|
49
|
+
* values, never reconstructed from JS length.
|
|
50
|
+
*/
|
|
51
|
+
interface WireMutationReport {
|
|
52
|
+
readonly submitted: bigint
|
|
53
|
+
readonly changed: bigint
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Engine fresh-id range as it crosses napi. Empty cannot yield a start —
|
|
58
|
+
* `start` is a minted id only on the nonempty arm. (C wires empty as
|
|
59
|
+
* `{ start: 0, end_exclusive: 0 }` at that boundary only.)
|
|
60
|
+
*/
|
|
61
|
+
type WireFreshRange =
|
|
62
|
+
| { readonly empty: true }
|
|
63
|
+
| { readonly empty: false; readonly start: bigint; readonly endExclusive: bigint }
|
|
64
|
+
|
|
47
65
|
/** A half-open interval `[start, end)` as it crosses the boundary. */
|
|
48
66
|
interface IntervalValue {
|
|
49
67
|
readonly start: bigint
|
|
@@ -118,15 +136,14 @@ interface RuleIr {
|
|
|
118
136
|
readonly conditions: readonly ConditionTreeIr[]
|
|
119
137
|
}
|
|
120
138
|
|
|
121
|
-
/** One find term (mirrors `ir::FindTerm`). Count
|
|
139
|
+
/** One find term (mirrors `ir::FindTerm`). Count is nullary; pack and folds carry `over`. */
|
|
122
140
|
type FoldOpIr = { readonly kind: "sum" } | { readonly kind: "min" } | { readonly kind: "max" }
|
|
123
141
|
|
|
124
|
-
type ArgOpIr = FoldOpIr | { readonly kind: "pack" }
|
|
125
|
-
|
|
126
142
|
type FindTermIr =
|
|
127
143
|
| { readonly kind: "var"; readonly var: number }
|
|
128
|
-
| { readonly kind: "
|
|
129
|
-
| { readonly kind: "aggregate"; readonly op:
|
|
144
|
+
| { readonly kind: "count" }
|
|
145
|
+
| { readonly kind: "aggregate"; readonly op: FoldOpIr; readonly over: number }
|
|
146
|
+
| { readonly kind: "pack"; readonly over: number }
|
|
130
147
|
| { readonly kind: "measure"; readonly var: number }
|
|
131
148
|
| { readonly kind: "aggregateMeasure"; readonly op: FoldOpIr; readonly over: number }
|
|
132
149
|
|
|
@@ -497,12 +514,14 @@ interface Native {
|
|
|
497
514
|
*/
|
|
498
515
|
dbWriteFrom(db: DbHandle, snap: SnapshotHandle): WriteFromResult
|
|
499
516
|
/**
|
|
500
|
-
* Records
|
|
501
|
-
*
|
|
517
|
+
* Records a collection of inserts into the delta; returns the engine
|
|
518
|
+
* `{ submitted, changed }` report. `rows` is an array of value-arrays
|
|
519
|
+
* in sealed field order. Empty is lawful and still a mutation (poison
|
|
520
|
+
* is observed). Nothing is judged until commit; shape violations throw typed.
|
|
502
521
|
*/
|
|
503
|
-
txInsert(tx: TxHandle, relationId: number,
|
|
504
|
-
/** Records a
|
|
505
|
-
txDelete(tx: TxHandle, relationId: number,
|
|
522
|
+
txInsert(tx: TxHandle, relationId: number, rows: readonly (readonly FactValue[])[]): WireMutationReport
|
|
523
|
+
/** Records a collection of deletes; returns the engine `{ submitted, changed }` report. */
|
|
524
|
+
txDelete(tx: TxHandle, relationId: number, rows: readonly (readonly FactValue[])[]): WireMutationReport
|
|
506
525
|
/**
|
|
507
526
|
* Final-state membership (base + pending delta — the exact view the
|
|
508
527
|
* commit judgment judges; check-then-act is race-free by construction).
|
|
@@ -511,12 +530,10 @@ interface Native {
|
|
|
511
530
|
/** Final-state point lookup through a key statement; `null` on a miss. */
|
|
512
531
|
txGet(tx: TxHandle, relationId: number, keyStatementId: number, keyValues: readonly FactValue[]): FactValue[] | null
|
|
513
532
|
/**
|
|
514
|
-
* Mints
|
|
515
|
-
*
|
|
516
|
-
* insert-with-omitted-fields spelling; include the minted id in the
|
|
517
|
-
* full row).
|
|
533
|
+
* Mints `count` consecutive fresh values for `(relationId, fieldId)`.
|
|
534
|
+
* `count === 0n` is empty and does not yield a start.
|
|
518
535
|
*/
|
|
519
|
-
|
|
536
|
+
txReserve(tx: TxHandle, relationId: number, fieldId: number, count: bigint): WireFreshRange
|
|
520
537
|
/**
|
|
521
538
|
* Commits the delta: every dependency statement judged against the
|
|
522
539
|
* final state; a rejection carries the complete violation rendering.
|
|
@@ -641,7 +658,6 @@ function bridged<T>(context: string, run: () => T): T {
|
|
|
641
658
|
|
|
642
659
|
export type {
|
|
643
660
|
AggOpIr,
|
|
644
|
-
ArgOpIr,
|
|
645
661
|
AtomIr,
|
|
646
662
|
AtomSourceIr,
|
|
647
663
|
CmpOpIr,
|
|
@@ -683,6 +699,8 @@ export type {
|
|
|
683
699
|
TxHandle,
|
|
684
700
|
Violation,
|
|
685
701
|
ViolationFact,
|
|
702
|
+
WireFreshRange,
|
|
703
|
+
WireMutationReport,
|
|
686
704
|
WriteFromResult
|
|
687
705
|
}
|
|
688
706
|
export { bridged, loadNativeBinding, native, SHIPPED_PLATFORMS }
|
package/src/query/find.ts
CHANGED
|
@@ -24,19 +24,25 @@ import type { IntervalVarOk, NumericVarOk, OrderVarOk } from "#query/atom.ts"
|
|
|
24
24
|
import type { AnyVar, Duration, MintSlotOf } from "#query/scope.ts"
|
|
25
25
|
|
|
26
26
|
/** One aggregate operator name of the find vocabulary. */
|
|
27
|
-
type
|
|
27
|
+
type FoldOpName = "sum" | "min" | "max" | "pack"
|
|
28
|
+
type AggOpName = "count" | FoldOpName
|
|
29
|
+
|
|
30
|
+
/** Nullary count: no `over` exists to inhabit. */
|
|
31
|
+
interface CountAgg {
|
|
32
|
+
readonly agg: "count"
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
|
-
* One aggregate
|
|
31
|
-
* BY REFERENCE.
|
|
36
|
+
* One fold aggregate: the op and the variable (or measure) it folds
|
|
37
|
+
* BY REFERENCE. Count is [`CountAgg`], not this type with `undefined`.
|
|
32
38
|
*/
|
|
33
|
-
interface Agg<Op extends
|
|
39
|
+
interface Agg<Op extends FoldOpName, Over extends AnyVar | Duration> {
|
|
34
40
|
readonly agg: Op
|
|
35
41
|
readonly over: Over
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/** Any aggregate find value. */
|
|
39
|
-
type AnyAgg = Agg<
|
|
45
|
+
type AnyAgg = CountAgg | Agg<FoldOpName, AnyVar | Duration>
|
|
40
46
|
|
|
41
47
|
/** One find entry: a projected variable, the measure, or an aggregate. */
|
|
42
48
|
type FindEntry = AnyVar | Duration | AnyAgg
|
|
@@ -44,17 +50,14 @@ type FindEntry = AnyVar | Duration | AnyAgg
|
|
|
44
50
|
/** The `find` record: column name → find entry. Keys ARE the answer columns. */
|
|
45
51
|
type FindShape = Readonly<Record<string, FindEntry>>
|
|
46
52
|
|
|
47
|
-
/** Builds one aggregate value. */
|
|
48
|
-
function aggregate<Op extends
|
|
49
|
-
op: Op,
|
|
50
|
-
over: Over
|
|
51
|
-
): Agg<Op, Over> {
|
|
53
|
+
/** Builds one fold aggregate value. */
|
|
54
|
+
function aggregate<Op extends FoldOpName, Over extends AnyVar | Duration>(op: Op, over: Over): Agg<Op, Over> {
|
|
52
55
|
return Object.freeze({ agg: op, over })
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/** Nullary count: |the group's set of distinct full bindings|, `bigint`. */
|
|
56
|
-
function count():
|
|
57
|
-
return
|
|
59
|
+
function count(): CountAgg {
|
|
60
|
+
return Object.freeze({ agg: "count" })
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
/**
|
|
@@ -119,7 +122,7 @@ type FindEntryOk<E> = E extends AnyVar
|
|
|
119
122
|
? true
|
|
120
123
|
: E extends Duration<infer V extends AnyVar>
|
|
121
124
|
? IntervalVarOk<V>
|
|
122
|
-
: E extends
|
|
125
|
+
: E extends CountAgg
|
|
123
126
|
? true
|
|
124
127
|
: E extends Agg<"sum", infer O>
|
|
125
128
|
? SumOverOk<O>
|
|
@@ -151,7 +154,7 @@ type FindValue<E> = E extends AnyVar
|
|
|
151
154
|
? Infer<E["field"]>
|
|
152
155
|
: E extends Duration<AnyVar>
|
|
153
156
|
? bigint
|
|
154
|
-
: E extends
|
|
157
|
+
: E extends CountAgg
|
|
155
158
|
? bigint
|
|
156
159
|
: E extends Agg<"sum" | "min" | "max", infer O>
|
|
157
160
|
? O extends AnyVar
|
|
@@ -179,10 +182,12 @@ export type {
|
|
|
179
182
|
AnyAgg,
|
|
180
183
|
CheckFind,
|
|
181
184
|
CheckRecFind,
|
|
185
|
+
CountAgg,
|
|
182
186
|
FindEntry,
|
|
183
187
|
FindEntryOk,
|
|
184
188
|
FindShape,
|
|
185
189
|
FindValue,
|
|
190
|
+
FoldOpName,
|
|
186
191
|
HeadRecordOf,
|
|
187
192
|
RowOfFind
|
|
188
193
|
}
|
package/src/query/lower.ts
CHANGED
|
@@ -772,7 +772,7 @@ function advanceInterior(
|
|
|
772
772
|
}
|
|
773
773
|
|
|
774
774
|
/** Narrows a find entry to an aggregate value. */
|
|
775
|
-
function isAggregateEntry(value: unknown): value is { readonly agg: string; readonly over
|
|
775
|
+
function isAggregateEntry(value: unknown): value is { readonly agg: string; readonly over?: unknown } {
|
|
776
776
|
return typeof value === "object" && value !== null && "agg" in value
|
|
777
777
|
}
|
|
778
778
|
|
|
@@ -785,11 +785,12 @@ function asVarTerm(context: string, value: unknown): AnyVar {
|
|
|
785
785
|
}
|
|
786
786
|
|
|
787
787
|
/** Classifies one aggregate find entry into its runtime data (variables ride by reference). */
|
|
788
|
-
function aggDataOf(name: string, entry: { readonly agg: string; readonly over
|
|
788
|
+
function aggDataOf(name: string, entry: { readonly agg: string; readonly over?: unknown }): AggData {
|
|
789
|
+
if (entry.agg === "count") {
|
|
790
|
+
return Object.freeze({ op: "count" as const })
|
|
791
|
+
}
|
|
789
792
|
const over = entry.over
|
|
790
793
|
switch (entry.agg) {
|
|
791
|
-
case "count":
|
|
792
|
-
return Object.freeze({ op: "count" as const })
|
|
793
794
|
case "sum":
|
|
794
795
|
case "min":
|
|
795
796
|
case "max": {
|
|
@@ -2051,7 +2052,7 @@ function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
|
2051
2052
|
const agg = entry.agg
|
|
2052
2053
|
switch (agg.op) {
|
|
2053
2054
|
case "count":
|
|
2054
|
-
return { kind: "
|
|
2055
|
+
return { kind: "count" }
|
|
2055
2056
|
case "fold": {
|
|
2056
2057
|
if ("duration" in agg.over) {
|
|
2057
2058
|
return { kind: "aggregateMeasure", op: { kind: agg.fold }, over: ids.of(agg.over.duration) }
|
|
@@ -2059,7 +2060,7 @@ function lowerFind(entry: FindEntryData, ids: VarIds): FindTermIr {
|
|
|
2059
2060
|
return { kind: "aggregate", op: { kind: agg.fold }, over: ids.of(agg.over) }
|
|
2060
2061
|
}
|
|
2061
2062
|
case "pack":
|
|
2062
|
-
return { kind: "
|
|
2063
|
+
return { kind: "pack", over: ids.of(agg.over) }
|
|
2063
2064
|
}
|
|
2064
2065
|
}
|
|
2065
2066
|
|
package/src/query/parse-ir.ts
CHANGED
|
@@ -50,28 +50,30 @@ function align(context: string, head: readonly HeadTermIr[], rules: readonly Rul
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/** Count
|
|
53
|
+
/** Count is nullary; pack and folds require `over`. */
|
|
54
54
|
function parseFind(context: string, find: FindTermIr): void {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (find.op.kind === "count") {
|
|
59
|
-
if ("over" in find) {
|
|
55
|
+
const raw = find as Record<string, unknown>
|
|
56
|
+
if (find.kind === "count") {
|
|
57
|
+
if ("over" in raw) {
|
|
60
58
|
throw errors.new(`${context}: Count carries no over`)
|
|
61
59
|
}
|
|
62
60
|
return
|
|
63
61
|
}
|
|
64
|
-
if (
|
|
65
|
-
|
|
62
|
+
if (find.kind === "pack" || find.kind === "aggregate" || find.kind === "aggregateMeasure") {
|
|
63
|
+
if (!("over" in raw)) {
|
|
64
|
+
throw errors.new(`${context}: ${find.kind} requires over`)
|
|
65
|
+
}
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** Head family of one find term: measure is a var slot;
|
|
69
|
+
/** Head family of one find term: measure is a var slot; count/pack/folds are aggregates. */
|
|
70
70
|
function findFamily(find: FindTermIr): "var" | "aggregate" {
|
|
71
71
|
switch (find.kind) {
|
|
72
72
|
case "var":
|
|
73
73
|
case "measure":
|
|
74
74
|
return "var"
|
|
75
|
+
case "count":
|
|
76
|
+
case "pack":
|
|
75
77
|
case "aggregate":
|
|
76
78
|
case "aggregateMeasure":
|
|
77
79
|
return "aggregate"
|
package/src/relation.ts
CHANGED
|
@@ -7,19 +7,15 @@
|
|
|
7
7
|
* verified against their roster at construction). Fields are addressed by
|
|
8
8
|
* NAME everywhere — statements (`on(R, "holder")`), selections, and match
|
|
9
9
|
* records all spell the field's own name, checked by type
|
|
10
|
-
* (`FaceFields`/`MatchShape`). `Fact
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* (resupply-to-preserve-identity), typed exactly.
|
|
10
|
+
* (`FaceFields`/`MatchShape`). `Fact<>` is the inferred row object type
|
|
11
|
+
* at BARE structural value types (no brands): every field is present,
|
|
12
|
+
* including fresh cells. Mint with `tx.reserve` before insert.
|
|
14
13
|
*/
|
|
15
14
|
|
|
16
15
|
import * as errors from "@superbuilders/errors"
|
|
17
16
|
import { type AnyField, assertDeclarationOrderKey, assertDeclarationRecord, type Infer, literalOf } from "#fields.ts"
|
|
18
17
|
import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
|
|
19
18
|
|
|
20
|
-
/** Flattens an intersection into one displayed object type (hover legibility). */
|
|
21
|
-
type Flatten<T> = { [K in keyof T]: T[K] }
|
|
22
|
-
|
|
23
19
|
/**
|
|
24
20
|
* Resolves one selection entry to its lowered literal set: a plain ARRAY
|
|
25
21
|
* (detected by `Array.isArray` — no field's value type is an array;
|
|
@@ -177,13 +173,6 @@ type FreshKeys<R extends AnyRelation> = {
|
|
|
177
173
|
[K in keyof RelationFields<R>]: RelationFields<R>[K] extends { readonly fresh: true } ? K : never
|
|
178
174
|
}[keyof RelationFields<R>]
|
|
179
175
|
|
|
180
|
-
/**
|
|
181
|
-
* The inferred row object type of a relation as INSERTED: fresh fields
|
|
182
|
-
* optional — omitted, the engine mints; supplied, identity is preserved
|
|
183
|
-
* (the ETL resupply idiom).
|
|
184
|
-
*/
|
|
185
|
-
type InsertFact<R extends AnyRelation> = Flatten<Omit<Fact<R>, FreshKeys<R>> & Partial<Pick<Fact<R>, FreshKeys<R>>>>
|
|
186
|
-
|
|
187
176
|
/**
|
|
188
177
|
* Declares one relation: `relation("Account", { id: u64.fresh,
|
|
189
178
|
* holder: u64, kind: Kind.id, ... })` — every field is a pure-structure
|
|
@@ -228,7 +217,6 @@ export type {
|
|
|
228
217
|
Fact,
|
|
229
218
|
FieldsShape,
|
|
230
219
|
FreshKeys,
|
|
231
|
-
InsertFact,
|
|
232
220
|
Relation,
|
|
233
221
|
RelationData,
|
|
234
222
|
RelationField,
|