@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/statements.ts CHANGED
@@ -4,18 +4,39 @@
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 DOMAIN compatibility through {@link SameDomains}: the two
11
+ * faces' projected domain labels are read structurally off the schema type
12
+ * (S1's `F["domain"]`) and constrained positionwise equal, so a
13
+ * cross-domain pair is a compile error by string-literal comparison of
14
+ * descriptor shapes — never by a value brand (the structural design's
15
+ * ratified check). What is only a SEMANTIC property — the target side of a
16
+ * containment resolving a declared key of its relation — is DELIBERATELY
17
+ * not (and cannot be) stated here: whether `B(y)` is a key of `B` depends
18
+ * on which `key()` statements the surrounding `schema()` collects, a set no
19
+ * face type can see; it stays the engine's typed `SchemaError` judgment at
20
+ * `Db.create`/`Db.open` (the two-boundary split, engine as final
21
+ * authority).
7
22
  */
8
23
 
9
24
  import * as errors from "@superbuilders/errors"
10
- import { phantom } from "#brand.ts"
11
25
  import type { Count } from "#count.ts"
12
- import { type AnyFace, type FaceData, renderFace, type SameArity } from "#face.ts"
26
+ import { type AnyFace, type FaceData, renderFace, type SameArity, type SameDomains } from "#face.ts"
13
27
  import type { AnyRelation, RelationFields } from "#relation.ts"
14
28
  import { renderWindow, type WindowSpec } from "#spec.ts"
15
29
 
30
+ /** A `key()` statement's runtime description — owner and projection carried at exact types. */
31
+ interface KeyData<R extends AnyRelation, Projection extends readonly string[]> {
32
+ readonly kind: "key"
33
+ readonly owner: R
34
+ readonly projection: Projection
35
+ }
36
+
16
37
  /** One statement's runtime description, tagged by form. */
17
38
  type StatementData =
18
- | { readonly kind: "key"; readonly owner: AnyRelation; readonly projection: readonly string[] }
39
+ | KeyData<AnyRelation, readonly string[]>
19
40
  | {
20
41
  readonly kind: "containment"
21
42
  readonly source: FaceData
@@ -35,15 +56,16 @@ interface Statement {
35
56
  }
36
57
 
37
58
  /**
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
59
+ * A `key()` statement as a TYPED value: its `data` carries the owner
60
+ * relation and the projection tuple at their EXACT types (honest runtime
61
+ * properties — no phantom), which is what the key-statement-selected
40
62
  * `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.
63
+ * (`docs/architecture/70-api.md` § the freeze, the multi-key typed get) and
64
+ * what resolves each projected field's domain label through the owner's
65
+ * schema type. Structurally still a plain {@link Statement}.
44
66
  */
45
67
  interface KeyStatement<R extends AnyRelation, Projection extends readonly string[]> extends Statement {
46
- readonly [phantom]?: { readonly owner: R; readonly projection: Projection }
68
+ readonly data: KeyData<R, Projection>
47
69
  }
48
70
 
49
71
  /**
@@ -51,9 +73,11 @@ interface KeyStatement<R extends AnyRelation, Projection extends readonly string
51
73
  * parameter exists (the FD-with-selection shape is unrepresentable, as in
52
74
  * the grammar), and only ordinary relations are accepted: a closed
53
75
  * 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.
76
+ * explicit one would only ever be a duplicate. Every projected name is
77
+ * checked against `R`'s field block in the type, and the tuple is carried
78
+ * in the returned value's type ({@link KeyStatement}) — keyed point reads
79
+ * through THIS statement are typed field-for-field, domains resolvable
80
+ * through the owner's schema type.
57
81
  */
58
82
  function key<
59
83
  R extends AnyRelation,
@@ -64,21 +88,27 @@ function key<
64
88
  `key(${relation.name}, ...): closedness already materializes ${relation.name}(id) -> ${relation.name} — an explicit key on a closed relation is rejected as a duplicate`
65
89
  )
66
90
  }
