@evolu/common 8.2.0 → 8.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 (221) hide show
  1. package/dist/src/Function.d.ts +1 -0
  2. package/dist/src/Function.d.ts.map +1 -1
  3. package/dist/src/Http.d.ts +9 -0
  4. package/dist/src/Http.d.ts.map +1 -1
  5. package/dist/src/Object.d.ts +1 -0
  6. package/dist/src/Object.d.ts.map +1 -1
  7. package/dist/src/RefCount.d.ts +1 -0
  8. package/dist/src/RefCount.d.ts.map +1 -1
  9. package/dist/src/Relation.d.ts +2 -0
  10. package/dist/src/Relation.d.ts.map +1 -1
  11. package/dist/src/Resource.d.ts +1 -0
  12. package/dist/src/Resource.d.ts.map +1 -1
  13. package/dist/src/Result.d.ts +383 -548
  14. package/dist/src/Result.d.ts.map +1 -1
  15. package/dist/src/Result.js +296 -46
  16. package/dist/src/StackTrace.d.ts +1 -0
  17. package/dist/src/StackTrace.d.ts.map +1 -1
  18. package/dist/src/Task.d.ts +985 -1069
  19. package/dist/src/Task.d.ts.map +1 -1
  20. package/dist/src/Task.js +1007 -156
  21. package/dist/src/Time.d.ts +1 -0
  22. package/dist/src/Time.d.ts.map +1 -1
  23. package/dist/src/Type.d.ts +1285 -734
  24. package/dist/src/Type.d.ts.map +1 -1
  25. package/dist/src/Type.js +1057 -193
  26. package/dist/src/intl/_en.d.ts +126 -0
  27. package/dist/src/intl/_en.d.ts.map +1 -0
  28. package/dist/src/intl/_en.js +267 -0
  29. package/dist/src/intl/ar.d.ts +117 -0
  30. package/dist/src/intl/ar.d.ts.map +1 -0
  31. package/dist/src/intl/ar.js +245 -0
  32. package/dist/src/intl/bn.d.ts +117 -0
  33. package/dist/src/intl/bn.d.ts.map +1 -0
  34. package/dist/src/intl/bn.js +258 -0
  35. package/dist/src/intl/ca.d.ts +117 -0
  36. package/dist/src/intl/ca.d.ts.map +1 -0
  37. package/dist/src/intl/ca.js +258 -0
  38. package/dist/src/intl/cs.d.ts +20 -7
  39. package/dist/src/intl/cs.d.ts.map +1 -1
  40. package/dist/src/intl/cs.js +54 -27
  41. package/dist/src/intl/da.d.ts +117 -0
  42. package/dist/src/intl/da.d.ts.map +1 -0
  43. package/dist/src/intl/da.js +258 -0
  44. package/dist/src/intl/de.d.ts +117 -0
  45. package/dist/src/intl/de.d.ts.map +1 -0
  46. package/dist/src/intl/de.js +258 -0
  47. package/dist/src/intl/el.d.ts +117 -0
  48. package/dist/src/intl/el.d.ts.map +1 -0
  49. package/dist/src/intl/el.js +265 -0
  50. package/dist/src/intl/es.d.ts +117 -0
  51. package/dist/src/intl/es.d.ts.map +1 -0
  52. package/dist/src/intl/es.js +258 -0
  53. package/dist/src/intl/fa.d.ts +117 -0
  54. package/dist/src/intl/fa.d.ts.map +1 -0
  55. package/dist/src/intl/fa.js +258 -0
  56. package/dist/src/intl/fi.d.ts +117 -0
  57. package/dist/src/intl/fi.d.ts.map +1 -0
  58. package/dist/src/intl/fi.js +258 -0
  59. package/dist/src/intl/fil.d.ts +117 -0
  60. package/dist/src/intl/fil.d.ts.map +1 -0
  61. package/dist/src/intl/fil.js +258 -0
  62. package/dist/src/intl/fr.d.ts +117 -0
  63. package/dist/src/intl/fr.d.ts.map +1 -0
  64. package/dist/src/intl/fr.js +258 -0
  65. package/dist/src/intl/he.d.ts +117 -0
  66. package/dist/src/intl/he.d.ts.map +1 -0
  67. package/dist/src/intl/he.js +258 -0
  68. package/dist/src/intl/hi.d.ts +117 -0
  69. package/dist/src/intl/hi.d.ts.map +1 -0
  70. package/dist/src/intl/hi.js +258 -0
  71. package/dist/src/intl/hr.d.ts +117 -0
  72. package/dist/src/intl/hr.d.ts.map +1 -0
  73. package/dist/src/intl/hr.js +265 -0
  74. package/dist/src/intl/hu.d.ts +63 -0
  75. package/dist/src/intl/hu.d.ts.map +1 -0
  76. package/dist/src/intl/hu.js +204 -0
  77. package/dist/src/intl/id.d.ts +117 -0
  78. package/dist/src/intl/id.d.ts.map +1 -0
  79. package/dist/src/intl/id.js +258 -0
  80. package/dist/src/intl/index.d.ts +42 -0
  81. package/dist/src/intl/index.d.ts.map +1 -1
  82. package/dist/src/intl/index.js +42 -0
  83. package/dist/src/intl/it.d.ts +117 -0
  84. package/dist/src/intl/it.d.ts.map +1 -0
  85. package/dist/src/intl/it.js +258 -0
  86. package/dist/src/intl/ja.d.ts +117 -0
  87. package/dist/src/intl/ja.d.ts.map +1 -0
  88. package/dist/src/intl/ja.js +258 -0
  89. package/dist/src/intl/ko.d.ts +117 -0
  90. package/dist/src/intl/ko.d.ts.map +1 -0
  91. package/dist/src/intl/ko.js +258 -0
  92. package/dist/src/intl/ml.d.ts +117 -0
  93. package/dist/src/intl/ml.d.ts.map +1 -0
  94. package/dist/src/intl/ml.js +258 -0
  95. package/dist/src/intl/mr.d.ts +117 -0
  96. package/dist/src/intl/mr.d.ts.map +1 -0
  97. package/dist/src/intl/mr.js +258 -0
  98. package/dist/src/intl/ms.d.ts +117 -0
  99. package/dist/src/intl/ms.d.ts.map +1 -0
  100. package/dist/src/intl/ms.js +245 -0
  101. package/dist/src/intl/nb.d.ts +63 -0
  102. package/dist/src/intl/nb.d.ts.map +1 -0
  103. package/dist/src/intl/nb.js +191 -0
  104. package/dist/src/intl/nl.d.ts +117 -0
  105. package/dist/src/intl/nl.d.ts.map +1 -0
  106. package/dist/src/intl/nl.js +258 -0
  107. package/dist/src/intl/pa.d.ts +117 -0
  108. package/dist/src/intl/pa.d.ts.map +1 -0
  109. package/dist/src/intl/pa.js +258 -0
  110. package/dist/src/intl/pl.d.ts +117 -0
  111. package/dist/src/intl/pl.d.ts.map +1 -0
  112. package/dist/src/intl/pl.js +242 -0
  113. package/dist/src/intl/pt-BR.d.ts +117 -0
  114. package/dist/src/intl/pt-BR.d.ts.map +1 -0
  115. package/dist/src/intl/pt-BR.js +258 -0
  116. package/dist/src/intl/pt.d.ts +117 -0
  117. package/dist/src/intl/pt.d.ts.map +1 -0
  118. package/dist/src/intl/pt.js +246 -0
  119. package/dist/src/intl/ro.d.ts +117 -0
  120. package/dist/src/intl/ro.d.ts.map +1 -0
  121. package/dist/src/intl/ro.js +258 -0
  122. package/dist/src/intl/sk.d.ts +117 -0
  123. package/dist/src/intl/sk.d.ts.map +1 -0
  124. package/dist/src/intl/sk.js +244 -0
  125. package/dist/src/intl/sl.d.ts +117 -0
  126. package/dist/src/intl/sl.d.ts.map +1 -0
  127. package/dist/src/intl/sl.js +258 -0
  128. package/dist/src/intl/sv.d.ts +117 -0
  129. package/dist/src/intl/sv.d.ts.map +1 -0
  130. package/dist/src/intl/sv.js +258 -0
  131. package/dist/src/intl/sw.d.ts +63 -0
  132. package/dist/src/intl/sw.d.ts.map +1 -0
  133. package/dist/src/intl/sw.js +191 -0
  134. package/dist/src/intl/ta.d.ts +117 -0
  135. package/dist/src/intl/ta.d.ts.map +1 -0
  136. package/dist/src/intl/ta.js +258 -0
  137. package/dist/src/intl/te.d.ts +117 -0
  138. package/dist/src/intl/te.d.ts.map +1 -0
  139. package/dist/src/intl/te.js +258 -0
  140. package/dist/src/intl/th.d.ts +117 -0
  141. package/dist/src/intl/th.d.ts.map +1 -0
  142. package/dist/src/intl/th.js +258 -0
  143. package/dist/src/intl/tr.d.ts +117 -0
  144. package/dist/src/intl/tr.d.ts.map +1 -0
  145. package/dist/src/intl/tr.js +255 -0
  146. package/dist/src/intl/uk.d.ts +117 -0
  147. package/dist/src/intl/uk.d.ts.map +1 -0
  148. package/dist/src/intl/uk.js +258 -0
  149. package/dist/src/intl/ur.d.ts +117 -0
  150. package/dist/src/intl/ur.d.ts.map +1 -0
  151. package/dist/src/intl/ur.js +245 -0
  152. package/dist/src/intl/vi.d.ts +117 -0
  153. package/dist/src/intl/vi.d.ts.map +1 -0
  154. package/dist/src/intl/vi.js +255 -0
  155. package/dist/src/intl/zh-CN.d.ts +117 -0
  156. package/dist/src/intl/zh-CN.d.ts.map +1 -0
  157. package/dist/src/intl/zh-CN.js +258 -0
  158. package/dist/src/intl/zh-TW.d.ts +117 -0
  159. package/dist/src/intl/zh-TW.d.ts.map +1 -0
  160. package/dist/src/intl/zh-TW.js +258 -0
  161. package/dist/src/local-first/Owner.d.ts +156 -62
  162. package/dist/src/local-first/Owner.d.ts.map +1 -1
  163. package/dist/src/local-first/Owner.js +119 -20
  164. package/package.json +1 -1
  165. package/src/Function.ts +2 -0
  166. package/src/Http.ts +9 -0
  167. package/src/Object.ts +2 -0
  168. package/src/RefCount.ts +2 -0
  169. package/src/Relation.ts +4 -0
  170. package/src/Resource.ts +2 -0
  171. package/src/Result.ts +397 -560
  172. package/src/StackTrace.ts +2 -0
  173. package/src/Task.ts +1143 -1222
  174. package/src/Time.ts +2 -0
  175. package/src/Type.ts +1598 -712
  176. package/src/intl/_en.ts +497 -0
  177. package/src/intl/ar.ts +417 -0
  178. package/src/intl/bn.ts +486 -0
  179. package/src/intl/ca.ts +488 -0
  180. package/src/intl/cs.ts +95 -31
  181. package/src/intl/da.ts +488 -0
  182. package/src/intl/de.ts +488 -0
  183. package/src/intl/el.ts +494 -0
  184. package/src/intl/es.ts +488 -0
  185. package/src/intl/fa.ts +487 -0
  186. package/src/intl/fi.ts +487 -0
  187. package/src/intl/fil.ts +488 -0
  188. package/src/intl/fr.ts +488 -0
  189. package/src/intl/he.ts +486 -0
  190. package/src/intl/hi.ts +487 -0
  191. package/src/intl/hr.ts +494 -0
  192. package/src/intl/hu.ts +387 -0
  193. package/src/intl/id.ts +487 -0
  194. package/src/intl/index.ts +42 -0
  195. package/src/intl/it.ts +488 -0
  196. package/src/intl/ja.ts +488 -0
  197. package/src/intl/ko.ts +487 -0
  198. package/src/intl/ml.ts +487 -0
  199. package/src/intl/mr.ts +487 -0
  200. package/src/intl/ms.ts +410 -0
  201. package/src/intl/nb.ts +364 -0
  202. package/src/intl/nl.ts +488 -0
  203. package/src/intl/pa.ts +487 -0
  204. package/src/intl/pl.ts +450 -0
  205. package/src/intl/pt-BR.ts +487 -0
  206. package/src/intl/pt.ts +424 -0
  207. package/src/intl/ro.ts +488 -0
  208. package/src/intl/sk.ts +412 -0
  209. package/src/intl/sl.ts +487 -0
  210. package/src/intl/sv.ts +488 -0
  211. package/src/intl/sw.ts +324 -0
  212. package/src/intl/ta.ts +487 -0
  213. package/src/intl/te.ts +487 -0
  214. package/src/intl/th.ts +484 -0
  215. package/src/intl/tr.ts +487 -0
  216. package/src/intl/uk.ts +488 -0
  217. package/src/intl/ur.ts +410 -0
  218. package/src/intl/vi.ts +435 -0
  219. package/src/intl/zh-CN.ts +482 -0
  220. package/src/intl/zh-TW.ts +482 -0
  221. package/src/local-first/Owner.ts +156 -62
package/src/Type.ts CHANGED
@@ -1,175 +1,100 @@
1
1
  /**
2
- * Runtime types.
2
+ * ## Intro
3
3
  *
4
- * @module
5
- */
6
-
7
- import { utf8ToBytes } from "@noble/ciphers/utils.js";
8
- import { sha256 } from "@noble/hashes/sha2.js";
9
- import * as bip39 from "@scure/bip39";
10
- import { wordlist } from "@scure/bip39/wordlists/english.js";
11
- import type { StandardSchemaV1 } from "@standard-schema/spec";
12
- import type {
13
- AtLeastTwoReadonlyArray,
14
- NonEmptyReadonlyArray,
15
- } from "./Array.ts";
16
- import { assert, assertNonNullable } from "./Assert.ts";
17
- import type { Brand } from "./Brand.ts";
18
- import type { RandomBytesDep } from "./Crypto.ts";
19
- import { identity, type Thunk } from "./Function.ts";
20
- import { createMutableRecord } from "./Object.ts";
21
- import { hasNodeBuffer } from "./Platform.ts";
22
- import {
23
- err,
24
- flatMapResult,
25
- getOk,
26
- getOrNull,
27
- getOrThrow,
28
- ok,
29
- trySync,
30
- type Result,
31
- } from "./Result.ts";
32
- import { safelyStringifyUnknownValue } from "./String.ts";
33
- import type { Task } from "./Task.ts";
34
- import type { TimeDep } from "./Time.ts";
35
- import {
36
- instance,
37
- isInstance,
38
- type CompileTimeError,
39
- type Instance,
40
- type IsUnion,
41
- type Literal,
42
- type Simplify,
43
- type ValueWithLength,
44
- type WidenLiteral,
45
- } from "./Types.ts";
46
-
47
- /**
48
- * A runtime representation of a TypeScript type with typed structured errors.
49
- *
50
- * Evolu Type reports expected decoding failures through {@link Result} rather
51
- * than exceptions. It represents both an encoded `Input` and its semantic
52
- * `Output`, supporting validation, transformation, and canonical encoding.
53
- *
54
- * Evolu Type is designed for correctness and developer experience. Correctness
55
- * is especially important for local-first data: application authors cannot
56
- * inspect or repair a user's data on a server because they do not have access
57
- * to it. Type declarations must reject invalid data at system boundaries, then
58
- * preserve those guarantees wherever the data travels.
59
- *
60
- * To make correct code the easiest code to write, Evolu Type preserves as much
61
- * information as TypeScript can express. Invalid declarations produce readable
62
- * {@link CompileTimeError} types when the compiler can detect them, while
63
- * runtime assertions enforce construction contracts it cannot prove. Together,
64
- * these choices create a pit of success.
65
- *
66
- * The implementation is optimized for minimal bundle size. Less descriptive
67
- * assertion messages could make it even smaller, but Evolu keeps actionable
68
- * messages as a deliberate tradeoff for developer experience.
69
- *
70
- * The main properties of Evolu Type are:
71
- *
72
- * - **Result-based error handling** – expected failures are explicit values.
73
- * - **Typed errors with decoupled formatters** – validation logic stays
74
- * independent of user-facing messages, and errors can be handled
75
- * exhaustively.
76
- * - **Type-safe, tree-shakeable localization** – formatter requirements are
77
- * inferred from selected Types, while apps bundle exactly the locales they
78
- * support so users can change language offline.
79
- * - **Consistent constraints through {@link Brand}** – every refinement constraint
80
- * is represented in the TypeScript type, so an unconstrained parent value
81
- * cannot be used where the constrained value is required.
82
- * - **Typed inputs** – prefer `from` and its `.parent` entry points to connect
83
- * precise producer and consumer contracts while preserving typed remaining
84
- * errors; reserve `fromUnknown` for genuinely unknown values.
85
- * - **Lawful codecs** – Types partially decode `Input` to `Output` and totally
86
- * encode every legitimate `Output` to `CanonicalInput`, the statically known
87
- * subtype of `Input` returned by complete encoding.
88
- * - **A top-down implementation** – the source is intended to be read from
89
- * beginning to end.
90
- *
91
- * Evolu Type supports [Standard Schema](https://standardschema.dev/) for
92
- * interoperability with compatible tools and frameworks while preserving each
93
- * Type's exact Input and Output.
94
- *
95
- * Evolu Type assumes that all executing code, including third-party
96
- * dependencies, has been audited and is trusted. It validates data contracts
97
- * under that assumption. Trusting code does not require trusting every value it
98
- * returns, so uncertain values from legacy code or another realm can still be
99
- * decoded at an explicit boundary. It does not protect against hostile
100
- * executable behavior such as sabotaged Proxies or throwing traps; Type
101
- * validation is not a security boundary for untrusted JavaScript.
102
- *
103
- * Type declarations and their callbacks are trusted construction code. Evolu
104
- * Type leverages that trust for better developer experience and does not try to
105
- * recover from code that defeats the type system with `any` or casts, including
106
- * fabricating an `Err` for `Result<_, never>`. Runtime assertions still enforce
107
- * contracts TypeScript cannot express, and every Type declaration must be
108
- * tested for its expected successes and failures.
4
+ * Runtime validation with precise TypeScript types and structured errors.
5
+ *
6
+ * Evolu {@link Type} is a pure, synchronous codec for defining semantic domains.
7
+ * It partially decodes an `Input` into an `Output` and totally encodes every
8
+ * valid `Output` into a `CanonicalInput`. Types can validate, refine,
9
+ * transform, and compose without losing the contracts TypeScript can express.
10
+ *
11
+ * ```ts
12
+ * import {
13
+ * NonEmptyTrimmedString100,
14
+ * PositiveInt,
15
+ * object,
16
+ * type InferType,
17
+ * } from "@evolu/common";
18
+ *
19
+ * const User = object({
20
+ * name: NonEmptyTrimmedString100,
21
+ * age: PositiveInt,
22
+ * });
23
+ * interface User extends InferType<typeof User> {}
24
+ *
25
+ * const value: unknown = { name: "Ada", age: 37 };
26
+ * const user = User.fromUnknown(value);
27
+ *
28
+ * expectOk(user, { name: "Ada", age: 37 });
29
+ * expectTypeOf(user.value).toExtend<User>();
30
+ * ```
31
+ *
32
+ * Decoding failures are explicit {@link Result} values. Error formatters are
33
+ * separate from validation, so structured errors remain exhaustively typed and
34
+ * can be localized without changing the Type.
35
+ *
36
+ * Evolu Type is designed to make correct code the easiest code to write:
37
+ *
38
+ * - {@link Brand} carries every refinement constraint into TypeScript.
39
+ * - Invalid declarations produce readable {@link CompileTimeError} types when the
40
+ * compiler can detect them; runtime assertions enforce construction contracts
41
+ * it cannot prove.
42
+ * - Typed `from` boundaries allow connecting value producers to domain fields
43
+ * through their exact TypeScript types, so incompatible contract changes are
44
+ * compile-time errors rather than runtime validation errors.
45
+ * - Lawful codecs compose without creating unencodable values: every valid
46
+ * Output has a canonical Input representation and round-trips to the same
47
+ * semantic value.
48
+ * - Type-safe localization infers the required error formatters from selected
49
+ * Types, so missing validation messages are compile-time errors.
50
+ *
51
+ * Correctness is especially important for local-first data: application authors
52
+ * cannot inspect or repair a user's data on a server. Type declarations reject
53
+ * invalid data at system boundaries, then preserve those guarantees wherever
54
+ * the data travels.
55
+ *
56
+ * Evolu Type supports [Standard Schema](https://standardschema.dev/) while
57
+ * preserving each Type's exact Input and Output. Its implementation is
58
+ * optimized for small bundles, but keeps actionable assertion messages as a
59
+ * deliberate developer-experience tradeoff.
60
+ *
61
+ * Evolu Type requires TypeScript 7 or newer and `exactOptionalPropertyTypes`.
62
+ * Predefined Types use the names of corresponding JavaScript built-ins. When
63
+ * one shadows a built-in, access the built-in through `globalThis`, such as
64
+ * `globalThis.String` or `globalThis.Date`.
65
+ *
66
+ * ## Boundaries
109
67
  *
110
68
  * `fromUnknown` validates untyped input through the complete pipeline. `from`
111
- * and its `.parent` operations use their declared boundary to determine which
112
- * remaining stages can return validation errors, but assert that boundary at
113
- * runtime. `orThrow` and `orNull` reuse the deepest `from` operation accepting
114
- * `Input`, while `to` asserts its Output boundary. A failed assertion means
115
- * application code violated its static contract; it is a developer error, not
116
- * an expected validation failure. Its message identifies the expected Type and
117
- * its cause preserves the exact structured Output validation error.
69
+ * and its `.parent` operations assert their declared boundary, then return only
70
+ * errors from the remaining stages. `orThrow` and `orNull` reuse the deepest
71
+ * `from` operation accepting `Input`, while `to` asserts its Output boundary. A
72
+ * failed assertion is a developer error; its cause preserves the exact
73
+ * structured Output validation error.
118
74
  *
119
75
  * Prefer the most precise typed boundary available. A value is not unknown
120
76
  * merely because it originated outside the application: forms, components, and
121
77
  * other producers often expose a `string` or a branded value that can connect
122
78
  * directly to a matching `from` boundary. Reserve `fromUnknown` for values
123
79
  * whose TypeScript type is genuinely `unknown`. `is` means exact membership in
124
- * the output domain, not merely that output-side parsing could succeed. A
125
- * successful `fromUnknown` result always satisfies `is`.
126
- *
127
- * Decoding accepts a representation outside the Output domain only when the
128
- * Type explicitly declares that representation, such as a transformation Input.
129
- * Structural Types do not implicitly repair another JavaScript representation.
130
- * In particular, {@link array} and {@link tuple} require dense own data elements,
131
- * while the predefined {@link Object}, {@link object}, and {@link record} require
132
- * plain objects with own enumerable data properties. They do not invoke
133
- * accessors or materialize inherited and non-enumerable properties.
134
- *
135
- * TypeScript object types are structural and do not encode JavaScript realm
136
- * identity. Structural Types therefore accept legitimate representations from
137
- * other realms. Prototype checks remain when the prototype defines the semantic
138
- * domain. Plain-object Types accept a `null` prototype or an immediate root
139
- * prototype whose own prototype is `null`; ordinary class instances and deeper
140
- * prototype chains are rejected. When Record decoding must construct a
141
- * normalized value, it uses a `null` prototype so every string key remains
142
- * ordinary data.
143
- *
144
- * Evolu Type requires TypeScript 7 or newer and expects the
145
- * `exactOptionalPropertyTypes` compiler option to be enabled.
146
- *
147
- * Predefined Types intentionally use the names of corresponding JavaScript
148
- * built-ins because they represent those familiar value categories. If an
149
- * imported Type shadows a built-in in the same scope, access the built-in
150
- * through `globalThis`, JavaScript's standard cross-environment global object,
151
- * such as `globalThis.String` or `globalThis.Date`.
152
- *
153
- * ### Example
80
+ * the Output domain, not merely that an encoded Input can be decoded.
154
81
  *
155
- * ```ts
156
- * import { String, type Result } from "@evolu/common";
82
+ * Typed boundaries also connect producer and domain constraints at compile
83
+ * time. Suppose a form and its domain field both use
84
+ * {@link NonEmptyTrimmedString100}. If the form is later relaxed to
85
+ * {@link NonEmptyTrimmedString1000}, the domain Type will still reject longer
86
+ * values, so invalid data cannot be stored. But a boundary accepting only
87
+ * `unknown` or `string` cannot reveal that the producer contract changed. The
88
+ * application still compiles, and users discover the incompatibility only when
89
+ * a valid form value fails to save.
157
90
  *
158
- * const value: unknown = "hello";
159
- * const result = String.fromUnknown(value);
91
+ * Passing the precise branded value to `from` makes that incompatibility a
92
+ * compile-time error. The developer must preserve the original form limit or
93
+ * introduce a new field for the wider domain instead of shipping a broken form.
160
94
  *
161
- * expectTypeOf(result).toEqualTypeOf<
162
- * Result<
163
- * string,
164
- * {
165
- * readonly type: "TypeOf";
166
- * readonly expected: "String";
167
- * readonly value: unknown;
168
- * }
169
- * >
170
- * >();
171
- * expectOk(result, "hello");
172
- * ```
95
+ * A weaker producer is sometimes intentional. In that case, a matching
96
+ * `.parent` boundary validates only the constraints the producer does not
97
+ * already guarantee, while preserving its existing guarantees in the type.
173
98
  *
174
99
  * ## FAQ
175
100
  *
@@ -287,10 +212,10 @@ import {
287
212
  * is own, enumerable, or a data property. It also permits a wider object with
288
213
  * excess properties where a narrower object type is expected.
289
214
  *
290
- * `fromUnknown` treats such invalid external values as expected data and
291
- * returns a typed error. Typed boundaries instead assert the domain promised by
292
- * their parameter type. If application code claims an accessor-backed object or
293
- * an object with excess properties is an Object Output, the assertion throws
215
+ * `fromUnknown` treats such invalid external values as input data and returns a
216
+ * typed error. Typed boundaries instead assert the domain promised by their
217
+ * parameter type. If application code claims an accessor-backed object or an
218
+ * object with excess properties is an Object Output, the assertion throws
294
219
  * because the application contract is broken. `orThrow` and `orNull` preserve
295
220
  * the assertion at their typed `Input` boundary, then apply {@link getOrThrow}
296
221
  * or {@link getOrNull} only to validation failures returned by the remaining
@@ -325,10 +250,12 @@ import {
325
250
  * without requiring conversion merely because its built-ins belong to another
326
251
  * realm.
327
252
  *
328
- * When an application trusts both the producer and its return contract, it can
329
- * cast the boundary API's `unknown` because validation is redundant. Use a
330
- * specialized Type or explicit transformation when the producer actually uses a
331
- * different representation that needs adaptation or normalization.
253
+ * When an application trusts both the producer and its return contract, expose
254
+ * that contract as an accurate TypeScript type and use the typed value directly.
255
+ * If the boundary returns `unknown`, validate it instead of bypassing the
256
+ * boundary with a cast. Use a specialized Type or explicit transformation when
257
+ * the producer uses a different representation that needs adaptation or
258
+ * normalization.
332
259
  *
333
260
  * All executing JavaScript remains trusted. Deliberately forged built-ins,
334
261
  * hostile Proxies, throwing traps, or sabotaged executable behavior can throw;
@@ -384,8 +311,8 @@ import {
384
311
  * value to a Task. A pure synchronous conversion that can fail can be an
385
312
  * ordinary function returning Result.
386
313
  *
387
- * Keeping those responsibilities separate prevents an Evolu Type from becoming
388
- * a hidden application workflow. It also keeps validation deterministic,
314
+ * Keeping those responsibilities separate prevents Evolu Type from becoming a
315
+ * hidden application workflow. It also keeps validation deterministic,
389
316
  * dependency-free, immediately composable, and straightforward to test.
390
317
  *
391
318
  * ### What if only decoding is needed?
@@ -397,7 +324,55 @@ import {
397
324
  * irreversible operation is a separate function or Task, not a Type
398
325
  * transformation.
399
326
  *
400
- * @group Core Types
327
+ * @module
328
+ */
329
+ import { utf8ToBytes } from "@noble/ciphers/utils.js";
330
+ import { sha256 } from "@noble/hashes/sha2.js";
331
+ import * as bip39 from "@scure/bip39";
332
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
333
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
334
+ import type {
335
+ AtLeastTwoReadonlyArray,
336
+ NonEmptyReadonlyArray,
337
+ } from "./Array.ts";
338
+ import { assert, assertNonNullable } from "./Assert.ts";
339
+ import type { Brand } from "./Brand.ts";
340
+ import type { RandomBytesDep } from "./Crypto.ts";
341
+ import { identity, type Thunk } from "./Function.ts";
342
+ import { createMutableRecord } from "./Object.ts";
343
+ import { hasNodeBuffer } from "./Platform.ts";
344
+ import {
345
+ err,
346
+ flatMapResult,
347
+ getOk,
348
+ getOrNull,
349
+ getOrThrow,
350
+ ok,
351
+ trySync,
352
+ type Result,
353
+ } from "./Result.ts";
354
+ import { safelyStringifyUnknownValue } from "./String.ts";
355
+ import type { Task } from "./Task.ts";
356
+ import type { TimeDep } from "./Time.ts";
357
+ import {
358
+ instance,
359
+ isInstance,
360
+ type CompileTimeError,
361
+ type Instance,
362
+ type IsUnion,
363
+ type Literal,
364
+ type Simplify,
365
+ type ValueWithLength,
366
+ type WidenLiteral,
367
+ } from "./Types.ts";
368
+
369
+ /**
370
+ * A runtime representation of a TypeScript type, including its encoded input,
371
+ * semantic output, structured errors, and canonical encoding.
372
+ *
373
+ * See the {@link @evolu/common!Type | Type overview}.
374
+ *
375
+ * @group Core
401
376
  */
