@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.
Files changed (103) hide show
  1. package/COOKBOOK.md +33 -49
  2. package/README.md +3 -3
  3. package/dist/capacity.d.ts +24 -136
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +18 -40
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/closed.d.ts +0 -156
  8. package/dist/closed.d.ts.map +1 -1
  9. package/dist/closed.js +0 -104
  10. package/dist/closed.js.map +1 -1
  11. package/dist/db.d.ts +7 -223
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +147 -396
  14. package/dist/db.js.map +1 -1
  15. package/dist/face.d.ts +0 -133
  16. package/dist/face.d.ts.map +1 -1
  17. package/dist/face.js +0 -33
  18. package/dist/face.js.map +1 -1
  19. package/dist/fields.d.ts +1 -145
  20. package/dist/fields.d.ts.map +1 -1
  21. package/dist/fields.js +2 -91
  22. package/dist/fields.js.map +1 -1
  23. package/dist/index.d.ts +12 -15
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +10 -13
  26. package/dist/index.js.map +1 -1
  27. package/dist/law.d.ts +111 -93
  28. package/dist/law.d.ts.map +1 -1
  29. package/dist/law.js +23 -27
  30. package/dist/law.js.map +1 -1
  31. package/dist/lower.d.ts +9 -35
  32. package/dist/lower.d.ts.map +1 -1
  33. package/dist/lower.js +8 -53
  34. package/dist/lower.js.map +1 -1
  35. package/dist/marshal.d.ts +0 -65
  36. package/dist/marshal.d.ts.map +1 -1
  37. package/dist/marshal.js +0 -72
  38. package/dist/marshal.js.map +1 -1
  39. package/dist/native.d.ts +31 -289
  40. package/dist/native.d.ts.map +1 -1
  41. package/dist/native.js +15 -64
  42. package/dist/native.js.map +1 -1
  43. package/dist/query/atom.d.ts +10 -276
  44. package/dist/query/atom.d.ts.map +1 -1
  45. package/dist/query/atom.js +1 -96
  46. package/dist/query/atom.js.map +1 -1
  47. package/dist/query/find.d.ts +10 -76
  48. package/dist/query/find.d.ts.map +1 -1
  49. package/dist/query/find.js +0 -30
  50. package/dist/query/find.js.map +1 -1
  51. package/dist/query/lower.d.ts +58 -146
  52. package/dist/query/lower.d.ts.map +1 -1
  53. package/dist/query/lower.js +19 -256
  54. package/dist/query/lower.js.map +1 -1
  55. package/dist/query/parse-ir.d.ts +0 -7
  56. package/dist/query/parse-ir.d.ts.map +1 -1
  57. package/dist/query/parse-ir.js +1 -13
  58. package/dist/query/parse-ir.js.map +1 -1
  59. package/dist/query/run.d.ts +0 -36
  60. package/dist/query/run.d.ts.map +1 -1
  61. package/dist/query/run.js +0 -44
  62. package/dist/query/run.js.map +1 -1
  63. package/dist/query/scope.d.ts +24 -180
  64. package/dist/query/scope.d.ts.map +1 -1
  65. package/dist/query/scope.js +2 -66
  66. package/dist/query/scope.js.map +1 -1
  67. package/dist/relation.d.ts +2 -50
  68. package/dist/relation.d.ts.map +1 -1
  69. package/dist/relation.js +2 -37
  70. package/dist/relation.js.map +1 -1
  71. package/dist/schema.d.ts +13 -63
  72. package/dist/schema.d.ts.map +1 -1
  73. package/dist/schema.js +118 -92
  74. package/dist/schema.js.map +1 -1
  75. package/dist/spec.d.ts +1 -140
  76. package/dist/spec.d.ts.map +1 -1
  77. package/dist/spec.js +1 -68
  78. package/dist/spec.js.map +1 -1
  79. package/dist/statements.d.ts +6 -137
  80. package/dist/statements.d.ts.map +1 -1
  81. package/dist/statements.js +16 -119
  82. package/dist/statements.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/capacity.ts +26 -140
  85. package/src/closed.ts +5 -206
  86. package/src/db.ts +203 -692
  87. package/src/face.ts +0 -142
  88. package/src/fields.ts +4 -172
  89. package/src/index.ts +10 -15
  90. package/src/law.ts +201 -129
  91. package/src/lower.ts +8 -53
  92. package/src/marshal.ts +1 -85
  93. package/src/native.ts +58 -321
  94. package/src/query/atom.ts +26 -313
  95. package/src/query/find.ts +24 -110
  96. package/src/query/lower.ts +126 -377
  97. package/src/query/parse-ir.ts +1 -14
  98. package/src/query/run.ts +0 -45
  99. package/src/query/scope.ts +25 -186
  100. package/src/relation.ts +2 -66
  101. package/src/schema.ts +143 -122
  102. package/src/spec.ts +1 -160
  103. package/src/statements.ts +22 -174
package/src/schema.ts CHANGED
@@ -1,12 +1,18 @@
1
1
  /**
2
- * `schema()` — assembles relations and statements into a theory value (the
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 and nothing more:
5
- * membership, implied-key duplicates, duplicate statements, and a
6
- * belt-and-braces handle re-verification. Everything semantically deeper
7
- * (containment targets resolving declared keys, fresh-on-u64, …) is
8
- * DELIBERATELY left to the engine's `SchemaError` at `Db.create` — the
9
- * same judge, the same two-boundary split as Rust.
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
- * Validates the relation record and collects the implied keys: the
24
- * fresh-implied `R(field) -> R` per minted field and the closed auto-key
25
- * `R(id) -> R` per closed relation, each rendered canonically so an
26
- * explicit duplicate is caught by string identity with the renderer as the
27
- * single spelling authority.
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 implied = new Set<string>()
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
- implied.add(`${member.name}(id) -> ${member.name}`)
41
- continue
42
- }
43
- for (const declared of member.data.fields) {
44
- if ("fresh" in declared.field && declared.field.fresh === true) {
45
- implied.add(`${member.name}(${declared.name}) -> ${member.name}`)
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 implied
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
- /** One member of a schema's relation record. */
221
- type SchemaRelation = AnyRelation | AnyClosed
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
- /** The relation record a schema is generic over — what `Db` and queries key on. */
224
- type SchemaRelations = Record<string, SchemaRelation>
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
- * A theory value: named relations, the DECLARED dependency statements, and
228
- * the LAW-COMPUTED class map (`classes` — relation → field → class name,
229
- * `undefined` = bare). The class map is THE domain authority: `schema()`
230
- * computes it FROM the statement list at both tiers (the type through
231
- * {@link ClassesOf}, the value through the union-find twin), queries
232
- * compare class names off it, and the wire lowering emits it as the spec
233
- * `newtype` labels. Nothing is ever synthesized: `statements` is exactly
234
- * the declared list, in written order.
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 { renderCapacityBound, renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight }
247
+ export { renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight }