@bjornpagen/bumbledb 0.1.0 → 0.3.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 (112) hide show
  1. package/COOKBOOK.md +1450 -0
  2. package/README.md +69 -30
  3. package/dist/closed.d.ts +157 -38
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +232 -47
  6. package/dist/closed.js.map +1 -1
  7. package/dist/count.d.ts +64 -17
  8. package/dist/count.d.ts.map +1 -1
  9. package/dist/count.js +37 -14
  10. package/dist/count.js.map +1 -1
  11. package/dist/db.d.ts +27 -8
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +33 -10
  14. package/dist/db.js.map +1 -1
  15. package/dist/exhume.d.ts +12 -11
  16. package/dist/exhume.d.ts.map +1 -1
  17. package/dist/exhume.js +7 -6
  18. package/dist/exhume.js.map +1 -1
  19. package/dist/face.d.ts +135 -30
  20. package/dist/face.d.ts.map +1 -1
  21. package/dist/face.js +44 -19
  22. package/dist/face.js.map +1 -1
  23. package/dist/fields.d.ts +130 -130
  24. package/dist/fields.d.ts.map +1 -1
  25. package/dist/fields.js +60 -94
  26. package/dist/fields.js.map +1 -1
  27. package/dist/index.d.ts +29 -24
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +19 -14
  30. package/dist/index.js.map +1 -1
  31. package/dist/law.d.ts +224 -0
  32. package/dist/law.d.ts.map +1 -0
  33. package/dist/law.js +224 -0
  34. package/dist/law.js.map +1 -0
  35. package/dist/lower.d.ts +17 -7
  36. package/dist/lower.d.ts.map +1 -1
  37. package/dist/lower.js +66 -24
  38. package/dist/lower.js.map +1 -1
  39. package/dist/marshal.d.ts +36 -21
  40. package/dist/marshal.d.ts.map +1 -1
  41. package/dist/marshal.js +41 -25
  42. package/dist/marshal.js.map +1 -1
  43. package/dist/native.d.ts +6 -2
  44. package/dist/native.d.ts.map +1 -1
  45. package/dist/native.js +2 -2
  46. package/dist/native.js.map +1 -1
  47. package/dist/query/atom.d.ts +370 -174
  48. package/dist/query/atom.d.ts.map +1 -1
  49. package/dist/query/atom.js +75 -172
  50. package/dist/query/atom.js.map +1 -1
  51. package/dist/query/lower.d.ts +312 -109
  52. package/dist/query/lower.d.ts.map +1 -1
  53. package/dist/query/lower.js +906 -387
  54. package/dist/query/lower.js.map +1 -1
  55. package/dist/query/predicate.d.ts +70 -88
  56. package/dist/query/predicate.d.ts.map +1 -1
  57. package/dist/query/predicate.js +118 -72
  58. package/dist/query/predicate.js.map +1 -1
  59. package/dist/query/run.d.ts +19 -16
  60. package/dist/query/run.d.ts.map +1 -1
  61. package/dist/query/run.js +31 -24
  62. package/dist/query/run.js.map +1 -1
  63. package/dist/query/scope.d.ts +178 -127
  64. package/dist/query/scope.d.ts.map +1 -1
  65. package/dist/query/scope.js +111 -108
  66. package/dist/query/scope.js.map +1 -1
  67. package/dist/query/select.d.ts +102 -80
  68. package/dist/query/select.d.ts.map +1 -1
  69. package/dist/query/select.js +39 -34
  70. package/dist/query/select.js.map +1 -1
  71. package/dist/relation.d.ts +48 -38
  72. package/dist/relation.d.ts.map +1 -1
  73. package/dist/relation.js +24 -19
  74. package/dist/relation.js.map +1 -1
  75. package/dist/schema.d.ts +41 -3
  76. package/dist/schema.d.ts.map +1 -1
  77. package/dist/schema.js +34 -9
  78. package/dist/schema.js.map +1 -1
  79. package/dist/spec.d.ts +7 -5
  80. package/dist/spec.d.ts.map +1 -1
  81. package/dist/spec.js.map +1 -1
  82. package/dist/statements.d.ts +91 -36
  83. package/dist/statements.d.ts.map +1 -1
  84. package/dist/statements.js +42 -12
  85. package/dist/statements.js.map +1 -1
  86. package/package.json +4 -3
  87. package/src/closed.ts +448 -95
  88. package/src/count.ts +112 -18
  89. package/src/db.ts +45 -17
  90. package/src/exhume.ts +12 -11
  91. package/src/face.ts +211 -41
  92. package/src/fields.ts +199 -234
  93. package/src/index.ts +78 -61
  94. package/src/law.ts +519 -0
  95. package/src/lower.ts +82 -27
  96. package/src/marshal.ts +48 -30
  97. package/src/native.ts +8 -4
  98. package/src/query/atom.ts +525 -374
  99. package/src/query/lower.ts +1469 -538
  100. package/src/query/predicate.ts +208 -161
  101. package/src/query/run.ts +35 -25
  102. package/src/query/scope.ts +264 -218
  103. package/src/query/select.ts +168 -93
  104. package/src/relation.ts +47 -47
  105. package/src/schema.ts +69 -18
  106. package/src/spec.ts +7 -5
  107. package/src/statements.ts +124 -41
  108. package/dist/brand.d.ts +0 -59
  109. package/dist/brand.d.ts.map +0 -1
  110. package/dist/brand.js +0 -47
  111. package/dist/brand.js.map +0 -1
  112. package/src/brand.ts +0 -82