402
377
  export interface Type<
403
378
  Name extends TypeName,
@@ -489,7 +464,20 @@ export interface Type<
489
464
  /** The one preceding Type node, or `null` for a root Type. */
490
465
  readonly parent: Parent;
491
466
 
492
- /** Decodes an unknown value through the complete Type pipeline. */
467
+ /**
468
+ * Decodes an unknown value through the complete Type pipeline.
469
+ *
470
+ * ### Example
471
+ *
472
+ * ```ts
473
+ * import { PositiveInt } from "@evolu/common";
474
+ *
475
+ * const value: unknown = 42;
476
+ * const result = PositiveInt.fromUnknown(value);
477
+ *
478
+ * expectOk(result, 42);
479
+ * ```
480
+ */
493
481
  readonly fromUnknown: (
494
482
  value: unknown,
495
483
  options?: ValidationOptions,
@@ -503,6 +491,19 @@ export interface Type<
503
491
  * Structural errors retain nested errors and their locations in the typed
504
492
  * error value. This formatter does not encode paths or enumerate nested
505
493
  * errors in its message.
494
+ *
495
+ * ### Example
496
+ *
497
+ * ```ts
498
+ * import { String } from "@evolu/common";
499
+ *
500
+ * const result = String.fromUnknown(42);
501
+ *
502
+ * expectErr(result, { type: "TypeOf", expected: "String", value: 42 });
503
+ * expect(String.formatError(result.error)).toBe(
504
+ * "A value 42 is not a string.",
505
+ * );
506
+ * ```
506
507
  */
507
508
  readonly formatError: TypeErrorFormatter<Errors>;
508
509
 
@@ -540,6 +541,52 @@ export interface Type<
540
541
  * developer error. The Error message identifies the expected boundary Type,
541
542
  * and its cause preserves the structured validation error. Only failures
542
543
  * introduced after that boundary are returned through `Result`.
544
+ *
545
+ * ### Example
546
+ *
547
+ * A form already guarantees every constraint on a title, while a new note
548
+ * input guarantees only trimmed text. The note's parent boundary validates
549
+ * only the constraints that the form does not guarantee:
550
+ *
551
+ * ```ts
552
+ * import {
553
+ * flatMapResult,
554
+ * NonEmptyTrimmedString100,
555
+ * object,
556
+ * TrimmedString,
557
+ * type MaxLengthError,
558
+ * type MinLengthError,
559
+ * type Result,
560
+ * } from "@evolu/common";
561
+ *
562
+ * const Todo = object({
563
+ * title: NonEmptyTrimmedString100,
564
+ * note: NonEmptyTrimmedString100,
565
+ * });
566
+ *
567
+ * const saveTodo = (
568
+ * title: NonEmptyTrimmedString100,
569
+ * note: TrimmedString,
570
+ * ) => {
571
+ * // @ts-expect-error TrimmedString does not guarantee a non-empty value
572
+ * // with at most 100 characters.
573
+ * Todo.from({ title, note });
574
+ *
575
+ * return flatMapResult(
576
+ * Todo.props.note.from.parent.parent(note),
577
+ * (note) => Todo.from({ title, note }),
578
+ * );
579
+ * };
580
+ *
581
+ * const title = NonEmptyTrimmedString100.orThrow("Buy milk");
582
+ * const note = TrimmedString.orThrow("Remember oat milk");
583
+ * const result = saveTodo(title, note);
584
+ *
585
+ * expectTypeOf(result).toEqualTypeOf<
586
+ * Result<typeof Todo.Output, MaxLengthError<100> | MinLengthError<1>>
587
+ * >();
588
+ * expectOk(result, { title, note });
589
+ * ```
543
590
  */
544
591
  readonly from: [CustomFrom] extends [never]
545
592
  ? [Parent] extends [infer P extends TypeNode]
@@ -554,6 +601,16 @@ export interface Type<
554
601
  * `to` runs the complete encoding pipeline. Its first `.parent` stops at the
555
602
  * immediate parent Output, and each additional suffix stops one Type closer
556
603
  * to the root. Every entry point accepts this Type's `Output`.
604
+ *
605
+ * ### Example
606
+ *
607
+ * ```ts
608
+ * import { Int64, Int64FromInt64String } from "@evolu/common";
609
+ *
610
+ * const value = Int64.orThrow(42n);
611
+ *
612
+ * expect(Int64FromInt64String.to(value)).toBe("42");
613
+ * ```
557
614
  */
558
615
  readonly to: [Parent] extends [infer P extends TypeNode]
559
616
  ? ToOperation<Output, CanonicalInput, P>
@@ -586,6 +643,9 @@ export interface Type<
586
643
  *
587
644
  * // Equivalent because `from.parent` is this Type's deepest `from` operation:
588
645
  * const sameValue = getOrThrow(NonEmptyString.from.parent("Evolu"));
646
+ *
647
+ * expect(value).toBe("Evolu");
648
+ * expect(sameValue).toBe(value);
589
649
  * ```
590
650
  */
591
651
  readonly orThrow: TypeOperationFn<"orThrow", Input, Output, never>;
@@ -617,12 +677,20 @@ export interface Type<
617
677
  *
618
678
  * // Equivalent because `from.parent` is this Type's deepest `from` operation:
619
679
  * const sameValue = getOrNull(NonEmptyString.from.parent("Evolu"));
680
+ *
681
+ * expect(value).toBe("Evolu");
682
+ * expect(sameValue).toBe(value);
683
+ * expect(NonEmptyString.orNull("")).toBeNull();
620
684
  * ```
621
685
  */
622
686
  readonly orNull: TypeOperationFn<"orNull", Input, Output, never>;
623
687
  }
624
688
 
625
- /** @group Core Types */
689
+ /**
690
+ * A capitalized name identifying a {@link Type} node.
691
+ *
692
+ * @group Core
693
+ */
626
694
  export type TypeName = Capitalize<string>;
627
695
 
628
696
  /**
@@ -635,7 +703,7 @@ export type TypeName = Capitalize<string>;
635
703
  * only when it intentionally represents the same error contract. Accidental
636
704
  * reuse prevents reliable discriminated-union narrowing.
637
705
  *
638
- * @group Core Types
706
+ * @group Core
639
707
  */
640
708
  export interface TypeError<Name extends TypeName = TypeName> {
641
709
  readonly type: Name;
@@ -653,7 +721,7 @@ interface TransparentTypeError {
653
721
  * Structural errors such as Array and Union errors extend {@link TypeError}
654
722
  * instead because they locate nested errors rather than own one value.
655
723
  *
656
- * @group Core Types
724
+ * @group Core
657
725
  */
658
726
  // Built-in errors intentionally repeat narrower `value` properties. Making
659
727
  // `value` generic here and sharing this base regresses `pnpm bench:type`.
@@ -666,13 +734,18 @@ export interface TypeValueError<
666
734
  /**
667
735
  * Formats a structured {@link TypeError} as a human-readable message.
668
736
  *
669
- * @group Core Types
737
+ * @group Core
670
738
  */
671
739
  export type TypeErrorFormatter<Error extends TypeError> = (
672
740
  error: Error,
673
741
  ) => string;
674
742
 
675
- /** @group Core Types */
743
+ /**
744
+ * The common structural shape of every {@link Type}, with its specific type
745
+ * parameters erased.
746
+ *
747
+ * @group Core
748
+ */
676
749
  export interface TypeNode {
677
750
  readonly name: TypeName;
678
751
  readonly "~standard": StandardSchemaV1.Props<unknown, unknown>;
@@ -804,7 +877,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
804
877
  * Asserts that a value belongs to a {@link Type} Output domain.
805
878
  *
806
879
  * Use this for internal invariants, not external input. Validate external input
807
- * with `Type.fromUnknown` so expected failures remain typed values. A failed
880
+ * with `Type.fromUnknown` so validation failures remain typed values. A failed
808
881
  * assertion uses the Type name for its message and preserves the exact Output
809
882
  * validation error as the thrown Error's cause.
810
883
  *
@@ -824,7 +897,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
824
897
  * >();
825
898
  * ```
826
899
  *
827
- * @group Core Types
900
+ * @group Core
828
901
  */
829
902
  export const assertType: <T extends TypeNode>(
830
903
  type: T,
@@ -860,34 +933,32 @@ const assertTypeOutput = <Error extends TypeError>(
860
933
  };
861
934
 
862
935
  /**
863
- * Localized copies of selected {@link Type} declarations for every locale.
936
+ * Creates localized copies of selected {@link Type} declarations.
864
937
  *
865
- * Each locale supplies one formatter for every Type that can own a formatted
866
- * error. Structural Types use their own formatter for structural failures and
867
- * delegate contained failures to the Type that produced them. A Union owns its
868
- * complete failure because no member matched. Formatter requirements are
869
- * inferred recursively from structured errors, including mutually recursive
870
- * Lazy error interfaces.
938
+ * Pass the Types used together in one localization scope and formatter maps
939
+ * keyed by locale. TypeScript infers every formatter required by the selected
940
+ * Types, including errors from nested structural Types and recursive Lazy
941
+ * Types. Every locale must provide the complete inferred formatter set;
942
+ * missing and unrelated formatters are compile-time errors.
871
943
  *
872
- * Parents and Types exposed through public reflection are localized recursively
873
- * through one shared cache. Lazy definitions stay opaque and are not evaluated;
874
- * their declared error types provide the formatter requirements instead.
944
+ * The result preserves the locale names, selected Type names, and exact
945
+ * TypeScript types. A localized Type validates exactly like its source Type;
946
+ * only its human-readable `formatError` and Standard Schema messages change.
947
+ * The source Types remain unchanged.
875
948
  *
876
- * The selected Type map, locale map, and each formatter map must be plain
877
- * objects whose entries are own enumerable string-keyed data properties.
949
+ * Parents and reflected child Types are localized with the same formatter set.
950
+ * Structural Types retain error paths and delegate nested messages to the Type
951
+ * that produced them. Different localized Type sets can coexist in separate
952
+ * application or dependency-injection scopes.
878
953
  *
879
- * All localized Types for one locale share the same formatter set. The result
880
- * preserves the selected names and exact TypeScript types under every locale
881
- * key, making one localized set easy to provide through dependency injection or
882
- * application context. Canonical Types remain unchanged.
954
+ * Localization is scoped to the selected Types instead of a package-wide
955
+ * translation registry. Static imports give bundlers an explicit dependency
956
+ * graph, so unrelated Types, locales, and formatters can be removed. Bundling
957
+ * every locale an app supports also allows language changes without a network
958
+ * connection.
883
959
  *
884
- * Localization is scoped to the Types an app imports instead of a package-wide
885
- * translation registry. Ordinary static imports give bundlers an explicit
886
- * dependency graph from those Types to their formatters, so unrelated Type and
887
- * localization code can be removed. An app supplies all locales it supports in
888
- * the same self-contained bundle, allowing users to change language without a
889
- * network connection. Different localized Type sets can coexist on one page or
890
- * in separate dependency-injection scopes.
960
+ * The selected Type map, locale map, and formatter maps must be plain objects
961
+ * with own enumerable string-keyed data properties.
891
962
  *
892
963
  * ### Example
893
964
  *
@@ -897,7 +968,7 @@ const assertTypeOutput = <Error extends TypeError>(
897
968
  *
898
969
  * const Label = minLength(1)(String);
899
970
  *
900
- * const TypesByLocale = localizeTypes(
971
+ * const typesByLocale = localizeTypes(
901
972
  * { Label },
902
973
  * {
903
974
  * cs: {
@@ -907,11 +978,66 @@ const assertTypeOutput = <Error extends TypeError>(
907
978
  * },
908
979
  * );
909
980
  *
910
- * expectTypeOf<typeof TypesByLocale.cs.Label>().toEqualTypeOf<
981
+ * expectTypeOf<typeof typesByLocale.cs.Label>().toEqualTypeOf<
911
982
  * typeof Label
912
983
  * >();
984
+ *
985
+ * const result = typesByLocale.cs.Label.fromUnknown("");
986
+ * expectErr(result, { type: "MinLength1", min: 1, value: "" });
987
+ * expect(typesByLocale.cs.Label.formatError(result.error)).toBe(
988
+ * "Text nesmí být prázdný.",
989
+ * );
913
990
  * ```
914
991
  *
992
+ * ### Supported locales
993
+ *
994
+ * English is built in; use {@link Type} directly for its default formatters.
995
+ * The following additional locales are available:
996
+ *
997
+ * - Arabic (`ar`)
998
+ * - Bengali (`bn`)
999
+ * - Catalan (`ca`)
1000
+ * - Chinese, Simplified (`zhCN`)
1001
+ * - Chinese, Traditional (`zhTW`)
1002
+ * - Croatian (`hr`)
1003
+ * - Czech (`cs`)
1004
+ * - Danish (`da`)
1005
+ * - Dutch (`nl`)
1006
+ * - Filipino (`fil`)
1007
+ * - Finnish (`fi`)
1008
+ * - French (`fr`)
1009
+ * - German (`de`)
1010
+ * - Greek (`el`)
1011
+ * - Hebrew (`he`)
1012
+ * - Hindi (`hi`)
1013
+ * - Hungarian (`hu`)
1014
+ * - Indonesian (`id`)
1015
+ * - Italian (`it`)
1016
+ * - Japanese (`ja`)
1017
+ * - Korean (`ko`)
1018
+ * - Malay (`ms`)
1019
+ * - Malayalam (`ml`)
1020
+ * - Marathi (`mr`)
1021
+ * - Norwegian Bokmål (`nb`)
1022
+ * - Persian (`fa`)
1023
+ * - Polish (`pl`)
1024
+ * - Portuguese (`pt`)
1025
+ * - Portuguese, Brazilian (`ptBR`)
1026
+ * - Punjabi (`pa`)
1027
+ * - Romanian (`ro`)
1028
+ * - Slovak (`sk`)
1029
+ * - Slovenian (`sl`)
1030
+ * - Spanish (`es`)
1031
+ * - Swahili (`sw`)
1032
+ * - Swedish (`sv`)
1033
+ * - Tamil (`ta`)
1034
+ * - Telugu (`te`)
1035
+ * - Thai (`th`)
1036
+ * - Turkish (`tr`)
1037
+ * - Ukrainian (`uk`)
1038
+ * - Urdu (`ur`)
1039
+ * - Vietnamese (`vi`)
1040
+ *
915
1041
  * @group Localization
916
1042
  */
917
1043
  export const localizeTypes = ((
@@ -1277,16 +1403,21 @@ declare const identityEncodingSymbol: unique symbol;
1277
1403
  * age: optional(PositiveInt),
1278
1404
  * });
1279
1405
  * interface User extends InferType<typeof User> {}
1406
+ *
1407
+ * const user = User.orThrow({ name: "Ada", age: 37 });
1408
+ *
1409
+ * expectTypeOf(user).toExtend<User>();
1410
+ * expect(user.name).toBe("Ada");
1280
1411
  * ```
1281
1412
  *
1282
- * @group Type utilities
1413
+ * @group Core
1283
1414
  */
1284
1415
  export type InferType<T extends TypeNode> = T["Output"];
1285
1416
 
1286
1417
  /**
1287
1418
  * The union of errors a {@link Type} can return from `fromUnknown`.
1288
1419
  *
1289
- * @group Type utilities
1420
+ * @group Core
1290
1421
  */
1291
1422
  export type InferErrors<T extends TypeNode> = T[typeof errorsSymbol];
1292
1423
 
@@ -1501,7 +1632,7 @@ type TypeOperationFn<
1501
1632
  /**
1502
1633
  * Configures how container {@link Type} operations report errors.
1503
1634
  *
1504
- * @group Core Types
1635
+ * @group Core
1505
1636
  */
1506
1637
  export interface ValidationOptions {
1507
1638
  /** Controls whether container {@link Type} operations return one or all errors. */
@@ -1637,7 +1768,7 @@ type ConcreteChildTypeNameError = CompileTimeError<
1637
1768
  * expectErr(Text.fromUnknown(42), { type: "Text", value: 42 });
1638
1769
  * ```
1639
1770
  *
1640
- * @group Type construction
1771
+ * @group Construction
1641
1772
  */
1642
1773
  export function createType<
1643
1774
  Name extends TypeName,
@@ -1649,6 +1780,8 @@ export function createType<
1649
1780
  // Validation alone determines Error; broad formatters must not widen it.
1650
1781
  formatError: TypeErrorFormatter<NoInfer<Error>>,
1651
1782
  ): Type<Name, Output, Output, Error, null, Error, never, Output>;
1783
+
1784
+ /** Creates an infallible child Type that preserves its parent's Output. */
1652
1785
  export function createType<
1653
1786
  Name extends TypeName,
1654
1787
  ParentType extends ConcreteTypeNode,
@@ -1669,6 +1802,8 @@ export function createType<
1669
1802
  CanonicalInputForChild<ParentType, ParentType["Output"]>,
1670
1803
  IdentityEncodingOf<ParentType>
1671
1804
  >;
1805
+
1806
+ /** Creates a fallible child Type that narrows its parent's Output. */
1672
1807
  export function createType<
1673
1808
  Name extends TypeName,
1674
1809
  ParentType extends ConcreteTypeNode,
@@ -1887,12 +2022,19 @@ const createChildType = <
1887
2022
  * `from` accepts the semantic Output. `from.parent` converts the parent Output
1888
2023
  * to the output Type Input, then runs the complete output Type pipeline. `to`
1889
2024
  * canonically encodes every output Type value before converting it back through
1890
- * the parent Type. Transformation callbacks are Type construction code. Their
1891
- * successful results are asserted against the declared boundary so a broken
1892
- * callback fails as a developer error rather than becoming a validation error.
1893
- * Like all Type-construction callbacks, they are trusted to follow their
1894
- * declared TypeScript types. A `Result<_, never>` callback is therefore trusted
1895
- * never to return an `Err`.
2025
+ * the parent Type.
2026
+ *
2027
+ * The callbacks must form a lawful codec: decoding `to(output)` must reproduce
2028
+ * the same semantic Output for every valid output value. Encoding may
2029
+ * canonicalize multiple parent representations, but it must be total and must
2030
+ * not lose distinctions present in the Output domain.
2031
+ *
2032
+ * Transformation callbacks are Type construction code. Their successful
2033
+ * results are asserted against the declared boundary so a broken callback fails
2034
+ * as a developer error rather than becoming a validation error. Like all
2035
+ * Type-construction callbacks, they are trusted to follow their declared
2036
+ * TypeScript types. A `Result<_, never>` callback is therefore trusted never to
2037
+ * return an `Err`.
1896
2038
  *
1897
2039
  * Errors from the parent and the forward callback remain unchanged. A forward
1898
2040
  * callback error must use the transformation name as its type. Errors from the
@@ -1905,18 +2047,30 @@ const createChildType = <
1905
2047
  * ### Example
1906
2048
  *
1907
2049
  * ```ts
1908
- * import { Number, String, ok, transform } from "@evolu/common";
2050
+ * import {
2051
+ * Boolean,
2052
+ * literal,
2053
+ * ok,
2054
+ * transform,
2055
+ * union,
2056
+ * } from "@evolu/common";
1909
2057
  *
1910
- * const NumberFromString = transform("NumberFromString", String, Number, {
1911
- * from: (value) => ok(globalThis.Number(value)),
1912
- * to: globalThis.String,
1913
- * });
2058
+ * const BooleanString = union(literal("false"), literal("true"));
2059
+ * const BooleanFromString = transform(
2060
+ * "BooleanFromString",
2061
+ * BooleanString,
2062
+ * Boolean,
2063
+ * {
2064
+ * from: (value) => ok(value === "true"),
2065
+ * to: (value) => (value ? "true" : "false"),
2066
+ * },
2067
+ * );
1914
2068
  *
1915
- * expectOk(NumberFromString.from.parent("42"), 42);
1916
- * assert(NumberFromString.to(42) === "42");
2069
+ * expectOk(BooleanFromString.from.parent("true"), true);
2070
+ * expect(BooleanFromString.to(false)).toBe("false");
1917
2071
  * ```
1918
2072
  *
1919
- * @group Type construction
2073
+ * @group Construction
1920
2074
  */
1921
2075
  export function transform<
1922
2076
  Name extends TypeName,
@@ -1934,6 +2088,8 @@ export function transform<
1934
2088
  readonly to: (value: CanonicalInputOf<OutputType>) => ToOutput;
1935
2089
  },
1936
2090
  ): TransformType<ParentType, OutputType, Name, never, ToOutput>;
