@bjornpagen/bumbledb 0.15.0 → 0.17.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.
Files changed (103) hide show
  1. package/COOKBOOK.md +33 -49
  2. package/README.md +3 -3
  3. package/dist/capacity.d.ts +24 -136
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +18 -40
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/closed.d.ts +0 -156
  8. package/dist/closed.d.ts.map +1 -1
  9. package/dist/closed.js +0 -104
  10. package/dist/closed.js.map +1 -1
  11. package/dist/db.d.ts +7 -223
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +147 -396
  14. package/dist/db.js.map +1 -1
  15. package/dist/face.d.ts +0 -133
  16. package/dist/face.d.ts.map +1 -1
  17. package/dist/face.js +0 -33
  18. package/dist/face.js.map +1 -1
  19. package/dist/fields.d.ts +1 -145
  20. package/dist/fields.d.ts.map +1 -1
  21. package/dist/fields.js +2 -91
  22. package/dist/fields.js.map +1 -1
  23. package/dist/index.d.ts +12 -15
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +10 -13
  26. package/dist/index.js.map +1 -1
  27. package/dist/law.d.ts +111 -93
  28. package/dist/law.d.ts.map +1 -1
  29. package/dist/law.js +23 -27
  30. package/dist/law.js.map +1 -1
  31. package/dist/lower.d.ts +9 -35
  32. package/dist/lower.d.ts.map +1 -1
  33. package/dist/lower.js +8 -53
  34. package/dist/lower.js.map +1 -1
  35. package/dist/marshal.d.ts +0 -65
  36. package/dist/marshal.d.ts.map +1 -1
  37. package/dist/marshal.js +0 -72
  38. package/dist/marshal.js.map +1 -1
  39. package/dist/native.d.ts +31 -289
  40. package/dist/native.d.ts.map +1 -1
  41. package/dist/native.js +15 -64
  42. package/dist/native.js.map +1 -1
  43. package/dist/query/atom.d.ts +10 -276
  44. package/dist/query/atom.d.ts.map +1 -1
  45. package/dist/query/atom.js +1 -96
  46. package/dist/query/atom.js.map +1 -1
  47. package/dist/query/find.d.ts +10 -76
  48. package/dist/query/find.d.ts.map +1 -1
  49. package/dist/query/find.js +0 -30
  50. package/dist/query/find.js.map +1 -1
  51. package/dist/query/lower.d.ts +58 -146
  52. package/dist/query/lower.d.ts.map +1 -1
  53. package/dist/query/lower.js +19 -256
  54. package/dist/query/lower.js.map +1 -1
  55. package/dist/query/parse-ir.d.ts +0 -7
  56. package/dist/query/parse-ir.d.ts.map +1 -1
  57. package/dist/query/parse-ir.js +1 -13
  58. package/dist/query/parse-ir.js.map +1 -1
  59. package/dist/query/run.d.ts +0 -36
  60. package/dist/query/run.d.ts.map +1 -1
  61. package/dist/query/run.js +0 -44
  62. package/dist/query/run.js.map +1 -1
  63. package/dist/query/scope.d.ts +24 -180
  64. package/dist/query/scope.d.ts.map +1 -1
  65. package/dist/query/scope.js +2 -66
  66. package/dist/query/scope.js.map +1 -1
  67. package/dist/relation.d.ts +2 -50
  68. package/dist/relation.d.ts.map +1 -1
  69. package/dist/relation.js +2 -37
  70. package/dist/relation.js.map +1 -1
  71. package/dist/schema.d.ts +13 -63
  72. package/dist/schema.d.ts.map +1 -1
  73. package/dist/schema.js +118 -92
  74. package/dist/schema.js.map +1 -1
  75. package/dist/spec.d.ts +1 -140
  76. package/dist/spec.d.ts.map +1 -1
  77. package/dist/spec.js +1 -68
  78. package/dist/spec.js.map +1 -1
  79. package/dist/statements.d.ts +6 -137
  80. package/dist/statements.d.ts.map +1 -1
  81. package/dist/statements.js +16 -119
  82. package/dist/statements.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/capacity.ts +26 -140
  85. package/src/closed.ts +5 -206
  86. package/src/db.ts +203 -692
  87. package/src/face.ts +0 -142
  88. package/src/fields.ts +4 -172
  89. package/src/index.ts +10 -15
  90. package/src/law.ts +201 -129
  91. package/src/lower.ts +8 -53
  92. package/src/marshal.ts +1 -85
  93. package/src/native.ts +58 -321
  94. package/src/query/atom.ts +26 -313
  95. package/src/query/find.ts +24 -110
  96. package/src/query/lower.ts +126 -377
  97. package/src/query/parse-ir.ts +1 -14
  98. package/src/query/run.ts +0 -45
  99. package/src/query/scope.ts +25 -186
  100. package/src/relation.ts +2 -66
  101. package/src/schema.ts +143 -122
  102. package/src/spec.ts +1 -160
  103. package/src/statements.ts +22 -174
