@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.
- package/COOKBOOK.md +1394 -0
- package/README.md +4 -0
- package/dist/closed.d.ts +60 -37
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +159 -40
- package/dist/closed.js.map +1 -1
- package/dist/count.d.ts +64 -17
- package/dist/count.d.ts.map +1 -1
- package/dist/count.js +37 -14
- package/dist/count.js.map +1 -1
- package/dist/db.d.ts +15 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +13 -5
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts +12 -11
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +7 -6
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +79 -19
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +14 -15
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +158 -129
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +80 -81
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +21 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/lower.d.ts +6 -3
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +48 -17
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +36 -21
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +41 -25
- package/dist/marshal.js.map +1 -1
- package/dist/native.js +2 -2
- package/dist/query/atom.d.ts +332 -174
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +72 -172
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +295 -107
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +860 -388
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +69 -88
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +118 -72
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +19 -16
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +31 -24
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +139 -123
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +71 -115
- package/dist/query/scope.js.map +1 -1
- package/dist/query/select.d.ts +102 -80
- package/dist/query/select.d.ts.map +1 -1
- package/dist/query/select.js +39 -34
- package/dist/query/select.js.map +1 -1
- package/dist/relation.d.ts +33 -36
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +15 -16
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +19 -8
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +6 -5
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +56 -31
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +38 -13
- package/dist/statements.js.map +1 -1
- package/package.json +4 -3
- package/src/closed.ts +271 -93
- package/src/count.ts +112 -18
- package/src/db.ts +22 -12
- package/src/exhume.ts +12 -11
- package/src/face.ts +114 -22
- package/src/fields.ts +261 -217
- package/src/index.ts +50 -59
- package/src/lower.ts +62 -20
- package/src/marshal.ts +48 -30
- package/src/native.ts +2 -2
- package/src/query/atom.ts +480 -376
- package/src/query/lower.ts +1341 -542
- package/src/query/predicate.ts +198 -161
- package/src/query/run.ts +35 -25
- package/src/query/scope.ts +188 -218
- package/src/query/select.ts +168 -93
- package/src/relation.ts +38 -44
- package/src/schema.ts +22 -12
- package/src/spec.ts +6 -5
- package/src/statements.ts +69 -26
- package/dist/brand.d.ts +0 -59
- package/dist/brand.d.ts.map +0 -1
- package/dist/brand.js +0 -47
- package/dist/brand.js.map +0 -1
- package/src/brand.ts +0 -82
package/dist/statements.js
CHANGED
|
@@ -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.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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(
|
|
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.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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
|
|
49
|
-
*
|
|
50
|
-
* (
|
|
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({
|
package/dist/statements.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"AAAA
|
|
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
|
+
"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.
|
|
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
|
|
5
|
-
* (host-enum analog): handle CONSTANTS on the value
|
|
6
|
-
* declaration order
|
|
7
|
-
*
|
|
8
|
-
* (`
|
|
9
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
52
|
+
type PayloadField = Exclude<AnyField, { readonly fresh: true }>
|
|
68
53
|
|
|
69
|
-
/** One declared payload column: name plus its field
|
|
54
|
+
/** One declared payload column: name plus its field descriptor. */
|
|
70
55
|
interface ClosedColumn {
|
|
71
56
|
readonly name: string
|
|
72
|
-
readonly field:
|
|
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
|
|
95
|
-
type AxiomRow<Cols
|
|
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
|
|
100
|
-
*
|
|
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
|
|
103
|
-
readonly [H in Handles
|
|
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
|
|
96
|
+
interface ClosedCore<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
|
|
111
97
|
readonly name: Name
|
|
112
98
|
/**
|
|
113
|
-
* The
|
|
114
|
-
* relation's field block is the reference through which bare
|
|
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
|
|
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:
|
|
118
|
+
fromId(id: bigint): Handles | undefined
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
/**
|
|
126
|
-
* A closed relation value: the core surface plus one
|
|
127
|
-
* handle (`Kind.Checking:
|
|
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
|
|
130
|
-
|
|
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
|
|
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
|
|
149
|
-
*
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
351
|
+
const handleList: readonly Handles[] = Object.freeze([...handles])
|
|
352
|
+
const roster: ClosedRoster = Object.freeze({ name, handles: handleList })
|
|
176
353
|
const cols: ClosedColumn[] = []
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
369
|
+
columns: cols,
|
|
194
370
|
rows: Object.freeze(rows)
|
|
195
371
|
})
|
|
196
|
-
const id: ClosedIdField
|
|
197
|
-
|
|
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
|
|
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
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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:
|
|
217
|
-
|
|
218
|
-
|
|
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 }
|