@bjornpagen/bumbledb 0.1.0 → 0.2.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 (103) hide show
  1. package/COOKBOOK.md +1394 -0
  2. package/README.md +4 -0
  3. package/dist/closed.d.ts +60 -37
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +159 -40
  6. package/dist/closed.js.map +1 -1
  7. package/dist/count.d.ts +64 -17
  8. package/dist/count.d.ts.map +1 -1
  9. package/dist/count.js +37 -14
  10. package/dist/count.js.map +1 -1
  11. package/dist/db.d.ts +15 -7
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +13 -5
  14. package/dist/db.js.map +1 -1
  15. package/dist/exhume.d.ts +12 -11
  16. package/dist/exhume.d.ts.map +1 -1
  17. package/dist/exhume.js +7 -6
  18. package/dist/exhume.js.map +1 -1
  19. package/dist/face.d.ts +79 -19
  20. package/dist/face.d.ts.map +1 -1
  21. package/dist/face.js +14 -15
  22. package/dist/face.js.map +1 -1
  23. package/dist/fields.d.ts +158 -129
  24. package/dist/fields.d.ts.map +1 -1
  25. package/dist/fields.js +80 -81
  26. package/dist/fields.js.map +1 -1
  27. package/dist/index.d.ts +21 -21
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +13 -12
  30. package/dist/index.js.map +1 -1
  31. package/dist/lower.d.ts +6 -3
  32. package/dist/lower.d.ts.map +1 -1
  33. package/dist/lower.js +48 -17
  34. package/dist/lower.js.map +1 -1
  35. package/dist/marshal.d.ts +36 -21
  36. package/dist/marshal.d.ts.map +1 -1
  37. package/dist/marshal.js +41 -25
  38. package/dist/marshal.js.map +1 -1
  39. package/dist/native.js +2 -2
  40. package/dist/query/atom.d.ts +332 -174
  41. package/dist/query/atom.d.ts.map +1 -1
  42. package/dist/query/atom.js +72 -172
  43. package/dist/query/atom.js.map +1 -1
  44. package/dist/query/lower.d.ts +295 -107
  45. package/dist/query/lower.d.ts.map +1 -1
  46. package/dist/query/lower.js +860 -388
  47. package/dist/query/lower.js.map +1 -1
  48. package/dist/query/predicate.d.ts +69 -88
  49. package/dist/query/predicate.d.ts.map +1 -1
  50. package/dist/query/predicate.js +118 -72
  51. package/dist/query/predicate.js.map +1 -1
  52. package/dist/query/run.d.ts +19 -16
  53. package/dist/query/run.d.ts.map +1 -1
  54. package/dist/query/run.js +31 -24
  55. package/dist/query/run.js.map +1 -1
  56. package/dist/query/scope.d.ts +139 -123
  57. package/dist/query/scope.d.ts.map +1 -1
  58. package/dist/query/scope.js +71 -115
  59. package/dist/query/scope.js.map +1 -1
  60. package/dist/query/select.d.ts +102 -80
  61. package/dist/query/select.d.ts.map +1 -1
  62. package/dist/query/select.js +39 -34
  63. package/dist/query/select.js.map +1 -1
  64. package/dist/relation.d.ts +33 -36
  65. package/dist/relation.d.ts.map +1 -1
  66. package/dist/relation.js +15 -16
  67. package/dist/relation.js.map +1 -1
  68. package/dist/schema.d.ts.map +1 -1
  69. package/dist/schema.js +19 -8
  70. package/dist/schema.js.map +1 -1
  71. package/dist/spec.d.ts +6 -5
  72. package/dist/spec.d.ts.map +1 -1
  73. package/dist/spec.js.map +1 -1
  74. package/dist/statements.d.ts +56 -31
  75. package/dist/statements.d.ts.map +1 -1
  76. package/dist/statements.js +38 -13
  77. package/dist/statements.js.map +1 -1
  78. package/package.json +4 -3
  79. package/src/closed.ts +271 -93
  80. package/src/count.ts +112 -18
  81. package/src/db.ts +22 -12
  82. package/src/exhume.ts +12 -11
  83. package/src/face.ts +114 -22
  84. package/src/fields.ts +261 -217
  85. package/src/index.ts +50 -59
  86. package/src/lower.ts +62 -20
  87. package/src/marshal.ts +48 -30
  88. package/src/native.ts +2 -2
  89. package/src/query/atom.ts +480 -376
  90. package/src/query/lower.ts +1341 -542
  91. package/src/query/predicate.ts +198 -161
  92. package/src/query/run.ts +35 -25
  93. package/src/query/scope.ts +188 -218
  94. package/src/query/select.ts +168 -93
  95. package/src/relation.ts +38 -44
  96. package/src/schema.ts +22 -12
  97. package/src/spec.ts +6 -5
  98. package/src/statements.ts +69 -26
  99. package/dist/brand.d.ts +0 -59
  100. package/dist/brand.d.ts.map +0 -1
  101. package/dist/brand.js +0 -47
  102. package/dist/brand.js.map +0 -1
  103. package/src/brand.ts +0 -82