2091
+
2092
+ /** Creates a fallible transformed Type with its own error formatter. */
1937
2093
  export function transform<
1938
2094
  Name extends TypeName,
1939
2095
  ParentType extends ConcreteTypeNode,
@@ -2038,7 +2194,11 @@ export function transform(
2038
2194
  );
2039
2195
  }
2040
2196
 
2041
- /** @group Type construction */
2197
+ /**
2198
+ * The {@link Type} returned by {@link transform}.
2199
+ *
2200
+ * @group Construction
2201
+ */
2042
2202
  export interface TransformType<
2043
2203
  ParentType extends TypeNode,
2044
2204
  OutputType extends TypeNode,
@@ -2065,7 +2225,12 @@ export interface TransformType<
2065
2225
  readonly output: OutputType;
2066
2226
  }
2067
2227
 
2068
- /** @group Type construction */
2228
+ /**
2229
+ * An error produced by {@link transform} while decoding or validating its
2230
+ * output.
2231
+ *
2232
+ * @group Construction
2233
+ */
2069
2234
  export type TransformError<
2070
2235
  Name extends TypeName,
2071
2236
  OwnError extends TypeError<Name>,
@@ -2079,7 +2244,7 @@ export type TransformError<
2079
2244
  /**
2080
2245
  * Wraps an error produced by the output {@link Type} of {@link transform}.
2081
2246
  *
2082
- * @group Type construction
2247
+ * @group Construction
2083
2248
  */
2084
2249
  export interface TransformOutputError<
2085
2250
  Name extends TypeName,
@@ -2345,14 +2510,22 @@ const addRuntimeAssertions = (
2345
2510
  return asserted;
2346
2511
  };
2347
2512
 
2348
- /** @group Base Types */
2513
+ /**
2514
+ * An infallible {@link Type} accepting every value.
2515
+ *
2516
+ * @group Base
2517
+ */
2349
2518
  export const Unknown = /*#__PURE__*/ createRootType<"Unknown", unknown, never>(
2350
2519
  "Unknown",
2351
2520
  ok,
2352
2521
  identity,
2353
2522
  );
2354
2523
 
