@bjornpagen/bumbledb 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +1450 -0
- package/README.md +69 -30
- package/dist/closed.d.ts +157 -38
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +232 -47
- 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 +27 -8
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +33 -10
- 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 +135 -30
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +44 -19
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +130 -130
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +60 -94
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +29 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -14
- 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 -7
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +66 -24
- 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.d.ts +6 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +2 -2
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +370 -174
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +75 -172
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +312 -109
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +906 -387
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +70 -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 +178 -127
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +111 -108
- 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 +48 -38
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +24 -19
- 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 +34 -9
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +7 -5
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +91 -36
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +42 -12
- package/dist/statements.js.map +1 -1
- package/package.json +4 -3
- package/src/closed.ts +448 -95
- package/src/count.ts +112 -18
- package/src/db.ts +45 -17
- package/src/exhume.ts +12 -11
- package/src/face.ts +211 -41
- package/src/fields.ts +199 -234
- package/src/index.ts +78 -61
- package/src/law.ts +519 -0
- package/src/lower.ts +82 -27
- package/src/marshal.ts +48 -30
- package/src/native.ts +8 -4
- package/src/query/atom.ts +525 -374
- package/src/query/lower.ts +1469 -538
- package/src/query/predicate.ts +208 -161
- package/src/query/run.ts +35 -25
- package/src/query/scope.ts +264 -218
- package/src/query/select.ts +168 -93
- package/src/relation.ts +47 -47
- package/src/schema.ts +69 -18
- package/src/spec.ts +7 -5
- package/src/statements.ts +124 -41
- 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/schema.ts
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
import * as errors from "@superbuilders/errors"
|
|
13
13
|
import type { AnyClosed } from "#closed.ts"
|
|
14
14
|
import type { FaceData } from "#face.ts"
|
|
15
|
-
import { assertDeclarationOrderKey, type
|
|
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"
|
|
@@ -38,7 +39,7 @@ function collectImplied(name: string, relations: SchemaRelations): Set<string> {
|
|
|
38
39
|
continue
|
|
39
40
|
}
|
|
40
41
|
for (const declared of member.data.fields) {
|
|
41
|
-
if (declared.field.
|
|
42
|
+
if ("fresh" in declared.field && declared.field.fresh === true) {
|
|
42
43
|
implied.add(`${member.name}(${declared.name}) -> ${member.name}`)
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -74,8 +75,8 @@ function verifyMembership(name: string, relations: SchemaRelations, statement: S
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/** Finds a face's field
|
|
78
|
-
function faceField(face: FaceData, fieldName: string):
|
|
78
|
+
/** Finds a face's field descriptor by name, across both relation kinds. */
|
|
79
|
+
function faceField(face: FaceData, fieldName: string): AnyField | undefined {
|
|
79
80
|
const data = face.owner.data
|
|
80
81
|
if ("handles" in data) {
|
|
81
82
|
const column = data.columns.find(function byName(candidate) {
|
|
@@ -89,6 +90,18 @@ function faceField(face: FaceData, fieldName: string): FieldData | undefined {
|
|
|
89
90
|
return declared?.field
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
/**
|
|
94
|
+
* The roster a field resolves handles through: present exactly on a closed
|
|
95
|
+
* reference descriptor (the structural `closed` property — S1's
|
|
96
|
+
* `ClosedIdField`), absent on every other field kind.
|
|
97
|
+
*/
|
|
98
|
+
function rosterOf(field: AnyField | undefined): ClosedRoster | undefined {
|
|
99
|
+
if (field !== undefined && "closed" in field) {
|
|
100
|
+
return field.closed
|
|
101
|
+
}
|
|
102
|
+
return undefined
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
/** Flattens one binding's literal set into its literals. */
|
|
93
106
|
function bindingLiterals(set: LiteralSetSpec): readonly LiteralSpec[] {
|
|
94
107
|
if (set.kind === "one") {
|
|
@@ -109,20 +122,18 @@ function verifyBindingHandles(
|
|
|
109
122
|
binding: { readonly field: string; readonly set: LiteralSetSpec },
|
|
110
123
|
rendered: string
|
|
111
124
|
): void {
|
|
112
|
-
const
|
|
125
|
+
const roster = rosterOf(faceField(face, binding.field))
|
|
113
126
|
for (const literal of bindingLiterals(binding.set)) {
|
|
114
127
|
if (literal.kind !== "handle") {
|
|
115
128
|
continue
|
|
116
129
|
}
|
|
117
|
-
if (
|
|
130
|
+
if (roster === undefined) {
|
|
118
131
|
throw errors.new(
|
|
119
|
-
`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}`
|
|
120
133
|
)
|
|
121
134
|
}
|
|
122
|
-
if (!
|
|
123
|
-
throw errors.new(
|
|
124
|
-
`schema ${name}: closed relation ${field.closed.name} has no handle ${literal.handle} — ${rendered}`
|
|
125
|
-
)
|
|
135
|
+
if (!roster.handles.includes(literal.handle)) {
|
|
136
|
+
throw errors.new(`schema ${name}: closed relation ${roster.name} has no handle ${literal.handle} — ${rendered}`)
|
|
126
137
|
}
|
|
127
138
|
}
|
|
128
139
|
}
|
|
@@ -216,7 +227,7 @@ function verifyClosedReferenceBinding(
|
|
|
216
227
|
if (!spellsHandle) {
|
|
217
228
|
return
|
|
218
229
|
}
|
|
219
|
-
const roster = faceField(face, binding.field)
|
|
230
|
+
const roster = rosterOf(faceField(face, binding.field))
|
|
220
231
|
if (roster === undefined) {
|
|
221
232
|
return
|
|
222
233
|
}
|
|
@@ -237,16 +248,43 @@ type SchemaRelation = AnyRelation | AnyClosed
|
|
|
237
248
|
/** The relation record a schema is generic over — what `Db` and queries key on. */
|
|
238
249
|
type SchemaRelations = Record<string, SchemaRelation>
|
|
239
250
|
|
|
240
|
-
/**
|
|
241
|
-
|
|
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> {
|
|
242
262
|
readonly name: string
|
|
243
263
|
readonly relations: Rels
|
|
244
264
|
readonly statements: readonly Statement[]
|
|
265
|
+
readonly classes: Classes
|
|
245
266
|
}
|
|
246
267
|
|
|
247
268
|
/** Any schema value, whatever its relation record. */
|
|
248
269
|
type AnySchema = Schema<SchemaRelations>
|
|
249
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
|
+
|
|
250
288
|
/**
|
|
251
289
|
* Assembles a theory:
|
|
252
290
|
* `schema("Ledger", { Kind, Account, Holder }, [ ...statements ])`.
|
|
@@ -267,12 +305,21 @@ type AnySchema = Schema<SchemaRelations>
|
|
|
267
305
|
* list: the engine materializes them itself, in its own pinned order
|
|
268
306
|
* (`SchemaDescriptor::materialized_statements`), and restating them would
|
|
269
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.
|
|
270
317
|
*/
|
|
271
|
-
function schema<const Rels extends SchemaRelations>(
|
|
318
|
+
function schema<const Rels extends SchemaRelations, const Stmts extends readonly Statement[]>(
|
|
272
319
|
name: string,
|
|
273
320
|
relations: Rels,
|
|
274
|
-
statements:
|
|
275
|
-
): Schema<Rels
|
|
321
|
+
statements: Stmts & LawfulStatements<Rels, Stmts>
|
|
322
|
+
): Schema<Rels, EvaluatedClasses<ClassesOf<Rels, Stmts>>> {
|
|
276
323
|
const implied = collectImplied(name, relations)
|
|
277
324
|
const seen = new Set<string>()
|
|
278
325
|
for (const statement of statements) {
|
|
@@ -290,7 +337,11 @@ function schema<const Rels extends SchemaRelations>(
|
|
|
290
337
|
verifyHandles(name, statement, rendered)
|
|
291
338
|
}
|
|
292
339
|
verifyClosedReferences(name, statements)
|
|
293
|
-
|
|
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 })
|
|
294
345
|
}
|
|
295
346
|
|
|
296
347
|
export type { AnySchema, Schema, SchemaRelation, SchemaRelations }
|
package/src/spec.ts
CHANGED
|
@@ -95,9 +95,10 @@ type WindowSpec =
|
|
|
95
95
|
| { readonly kind: "floor"; readonly lo: bigint }
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
* One field: name, structural type, host newtype name
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* One field: name, structural type, host newtype name — the field's
|
|
99
|
+
* DOMAIN (the macro's declared `as NewType`; the SDK's law-computed class
|
|
100
|
+
* name), carried for handle resolution only, dropped by the engine at
|
|
101
|
+
* descriptor lowering and never fingerprinted — and the `fresh` mint mark.
|
|
101
102
|
*/
|
|
102
103
|
interface FieldSpec {
|
|
103
104
|
readonly name: string
|
|
@@ -120,8 +121,9 @@ interface RowSpec {
|
|
|
120
121
|
* kind); a closed relation's `fields` are its declared intrinsic columns
|
|
121
122
|
* only — the synthetic (`id`, u64) handle field is materialized by the
|
|
122
123
|
* engine's schema validation. `newtype` is the handle newtype of a closed
|
|
123
|
-
* relation (the SDK
|
|
124
|
-
*
|
|
124
|
+
* relation (the SDK emits the id's law-computed generator class,
|
|
125
|
+
* `` `${name}.id` `` — the same label every referencing field carries by
|
|
126
|
+
* law), undefined on an ordinary one.
|
|
125
127
|
*/
|
|
126
128
|
interface RelationSpec {
|
|
127
129
|
readonly name: string
|
package/src/statements.ts
CHANGED
|
@@ -4,30 +4,64 @@
|
|
|
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 STRUCTURAL compatibility through {@link SameShapes}: the two
|
|
11
|
+
* faces' projected kind/width/element triples are read off the schema type
|
|
12
|
+
* (the minimal kernel — descriptors are pure structure) and constrained
|
|
13
|
+
* positionwise equal, so a u64 face against a str face, a bytes width
|
|
14
|
+
* mismatch, or an interval element mismatch is a compile error. Domains are
|
|
15
|
+
* NOT compared here — there is no domain to compare at construction: the
|
|
16
|
+
* statements themselves are what define the equivalence classes, and the
|
|
17
|
+
* domain wall lives where they aggregate — `schema()` (the
|
|
18
|
+
* one-generator-per-class law) and query joins (class names off the schema
|
|
19
|
+
* type). What is only a SEMANTIC property — the target side of a
|
|
20
|
+
* containment resolving a declared key of its relation — is DELIBERATELY
|
|
21
|
+
* not (and cannot be) stated here: whether `B(y)` is a key of `B` depends
|
|
22
|
+
* on which `key()` statements the surrounding `schema()` collects, a set no
|
|
23
|
+
* face type can see; it stays the engine's typed `SchemaError` judgment at
|
|
24
|
+
* `Db.create`/`Db.open` (the two-boundary split, engine as final
|
|
25
|
+
* authority).
|
|
7
26
|
*/
|
|
8
27
|
|
|
9
28
|
import * as errors from "@superbuilders/errors"
|
|
10
|
-
import { phantom } from "#brand.ts"
|
|
11
29
|
import type { Count } from "#count.ts"
|
|
12
|
-
import { type AnyFace, type FaceData, renderFace, type SameArity } from "#face.ts"
|
|
30
|
+
import { type AnyFace, type FaceData, renderFace, type SameArity, type SameShapes } from "#face.ts"
|
|
13
31
|
import type { AnyRelation, RelationFields } from "#relation.ts"
|
|
14
32
|
import { renderWindow, type WindowSpec } from "#spec.ts"
|
|
15
33
|
|
|
34
|
+
/** A `key()` statement's runtime description — owner and projection carried at exact types. */
|
|
35
|
+
interface KeyData<R extends AnyRelation, Projection extends readonly string[]> {
|
|
36
|
+
readonly kind: "key"
|
|
37
|
+
readonly owner: R
|
|
38
|
+
readonly projection: Projection
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A containment statement's runtime description — the two faces carried at
|
|
43
|
+
* their EXACT types (owner names and projection tuples are honest runtime
|
|
44
|
+
* properties, and they are the type-level carrier `schema()`'s law-typing
|
|
45
|
+
* pairs slots through). The defaults are the wide shape renderers and the
|
|
46
|
+
* wire lowering consume.
|
|
47
|
+
*/
|
|
48
|
+
interface ContainmentData<Src extends FaceData = FaceData, Tgt extends FaceData = FaceData> {
|
|
49
|
+
readonly kind: "containment"
|
|
50
|
+
readonly source: Src
|
|
51
|
+
readonly target: Tgt
|
|
52
|
+
readonly bidirectional: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A window statement's runtime description — target-left, faces at exact types like {@link ContainmentData}. */
|
|
56
|
+
interface WindowData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
|
|
57
|
+
readonly kind: "window"
|
|
58
|
+
readonly target: Tgt
|
|
59
|
+
readonly window: WindowSpec
|
|
60
|
+
readonly source: Src
|
|
61
|
+
}
|
|
62
|
+
|
|
16
63
|
/** One statement's runtime description, tagged by form. */
|
|
17
|
-
type StatementData =
|
|
18
|
-
| { readonly kind: "key"; readonly owner: AnyRelation; readonly projection: readonly string[] }
|
|
19
|
-
| {
|
|
20
|
-
readonly kind: "containment"
|
|
21
|
-
readonly source: FaceData
|
|
22
|
-
readonly target: FaceData
|
|
23
|
-
readonly bidirectional: boolean
|
|
24
|
-
}
|
|
25
|
-
| {
|
|
26
|
-
readonly kind: "window"
|
|
27
|
-
readonly target: FaceData
|
|
28
|
-
readonly window: WindowSpec
|
|
29
|
-
readonly source: FaceData
|
|
30
|
-
}
|
|
64
|
+
type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | WindowData
|
|
31
65
|
|
|
32
66
|
/** An opaque statement value — what `schema()` assembles into a theory. */
|
|
33
67
|
interface Statement {
|
|
@@ -35,15 +69,33 @@ interface Statement {
|
|
|
35
69
|
}
|
|
36
70
|
|
|
37
71
|
/**
|
|
38
|
-
* A
|
|
39
|
-
*
|
|
72
|
+
* A containment (or `==` bijection) statement as a TYPED value: `data`
|
|
73
|
+
* carries both faces at their exact types, so the schema-level class laws
|
|
74
|
+
* can read every paired (relation, field) slot off the statement type —
|
|
75
|
+
* spell the statement list inline in `schema()` and the equivalence
|
|
76
|
+
* classes compute at the type level too. Structurally still a plain
|
|
77
|
+
* {@link Statement}.
|
|
78
|
+
*/
|
|
79
|
+
interface ContainedStatement<Src extends FaceData, Tgt extends FaceData> extends Statement {
|
|
80
|
+
readonly data: ContainmentData<Src, Tgt>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A window statement as a TYPED value — the {@link ContainedStatement} of the window form. */
|
|
84
|
+
interface WindowStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
|
|
85
|
+
readonly data: WindowData<Tgt, Src>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A `key()` statement as a TYPED value: its `data` carries the owner
|
|
90
|
+
* relation and the projection tuple at their EXACT types (honest runtime
|
|
91
|
+
* properties — no phantom), which is what the key-statement-selected
|
|
40
92
|
* `get(relation, keyStatement, key)` overload types its key object by
|
|
41
|
-
* (`docs/architecture/70-api.md` § the freeze, the multi-key typed get)
|
|
42
|
-
*
|
|
43
|
-
*
|
|
93
|
+
* (`docs/architecture/70-api.md` § the freeze, the multi-key typed get) and
|
|
94
|
+
* what resolves each projected field's descriptor through the owner's
|
|
95
|
+
* schema type. Structurally still a plain {@link Statement}.
|
|
44
96
|
*/
|
|
45
97
|
interface KeyStatement<R extends AnyRelation, Projection extends readonly string[]> extends Statement {
|
|
46
|
-
readonly
|
|
98
|
+
readonly data: KeyData<R, Projection>
|
|
47
99
|
}
|
|
48
100
|
|
|
49
101
|
/**
|
|
@@ -51,9 +103,11 @@ interface KeyStatement<R extends AnyRelation, Projection extends readonly string
|
|
|
51
103
|
* parameter exists (the FD-with-selection shape is unrepresentable, as in
|
|
52
104
|
* the grammar), and only ordinary relations are accepted: a closed
|
|
53
105
|
* relation's key `R(id) -> R` is materialized by the engine, so an
|
|
54
|
-
* explicit one would only ever be a duplicate.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
106
|
+
* explicit one would only ever be a duplicate. Every projected name is
|
|
107
|
+
* checked against `R`'s field block in the type, and the tuple is carried
|
|
108
|
+
* in the returned value's type ({@link KeyStatement}) — keyed point reads
|
|
109
|
+
* through THIS statement are typed field-for-field, descriptors resolvable
|
|
110
|
+
* through the owner's schema type.
|
|
57
111
|
*/
|
|
58
112
|
function key<
|
|
59
113
|
R extends AnyRelation,
|
|
@@ -64,22 +118,28 @@ function key<
|
|
|
64
118
|
`key(${relation.name}, ...): closedness already materializes ${relation.name}(id) -> ${relation.name} — an explicit key on a closed relation is rejected as a duplicate`
|
|
65
119
|
)
|
|
66
120
|
}
|
|
67
|
-
const data:
|
|
121
|
+
const data: KeyData<R, Projection> = Object.freeze({
|
|
68
122
|
kind: "key",
|
|
69
123
|
owner: relation,
|
|
70
|
-
projection: Object.freeze(
|
|
124
|
+
projection: Object.freeze(fields)
|
|
71
125
|
})
|
|
72
126
|
return Object.freeze({ data })
|
|
73
127
|
}
|
|
74
128
|
|
|
75
129
|
/**
|
|
76
|
-
* `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left.
|
|
77
|
-
*
|
|
130
|
+
* `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. Arity mismatch
|
|
131
|
+
* between the two faces is a type error ({@link SameArity}); a structurally
|
|
132
|
+
* mismatched pair is a type error ({@link SameShapes} — positionwise
|
|
133
|
+
* equality of the projected kind/width/element triples). The target side
|
|
134
|
+
* must resolve a declared key of B — a SEMANTIC property of the whole
|
|
135
|
+
* statement set that no face type can state, DELIBERATELY judged by the
|
|
78
136
|
* engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
|
|
79
|
-
* Arity mismatch between the two faces is a type error ({@link SameArity}).
|
|
80
137
|
*/
|
|
81
|
-
function contained<A extends AnyFace, B extends AnyFace>(
|
|
82
|
-
|
|
138
|
+
function contained<A extends AnyFace, B extends AnyFace>(
|
|
139
|
+
source: A,
|
|
140
|
+
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
141
|
+
): ContainedStatement<A["data"], B["data"]> {
|
|
142
|
+
const data: ContainmentData<A["data"], B["data"]> = Object.freeze({
|
|
83
143
|
kind: "containment",
|
|
84
144
|
source: source.data,
|
|
85
145
|
target: target.data,
|
|
@@ -89,12 +149,19 @@ function contained<A extends AnyFace, B extends AnyFace>(source: A, target: B &
|
|
|
89
149
|
}
|
|
90
150
|
|
|
91
151
|
/**
|
|
92
|
-
* `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance
|
|
93
|
-
*
|
|
94
|
-
* (
|
|
152
|
+
* `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance: the
|
|
153
|
+
* selected `==` bijection, a keyed one-to-one correspondence between the
|
|
154
|
+
* two faces (each side contains the other). It lowers to the two adjacent
|
|
155
|
+
* containments in the `A <= B` first order (macro parity — the engine
|
|
156
|
+
* performs the split, source-first) and renders as `==` once, in the
|
|
157
|
+
* written orientation. Faces pair by arity AND structural shape, exactly
|
|
158
|
+
* as {@link contained}.
|
|
95
159
|
*/
|
|
96
|
-
function mirrors<A extends AnyFace, B extends AnyFace>(
|
|
97
|
-
|
|
160
|
+
function mirrors<A extends AnyFace, B extends AnyFace>(
|
|
161
|
+
source: A,
|
|
162
|
+
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
163
|
+
): ContainedStatement<A["data"], B["data"]> {
|
|
164
|
+
const data: ContainmentData<A["data"], B["data"]> = Object.freeze({
|
|
98
165
|
kind: "containment",
|
|
99
166
|
source: source.data,
|
|
100
167
|
target: target.data,
|
|
@@ -108,10 +175,17 @@ function mirrors<A extends AnyFace, B extends AnyFace>(source: A, target: B & Sa
|
|
|
108
175
|
* LEFT face is the window's TARGET, the per-group parent (B-family,
|
|
109
176
|
* target-left — macro parity), and the RIGHT face is the counted source.
|
|
110
177
|
* `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
|
|
111
|
-
* Holder id groups at most three Account rows by holder.
|
|
178
|
+
* Holder id groups at most three Account rows by holder. The two faces
|
|
179
|
+
* pair by arity AND structural shape ({@link SameShapes}), exactly as
|
|
180
|
+
* containment — the grouping join reads the same positionwise field
|
|
181
|
+
* pairing.
|
|
112
182
|
*/
|
|
113
|
-
function window<B extends AnyFace, A extends AnyFace>(
|
|
114
|
-
|
|
183
|
+
function window<B extends AnyFace, A extends AnyFace>(
|
|
184
|
+
target: B,
|
|
185
|
+
count: Count,
|
|
186
|
+
source: A & SameArity<B, A> & SameShapes<B, A>
|
|
187
|
+
): WindowStatement<B["data"], A["data"]> {
|
|
188
|
+
const data: WindowData<B["data"], A["data"]> = Object.freeze({
|
|
115
189
|
kind: "window",
|
|
116
190
|
target: target.data,
|
|
117
191
|
window: count.window,
|
|
@@ -144,5 +218,14 @@ function renderStatement(statement: Statement): string {
|
|
|
144
218
|
}
|
|
145
219
|
}
|
|
146
220
|
|
|
147
|
-
export type {
|
|
221
|
+
export type {
|
|
222
|
+
ContainedStatement,
|
|
223
|
+
ContainmentData,
|
|
224
|
+
KeyData,
|
|
225
|
+
KeyStatement,
|
|
226
|
+
Statement,
|
|
227
|
+
StatementData,
|
|
228
|
+
WindowData,
|
|
229
|
+
WindowStatement
|
|
230
|
+
}
|
|
148
231
|
export { contained, key, mirrors, renderStatement, window }
|
package/dist/brand.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nominal branding — the TS analog of the Rust macro's host newtypes
|
|
3
|
-
* (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
|
|
4
|
-
* is a phantom: it exists only in the type, tsc polices the wall exactly as
|
|
5
|
-
* rustc polices newtype domains, and nothing is allocated or wrapped at
|
|
6
|
-
* runtime.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* The brand key. A real runtime symbol (so modules can import it without a
|
|
10
|
-
* `declare`-only lie), but no branded value ever carries the property — the
|
|
11
|
-
* brand is purely a typing device.
|
|
12
|
-
*/
|
|
13
|
-
declare const brand: unique symbol;
|
|
14
|
-
/**
|
|
15
|
-
* The phantom-value key used by field values, field references, and faces
|
|
16
|
-
* to carry their value type without any runtime representation.
|
|
17
|
-
*/
|
|
18
|
-
declare const phantom: unique symbol;
|
|
19
|
-
/**
|
|
20
|
-
* A branded scalar: `T` walled off under the literal name `Name`. A
|
|
21
|
-
* `Brand<bigint, "HolderId">` is not assignable where a
|
|
22
|
-
* `Brand<bigint, "AccountId">` is expected — the Rust newtype wall,
|
|
23
|
-
* verbatim. Scalars brand as `bigint` (u64/i64), `Uint8Array` (bytes), and
|
|
24
|
-
* whole interval objects (`Interval<Name>`); `bool` and `str` take no
|
|
25
|
-
* newtype, exactly as the macro's `as` grammar refuses them.
|
|
26
|
-
*/
|
|
27
|
-
type Brand<T, Name extends string> = T & {
|
|
28
|
-
readonly [brand]: Name;
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* A half-open interval `[start, end)` as a plain value object. The ray is
|
|
32
|
-
* representable (`end` = the element domain's MAX_END); widths and
|
|
33
|
-
* signedness are NOT modeled here — the engine judges widths at the typed
|
|
34
|
-
* write boundary, the brand blocks cross-field assignment, and nothing
|
|
35
|
-
* else is TS's business. Interval newtypes derive no order (the Rust
|
|
36
|
-
* refusal, `docs/architecture/10-data-model.md`), so no comparators exist.
|
|
37
|
-
*/
|
|
38
|
-
interface IntervalValue {
|
|
39
|
-
readonly start: bigint;
|
|
40
|
-
readonly end: bigint;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* A branded interval: the whole `{ start, end }` object walled under
|
|
44
|
-
* `Name` — the `interval<i64> as ActiveDuring` analog.
|
|
45
|
-
*/
|
|
46
|
-
type Interval<Name extends string> = Brand<IntervalValue, Name>;
|
|
47
|
-
/**
|
|
48
|
-
* Constructs an interval literal — the `start..end` spelling. Half-open
|
|
49
|
-
* and nonempty by construction: `start >= end` is a typed construction
|
|
50
|
-
* error (parse, don't validate — the same invariant Rust's
|
|
51
|
-
* `Interval::new` enforces at the host boundary). The default `never`
|
|
52
|
-
* brand makes a fresh literal assignable to any interval field or brand,
|
|
53
|
-
* the wrap-at-construction idiom; pass the brand explicitly
|
|
54
|
-
* (`span<"ActiveDuring">(0n, 10n)`) to pin it.
|
|
55
|
-
*/
|
|
56
|
-
declare function span<Name extends string = never>(start: bigint, end: bigint): Interval<Name>;
|
|
57
|
-
export type { Brand, Interval, IntervalValue };
|
|
58
|
-
export { brand, phantom, span };
|
|
59
|
-
//# sourceMappingURL=brand.d.ts.map
|
package/dist/brand.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,QAAA,MAAM,KAAK,EAAE,OAAO,MAAiC,CAAA;AAErD;;;GAGG;AACH,QAAA,MAAM,OAAO,EAAE,OAAO,MAAmC,CAAA;AAEzD;;;;;;;GAOG;AACH,KAAK,KAAK,CAAC,CAAC,EAAE,IAAI,SAAS,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAEnE;;;;;;;GAOG;AACH,UAAU,aAAa;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,KAAK,QAAQ,CAAC,IAAI,SAAS,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;AAE/D;;;;;;;;GAQG;AACH,iBAAS,IAAI,CAAC,IAAI,SAAS,MAAM,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAMrF;AAED,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
package/dist/brand.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nominal branding — the TS analog of the Rust macro's host newtypes
|
|
3
|
-
* (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
|
|
4
|
-
* is a phantom: it exists only in the type, tsc polices the wall exactly as
|
|
5
|
-
* rustc polices newtype domains, and nothing is allocated or wrapped at
|
|
6
|
-
* runtime.
|
|
7
|
-
*/
|
|
8
|
-
import * as errors from "@superbuilders/errors";
|
|
9
|
-
/**
|
|
10
|
-
* The brand-minting guard behind {@link span} — the one nominal step, as a
|
|
11
|
-
* type guard carrying the interval's REAL invariant (`start < end`, the
|
|
12
|
-
* same check Rust's `Interval::new` runs): a value that passes IS a legal
|
|
13
|
-
* interval of any brand, exactly as a Rust newtype wraps a checked
|
|
14
|
-
* `Interval<T>` at construction.
|
|
15
|
-
*/
|
|
16
|
-
function isNonemptyInterval(value) {
|
|
17
|
-
return value.start < value.end;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* The brand key. A real runtime symbol (so modules can import it without a
|
|
21
|
-
* `declare`-only lie), but no branded value ever carries the property — the
|
|
22
|
-
* brand is purely a typing device.
|
|
23
|
-
*/
|
|
24
|
-
const brand = Symbol("bumbledb.brand");
|
|
25
|
-
/**
|
|
26
|
-
* The phantom-value key used by field values, field references, and faces
|
|
27
|
-
* to carry their value type without any runtime representation.
|
|
28
|
-
*/
|
|
29
|
-
const phantom = Symbol("bumbledb.phantom");
|
|
30
|
-
/**
|
|
31
|
-
* Constructs an interval literal — the `start..end` spelling. Half-open
|
|
32
|
-
* and nonempty by construction: `start >= end` is a typed construction
|
|
33
|
-
* error (parse, don't validate — the same invariant Rust's
|
|
34
|
-
* `Interval::new` enforces at the host boundary). The default `never`
|
|
35
|
-
* brand makes a fresh literal assignable to any interval field or brand,
|
|
36
|
-
* the wrap-at-construction idiom; pass the brand explicitly
|
|
37
|
-
* (`span<"ActiveDuring">(0n, 10n)`) to pin it.
|
|
38
|
-
*/
|
|
39
|
-
function span(start, end) {
|
|
40
|
-
const value = Object.freeze({ start, end });
|
|
41
|
-
if (!isNonemptyInterval(value)) {
|
|
42
|
-
throw errors.new(`interval is half-open and nonempty: start must be < end (got ${start}..${end})`);
|
|
43
|
-
}
|
|
44
|
-
return value;
|
|
45
|
-
}
|
|
46
|
-
export { brand, phantom, span };
|
|
47
|
-
//# sourceMappingURL=brand.js.map
|
package/dist/brand.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"brand.js","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAA;AAE/C;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAsB,KAAoB;IACpE,OAAO,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,CAAA;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,KAAK,GAAkB,MAAM,CAAC,gBAAgB,CAAC,CAAA;AAErD;;;GAGG;AACH,MAAM,OAAO,GAAkB,MAAM,CAAC,kBAAkB,CAAC,CAAA;AA+BzD;;;;;;;;GAQG;AACH,SAAS,IAAI,CAA8B,KAAa,EAAE,GAAW;IACpE,MAAM,KAAK,GAAkB,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAC1D,IAAI,CAAC,kBAAkB,CAAO,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,MAAM,CAAC,GAAG,CAAC,gEAAgE,KAAK,KAAK,GAAG,GAAG,CAAC,CAAA;IACnG,CAAC;IACD,OAAO,KAAK,CAAA;AACb,CAAC;AAGD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
package/src/brand.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nominal branding — the TS analog of the Rust macro's host newtypes
|
|
3
|
-
* (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
|
|
4
|
-
* is a phantom: it exists only in the type, tsc polices the wall exactly as
|
|
5
|
-
* rustc polices newtype domains, and nothing is allocated or wrapped at
|
|
6
|
-
* runtime.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as errors from "@superbuilders/errors"
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The brand-minting guard behind {@link span} — the one nominal step, as a
|
|
13
|
-
* type guard carrying the interval's REAL invariant (`start < end`, the
|
|
14
|
-
* same check Rust's `Interval::new` runs): a value that passes IS a legal
|
|
15
|
-
* interval of any brand, exactly as a Rust newtype wraps a checked
|
|
16
|
-
* `Interval<T>` at construction.
|
|
17
|
-
*/
|
|
18
|
-
function isNonemptyInterval<Name extends string>(value: IntervalValue): value is Interval<Name> {
|
|
19
|
-
return value.start < value.end
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The brand key. A real runtime symbol (so modules can import it without a
|
|
24
|
-
* `declare`-only lie), but no branded value ever carries the property — the
|
|
25
|
-
* brand is purely a typing device.
|
|
26
|
-
*/
|
|
27
|
-
const brand: unique symbol = Symbol("bumbledb.brand")
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The phantom-value key used by field values, field references, and faces
|
|
31
|
-
* to carry their value type without any runtime representation.
|
|
32
|
-
*/
|
|
33
|
-
const phantom: unique symbol = Symbol("bumbledb.phantom")
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* A branded scalar: `T` walled off under the literal name `Name`. A
|
|
37
|
-
* `Brand<bigint, "HolderId">` is not assignable where a
|
|
38
|
-
* `Brand<bigint, "AccountId">` is expected — the Rust newtype wall,
|
|
39
|
-
* verbatim. Scalars brand as `bigint` (u64/i64), `Uint8Array` (bytes), and
|
|
40
|
-
* whole interval objects (`Interval<Name>`); `bool` and `str` take no
|
|
41
|
-
* newtype, exactly as the macro's `as` grammar refuses them.
|
|
42
|
-
*/
|
|
43
|
-
type Brand<T, Name extends string> = T & { readonly [brand]: Name }
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* A half-open interval `[start, end)` as a plain value object. The ray is
|
|
47
|
-
* representable (`end` = the element domain's MAX_END); widths and
|
|
48
|
-
* signedness are NOT modeled here — the engine judges widths at the typed
|
|
49
|
-
* write boundary, the brand blocks cross-field assignment, and nothing
|
|
50
|
-
* else is TS's business. Interval newtypes derive no order (the Rust
|
|
51
|
-
* refusal, `docs/architecture/10-data-model.md`), so no comparators exist.
|
|
52
|
-
*/
|
|
53
|
-
interface IntervalValue {
|
|
54
|
-
readonly start: bigint
|
|
55
|
-
readonly end: bigint
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* A branded interval: the whole `{ start, end }` object walled under
|
|
60
|
-
* `Name` — the `interval<i64> as ActiveDuring` analog.
|
|
61
|
-
*/
|
|
62
|
-
type Interval<Name extends string> = Brand<IntervalValue, Name>
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Constructs an interval literal — the `start..end` spelling. Half-open
|
|
66
|
-
* and nonempty by construction: `start >= end` is a typed construction
|
|
67
|
-
* error (parse, don't validate — the same invariant Rust's
|
|
68
|
-
* `Interval::new` enforces at the host boundary). The default `never`
|
|
69
|
-
* brand makes a fresh literal assignable to any interval field or brand,
|
|
70
|
-
* the wrap-at-construction idiom; pass the brand explicitly
|
|
71
|
-
* (`span<"ActiveDuring">(0n, 10n)`) to pin it.
|
|
72
|
-
*/
|
|
73
|
-
function span<Name extends string = never>(start: bigint, end: bigint): Interval<Name> {
|
|
74
|
-
const value: IntervalValue = Object.freeze({ start, end })
|
|
75
|
-
if (!isNonemptyInterval<Name>(value)) {
|
|
76
|
-
throw errors.new(`interval is half-open and nonempty: start must be < end (got ${start}..${end})`)
|
|
77
|
-
}
|
|
78
|
-
return value
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export type { Brand, Interval, IntervalValue }
|
|
82
|
-
export { brand, phantom, span }
|