@bjornpagen/bumbledb 0.1.0 → 0.2.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 (103) hide show
  1. package/COOKBOOK.md +1394 -0
  2. package/README.md +4 -0
  3. package/dist/closed.d.ts +60 -37
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +159 -40
  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 +15 -7
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +13 -5
  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 +79 -19
  20. package/dist/face.d.ts.map +1 -1
  21. package/dist/face.js +14 -15
  22. package/dist/face.js.map +1 -1
  23. package/dist/fields.d.ts +158 -129
  24. package/dist/fields.d.ts.map +1 -1
  25. package/dist/fields.js +80 -81
  26. package/dist/fields.js.map +1 -1
  27. package/dist/index.d.ts +21 -21
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +13 -12
  30. package/dist/index.js.map +1 -1
  31. package/dist/lower.d.ts +6 -3
  32. package/dist/lower.d.ts.map +1 -1
  33. package/dist/lower.js +48 -17
  34. package/dist/lower.js.map +1 -1
  35. package/dist/marshal.d.ts +36 -21
  36. package/dist/marshal.d.ts.map +1 -1
  37. package/dist/marshal.js +41 -25
  38. package/dist/marshal.js.map +1 -1
  39. package/dist/native.js +2 -2
  40. package/dist/query/atom.d.ts +332 -174
  41. package/dist/query/atom.d.ts.map +1 -1
  42. package/dist/query/atom.js +72 -172
  43. package/dist/query/atom.js.map +1 -1
  44. package/dist/query/lower.d.ts +295 -107
  45. package/dist/query/lower.d.ts.map +1 -1
  46. package/dist/query/lower.js +860 -388
  47. package/dist/query/lower.js.map +1 -1
  48. package/dist/query/predicate.d.ts +69 -88
  49. package/dist/query/predicate.d.ts.map +1 -1
  50. package/dist/query/predicate.js +118 -72
  51. package/dist/query/predicate.js.map +1 -1
  52. package/dist/query/run.d.ts +19 -16
  53. package/dist/query/run.d.ts.map +1 -1
  54. package/dist/query/run.js +31 -24
  55. package/dist/query/run.js.map +1 -1
  56. package/dist/query/scope.d.ts +139 -123
  57. package/dist/query/scope.d.ts.map +1 -1
  58. package/dist/query/scope.js +71 -115
  59. package/dist/query/scope.js.map +1 -1
  60. package/dist/query/select.d.ts +102 -80
  61. package/dist/query/select.d.ts.map +1 -1
  62. package/dist/query/select.js +39 -34
  63. package/dist/query/select.js.map +1 -1
  64. package/dist/relation.d.ts +33 -36
  65. package/dist/relation.d.ts.map +1 -1
  66. package/dist/relation.js +15 -16
  67. package/dist/relation.js.map +1 -1
  68. package/dist/schema.d.ts.map +1 -1
  69. package/dist/schema.js +19 -8
  70. package/dist/schema.js.map +1 -1
  71. package/dist/spec.d.ts +6 -5
  72. package/dist/spec.d.ts.map +1 -1
  73. package/dist/spec.js.map +1 -1
  74. package/dist/statements.d.ts +56 -31
  75. package/dist/statements.d.ts.map +1 -1
  76. package/dist/statements.js +38 -13
  77. package/dist/statements.js.map +1 -1
  78. package/package.json +4 -3
  79. package/src/closed.ts +271 -93
  80. package/src/count.ts +112 -18
  81. package/src/db.ts +22 -12
  82. package/src/exhume.ts +12 -11
  83. package/src/face.ts +114 -22
  84. package/src/fields.ts +261 -217
  85. package/src/index.ts +50 -59
  86. package/src/lower.ts +62 -20
  87. package/src/marshal.ts +48 -30
  88. package/src/native.ts +2 -2
  89. package/src/query/atom.ts +480 -376
  90. package/src/query/lower.ts +1341 -542
  91. package/src/query/predicate.ts +198 -161
  92. package/src/query/run.ts +35 -25
  93. package/src/query/scope.ts +188 -218
  94. package/src/query/select.ts +168 -93
  95. package/src/relation.ts +38 -44
  96. package/src/schema.ts +22 -12
  97. package/src/spec.ts +6 -5
  98. package/src/statements.ts +69 -26
  99. package/dist/brand.d.ts +0 -59
  100. package/dist/brand.d.ts.map +0 -1
  101. package/dist/brand.js +0 -47
  102. package/dist/brand.js.map +0 -1
  103. package/src/brand.ts +0 -82
package/src/fields.ts CHANGED
@@ -1,203 +1,256 @@
1
1
  /**
2
- * Field type constructors — the value half of the `schema!` field grammar
3
- * (`docs/architecture/70-api.md`): `bool`, `u64`, `i64`, `str`, `bytes(n)`,
4
- * `interval(u64|i64[, width])`, each a plain frozen value carrying its
5
- * structural type at runtime and its host value type in a phantom generic.
6
- * Newtypes are DECLARATION-FIRST, one spelling only (owner ruling
7
- * 2026-07-16): `const AccountId = u64.newtype("AccountId")` declares the
8
- * brand ONCE as a value that IS the field, paired with
9
- * `type AccountId = Infer<typeof AccountId>` for signatures; every field
10
- * position references the declared value (`holder: HolderId`). The macro's
11
- * refusals are reproduced representationally: `.newtype` exists only where
12
- * Rust's `as` is legal (u64, i64, bytes, intervals never bool/str),
13
- * `.fresh` exists only on declared u64 newtypes (the macro demands `as
14
- * NewType` on fresh fields), and no field-level constraint vocabulary of
15
- * any kind exists — `unique`/`fk` are unwritable, not rejected.
2
+ * Field descriptors — the value half of the `schema!` field grammar
3
+ * (`docs/architecture/70-api.md`), STRUCTURAL edition: `bool`, `u64`, `i64`,
4
+ * `str`, `bytes(n)`, `interval(u64|i64[, width])`, each a plain frozen value
5
+ * that IS its own descriptor type `{ kind, domain, fresh?, width?,
6
+ * element? }` honest at runtime and in the type alike. A field's VALUE
7
+ * type is its bare structural type (`u64` `bigint`, `str` → `string`,
8
+ * `bytes(n)` `Uint8Array`, intervals `{ start, end }`): no brands, no
9
+ * phantoms, no minting casts. The domain is a string LABEL in the
10
+ * descriptor type, attached by `.as("HolderId")` (the mirror of Rust's
11
+ * `as HolderId`); same-string domains link fields, and the relational
12
+ * builders (statements, queries) compare the labels structurallythe
13
+ * domain wall lives in the builders and the engine, never on the value.
14
+ * The macro's refusals are reproduced representationally: `.as` exists only
15
+ * where Rust's `as` is legal (u64, i64, bytes, intervals — never bool/str),
16
+ * `.fresh` exists only on u64 (bare or after `.as`), and no field-level
17
+ * constraint vocabulary of any kind exists — `unique`/`fk` are unwritable,
18
+ * not rejected.
16
19
  */
17
20
 
18
21
  import * as errors from "@superbuilders/errors"
19
- import type { Brand, Interval, IntervalValue } from "#brand.ts"
20
- import { phantom } from "#brand.ts"
21
- import type { LiteralSpec, ValueTypeSpec } from "#spec.ts"
22
+ import type { LiteralSpec } from "#spec.ts"
22
23
 
23
24
  /**
24
- * The typed shape refusal of the selection-literal machinereached only
25
- * through ill-typed input (the well-typed surfaces make it unrepresentable).
25
+ * A half-open interval `[start, end)` as a plain value object the ONE
26
+ * interval value type, whatever the field's element domain or width label.
27
+ * The ray is representable (`end` = the element domain's MAX_END); widths
28
+ * and signedness are NOT modeled on the value — they are descriptor-type
29
+ * labels the engine judges at the typed write boundary. Interval fields
30
+ * derive no order (the Rust refusal, `docs/architecture/10-data-model.md`),
31
+ * so no comparators exist on the value type.
26
32
  */
