@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
@@ -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
@@ -27,6 +33,66 @@
27
33
  import * as errors from "@superbuilders/errors";
28
34
  import { renderFace } from "#face.ts";
29
35
  import { renderWindow } from "#spec.ts";
36
+ /**
37
+ * The field descriptor one face position projects: an ordinary relation's
38
+ * declared field; a closed relation's SEALED shape — the synthetic `id`
39
+ * (the value's own roster-carrying descriptor, by identity) or a declared
40
+ * payload column. `undefined` when the name is foreign (the type tier makes
41
+ * that unwritable; the engine re-judges projections at `Db.create`).
42
+ */
43
+ function projectedFieldOf(face, fieldName) {
44
+ const owner = face.owner;
45
+ if ("axioms" in owner) {
46
+ if (fieldName === "id") {
47
+ return owner.id;
48
+ }
49
+ const column = owner.data.columns.find(function byName(candidate) {
50
+ return candidate.name === fieldName;
51
+ });
52
+ return column?.field;
53
+ }
54
+ const declared = owner.data.fields.find(function byName(candidate) {
55
+ return candidate.name === fieldName;
56
+ });
57
+ return declared?.field;
58
+ }
59
+ /** The roster a descriptor carries: present exactly on a closed reference, absent on every other kind. */
60
+ function rosterOfField(field) {
61
+ if (field !== undefined && "closed" in field) {
62
+ return field.closed;
63
+ }
64
+ return undefined;
65
+ }
66
+ /** Renders one face position's closedness for the roster-agreement diagnostics. */
67
+ function renderRosterSide(roster) {
68
+ return roster === undefined ? "a bare column" : `a ${roster.name} reference`;
69
+ }
70
+ /**
71
+ * The runtime twin of {@link SameShapes}'s roster slot: the two faces'
72
+ * projected descriptors must agree POSITIONWISE on closedness — the same
73
+ * roster (value identity — vocabulary identity is value identity, the
74
+ * SDK's membership rule everywhere) or none. Without this wall a plain u64
75
+ * column could alias a closed vocabulary through a declared containment
76
+ * (`docs/architecture/10-data-model.md` spells the ENGINE encoding that
77
+ * way), and every descriptor-keyed closed judgment — the orderable ban,
78
+ * the name↔id marshal, answer decode — would silently miss it. The
79
+ * vocabulary's own descriptor (`Kind.id`) is the ONE spelling of a closed
80
+ * reference at this surface (the canonical-utterance law); the engine
81
+ * cannot backstop this one — the wire carries plain u64s, no rosters.
82
+ */
83
+ function assertRosterAgreement(source, target, statement) {
84
+ source.projection.forEach(function agreeAt(fieldName, position) {
85
+ const targetField = target.projection[position];
86
+ if (targetField === undefined) {
87
+ return;
88
+ }
89
+ const sourceRoster = rosterOfField(projectedFieldOf(source, fieldName));
90
+ const targetRoster = rosterOfField(projectedFieldOf(target, targetField));
91
+ if (sourceRoster !== targetRoster) {
92
+ throw errors.new(`${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)}`);
93
+ }
94
+ });
95
+ }
30
96
  /**
31
97
  * `R(X) -> R` — the FD key form, composite keys as tuples. No selection
32
98
  * parameter exists (the FD-with-selection shape is unrepresentable, as in
@@ -65,7 +131,9 @@ function contained(source, target) {
65
131
  target: target.data,
66
132
  bidirectional: false
67
133
  });
68
- return Object.freeze({ data });
134
+ const statement = Object.freeze({ data });
135
+ assertRosterAgreement(data.source, data.target, statement);
136
+ return statement;
69
137
  }
70
138
  /**
71
139
  * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance: the
@@ -83,7 +151,9 @@ function mirrors(source, target) {
83
151
  target: target.data,
84
152
  bidirectional: true
85
153
  });
86
- return Object.freeze({ data });
154
+ const statement = Object.freeze({ data });
155
+ assertRosterAgreement(data.source, data.target, statement);
156
+ return statement;
87
157
  }
88
158
  /**
89
159
  * `B(Y|ψ) <={window} A(X|φ)` — the cardinality window. READ CAREFULLY: the
@@ -102,7 +172,9 @@ function window(target, count, source) {
102
172
  window: count.window,
103
173
  source: source.data
104
174
  });
105
- return Object.freeze({ data });
175
+ const statement = Object.freeze({ data });
176
+ assertRosterAgreement(data.source, data.target, statement);
177
+ return statement;
106
178
  }
107
179
  /**
108
180
  * Renders one statement in the CANONICAL macro spelling
@@ -1 +1 @@
1
- {"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bjornpagen/bumbledb",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Type-theoretic TypeScript SDK for the bumbledb embedded relational engine",
5
5
  "module": "dist/index.js",
6
6
  "main": "dist/index.js",
@@ -44,7 +44,7 @@
44
44
  "@superbuilders/errors": "^4.0.2"
45
45
  },
46
46
  "optionalDependencies": {
47
- "@bjornpagen/bumbledb-darwin-arm64": "0.3.0"
47
+ "@bjornpagen/bumbledb-darwin-arm64": "0.4.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@biomejs/biome": "^2.0.0-beta.5",
package/src/closed.ts CHANGED
@@ -1,34 +1,36 @@
1
1
  /**
2
2
  * Closed relations (`docs/architecture/10-data-model.md` § closed
3
3
  * relations): a vocabulary whose extension is declared in the schema — two
4
- * tiers, one function. The emission per closed relation mirrors the
5
- * macro's (host-enum analog): handle CONSTANTS on the value
6
- * (`Kind.Checking`, ids = declaration order, each a BARE `bigint` — no
7
- * brand), the `fromId` weld, an `id` field descriptor carrying the CLOSED
8
- * LINKAGE (the roster — pure structure, no declared domain: the laws type
9
- * the columns, and `schema()` names the id's generator class `"Kind.id"`)
10
- * for other relations' field blocks (`kind: Kind.id`), payload readback
11
- * (`Kind.axioms`), and the declared payload column descriptors
12
- * (`Kind.columns` — the runtime twin of the `Cols` type parameter, which
13
- * the face layer's structural wall reads). Bare tier: `closed("Kind", ["Checking",
4
+ * tiers, one function. Bare tier: `closed("Kind", ["Checking",
14
5
  * "Savings"])`. Payload tier: `closed("Sev", { pages: bool }, { Critical:
15
6
  * { pages: true }, ... })` — one call, three arguments (the curried tier-2
16
7
  * spelling is DELETED — canonical utterance): the axioms record IS the
17
8
  * handle declaration, every handle carrying every column exactly once
18
- * (type-enforced). No fact type and no insert surface existclosed
19
- * relations are unwritable by construction: the value simply lacks the
20
- * writable relation shape. Both tiers mint `match()` exhaustive dispatch
21
- * over the handle union (arms typed by the mapped type, so a missing or
22
- * extra arm is a compile error; the payload tier's arm receives the typed
23
- * axiom row). The payload tier additionally mints `where()` the
24
- * ψ-selection surface (`Kind.where({ mastered: true })` as a face source),
25
- * resolved through the ONE selection machine
26
- * (`relation.ts::resolveSelection`); the bare tier has no payload columns to
27
- * select on, so `.where` is absent there, at the type AND on the value.
9
+ * (type-enforced). At the host surface a handle is its NAME a string
10
+ * literal of the roster's union (the drizzle law: translation, not
11
+ * abstraction; dispatch over a vocabulary is native `switch` narrowing, so
12
+ * no match operator is minted and no handle constants exist the literal
13
+ * `"Checking"` is the ONE spelling). Handles are pure DATA, not properties
14
+ * of the value, so NO handle name is reserved: a vocabulary may legally
15
+ * contain handles named `match`, `where`, or `id` the axioms record and
16
+ * the roster are their own namespaces. The value's whole surface: `name`;
17
+ * `id` the field descriptor carrying the CLOSED LINKAGE (the roster —
18
+ * pure structure, no declared domain: the laws type the columns, and
19
+ * `schema()` names the id's generator class `"Kind.id"`) for other
20
+ * relations' field blocks (`kind: Kind.id`); `data` (the lowering
21
+ * carrier); `axioms` (payload readback, `Kind.axioms`); `columns` (the
22
+ * runtime twin of the `Cols` type parameter, which the face layer's
23
+ * structural wall reads); and — exactly when payload columns exist —
24
+ * `where()`, the ψ-selection surface (`Kind.where({ mastered: true })` as a
25
+ * face source), resolved through the ONE selection machine
26
+ * (`relation.ts::resolveSelection`); the bare tier has no payload columns
27
+ * to select on, so `.where` is absent there, at the type AND on the value.
28
+ * No fact type and no insert surface exist — closed relations are
29
+ * unwritable by construction: the value simply lacks the writable relation
30
+ * shape.
28
31
  */
