@bjornpagen/bumbledb 0.19.0 → 0.19.2

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 (50) hide show
  1. package/COOKBOOK.md +2 -0
  2. package/README.md +1 -0
  3. package/dist/closed.d.ts +9 -9
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +27 -18
  6. package/dist/closed.js.map +1 -1
  7. package/dist/face.d.ts +18 -23
  8. package/dist/face.d.ts.map +1 -1
  9. package/dist/face.js.map +1 -1
  10. package/dist/fields.d.ts +54 -8
  11. package/dist/fields.d.ts.map +1 -1
  12. package/dist/fields.js +28 -1
  13. package/dist/fields.js.map +1 -1
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/judgment.d.ts +13 -0
  18. package/dist/judgment.d.ts.map +1 -0
  19. package/dist/judgment.js +11 -0
  20. package/dist/judgment.js.map +1 -0
  21. package/dist/law.d.ts +2 -1
  22. package/dist/law.d.ts.map +1 -1
  23. package/dist/law.js.map +1 -1
  24. package/dist/marshal.d.ts +2 -2
  25. package/dist/marshal.d.ts.map +1 -1
  26. package/dist/marshal.js.map +1 -1
  27. package/dist/query/atom.d.ts +10 -6
  28. package/dist/query/atom.d.ts.map +1 -1
  29. package/dist/query/atom.js.map +1 -1
  30. package/dist/query/lower.d.ts.map +1 -1
  31. package/dist/query/lower.js +4 -4
  32. package/dist/query/lower.js.map +1 -1
  33. package/dist/query/scope.d.ts +38 -31
  34. package/dist/query/scope.d.ts.map +1 -1
  35. package/dist/query/scope.js +17 -13
  36. package/dist/query/scope.js.map +1 -1
  37. package/dist/statements.js +2 -2
  38. package/dist/statements.js.map +1 -1
  39. package/package.json +3 -3
  40. package/src/closed.ts +85 -51
  41. package/src/face.ts +18 -24
  42. package/src/fields.ts +85 -7
  43. package/src/index.ts +5 -0
  44. package/src/judgment.ts +25 -0
  45. package/src/law.ts +2 -1
  46. package/src/marshal.ts +3 -3
  47. package/src/query/atom.ts +6 -5
  48. package/src/query/lower.ts +17 -7
  49. package/src/query/scope.ts +79 -47
  50. package/src/statements.ts +3 -3
package/src/closed.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import * as errors from "@superbuilders/errors"
2
2
  import {
3
+ type AnyClosedIdField,
3
4
  type AnyField,
4
5
  assertDeclarationOrderKey,
5
6
  assertDeclarationRecord,
7
+ type ClosedHandleTuple,
6
8
  type ClosedIdField,
7
9
  type ClosedRoster,
8
10
  type Infer,
@@ -39,20 +41,28 @@ type Axioms<Handles extends string, Cols extends Record<string, PayloadField>> =
39
41
  readonly [H in Handles]: AxiomRow<Cols>
40
42
  }
41
43
 
42
- interface ClosedCore<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
44
+ interface ClosedCore<
45
+ Name extends string,
46
+ Handles extends ClosedHandleTuple,
47
+ Cols extends Record<string, PayloadField>
48
+ > {
43
49
  readonly name: Name
44
50
 
45
51
  readonly id: ClosedIdField<Name, Handles>
46
52
  readonly data: ClosedData
47
53
 
48
- readonly axioms: Axioms<Handles, Cols>
54
+ readonly axioms: Axioms<Handles[number], Cols>
49
55
 
50
56
  readonly columns: Cols
51
57
  }
52
58
 
53
59
  type ClosedSelectionInput<Cols extends Record<string, PayloadField>> = SelectionInput<Cols>
54
60
 
55
- interface SelectedClosed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
61
+ interface SelectedClosed<
62
+ Name extends string,
63
+ Handles extends ClosedHandleTuple,
64
+ Cols extends Record<string, PayloadField>
65
+ > {
56
66
  readonly relation: Closed<Name, Handles, Cols>
57
67
  readonly selection: readonly SelectionBinding[]
58
68
  }
@@ -62,11 +72,15 @@ interface AnySelectedClosed {
62
72
  readonly selection: readonly SelectionBinding[]
63
73
  }
64
74
 
