@bjornpagen/bumbledb 0.3.0 → 0.4.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 (66) hide show
  1. package/COOKBOOK.md +66 -46
  2. package/README.md +28 -15
  3. package/dist/closed.d.ts +50 -73
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +38 -109
  6. package/dist/closed.js.map +1 -1
  7. package/dist/db.d.ts +4 -1
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +26 -3
  10. package/dist/db.js.map +1 -1
  11. package/dist/face.d.ts +39 -39
  12. package/dist/face.d.ts.map +1 -1
  13. package/dist/face.js +7 -16
  14. package/dist/face.js.map +1 -1
  15. package/dist/fields.d.ts +28 -14
  16. package/dist/fields.d.ts.map +1 -1
  17. package/dist/fields.js +15 -14
  18. package/dist/fields.js.map +1 -1
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/marshal.d.ts +33 -6
  24. package/dist/marshal.d.ts.map +1 -1
  25. package/dist/marshal.js +67 -6
  26. package/dist/marshal.js.map +1 -1
  27. package/dist/query/atom.d.ts +59 -14
  28. package/dist/query/atom.d.ts.map +1 -1
  29. package/dist/query/atom.js +3 -0
  30. package/dist/query/atom.js.map +1 -1
  31. package/dist/query/lower.d.ts.map +1 -1
  32. package/dist/query/lower.js +251 -26
  33. package/dist/query/lower.js.map +1 -1
  34. package/dist/query/run.d.ts +15 -5
  35. package/dist/query/run.d.ts.map +1 -1
  36. package/dist/query/run.js +26 -6
  37. package/dist/query/run.js.map +1 -1
  38. package/dist/query/scope.d.ts +35 -13
  39. package/dist/query/scope.d.ts.map +1 -1
  40. package/dist/query/scope.js +16 -4
  41. package/dist/query/scope.js.map +1 -1
  42. package/dist/relation.d.ts +8 -7
  43. package/dist/relation.d.ts.map +1 -1
  44. package/dist/relation.js +32 -10
  45. package/dist/relation.js.map +1 -1
  46. package/dist/spec.d.ts +3 -2
  47. package/dist/spec.d.ts.map +1 -1
  48. package/dist/spec.js.map +1 -1
  49. package/dist/statements.d.ts +10 -4
  50. package/dist/statements.d.ts.map +1 -1
  51. package/dist/statements.js +79 -7
  52. package/dist/statements.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/closed.ts +72 -179
  55. package/src/db.ts +42 -5
  56. package/src/face.ts +34 -45
  57. package/src/fields.ts +46 -34
  58. package/src/index.ts +1 -2
  59. package/src/marshal.ts +74 -7
  60. package/src/query/atom.ts +58 -15
  61. package/src/query/lower.ts +301 -28
  62. package/src/query/run.ts +26 -6
  63. package/src/query/scope.ts +49 -15
  64. package/src/relation.ts +45 -17
  65. package/src/spec.ts +3 -2
  66. package/src/statements.ts +86 -7
@@ -228,25 +228,38 @@ type WidthOf<F extends AnyField> = F extends { readonly width: infer W } ? W : u
228
228
  /** Reads an interval descriptor's element kind; `undefined` on scalar kinds. */
229
229
  type ElementOf<F extends AnyField> = F extends { readonly element: infer E } ? E : undefined
230
230
 
231
+ /** Reads a closed reference's handle union; `undefined` on every non-closed kind (the roster IS descriptor structure). */
232
+ type RosterOf<F extends AnyField> = F extends {
233
+ readonly closed: { readonly handles: readonly (infer H extends string)[] }
234
+ }
235
+ ? H
236
+ : undefined
237
+
231
238
  /**
232
239
  * The join judgment: two bound slots join iff their descriptors' structure
233
- * agrees (kind, width label, interval element) AND their law-computed
234
- * classes agree same class name joins, and bare (`undefined`) pairs only
235
- * with bare (ruling 3: a field in no law has no class; a bare↔classed
236
- * pairing refuses). The class names come off the SCHEMA type's class map —
237
- * the statements are the typing; no descriptor label exists to compare.
240
+ * agrees (kind, width label, interval element, and the closed ROSTER — a
241
+ * closed reference pairs only with the same vocabulary, never with a bare
242
+ * u64: the roster keys every closed judgment downstream, so a join across
243
+ * it would decode/order/translate incoherently by binding order) AND their
244
+ * law-computed classes agree same class name joins, and bare
245
+ * (`undefined`) pairs only with bare (ruling 3: a field in no law has no
246
+ * class; a bare↔classed pairing refuses). The class names come off the
247
+ * SCHEMA type's class map — the statements are the typing; no descriptor
248
+ * label beyond the roster exists to compare.
238
249
  */