package/src/schema.ts CHANGED
@@ -12,7 +12,8 @@
12
12
  import * as errors from "@superbuilders/errors"
13
13
  import type { AnyClosed } from "#closed.ts"
14
14
  import type { FaceData } from "#face.ts"
15
- import { assertDeclarationOrderKey, type FieldData } from "#fields.ts"
15
+ import { type AnyField, assertDeclarationOrderKey, type ClosedRoster } from "#fields.ts"
16
+ import { type ClassesOf, classesComplete, computeClasses, type LawfulStatements, type SchemaClasses } from "#law.ts"
16
17
  import type { AnyRelation } from "#relation.ts"
17
18
  import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
18
19
  import { renderStatement, type Statement } from "#statements.ts"
@@ -38,7 +39,7 @@ function collectImplied(name: string, relations: SchemaRelations): Set<string> {
38
39
  continue
39
40
  }
40
41
  for (const declared of member.data.fields) {
41
- if (declared.field.minted) {
42
+ if ("fresh" in declared.field && declared.field.fresh === true) {
42
43
  implied.add(`${member.name}(${declared.name}) -> ${member.name}`)
43
44
  }
44
45
  }
@@ -74,8 +75,8 @@ function verifyMembership(name: string, relations: SchemaRelations, statement: S
74
75
  }
75
76
  }
76
77
 
