@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/law.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The law-typing engine (owner ruling 2026-07-18, "option 2, zero debate"):
3
- * THE LAWS TYPE THE COLUMNS. Domains are declared nowhere — `schema()`
3
+ * THE LAWS TYPE THE COLUMNS. Domains are declared nowhere — `schema`
4
4
  * computes every field's domain FROM the statement list, at BOTH the type
5
5
  * level (this module's type machinery, reading the statements tuple type)
6
6
  * and at runtime (a plain union-find over the same pairs), and the two
@@ -8,37 +8,41 @@
8
8
  *
9
9
  * The three class laws (ratified; implemented exactly):
10
10
  *
11
- * 1. GENERATORS — a `fresh` field is a generator and names its class by
12
- * its declaration coordinate (`"Account.id"`); a closed relation's
13
- * synthetic id is a generator named `"Kind.id"`.
14
- * 2. GENERATOR-LESS classes are named by their least member coordinate
15
- * in relation-declaration × field-declaration order (readable off the
16
- * relation record and each member's frozen field list at the VALUE
17
- * tier — deterministic, pinned forever; the wire reads only this
18
- * tier). At the TYPE tier the same class is carried as its
19
- * member-coordinate SET (see {@link ClassOfCoord}): TypeScript's
20
- * union member order is not observably deterministic, so a type-level
21
- * least-member pick would drift between compilations — the set is the
22
- * canonical deterministic spelling, the runtime name is always a
23
- * member of it, and the join judgment is identical at both tiers.
24
- * 3. BARE — a field in no law has NO class and pairs only with bare in
25
- * queries (the deliberate sum-domain pointer stays legal).
11
+ * 1. GENERATORS — a `fresh` field is a generator and names its class by
12
+ * its declaration coordinate (`"Account.id"`); a closed relation's
13
+ * synthetic id is a generator named `"Kind.id"`.
14
+ * 2. GENERATOR-LESS classes are named by their least member coordinate
15
+ * in relation-declaration × field-declaration order (readable off the
16
+ * relation record and each member's frozen field list at the VALUE
17
+ * tier — deterministic, pinned forever; the wire reads only this
18
+ * tier). At the TYPE tier the same class is carried as its
19
+ * member-coordinate SET (see {@link ClassOfCoord}): TypeScript's
20
+ * union member order is not observably deterministic, so a type-level
21
+ * least-member pick would drift between compilations — the set is the
22
+ * canonical deterministic spelling, the runtime name is always a
23
+ * member of it, and the join judgment is identical at both tiers.
24
+ * 3. BARE — a field in no law has NO class and pairs only with bare in
25
+ * queries (the deliberate sum-domain pointer stays legal).
26
26
  *
27
27
  * THE WALL: a class containing more than one generator is a contradiction
28
28
  * (two mints cannot share a carrier) — a schema-level COMPILE error (the
29
29
  * named, self-locating {@link ClassWall}: which generator coordinates
30
30
  * collided, through which paired slots) with a construction-time runtime
31
31
  * twin (`computeClasses` throws with the same content, naming the exact
32
- * statement).
32
+ * statement). The TARGET-KEY WALL ({@link TargetKeyWall},
33
+ * 60-containment-parity) rides the same constraint seam: a containment/
34
+ * mirrors/capacity target projection that resolves no key of its relation
35
+ * is the same kind of schema-level compile error, with `schema`'s
36
+ * `verifyTargetKeys` as its authoritative runtime twin.
33
37
  *
34
38
  * Every paired face of the statement tuple unions its positionwise field
35
39
  * slots: containment (ψ-selected targets included — a selection changes
36
40
  * pairing not at all), the `==` bijection, and capacity source/target pairs.