package/src/statements.ts CHANGED
@@ -1,39 +1,3 @@
1
- /**
2
- * Dependency statements as typed values (`docs/architecture/30-dependencies.md`
3
- * owns the semantics; `docs/architecture/70-api.md` the surface): the FD key
4
- * form, conditional containment, the bidirectional `==` abbreviation, and
5
- * the capacity statement. A statement value is opaque and inert — no
6
- * methods, no fluent continuation: a fact about the theory, not a builder.
7
- *
8
- * Every field reference is checked against the relation it names in the
9
- * TYPE — existence through {@link FaceFields} (`on(R, "nope")` does not
10
- * compile) and STRUCTURAL compatibility through {@link SameShapes}: the two
11
- * faces' projected kind/width/element/roster quadruples are read off the
12
- * schema type (the minimal kernel — descriptors are pure structure) and
13
- * constrained positionwise equal, so a u64 face against a str face, a
14
- * bytes width mismatch, an interval element mismatch, or a bare column
15
- * against a closed reference is a compile error. TWO of those walls carry
16
- * construction-time runtime twins here for untyped callers: ARITY
17
- * ({@link assertArityAgreement} — cleanup-0.5.0 ruling 9: an
18
- * arity-mismatched pairing fails at the statement, never by silent
19
- * truncation) and the ROSTER slot ({@link assertRosterAgreement} —
20
- * roster IDENTITY, positionwise: a closed vocabulary's referencing column
21
- * is spelled with the vocabulary's own id descriptor, the ONE spelling, so
22
- * a plain u64 column can never alias a vocabulary through a declared law
23
- * and the SDK's descriptor-keyed closed judgments stay sound). Domains are
24
- * NOT compared here — there is no domain to compare at construction: the
25
- * statements themselves are what define the equivalence classes, and the
26
- * domain wall lives where they aggregate — `schema()` (the
27
- * one-generator-per-class law) and query joins (class names off the schema
28
- * type). What is only a SEMANTIC property — the target side of a
29
- * containment resolving a declared key of its relation — is DELIBERATELY
30
- * not (and cannot be) stated here: whether `B(y)` is a key of `B` depends
31
- * on which `key()` statements the surrounding `schema()` collects, a set no
32
- * face type can see; it stays the engine's typed `SchemaError` judgment at
33
- * `Db.create`/`Db.open` (the two-boundary split, engine as final
34
- * authority).
35
- */
36
-
37
1
  import * as errors from "@superbuilders/errors"
38
2
  import {
39
3
  type BoundsOnTarget,
@@ -52,20 +16,12 @@ import { type ClosedRoster, rosterOf } from "#fields.ts"
52
16
  import type { AnyRelation, RelationFields } from "#relation.ts"
53
17
  import { type CapacityWindowSpec, renderCapacityWindow, renderWeight, type WeightSpec } from "#spec.ts"
54
18
 
55
- /** A `key()` statement's runtime description — owner and projection carried at exact types. */
56
19
  interface KeyData<R extends AnyRelation, Projection extends readonly string[]> {
57
20
  readonly kind: "key"
58
21
  readonly owner: R
59
22
  readonly projection: Projection
60
23
  }
61
24
 
62
- /**
63
- * A containment statement's runtime description — the two faces carried at
64
- * their EXACT types (owner names and projection tuples are honest runtime
65
- * properties, and they are the type-level carrier `schema()`'s law-typing
66
- * pairs slots through). The defaults are the wide shape renderers and the
67
- * wire lowering consume.
68
- */
69
25
  interface ContainmentData<Src extends FaceData = FaceData, Tgt extends FaceData = FaceData> {
70
26
  readonly kind: "containment"
71
27
  readonly source: Src
@@ -78,12 +34,6 @@ interface MirrorsData<Src extends FaceData = FaceData, Tgt extends FaceData = Fa
78
34
  readonly target: Tgt
79
35
  }
80
36
 
81
- /**
82
- * A capacity statement's runtime description — target-left, in the
83
- * operator's own order (C2: target, weight, window, source), faces at
84
- * exact types like {@link ContainmentData}. The weight is ALWAYS present
85
- * (C4 — `unit` is a case, not an absence).
86
- */
87
37
  interface CapacityData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
88
38
  readonly kind: "capacity"
89
39
  readonly target: Tgt
@@ -92,69 +42,31 @@ interface CapacityData<Tgt extends FaceData = FaceData, Src extends FaceData = F
92
42
  readonly source: Src
93
43
  }