29
32
 
30
33
  import * as errors from "@superbuilders/errors"
31
- import type { OneOf } from "#face.ts"
32
34
  import {
33
35
  type AnyField,
34
36
  assertDeclarationOrderKey,
@@ -37,31 +39,9 @@ import {
37
39
  type Infer,
38
40
  literalOf
39
41
  } from "#fields.ts"
40
- import { resolveSelection, type SelectionBinding } from "#relation.ts"
42
+ import { resolveSelection, type SelectionBinding, type SelectionInput } from "#relation.ts"
41
43
  import type { LiteralSpec } from "#spec.ts"
42
44
 
43
- /**
44
- * The value-surface property names a handle may not shadow — the macro's
45
- * name-collision diagnostic, here over the closed value's own properties
46
- * (`relation`/`selection` are reserved so a closed value can never be
47
- * mistaken for a selected relation by `on()`'s discriminant; `where` is
48
- * reserved because the payload tier mints the ψ-selection method under
49
- * exactly that name; `match` because BOTH tiers mint the exhaustive
50
- * dispatch under exactly that name).
51
- */
52
- const reservedHandleNames: readonly string[] = Object.freeze([
53
- "name",
54
- "id",
55
- "data",
56
- "axioms",
57
- "columns",
58
- "fromId",
59
- "where",
60
- "match",
61
- "relation",
62
- "selection"
63
- ])
64
-
65
45
  /**
66
46
  * A payload column of a closed relation: any field descriptor except a
67
47
  * fresh-marked one (a vocabulary's rows are ground axioms, never minted).
@@ -126,12 +106,14 @@ interface ClosedCore<Name extends string, Handles extends string, Cols extends R
126
106
  readonly name: Name
127
107
  /**
128
108
  * The closed reference descriptor: `kind: Kind.id` in another relation's
129
- * field block is the reference through which bare handle ids become
130
- * legal in that relation's selections. Pure structure plus the roster —
131
- * the referencing field's domain is law-born: `schema()` computes it
132
- * from the declared containment (`"Kind.id"`, the generator class).
109
+ * field block is the reference through which handle literals become
110
+ * legal in that relation's selections. Pure structure plus the PRECISE
111
+ * roster (`ClosedIdField<Handles>` the handle union is the field's
112
+ * value type under `Infer`); the referencing field's domain is law-born:
113
+ * `schema()` computes it from the declared containment (`"Kind.id"`, the
114
+ * generator class).
133
115
  */
134
- readonly id: ClosedIdField
116
+ readonly id: ClosedIdField<Handles>
135
117
  readonly data: ClosedData
136
118
  /** Payload readback: handle to its declared column values, bare and structural. */
137
119
  readonly axioms: Axioms<Handles, Cols>
@@ -144,20 +126,18 @@ interface ClosedCore<Name extends string, Handles extends string, Cols extends R
144
126
  * descriptors in declaration order for the lowering).