65
- interface ClosedSelectable<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> {
75
+ interface ClosedSelectable<
76
+ Name extends string,
77
+ Handles extends ClosedHandleTuple,
78
+ Cols extends Record<string, PayloadField>
79
+ > {
66
80
  where(selection: ClosedSelectionInput<Cols>): SelectedClosed<Name, Handles, Cols>
67
81
  }
68
82
 
69
- type Closed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>> = [
83
+ type Closed<Name extends string, Handles extends ClosedHandleTuple, Cols extends Record<string, PayloadField>> = [
70
84
  keyof Cols
71
85
  ] extends [never]
72
86
  ? ClosedCore<Name, Handles, Cols>
@@ -74,7 +88,7 @@ type Closed<Name extends string, Handles extends string, Cols extends Record<str
74
88
 
75
89
  interface AnyClosed {
76
90
  readonly name: string
77
- readonly id: ClosedIdField
91
+ readonly id: AnyClosedIdField
78
92
  readonly data: ClosedData
79
93
  readonly axioms: Readonly<Record<string, object>>
80
94
  readonly columns: Readonly<Record<string, PayloadField>>
@@ -98,25 +112,34 @@ function sealedFieldOf(member: AnyRelation | AnyClosed, fieldName: string): AnyF
98
112
  return declared?.field
99
113
  }
100
114
 
101
- function isHandleTuple(
102
- shape: readonly [string, ...string[]] | Record<string, PayloadField>
103
- ): shape is readonly [string, ...string[]] {
104
- return Array.isArray(shape)
115
+ function isHandleTuple(shape: unknown): shape is ClosedHandleTuple {
116
+ return (
117
+ Array.isArray(shape) &&
118
+ shape.length > 0 &&
119
+ shape.every(function stringHandle(handle) {
120
+ return typeof handle === "string"
121
+ })
122
+ )
105
123
  }
106
124
 
107
125
  /**
108
- * The trusted seam of the payload tier's handle enumeration: the axioms
109
- * record's own enumerable keys ARE its handle set (the type says so
110
- * {@link Axioms} is keyed by the handles), and this guard verifies exactly
111
- * that checkable fact before the key list is admitted at the handle type.
126
+ * The trusted seam of the payload tier: the handle tuple and the axioms
127
+ * record are the same finite set every tuple name is an own key, and
128
+ * every own key is in the tuple.
112
129
  */
113
130
  function handleKeysOwn<Handles extends string>(
114
131
  axioms: { readonly [H in Handles]: object },
115
- names: readonly string[]
116
- ): names is readonly Handles[] {
117
- return names.every(function ownHandle(name) {
118
- return Object.hasOwn(axioms, name)
119
- })
132
+ names: readonly Handles[]
133
+ ): boolean {
134
+ const listed = new Set<string>(names)
135
+ return (
136
+ names.every(function ownHandle(name) {
137
+ return Object.hasOwn(axioms, name)
138
+ }) &&
139
+ Object.keys(axioms).every(function listedHandle(name) {
140
+ return listed.has(name)
141
+ })
142
+ )
120
143
  }
121
144
 
122
145
  /**
@@ -160,40 +183,50 @@ function mintAxioms<Handles extends string, Cols extends Record<string, PayloadF
160
183
  return out
161
184
  }
162
185
 
163
- function closed<const Name extends string, const Handles extends readonly [string, ...string[]]>(
186
+ function closed<const Name extends string, const Handles extends ClosedHandleTuple>(
164
187
  name: Name,
165
188
  handles: Handles
166
- ): Closed<Name, Handles[number], Record<never, never>>
189
+ ): Closed<Name, Handles, Record<never, never>>
167
190
 
168
- function closed<const Name extends string, const Cols extends PayloadColumns, Handles extends string>(
191
+ function closed<const Name extends string, const Handles extends ClosedHandleTuple, const Cols extends PayloadColumns>(
169
192
  name: Name,
193
+ handles: Handles,
170
194
  columns: Cols,
171
- axioms: Axioms<Handles, Cols>
195
+ axioms: Axioms<Handles[number], Cols>
172
196
  ): Closed<Name, Handles, Cols>
173
197
 
174
- function closed<const Name extends string, const Cols extends PayloadColumns, Handles extends string>(
198
+ function closed<Name extends string, Handles extends ClosedHandleTuple, Cols extends PayloadColumns>(
175
199
  name: Name,
176
- shape: readonly [string, ...string[]] | Cols,
177
- axioms?: Axioms<Handles, Cols>
178
- ): Closed<Name, string, Record<never, never>> | Closed<Name, Handles, Cols> {
179
- if (isHandleTuple(shape)) {
200
+ handles: Handles,
201
+ columns?: Cols,
202
+ axioms?: Axioms<Handles[number], Cols>
203
+ ): Closed<Name, Handles, Record<never, never>> | Closed<Name, Handles, Cols> {
204
+ if (!Array.isArray(handles)) {
205
+ throw errors.new(
206
+ `closed relation ${name}: payload columns declared without ground axioms — the payload tier is spelled closed(name, handles, columns, axioms)`
207
+ )
208
+ }
209
+ if (!isHandleTuple(handles)) {
210
+ throw errors.new(`closed relation ${name}: at least one handle is required (an empty vocabulary declares nothing)`)
211
+ }
212
+ if (columns === undefined) {
180
213
  if (axioms !== undefined) {
181
214
  throw errors.new(`closed relation ${name}: the bare tier declares no columns, so ground axioms are inadmissible`)
182
215
  }
183
- return closedBare(name, shape)
216
+ return closedBare(name, handles)
184
217
  }
185
218
  if (axioms === undefined) {
186
219
  throw errors.new(
187
- `closed relation ${name}: payload columns declared without ground axioms — the payload tier is spelled closed(name, columns, axioms) (the curried spelling is deleted)`
220
+ `closed relation ${name}: payload columns declared without ground axioms — the payload tier is spelled closed(name, handles, columns, axioms)`
188
221
  )
189
222
  }
190
- return closedPayload(name, shape, axioms)
223
+ return closedPayload(name, handles, columns, axioms)
191
224
  }
192
225
 
193
- function closedBare<Name extends string, Handles extends string>(
226
+ function closedBare<Name extends string, const Hs extends ClosedHandleTuple>(
194
227
  name: Name,
195
- handles: readonly [Handles, ...Handles[]]
196
- ): Closed<Name, Handles, Record<never, never>> {
228
+ handles: Hs
229
+ ): Closed<Name, Hs, Record<never, never>> {
197
230
  const empty: Record<string, object> = {}
198
231
  for (const handle of handles) {
199
232
  /** A duplicated name mints one row; the roster's own duplicate refusal in {@link mintClosed} stays the judge. */
@@ -202,33 +235,37 @@ function closedBare<Name extends string, Handles extends string>(
202
235
  }
203
236
  }
204
237
  Object.freeze(empty)
205
- if (!axiomsMinted<Handles, Record<never, never>>(empty, handles, [])) {
238
+ if (!axiomsMinted<Hs[number], Record<never, never>>(empty, handles, [])) {
206
239
  throw errors.new(`closed relation ${name}: bare-tier axiom-row minting incomplete`)
207
240
  }
208
- return mintClosed<Name, Handles, Record<never, never>>(name, handles, {}, empty)
241
+ return mintClosed(name, handles, {}, empty)
209
242
  }
210
243
 
211
- function closedPayload<Name extends string, Handles extends string, Cols extends PayloadColumns>(
244
+ function closedPayload<Name extends string, Handles extends ClosedHandleTuple, Cols extends PayloadColumns>(
212
245
  name: Name,
246
+ handles: Handles,
213
247
  columns: Cols,
214
- axioms: Axioms<Handles, Cols>
248
+ axioms: Axioms<Handles[number], Cols>
215
249
  ): Closed<Name, Handles, Cols> {
216
250
  assertDeclarationRecord(`closed relation ${name} columns`, columns)
217
251
  for (const columnName of Object.keys(columns)) {
218
252
  assertDeclarationOrderKey(`closed relation ${name} column`, columnName)
219
253
  }
220
254
  assertDeclarationRecord(`closed relation ${name} axioms`, axioms)
221
- const handles = Object.keys(axioms)
222
255
  for (const handle of handles) {
223
256
  assertDeclarationOrderKey(`closed relation ${name} handle`, handle)
224
257
  }
225
258
  if (!handleKeysOwn(axioms, handles)) {
226
259
  throw errors.new(`closed relation ${name}: handle enumeration incomplete`)
227
260
  }
228
- return mintClosed<Name, Handles, Cols>(name, handles, columns, axioms)
261
+ return mintClosed(name, handles, columns, axioms)
229
262
  }
230
263
 
231
- function surfaceMinted<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>>(
264
+ function surfaceMinted<
265
+ Name extends string,
266
+ Handles extends ClosedHandleTuple,
267
+ Cols extends Record<string, PayloadField>
268
+ >(
232
269
  value: ClosedCore<Name, Handles, Cols>,
233
270
  cols: readonly ClosedColumn[]
234
271
  ): value is ClosedCore<Name, Handles, Cols> & Closed<Name, Handles, Cols> {
@@ -236,16 +273,13 @@ function surfaceMinted<Name extends string, Handles extends string, Cols extends
236
273
  return cols.length > 0 ? selectable : !selectable
237
274
  }
238
275
 
239
- function mintClosed<Name extends string, Handles extends string, Cols extends Record<string, PayloadField>>(
276
+ function mintClosed<Name extends string, Handles extends ClosedHandleTuple, Cols extends Record<string, PayloadField>>(
240
277
  name: Name,
241
- handles: readonly Handles[],
278
+ handles: Handles,
242
279
  columns: Cols,
243
- axioms: Axioms<Handles, Cols>
280
+ axioms: Axioms<Handles[number], Cols>
244
281
  ): Closed<Name, Handles, Cols> {
245
282
  assertDeclarationOrderKey("closed relation", name)
246
- if (handles.length === 0) {
247
- throw errors.new(`closed relation ${name}: at least one handle is required (an empty vocabulary declares nothing)`)
248
- }
249
283
  const seen = new Set<string>()
250
284
  for (const handle of handles) {
251
285
  if (seen.has(handle)) {
@@ -253,7 +287,7 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
253
287
  }
254
288
  seen.add(handle)
255
289
  }
256
- const handleList: readonly Handles[] = Object.freeze([...handles])
290
+ const handleList = Object.freeze(handles)
257
291
  const roster: ClosedRoster<Name, Handles> = Object.freeze({ name, handles: handleList })
258
292
  const cols: ClosedColumn[] = []
259
293
  for (const [columnName, field] of Object.entries(columns)) {
@@ -266,7 +300,7 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
266
300
  cols.push(Object.freeze({ name: columnName, field }))
267
301
  }
268
302
  Object.freeze(cols)
269
- const rows: ClosedRow[] = handleList.map(function lowerRow(handle) {
303
+ const rows: ClosedRow[] = handleList.map(function lowerRow(handle: Handles[number]) {
270
304
  const row: Readonly<Record<string, unknown>> = axioms[handle]
271
305
  const values = cols.map(function lowerAxiomLiteral(column) {
272
306
  return Object.freeze(literalOf(column.field, row[column.name]))
@@ -281,7 +315,7 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
281
315
  })
282
316
  const id: ClosedIdField<Name, Handles> = Object.freeze({ kind: "u64", closed: roster })
283
317
 
284
- const axiomsOut = mintAxioms<Handles, Cols>(name, handleList, cols, axioms)
318
+ const axiomsOut = mintAxioms<Handles[number], Cols>(name, handleList, cols, axioms)
285
319
  const columnsOut: Cols = { ...columns }
286
320
  Object.freeze(columnsOut)
287
321
  const holder: { value: Closed<Name, Handles, Cols> | undefined } = { value: undefined }
@@ -299,7 +333,7 @@ function mintClosed<Name extends string, Handles extends string, Cols extends Re
299
333
  const core = { name, id, data, axioms: axiomsOut, columns: columnsOut }
300
334
  const value: ClosedCore<Name, Handles, Cols> =
301
335
  cols.length > 0 ? Object.freeze({ ...core, where }) : Object.freeze(core)
302
- if (!surfaceMinted<Name, Handles, Cols>(value, cols)) {
336
+ if (!surfaceMinted(value, cols)) {
303
337
  throw errors.new(`closed relation ${name}: ergonomic-surface minting incomplete`)
304
338
  }
305
339
  holder.value = value
package/src/face.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as errors from "@superbuilders/errors"
2
2
  import type { AnyClosed, AnySelectedClosed, PayloadField } from "#closed.ts"
3
- import type { AnyField } from "#fields.ts"
3
+ import type { AnyField, SignatureOf } from "#fields.ts"
4
+ import type { Same } from "#judgment.ts"
4
5
  import type { AnyRelation, AnySelected, FieldsShape, RelationFields, SelectionBinding } from "#relation.ts"
5
6
  import { renderLiteralSet } from "#spec.ts"
6
7
 
@@ -46,24 +47,25 @@ type FaceFields<S extends FaceSource> = S extends AnySelected
46
47
  ? "id" | (keyof Row & string)
47
48
  : never
48
49
 
49
- type ShapeOf<F extends AnyField> = readonly [
50
- F["kind"],
51
- F extends { readonly element: unknown } ? undefined : F extends { readonly width: infer W } ? W : undefined,
52
- F extends { readonly element: infer E } ? E : undefined,
53
- F extends {
54
- readonly closed: { readonly name: infer N extends string; readonly handles: readonly (infer H extends string)[] }
55
- }
56
- ? readonly [N, H]
57
- : undefined
58
- ]
50
+ /**
51
+ * The wire shape a face projects: the field's {@link SignatureOf} with an
52
+ * interval's width erased. A fixed-width interval pairs with a plain one
53
+ * width is a measure label, not wire structure. Bytes width IS wire
54
+ * structure and stays. The roster stays the full handle vector.
55
+ */
56
+ type ProjectedSignature<F extends AnyField> = F extends { readonly kind: "interval" }
57
+ ? readonly [F["kind"], undefined, F extends { readonly element: infer E } ? E : undefined, undefined]
58
+ : SignatureOf<F>
59
59
 
60
- type ShapeIn<Fields extends FieldsShape, K extends string> = K extends keyof Fields ? ShapeOf<Fields[K]> : undefined
60
+ type ShapeIn<Fields extends FieldsShape, K extends string> = K extends keyof Fields
61
+ ? ProjectedSignature<Fields[K]>
62
+ : undefined
61
63
 
62
64
  type ProjectedShape<S extends FaceSource, K extends string> = S extends AnySelected
63
65
  ? ShapeIn<RelationFields<S["relation"]>, K>
64
66
  : S extends AnySelectedClosed
65
67
  ? K extends "id"
66
- ? ShapeOf<S["relation"]["id"]>
68
+ ? ProjectedSignature<S["relation"]["id"]>
67
69
  : ShapeIn<S["relation"]["columns"], K>
68
70
  : S extends AnyRelation
69
71
  ? ShapeIn<RelationFields<S>, K>
@@ -72,7 +74,7 @@ type ProjectedShape<S extends FaceSource, K extends string> = S extends AnySelec
72
74
  readonly columns: infer Cols extends Record<string, PayloadField>
73
75
  }
74
76
  ? K extends "id"
75
- ? ShapeOf<Id>
77
+ ? ProjectedSignature<Id>
76
78
  : ShapeIn<Cols, K>
77
79
  : undefined
78
80
 
@@ -92,11 +94,7 @@ interface FaceArityMismatch<Left, Right> {
92
94
  }
93
95
 
94
96
  type SameArity<A extends AnyFace, B extends AnyFace> =
95
- Arity<A> extends Arity<B>
96
- ? Arity<B> extends Arity<A>
97
- ? unknown
98
- : FaceArityMismatch<Arity<A>, Arity<B>>
99
- : FaceArityMismatch<Arity<A>, Arity<B>>
97
+ Same<Arity<A>, Arity<B>> extends true ? unknown : FaceArityMismatch<Arity<A>, Arity<B>>
100
98
 
101
99
  interface FaceShapeMismatch<Left, Right> {
102
100
  readonly "face shape mismatch — positionwise kind, width, element, and closed roster must be equal on both sides": readonly [
@@ -106,11 +104,7 @@ interface FaceShapeMismatch<Left, Right> {
106
104
  }
107
105
 
108
106
  type SameShapes<A extends AnyFace, B extends AnyFace> =
109
- FaceShapes<A> extends FaceShapes<B>
110
- ? FaceShapes<B> extends FaceShapes<A>
111
- ? unknown
112
- : FaceShapeMismatch<FaceShapes<A>, FaceShapes<B>>
113
- : FaceShapeMismatch<FaceShapes<A>, FaceShapes<B>>
107
+ Same<FaceShapes<A>, FaceShapes<B>> extends true ? unknown : FaceShapeMismatch<FaceShapes<A>, FaceShapes<B>>
114
108
 
115
109
  function on<S extends FaceSource, const F extends FaceFields<S>>(source: S, field: F): Face<S, readonly [F]>
116
110
  function on<S extends FaceSource, const P extends readonly [FaceFields<S>, ...FaceFields<S>[]]>(
package/src/fields.ts CHANGED
@@ -32,11 +32,22 @@ function span(start: bigint, end: bigint): IntervalValue {
32
32
  return Object.freeze({ start, end })
33
33
  }
34
34
 
35
- interface ClosedRoster<Name extends string = string, H extends string = string> {
35
+ /**
36
+ * Nonempty declaration-order handle vector — the ONE roster carrier. The
37
+ * handle union is `Handles[number]`, the ordinal of a handle is its tuple
38
+ * position, and the roster size is the tuple length. An empty vocabulary
39
+ * is unspellable.
40
+ */
41
+ type ClosedHandleTuple = readonly [string, ...string[]]
42
+
43
+ interface ClosedRoster<Name extends string, Handles extends ClosedHandleTuple> {
36
44
  readonly name: Name
37
- readonly handles: readonly H[]
45
+ readonly handles: Handles
38
46
  }
39
47
 
48
+ /** The roster top type — what an erased carrier knows about any roster. */
49
+ type AnyClosedRoster = ClosedRoster<string, ClosedHandleTuple>
50
+
40
51
  interface BoolField {
41
52
  readonly kind: "bool"
42
53
  }
@@ -73,12 +84,42 @@ interface IntervalField<
73
84
  readonly width: Width
74
85
  }
75
86
 
76
- interface ClosedIdField<Name extends string = string, H extends string = string> {
87
+ interface ClosedIdField<Name extends string, Handles extends ClosedHandleTuple> {
77
88
  readonly kind: "u64"
78
- readonly closed: ClosedRoster<Name, H>
89
+ readonly closed: ClosedRoster<Name, Handles>
79
90
  }
80
91
 
81
- type AnyField = BoolField | StrField | U64Field | FreshU64Field | I64Field | BytesField | IntervalField | ClosedIdField
92
+ /** The closed-id top type the erased carrier's view of any closed id. */
93
+ type AnyClosedIdField = ClosedIdField<string, ClosedHandleTuple>
94
+
95
+ type AnyField =
96
+ | BoolField
97
+ | StrField
98
+ | U64Field
99
+ | FreshU64Field
100
+ | I64Field
101
+ | BytesField
102
+ | IntervalField
103
+ | AnyClosedIdField
104
+
105
+ /**
106
+ * The ONE structural interpreter of a field descriptor — the positional
107
+ * signature every equality judgment reads (the positive join wall in
108
+ * `#query/scope.ts` and the face pairing wall in `#face.ts`). Two fields
109
+ * are one shape exactly when their signatures are the same tuple: kind,
110
+ * width, interval element, and the roster as name plus the handle VECTOR
111
+ * (order and length carry meaning; a set would forget both).
112
+ */
113
+ type SignatureOf<F extends AnyField> = readonly [
114
+ F["kind"],
115
+ F extends { readonly width: infer W } ? W : undefined,
116
+ F extends { readonly element: infer E } ? E : undefined,
117
+ F extends {
118
+ readonly closed: { readonly name: infer N extends string; readonly handles: infer H extends ClosedHandleTuple }
119
+ }
120
+ ? readonly [N, H]
121
+ : undefined
122
+ ]
82
123
 
83
124
  type Infer<F extends AnyField> = F extends { readonly kind: "bool" }
84
125
  ? boolean
@@ -106,13 +147,44 @@ function literalShapeError(context: string, expected: string, value: unknown): E
106
147
  return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
107
148
  }
108
149
 
109
- function rosterOf(field: AnyField | undefined): ClosedRoster | undefined {
150
+ function rosterOf(field: AnyField | undefined): AnyClosedRoster | undefined {
110
151
  if (field !== undefined && "closed" in field) {
111
152
  return field.closed
112
153
  }
113
154
  return undefined
114
155
  }
115
156
 
157
+ /**
158
+ * The runtime twin of roster equality inside {@link SignatureOf}: same
159
+ * vocabulary name, same handle vector (order and length). Two absent
160
+ * rosters agree; a roster never agrees with a bare field.
161
+ */
162
+ function rostersAgree(a: AnyClosedRoster | undefined, b: AnyClosedRoster | undefined): boolean {
163
+ if (a === undefined || b === undefined) {
164
+ return a === b
165
+ }
166
+ return (
167
+ a.name === b.name &&
168
+ a.handles.length === b.handles.length &&
169
+ a.handles.every(function sameHandle(handle, index) {
170
+ return handle === b.handles[index]
171
+ })
172
+ )
173
+ }
174
+
175
+ /**
176
+ * The runtime twin of {@link SignatureOf} equality — the ONE spelling of
177
+ * "these two descriptors are one shape". Kind, width, interval element,
178
+ * and the roster vector must all agree.
179
+ */
180
+ function signaturesAgree(a: AnyField, b: AnyField): boolean {
181
+ const widthA = "width" in a ? a.width : undefined
182
+ const widthB = "width" in b ? b.width : undefined
183
+ const elementA = "element" in a ? a.element : undefined
184
+ const elementB = "element" in b ? b.element : undefined
185
+ return a.kind === b.kind && widthA === widthB && elementA === elementB && rostersAgree(rosterOf(a), rosterOf(b))
186
+ }
187
+
116
188
  function isIntervalValue(value: unknown): value is IntervalValue {
117
189
  return (
118
190
  typeof value === "object" &&
@@ -124,7 +196,7 @@ function isIntervalValue(value: unknown): value is IntervalValue {
124
196
  )
125
197
  }
126
198
 
127
- function handleLiteral(closed: ClosedRoster, value: unknown): LiteralSpec {
199
+ function handleLiteral(closed: AnyClosedRoster, value: unknown): LiteralSpec {
128
200
  if (typeof value !== "string") {
129
201
  throw literalShapeError("selection literal", `a ${closed.name} handle name (string)`, value)
130
202
  }
@@ -245,9 +317,12 @@ function literalOf(field: AnyField, value: unknown): LiteralSpec {
245
317
  }
246
318
 
247
319
  export type {
320
+ AnyClosedIdField,
321
+ AnyClosedRoster,
248
322
  AnyField,
249
323
  BoolField,
250
324
  BytesField,
325
+ ClosedHandleTuple,
251
326
  ClosedIdField,
252
327
  ClosedRoster,
253
328
  FreshU64Field,
@@ -255,6 +330,7 @@ export type {
255
330
  Infer,
256
331
  IntervalField,
257
332
  IntervalValue,
333
+ SignatureOf,
258
334
  StrField,
259
335
  U64Field
260
336
  }
@@ -269,6 +345,8 @@ export {
269
345
  literalOf,
270
346
  literalShapeError,
271
347
  rosterOf,
348
+ rostersAgree,
349
+ signaturesAgree,
272
350
  span,
273
351
  str,
274
352
  u64
package/src/index.ts CHANGED
@@ -100,9 +100,12 @@ export type {
100
100
  } from "#face.ts"
101
101
  export { on } from "#face.ts"
102
102
  export type {
103
+ AnyClosedIdField,
104
+ AnyClosedRoster,
103
105
  AnyField,
104
106
  BoolField,
105
107
  BytesField,
108
+ ClosedHandleTuple,
106
109
  ClosedIdField,
107
110
  ClosedRoster,
108
111
  FreshU64Field,
@@ -110,10 +113,12 @@ export type {
110
113
  Infer,
111
114
  IntervalField,
112
115
  IntervalValue,
116
+ SignatureOf,
113
117
  StrField,
114
118
  U64Field
115
119
  } from "#fields.ts"
116
120
  export { bool, bytes, i64, interval, span, str, u64 } from "#fields.ts"
121
+ export type { Same, SameLen } from "#judgment.ts"
117
122
  export type { ClassesOf, ClassWall, LawfulStatements, RelationClasses, SchemaClasses } from "#law.ts"
118
123
  export { lower } from "#lower.ts"
119
124
  export type { KeyFact } from "#marshal.ts"
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The type-level judgment kernel. Two primitives, no imports, no runtime.
3
+ *
4
+ * {@link Same} is definitional equality by mutual extension — the ONE
5
+ * spelling of `A = B` at the type tier. {@link SameLen} is Peano equality
6
+ * on tuple lengths — zero equals zero, successor recurses on successor,
7
+ * everything else is refused. An open array carries no Nat (its length is
8
+ * `number`), so it proves nothing.
9
+ */
10
+
11
+ type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false
12
+
13
+ type SameLen<A extends readonly unknown[], B extends readonly unknown[]> = number extends A["length"]
14
+ ? false
15
+ : number extends B["length"]
16
+ ? false
17
+ : A extends readonly [unknown, ...infer ARest]
18
+ ? B extends readonly [unknown, ...infer BRest]
19
+ ? SameLen<ARest, BRest>
20
+ : false
21
+ : B extends readonly [unknown, ...unknown[]]
22
+ ? false
23
+ : true
24
+
25
+ export type { Same, SameLen }
package/src/law.ts CHANGED
@@ -58,6 +58,7 @@ import * as errors from "@superbuilders/errors"
58
58
  import type { AnyClosed } from "#closed.ts"
59
59
  import { isClosedMember, sealedFieldsOf } from "#closed.ts"
60
60
  import type { FaceData } from "#face.ts"
61
+ import type { Same } from "#judgment.ts"
61
62
  import type { AnyRelation, RelationFields } from "#relation.ts"
62
63
  import type { SchemaRelation, SchemaRelations } from "#schema.ts"
63
64
  import { renderStatement, type Statement } from "#statements.ts"
@@ -196,7 +197,7 @@ type WallScan<Comps extends readonly string[], Gens extends string, Pairs extend
196
197
  : WallScan<T, Gens, Pairs>
197
198
  : unknown
198
199
 
199
- type SetEq<A extends string, B extends string> = [A] extends [B] ? ([B] extends [A] ? true : false) : false
200
+ type SetEq<A extends string, B extends string> = Same<A, B>
200
201
 
201
202
  type KeyEntry = readonly [string, string]
202
203
 
package/src/marshal.ts CHANGED
@@ -28,7 +28,7 @@
28
28
  */
29
29
 
30
30
  import * as errors from "@superbuilders/errors"
31
- import type { AnyField, ClosedRoster } from "#fields.ts"
31
+ import type { AnyClosedRoster, AnyField } from "#fields.ts"
32
32
  import { isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
33
33
  import type { FactValue } from "#native.ts"
34
34
  import type { AnyRelation, Fact, FreshKeys, RelationData } from "#relation.ts"
@@ -61,7 +61,7 @@ function isStringIndexed(value: object): value is Readonly<Record<string, unknow
61
61
  * violation. `indexOf` is the whole machine (the roster is ≤ 256 rows,
62
62
  * engine law — no map is warranted).
63
63
  */
64
- function closedCellOf(context: string, closed: ClosedRoster, name: string): FactValue {
64
+ function closedCellOf(context: string, closed: AnyClosedRoster, name: string): FactValue {
65
65
  const id = closed.handles.indexOf(name)
66
66
  if (id === -1) {
67
67
  throw errors.new(
@@ -71,7 +71,7 @@ function closedCellOf(context: string, closed: ClosedRoster, name: string): Fact
71
71
  return BigInt(id)
72
72
  }
73
73
 
74
- function handleOf(context: string, closed: ClosedRoster, cell: FactValue): string {
74
+ function handleOf(context: string, closed: AnyClosedRoster, cell: FactValue): string {
75
75
  if (typeof cell !== "bigint") {
76
76
  throw literalShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
77
77
  }
package/src/query/atom.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as errors from "@superbuilders/errors"
2
- import type { AnyField, ClosedIdField, ClosedRoster, Infer, IntervalValue } from "#fields.ts"
2
+ import type { AnyClosedRoster, AnyField, Infer, IntervalValue } from "#fields.ts"
3
3
  import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
4
4
  import type {
5
+ AntiJoinOk,
5
6
  AnyVar,
6
7
  ClassedField,
7
8
  JoinOk,
@@ -76,7 +77,7 @@ type FindEntryData =
76
77
  interface FindColumn {
77
78
  readonly name: string
78
79
  readonly entry: FindEntryData
79
- readonly closed: ClosedRoster | undefined
80
+ readonly closed: AnyClosedRoster | undefined
80
81
  readonly slot: ClassedField | undefined
81
82
  }
82
83
 
@@ -139,7 +140,7 @@ type DerivedTable = InteriorData | RecHead
139
140
 
140
141
  type BindingInput<F extends AnyField> =
141
142
  | Infer<F>
142
- | (F extends ClosedIdField ? readonly Infer<F>[] : never)
143
+ | (F extends { readonly closed: { readonly handles: readonly string[] } } ? readonly Infer<F>[] : never)
143
144
  | (F extends { readonly kind: "interval" } ? bigint : never)
144
145
  | AnyVar
145
146
  | Param<string>
@@ -363,7 +364,7 @@ function not(
363
364
  * closed-bound terms — the construction-time validations in
364
365
  * `#query/lower.ts` are that ban's runtime twin.
365
366
  */
366
- type NumericVarOk<V extends AnyVar> = V["field"] extends { readonly closed: ClosedRoster }
367
+ type NumericVarOk<V extends AnyVar> = V["field"] extends { readonly closed: AnyClosedRoster }
367
368
  ? false
368
369
  : V["field"]["kind"] extends "u64" | "i64"
369
370
  ? true
@@ -450,7 +451,7 @@ type EqOk<Classes extends SchemaClasses, L, R> = L extends AnyVar
450
451
  : false
451
452
 
452
453
  type NotBindingOk<Classes extends SchemaClasses, S extends ClassedField, T> = T extends AnyVar
453
- ? JoinOk<MintSlotOf<Classes, T>, S> extends true
454
+ ? AntiJoinOk<MintSlotOf<Classes, T>, S> extends true
454
455
  ? true
455
456
  : false
456
457
  : true