77
- /** Finds a face's field description by name, across both relation kinds. */
78
- function faceField(face: FaceData, fieldName: string): FieldData | undefined {
78
+ /** Finds a face's field descriptor by name, across both relation kinds. */
79
+ function faceField(face: FaceData, fieldName: string): AnyField | undefined {
79
80
  const data = face.owner.data
80
81
  if ("handles" in data) {
81
82
  const column = data.columns.find(function byName(candidate) {
@@ -89,6 +90,18 @@ function faceField(face: FaceData, fieldName: string): FieldData | undefined {
89
90
  return declared?.field
90
91
  }
91
92
 
93
+ /**
94
+ * The roster a field resolves handles through: present exactly on a closed
95
+ * reference descriptor (the structural `closed` property — S1's
96
+ * `ClosedIdField`), absent on every other field kind.
97
+ */
98
+ function rosterOf(field: AnyField | undefined): ClosedRoster | undefined {
99
+ if (field !== undefined && "closed" in field) {
100
+ return field.closed
101
+ }
102
+ return undefined
103
+ }
104
+
92
105
  /** Flattens one binding's literal set into its literals. */
93
106
  function bindingLiterals(set: LiteralSetSpec): readonly LiteralSpec[] {
94
107
  if (set.kind === "one") {
@@ -109,20 +122,18 @@ function verifyBindingHandles(
109
122
  binding: { readonly field: string; readonly set: LiteralSetSpec },
110
123
  rendered: string
111
124
  ): void {
112
- const field = faceField(face, binding.field)
125
+ const roster = rosterOf(faceField(face, binding.field))
113
126
  for (const literal of bindingLiterals(binding.set)) {
114
127
  if (literal.kind !== "handle") {
115
128
  continue
116
129
  }
117
- if (field?.closed === undefined) {
130
+ if (roster === undefined) {
118
131
  throw errors.new(
119
- `schema ${name}: ${face.owner.name}.${binding.field} is not a closed-relation reference — the handle literal ${literal.handle} is legal only on a field whose newtype is a closed relation's handle newtype — ${rendered}`
132
+ `schema ${name}: ${face.owner.name}.${binding.field} is not a closed-relation reference — the handle literal ${literal.handle} is legal only on a field carrying a closed relation's roster — ${rendered}`
120
133
  )
121
134
  }
122
- if (!field.closed.handles.includes(literal.handle)) {
123
- throw errors.new(
124
- `schema ${name}: closed relation ${field.closed.name} has no handle ${literal.handle} — ${rendered}`
125
- )
135
+ if (!roster.handles.includes(literal.handle)) {
136
+ throw errors.new(`schema ${name}: closed relation ${roster.name} has no handle ${literal.handle} — ${rendered}`)
126
137
  }
127
138
  }
128
139
  }
@@ -216,7 +227,7 @@ function verifyClosedReferenceBinding(
216
227
  if (!spellsHandle) {
217
228
  return
218
229
  }
219
- const roster = faceField(face, binding.field)?.closed
230
+ const roster = rosterOf(faceField(face, binding.field))
220
231
  if (roster === undefined) {
221
232
  return
222
233
  }
@@ -237,16 +248,43 @@ type SchemaRelation = AnyRelation | AnyClosed
237
248
  /** The relation record a schema is generic over — what `Db` and queries key on. */
238
249
  type SchemaRelations = Record<string, SchemaRelation>
239
250
 
240
- /** A theory value: named relations plus the DECLARED dependency statements. */
241
- interface Schema<Rels extends SchemaRelations> {
251
+ /**
252
+ * A theory value: named relations, the DECLARED dependency statements, and
253
+ * the LAW-COMPUTED class map (`classes` — relation → field → class name,
254
+ * `undefined` = bare). The class map is THE domain authority: `schema()`
255
+ * computes it FROM the statement list at both tiers (the type through
256
+ * {@link ClassesOf}, the value through the union-find twin), queries
257
+ * compare class names off it, and the wire lowering emits it as the spec
258
+ * `newtype` labels. Nothing is ever synthesized: `statements` is exactly
259
+ * the declared list, in written order.
260
+ */
261
+ interface Schema<Rels extends SchemaRelations, Classes extends SchemaClasses = SchemaClasses> {
242
262
  readonly name: string
243
263
  readonly relations: Rels
244
264
  readonly statements: readonly Statement[]
265
+ readonly classes: Classes
245
266
  }
246
267
 
247
268
  /** Any schema value, whatever its relation record. */
248
269
  type AnySchema = Schema<SchemaRelations>
249
270
 
271
+ /**
272
+ * Forces the class map to EVALUATE at the `schema()` boundary: a two-level
273
+ * mapped copy, type-identical to `C` — but instantiation resolves it to
274
+ * the finished relation → field → class record, so hovering a schema value
275
+ * (or anything carrying its `Classes` parameter — queries, `Db`) shows the
276
+ * computed record instead of the unevaluated `ClassesOf<...>` application
277
+ * dragging the whole statement-tuple type along. The conditional wrapper
278
+ * is the display mechanism, not a judgment: resolving it drops the alias
279
+ * reference, so tsc renders the finished record (measured against tsc's
280
+ * own type rendering; a bare mapped alias still displays by name).
281
+ * Display-only by construction; `classesComplete` guards the same type at
282
+ * the value tier.
283
+ */
284
+ type EvaluatedClasses<C extends SchemaClasses> = C extends SchemaClasses
285
+ ? { readonly [N in keyof C]: { readonly [F in keyof C[N]]: C[N][F] } }
286
+ : never
287
+
250
288
  /**
251
289
  * Assembles a theory:
252
290
  * `schema("Ledger", { Kind, Account, Holder }, [ ...statements ])`.
@@ -267,12 +305,21 @@ type AnySchema = Schema<SchemaRelations>
267
305
  * list: the engine materializes them itself, in its own pinned order
268
306
  * (`SchemaDescriptor::materialized_statements`), and restating them would
269
307
  * double them.
308
+ *
309
+ * THE LAW-TYPING happens here too (rulings 2/3 — the laws type the
310
+ * columns): the statement list induces the equivalence classes over field
311
+ * slots, at the TYPE level ({@link ClassesOf} — spell the statement list
312
+ * inline so the tuple type stays precise) and at runtime (the union-find
313
+ * twin), and the one-generator-per-class wall holds at both tiers — the
314
+ * {@link LawfulStatements} verdict lands the compile error on the
315
+ * statements argument; `computeClasses` throws the same content naming the
316
+ * exact statement.
270
317
  */
271
- function schema<const Rels extends SchemaRelations>(
318
+ function schema<const Rels extends SchemaRelations, const Stmts extends readonly Statement[]>(
272
319
  name: string,
273
320
  relations: Rels,
274
- statements: readonly Statement[]
275
- ): Schema<Rels> {
321
+ statements: Stmts & LawfulStatements<Rels, Stmts>
322
+ ): Schema<Rels, EvaluatedClasses<ClassesOf<Rels, Stmts>>> {
276
323
  const implied = collectImplied(name, relations)
277
324
  const seen = new Set<string>()
278
325
  for (const statement of statements) {
@@ -290,7 +337,11 @@ function schema<const Rels extends SchemaRelations>(
290
337
  verifyHandles(name, statement, rendered)
291
338
  }
292
339
  verifyClosedReferences(name, statements)
293
- return Object.freeze({ name, relations, statements: Object.freeze([...statements]) })
340
+ const classes = computeClasses(name, relations, statements)
341
+ if (!classesComplete<EvaluatedClasses<ClassesOf<Rels, Stmts>>>(classes, relations)) {
342
+ throw errors.new(`schema ${name}: class-map construction incomplete`)
343
+ }
344
+ return Object.freeze({ name, relations, statements: Object.freeze([...statements]), classes })
294
345
  }
295
346
 
296
347
  export type { AnySchema, Schema, SchemaRelation, SchemaRelations }
package/src/spec.ts CHANGED
@@ -95,9 +95,10 @@ type WindowSpec =
95
95
  | { readonly kind: "floor"; readonly lo: bigint }
96
96
 
97
97
  /**
98
- * One field: name, structural type, host newtype name (carried for handle
99
- * resolution only dropped by the engine at descriptor lowering), and the
100
- * `fresh` mint mark.
98
+ * One field: name, structural type, host newtype name the field's
99
+ * DOMAIN (the macro's declared `as NewType`; the SDK's law-computed class
100
+ * name), carried for handle resolution only, dropped by the engine at
101
+ * descriptor lowering and never fingerprinted — and the `fresh` mint mark.
101
102
  */
102
103
  interface FieldSpec {
103
104
  readonly name: string
@@ -120,8 +121,9 @@ interface RowSpec {
120
121
  * kind); a closed relation's `fields` are its declared intrinsic columns
121
122
  * only — the synthetic (`id`, u64) handle field is materialized by the
122
123
  * engine's schema validation. `newtype` is the handle newtype of a closed
123
- * relation (the SDK uses the closed relation's own name), undefined on an
124
- * ordinary one.
124
+ * relation (the SDK emits the id's law-computed generator class,
125
+ * `` `${name}.id` `` — the same label every referencing field carries by
126
+ * law), undefined on an ordinary one.
125
127
  */
126
128
  interface RelationSpec {
127
129
  readonly name: string
package/src/statements.ts CHANGED
@@ -4,30 +4,64 @@
4
4
  * form, conditional containment, the bidirectional `==` abbreviation, and
5
5
  * the cardinality window. A statement value is opaque and inert — no
6
6
  * methods, no fluent continuation: a fact about the theory, not a builder.
7
+ *
8
+ * Every field reference is checked against the relation it names in the
9
+ * TYPE — existence through {@link FaceFields} (`on(R, "nope")` does not
10
+ * compile) and STRUCTURAL compatibility through {@link SameShapes}: the two
11
+ * faces' projected kind/width/element triples are read off the schema type
12
+ * (the minimal kernel — descriptors are pure structure) and constrained
13
+ * positionwise equal, so a u64 face against a str face, a bytes width
14
+ * mismatch, or an interval element mismatch is a compile error. Domains are
15
+ * NOT compared here — there is no domain to compare at construction: the
16
+ * statements themselves are what define the equivalence classes, and the
17
+ * domain wall lives where they aggregate — `schema()` (the
18
+ * one-generator-per-class law) and query joins (class names off the schema
19
+ * type). What is only a SEMANTIC property — the target side of a
20
+ * containment resolving a declared key of its relation — is DELIBERATELY
21
+ * not (and cannot be) stated here: whether `B(y)` is a key of `B` depends
22
+ * on which `key()` statements the surrounding `schema()` collects, a set no
23
+ * face type can see; it stays the engine's typed `SchemaError` judgment at
24
+ * `Db.create`/`Db.open` (the two-boundary split, engine as final
25
+ * authority).
7
26
  */
8
27
 
9
28
  import * as errors from "@superbuilders/errors"
10
- import { phantom } from "#brand.ts"
11
29
  import type { Count } from "#count.ts"
12
- import { type AnyFace, type FaceData, renderFace, type SameArity } from "#face.ts"
30
+ import { type AnyFace, type FaceData, renderFace, type SameArity, type SameShapes } from "#face.ts"
13
31
  import type { AnyRelation, RelationFields } from "#relation.ts"
14
32
  import { renderWindow, type WindowSpec } from "#spec.ts"
15
33
 
34
+ /** A `key()` statement's runtime description — owner and projection carried at exact types. */
35
+ interface KeyData<R extends AnyRelation, Projection extends readonly string[]> {
36
+ readonly kind: "key"
37
+ readonly owner: R
38
+ readonly projection: Projection
39
+ }
40
+
41
+ /**
42
+ * A containment statement's runtime description — the two faces carried at
43
+ * their EXACT types (owner names and projection tuples are honest runtime
44
+ * properties, and they are the type-level carrier `schema()`'s law-typing
45
+ * pairs slots through). The defaults are the wide shape renderers and the
46
+ * wire lowering consume.
47
+ */
48
+ interface ContainmentData<Src extends FaceData = FaceData, Tgt extends FaceData = FaceData> {
49
+ readonly kind: "containment"
50
+ readonly source: Src
51
+ readonly target: Tgt
52
+ readonly bidirectional: boolean
53
+ }
54
+
55
+ /** A window statement's runtime description — target-left, faces at exact types like {@link ContainmentData}. */
56
+ interface WindowData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
57
+ readonly kind: "window"
58
+ readonly target: Tgt
59
+ readonly window: WindowSpec
60
+ readonly source: Src
61
+ }
62
+
16
63
  /** One statement's runtime description, tagged by form. */
17
- type StatementData =
18
- | { readonly kind: "key"; readonly owner: AnyRelation; readonly projection: readonly string[] }
19
- | {
20
- readonly kind: "containment"
21
- readonly source: FaceData
22
- readonly target: FaceData
23
- readonly bidirectional: boolean
24
- }
25
- | {
26
- readonly kind: "window"
27
- readonly target: FaceData
28
- readonly window: WindowSpec
29
- readonly source: FaceData
30
- }
64
+ type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | WindowData
31
65
 
32
66
  /** An opaque statement value — what `schema()` assembles into a theory. */
33
67
  interface Statement {
@@ -35,15 +69,33 @@ interface Statement {
35
69
  }
36
70
 
37
71
  /**
38
- * A `key()` statement as a TYPED value: the statement plus a phantom
39
- * carrying its owner and projection tuple what the key-statement-selected
72
+ * A containment (or `==` bijection) statement as a TYPED value: `data`
73
+ * carries both faces at their exact types, so the schema-level class laws
74
+ * can read every paired (relation, field) slot off the statement type —
75
+ * spell the statement list inline in `schema()` and the equivalence
76
+ * classes compute at the type level too. Structurally still a plain
77
+ * {@link Statement}.
78
+ */
79
+ interface ContainedStatement<Src extends FaceData, Tgt extends FaceData> extends Statement {
80
+ readonly data: ContainmentData<Src, Tgt>
81
+ }
82
+
83
+ /** A window statement as a TYPED value — the {@link ContainedStatement} of the window form. */
84
+ interface WindowStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
85
+ readonly data: WindowData<Tgt, Src>
86
+ }
87
+
88
+ /**
89
+ * A `key()` statement as a TYPED value: its `data` carries the owner
90
+ * relation and the projection tuple at their EXACT types (honest runtime
91
+ * properties — no phantom), which is what the key-statement-selected
40
92
  * `get(relation, keyStatement, key)` overload types its key object by
41
- * (`docs/architecture/70-api.md` § the freeze, the multi-key typed get).
42
- * Structurally still a plain {@link Statement}; the phantom is never present
43
- * at runtime.
93
+ * (`docs/architecture/70-api.md` § the freeze, the multi-key typed get) and
94
+ * what resolves each projected field's descriptor through the owner's
95
+ * schema type. Structurally still a plain {@link Statement}.
44
96
  */
45
97
  interface KeyStatement<R extends AnyRelation, Projection extends readonly string[]> extends Statement {
46
- readonly [phantom]?: { readonly owner: R; readonly projection: Projection }
98
+ readonly data: KeyData<R, Projection>
47
99
  }
48
100
 
49
101
  /**
@@ -51,9 +103,11 @@ interface KeyStatement<R extends AnyRelation, Projection extends readonly string
51
103
  * parameter exists (the FD-with-selection shape is unrepresentable, as in
52
104
  * the grammar), and only ordinary relations are accepted: a closed
53
105
  * relation's key `R(id) -> R` is materialized by the engine, so an
54
- * explicit one would only ever be a duplicate. The projection tuple is
55
- * carried in the returned value's type ({@link KeyStatement}), so keyed
56
- * point reads through THIS statement are typed field-for-field.
106
+ * explicit one would only ever be a duplicate. Every projected name is
107
+ * checked against `R`'s field block in the type, and the tuple is carried
108
+ * in the returned value's type ({@link KeyStatement}) — keyed point reads
109
+ * through THIS statement are typed field-for-field, descriptors resolvable
110
+ * through the owner's schema type.
57
111
  */
58
112
  function key<
59
113
  R extends AnyRelation,
@@ -64,22 +118,28 @@ function key<
64
118
  `key(${relation.name}, ...): closedness already materializes ${relation.name}(id) -> ${relation.name} — an explicit key on a closed relation is rejected as a duplicate`
65
119
  )
66
120
  }
67
- const data: StatementData = Object.freeze({
121
+ const data: KeyData<R, Projection> = Object.freeze({
68
122
  kind: "key",
69
123
  owner: relation,
70
- projection: Object.freeze([...fields])
124
+ projection: Object.freeze(fields)
71
125
  })
72
126
  return Object.freeze({ data })
73
127
  }
74
128
 
75
129
  /**
76
- * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. The target
77
- * side must resolve a declared key of B DELIBERATELY judged by the
130
+ * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. Arity mismatch
131
+ * between the two faces is a type error ({@link SameArity}); a structurally
132
+ * mismatched pair is a type error ({@link SameShapes} — positionwise
133
+ * equality of the projected kind/width/element triples). The target side
134
+ * must resolve a declared key of B — a SEMANTIC property of the whole
135
+ * statement set that no face type can state, DELIBERATELY judged by the
78
136
  * engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
79
- * Arity mismatch between the two faces is a type error ({@link SameArity}).
80
137
  */
81
- function contained<A extends AnyFace, B extends AnyFace>(source: A, target: B & SameArity<A, B>): Statement {
82
- const data: StatementData = Object.freeze({
138
+ function contained<A extends AnyFace, B extends AnyFace>(
139
+ source: A,
140
+ target: B & SameArity<A, B> & SameShapes<A, B>
141
+ ): ContainedStatement<A["data"], B["data"]> {
142
+ const data: ContainmentData<A["data"], B["data"]> = Object.freeze({
83
143
  kind: "containment",
84
144
  source: source.data,
85
145
  target: target.data,
@@ -89,12 +149,19 @@ function contained<A extends AnyFace, B extends AnyFace>(source: A, target: B &
89
149
  }
90
150
 
91
151
  /**
92
- * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance. It
93
- * lowers to the two adjacent containments in the `A <= B` first order
94
- * (macro parity) and renders as `==` once, in the written orientation.
152
+ * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance: the
153
+ * selected `==` bijection, a keyed one-to-one correspondence between the
154
+ * two faces (each side contains the other). It lowers to the two adjacent
155
+ * containments in the `A <= B` first order (macro parity — the engine
156
+ * performs the split, source-first) and renders as `==` once, in the
157
+ * written orientation. Faces pair by arity AND structural shape, exactly
158
+ * as {@link contained}.
95
159
  */
96
- function mirrors<A extends AnyFace, B extends AnyFace>(source: A, target: B & SameArity<A, B>): Statement {
97
- const data: StatementData = Object.freeze({
160
+ function mirrors<A extends AnyFace, B extends AnyFace>(
161
+ source: A,
162
+ target: B & SameArity<A, B> & SameShapes<A, B>
163
+ ): ContainedStatement<A["data"], B["data"]> {
164
+ const data: ContainmentData<A["data"], B["data"]> = Object.freeze({
98
165
  kind: "containment",
99
166
  source: source.data,
100
167
  target: target.data,
@@ -108,10 +175,17 @@ function mirrors<A extends AnyFace, B extends AnyFace>(source: A, target: B & Sa
108
175
  * LEFT face is the window's TARGET, the per-group parent (B-family,
109
176
  * target-left — macro parity), and the RIGHT face is the counted source.
110
177
  * `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
111
- * Holder id groups at most three Account rows by holder.
178
+ * Holder id groups at most three Account rows by holder. The two faces
179
+ * pair by arity AND structural shape ({@link SameShapes}), exactly as
180
+ * containment — the grouping join reads the same positionwise field
181
+ * pairing.
112
182
  */
113
- function window<B extends AnyFace, A extends AnyFace>(target: B, count: Count, source: A & SameArity<B, A>): Statement {
114
- const data: StatementData = Object.freeze({
183
+ function window<B extends AnyFace, A extends AnyFace>(
184
+ target: B,
185
+ count: Count,
186
+ source: A & SameArity<B, A> & SameShapes<B, A>
187
+ ): WindowStatement<B["data"], A["data"]> {
188
+ const data: WindowData<B["data"], A["data"]> = Object.freeze({
115
189
  kind: "window",
116
190
  target: target.data,
117
191
  window: count.window,
@@ -144,5 +218,14 @@ function renderStatement(statement: Statement): string {
144
218
  }
145
219
  }
146
220
 
147
- export type { KeyStatement, Statement, StatementData }
221
+ export type {
222
+ ContainedStatement,
223
+ ContainmentData,
224
+ KeyData,
225
+ KeyStatement,
226
+ Statement,
227
+ StatementData,
228
+ WindowData,
229
+ WindowStatement
230
+ }
148
231
  export { contained, key, mirrors, renderStatement, window }
package/dist/brand.d.ts DELETED
@@ -1,59 +0,0 @@
1
- /**
2
- * Nominal branding — the TS analog of the Rust macro's host newtypes
3
- * (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
4
- * is a phantom: it exists only in the type, tsc polices the wall exactly as
5
- * rustc polices newtype domains, and nothing is allocated or wrapped at
6
- * runtime.
7
- */
8
- /**
9
- * The brand key. A real runtime symbol (so modules can import it without a
10
- * `declare`-only lie), but no branded value ever carries the property — the
11
- * brand is purely a typing device.
12
- */
13
- declare const brand: unique symbol;
14
- /**
15
- * The phantom-value key used by field values, field references, and faces
16
- * to carry their value type without any runtime representation.
17
- */
18
- declare const phantom: unique symbol;
19
- /**
20
- * A branded scalar: `T` walled off under the literal name `Name`. A
21
- * `Brand<bigint, "HolderId">` is not assignable where a
22
- * `Brand<bigint, "AccountId">` is expected — the Rust newtype wall,
23
- * verbatim. Scalars brand as `bigint` (u64/i64), `Uint8Array` (bytes), and
24
- * whole interval objects (`Interval<Name>`); `bool` and `str` take no
25
- * newtype, exactly as the macro's `as` grammar refuses them.
26
- */
27
- type Brand<T, Name extends string> = T & {
28
- readonly [brand]: Name;
29
- };
30
- /**
31
- * A half-open interval `[start, end)` as a plain value object. The ray is
32
- * representable (`end` = the element domain's MAX_END); widths and
33
- * signedness are NOT modeled here — the engine judges widths at the typed
34
- * write boundary, the brand blocks cross-field assignment, and nothing
35
- * else is TS's business. Interval newtypes derive no order (the Rust
36
- * refusal, `docs/architecture/10-data-model.md`), so no comparators exist.
37
- */
38
- interface IntervalValue {
39
- readonly start: bigint;
40
- readonly end: bigint;
41
- }
42
- /**
43
- * A branded interval: the whole `{ start, end }` object walled under
44
- * `Name` — the `interval<i64> as ActiveDuring` analog.
45
- */
46
- type Interval<Name extends string> = Brand<IntervalValue, Name>;
47
- /**
48
- * Constructs an interval literal — the `start..end` spelling. Half-open
49
- * and nonempty by construction: `start >= end` is a typed construction
50
- * error (parse, don't validate — the same invariant Rust's
51
- * `Interval::new` enforces at the host boundary). The default `never`
52
- * brand makes a fresh literal assignable to any interval field or brand,
53
- * the wrap-at-construction idiom; pass the brand explicitly
54
- * (`span<"ActiveDuring">(0n, 10n)`) to pin it.
55
- */
56
- declare function span<Name extends string = never>(start: bigint, end: bigint): Interval<Name>;
57
- export type { Brand, Interval, IntervalValue };
58
- export { brand, phantom, span };
59
- //# sourceMappingURL=brand.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,QAAA,MAAM,KAAK,EAAE,OAAO,MAAiC,CAAA;AAErD;;;GAGG;AACH,QAAA,MAAM,OAAO,EAAE,OAAO,MAAmC,CAAA;AAEzD;;;;;;;GAOG;AACH,KAAK,KAAK,CAAC,CAAC,EAAE,IAAI,SAAS,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAEnE;;;;;;;GAOG;AACH,UAAU,aAAa;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,KAAK,QAAQ,CAAC,IAAI,SAAS,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;AAE/D;;;;;;;;GAQG;AACH,iBAAS,IAAI,CAAC,IAAI,SAAS,MAAM,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAMrF;AAED,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA"}
package/dist/brand.js DELETED
@@ -1,47 +0,0 @@
1
- /**
2
- * Nominal branding — the TS analog of the Rust macro's host newtypes
3
- * (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
4
- * is a phantom: it exists only in the type, tsc polices the wall exactly as
5
- * rustc polices newtype domains, and nothing is allocated or wrapped at
6
- * runtime.
7
- */
8
- import * as errors from "@superbuilders/errors";
9
- /**
10
- * The brand-minting guard behind {@link span} — the one nominal step, as a
11
- * type guard carrying the interval's REAL invariant (`start < end`, the
12
- * same check Rust's `Interval::new` runs): a value that passes IS a legal
13
- * interval of any brand, exactly as a Rust newtype wraps a checked
14
- * `Interval<T>` at construction.
15
- */
16
- function isNonemptyInterval(value) {
17
- return value.start < value.end;
18
- }
19
- /**
20
- * The brand key. A real runtime symbol (so modules can import it without a
21
- * `declare`-only lie), but no branded value ever carries the property — the
22
- * brand is purely a typing device.
23
- */
24
- const brand = Symbol("bumbledb.brand");
25
- /**
26
- * The phantom-value key used by field values, field references, and faces
27
- * to carry their value type without any runtime representation.
28
- */
29
- const phantom = Symbol("bumbledb.phantom");
30
- /**
31
- * Constructs an interval literal — the `start..end` spelling. Half-open
32
- * and nonempty by construction: `start >= end` is a typed construction
33
- * error (parse, don't validate — the same invariant Rust's
34
- * `Interval::new` enforces at the host boundary). The default `never`
35
- * brand makes a fresh literal assignable to any interval field or brand,
36
- * the wrap-at-construction idiom; pass the brand explicitly
37
- * (`span<"ActiveDuring">(0n, 10n)`) to pin it.
38
- */
39
- function span(start, end) {
40
- const value = Object.freeze({ start, end });
41
- if (!isNonemptyInterval(value)) {
42
- throw errors.new(`interval is half-open and nonempty: start must be < end (got ${start}..${end})`);
43
- }
44
- return value;
45
- }
46
- export { brand, phantom, span };
47
- //# sourceMappingURL=brand.js.map
package/dist/brand.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"brand.js","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAA;AAE/C;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAsB,KAAoB;IACpE,OAAO,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,CAAA;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,KAAK,GAAkB,MAAM,CAAC,gBAAgB,CAAC,CAAA;AAErD;;;GAGG;AACH,MAAM,OAAO,GAAkB,MAAM,CAAC,kBAAkB,CAAC,CAAA;AA+BzD;;;;;;;;GAQG;AACH,SAAS,IAAI,CAA8B,KAAa,EAAE,GAAW;IACpE,MAAM,KAAK,GAAkB,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAC1D,IAAI,CAAC,kBAAkB,CAAO,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,MAAM,CAAC,GAAG,CAAC,gEAAgE,KAAK,KAAK,GAAG,GAAG,CAAC,CAAA;IACnG,CAAC;IACD,OAAO,KAAK,CAAA;AACb,CAAC;AAGD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA"}
package/src/brand.ts DELETED
@@ -1,82 +0,0 @@
1
- /**
2
- * Nominal branding — the TS analog of the Rust macro's host newtypes
3
- * (`docs/architecture/10-data-model.md`, the nominal-safety layer). A brand
4
- * is a phantom: it exists only in the type, tsc polices the wall exactly as
5
- * rustc polices newtype domains, and nothing is allocated or wrapped at
6
- * runtime.
7
- */
8
-
9
- import * as errors from "@superbuilders/errors"
10
-
11
- /**
12
- * The brand-minting guard behind {@link span} — the one nominal step, as a
13
- * type guard carrying the interval's REAL invariant (`start < end`, the
14
- * same check Rust's `Interval::new` runs): a value that passes IS a legal
15
- * interval of any brand, exactly as a Rust newtype wraps a checked
16
- * `Interval<T>` at construction.
17
- */
18
- function isNonemptyInterval<Name extends string>(value: IntervalValue): value is Interval<Name> {
19
- return value.start < value.end
20
- }
21
-
22
- /**
23
- * The brand key. A real runtime symbol (so modules can import it without a
24
- * `declare`-only lie), but no branded value ever carries the property — the
25
- * brand is purely a typing device.
26
- */
27
- const brand: unique symbol = Symbol("bumbledb.brand")
28
-
29
- /**
30
- * The phantom-value key used by field values, field references, and faces
31
- * to carry their value type without any runtime representation.
32
- */
33
- const phantom: unique symbol = Symbol("bumbledb.phantom")
34
-
35
- /**
36
- * A branded scalar: `T` walled off under the literal name `Name`. A
37
- * `Brand<bigint, "HolderId">` is not assignable where a
38
- * `Brand<bigint, "AccountId">` is expected — the Rust newtype wall,
39
- * verbatim. Scalars brand as `bigint` (u64/i64), `Uint8Array` (bytes), and
40
- * whole interval objects (`Interval<Name>`); `bool` and `str` take no
41
- * newtype, exactly as the macro's `as` grammar refuses them.
42
- */
43
- type Brand<T, Name extends string> = T & { readonly [brand]: Name }
44
-
45
- /**
46
- * A half-open interval `[start, end)` as a plain value object. The ray is
47
- * representable (`end` = the element domain's MAX_END); widths and
48
- * signedness are NOT modeled here — the engine judges widths at the typed
49
- * write boundary, the brand blocks cross-field assignment, and nothing
50
- * else is TS's business. Interval newtypes derive no order (the Rust
51
- * refusal, `docs/architecture/10-data-model.md`), so no comparators exist.
52
- */
53
- interface IntervalValue {
54
- readonly start: bigint
55
- readonly end: bigint
56
- }
57
-
58
- /**
59
- * A branded interval: the whole `{ start, end }` object walled under
60
- * `Name` — the `interval<i64> as ActiveDuring` analog.
61
- */
62
- type Interval<Name extends string> = Brand<IntervalValue, Name>
63
-
64
- /**
65
- * Constructs an interval literal — the `start..end` spelling. Half-open
66
- * and nonempty by construction: `start >= end` is a typed construction
67
- * error (parse, don't validate — the same invariant Rust's
68
- * `Interval::new` enforces at the host boundary). The default `never`
69
- * brand makes a fresh literal assignable to any interval field or brand,
70
- * the wrap-at-construction idiom; pass the brand explicitly
71
- * (`span<"ActiveDuring">(0n, 10n)`) to pin it.
72
- */
73
- function span<Name extends string = never>(start: bigint, end: bigint): Interval<Name> {
74
- const value: IntervalValue = Object.freeze({ start, end })
75
- if (!isNonemptyInterval<Name>(value)) {
76
- throw errors.new(`interval is half-open and nonempty: start must be < end (got ${start}..${end})`)
77
- }
78
- return value
79
- }
80
-
81
- export type { Brand, Interval, IntervalValue }
82
- export { brand, phantom, span }