145
127
  */
146
128
  readonly columns: Cols
147
- /** The weld: declaration-order id back to its handle, or undefined beyond the roster. */
148
- fromId(id: bigint): Handles | undefined
149
129
  }
150
130
 
151
131
  /**
152
- * The `where()` argument of a closed relation: per PAYLOAD column, a bare
153
- * structural literal of that column's value type or an `oneOf(a, b, ...)`
154
- * literal set the ordinary `where()`'s vocabulary exactly. The synthetic
155
- * `id` is deliberately absent: an id selection is spelled only as handle
156
- * literals on the REFERENCING side (the canonical-utterance law).
132
+ * The `where()` argument of a closed relation: EXACTLY the relation
133
+ * surface's {@link SelectionInput}, over the declared payload columns the
134
+ * ONE selection vocabulary, so a spelling change there (H3's membership
135
+ * arrays) flows through with no local change here. The synthetic `id` is
136
+ * deliberately unspellable ({@link PayloadColumns} refuses an `id` column):
137
+ * an id selection is spelled only as handle literals on the REFERENCING
138
+ * side (the canonical-utterance law).
157
139
  */
158
- type ClosedSelectionInput<Cols extends Record<string, PayloadField>> = {
159
- readonly [C in keyof Cols]?: Infer<Cols[C]> | OneOf<Infer<Cols[C]>>
160
- }
140
+ type ClosedSelectionInput<Cols extends Record<string, PayloadField>> = SelectionInput<Cols>
161
141
 
