@bjornpagen/bumbledb 0.14.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/COOKBOOK.md +58 -62
  2. package/README.md +82 -56
  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 +93 -290
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +713 -556
  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 +13 -23
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +11 -20
  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 +97 -390
  40. package/dist/native.d.ts.map +1 -1
  41. package/dist/native.js +39 -61
  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 +64 -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 +3 -3
  84. package/src/capacity.ts +26 -140
  85. package/src/closed.ts +5 -206
  86. package/src/db.ts +997 -854
  87. package/src/face.ts +0 -142
  88. package/src/fields.ts +4 -172
  89. package/src/index.ts +32 -35
  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 +192 -413
  94. package/src/query/atom.ts +26 -313
  95. package/src/query/find.ts +24 -110
  96. package/src/query/lower.ts +132 -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
  104. package/dist/exhume.d.ts +0 -143
  105. package/dist/exhume.d.ts.map +0 -1
  106. package/dist/exhume.js +0 -166
  107. package/dist/exhume.js.map +0 -1
  108. package/src/exhume.ts +0 -267
package/src/index.ts CHANGED
@@ -1,26 +1,20 @@
1
1
  /**
2
2
  * @bjornpagen/bumbledb — the type-theoretic TypeScript SDK for the
3
3
  * bumbledb embedded relational engine. Public surface: the structural type
4
- * kernel (fields as pure structure, `relation()`, `closed()` — domains are
5
- * never declared: THE LAWS TYPE THE COLUMNS, `schema()` computing every
4
+ * kernel (fields as pure structure, `relation`, `closed` — domains are
5
+ * never declared: THE LAWS TYPE THE COLUMNS, `schema` computing every
6
6
  * field's equivalence class FROM the statement list at both tiers), the
7
- * statement algebra with `schema()` and `SchemaSpec` lowering (PRD-06), the `Db`
8
- * runtime (exclusive-lock stores, transactions, typed violations, scoped
9
- * snapshot reads, one-shot `write`/`writeFrom` with `abandon` — PRD-07, zero
10
- * closables), the query surface (kysely-shaped:
7
+ * statement algebra with `schema` and `SchemaSpec` lowering (PRD-06), the `Db`
8
+ * runtime (exclusive-lock stores, transactions, typed violations, callback
9
+ * instance reads, one-shot `write`/`writeFrom` with `abandon` — PRD-07), the query surface (kysely-shaped:
11
10
  * `query(S).rule(r => { const { id, name } = v(Holder); return r.match(Holder, { id, name }).find({ name }) })` —
12
- * variables minted by `v()` and joined by OBJECT REFERENCE (reuse is the
11
+ * variables minted by `v` and joined by OBJECT REFERENCE (reuse is the
13
12
  * join), the head a `find` RECORD whose keys name the answer columns
14
13
  * (renames are real), params still STRING-named, plus negation,
15
14
  * conditions, aggregates, and interiors / one linear rec via
16
15
  * `q.interior` / `q.reach` —
17
- * `db.prepare` as a plain value; the comparison/connective builders are
18
- * also free exports, and the free names `eq`/`not`/`and`/`or` collide with
19
- * common host identifiers — import aliasing is the answer; the SDK does
20
- * not rename for collision-avoidance), the exhume surface
21
- * (`Db.exhume` — the one schema-independent read path: the store's
22
- * self-described shapes and raw facts by name, typed at bare structural
23
- * values, deliberately schema-free). The raw native bridge is not exported.
16
+ * `db.prepare` as a plain value; comparisons and connectives live on
17
+ * the rule scope). The raw native bridge is not exported.
24
18
  */
25
19
 
26
20
  export type {
@@ -51,7 +45,9 @@ export { closed } from "#closed.ts"
51
45
  export type {
52
46
  Abandon,
53
47
  AbandonedArm,
48
+ Admission,
54
49
  CapacityViolation,
50
+ Committed,
55
51
  ContainmentViolation,
56
52
  DeclaredKeyFact,
57
53
  DeclaredKeyViolation,
@@ -62,26 +58,30 @@ export type {
62
58
  MirrorViolation,
63
59
  MutationReport,
64
60
  OffendingFact,
61
+ OwnedInstance,
65
62
  Prepared,
66
- ReadScope,
67
- Tx,
63
+ ReadInstance,
64
+ SyncResult,
68
65
  Violation,
69
- WriteResult
66
+ Witness,
67
+ WriteFromOutcome,
68
+ WriteOutcome,
69
+ WriteTx
70
70
  } from "#db.ts"
71
- export { abandon, Db, ErrGenerationMoved, ErrNewtypeMismatch } from "#db.ts"
72
- export type {
73
- Exhumed,
74
- ExhumedAxiom,
75
- ExhumedDescriptor,
76
- ExhumedFact,
77
- ExhumedField,
78
- ExhumedRelation
79
- } from "#exhume.ts"
80
71
  export {
81
- ErrExhumeCorruption,
82
- ErrExhumeFormatMismatch,
83
- ErrExhumeNoDescriptor
84
- } from "#exhume.ts"
72
+ abandon,
73
+ Db,
74
+ ErrAsyncCallback,
75
+ ErrFingerprintMismatch,
76
+ ErrForeignPrepared,
77
+ ErrForeignWitness,
78
+ ErrIrError,
79
+ ErrNewtypeMismatch,
80
+ ErrSchemaError,
81
+ ErrSpentHandle,
82
+ ErrUseAfterScope,
83
+ InstanceBuilder
84
+ } from "#db.ts"
85
85
  export type {
86
86
  AnyFace,
87
87
  Arity,
@@ -115,7 +115,7 @@ export type {
115
115
  } from "#fields.ts"
116
116
  export { bool, bytes, i64, interval, span, str, u64 } from "#fields.ts"
117
117
  export type { ClassesOf, ClassWall, LawfulStatements, RelationClasses, SchemaClasses } from "#law.ts"
118
- export { lower, lowerClosed, lowerRelation } from "#lower.ts"
118
+ export { lower } from "#lower.ts"
119
119
  export type { KeyFact } from "#marshal.ts"
120
120
  export type { FactValue, ParsedQuery, QueryIr, StatementKindTag } from "#native.ts"
121
121
 
@@ -131,7 +131,7 @@ export type {
131
131
  RuleData,
132
132
  Tree
133
133
  } from "#query/atom.ts"
134
- export { ALLEN, allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
134
+ export { ALLEN } from "#query/atom.ts"
135
135
  export type { Agg, FindEntry } from "#query/find.ts"
136
136
  export type {
137
137
  AnyQuery,
@@ -151,10 +151,8 @@ export type {
151
151
  TermOps
152
152
  } from "#query/lower.ts"
153
153
  export { lowerQuery, query } from "#query/lower.ts"
154
- export { parseQueryIr } from "#query/parse-ir.ts"
155
154
  export type {
156
155
  ClassedField,
157
- Duration,
158
156
  MatchFields,
159
157
  MatchOwner,
160
158
  Param,
@@ -197,7 +195,6 @@ export type {
197
195
  ValueTypeSpec,
198
196
  WeightSpec
199
197
  } from "#spec.ts"
200
- export { renderCapacityBound, renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight } from "#spec.ts"
201
198
  export type {
202
199
  CapacityData,
203
200
  CapacityStatement,
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 }