67
- const data: StatementData = Object.freeze({
91
+ const data: KeyData<R, Projection> = Object.freeze({
68
92
  kind: "key",
69
93
  owner: relation,
70
- projection: Object.freeze([...fields])
94
+ projection: Object.freeze(fields)
71
95
  })
72
96
  return Object.freeze({ data })
73
97
  }
74
98
 
75
99
  /**
76
- * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. The target
77
- * side must resolve a declared key of B DELIBERATELY judged by the
78
- * engine at `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
79
- * Arity mismatch between the two faces is a type error ({@link SameArity}).
100
+ * `A(X|φ) <= B(Y|ψ)` — conditional inclusion, source left. Arity mismatch
101
+ * between the two faces is a type error ({@link SameArity}); a cross-domain
102
+ * pair is a type error ({@link SameDomains} — positionwise string-literal
103
+ * equality of the projected S1 domain labels). The target side must
104
+ * resolve a declared key of B — a SEMANTIC property of the whole statement
105
+ * set that no face type can state, DELIBERATELY judged by the engine at
106
+ * `Db.create`/`Db.open` (`SchemaError`), never re-checked here.
80
107
  */
81
- function contained<A extends AnyFace, B extends AnyFace>(source: A, target: B & SameArity<A, B>): Statement {
108
+ function contained<A extends AnyFace, B extends AnyFace>(
109
+ source: A,
110
+ target: B & SameArity<A, B> & SameDomains<A, B>
111
+ ): Statement {
82
112
  const data: StatementData = Object.freeze({
83
113
  kind: "containment",
84
114
  source: source.data,
@@ -89,11 +119,18 @@ function contained<A extends AnyFace, B extends AnyFace>(source: A, target: B &
89
119
  }
90
120
 
91
121
  /**
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.
122
+ * `A(X|φ) == B(Y|ψ)` — the bidirectional abbreviation, one utterance: the
123
+ * selected `==` bijection, a keyed one-to-one correspondence between the
124
+ * two faces (each side contains the other). It lowers to the two adjacent
125
+ * containments in the `A <= B` first order (macro parity — the engine
126
+ * performs the split, source-first) and renders as `==` once, in the
127
+ * written orientation. Faces pair by arity AND domain, exactly as
128
+ * {@link contained}.
95
129
  */
96
- function mirrors<A extends AnyFace, B extends AnyFace>(source: A, target: B & SameArity<A, B>): Statement {
130
+ function mirrors<A extends AnyFace, B extends AnyFace>(
131
+ source: A,
132
+ target: B & SameArity<A, B> & SameDomains<A, B>
133
+ ): Statement {
97
134
  const data: StatementData = Object.freeze({
98
135
  kind: "containment",
99
136
  source: source.data,
@@ -108,9 +145,15 @@ function mirrors<A extends AnyFace, B extends AnyFace>(source: A, target: B & Sa
108
145
  * LEFT face is the window's TARGET, the per-group parent (B-family,
109
146
  * target-left — macro parity), and the RIGHT face is the counted source.
110
147
  * `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
111
- * Holder id groups at most three Account rows by holder.
148
+ * Holder id groups at most three Account rows by holder. The two faces
149
+ * pair by arity AND domain ({@link SameDomains}), exactly as containment —
150
+ * the grouping join reads the same positionwise field pairing.
112
151
  */
113
- function window<B extends AnyFace, A extends AnyFace>(target: B, count: Count, source: A & SameArity<B, A>): Statement {
152
+ function window<B extends AnyFace, A extends AnyFace>(
153
+ target: B,
154
+ count: Count,
155
+ source: A & SameArity<B, A> & SameDomains<B, A>
156
+ ): Statement {
114
157
  const data: StatementData = Object.freeze({
115
158
  kind: "window",
116
159
  target: target.data,
@@ -144,5 +187,5 @@ function renderStatement(statement: Statement): string {
144
187
  }
145
188
  }
146
189
 
147
- export type { KeyStatement, Statement, StatementData }
190
+ export type { KeyData, KeyStatement, Statement, StatementData }
148
191
  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 }