162
142
  /**
163
143
  * A closed relation with a ψ selection applied — what `on()` consumes as a
@@ -190,44 +170,19 @@ interface ClosedSelectable<Name extends string, Handles extends string, Cols ext
190
170
  }
191
171
 
192
172
  /**
193
- * Exhaustive dispatch over a BARE closed vocabulary: one arm per handle,
194
- * no literal types and no brands the handle-name union on the value's
195
- * type IS the exhaustiveness proof (a missing arm is a missing-property
196
- * compile error; an extra arm an excess-property compile error). The bare
197
- * tier declares no payload, so an arm takes nothing. The arms record types
198
- * any `bigint` inthe runtime roster refuses an out-of-vocabulary id
199
- * with a throw, never a misdispatch.
173
+ * A closed relation value: the core surface plus exactly when payload
174
+ * columns exist `where()` (the bare tier has nothing to select on, so
175
+ * the method is ABSENT there, not merely uncallable). NOTHING else:
176
+ * handles are data on the roster, never properties of the value (the
177
+ * handle constants, the match operator, and the id-to-handle weld died
178
+ * with the bigint eradispatch is native `switch` narrowing over the
179
+ * handle union).
200
180
  */
201
- interface ClosedMatchBare<Handles extends string> {
202
- match<T>(id: bigint, arms: { readonly [H in Handles]: () => T }): T
203
- }
204
-
205
- /**
206
- * Exhaustive dispatch over a PAYLOAD-tier closed vocabulary: the same
207
- * mapped-type exhaustiveness as the bare tier, and each arm receives its
208
- * handle's typed axiom row (the declared columns, bare and structural —
209
- * the frozen readback row from `axioms`).
210
- */
211
- interface ClosedMatchPayload<Handles extends string, Cols extends Record<string, PayloadField>> {
212
- match<T>(id: bigint, arms: { readonly [H in Handles]: (row: AxiomRow<Cols>) => T }): T
213
- }
214
-
215
- /**
216
- * A closed relation value: the core surface plus one BARE constant per
217
- * handle (`Kind.Checking: bigint`, ids = declaration order — the value is
218
- * structural; the roster judges out-of-vocabulary ids at construction and
219
- * the engine at commit), plus `match()` on BOTH tiers (bare arms take
220
- * nothing; payload arms receive the typed axiom row), plus — exactly when
221
- * payload columns exist — `where()` (the bare tier has nothing to select
222
- * on, so the method is ABSENT there, not merely uncallable).
223
- */
224
- type Closed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> = ClosedCore<
225
- Name,
226
- Handles,
227
- Cols
228
- > & { readonly [H in Handles]: bigint } & ([keyof Cols] extends [never]
229
- ? ClosedMatchBare<Handles>
230
- : ClosedSelectable<Name, Handles, Cols> & ClosedMatchPayload<Handles, Cols>)
181
+ type Closed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> = [
182
+ keyof Cols
183
+ ] extends [never]
184
+ ? ClosedCore<Name, Handles, Cols>
185
+ : ClosedCore<Name, Handles, Cols> & ClosedSelectable<Name, Handles, Cols>
231
186
 
232
187
  /** Any closed relation value, whatever its roster and columns. */
233
188
  interface AnyClosed {
@@ -282,21 +237,6 @@ function axiomsMinted<Handles extends string, Cols extends Record<string, Payloa
282
237
  })
283
238
  }
284
239
 
