@bjornpagen/bumbledb 0.2.0 → 0.3.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.
- package/COOKBOOK.md +462 -406
- package/README.md +66 -31
- package/dist/closed.d.ts +121 -25
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +108 -42
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +12 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -5
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +100 -55
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +36 -10
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +50 -79
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +20 -53
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +17 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +224 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +224 -0
- package/dist/law.js.map +1 -0
- package/dist/lower.d.ts +17 -10
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +34 -23
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +6 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +88 -50
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +4 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +71 -56
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +90 -43
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +10 -9
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +2 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/scope.d.ts +76 -41
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +77 -30
- package/dist/query/scope.js.map +1 -1
- package/dist/query/select.d.ts +5 -5
- package/dist/query/select.d.ts.map +1 -1
- package/dist/relation.d.ts +21 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +13 -7
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +41 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +16 -2
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +7 -6
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +61 -31
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +22 -17
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/closed.ts +243 -68
- package/src/db.ts +23 -5
- package/src/face.ts +162 -84
- package/src/fields.ts +57 -136
- package/src/index.ts +42 -16
- package/src/law.ts +519 -0
- package/src/lower.ts +36 -23
- package/src/native.ts +6 -2
- package/src/query/atom.ts +105 -58
- package/src/query/lower.ts +271 -139
- package/src/query/predicate.ts +43 -33
- package/src/query/scope.ts +125 -49
- package/src/query/select.ts +5 -5
- package/src/relation.ts +15 -9
- package/src/schema.ts +48 -7
- package/src/spec.ts +7 -6
- package/src/statements.ts +83 -43
package/src/closed.ts
CHANGED
|
@@ -4,20 +4,31 @@
|
|
|
4
4
|
* tiers, one function. The emission per closed relation mirrors the
|
|
5
5
|
* macro's (host-enum analog): handle CONSTANTS on the value
|
|
6
6
|
* (`Kind.Checking`, ids = declaration order, each a BARE `bigint` — no
|
|
7
|
-
* brand), the `fromId` weld, an `id` field descriptor carrying the
|
|
8
|
-
*
|
|
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"`)
|
|
9
10
|
* for other relations' field blocks (`kind: Kind.id`), payload readback
|
|
10
11
|
* (`Kind.axioms`), and the declared payload column descriptors
|
|
11
12
|
* (`Kind.columns` — the runtime twin of the `Cols` type parameter, which
|
|
12
|
-
* the face layer's
|
|
13
|
-
* "Savings"])`. Payload tier: `closed("Sev", { pages: bool }
|
|
14
|
-
* { pages: true }, ... })` —
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* the face layer's structural wall reads). Bare tier: `closed("Kind", ["Checking",
|
|
14
|
+
* "Savings"])`. Payload tier: `closed("Sev", { pages: bool }, { Critical:
|
|
15
|
+
* { pages: true }, ... })` — one call, three arguments (the curried tier-2
|
|
16
|
+
* spelling is DELETED — canonical utterance): the axioms record IS the
|
|
17
|
+
* handle declaration, every handle carrying every column exactly once
|
|
18
|
+
* (type-enforced). No fact type and no insert surface exist — closed
|
|
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.
|
|
18
28
|
*/
|
|
19
29
|
|
|
20
30
|
import * as errors from "@superbuilders/errors"
|
|
31
|
+
import type { OneOf } from "#face.ts"
|
|
21
32
|
import {
|
|
22
33
|
type AnyField,
|
|
23
34
|
assertDeclarationOrderKey,
|
|
@@ -26,13 +37,17 @@ import {
|
|
|
26
37
|
type Infer,
|
|
27
38
|
literalOf
|
|
28
39
|
} from "#fields.ts"
|
|
40
|
+
import { resolveSelection, type SelectionBinding } from "#relation.ts"
|
|
29
41
|
import type { LiteralSpec } from "#spec.ts"
|
|
30
42
|
|
|
31
43
|
/**
|
|
32
44
|
* The value-surface property names a handle may not shadow — the macro's
|
|
33
45
|
* name-collision diagnostic, here over the closed value's own properties
|
|
34
46
|
* (`relation`/`selection` are reserved so a closed value can never be
|
|
35
|
-
* mistaken for a selected relation by `on()`'s discriminant
|
|
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).
|
|
36
51
|
*/
|
|
37
52
|
const reservedHandleNames: readonly string[] = Object.freeze([
|
|
38
53
|
"name",
|
|
@@ -41,6 +56,8 @@ const reservedHandleNames: readonly string[] = Object.freeze([
|
|
|
41
56
|
"axioms",
|
|
42
57
|
"columns",
|
|
43
58
|
"fromId",
|
|
59
|
+
"where",
|
|
60
|
+
"match",
|
|
44
61
|
"relation",
|
|
45
62
|
"selection"
|
|
46
63
|
])
|
|
@@ -51,6 +68,18 @@ const reservedHandleNames: readonly string[] = Object.freeze([
|
|
|
51
68
|
*/
|
|
52
69
|
type PayloadField = Exclude<AnyField, { readonly fresh: true }>
|
|
53
70
|
|
|
71
|
+
/**
|
|
72
|
+
* A declared payload column BLOCK: name → descriptor, with `id`
|
|
73
|
+
* unspellable — the sealed shape mints the synthetic `id` itself (ordinal
|
|
74
|
+
* 0 of the matchable fields), so a declared column named `id` would be
|
|
75
|
+
* shadowed by the synthetic slot everywhere the shape resolves by name
|
|
76
|
+
* (`matchFieldsOf`, the projected face, `spec.rs`'s resolver). The wall is
|
|
77
|
+
* typed here and judged again at construction in {@link mintClosed} — the
|
|
78
|
+
* runtime twin for untyped callers, warmer and earlier than the engine's
|
|
79
|
+
* `DuplicateFieldName` at `Db.create`.
|
|
80
|
+
*/
|
|
81
|
+
type PayloadColumns = Record<string, PayloadField> & { readonly id?: never }
|
|
82
|
+
|
|
54
83
|
/** One declared payload column: name plus its field descriptor. */
|
|
55
84
|
interface ClosedColumn {
|
|
56
85
|
readonly name: string
|
|
@@ -96,21 +125,22 @@ type Axioms<Handles extends string, Cols extends Record<string, PayloadField>> =
|
|
|
96
125
|
interface ClosedCore<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
|
|
97
126
|
readonly name: Name
|
|
98
127
|
/**
|
|
99
|
-
* The
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
128
|
+
* 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).
|
|
103
133
|
*/
|
|
104
|
-
readonly id: ClosedIdField
|
|
134
|
+
readonly id: ClosedIdField
|
|
105
135
|
readonly data: ClosedData
|
|
106
136
|
/** Payload readback: handle to its declared column values, bare and structural. */
|
|
107
137
|
readonly axioms: Axioms<Handles, Cols>
|
|
108
138
|
/**
|
|
109
|
-
* The declared payload columns, name →
|
|
139
|
+
* The declared payload columns, name → field descriptor — an HONEST
|
|
110
140
|
* frozen runtime record (the descriptors themselves, by identity), and
|
|
111
|
-
* the typed carrier a projected payload column's
|
|
141
|
+
* the typed carrier a projected payload column's structural shape is
|
|
112
142
|
* recovered through off the schema type (the face layer's
|
|
113
|
-
* `
|
|
143
|
+
* `ProjectedShape` reads it; `data.columns` carries the same
|
|
114
144
|
* descriptors in declaration order for the lowering).
|
|
115
145
|
*/
|
|
116
146
|
readonly columns: Cols
|
|
@@ -118,17 +148,86 @@ interface ClosedCore<Name extends string, Handles extends string, Cols extends R
|
|
|
118
148
|
fromId(id: bigint): Handles | undefined
|
|
119
149
|
}
|
|
120
150
|
|
|
151
|
+
/**
|
|
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).
|
|
157
|
+
*/
|
|
158
|
+
type ClosedSelectionInput<Cols extends Record<string, PayloadField>> = {
|
|
159
|
+
readonly [C in keyof Cols]?: Infer<Cols[C]> | OneOf<Infer<Cols[C]>>
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A closed relation with a ψ selection applied — what `on()` consumes as a
|
|
164
|
+
* σ-carrying closed source (`on(Kind.where({ mastered: true }), "id")`).
|
|
165
|
+
* Deliberately the SAME discriminant shape as the ordinary `Selected`
|
|
166
|
+
* (`relation`/`selection` — `face.ts::faceParts` splits both by `"relation"
|
|
167
|
+
* in source`), and structurally UNMISTAKABLE for one: an `AnyClosed` lacks
|
|
168
|
+
* the relation shape (no `fields` record, no `RelationData`).
|
|
169
|
+
*/
|
|
170
|
+
interface SelectedClosed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
|
|
171
|
+
readonly relation: Closed<Name, Handles, Cols>
|
|
172
|
+
readonly selection: readonly SelectionBinding[]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Any ψ-selected closed relation value. */
|
|
176
|
+
interface AnySelectedClosed {
|
|
177
|
+
readonly relation: AnyClosed
|
|
178
|
+
readonly selection: readonly SelectionBinding[]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The ψ-selection surface of a payload-tier closed value. The selection is
|
|
183
|
+
* resolved EAGERLY against the declared columns and lowered as-is — the SDK
|
|
184
|
+
* never pre-folds ψ into an id set: pass-through lowering is what the macro
|
|
185
|
+
* does, and the ENGINE folds against the sealed extension at validate
|
|
186
|
+
* (`compile_member_set`).
|
|
187
|
+
*/
|
|
188
|
+
interface ClosedSelectable<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
|
|
189
|
+
where(selection: ClosedSelectionInput<Cols>): SelectedClosed<Name, Handles, Cols>
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
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` in — the runtime roster refuses an out-of-vocabulary id
|
|
199
|
+
* with a throw, never a misdispatch.
|
|
200
|
+
*/
|
|
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
|
+
|
|
121
215
|
/**
|
|
122
216
|
* A closed relation value: the core surface plus one BARE constant per
|
|
123
217
|
* handle (`Kind.Checking: bigint`, ids = declaration order — the value is
|
|
124
218
|
* structural; the roster judges out-of-vocabulary ids at construction and
|
|
125
|
-
* the engine at commit)
|
|
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).
|
|
126
223
|
*/
|
|
127
224
|
type Closed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> = ClosedCore<
|
|
128
225
|
Name,
|
|
129
226
|
Handles,
|
|
130
227
|
Cols
|
|
131
|
-
> & { readonly [H in Handles]: bigint }
|
|
228
|
+
> & { readonly [H in Handles]: bigint } & ([keyof Cols] extends [never]
|
|
229
|
+
? ClosedMatchBare<Handles>
|
|
230
|
+
: ClosedSelectable<Name, Handles, Cols> & ClosedMatchPayload<Handles, Cols>)
|
|
132
231
|
|
|
133
232
|
/** Any closed relation value, whatever its roster and columns. */
|
|
134
233
|
interface AnyClosed {
|
|
@@ -262,29 +361,40 @@ function closed<const Name extends string, const Handles extends readonly [strin
|
|
|
262
361
|
): Closed<Name, Handles[number], Record<never, never>>
|
|
263
362
|
|
|
264
363
|
/**
|
|
265
|
-
* Payload tier: declared columns
|
|
266
|
-
* { mastered: bool }
|
|
267
|
-
* false } })`. The
|
|
268
|
-
*
|
|
269
|
-
*
|
|
364
|
+
* Payload tier: declared columns AND ground axioms, one call — `closed(
|
|
365
|
+
* "Grade", { mastered: bool }, { DirectPass: { mastered: true }, Failed:
|
|
366
|
+
* { mastered: false } })`. The curried tier-2 spelling is DELETED
|
|
367
|
+
* (canonical utterance): `Cols` infers from the column block, the handle
|
|
368
|
+
* set from the axioms record's keys (reverse mapped-type inference), and
|
|
369
|
+
* every row is contextually checked against the declared columns — a
|
|
370
|
+
* wrong-typed value errors ON its property. The axioms record's keys ARE
|
|
371
|
+
* the handles (declaration order = key order, integer-index names
|
|
372
|
+
* rejected); every row carries every column exactly once (type-enforced by
|
|
373
|
+
* {@link Axioms}).
|
|
270
374
|
*/
|
|
271
|
-
function closed<const Name extends string, const Cols extends
|
|
375
|
+
function closed<const Name extends string, const Cols extends PayloadColumns, Handles extends string>(
|
|
272
376
|
name: Name,
|
|
273
|
-
columns: Cols
|
|
274
|
-
|
|
377
|
+
columns: Cols,
|
|
378
|
+
axioms: Axioms<Handles, Cols>
|
|
379
|
+
): Closed<Name, Handles, Cols>
|
|
275
380
|
|
|
276
|
-
function closed<const Name extends string>(
|
|
381
|
+
function closed<const Name extends string, const Cols extends PayloadColumns, Handles extends string>(
|
|
277
382
|
name: Name,
|
|
278
|
-
shape: readonly [string, ...string[]] |
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
| (<Handles extends string>(
|
|
282
|
-
axioms: Axioms<Handles, Record<string, PayloadField>>
|
|
283
|
-
) => Closed<Name, Handles, Record<string, PayloadField>>) {
|
|
383
|
+
shape: readonly [string, ...string[]] | Cols,
|
|
384
|
+
axioms?: Axioms<Handles, Cols>
|
|
385
|
+
): Closed<Name, string, Record<never, never>> | Closed<Name, Handles, Cols> {
|
|
284
386
|
if (isHandleTuple(shape)) {
|
|
387
|
+
if (axioms !== undefined) {
|
|
388
|
+
throw errors.new(`closed relation ${name}: the bare tier declares no columns, so ground axioms are inadmissible`)
|
|
389
|
+
}
|
|
285
390
|
return closedBare(name, shape)
|
|
286
391
|
}
|
|
287
|
-
|
|
392
|
+
if (axioms === undefined) {
|
|
393
|
+
throw errors.new(
|
|
394
|
+
`closed relation ${name}: payload columns declared without ground axioms — the payload tier is spelled closed(name, columns, axioms) (the curried spelling is deleted)`
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
return closedPayload(name, shape, axioms)
|
|
288
398
|
}
|
|
289
399
|
|
|
290
400
|
/** The bare tier's precisely-typed builder: no columns, no axioms. */
|
|
@@ -297,35 +407,51 @@ function closedBare<Name extends string, Handles extends string>(
|
|
|
297
407
|
|
|
298
408
|
/**
|
|
299
409
|
* The payload tier's precisely-typed builder: column names are judged
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* set off the axioms record's own keys.
|
|
410
|
+
* first (the macro-expansion analog), then the handle set is read off the
|
|
411
|
+
* axioms record's own keys.
|
|
303
412
|
*/
|
|
304
|
-
function closedPayload<Name extends string,
|
|
413
|
+
function closedPayload<Name extends string, Handles extends string, Cols extends PayloadColumns>(
|
|
305
414
|
name: Name,
|
|
306
|
-
columns: Cols
|
|
307
|
-
|
|
415
|
+
columns: Cols,
|
|
416
|
+
axioms: Axioms<Handles, Cols>
|
|
417
|
+
): Closed<Name, Handles, Cols> {
|
|
308
418
|
for (const columnName of Object.keys(columns)) {
|
|
309
419
|
assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
|
|
310
420
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
throw errors.new(`closed relation ${name}: handle enumeration incomplete`)
|
|
318
|
-
}
|
|
319
|
-
return mintClosed<Name, Handles, Cols>(name, handles, columns, axioms)
|
|
421
|
+
const handles = Object.keys(axioms)
|
|
422
|
+
for (const handle of handles) {
|
|
423
|
+
assertDeclarationOrderKey(`closed relation ${name} handle`, handle)
|
|
424
|
+
}
|
|
425
|
+
if (!handleKeysOwn(axioms, handles)) {
|
|
426
|
+
throw errors.new(`closed relation ${name}: handle enumeration incomplete`)
|
|
320
427
|
}
|
|
428
|
+
return mintClosed<Name, Handles, Cols>(name, handles, columns, axioms)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
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.
|
|
437
|
+
*/
|
|
438
|
+
function surfaceMinted<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>>(
|
|
439
|
+
value: ClosedCore<Name, Handles, Cols> & { readonly [H in Handles]: bigint },
|
|
440
|
+
cols: readonly ClosedColumn[]
|
|
441
|
+
): value is Closed<Name, Handles, Cols> {
|
|
442
|
+
const matchable = "match" in value && typeof value.match === "function"
|
|
443
|
+
const selectable = "where" in value && typeof value.where === "function"
|
|
444
|
+
return matchable && (cols.length > 0 ? selectable : !selectable)
|
|
321
445
|
}
|
|
322
446
|
|
|
323
447
|
/**
|
|
324
448
|
* Mints one closed relation value — the shared seam of both tiers, HONESTLY
|
|
325
449
|
* typed end to end (a wrong-shaped mint is a compile error here, not a
|
|
326
450
|
* laundered `unknown`): roster checks, eager axiom lowering, the
|
|
327
|
-
*
|
|
328
|
-
* twin of the `Cols` type parameter),
|
|
451
|
+
* 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
|
|
454
|
+
* ψ-selection `where()`.
|
|
329
455
|
*/
|
|
330
456
|
function mintClosed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>>(
|
|
331
457
|
name: Name,
|
|
@@ -353,6 +479,11 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
|
|
|
353
479
|
const cols: ClosedColumn[] = []
|
|
354
480
|
for (const [columnName, field] of Object.entries(columns)) {
|
|
355
481
|
assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
|
|
482
|
+
if (columnName === "id") {
|
|
483
|
+
throw errors.new(
|
|
484
|
+
`closed relation ${name}: the payload column id collides with the sealed shape's synthetic id (the relation mints its own id at ordinal 0; name the column something else)`
|
|
485
|
+
)
|
|
486
|
+
}
|
|
356
487
|
cols.push(Object.freeze({ name: columnName, field }))
|
|
357
488
|
}
|
|
358
489
|
Object.freeze(cols)
|
|
@@ -369,11 +500,7 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
|
|
|
369
500
|
columns: cols,
|
|
370
501
|
rows: Object.freeze(rows)
|
|
371
502
|
})
|
|
372
|
-
const id: ClosedIdField
|
|
373
|
-
kind: "u64",
|
|
374
|
-
domain: `${name}Id`,
|
|
375
|
-
closed: roster
|
|
376
|
-
})
|
|
503
|
+
const id: ClosedIdField = Object.freeze({ kind: "u64", closed: roster })
|
|
377
504
|
/**
|
|
378
505
|
* Handle names are arbitrary identifiers, so rows and constants are
|
|
379
506
|
* minted with OWN-property definition (inside {@link mintAxioms} and
|
|
@@ -389,18 +516,66 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
|
|
|
389
516
|
const constants = mintHandleConstants(name, handleList)
|
|
390
517
|
const columnsOut: Cols = { ...columns }
|
|
391
518
|
Object.freeze(columnsOut)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
519
|
+
function fromId(idValue: bigint): Handles | undefined {
|
|
520
|
+
return handleList[Number(idValue)]
|
|
521
|
+
}
|
|
522
|
+
const holder: { value: Closed<Name, Handles, Cols> | undefined } = { value: undefined }
|
|
523
|
+
/**
|
|
524
|
+
* The ψ selection: resolved against the declared payload columns through
|
|
525
|
+
* the ONE selection machine (`relation.ts::resolveSelection` — a
|
|
526
|
+
* `ClosedColumn` is structurally a `RelationField`), never pre-folded
|
|
527
|
+
* into an id set (the engine folds at validate).
|
|
528
|
+
*/
|
|
529
|
+
function where(selection: ClosedSelectionInput<Cols>): SelectedClosed<Name, Handles, Cols> {
|
|
530
|
+
const owner = holder.value
|
|
531
|
+
if (owner === undefined) {
|
|
532
|
+
throw errors.new(`closed relation ${name}: self-reference read before construction completed`)
|
|
401
533
|
}
|
|
402
|
-
|
|
534
|
+
return Object.freeze({
|
|
535
|
+
relation: owner,
|
|
536
|
+
selection: resolveSelection(name, cols, Object.entries(selection))
|
|
537
|
+
})
|
|
538
|
+
}
|
|
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 })
|
|
560
|
+
if (!surfaceMinted<Name, Handles, Cols>(value, cols)) {
|
|
561
|
+
throw errors.new(`closed relation ${name}: ergonomic-surface minting incomplete`)
|
|
562
|
+
}
|
|
563
|
+
holder.value = value
|
|
564
|
+
return value
|
|
403
565
|
}
|
|
404
566
|
|
|
405
|
-
export type {
|
|
567
|
+
export type {
|
|
568
|
+
AnyClosed,
|
|
569
|
+
AnySelectedClosed,
|
|
570
|
+
AxiomRow,
|
|
571
|
+
Axioms,
|
|
572
|
+
Closed,
|
|
573
|
+
ClosedColumn,
|
|
574
|
+
ClosedCore,
|
|
575
|
+
ClosedData,
|
|
576
|
+
ClosedRow,
|
|
577
|
+
ClosedSelectionInput,
|
|
578
|
+
PayloadField,
|
|
579
|
+
SelectedClosed
|
|
580
|
+
}
|
|
406
581
|
export { closed }
|
package/src/db.ts
CHANGED
|
@@ -1419,16 +1419,31 @@ process.once("exit", function closeCachedStores() {
|
|
|
1419
1419
|
}
|
|
1420
1420
|
})
|
|
1421
1421
|
|
|
1422
|
+
/**
|
|
1423
|
+
* The engine twin of the schema-level class wall, as a matchable value
|
|
1424
|
+
* (`errors.is`): the shared lowering rejected a spec whose statement pairs
|
|
1425
|
+
* faces with disagreeing newtype labels — the faces of a dependency agree
|
|
1426
|
+
* on their newtype, or neither carries one. UNREACHABLE through the typed
|
|
1427
|
+
* builder (the SDK computes every label from the laws, so its lowered
|
|
1428
|
+
* specs cohere by construction); a raw spec handed to the bridge is the
|
|
1429
|
+
* one road here, and the runtime referee that proves the engine judges
|
|
1430
|
+
* what the types claim.
|
|
1431
|
+
*/
|
|
1432
|
+
const ErrNewtypeMismatch = errors.new(
|
|
1433
|
+
"bumbledb newtypeMismatch: a statement pairs faces whose newtypes disagree — the faces of a dependency agree on their newtype, or neither carries one"
|
|
1434
|
+
)
|
|
1435
|
+
|
|
1422
1436
|
/**
|
|
1423
1437
|
* The one admission path both verbs share: canonical-path cache lookup
|
|
1424
1438
|
* first (a hit returns the SAME `Db` value for the identical theory, a
|
|
1425
1439
|
* typed fingerprint error for a different one, and a typed refusal for
|
|
1426
1440
|
* `create` — the store a cache entry proves initialized is exactly what
|
|
1427
1441
|
* create refuses). On a miss: lower the theory, run one bridge call, and
|
|
1428
|
-
* wrap the
|
|
1429
|
-
* validation, every issue in one message)
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1442
|
+
* wrap the domain refusals — `schemaError` (spec resolution + schema
|
|
1443
|
+
* validation, every issue in one message), `newtypeMismatch` (the
|
|
1444
|
+
* coherence wall, {@link ErrNewtypeMismatch}), and `fingerprintMismatch`
|
|
1445
|
+
* (a different theory cannot open the store) — into typed errors carrying
|
|
1446
|
+
* the engine's message intact.
|
|
1432
1447
|
*/
|
|
1433
1448
|
function admit<Rels extends SchemaRelations>(
|
|
1434
1449
|
verb: "create" | "open",
|
|
@@ -1458,6 +1473,9 @@ function admit<Rels extends SchemaRelations>(
|
|
|
1458
1473
|
return native.dbOpen(canonical, spec)
|
|
1459
1474
|
})
|
|
1460
1475
|
if (!opened.ok) {
|
|
1476
|
+
if (opened.kind === "newtypeMismatch") {
|
|
1477
|
+
throw errors.wrap(ErrNewtypeMismatch, `${verb} ${canonical}: ${opened.message}`)
|
|
1478
|
+
}
|
|
1461
1479
|
throw errors.new(`bumbledb ${opened.kind} (${verb} ${canonical}): ${opened.message}`)
|
|
1462
1480
|
}
|
|
1463
1481
|
const manifest = bridged("fetch bumbledb manifest", function fetchManifest() {
|
|
@@ -1526,4 +1544,4 @@ export type {
|
|
|
1526
1544
|
WitnessedWriteResult,
|
|
1527
1545
|
WriteResult
|
|
1528
1546
|
}
|
|
1529
|
-
export { abandon, Db }
|
|
1547
|
+
export { abandon, Db, ErrNewtypeMismatch }
|