@@ -4,9 +4,23 @@
4
4
  * form, conditional containment, the bidirectional `==` abbreviation, and
5
5
  * the cardinality window. A statement value is opaque and inert — no
6
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 DOMAIN compatibility through {@link SameDomains}: the two
11
+ * faces' projected domain labels are read structurally off the schema type
12
+ * (S1's `F["domain"]`) and constrained positionwise equal, so a
13
+ * cross-domain pair is a compile error by string-literal comparison of
14
+ * descriptor shapes — never by a value brand (the structural design's
15
+ * ratified check). What is only a SEMANTIC property — the target side of a
16
+ * containment resolving a declared key of its relation — is DELIBERATELY
17
+ * not (and cannot be) stated here: whether `B(y)` is a key of `B` depends
18
+ * on which `key()` statements the surrounding `schema()` collects, a set no
19
+ * face type can see; it stays the engine's typed `SchemaError` judgment at
20
+ * `Db.create`/`Db.open` (the two-boundary split, engine as final
21
+ * authority).
7
22
  */
8
23
  import * as errors from "@superbuilders/errors";
9
- import { phantom } from "#brand.ts";
10
24
  import { renderFace } from "#face.ts";
11
25
  import { renderWindow } from "#spec.ts";
12
26
  /**
@@ -14,9 +28,11 @@ import { renderWindow } from "#spec.ts";
14
28
  * parameter exists (the FD-with-selection shape is unrepresentable, as in
15
29
  * the grammar), and only ordinary relations are accepted: a closed
16
30
  * relation's key `R(id) -> R` is materialized by the engine, so an
17
- * explicit one would only ever be a duplicate. The projection tuple is
18
- * carried in the returned value's type ({@link KeyStatement}), so keyed
19
- * point reads through THIS statement are typed field-for-field.
31
+ * explicit one would only ever be a duplicate. Every projected name is
32
+ * checked against `R`'s field block in the type, and the tuple is carried
33
+ * in the returned value's type ({@link KeyStatement}) — keyed point reads
34
+ * through THIS statement are typed field-for-field, domains resolvable
35
+ * through the owner's schema type.
20
36
  */
21
37
  function key(relation, fields) {
22
38
  if (!("fields" in relation.data)) {
@@ -25,15 +41,18 @@ function key(relation, fields) {
25
41
  const data = Object.freeze({
26
42
  kind: "key",
27
43
  owner: relation,
28
- projection: Object.freeze([...fields])
44
+ projection: Object.freeze(fields)
29
45
  });
30
46
  return Object.freeze({ data });
31
47
  }
32
48
  /**
33
- * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. The target
34
- * side must resolve a declared key of B DELIBERATELY judged by the
35
- * engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
36
- * Arity mismatch between the two faces is a type error ({@link SameArity}).
49
+ * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. Arity mismatch
50
+ * between the two faces is a type error ({@link SameArity}); a cross-domain
51
+ * pair is a type error ({@link SameDomains} — positionwise string-literal
52
+ * equality of the projected S1 domain labels). The target side must
53
+ * resolve a declared key of B — a SEMANTIC property of the whole statement
54
+ * set that no face type can state, DELIBERATELY judged by the engine at
55
+ * `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
37
56
  */
38
57
  function contained(source, target) {
39
58
  const data = Object.freeze({
@@ -45,9 +64,13 @@ function contained(source, target) {
45
64
  return Object.freeze({ data });
46
65
  }
47
66
  /**
48
- * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance. It
49
- * lowers to the two adjacent containments in the `A <= B` first order
50
- * (macro parity) and renders as `==` once, in the written orientation.
67
+ * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance: the
68
+ * selected `==` bijection, a keyed one-to-one correspondence between the
69
+ * two faces (each side contains the other). It lowers to the two adjacent
70
+ * containments in the `A <= B` first order (macro parity — the engine
71
+ * performs the split, source-first) and renders as `==` once, in the
72
+ * written orientation. Faces pair by arity AND domain, exactly as
73
+ * {@link contained}.
51
74
  */
52
75
  function mirrors(source, target) {
53
76
  const data = Object.freeze({
@@ -63,7 +86,9 @@ function mirrors(source, target) {
63
86
  * LEFT face is the window's TARGET, the per-group parent (B-family,
64
87
  * target-left — macro parity), and the RIGHT face is the counted source.
65
88
  * `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
66
- * Holder id groups at most three Account rows by holder.
89
+ * Holder id groups at most three Account rows by holder. The two faces
90
+ * pair by arity AND domain ({@link SameDomains}), exactly as containment —
91
+ * the grouping join reads the same positionwise field pairing.
67
92
  */
68
93
  function window(target, count, source) {
69
94
  const data = Object.freeze({
@@ -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.1.0",
3
+ "version": "0.2.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",
@@ -21,7 +21,8 @@
21
21
  },
22
22
  "files": [
23
23
  "dist",
24
- "src"
24
+ "src",
25
+ "COOKBOOK.md"
25
26
  ],
26
27
  "keywords": [
27
28
  "database",
@@ -43,7 +44,7 @@
43
44
  "@superbuilders/errors": "^4.0.2"
44
45
  },
45
46
  "optionalDependencies": {
46
- "@bjornpagen/bumbledb-darwin-arm64": "0.1.0"
47
+ "@bjornpagen/bumbledb-darwin-arm64": "0.2.0"
47
48
  },
48
49
  "devDependencies": {
49
50
  "@biomejs/biome": "^2.0.0-beta.5",
package/src/closed.ts CHANGED
@@ -1,25 +1,29 @@
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 macro's
5
- * (host-enum analog): handle CONSTANTS on the value (`Kind.Checking`, ids =
6
- * declaration order), the `fromId` weld, an `id` field constructor
7
- * pre-branded with the handle newtype for other relations' field blocks
8
- * (`kind: Kind.id`), and payload readback (`Kind.axioms`). No fact type and
9
- * no insert surface exist closed relations are unwritable by
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 handle
8
+ * DOMAIN (`"KindId"`, mirroring Rust's `closed relation Kind as KindId`)
9
+ * for other relations' field blocks (`kind: Kind.id`), payload readback
10
+ * (`Kind.axioms`), and the declared payload column descriptors
11
+ * (`Kind.columns` — the runtime twin of the `Cols` type parameter, which
12
+ * the face layer's domain wall reads). Bare tier: `closed("Kind", ["Checking",
13
+ * "Savings"])`. Payload tier: `closed("Sev", { pages: bool })({ Critical:
14
+ * { pages: true }, ... })` — the axioms record IS the handle declaration,
15
+ * every handle carrying every column exactly once (type-enforced). No fact
16
+ * type and no insert surface exist — closed relations are unwritable by
10
17
  * construction: the value simply lacks the writable relation shape.
11
18
  */
12
19
 
13
20
  import * as errors from "@superbuilders/errors"
14
- import type { Brand } from "#brand.ts"
15
21
  import {
16
22
  type AnyField,
17
23
  assertDeclarationOrderKey,
18
24
  type ClosedIdField,
19
25
  type ClosedRoster,
20
- type FieldData,
21
- type FieldValue,
22
- fieldData,
26
+ type Infer,
23
27
  literalOf
24
28
  } from "#fields.ts"
25
29
  import type { LiteralSpec } from "#spec.ts"
@@ -35,41 +39,22 @@ const reservedHandleNames: readonly string[] = Object.freeze([
35
39
  "id",
36
40
  "data",
37
41
  "axioms",
42
+ "columns",
38
43
  "fromId",
39
44
  "relation",
40
45
  "selection"
41
46
  ])
42
47
 
43
48
  /**
44
- * Reads one handle's axiom row, refusing absence loudly: the payload tier's
45
- * overload types the record exhaustively, so a missing row is ill-typed
46
- * input, and the bare tier never reaches here (it declares no columns).
47
- */
48
- function axiomRow(
49
- name: string,
50
- axioms: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
51
- handle: string
52
- ): Readonly<Record<string, unknown>> {
53
- if (axioms === undefined) {
54
- throw errors.new(`closed relation ${name}: payload columns declared without ground axioms`)
55
- }
56
- const row = axioms[handle]
57
- if (row === undefined) {
58
- throw errors.new(`closed relation ${name}: no ground axiom for handle ${handle}`)
59
- }
60
- return row
61
- }
62
-
63
- /**
64
- * A payload column of a closed relation: any field constructor except a
49
+ * A payload column of a closed relation: any field descriptor except a
65
50
  * fresh-marked one (a vocabulary's rows are ground axioms, never minted).
66
51
  */
67
- type PayloadField = AnyField & { readonly data: FieldData<false> }
52
+ type PayloadField = Exclude<AnyField, { readonly fresh: true }>
68
53
 
69
- /** One declared payload column: name plus its field description. */
54
+ /** One declared payload column: name plus its field descriptor. */
70
55
  interface ClosedColumn {
71
56
  readonly name: string
72
- readonly field: FieldData
57
+ readonly field: PayloadField
73
58
  }
74
59
 
75
60
  /**
@@ -91,75 +76,266 @@ interface ClosedData {
91
76
  readonly rows: readonly ClosedRow[]
92
77
  }
93
78
 
94
- /** One axiom row as the host writes and reads it: column name to host value. */
95
- type AxiomRow<Cols> = { readonly [C in keyof Cols]: FieldValue<Cols[C]> }
79
+ /** One axiom row as the host writes and reads it: column name to bare structural value. */
80
+ type AxiomRow<Cols extends Record<string, PayloadField>> = { readonly [C in keyof Cols]: Infer<Cols[C]> }
96
81
 
97
82
  /**
98
83
  * The whole axiom record: every handle exactly once, every column exactly
99
- * once per row — a missing or extra axiom, column, or handle is a TYPE
100
- * error (mapped over the handle tuple).
84
+ * once per row — a missing or extra column is a TYPE error (each row is
85
+ * contextually checked against the declared columns), and the handle set
86
+ * IS the record's key set.
101
87
  */
102
- type Axioms<Handles extends readonly string[], Cols> = {
103
- readonly [H in Handles[number]]: AxiomRow<Cols>
88
+ type Axioms<Handles extends string, Cols extends Record<string, PayloadField>> = {
89
+ readonly [H in Handles]: AxiomRow<Cols>
104
90
  }
105
91
 
106
92
  /**
107
93
  * The named surface of a closed relation value, minus the handle constants
108
94
  * (which {@link Closed} intersects in).
109
95
  */
110
- interface ClosedCore<Name extends string, Handles extends readonly string[], Cols> {
96
+ interface ClosedCore<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
111
97
  readonly name: Name
112
98
  /**
113
- * The pre-branded u64 field constructor: `kind: Kind.id` in another
114
- * relation's field block is the reference through which bare handles
115
- * become legal in that relation's selections.
99
+ * The handle-domain reference descriptor: `kind: Kind.id` in another
100
+ * relation's field block is the reference through which bare handle ids
101
+ * become legal in that relation's selections. Its domain is the closed
102
+ * relation's handle domain (`"KindId"` — Rust's `as KindId`).
116
103
  */
117
- readonly id: ClosedIdField<Name>
104
+ readonly id: ClosedIdField<`${Name}Id`>
118
105
  readonly data: ClosedData
119
- /** Payload readback: handle to its declared column values. */
106
+ /** Payload readback: handle to its declared column values, bare and structural. */
120
107
  readonly axioms: Axioms<Handles, Cols>
108
+ /**
109
+ * The declared payload columns, name → S1 field descriptor — an HONEST
110
+ * frozen runtime record (the descriptors themselves, by identity), and
111
+ * the typed carrier a projected payload column's domain label is
112
+ * recovered through off the schema type (the face layer's
113
+ * `ProjectedDomain` reads it; `data.columns` carries the same
114
+ * descriptors in declaration order for the lowering).
115
+ */
116
+ readonly columns: Cols
121
117
  /** The weld: declaration-order id back to its handle, or undefined beyond the roster. */
122
- fromId(id: Brand<bigint, Name>): Handles[number] | undefined
118
+ fromId(id: bigint): Handles | undefined
123
119
  }
124
120
 
125
121
  /**
126
- * A closed relation value: the core surface plus one branded constant per
127
- * handle (`Kind.Checking: Brand<bigint, "Kind">`, ids = declaration order).
122
+ * A closed relation value: the core surface plus one BARE constant per
123
+ * handle (`Kind.Checking: bigint`, ids = declaration order — the value is
124
+ * structural; the roster judges out-of-vocabulary ids at construction and
125
+ * the engine at commit).
128
126
  */
129
- type Closed<Name extends string, Handles extends readonly string[], Cols> = ClosedCore<Name, Handles, Cols> & {
130
- readonly [H in Handles[number]]: Brand<bigint, Name>
131
- }
127
+ type Closed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> = ClosedCore<
128
+ Name,
129
+ Handles,
130
+ Cols
131
+ > & { readonly [H in Handles]: bigint }
132
132
 
133
133
  /** Any closed relation value, whatever its roster and columns. */
134
134
  interface AnyClosed {
135
135
  readonly name: string
136
- readonly id: ClosedIdField<string>
136
+ readonly id: ClosedIdField
137
137
  readonly data: ClosedData
138
138
  readonly axioms: Readonly<Record<string, object>>
139
+ readonly columns: Readonly<Record<string, PayloadField>>
140
+ }
141
+
142
+ /** Narrows the two-tier second argument: a handle tuple (bare tier) or a column block (payload tier). */
143
+ function isHandleTuple(
144
+ shape: readonly [string, ...string[]] | Record<string, PayloadField>
145
+ ): shape is readonly [string, ...string[]] {
146
+ return Array.isArray(shape)
147
+ }
148
+
149
+ /**
150
+ * The trusted seam of the payload tier's handle enumeration: the axioms
151
+ * record's own enumerable keys ARE its handle set (the type says so —
152
+ * {@link Axioms} is keyed by the handles), and this guard verifies exactly
153
+ * that checkable fact before the key list is admitted at the handle type.
154
+ */
155
+ function handleKeysOwn<Handles extends string>(
156
+ axioms: { readonly [H in Handles]: object },
157
+ names: readonly string[]
158
+ ): names is readonly Handles[] {
159
+ return names.every(function ownHandle(name) {
160
+ return Object.hasOwn(axioms, name)
161
+ })
162
+ }
163
+
164
+ /**
165
+ * The trusted seam of the axiom-readback mint: every handle carries an own
166
+ * frozen row and every row carries every declared column as an own
167
+ * property — verified before the record is admitted as the typed
168
+ * {@link Axioms} (the `refsComplete` analog of `relation()`).
169
+ */
170
+ function axiomsMinted<Handles extends string, Cols extends Record<string, PayloadField>>(
171
+ record: Readonly<Record<string, object>>,
172
+ handles: readonly Handles[],
173
+ cols: readonly ClosedColumn[]
174
+ ): record is Axioms<Handles, Cols> & Readonly<Record<string, object>> {
175
+ return handles.every(function rowMinted(handle) {
176
+ const row = record[handle]
177
+ return (
178
+ row !== undefined &&
179
+ cols.every(function columnMinted(column) {
180
+ return Object.hasOwn(row, column.name)
181
+ })
182
+ )
183
+ })
184
+ }
185
+
186
+ /**
187
+ * The trusted seam of the handle-constant mint: every handle reads back as
188
+ * an own bigint — verified before the record is admitted at the constants
189
+ * type (a "__proto__"-named handle riding the object-protocol accessor
190
+ * would fail exactly this check).
191
+ */
192
+ function constantsMinted<Handles extends string>(
193
+ record: Readonly<Record<string, bigint>>,
194
+ handles: readonly Handles[]
195
+ ): record is Readonly<Record<string, bigint>> & { readonly [H in Handles]: bigint } {
196
+ return handles.every(function constantMinted(handle) {
197
+ return typeof record[handle] === "bigint"
198
+ })
199
+ }
200
+
201
+ /**
202
+ * Reads one handle's ground axiom row for lowering. The typed payload
203
+ * surface makes absence unrepresentable ({@link Axioms} carries every
204
+ * handle's row); the refusal below guards the one ill-typed path — payload
205
+ * columns with the bare tier's absent axioms — which no public spelling
206
+ * reaches.
207
+ */
208
+ function groundRow<Handles extends string, Cols extends Record<string, PayloadField>>(
209
+ name: string,
210
+ axioms: Axioms<Handles, Cols> | undefined,
211
+ handle: Handles
212
+ ): Readonly<Record<string, unknown>> {
213
+ if (axioms === undefined) {
214
+ throw errors.new(`closed relation ${name}: payload columns declared without ground axioms`)
215
+ }
216
+ return axioms[handle]
217
+ }
218
+
219
+ /**
220
+ * Mints the axiom-readback record: one own frozen row per handle (the bare
221
+ * tier's rows are empty — it declares no columns), each row a fresh copy of
222
+ * its ground axiom.
223
+ */
224
+ function mintAxioms<Handles extends string, Cols extends Record<string, PayloadField>>(
225
+ name: string,
226
+ handles: readonly Handles[],
227
+ cols: readonly ClosedColumn[],
228
+ axioms: Axioms<Handles, Cols> | undefined
229
+ ): Axioms<Handles, Cols> {
230
+ const out: Record<string, object> = {}
231
+ for (const handle of handles) {
232
+ const row = axioms === undefined ? Object.freeze({}) : Object.freeze({ ...groundRow(name, axioms, handle) })
233
+ Object.defineProperty(out, handle, { value: row, enumerable: true })
234
+ }
235
+ Object.freeze(out)
236
+ if (!axiomsMinted<Handles, Cols>(out, handles, cols)) {
237
+ throw errors.new(`closed relation ${name}: axiom-row minting incomplete`)
238
+ }
239
+ return out
240
+ }
241
+
242
+ /** Mints the handle constants: one own bigint per handle, ids = declaration order. */
243
+ function mintHandleConstants<Handles extends string>(
244
+ name: string,
245
+ handles: readonly Handles[]
246
+ ): { readonly [H in Handles]: bigint } {
247
+ const out: Record<string, bigint> = {}
248
+ handles.forEach(function mintHandleConstant(handle, index) {
249
+ Object.defineProperty(out, handle, { value: BigInt(index), enumerable: true })
250
+ })
251
+ Object.freeze(out)
252
+ if (!constantsMinted(out, handles)) {
253
+ throw errors.new(`closed relation ${name}: handle-constant minting incomplete`)
254
+ }
255
+ return out
139
256
  }
140
257
 
141
258
  /** Bare tier: `closed("Kind", ["Checking", "Savings"])` — handles only. */
142
259
  function closed<const Name extends string, const Handles extends readonly [string, ...string[]]>(
143
260
  name: Name,
144
261
  handles: Handles
145
- ): Closed<Name, Handles, Record<never, never>>
262
+ ): Closed<Name, Handles[number], Record<never, never>>
146
263
 
147
264
  /**
148
- * Payload tier: declared columns plus ground axioms, every handle with
149
- * every column exactly once (type-enforced by {@link Axioms}).
265
+ * Payload tier: declared columns, then ground axioms `closed("Grade",
266
+ * { mastered: bool })({ DirectPass: { mastered: true }, Failed: { mastered:
267
+ * false } })`. The axioms record's keys ARE the handles (declaration order
268
+ * = key order, integer-index names rejected); every row carries every
269
+ * column exactly once (type-enforced by {@link Axioms}).
150
270
  */
151
- function closed<
152
- const Name extends string,
153
- const Handles extends readonly [string, ...string[]],
154
- const Cols extends Record<string, PayloadField>
155
- >(name: Name, handles: Handles, columns: Cols, axioms: Axioms<Handles, Cols>): Closed<Name, Handles, Cols>
271
+ function closed<const Name extends string, const Cols extends Record<string, PayloadField>>(
272
+ name: Name,
273
+ columns: Cols
274
+ ): <Handles extends string>(axioms: Axioms<Handles, Cols>) => Closed<Name, Handles, Cols>
156
275
 
157
- function closed(
158
- name: string,
159
- handles: readonly [string, ...string[]],
160
- columns?: Record<string, PayloadField>,
161
- axioms?: Readonly<Record<string, Readonly<Record<string, unknown>>>>
162
- ): unknown {
276
+ function closed<const Name extends string>(
277
+ name: Name,
278
+ shape: readonly [string, ...string[]] | Record<string, PayloadField>
279
+ ):
280
+ | Closed<Name, string, Record<never, never>>
281
+ | (<Handles extends string>(
282
+ axioms: Axioms<Handles, Record<string, PayloadField>>
283
+ ) => Closed<Name, Handles, Record<string, PayloadField>>) {
284
+ if (isHandleTuple(shape)) {
285
+ return closedBare(name, shape)
286
+ }
287
+ return closedPayload(name, shape)
288
+ }
289
+
290
+ /** The bare tier's precisely-typed builder: no columns, no axioms. */
291
+ function closedBare<Name extends string, Handles extends string>(
292
+ name: Name,
293
+ handles: readonly [Handles, ...Handles[]]
294
+ ): Closed<Name, Handles, Record<never, never>> {
295
+ return mintClosed<Name, Handles, Record<never, never>>(name, handles, {}, undefined)
296
+ }
297
+
298
+ /**
299
+ * The payload tier's precisely-typed builder: column names are judged
300
+ * EAGERLY (at `closed(name, columns)`, before any axioms arrive — the
301
+ * macro-expansion analog), and the returned `withAxioms` reads its handle
302
+ * set off the axioms record's own keys.
303
+ */
304
+ function closedPayload<Name extends string, Cols extends Record<string, PayloadField>>(
305
+ name: Name,
306
+ columns: Cols
307
+ ): <Handles extends string>(axioms: Axioms<Handles, Cols>) => Closed<Name, Handles, Cols> {
308
+ for (const columnName of Object.keys(columns)) {
309
+ assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
310
+ }
311
+ return function withAxioms<Handles extends string>(axioms: Axioms<Handles, Cols>): Closed<Name, Handles, Cols> {
312
+ const handles = Object.keys(axioms)
313
+ for (const handle of handles) {
314
+ assertDeclarationOrderKey(`closed relation ${name} handle`, handle)
315
+ }
316
+ if (!handleKeysOwn(axioms, handles)) {
317
+ throw errors.new(`closed relation ${name}: handle enumeration incomplete`)
318
+ }
319
+ return mintClosed<Name, Handles, Cols>(name, handles, columns, axioms)
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Mints one closed relation value — the shared seam of both tiers, HONESTLY
325
+ * typed end to end (a wrong-shaped mint is a compile error here, not a
326
+ * laundered `unknown`): roster checks, eager axiom lowering, the
327
+ * domain-labeled `id` descriptor, the frozen `columns` carrier (the runtime
328
+ * twin of the `Cols` type parameter), and the handle constants.
329
+ */
330
+ function mintClosed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>>(
331
+ name: Name,
332
+ handles: readonly Handles[],
333
+ columns: Cols,
334
+ axioms: Axioms<Handles, Cols> | undefined
335
+ ): Closed<Name, Handles, Cols> {
336
+ if (handles.length === 0) {
337
+ throw errors.new(`closed relation ${name}: at least one handle is required (an empty vocabulary declares nothing)`)
338
+ }
163
339
  const seen = new Set<string>()
164
340
  for (const handle of handles) {
165
341
  if (seen.has(handle)) {
@@ -172,17 +348,17 @@ function closed(
172
348
  )
173
349
  }
174
350
  }
175
- const roster: ClosedRoster = Object.freeze({ name, handles: Object.freeze([...handles]) })
351
+ const handleList: readonly Handles[] = Object.freeze([...handles])
352
+ const roster: ClosedRoster = Object.freeze({ name, handles: handleList })
176
353
  const cols: ClosedColumn[] = []
177
- if (columns !== undefined) {
178
- for (const [columnName, field] of Object.entries(columns)) {
179
- assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
180
- cols.push(Object.freeze({ name: columnName, field: field.data }))
181
- }
354
+ for (const [columnName, field] of Object.entries(columns)) {
355
+ assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
356
+ cols.push(Object.freeze({ name: columnName, field }))
182
357
  }
183
- const rows: ClosedRow[] = handles.map(function lowerRow(handle) {
358
+ Object.freeze(cols)
359
+ const rows: ClosedRow[] = handleList.map(function lowerRow(handle) {
184
360
  const values = cols.map(function lowerAxiomLiteral(column) {
185
- const row = axiomRow(name, axioms, handle)
361
+ const row = groundRow(name, axioms, handle)
186
362
  return Object.freeze(literalOf(column.field, row[column.name]))
187
363
  })
188
364
  return Object.freeze({ handle, values: Object.freeze(values) })
@@ -190,38 +366,40 @@ function closed(
190
366
  const data: ClosedData = Object.freeze({
191
367
  name,
192
368
  handles: roster.handles,
193
- columns: Object.freeze(cols),
369
+ columns: cols,
194
370
  rows: Object.freeze(rows)
195
371
  })
196
- const id: ClosedIdField<string> = Object.freeze({
197
- data: fieldData({ kind: "u64" }, name, false, roster)
372
+ const id: ClosedIdField<`${Name}Id`> = Object.freeze({
373
+ kind: "u64",
374
+ domain: `${name}Id`,
375
+ closed: roster
198
376
  })
199
377
  /**
200
378
  * Handle names are arbitrary identifiers, so rows and constants are
201
- * minted with OWN-property definition, never assignment: a handle named
379
+ * minted with OWN-property definition (inside {@link mintAxioms} and
380
+ * {@link mintHandleConstants}), never assignment: a handle named
202
381
  * "__proto__" would otherwise ride the Object.prototype accessor —
203
382
  * silently swapping the record's prototype instead of creating the row,
204
383
  * and no-oping the constant (a primitive through the setter) — minting a
205
- * value whose type claims Brand<bigint, Name> but reads back an object.
384
+ * value whose type claims a bigint constant but reads back an object.
385
+ * (Object SPREAD is CreateDataProperty by spec, so the copies below are
386
+ * own-property safe for column names too.)
206
387
  */
207
- const axiomsOut: Record<string, object> = {}
208
- for (const handle of handles) {
209
- const row = axioms === undefined ? Object.freeze({}) : Object.freeze({ ...axiomRow(name, axioms, handle) })
210
- Object.defineProperty(axiomsOut, handle, { value: row, enumerable: true })
211
- }
212
- const value: Record<string, unknown> = {
388
+ const axiomsOut = mintAxioms<Handles, Cols>(name, handleList, cols, axioms)
389
+ const constants = mintHandleConstants(name, handleList)
390
+ const columnsOut: Cols = { ...columns }
391
+ Object.freeze(columnsOut)
392
+ return Object.freeze({
393
+ ...constants,
213
394
  name,
214
395
  id,
215
396
  data,
216
- axioms: Object.freeze(axiomsOut),
217
- fromId(idValue: bigint): string | undefined {
218
- return roster.handles[Number(idValue)]
397
+ axioms: axiomsOut,
398
+ columns: columnsOut,
399
+ fromId(idValue: bigint): Handles | undefined {
400
+ return handleList[Number(idValue)]
219
401
  }
220
- }
221
- handles.forEach(function mintHandleConstant(handle, index) {
222
- Object.defineProperty(value, handle, { value: BigInt(index), enumerable: true })
223
402
  })
224
- return Object.freeze(value)
225
403
  }
226
404
 
227
405
  export type { AnyClosed, AxiomRow, Axioms, Closed, ClosedColumn, ClosedCore, ClosedData, ClosedRow, PayloadField }