285
- /**
286
- * The trusted seam of the handle-constant mint: every handle reads back as
287
- * an own bigint — verified before the record is admitted at the constants
288
- * type (a "__proto__"-named handle riding the object-protocol accessor
289
- * would fail exactly this check).
290
- */
291
- function constantsMinted<Handles extends string>(
292
- record: Readonly<Record<string, bigint>>,
293
- handles: readonly Handles[]
294
- ): record is Readonly<Record<string, bigint>> & { readonly [H in Handles]: bigint } {
295
- return handles.every(function constantMinted(handle) {
296
- return typeof record[handle] === "bigint"
297
- })
298
- }
299
-
300
240
  /**
301
241
  * Reads one handle's ground axiom row for lowering. The typed payload
302
242
  * surface makes absence unrepresentable ({@link Axioms} carries every
@@ -338,22 +278,6 @@ function mintAxioms<Handles extends string, Cols extends Record<string, PayloadF
338
278
  return out
339
279
  }
340
280
 
341
- /** Mints the handle constants: one own bigint per handle, ids = declaration order. */
342
- function mintHandleConstants<Handles extends string>(
343
- name: string,
344
- handles: readonly Handles[]
345
- ): { readonly [H in Handles]: bigint } {
346
- const out: Record<string, bigint> = {}
347
- handles.forEach(function mintHandleConstant(handle, index) {
348
- Object.defineProperty(out, handle, { value: BigInt(index), enumerable: true })
349
- })
350
- Object.freeze(out)
351
- if (!constantsMinted(out, handles)) {
352
- throw errors.new(`closed relation ${name}: handle-constant minting incomplete`)
353
- }
354
- return out
355
- }
356
-
357
281
  /** Bare tier: `closed("Kind", ["Checking", "Savings"])` — handles only. */
358
282
  function closed<const Name extends string, const Handles extends readonly [string, ...string[]]>(
359
283
  name: Name,
@@ -429,19 +353,18 @@ function closedPayload<Name extends string, Handles extends string, Cols extends
429
353
  }
430
354
 
431
355
  /**
432
- * The trusted seam of the ergonomic-surface mint: `match` reads back as an
433
- * own function on BOTH tiers, and `where` exactly when payload columns
434
- * exist — the runtime twin of the {@link Closed} type's conditional arm
435
- * (`ClosedMatchBare` vs `ClosedSelectable & ClosedMatchPayload`), verified
436
- * before the minted value is admitted at the conditional type.
356
+ * The trusted seam of the ergonomic-surface mint: `where` reads back as an
357
+ * own function exactly when payload columns exist, and is ABSENT otherwise
358
+ * — the runtime twin of the {@link Closed} type's conditional arm
359
+ * (`ClosedCore` alone vs `ClosedCore & ClosedSelectable`), verified before
360
+ * the minted value is admitted at the conditional type.
437
361
  */
438
362
  function surfaceMinted<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>>(
439
- value: ClosedCore<Name, Handles, Cols> & { readonly [H in Handles]: bigint },
363
+ value: ClosedCore<Name, Handles, Cols>,
440
364
  cols: readonly ClosedColumn[]
441
- ): value is Closed<Name, Handles, Cols> {
442
- const matchable = "match" in value && typeof value.match === "function"
365
+ ): value is ClosedCore<Name, Handles, Cols> & Closed<Name, Handles, Cols> {
443
366
  const selectable = "where" in value && typeof value.where === "function"
444
- return matchable && (cols.length > 0 ? selectable : !selectable)
367
+ return cols.length > 0 ? selectable : !selectable
445
368
  }
446
369
 
447
370
  /**
@@ -449,8 +372,7 @@ function surfaceMinted<Name extends string, Handles extends string, Cols extends
449
372
  * typed end to end (a wrong-shaped mint is a compile error here, not a
450
373
  * laundered `unknown`): roster checks, eager axiom lowering, the
451
374
  * roster-carrying `id` descriptor, the frozen `columns` carrier (the runtime
452
- * twin of the `Cols` type parameter), the handle constants, the exhaustive
453
- * `match()` on both tiers, and — on the payload tier only — the
375
+ * twin of the `Cols` type parameter), and — on the payload tier only — the
454
376
  * ψ-selection `where()`.
455
377
  */
456
378
  function mintClosed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>>(
@@ -468,14 +390,9 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
468
390
  throw errors.new(`closed relation ${name}: duplicate handle ${handle}`)
469
391
  }
470
392
  seen.add(handle)
471
- if (reservedHandleNames.includes(handle)) {
472
- throw errors.new(
473
- `closed relation ${name}: handle ${handle} collides with the closed value's own surface (${reservedHandleNames.join(", ")})`
474
- )
475
- }
476
393
  }
477
394
  const handleList: readonly Handles[] = Object.freeze([...handles])
478
- const roster: ClosedRoster = Object.freeze({ name, handles: handleList })
395
+ const roster: ClosedRoster<Handles> = Object.freeze({ name, handles: handleList })
479
396
  const cols: ClosedColumn[] = []