37
- * `key()` statements pair nothing (an FD constrains one relation's own
41
+ * `key` statements pair nothing (an FD constrains one relation's own
38
42
  * rows; it identifies no carriers).
39
43
  *
40
44
  * The type tier reads pairs off the statement types' exact face data, so
41
- * spell the statement list INLINE in `schema()` (the `const` type
45
+ * spell the statement list INLINE in `schema` (the `const` type
42
46
  * parameter keeps the tuple precise). A widened `Statement[]` list
43
47
  * degrades the TYPE tier to generators-only (no pair is readable off a
44
48
  * widened type) — the runtime map stays complete and authoritative, and
@@ -58,75 +62,42 @@ import type { AnyRelation, RelationFields } from "#relation.ts"
58
62
  import type { SchemaRelation, SchemaRelations } from "#schema.ts"
59
63
  import { renderStatement, type Statement } from "#statements.ts"
60
64
 
61
- // ————————————————————————————————————————————————————————————————————————
62
- // The class-map shapes.
63
- // ————————————————————————————————————————————————————————————————————————
64
-
65
- /** One relation's computed classes: field name → class name, `undefined` = bare. */
66
65
  type RelationClasses = { readonly [field: string]: string | undefined }
67
66
 
68
- /**
69
- * The class map a schema carries — relation name → field name → the
70
- * computed class name (`undefined` = bare). THE domain authority: queries
71
- * and the wire lowering read domains from here and nowhere else. The wide
72
- * shape is the default every `Schema`-generic surface accepts; a concrete
73
- * schema's `classes` property carries the EXACT computed map
74
- * ({@link ClassesOf}) at the type level and the frozen runtime twin
75
- * (`computeClasses`) at the value level — one property, two tiers, one
76
- * computation.
77
- */
78
67
  type SchemaClasses = { readonly [relation: string]: RelationClasses }
79
68
 
80
- /** Looks one relation's class record up in a schema's class map (absent relation = no classes). */
81
69
  type ClassRecordOf<Classes extends SchemaClasses, N extends string> = N extends keyof Classes
82
70
  ? Classes[N]
83
71
  : Record<never, never>
84
72
 
85
- /** Looks one field's class up in a relation's class record (absent field = bare). */
86
73
  type ClassLookup<CR, K> = K extends keyof CR ? CR[K] & (string | undefined) : undefined
87
74
 
88
- // ————————————————————————————————————————————————————————————————————————
89
- // Coordinates.
90
- // ————————————————————————————————————————————————————————————————————————
91
-
92
- /** A closed member's declared payload-column record (`never` on ordinary relations). */
93
75
  type MemberColumns<M> = M extends AnyClosed ? M["columns"] : never
94
76
 
95
- /** The field names of one schema member: a relation's declared fields; a closed relation's sealed `id` + columns. */
96
77
  type MemberFieldNames<M extends SchemaRelation> = M extends AnyClosed
97
78
  ? "id" | (keyof MemberColumns<M> & string)
98
79
  : M extends AnyRelation
99
80
  ? keyof RelationFields<M> & string
100
81
  : never
101
82
 
102
- /** The fresh-marked field names of one field block. */
103
83
  type FreshFieldNames<Fields> = {
104
84
  [F in keyof Fields & string]: Fields[F] extends { readonly fresh: true } ? F : never
105
85
  }[keyof Fields & string]
106
86
 
107
- /** One member's generator coordinates: fresh fields; a closed relation's synthetic id. */
108
87
  type MemberGenerators<N extends string, M extends SchemaRelation> = M extends AnyClosed
109
88
  ? `${N}.id`
110
89
  : M extends AnyRelation
111
90
  ? `${N}.${FreshFieldNames<RelationFields<M>>}`
112
91
  : never
113
92
 
114
- /** Every generator coordinate of a relation record (a union). */
115
93
  type GeneratorsOf<Rels extends SchemaRelations> = {
116
94
  [N in keyof Rels & string]: MemberGenerators<N, Rels[N]>
117
95
  }[keyof Rels & string]
118
96
 
119
- // ————————————————————————————————————————————————————————————————————————
120
- // Pairs: the positionwise slot pairs of every paired face.
121
- // ————————————————————————————————————————————————————————————————————————
122
-
123
- /** One paired-slot pair of coordinates. */
124
97
  type Pair = readonly [string, string]
125
98
 
126
- /** A list of slot pairs. */
127
99
  type PairList = readonly Pair[]
128
100
 
129
- /** Zips two faces' projections into coordinate pairs, positionwise. */
130
101
  type ZipCoords<
131
102
  SN extends string,
132
103
  SP extends readonly string[],
@@ -139,13 +110,6 @@ type ZipCoords<
139
110
  : Acc
140
111
  : Acc
141
112
 
142
- /**
143
- * One statement's slot pairs: containments (bidirectional included — pair
144
- * unions are symmetric) and capacity statements pair their two faces
145
- * positionwise;
146
- * `key()` pairs nothing. A widened face (owner name or projection no
147
- * longer literal) contributes nothing — the runtime map stays complete.
148
- */
149
113
  type StatementPairs<St extends Statement> = St["data"] extends {
150
114
  readonly source: infer S extends FaceData
151
115
  readonly target: infer T extends FaceData
@@ -157,7 +121,6 @@ type StatementPairs<St extends Statement> = St["data"] extends {
157
121
  : ZipCoords<S["owner"]["name"], S["projection"], T["owner"]["name"], T["projection"]>
158
122
  : []
159
123
 
160
- /** Every slot pair of a statements tuple, in written order. */
161
124
  type PairsOf<Stmts extends readonly Statement[], Acc extends PairList = []> = Stmts extends readonly [
162
125
  infer H extends Statement,
163
126
  ...infer T extends readonly Statement[]
@@ -165,11 +128,6 @@ type PairsOf<Stmts extends readonly Statement[], Acc extends PairList = []> = St
165
128
  ? PairsOf<T, readonly [...Acc, ...StatementPairs<H>]>
166
129
  : Acc
167
130
 
168
- // ————————————————————————————————————————————————————————————————————————
169
- // Union-find over the pairs: connected components as coordinate unions.
170
- // ————————————————————————————————————————————————————————————————————————
171
-
172
- /** The component (a union of coordinates) containing `X`, or `never` when `X` is in none. */
173
131
  type CompOf<Comps extends readonly string[], X extends string> = Comps extends readonly [
174
132
  infer H extends string,
175
133
  ...infer T extends readonly string[]
@@ -179,7 +137,6 @@ type CompOf<Comps extends readonly string[], X extends string> = Comps extends r
179
137
  : CompOf<T, X>
180
138
  : never
181
139
 
182
- /** Rebuilds the component list without the component `C` (components are disjoint; identity is mutual extension). */
183
140
  type WithoutComp<
184
141
  Comps extends readonly string[],
185
142
  C extends string,
@@ -190,7 +147,6 @@ type WithoutComp<
190
147
  : WithoutComp<T, C, readonly [...Acc, H]>
191
148
  : Acc
192
149
 
193
- /** Unions one pair into the component list: create, extend, keep, or merge. */
194
150
  type AddPair<Comps extends readonly string[], A extends string, B extends string> = [
195
151
  CompOf<Comps, A>,
196
152
  CompOf<Comps, B>
@@ -206,7 +162,6 @@ type AddPair<Comps extends readonly string[], A extends string, B extends string
206
162
  : readonly [...WithoutComp<WithoutComp<Comps, CA>, CB>, CA | CB]
207
163
  : Comps
208
164
 
209
- /** Folds every pair into connected components (tail-recursive — the whole walk is one loop). */
210
165
  type BuildComps<Pairs extends PairList, Comps extends readonly string[] = readonly []> = Pairs extends readonly [
211
166
  infer P extends Pair,
212
167
  ...infer T extends PairList
@@ -214,21 +169,8 @@ type BuildComps<Pairs extends PairList, Comps extends readonly string[] = readon
214
169
  ? BuildComps<T, AddPair<Comps, P[0], P[1]>>
215
170
  : Comps
216
171
 
217
- // ————————————————————————————————————————————————————————————————————————
218
- // The wall and the names.
219
- // ————————————————————————————————————————————————————————————————————————
220
-
221
- /** Whether a union holds two or more members (`All` captures the whole union across distribution). */
222
172
  type IsMulti<U, All = U> = [U] extends [never] ? false : U extends unknown ? ([All] extends [U] ? false : true) : never
223
173
 
224
- /**
225
- * The named, self-locating compile verdict of the one-generator wall: the
226
- * generator coordinates that collided and the paired slots (rendered
227
- * `A.x ~ B.y`, statement order) whose chain unified them. Intersected into
228
- * `schema()`'s statements parameter, so the error lands ON the statement
229
- * list with this key naming the law. The runtime twin throws from
230
- * `computeClasses` with the same content, naming the exact statement.
231
- */
232
174
  interface ClassWall<Generators extends string, Chain extends readonly string[]> {
233
175
  readonly "schema class wall — the statements unify two generators into one class (two mints cannot share a carrier)": {
234
176
  readonly generators: Generators
@@ -236,7 +178,6 @@ interface ClassWall<Generators extends string, Chain extends readonly string[]>
236
178
  }
237
179
  }
238
180
 
239
- /** The paired slots lying inside component `C`, rendered — the wall's self-locating chain. */
240
181
  type ChainOf<Pairs extends PairList, C extends string, Acc extends readonly string[] = []> = Pairs extends readonly [
241
182
  infer P extends Pair,
242
183
  ...infer T extends PairList
@@ -246,7 +187,6 @@ type ChainOf<Pairs extends PairList, C extends string, Acc extends readonly stri
246
187
  : ChainOf<T, C, Acc>
247
188
  : Acc
248
189
 
249
- /** Scans the components for a two-generator class: `unknown` (lawful) or the {@link ClassWall}. */
250
190
  type WallScan<Comps extends readonly string[], Gens extends string, Pairs extends PairList> = Comps extends readonly [
251
191
  infer H extends string,
252
192
  ...infer T extends readonly string[]
@@ -256,35 +196,181 @@ type WallScan<Comps extends readonly string[], Gens extends string, Pairs extend
256
196
  : WallScan<T, Gens, Pairs>
257
197
  : unknown
258
198
 
199
+ type SetEq<A extends string, B extends string> = [A] extends [B] ? ([B] extends [A] ? true : false) : false
200
+
201
+ type KeyEntry = readonly [string, string]
202
+
203
+ /**
204
+ * Every declared `key` of a statements tuple as {@link KeyEntry} rows —
205
+ * the declared half of the target-key roster (the implied half is read off
206
+ * each target face's own relation value: fresh marks, closed ids). A
207
+ * statement-tuple union or any undecidable key element degrades the WHOLE
208
+ * wall to silent before this roster is ever consulted
209
+ * ({@link DecidableRoster}) — the skip arm here is the same judgment
210
+ * stated locally, kept as belt, as is {@link DeclaredKeyMatch}'s
211
+ * unjudgeable-projection arm.
212
+ */
213
+ type DeclaredKeysOf<Stmts extends readonly Statement[], Acc extends readonly KeyEntry[] = []> = Stmts extends readonly [
214
+ infer H extends Statement,
215
+ ...infer T extends readonly Statement[]
216
+ ]
217
+ ? H["data"] extends {
218
+ readonly kind: "key"
219
+ readonly owner: infer O extends AnyRelation
220
+ readonly projection: infer P extends readonly string[]
221
+ }
222
+ ? string extends O["name"]
223
+ ? DeclaredKeysOf<T, Acc>
224
+ : DeclaredKeysOf<T, readonly [...Acc, readonly [O["name"], P[number]]]>
225
+ : DeclaredKeysOf<T, Acc>
226
+ : Acc
227
+
228
+ type LiteralProjection<P extends readonly string[]> = [true] extends [IsMulti<P>]
229
+ ? false
230
+ : [number] extends [P["length"]]
231
+ ? false
232
+ : [P] extends [readonly [infer H extends string, ...infer T extends readonly string[]]]
233
+ ? [string] extends [H]
234
+ ? false
235
+ : [true] extends [IsMulti<H>]
236
+ ? false
237
+ : LiteralProjection<T>
238
+ : true
239
+
240
+ type DecidableKeyData<D> = [D] extends [
241
+ {
242
+ readonly kind: "key"
243
+ readonly owner: infer O extends AnyRelation
244
+ readonly projection: infer P extends readonly string[]
245
+ }
246
+ ]
247
+ ? [true] extends [IsMulti<D>]
248
+ ? false
249
+ : [string] extends [O["name"]]
250
+ ? false
251
+ : [true] extends [IsMulti<O["name"]>]
252
+ ? false
253
+ : LiteralProjection<P>
254
+ : false
255
+
259
256
  /**
260
- * The one-generator-per-class law as a constraint: resolves to `unknown`
261
- * (a no-op intersection into the statements parameter) when the statement
262
- * list is lawful, and to the named {@link ClassWall} otherwise.
257
+ * THE total decidability detector of the type-tier target-key judgment —
258
+ * a strict WHITELIST of provably-judgeable shapes, never a blocklist of
259
+ * known-bad spellings (representation over control flow: the detector is
260
+ * total by construction, so a spelling nobody has met yet degrades
261
+ * instead of firing). It answers `true` ONLY when ALL hold, each check
262
+ * tuple-bracketed against distribution:
263
+ *
264
+ * - SINGULAR — `Stmts` itself is a single non-union type (a ternary
265
+ * between two individually-lawful `as const` statement lists infers a
266
+ * UNION of tuples; a naked `Stmts` would distribute the scan
267
+ * ({@link TargetKeyScan}) and the roster ({@link DeclaredKeysOf})
268
+ * INDEPENDENTLY, cross-judging one arm's faces against the other
269
+ * arm's keys). The union test is the house device ({@link IsMulti}).
270
+ * - FIXED-LENGTH — `Stmts` is a literal tuple with no rest tail:
271
+ * `[number] extends [Stmts["length"]]` detects a rest/array length. A
272
+ * rest-tail tuple (`readonly [typeof stmt,...Statement[]]`) peels its
273
+ * literal head and hides EVERY key in the tail from a head-peeling
274
+ * roster read — the scan would judge the head against a roster blind
275
+ * to keys the value tier can see.
276
+ * - CONCRETE ELEMENTS — every element is a single non-union statement
277
+ * (per-element {@link IsMulti}), and every element whose
278
+ * `data["kind"]` type INCLUDES `"key"` is a single concrete `KeyData`
279
+ * with a literal owner name and a projection that is itself a single
280
+ * non-union fixed-length tuple of string literals
281
+ * ({@link DecidableKeyData}, {@link LiteralProjection}). A bare
282
+ * `Statement` element, a `Statement` union, a `KeyStatement` union, a
283
+ * widened owner, a widened or union projection each make the roster
284
+ * UNKNOWABLE.
285
+ *
286
+ * Any failure is the tier's one forbidden verdict in waiting — a false
287
+ * wall — so anything outside the whitelist degrades the WHOLE
288
+ * {@link TargetKeyWall} to silent (the degradation law: best effort
289
+ * degrades to silent, never to a wrong judgment), exactly as a widened
290
+ * FACE already silences its own judgment; the value tier stays
291
+ * authoritative. ONE recorded limit, pre-existing and outside this
292
+ * detector's reach: a generic type parameter CONSTRAINED to a union of
293
+ * tuples hangs tsc in the deferred-scan machinery before any verdict is
294
+ * reached — a degenerate spelling no shipped surface writes, recorded
295
+ * here as the tier's known boundary, not fixed.
263
296
  */
297
+ type DecidableRoster<Stmts extends readonly Statement[]> = [true] extends [IsMulti<Stmts>]
298
+ ? false
299
+ : [number] extends [Stmts["length"]]
300
+ ? false
301
+ : [Stmts] extends [readonly [infer H extends Statement, ...infer T extends readonly Statement[]]]
302
+ ? [true] extends [IsMulti<H>]
303
+ ? false
304
+ : "key" extends H["data"]["kind"]
305
+ ? [DecidableKeyData<H["data"]>] extends [true]
306
+ ? DecidableRoster<T>
307
+ : false
308
+ : DecidableRoster<T>
309
+ : true
310
+
311
+ interface TargetKeyWall<Target extends string, Projection extends string> {
312
+ readonly "schema target-key wall — a containment/mirrors/capacity target projection matches no key of the target relation (declare the key() it resolves, or project an existing one)": {
313
+ readonly target: Target
314
+ readonly projection: Projection
315
+ }
316
+ }
317
+
318
+ type DeclaredKeyMatch<N extends string, PU extends string, Keys extends readonly KeyEntry[]> = Keys extends readonly [
319
+ infer H extends KeyEntry,
320
+ ...infer T extends readonly KeyEntry[]
321
+ ]
322
+ ? SetEq<H[0], N> extends true
323
+ ? string extends H[1]
324
+ ? true
325
+ : SetEq<PU, H[1]> extends true
326
+ ? true
327
+ : DeclaredKeyMatch<N, PU, T>
328
+ : DeclaredKeyMatch<N, PU, T>
329
+ : TargetKeyWall<N, PU>
330
+
331
+ type FreshKeyMatch<PU extends string, O extends AnyRelation> = [PU] extends [FreshFieldNames<RelationFields<O>>]
332
+ ? true extends IsMulti<PU>
333
+ ? false
334
+ : true
335
+ : false
336
+
337
+ type JudgeTargetFace<F extends FaceData, Keys extends readonly KeyEntry[]> = string extends F["owner"]["name"]
338
+ ? true
339
+ : string extends F["projection"][number]
340
+ ? true
341
+ : F["owner"] extends AnyClosed
342
+ ? SetEq<F["projection"][number], "id"> extends true
343
+ ? true
344
+ : TargetKeyWall<F["owner"]["name"], F["projection"][number]>
345
+ : F["owner"] extends AnyRelation
346
+ ? FreshKeyMatch<F["projection"][number], F["owner"]> extends true
347
+ ? true
348
+ : DeclaredKeyMatch<F["owner"]["name"], F["projection"][number], Keys>
349
+ : true
350
+
351
+ type JudgeStatement<St extends Statement, Keys extends readonly KeyEntry[]> = St["data"] extends {
352
+ readonly source: FaceData
353
+ readonly target: infer Tg extends FaceData
354
+ }
355
+ ? JudgeTargetFace<Tg, Keys>
356
+ : true
357
+
358
+ type TargetKeyScan<Stmts extends readonly Statement[], Keys extends readonly KeyEntry[]> = Stmts extends readonly [
359
+ infer H extends Statement,
360
+ ...infer T extends readonly Statement[]
361
+ ]
362
+ ? [JudgeStatement<H, Keys>] extends [true]
363
+ ? TargetKeyScan<T, Keys>
364
+ : JudgeStatement<H, Keys>
365
+ : unknown
366
+
264
367
  type LawfulStatements<Rels extends SchemaRelations, Stmts extends readonly Statement[]> = WallScan<
265
368
  BuildComps<PairsOf<Stmts>>,
266
369
  GeneratorsOf<Rels>,
267
370
  PairsOf<Stmts>
268
- >
371
+ > &
372
+ ([DecidableRoster<Stmts>] extends [true] ? TargetKeyScan<Stmts, DeclaredKeysOf<Stmts>> : unknown)
269
373
 
270
- /**
271
- * One coordinate's class per the three laws, at the TYPE tier: its
272
- * component's single generator (the exact literal — a generator names its
273
- * class); a component-less generator is its own class; a component-less
274
- * non-generator is bare (`undefined`); and a GENERATOR-LESS component is
275
- * carried as its member-coordinate SET (the union of the component's
276
- * coordinates — a canonical, deterministic type). The set REPRESENTS the
277
- * runtime's least-member class name faithfully: the runtime name is by
278
- * construction a member, two slots share a class exactly when their sets
279
- * are identical (so `JoinOk` judges identically at both tiers), and bare
280
- * never equals a set. The least-member PICK itself is deliberately not
281
- * made at the type tier: TypeScript's union member order is not observably
282
- * deterministic (the same key union tuples differently across checking
283
- * contexts — measured, not conjectured), so any type-level "least in
284
- * declaration order" would drift between compilations; the ratified
285
- * declaration-order name lives at the VALUE tier (`computeClasses`), which
286
- * is the only tier the wire reads.
287
- */
288
374
  type ClassOfCoord<Comps extends readonly string[], Gens extends string, C extends string> = [CompOf<Comps, C>] extends [
289
375
  infer M extends string
290
376
  ]
@@ -299,43 +385,23 @@ type ClassOfCoord<Comps extends readonly string[], Gens extends string, C extend
299
385
  : never
300
386
  : never
301
387
 
302
- /** The computed class map over precomputed components/generators. */
303
388
  type ComputedClasses<Rels extends SchemaRelations, Comps extends readonly string[], Gens extends string> = {
304
389
  readonly [N in keyof Rels & string]: {
305
390
  readonly [F in MemberFieldNames<Rels[N]>]: ClassOfCoord<Comps, Gens, `${N}.${F}`>
306
391
  }
307
392
  }
308
393
 
309
- /**
310
- * THE type-level class map of a schema: relation name → field name → the
311
- * law-computed class (`undefined` = bare) — what `schema()` returns as the
312
- * `classes` property's type, and what query joins compare. Generator
313
- * classes are exact name literals; generator-less classes are their
314
- * member-coordinate sets (see {@link ClassOfCoord} — the runtime map's
315
- * least-member name is always a member, so the property type is honest).
316
- */
317
394
  type ClassesOf<Rels extends SchemaRelations, Stmts extends readonly Statement[]> = ComputedClasses<
318
395
  Rels,
319
396
  BuildComps<PairsOf<Stmts>>,
320
397
  GeneratorsOf<Rels>
321
398
  >
322
399
 
323
- // ————————————————————————————————————————————————————————————————————————
324
- // The runtime twin: the same computation as a plain union-find.
325
- // ————————————————————————————————————————————————————————————————————————
326
-
327
- /** One relation's declared coordinates and generator flags, in declaration order. */
328
400
  interface MemberCoords {
329
401
  readonly relation: string
330
402
  readonly fields: ReadonlyArray<{ readonly name: string; readonly generator: boolean }>
331
403
  }
332
404
 
333
- /**
334
- * Reads every member's coordinates off the relation record, declaration
335
- * order throughout — the sealed shape through THE one reader
336
- * (`sealedFieldsOf`): a closed member's generator is its synthetic `id`
337
- * (ordinal 0), an ordinary member's generators are its fresh-marked fields.
338
- */
339
405
  function memberCoords(relations: SchemaRelations): MemberCoords[] {
340
406
  const out: MemberCoords[] = []
341
407
  for (const [relationName, member] of Object.entries(relations)) {
@@ -351,7 +417,6 @@ function memberCoords(relations: SchemaRelations): MemberCoords[] {
351
417
  return out
352
418
  }
353
419
 
354
- /** A plain union-find over coordinate strings, with per-root generator rosters. */
355
420
  interface UnionFind {
356
421
  find(coord: string): string
357
422
  union(a: string, b: string): string
@@ -359,7 +424,6 @@ interface UnionFind {
359
424
  markGenerator(coord: string): void
360
425
  }
361
426
 
362
- /** Builds the union-find. */
363
427
  function makeUnionFind(): UnionFind {
364
428
  const parent = new Map<string, string>()
365
429
  const generators = new Map<string, string[]>()
@@ -402,7 +466,6 @@ function makeUnionFind(): UnionFind {
402
466
  }
403
467
  }
404
468
 
405
- /** The paired faces of one statement, or undefined for a key (an FD pairs nothing). */
406
469
  function statementFaces(statement: Statement): readonly [FaceData, FaceData] | undefined {
407
470
  const data = statement.data
408
471
  if (data.kind === "key") {
@@ -517,5 +580,14 @@ function classesComplete<Classes extends SchemaClasses>(
517
580
  })
518
581
  }
519
582
 
520
- export type { ClassesOf, ClassLookup, ClassRecordOf, ClassWall, LawfulStatements, RelationClasses, SchemaClasses }
583
+ export type {
584
+ ClassesOf,
585
+ ClassLookup,
586
+ ClassRecordOf,
587
+ ClassWall,
588
+ LawfulStatements,
589
+ RelationClasses,
590
+ SchemaClasses,
591
+ TargetKeyWall
592
+ }
521
593
  export { classesComplete, computeClasses }
package/src/lower.ts CHANGED
@@ -3,9 +3,13 @@
3
3
  * data (`#spec.ts`), which the napi bridge marshals verbatim. Lowering is
4
4
  * TOTAL on well-typed inputs — no validation lives here beyond what the
5
5
  * types and the construction boundaries already guarantee — and it is the
6
- * only place statement internals are read for the wire. Ordering is
7
- * declaration order throughout, and every output object is built with one
8
- * fixed key order, so serialization is deterministic (byte-stable).
6
+ * only place statement internals are read for the wire. In particular,
7
+ * lowering never emits an engine-refused containment target: it accepts
8
+ * only `schema` outputs, and `schema`'s target-key wall already
9
+ * refused any non-key target projection (60-containment-parity —
10
+ * totality INHERITED, not re-checked). Ordering is declaration order
11
+ * throughout, and every output object is built with one fixed key order,
12
+ * so serialization is deterministic (byte-stable).
9
13
  */
10
14
 
11
15
  import * as errors from "@superbuilders/errors"
@@ -27,12 +31,6 @@ import type {
27
31
  } from "#spec.ts"
28
32
  import type { Statement } from "#statements.ts"
29
33
 
30
- /**
31
- * Lowers one field descriptor's structural type to the wire
32
- * {@link ValueTypeSpec}: the S1 kind tags map 1:1 onto the `ValueType`
33
- * vocabulary (`str` spells `string`, `bytes` spells `fixedBytes` with its
34
- * width label as `len`; intervals carry element and width labels through).
35
- */
36
34
  function valueTypeOf(field: AnyField): ValueTypeSpec {
37
35
  switch (field.kind) {
38
36
  case "bool":
@@ -50,16 +48,6 @@ function valueTypeOf(field: AnyField): ValueTypeSpec {
50
48
  }
51
49
  }
52
50
 
53
- /**
54
- * Lowers one field descriptor to its {@link FieldSpec}: the structural
55
- * type, the structural fresh mark (`fresh` is the literal `true` exactly
56
- * on a fresh-marked u64 — on an unmarked one the property holds the marked
57
- * descriptor, never `true`), and the wire's `newtype` — the COMPUTED class
58
- * name `schema()` derived from the statement list (the laws type the
59
- * columns), `undefined` on a bare field. The engine reads newtypes for
60
- * handle resolution and the coherence check only and DROPS them at
61
- * descriptor lowering — class names are never fingerprinted.
62
- */
63
51
  function lowerField(name: string, field: AnyField, newtype: string | undefined): FieldSpec {
64
52
  return {
65
53
  name,
@@ -69,7 +57,6 @@ function lowerField(name: string, field: AnyField, newtype: string | undefined):
69
57
  }
70
58
  }
71
59
 
72
- /** Lowers one face to a `SideSpec`: names only, σ as (field, set) pairs. */
73
60
  function lowerFace(face: FaceData): SideSpec {
74
61
  return {
75
62
  relation: face.owner.name,
@@ -80,12 +67,6 @@ function lowerFace(face: FaceData): SideSpec {
80
67
  }
81
68
  }
82
69
 
83
- /**
84
- * Lowers one statement. `mirrors` stays ONE spec statement
85
- * (`bidirectional: true`) — the engine performs the `==` lowering to two
86
- * adjacent containments, `source <= target` first, exactly as the macro
87
- * does.
88
- */
89
70
  function lowerStatement(statement: Statement): StatementSpec {
90
71
  const data = statement.data
91
72
  switch (data.kind) {
@@ -116,13 +97,6 @@ function lowerStatement(statement: Statement): StatementSpec {
116
97
  }
117
98
  }
118
99
 
119
- /**
120
- * Lowers one ordinary relation to its `RelationSpec` fragment: fields in
121
- * declaration order, each carrying its law-computed class name as the
122
- * `newtype` (`classes` — the schema's class record for this relation;
123
- * bare fields carry `undefined`), `closed: undefined` (the option is the
124
- * kind — one sum, R7).
125
- */
126
100
  function lowerRelation(relation: AnyRelation, classes: RelationClasses): RelationSpec {
127
101
  const fields: FieldSpec[] = relation.data.fields.map(function lowerDeclared(declared) {
128
102
  return lowerField(declared.name, declared.field, classes[declared.name])
@@ -130,16 +104,6 @@ function lowerRelation(relation: AnyRelation, classes: RelationClasses): Relatio
130
104
  return { name: relation.name, fields, closed: undefined }
131
105
  }
132
106
 
133
- /**
134
- * Lowers one closed relation to its `RelationSpec` fragment: declared
135
- * intrinsic columns only (the engine materializes the synthetic `id`) and
136
- * the fused closedness sum (R7) — the handle newtype (the COMPUTED class
137
- * name of the id's generator class, `"Kind.id"`, always present: a closed
138
- * id is a generator; every referencing field shares it by law, which is
139
- * how the engine resolves a handle literal back to its roster) together
140
- * with the ground axioms in declaration order (row id = index); the
141
- * literals were already lowered at `closed()` construction.
142
- */
143
107
  function lowerClosed(member: AnyClosed, classes: RelationClasses): RelationSpec {
144
108
  const fields: FieldSpec[] = member.data.columns.map(function lowerColumn(column) {
145
109
  return lowerField(column.name, column.field, classes[column.name])
@@ -154,17 +118,8 @@ function lowerClosed(member: AnyClosed, classes: RelationClasses): RelationSpec
154
118
  return { name: member.name, fields, closed: { newtype, rows } }
155
119
  }
156
120
 
157
- /** The frozen empty class record a relation outside the schema's map lowers under (nothing classed). */
158
121
  const noClasses: RelationClasses = Object.freeze({})
159
122
 
160
- /**
161
- * Lowers a whole theory to the `SchemaSpec` the bridge takes: relations in
162
- * record declaration order, DECLARED statements only in written order (the
163
- * engine materializes the fresh-implied and closed auto-keys itself), and
164
- * every field's `newtype` slot fed from the schema's law-computed class
165
- * map — the ONE domain authority (fingerprint-neutral: the engine drops
166
- * newtypes at descriptor lowering).
167
- */
168
123
  function lower(theory: AnySchema): SchemaSpec {
169
124
  const relations: RelationSpec[] = Object.entries(theory.relations).map(function lowerMember([name, member]) {
170
125
  const classes = theory.classes[name] ?? noClasses
@@ -176,4 +131,4 @@ function lower(theory: AnySchema): SchemaSpec {
176
131
  return { relations, statements: theory.statements.map(lowerStatement) }
177
132
  }
178
133
 
179
- export { lower, lowerClosed, lowerRelation }
134
+ export { lower }