@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.
Files changed (53) hide show
  1. package/COOKBOOK.md +10 -8
  2. package/README.md +5 -4
  3. package/dist/capacity.d.ts +2 -2
  4. package/dist/closed.d.ts +4 -4
  5. package/dist/db.d.ts +109 -60
  6. package/dist/db.d.ts.map +1 -1
  7. package/dist/db.js +90 -71
  8. package/dist/db.js.map +1 -1
  9. package/dist/exhume.d.ts +2 -2
  10. package/dist/face.d.ts +3 -3
  11. package/dist/fields.d.ts +1 -1
  12. package/dist/index.d.ts +32 -32
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/law.d.ts +5 -5
  16. package/dist/lower.d.ts +5 -5
  17. package/dist/marshal.d.ts +8 -30
  18. package/dist/marshal.d.ts.map +1 -1
  19. package/dist/marshal.js +4 -21
  20. package/dist/marshal.js.map +1 -1
  21. package/dist/native.d.ts +38 -21
  22. package/dist/native.d.ts.map +1 -1
  23. package/dist/native.js.map +1 -1
  24. package/dist/query/atom.d.ts +4 -4
  25. package/dist/query/find.d.ts +18 -13
  26. package/dist/query/find.d.ts.map +1 -1
  27. package/dist/query/find.js +2 -2
  28. package/dist/query/find.js.map +1 -1
  29. package/dist/query/lower.d.ts +10 -10
  30. package/dist/query/lower.d.ts.map +1 -1
  31. package/dist/query/lower.js +5 -4
  32. package/dist/query/lower.js.map +1 -1
  33. package/dist/query/parse-ir.d.ts +1 -1
  34. package/dist/query/parse-ir.d.ts.map +1 -1
  35. package/dist/query/parse-ir.js +11 -9
  36. package/dist/query/parse-ir.js.map +1 -1
  37. package/dist/query/run.d.ts +3 -3
  38. package/dist/query/scope.d.ts +5 -5
  39. package/dist/relation.d.ts +6 -17
  40. package/dist/relation.d.ts.map +1 -1
  41. package/dist/relation.js +3 -4
  42. package/dist/relation.js.map +1 -1
  43. package/dist/schema.d.ts +4 -4
  44. package/dist/statements.d.ts +4 -4
  45. package/package.json +4 -3
  46. package/src/db.ts +248 -168
  47. package/src/index.ts +8 -2
  48. package/src/marshal.ts +5 -35
  49. package/src/native.ts +34 -16
  50. package/src/query/find.ts +19 -14
  51. package/src/query/lower.ts +7 -6
  52. package/src/query/parse-ir.ts +11 -9
  53. 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 carries no `over`; folds require it. */
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: "aggregate"; readonly op: { readonly kind: "count" } }
129
- | { readonly kind: "aggregate"; readonly op: ArgOpIr; readonly over: number }
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 an insert into the delta; `true` iff the final state changed.
501
- * Nothing is judged until commit; shape violations throw typed.
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, values: readonly FactValue[]): boolean
504
- /** Records a delete into the delta; `true` iff the final state changed. */
505
- txDelete(tx: TxHandle, relationId: number, values: readonly FactValue[]): boolean
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 the next fresh value for `(relationId, fieldId)` and returns it
515
- * — the engine's alloc-then-insert dyn-lane mint (there is no
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
- txAlloc(tx: TxHandle, relationId: number, fieldId: number): bigint
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 AggOpName = "count" | "sum" | "min" | "max" | "pack"
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 find VALUE: the op and the variable (or measure) it folds
31
- * BY REFERENCE. The variable's own descriptor types the result.
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 AggOpName, Over extends AnyVar | Duration | undefined> {
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<AggOpName, AnyVar | Duration | undefined>
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 AggOpName, Over extends AnyVar | Duration | undefined>(
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(): Agg<"count", undefined> {
57
- return aggregate("count", undefined)
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 Agg<"count", undefined>
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 Agg<"count", undefined>
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
  }
@@ -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: unknown } {
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: unknown }): AggData {
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: "aggregate", op: { kind: "count" } }
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: "aggregate", op: { kind: "pack" }, over: ids.of(agg.over) }
2063
+ return { kind: "pack", over: ids.of(agg.over) }
2063
2064
  }
2064
2065
  }
2065
2066
 
@@ -50,28 +50,30 @@ function align(context: string, head: readonly HeadTermIr[], rules: readonly Rul
50
50
  }
51
51
  }
52
52
 
53
- /** Count forbids `over`; every other aggregate requires it. */
53
+ /** Count is nullary; pack and folds require `over`. */
54
54
  function parseFind(context: string, find: FindTermIr): void {
55
- if (find.kind !== "aggregate") {
56
- return
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 (!("over" in find)) {
65
- throw errors.new(`${context}: fold aggregate requires over`)
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; measure-folds are aggregates. */
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<>`/`InsertFact<>` are the inferred
11
- * row object types at BARE structural value types (no brands): fresh
12
- * fields are optional on insert input (omit-to-mint) and present on read
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,