94
44
 
95
- /** One statement's runtime description, tagged by form. */
96
45
  type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | MirrorsData | CapacityData
97
46
 
98
- /**
99
- * The admission brand — a module-private symbol, deliberately unexported
100
- * (the `capacity.ts` pattern): `Statement` is a public structural type, so
101
- * without this brand a forged plain object of the right shape would walk
102
- * past the construction-time arity and roster walls into `schema()` — and
103
- * the roster wall is the one the engine cannot backstop (the wire carries
104
- * plain u64s, no rosters). The symbol makes the four constructors the ONLY
105
- * mints, so a statement that skipped the walls is unspellable.
106
- */
107
47
  const admitted: unique symbol = Symbol("bumbledb.statement.admitted")
108
48
 
109
- /** An opaque statement value — what `schema()` assembles into a theory. Only the four constructors produce one. */
110
49
  interface Statement {
111
50
  readonly data: StatementData
112
51
  readonly [admitted]: true
113
52
  }
114
53
 
115
- /**
116
- * Narrows any value to an admitted statement — the probe is the
117
- * module-private {@link admitted} brand only the four constructors set, so
118
- * no host-built value (fact cells are structurally OPEN — an interval with
119
- * an excess `kind` property is a legal cell) can ever be misread as one.
120
- * The keyed-get selector dispatch and `schema()`'s admission both judge
121
- * through here.
122
- */
123
54
  function isStatement(value: unknown): value is Statement {
124
55
  return typeof value === "object" && value !== null && admitted in value
125
56
  }
126
57
 
127
- /**
128
- * A containment (or `==` bijection) statement as a TYPED value: `data`
129
- * carries both faces at their exact types, so the schema-level class laws
130
- * can read every paired (relation, field) slot off the statement type —
131
- * spell the statement list inline in `schema()` and the equivalence
132
- * classes compute at the type level too. Structurally still a plain
133
- * {@link Statement}.
134
- */
135
58
  interface ContainedStatement<Src extends FaceData, Tgt extends FaceData> extends Statement {
136
59
  readonly data: ContainmentData<Src, Tgt> | MirrorsData<Src, Tgt>
137
60
  }
138
61
 
139
- /** A capacity statement as a TYPED value — the {@link ContainedStatement} of the capacity form. */
140
62
  interface CapacityStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
141
63
  readonly data: CapacityData<Tgt, Src>
142
64
  }
143
65
 
144
- /**
145
- * A `key()` statement as a TYPED value: its `data` carries the owner
146
- * relation and the projection tuple at their EXACT types (honest runtime
147
- * properties — no phantom), which is what the key-statement-selected
148
- * `get(relation, keyStatement, key)` overload types its key object by
149
- * (`docs/architecture/70-api.md` § the freeze, the multi-key typed get) and
150
- * what resolves each projected field's descriptor through the owner's
151
- * schema type. Structurally still a plain {@link Statement}.
152
- */
153
66
  interface KeyStatement<R extends AnyRelation, Projection extends readonly string[]> extends Statement {
154
67
  readonly data: KeyData<R, Projection>
155
68
  }
156
69
 
157
- /** Renders one face position's closedness for the roster-agreement diagnostics. */
158
70
  function renderRosterSide(roster: ClosedRoster | undefined): string {
159
71
  return roster === undefined ? "a bare column" : `a ${roster.name} reference`
160
72
  }
@@ -176,22 +88,6 @@ function assertArityAgreement(source: FaceData, target: FaceData, statement: Sta
176
88
  }
177
89
  }
178
90
 
