@bjornpagen/bumbledb 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +503 -427
- package/README.md +84 -36
- package/dist/closed.d.ts +111 -38
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +94 -99
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +16 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +46 -8
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +114 -69
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +38 -21
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +72 -87
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +35 -67
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +18 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -7
- 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/marshal.d.ts +33 -6
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +67 -6
- package/dist/marshal.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 +139 -56
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +7 -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 +341 -69
- 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/run.d.ts +15 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +26 -6
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +100 -43
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +89 -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 +29 -15
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +45 -17
- 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 +10 -8
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +67 -31
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +97 -20
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/closed.ts +214 -146
- package/src/db.ts +65 -10
- package/src/face.ts +169 -102
- package/src/fields.ts +97 -164
- package/src/index.ts +43 -18
- package/src/law.ts +519 -0
- package/src/lower.ts +36 -23
- package/src/marshal.ts +74 -7
- package/src/native.ts +6 -2
- package/src/query/atom.ts +155 -65
- package/src/query/lower.ts +572 -167
- package/src/query/predicate.ts +43 -33
- package/src/query/run.ts +26 -6
- package/src/query/scope.ts +161 -51
- package/src/query/select.ts +5 -5
- package/src/relation.ts +60 -26
- package/src/schema.ts +48 -7
- package/src/spec.ts +10 -8
- package/src/statements.ts +165 -46
package/src/relation.ts
CHANGED
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import * as errors from "@superbuilders/errors"
|
|
15
|
-
import type { OneOf } from "#face.ts"
|
|
16
15
|
import { type AnyField, assertDeclarationOrderKey, type Infer, literalOf } from "#fields.ts"
|
|
17
|
-
import type
|
|
16
|
+
import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
|
|
18
17
|
|
|
19
18
|
/** Flattens an intersection into one displayed object type (hover legibility). */
|
|
20
19
|
type Flatten<T> = { [K in keyof T]: T[K] }
|
|
@@ -37,16 +36,41 @@ function refsComplete<RName extends string, Fields extends FieldsShape>(
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
|
-
* Resolves one selection entry to its lowered literal set:
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* the
|
|
39
|
+
* Resolves one selection entry to its lowered literal set: a plain ARRAY
|
|
40
|
+
* (detected by `Array.isArray` — no field's value type is an array;
|
|
41
|
+
* `Uint8Array` is not one) becomes a disjunctive set, anything else the
|
|
42
|
+
* bare literal. The degenerate sets are construction errors, each
|
|
43
|
+
* self-locating (`context` names the relation and field) — the empty set
|
|
44
|
+
* selects nothing, the one-element set is the bare literal respelled, and
|
|
45
|
+
* a DUPLICATE literal (judged on the canonical rendering — the engine's
|
|
46
|
+
* own duplicate test, reached here first so its index-speak twin at
|
|
47
|
+
* `Db.create` stays unreachable from this surface) is the same respelling
|
|
48
|
+
* in disguise (the canonical-utterance law; the old set combinator's
|
|
49
|
+
* signature made the length degenerates unwritable, and the refusals here
|
|
50
|
+
* are that law's runtime seat). The lowered set — `{ kind: "many",
|
|
51
|
+
* literals }` — is byte-identical to what the combinator produced, so no
|
|
52
|
+
* fingerprint moves.
|
|
45
53
|
*/
|
|
46
|
-
function resolveEntry(field: AnyField, entry: unknown): LiteralSetSpec {
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
function resolveEntry(context: string, field: AnyField, entry: unknown): LiteralSetSpec {
|
|
55
|
+
if (Array.isArray(entry)) {
|
|
56
|
+
if (entry.length < 2) {
|
|
57
|
+
throw errors.new(
|
|
58
|
+
entry.length === 0
|
|
59
|
+
? `${context}: an empty literal set selects nothing — write the selection you mean`
|
|
60
|
+
: `${context}: a one-element literal set is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
const seen = new Set<string>()
|
|
64
|
+
const literals: LiteralSpec[] = entry.map(function lowerSetLiteral(literal: unknown) {
|
|
65
|
+
const lowered = Object.freeze(literalOf(field, literal))
|
|
66
|
+
const rendered = renderLiteral(lowered)
|
|
67
|
+
if (seen.has(rendered)) {
|
|
68
|
+
throw errors.new(
|
|
69
|
+
`${context}: the literal set spells ${rendered} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
seen.add(rendered)
|
|
73
|
+
return lowered
|
|
50
74
|
})
|
|
51
75
|
return Object.freeze({ kind: "many", literals: Object.freeze(literals) })
|
|
52
76
|
}
|
|
@@ -57,7 +81,10 @@ function resolveEntry(field: AnyField, entry: unknown): LiteralSetSpec {
|
|
|
57
81
|
* Resolves a whole `where()` selection against the declared fields, in the
|
|
58
82
|
* selection's written order (macro parity: σ is spelled, not sorted). An
|
|
59
83
|
* empty selection is the bare relation respelled and rejected (the
|
|
60
|
-
* canonical-utterance law).
|
|
84
|
+
* canonical-utterance law). THE one selection resolver — `closed()`'s
|
|
85
|
+
* `where()` resolves its payload columns through this same machine (a
|
|
86
|
+
* `ClosedColumn` is structurally a {@link RelationField}), so both surfaces
|
|
87
|
+
* share one vocabulary and one error voice.
|
|
61
88
|
*/
|
|
62
89
|
function resolveSelection(
|
|
63
90
|
name: string,
|
|
@@ -75,7 +102,9 @@ function resolveSelection(
|
|
|
75
102
|
if (declared === undefined) {
|
|
76
103
|
throw errors.new(`relation ${name} has no field ${fieldName}`)
|
|
77
104
|
}
|
|
78
|
-
bindings.push(
|
|
105
|
+
bindings.push(
|
|
106
|
+
Object.freeze({ field: fieldName, set: resolveEntry(`relation ${name}.${fieldName}`, declared.field, entry) })
|
|
107
|
+
)
|
|
79
108
|
}
|
|
80
109
|
if (bindings.length === 0) {
|
|
81
110
|
throw errors.new(
|
|
@@ -91,8 +120,8 @@ type FieldsShape = Record<string, AnyField>
|
|
|
91
120
|
/**
|
|
92
121
|
* A typed field reference (`Account.fields.holder`) — the value statements,
|
|
93
122
|
* selections, and queries address a field through. Purely positional
|
|
94
|
-
* (relation name + field name); the field's descriptor
|
|
95
|
-
*
|
|
123
|
+
* (relation name + field name); the field's descriptor is read off the
|
|
124
|
+
* relation's schema type structurally.
|
|
96
125
|
*/
|
|
97
126
|
interface FieldRef<Rel extends string, Name extends string> {
|
|
98
127
|
readonly relation: Rel
|
|
@@ -128,13 +157,15 @@ interface SelectionBinding {
|
|
|
128
157
|
|
|
129
158
|
/**
|
|
130
159
|
* The `where()` argument: per field, a bare structural literal of that
|
|
131
|
-
* field's value type (a closed
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
160
|
+
* field's value type (a closed reference's literal IS its handle name —
|
|
161
|
+
* `"Savings"`, verified against the roster at construction), a plain
|
|
162
|
+
* ARRAY of such literals read disjunctively — `kind: ["Checking",
|
|
163
|
+
* "Savings"]` — or a `span(start, end)` interval literal. Membership is
|
|
164
|
+
* an array, never an operator (the drizzle law); equality-only by
|
|
165
|
+
* construction: no operator parameter exists anywhere.
|
|
135
166
|
*/
|
|
136
167
|
type SelectionInput<Fields extends FieldsShape> = {
|
|
137
|
-
readonly [K in keyof Fields]?: Infer<Fields[K]> |
|
|
168
|
+
readonly [K in keyof Fields]?: Infer<Fields[K]> | readonly Infer<Fields[K]>[]
|
|
138
169
|
}
|
|
139
170
|
|
|
140
171
|
/** A relation with a selection applied — what `on()` consumes as a σ-carrying source. */
|
|
@@ -186,11 +217,14 @@ type FreshKeys<R extends AnyRelation> = {
|
|
|
186
217
|
type InsertFact<R extends AnyRelation> = Flatten<Omit<Fact<R>, FreshKeys<R>> & Partial<Pick<Fact<R>, FreshKeys<R>>>>
|
|
187
218
|
|
|
188
219
|
/**
|
|
189
|
-
* Declares one relation: `relation("Account", { id:
|
|
190
|
-
* holder:
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
220
|
+
* Declares one relation: `relation("Account", { id: u64.fresh,
|
|
221
|
+
* holder: u64, kind: Kind.id, ... })` — every field is a pure-structure
|
|
222
|
+
* descriptor (the constructor values themselves; domains are never
|
|
223
|
+
* declared: `schema()` computes them from the statements). Field
|
|
224
|
+
* declaration order is ordinal-id order (macro parity), carried at BOTH
|
|
225
|
+
* levels: the type level by the fields object, the value level by the
|
|
226
|
+
* frozen `data.fields` list — the law the schema-level class naming leans
|
|
227
|
+
* on. The returned value is frozen and side-effect free.
|
|
194
228
|
*/
|
|
195
229
|
function relation<const Name extends string, Fields extends FieldsShape>(
|
|
196
230
|
name: Name,
|
|
@@ -243,4 +277,4 @@ export type {
|
|
|
243
277
|
SelectionBinding,
|
|
244
278
|
SelectionInput
|
|
245
279
|
}
|
|
246
|
-
export { relation }
|
|
280
|
+
export { relation, resolveSelection }
|
package/src/schema.ts
CHANGED
|
@@ -13,6 +13,7 @@ import * as errors from "@superbuilders/errors"
|
|
|
13
13
|
import type { AnyClosed } from "#closed.ts"
|
|
14
14
|
import type { FaceData } from "#face.ts"
|
|
15
15
|
import { type AnyField, assertDeclarationOrderKey, type ClosedRoster } from "#fields.ts"
|
|
16
|
+
import { type ClassesOf, classesComplete, computeClasses, type LawfulStatements, type SchemaClasses } from "#law.ts"
|
|
16
17
|
import type { AnyRelation } from "#relation.ts"
|
|
17
18
|
import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
|
|
18
19
|
import { renderStatement, type Statement } from "#statements.ts"
|
|
@@ -128,7 +129,7 @@ function verifyBindingHandles(
|
|
|
128
129
|
}
|
|
129
130
|
if (roster === undefined) {
|
|
130
131
|
throw errors.new(
|
|
131
|
-
`schema ${name}: ${face.owner.name}.${binding.field} is not a closed-relation reference — the handle literal ${literal.handle} is legal only on a field
|
|
132
|
+
`schema ${name}: ${face.owner.name}.${binding.field} is not a closed-relation reference — the handle literal ${literal.handle} is legal only on a field carrying a closed relation's roster — ${rendered}`
|
|
132
133
|
)
|
|
133
134
|
}
|
|
134
135
|
if (!roster.handles.includes(literal.handle)) {
|
|
@@ -247,16 +248,43 @@ type SchemaRelation = AnyRelation | AnyClosed
|
|
|
247
248
|
/** The relation record a schema is generic over — what `Db` and queries key on. */
|
|
248
249
|
type SchemaRelations = Record<string, SchemaRelation>
|
|
249
250
|
|
|
250
|
-
/**
|
|
251
|
-
|
|
251
|
+
/**
|
|
252
|
+
* A theory value: named relations, the DECLARED dependency statements, and
|
|
253
|
+
* the LAW-COMPUTED class map (`classes` — relation → field → class name,
|
|
254
|
+
* `undefined` = bare). The class map is THE domain authority: `schema()`
|
|
255
|
+
* computes it FROM the statement list at both tiers (the type through
|
|
256
|
+
* {@link ClassesOf}, the value through the union-find twin), queries
|
|
257
|
+
* compare class names off it, and the wire lowering emits it as the spec
|
|
258
|
+
* `newtype` labels. Nothing is ever synthesized: `statements` is exactly
|
|
259
|
+
* the declared list, in written order.
|
|
260
|
+
*/
|
|
261
|
+
interface Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> {
|
|
252
262
|
readonly name: string
|
|
253
263
|
readonly relations: Rels
|
|
254
264
|
readonly statements: readonly Statement[]
|
|
265
|
+
readonly classes: Classes
|
|
255
266
|
}
|
|
256
267
|
|
|
257
268
|
/** Any schema value, whatever its relation record. */
|
|
258
269
|
type AnySchema = Schema<SchemaRelations>
|
|
259
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Forces the class map to EVALUATE at the `schema()` boundary: a two-level
|
|
273
|
+
* mapped copy, type-identical to `C` — but instantiation resolves it to
|
|
274
|
+
* the finished relation → field → class record, so hovering a schema value
|
|
275
|
+
* (or anything carrying its `Classes` parameter — queries, `Db`) shows the
|
|
276
|
+
* computed record instead of the unevaluated `ClassesOf<...>` application
|
|
277
|
+
* dragging the whole statement-tuple type along. The conditional wrapper
|
|
278
|
+
* is the display mechanism, not a judgment: resolving it drops the alias
|
|
279
|
+
* reference, so tsc renders the finished record (measured against tsc's
|
|
280
|
+
* own type rendering; a bare mapped alias still displays by name).
|
|
281
|
+
* Display-only by construction; `classesComplete` guards the same type at
|
|
282
|
+
* the value tier.
|
|
283
|
+
*/
|
|
284
|
+
type EvaluatedClasses<C extends SchemaClasses> = C extends SchemaClasses
|
|
285
|
+
? { readonly [N in keyof C]: { readonly [F in keyof C[N]]: C[N][F] } }
|
|
286
|
+
: never
|
|
287
|
+
|
|
260
288
|
/**
|
|
261
289
|
* Assembles a theory:
|
|
262
290
|
* `schema("Ledger", { Kind, Account, Holder }, [ ...statements ])`.
|
|
@@ -277,12 +305,21 @@ type AnySchema = Schema<SchemaRelations>
|
|
|
277
305
|
* list: the engine materializes them itself, in its own pinned order
|
|
278
306
|
* (`SchemaDescriptor::materialized_statements`), and restating them would
|
|
279
307
|
* double them.
|
|
308
|
+
*
|
|
309
|
+
* THE LAW-TYPING happens here too (rulings 2/3 — the laws type the
|
|
310
|
+
* columns): the statement list induces the equivalence classes over field
|
|
311
|
+
* slots, at the TYPE level ({@link ClassesOf} — spell the statement list
|
|
312
|
+
* inline so the tuple type stays precise) and at runtime (the union-find
|
|
313
|
+
* twin), and the one-generator-per-class wall holds at both tiers — the
|
|
314
|
+
* {@link LawfulStatements} verdict lands the compile error on the
|
|
315
|
+
* statements argument; `computeClasses` throws the same content naming the
|
|
316
|
+
* exact statement.
|
|
280
317
|
*/
|
|
281
|
-
function schema<const Rels extends SchemaRelations>(
|
|
318
|
+
function schema<const Rels extends SchemaRelations, const Stmts extends readonly Statement[]>(
|
|
282
319
|
name: string,
|
|
283
320
|
relations: Rels,
|
|
284
|
-
statements:
|
|
285
|
-
): Schema<Rels
|
|
321
|
+
statements: Stmts & LawfulStatements<Rels, Stmts>
|
|
322
|
+
): Schema<Rels, EvaluatedClasses<ClassesOf<Rels, Stmts>>> {
|
|
286
323
|
const implied = collectImplied(name, relations)
|
|
287
324
|
const seen = new Set<string>()
|
|
288
325
|
for (const statement of statements) {
|
|
@@ -300,7 +337,11 @@ function schema<const Rels extends SchemaRelations>(
|
|
|
300
337
|
verifyHandles(name, statement, rendered)
|
|
301
338
|
}
|
|
302
339
|
verifyClosedReferences(name, statements)
|
|
303
|
-
|
|
340
|
+
const classes = computeClasses(name, relations, statements)
|
|
341
|
+
if (!classesComplete<EvaluatedClasses<ClassesOf<Rels, Stmts>>>(classes, relations)) {
|
|
342
|
+
throw errors.new(`schema ${name}: class-map construction incomplete`)
|
|
343
|
+
}
|
|
344
|
+
return Object.freeze({ name, relations, statements: Object.freeze([...statements]), classes })
|
|
304
345
|
}
|
|
305
346
|
|
|
306
347
|
export type { AnySchema, Schema, SchemaRelation, SchemaRelations }
|
package/src/spec.ts
CHANGED
|
@@ -62,8 +62,9 @@ type LiteralSpec =
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* One σ binding's right side: a single literal or a literal set (read
|
|
65
|
-
* disjunctively). The SDK's selection
|
|
66
|
-
*
|
|
65
|
+
* disjunctively). The SDK's selection resolver refuses the degenerate sets
|
|
66
|
+
* (a membership array needs two members — the empty set selects nothing,
|
|
67
|
+
* the one-element set is the bare literal respelled), so a lowered `many`
|
|
67
68
|
* always carries ≥ 2 literals.
|
|
68
69
|
*/
|
|
69
70
|
type LiteralSetSpec =
|
|
@@ -95,10 +96,10 @@ type WindowSpec =
|
|
|
95
96
|
| { readonly kind: "floor"; readonly lo: bigint }
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
|
-
* One field: name, structural type, host newtype name — the
|
|
99
|
-
* DOMAIN
|
|
100
|
-
* carried for handle resolution only, dropped by the engine at
|
|
101
|
-
* lowering and never fingerprinted — and the `fresh` mint mark.
|
|
99
|
+
* One field: name, structural type, host newtype name — the field's
|
|
100
|
+
* DOMAIN (the macro's declared `as NewType`; the SDK's law-computed class
|
|
101
|
+
* name), carried for handle resolution only, dropped by the engine at
|
|
102
|
+
* descriptor lowering and never fingerprinted — and the `fresh` mint mark.
|
|
102
103
|
*/
|
|
103
104
|
interface FieldSpec {
|
|
104
105
|
readonly name: string
|
|
@@ -121,8 +122,9 @@ interface RowSpec {
|
|
|
121
122
|
* kind); a closed relation's `fields` are its declared intrinsic columns
|
|
122
123
|
* only — the synthetic (`id`, u64) handle field is materialized by the
|
|
123
124
|
* engine's schema validation. `newtype` is the handle newtype of a closed
|
|
124
|
-
* relation (the SDK emits
|
|
125
|
-
* label every referencing field carries
|
|
125
|
+
* relation (the SDK emits the id's law-computed generator class,
|
|
126
|
+
* `` `${name}.id` `` — the same label every referencing field carries by
|
|
127
|
+
* law), undefined on an ordinary one.
|
|
126
128
|
*/
|
|
127
129
|
interface RelationSpec {
|
|
128
130
|
readonly name: string
|
package/src/statements.ts
CHANGED
|
@@ -7,12 +7,22 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Every field reference is checked against the relation it names in the
|
|
9
9
|
* TYPE — existence through {@link FaceFields} (`on(R, "nope")` does not
|
|
10
|
-
* compile) and
|
|
11
|
-
* faces' projected
|
|
12
|
-
* (
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* compile) and STRUCTURAL compatibility through {@link SameShapes}: the two
|
|
11
|
+
* faces' projected kind/width/element/roster quadruples are read off the
|
|
12
|
+
* schema type (the minimal kernel — descriptors are pure structure) and
|
|
13
|
+
* constrained positionwise equal, so a u64 face against a str face, a
|
|
14
|
+
* bytes width mismatch, an interval element mismatch, or a bare column
|
|
15
|
+
* against a closed reference is a compile error. The ROSTER slot carries a
|
|
16
|
+
* construction-time runtime twin here ({@link assertRosterAgreement} —
|
|
17
|
+
* roster IDENTITY, positionwise: a closed vocabulary's referencing column
|
|
18
|
+
* is spelled with the vocabulary's own id descriptor, the ONE spelling, so
|
|
19
|
+
* a plain u64 column can never alias a vocabulary through a declared law
|
|
20
|
+
* and the SDK's descriptor-keyed closed judgments stay sound). Domains are
|
|
21
|
+
* NOT compared here — there is no domain to compare at construction: the
|
|
22
|
+
* statements themselves are what define the equivalence classes, and the
|
|
23
|
+
* domain wall lives where they aggregate — `schema()` (the
|
|
24
|
+
* one-generator-per-class law) and query joins (class names off the schema
|
|
25
|
+
* type). What is only a SEMANTIC property — the target side of a
|
|
16
26
|
* containment resolving a declared key of its relation — is DELIBERATELY
|
|
17
27
|
* not (and cannot be) stated here: whether `B(y)` is a key of `B` depends
|
|
18
28
|
* on which `key()` statements the surrounding `schema()` collects, a set no
|
|
@@ -23,7 +33,8 @@
|
|
|
23
33
|
|
|
24
34
|
import * as errors from "@superbuilders/errors"
|
|
25
35
|
import type { Count } from "#count.ts"
|
|
26
|
-
import { type AnyFace, type FaceData, renderFace, type SameArity, type
|
|
36
|
+
import { type AnyFace, type FaceData, renderFace, type SameArity, type SameShapes } from "#face.ts"
|
|
37
|
+
import type { AnyField, ClosedRoster } from "#fields.ts"
|
|
27
38
|
import type { AnyRelation, RelationFields } from "#relation.ts"
|
|
28
39
|
import { renderWindow, type WindowSpec } from "#spec.ts"
|
|
29
40
|
|
|
@@ -34,40 +45,132 @@ interface KeyData<R extends AnyRelation, Projection extends readonly string[]> {
|
|
|
34
45
|
readonly projection: Projection
|
|
35
46
|
}
|
|
36
47
|
|
|
48
|
+
/**
|
|
49
|
+
* A containment statement's runtime description — the two faces carried at
|
|
50
|
+
* their EXACT types (owner names and projection tuples are honest runtime
|
|
51
|
+
* properties, and they are the type-level carrier `schema()`'s law-typing
|
|
52
|
+
* pairs slots through). The defaults are the wide shape renderers and the
|
|
53
|
+
* wire lowering consume.
|
|
54
|
+
*/
|
|
55
|
+
interface ContainmentData<Src extends FaceData = FaceData, Tgt extends FaceData = FaceData> {
|
|
56
|
+
readonly kind: "containment"
|
|
57
|
+
readonly source: Src
|
|
58
|
+
readonly target: Tgt
|
|
59
|
+
readonly bidirectional: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A window statement's runtime description — target-left, faces at exact types like {@link ContainmentData}. */
|
|
63
|
+
interface WindowData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
|
|
64
|
+
readonly kind: "window"
|
|
65
|
+
readonly target: Tgt
|
|
66
|
+
readonly window: WindowSpec
|
|
67
|
+
readonly source: Src
|
|
68
|
+
}
|
|
69
|
+
|
|
37
70
|
/** One statement's runtime description, tagged by form. */
|
|
38
|
-
type StatementData =
|
|
39
|
-
| KeyData<AnyRelation, readonly string[]>
|
|
40
|
-
| {
|
|
41
|
-
readonly kind: "containment"
|
|
42
|
-
readonly source: FaceData
|
|
43
|
-
readonly target: FaceData
|
|
44
|
-
readonly bidirectional: boolean
|
|
45
|
-
}
|
|
46
|
-
| {
|
|
47
|
-
readonly kind: "window"
|
|
48
|
-
readonly target: FaceData
|
|
49
|
-
readonly window: WindowSpec
|
|
50
|
-
readonly source: FaceData
|
|
51
|
-
}
|
|
71
|
+
type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | WindowData
|
|
52
72
|
|
|
53
73
|
/** An opaque statement value — what `schema()` assembles into a theory. */
|
|
54
74
|
interface Statement {
|
|
55
75
|
readonly data: StatementData
|
|
56
76
|
}
|
|
57
77
|
|
|
78
|
+
/**
|
|
79
|
+
* A containment (or `==` bijection) statement as a TYPED value: `data`
|
|
80
|
+
* carries both faces at their exact types, so the schema-level class laws
|
|
81
|
+
* can read every paired (relation, field) slot off the statement type —
|
|
82
|
+
* spell the statement list inline in `schema()` and the equivalence
|
|
83
|
+
* classes compute at the type level too. Structurally still a plain
|
|
84
|
+
* {@link Statement}.
|
|
85
|
+
*/
|
|
86
|
+
interface ContainedStatement<Src extends FaceData, Tgt extends FaceData> extends Statement {
|
|
87
|
+
readonly data: ContainmentData<Src, Tgt>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A window statement as a TYPED value — the {@link ContainedStatement} of the window form. */
|
|
91
|
+
interface WindowStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
|
|
92
|
+
readonly data: WindowData<Tgt, Src>
|
|
93
|
+
}
|
|
94
|
+
|
|
58
95
|
/**
|
|
59
96
|
* A `key()` statement as a TYPED value: its `data` carries the owner
|
|
60
97
|
* relation and the projection tuple at their EXACT types (honest runtime
|
|
61
98
|
* properties — no phantom), which is what the key-statement-selected
|
|
62
99
|
* `get(relation, keyStatement, key)` overload types its key object by
|
|
63
100
|
* (`docs/architecture/70-api.md` § the freeze, the multi-key typed get) and
|
|
64
|
-
* what resolves each projected field's
|
|
101
|
+
* what resolves each projected field's descriptor through the owner's
|
|
65
102
|
* schema type. Structurally still a plain {@link Statement}.
|
|
66
103
|
*/
|
|
67
104
|
interface KeyStatement<R extends AnyRelation, Projection extends readonly string[]> extends Statement {
|
|
68
105
|
readonly data: KeyData<R, Projection>
|
|
69
106
|
}
|
|
70
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The field descriptor one face position projects: an ordinary relation's
|
|
110
|
+
* declared field; a closed relation's SEALED shape — the synthetic `id`
|
|
111
|
+
* (the value's own roster-carrying descriptor, by identity) or a declared
|
|
112
|
+
* payload column. `undefined` when the name is foreign (the type tier makes
|
|
113
|
+
* that unwritable; the engine re-judges projections at `Db.create`).
|
|
114
|
+
*/
|
|
115
|
+
function projectedFieldOf(face: FaceData, fieldName: string): AnyField | undefined {
|
|
116
|
+
const owner = face.owner
|
|
117
|
+
if ("axioms" in owner) {
|
|
118
|
+
if (fieldName === "id") {
|
|
119
|
+
return owner.id
|
|
120
|
+
}
|
|
121
|
+
const column = owner.data.columns.find(function byName(candidate) {
|
|
122
|
+
return candidate.name === fieldName
|
|
123
|
+
})
|
|
124
|
+
return column?.field
|
|
125
|
+
}
|
|
126
|
+
const declared = owner.data.fields.find(function byName(candidate) {
|
|
127
|
+
return candidate.name === fieldName
|
|
128
|
+
})
|
|
129
|
+
return declared?.field
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The roster a descriptor carries: present exactly on a closed reference, absent on every other kind. */
|
|
133
|
+
function rosterOfField(field: AnyField | undefined): ClosedRoster | undefined {
|
|
134
|
+
if (field !== undefined && "closed" in field) {
|
|
135
|
+
return field.closed
|
|
136
|
+
}
|
|
137
|
+
return undefined
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Renders one face position's closedness for the roster-agreement diagnostics. */
|
|
141
|
+
function renderRosterSide(roster: ClosedRoster | undefined): string {
|
|
142
|
+
return roster === undefined ? "a bare column" : `a ${roster.name} reference`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The runtime twin of {@link SameShapes}'s roster slot: the two faces'
|
|
147
|
+
* projected descriptors must agree POSITIONWISE on closedness — the same
|
|
148
|
+
* roster (value identity — vocabulary identity is value identity, the
|
|
149
|
+
* SDK's membership rule everywhere) or none. Without this wall a plain u64
|
|
150
|
+
* column could alias a closed vocabulary through a declared containment
|
|
151
|
+
* (`docs/architecture/10-data-model.md` spells the ENGINE encoding that
|
|
152
|
+
* way), and every descriptor-keyed closed judgment — the orderable ban,
|
|
153
|
+
* the name↔id marshal, answer decode — would silently miss it. The
|
|
154
|
+
* vocabulary's own descriptor (`Kind.id`) is the ONE spelling of a closed
|
|
155
|
+
* reference at this surface (the canonical-utterance law); the engine
|
|
156
|
+
* cannot backstop this one — the wire carries plain u64s, no rosters.
|
|
157
|
+
*/
|
|
158
|
+
function assertRosterAgreement(source: FaceData, target: FaceData, statement: Statement): void {
|
|
159
|
+
source.projection.forEach(function agreeAt(fieldName, position) {
|
|
160
|
+
const targetField = target.projection[position]
|
|
161
|
+
if (targetField === undefined) {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
const sourceRoster = rosterOfField(projectedFieldOf(source, fieldName))
|
|
165
|
+
const targetRoster = rosterOfField(projectedFieldOf(target, targetField))
|
|
166
|
+
if (sourceRoster !== targetRoster) {
|
|
167
|
+
throw errors.new(
|
|
168
|
+
`${source.owner.name}.${fieldName} is ${renderRosterSide(sourceRoster)} but ${target.owner.name}.${targetField} is ${renderRosterSide(targetRoster)} — closedness rides the descriptor: a closed reference is spelled with the vocabulary's own id descriptor (one meaning, one spelling), so faces pair closed-with-closed through one roster or bare-with-bare, never across — ${renderStatement(statement)}`
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
71
174
|
/**
|
|
72
175
|
* `R(X) -> R` — the FD key form, composite keys as tuples. No selection
|
|
73
176
|
* parameter exists (the FD-with-selection shape is unrepresentable, as in
|
|
@@ -76,7 +179,7 @@ interface KeyStatement<R extends AnyRelation, Projection extends readonly string
|
|
|
76
179
|
* explicit one would only ever be a duplicate. Every projected name is
|
|
77
180
|
* checked against `R`'s field block in the type, and the tuple is carried
|
|
78
181
|
* in the returned value's type ({@link KeyStatement}) — keyed point reads
|
|
79
|
-
* through THIS statement are typed field-for-field,
|
|
182
|
+
* through THIS statement are typed field-for-field, descriptors resolvable
|
|
80
183
|
* through the owner's schema type.
|
|
81
184
|
*/
|
|
82
185
|
function key<
|
|
@@ -98,24 +201,26 @@ function key<
|
|
|
98
201
|
|
|
99
202
|
/**
|
|
100
203
|
* `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. Arity mismatch
|
|
101
|
-
* between the two faces is a type error ({@link SameArity}); a
|
|
102
|
-
* pair is a type error ({@link
|
|
103
|
-
* equality of the projected
|
|
104
|
-
* resolve a declared key of B — a SEMANTIC property of the whole
|
|
105
|
-
* set that no face type can state, DELIBERATELY judged by the
|
|
106
|
-
* `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
|
|
204
|
+
* between the two faces is a type error ({@link SameArity}); a structurally
|
|
205
|
+
* mismatched pair is a type error ({@link SameShapes} — positionwise
|
|
206
|
+
* equality of the projected kind/width/element triples). The target side
|
|
207
|
+
* must resolve a declared key of B — a SEMANTIC property of the whole
|
|
208
|
+
* statement set that no face type can state, DELIBERATELY judged by the
|
|
209
|
+
* engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
|
|
107
210
|
*/
|
|
108
211
|
function contained<A extends AnyFace, B extends AnyFace>(
|
|
109
212
|
source: A,
|
|
110
|
-
target: B & SameArity<A, B> &
|
|
111
|
-
):
|
|
112
|
-
const data:
|
|
213
|
+
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
214
|
+
): ContainedStatement<A["data"], B["data"]> {
|
|
215
|
+
const data: ContainmentData<A["data"], B["data"]> = Object.freeze({
|
|
113
216
|
kind: "containment",
|
|
114
217
|
source: source.data,
|
|
115
218
|
target: target.data,
|
|
116
219
|
bidirectional: false
|
|
117
220
|
})
|
|
118
|
-
|
|
221
|
+
const statement = Object.freeze({ data })
|
|
222
|
+
assertRosterAgreement(data.source, data.target, statement)
|
|
223
|
+
return statement
|
|
119
224
|
}
|
|
120
225
|
|
|
121
226
|
/**
|
|
@@ -124,20 +229,22 @@ function contained<A extends AnyFace, B extends AnyFace>(
|
|
|
124
229
|
* two faces (each side contains the other). It lowers to the two adjacent
|
|
125
230
|
* containments in the `A <= B` first order (macro parity — the engine
|
|
126
231
|
* performs the split, source-first) and renders as `==` once, in the
|
|
127
|
-
* written orientation. Faces pair by arity AND
|
|
128
|
-
* {@link contained}.
|
|
232
|
+
* written orientation. Faces pair by arity AND structural shape, exactly
|
|
233
|
+
* as {@link contained}.
|
|
129
234
|
*/
|
|
130
235
|
function mirrors<A extends AnyFace, B extends AnyFace>(
|
|
131
236
|
source: A,
|
|
132
|
-
target: B & SameArity<A, B> &
|
|
133
|
-
):
|
|
134
|
-
const data:
|
|
237
|
+
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
238
|
+
): ContainedStatement<A["data"], B["data"]> {
|
|
239
|
+
const data: ContainmentData<A["data"], B["data"]> = Object.freeze({
|
|
135
240
|
kind: "containment",
|
|
136
241
|
source: source.data,
|
|
137
242
|
target: target.data,
|
|
138
243
|
bidirectional: true
|
|
139
244
|
})
|
|
140
|
-
|
|
245
|
+
const statement = Object.freeze({ data })
|
|
246
|
+
assertRosterAgreement(data.source, data.target, statement)
|
|
247
|
+
return statement
|
|
141
248
|
}
|
|
142
249
|
|
|
143
250
|
/**
|
|
@@ -146,21 +253,24 @@ function mirrors<A extends AnyFace, B extends AnyFace>(
|
|
|
146
253
|
* target-left — macro parity), and the RIGHT face is the counted source.
|
|
147
254
|
* `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
|
|
148
255
|
* Holder id groups at most three Account rows by holder. The two faces
|
|
149
|
-
* pair by arity AND
|
|
150
|
-
* the grouping join reads the same positionwise field
|
|
256
|
+
* pair by arity AND structural shape ({@link SameShapes}), exactly as
|
|
257
|
+
* containment — the grouping join reads the same positionwise field
|
|
258
|
+
* pairing.
|
|
151
259
|
*/
|
|
152
260
|
function window<B extends AnyFace, A extends AnyFace>(
|
|
153
261
|
target: B,
|
|
154
262
|
count: Count,
|
|
155
|
-
source: A & SameArity<B, A> &
|
|
156
|
-
):
|
|
157
|
-
const data:
|
|
263
|
+
source: A & SameArity<B, A> & SameShapes<B, A>
|
|
264
|
+
): WindowStatement<B["data"], A["data"]> {
|
|
265
|
+
const data: WindowData<B["data"], A["data"]> = Object.freeze({
|
|
158
266
|
kind: "window",
|
|
159
267
|
target: target.data,
|
|
160
268
|
window: count.window,
|
|
161
269
|
source: source.data
|
|
162
270
|
})
|
|
163
|
-
|
|
271
|
+
const statement = Object.freeze({ data })
|
|
272
|
+
assertRosterAgreement(data.source, data.target, statement)
|
|
273
|
+
return statement
|
|
164
274
|
}
|
|
165
275
|
|
|
166
276
|
/**
|
|
@@ -187,5 +297,14 @@ function renderStatement(statement: Statement): string {
|
|
|
187
297
|
}
|
|
188
298
|
}
|
|
189
299
|
|
|
190
|
-
export type {
|
|
300
|
+
export type {
|
|
301
|
+
ContainedStatement,
|
|
302
|
+
ContainmentData,
|
|
303
|
+
KeyData,
|
|
304
|
+
KeyStatement,
|
|
305
|
+
Statement,
|
|
306
|
+
StatementData,
|
|
307
|
+
WindowData,
|
|
308
|
+
WindowStatement
|
|
309
|
+
}
|
|
191
310
|
export { contained, key, mirrors, renderStatement, window }
|