27
- function literalShapeError(expected: string, value: unknown): Error {
28
- return errors.new(`selection literal shape mismatch: expected ${expected}, got ${typeof value}`)
29
- }
30
-
31
- /** Narrows an interval literal: a plain object with bigint start/end. */
32
- function isIntervalLiteral(value: unknown): value is IntervalValue {
33
- return (
34
- typeof value === "object" &&
35
- value !== null &&
36
- "start" in value &&
37
- "end" in value &&
38
- typeof value.start === "bigint" &&
39
- typeof value.end === "bigint"
40
- )
33
+ interface IntervalValue {
34
+ readonly start: bigint
35
+ readonly end: bigint
41
36
  }
42
37
 
43
38
  /**
44
- * Resolves one closed-handle literal: the branded id (a bigint at runtime)
45
- * back to its handle NAME through the roster an out-of-roster id is a
46
- * construction error, the belt the type level cannot provide against
47
- * forged brands.
39
+ * Constructs an interval literal the `start..end` spelling. Half-open
40
+ * and nonempty by construction: `start >= end` is a typed construction
41
+ * error (parse, don't validate the same invariant Rust's
42
+ * `Interval::new` enforces at the host boundary). The value is bare and
43
+ * structural: it is assignable to any interval field.
48
44
  */
49
- function handleLiteral(closed: ClosedRoster, value: unknown): LiteralSpec {
50
- if (typeof value !== "bigint") {
51
- throw literalShapeError(`a ${closed.name} handle id (bigint)`, value)
52
- }
53
- const handle = closed.handles[Number(value)]
54
- if (handle === undefined) {
55
- throw errors.new(
56
- `closed relation ${closed.name} has no handle with id ${value} (roster holds ${closed.handles.length})`
57
- )
58
- }
59
- return { kind: "handle", handle }
60
- }
61
-
62
- /** Lowers one interval literal at its element type. */
63
- function intervalLiteral(element: "u64" | "i64", value: unknown): LiteralSpec {
64
- if (!isIntervalLiteral(value)) {
65
- throw literalShapeError("interval ({ start, end } bigints)", value)
66
- }
67
- if (element === "u64") {
68
- return { kind: "value", value: { kind: "intervalU64", start: value.start, end: value.end } }
45
+ function span(start: bigint, end: bigint): IntervalValue {
46
+ if (start >= end) {
47
+ throw errors.new(`interval is half-open and nonempty: start must be < end (got ${start}..${end})`)
69
48
  }
70
- return { kind: "value", value: { kind: "intervalI64", start: value.start, end: value.end } }
49
+ return Object.freeze({ start, end })
71
50
  }
72
51
 
73
52
  /**
74
53
  * A closed relation's roster as seen from a referencing field: the handle
75
- * namespace `where()` selections and ground axioms resolve bare handles
54
+ * namespace `where()` selections and ground axioms resolve bare handle ids
76
55
  * through (the macro's own rule: a handle is legal exactly on a field whose
77
- * newtype is a closed relation's handle newtype).
56
+ * domain is a closed relation's handle domain).
78
57
  */
79
58
  interface ClosedRoster {
80
59
  readonly name: string
81
60
  readonly handles: readonly string[]
82
61
  }
83
62
 
63
+ /** The `bool` field descriptor: value type `boolean`. No `.as`, no `.fresh` (macro parity). */
64
+ interface BoolField {
65
+ readonly kind: "bool"
66
+ readonly domain: undefined
67
+ }
68
+
69
+ /** The `str` field descriptor: value type `string`. No `.as`, no `.fresh` (macro parity). */
70
+ interface StrField {
71
+ readonly kind: "str"
72
+ readonly domain: undefined
73
+ }
74
+
84
75
  /**
85
- * One field's runtime description: structural type, host newtype name, the
86
- * `fresh` mint mark (`minted` the property name `fresh` is taken by the
87
- * builder surface), and the closed-relation reference when the field is a
88
- * closed relation's id type.
76
+ * A `fresh`-marked u64 field descriptor `id: u64.as("AccountId").fresh`
77
+ * (Rust: `id: u64 as AccountId, fresh`). The mark is a structural label
78
+ * (`fresh: true`) in the descriptor type AND on the runtime value; it
79
+ * implies the key `R(field) -> R`, which the ENGINE materializes
80
+ * (`SchemaDescriptor::materialized_statements`). Terminal: no builder
81
+ * property survives the mark.
89
82
  */
90
- interface FieldData<Minted extends boolean = boolean> {
91
- readonly type: ValueTypeSpec
92
- readonly newtype: string | undefined
93
- readonly minted: Minted
94
- readonly closed: ClosedRoster | undefined
83
+ interface FreshU64Field<Domain extends string | undefined = undefined> {
84
+ readonly kind: "u64"
85
+ readonly domain: Domain
86
+ readonly fresh: true
95
87
  }
96
88
 
97
89
  /**
98
- * The base shape every field value shares: runtime data plus the phantom
99
- * host value type `V` (never present at runtime).
90
+ * A domain-labeled u64 field descriptor `const HolderId =
91
+ * u64.as("HolderId")` (Rust: `u64 as HolderId`). The label lives in the
92
+ * descriptor type only; the value type stays bare `bigint`. `.fresh` marks
93
+ * the field as engine-minted — the property doubles as the mark itself:
94
+ * on an unmarked descriptor it holds the marked descriptor, on a marked
95
+ * one it IS the literal `true` (one structural property, read either way).
100
96
  */
101
- interface Field<V> {
102
- readonly data: FieldData
103
- readonly [phantom]?: V
97
+ interface U64Field<Domain extends string | undefined = undefined> {
98
+ readonly kind: "u64"
99
+ readonly domain: Domain
100
+ readonly fresh: FreshU64Field<Domain>
104
101
  }
105
102
 
106
- /** Extracts a field value's host value type from its phantom. */
107
- type FieldValue<F> = F extends Field<infer V> ? V : never
108
-
109
- /** Any field value, whatever its host value type. */
110
- type AnyField = Field<unknown>
111
-
112
- /** The `bool` field: host type `boolean`. No `.newtype`, no `.fresh` (macro parity). */
113
- interface BoolField extends Field<boolean> {
114
- readonly data: FieldData<false>
103
+ /** The `u64` constructor value: a bare u64 descriptor plus `.as` (one application — `.as` is absent on the result). */
104
+ interface U64Ctor extends U64Field<undefined> {
105
+ as<const Domain extends string>(domain: Domain): U64Field<Domain>
115
106
  }
116
107
 
117
- /** The `str` field: host type `string`. No `.newtype`, no `.fresh` (macro parity). */
118
- interface StrField extends Field<string> {
119
- readonly data: FieldData<false>
108
+ /** A domain-labeled i64 field descriptor. Terminal: `.fresh` is legal on u64 only. */
109
+ interface I64Field<Domain extends string | undefined = undefined> {
110
+ readonly kind: "i64"
111
+ readonly domain: Domain
120
112
  }
121
113
 
122
- /**
123
- * A `fresh`-marked u64 newtype field — `id: AccountId.fresh` (Rust: `id:
124
- * u64 as AccountId, fresh`). Terminal: the mark implies the key
125
- * `R(field) -> R`, which the ENGINE materializes
126
- * (`SchemaDescriptor::materialized_statements`); `schema()` rejects an
127
- * explicit duplicate of it (macro parity).
128
- */
129
- interface FreshU64Newtype<Name extends string> extends Field<Brand<bigint, Name>> {
130
- readonly data: FieldData<true>
114
+ /** The `i64` constructor value: a bare i64 descriptor plus `.as`. */
115
+ interface I64Ctor extends I64Field<undefined> {
116
+ as<const Domain extends string>(domain: Domain): I64Field<Domain>
131
117
  }
132
118
 
133
119
  /**
134
- * A declared u64 newtype `const AccountId = u64.newtype("AccountId")`
135
- * (Rust: `u64 as AccountId`). The value IS the field: relation blocks
136
- * reference it (`holder: HolderId`). `.fresh` marks it as minted; the
137
- * property exists ONLY here (the macro demands `as NewType` on fresh
138
- * fields, so bare-u64 fresh is unwritable).
120
+ * A `bytes<N>` field descriptor. The width is a descriptor-type label
121
+ * (load-bearing: the engine enforces it at the write boundary) and the
122
+ * value type is bare `Uint8Array`. No order is derived — no comparators
123
+ * exist on the value type (the engine refuses order on bytes).
139
124
  */
140
- interface U64Newtype<Name extends string> extends Field<Brand<bigint, Name>> {
141
- readonly data: FieldData<false>
142
- readonly fresh: FreshU64Newtype<Name>
125
+ interface BytesField<Width extends number = number, Domain extends string | undefined = undefined> {
126
+ readonly kind: "bytes"
127
+ readonly width: Width
128
+ readonly domain: Domain
143
129
  }
144
130
 
145
- /** The bare `u64` field; `.newtype(name)` declares a branded u64 newtype. */
146
- interface U64Field extends Field<bigint> {
147
- readonly data: FieldData<false>
148
- newtype<const Name extends string>(name: Name): U64Newtype<Name>
131
+ /** A `bytes(n)` constructor value: a bare bytes descriptor plus `.as`. */
132
+ interface BytesCtor<Width extends number = number> extends BytesField<Width, undefined> {
133
+ as<const Domain extends string>(domain: Domain): BytesField<Width, Domain>
149
134
  }
150
135
 
151
- /** A declared i64 newtype. Terminal: `fresh` is legal on u64 only. */
152
- interface I64Newtype<Name extends string> extends Field<Brand<bigint, Name>> {
153
- readonly data: FieldData<false>
136
+ /**
137
+ * An interval field descriptor `interval(i64)` general (rays
138
+ * representable), `interval(u64, w)` the fixed-width family. Element and
139
+ * width are descriptor-type labels; the value type is always the bare
140
+ * {@link IntervalValue}.
141
+ */
142
+ interface IntervalField<
143
+ Element extends "u64" | "i64" = "u64" | "i64",
144
+ Width extends bigint | undefined = bigint | undefined,
145
+ Domain extends string | undefined = undefined
146
+ > {
147
+ readonly kind: "interval"
148
+ readonly element: Element
149
+ readonly width: Width
150
+ readonly domain: Domain
154
151
  }
155
152
 
156
- /** The bare `i64` field; `.newtype(name)` declares a branded i64 newtype. */
157
- interface I64Field extends Field<bigint> {
158
- readonly data: FieldData<false>
159
- newtype<const Name extends string>(name: Name): I64Newtype<Name>
153
+ /** An `interval(e[, w])` constructor value: a bare interval descriptor plus `.as`. */
154
+ interface IntervalCtor<
155
+ Element extends "u64" | "i64" = "u64" | "i64",
156
+ Width extends bigint | undefined = bigint | undefined
157
+ > extends IntervalField<Element, Width, undefined> {
158
+ as<const Domain extends string>(domain: Domain): IntervalField<Element, Width, Domain>
160
159
  }
161
160
 
162
- /** A declared `bytes<N>` newtype (no order derived — no comparators exist). */
163
- interface BytesNewtype<Name extends string> extends Field<Brand<Uint8Array, Name>> {
164
- readonly data: FieldData<false>
161
+ /**
162
+ * A closed relation's reference field descriptor (`Kind.id`) — a u64
163
+ * descriptor whose domain is the closed relation's handle domain
164
+ * (`"KindId"`, mirroring Rust's `closed relation Kind as KindId`) and
165
+ * whose roster resolves bare handle ids in selections and ground axioms.
166
+ * Terminal: no `.as`, no `.fresh` — its domain IS the closed relation's.
167
+ */
168
+ interface ClosedIdField<Domain extends string = string> {
169
+ readonly kind: "u64"
170
+ readonly domain: Domain
171
+ readonly closed: ClosedRoster
165
172
  }
166
173
 
167
- /** A `bytes<N>` field; `.newtype(name)` declares a branded bytes newtype. */
168
- interface BytesField extends Field<Uint8Array> {
169
- readonly data: FieldData<false>
170
- newtype<const Name extends string>(name: Name): BytesNewtype<Name>
171
- }
174
+ /** Any field descriptor, whatever its kind, domain label, or marks. */
175
+ type AnyField =
176
+ | BoolField
177
+ | StrField
178
+ | U64Field<string | undefined>
179
+ | FreshU64Field<string | undefined>
180
+ | I64Field<string | undefined>
181
+ | BytesField<number, string | undefined>
182
+ | IntervalField<"u64" | "i64", bigint | undefined, string | undefined>
183
+ | ClosedIdField
184
+
185
+ /**
186
+ * The bare structural VALUE type of a field descriptor — the one total
187
+ * definition every fact, result row, and query term reads: `bool` →
188
+ * `boolean`, `str` → `string`, `u64`/`i64` → `bigint` (domain labels
189
+ * included — the label never touches the value), `bytes<N>` →
190
+ * `Uint8Array`, intervals → {@link IntervalValue}.
191
+ */
192
+ type Infer<F extends AnyField> = F extends { readonly kind: "bool" }
193
+ ? boolean
194
+ : F extends { readonly kind: "str" }
195
+ ? string
196
+ : F extends { readonly kind: "u64" }
197
+ ? bigint
198
+ : F extends { readonly kind: "i64" }
199
+ ? bigint
200
+ : F extends { readonly kind: "bytes" }
201
+ ? Uint8Array
202
+ : F extends { readonly kind: "interval" }
203
+ ? IntervalValue
204
+ : never
172
205
 
173
- /** A declared interval newtype — Rust: `interval<i64> as ActiveDuring`. */
174
- interface IntervalNewtype<Name extends string> extends Field<Interval<Name>> {
175
- readonly data: FieldData<false>
206
+ /**
207
+ * The typed shape refusal of the selection-literal machine — reached only
208
+ * through ill-typed input (the well-typed surfaces make it unrepresentable).
209
+ */
210
+ function literalShapeError(expected: string, value: unknown): Error {
211
+ return errors.new(`selection literal shape mismatch: expected ${expected}, got ${typeof value}`)
176
212
  }
177
213
 
178
- /** An interval field; `.newtype(name)` brands the whole `{ start, end }` object. */
179
- interface IntervalField extends Field<IntervalValue> {
180
- readonly data: FieldData<false>
181
- newtype<const Name extends string>(name: Name): IntervalNewtype<Name>
214
+ /** Narrows an interval literal: a plain object with bigint start/end. */
215
+ function isIntervalLiteral(value: unknown): value is IntervalValue {
216
+ return (
217
+ typeof value === "object" &&
218
+ value !== null &&
219
+ "start" in value &&
220
+ "end" in value &&
221
+ typeof value.start === "bigint" &&
222
+ typeof value.end === "bigint"
223
+ )
182
224
  }
183
225
 
184
226
  /**
185
- * The branded value type of a declared newtype the type half of the
186
- * declaration-first pairing (owner ruling 2026-07-16): `const AccountId =
187
- * u64.newtype("AccountId")` + `type AccountId = Infer<typeof AccountId>`.
188
- * Reads the phantom, so it works on any field value (a closed relation's
189
- * `id` field infers its handle brand the same way).
227
+ * Resolves one closed-handle literal: the handle id (a bare bigint) back to
228
+ * its handle NAME through the roster — an out-of-roster id is a
229
+ * construction error, the belt the type level deliberately does not provide
230
+ * (structural values make any bigint spellable here; the roster judges).
190
231
  */
191
- type Infer<F extends AnyField> = F extends Field<infer V> ? V : never
232
+ function handleLiteral(closed: ClosedRoster, value: unknown): LiteralSpec {
233
+ if (typeof value !== "bigint") {
234
+ throw literalShapeError(`a ${closed.name} handle id (bigint)`, value)
235
+ }
236
+ const handle = closed.handles[Number(value)]
237
+ if (handle === undefined) {
238
+ throw errors.new(
239
+ `closed relation ${closed.name} has no handle with id ${value} (roster holds ${closed.handles.length})`
240
+ )
241
+ }
242
+ return { kind: "handle", handle }
243
+ }
192
244
 
193
- /**
194
- * A closed relation's id field constructor (`Kind.id`) a u64 field
195
- * pre-branded with the closed relation's handle newtype, for use in other
196
- * relations' field blocks (`kind: Kind.id`). Terminal: its newtype IS the
197
- * closed relation, so `.newtype` and `.fresh` do not exist.
198
- */
199
- interface ClosedIdField<Name extends string> extends Field<Brand<bigint, Name>> {
200
- readonly data: FieldData<false>
245
+ /** Lowers one interval literal at its element type. */
246
+ function intervalLiteral(element: "u64" | "i64", value: unknown): LiteralSpec {
247
+ if (!isIntervalLiteral(value)) {
248
+ throw literalShapeError("interval ({ start, end } bigints)", value)
249
+ }
250
+ if (element === "u64") {
251
+ return { kind: "value", value: { kind: "intervalU64", start: value.start, end: value.end } }
252
+ }
253
+ return { kind: "value", value: { kind: "intervalI64", start: value.start, end: value.end } }
201
254
  }
202
255
 
203
256
  /**
@@ -218,69 +271,55 @@ function assertDeclarationOrderKey(where: string, name: string): void {
218
271
  }
219
272
  }
220
273
 
221
- /**
222
- * Builds one frozen {@link FieldData}. Internal seam shared by the field
223
- * constructors here and by `closed()` (which mints {@link ClosedIdField}
224
- * values against its own roster).
225
- */
226
- function fieldData<Minted extends boolean>(
227
- type: ValueTypeSpec,
228
- newtype: string | undefined,
229
- minted: Minted,
230
- closed: ClosedRoster | undefined
231
- ): FieldData<Minted> {
232
- return Object.freeze({ type: Object.freeze(type), newtype, minted, closed })
274
+ /** Builds one fresh-marked u64 descriptor (the `.fresh` property of an unmarked one). */
275
+ function freshU64<Domain extends string | undefined>(domain: Domain): FreshU64Field<Domain> {
276
+ return Object.freeze({ kind: "u64", domain, fresh: true })
233
277
  }
234
278
 
235
- /** The one `u64` field constructor value. */
236
- const u64: U64Field = Object.freeze({
237
- data: fieldData({ kind: "u64" }, undefined, false, undefined),
238
- newtype<const Name extends string>(name: Name): U64Newtype<Name> {
239
- const fresh: FreshU64Newtype<Name> = Object.freeze({
240
- data: fieldData({ kind: "u64" }, name, true, undefined)
241
- })
242
- return Object.freeze({
243
- data: fieldData({ kind: "u64" }, name, false, undefined),
244
- fresh
245
- })
279
+ /** The one `u64` constructor value. */
280
+ const u64: U64Ctor = Object.freeze({
281
+ kind: "u64",
282
+ domain: undefined,
283
+ fresh: freshU64(undefined),
284
+ as<const Domain extends string>(domain: Domain): U64Field<Domain> {
285
+ return Object.freeze({ kind: "u64", domain, fresh: freshU64(domain) })
246
286
  }
247
287
  })
248
288
 
249
- /** The one `i64` field constructor value. */
250
- const i64: I64Field = Object.freeze({
251
- data: fieldData({ kind: "i64" }, undefined, false, undefined),
252
- newtype<const Name extends string>(name: Name): I64Newtype<Name> {
253
- return Object.freeze({ data: fieldData({ kind: "i64" }, name, false, undefined) })
289
+ /** The one `i64` constructor value. */
290
+ const i64: I64Ctor = Object.freeze({
291
+ kind: "i64",
292
+ domain: undefined,
293
+ as<const Domain extends string>(domain: Domain): I64Field<Domain> {
294
+ return Object.freeze({ kind: "i64", domain })
254
295
  }
255
296
  })
256
297
 
257
- /** The one `bool` field constructor value. */
258
- const bool: BoolField = Object.freeze({
259
- data: fieldData({ kind: "bool" }, undefined, false, undefined)
260
- })
298
+ /** The one `bool` constructor value. */
299
+ const bool: BoolField = Object.freeze({ kind: "bool", domain: undefined })
261
300
 
262
- /** The one `str` field constructor value. */
263
- const str: StrField = Object.freeze({
264
- data: fieldData({ kind: "string" }, undefined, false, undefined)
265
- })
301
+ /** The one `str` constructor value. */
302
+ const str: StrField = Object.freeze({ kind: "str", domain: undefined })
266
303
 
267
304
  /**
268
- * The `bytes<N>` field constructor. The width is mandatory and part of the
269
- * type; `len` is validated to 1..=64 here because the grammar pins that
270
- * range at declaration (`docs/architecture/70-api.md` § the `schema!`
271
- * grammar: N ∈ 1..=64 — bare `bytes` does not parse), the macro-expansion
272
- * boundary's analog being construction.
305
+ * The `bytes<N>` field constructor. The width is mandatory and a
306
+ * descriptor-type label; `width` is validated to 1..=64 here because the
307
+ * grammar pins that range at declaration (`docs/architecture/70-api.md`
308
+ * § the `schema!` grammar: N ∈ 1..=64 — bare `bytes` does not parse), the
309
+ * macro-expansion boundary's analog being construction.
273
310
  */
274
- function bytes(len: number): BytesField {
275
- if (!Number.isInteger(len) || len < 1 || len > 64) {
311
+ function bytes<const Width extends number>(width: Width): BytesCtor<Width> {
312
+ if (!Number.isInteger(width) || width < 1 || width > 64) {
276
313
  throw errors.new(
277
- `bytes width must be an integer in 1..=64 (got ${len}) — docs/architecture/70-api.md pins the range at declaration`
314
+ `bytes width must be an integer in 1..=64 (got ${width}) — docs/architecture/70-api.md pins the range at declaration`
278
315
  )
279
316
  }
280
317
  return Object.freeze({
281
- data: fieldData({ kind: "fixedBytes", len }, undefined, false, undefined),
282
- newtype<const Name extends string>(name: Name): BytesNewtype<Name> {
283
- return Object.freeze({ data: fieldData({ kind: "fixedBytes", len }, name, false, undefined) })
318
+ kind: "bytes",
319
+ width,
320
+ domain: undefined,
321
+ as<const Domain extends string>(domain: Domain): BytesField<Width, Domain> {
322
+ return Object.freeze({ kind: "bytes", width, domain })
284
323
  }
285
324
  })
286
325
  }
@@ -288,14 +327,19 @@ function bytes(len: number): BytesField {
288
327
  /**
289
328
  * The interval field constructor — `interval(u64)` / `interval(i64)` for
290
329
  * the general type (rays representable), `interval(u64, w)` for the
291
- * fixed-width family whose width IS the type. The element is spelled with
292
- * the u64/i64 field constructor values themselves, never a string. `width
293
- * >= 1` is validated here because the grammar pins it at declaration
330
+ * fixed-width family whose width IS a descriptor-type label. The element is
331
+ * spelled with the u64/i64 constructor values themselves, never a string.
332
+ * `width >= 1` is validated here because the grammar pins it at declaration
294
333
  * (`docs/architecture/70-api.md`: w ≥ 1; `interval<u64, 0>` is an
295
334
  * expansion error naming the field).
296
335
  */
297
- function interval(element: U64Field | I64Field, width?: bigint): IntervalField {
298
- const elementKind = element.data.type.kind
336
+ function interval<Element extends U64Ctor | I64Ctor>(element: Element): IntervalCtor<Element["kind"], undefined>
337
+ function interval<Element extends U64Ctor | I64Ctor, const Width extends bigint>(
338
+ element: Element,
339
+ width: Width
340
+ ): IntervalCtor<Element["kind"], Width>
341
+ function interval(element: U64Ctor | I64Ctor, width?: bigint): IntervalCtor<"u64" | "i64", bigint | undefined> {
342
+ const elementKind = element.kind
299
343
  if (elementKind !== "u64" && elementKind !== "i64") {
300
344
  throw errors.new(`interval element must be the u64 or i64 field constructor (got ${elementKind})`)
301
345
  }
@@ -304,11 +348,13 @@ function interval(element: U64Field | I64Field, width?: bigint): IntervalField {
304
348
  `interval width must be >= 1 (got ${width}) — docs/architecture/70-api.md pins w >= 1 at declaration`
305
349
  )
306
350
  }
307
- const type: ValueTypeSpec = { kind: "interval", element: elementKind, width }
308
351
  return Object.freeze({
309
- data: fieldData(type, undefined, false, undefined),
310
- newtype<const Name extends string>(name: Name): IntervalNewtype<Name> {
311
- return Object.freeze({ data: fieldData(type, name, false, undefined) })
352
+ kind: "interval",
353
+ element: elementKind,
354
+ width,
355
+ domain: undefined,
356
+ as<const Domain extends string>(domain: Domain): IntervalField<"u64" | "i64", bigint | undefined, Domain> {
357
+ return Object.freeze({ kind: "interval", element: elementKind, width, domain })
312
358
  }
313
359
  })
314
360
  }
@@ -318,15 +364,15 @@ function interval(element: U64Field | I64Field, width?: bigint): IntervalField {
318
364
  * {@link LiteralSpec} — the selection-literal machine ground axioms and
319
365
  * `where()` bindings both ride (one machine, same errors — the macro's own
320
366
  * rule). A value on a closed-reference field resolves to its handle NAME
321
- * (the id is re-verified against the roster: an out-of-roster id is a
367
+ * (the id is verified against the roster: an out-of-roster id is a
322
368
  * construction error); everything else lowers to a plain value tagged by
323
- * the field's structural type.
369
+ * the field's structural kind.
324
370
  */
325
- function literalOf(field: FieldData, value: unknown): LiteralSpec {
326
- if (field.closed !== undefined) {
371
+ function literalOf(field: AnyField, value: unknown): LiteralSpec {
372
+ if ("closed" in field) {
327
373
  return handleLiteral(field.closed, value)
328
374
  }
329
- switch (field.type.kind) {
375
+ switch (field.kind) {
330
376
  case "bool": {
331
377
  if (typeof value !== "boolean") {
332
378
  throw literalShapeError("boolean", value)
@@ -345,41 +391,39 @@ function literalOf(field: FieldData, value: unknown): LiteralSpec {
345
391
  }
346
392
  return { kind: "value", value: { kind: "i64", value } }
347
393
  }
348
- case "string": {
394
+ case "str": {
349
395
  if (typeof value !== "string") {
350
396
  throw literalShapeError("string", value)
351
397
  }
352
398
  return { kind: "value", value: { kind: "string", value } }
353
399
  }
354
- case "fixedBytes": {
400
+ case "bytes": {
355
401
  if (!(value instanceof Uint8Array)) {
356
402
  throw literalShapeError("Uint8Array", value)
357
403
  }
358
404
  return { kind: "value", value: { kind: "fixedBytes", value } }
359
405
  }
360
406
  case "interval":
361
- return intervalLiteral(field.type.element, value)
407
+ return intervalLiteral(field.element, value)
362
408
  }
363
409
  }
364
410
 
365
411
  export type {
366
412
  AnyField,
367
413
  BoolField,
414
+ BytesCtor,
368
415
  BytesField,
369
- BytesNewtype,
370
416
  ClosedIdField,
371
417
  ClosedRoster,
372
- Field,
373
- FieldData,
374
- FieldValue,
375
- FreshU64Newtype,
418
+ FreshU64Field,
419
+ I64Ctor,
376
420
  I64Field,
377
- I64Newtype,
378
421
  Infer,
422
+ IntervalCtor,
379
423
  IntervalField,
380
- IntervalNewtype,
424
+ IntervalValue,
381
425
  StrField,
382
- U64Field,
383
- U64Newtype
426
+ U64Ctor,
427
+ U64Field
384
428
  }
385
- export { assertDeclarationOrderKey, bool, bytes, fieldData, i64, interval, literalOf, str, u64 }
429
+ export { assertDeclarationOrderKey, bool, bytes, i64, interval, literalOf, span, str, u64 }