480
397
  for (const [columnName, field] of Object.entries(columns)) {
481
398
  assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
@@ -500,25 +417,18 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
500
417
  columns: cols,
501
418
  rows: Object.freeze(rows)
502
419
  })
503
- const id: ClosedIdField = Object.freeze({ kind: "u64", closed: roster })
420
+ const id: ClosedIdField<Handles> = Object.freeze({ kind: "u64", closed: roster })
504
421
  /**
505
- * Handle names are arbitrary identifiers, so rows and constants are
506
- * minted with OWN-property definition (inside {@link mintAxioms} and
507
- * {@link mintHandleConstants}), never assignment: a handle named
508
- * "__proto__" would otherwise ride the Object.prototype accessor
509
- * silently swapping the record's prototype instead of creating the row,
510
- * and no-oping the constant (a primitive through the setter) — minting a
511
- * value whose type claims a bigint constant but reads back an object.
512
- * (Object SPREAD is CreateDataProperty by spec, so the copies below are
513
- * own-property safe for column names too.)
422
+ * Handle names are arbitrary identifiers, so axiom rows are minted with
423
+ * OWN-property definition (inside {@link mintAxioms}), never assignment:
424
+ * a handle named "__proto__" would otherwise ride the Object.prototype
425
+ * accessor silently swapping the record's prototype instead of
426
+ * creating the row. (Object SPREAD is CreateDataProperty by spec, so the
427
+ * copies below are own-property safe for column names too.)
514
428
  */
515
429
  const axiomsOut = mintAxioms<Handles, Cols>(name, handleList, cols, axioms)
516
- const constants = mintHandleConstants(name, handleList)
517
430
  const columnsOut: Cols = { ...columns }
518
431
  Object.freeze(columnsOut)
519
- function fromId(idValue: bigint): Handles | undefined {
520
- return handleList[Number(idValue)]
521
- }
522
432
  const holder: { value: Closed<Name, Handles, Cols> | undefined } = { value: undefined }
523
433
  /**
524
434
  * The ψ selection: resolved against the declared payload columns through
@@ -536,27 +446,9 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
536
446
  selection: resolveSelection(name, cols, Object.entries(selection))
537
447
  })
538
448
  }
539
- /**
540
- * The exhaustive dispatch: the ROSTER judges the id (the structural type
541
- * admits any bigint a dishonest id is a THROW, never a misdispatch),
542
- * and the chosen arm receives the handle's frozen axiom row (the bare
543
- * tier's arms are typed to take nothing; the empty row rides along
544
- * unread). One implementation serves both tiers — the conditional
545
- * {@link Closed} arm claims the tier-exact arm signature and
546
- * {@link surfaceMinted} is the trusted seam that admits it.
547
- */
548
- function match<T>(idValue: bigint, arms: { readonly [H in Handles]: (row: AxiomRow<Cols>) => T }): T {
549
- const handle = fromId(idValue)
550
- if (handle === undefined) {
551
- throw errors.new(`closed relation ${name}: match on id ${idValue} misses the roster (${handleList.join(", ")})`)
552
- }
553
- return arms[handle](axiomsOut[handle])
554
- }
555
- const core = { name, id, data, axioms: axiomsOut, columns: columnsOut, fromId }
556
- const value: ClosedCore<Name, Handles, Cols> & { readonly [H in Handles]: bigint } =
557
- cols.length > 0
558
- ? Object.freeze({ ...constants, ...core, where, match })
559
- : Object.freeze({ ...constants, ...core, match })
449
+ const core = { name, id, data, axioms: axiomsOut, columns: columnsOut }
450
+ const value: ClosedCore<Name, Handles, Cols> =
451
+ cols.length > 0 ? Object.freeze({ ...core, where }) : Object.freeze(core)
560
452
  if (!surfaceMinted<Name, Handles, Cols>(value, cols)) {
561
453
  throw errors.new(`closed relation ${name}: ergonomic-surface minting incomplete`)
562
454
  }
