@bjornpagen/bumbledb 0.15.0 → 0.17.1
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 +33 -49
- package/README.md +3 -3
- package/dist/capacity.d.ts +24 -136
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +18 -40
- package/dist/capacity.js.map +1 -1
- package/dist/closed.d.ts +0 -156
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +0 -104
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +7 -223
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +147 -396
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +0 -133
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +0 -33
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +1 -145
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +2 -91
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +12 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -13
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +111 -93
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +23 -27
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +9 -35
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +8 -53
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +0 -65
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +0 -72
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +31 -289
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +15 -64
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +10 -276
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +1 -96
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +10 -76
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +0 -30
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +58 -146
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +19 -256
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +0 -7
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +1 -13
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +0 -36
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +0 -44
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +24 -180
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +2 -66
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +2 -50
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +2 -37
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +13 -63
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +118 -92
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +1 -140
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +1 -68
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +6 -137
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +16 -119
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/capacity.ts +26 -140
- package/src/closed.ts +5 -206
- package/src/db.ts +203 -692
- package/src/face.ts +0 -142
- package/src/fields.ts +4 -172
- package/src/index.ts +10 -15
- package/src/law.ts +201 -129
- package/src/lower.ts +8 -53
- package/src/marshal.ts +1 -85
- package/src/native.ts +58 -321
- package/src/query/atom.ts +26 -313
- package/src/query/find.ts +24 -110
- package/src/query/lower.ts +126 -377
- package/src/query/parse-ir.ts +1 -14
- package/src/query/run.ts +0 -45
- package/src/query/scope.ts +25 -186
- package/src/relation.ts +2 -66
- package/src/schema.ts +143 -122
- package/src/spec.ts +1 -160
- package/src/statements.ts +22 -174
package/src/schema.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `schema
|
|
2
|
+
* `schema` — assembles relations and statements into a theory value (the
|
|
3
3
|
* `Theory` analog; what `Db.create`/`Db.open` take). Construction-time
|
|
4
|
-
* validation is the macro-EXPANSION-boundary analog
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* validation is the macro-EXPANSION-boundary analog: membership,
|
|
5
|
+
* implied-key duplicates, duplicate statements, a belt-and-braces handle
|
|
6
|
+
* re-verification — and the TARGET-KEY WALL ({@link verifyTargetKeys}),
|
|
7
|
+
* the value tier of the two-tier containment law
|
|
8
|
+
* (60-containment-parity): every containment/mirrors/capacity target
|
|
9
|
+
* projection must set-match a key of its relation, judged HERE with the
|
|
10
|
+
* engine's exact rule so `lower` never emits an engine-refused
|
|
11
|
+
* containment. The type tier is `law.ts`'s `TargetKeyWall` (best effort,
|
|
12
|
+
* statically known tuples); every OTHER semantic judgment (key-internal
|
|
13
|
+
* legality, fresh-on-u64, …) stays the engine's `SchemaError` at
|
|
14
|
+
* `Db.create` — the engine is the final authority for every boundary,
|
|
15
|
+
* this wall just makes the SDK agree with it first.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
import * as errors from "@superbuilders/errors"
|
|
@@ -19,16 +25,15 @@ import type { AnyRelation } from "#relation.ts"
|
|
|
19
25
|
import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
|
|
20
26
|
import { isStatement, renderStatement, type Statement } from "#statements.ts"
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
function collectImplied(name: string, relations: SchemaRelations): Set<string> {
|
|
28
|
+
interface ImpliedKeys {
|
|
29
|
+
readonly rendered: ReadonlySet<string>
|
|
30
|
+
readonly roster: ReadonlyMap<string, ReadonlyArray<readonly string[]>>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectImplied(name: string, relations: SchemaRelations): ImpliedKeys {
|
|
30
34
|
assertDeclarationRecord(`schema ${name} relations`, relations)
|
|
31
|
-
const
|
|
35
|
+
const rendered = new Set<string>()
|
|
36
|
+
const roster = new Map<string, ReadonlyArray<readonly string[]>>()
|
|
32
37
|
for (const [recordKey, member] of Object.entries(relations)) {
|
|
33
38
|
assertDeclarationOrderKey(`schema ${name} relation`, recordKey)
|
|
34
39
|
if (member.name !== recordKey) {
|
|
@@ -36,20 +41,24 @@ function collectImplied(name: string, relations: SchemaRelations): Set<string> {
|
|
|
36
41
|
`schema ${name}: record key ${recordKey} holds relation ${member.name} — the key must equal the relation's declared name`
|
|
37
42
|
)
|
|
38
43
|
}
|
|
44
|
+
const projections: Array<readonly string[]> = []
|
|
39
45
|
if (isClosedMember(member)) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
projections.push(Object.freeze(["id"]))
|
|
47
|
+
} else {
|
|
48
|
+
for (const declared of member.data.fields) {
|
|
49
|
+
if ("fresh" in declared.field && declared.field.fresh === true) {
|
|
50
|
+
projections.push(Object.freeze([declared.name]))
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
}
|
|
54
|
+
for (const projection of projections) {
|
|
55
|
+
rendered.add(`${member.name}(${projection.join(", ")}) -> ${member.name}`)
|
|
56
|
+
}
|
|
57
|
+
roster.set(member.name, Object.freeze(projections))
|
|
48
58
|
}
|
|
49
|
-
return
|
|
59
|
+
return { rendered, roster }
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
/** The relation values a statement addresses, for membership checking. */
|
|
53
62
|
function statementOwners(statement: Statement): readonly SchemaRelation[] {
|
|
54
63
|
const data = statement.data
|
|
55
64
|
if (data.kind === "key") {
|
|
@@ -58,11 +67,6 @@ function statementOwners(statement: Statement): readonly SchemaRelation[] {
|
|
|
58
67
|
return [data.source.owner, data.target.owner]
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
/**
|
|
62
|
-
* Requires every relation a statement addresses to be the IDENTICAL value
|
|
63
|
-
* the schema record declares — same-name-different-value is a forgery, not
|
|
64
|
-
* a membership.
|
|
65
|
-
*/
|
|
66
70
|
function verifyMembership(name: string, relations: SchemaRelations, statement: Statement, rendered: string): void {
|
|
67
71
|
for (const owner of statementOwners(statement)) {
|
|
68
72
|
const member = relations[owner.name]
|
|
@@ -77,7 +81,6 @@ function verifyMembership(name: string, relations: SchemaRelations, statement: S
|
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
/** Flattens one binding's literal set into its literals. */
|
|
81
84
|
function bindingLiterals(set: LiteralSetSpec): readonly LiteralSpec[] {
|
|
82
85
|
if (set.kind === "one") {
|
|
83
86
|
return [set.literal]
|
|
@@ -85,12 +88,6 @@ function bindingLiterals(set: LiteralSetSpec): readonly LiteralSpec[] {
|
|
|
85
88
|
return set.literals
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
/**
|
|
89
|
-
* Re-verifies one binding's handle literals against the field's roster —
|
|
90
|
-
* belt-and-braces over what `where()` already resolved and the type level
|
|
91
|
-
* already blocked, so a forged binding fails here rather than at the
|
|
92
|
-
* engine boundary with a colder message.
|
|
93
|
-
*/
|
|
94
91
|
function verifyBindingHandles(
|
|
95
92
|
name: string,
|
|
96
93
|
face: FaceData,
|
|
@@ -113,7 +110,6 @@ function verifyBindingHandles(
|
|
|
113
110
|
}
|
|
114
111
|
}
|
|
115
112
|
|
|
116
|
-
/** Walks every face of a statement through the handle re-verification. */
|
|
117
113
|
function verifyHandles(name: string, statement: Statement, rendered: string): void {
|
|
118
114
|
const data = statement.data
|
|
119
115
|
if (data.kind === "key") {
|
|
@@ -126,15 +122,6 @@ function verifyHandles(name: string, statement: Statement, rendered: string): vo
|
|
|
126
122
|
}
|
|
127
123
|
}
|
|
128
124
|
|
|
129
|
-
/**
|
|
130
|
-
* Resolves the closed relation a `(relation, field)` pair references
|
|
131
|
-
* through the DECLARED containments — the identical walk the engine's
|
|
132
|
-
* canonical renderer performs (`schema/render.rs` `closed_target_of`): one
|
|
133
|
-
* hop, source projecting exactly `[field]`, target projecting exactly the
|
|
134
|
-
* closed relation's `[id]`, first declared match wins; a `mirrors`
|
|
135
|
-
* contributes both of its materialized orientations. `undefined` = the
|
|
136
|
-
* engine would render the field's selection literals as raw row ids.
|
|
137
|
-
*/
|
|
138
125
|
function closedTargetOf(statements: readonly Statement[], owner: string, field: string): string | undefined {
|
|
139
126
|
for (const statement of statements) {
|
|
140
127
|
const data = statement.data
|
|
@@ -161,18 +148,6 @@ function closedTargetOf(statements: readonly Statement[], owner: string, field:
|
|
|
161
148
|
return undefined
|
|
162
149
|
}
|
|
163
150
|
|
|
164
|
-
/**
|
|
165
|
-
* Admits a handle spelling only when the schema also declares the
|
|
166
|
-
* containment the ENGINE's canonical renderer resolves it through
|
|
167
|
-
* (`docs/architecture/10-data-model.md` § closed relations: a closed
|
|
168
|
-
* reference is the plain u64 column PLUS a declared containment). Without
|
|
169
|
-
* it the two renderers drift — `renderStatement` prints the handle name,
|
|
170
|
-
* the engine's violation `canonical` prints the raw row id — and the
|
|
171
|
-
* paste-back law (`violation.canonical === renderStatement(statement)`)
|
|
172
|
-
* breaks. Runs over the COMPLETE statement list, so declaration order
|
|
173
|
-
* never matters. The closed relation's own `id` field resolves directly
|
|
174
|
-
* (the walk's field-0 case).
|
|
175
|
-
*/
|
|
176
151
|
function verifyClosedReferences(name: string, statements: readonly Statement[]): void {
|
|
177
152
|
for (const statement of statements) {
|
|
178
153
|
const data = statement.data
|
|
@@ -188,7 +163,6 @@ function verifyClosedReferences(name: string, statements: readonly Statement[]):
|
|
|
188
163
|
}
|
|
189
164
|
}
|
|
190
165
|
|
|
191
|
-
/** One binding's closed-reference resolution check (the {@link verifyClosedReferences} leaf). */
|
|
192
166
|
function verifyClosedReferenceBinding(
|
|
193
167
|
name: string,
|
|
194
168
|
statements: readonly Statement[],
|
|
@@ -217,22 +191,118 @@ function verifyClosedReferenceBinding(
|
|
|
217
191
|
}
|
|
218
192
|
}
|
|
219
193
|
|
|
220
|
-
/**
|
|
221
|
-
|
|
194
|
+
/**
|
|
195
|
+
* THE TARGET-KEY WALL, value tier (60-containment-parity — the runtime
|
|
196
|
+
* twin of `law.ts`'s `TargetKeyWall`, the engine's `resolve_target_key` /
|
|
197
|
+
* `resolve_capacity_target` mirrored exactly): every `contained`/
|
|
198
|
+
* `mirrors`/`capacity` statement's target projection must resolve a key
|
|
199
|
+
* of the target relation, judged over the SAME key population the engine
|
|
200
|
+
* materializes — the fresh-implied and closed auto-keys
|
|
201
|
+
* ({@link collectImplied}'s roster) first, then the declared `key`
|
|
202
|
+
* statements in written order (a key may be declared after its probe, so
|
|
203
|
+
* this wall runs over the COMPLETE list, never inside the statement
|
|
204
|
+
* loop). `mirrors` materializes as two containments source-first, so both
|
|
205
|
+
* orientations judge their own target. SOUNDNESS BAR: the set-match +
|
|
206
|
+
* closed-id rule below is the engine's COMPLETE rule for this law
|
|
207
|
+
* (`matching_functionality` compares field SETS — permutations resolve,
|
|
208
|
+
* subsets/supersets refuse), so this wall never rejects what the engine
|
|
209
|
+
* accepts; every other schema judgment stays engine-first.
|
|
210
|
+
*/
|
|
211
|
+
function verifyTargetKeys(
|
|
212
|
+
name: string,
|
|
213
|
+
statements: readonly Statement[],
|
|
214
|
+
implied: ReadonlyMap<string, ReadonlyArray<readonly string[]>>
|
|
215
|
+
): void {
|
|
216
|
+
const declared = new Map<string, Array<readonly string[]>>()
|
|
217
|
+
for (const statement of statements) {
|
|
218
|
+
const data = statement.data
|
|
219
|
+
if (data.kind !== "key") {
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
222
|
+
const keys = declared.get(data.owner.name)
|
|
223
|
+
if (keys === undefined) {
|
|
224
|
+
declared.set(data.owner.name, [data.projection])
|
|
225
|
+
} else {
|
|
226
|
+
keys.push(data.projection)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const statement of statements) {
|
|
230
|
+
const data = statement.data
|
|
231
|
+
if (data.kind === "key") {
|
|
232
|
+
continue
|
|
233
|
+
}
|
|
234
|
+
const rendered = renderStatement(statement)
|
|
222
235
|
|
|
223
|
-
|
|
224
|
-
|
|
236
|
+
const faces = data.kind === "mirrors" ? [data.target, data.source] : [data.target]
|
|
237
|
+
for (const face of faces) {
|
|
238
|
+
verifyTargetKeyFace(name, face, implied, declared, rendered)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
225
242
|
|
|
226
243
|
/**
|
|
227
|
-
*
|
|
228
|
-
* the
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* the
|
|
244
|
+
* One target face's key resolution (the {@link verifyTargetKeys} leaf).
|
|
245
|
+
* Closed target: the handle id is the ONE probe-able identity of a closed
|
|
246
|
+
* relation, so the projection must be exactly `["id"]` — its own refusal
|
|
247
|
+
* (the engine's `ClosedTargetNotHandle`): the rule is CLOSEDNESS, not key
|
|
248
|
+
* absence. Ordinary target: the projection's field-name SET must equal
|
|
249
|
+
* some roster member's set (the engine's `matching_functionality` —
|
|
250
|
+
* permutations resolve, subsets and supersets do not). The refusal speaks
|
|
251
|
+
* the engine's shape in NAMES, with the engine's pointwise hint verbatim
|
|
252
|
+
* when the projection carries an interval position.
|
|
235
253
|
*/
|
|
254
|
+
function verifyTargetKeyFace(
|
|
255
|
+
name: string,
|
|
256
|
+
face: FaceData,
|
|
257
|
+
implied: ReadonlyMap<string, ReadonlyArray<readonly string[]>>,
|
|
258
|
+
declared: ReadonlyMap<string, ReadonlyArray<readonly string[]>>,
|
|
259
|
+
rendered: string
|
|
260
|
+
): void {
|
|
261
|
+
if (isClosedMember(face.owner)) {
|
|
262
|
+
if (face.projection.length === 1 && face.projection[0] === "id") {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
throw errors.new(
|
|
266
|
+
`schema ${name}: ${rendered}: closed target ${face.owner.name} is addressed by its synthetic id only — projection (${face.projection.join(", ")}) must be exactly (id) (rewrite the target side as on(${face.owner.name}, "id"))`
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
const roster = [...(implied.get(face.owner.name) ?? []), ...(declared.get(face.owner.name) ?? [])]
|
|
270
|
+
const want = new Set(face.projection)
|
|
271
|
+
const matched = roster.some(function sameFieldSet(key) {
|
|
272
|
+
// engine's FieldSet refuses (duplicates are refused at the key()
|
|
273
|
+
|
|
274
|
+
if (key.length !== face.projection.length || want.size !== face.projection.length) {
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
const keySet = new Set(key)
|
|
278
|
+
if (keySet.size !== want.size) {
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
for (const field of keySet) {
|
|
282
|
+
if (!want.has(field)) {
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return true
|
|
287
|
+
})
|
|
288
|
+
if (matched) {
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
const available = roster.length === 0 ? "none" : roster.map((key) => `(${key.join(", ")})`).join("; ")
|
|
292
|
+
const pointwise = face.projection.some(function carriesInterval(fieldName) {
|
|
293
|
+
const descriptor = sealedFieldOf(face.owner, fieldName)
|
|
294
|
+
return descriptor !== undefined && descriptor.kind === "interval"
|
|
295
|
+
})
|
|
296
|
+
const hint = pointwise ? "; hint: declare the exact pointwise key `R(prefix…, interval) -> R`" : ""
|
|
297
|
+
throw errors.new(
|
|
298
|
+
`schema ${name}: ${rendered}: target projection (${face.projection.join(", ")}) matches no declared key of ${face.owner.name} — available keys: ${available}${hint}`
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
type SchemaRelation = AnyRelation | AnyClosed
|
|
303
|
+
|
|
304
|
+
type SchemaRelations = Record<string, SchemaRelation>
|
|
305
|
+
|
|
236
306
|
interface Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> {
|
|
237
307
|
readonly name: string
|
|
238
308
|
readonly relations: Rels
|
|
@@ -240,56 +310,12 @@ interface Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = S
|
|
|
240
310
|
readonly classes: Classes
|
|
241
311
|
}
|
|
242
312
|
|
|
243
|
-
/** Any schema value, whatever its relation record. */
|
|
244
313
|
type AnySchema = Schema<SchemaRelations>
|
|
245
314
|
|
|
246
|
-
/**
|
|
247
|
-
* Forces the class map to EVALUATE at the `schema()` boundary: a two-level
|
|
248
|
-
* mapped copy, type-identical to `C` — but instantiation resolves it to
|
|
249
|
-
* the finished relation → field → class record, so hovering a schema value
|
|
250
|
-
* (or anything carrying its `Classes` parameter — queries, `Db`) shows the
|
|
251
|
-
* computed record instead of the unevaluated `ClassesOf<...>` application
|
|
252
|
-
* dragging the whole statement-tuple type along. The conditional wrapper
|
|
253
|
-
* is the display mechanism, not a judgment: resolving it drops the alias
|
|
254
|
-
* reference, so tsc renders the finished record (measured against tsc's
|
|
255
|
-
* own type rendering; a bare mapped alias still displays by name).
|
|
256
|
-
* Display-only by construction; `classesComplete` guards the same type at
|
|
257
|
-
* the value tier.
|
|
258
|
-
*/
|
|
259
315
|
type EvaluatedClasses<C extends SchemaClasses> = C extends SchemaClasses
|
|
260
316
|
? { readonly [N in keyof C]: { readonly [F in keyof C[N]]: C[N][F] } }
|
|
261
317
|
: never
|
|
262
318
|
|
|
263
|
-
/**
|
|
264
|
-
* Assembles a theory:
|
|
265
|
-
* `schema("Ledger", { Kind, Account, Holder }, [ ...statements ])`.
|
|
266
|
-
*
|
|
267
|
-
* Rejected here, each with the offending statement rendered canonically:
|
|
268
|
-
* a record key differing from its relation's declared name; a statement
|
|
269
|
-
* whose relation is not (identically) a member of the record; an explicit
|
|
270
|
-
* duplicate of a fresh-implied or closedness-implied key (macro parity:
|
|
271
|
-
* "redundant here — and rejected as a duplicate"); a duplicate statement
|
|
272
|
-
* (two statements rendering to one canonical utterance ARE one judgment);
|
|
273
|
-
* a handle selection that its roster does not hold (belt-and-braces —
|
|
274
|
-
* the type level already blocks it); and a handle selection whose closed
|
|
275
|
-
* reference no declared containment resolves (the engine's canonical
|
|
276
|
-
* renderer would print the raw row id where `renderStatement` prints the
|
|
277
|
-
* handle — the paste-back law demands the two spellings agree).
|
|
278
|
-
*
|
|
279
|
-
* The fresh-implied and closed auto-keys are NOT added to the statement
|
|
280
|
-
* list: the engine materializes them itself, in its own pinned order
|
|
281
|
-
* (`SchemaDescriptor::materialized_statements`), and restating them would
|
|
282
|
-
* double them.
|
|
283
|
-
*
|
|
284
|
-
* THE LAW-TYPING happens here too (rulings 2/3 — the laws type the
|
|
285
|
-
* columns): the statement list induces the equivalence classes over field
|
|
286
|
-
* slots, at the TYPE level ({@link ClassesOf} — spell the statement list
|
|
287
|
-
* inline so the tuple type stays precise) and at runtime (the union-find
|
|
288
|
-
* twin), and the one-generator-per-class wall holds at both tiers — the
|
|
289
|
-
* {@link LawfulStatements} verdict lands the compile error on the
|
|
290
|
-
* statements argument; `computeClasses` throws the same content naming the
|
|
291
|
-
* exact statement.
|
|
292
|
-
*/
|
|
293
319
|
function schema<const Rels extends SchemaRelations, const Stmts extends readonly Statement[]>(
|
|
294
320
|
name: string,
|
|
295
321
|
relations: Rels,
|
|
@@ -298,12 +324,6 @@ function schema<const Rels extends SchemaRelations, const Stmts extends readonly
|
|
|
298
324
|
const implied = collectImplied(name, relations)
|
|
299
325
|
const seen = new Set<string>()
|
|
300
326
|
for (const statement of statements) {
|
|
301
|
-
/**
|
|
302
|
-
* The untyped caller's half of the admission brand: the type tier
|
|
303
|
-
* already refuses an unbranded structural literal, and this probe
|
|
304
|
-
* refuses the same forgery at runtime — a statement that skipped the
|
|
305
|
-
* construction-time arity and roster walls never enters the theory.
|
|
306
|
-
*/
|
|
307
327
|
if (!isStatement(statement)) {
|
|
308
328
|
throw errors.new(
|
|
309
329
|
`schema ${name}: a statement is minted only by key/contained/mirrors/capacity — a structural literal skips the construction-time arity and roster walls`
|
|
@@ -311,7 +331,7 @@ function schema<const Rels extends SchemaRelations, const Stmts extends readonly
|
|
|
311
331
|
}
|
|
312
332
|
const rendered = renderStatement(statement)
|
|
313
333
|
verifyMembership(name, relations, statement, rendered)
|
|
314
|
-
if (implied.has(rendered)) {
|
|
334
|
+
if (implied.rendered.has(rendered)) {
|
|
315
335
|
throw errors.new(
|
|
316
336
|
`schema ${name}: ${rendered} is redundant here (the fresh mark or closedness already implies it) — and rejected as a duplicate`
|
|
317
337
|
)
|
|
@@ -323,6 +343,7 @@ function schema<const Rels extends SchemaRelations, const Stmts extends readonly
|
|
|
323
343
|
verifyHandles(name, statement, rendered)
|
|
324
344
|
}
|
|
325
345
|
verifyClosedReferences(name, statements)
|
|
346
|
+
verifyTargetKeys(name, statements, implied.roster)
|
|
326
347
|
const classes = computeClasses(name, relations, statements)
|
|
327
348
|
if (!classesComplete<EvaluatedClasses<ClassesOf<Rels, Stmts>>>(classes, relations)) {
|
|
328
349
|
throw errors.new(`schema ${name}: class-map construction incomplete`)
|
package/src/spec.ts
CHANGED
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The lowered wire shapes — a 1:1 TypeScript mirror of bumbledb's
|
|
3
|
-
* `SchemaSpec` bindings contract (PRD-01;
|
|
4
|
-
* `bumbledb/crates/bumbledb/src/schema/spec.rs`): a schema as named plain
|
|
5
|
-
* data, relations and dependency statements each in declaration order (the
|
|
6
|
-
* declaration-order law that mints every id). The SDK's `lower()` emits
|
|
7
|
-
* these values; the napi bridge (PRD-04) marshals them into the Rust
|
|
8
|
-
* `SchemaSpec` verbatim, and the engine's own judge (`SchemaSpec::descriptor`
|
|
9
|
-
* name resolution + `SchemaDescriptor::validate` at `Db.create`/`Db.open`)
|
|
10
|
-
* stays the single semantic authority — the SDK lowers, it never re-judges.
|
|
11
|
-
*
|
|
12
|
-
* Every u64 crosses as `bigint`, never `number` (PRD-04's marshaling law: no
|
|
13
|
-
* 53-bit hazards, no branch). Object keys are always written in one fixed
|
|
14
|
-
* literal order per shape, so serialization of a lowered schema is
|
|
15
|
-
* deterministic (byte-stable) by construction.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* A structural value type — the one type vocabulary of the `schema!` field
|
|
20
|
-
* grammar (`ValueType` in Rust): `bool`, `u64`, `i64`, `str` (`string`
|
|
21
|
-
* here), `bytes<N>` (`fixedBytes`), and the interval family (`width:
|
|
22
|
-
* undefined` is the general 16-byte encoding with rays representable;
|
|
23
|
-
* `width: w` is the fixed-width `interval<E, w>` whose encoding stores only
|
|
24
|
-
* the start).
|
|
25
|
-
*/
|
|
26
1
|
type ValueTypeSpec =
|
|
27
2
|
| { readonly kind: "bool" }
|
|
28
3
|
| { readonly kind: "u64" }
|
|
@@ -35,12 +10,6 @@ type ValueTypeSpec =
|
|
|
35
10
|
readonly width: bigint | undefined
|
|
36
11
|
}
|
|
37
12
|
|
|
38
|
-
/**
|
|
39
|
-
* One plain engine value as carried by a lowered literal — the mirror of
|
|
40
|
-
* `bumbledb::Value` restricted to the schema-literal vocabulary (Allen masks
|
|
41
|
-
* are query-side values, never schema literals). Intervals are half-open
|
|
42
|
-
* `[start, end)`.
|
|
43
|
-
*/
|
|
44
13
|
type ValueSpec =
|
|
45
14
|
| { readonly kind: "bool"; readonly value: boolean }
|
|
46
15
|
| { readonly kind: "u64"; readonly value: bigint }
|
|
@@ -50,84 +19,35 @@ type ValueSpec =
|
|
|
50
19
|
| { readonly kind: "intervalU64"; readonly start: bigint; readonly end: bigint }
|
|
51
20
|
| { readonly kind: "intervalI64"; readonly start: bigint; readonly end: bigint }
|
|
52
21
|
|
|
53
|
-
/**
|
|
54
|
-
* One literal as spelled: a plain value, or a closed relation's handle by
|
|
55
|
-
* name (the `| status == Frozen` spelling) — resolved by the engine through
|
|
56
|
-
* the selected field's newtype to the handle's declaration-order row id,
|
|
57
|
-
* exactly as the macro resolves it at expansion.
|
|
58
|
-
*/
|
|
59
22
|
type LiteralSpec =
|
|
60
23
|
| { readonly kind: "value"; readonly value: ValueSpec }
|
|
61
24
|
| { readonly kind: "handle"; readonly handle: string }
|
|
62
25
|
|
|
63
|
-
/**
|
|
64
|
-
* One σ binding's right side: a single literal or a literal set (read
|
|
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`
|
|
68
|
-
* always carries ≥ 2 literals.
|
|
69
|
-
*/
|
|
70
26
|
type LiteralSetSpec =
|
|
71
27
|
| { readonly kind: "one"; readonly literal: LiteralSpec }
|
|
72
28
|
| { readonly kind: "many"; readonly literals: readonly LiteralSpec[] }
|
|
73
29
|
|
|
74
|
-
/**
|
|
75
|
-
* One side of a containment or capacity statement:
|
|
76
|
-
* `R(fields… | field == literal…)`, all names. `projection` is π in the
|
|
77
|
-
* statement's written order (positional pairing with the other side);
|
|
78
|
-
* `selection` is σ as (field, literal-or-set) pairs, read conjunctively.
|
|
79
|
-
*/
|
|
80
30
|
interface SideSpec {
|
|
81
31
|
readonly relation: string
|
|
82
32
|
readonly projection: readonly string[]
|
|
83
33
|
readonly selection: ReadonlyArray<readonly [string, LiteralSetSpec]>
|
|
84
34
|
}
|
|
85
35
|
|
|
86
|
-
/**
|
|
87
|
-
* One capacity bound: a non-negative literal, a u64 field of the TARGET
|
|
88
|
-
* row (the dependent bound — per-group capacity read at judge time), or
|
|
89
|
-
* the interval-measure of a TARGET-row field (`Duration(span)`). Names,
|
|
90
|
-
* not ids — the spec is the name-level wire; the engine resolves bound
|
|
91
|
-
* names against the target's FULL roster (C1), never the projection.
|
|
92
|
-
*/
|
|
93
36
|
type CapacityBoundSpec =
|
|
94
37
|
| { readonly kind: "lit"; readonly value: bigint }
|
|
95
38
|
| { readonly kind: "field"; readonly field: string }
|
|
96
39
|
| { readonly kind: "durationField"; readonly field: string }
|
|
97
40
|
|
|
98
|
-
/**
|
|
99
|
-
* A capacity statement's weight — a TOTAL sum (C4: `unit` is a case, not
|
|
100
|
-
* an absence): the count instance (`unit`), a u64 field of the SOURCE row
|
|
101
|
-
* (`field`), or a SOURCE-row interval's measure (`durationField`). The
|
|
102
|
-
* wire always carries it — a unit statement crosses as `{ kind: "unit" }`,
|
|
103
|
-
* never by omission.
|
|
104
|
-
*/
|
|
105
41
|
type WeightSpec =
|
|
106
42
|
| { readonly kind: "unit" }
|
|
107
43
|
| { readonly kind: "field"; readonly field: string }
|
|
108
44
|
| { readonly kind: "durationField"; readonly field: string }
|
|
109
45
|
|
|
110
|
-
/**
|
|
111
|
-
* A capacity statement's window — the canonical-utterance law's surviving
|
|
112
|
-
* spellings, per-aggregate where weight-sensitive (design § 6), since the
|
|
113
|
-
* SDK's `within()` mint makes every banned spelling unwritable or a
|
|
114
|
-
* construction error: `exact` is `{n}` (`{0}` the exclusion on the unit
|
|
115
|
-
* instance, "total is zero" on a weighted one), `range` is `{lo..hi}` with
|
|
116
|
-
* lo < hi (`{0..hi}` the canonical ceiling; the hi slot admits a dependent
|
|
117
|
-
* bound — C6: hi only), `floor` is `{lo..*}` (`{1..*}` legal on weighted
|
|
118
|
-
* statements only).
|
|
119
|
-
*/
|
|
120
46
|
type CapacityWindowSpec =
|
|
121
47
|
| { readonly kind: "exact"; readonly n: CapacityBoundSpec }
|
|
122
48
|
| { readonly kind: "range"; readonly lo: CapacityBoundSpec; readonly hi: CapacityBoundSpec }
|
|
123
49
|
| { readonly kind: "floor"; readonly lo: CapacityBoundSpec }
|
|
124
50
|
|
|
125
|
-
/**
|
|
126
|
-
* One field: name, structural type, host newtype name — the field's
|
|
127
|
-
* DOMAIN (the macro's declared `as NewType`; the SDK's law-computed class
|
|
128
|
-
* name), carried for handle resolution only, dropped by the engine at
|
|
129
|
-
* descriptor lowering and never fingerprinted — and the `fresh` mint mark.
|
|
130
|
-
*/
|
|
131
51
|
interface FieldSpec {
|
|
132
52
|
readonly name: string
|
|
133
53
|
readonly valueType: ValueTypeSpec
|
|
@@ -135,10 +55,6 @@ interface FieldSpec {
|
|
|
135
55
|
readonly fresh: boolean
|
|
136
56
|
}
|
|
137
57
|
|
|
138
|
-
/**
|
|
139
|
-
* One ground axiom of a closed relation: the handle plus one literal per
|
|
140
|
-
* declared intrinsic column, in field-declaration order (row id = index).
|
|
141
|
-
*/
|
|
142
58
|
interface RowSpec {
|
|
143
59
|
readonly handle: string
|
|
144
60
|
readonly values: readonly LiteralSpec[]
|
|
@@ -159,27 +75,12 @@ interface ClosedSpec {
|
|
|
159
75
|
readonly rows: readonly RowSpec[]
|
|
160
76
|
}
|
|
161
77
|
|
|
162
|
-
/**
|
|
163
|
-
* One relation. A present `closed` declares it closed (the option is the
|
|
164
|
-
* kind, one sum — R7); a closed relation's `fields` are its declared
|
|
165
|
-
* intrinsic columns only — the synthetic (`id`, u64) handle field is
|
|
166
|
-
* materialized by the engine's schema validation.
|
|
167
|
-
*/
|
|
168
78
|
interface RelationSpec {
|
|
169
79
|
readonly name: string
|
|
170
80
|
readonly fields: readonly FieldSpec[]
|
|
171
81
|
readonly closed: ClosedSpec | undefined
|
|
172
82
|
}
|
|
173
83
|
|
|
174
|
-
/**
|
|
175
|
-
* One dependency statement, tagged by form. `==` is not a variant: a
|
|
176
|
-
* bidirectional containment is `containment` with `bidirectional: true`,
|
|
177
|
-
* lowered by the engine to the two adjacent containments (`source <=
|
|
178
|
-
* target` first). `capacity` reads as the operator does (C2 — target,
|
|
179
|
-
* weight, window, source): the target is the per-group parent, the source
|
|
180
|
-
* is the weighed side, and the weight is ALWAYS present (`unit` the count
|
|
181
|
-
* instance).
|
|
182
|
-
*/
|
|
183
84
|
type StatementSpec =
|
|
184
85
|
| { readonly kind: "fd"; readonly relation: string; readonly projection: readonly string[] }
|
|
185
86
|
| {
|
|
@@ -196,38 +97,15 @@ type StatementSpec =
|
|
|
196
97
|
readonly source: SideSpec
|
|
197
98
|
}
|
|
198
99
|
|
|
199
|
-
/**
|
|
200
|
-
* The whole theory as named plain data — what `lower()` produces and what
|
|
201
|
-
* the bridge's `dbCreate`/`dbOpen` take. Both lists are in declaration
|
|
202
|
-
* order. Only DECLARED statements appear: the engine materializes the
|
|
203
|
-
* fresh-implied and closed auto-keys itself
|
|
204
|
-
* (`SchemaDescriptor::materialized_statements` — fresh keys first, closed
|
|
205
|
-
* auto-keys second, declared statements last), so re-stating them here
|
|
206
|
-
* would double them and change the fingerprint.
|
|
207
|
-
*/
|
|
208
100
|
interface SchemaSpec {
|
|
209
101
|
readonly relations: readonly RelationSpec[]
|
|
210
102
|
readonly statements: readonly StatementSpec[]
|
|
211
103
|
}
|
|
212
104
|
|
|
213
|
-
/**
|
|
214
|
-
* The characters Rust's `char::escape_debug` (the engine renderer's string
|
|
215
|
-
* formatter, `schema/render.rs` `literal`) escapes as `\u{…}`: everything
|
|
216
|
-
* non-printable per rustc's generated tables — the C categories (Cc, Cf,
|
|
217
|
-
* Cs, Co, Cn) and the Z separators (Zs, Zl, Zp) except U+0020 itself
|
|
218
|
-
* (`library/core/src/unicode/printable.py`).
|
|
219
|
-
*/
|
|
220
105
|
const NON_PRINTABLE = /[\p{C}\p{Z}]/u
|
|
221
106
|
|
|
222
|
-
/** Grapheme-extending characters, which `char::escape_debug` always escapes even when printable. */
|
|
223
107
|
const GRAPHEME_EXTEND = /\p{Grapheme_Extend}/u
|
|
224
108
|
|
|
225
|
-
/**
|
|
226
|
-
* One char exactly as Rust's `char::escape_debug` spells it (the engine
|
|
227
|
-
* renders strings char by char through it): `\0`, `\t`, `\r`, `\n`,
|
|
228
|
-
* backslash-escaped `\\`/`\'`/`\"`, `\u{hex}` (lowercase, unpadded) for
|
|
229
|
-
* grapheme-extending and non-printable chars, the char itself otherwise.
|
|
230
|
-
*/
|
|
231
109
|
function escapeDebugChar(ch: string): string {
|
|
232
110
|
if (ch === "\0") {
|
|
233
111
|
return "\\0"
|
|
@@ -254,12 +132,6 @@ function escapeDebugChar(ch: string): string {
|
|
|
254
132
|
return ch
|
|
255
133
|
}
|
|
256
134
|
|
|
257
|
-
/**
|
|
258
|
-
* One byte exactly as Rust's `u8::escape_ascii` spells it (the engine
|
|
259
|
-
* renders `bytes<N>` literals byte by byte through it): `\t`, `\r`, `\n`,
|
|
260
|
-
* `\\`, `\'`, `\"` as two-char escapes, printable ASCII (0x20–0x7e)
|
|
261
|
-
* verbatim, everything else `\xNN` lowercase.
|
|
262
|
-
*/
|
|
263
135
|
function escapeAsciiByte(byte: number): string {
|
|
264
136
|
if (byte === 0x09) {
|
|
265
137
|
return "\\t"
|
|
@@ -285,16 +157,6 @@ function escapeAsciiByte(byte: number): string {
|
|
|
285
157
|
return `\\x${byte.toString(16).padStart(2, "0")}`
|
|
286
158
|
}
|
|
287
159
|
|
|
288
|
-
/**
|
|
289
|
-
* Renders one lowered literal in the exact macro spelling the engine's own
|
|
290
|
-
* renderer uses (`schema/render.rs` `literal`): handles bare by name,
|
|
291
|
-
* integers as digits, `true`/`false`, intervals as `start..end`, strings
|
|
292
|
-
* char-escaped through the `char::escape_debug` mirror, bytes as `b"…"`
|
|
293
|
-
* byte-escaped through the `u8::escape_ascii` mirror — byte-for-byte the
|
|
294
|
-
* engine's violation canonicals, so TS-side construction errors and
|
|
295
|
-
* engine-side violations read identically and `renderStatement` equals the
|
|
296
|
-
* violation's `canonical`.
|
|
297
|
-
*/
|
|
298
160
|
function renderLiteral(literal: LiteralSpec): string {
|
|
299
161
|
if (literal.kind === "handle") {
|
|
300
162
|
return literal.handle
|
|
@@ -326,10 +188,6 @@ function renderLiteral(literal: LiteralSpec): string {
|
|
|
326
188
|
}
|
|
327
189
|
}
|
|
328
190
|
|
|
329
|
-
/**
|
|
330
|
-
* Renders one σ binding's right side: a bare literal, or a disjunctive
|
|
331
|
-
* literal set in braces (`{A, B}`).
|
|
332
|
-
*/
|
|
333
191
|
function renderLiteralSet(set: LiteralSetSpec): string {
|
|
334
192
|
if (set.kind === "one") {
|
|
335
193
|
return renderLiteral(set.literal)
|
|
@@ -337,11 +195,6 @@ function renderLiteralSet(set: LiteralSetSpec): string {
|
|
|
337
195
|
return `{${set.literals.map(renderLiteral).join(", ")}}`
|
|
338
196
|
}
|
|
339
197
|
|
|
340
|
-
/**
|
|
341
|
-
* Renders one capacity bound in its one canonical spelling: a literal as
|
|
342
|
-
* digits, a dependent bound bare by field name, an interval-measure bound
|
|
343
|
-
* as `Duration(field)` — the spellings the engine's renderer emits.
|
|
344
|
-
*/
|
|
345
198
|
function renderCapacityBound(bound: CapacityBoundSpec): string {
|
|
346
199
|
switch (bound.kind) {
|
|
347
200
|
case "lit":
|
|
@@ -353,12 +206,6 @@ function renderCapacityBound(bound: CapacityBoundSpec): string {
|
|
|
353
206
|
}
|
|
354
207
|
}
|
|
355
208
|
|
|
356
|
-
/**
|
|
357
|
-
* Renders a capacity window in its one canonical spelling: `{n}` exact
|
|
358
|
-
* (`{0}` the exclusion), `{lo..hi}`, `{lo..*}` — the spelling set the
|
|
359
|
-
* engine's renderer emits for sealed statements, bounds through
|
|
360
|
-
* {@link renderCapacityBound}.
|
|
361
|
-
*/
|
|
362
209
|
function renderCapacityWindow(window: CapacityWindowSpec): string {
|
|
363
210
|
switch (window.kind) {
|
|
364
211
|
case "exact":
|
|
@@ -370,12 +217,6 @@ function renderCapacityWindow(window: CapacityWindowSpec): string {
|
|
|
370
217
|
}
|
|
371
218
|
}
|
|
372
219
|
|
|
373
|
-
/**
|
|
374
|
-
* Renders a capacity weight as the operator's bracket: the unit weight
|
|
375
|
-
* renders NOTHING — the count utterance `<={lo..hi}` falls out of the one
|
|
376
|
-
* printer, never a second "legacy" arm — a field weight as `[field]`, an
|
|
377
|
-
* interval measure as `[Duration(field)]`.
|
|
378
|
-
*/
|
|
379
220
|
function renderWeight(weight: WeightSpec): string {
|
|
380
221
|
switch (weight.kind) {
|
|
381
222
|
case "unit":
|
|
@@ -403,4 +244,4 @@ export type {
|
|
|
403
244
|
ValueTypeSpec,
|
|
404
245
|
WeightSpec
|
|
405
246
|
}
|
|
406
|
-
export {
|
|
247
|
+
export { renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight }
|