2355
- /** @group Base Types */
2524
+ /**
2525
+ * A {@link Type} rejecting every value.
2526
+ *
2527
+ * @group Base
2528
+ */
2356
2529
  export const Never = /*#__PURE__*/ createRootType(
2357
2530
  "Never",
2358
2531
  (value): Result<never, NeverError> => err({ type: "Never", value }),
@@ -2360,6 +2533,11 @@ export const Never = /*#__PURE__*/ createRootType(
2360
2533
  `A value ${safelyStringifyUnknownValue(error.value)} is not valid for type Never.`,
2361
2534
  );
2362
2535
 
2536
+ /**
2537
+ * Error returned by {@link Never} for every value.
2538
+ *
2539
+ * @group Base
2540
+ */
2363
2541
  export interface NeverError extends TypeError<"Never"> {
2364
2542
  readonly value: unknown;
2365
2543
  }
@@ -2398,7 +2576,11 @@ interface TypeOfOutputByName {
2398
2576
  readonly Function: globalThis.Function;
2399
2577
  }
2400
2578
 
2401
- /** @group Base Types */
2579
+ /**
2580
+ * Error returned when `typeof` does not match the expected JavaScript type.
2581
+ *
2582
+ * @group Base
2583
+ */
2402
2584
  export interface TypeOfError<
2403
2585
  Name extends keyof TypeOfOutputByName,
2404
2586
  > extends TypeError<"TypeOf"> {
@@ -2429,6 +2611,8 @@ export interface TypeOfError<
2429
2611
  * expectTypeOf<WireValue100>().toEqualTypeOf<
2430
2612
  * string & Brand<"MaxLength100">
2431
2613
  * >();
2614
+ * expectOk(WireValue100.fromUnknown(""), "");
2615
+ * expectOk(WireValue100.fromUnknown(" value "), " value ");
2432
2616
  * ```
2433
2617
  *
2434
2618
  * @group String
@@ -2505,25 +2689,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
2505
2689
  */
2506
2690
  export const Number = /*#__PURE__*/ createTypeOfType("Number");
2507
2691
 
2508
- /** @group Base Types */
2692
+ /**
2693
+ * A JavaScript bigint {@link Type}.
2694
+ *
2695
+ * @group Base
2696
+ */
2509
2697
  export const BigInt = /*#__PURE__*/ createTypeOfType("BigInt");
2510
2698
 
2511
- /** @group Base Types */
2699
+ /**
2700
+ * A JavaScript boolean {@link Type}.
2701
+ *
2702
+ * @group Base
2703
+ */
2512
2704
  export const Boolean = /*#__PURE__*/ createTypeOfType("Boolean");
2513
2705
 
2514
- /** @group Base Types */
2706
+ /**
2707
+ * A JavaScript symbol {@link Type}.
2708
+ *
2709
+ * @group Base
2710
+ */
2515
2711
  export const Symbol = /*#__PURE__*/ createTypeOfType("Symbol");
2516
2712
 
2517
- /** @group Base Types */
2713
+ /**
2714
+ * A JavaScript function {@link Type}.
2715
+ *
2716
+ * @group Base
2717
+ */
2518
2718
  export const Function = /*#__PURE__*/ createTypeOfType("Function");
2519
2719
 
2520
2720
  /**
2521
- * An Evolu {@link Type} validating other Evolu Types.
2721
+ * A {@link Type} validating Evolu Type declarations.
2522
2722
  *
2523
2723
  * This is useful when a Type itself crosses an unknown boundary or must be
2524
2724
  * asserted with {@link assertType}.
2525
2725
  *
2526
- * @group Core Types
2726
+ * @group Core
2527
2727
  */
2528
2728
  export const EvoluType = /*#__PURE__*/ createType(
2529
2729
  "EvoluType",
@@ -2545,23 +2745,23 @@ export const EvoluType = /*#__PURE__*/ createType(
2545
2745
  * the recursive `TypeNode` shape so composing Types does not repeatedly add its
2546
2746
  * compiler cost; {@link EvoluType} bridges that runtime evidence to this type.
2547
2747
  *
2548
- * @group Core Types
2748
+ * @group Core
2549
2749
  */
2550
2750
  export interface AnyType extends TypeNode {
2551
2751
  readonly [concreteTypeSymbol]: true;
2552
2752
  }
2553
2753
 
2554
2754
  /**
2555
- * Error returned when a value is not an Evolu {@link Type}.
2755
+ * Error returned when {@link EvoluType} rejects a value.
2556
2756
  *
2557
- * @group Core Types
2757
+ * @group Core
2558
2758
  */
2559
2759
  export interface EvoluTypeError extends TypeValueError<"EvoluType"> {}
2560
2760
 
2561
2761
  /**
2562
2762
  * Nominal evidence that a value has one object tag.
2563
2763
  *
2564
- * @group Base Types
2764
+ * @group Base
2565
2765
  */
2566
2766
  export interface ObjectTag<Name extends TypeName> {
2567
2767
  readonly [objectTagSymbol]: Name;
@@ -2570,7 +2770,7 @@ export interface ObjectTag<Name extends TypeName> {
2570
2770
  /**
2571
2771
  * The {@link Type} returned by {@link objectTag}.
2572
2772
  *
2573
- * @group Base Types
2773
+ * @group Base
2574
2774
  */
2575
2775
  export interface ObjectTagType<
2576
2776
  Name extends TypeName,
@@ -2597,7 +2797,7 @@ export interface ObjectTagType<
2597
2797
  /**
2598
2798
  * An error returned when an object does not report the expected tag.
2599
2799
  *
2600
- * @group Base Types
2800
+ * @group Base
2601
2801
  */
2602
2802
  export interface ObjectTagError<
2603
2803
  Expected extends TypeName = TypeName,
@@ -2628,7 +2828,18 @@ interface ObjectTagOutputByName {
2628
2828
  * assumption of the predefined Type. Primitive Outputs are rejected at compile
2629
2829
  * time.
2630
2830
  *
2631
- * @group Base Types
2831
+ * ### Example
2832
+ *
2833
+ * ```ts
2834
+ * import { objectTag } from "@evolu/common";
2835
+ *
2836
+ * const DateType = objectTag("Date");
2837
+ * const date = new globalThis.Date("2025-01-01T00:00:00.000Z");
2838
+ *
2839
+ * expectOk(DateType.fromUnknown(date), date);
2840
+ * ```
2841
+ *
2842
+ * @group Base
2632
2843
  */
2633
2844
  export function objectTag<Name extends keyof ObjectTagOutputByName>(
2634
2845
  name: ValidateConcreteTypeName<Name>,
@@ -2642,6 +2853,8 @@ export function objectTag<Name extends keyof ObjectTagOutputByName>(
2642
2853
  never,
2643
2854
  ObjectTagOutputByName[Name]
2644
2855
  >;
2856
+
2857
+ /** Creates an object-tag Type by refining an existing object Type. */
2645
2858
  export function objectTag<
2646
2859
  Name extends TypeName,
2647
2860
  OutputType extends ConcreteTypeNode & { readonly Output: object },
@@ -2656,6 +2869,9 @@ export function objectTag(
2656
2869
  name: TypeName,
2657
2870
  outputType?: ConcreteTypeNode & { readonly Output: object },
2658
2871
  ): TypeNode {
2872
+ const formatError: TypeErrorFormatter<ObjectTagError> = (error) =>
2873
+ `A value ${safelyStringifyUnknownValue(error.value)} does not have the expected object tag ${safelyStringifyUnknownValue(error.expected)}.`;
2874
+
2659
2875
  if (outputType === undefined) {
2660
2876
  return createRootType(
2661
2877
  name,
@@ -2663,7 +2879,7 @@ export function objectTag(
2663
2879
  hasObjectTag(value, name)
2664
2880
  ? ok(value as object)
2665
2881
  : err({ type: "ObjectTag", expected: name, value }),
2666
- formatObjectTagError,
2882
+ formatError,
2667
2883
  );
2668
2884
  }
2669
2885
 
@@ -2675,7 +2891,7 @@ export function objectTag(
2675
2891
  hasObjectTag(value, name)
2676
2892
  ? ok(value as object & ObjectTag<TypeName>)
2677
2893
  : err({ type: "ObjectTag", expected: name, value }),
2678
- formatObjectTagError,
2894
+ formatError,
2679
2895
  ),
2680
2896
  { expected: name },
2681
2897
  );
@@ -2683,9 +2899,6 @@ export function objectTag(
2683
2899
 
2684
2900
  declare const objectTagSymbol: unique symbol;
2685
2901
 
2686
- const formatObjectTagError: TypeErrorFormatter<ObjectTagError> = (error) =>
2687
- `A value ${safelyStringifyUnknownValue(error.value)} does not have the object tag ${safelyStringifyUnknownValue(error.expected)}.`;
2688
-
2689
2902
  const hasObjectTag = (value: unknown, expected: string): boolean =>
2690
2903
  value !== null &&
2691
2904
  (typeof value === "object" || typeof value === "function") &&
@@ -2696,7 +2909,7 @@ const hasObjectTag = (value: unknown, expected: string): boolean =>
2696
2909
  *
2697
2910
  * It trusts the reported object tag and does not verify Date internal slots.
2698
2911
  *
2699
- * @group Base Types
2912
+ * @group Base
2700
2913
  */
2701
2914
  export const Date = /*#__PURE__*/ objectTag("Date");
2702
2915
 
@@ -2706,7 +2919,7 @@ export const Date = /*#__PURE__*/ objectTag("Date");
2706
2919
  * It trusts the reported object tag and does not verify Uint8Array internal
2707
2920
  * slots.
2708
2921
  *
2709
- * @group Base Types
2922
+ * @group Base
2710
2923
  */
2711
2924
  export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
2712
2925
 
@@ -2716,7 +2929,7 @@ export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
2716
2929
  * It trusts the reported object tag and does not verify ArrayBuffer internal
2717
2930
  * slots.
2718
2931
  *
2719
- * @group Base Types
2932
+ * @group Base
2720
2933
  */
2721
2934
  export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
2722
2935
 
@@ -2748,7 +2961,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
2748
2961
  * assert(!UserInstance.is({ name: "Ada" }));
2749
2962
  * ```
2750
2963
  *
2751
- * @group Base Types
2964
+ * @group Base
2752
2965
  */
2753
2966
  export const instanceOf = <Constructor extends InstanceConstructor>(
2754
2967
  constructor: ValidateInstanceConstructor<Constructor>,
@@ -2779,14 +2992,18 @@ export const instanceOf = <Constructor extends InstanceConstructor>(
2779
2992
  /**
2780
2993
  * A JavaScript class constructor accepted by {@link instanceOf}.
2781
2994
  *
2782
- * @group Base Types
2995
+ * @group Base
2783
2996
  */
2784
2997
  export type InstanceConstructor<Instance extends object = object> =
2785
2998
  (abstract new (...args: ReadonlyArray<never>) => Instance) & {
2786
2999
  readonly name: string;
2787
3000
  };
2788
3001
 
2789
- /** @group Base Types */
3002
+ /**
3003
+ * The {@link Type} returned by {@link instanceOf}.
3004
+ *
3005
+ * @group Base
3006
+ */
2790
3007
  export interface InstanceOfType<
2791
3008
  Constructor extends InstanceConstructor,
2792
3009
  > extends Type<
@@ -2807,7 +3024,11 @@ type InstanceOfOutput<Constructor extends InstanceConstructor> =
2807
3024
  ? Output
2808
3025
  : InstanceType<Constructor>;
2809
3026
 
2810
- /** @group Base Types */
3027
+ /**
3028
+ * Error returned when a value is not an instance of the expected constructor.
3029
+ *
3030
+ * @group Base
3031
+ */
2811
3032
  export interface InstanceOfError extends TypeValueError<"InstanceOf"> {
2812
3033
  readonly constructorName: string;
2813
3034
  }
@@ -2895,7 +3116,11 @@ export const literal = <const Expected extends Literal>(
2895
3116
  ) as unknown as LiteralType<Expected>;
2896
3117
  };
2897
3118
 
2898
- /** @group Unions */
3119
+ /**
3120
+ * The {@link Type} returned by {@link literal}.
3121
+ *
3122
+ * @group Unions
3123
+ */
2899
3124
  export interface LiteralType<Expected extends Literal> extends Type<
2900
3125
  "Literal",
2901
3126
  WidenLiteral<Expected>,
@@ -2941,7 +3166,11 @@ type LiteralCompileTimeError = CompileTimeError<
2941
3166
  "Expected must be one concrete literal value."
2942
3167
  >;
2943
3168
 
2944
- /** @group Unions */
3169
+ /**
3170
+ * Error returned when a value does not equal the expected literal.
3171
+ *
3172
+ * @group Unions
3173
+ */
2945
3174
  export interface LiteralError<
2946
3175
  Expected extends Literal = Literal,
2947
3176
  > extends TypeError<"Literal"> {
@@ -2949,10 +3178,18 @@ export interface LiteralError<
2949
3178
  readonly value: unknown;
2950
3179
  }
2951
3180
 
2952
- /** @group Unions */
3181
+ /**
3182
+ * Literal {@link Type} accepting only `undefined`.
3183
+ *
3184
+ * @group Unions
3185
+ */
2953
3186
  export const Undefined = /*#__PURE__*/ literal(undefined);
2954
3187
 
2955
- /** @group Unions */
3188
+ /**
3189
+ * Literal {@link Type} accepting only `null`.
3190
+ *
3191
+ * @group Unions
3192
+ */
2956
3193
  export const Null = /*#__PURE__*/ literal(null);
2957
3194
 
2958
3195
  /**
@@ -2978,7 +3215,10 @@ export const Null = /*#__PURE__*/ literal(null);
2978
3215
  * within each member.
2979
3216
  *
2980
3217
  * Member order matters when multiple members accept the same value: validation
2981
- * and encoding use the first matching member.
3218
+ * and encoding use the first matching member. When member Inputs overlap,
3219
+ * decoding the value emitted by the first member selected for an Output must
3220
+ * reproduce that semantic Output; otherwise the Union violates the round-trip
3221
+ * law.
2982
3222
  *
2983
3223
  * ### Example
2984
3224
  *
@@ -3017,11 +3257,15 @@ export function union<const Expected extends AtLeastTwoReadonlyArray<Literal>>(
3017
3257
  ): UnionType<{
3018
3258
  readonly [Index in keyof Expected]: LiteralType<Expected[Index]>;
3019
3259
  }>;
3260
+
3261
+ /** Creates a Union Type from Type members. */
3020
3262
  export function union<const Members extends AtLeastTwoReadonlyArray<TypeNode>>(
3021
3263
  ...members: {
3022
3264
  readonly [Index in keyof Members]: ValidateUnionTypeMember<Members[Index]>;
3023
3265
  }
3024
3266
  ): UnionType<Members>;
3267
+
3268
+ /** Creates a Union Type from Type and literal members. */
3025
3269
  export function union<
3026
3270
  const Members extends AtLeastTwoReadonlyArray<TypeNode | Literal>,
3027
3271
  >(
@@ -3046,7 +3290,8 @@ export function union(
3046
3290
  inputMembers,
3047
3291
  (member, value, options) => member[outputValidationSymbol](value, options),
3048
3292
  );
3049
- const defaultFormatter = formatUnionError as TypeErrorFormatter<TypeError>;
3293
+ const defaultFormatter = (() =>
3294
+ "A value does not match any allowed variant.") as TypeErrorFormatter<TypeError>;
3050
3295
  const getTypeIssues: RuntimeGetTypeIssues = (error) =>
3051
3296
  singleRuntimeTypeIssue("Union", error, defaultFormatter);
3052
3297
  const input = createTypeNode<
@@ -3101,9 +3346,6 @@ export function union(
3101
3346
  );
3102
3347
  }
3103
3348
 
3104
- const formatUnionError: TypeErrorFormatter<UnionErrorValue<TypeError>> = () =>
3105
- "A value does not match any union member.";
3106
-
3107
3349
  const createUnionValidation =
3108
3350
  (
3109
3351
  members: ReadonlyArray<RuntimeTypeNode>,
@@ -3146,6 +3388,16 @@ const createUnionValidation =
3146
3388
  * This does not make an object property optional. It changes only the values
3147
3389
  * accepted when the property is present.
3148
3390
  *
3391
+ * ### Example
3392
+ *
3393
+ * ```ts
3394
+ * import { String, undefinedOr } from "@evolu/common";
3395
+ *
3396
+ * const StringOrUndefined = undefinedOr(String);
3397
+ *
3398
+ * expectOk(StringOrUndefined.fromUnknown(undefined), undefined);
3399
+ * ```
3400
+ *
3149
3401
  * @group Unions
3150
3402
  */
3151
3403
  export const undefinedOr = <ValueType extends TypeNode>(
@@ -3155,6 +3407,16 @@ export const undefinedOr = <ValueType extends TypeNode>(
3155
3407
  /**
3156
3408
  * Union {@link Type} containing the supplied Type and `null`.
3157
3409
  *
3410
+ * ### Example
3411
+ *
3412
+ * ```ts
3413
+ * import { String, nullOr } from "@evolu/common";
3414
+ *
3415
+ * const NullableString = nullOr(String);
3416
+ *
3417
+ * expectOk(NullableString.fromUnknown(null), null);
3418
+ * ```
3419
+ *
3158
3420
  * @group Unions
3159
3421
  */
3160
3422
  export const nullOr = <ValueType extends TypeNode>(
@@ -3164,9 +3426,20 @@ export const nullOr = <ValueType extends TypeNode>(
3164
3426
  /**
3165
3427
  * Union {@link Type} containing the supplied Type, `null`, and `undefined`.
3166
3428
  *
3167
- * @group Unions
3168
- */
3169
- export const nullishOr = <ValueType extends TypeNode>(
3429
+ * ### Example
3430
+ *
3431
+ * ```ts
3432
+ * import { String, nullishOr } from "@evolu/common";
3433
+ *
3434
+ * const NullishString = nullishOr(String);
3435
+ *
3436
+ * expectOk(NullishString.fromUnknown(undefined), undefined);
3437
+ * expectOk(NullishString.fromUnknown(null), null);
3438
+ * ```
3439
+ *
3440
+ * @group Unions
3441
+ */
3442
+ export const nullishOr = <ValueType extends TypeNode>(
3170
3443
  type: ValidateUnionTypeMember<ValueType>,
3171
3444
  ): UnionType<readonly [ValueType, typeof Null, typeof Undefined]> =>
3172
3445
  union(type, Null, Undefined);
@@ -3205,7 +3478,11 @@ type NormalizeUnionMembers<
3205
3478
  ? Normalized
3206
3479
  : never;
3207
3480
 
3208
- /** @group Unions */
3481
+ /**
3482
+ * The {@link Type} returned by {@link union}.
3483
+ *
3484
+ * @group Unions
3485
+ */
3209
3486
  export interface UnionType<
3210
3487
  Members extends AtLeastTwoReadonlyArray<TypeNode>,
3211
3488
  > extends Type<
@@ -3283,13 +3560,21 @@ type RootUnionMembers<Members extends AtLeastTwoReadonlyArray<TypeNode>> = {
3283
3560
  ? RootMembers
3284
3561
  : never;
3285
3562
 
3286
- /** @group Unions */
3563
+ /**
3564
+ * Error returned when every member of a {@link union} rejects an input.
3565
+ *
3566
+ * @group Unions
3567
+ */
3287
3568
  export type UnionError<
3288
3569
  Error extends TypeError = TypeError,
3289
3570
  MemberError extends UnionMemberError<Error> = UnionMemberError<Error>,
3290
3571
  > = [Error] extends [never] ? never : UnionErrorValue<Error, MemberError>;
3291
3572
 
3292
- /** @group Unions */
3573
+ /**
3574
+ * An error returned by one {@link union} member and its index.
3575
+ *
3576
+ * @group Unions
3577
+ */
3293
3578
  export interface UnionMemberError<
3294
3579
  Error extends TypeError,
3295
3580
  Index extends number = number,
@@ -3353,9 +3638,7 @@ interface UnionErrorValue<
3353
3638
  }
3354
3639
 
3355
3640
  /**
3356
- * Template literal {@link Type} for validation and parsing.
3357
- *
3358
- * Parses and creates structured strings.
3641
+ * Template literal {@link Type} that parses canonical strings into Tuples.
3359
3642
  *
3360
3643
  * Accepts the same template parts as {@link templateLiteral}: fixed string
3361
3644
  * literals and Types canonically encoded as strings. Instead of keeping Output
@@ -3371,10 +3654,10 @@ interface UnionErrorValue<
3371
3654
  * reversibility and keeps capture boundaries unambiguous. Different capture
3372
3655
  * Tuples must never encode to the same string. The parser provides predictable
3373
3656
  * parsing without pathological backtracking and decodes each capture once, so
3374
- * adversarial input cannot trigger exponential parser work. Fixed-width captures
3375
- * may be adjacent, but only one variable-width capture is allowed. Declarations
3376
- * that could join UTF-16 surrogate halves across parts are rejected during
3377
- * construction.
3657
+ * adversarial input cannot trigger exponential parser work. Fixed-width
3658
+ * captures may be adjacent, but only one variable-width capture is allowed.
3659
+ * Declarations that could join UTF-16 surrogate halves across parts are
3660
+ * rejected during construction.
3378
3661
  *
3379
3662
  * Keep capture unions reasonably small to avoid excessive compiler work.
3380
3663
  *
@@ -3411,10 +3694,9 @@ interface UnionErrorValue<
3411
3694
  *
3412
3695
  * // Parse an unknown string into structured data.
3413
3696
  * const result = SupportedLocale.fromUnknown("cs-CZ");
3414
- * assert(result.ok);
3697
+ * expectOk(result, ["cs", "CZ"]);
3415
3698
  * const locale = result.value;
3416
3699
  * expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
3417
- * expect(locale).toEqual(["cs", "CZ"]);
3418
3700
  * expectErr(SupportedLocale.fromUnknown("cs/CZ"), {
3419
3701
  * type: "TemplateLiteral",
3420
3702
  * value: "cs/CZ",
@@ -3473,10 +3755,9 @@ interface UnionErrorValue<
3473
3755
  *
3474
3756
  * // Decode the string into structured data.
3475
3757
  * const result = ItemId.fromUnknown("item-42");
3476
- * assert(result.ok);
3758
+ * expectOk(result, [42n]);
3477
3759
  * const itemId = result.value;
3478
3760
  * expectTypeOf(itemId).toEqualTypeOf<ItemId>();
3479
- * expect(itemId).toEqual([42n]);
3480
3761
  *
3481
3762
  * // Encode the structured data into its canonical string.
3482
3763
  * const itemIdLiteral = ItemId.to(itemId);
@@ -3569,12 +3850,14 @@ const createTemplateLiteralParserType = <
3569
3850
  options,
3570
3851
  false,
3571
3852
  );
3572
- return (outputResult.ok
3573
- ? outputResult
3574
- : err({
3575
- type: "TemplateLiteral",
3576
- outputError: outputResult.error,
3577
- })) as Result<
3853
+ return (
3854
+ outputResult.ok
3855
+ ? outputResult
3856
+ : err({
3857
+ type: "TemplateLiteral",
3858
+ outputError: outputResult.error,
3859
+ })
3860
+ ) as Result<
3578
3861
  TemplateLiteralCaptureTuple<Parts>["Output"],
3579
3862
  TemplateLiteralRuntimeParseError<Parts>
3580
3863
  >;
@@ -3635,7 +3918,8 @@ const createTemplateLiteralParserType = <
3635
3918
  return singleRuntimeTypeIssue(
3636
3919
  "TemplateLiteral",
3637
3920
  error,
3638
- formatTemplateLiteralError as TypeErrorFormatter<TypeError>,
3921
+ ((error: TemplateLiteralError) =>
3922
+ `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`) as TypeErrorFormatter<TypeError>,
3639
3923
  );
3640
3924
  };
3641
3925
  const canonicalStringFromUnknown = (
@@ -3726,7 +4010,11 @@ const createTemplateLiteralParserType = <
3726
4010
  return type;
3727
4011
  };
3728
4012
 
3729
- /** @group Template literals */
4013
+ /**
4014
+ * The parsing {@link Type} returned by {@link templateLiteralParser}.
4015
+ *
4016
+ * @group Template literals
4017
+ */
3730
4018
  export interface TemplateLiteralParserType<
3731
4019
  Parts extends TemplateLiteralParts,
3732
4020
  > extends Type<
@@ -3746,7 +4034,11 @@ export interface TemplateLiteralParserType<
3746
4034
  readonly parts: Parts;
3747
4035
  }
3748
4036
 
3749
- /** @group Template literals */
4037
+ /**
4038
+ * The validating string {@link Type} returned by {@link templateLiteral}.
4039
+ *
4040
+ * @group Template literals
4041
+ */
3750
4042
  export interface TemplateLiteralType<
3751
4043
  Parts extends TemplateLiteralParts,
3752
4044
  > extends Type<
@@ -3772,8 +4064,8 @@ export interface TemplateLiteralType<
3772
4064
  * Creates a canonical string Type from fixed strings and string-encoded Types.
3773
4065
  *
3774
4066
  * Use this factory when Output should remain a string. Switch to
3775
- * {@link templateLiteralParser} when the individual Type parts should be
3776
- * decoded into a Tuple.
4067
+ * {@link templateLiteralParser} when the individual Type parts should be decoded
4068
+ * into a Tuple.
3777
4069
  *
3778
4070
  * ### Example
3779
4071
  *
@@ -3830,15 +4122,15 @@ type TemplateLiteralCaptureFromStringError<T extends TypeNode> =
3830
4122
  : InferErrors<T>
3831
4123
  : never;
3832
4124
 
3833
- /** @group Template literals */
4125
+ /**
4126
+ * Error returned when a string does not match a template literal declaration.
4127
+ *
4128
+ * @group Template literals
4129
+ */
3834
4130
  export interface TemplateLiteralError extends TypeError<"TemplateLiteral"> {
3835
4131
  readonly value: string;
3836
4132
  }
3837
4133
 
3838
- const formatTemplateLiteralError: TypeErrorFormatter<TemplateLiteralError> =
3839
- (error) =>
3840
- `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`;
3841
-
3842
4134
  declare const templateLiteralStringBrandSymbol: unique symbol;
3843
4135
 
3844
4136
  interface TemplateLiteralStringBrand<Parts extends TemplateLiteralParts> {
@@ -4084,9 +4376,7 @@ const compileTemplateLiteralParser = <Parts extends TemplateLiteralParts>(
4084
4376
  }
4085
4377
 
4086
4378
  return position === inputCodePoints.length
4087
- ? ok(
4088
- captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"],
4089
- )
4379
+ ? ok(captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"])
4090
4380
  : err({ type: "TemplateLiteral", value: input });
4091
4381
  };
4092
4382
  };
@@ -4254,68 +4544,10 @@ const getTemplateLiteralPartFraming = (
4254
4544
  * });
4255
4545
  * ```
4256
4546
  *
4257
- * To reuse a Brand constraint with different parent Types, define a
4258
- * {@link BrandFactory}. Brand Factories can then be composed:
4259
- *
4260
- * ```ts
4261
- * import {
4262
- * String,
4263
- * brand,
4264
- * err,
4265
- * minLength,
4266
- * ok,
4267
- * type Brand,
4268
- * type BrandFactory,
4269
- * type TypeError,
4270
- * } from "@evolu/common";
4271
- *
4272
- * const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
4273
- * parent,
4274
- * ) =>
4275
- * brand(
4276
- * "Trimmed",
4277
- * parent,
4278
- * (value) =>
4279
- * value === value.trim()
4280
- * ? ok()
4281
- * : err<TrimmedError>({ type: "Trimmed", value }),
4282
- * () => "Expected a string without surrounding whitespace.",
4283
- * );
4284
- *
4285
- * const TrimmedString = trimmed(String);
4286
- * type TrimmedString = typeof TrimmedString.Output;
4287
- *
4288
- * expectTypeOf<TrimmedString>().toEqualTypeOf<string & Brand<"Trimmed">>();
4289
- *
4290
- * const NonEmptyString = minLength(1)(String);
4291
- * type NonEmptyString = typeof NonEmptyString.Output;
4292
- *
4293
- * expectTypeOf<NonEmptyString>().toEqualTypeOf<
4294
- * string & Brand<"MinLength1">
4295
- * >();
4296
- *
4297
- * const NonEmptyTrimmedString = minLength(1)(TrimmedString);
4298
- * type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
4299
- *
4300
- * expectTypeOf<NonEmptyTrimmedString>().toEqualTypeOf<
4301
- * string & Brand<"Trimmed"> & Brand<"MinLength1">
4302
- * >();
4303
- *
4304
- * interface TrimmedError extends TypeError<"Trimmed"> {
4305
- * readonly value: string;
4306
- * }
4307
- *
4308
- * // Validation from unknown.
4309
- * expectOk(NonEmptyTrimmedString.fromUnknown("Evolu"), "Evolu");
4547
+ * To reuse and compose a Brand constraint with different parent Types, define a
4548
+ * {@link BrandFactory}.
4310
4549
  *
4311
- * // The typed input selects TrimmedString as the validated boundary.
4312
- * expectOk(
4313
- * NonEmptyTrimmedString.from.parent(TrimmedString.orThrow("Evolu")),
4314
- * "Evolu",
4315
- * );
4316
- * ```
4317
- *
4318
- * @group Type construction
4550
+ * @group Construction
4319
4551
  */
4320
4552
  export function brand<
4321
4553
  Name extends TypeName,
@@ -4325,6 +4557,8 @@ export function brand<
4325
4557
  parent: ValidateParent<ParentType>,
4326
4558
  validate?: (value: ParentType["Output"]) => Result<void, never>,
4327
4559
  ): BrandType<ParentType, Name, never>;
4560
+
4561
+ /** Creates a validated Brand Type with its own error formatter. */
4328
4562
  export function brand<
4329
4563
  Name extends TypeName,
4330
4564
  ParentType extends ConcreteTypeNode,
@@ -4352,7 +4586,11 @@ export function brand(
4352
4586
  );
4353
4587
  }
4354
4588
 
4355
- /** @group Type construction */
4589
+ /**
4590
+ * The {@link Type} returned by {@link brand}.
4591
+ *
4592
+ * @group Construction
4593
+ */
4356
4594
  export interface BrandType<
4357
4595
  ParentType extends TypeNode,
4358
4596
  Name extends TypeName,
@@ -4409,7 +4647,11 @@ export const DateIso = /*#__PURE__*/ brand(
4409
4647
  );
4410
4648
  export type DateIso = typeof DateIso.Output;
4411
4649
 
4412
- /** @group String */
4650
+ /**
4651
+ * Error returned when a string is not a canonical {@link DateIso}.
4652
+ *
4653
+ * @group String
4654
+ */
4413
4655
  export interface DateIsoError extends TypeError<"DateIso"> {
4414
4656
  readonly value: string;
4415
4657
  }
@@ -4417,6 +4659,18 @@ export interface DateIsoError extends TypeError<"DateIso"> {
4417
4659
  /**
4418
4660
  * Safely transforms a {@link Date} into a canonical {@link DateIso}.
4419
4661
  *
4662
+ * ### Example
4663
+ *
4664
+ * ```ts
4665
+ * import { DateIsoFromDate } from "@evolu/common";
4666
+ *
4667
+ * const date = new globalThis.Date("2025-01-01T12:00:00.000Z");
4668
+ * const result = DateIsoFromDate.fromUnknown(date);
4669
+ *
4670
+ * expectOk(result, "2025-01-01T12:00:00.000Z");
4671
+ * expect(DateIsoFromDate.to(result.value)).toEqual(date);
4672
+ * ```
4673
+ *
4420
4674
  * @group String
4421
4675
  */
4422
4676
  export const DateIsoFromDate = /*#__PURE__*/ transform(
@@ -4434,7 +4688,11 @@ export const DateIsoFromDate = /*#__PURE__*/ transform(
4434
4688
  () => "The Date cannot be represented as DateIso.",
4435
4689
  );
4436
4690
 
4437
- /** @group String */
4691
+ /**
4692
+ * Error returned when a {@link Date} cannot be represented as {@link DateIso}.
4693
+ *
4694
+ * @group String
4695
+ */
4438
4696
  export interface DateIsoFromDateError extends TypeError<"DateIsoFromDate"> {
4439
4697
  readonly value: globalThis.Date;
4440
4698
  }
@@ -4456,7 +4714,11 @@ export const Int64 = /*#__PURE__*/ brand(
4456
4714
  );
4457
4715
  export type Int64 = typeof Int64.Output;
4458
4716
 
4459
- /** @group Number */
4717
+ /**
4718
+ * Error returned when a bigint is outside the signed 64-bit {@link Int64} range.
4719
+ *
4720
+ * @group Number
4721
+ */
4460
4722
  export interface Int64Error extends TypeError<"Int64"> {
4461
4723
  readonly value: bigint;
4462
4724
  }
@@ -4478,7 +4740,12 @@ export const UInt64 = /*#__PURE__*/ brand(
4478
4740
  );
4479
4741
  export type UInt64 = typeof UInt64.Output;
4480
4742
 
4481
- /** @group Number */
4743
+ /**
4744
+ * Error returned when a bigint is outside the unsigned 64-bit {@link UInt64}
4745
+ * range.
4746
+ *
4747
+ * @group Number
4748
+ */
4482
4749
  export interface UInt64Error extends TypeError<"UInt64"> {
4483
4750
  readonly value: bigint;
4484
4751
  }
@@ -4535,7 +4802,7 @@ export interface UInt64Error extends TypeError<"UInt64"> {
4535
4802
  * For numeric parameters encoded in a Brand name, use
4536
4803
  * {@link ValidateBrandFactoryNumber}.
4537
4804
  *
4538
- * @group Type construction
4805
+ * @group Construction
4539
4806
  */
4540
4807
  export type BrandFactory<
4541
4808
  Name extends TypeName,
@@ -4605,7 +4872,7 @@ export type BrandFactory<
4605
4872
  * lessThan(100 - 1)(Number);
4606
4873
  * ```
4607
4874
  *
4608
- * @group Type utilities
4875
+ * @group Construction
4609
4876
  */
4610
4877
  export type ValidateBrandFactoryNumber<Value extends number> =
4611
4878
  IsUnion<Value> extends false
@@ -4664,7 +4931,11 @@ export const capitalized: BrandFactory<
4664
4931
  `The value ${safelyStringifyUnknownValue(error.value)} must be capitalized.`,
4665
4932
  );
4666
4933
 
4667
- /** @group String */
4934
+ /**
4935
+ * Error returned when {@link capitalized} rejects a string.
4936
+ *
4937
+ * @group String
4938
+ */
4668
4939
  export interface CapitalizedError extends TypeError<"Capitalized"> {
4669
4940
  readonly value: string;
4670
4941
  }
@@ -4680,6 +4951,16 @@ export type CapitalizedString = typeof CapitalizedString.Output;
4680
4951
  /**
4681
4952
  * String {@link Brand} without surrounding whitespace.
4682
4953
  *
4954
+ * ### Example
4955
+ *
4956
+ * ```ts
4957
+ * import { String, trimmed } from "@evolu/common";
4958
+ *
4959
+ * const Trimmed = trimmed(String);
4960
+ *
4961
+ * expectOk(Trimmed.fromUnknown("Evolu"), "Evolu");
4962
+ * ```
4963
+ *
4683
4964
  * @group String
4684
4965
  */
4685
4966
  export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
@@ -4696,7 +4977,11 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
4696
4977
  `The value ${safelyStringifyUnknownValue(error.value)} must be trimmed.`,
4697
4978
  );
4698
4979
 
4699
- /** @group String */
4980
+ /**
4981
+ * Error returned when {@link trimmed} rejects a string.
4982
+ *
4983
+ * @group String
4984
+ */
4700
4985
  export interface TrimmedError extends TypeError<"Trimmed"> {
4701
4986
  readonly value: string;
4702
4987
  }
@@ -4718,13 +5003,33 @@ export type TrimmedString = typeof TrimmedString.Output;
4718
5003
  /**
4719
5004
  * Trims a string and returns a {@link TrimmedString}.
4720
5005
  *
5006
+ * ### Example
5007
+ *
5008
+ * ```ts
5009
+ * import { trim } from "@evolu/common";
5010
+ *
5011
+ * expect(trim(" Evolu ")).toBe("Evolu");
5012
+ * ```
5013
+ *
4721
5014
  * @group String
4722
5015
  */
4723
5016
  export const trim = (value: string): TrimmedString =>
4724
5017
  value.trim() as TrimmedString;
4725
5018
 
4726
5019
  /**
4727
- * Minimum-length {@link Brand} requiring a value to have at least `min` items.
5020
+ * Minimum-length {@link Brand} for values whose `length` is at least `min`.
5021
+ *
5022
+ * ### Example
5023
+ *
5024
+ * ```ts
5025
+ * import { String, array, minLength } from "@evolu/common";
5026
+ *
5027
+ * const AtLeastThreeCharacters = minLength(3)(String);
5028
+ * const AtLeastTwoItems = minLength(2)(array(String));
5029
+ *
5030
+ * expectOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
5031
+ * expectOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
5032
+ * ```
4728
5033
  *
4729
5034
  * @group String
4730
5035
  * @group Collection
@@ -4748,7 +5053,11 @@ export const minLength =
4748
5053
  );
4749
5054
  };
4750
5055
 
4751
- /** @group Collection */
5056
+ /**
5057
+ * Error returned when {@link minLength} rejects a value.
5058
+ *
5059
+ * @group Collection
5060
+ */
4752
5061
  export interface MinLengthError<
4753
5062
  Min extends number = number,
4754
5063
  > extends TypeError<`MinLength${Min}`> {
@@ -4774,7 +5083,19 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
4774
5083
  export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
4775
5084
 
4776
5085
  /**
4777
- * Maximum-length {@link Brand} requiring a value to have at most `max` items.
5086
+ * Maximum-length {@link Brand} for values whose `length` is at most `max`.
5087
+ *
5088
+ * ### Example
5089
+ *
5090
+ * ```ts
5091
+ * import { String, array, maxLength } from "@evolu/common";
5092
+ *
5093
+ * const AtMostThreeCharacters = maxLength(3)(String);
5094
+ * const AtMostTwoItems = maxLength(2)(array(String));
5095
+ *
5096
+ * expectOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
5097
+ * expectOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
5098
+ * ```
4778
5099
  *
4779
5100
  * @group String
4780
5101
  * @group Collection
@@ -4798,7 +5119,11 @@ export const maxLength =
4798
5119
  );
4799
5120
  };
4800
5121
 
4801
- /** @group Collection */
5122
+ /**
5123
+ * Error returned when {@link maxLength} rejects a value.
5124
+ *
5125
+ * @group Collection
5126
+ */
4802
5127
  export interface MaxLengthError<
4803
5128
  Max extends number = number,
4804
5129
  > extends TypeError<`MaxLength${Max}`> {
@@ -4827,7 +5152,19 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(
4827
5152
  export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Output;
4828
5153
 
4829
5154
  /**
4830
- * Exact-length {@link Brand} requiring a value to have exactly `exact` items.
5155
+ * Exact-length {@link Brand} for values whose `length` equals `exact`.
5156
+ *
5157
+ * ### Example
5158
+ *
5159
+ * ```ts
5160
+ * import { String, array, length } from "@evolu/common";
5161
+ *
5162
+ * const ThreeCharacters = length(3)(String);
5163
+ * const TwoItems = length(2)(array(String));
5164
+ *
5165
+ * expectOk(ThreeCharacters.fromUnknown("abc"), "abc");
5166
+ * expectOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
5167
+ * ```
4831
5168
  *
4832
5169
  * @group String
4833
5170
  * @group Collection
@@ -4851,7 +5188,11 @@ export const length =
4851
5188
  );
4852
5189
  };
4853
5190
 
4854
- /** @group Collection */
5191
+ /**
5192
+ * Error returned when {@link length} rejects a value.
5193
+ *
5194
+ * @group Collection
5195
+ */
4855
5196
  export interface LengthError<
4856
5197
  Exact extends number = number,
4857
5198
  > extends TypeError<`Length${Exact}`> {
@@ -4917,7 +5258,12 @@ export const regex = <const Name extends TypeName>(
4917
5258
  );
4918
5259
  };
4919
5260
 
4920
- /** @group String */
5261
+ /**
5262
+ * Error returned when a string does not match the regular expression supplied
5263
+ * to {@link regex}.
5264
+ *
5265
+ * @group String
5266
+ */
4921
5267
  export interface RegexError<
4922
5268
  Name extends TypeName = TypeName,
4923
5269
  > extends TypeError<Name> {
@@ -5005,7 +5351,11 @@ export const Base64Url = /*#__PURE__*/ brand(
5005
5351
  );
5006
5352
  export type Base64Url = typeof Base64Url.Output;
5007
5353
 
5008
- /** @group String */
5354
+ /**
5355
+ * Error returned when a string is not valid {@link Base64Url} text.
5356
+ *
5357
+ * @group String
5358
+ */
5009
5359
  export interface Base64UrlError extends TypeError<"Base64Url"> {
5010
5360
  readonly value: string;
5011
5361
  }
@@ -5013,6 +5363,16 @@ export interface Base64UrlError extends TypeError<"Base64Url"> {
5013
5363
  /**
5014
5364
  * Encodes bytes as {@link Base64Url}.
5015
5365
  *
5366
+ * ### Example
5367
+ *
5368
+ * ```ts
5369
+ * import { uint8ArrayToBase64Url } from "@evolu/common";
5370
+ *
5371
+ * expect(uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255]))).toBe(
5372
+ * "AAEC_w",
5373
+ * );
5374
+ * ```
5375
+ *
5016
5376
  * @group String
5017
5377
  */
5018
5378
  export const uint8ArrayToBase64Url = (bytes: Uint8Array): Base64Url =>
@@ -5021,6 +5381,18 @@ export const uint8ArrayToBase64Url = (bytes: Uint8Array): Base64Url =>
5021
5381
  /**
5022
5382
  * Decodes {@link Base64Url} as bytes.
5023
5383
  *
5384
+ * ### Example
5385
+ *
5386
+ * ```ts
5387
+ * import { Base64Url, base64UrlToUint8Array } from "@evolu/common";
5388
+ *
5389
+ * const value = Base64Url.orThrow("AAEC_w");
5390
+ *
5391
+ * expect(base64UrlToUint8Array(value)).toEqual(
5392
+ * new Uint8Array([0, 1, 2, 255]),
5393
+ * );
5394
+ * ```
5395
+ *
5024
5396
  * @group String
5025
5397
  */
5026
5398
  export const base64UrlToUint8Array = (value: Base64Url): Uint8Array =>
@@ -5041,7 +5413,11 @@ export const Name = /*#__PURE__*/ brand(
5041
5413
  );
5042
5414
  export type Name = typeof Name.Output;
5043
5415
 
5044
- /** @group String */
5416
+ /**
5417
+ * Error returned when a string is not a valid {@link Name}.
5418
+ *
5419
+ * @group String
5420
+ */
5045
5421
  export interface NameError extends TypeError<"Name"> {
5046
5422
  readonly value: string;
5047
5423
  }
@@ -5077,11 +5453,15 @@ export const Mnemonic = /*#__PURE__*/ brand(
5077
5453
  ? ok()
5078
5454
  : err<MnemonicError>({ type: "Mnemonic", value }),
5079
5455
  (error) =>
5080
- `The value ${safelyStringifyUnknownValue(error.value)} is not a valid BIP39 mnemonic.`,
5456
+ `The value ${safelyStringifyUnknownValue(error.value)} is not a valid English BIP39 mnemonic.`,
5081
5457
  );
5082
5458
  export type Mnemonic = typeof Mnemonic.Output;
5083
5459
 
5084
- /** @group String */
5460
+ /**
5461
+ * Error returned when a string is not a valid English BIP39 {@link Mnemonic}.
5462
+ *
5463
+ * @group String
5464
+ */
5085
5465
  export interface MnemonicError extends TypeError<"Mnemonic"> {
5086
5466
  readonly value: string;
5087
5467
  }
@@ -5108,7 +5488,11 @@ export const Id = /*#__PURE__*/ brand(
5108
5488
  );
5109
5489
  export type Id = typeof Id.Output;
5110
5490
 
5111
- /** @group String */
5491
+ /**
5492
+ * Error returned when a string is not a valid {@link Id}.
5493
+ *
5494
+ * @group String
5495
+ */
5112
5496
  export interface IdError extends TypeError<"Id"> {
5113
5497
  readonly value: string;
5114
5498
  }
@@ -5119,6 +5503,22 @@ export interface IdError extends TypeError<"Id"> {
5119
5503
  * This is the recommended default because it does not encode creation time.
5120
5504
  * Pass a Brand name when the returned Id belongs to one domain entity.
5121
5505
  *
5506
+ * ### Example
5507
+ *
5508
+ * ```ts
5509
+ * import {
5510
+ * Id,
5511
+ * createId,
5512
+ * createRandomBytes,
5513
+ * type Brand,
5514
+ * } from "@evolu/common";
5515
+ *
5516
+ * const userId = createId<"User">({ randomBytes: createRandomBytes() });
5517
+ *
5518
+ * expect(Id.is(userId)).toBe(true);
5519
+ * expectTypeOf(userId).toEqualTypeOf<Id & Brand<"User">>();
5520
+ * ```
5521
+ *
5122
5522
  * @group String
5123
5523
  */
5124
5524
  export const createId = <B extends string = never>(
@@ -5165,6 +5565,26 @@ export const createIdFromString = <B extends string = never>(
5165
5565
  * logs, URLs, and exports. Prefer {@link createId} unless that tradeoff is
5166
5566
  * deliberate.
5167
5567
  *
5568
+ * ### Example
5569
+ *
5570
+ * ```ts
5571
+ * import {
5572
+ * createIdAsUuidv7,
5573
+ * createRandomBytes,
5574
+ * createTime,
5575
+ * idToIdBytes,
5576
+ * } from "@evolu/common";
5577
+ *
5578
+ * const value = createIdAsUuidv7({
5579
+ * randomBytes: createRandomBytes(),
5580
+ * time: createTime(),
5581
+ * });
5582
+ * const bytes = idToIdBytes(value);
5583
+ *
5584
+ * expect(bytes[6] >> 4).toBe(0x7);
5585
+ * expect(bytes[8] & 0xc0).toBe(0x80);
5586
+ * ```
5587
+ *
5168
5588
  * @group String
5169
5589
  */
5170
5590
  export const createIdAsUuidv7 = <B extends string = never>(
@@ -5189,6 +5609,22 @@ export const createIdAsUuidv7 = <B extends string = never>(
5189
5609
  /**
5190
5610
  * Table-specific {@link Id} Type.
5191
5611
  *
5612
+ * ### Example
5613
+ *
5614
+ * ```ts
5615
+ * import {
5616
+ * createIdFromString,
5617
+ * id,
5618
+ * type Brand,
5619
+ * type Id,
5620
+ * } from "@evolu/common";
5621
+ *
5622
+ * const TodoId = id("Todo");
5623
+ * const todoId = TodoId.orThrow(createIdFromString("todo"));
5624
+ *
5625
+ * expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
5626
+ * ```
5627
+ *
5192
5628
  * @group String
5193
5629
  */
5194
5630
  export const id = <Table extends TypeName>(
@@ -5211,7 +5647,11 @@ export const id = <Table extends TypeName>(
5211
5647
  );
5212
5648
  };
5213
5649
 
5214
- /** @group String */
5650
+ /**
5651
+ * The {@link Type} returned by {@link id} for one table.
5652
+ *
5653
+ * @group String
5654
+ */
5215
5655
  export interface TableId<Table extends TypeName> extends Type<
5216
5656
  "TableId",
5217
5657
  string,
@@ -5226,7 +5666,11 @@ export interface TableId<Table extends TypeName> extends Type<
5226
5666
  readonly table: Table;
5227
5667
  }
5228
5668
 
5229
- /** @group String */
5669
+ /**
5670
+ * Error returned when a string is not a valid {@link Id} for the expected table.
5671
+ *
5672
+ * @group String
5673
+ */
5230
5674
  export interface TableIdError<
5231
5675
  Table extends TypeName = TypeName,
5232
5676
  > extends TypeError<"TableId"> {
@@ -5278,15 +5722,51 @@ export const IdBytes = /*#__PURE__*/ brand(
5278
5722
  );
5279
5723
  export type IdBytes = typeof IdBytes.Output;
5280
5724
 
5281
- /** @group String */
5725
+ /**
5726
+ * Byte length of an {@link IdBytes} value.
5727
+ *
5728
+ * @group String
5729
+ */
5282
5730
  export const idBytesTypeValueLength = 16 as NonNegativeInt;
5283
5731
 
5284
- /** @group String */
5732
+ /**
5733
+ * Converts an {@link Id} to {@link IdBytes}.
5734
+ *
5735
+ * ### Example
5736
+ *
5737
+ * ```ts
5738
+ * import { createIdFromString, idToIdBytes } from "@evolu/common";
5739
+ *
5740
+ * const bytes = idToIdBytes(createIdFromString("todo"));
5741
+ *
5742
+ * expect(bytes).toHaveLength(16);
5743
+ * ```
5744
+ *
5745
+ * @group String
5746
+ */
5285
5747
  export const idToIdBytes = (value: Id): IdBytes =>
5286
5748
  base64UrlToUint8Array(value as unknown as Base64Url) as IdBytes;
5287
5749
 
5288
- /** @group String */
5289
- export const idBytesToId = (value: IdBytes): Id =>
5750
+ /**
5751
+ * Converts {@link IdBytes} to an {@link Id}.
5752
+ *
5753
+ * ### Example
5754
+ *
5755
+ * ```ts
5756
+ * import {
5757
+ * createIdFromString,
5758
+ * idBytesToId,
5759
+ * idToIdBytes,
5760
+ * } from "@evolu/common";
5761
+ *
5762
+ * const value = createIdFromString("todo");
5763
+ *
5764
+ * expect(idBytesToId(idToIdBytes(value))).toBe(value);
5765
+ * ```
5766
+ *
5767
+ * @group String
5768
+ */
5769
+ export const idBytesToId = (value: IdBytes): Id =>
5290
5770
  uint8ArrayToBase64Url(value) as unknown as Id;
5291
5771
 
5292
5772
  /**
@@ -5319,7 +5799,11 @@ export const Int64String = /*#__PURE__*/ brand(
5319
5799
  );
5320
5800
  export type Int64String = typeof Int64String.Output;
5321
5801
 
5322
- /** @group Number */
5802
+ /**
5803
+ * Error returned when a string is not a canonical {@link Int64String}.
5804
+ *
5805
+ * @group Number
5806
+ */
5323
5807
  export interface Int64StringError extends TypeError<"Int64String"> {
5324
5808
  readonly value: string;
5325
5809
  }
@@ -5331,6 +5815,17 @@ export interface Int64StringError extends TypeError<"Int64String"> {
5331
5815
  * text, including SQLite queries that cast INTEGER values to TEXT to avoid a
5332
5816
  * lossy JavaScript number conversion.
5333
5817
  *
5818
+ * ### Example
5819
+ *
5820
+ * ```ts
5821
+ * import { Int64FromInt64String } from "@evolu/common";
5822
+ *
5823
+ * const result = Int64FromInt64String.fromUnknown("9223372036854775807");
5824
+ *
5825
+ * expectOk(result, 9223372036854775807n);
5826
+ * expect(Int64FromInt64String.to(result.value)).toBe("9223372036854775807");
5827
+ * ```
5828
+ *
5334
5829
  * @group Number
5335
5830
  */
5336
5831
  export const Int64FromInt64String = /*#__PURE__*/ transform(
@@ -5346,6 +5841,16 @@ export const Int64FromInt64String = /*#__PURE__*/ transform(
5346
5841
  /**
5347
5842
  * Number {@link Brand} requiring a value greater than or equal to zero.
5348
5843
  *
5844
+ * ### Example
5845
+ *
5846
+ * ```ts
5847
+ * import { Number, nonNegative } from "@evolu/common";
5848
+ *
5849
+ * const NonNegative = nonNegative(Number);
5850
+ *
5851
+ * expectOk(NonNegative.fromUnknown(0), 0);
5852
+ * ```
5853
+ *
5349
5854
  * @group Number
5350
5855
  */
5351
5856
  export const nonNegative: BrandFactory<
@@ -5362,7 +5867,11 @@ export const nonNegative: BrandFactory<
5362
5867
  `The value ${safelyStringifyUnknownValue(error.value)} must be non-negative (>= 0).`,
5363
5868
  );
5364
5869
 
5365
- /** @group Number */
5870
+ /**
5871
+ * Error returned when {@link nonNegative} rejects a number.
5872
+ *
5873
+ * @group Number
5874
+ */
5366
5875
  export interface NonNegativeError extends TypeError<"NonNegative"> {
5367
5876
  readonly value: number;
5368
5877
  }
@@ -5378,6 +5887,16 @@ export type NonNegativeNumber = typeof NonNegativeNumber.Output;
5378
5887
  /**
5379
5888
  * Number {@link Brand} requiring a value greater than zero.
5380
5889
  *
5890
+ * ### Example
5891
+ *
5892
+ * ```ts
5893
+ * import { Number, positive } from "@evolu/common";
5894
+ *
5895
+ * const Positive = positive(Number);
5896
+ *
5897
+ * expectOk(Positive.fromUnknown(1), 1);
5898
+ * ```
5899
+ *
5381
5900
  * @group Number
5382
5901
  */
5383
5902
  export const positive: BrandFactory<"Positive", number, PositiveError> = (
@@ -5392,7 +5911,11 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
5392
5911
  `The value ${safelyStringifyUnknownValue(error.value)} must be positive (> 0).`,
5393
5912
  );
5394
5913
 
5395
- /** @group Number */
5914
+ /**
5915
+ * Error returned when {@link positive} rejects a number.
5916
+ *
5917
+ * @group Number
5918
+ */
5396
5919
  export interface PositiveError extends TypeError<"Positive"> {
5397
5920
  readonly value: number;
5398
5921
  }
@@ -5411,6 +5934,16 @@ export type PositiveNumber = typeof PositiveNumber.Output;
5411
5934
  /**
5412
5935
  * Number {@link Brand} requiring a value less than or equal to zero.
5413
5936
  *
5937
+ * ### Example
5938
+ *
5939
+ * ```ts
5940
+ * import { Number, nonPositive } from "@evolu/common";
5941
+ *
5942
+ * const NonPositive = nonPositive(Number);
5943
+ *
5944
+ * expectOk(NonPositive.fromUnknown(0), 0);
5945
+ * ```
5946
+ *
5414
5947
  * @group Number
5415
5948
  */
5416
5949
  export const nonPositive: BrandFactory<
@@ -5427,7 +5960,11 @@ export const nonPositive: BrandFactory<
5427
5960
  `The value ${safelyStringifyUnknownValue(error.value)} must be non-positive (<= 0).`,
5428
5961
  );
5429
5962
 
5430
- /** @group Number */
5963
+ /**
5964
+ * Error returned when {@link nonPositive} rejects a number.
5965
+ *
5966
+ * @group Number
5967
+ */
5431
5968
  export interface NonPositiveError extends TypeError<"NonPositive"> {
5432
5969
  readonly value: number;
5433
5970
  }
@@ -5443,6 +5980,16 @@ export type NonPositiveNumber = typeof NonPositiveNumber.Output;
5443
5980
  /**
5444
5981
  * Number {@link Brand} requiring a value less than zero.
5445
5982
  *
5983
+ * ### Example
5984
+ *
5985
+ * ```ts
5986
+ * import { Number, negative } from "@evolu/common";
5987
+ *
5988
+ * const Negative = negative(Number);
5989
+ *
5990
+ * expectOk(Negative.fromUnknown(-1), -1);
5991
+ * ```
5992
+ *
5446
5993
  * @group Number
5447
5994
  */
5448
5995
  export const negative: BrandFactory<"Negative", number, NegativeError> = (
@@ -5457,7 +6004,11 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
5457
6004
  `The value ${safelyStringifyUnknownValue(error.value)} must be negative (< 0).`,
5458
6005
  );
5459
6006
 
5460
- /** @group Number */
6007
+ /**
6008
+ * Error returned when {@link negative} rejects a number.
6009
+ *
6010
+ * @group Number
6011
+ */
5461
6012
  export interface NegativeError extends TypeError<"Negative"> {
5462
6013
  readonly value: number;
5463
6014
  }
@@ -5476,6 +6027,16 @@ export type NegativeNumber = typeof NegativeNumber.Output;
5476
6027
  /**
5477
6028
  * Number {@link Brand} requiring a value other than `NaN`.
5478
6029
  *
6030
+ * ### Example
6031
+ *
6032
+ * ```ts
6033
+ * import { Number, nonNaN } from "@evolu/common";
6034
+ *
6035
+ * const NonNaN = nonNaN(Number);
6036
+ *
6037
+ * expectOk(NonNaN.fromUnknown(Infinity), Infinity);
6038
+ * ```
6039
+ *
5479
6040
  * @group Number
5480
6041
  */
5481
6042
  export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
@@ -5489,7 +6050,11 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
5489
6050
  () => "The value must not be NaN.",
5490
6051
  );
5491
6052
 
5492
- /** @group Number */
6053
+ /**
6054
+ * Error returned when {@link nonNaN} rejects `NaN`.
6055
+ *
6056
+ * @group Number
6057
+ */
5493
6058
  export interface NonNaNError extends TypeError<"NonNaN"> {
5494
6059
  readonly value: number;
5495
6060
  }
@@ -5510,6 +6075,16 @@ export type NonNaNNumber = typeof NonNaNNumber.Output;
5510
6075
  /**
5511
6076
  * Number {@link Brand} requiring a finite value.
5512
6077
  *
6078
+ * ### Example
6079
+ *
6080
+ * ```ts
6081
+ * import { Number, finite } from "@evolu/common";
6082
+ *
6083
+ * const Finite = finite(Number);
6084
+ *
6085
+ * expectOk(Finite.fromUnknown(42), 42);
6086
+ * ```
6087
+ *
5513
6088
  * @group Number
5514
6089
  */
5515
6090
  export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
@@ -5524,7 +6099,11 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
5524
6099
  `The value ${safelyStringifyUnknownValue(error.value)} must be finite.`,
5525
6100
  );
5526
6101
 
5527
- /** @group Number */
6102
+ /**
6103
+ * Error returned when {@link finite} rejects a non-finite number.
6104
+ *
6105
+ * @group Number
6106
+ */
5528
6107
  export interface FiniteError extends TypeError<"Finite"> {
5529
6108
  readonly value: number;
5530
6109
  }
@@ -5589,7 +6168,11 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
5589
6168
  `The value ${safelyStringifyUnknownValue(error.value)} must be a safe integer.`,
5590
6169
  );
5591
6170
 
5592
- /** @group Number */
6171
+ /**
6172
+ * Error returned when {@link int} rejects a number that is not a safe integer.
6173
+ *
6174
+ * @group Number
6175
+ */
5593
6176
  export interface IntError extends TypeError<"Int"> {
5594
6177
  readonly value: number;
5595
6178
  }
@@ -5665,6 +6248,16 @@ export type NegativeInt = typeof NegativeInt.Output;
5665
6248
  /**
5666
6249
  * Number {@link Brand} requiring a value greater than `min`.
5667
6250
  *
6251
+ * ### Example
6252
+ *
6253
+ * ```ts
6254
+ * import { Number, greaterThan } from "@evolu/common";
6255
+ *
6256
+ * const GreaterThanTen = greaterThan(10)(Number);
6257
+ *
6258
+ * expectOk(GreaterThanTen.fromUnknown(11), 11);
6259
+ * ```
6260
+ *
5668
6261
  * @group Number
5669
6262
  */
5670
6263
  export const greaterThan =
@@ -5686,7 +6279,11 @@ export const greaterThan =
5686
6279
  );
5687
6280
  };
5688
6281
 
5689
- /** @group Number */
6282
+ /**
6283
+ * Error returned when {@link greaterThan} rejects a number.
6284
+ *
6285
+ * @group Number
6286
+ */
5690
6287
  export interface GreaterThanError<
5691
6288
  Min extends number = number,
5692
6289
  > extends TypeError<`GreaterThan${Min}`> {
@@ -5697,6 +6294,16 @@ export interface GreaterThanError<
5697
6294
  /**
5698
6295
  * Number {@link Brand} requiring a value greater than or equal to `min`.
5699
6296
  *
6297
+ * ### Example
6298
+ *
6299
+ * ```ts
6300
+ * import { Number, greaterThanOrEqualTo } from "@evolu/common";
6301
+ *
6302
+ * const AtLeastTen = greaterThanOrEqualTo(10)(Number);
6303
+ *
6304
+ * expectOk(AtLeastTen.fromUnknown(10), 10);
6305
+ * ```
6306
+ *
5700
6307
  * @group Number
5701
6308
  */
5702
6309
  export const greaterThanOrEqualTo =
@@ -5726,7 +6333,11 @@ export const greaterThanOrEqualTo =
5726
6333
  );
5727
6334
  };
5728
6335
 
5729
- /** @group Number */
6336
+ /**
6337
+ * Error returned when {@link greaterThanOrEqualTo} rejects a number.
6338
+ *
6339
+ * @group Number
6340
+ */
5730
6341
  export interface GreaterThanOrEqualToError<
5731
6342
  Min extends number = number,
5732
6343
  > extends TypeError<`GreaterThanOrEqualTo${Min}`> {
@@ -5737,6 +6348,16 @@ export interface GreaterThanOrEqualToError<
5737
6348
  /**
5738
6349
  * Number {@link Brand} requiring a value less than `max`.
5739
6350
  *
6351
+ * ### Example
6352
+ *
6353
+ * ```ts
6354
+ * import { Number, lessThan } from "@evolu/common";
6355
+ *
6356
+ * const LessThanTen = lessThan(10)(Number);
6357
+ *
6358
+ * expectOk(LessThanTen.fromUnknown(9), 9);
6359
+ * ```
6360
+ *
5740
6361
  * @group Number
5741
6362
  */
5742
6363
  export const lessThan =
@@ -5758,7 +6379,11 @@ export const lessThan =
5758
6379
  );
5759
6380
  };
5760
6381
 
5761
- /** @group Number */
6382
+ /**
6383
+ * Error returned when {@link lessThan} rejects a number.
6384
+ *
6385
+ * @group Number
6386
+ */
5762
6387
  export interface LessThanError<
5763
6388
  Max extends number = number,
5764
6389
  > extends TypeError<`LessThan${Max}`> {
@@ -5780,6 +6405,16 @@ export type Age = typeof Age.Output;
5780
6405
  /**
5781
6406
  * Number {@link Brand} requiring a value less than or equal to `max`.
5782
6407
  *
6408
+ * ### Example
6409
+ *
6410
+ * ```ts
6411
+ * import { Number, lessThanOrEqualTo } from "@evolu/common";
6412
+ *
6413
+ * const AtMostTen = lessThanOrEqualTo(10)(Number);
6414
+ *
6415
+ * expectOk(AtMostTen.fromUnknown(10), 10);
6416
+ * ```
6417
+ *
5783
6418
  * @group Number
5784
6419
  */
5785
6420
  export const lessThanOrEqualTo =
@@ -5805,7 +6440,11 @@ export const lessThanOrEqualTo =
5805
6440
  );
5806
6441
  };
5807
6442
 
5808
- /** @group Number */
6443
+ /**
6444
+ * Error returned when {@link lessThanOrEqualTo} rejects a number.
6445
+ *
6446
+ * @group Number
6447
+ */
5809
6448
  export interface LessThanOrEqualToError<
5810
6449
  Max extends number = number,
5811
6450
  > extends TypeError<`LessThanOrEqualTo${Max}`> {
@@ -5879,7 +6518,11 @@ export const DecimalString = /*#__PURE__*/ brand(
5879
6518
  );
5880
6519
  export type DecimalString = typeof DecimalString.Output;
5881
6520
 
5882
- /** @group Number */
6521
+ /**
6522
+ * Error returned when a string is not a canonical {@link DecimalString}.
6523
+ *
6524
+ * @group Number
6525
+ */
5883
6526
  export interface DecimalStringError extends TypeError<"DecimalString"> {
5884
6527
  readonly value: string;
5885
6528
  }
@@ -5887,6 +6530,16 @@ export interface DecimalStringError extends TypeError<"DecimalString"> {
5887
6530
  /**
5888
6531
  * {@link DecimalString} Brand requiring a value greater than or equal to zero.
5889
6532
  *
6533
+ * ### Example
6534
+ *
6535
+ * ```ts
6536
+ * import { DecimalString, nonNegativeDecimalString } from "@evolu/common";
6537
+ *
6538
+ * const NonNegative = nonNegativeDecimalString(DecimalString);
6539
+ *
6540
+ * expectOk(NonNegative.fromUnknown("0.5"), "0.5");
6541
+ * ```
6542
+ *
5890
6543
  * @group Number
5891
6544
  */
5892
6545
  export const nonNegativeDecimalString: BrandFactory<
@@ -5908,7 +6561,11 @@ export const nonNegativeDecimalString: BrandFactory<
5908
6561
  `The value ${safelyStringifyUnknownValue(error.value)} must be a non-negative decimal string.`,
5909
6562
  );
5910
6563
 
5911
- /** @group Number */
6564
+ /**
6565
+ * Error returned when {@link nonNegativeDecimalString} rejects a decimal string.
6566
+ *
6567
+ * @group Number
6568
+ */
5912
6569
  export interface NonNegativeDecimalStringError extends TypeError<"NonNegativeDecimalString"> {
5913
6570
  readonly value: string;
5914
6571
  }
@@ -5925,6 +6582,16 @@ export type NonNegativeDecimalString = typeof NonNegativeDecimalString.Output;
5925
6582
  /**
5926
6583
  * {@link DecimalString} Brand requiring a value greater than zero.
5927
6584
  *
6585
+ * ### Example
6586
+ *
6587
+ * ```ts
6588
+ * import { DecimalString, positiveDecimalString } from "@evolu/common";
6589
+ *
6590
+ * const Positive = positiveDecimalString(DecimalString);
6591
+ *
6592
+ * expectOk(Positive.fromUnknown("0.5"), "0.5");
6593
+ * ```
6594
+ *
5928
6595
  * @group Number
5929
6596
  */
5930
6597
  export const positiveDecimalString: BrandFactory<
@@ -5946,7 +6613,11 @@ export const positiveDecimalString: BrandFactory<
5946
6613
  `The value ${safelyStringifyUnknownValue(error.value)} must be a positive decimal string.`,
5947
6614
  );
5948
6615
 
5949
- /** @group Number */
6616
+ /**
6617
+ * Error returned when {@link positiveDecimalString} rejects a decimal string.
6618
+ *
6619
+ * @group Number
6620
+ */
5950
6621
  export interface PositiveDecimalStringError extends TypeError<"PositiveDecimalString"> {
5951
6622
  readonly value: string;
5952
6623
  }
@@ -5967,6 +6638,16 @@ export type PositiveDecimalString = typeof PositiveDecimalString.Output;
5967
6638
  /**
5968
6639
  * {@link DecimalString} Brand requiring a value less than or equal to zero.
5969
6640
  *
6641
+ * ### Example
6642
+ *
6643
+ * ```ts
6644
+ * import { DecimalString, nonPositiveDecimalString } from "@evolu/common";
6645
+ *
6646
+ * const NonPositive = nonPositiveDecimalString(DecimalString);
6647
+ *
6648
+ * expectOk(NonPositive.fromUnknown("-0.5"), "-0.5");
6649
+ * ```
6650
+ *
5970
6651
  * @group Number
5971
6652
  */
5972
6653
  export const nonPositiveDecimalString: BrandFactory<
@@ -5988,7 +6669,11 @@ export const nonPositiveDecimalString: BrandFactory<
5988
6669
  `The value ${safelyStringifyUnknownValue(error.value)} must be a non-positive decimal string.`,
5989
6670
  );
5990
6671
 
5991
- /** @group Number */
6672
+ /**
6673
+ * Error returned when {@link nonPositiveDecimalString} rejects a decimal string.
6674
+ *
6675
+ * @group Number
6676
+ */
5992
6677
  export interface NonPositiveDecimalStringError extends TypeError<"NonPositiveDecimalString"> {
5993
6678
  readonly value: string;
5994
6679
  }
@@ -6005,6 +6690,16 @@ export type NonPositiveDecimalString = typeof NonPositiveDecimalString.Output;
6005
6690
  /**
6006
6691
  * {@link DecimalString} Brand requiring a value less than zero.
6007
6692
  *
6693
+ * ### Example
6694
+ *
6695
+ * ```ts
6696
+ * import { DecimalString, negativeDecimalString } from "@evolu/common";
6697
+ *
6698
+ * const Negative = negativeDecimalString(DecimalString);
6699
+ *
6700
+ * expectOk(Negative.fromUnknown("-0.5"), "-0.5");
6701
+ * ```
6702
+ *
6008
6703
  * @group Number
6009
6704
  */
6010
6705
  export const negativeDecimalString: BrandFactory<
@@ -6026,7 +6721,11 @@ export const negativeDecimalString: BrandFactory<
6026
6721
  `The value ${safelyStringifyUnknownValue(error.value)} must be a negative decimal string.`,
6027
6722
  );
6028
6723
 
6029
- /** @group Number */
6724
+ /**
6725
+ * Error returned when {@link negativeDecimalString} rejects a decimal string.
6726
+ *
6727
+ * @group Number
6728
+ */
6030
6729
  export interface NegativeDecimalStringError extends TypeError<"NegativeDecimalString"> {
6031
6730
  readonly value: string;
6032
6731
  }
@@ -6118,7 +6817,11 @@ export const multipleOf = <const Divisor extends string>(
6118
6817
  );
6119
6818
  };
6120
6819
 
6121
- /** @group Number */
6820
+ /**
6821
+ * Error returned when {@link multipleOf} rejects a number.
6822
+ *
6823
+ * @group Number
6824
+ */
6122
6825
  export interface MultipleOfError<
6123
6826
  Divisor extends string = string,
6124
6827
  > extends TypeError<`MultipleOf${Divisor}`> {
@@ -6197,6 +6900,16 @@ const decimalStringToParts = (value: string): DecimalParts => {
6197
6900
  /**
6198
6901
  * Number {@link Brand} requiring a value within an inclusive range.
6199
6902
  *
6903
+ * ### Example
6904
+ *
6905
+ * ```ts
6906
+ * import { Number, between } from "@evolu/common";
6907
+ *
6908
+ * const Percentage = between(0, 100)(Number);
6909
+ *
6910
+ * expectOk(Percentage.fromUnknown(75), 75);
6911
+ * ```
6912
+ *
6200
6913
  * @group Number
6201
6914
  */
6202
6915
  export const between =
@@ -6224,7 +6937,11 @@ export const between =
6224
6937
  );
6225
6938
  };
6226
6939
 
6227
- /** @group Number */
6940
+ /**
6941
+ * Error returned when {@link between} rejects a number.
6942
+ *
6943
+ * @group Number
6944
+ */
6228
6945
  export interface BetweenError<
6229
6946
  Min extends number = number,
6230
6947
  Max extends number = number,
@@ -6292,7 +7009,11 @@ export const array = <ElementType extends ConcreteTypeNode>(
6292
7009
  arrayRuntimeConfig,
6293
7010
  ) as ArrayType<ElementType>;
6294
7011
 
6295
- /** @group Collection */
7012
+ /**
7013
+ * The homogeneous readonly-array {@link Type} returned by {@link array}.
7014
+ *
7015
+ * @group Collection
7016
+ */
6296
7017
  export interface ArrayType<ElementType extends TypeNode> extends Type<
6297
7018
  "Array",
6298
7019
  ReadonlyArray<ElementType["Input"]>,
@@ -6371,11 +7092,19 @@ type ArrayNodeError<ElementType extends TypeNode> = [
6371
7092
  ? ArrayElementsError<ElementType["Error"]>
6372
7093
  : ArrayError<ElementType["Error"]>;
6373
7094
 
6374
- /** @group Collection */
7095
+ /**
7096
+ * Error returned by {@link array} for a non-array value or invalid array items.
7097
+ *
7098
+ * @group Collection
7099
+ */
6375
7100
  export type ArrayError<Error extends TypeError = TypeError> =
6376
7101
  ArrayNotArrayError | ArrayItemsErrorValue<Error, true>;
6377
7102
 
6378
- /** @group Collection */
7103
+ /**
7104
+ * Error returned when an {@link array} input is not an array.
7105
+ *
7106
+ * @group Collection
7107
+ */
6379
7108
  export interface ArrayNotArrayError extends TypeError<"Array"> {
6380
7109
  readonly reason: {
6381
7110
  readonly kind: "NotArray";
@@ -6383,29 +7112,49 @@ export interface ArrayNotArrayError extends TypeError<"Array"> {
6383
7112
  };
6384
7113
  }
6385
7114
 
6386
- /** @group Collection */
7115
+ /**
7116
+ * An {@link array} error containing structural or element issues.
7117
+ *
7118
+ * @group Collection
7119
+ */
6387
7120
  export type ArrayItemsError<Error extends TypeError> = ArrayItemsErrorValue<
6388
7121
  Error,
6389
7122
  true
6390
7123
  >;
6391
7124
 
6392
- /** @group Collection */
7125
+ /**
7126
+ * One structural or element issue found by {@link array}.
7127
+ *
7128
+ * @group Collection
7129
+ */
6393
7130
  export type ArrayIssue<Error extends TypeError> =
6394
7131
  ArrayStructuralIssue | ArrayElementIssue<Error>;
6395
7132
 
6396
- /** @group Collection */
7133
+ /**
7134
+ * A missing array element.
7135
+ *
7136
+ * @group Collection
7137
+ */
6397
7138
  export interface ArrayHoleIssue {
6398
7139
  readonly kind: "Hole";
6399
7140
  readonly index: number;
6400
7141
  }
6401
7142
 
6402
- /** @group Collection */
7143
+ /**
7144
+ * An array element defined by an accessor instead of a data property.
7145
+ *
7146
+ * @group Collection
7147
+ */
6403
7148
  export interface ArrayAccessorIssue {
6404
7149
  readonly kind: "Accessor";
6405
7150
  readonly index: number;
6406
7151
  }
6407
7152
 
6408
- /** @group Collection */
7153
+ /**
7154
+ * An own array property other than `length` or an indexed element.
7155
+ *
7156
+ * @group Collection
7157
+ */
6409
7158
  export interface ArrayExcessPropertyIssue {
6410
7159
  readonly kind: "ExcessProperty";
6411
7160
  readonly key: string | symbol;
@@ -6414,7 +7163,11 @@ export interface ArrayExcessPropertyIssue {
6414
7163
  type ArrayStructuralIssue =
6415
7164
  ArrayHoleIssue | ArrayAccessorIssue | ArrayExcessPropertyIssue;
6416
7165
 
6417
- /** @group Collection */
7166
+ /**
7167
+ * An invalid array element and its index.
7168
+ *
7169
+ * @group Collection
7170
+ */
6418
7171
  export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
6419
7172
  ? {
6420
7173
  readonly kind: "Element";
@@ -6423,7 +7176,11 @@ export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
6423
7176
  }
6424
7177
  : never;
6425
7178
 
6426
- /** @group Collection */
7179
+ /**
7180
+ * An {@link array} error containing element errors from a typed boundary.
7181
+ *
7182
+ * @group Collection
7183
+ */
6427
7184
  export type ArrayElementsError<Error extends TypeError> = [Error] extends [
6428
7185
  never,
6429
7186
  ]
@@ -6494,23 +7251,6 @@ const isArrayCollection = (
6494
7251
  return true;
6495
7252
  };
6496
7253
 
6497
- const formatArrayError: TypeErrorFormatter<ArrayError> = (error) => {
6498
- if (error.reason.kind === "NotArray") {
6499
- return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
6500
- }
6501
-
6502
- const issue = error.reason.issues[0] as ArrayStructuralIssue;
6503
-
6504
- switch (issue.kind) {
6505
- case "Hole":
6506
- return `An array element at index ${issue.index} is missing.`;
6507
- case "Accessor":
6508
- return `An array element at index ${issue.index} must be a data property.`;
6509
- case "ExcessProperty":
6510
- return "An excess Array property is not allowed. Remove it or use a different Type.";
6511
- }
6512
- };
6513
-
6514
7254
  const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
6515
7255
  ReadonlyArray<unknown>
6516
7256
  > = {
@@ -6521,7 +7261,23 @@ const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
6521
7261
  validateArrayItems(value, validateElement, options, false),
6522
7262
  encode: encodeArrayCollection,
6523
7263
  is: isArrayCollection,
6524
- formatError: formatArrayError as TypeErrorFormatter<TypeError>,
7264
+ formatError: ((error: ArrayError) => {
7265
+ if (error.reason.kind === "NotArray") {
7266
+ return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
7267
+ }
7268
+ const issue = error.reason.issues[0];
7269
+
7270
+ switch (issue.kind) {
7271
+ case "Hole":
7272
+ return `An array element at index ${issue.index} is missing.`;
7273
+ case "Accessor":
7274
+ return `An array element at index ${issue.index} must be a data property.`;
7275
+ case "ExcessProperty":
7276
+ return "An excess Array property is not allowed. Remove it or use a different Type.";
7277
+ case "Element":
7278
+ return `An array element at index ${issue.index} is invalid.`;
7279
+ }
7280
+ }) as TypeErrorFormatter<TypeError>,
6525
7281
  };
6526
7282
 
6527
7283
  const validateArrayItems = (
@@ -6658,6 +7414,17 @@ const copyArrayPrefix = (
6658
7414
  * iteration order. Classification uses the realm-neutral object tag and
6659
7415
  * prototype structure under Evolu Type's trusted JavaScript policy.
6660
7416
  *
7417
+ * ### Example
7418
+ *
7419
+ * ```ts
7420
+ * import { String, set } from "@evolu/common";
7421
+ *
7422
+ * const Tags = set(String);
7423
+ * const tags = new Set(["local-first", "offline"]);
7424
+ *
7425
+ * expectOk(Tags.fromUnknown(tags), tags);
7426
+ * ```
7427
+ *
6661
7428
  * @group Collection
6662
7429
  */
6663
7430
  export const set = <ElementType extends ConcreteTypeNode>(
@@ -6668,7 +7435,11 @@ export const set = <ElementType extends ConcreteTypeNode>(
6668
7435
  setRuntimeConfig,
6669
7436
  ) as SetType<ElementType>;
6670
7437
 
6671
- /** @group Collection */
7438
+ /**
7439
+ * The homogeneous readonly-set {@link Type} returned by {@link set}.
7440
+ *
7441
+ * @group Collection
7442
+ */
6672
7443
  export interface SetType<ElementType extends TypeNode> extends Type<
6673
7444
  "Set",
6674
7445
  ReadonlySet<ElementType["Input"]>,
@@ -6739,13 +7510,22 @@ type SetNodeError<ElementType extends TypeNode> = [
6739
7510
  ? SetElementsError<ElementType["Error"]>
6740
7511
  : SetError<ElementType["Error"]>;
6741
7512
 
6742
- /** @group Collection */
7513
+ /**
7514
+ * Error returned by {@link set} for a non-Set value, an invalid Set prototype,
7515
+ * or invalid Set items.
7516
+ *
7517
+ * @group Collection
7518
+ */
6743
7519
  export type SetError<Error extends TypeError = TypeError> =
6744
7520
  | SetNotSetError
6745
7521
  | SetUnexpectedPrototypeError
6746
7522
  | SetItemsErrorValue<Error, true>;
6747
7523
 
6748
- /** @group Collection */
7524
+ /**
7525
+ * Error returned when a {@link set} input is not a Set.
7526
+ *
7527
+ * @group Collection
7528
+ */
6749
7529
  export interface SetNotSetError extends TypeError<"Set"> {
6750
7530
  readonly reason: {
6751
7531
  readonly kind: "NotSet";
@@ -6765,7 +7545,11 @@ export interface SetUnexpectedPrototypeError extends TypeError<"Set"> {
6765
7545
  };
6766
7546
  }
6767
7547
 
6768
- /** @group Collection */
7548
+ /**
7549
+ * An own property found on a Set value.
7550
+ *
7551
+ * @group Collection
7552
+ */
6769
7553
  export interface SetExcessPropertyIssue {
6770
7554
  readonly kind: "ExcessProperty";
6771
7555
  readonly key: string | symbol;
@@ -6773,7 +7557,11 @@ export interface SetExcessPropertyIssue {
6773
7557
 
6774
7558
  type SetStructuralIssue = SetExcessPropertyIssue;
6775
7559
 
6776
- /** @group Collection */
7560
+ /**
7561
+ * An invalid Set element and its iteration index.
7562
+ *
7563
+ * @group Collection
7564
+ */
6777
7565
  export type SetElementIssue<Error extends TypeError> = Error extends TypeError
6778
7566
  ? {
6779
7567
  readonly kind: "Element";
@@ -6782,13 +7570,21 @@ export type SetElementIssue<Error extends TypeError> = Error extends TypeError
6782
7570
  }
6783
7571
  : never;
6784
7572
 
6785
- /** @group Collection */
7573
+ /**
7574
+ * A {@link set} error containing structural or element issues.
7575
+ *
7576
+ * @group Collection
7577
+ */
6786
7578
  export type SetItemsError<Error extends TypeError> = SetItemsErrorValue<
6787
7579
  Error,
6788
7580
  true
6789
7581
  >;
6790
7582
 
6791
- /** @group Collection */
7583
+ /**
7584
+ * A {@link set} error containing element errors from a typed boundary.
7585
+ *
7586
+ * @group Collection
7587
+ */
6792
7588
  export type SetElementsError<Error extends TypeError> = [Error] extends [never]
6793
7589
  ? never
6794
7590
  : SetItemsErrorValue<Error, false>;
@@ -6960,18 +7756,6 @@ const isSetCollection = (
6960
7756
  return true;
6961
7757
  };
6962
7758
 
6963
- const formatSetError: TypeErrorFormatter<SetError> = (error) => {
6964
- if (error.reason.kind === "NotSet") {
6965
- return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
6966
- }
6967
- if (error.reason.kind === "UnexpectedPrototype") {
6968
- return "The value is a Set subclass, but a Set Output must be a direct Set.";
6969
- }
6970
-
6971
- const issue = error.reason.issues[0] as SetStructuralIssue;
6972
- return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
6973
- };
6974
-
6975
7759
  const hasDirectSetPrototype = (value: object): boolean => {
6976
7760
  const prototype: unknown = globalThis.Object.getPrototypeOf(value);
6977
7761
  if (prototype === null) return false;
@@ -6994,7 +7778,19 @@ const setRuntimeConfig: HomogeneousCollectionRuntimeConfig<
6994
7778
  validateSetItems(value, validateElement, options, false),
6995
7779
  encode: encodeSetCollection,
6996
7780
  is: isSetCollection,
6997
- formatError: formatSetError as TypeErrorFormatter<TypeError>,
7781
+ formatError: ((error: SetError) => {
7782
+ if (error.reason.kind === "NotSet")
7783
+ return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
7784
+ if (error.reason.kind === "UnexpectedPrototype")
7785
+ return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
7786
+ const issue = error.reason.issues[0];
7787
+ switch (issue.kind) {
7788
+ case "ExcessProperty":
7789
+ return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
7790
+ case "Element":
7791
+ return `A Set element at index ${issue.index} is invalid.`;
7792
+ }
7793
+ }) as TypeErrorFormatter<TypeError>,
6998
7794
  };
6999
7795
 
7000
7796
  const validateSetItems = (
@@ -7067,17 +7863,12 @@ const validateSetItems = (
7067
7863
  * ### Example
7068
7864
  *
7069
7865
  * ```ts
7070
- * import { Number, String, ok, transform, tuple } from "@evolu/common";
7866
+ * import { Int64FromInt64String, String, tuple } from "@evolu/common";
7071
7867
  *
7072
- * const NumberFromString = transform("NumberFromString", String, Number, {
7073
- * from: (value) => ok(globalThis.Number(value)),
7074
- * to: globalThis.String,
7075
- * });
7076
- *
7077
- * const Entry = tuple(String, NumberFromString);
7868
+ * const Entry = tuple(String, Int64FromInt64String);
7078
7869
  *
7079
- * expectOk(Entry.fromUnknown(["count", "1"]), ["count", 1]);
7080
- * expectOk(Entry.from.parent(["count", "1"]), ["count", 1]);
7870
+ * expectOk(Entry.fromUnknown(["count", "1"]), ["count", 1n]);
7871
+ * expectOk(Entry.from.parent(["count", "1"]), ["count", 1n]);
7081
7872
  * ```
7082
7873
  *
7083
7874
  * @group Collection
@@ -7148,14 +7939,11 @@ const createTupleType = (
7148
7939
  options,
7149
7940
  );
7150
7941
  const formatError: TypeErrorFormatter<TupleError> = (error) => {
7151
- if (error.reason.kind === "NotArray") {
7942
+ if (error.reason.kind === "NotArray")
7152
7943
  return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a tuple.`;
7153
- }
7154
- if (error.reason.kind === "InvalidLength") {
7944
+ if (error.reason.kind === "InvalidLength")
7155
7945
  return `A Tuple must contain exactly ${error.reason.expected} elements, but the value contains ${error.reason.actual}.`;
7156
- }
7157
- const issue = error.reason.issues[0] as TupleStructuralIssue;
7158
-
7946
+ const issue = error.reason.issues[0];
7159
7947
  switch (issue.kind) {
7160
7948
  case "Hole":
7161
7949
  return `A Tuple element at index ${issue.index} is missing.`;
@@ -7163,6 +7951,8 @@ const createTupleType = (
7163
7951
  return `A Tuple element at index ${issue.index} must be a data property.`;
7164
7952
  case "ExcessProperty":
7165
7953
  return "An excess Tuple property is not allowed. Remove it or use a different Type.";
7954
+ case "Element":
7955
+ return `A Tuple element at index ${issue.index} is invalid.`;
7166
7956
  }
7167
7957
  };
7168
7958
  const rootElements = typeElements.map(
@@ -7538,10 +8328,7 @@ export type Digit1To6 = typeof Digit1To6.Output;
7538
8328
  export const Digit1To23 = /*#__PURE__*/ union(
7539
8329
  Digit1To9,
7540
8330
  /*#__PURE__*/ templateLiteral("1", Digit),
7541
- /*#__PURE__*/ templateLiteral(
7542
- "2",
7543
- /*#__PURE__*/ union("0", "1", "2", "3"),
7544
- ),
8331
+ /*#__PURE__*/ templateLiteral("2", /*#__PURE__*/ union("0", "1", "2", "3")),
7545
8332
  );
7546
8333
  export type Digit1To23 = typeof Digit1To23.Output;
7547
8334
 
@@ -7552,10 +8339,7 @@ export type Digit1To23 = typeof Digit1To23.Output;
7552
8339
  */
7553
8340
  export const Digit1To51 = /*#__PURE__*/ union(
7554
8341
  Digit1To9,
7555
- /*#__PURE__*/ templateLiteral(
7556
- /*#__PURE__*/ union("1", "2", "3", "4"),
7557
- Digit,
7558
- ),
8342
+ /*#__PURE__*/ templateLiteral(/*#__PURE__*/ union("1", "2", "3", "4"), Digit),
7559
8343
  /*#__PURE__*/ templateLiteral("5", /*#__PURE__*/ union("0", "1")),
7560
8344
  );
7561
8345
  export type Digit1To51 = typeof Digit1To51.Output;
@@ -7654,31 +8438,19 @@ const createObjectRuntimeTypeIssues =
7654
8438
  }) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
7655
8439
  };
7656
8440
 
8441
+ const formatPlainObjectRootError = (
8442
+ reason:
8443
+ ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
8444
+ ): string =>
8445
+ reason.kind === "NotObject"
8446
+ ? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
8447
+ : "The value is an object, but an Object Output must be a plain object or have a null prototype.";
8448
+
7657
8449
  type PlainObjectError = ObjectError<
7658
8450
  Readonly<Record<never, never>>,
7659
8451
  ObjectPropertyAccessError | ObjectExcessPropertyError
7660
8452
  >;
7661
8453
 
7662
- const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
7663
- error,
7664
- ) => {
7665
- if (error.reason.kind !== "Properties") {
7666
- return formatPlainObjectRootError(error.reason);
7667
- }
7668
-
7669
- const key = Reflect.ownKeys(error.reason.errors).at(0);
7670
- assertNonNullable(key);
7671
- const propertyError = error.reason.errors[key];
7672
- assertNonNullable(propertyError);
7673
-
7674
- if (propertyError.type === "ObjectPropertyAccess") {
7675
- return propertyError.reason === "Accessor"
7676
- ? "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type."
7677
- : "An Object property must be enumerable. Make it enumerable or use a different Type.";
7678
- }
7679
- return "An Object property key must be a string. Remove it or use a different Type.";
7680
- };
7681
-
7682
8454
  /**
7683
8455
  * A {@link Type} for readonly plain objects with unknown property values.
7684
8456
  *
@@ -7693,7 +8465,7 @@ const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
7693
8465
  * values have their own Types, and {@link instanceOf} when an instance belongs
7694
8466
  * to the domain.
7695
8467
  *
7696
- * @group Base Types
8468
+ * @group Base
7697
8469
  */
7698
8470
  export const Object: Type<
7699
8471
  "Object",
@@ -7768,10 +8540,52 @@ export const Object: Type<
7768
8540
  reason: { kind: "Properties", errors },
7769
8541
  } as PlainObjectError);
7770
8542
  },
7771
- formatPlainObjectError,
7772
- /*#__PURE__*/ createObjectRuntimeTypeIssues(
7773
- formatPlainObjectError as TypeErrorFormatter<TypeError>,
7774
- ),
8543
+ (error: ObjectError) => {
8544
+ if (error.reason.kind !== "Properties")
8545
+ return formatPlainObjectRootError(error.reason);
8546
+ const key = Reflect.ownKeys(error.reason.errors).at(0);
8547
+ assertNonNullable(key);
8548
+ const propertyError = error.reason.errors[key];
8549
+ assertNonNullable(propertyError);
8550
+ if (propertyError.type === "ObjectPropertyAccess") {
8551
+ switch ((propertyError as ObjectPropertyAccessError).reason) {
8552
+ case "Accessor":
8553
+ return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
8554
+ case "NonEnumerable":
8555
+ return "An Object property must be enumerable. Make it enumerable or use a different Type.";
8556
+ }
8557
+ }
8558
+ if (propertyError.type === "ObjectMissingProperty")
8559
+ return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
8560
+ if (typeof key === "symbol")
8561
+ return "An Object property key must be a string. Remove the symbol property or use a different Type.";
8562
+ if (propertyError.type === "ObjectExcessProperty")
8563
+ return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
8564
+ return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
8565
+ },
8566
+ /*#__PURE__*/ createObjectRuntimeTypeIssues(((error: ObjectError) => {
8567
+ if (error.reason.kind !== "Properties")
8568
+ return formatPlainObjectRootError(error.reason);
8569
+ const key = Reflect.ownKeys(error.reason.errors).at(0);
8570
+ assertNonNullable(key);
8571
+ const propertyError = error.reason.errors[key];
8572
+ assertNonNullable(propertyError);
8573
+ if (propertyError.type === "ObjectPropertyAccess") {
8574
+ switch ((propertyError as ObjectPropertyAccessError).reason) {
8575
+ case "Accessor":
8576
+ return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
8577
+ case "NonEnumerable":
8578
+ return "An Object property must be enumerable. Make it enumerable or use a different Type.";
8579
+ }
8580
+ }
8581
+ if (propertyError.type === "ObjectMissingProperty")
8582
+ return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
8583
+ if (typeof key === "symbol")
8584
+ return "An Object property key must be a string. Remove the symbol property or use a different Type.";
8585
+ if (propertyError.type === "ObjectExcessProperty")
8586
+ return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
8587
+ return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
8588
+ }) as TypeErrorFormatter<TypeError>),
7775
8589
  );
7776
8590
 
7777
8591
  const isPlainObject = (value: object): boolean => {
@@ -7781,14 +8595,6 @@ const isPlainObject = (value: object): boolean => {
7781
8595
  );
7782
8596
  };
7783
8597
 
7784
- const formatPlainObjectRootError = (
7785
- reason:
7786
- ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
7787
- ): string =>
7788
- reason.kind === "NotObject"
7789
- ? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
7790
- : "The value is an object, but an Object Output must be a plain object or have a null prototype.";
7791
-
7792
8598
  /**
7793
8599
  * Record {@link Type}.
7794
8600
  *
@@ -7819,20 +8625,13 @@ const formatPlainObjectRootError = (
7819
8625
  *
7820
8626
  * ```ts
7821
8627
  * import {
7822
- * Number,
8628
+ * Int64FromInt64String,
7823
8629
  * String,
7824
- * ok,
7825
8630
  * record,
7826
- * transform,
7827
- * type Result,
8631
+ * type Int64,
7828
8632
  * } from "@evolu/common";
7829
8633
  *
7830
- * const ScoreFromString = transform("ScoreFromString", String, Number, {
7831
- * from: (value) => ok(globalThis.Number(value)),
7832
- * to: globalThis.String,
7833
- * });
7834
- *
7835
- * const ScoresByUser = record(String, ScoreFromString);
8634
+ * const ScoresByUser = record(String, Int64FromInt64String);
7836
8635
  * type ScoresByUser = typeof ScoresByUser.Output;
7837
8636
  *
7838
8637
  * // Validate an unknown value.
@@ -7841,7 +8640,7 @@ const formatPlainObjectRootError = (
7841
8640
  * grace: "20",
7842
8641
  * });
7843
8642
  *
7844
- * expectOk(scoresFromUnknown, { ada: 10, grace: 20 });
8643
+ * expectOk(scoresFromUnknown, { ada: 10n, grace: 20n });
7845
8644
  *
7846
8645
  * // Validate keys and values with their root Types.
7847
8646
  * const scoresInput = ScoresByUser.parent.fromUnknown({
@@ -7853,11 +8652,10 @@ const formatPlainObjectRootError = (
7853
8652
  * // Run the remaining key and value stages.
7854
8653
  * const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
7855
8654
  *
7856
- * expectTypeOf(scoresFromInput).toEqualTypeOf<
7857
- * Result<Readonly<Partial<Record<string, number>>>, never>
8655
+ * expectOk(scoresFromInput, { ada: 10n, grace: 20n });
8656
+ * expectTypeOf(scoresFromInput.value).toEqualTypeOf<
8657
+ * Readonly<Partial<Record<string, Int64>>>
7858
8658
  * >();
7859
- *
7860
- * expectOk(scoresFromInput, { ada: 10, grace: 20 });
7861
8659
  * ```
7862
8660
  *
7863
8661
  * Note that TypeScript does not model an object's runtime prototype. This can
@@ -7954,20 +8752,20 @@ export const record = <
7954
8752
  options,
7955
8753
  );
7956
8754
  const formatError: TypeErrorFormatter<RecordError> = (error) => {
7957
- if (error.reason.kind === "NotRecord") {
7958
- return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a record.`;
7959
- }
7960
- if (error.reason.kind === "NotPlainRecord") {
8755
+ if (error.reason.kind === "NotRecord")
8756
+ return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Record.`;
8757
+ if (error.reason.kind === "NotPlainRecord")
7961
8758
  return "The value is an object, but a Record Output must be a plain object or have a null prototype.";
7962
- }
7963
-
7964
- const issue = error.reason.issues[0] as RecordStructuralIssue;
7965
-
8759
+ const issue = error.reason.issues[0];
7966
8760
  switch (issue.kind) {
8761
+ case "Key":
8762
+ return `Property key ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
8763
+ case "Value":
8764
+ return `The value of property ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
7967
8765
  case "Accessor":
7968
- return `A record property ${safelyStringifyUnknownValue(issue.key)} must be a data property.`;
8766
+ return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be a data property.`;
7969
8767
  case "NonEnumerable":
7970
- return `A record property ${safelyStringifyUnknownValue(issue.key)} must be enumerable.`;
8768
+ return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be enumerable.`;
7971
8769
  case "Collision":
7972
8770
  return `Record keys ${safelyStringifyUnknownValue(issue.previousKey)} and ${safelyStringifyUnknownValue(issue.key)} decode to the same key ${safelyStringifyUnknownValue(issue.outputKey)}.`;
7973
8771
  }
@@ -8073,7 +8871,11 @@ export const record = <
8073
8871
  );
8074
8872
  };
8075
8873
 
8076
- /** @group Objects */
8874
+ /**
8875
+ * The {@link Type} returned by {@link record}.
8876
+ *
8877
+ * @group Objects
8878
+ */
8077
8879
  export interface RecordType<
8078
8880
  KeyType extends TypeNode,
8079
8881
  ValueType extends TypeNode,
@@ -8197,7 +8999,11 @@ type RecordKeyStringTypeError = CompileTimeError<
8197
8999
  "Record key Type Input and Output must extend string."
8198
9000
  >;
8199
9001
 
8200
- /** @group Objects */
9002
+ /**
9003
+ * Error returned while validating a {@link record} and its entries.
9004
+ *
9005
+ * @group Objects
9006
+ */
8201
9007
  export type RecordError<
8202
9008
  KeyError extends TypeError = TypeError,
8203
9009
  ValueError extends TypeError = TypeError,
@@ -8211,7 +9017,11 @@ export type RecordError<
8211
9017
  Collision | RecordAccessorIssue | RecordNonEnumerableIssue
8212
9018
  >;
8213
9019
 
8214
- /** @group Objects */
9020
+ /**
9021
+ * Error returned when a {@link record} input is not an object.
9022
+ *
9023
+ * @group Objects
9024
+ */
8215
9025
  export interface RecordNotRecordError extends TypeError<"Record"> {
8216
9026
  readonly reason: {
8217
9027
  readonly kind: "NotRecord";
@@ -8219,7 +9029,11 @@ export interface RecordNotRecordError extends TypeError<"Record"> {
8219
9029
  };
8220
9030
  }
8221
9031
 
8222
- /** @group Objects */
9032
+ /**
9033
+ * Error returned when a {@link record} input is not a plain object.
9034
+ *
9035
+ * @group Objects
9036
+ */
8223
9037
  export interface RecordNotPlainRecordError extends TypeError<"Record"> {
8224
9038
  readonly reason: {
8225
9039
  readonly kind: "NotPlainRecord";
@@ -8244,14 +9058,22 @@ export type RecordEntriesError<
8244
9058
  ? never
8245
9059
  : RecordEntriesErrorValue<KeyError, ValueError, StructuralIssue>;
8246
9060
 
8247
- /** @group Objects */
9061
+ /**
9062
+ * An invalid key, value, or property structure in a {@link record}.
9063
+ *
9064
+ * @group Objects
9065
+ */
8248
9066
  export type RecordIssue<
8249
9067
  KeyError extends TypeError,
8250
9068
  ValueError extends TypeError,
8251
9069
  StructuralIssue extends RecordStructuralIssue = RecordCollisionIssue,
8252
9070
  > = RecordKeyIssue<KeyError> | RecordValueIssue<ValueError> | StructuralIssue;
8253
9071
 
8254
- /** @group Objects */
9072
+ /**
9073
+ * A property-structure issue returned by {@link record}.
9074
+ *
9075
+ * @group Objects
9076
+ */
8255
9077
  export type RecordStructuralIssue =
8256
9078
  RecordAccessorIssue | RecordCollisionIssue | RecordNonEnumerableIssue;
8257
9079
 
@@ -8275,7 +9097,11 @@ export interface RecordNonEnumerableIssue {
8275
9097
  readonly key: string | symbol;
8276
9098
  }
8277
9099
 
8278
- /** @group Objects */
9100
+ /**
9101
+ * An invalid key and its source property key in a {@link record}.
9102
+ *
9103
+ * @group Objects
9104
+ */
8279
9105
  export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
8280
9106
  ? {
8281
9107
  readonly kind: "Key";
@@ -8284,7 +9110,11 @@ export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
8284
9110
  }
8285
9111
  : never;
8286
9112
 
8287
- /** @group Objects */
9113
+ /**
9114
+ * An invalid value and its property key in a {@link record}.
9115
+ *
9116
+ * @group Objects
9117
+ */
8288
9118
  export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
8289
9119
  ? {
8290
9120
  readonly kind: "Value";
@@ -8293,7 +9123,11 @@ export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
8293
9123
  }
8294
9124
  : never;
8295
9125
 
8296
- /** @group Objects */
9126
+ /**
9127
+ * Two {@link record} keys that decode to the same output key.
9128
+ *
9129
+ * @group Objects
9130
+ */
8297
9131
  export interface RecordCollisionIssue {
8298
9132
  readonly kind: "Collision";
8299
9133
  readonly key: string | symbol;
@@ -8520,48 +9354,38 @@ type ObjectProperty = ObjectProps[string];
8520
9354
  *
8521
9355
  * ```ts
8522
9356
  * import {
8523
- * Number,
9357
+ * Int64FromInt64String,
8524
9358
  * String,
8525
9359
  * object,
8526
- * ok,
8527
- * transform,
8528
9360
  * type InferType,
8529
- * type Result,
8530
9361
  * } from "@evolu/common";
8531
9362
  *
8532
- * const AgeFromString = transform("AgeFromString", String, Number, {
8533
- * from: (value) => ok(globalThis.Number(value)),
8534
- * to: globalThis.String,
8535
- * });
8536
- *
8537
9363
  * const User = object({
8538
9364
  * name: String,
8539
- * age: AgeFromString,
9365
+ * loginCount: Int64FromInt64String,
8540
9366
  * });
8541
9367
  * interface User extends InferType<typeof User> {}
8542
9368
  *
8543
9369
  * // Validate an unknown value.
8544
- * const userFromUnknown = User.fromUnknown({ name: "Ada", age: "42" });
9370
+ * const userFromUnknown = User.fromUnknown({
9371
+ * name: "Ada",
9372
+ * loginCount: "42",
9373
+ * });
8545
9374
  *
8546
- * expectOk(userFromUnknown, { name: "Ada", age: 42 });
9375
+ * expectOk(userFromUnknown, { name: "Ada", loginCount: 42n });
8547
9376
  *
8548
9377
  * // Validate the object and root property Types.
8549
9378
  * const userInput = User.parent.fromUnknown({
8550
9379
  * name: "Ada",
8551
- * age: "42",
9380
+ * loginCount: "42",
8552
9381
  * });
8553
- * expectOk(userInput, { name: "Ada", age: "42" });
9382
+ * expectOk(userInput, { name: "Ada", loginCount: "42" });
8554
9383
  *
8555
9384
  * // Run the remaining property stages.
8556
- * const userFromInput: Result<
8557
- * { readonly name: string; readonly age: number },
8558
- * never
8559
- * > = User.from.parent(userInput.value);
9385
+ * const userFromInput = User.from.parent(userInput.value);
8560
9386
  *
8561
- * expectTypeOf(userFromInput).toEqualTypeOf<
8562
- * Result<{ readonly name: string; readonly age: number }, never>
8563
- * >();
8564
- * expectOk(userFromInput, { name: "Ada", age: 42 });
9387
+ * expectOk(userFromInput, { name: "Ada", loginCount: 42n });
9388
+ * expectTypeOf(userFromInput.value).toExtend<User>();
8565
9389
  * ```
8566
9390
  *
8567
9391
  * Note that TypeScript does not model an object's runtime prototype. This can
@@ -8614,6 +9438,8 @@ export function object<const Props extends ObjectProps>(
8614
9438
  ? []
8615
9439
  : [ValidationFailure<ObjectValidationError<Props>>]
8616
9440
  ): StrictObjectType<Props>;
9441
+
9442
+ /** Creates an Object Type with additional record properties. */
8617
9443
  export function object<
8618
9444
  const Props extends ObjectProps,
8619
9445
  const Rest extends RecordTypeNode & ConcreteTypeNode,
@@ -8818,31 +9644,27 @@ const createObjectType = (
8818
9644
  options: ValidationOptions = firstValidationOptions,
8819
9645
  ) => validate(value, options, true);
8820
9646
  const formatError: TypeErrorFormatter<ObjectError> = (error) => {
8821
- if (error.reason.kind !== "Properties") {
9647
+ if (error.reason.kind !== "Properties")
8822
9648
  return formatPlainObjectRootError(error.reason);
8823
- }
8824
-
8825
9649
  const key = Reflect.ownKeys(error.reason.errors).at(0);
8826
9650
  assertNonNullable(key);
8827
- const propertyError = (
8828
- error.reason.errors as Readonly<Partial<Record<PropertyKey, TypeError>>>
8829
- )[key]!;
8830
-
9651
+ const propertyError = error.reason.errors[key];
9652
+ assertNonNullable(propertyError);
8831
9653
  if (propertyError.type === "ObjectPropertyAccess") {
8832
9654
  switch ((propertyError as ObjectPropertyAccessError).reason) {
8833
9655
  case "Accessor":
8834
9656
  return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
8835
9657
  case "NonEnumerable":
8836
- return "An Object property must be enumerable.";
9658
+ return "An Object property must be enumerable. Make it enumerable or use a different Type.";
8837
9659
  }
8838
9660
  }
8839
- if (
8840
- typeof key === "string" &&
8841
- globalThis.Object.hasOwn(runtimeProps, key)
8842
- ) {
8843
- return "A required property is missing.";
8844
- }
8845
- return "An excess property is not allowed. Remove it or use a different Type.";
9661
+ if (propertyError.type === "ObjectMissingProperty")
9662
+ return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
9663
+ if (typeof key === "symbol")
9664
+ return "An Object property key must be a string. Remove the symbol property or use a different Type.";
9665
+ if (propertyError.type === "ObjectExcessProperty")
9666
+ return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
9667
+ return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
8846
9668
  };
8847
9669
  const rootProps = createMutableRecord<string, RuntimeObjectProperty>();
8848
9670
  let hasNonRootType = false;
@@ -9214,7 +10036,11 @@ type ObjectRecordCanonicalInputTypeError = CompileTimeError<
9214
10036
  "Every declared property Type CanonicalInput must extend the Object Record value Type CanonicalInput."
9215
10037
  >;
9216
10038
 
9217
- /** @group Objects */
10039
+ /**
10040
+ * The {@link Type} returned by {@link object}.
10041
+ *
10042
+ * @group Objects
10043
+ */
9218
10044
  export type ObjectType<
9219
10045
  Props extends ObjectProps,
9220
10046
  Rest extends ObjectRecordTypeNode | undefined = undefined,
@@ -9649,7 +10475,11 @@ export const partial = <const Props extends ObjectProps>(
9649
10475
  >;
9650
10476
  };
9651
10477
 
9652
- /** @group Objects */
10478
+ /**
10479
+ * Maps every required object property Type to an optional property.
10480
+ *
10481
+ * @group Objects
10482
+ */
9653
10483
  export type PartialObjectProps<Props extends ObjectProps> = {
9654
10484
  readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
9655
10485
  ? Props[Key]
@@ -9719,7 +10549,12 @@ export const nullableToOptional = <const Props extends ObjectProps>(
9719
10549
  >;
9720
10550
  };
9721
10551
 
9722
- /** @group Objects */
10552
+ /**
10553
+ * Maps object properties whose Union Type includes {@link Null} to optional
10554
+ * properties.
10555
+ *
10556
+ * @group Objects
10557
+ */
9723
10558
  export type NullableToOptionalProps<Props extends ObjectProps> = {
9724
10559
  readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
9725
10560
  ? Props[Key]
@@ -9733,6 +10568,17 @@ export type NullableToOptionalProps<Props extends ObjectProps> = {
9733
10568
  /**
9734
10569
  * Object {@link Type} without the selected declared properties.
9735
10570
  *
10571
+ * ### Example
10572
+ *
10573
+ * ```ts
10574
+ * import { String, object, omit } from "@evolu/common";
10575
+ *
10576
+ * const User = object({ name: String, password: String });
10577
+ * const PublicUser = omit(User, "password");
10578
+ *
10579
+ * expectOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
10580
+ * ```
10581
+ *
9736
10582
  * @group Objects
9737
10583
  */
9738
10584
  export const omit = <
@@ -9785,11 +10631,11 @@ type OmitKeyConcreteTypeError = CompileTimeError<
9785
10631
  >;
9786
10632
 
9787
10633
  /**
9788
- * {@link Result} {@link Type} for typed success and error values.
10634
+ * Creates a {@link Type} for {@link Result} values.
9789
10635
  *
9790
10636
  * Use this to validate Results crossing a storage, worker, API, or other
9791
- * serialization boundary. The operation returns an outer validation Result. Its
9792
- * successful value is the inner domain Result described by `okType` and
10637
+ * serialization boundary. `fromUnknown` returns an outer validation Result.
10638
+ * Its successful value is the inner domain Result described by `okType` and
9793
10639
  * `errorType`.
9794
10640
  *
9795
10641
  * ### Example
@@ -9805,34 +10651,16 @@ type OmitKeyConcreteTypeError = CompileTimeError<
9805
10651
  *
9806
10652
  * const SyncResponse = result(
9807
10653
  * object({ timestamp: NonNegativeInt }),
9808
- * typed("SyncError", { message: String }),
10654
+ * typed("SyncFailed", { message: String }),
9809
10655
  * );
9810
10656
  *
9811
- * const describeResponse = (input: unknown): string => {
9812
- * const validated = SyncResponse.fromUnknown(input);
9813
- * if (!validated.ok) return "Invalid response";
9814
- *
9815
- * const response = validated.value;
9816
- * return response.ok
9817
- * ? `Synced at ${response.value.timestamp}`
9818
- * : response.error.message;
9819
- * };
10657
+ * const validated = SyncResponse.fromUnknown({
10658
+ * ok: true,
10659
+ * value: { timestamp: 42 },
10660
+ * });
9820
10661
  *
9821
- * expect(
9822
- * describeResponse({
9823
- * ok: true,
9824
- * value: { timestamp: 42 },
9825
- * }),
9826
- * ).toBe("Synced at 42");
9827
- * expect(
9828
- * describeResponse({
9829
- * ok: false,
9830
- * error: { type: "SyncError", message: "Offline" },
9831
- * }),
9832
- * ).toBe("Offline");
9833
- * expect(describeResponse({ ok: true, value: { timestamp: -1 } })).toBe(
9834
- * "Invalid response",
9835
- * );
10662
+ * expectOk(validated, { ok: true, value: { timestamp: 42 } });
10663
+ * expectOk(validated.value, { timestamp: 42 });
9836
10664
  * ```
9837
10665
  *
9838
10666
  * @group Results
@@ -9887,7 +10715,7 @@ export const UnknownResult = /*#__PURE__*/ result(Unknown, Unknown);
9887
10715
  export type UnknownResult = typeof UnknownResult.Output;
9888
10716
 
9889
10717
  /**
9890
- * Tagged {@link ObjectType}.
10718
+ * Creates an {@link ObjectType} with a literal `type` property.
9891
10719
  *
9892
10720
  * The discriminator belongs to `typed`, so additional properties cannot declare
9893
10721
  * `type`. The discriminator Input is `string`, inherited from {@link String},
@@ -9899,22 +10727,16 @@ export type UnknownResult = typeof UnknownResult.Output;
9899
10727
  * ### Example
9900
10728
  *
9901
10729
  * ```ts
9902
- * import { String, discriminatedUnion, typed } from "@evolu/common";
10730
+ * import { String, typed } from "@evolu/common";
9903
10731
  *
9904
10732
  * const Pending = typed("Pending", {
9905
10733
  * label: String,
9906
10734
  * });
9907
10735
  *
9908
- * const Completed = typed("Completed");
9909
- * const Status = discriminatedUnion(Pending, Completed);
9910
- *
9911
- * expectOk(Status.fromUnknown({ type: "Pending", label: "Waiting" }), {
10736
+ * expectOk(Pending.fromUnknown({ type: "Pending", label: "Waiting" }), {
9912
10737
  * type: "Pending",
9913
10738
  * label: "Waiting",
9914
10739
  * });
9915
- * expectOk(Status.fromUnknown({ type: "Completed" }), {
9916
- * type: "Completed",
9917
- * });
9918
10740
  * ```
9919
10741
  *
9920
10742
  * @group Discriminated unions
@@ -9922,6 +10744,8 @@ export type UnknownResult = typeof UnknownResult.Output;
9922
10744
  export function typed<const Tag extends TypeName>(
9923
10745
  tag: ValidateTypedTag<Tag>,
9924
10746
  ): TypedType<Tag>;
10747
+
10748
+ /** Creates a Tagged Object Type with declared properties. */
9925
10749
  export function typed<
9926
10750
  const Tag extends TypeName,
9927
10751
  const Props extends ObjectProps,
@@ -9932,6 +10756,8 @@ export function typed<
9932
10756
  ? []
9933
10757
  : [ValidationFailure<TypedValidationError<Props>>]
9934
10758
  ): TypedType<Tag, Props>;
10759
+
10760
+ /** Creates a Tagged Object Type with additional record properties. */
9935
10761
  export function typed<
9936
10762
  const Tag extends TypeName,
9937
10763
  const Props extends ObjectProps,
@@ -9971,7 +10797,14 @@ export function typed(
9971
10797
  }
9972
10798
 
9973
10799
  /**
9974
- * A structurally tagged value created by {@link typed}.
10800
+ * A TypeScript interface with a literal `type` property.
10801
+ *
10802
+ * Use `Typed` for both domain objects in discriminated unions and plain domain
10803
+ * errors returned by {@link Result}. Name a domain error interface `XError`.
10804
+ * When `X` already describes a failure, use `X` for its `type` discriminant
10805
+ * because `Error` describes the interface's role rather than the runtime error
10806
+ * kind. Keep `Error` when it is needed to make the discriminant unambiguous,
10807
+ * such as `TimeoutError`.
9975
10808
  *
9976
10809
  * Typed unions model mutually exclusive states as separate variants instead of
9977
10810
  * combinations of flags and optional properties. TypeScript narrows a union by
@@ -9982,20 +10815,23 @@ export function typed(
9982
10815
  * ### Example
9983
10816
  *
9984
10817
  * ```ts
9985
- * import { type Typed } from "@evolu/common";
10818
+ * import { err, ok, type Result, type Typed } from "@evolu/common";
9986
10819
  *
9987
- * type Status = Typed<"Pending"> | Typed<"Completed">;
10820
+ * interface User extends Typed<"User"> {
10821
+ * readonly id: string;
10822
+ * }
9988
10823
  *
9989
- * const getStatusMessage = (status: Status): string => {
9990
- * switch (status.type) {
9991
- * case "Pending":
9992
- * return "Waiting";
9993
- * case "Completed":
9994
- * return "Done";
9995
- * }
9996
- * };
10824
+ * const getUser = (id: string): Result<User, UserNotFoundError> =>
10825
+ * id === "user-1"
10826
+ * ? ok({ type: "User", id })
10827
+ * : err({ type: "UserNotFound", id });
10828
+ *
10829
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {
10830
+ * readonly id: string;
10831
+ * }
9997
10832
  *
9998
- * expect(getStatusMessage({ type: "Pending" })).toBe("Waiting");
10833
+ * expectOk(getUser("user-1"), { type: "User", id: "user-1" });
10834
+ * expectErr(getUser("missing"), { type: "UserNotFound", id: "missing" });
9999
10835
  * ```
10000
10836
  *
10001
10837
  * @group Discriminated unions
@@ -10007,9 +10843,8 @@ export interface Typed<Tag extends TypeName> {
10007
10843
  /**
10008
10844
  * Extracts members of a {@link Typed} Output union by their `type` literal.
10009
10845
  *
10010
- * The requested name is constrained to the union's actual discriminator values,
10011
- * so a misspelling is a TypeScript error instead of silently producing
10012
- * `never`.
10846
+ * The requested tag is constrained to the union's actual discriminator values,
10847
+ * so a misspelling is a TypeScript error instead of silently producing `never`.
10013
10848
  *
10014
10849
  * ### Example
10015
10850
  *
@@ -10079,7 +10914,8 @@ type TypedTypePropertyError = CompileTimeError<
10079
10914
  >;
10080
10915
 
10081
10916
  /**
10082
- * Producer-result {@link Type} for value, error, or done outcomes.
10917
+ * Creates a {@link Type} for producer Results with value, error, or done
10918
+ * outcomes.
10083
10919
  *
10084
10920
  * The three outcomes are `Ok<Value>`, `Err<Error>`, and `Err<Typed<"Done"> & {
10085
10921
  * done: Done }>`. This keeps normal completion distinct from failure while
@@ -10092,7 +10928,7 @@ type TypedTypePropertyError = CompileTimeError<
10092
10928
  *
10093
10929
  * const StringNextResult = nextResult(
10094
10930
  * String,
10095
- * typed("ProducerError", { message: String }),
10931
+ * typed("ReadFailed", { message: String }),
10096
10932
  * String,
10097
10933
  * );
10098
10934
  *
@@ -10116,7 +10952,7 @@ type TypedTypePropertyError = CompileTimeError<
10116
10952
  * expect(
10117
10953
  * describeNext({
10118
10954
  * ok: false,
10119
- * error: { type: "ProducerError", message: "Offline" },
10955
+ * error: { type: "ReadFailed", message: "Offline" },
10120
10956
  * }),
10121
10957
  * ).toBe("Error: Offline");
10122
10958
  * ```
@@ -10178,7 +11014,7 @@ export const UnknownNextResult = /*#__PURE__*/ nextResult(
10178
11014
  export type UnknownNextResult = typeof UnknownNextResult.Output;
10179
11015
 
10180
11016
  /**
10181
- * Discriminated Union {@link Type}.
11017
+ * Discriminated union {@link Type}.
10182
11018
  *
10183
11019
  * With no explicit key, the conventional `type` property created by
10184
11020
  * {@link typed} is used. Pass a key first to discriminate
@@ -10213,6 +11049,35 @@ export function discriminatedUnion<
10213
11049
  >(
10214
11050
  ...members: Members & DiscriminatedUnionValidation<"type", Members>
10215
11051
  ): DiscriminatedUnionType<"type", Members>;
11052
+
11053
+ /**
11054
+ * Creates a Discriminated Union Type with an explicit discriminator key.
11055
+ *
11056
+ * ### Example
11057
+ *
11058
+ * ```ts
11059
+ * import {
11060
+ * Number,
11061
+ * String,
11062
+ * discriminatedUnion,
11063
+ * literal,
11064
+ * object,
11065
+ * } from "@evolu/common";
11066
+ *
11067
+ * const Added = object({ kind: literal("added"), value: String });
11068
+ * const Removed = object({ kind: literal("removed"), id: Number });
11069
+ * const Event = discriminatedUnion("kind", Added, Removed);
11070
+ *
11071
+ * expectOk(Event.fromUnknown({ kind: "added", value: "Evolu" }), {
11072
+ * kind: "added",
11073
+ * value: "Evolu",
11074
+ * });
11075
+ * expectOk(Event.fromUnknown({ kind: "removed", id: 1 }), {
11076
+ * kind: "removed",
11077
+ * id: 1,
11078
+ * });
11079
+ * ```
11080
+ */
10216
11081
  export function discriminatedUnion<
10217
11082
  const Key extends string,
10218
11083
  const Members extends DiscriminatedUnionMembers,
@@ -10354,26 +11219,21 @@ export function discriminatedUnion(
10354
11219
  Reflect.get(value as Readonly<Record<string, unknown>>, key),
10355
11220
  )!;
10356
11221
  const formatError: TypeErrorFormatter<DiscriminatedUnionError> = (error) => {
10357
- const reason = error.reason as Exclude<
10358
- DiscriminatedUnionError["reason"],
10359
- DiscriminatedUnionMemberIssue
10360
- >;
10361
-
10362
- switch (reason.kind) {
11222
+ switch (error.reason.kind) {
10363
11223
  case "Object":
10364
- return formatPlainObjectRootError(reason.error.reason);
11224
+ return formatPlainObjectRootError(error.reason.error.reason);
10365
11225
  case "PropertyAccess": {
10366
- const property = `The discriminator property ${safelyStringifyUnknownValue(reason.key)}`;
10367
- if (reason.reason === "Accessor") {
11226
+ const property = `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)}`;
11227
+ if (error.reason.reason === "Accessor")
10368
11228
  return `${property} must be a data property.`;
10369
- }
10370
- if (reason.reason === "Inherited") {
11229
+ if (error.reason.reason === "Inherited")
10371
11230
  return `${property} must be an own property.`;
10372
- }
10373
11231
  return `${property} must be enumerable.`;
10374
11232
  }
10375
11233
  case "Discriminator":
10376
- return `The discriminator property ${safelyStringifyUnknownValue(reason.key)} has an unexpected value ${safelyStringifyUnknownValue(reason.value)}.`;
11234
+ return `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)} has an unexpected value ${safelyStringifyUnknownValue(error.reason.value)}.`;
11235
+ case "Member":
11236
+ return `The selected variant ${safelyStringifyUnknownValue(error.reason.discriminator)} is invalid.`;
10377
11237
  }
10378
11238
  };
10379
11239
  const defaultFormatter = formatError as TypeErrorFormatter<TypeError>;
@@ -10799,10 +11659,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
10799
11659
  };
10800
11660
 
10801
11661
  /**
10802
- * Lazy {@link Type} for recursive definitions.
11662
+ * Creates a lazy {@link Type} for recursive definitions.
10803
11663
  *
10804
- * Lazy defers and caches a Type definition, allowing recursive data such as
10805
- * trees and mutually recursive models.
11664
+ * The definition is evaluated on first use and then cached, allowing recursive
11665
+ * data such as trees and mutually recursive models.
10806
11666
  *
10807
11667
  * A recursive declaration refers to its own variable while that variable is
10808
11668
  * being initialized, so TypeScript cannot infer it reliably. Getter-based
@@ -10822,12 +11682,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
10822
11682
  *
10823
11683
  * ```ts
10824
11684
  * import {
10825
- * Number,
10826
11685
  * String,
10827
11686
  * array,
10828
11687
  * lazy,
10829
11688
  * object,
10830
- * optional,
10831
11689
  * type ArrayError,
10832
11690
  * type LazyType,
10833
11691
  * type ObjectError,
@@ -10848,35 +11706,19 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
10848
11706
  * () => object({ value: String, children: array(Tree) }),
10849
11707
  * );
10850
11708
  *
10851
- * interface Left {
10852
- * readonly label: string;
10853
- * readonly right?: Right;
10854
- * }
10855
- *
10856
- * interface Right {
10857
- * readonly count: number;
10858
- * readonly left?: Left;
10859
- * }
10860
- *
10861
- * interface LeftError extends ObjectError<{
10862
- * readonly label: TypeOfError<"String">;
10863
- * readonly right?: RightError;
10864
- * }> {}
10865
- *
10866
- * interface RightError extends ObjectError<{
10867
- * readonly count: TypeOfError<"Number">;
10868
- * readonly left?: LeftError;
10869
- * }> {}
10870
- *
10871
- * const Left: LazyType<Left, Left, never, LeftError, LeftError> = lazy(
10872
- * () => object({ label: String, right: optional(Right) }),
10873
- * );
11709
+ * const result = Tree.fromUnknown({
11710
+ * value: "root",
11711
+ * children: [{ value: "leaf", children: [] }],
11712
+ * });
10874
11713
  *
10875
- * const Right: LazyType<Right, Right, never, RightError, RightError> =
10876
- * lazy(() => object({ count: Number, left: optional(Left) }));
11714
+ * expectOk(result, {
11715
+ * value: "root",
11716
+ * children: [{ value: "leaf", children: [] }],
11717
+ * });
11718
+ * expectTypeOf(result.value).toEqualTypeOf<Tree>();
10877
11719
  * ```
10878
11720
  *
10879
- * @group Recursive Types
11721
+ * @group Recursive
10880
11722
  */
10881
11723
  export function lazy<Target extends ConcreteTypeNode>(
10882
11724
  getType: Thunk<ValidateLazyTarget<Target>>,
@@ -10995,7 +11837,7 @@ export function lazy(getType: Thunk<TypeNode>): TypeNode {
10995
11837
  * recursive declaration finite for TypeScript while preserving structured
10996
11838
  * errors at every boundary.
10997
11839
  *
10998
- * @group Recursive Types
11840
+ * @group Recursive
10999
11841
  */
11000
11842
  export interface LazyType<
11001
11843
  // Explicit invariance prevents recursive comparisons from repeatedly
@@ -11123,7 +11965,7 @@ export interface JsonObject {
11123
11965
  }
11124
11966
 
11125
11967
  /**
11126
- * One issue found while validating an exact {@link JsonValue}.
11968
+ * One issue found while validating a candidate as an exact {@link JsonValue}.
11127
11969
  *
11128
11970
  * @group JSON
11129
11971
  */
@@ -11171,7 +12013,8 @@ export type JsonValueIssue =
11171
12013
  };
11172
12014
 
11173
12015
  /**
11174
- * An error containing one or more issues found in a {@link JsonValue}.
12016
+ * An error containing one or more issues found while validating a candidate as
12017
+ * an exact {@link JsonValue}.
11175
12018
  *
11176
12019
  * @group JSON
11177
12020
  */
@@ -11500,31 +12343,6 @@ const validateJsonValue = (
11500
12343
  });
11501
12344
  };
11502
12345
 
11503
- const formatJsonValueError: TypeErrorFormatter<JsonValueError> = (error) => {
11504
- const issue = error.reason.issues[0];
11505
-
11506
- switch (issue.kind) {
11507
- case "InvalidType":
11508
- return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
11509
- case "NonFiniteNumber":
11510
- return "A JSON number must be finite.";
11511
- case "UnexpectedPrototype":
11512
- return "The value is an object, but a JsonValue Object must be a plain object or have a null prototype.";
11513
- case "Accessor":
11514
- return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
11515
- case "NonEnumerable":
11516
- return "A JSON Object property must be enumerable. Remove it or use a different Type.";
11517
- case "SymbolProperty":
11518
- return "A JSON Object property key must be a string. Remove the symbol property or use a different Type.";
11519
- case "Hole":
11520
- return "A JSON Array element is missing.";
11521
- case "ExcessProperty":
11522
- return "An excess JSON Array property is not allowed. Remove it or use a different Type.";
11523
- case "CircularReference":
11524
- return "A JsonValue must not contain circular references.";
11525
- }
11526
- };
11527
-
11528
12346
  const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
11529
12347
  const jsonValueError = error as JsonValueError;
11530
12348
  const issues =
@@ -11542,7 +12360,29 @@ const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
11542
12360
  reason: { kind: "Issues", issues: [issue] },
11543
12361
  },
11544
12362
  path: issue.path,
11545
- formatError: formatJsonValueError as TypeErrorFormatter<TypeError>,
12363
+ formatError: ((error: JsonValueError) => {
12364
+ const issue = error.reason.issues[0];
12365
+ switch (issue.kind) {
12366
+ case "InvalidType":
12367
+ return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
12368
+ case "NonFiniteNumber":
12369
+ return "A JSON number must be finite.";
12370
+ case "UnexpectedPrototype":
12371
+ return "The value is an object, but a JsonValue object must be a plain object or have a null prototype.";
12372
+ case "Accessor":
12373
+ return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
12374
+ case "NonEnumerable":
12375
+ return "A JSON object property must be enumerable. Remove it or use a different Type.";
12376
+ case "SymbolProperty":
12377
+ return "A JSON object property key must be a string. Remove the symbol property or use a different Type.";
12378
+ case "Hole":
12379
+ return "A JSON array element is missing.";
12380
+ case "ExcessProperty":
12381
+ return "An excess JSON array property is not allowed. Remove it or use a different Type.";
12382
+ case "CircularReference":
12383
+ return "A JsonValue must not contain circular references.";
12384
+ }
12385
+ }) as TypeErrorFormatter<TypeError>,
11546
12386
  })) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
11547
12387
  };
11548
12388
 
@@ -11627,6 +12467,21 @@ const stringifyJsonValue = (value: JsonValue): Json => {
11627
12467
  /**
11628
12468
  * Exact root Type for {@link JsonValue} data trees.
11629
12469
  *
12470
+ * ### Example
12471
+ *
12472
+ * ```ts
12473
+ * import { JsonValue, type JsonValueInput } from "@evolu/common";
12474
+ *
12475
+ * const input: JsonValueInput = {
12476
+ * name: "Ada",
12477
+ * scores: [10, 20],
12478
+ * };
12479
+ * const result = JsonValue.fromUnknown(input);
12480
+ *
12481
+ * expectOk(result, input);
12482
+ * expectTypeOf(result.value).toEqualTypeOf<JsonValue>();
12483
+ * ```
12484
+ *
11630
12485
  * @group JSON
11631
12486
  */
11632
12487
  export const JsonValue: JsonValueType =
@@ -11683,6 +12538,16 @@ export type Json = typeof Json.Output;
11683
12538
  /**
11684
12539
  * Totally parses proven {@link Json} text into an exact {@link JsonValue}.
11685
12540
  *
12541
+ * ### Example
12542
+ *
12543
+ * ```ts
12544
+ * import { Json, jsonToJsonValue } from "@evolu/common";
12545
+ *
12546
+ * const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
12547
+ *
12548
+ * expect(value).toEqual({ name: "Ada" });
12549
+ * ```
12550
+ *
11686
12551
  * @group JSON
11687
12552
  */
11688
12553
  export const jsonToJsonValue = (value: Json): JsonValue => parseJson(value);
@@ -11690,17 +12555,38 @@ export const jsonToJsonValue = (value: Json): JsonValue => parseJson(value);
11690
12555
  /**
11691
12556
  * Totally encodes an exact {@link JsonValue} as canonical {@link Json} text.
11692
12557
  *
12558
+ * ### Example
12559
+ *
12560
+ * ```ts
12561
+ * import { jsonValueToJson, type JsonValue } from "@evolu/common";
12562
+ *
12563
+ * const value: JsonValue = { name: "Ada" };
12564
+ *
12565
+ * expect(jsonValueToJson(value)).toBe('{"name":"Ada"}');
12566
+ * ```
12567
+ *
11693
12568
  * @group JSON
11694
12569
  */
11695
12570
  export const jsonValueToJson = (value: JsonValue): Json =>
11696
12571
  stringifyJsonValue(JsonValue.to(value));
11697
12572
 
11698
12573
  /**
11699
- * {@link Json} to {@link JsonValue} transformation.
12574
+ * Transformation {@link Type} that parses {@link Json} into {@link JsonValue}.
11700
12575
  *
11701
12576
  * Decoding unknown input first validates the Json Brand. Starting from the
11702
12577
  * typed Json parent is infallible. Encoding canonicalizes JSON text.
11703
12578
  *
12579
+ * ### Example
12580
+ *
12581
+ * ```ts
12582
+ * import { JsonValueFromJson } from "@evolu/common";
12583
+ *
12584
+ * const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
12585
+ *
12586
+ * expectOk(result, { name: "Ada" });
12587
+ * expect(JsonValueFromJson.to(result.value)).toBe('{"name":"Ada"}');
12588
+ * ```
12589
+ *
11704
12590
  * @group JSON
11705
12591
  */
11706
12592
  export const JsonValueFromJson = /*#__PURE__*/ transform(