239
250
  type JoinOk<A extends ClassedField, B extends ClassedField> = [
240
251
  A["field"]["kind"],
241
252
  A["class"],
242
253
  WidthOf<A["field"]>,
243
- ElementOf<A["field"]>
244
- ] extends [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>]
245
- ? [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>] extends [
254
+ ElementOf<A["field"]>,
255
+ RosterOf<A["field"]>
256
+ ] extends [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>, RosterOf<B["field"]>]
257
+ ? [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>, RosterOf<B["field"]>] extends [
246
258
  A["field"]["kind"],
247
259
  A["class"],
248
260
  WidthOf<A["field"]>,
249
- ElementOf<A["field"]>
261
+ ElementOf<A["field"]>,
262
+ RosterOf<A["field"]>
250
263
  ]
251
264
  ? true
252
265
  : false
@@ -255,7 +268,8 @@ type JoinOk<A extends ClassedField, B extends ClassedField> = [
255
268
  /**
256
269
  * The runtime twin of {@link JoinOk}: two bound slots join iff descriptor
257
270
  * structure and class agree — the same comparison the type tier makes,
258
- * judged on the honest runtime values (the descriptor and the schema
271
+ * judged on the honest runtime values (the descriptor, the roster by VALUE
272
+ * IDENTITY — vocabulary identity is value identity — and the schema
259
273
  * value's frozen class map). The rule builders throw through this on a
260
274
  * class-unequal variable reuse, so the wall holds for untyped callers too,
261
275
  * not only where the compiler can see.
@@ -265,17 +279,30 @@ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
265
279
  const widthB = "width" in b.field ? b.field.width : undefined
266
280
  const elementA = "element" in a.field ? a.field.element : undefined
267
281
  const elementB = "element" in b.field ? b.field.element : undefined
268
- return a.field.kind === b.field.kind && a.class === b.class && widthA === widthB && elementA === elementB
282
+ const rosterA = "closed" in a.field ? a.field.closed : undefined
283
+ const rosterB = "closed" in b.field ? b.field.closed : undefined
284
+ return (
285
+ a.field.kind === b.field.kind &&
286
+ a.class === b.class &&
287
+ widthA === widthB &&
288
+ elementA === elementB &&
289
+ rosterA === rosterB
290
+ )
269
291
  }
270
292
 
271
293
  /**
272
294
  * Renders one bound slot for join-mismatch diagnostics — the structural
273
- * kind in the schema grammar's spelling plus the slot's law-computed class
274
- * (`u64 in class Holder.id`; a lawless slot renders `bare`).
295
+ * kind in the schema grammar's spelling (a closed reference names its
296
+ * vocabulary: the roster is part of the structure being compared) plus the
297
+ * slot's law-computed class (`u64 in class Holder.id`; a lawless slot
298
+ * renders `bare`).
275
299
  */
276
300
  function renderFieldKind(slot: ClassedField): string {
277
301
  const field = slot.field
278
302
  let base: string = field.kind
303
+ if ("closed" in field) {
304
+ base = `u64 referencing ${field.closed.name}`
305
+ }
279
306
  if (field.kind === "bytes") {
280
307
  base = `bytes<${field.width}>`
281
308
  }
@@ -287,7 +314,10 @@ function renderFieldKind(slot: ClassedField): string {
287
314
 
288
315
  /**
289
316
  * What a PARAM anchored at field `F` accepts at execution: the field's
290
- * bare value type, exactly. At an interval field the engine resolves the
317
+ * bare value type, exactly at a CLOSED-reference field that is the
318
+ * handle-name union (`"DirectPass" | "Failed"`), translated name → row id
319
+ * at execute through the one roster-verification point
320
+ * (`taggedHandleId`). At an interval field the engine resolves the
291
321
  * bivalent anchor to the INTERVAL reading (value equality) — the point
292
322
  * reading of a param is spelled `pointIn(r.param(...), w)`, whose sibling
293
323
  * anchors it element-typed.
@@ -304,13 +334,17 @@ type InferredOf<T> = T extends { readonly [inferred]?: infer S } ? Exclude<S, un
304
334
  * positions) — the op keeps literal tagging op-aware at `pointIn`
305
335
  * (the bug-hunt fix, preserved). `anchor` is `undefined` only on a query
306
336
  * built but not yet anchored by any rule; lowering and the wire both refuse
307
- * that state typed.
337
+ * that state typed. `members` is present exactly on a MEMBERSHIP-ARRAY
338
+ * entry (a literal set at a closed field, folded into the program): the
339
+ * SDK itself translates and supplies the set at every execute — the entry
340
+ * is never read from, and never demanded of, the host's params object.
308
341
  */
309
342
  interface ParamEntry {
310
343
  readonly name: string
311
344
  readonly shape: "value" | "set" | "mask"
312
345
  readonly anchor: AnyField | "measure" | undefined
313
346
  readonly op: "binding" | "eq" | "ne" | "lt" | "le" | "gt" | "ge" | "pointIn" | "allen"
347
+ readonly members: readonly string[] | undefined
314
348
  }
315
349
 
316
350
  export type {
package/src/relation.ts CHANGED
@@ -12,9 +12,8 @@
12
12
  */
13
13
 
14
14
  import * as errors from "@superbuilders/errors"
15
- import type { OneOf } from "#face.ts"
16
15
  import { type AnyField, assertDeclarationOrderKey, type Infer, literalOf } from "#fields.ts"
17
- import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
16
+ import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
18
17
 
19
18
  /** Flattens an intersection into one displayed object type (hover legibility). */
20
19
  type Flatten<T> = { [K in keyof T]: T[K] }
@@ -37,16 +36,41 @@ function refsComplete<RName extends string, Fields extends FieldsShape>(
37
36
  }
38
37
 
39
38
  /**
40
- * Resolves one selection entry to its lowered literal set: an `oneOf`
41
- * value (detected by its `literals` tuple — no field value is ever an
42
- * object carrying `literals`) becomes a disjunctive set (≥ 2 by the
43
- * `oneOf` signature the one-element set is unwritable); anything else is
44
- * the bare literal.
39
+ * Resolves one selection entry to its lowered literal set: a plain ARRAY
40
+ * (detected by `Array.isArray` — no field's value type is an array;
41
+ * `Uint8Array` is not one) becomes a disjunctive set, anything else the
42
+ * bare literal. The degenerate sets are construction errors, each
43
+ * self-locating (`context` names the relation and field) — the empty set
44
+ * selects nothing, the one-element set is the bare literal respelled, and
45
+ * a DUPLICATE literal (judged on the canonical rendering — the engine's
46
+ * own duplicate test, reached here first so its index-speak twin at
47
+ * `Db.create` stays unreachable from this surface) is the same respelling
48
+ * in disguise (the canonical-utterance law; the old set combinator's
49
+ * signature made the length degenerates unwritable, and the refusals here
50
+ * are that law's runtime seat). The lowered set — `{ kind: "many",
51
+ * literals }` — is byte-identical to what the combinator produced, so no
52
+ * fingerprint moves.
45
53
  */
46
- function resolveEntry(field: AnyField, entry: unknown): LiteralSetSpec {
47
- if (typeof entry === "object" && entry !== null && "literals" in entry && Array.isArray(entry.literals)) {
48
- const literals: LiteralSpec[] = entry.literals.map(function lowerSetLiteral(literal: unknown) {
49
- return Object.freeze(literalOf(field, literal))
54
+ function resolveEntry(context: string, field: AnyField, entry: unknown): LiteralSetSpec {
55
+ if (Array.isArray(entry)) {
56
+ if (entry.length < 2) {
57
+ throw errors.new(
58
+ entry.length === 0
59
+ ? `${context}: an empty literal set selects nothing — write the selection you mean`
60
+ : `${context}: a one-element literal set is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
61
+ )
62
+ }
63
+ const seen = new Set<string>()
64
+ const literals: LiteralSpec[] = entry.map(function lowerSetLiteral(literal: unknown) {
65
+ const lowered = Object.freeze(literalOf(field, literal))
66
+ const rendered = renderLiteral(lowered)
67
+ if (seen.has(rendered)) {
68
+ throw errors.new(
69
+ `${context}: the literal set spells ${rendered} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
70
+ )
71
+ }
72
+ seen.add(rendered)
73
+ return lowered
50
74
  })
51
75
  return Object.freeze({ kind: "many", literals: Object.freeze(literals) })
52
76
  }
@@ -78,7 +102,9 @@ function resolveSelection(
78
102
  if (declared === undefined) {
79
103
  throw errors.new(`relation ${name} has no field ${fieldName}`)
80
104
  }
81
- bindings.push(Object.freeze({ field: fieldName, set: resolveEntry(declared.field, entry) }))
105
+ bindings.push(
106
+ Object.freeze({ field: fieldName, set: resolveEntry(`relation ${name}.${fieldName}`, declared.field, entry) })
107
+ )
82
108
  }
83
109
  if (bindings.length === 0) {
84
110
  throw errors.new(
@@ -131,13 +157,15 @@ interface SelectionBinding {
131
157
 
132
158
  /**
133
159
  * The `where()` argument: per field, a bare structural literal of that
134
- * field's value type (a closed handle constant IS such a literal a
135
- * bigint verified against the roster at construction), an `oneOf(a, b,
136
- * ...)` literal set, or a `span(start, end)` interval literal.
137
- * Equality-only by construction: no operator parameter exists anywhere.
160
+ * field's value type (a closed reference's literal IS its handle name
161
+ * `"Savings"`, verified against the roster at construction), a plain
162
+ * ARRAY of such literals read disjunctively `kind: ["Checking",
163
+ * "Savings"]` or a `span(start, end)` interval literal. Membership is
164
+ * an array, never an operator (the drizzle law); equality-only by
165
+ * construction: no operator parameter exists anywhere.
138
166
  */
139
167
  type SelectionInput<Fields extends FieldsShape> = {
140
- readonly [K in keyof Fields]?: Infer<Fields[K]> | OneOf<Infer<Fields[K]>>
168
+ readonly [K in keyof Fields]?: Infer<Fields[K]> | readonly Infer<Fields[K]>[]
141
169
  }
142
170
 
143
171
  /** A relation with a selection applied — what `on()` consumes as a σ-carrying source. */
package/src/spec.ts CHANGED
@@ -62,8 +62,9 @@ type LiteralSpec =
62
62
 
63
63
  /**
64
64
  * One σ binding's right side: a single literal or a literal set (read
65
- * disjunctively). The SDK's selection constructors make the degenerate sets
66
- * unwritable (`oneOf` demands two leading literals), so a lowered `many`
65
+ * disjunctively). The SDK's selection resolver refuses the degenerate sets
66
+ * (a membership array needs two members the empty set selects nothing,
67
+ * the one-element set is the bare literal respelled), so a lowered `many`
67
68
  * always carries ≥ 2 literals.
68
69
  */
69
70
  type LiteralSetSpec =
package/src/statements.ts CHANGED
@@ -8,10 +8,16 @@
8
8
  * Every field reference is checked against the relation it names in the
9
9
  * TYPE — existence through {@link FaceFields} (`on(R, "nope")` does not
10
10
  * compile) and STRUCTURAL compatibility through {@link SameShapes}: the two
11
- * faces' projected kind/width/element triples are read off the schema type
12
- * (the minimal kernel — descriptors are pure structure) and constrained
13
- * positionwise equal, so a u64 face against a str face, a bytes width
14
- * mismatch, or an interval element mismatch is a compile error. Domains are
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. The ROSTER slot carries a
16
+ * construction-time runtime twin here ({@link assertRosterAgreement} —
17
+ * roster IDENTITY, positionwise: a closed vocabulary's referencing column
18
+ * is spelled with the vocabulary's own id descriptor, the ONE spelling, so
19
+ * a plain u64 column can never alias a vocabulary through a declared law
20
+ * and the SDK's descriptor-keyed closed judgments stay sound). Domains are
15
21
  * NOT compared here — there is no domain to compare at construction: the
16
22
  * statements themselves are what define the equivalence classes, and the
17
23
  * domain wall lives where they aggregate — `schema()` (the
@@ -28,6 +34,7 @@
28
34
  import * as errors from "@superbuilders/errors"
29
35
  import type { Count } from "#count.ts"
30
36
  import { type AnyFace, type FaceData, renderFace, type SameArity, type SameShapes } from "#face.ts"
37
+ import type { AnyField, ClosedRoster } from "#fields.ts"
31
38
  import type { AnyRelation, RelationFields } from "#relation.ts"
32
39
  import { renderWindow, type WindowSpec } from "#spec.ts"
33
40
 
@@ -98,6 +105,72 @@ interface KeyStatement<R extends AnyRelation, Projection extends readonly string
98
105
  readonly data: KeyData<R, Projection>
99
106
  }
100
107
 
108
+ /**
109
+ * The field descriptor one face position projects: an ordinary relation's
110
+ * declared field; a closed relation's SEALED shape — the synthetic `id`
111
+ * (the value's own roster-carrying descriptor, by identity) or a declared
112
+ * payload column. `undefined` when the name is foreign (the type tier makes
113
+ * that unwritable; the engine re-judges projections at `Db.create`).
114
+ */
115
+ function projectedFieldOf(face: FaceData, fieldName: string): AnyField | undefined {
116
+ const owner = face.owner
117
+ if ("axioms" in owner) {
118
+ if (fieldName === "id") {
119
+ return owner.id
120
+ }
121
+ const column = owner.data.columns.find(function byName(candidate) {
122
+ return candidate.name === fieldName
123
+ })
124
+ return column?.field
125
+ }
126
+ const declared = owner.data.fields.find(function byName(candidate) {
127
+ return candidate.name === fieldName
128
+ })
129
+ return declared?.field
130
+ }
131
+
132
+ /** The roster a descriptor carries: present exactly on a closed reference, absent on every other kind. */
133
+ function rosterOfField(field: AnyField | undefined): ClosedRoster | undefined {
134
+ if (field !== undefined && "closed" in field) {
135
+ return field.closed
136
+ }
137
+ return undefined
138
+ }
139
+
140
+ /** Renders one face position's closedness for the roster-agreement diagnostics. */
141
+ function renderRosterSide(roster: ClosedRoster | undefined): string {
142
+ return roster === undefined ? "a bare column" : `a ${roster.name} reference`
143
+ }
144
+
145
+ /**
146
+ * The runtime twin of {@link SameShapes}'s roster slot: the two faces'
147
+ * projected descriptors must agree POSITIONWISE on closedness — the same
148
+ * roster (value identity — vocabulary identity is value identity, the
149
+ * SDK's membership rule everywhere) or none. Without this wall a plain u64
150
+ * column could alias a closed vocabulary through a declared containment
151
+ * (`docs/architecture/10-data-model.md` spells the ENGINE encoding that
152
+ * way), and every descriptor-keyed closed judgment — the orderable ban,
153
+ * the name↔id marshal, answer decode — would silently miss it. The
154
+ * vocabulary's own descriptor (`Kind.id`) is the ONE spelling of a closed
155
+ * reference at this surface (the canonical-utterance law); the engine
156
+ * cannot backstop this one — the wire carries plain u64s, no rosters.
157
+ */
158
+ function assertRosterAgreement(source: FaceData, target: FaceData, statement: Statement): void {
159
+ source.projection.forEach(function agreeAt(fieldName, position) {
160
+ const targetField = target.projection[position]
161
+ if (targetField === undefined) {
162
+ return
163
+ }
164
+ const sourceRoster = rosterOfField(projectedFieldOf(source, fieldName))
165
+ const targetRoster = rosterOfField(projectedFieldOf(target, targetField))
166
+ if (sourceRoster !== targetRoster) {
167
+ throw errors.new(
168
+ `${source.owner.name}.${fieldName} is ${renderRosterSide(sourceRoster)} but ${target.owner.name}.${targetField} is ${renderRosterSide(targetRoster)} — closedness rides the descriptor: a closed reference is spelled with the vocabulary's own id descriptor (one meaning, one spelling), so faces pair closed-with-closed through one roster or bare-with-bare, never across — ${renderStatement(statement)}`
169
+ )
170
+ }
171
+ })
172
+ }
173
+
101
174
  /**
102
175
  * `R(X) -> R` — the FD key form, composite keys as tuples. No selection
103
176
  * parameter exists (the FD-with-selection shape is unrepresentable, as in
@@ -145,7 +218,9 @@ function contained<A extends AnyFace, B extends AnyFace>(
145
218
  target: target.data,
146
219
  bidirectional: false
147
220
  })
148
- return Object.freeze({ data })
221
+ const statement = Object.freeze({ data })
222
+ assertRosterAgreement(data.source, data.target, statement)
223
+ return statement
149
224
  }
150
225
 
151
226
  /**
@@ -167,7 +242,9 @@ function mirrors<A extends AnyFace, B extends AnyFace>(
167
242
  target: target.data,
168
243
  bidirectional: true
169
244
  })
170
- return Object.freeze({ data })
245
+ const statement = Object.freeze({ data })
246
+ assertRosterAgreement(data.source, data.target, statement)
247
+ return statement
171
248
  }
172
249
 
173
250
  /**
@@ -191,7 +268,9 @@ function window<B extends AnyFace, A extends AnyFace>(
191
268
  window: count.window,
192
269
  source: source.data
193
270
  })
194
- return Object.freeze({ data })
271
+ const statement = Object.freeze({ data })
272
+ assertRosterAgreement(data.source, data.target, statement)
273
+ return statement
195
274
  }
196
275
 
197
276
  /**