179
- /**
180
- * The runtime twin of {@link SameShapes}'s roster slot: the two faces'
181
- * projected descriptors must agree POSITIONWISE on closedness — the same
182
- * roster (value identity — vocabulary identity is value identity, the
183
- * SDK's membership rule everywhere) or none. Without this wall a plain u64
184
- * column could alias a closed vocabulary through a declared containment
185
- * (`docs/architecture/10-data-model.md` spells the ENGINE encoding that
186
- * way), and every descriptor-keyed closed judgment — the orderable ban,
187
- * the name↔id marshal, answer decode — would silently miss it. The
188
- * vocabulary's own descriptor (`Kind.id`) is the ONE spelling of a closed
189
- * reference at this surface (the canonical-utterance law); the engine
190
- * cannot backstop this one — the wire carries plain u64s, no rosters.
191
- * Arity agreement ({@link assertArityAgreement}) runs first, so the
192
- * positionwise walk here never sees an unpaired position from a well-typed
193
- * OR an untyped caller.
194
- */
195
91
  function assertRosterAgreement(source: FaceData, target: FaceData, statement: Statement): void {
196
92
  source.projection.forEach(function agreeAt(fieldName, position) {
197
93
  const targetField = target.projection[position]
@@ -217,7 +113,12 @@ function assertRosterAgreement(source: FaceData, target: FaceData, statement: St
217
113
  * checked against `R`'s field block in the type, and the tuple is carried
218
114
  * in the returned value's type ({@link KeyStatement}) — keyed point reads
219
115
  * through THIS statement are typed field-for-field, descriptors resolvable
220
- * through the owner's schema type.
116
+ * through the owner's schema type. A DUPLICATE field in the projection is
117
+ * refused here, at the mint (the engine's `FieldSet` refuses the same
118
+ * duplicate at `Db.create`; canonical utterance says the twice-spelled
119
+ * field is the once-spelled projection respelled) — without this wall a
120
+ * `new Set`-collapsed duplicate could set-match a shorter target
121
+ * projection the engine refuses.
221
122
  */
222
123
  function key<
223
124
  R extends AnyRelation,
@@ -228,6 +129,15 @@ function key<
228
129
  `key(${relation.name}, ...): closedness already materializes ${relation.name}(id) -> ${relation.name} — an explicit key on a closed relation is rejected as a duplicate`
229
130
  )
230
131
  }
132
+ const seen = new Set<string>()
133
+ for (const fieldName of fields) {
134
+ if (seen.has(fieldName)) {
135
+ throw errors.new(
136
+ `key(${relation.name}, ...): the projection spells ${fieldName} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
137
+ )
138
+ }
139
+ seen.add(fieldName)
140
+ }
231
141
  const data: KeyData<R, Projection> = Object.freeze({
232
142
  kind: "key",
233
143
  owner: relation,
@@ -236,15 +146,6 @@ function key<
236
146
  return Object.freeze({ data, [admitted]: true as const })
237
147
  }
238
148
 
239
- /**
240
- * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. Arity mismatch
241
- * between the two faces is a type error ({@link SameArity}); a structurally
242
- * mismatched pair is a type error ({@link SameShapes} — positionwise
243
- * equality of the projected kind/width/element triples). The target side
244
- * must resolve a declared key of B — a SEMANTIC property of the whole
245
- * statement set that no face type can state, DELIBERATELY judged by the
246
- * engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
247
- */
248
149
  function contained<A extends AnyFace, B extends AnyFace>(
249
150
  source: A,
250
151
  target: B & SameArity<A, B> & SameShapes<A, B>
@@ -260,15 +161,6 @@ function contained<A extends AnyFace, B extends AnyFace>(
260
161
  return statement
261
162
  }
262
163
 
263
- /**
264
- * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance: the
265
- * selected `==` bijection, a keyed one-to-one correspondence between the
266
- * two faces (each side contains the other). It lowers to the two adjacent
267
- * containments in the `A <= B` first order (macro parity — the engine
268
- * performs the split, source-first) and renders as `==` once, in the
269
- * written orientation. Faces pair by arity AND structural shape, exactly
270
- * as {@link contained}.
271
- */
272
164
  function mirrors<A extends AnyFace, B extends AnyFace>(
273
165
  source: A,
274
166
  target: B & SameArity<A, B> & SameShapes<A, B>
@@ -284,14 +176,6 @@ function mirrors<A extends AnyFace, B extends AnyFace>(
284
176
  return statement
285
177
  }
286
178
 
287
- /**
288
- * The runtime twin of the weight source wall ({@link WeightOnSource}): the
289
- * weighed field must be a u64-encoded position of the SOURCE's own row
290
- * (a signed weight would break the polarity scheduler — the illegal weight
291
- * is unrepresentable, not checked), an interval position for the
292
- * `Duration(...)` form. Judged at CONSTRUCTION for untyped callers; the
293
- * engine's `validate_capacity` stays the final authority.
294
- */
295
179
  function assertWeightOnSource(weight: WeightSpec, source: FaceData, statement: Statement): void {
296
180
  if (weight.kind === "unit") {
297
181
  return
@@ -314,14 +198,6 @@ function assertWeightOnSource(weight: WeightSpec, source: FaceData, statement: S
314
198
  }
315
199
  }
316
200
 
317
- /**
318
- * The runtime twin of the dependent-bound target wall
319
- * ({@link BoundsOnTarget}): a `ref()` bound must name a u64 field of the
320
- * TARGET's own row, a `duration()` bound an interval field — bound names
321
- * resolve against the target's FULL roster (C1), never the projection.
322
- * `within()` mints dependent bounds in the hi slot only (C6), but the walk
323
- * here is total over the window's bound slots.
324
- */
325
201
  function assertBoundsOnTarget(window: CapacityWindowSpec, target: FaceData, statement: Statement): void {
326
202
  const bounds = window.kind === "range" ? [window.lo, window.hi] : [window.kind === "exact" ? window.n : window.lo]
327
203
  for (const bound of bounds) {
@@ -347,26 +223,6 @@ function assertBoundsOnTarget(window: CapacityWindowSpec, target: FaceData, stat
347
223
  }
348
224
  }
349
225
 
350
- /**
351
- * `B(Y|ψ) <=[w]{window} A(X|φ)` — the capacity statement, the one
352
- * extension form: per ψ-selected target fact, the group of φ-selected
353
- * source facts sharing its key tuple must have its MEASURE (Σ weight; the
354
- * unit weight IS the count instance) inside the window. READ CAREFULLY:
355
- * the LEFT face is the TARGET, the per-group parent (B-family, target-left
356
- * — macro parity), and the RIGHT face is the weighed source. Two
357
- * overloads mirror the operator positionally (target, weight?, window,
358
- * source): `capacity(on(Holder, "id"), within(0n, 3n), on(Account,
359
- * "holder"))` says each Holder id groups at most three Account rows;
360
- * `capacity(on(Pool, "id"), weigh("watts"), within(0n, ref("supply")),
361
- * on(Device, "pool"))` bounds each pool's summed draw by the pool's own
362
- * row. The two faces pair by arity AND structural shape
363
- * ({@link SameShapes}), exactly as containment — the grouping join reads
364
- * the same positionwise field pairing. The weight-sensitive bans ride the
365
- * UNIT overload only: `{1..*}` ({@link UnitWindowBan} — on a weighted
366
- * statement "positive total" is a different, weaker law than containment)
367
- * and the `duration()` bound ({@link UnitDimensionBan} — a count of facts
368
- * bounded by a span of time mixes dimensions, C18).
369
- */
370
226
  function capacity<B extends AnyFace, W extends CapacityWindow, A extends AnyFace>(
371
227
  target: B,
372
228
  window: W & UnitWindowBan<W> & UnitDimensionBan<W> & BoundsOnTarget<W, B>,
@@ -407,10 +263,14 @@ function capacity(
407
263
  "`{1..*}` on the unit instance says only what the bare containment says — drop the annotation and write the containment: contained(source, target)"
408
264
  )
409
265
  }
410
- // The C18 dimension gate, unit instance (the engine's
266
+ if (weight.kind === "unit" && window.kind === "floor") {
267
+ throw errors.new(
268
+ "`{N..*}` on the unit instance — a bare count floor is refused; weigh the source (`<=[w]{N..*}` stays legal) or drop the bound"
269
+ )
270
+ }
271
+
411
272
  // CapacityDimensionMixing twin — ruled 2026-07-24): a count of facts
412
- // bounded by a span of time mixes dimensions. Judged here for untyped
413
- // callers; the engine's validate_capacity stays the final authority.
273
+
414
274
  if (weight.kind === "unit" && window.kind === "range" && window.hi.kind === "durationField") {
415
275
  throw errors.new(
416
276
  `a unit (count) window against the duration() bound on ${window.hi.field} mixes dimensions (C18) — weigh the source with weigh(duration(field)), or bound by a u64 field or literal`
@@ -431,18 +291,6 @@ function capacity(
431
291
  return statement
432
292
  }
433
293
 
434
- /**
435
- * Renders one statement in the CANONICAL macro spelling
436
- * (`docs/architecture/70-api.md`; the engine's `schema/render.rs` emits the
437
- * same shapes for violations) — `Account(id) -> Account`,
438
- * `Account(holder) <= Holder(id)`,
439
- * `Account(id | kind == Savings) == SavingsTerms(account)`,
440
- * `Holder(id) <={0..3} Account(holder)`,
441
- * `Pool(id) <=[watts]{0..supply} Device(pool)` — so TS-side errors and
442
- * engine-side diagnostics read identically. A renderer, never a parser:
443
- * strings are output-only. The unit weight renders nothing — the count
444
- * utterance falls out of the one printer.
445
- */
446
294
  function renderStatement(statement: Statement): string {
447
295
  const data = statement.data
448
296
  switch (data.kind) {