@@ -574,6 +466,7 @@ export type {
574
466
  ClosedCore,
575
467
  ClosedData,
576
468
  ClosedRow,
469
+ ClosedSelectable,
577
470
  ClosedSelectionInput,
578
471
  PayloadField,
579
472
  SelectedClosed
package/src/db.ts CHANGED
@@ -35,7 +35,17 @@ import * as errors from "@superbuilders/errors"
35
35
  import type { Exhumed } from "#exhume.ts"
36
36
  import { exhumeStore } from "#exhume.ts"
37
37
  import { lower } from "#lower.ts"
38
- import { factOf, isFreshField, isMintedFresh, type KeyFact, keyRowOf, type Minted, recordOf, rowOf } from "#marshal.ts"
38
+ import {
39
+ factOf,
40
+ handleOf,
41
+ isFreshField,
42
+ isMintedFresh,
43
+ type KeyFact,
44
+ keyRowOf,
45
+ type Minted,
46
+ recordOf,
47
+ rowOf
48
+ } from "#marshal.ts"
39
49
 
40
50
  import type {
41
51
  DbHandle,
@@ -55,7 +65,7 @@ import type { Query } from "#query/lower.ts"
55
65
  import { lowerQuery } from "#query/lower.ts"
56
66
  import { decodeAnswers, wireParams } from "#query/run.ts"
57
67
  import type { ParamEntry, ParamsRecord } from "#query/scope.ts"
58
- import type { AnyRelation, Fact, InsertFact } from "#relation.ts"
68
+ import type { AnyRelation, Fact, InsertFact, RelationField } from "#relation.ts"
59
69
  import type { AnySchema, Schema, SchemaRelation, SchemaRelations } from "#schema.ts"
60
70
  import type { KeyStatement, Statement } from "#statements.ts"
61
71
 
@@ -79,7 +89,10 @@ type DeclaredKeyFact<R extends AnyRelation, Projection extends readonly string[]
79
89
  /**
80
90
  * One offending fact of a violation: the cited relation's name (a member
81
91
  * of the schema's record) and the fact decoded to a named natural-value
82
- * object — partial exactly as the engine cites it.
92
+ * object — partial exactly as the engine cites it. Closed-referencing
93
+ * cells arrive as handle NAMES (the marshal bijection's read half), so the
94
+ * record and the violation's `canonical` string — which the engine already
95
+ * renders with handle names — agree on the one spelling.
83
96
  */
84
97
  interface OffendingFact<Rels extends SchemaRelations> {
85
98
  readonly relation: keyof Rels & string
@@ -525,6 +538,22 @@ function orientationOf(reversed: boolean | undefined): "written" | "mirrored" |
525
538
  return "written"
526
539
  }
527
540
 
541
+ /**
542
+ * The declared field descriptors a violation's offending cells decode
543
+ * through: an ordinary relation's declared fields; a closed relation's
544
+ * SEALED shape — the roster-carrying synthetic `id` (the member's own
545
+ * reference descriptor) plus its payload columns (a `ClosedColumn` is
546
+ * structurally a `RelationField`). This is how {@link openDb}'s
547
+ * `offendingFactOf` reaches the open-time descriptors: the cited relation
548
+ * name resolves to the schema member, and the member carries them.
549
+ */
550
+ function declaredFieldsOf(member: SchemaRelation): readonly RelationField[] {
551
+ if ("axioms" in member) {
552
+ return [{ name: "id", field: member.id }, ...member.data.columns]
553
+ }
554
+ return member.data.fields
555
+ }
556
+
528
557
  /** The id-resolution tables one open builds: relation entries by name, statement slots by id. */
529
558
  interface Tables {
530
559
  readonly relations: ReadonlyMap<string, RelationEntry>
@@ -705,12 +734,20 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
705
734
  }
706
735
 
707
736
  function offendingFactOf(fact: WireViolationFact): OffendingFact<Rels> {
708
- if (!isMemberName(fact.relation)) {
737
+ const entry = tables.relations.get(fact.relation)
738
+ if (entry === undefined || !isMemberName(fact.relation)) {
709
739
  throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`)
710
740
  }
741
+ const declared = declaredFieldsOf(entry.member)
711
742
  const decoded: Record<string, FactValue> = {}
712
743
  for (const cell of fact.fields) {
713
- decoded[cell.name] = cell.value
744
+ const cited = declared.find(function byName(candidate) {
745
+ return candidate.name === cell.name
746
+ })
747
+ decoded[cell.name] =
748
+ cited !== undefined && "closed" in cited.field
749
+ ? handleOf(`violation fact ${fact.relation} field ${cell.name}`, cited.field.closed, cell.value)
750
+ : cell.value
714
751
  }
715
752
  return Object.freeze({ relation: fact.relation, fact: Object.freeze(decoded) })
716
753
  }