@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/src/count.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cardinality-window counts — exactly five constructors, and nothing else
|
|
3
|
-
* (`docs/architecture/70-api.md` § the canonical-utterance law). The
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* (`docs/architecture/70-api.md` § the canonical-utterance law). The five
|
|
4
|
+
* constructors PARTITION the legal windows, and the ban table is enforced
|
|
5
|
+
* REPRESENTATIONALLY, stronger than Rust's expansion errors, in two tiers:
|
|
6
|
+
*
|
|
7
|
+
* - **The type tier**: a banned spelling written as a LITERAL does not
|
|
8
|
+
* compile — `exactly(0n)`, `between(n, n)`, `between(0n, hi)`,
|
|
9
|
+
* `atLeast(0n)`, `atLeast(1n)`, `atMost(0n)`, and every negative bound
|
|
10
|
+
* are type errors naming the canonical form (`{n..n}`, `{0..0}`,
|
|
11
|
+
* `{0..hi}`-via-between, `{0..*}`, `{1..*}` have NO argument shape that
|
|
12
|
+
* produces them), and no sixth constructor exists at all.
|
|
13
|
+
* - **The construction tier**: a bound the type level cannot judge — a
|
|
14
|
+
* COMPUTED `bigint`, whose literal identity is erased, or an inverted
|
|
15
|
+
* `between(lo, hi)` order, which type-level bigints cannot compare — is
|
|
16
|
+
* judged here at construction with the same canonical-naming errors; and
|
|
17
|
+
* past both tiers the engine's own spec validation remains the law for a
|
|
18
|
+
* hostile FFI caller (the standing two-tier ban enforcement).
|
|
19
|
+
*
|
|
8
20
|
* Bounds are `bigint` (u64 crosses as bigint always, PRD-04's law).
|
|
9
21
|
*/
|
|
10
22
|
|
|
@@ -36,14 +48,87 @@ interface Count {
|
|
|
36
48
|
|
|
37
49
|
/** Stamps one admitted window as a frozen `Count` value. */
|
|
38
50
|
function admit(window: WindowSpec): Count {
|
|
39
|
-
|
|
51
|
+
const count: Count = { window, [admitted]: true }
|
|
52
|
+
return Object.freeze(count)
|
|
40
53
|
}
|
|
41
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The legible banned-spelling verdict: intersected into a count
|
|
57
|
+
* constructor's parameter when the LITERAL argument spells a banned window,
|
|
58
|
+
* naming the canonical form — the compile-time face of the ban table.
|
|
59
|
+
*/
|
|
60
|
+
interface BannedWindow<Canonical extends string> {
|
|
61
|
+
readonly "banned window spelling — the canonical-utterance law names the one legal form": Canonical
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `true` exactly when the literal bigint `N` is negative (out of the u64 count domain). */
|
|
65
|
+
type IsNegative<N extends bigint> = `${N}` extends `-${string}` ? true : false
|
|
66
|
+
|
|
67
|
+
/** The ban verdict on `exactly(n)`: negatives are out of domain; `{0}` is the exclusion, written `none`. */
|
|
68
|
+
type ExactlyBan<N extends bigint> = bigint extends N
|
|
69
|
+
? unknown
|
|
70
|
+
: IsNegative<N> extends true
|
|
71
|
+
? BannedWindow<"window counts are u64 — a negative count is out of domain">
|
|
72
|
+
: N extends 0n
|
|
73
|
+
? BannedWindow<"`{0}` is the exclusion — write none">
|
|
74
|
+
: unknown
|
|
75
|
+
|
|
76
|
+
/** The ban verdict on `atLeast(lo)`: `{0..*}` is vacuous; `{1..*}` is the bare containment respelled. */
|
|
77
|
+
type AtLeastBan<N extends bigint> = bigint extends N
|
|
78
|
+
? unknown
|
|
79
|
+
: IsNegative<N> extends true
|
|
80
|
+
? BannedWindow<"window counts are u64 — a negative count is out of domain">
|
|
81
|
+
: N extends 0n
|
|
82
|
+
? BannedWindow<"`{0..*}` is vacuous — it provably says nothing; delete the statement">
|
|
83
|
+
: N extends 1n
|
|
84
|
+
? BannedWindow<"`{1..*}` says only what the bare containment says — write contained(source, target)">
|
|
85
|
+
: unknown
|
|
86
|
+
|
|
87
|
+
/** The ban verdict on `atMost(hi)`: `{0..0}` is the exclusion, written `none`. */
|
|
88
|
+
type AtMostBan<N extends bigint> = bigint extends N
|
|
89
|
+
? unknown
|
|
90
|
+
: IsNegative<N> extends true
|
|
91
|
+
? BannedWindow<"window counts are u64 — a negative count is out of domain">
|
|
92
|
+
: N extends 0n
|
|
93
|
+
? BannedWindow<"`{0..0}` — the exclusion is written `{0}`: use none">
|
|
94
|
+
: unknown
|
|
95
|
+
|
|
96
|
+
/** The ban verdict on a `between` floor of zero: `{0..hi}` is the ceiling respelled (`atMost(hi)`). */
|
|
97
|
+
type BetweenFloorBan<Lo extends bigint> = Lo extends 0n
|
|
98
|
+
? BannedWindow<"`{0..hi}` — a ceiling is written atMost(hi)">
|
|
99
|
+
: unknown
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The ban verdict on `between(lo, hi)`, judged on the second bound once
|
|
103
|
+
* both literals are known: `{n..n}` is the exact count respelled
|
|
104
|
+
* (`exactly(n)`, or `none` at 0), and `{0..hi}` is the ceiling respelled
|
|
105
|
+
* (`atMost(hi)` — the five constructors PARTITION the legal windows, so
|
|
106
|
+
* the one ceiling window keeps its one spelling). Bound ORDER (`{hi..lo}`
|
|
107
|
+
* inverted) is not type-expressible — bigint literals have no type-level
|
|
108
|
+
* comparison — so inversion stays a construction error below.
|
|
109
|
+
*/
|
|
110
|
+
type BetweenBan<Lo extends bigint, Hi extends bigint> = bigint extends Lo
|
|
111
|
+
? unknown
|
|
112
|
+
: bigint extends Hi
|
|
113
|
+
? unknown
|
|
114
|
+
: IsNegative<Lo> extends true
|
|
115
|
+
? BannedWindow<"window counts are u64 — a negative bound is out of domain">
|
|
116
|
+
: IsNegative<Hi> extends true
|
|
117
|
+
? BannedWindow<"window counts are u64 — a negative bound is out of domain">
|
|
118
|
+
: Lo extends Hi
|
|
119
|
+
? Hi extends Lo
|
|
120
|
+
? Lo extends 0n
|
|
121
|
+
? BannedWindow<"`{0..0}` — the exclusion is written `{0}`: use none">
|
|
122
|
+
: BannedWindow<"`{n..n}` — an exact count is written `{n}`: use exactly(n)">
|
|
123
|
+
: BetweenFloorBan<Lo>
|
|
124
|
+
: BetweenFloorBan<Lo>
|
|
125
|
+
|
|
42
126
|
/**
|
|
43
127
|
* `{n}` — THE exact-count spelling, n ≥ 1. `exactly(0)` is the exclusion
|
|
44
|
-
* respelled
|
|
128
|
+
* respelled: unwritable as a literal ({@link ExactlyBan} names `none`),
|
|
129
|
+
* rejected at construction when computed.
|
|
45
130
|
*/
|
|
46
|
-
function exactly(n:
|
|
131
|
+
function exactly<const N extends bigint>(n: N & ExactlyBan<N>): Count {
|
|
47
132
|
if (n < 0n) {
|
|
48
133
|
throw errors.new(`window counts are u64: exactly(${n}) is out of domain`)
|
|
49
134
|
}
|
|
@@ -57,11 +142,15 @@ function exactly(n: bigint): Count {
|
|
|
57
142
|
const none: Count = admit(exclusion)
|
|
58
143
|
|
|
59
144
|
/**
|
|
60
|
-
* `{lo..hi}` — both bounds explicit,
|
|
61
|
-
* count respelled
|
|
62
|
-
*
|
|
145
|
+
* `{lo..hi}` — both bounds explicit, 1 ≤ lo < hi. `lo === hi` is the exact
|
|
146
|
+
* count respelled and `lo === 0` is the ceiling respelled: unwritable as
|
|
147
|
+
* literals ({@link BetweenBan} names `exactly(n)`, `none` at `{0..0}`, or
|
|
148
|
+
* `atMost(hi)` at a zero floor — the five constructors PARTITION the legal
|
|
149
|
+
* windows), rejected at construction when computed; an inverted window is
|
|
150
|
+
* unsatisfiable and rejected at construction (bigint literals carry no
|
|
151
|
+
* type-level order).
|
|
63
152
|
*/
|
|
64
|
-
function between(lo:
|
|
153
|
+
function between<const Lo extends bigint, const Hi extends bigint>(lo: Lo, hi: Hi & BetweenBan<Lo, Hi>): Count {
|
|
65
154
|
if (lo < 0n || hi < 0n) {
|
|
66
155
|
throw errors.new(`window counts are u64: between(${lo}, ${hi}) is out of domain`)
|
|
67
156
|
}
|
|
@@ -76,15 +165,19 @@ function between(lo: bigint, hi: bigint): Count {
|
|
|
76
165
|
}
|
|
77
166
|
throw errors.new(`\`{${lo}..${lo}}\` — an exact count is written \`{${lo}}\`: use exactly(${lo})`)
|
|
78
167
|
}
|
|
168
|
+
if (lo === 0n) {
|
|
169
|
+
throw errors.new(`\`{0..${hi}}\` — a ceiling is written atMost: use atMost(${hi})`)
|
|
170
|
+
}
|
|
79
171
|
return admit(Object.freeze({ kind: "range", lo, hi }))
|
|
80
172
|
}
|
|
81
173
|
|
|
82
174
|
/**
|
|
83
175
|
* `{lo..*}` — a floor with no ceiling, lo ≥ 2: `atLeast(1)` says only what
|
|
84
|
-
* the bare containment says
|
|
85
|
-
*
|
|
176
|
+
* the bare containment says and `atLeast(0)` is vacuous — both unwritable
|
|
177
|
+
* as literals ({@link AtLeastBan} names the canonical form), rejected at
|
|
178
|
+
* construction when computed.
|
|
86
179
|
*/
|
|
87
|
-
function atLeast(lo:
|
|
180
|
+
function atLeast<const N extends bigint>(lo: N & AtLeastBan<N>): Count {
|
|
88
181
|
if (lo < 0n) {
|
|
89
182
|
throw errors.new(`window counts are u64: atLeast(${lo}) is out of domain`)
|
|
90
183
|
}
|
|
@@ -100,10 +193,11 @@ function atLeast(lo: bigint): Count {
|
|
|
100
193
|
}
|
|
101
194
|
|
|
102
195
|
/**
|
|
103
|
-
* `{0..hi}` — a ceiling, hi ≥ 1: `atMost(0)` is the exclusion respelled
|
|
104
|
-
*
|
|
196
|
+
* `{0..hi}` — a ceiling, hi ≥ 1: `atMost(0)` is the exclusion respelled —
|
|
197
|
+
* unwritable as a literal ({@link AtMostBan} names `none`), rejected at
|
|
198
|
+
* construction when computed.
|
|
105
199
|
*/
|
|
106
|
-
function atMost(hi:
|
|
200
|
+
function atMost<const N extends bigint>(hi: N & AtMostBan<N>): Count {
|
|
107
201
|
if (hi < 0n) {
|
|
108
202
|
throw errors.new(`window counts are u64: atMost(${hi}) is out of domain`)
|
|
109
203
|
}
|
package/src/db.ts
CHANGED
|
@@ -32,11 +32,10 @@
|
|
|
32
32
|
|
|
33
33
|
import * as path from "node:path"
|
|
34
34
|
import * as errors from "@superbuilders/errors"
|
|
35
|
-
import { phantom } from "#brand.ts"
|
|
36
35
|
import type { Exhumed } from "#exhume.ts"
|
|
37
36
|
import { exhumeStore } from "#exhume.ts"
|
|
38
37
|
import { lower } from "#lower.ts"
|
|
39
|
-
import { factOf, isMintedFresh, type KeyFact, keyRowOf, type Minted, recordOf, rowOf } from "#marshal.ts"
|
|
38
|
+
import { factOf, isFreshField, isMintedFresh, type KeyFact, keyRowOf, type Minted, recordOf, rowOf } from "#marshal.ts"
|
|
40
39
|
|
|
41
40
|
import type {
|
|
42
41
|
DbHandle,
|
|
@@ -51,7 +50,8 @@ import type {
|
|
|
51
50
|
ViolationFact as WireViolationFact
|
|
52
51
|
} from "#native.ts"
|
|
53
52
|
import { native } from "#native.ts"
|
|
54
|
-
import type {
|
|
53
|
+
import type { SelectColumn } from "#query/atom.ts"
|
|
54
|
+
import type { Query } from "#query/lower.ts"
|
|
55
55
|
import { lowerQuery } from "#query/lower.ts"
|
|
56
56
|
import { decodeAnswers, wireParams } from "#query/run.ts"
|
|
57
57
|
import type { ParamEntry, ParamsRecord } from "#query/scope.ts"
|
|
@@ -68,8 +68,8 @@ type MemberRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], An
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* The key object of a key-statement-selected `get`: exactly the selected
|
|
71
|
-
* `key()` statement's projection fields, each
|
|
72
|
-
*
|
|
71
|
+
* `key()` statement's projection fields, each at the relation's own BARE
|
|
72
|
+
* structural value type — the {@link KeyFact} rule generalized from the
|
|
73
73
|
* primary key to ANY declared key statement.
|
|
74
74
|
*/
|
|
75
75
|
type DeclaredKeyFact<R extends AnyRelation, Projection extends readonly string[]> = {
|
|
@@ -194,7 +194,7 @@ type WitnessedWriteResult<Rels extends SchemaRelations, R> =
|
|
|
194
194
|
interface Tx<Rels extends SchemaRelations> {
|
|
195
195
|
/**
|
|
196
196
|
* Records one insert. Omitted fresh fields are MINTED through the
|
|
197
|
-
* engine's alloc lane and returned
|
|
197
|
+
* engine's alloc lane and returned as bare bigints; supplying them instead
|
|
198
198
|
* preserves identity (the resupply idiom). Returns the relation's
|
|
199
199
|
* fresh cells, minted or resupplied.
|
|
200
200
|
*/
|
|
@@ -236,7 +236,7 @@ interface ReadScope<Rels extends SchemaRelations> {
|
|
|
236
236
|
* open and the generation read.
|
|
237
237
|
*/
|
|
238
238
|
readonly generation: bigint
|
|
239
|
-
/** Full-relation export in row-id order, decoded to
|
|
239
|
+
/** Full-relation export in row-id order, decoded to bare structural facts. */
|
|
240
240
|
scan<R extends MemberRelation<Rels>>(relation: R): Fact<R>[]
|
|
241
241
|
/**
|
|
242
242
|
* Committed-state point lookup through the relation's primary key
|
|
@@ -258,12 +258,22 @@ interface ReadScope<Rels extends SchemaRelations> {
|
|
|
258
258
|
/**
|
|
259
259
|
* Executes a prepared query against this scope's snapshot with the
|
|
260
260
|
* typed params object; returns the answer SET as plain rows with
|
|
261
|
-
*
|
|
261
|
+
* bare structural values (no order — the host sorts). This is the ONE
|
|
262
262
|
* execution spelling ({@link Prepared} carries no `execute`).
|
|
263
263
|
*/
|
|
264
264
|
execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[]
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The module-private inference slot of {@link Prepared}: an optional symbol
|
|
269
|
+
* property (never set at runtime) that keeps the prepared value's `Row` and
|
|
270
|
+
* `Params` type arguments load-bearing, so `execute` infers the typed rows
|
|
271
|
+
* and the typed params object from the value alone — the query module's
|
|
272
|
+
* `inferred` pattern, local to this module. A type-level carrier only:
|
|
273
|
+
* values stay bare, nothing is asserted.
|
|
274
|
+
*/
|
|
275
|
+
const preparedTypes: unique symbol = Symbol("bumbledb.prepared.types")
|
|
276
|
+
|
|
267
277
|
/**
|
|
268
278
|
* One prepared query as a plain VALUE: explicit visible compilation
|
|
269
279
|
* (`db.prepare(q)` lowers, pins the plan, and surfaces every engine roster
|
|
@@ -281,7 +291,7 @@ interface Prepared<Rels extends SchemaRelations, Row, Params extends ParamsRecor
|
|
|
281
291
|
* re-prepare.
|
|
282
292
|
*/
|
|
283
293
|
staleness(snap: ReadScope<Rels>): Staleness
|
|
284
|
-
readonly [
|
|
294
|
+
readonly [preparedTypes]?: { readonly row: Row; readonly params: Params }
|
|
285
295
|
}
|
|
286
296
|
|
|
287
297
|
/**
|
|
@@ -424,7 +434,7 @@ function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
|
|
|
424
434
|
continue
|
|
425
435
|
}
|
|
426
436
|
for (const declared of member.data.fields) {
|
|
427
|
-
if (declared.field
|
|
437
|
+
if (isFreshField(declared.field)) {
|
|
428
438
|
entries.push({
|
|
429
439
|
kind: "functionality",
|
|
430
440
|
statement: undefined,
|
|
@@ -642,7 +652,7 @@ function mintFreshCells(
|
|
|
642
652
|
): Record<string, FactValue> {
|
|
643
653
|
const fresh: Record<string, FactValue> = {}
|
|
644
654
|
for (const declared of relation.data.fields) {
|
|
645
|
-
if (!declared.field
|
|
655
|
+
if (!isFreshField(declared.field)) {
|
|
646
656
|
continue
|
|
647
657
|
}
|
|
648
658
|
let cell = values[declared.name]
|
|
@@ -1338,7 +1348,7 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1338
1348
|
Object.freeze({
|
|
1339
1349
|
handle: preparedHandle,
|
|
1340
1350
|
owner,
|
|
1341
|
-
params: q.data.
|
|
1351
|
+
params: q.data.params,
|
|
1342
1352
|
select: q.data.select
|
|
1343
1353
|
})
|
|
1344
1354
|
)
|
package/src/exhume.ts
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
* is read back for rebirth (exhume the old store, create the successor
|
|
9
9
|
* under the new theory, copy by NAME, re-derive).
|
|
10
10
|
*
|
|
11
|
-
* DELIBERATELY
|
|
12
|
-
* The caller's schema is the wrong theory for an exhumed store BY
|
|
11
|
+
* DELIBERATELY SCHEMA-FREE: no schema type appears anywhere on this
|
|
12
|
+
* surface. The caller's schema is the wrong theory for an exhumed store BY
|
|
13
13
|
* DEFINITION (a store the current theory could open would never need
|
|
14
|
-
* exhuming), so every value crosses
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the
|
|
14
|
+
* exhuming), so every value crosses TYPED at its bare structural form
|
|
15
|
+
* ({@link FactValue} — bigint/string/boolean/bytes/interval, never
|
|
16
|
+
* `unknown`) and every fact is keyed by field NAME. The SDK never
|
|
17
|
+
* reconstructs a `Schema` value from the descriptor — that inverse mapping
|
|
18
|
+
* is deliberately out of scope; the rebirth tool keys by name.
|
|
18
19
|
*
|
|
19
20
|
* ZERO CLOSABLES: no value here carries a close, dispose, or release
|
|
20
21
|
* spelling. The engine-side handle (and the store's exclusive advisory
|
|
@@ -57,9 +58,9 @@ const ErrExhumeFormatMismatch = errors.new(
|
|
|
57
58
|
const ErrExhumeCorruption = errors.new("bumbledb exhume: the persisted schema descriptor fails its integrity gates")
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
|
-
* One exhumed fact as a plain name-keyed record of
|
|
61
|
-
* deliberately
|
|
62
|
-
* str, `boolean` for bool, `Uint8Array` for bytes<N>, a `{ start, end }`
|
|
61
|
+
* One exhumed fact as a plain name-keyed record of bare structural values
|
|
62
|
+
* — deliberately schema-free (module doc): `bigint` for u64/i64, `string`
|
|
63
|
+
* for str, `boolean` for bool, `Uint8Array` for bytes<N>, a `{ start, end }`
|
|
63
64
|
* bigint pair for intervals. Closed-relation rows open with the synthetic
|
|
64
65
|
* `id` field, exactly as the descriptor's sealed field list declares.
|
|
65
66
|
*/
|
|
@@ -105,8 +106,8 @@ interface ExhumedRelation {
|
|
|
105
106
|
* persisted descriptor: relations in engine-id order (declaration order
|
|
106
107
|
* mints every id), each with its ordered field descriptions and closed
|
|
107
108
|
* roster — enough for a caller to key facts by name and re-insert them
|
|
108
|
-
* into a differently-fingerprinted successor store. No
|
|
109
|
-
*
|
|
109
|
+
* into a differently-fingerprinted successor store. No schema type appears
|
|
110
|
+
* here (module doc: the surface is schema-free; rows are typed bare).
|
|
110
111
|
*/
|
|
111
112
|
interface ExhumedDescriptor {
|
|
112
113
|
readonly relations: readonly ExhumedRelation[]
|
package/src/face.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Faces — the projection-with-selection value both containments and
|
|
3
|
-
* windows consume: `on(Account, "holder")
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* windows consume: `on(Account, "holder")` the common single-field
|
|
4
|
+
* position, `on(Booking, ["room", "during"])` the composite/pointwise
|
|
5
|
+
* position (one spelling, arity-generic), `on(Account.where({ kind:
|
|
6
|
+
* Kind.Savings }), "id")` the σ-carrying source, `on(Kind, "id")` a closed
|
|
7
|
+
* relation's sealed shape opened through its synthetic `id`. Projection is
|
|
8
|
+
* positional: tuple order is preserved in the type, and the statement
|
|
9
|
+
* constructors pair the two sides' tuples by arity ({@link SameArity}) AND
|
|
10
|
+
* by domain ({@link SameDomains}) — the domain wall of the structural
|
|
11
|
+
* design: every projected field's domain LABEL is read off the schema type
|
|
12
|
+
* (`F["domain"]`, the S1 kernel) and compared positionwise by
|
|
13
|
+
* string-literal equality, never by any value brand.
|
|
8
14
|
*/
|
|
9
15
|
|
|
10
|
-
import type { AnyClosed } from "#closed.ts"
|
|
11
|
-
import type { AnyRelation, AnySelected, SelectionBinding } from "#relation.ts"
|
|
16
|
+
import type { AnyClosed, PayloadField } from "#closed.ts"
|
|
17
|
+
import type { AnyRelation, AnySelected, FieldsShape, RelationFields, SelectionBinding } from "#relation.ts"
|
|
12
18
|
import { renderLiteralSet } from "#spec.ts"
|
|
13
19
|
|
|
14
20
|
/** The empty σ of a selection-free face, shared by every bare projection. */
|
|
@@ -62,16 +68,20 @@ interface FaceData {
|
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/**
|
|
65
|
-
* A face value. `
|
|
66
|
-
*
|
|
71
|
+
* A face value. `S` is the source exactly as written (the relation,
|
|
72
|
+
* selected relation, or closed relation `on()` was handed — the statement
|
|
73
|
+
* constructors resolve each projected field's DOMAIN through it), and `P`
|
|
74
|
+
* is the projection tuple as written (its length is the positional-pairing
|
|
75
|
+
* arity). Both are honest runtime properties, not phantoms.
|
|
67
76
|
*/
|
|
68
|
-
interface Face<P extends readonly string[]> {
|
|
77
|
+
interface Face<S extends FaceSource, P extends readonly string[]> {
|
|
78
|
+
readonly source: S
|
|
69
79
|
readonly projection: P
|
|
70
80
|
readonly data: FaceData
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
/** Any face value, whatever its projection. */
|
|
74
|
-
type AnyFace = Face<readonly string[]>
|
|
83
|
+
/** Any face value, whatever its source and projection. */
|
|
84
|
+
type AnyFace = Face<FaceSource, readonly string[]>
|
|
75
85
|
|
|
76
86
|
/** What `on()` accepts: a relation, a selected relation, or a closed relation. */
|
|
77
87
|
type FaceSource = AnyRelation | AnyClosed | AnySelected
|
|
@@ -91,6 +101,42 @@ type FaceFields<S extends FaceSource> = S extends AnySelected
|
|
|
91
101
|
? "id" | (keyof Row & string)
|
|
92
102
|
: never
|
|
93
103
|
|
|
104
|
+
/** One field's domain label within a declared field block (`undefined` when the name is foreign). */
|
|
105
|
+
type DomainIn<Fields extends FieldsShape, K extends string> = K extends keyof Fields ? Fields[K]["domain"] : undefined
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The domain LABEL of one projected field, read structurally off the
|
|
109
|
+
* source's schema type — an ordinary or selected relation's field carries
|
|
110
|
+
* its S1 descriptor's `domain`; a closed relation's synthetic `id` carries
|
|
111
|
+
* the handle domain (`"KindId"`), and its payload columns carry their own
|
|
112
|
+
* declared descriptors' labels through the closed value's typed `columns`
|
|
113
|
+
* carrier (whose runtime twin is the frozen `columns` record the mint
|
|
114
|
+
* carries), so a same-label closed-payload ↔ relation-field pairing
|
|
115
|
+
* compiles and a mismatched one refuses — the identical wall every other
|
|
116
|
+
* face position gets. The lowering carries the same label to the engine,
|
|
117
|
+
* which stays the final authority.
|
|
118
|
+
*/
|
|
119
|
+
type ProjectedDomain<S extends FaceSource, K extends string> = S extends AnySelected
|
|
120
|
+
? DomainIn<RelationFields<S["relation"]>, K>
|
|
121
|
+
: S extends AnyRelation
|
|
122
|
+
? DomainIn<RelationFields<S>, K>
|
|
123
|
+
: S extends {
|
|
124
|
+
readonly id: { readonly domain: infer D extends string }
|
|
125
|
+
readonly columns: infer Cols extends Record<string, PayloadField>
|
|
126
|
+
}
|
|
127
|
+
? K extends "id"
|
|
128
|
+
? D
|
|
129
|
+
: DomainIn<Cols, K>
|
|
130
|
+
: undefined
|
|
131
|
+
|
|
132
|
+
/** The positionwise domain-label tuple of a projection over `S`. */
|
|
133
|
+
type DomainsOf<S extends FaceSource, P extends readonly string[]> = {
|
|
134
|
+
readonly [I in keyof P]: ProjectedDomain<S, P[I] & string>
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The domain-label tuple a face projects, positionwise — the comparand of {@link SameDomains}. */
|
|
138
|
+
type FaceDomains<F extends AnyFace> = F extends Face<infer S, infer P> ? DomainsOf<S, P> : never
|
|
139
|
+
|
|
94
140
|
/** The projection arity of a face. */
|
|
95
141
|
type Arity<F extends AnyFace> = F["projection"]["length"]
|
|
96
142
|
|
|
@@ -119,24 +165,56 @@ type SameArity<A extends AnyFace, B extends AnyFace> =
|
|
|
119
165
|
: FaceArityMismatch<Arity<A>, Arity<B>>
|
|
120
166
|
|
|
121
167
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
168
|
+
* The legible domain-mismatch verdict: when the two faces of a containment,
|
|
169
|
+
* bijection, or window project different domain labels at any position,
|
|
170
|
+
* this type is intersected into the second face's parameter and names both
|
|
171
|
+
* label tuples — a cross-domain pair is a COMPILE error, achieved by
|
|
172
|
+
* string-literal comparison of descriptor shapes (the structural design's
|
|
173
|
+
* ratified check), never by a value brand.
|
|
127
174
|
*/
|
|
175
|
+
interface FaceDomainMismatch<Left, Right> {
|
|
176
|
+
readonly "face domain mismatch — positionwise domain labels must be equal on both sides": readonly [Left, Right]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Resolves to `unknown` (a no-op intersection) when the two faces project
|
|
181
|
+
* positionwise-equal domain labels, and to {@link FaceDomainMismatch}
|
|
182
|
+
* otherwise. Equality is mutual tuple assignability over string-literal
|
|
183
|
+
* labels (`undefined` pairs only with `undefined` — an unlabeled field
|
|
184
|
+
* links only unlabeled fields, mirroring Rust where `u64` and `u64 as
|
|
185
|
+
* HolderId` are different host types).
|
|
186
|
+
*/
|
|
187
|
+
type SameDomains<A extends AnyFace, B extends AnyFace> =
|
|
188
|
+
FaceDomains<A> extends FaceDomains<B>
|
|
189
|
+
? FaceDomains<B> extends FaceDomains<A>
|
|
190
|
+
? unknown
|
|
191
|
+
: FaceDomainMismatch<FaceDomains<A>, FaceDomains<B>>
|
|
192
|
+
: FaceDomainMismatch<FaceDomains<A>, FaceDomains<B>>
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Projects a face — one spelling, arity-generic: `on(Account, "holder")`
|
|
196
|
+
* for the common single-field position, `on(Booking, ["room", "during"])`
|
|
197
|
+
* for the composite/pointwise position (the interval-pointwise `==` and
|
|
198
|
+
* coverage recipes), `on(Account.where({...}), "id")` for a σ-carrying
|
|
199
|
+
* source. Field names are typechecked against the source (unknown field =
|
|
200
|
+
* type error, names autocomplete); tuple order is preserved (positional
|
|
201
|
+
* pairing with the other side, macro parity). The empty projection is
|
|
202
|
+
* unwritable by signature — it has no meaning in the statement grammar.
|
|
203
|
+
*/
|
|
204
|
+
function on<S extends FaceSource, const F extends FaceFields<S>>(source: S, field: F): Face<S, readonly [F]>
|
|
128
205
|
function on<S extends FaceSource, const P extends readonly [FaceFields<S>, ...FaceFields<S>[]]>(
|
|
129
206
|
source: S,
|
|
130
|
-
|
|
131
|
-
): Face<P>
|
|
207
|
+
fields: P
|
|
208
|
+
): Face<S, P>
|
|
209
|
+
function on(source: FaceSource, fields: string | readonly string[]): Face<FaceSource, readonly string[]> {
|
|
210
|
+
const projection: readonly string[] = Object.freeze(typeof fields === "string" ? [fields] : [...fields])
|
|
132
211
|
const parts = faceParts(source)
|
|
133
|
-
Object.freeze(projection)
|
|
134
212
|
const data: FaceData = Object.freeze({
|
|
135
213
|
owner: parts.owner,
|
|
136
214
|
projection,
|
|
137
215
|
selection: parts.selection
|
|
138
216
|
})
|
|
139
|
-
return Object.freeze({ projection, data })
|
|
217
|
+
return Object.freeze({ source, projection, data })
|
|
140
218
|
}
|
|
141
219
|
|
|
142
220
|
/**
|
|
@@ -157,5 +235,19 @@ function renderFace(face: FaceData): string {
|
|
|
157
235
|
return `${face.owner.name}(${projection} | ${bindings})`
|
|
158
236
|
}
|
|
159
237
|
|
|
160
|
-
export type {
|
|
238
|
+
export type {
|
|
239
|
+
AnyFace,
|
|
240
|
+
Arity,
|
|
241
|
+
Face,
|
|
242
|
+
FaceArityMismatch,
|
|
243
|
+
FaceData,
|
|
244
|
+
FaceDomainMismatch,
|
|
245
|
+
FaceDomains,
|
|
246
|
+
FaceFields,
|
|
247
|
+
FaceOwner,
|
|
248
|
+
FaceSource,
|
|
249
|
+
OneOf,
|
|
250
|
+
SameArity,
|
|
251
|
+
SameDomains
|
|
252
|
+
}
|
|
161
253
|
export { on, oneOf, renderFace }
|