@evolu/common 7.4.1 → 8.0.0-next.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 (251) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +622 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +36 -26
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +415 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +465 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +851 -430
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -824
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +206 -277
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -526
  246. package/src/Instances.ts +0 -92
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -949
package/src/Type.ts CHANGED
@@ -1,24 +1,35 @@
1
+ /**
2
+ * Runtime type validation, parsing, and branded types.
3
+ *
4
+ * @module
5
+ */
6
+
1
7
  import { utf8ToBytes } from "@noble/ciphers/utils.js";
2
8
  import { sha256 } from "@noble/hashes/sha2.js";
3
9
  import * as bip39 from "@scure/bip39";
4
10
  import { wordlist } from "@scure/bip39/wordlists/english.js";
5
- import { pack } from "msgpackr";
6
11
  import type { Brand } from "./Brand.js";
7
12
  import type { RandomBytesDep } from "./Crypto.js";
8
- import { isPlainObject } from "./Object.js";
13
+ import { exhaustiveCheck } from "./Function.js";
14
+ import { isFunction, isPlainObject } from "./Object.js";
9
15
  import { hasNodeBuffer } from "./Platform.js";
10
- import { err, getOrNull, getOrThrow, ok, Result, trySync } from "./Result.js";
16
+ import type { NextResult, Result } from "./Result.js";
17
+ import { err, getOrNull, getOrThrow, ok, trySync } from "./Result.js";
11
18
  import { safelyStringifyUnknownValue } from "./String.js";
12
19
  import type { TimeDep } from "./Time.js";
13
20
  import type { Literal, Refinement, Simplify, WidenLiteral } from "./Types.js";
14
- import { IntentionalNever } from "./Types.js";
15
21
 
16
22
  /**
17
23
  * Evolu {@link Type} is like a type guard that returns typed errors (via
18
- * {@link Result}) instead of throwing. We either get a safely typed value or a
19
- * composable typed error telling us exactly why validation failed.
24
+ * {@link Result}) instead of throwing. We either receive a safely typed value or
25
+ * a composable typed error that tells us exactly why the validation failed.
20
26
  *
21
- * Why another validation library?
27
+ * The reason why Evolu Type exists is that no other TypeScript validation
28
+ * library met Evolu's requirements. A distinctive feature of Evolu Type
29
+ * compared to other validation libraries is that it returns typed errors rather
30
+ * than string messages. This allows TypeScript to enforce that all validation
31
+ * errors are handled via {@link exhaustiveCheck}, significantly improving the
32
+ * developer experience. Those requirements are:
22
33
  *
23
34
  * - **Result-based error handling** – no exceptions for normal control flow.
24
35
  * - **Typed errors with decoupled formatters** – validation logic ≠ user
@@ -31,15 +42,10 @@ import { IntentionalNever } from "./Types.js";
31
42
  * bottom.
32
43
  * - **No user-land chaining DSL** – prepared for TC39 Hack pipes.
33
44
  *
34
- * A distinctive feature of Evolu Type compared to other validation libraries is
35
- * that it returns typed errors rather than string messages. This allows
36
- * TypeScript to enforce that all validation errors are handled by type
37
- * checking, significantly improving the developer experience.
38
- *
39
45
  * Evolu Type supports [Standard Schema](https://standardschema.dev/) for
40
46
  * interoperability with 40+ validation-compatible tools and frameworks.
41
47
  *
42
- * ### Base Types Quick Start
48
+ * ## Base Types
43
49
  *
44
50
  * ```ts
45
51
  * // Validate unknown values
@@ -47,7 +53,7 @@ import { IntentionalNever } from "./Types.js";
47
53
  * const stringResult = String.fromUnknown(value);
48
54
  * if (!stringResult.ok) {
49
55
  * // console.error(formatStringError(stringResult.error));
50
- * return stringResult; // inside a function returning Result<string, _>
56
+ * return stringResult;
51
57
  * }
52
58
  * // Safe branch: value is now string
53
59
  * const upper = stringResult.value.toUpperCase();
@@ -66,10 +72,12 @@ import { IntentionalNever } from "./Types.js";
66
72
  * Point.from({ x: 1, y: "2" }); // err -> nested Number error
67
73
  * ```
68
74
  *
69
- * ### Branding Basics
75
+ * ## Branded types
70
76
  *
71
- * Branding adds semantic meaning & constraints while preserving the runtime
72
- * shape:
77
+ * Branding is the recommended way to define types in Evolu. Instead of using
78
+ * primitive types like `string` or `number` directly, wrap them with
79
+ * {@link brand} to create semantically meaningful types. See {@link Brand} for
80
+ * why this matters.
73
81
  *
74
82
  * ```ts
75
83
  * const CurrencyCode = brand("CurrencyCode", String, (value) =>
@@ -93,20 +101,25 @@ import { IntentionalNever } from "./Types.js";
93
101
  * See also reusable brand factories like `minLength`, `maxLength`, `trimmed`,
94
102
  * `positive`, `between`, etc.
95
103
  *
96
- * ### Objects & Optional Fields
104
+ * ## Object types
97
105
  *
98
106
  * ```ts
99
107
  * const User = object({
100
108
  * name: NonEmptyTrimmedString100,
101
109
  * age: optional(PositiveInt),
102
110
  * });
103
- * type User = typeof User.Type;
111
+ *
112
+ * // Use interface for objects. TypeScript displays the interface name
113
+ * // instead of expanding all properties.
114
+ * interface User extends InferType<typeof User> {}
104
115
  *
105
116
  * User.from({ name: "Alice" }); // ok
106
117
  * User.from({ name: "Alice", age: -1 }); // err(PositiveInt)
118
+ *
119
+ * // TODO: Add `record`
107
120
  * ```
108
121
  *
109
- * ### Deriving JSON String Types
122
+ * ## JSON type
110
123
  *
111
124
  * ```ts
112
125
  * const Person = object({
@@ -115,7 +128,7 @@ import { IntentionalNever } from "./Types.js";
115
128
  * // To prevent this, use FiniteNumber.
116
129
  * age: FiniteNumber,
117
130
  * });
118
- * type Person = typeof Person.Type;
131
+ * interface Person extends InferType<typeof Person> {}
119
132
  *
120
133
  * const [PersonJson, personToPersonJson, personJsonToPerson] = json(
121
134
  * Person,
@@ -133,7 +146,7 @@ import { IntentionalNever } from "./Types.js";
133
146
  * expect(personJsonToPerson(personJson)).toEqual(person);
134
147
  * ```
135
148
  *
136
- * ### Error Formatting
149
+ * ## Error Formatting
137
150
  *
138
151
  * Evolu separates validation logic from human-readable messages. There are two
139
152
  * layers:
@@ -144,14 +157,14 @@ import { IntentionalNever } from "./Types.js";
144
157
  * custom errors (including nested composite types) and lets us override
145
158
  * selected messages.
146
159
  *
147
- * #### 1. Per-Type Formatter (recap)
160
+ * ### 1. Per-Type formatter
148
161
  *
149
162
  * ```ts
150
163
  * const r = String.fromUnknown(42);
151
164
  * if (!r.ok) console.error(formatStringError(r.error));
152
165
  * ```
153
166
  *
154
- * #### 2. Unified Formatter with Overrides
167
+ * ### 2. Unified formatter with overrides
155
168
  *
156
169
  * ```ts
157
170
  * // Override only what we care about; fall back to built-ins for the rest.
@@ -174,12 +187,14 @@ import { IntentionalNever } from "./Types.js";
174
187
  * tuple / union) and applies overrides only where specified, greatly reducing
175
188
  * boilerplate when formatting complex validation errors.
176
189
  *
177
- * ### Tip
190
+ * ## Naming
178
191
  *
179
- * If necessary, write `globalThis.String` instead of `String` to avoid naming
180
- * clashes with native types.
192
+ * Evolu Types intentionally use the same names as native JavaScript types
193
+ * (String, Number, Boolean, etc.). When you need to distinguish between an
194
+ * Evolu Type and the native type, use `globalThis` to reference the native one
195
+ * (e.g., `globalThis.String`, `globalThis.Number`).
181
196
  *
182
- * ### Design Decision: No Bidirectional Transformations
197
+ * ## Design decision
183
198
  *
184
199
  * Evolu Type intentionally does not support bidirectional transformations. It
185
200
  * previously did, but supporting that while keeping typed error fidelity added
@@ -188,28 +203,62 @@ import { IntentionalNever } from "./Types.js";
188
203
  * reverse transforms would not buy much. We may revisit this if we can design a
189
204
  * minimal, 100% safe API that preserves simplicity.
190
205
  *
191
- * ### Prepared for TC39 Hack Pipes
206
+ * ## Composition without pipe
192
207
  *
193
208
  * Take a look how `SimplePassword` is defined:
194
209
  *
195
210
  * ```ts
196
- * export const SimplePassword = brand(
211
+ * const SimplePassword = brand(
197
212
  * "SimplePassword",
198
213
  * minLength(8)(maxLength(64)(TrimmedString)),
199
214
  * );
200
215
  * ```
201
216
  *
202
- * Nested functions are often OK (if not, make a helper) and read well, but with
203
- * TC39 Hack pipes it would be clearer:
217
+ * Shallow nesting often fits one line. If it doesn't, split into named parts:
204
218
  *
205
219
  * ```ts
206
- * // TrimmedString
207
- * // |> minLength(8)(%)
208
- * // |> maxLength(64)(%)
209
- * // |> brand("SimplePassword", %)
220
+ * const Min8TrimmedString64 = minLength(8)(maxLength(64)(TrimmedString));
221
+ * const SimplePassword = brand("SimplePassword", Min8TrimmedString64);
222
+ * ```
223
+ *
224
+ * ## FAQ
225
+ *
226
+ * ### How do I create a generic interface like `FooState<T>`?
227
+ *
228
+ * TypeScript's {@link InferType} extracts a concrete type, not a generic one. We
229
+ * cannot write `interface FooState<T> extends InferType<typeof fooState<T>>`
230
+ * because `InferType` needs a concrete Type instance.
231
+ *
232
+ * The recommended approach is to define the generic interface manually, then
233
+ * create a Type factory that produces structurally compatible Types:
234
+ *
235
+ * ```ts
236
+ * // Define the generic interface manually
237
+ * interface FooState<T> {
238
+ * readonly value: T;
239
+ * readonly loading: boolean;
240
+ * }
241
+ *
242
+ * // Create a Type factory that produces Types matching the interface
243
+ * const fooState = <T extends AnyType>(valueType: T) =>
244
+ * object({
245
+ * value: valueType,
246
+ * loading: Boolean,
247
+ * });
248
+ *
249
+ * // Usage
250
+ * const StringFooState = fooState(String);
251
+ * type StringFooState = InferType<typeof StringFooState>;
252
+ *
253
+ * // The interface and inferred type are structurally compatible
254
+ * const state: FooState<string> = StringFooState.orThrow({
255
+ * value: "hi",
256
+ * loading: false,
257
+ * });
210
258
  * ```
211
259
  *
212
- * Note `minLength` and `maxLength` are curried because they are factories.
260
+ * This keeps the interface generic while having type-safe runtime validation
261
+ * for each concrete use.
213
262
  */
214
263
  export interface Type<
215
264
  Name extends TypeName,
@@ -238,6 +287,9 @@ export interface Type<
238
287
  /**
239
288
  * Creates `T` from an `Input` value, throwing an error if validation fails.
240
289
  *
290
+ * Use this where failure should crash the current flow instead of being
291
+ * handled locally.
292
+ *
241
293
  * Throws an Error with the Type validation error in its `cause` property,
242
294
  * making it debuggable while avoiding the need for custom error messages.
243
295
  *
@@ -245,36 +297,41 @@ export interface Type<
245
297
  *
246
298
  * **When to use:**
247
299
  *
248
- * - Configuration values that are guaranteed to be valid (e.g., hardcoded
249
- * constants)
250
- * - Application startup where failure should crash the program
300
+ * - Application startup or composition-root setup where errors must stop the
301
+ * program immediately. In Evolu apps, errors are handled by
302
+ * platform-specific `createRun` adapters at the app boundary.
303
+ * - Module-level constants
304
+ * - Test setup with values that are expected to be valid
251
305
  * - As an alternative to assertions when the Type error in the thrown Error's
252
306
  * `cause` provides sufficient debugging information
253
- * - Test code with known valid inputs (when error message clarity is not
254
- * critical; for better test error messages, use Vitest `schemaMatching` +
255
- * `assert` with `.is()`)
307
+ *
308
+ * Prefer `from` in ordinary application logic where the caller can recover,
309
+ * show validation errors, or choose a different flow.
310
+ *
311
+ * For clearer test failure messages on invalid input, use Vitest
312
+ * `schemaMatching` + `assert` with `.is()`.
256
313
  *
257
314
  * ### Example
258
315
  *
259
316
  * ```ts
260
- * // Good: Known valid constant
317
+ * // Good: Known valid constant
261
318
  * const maxRetries = PositiveInt.orThrow(3);
262
319
  *
263
- * // Good: App configuration that should crash on invalid values
264
- * const appName = SimpleName.orThrow("MyApp");
320
+ * // Good: App configuration that should crash on invalid values
321
+ * const appName = Name.orThrow("MyApp");
265
322
  *
266
- * // Good: Instead of assert when Type error is clear enough
323
+ * // Good: Instead of assert when Type error is clear enough
267
324
  * // Context makes it obvious: count increments from non-negative value
268
325
  * const currentCount = counts.get(id) ?? 0;
269
326
  * const newCount = PositiveInt.orThrow(currentCount + 1);
270
327
  *
271
- * // Good: Test setup with known valid values
328
+ * // Good: Test setup with known valid values
272
329
  * const testUser = User.orThrow({ name: "Alice", age: 30 });
273
330
  *
274
- * // Avoid: User input (use `from` instead)
331
+ * // Avoid: User input (use `from` instead)
275
332
  * const userAge = PositiveInt.orThrow(userInput); // Could crash!
276
333
  *
277
- * // Better: Handle user input gracefully
334
+ * // Better: Handle user input gracefully
278
335
  * const ageResult = PositiveInt.from(userInput);
279
336
  * if (!ageResult.ok) {
280
337
  * // Handle validation error
@@ -296,16 +353,16 @@ export interface Type<
296
353
  * ### Example
297
354
  *
298
355
  * ```ts
299
- * // Good: Optional user input
356
+ * // Good: Optional user input
300
357
  * const age = PositiveInt.orNull(userInput);
301
358
  * if (age != null) {
302
359
  * console.log("Valid age:", age);
303
360
  * }
304
361
  *
305
- * // Good: Default fallback
362
+ * // Good: Default fallback
306
363
  * const maxRetries = PositiveInt.orNull(config.retries) ?? 3;
307
364
  *
308
- * // Avoid: When you need to know why validation failed (use `from` instead)
365
+ * // Avoid: When you need to know why validation failed (use `from` instead)
309
366
  * const result = PositiveInt.from(userInput);
310
367
  * if (!result.ok) {
311
368
  * console.error(formatPositiveError(result.error));
@@ -420,7 +477,7 @@ export interface Type<
420
477
  /**
421
478
  * Unique identifier for a {@link Type}.
422
479
  *
423
- * @category Utilities
480
+ * @group Utilities
424
481
  */
425
482
  export type TypeName = Capitalize<string>;
426
483
 
@@ -444,12 +501,17 @@ export interface TypeErrorWithReason<
444
501
  readonly reason: Reason;
445
502
  }
446
503
 
504
+ /**
505
+ * A {@link Type} with all type parameters set to `any`.
506
+ *
507
+ * @group Utilities
508
+ */
447
509
  export type AnyType = Type<any, any, any, any, any, any>;
448
510
 
449
511
  /**
450
512
  * Extracts the name from a {@link Type}.
451
513
  *
452
- * @category Utilities
514
+ * @group Utilities
453
515
  */
454
516
  export type InferName<A extends AnyType> =
455
517
  A extends Type<infer Name, any, any, any, any, any> ? Name : never;
@@ -457,7 +519,20 @@ export type InferName<A extends AnyType> =
457
519
  /**
458
520
  * Extracts the type from a {@link Type}.
459
521
  *
460
- * @category Utilities
522
+ * ### Example
523
+ *
524
+ * ```ts
525
+ * const User = object({
526
+ * name: NonEmptyTrimmedString100,
527
+ * age: optional(PositiveInt),
528
+ * });
529
+ *
530
+ * // Use interface for objects. TypeScript displays the interface name
531
+ * // instead of expanding all properties.
532
+ * interface User extends InferType<typeof User> {}
533
+ * ```
534
+ *
535
+ * @group Utilities
461
536
  */
462
537
  export type InferType<A extends AnyType> =
463
538
  A extends Type<any, infer T, any, any, any, any> ? T : never;
@@ -465,7 +540,7 @@ export type InferType<A extends AnyType> =
465
540
  /**
466
541
  * Extracts the input type from a {@link Type}.
467
542
  *
468
- * @category Utilities
543
+ * @group Utilities
469
544
  */
470
545
  export type InferInput<A extends AnyType> =
471
546
  A extends Type<any, any, infer Input, any, any, any> ? Input : never;
@@ -473,7 +548,7 @@ export type InferInput<A extends AnyType> =
473
548
  /**
474
549
  * Extracts the specific error type from a {@link Type}.
475
550
  *
476
- * @category Utilities
551
+ * @group Utilities
477
552
  */
478
553
  export type InferError<A extends AnyType> =
479
554
  A extends Type<any, any, any, infer Error, any, any> ? Error : never;
@@ -481,7 +556,7 @@ export type InferError<A extends AnyType> =
481
556
  /**
482
557
  * Extracts the parent type from a {@link Type}.
483
558
  *
484
- * @category Utilities
559
+ * @group Utilities
485
560
  */
486
561
  export type InferParent<A extends AnyType> =
487
562
  A extends Type<any, any, any, any, infer Parent, any> ? Parent : never;
@@ -489,7 +564,7 @@ export type InferParent<A extends AnyType> =
489
564
  /**
490
565
  * Extracts the parent error type from a {@link Type}.
491
566
  *
492
- * @category Utilities
567
+ * @group Utilities
493
568
  */
494
569
  export type InferParentError<A extends AnyType> =
495
570
  A extends Type<any, any, any, any, any, infer ParentError>
@@ -499,7 +574,7 @@ export type InferParentError<A extends AnyType> =
499
574
  /**
500
575
  * Extracts all error types from a {@link Type}.
501
576
  *
502
- * @category Utilities
577
+ * @group Utilities
503
578
  */
504
579
  export type InferErrors<T extends AnyType> =
505
580
  T extends Type<any, any, any, infer Error, any, infer ParentError>
@@ -511,7 +586,7 @@ const EvoluTypeSymbol = Symbol("evolu.Type");
511
586
  /**
512
587
  * Checks if the given value is an {@link Type}.
513
588
  *
514
- * @category Utilities
589
+ * @group Utilities
515
590
  */
516
591
  export const isType = (value: unknown): value is AnyType =>
517
592
  typeof value === "object" && value !== null && EvoluTypeSymbol in value;
@@ -563,11 +638,9 @@ const createType = <
563
638
  if (result.ok) {
564
639
  return { value: result.value };
565
640
  }
566
- cachedStandardSchemaFormatTypeError ??= createFormatTypeError();
567
641
  return {
568
642
  issues: typeErrorToStandardSchemaIssues(
569
643
  result.error as TypeErrors<Error>,
570
- cachedStandardSchemaFormatTypeError,
571
644
  ),
572
645
  };
573
646
  },
@@ -592,7 +665,7 @@ const createType = <
592
665
  * );
593
666
  * ```
594
667
  *
595
- * @category Utilities
668
+ * @group Utilities
596
669
  */
597
670
  export const createTypeErrorFormatter =
598
671
  <Error extends TypeError>(
@@ -628,7 +701,7 @@ export type TypeErrorFormatter<Error extends TypeError> = (
628
701
  * );
629
702
  * ```
630
703
  *
631
- * @category Base Factories
704
+ * @group Base factories
632
705
  */
633
706
  export const base = <Name extends TypeName, T, Error extends TypeError>(
634
707
  name: Name,
@@ -652,7 +725,7 @@ export const base = <Name extends TypeName, T, Error extends TypeError>(
652
725
  * createBaseTypeErrorFormatter<StringError>();
653
726
  * ```
654
727
  *
655
- * @category Utilities
728
+ * @group Utilities
656
729
  */
657
730
  export const createBaseTypeErrorFormatter = <
658
731
  Error extends TypeError,
@@ -661,14 +734,17 @@ export const createBaseTypeErrorFormatter = <
661
734
  (error) => `A value ${error.value} is not a ${error.type.toLowerCase()}.`,
662
735
  );
663
736
 
664
- /** @category Base Types */
665
- export const Unknown = base<"Unknown", unknown, never>("Unknown", ok);
737
+ /** @group Base Types */
738
+ export const Unknown = /*#__PURE__*/ base<"Unknown", unknown, never>(
739
+ "Unknown",
740
+ ok,
741
+ );
666
742
 
667
743
  /**
668
- * @category Base Types
669
- * @category String
744
+ * @group Base Types
745
+ * @group String
670
746
  */
671
- export const String = base("String", (value) =>
747
+ export const String = /*#__PURE__*/ base("String", (value) =>
672
748
  typeof value === "string"
673
749
  ? ok(value)
674
750
  : err<StringError>({ type: "String", value }),
@@ -676,10 +752,11 @@ export const String = base("String", (value) =>
676
752
 
677
753
  export interface StringError extends TypeError<"String"> {}
678
754
 
679
- export const formatStringError = createBaseTypeErrorFormatter<StringError>();
755
+ export const formatStringError =
756
+ /*#__PURE__*/ createBaseTypeErrorFormatter<StringError>();
680
757
 
681
- /** @category Base Types */
682
- export const Number = base("Number", (value) =>
758
+ /** @group Base Types */
759
+ export const Number = /*#__PURE__*/ base("Number", (value) =>
683
760
  typeof value === "number"
684
761
  ? ok(value)
685
762
  : err<NumberError>({ type: "Number", value }),
@@ -687,10 +764,11 @@ export const Number = base("Number", (value) =>
687
764
 
688
765
  export interface NumberError extends TypeError<"Number"> {}
689
766
 
690
- export const formatNumberError = createBaseTypeErrorFormatter<NumberError>();
767
+ export const formatNumberError =
768
+ /*#__PURE__*/ createBaseTypeErrorFormatter<NumberError>();
691
769
 
692
- /** @category Base Types */
693
- export const BigInt = base("BigInt", (value) =>
770
+ /** @group Base Types */
771
+ export const BigInt = /*#__PURE__*/ base("BigInt", (value) =>
694
772
  typeof value === "bigint"
695
773
  ? ok(value)
696
774
  : err<BigIntError>({ type: "BigInt", value }),
@@ -698,10 +776,11 @@ export const BigInt = base("BigInt", (value) =>
698
776
 
699
777
  export interface BigIntError extends TypeError<"BigInt"> {}
700
778
 
701
- export const formatBigIntError = createBaseTypeErrorFormatter<BigIntError>();
779
+ export const formatBigIntError =
780
+ /*#__PURE__*/ createBaseTypeErrorFormatter<BigIntError>();
702
781
 
703
- /** @category Base Types */
704
- export const Boolean = base("Boolean", (value) =>
782
+ /** @group Base Types */
783
+ export const Boolean = /*#__PURE__*/ base("Boolean", (value) =>
705
784
  typeof value === "boolean"
706
785
  ? ok(value)
707
786
  : err<BooleanError>({ type: "Boolean", value }),
@@ -709,10 +788,11 @@ export const Boolean = base("Boolean", (value) =>
709
788
 
710
789
  export interface BooleanError extends TypeError<"Boolean"> {}
711
790
 
712
- export const formatBooleanError = createBaseTypeErrorFormatter<BooleanError>();
791
+ export const formatBooleanError =
792
+ /*#__PURE__*/ createBaseTypeErrorFormatter<BooleanError>();
713
793
 
714
- /** @category Base Types */
715
- export const Undefined = base("Undefined", (value) =>
794
+ /** @group Base Types */
795
+ export const Undefined = /*#__PURE__*/ base("Undefined", (value) =>
716
796
  value === undefined
717
797
  ? ok(value)
718
798
  : err<UndefinedError>({ type: "Undefined", value }),
@@ -721,20 +801,21 @@ export const Undefined = base("Undefined", (value) =>
721
801
  export interface UndefinedError extends TypeError<"Undefined"> {}
722
802
 
723
803
  export const formatUndefinedError =
724
- createBaseTypeErrorFormatter<UndefinedError>();
804
+ /*#__PURE__*/ createBaseTypeErrorFormatter<UndefinedError>();
725
805
 
726
- /** @category Base Types */
727
- export const Null = base("Null", (value) =>
806
+ /** @group Base Types */
807
+ export const Null = /*#__PURE__*/ base("Null", (value) =>
728
808
  value === null ? ok(value) : err<NullError>({ type: "Null", value }),
729
809
  );
730
810
 
731
811
  export interface NullError extends TypeError<"Null"> {}
732
812
 
733
- export const formatNullError = createBaseTypeErrorFormatter<NullError>();
813
+ export const formatNullError =
814
+ /*#__PURE__*/ createBaseTypeErrorFormatter<NullError>();
734
815
 
735
- /** @category Base Types */
736
- export const Function = base("Function", (value) =>
737
- typeof value === "function"
816
+ /** @group Base Types */
817
+ export const Function = /*#__PURE__*/ base("Function", (value) =>
818
+ isFunction(value)
738
819
  ? ok(value)
739
820
  : err<FunctionError>({ type: "Function", value }),
740
821
  );
@@ -742,19 +823,19 @@ export const Function = base("Function", (value) =>
742
823
  export interface FunctionError extends TypeError<"Function"> {}
743
824
 
744
825
  export const formatFunctionError =
745
- createBaseTypeErrorFormatter<FunctionError>();
826
+ /*#__PURE__*/ createBaseTypeErrorFormatter<FunctionError>();
746
827
 
747
- /** @category Base Types */
748
- export const Uint8Array = base("Uint8Array", (value) =>
749
- value instanceof globalThis.Uint8Array
750
- ? ok(value)
828
+ /** @group Base Types */
829
+ export const Uint8Array = /*#__PURE__*/ base("Uint8Array", (value) =>
830
+ Object.prototype.toString.call(value as object) === "[object Uint8Array]"
831
+ ? ok(value as globalThis.Uint8Array)
751
832
  : err<Uint8ArrayError>({ type: "Uint8Array", value }),
752
833
  );
753
834
 
754
835
  export interface Uint8ArrayError extends TypeError<"Uint8Array"> {}
755
836
 
756
837
  export const formatUint8ArrayError =
757
- createBaseTypeErrorFormatter<Uint8ArrayError>();
838
+ /*#__PURE__*/ createBaseTypeErrorFormatter<Uint8ArrayError>();
758
839
 
759
840
  /**
760
841
  * `instanceof` {@link Type}.
@@ -774,7 +855,7 @@ export const formatUint8ArrayError =
774
855
  * const error = UserInstance.from({}); // err
775
856
  * ```
776
857
  *
777
- * @category Base Factories
858
+ * @group Base factories
778
859
  */
779
860
  export const instanceOf = <T extends abstract new (...args: any) => any>(
780
861
  ctor: T,
@@ -802,16 +883,17 @@ export interface InstanceOfType<
802
883
  ctor: T;
803
884
  }
804
885
 
805
- export const formatInstanceOfError = createTypeErrorFormatter<InstanceOfError>(
806
- (error) => `The value ${error.value} is not an instance of ${error.ctor}.`,
807
- );
886
+ export const formatInstanceOfError =
887
+ /*#__PURE__*/ createTypeErrorFormatter<InstanceOfError>(
888
+ (error) => `The value ${error.value} is not an instance of ${error.ctor}.`,
889
+ );
808
890
 
809
891
  /**
810
892
  * JavaScript Date.
811
893
  *
812
- * @category Base Types
894
+ * @group Base Types
813
895
  */
814
- export const Date = instanceOf(globalThis.Date);
896
+ export const Date = /*#__PURE__*/ instanceOf(globalThis.Date);
815
897
 
816
898
  /**
817
899
  * Validates that an unknown value is an Evolu {@link Type} (i.e., satisfies
@@ -825,7 +907,7 @@ export const Date = instanceOf(globalThis.Date);
825
907
  * ```
826
908
  */
827
909
  // TODO: Rename to TypeInstance or something like that.
828
- export const EvoluType = base("EvoluType", (value) =>
910
+ export const EvoluType = /*#__PURE__*/ base("EvoluType", (value) =>
829
911
  isType(value)
830
912
  ? ok(value)
831
913
  : err<EvoluTypeError>({
@@ -836,19 +918,24 @@ export const EvoluType = base("EvoluType", (value) =>
836
918
 
837
919
  export interface EvoluTypeError extends TypeError<"EvoluType"> {}
838
920
 
839
- export const formatIsTypeError = createTypeErrorFormatter<EvoluTypeError>(
840
- (error) => `Value ${error.value} is not a valid Evolu Type.`,
841
- );
921
+ export const formatIsTypeError =
922
+ /*#__PURE__*/ createTypeErrorFormatter<EvoluTypeError>(
923
+ (error) => `Value ${error.value} is not a valid Evolu Type.`,
924
+ );
842
925
 
843
926
  /**
844
927
  * Branded {@link Type}.
845
928
  *
929
+ * Branding is the recommended way to define types in Evolu. Instead of using
930
+ * primitive types like `string` or `number` directly, wrap them with `brand` to
931
+ * create semantically meaningful types. See {@link Brand} for why this matters.
932
+ *
846
933
  * The `brand` Type Factory takes the name of a new {@link Brand}, a parent Type
847
934
  * to be branded, and the optional `refine` function for additional constraint.
848
935
  *
849
936
  * The `refine` function can be omitted if we only want to add a brand.
850
937
  *
851
- * ### Examples
938
+ * ### Example
852
939
  *
853
940
  * A simple `CurrencyCode` Type:
854
941
  *
@@ -967,7 +1054,7 @@ export const formatIsTypeError = createTypeErrorFormatter<EvoluTypeError>(
967
1054
  * );
968
1055
  * ```
969
1056
  *
970
- * @category Base Factories
1057
+ * @group Base factories
971
1058
  */
972
1059
  export function brand<
973
1060
  Name extends TypeName,
@@ -980,6 +1067,7 @@ export function brand<
980
1067
  refine: (value: Parent) => Result<Parent, RefineError>,
981
1068
  ): BrandType<ParentType, Name, RefineError, InferErrors<ParentType>>;
982
1069
 
1070
+ /** Without refine function. */
983
1071
  export function brand<Name extends TypeName, ParentType extends AnyType>(
984
1072
  name: Name,
985
1073
  parent: ParentType,
@@ -1003,15 +1091,15 @@ export function brand<
1003
1091
  ? (value: unknown) => {
1004
1092
  const parentResult = parent.fromUnknown(value);
1005
1093
  if (!parentResult.ok) return parentResult;
1006
- return refine(parentResult.value as IntentionalNever);
1094
+ return refine(parentResult.value as never);
1007
1095
  }
1008
1096
  : (value: unknown) => {
1009
1097
  const parentResult = parent.fromUnknown(value);
1010
1098
  if (!parentResult.ok)
1011
- return err<BrandWithoutRefineError<Name, IntentionalNever>>({
1099
+ return err<BrandWithoutRefineError<Name, never>>({
1012
1100
  type: name,
1013
1101
  value,
1014
- parentError: parentResult.error as IntentionalNever,
1102
+ parentError: parentResult.error as never,
1015
1103
  });
1016
1104
  return ok(parentResult.value);
1017
1105
  };
@@ -1019,7 +1107,7 @@ export function brand<
1019
1107
  return {
1020
1108
  ...createType("Brand", {
1021
1109
  fromUnknown,
1022
- fromParent: (refine ?? ok) as IntentionalNever,
1110
+ fromParent: (refine ?? ok) as never,
1023
1111
  }),
1024
1112
  brand: name,
1025
1113
  parentType: parent,
@@ -1053,12 +1141,15 @@ export interface BrandWithoutRefineError<
1053
1141
  /**
1054
1142
  * A three-letter ISO 4217 currency code (e.g., USD, EUR).
1055
1143
  *
1056
- * @category String
1144
+ * @group String
1057
1145
  */
1058
- export const CurrencyCode = brand("CurrencyCode", String, (value) =>
1059
- /^[A-Z]{3}$/.test(value)
1060
- ? ok(value)
1061
- : err<CurrencyCodeError>({ type: "CurrencyCode", value }),
1146
+ export const CurrencyCode = /*#__PURE__*/ brand(
1147
+ "CurrencyCode",
1148
+ String,
1149
+ (value) =>
1150
+ /^[A-Z]{3}$/.test(value)
1151
+ ? ok(value)
1152
+ : err<CurrencyCodeError>({ type: "CurrencyCode", value }),
1062
1153
  );
1063
1154
 
1064
1155
  export type CurrencyCode = typeof CurrencyCode.Type;
@@ -1066,7 +1157,7 @@ export type CurrencyCode = typeof CurrencyCode.Type;
1066
1157
  export interface CurrencyCodeError extends TypeError<"CurrencyCode"> {}
1067
1158
 
1068
1159
  export const formatCurrencyCodeError =
1069
- createTypeErrorFormatter<CurrencyCodeError>(
1160
+ /*#__PURE__*/ createTypeErrorFormatter<CurrencyCodeError>(
1070
1161
  (error) => `Invalid currency code: ${error.value}.`,
1071
1162
  );
1072
1163
 
@@ -1089,9 +1180,9 @@ export const formatCurrencyCodeError =
1089
1180
  * const error = DateIso.from("10000-01-01T00:00:00.000Z"); // err
1090
1181
  * ```
1091
1182
  *
1092
- * @category String
1183
+ * @group String
1093
1184
  */
1094
- export const DateIso = brand("DateIso", String, (value) => {
1185
+ export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => {
1095
1186
  if (value.length !== 24) {
1096
1187
  return err<DateIsoError>({ type: "DateIso", value });
1097
1188
  }
@@ -1111,9 +1202,10 @@ export type DateIso = typeof DateIso.Type;
1111
1202
 
1112
1203
  export interface DateIsoError extends TypeError<"DateIso"> {}
1113
1204
 
1114
- export const formatDateIsoError = createTypeErrorFormatter<DateIsoError>(
1115
- (error) => `The value ${error.value} is not a valid ISO 8601 date string.`,
1116
- );
1205
+ export const formatDateIsoError =
1206
+ /*#__PURE__*/ createTypeErrorFormatter<DateIsoError>(
1207
+ (error) => `The value ${error.value} is not a valid ISO 8601 date string.`,
1208
+ );
1117
1209
 
1118
1210
  export const dateToDateIso = (value: Date): Result<DateIso, DateIsoError> =>
1119
1211
  DateIso.fromParent(value.toISOString());
@@ -1137,7 +1229,18 @@ export const dateIsoToDate = (value: DateIso): Date =>
1137
1229
  * );
1138
1230
  * ```
1139
1231
  *
1140
- * @category Utilities
1232
+ * ### Numeric literal inference
1233
+ *
1234
+ * When using factories with numeric parameters (like `lessThan`, `maxLength`,
1235
+ * `between`), use numeric literals instead of expressions. TypeScript widens
1236
+ * expressions to `number`, losing the literal type in the brand name:
1237
+ *
1238
+ * ```ts
1239
+ * lessThan(100)(Number); // Brand<"LessThan100"> ✓
1240
+ * lessThan(100 - 1)(Number); // Brand<"LessThan" + number> ✗
1241
+ * ```
1242
+ *
1243
+ * @group Utilities
1141
1244
  */
1142
1245
  export type BrandFactory<
1143
1246
  Name extends TypeName,
@@ -1173,7 +1276,7 @@ export type BrandFactory<
1173
1276
  * type TrimmedNonEmptyString = typeof TrimmedNonEmptyString.Type;
1174
1277
  * ```
1175
1278
  *
1176
- * @category String
1279
+ * @group String
1177
1280
  */
1178
1281
  export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
1179
1282
  parent,
@@ -1186,9 +1289,10 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
1186
1289
 
1187
1290
  export interface TrimmedError extends TypeError<"Trimmed"> {}
1188
1291
 
1189
- export const formatTrimmedError = createTypeErrorFormatter<TrimmedError>(
1190
- (error) => `The value ${error.value} must be trimmed.`,
1191
- );
1292
+ export const formatTrimmedError =
1293
+ /*#__PURE__*/ createTypeErrorFormatter<TrimmedError>(
1294
+ (error) => `The value ${error.value} must be trimmed.`,
1295
+ );
1192
1296
 
1193
1297
  /**
1194
1298
  * Trimmed string
@@ -1196,9 +1300,9 @@ export const formatTrimmedError = createTypeErrorFormatter<TrimmedError>(
1196
1300
  * - Use `TrimmedString.is` to check if an unknown value is trimmed.
1197
1301
  * - Use `TrimmedString.from` to check if a string is trimmed.
1198
1302
  *
1199
- * @category String
1303
+ * @group String
1200
1304
  */
1201
- export const TrimmedString = trimmed(String);
1305
+ export const TrimmedString = /*#__PURE__*/ trimmed(String);
1202
1306
  export type TrimmedString = typeof TrimmedString.Type;
1203
1307
 
1204
1308
  export const trim = (value: string): TrimmedString =>
@@ -1207,6 +1311,8 @@ export const trim = (value: string): TrimmedString =>
1207
1311
  /**
1208
1312
  * Minimum length.
1209
1313
  *
1314
+ * Use numeric literal, not expression. See {@link BrandFactory}.
1315
+ *
1210
1316
  * ### Example
1211
1317
  *
1212
1318
  * ```ts
@@ -1214,8 +1320,8 @@ export const trim = (value: string): TrimmedString =>
1214
1320
  * const NonEmptyString = minLength(1)(String);
1215
1321
  * ```
1216
1322
  *
1217
- * @category String
1218
- * @category Array
1323
+ * @group String
1324
+ * @group Array
1219
1325
  */
1220
1326
  export const minLength: <Min extends number>(
1221
1327
  min: Min,
@@ -1231,14 +1337,17 @@ export interface MinLengthError<
1231
1337
  readonly min: Min;
1232
1338
  }
1233
1339
 
1234
- export const formatMinLengthError = createTypeErrorFormatter<MinLengthError>(
1235
- (error) =>
1236
- `The value ${error.value} does not meet the minimum length of ${error.min}.`,
1237
- );
1340
+ export const formatMinLengthError =
1341
+ /*#__PURE__*/ createTypeErrorFormatter<MinLengthError>(
1342
+ (error) =>
1343
+ `The value ${error.value} does not meet the minimum length of ${error.min}.`,
1344
+ );
1238
1345
 
1239
1346
  /**
1240
1347
  * Maximum length.
1241
1348
  *
1349
+ * Use numeric literal, not expression. See {@link BrandFactory}.
1350
+ *
1242
1351
  * ### Example
1243
1352
  *
1244
1353
  * ```ts
@@ -1246,8 +1355,8 @@ export const formatMinLengthError = createTypeErrorFormatter<MinLengthError>(
1246
1355
  * const String100 = maxLength(100)(String);
1247
1356
  * ```
1248
1357
  *
1249
- * @category String
1250
- * @category Array
1358
+ * @group String
1359
+ * @group Array
1251
1360
  */
1252
1361
  export const maxLength: <Max extends number>(
1253
1362
  max: Max,
@@ -1263,14 +1372,17 @@ export interface MaxLengthError<
1263
1372
  readonly max: Max;
1264
1373
  }
1265
1374
 
1266
- export const formatMaxLengthError = createTypeErrorFormatter<MaxLengthError>(
1267
- (error) =>
1268
- `The value ${error.value} exceeds the maximum length of ${error.max}.`,
1269
- );
1375
+ export const formatMaxLengthError =
1376
+ /*#__PURE__*/ createTypeErrorFormatter<MaxLengthError>(
1377
+ (error) =>
1378
+ `The value ${error.value} exceeds the maximum length of ${error.max}.`,
1379
+ );
1270
1380
 
1271
1381
  /**
1272
1382
  * Exact length.
1273
1383
  *
1384
+ * Use numeric literal, not expression. See {@link BrandFactory}.
1385
+ *
1274
1386
  * ### Example
1275
1387
  *
1276
1388
  * ```ts
@@ -1278,8 +1390,8 @@ export const formatMaxLengthError = createTypeErrorFormatter<MaxLengthError>(
1278
1390
  * const Length1String = length(1)(String);
1279
1391
  * ```
1280
1392
  *
1281
- * @category String
1282
- * @category Array
1393
+ * @group String
1394
+ * @group Array
1283
1395
  */
1284
1396
  export const length: <Exact extends number>(
1285
1397
  exact: Exact,
@@ -1297,49 +1409,52 @@ export interface LengthError<
1297
1409
  readonly exact: Exact;
1298
1410
  }
1299
1411
 
1300
- export const formatLengthError = createTypeErrorFormatter<LengthError>(
1301
- (error) =>
1302
- `The value ${error.value} does not have the required length of ${error.exact}.`,
1303
- );
1412
+ export const formatLengthError =
1413
+ /*#__PURE__*/ createTypeErrorFormatter<LengthError>(
1414
+ (error) =>
1415
+ `The value ${error.value} does not have the required length of ${error.exact}.`,
1416
+ );
1304
1417
 
1305
- /** @category String */
1306
- export const NonEmptyString = minLength(1)(String);
1418
+ /** @group String */
1419
+ export const NonEmptyString = /*#__PURE__*/ minLength(1)(String);
1307
1420
  export type NonEmptyString = typeof NonEmptyString.Type;
1308
1421
 
1309
- /** @category String */
1310
- export const String100 = maxLength(100)(String);
1422
+ /** @group String */
1423
+ export const String100 = /*#__PURE__*/ maxLength(100)(String);
1311
1424
  export type String100 = typeof String100.Type;
1312
1425
 
1313
- /** @category String */
1314
- export const String1000 = maxLength(1000)(String);
1426
+ /** @group String */
1427
+ export const String1000 = /*#__PURE__*/ maxLength(1000)(String);
1315
1428
  export type String1000 = typeof String1000.Type;
1316
1429
 
1317
- /** @category String */
1318
- export const NonEmptyString100 = minLength(1)(String100);
1430
+ /** @group String */
1431
+ export const NonEmptyString100 = /*#__PURE__*/ minLength(1)(String100);
1319
1432
  export type NonEmptyString100 = typeof NonEmptyString100.Type;
1320
1433
 
1321
- /** @category String */
1322
- export const NonEmptyString1000 = minLength(1)(String1000);
1434
+ /** @group String */
1435
+ export const NonEmptyString1000 = /*#__PURE__*/ minLength(1)(String1000);
1323
1436
  export type NonEmptyString1000 = typeof NonEmptyString1000.Type;
1324
1437
 
1325
- /** @category String */
1326
- export const NonEmptyTrimmedString = minLength(1)(TrimmedString);
1438
+ /** @group String */
1439
+ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
1327
1440
  export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Type;
1328
1441
 
1329
- /** @category String */
1330
- export const TrimmedString100 = maxLength(100)(TrimmedString);
1442
+ /** @group String */
1443
+ export const TrimmedString100 = /*#__PURE__*/ maxLength(100)(TrimmedString);
1331
1444
  export type TrimmedString100 = typeof TrimmedString100.Type;
1332
1445
 
1333
- /** @category String */
1334
- export const TrimmedString1000 = maxLength(1000)(TrimmedString);
1446
+ /** @group String */
1447
+ export const TrimmedString1000 = /*#__PURE__*/ maxLength(1000)(TrimmedString);
1335
1448
  export type TrimmedString1000 = typeof TrimmedString1000.Type;
1336
1449
 
1337
- /** @category String */
1338
- export const NonEmptyTrimmedString100 = minLength(1)(TrimmedString100);
1450
+ /** @group String */
1451
+ export const NonEmptyTrimmedString100 =
1452
+ /*#__PURE__*/ minLength(1)(TrimmedString100);
1339
1453
  export type NonEmptyTrimmedString100 = typeof NonEmptyTrimmedString100.Type;
1340
1454
 
1341
- /** @category String */
1342
- export const NonEmptyTrimmedString1000 = minLength(1)(TrimmedString1000);
1455
+ /** @group String */
1456
+ export const NonEmptyTrimmedString1000 =
1457
+ /*#__PURE__*/ minLength(1)(TrimmedString1000);
1343
1458
  export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
1344
1459
 
1345
1460
  /**
@@ -1349,20 +1464,24 @@ export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
1349
1464
  * safely on the user's device using cryptographically secure random number
1350
1465
  * generation, ensuring it remains private and unique.
1351
1466
  *
1352
- * @category String
1467
+ * @group String
1353
1468
  */
1354
- export const Mnemonic = brand("Mnemonic", NonEmptyTrimmedString, (value) =>
1355
- bip39.validateMnemonic(value, wordlist)
1356
- ? ok(value)
1357
- : err<MnemonicError>({ type: "Mnemonic", value }),
1469
+ export const Mnemonic = /*#__PURE__*/ brand(
1470
+ "Mnemonic",
1471
+ NonEmptyTrimmedString,
1472
+ (value) =>
1473
+ bip39.validateMnemonic(value, wordlist)
1474
+ ? ok(value)
1475
+ : err<MnemonicError>({ type: "Mnemonic", value }),
1358
1476
  );
1359
1477
  export type Mnemonic = typeof Mnemonic.Type;
1360
1478
 
1361
1479
  export interface MnemonicError extends TypeError<"Mnemonic"> {}
1362
1480
 
1363
- export const formatMnemonicError = createTypeErrorFormatter<MnemonicError>(
1364
- (error) => `Invalid BIP39 mnemonic: ${error.value}.`,
1365
- );
1481
+ export const formatMnemonicError =
1482
+ /*#__PURE__*/ createTypeErrorFormatter<MnemonicError>(
1483
+ (error) => `Invalid BIP39 mnemonic: ${error.value}.`,
1484
+ );
1366
1485
 
1367
1486
  /**
1368
1487
  * String matching a regular expression.
@@ -1373,7 +1492,7 @@ export const formatMnemonicError = createTypeErrorFormatter<MnemonicError>(
1373
1492
  * const Alphanumeric = regex("Alphanumeric", /^[a-z0-9]+$/i)(String);
1374
1493
  * ```
1375
1494
  *
1376
- * @category String
1495
+ * @group String
1377
1496
  */
1378
1497
  export const regex: <Name extends TypeName>(
1379
1498
  name: Name,
@@ -1399,10 +1518,11 @@ export interface RegexError<
1399
1518
  readonly pattern: RegExp;
1400
1519
  }
1401
1520
 
1402
- export const formatRegexError = createTypeErrorFormatter<RegexError>(
1403
- (error) =>
1404
- `The value ${error.value} does not match the pattern for ${error.name}: ${error.pattern}.`,
1405
- );
1521
+ export const formatRegexError =
1522
+ /*#__PURE__*/ createTypeErrorFormatter<RegexError>(
1523
+ (error) =>
1524
+ `The value ${error.value} does not match the pattern for ${error.name}: ${error.pattern}.`,
1525
+ );
1406
1526
 
1407
1527
  /**
1408
1528
  * URL-safe string.
@@ -1429,9 +1549,12 @@ export const formatRegexError = createTypeErrorFormatter<RegexError>(
1429
1549
  * }
1430
1550
  * ```
1431
1551
  *
1432
- * @category String
1552
+ * @group String
1433
1553
  */
1434
- export const UrlSafeString = regex("UrlSafeString", /^[A-Za-z0-9_-]+$/)(String);
1554
+ export const UrlSafeString = /*#__PURE__*/ regex(
1555
+ "UrlSafeString",
1556
+ /^[A-Za-z0-9_-]+$/,
1557
+ )(String);
1435
1558
  export type UrlSafeString = typeof UrlSafeString.Type;
1436
1559
  export type UrlSafeStringError = typeof UrlSafeString.Error;
1437
1560
 
@@ -1441,9 +1564,9 @@ export type UrlSafeStringError = typeof UrlSafeString.Error;
1441
1564
  * Encode with {@link uint8ArrayToBase64Url}, decode with
1442
1565
  * {@link base64UrlToUint8Array}.
1443
1566
  *
1444
- * @category String
1567
+ * @group String
1445
1568
  */
1446
- export const Base64Url = brand(
1569
+ export const Base64Url = /*#__PURE__*/ brand(
1447
1570
  "Base64Url",
1448
1571
  String,
1449
1572
  (value: string): Result<string, Base64UrlError> => {
@@ -1466,9 +1589,10 @@ export const Base64Url = brand(
1466
1589
  export type Base64Url = typeof Base64Url.Type;
1467
1590
  export interface Base64UrlError extends TypeError<"Base64Url"> {}
1468
1591
 
1469
- export const formatBase64UrlError = createTypeErrorFormatter<Base64UrlError>(
1470
- (error) => `The value ${error.value} is not a valid Base64Url string.`,
1471
- );
1592
+ export const formatBase64UrlError =
1593
+ /*#__PURE__*/ createTypeErrorFormatter<Base64UrlError>(
1594
+ (error) => `The value ${error.value} is not a valid Base64Url string.`,
1595
+ );
1472
1596
 
1473
1597
  const base64UrlOptions = { alphabet: "base64url", omitPadding: true };
1474
1598
 
@@ -1520,33 +1644,35 @@ export const base64UrlToUint8Array: (str: Base64Url) => Uint8Array =
1520
1644
  };
1521
1645
 
1522
1646
  /**
1523
- * Simple alphanumeric string for naming in file systems, URLs, and identifiers.
1647
+ * Alphanumeric string for naming in file systems, URLs, and identifiers.
1524
1648
  *
1525
1649
  * Uses the same safe alphabet as {@link UrlSafeString} (letters, digits, `-`,
1526
- * `_`). See `UrlSafeString` for details.
1650
+ * `_`).
1527
1651
  *
1528
1652
  * The string must be between 1 and 64 characters.
1529
1653
  *
1530
1654
  * ### Example
1531
1655
  *
1532
1656
  * ```ts
1533
- * const result = SimpleName.from("data-report-123");
1657
+ * const result = Name.from("data-report-123");
1534
1658
  * if (result.ok) {
1535
- * console.log("Valid SimpleName string:", result.value);
1659
+ * console.log("Valid Name string:", result.value);
1536
1660
  * } else {
1537
- * console.error("Invalid SimpleName string:", result.error);
1661
+ * console.error("Invalid Name string:", result.error);
1538
1662
  * }
1539
1663
  * ```
1540
1664
  *
1541
- * @category String
1665
+ * @group String
1542
1666
  */
1543
- export const SimpleName = brand("SimpleName", UrlSafeString, (value) =>
1667
+ export const Name = /*#__PURE__*/ brand("Name", UrlSafeString, (value) =>
1544
1668
  value.length >= 1 && value.length <= 64
1545
1669
  ? ok(value)
1546
- : err<SimpleNameError>({ type: "SimpleName", value }),
1670
+ : err<NameError>({ type: "Name", value }),
1547
1671
  );
1548
- export type SimpleName = typeof SimpleName.Type;
1549
- export interface SimpleNameError extends TypeError<"SimpleName"> {}
1672
+ export type Name = typeof Name.Type;
1673
+ export interface NameError extends TypeError<"Name"> {}
1674
+
1675
+ export const testName = /*#__PURE__*/ Name.orThrow("Name");
1550
1676
 
1551
1677
  /**
1552
1678
  * Trimmed string between 8 and 64 characters, branded as `SimplePassword`.
@@ -1570,11 +1696,11 @@ export interface SimpleNameError extends TypeError<"SimpleName"> {}
1570
1696
  * // |> brand("SimplePassword", %)
1571
1697
  * ```
1572
1698
  *
1573
- * @category String
1699
+ * @group String
1574
1700
  */
1575
- export const SimplePassword = brand(
1701
+ export const SimplePassword = /*#__PURE__*/ brand(
1576
1702
  "SimplePassword",
1577
- minLength(8)(maxLength(64)(TrimmedString)),
1703
+ /*#__PURE__*/ minLength(8)(/*#__PURE__*/ maxLength(64)(TrimmedString)),
1578
1704
  );
1579
1705
  export type SimplePassword = typeof SimplePassword.Type;
1580
1706
 
@@ -1605,16 +1731,16 @@ export const formatSimplePasswordError = (
1605
1731
  * copied (logs, URLs, exports); only use it when you explicitly want insertion
1606
1732
  * locality for very large write‑heavy tables and accept timestamp exposure.
1607
1733
  *
1608
- * ### Future
1734
+ * ## Future
1609
1735
  *
1610
1736
  * A possible hybrid masked‑time approach (`timestamp ^ H(cluster_id, timestamp
1611
1737
  *
1612
1738
  * > > N)`) could provide locality without exposing raw creation time. See
1613
1739
  * > > https://brooker.co.za/blog/2025/10/22/uuidv7.html
1614
1740
  *
1615
- * @category String
1741
+ * @group String
1616
1742
  */
1617
- export const Id = brand("Id", String, (value) =>
1743
+ export const Id = /*#__PURE__*/ brand("Id", String, (value) =>
1618
1744
  value.length === 22 && Base64Url.fromParent(value).ok
1619
1745
  ? ok(value)
1620
1746
  : err<IdError>({ type: "Id", value }),
@@ -1623,7 +1749,7 @@ export type Id = typeof Id.Type;
1623
1749
 
1624
1750
  export interface IdError extends TypeError<"Id"> {}
1625
1751
 
1626
- export const formatIdError = createTypeErrorFormatter<IdError>(
1752
+ export const formatIdError = /*#__PURE__*/ createTypeErrorFormatter<IdError>(
1627
1753
  (error) => `The value ${error.value} is not a valid Id.`,
1628
1754
  );
1629
1755
 
@@ -1678,7 +1804,7 @@ export const createId = <B extends string = never>(
1678
1804
  * external string from the generated {@link Id}. If you need to preserve the
1679
1805
  * original external ID, store it in a separate column.
1680
1806
  *
1681
- * @category String
1807
+ * @group String
1682
1808
  */
1683
1809
  export const createIdFromString = <B extends string = never>(
1684
1810
  value: string,
@@ -1723,7 +1849,9 @@ export const createIdAsUuidv7 = <B extends string = never>(
1723
1849
  id[6] = (id[6] & 0x0f) | 0x70;
1724
1850
  id[8] = (id[8] & 0x3f) | 0x80;
1725
1851
 
1726
- return id as unknown as [B] extends [never] ? Id : Id & Brand<B>;
1852
+ return uint8ArrayToBase64Url(id) as unknown as [B] extends [never]
1853
+ ? Id
1854
+ : Id & Brand<B>;
1727
1855
  };
1728
1856
 
1729
1857
  /**
@@ -1739,7 +1867,7 @@ export const createIdAsUuidv7 = <B extends string = never>(
1739
1867
  * type TodoId = typeof TodoId.Type;
1740
1868
  * ```
1741
1869
  *
1742
- * @category String
1870
+ * @group String
1743
1871
  */
1744
1872
  export const id = <Table extends TypeName>(table: Table): TableId<Table> => {
1745
1873
  const fromUnknown = (value: unknown) => {
@@ -1779,12 +1907,16 @@ export interface TableIdError<
1779
1907
  readonly table: Table;
1780
1908
  }
1781
1909
 
1782
- export const formatTableIdError = createTypeErrorFormatter<TableIdError>(
1783
- (error) => `Invalid Id for table ${error.table}: ${error.value}.`,
1784
- );
1910
+ export const formatTableIdError =
1911
+ /*#__PURE__*/ createTypeErrorFormatter<TableIdError>(
1912
+ (error) => `Invalid Id for table ${error.table}: ${error.value}.`,
1913
+ );
1785
1914
 
1786
1915
  /** Binary representation of an {@link Id}. */
1787
- export const IdBytes = brand("IdBytes", length(16)(Uint8Array));
1916
+ export const IdBytes = /*#__PURE__*/ brand(
1917
+ "IdBytes",
1918
+ /*#__PURE__*/ length(16)(Uint8Array),
1919
+ );
1788
1920
  export type IdBytes = typeof IdBytes.Type;
1789
1921
 
1790
1922
  export const idBytesTypeValueLength = 16 as NonNegativeInt;
@@ -1809,7 +1941,7 @@ export const idBytesToId = (idBytes: IdBytes): Id =>
1809
1941
  * const errorResult = PositiveNumber.from(-5); // err
1810
1942
  * ```
1811
1943
  *
1812
- * @category Number
1944
+ * @group Number
1813
1945
  */
1814
1946
  export const positive: BrandFactory<"Positive", number, PositiveError> = (
1815
1947
  parent,
@@ -1820,9 +1952,10 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
1820
1952
 
1821
1953
  export interface PositiveError extends TypeError<"Positive"> {}
1822
1954
 
1823
- export const formatPositiveError = createTypeErrorFormatter<PositiveError>(
1824
- (error) => `The value ${error.value} must be positive (> 0).`,
1825
- );
1955
+ export const formatPositiveError =
1956
+ /*#__PURE__*/ createTypeErrorFormatter<PositiveError>(
1957
+ (error) => `The value ${error.value} must be positive (> 0).`,
1958
+ );
1826
1959
 
1827
1960
  /**
1828
1961
  * Negative number (< 0).
@@ -1833,7 +1966,7 @@ export const formatPositiveError = createTypeErrorFormatter<PositiveError>(
1833
1966
  * const NegativeNumber = negative(Number);
1834
1967
  * ```
1835
1968
  *
1836
- * @category Number
1969
+ * @group Number
1837
1970
  */
1838
1971
  export const negative: BrandFactory<"Negative", number, NegativeError> = (
1839
1972
  parent,
@@ -1844,9 +1977,10 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
1844
1977
 
1845
1978
  export interface NegativeError extends TypeError<"Negative"> {}
1846
1979
 
1847
- export const formatNegativeError = createTypeErrorFormatter<NegativeError>(
1848
- (error) => `The value ${error.value} must be negative (< 0).`,
1849
- );
1980
+ export const formatNegativeError =
1981
+ /*#__PURE__*/ createTypeErrorFormatter<NegativeError>(
1982
+ (error) => `The value ${error.value} must be negative (< 0).`,
1983
+ );
1850
1984
 
1851
1985
  /**
1852
1986
  * Non-positive number (≤ 0).
@@ -1857,7 +1991,7 @@ export const formatNegativeError = createTypeErrorFormatter<NegativeError>(
1857
1991
  * const NonPositiveNumber = nonPositive(Number);
1858
1992
  * ```
1859
1993
  *
1860
- * @category Number
1994
+ * @group Number
1861
1995
  */
1862
1996
  export const nonPositive: BrandFactory<
1863
1997
  "NonPositive",
@@ -1873,7 +2007,7 @@ export const nonPositive: BrandFactory<
1873
2007
  export interface NonPositiveError extends TypeError<"NonPositive"> {}
1874
2008
 
1875
2009
  export const formatNonPositiveError =
1876
- createTypeErrorFormatter<NonPositiveError>(
2010
+ /*#__PURE__*/ createTypeErrorFormatter<NonPositiveError>(
1877
2011
  (error) => `The value ${error.value} must be non-positive (≤ 0).`,
1878
2012
  );
1879
2013
 
@@ -1886,7 +2020,7 @@ export const formatNonPositiveError =
1886
2020
  * const NonNegativeNumber = nonNegative(Number);
1887
2021
  * ```
1888
2022
  *
1889
- * @category Number
2023
+ * @group Number
1890
2024
  */
1891
2025
  export const nonNegative: BrandFactory<
1892
2026
  "NonNegative",
@@ -1902,40 +2036,40 @@ export const nonNegative: BrandFactory<
1902
2036
  export interface NonNegativeError extends TypeError<"NonNegative"> {}
1903
2037
 
1904
2038
  export const formatNonNegativeError =
1905
- createTypeErrorFormatter<NonNegativeError>(
2039
+ /*#__PURE__*/ createTypeErrorFormatter<NonNegativeError>(
1906
2040
  (error) => `The value ${error.value} must be non-negative (≥ 0).`,
1907
2041
  );
1908
2042
 
1909
2043
  /**
1910
2044
  * Non-negative number (≥ 0).
1911
2045
  *
1912
- * @category Number
2046
+ * @group Number
1913
2047
  */
1914
- export const NonNegativeNumber = nonNegative(Number);
2048
+ export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
1915
2049
  export type NonNegativeNumber = typeof NonNegativeNumber.Type;
1916
2050
 
1917
2051
  /**
1918
2052
  * Positive number (> 0).
1919
2053
  *
1920
- * @category Number
2054
+ * @group Number
1921
2055
  */
1922
- export const PositiveNumber = positive(NonNegativeNumber);
2056
+ export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
1923
2057
  export type PositiveNumber = typeof PositiveNumber.Type;
1924
2058
 
1925
2059
  /**
1926
2060
  * Non-positive number (≤ 0).
1927
2061
  *
1928
- * @category Number
2062
+ * @group Number
1929
2063
  */
1930
- export const NonPositiveNumber = nonPositive(Number);
2064
+ export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
1931
2065
  export type NonPositiveNumber = typeof NonPositiveNumber.Type;
1932
2066
 
1933
2067
  /**
1934
2068
  * Negative number (< 0).
1935
2069
  *
1936
- * @category Number
2070
+ * @group Number
1937
2071
  */
1938
- export const NegativeNumber = negative(NonPositiveNumber);
2072
+ export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
1939
2073
  export type NegativeNumber = typeof NegativeNumber.Type;
1940
2074
 
1941
2075
  /**
@@ -1944,12 +2078,17 @@ export type NegativeNumber = typeof NegativeNumber.Type;
1944
2078
  * ### Example
1945
2079
  *
1946
2080
  * ```ts
1947
- * const Int = int(Number);
2081
+ * const Int = safeInt(Number);
1948
2082
  * ```
1949
2083
  *
1950
- * @category Number
2084
+ * Note: This helper was previously named `int`. A Next.js regression produced
2085
+ * runtime `int is not defined`, so we keep the implementation under the
2086
+ * `safeInt` alias.
2087
+ *
2088
+ * @group Number
1951
2089
  */
1952
- export const int: BrandFactory<"Int", number, IntError> = (parent) =>
2090
+
2091
+ const safeInt: BrandFactory<"Int", number, IntError> = (parent) =>
1953
2092
  brand("Int", parent, (value) =>
1954
2093
  globalThis.Number.isSafeInteger(value)
1955
2094
  ? ok(value)
@@ -1958,59 +2097,67 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
1958
2097
 
1959
2098
  export interface IntError extends TypeError<"Int"> {}
1960
2099
 
1961
- export const formatIntError = createTypeErrorFormatter<IntError>(
2100
+ export const formatIntError = /*#__PURE__*/ createTypeErrorFormatter<IntError>(
1962
2101
  (error) => `The value ${error.value} must be an integer.`,
1963
2102
  );
1964
2103
 
1965
2104
  /**
1966
2105
  * Integer within the safe range of JavaScript numbers.
1967
2106
  *
1968
- * @category Number
2107
+ * @group Number
1969
2108
  */
1970
- export const Int = int(Number);
2109
+ export const Int = /*#__PURE__*/ safeInt(Number);
1971
2110
  export type Int = typeof Int.Type;
1972
2111
 
1973
2112
  /**
1974
2113
  * Non-negative integer (≥ 0).
1975
2114
  *
1976
- * @category Number
2115
+ * @group Number
1977
2116
  */
1978
- export const NonNegativeInt = nonNegative(Int);
2117
+ export const NonNegativeInt = /*#__PURE__*/ nonNegative(Int);
1979
2118
  export type NonNegativeInt = typeof NonNegativeInt.Type;
1980
2119
 
2120
+ /** Minimum {@link NonNegativeInt} value (0). */
2121
+ export const zeroNonNegativeInt = /*#__PURE__*/ NonNegativeInt.orThrow(0);
2122
+
1981
2123
  /**
1982
2124
  * Positive integer (> 0).
1983
2125
  *
1984
- * @category Number
2126
+ * @group Number
1985
2127
  */
1986
- export const PositiveInt = positive(NonNegativeInt);
2128
+ export const PositiveInt = /*#__PURE__*/ positive(NonNegativeInt);
1987
2129
  export type PositiveInt = typeof PositiveInt.Type;
1988
2130
 
1989
- /** Maximum safe positive integer value for practically infinite operations. */
1990
- export const maxPositiveInt = PositiveInt.orThrow(
2131
+ /** {@link PositiveInt} value 1. */
2132
+ export const onePositiveInt = /*#__PURE__*/ PositiveInt.orThrow(1);
2133
+
2134
+ /** Maximum {@link PositiveInt} value (MAX_SAFE_INTEGER). */
2135
+ export const maxPositiveInt = /*#__PURE__*/ PositiveInt.orThrow(
1991
2136
  globalThis.Number.MAX_SAFE_INTEGER,
1992
2137
  );
1993
2138
 
1994
2139
  /**
1995
2140
  * Non-positive integer (≤ 0).
1996
2141
  *
1997
- * @category Number
2142
+ * @group Number
1998
2143
  */
1999
- export const NonPositiveInt = nonPositive(Int);
2144
+ export const NonPositiveInt = /*#__PURE__*/ nonPositive(Int);
2000
2145
  export type NonPositiveInt = typeof NonPositiveInt.Type;
2001
2146
 
2002
2147
  /**
2003
2148
  * Negative integer (< 0).
2004
2149
  *
2005
- * @category Number
2150
+ * @group Number
2006
2151
  */
2007
- export const NegativeInt = negative(NonPositiveInt);
2152
+ export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
2008
2153
  export type NegativeInt = typeof NegativeInt.Type;
2009
2154
 
2010
2155
  /**
2011
2156
  * Number greater than a specified value.
2012
2157
  *
2013
- * @category Number
2158
+ * Use numeric literal, not expression. See {@link BrandFactory}.
2159
+ *
2160
+ * @group Number
2014
2161
  */
2015
2162
  export const greaterThan: <Min extends number>(
2016
2163
  min: Min,
@@ -2027,14 +2174,16 @@ export interface GreaterThanError<
2027
2174
  }
2028
2175
 
2029
2176
  export const formatGreaterThanError =
2030
- createTypeErrorFormatter<GreaterThanError>(
2177
+ /*#__PURE__*/ createTypeErrorFormatter<GreaterThanError>(
2031
2178
  (error) => `The value ${error.value} is not > ${error.min}.`,
2032
2179
  );
2033
2180
 
2034
2181
  /**
2035
2182
  * Number less than a specified value.
2036
2183
  *
2037
- * @category Number
2184
+ * Use numeric literal, not expression. See {@link BrandFactory}.
2185
+ *
2186
+ * @group Number
2038
2187
  */
2039
2188
  export const lessThan: <Max extends number>(
2040
2189
  max: Max,
@@ -2050,14 +2199,17 @@ export interface LessThanError<
2050
2199
  readonly max: Max;
2051
2200
  }
2052
2201
 
2053
- export const formatLessThanError = createTypeErrorFormatter<LessThanError>(
2054
- (error) => `The value ${error.value} is not < ${error.max}.`,
2055
- );
2202
+ export const formatLessThanError =
2203
+ /*#__PURE__*/ createTypeErrorFormatter<LessThanError>(
2204
+ (error) => `The value ${error.value} is not < ${error.max}.`,
2205
+ );
2056
2206
 
2057
2207
  /**
2058
2208
  * Number ≥ a specified value.
2059
2209
  *
2060
- * @category Number
2210
+ * Use numeric literal, not expression. See {@link BrandFactory}.
2211
+ *
2212
+ * @group Number
2061
2213
  */
2062
2214
  export const greaterThanOrEqualTo: <Min extends number>(
2063
2215
  min: Min,
@@ -2079,14 +2231,16 @@ export interface GreaterThanOrEqualToError<
2079
2231
  }
2080
2232
 
2081
2233
  export const formatGreaterThanOrEqualToError =
2082
- createTypeErrorFormatter<GreaterThanOrEqualToError>(
2234
+ /*#__PURE__*/ createTypeErrorFormatter<GreaterThanOrEqualToError>(
2083
2235
  (error) => `The value ${error.value} is not >= ${error.min}.`,
2084
2236
  );
2085
2237
 
2086
2238
  /**
2087
2239
  * Number ≤ a specified value.
2088
2240
  *
2089
- * @category Number
2241
+ * Use numeric literal, not expression. See {@link BrandFactory}.
2242
+ *
2243
+ * @group Number
2090
2244
  */
2091
2245
  export const lessThanOrEqualTo: <Max extends number>(
2092
2246
  max: Max,
@@ -2106,14 +2260,14 @@ export interface LessThanOrEqualToError<
2106
2260
  }
2107
2261
 
2108
2262
  export const formatLessThanOrEqualToError =
2109
- createTypeErrorFormatter<LessThanOrEqualToError>(
2263
+ /*#__PURE__*/ createTypeErrorFormatter<LessThanOrEqualToError>(
2110
2264
  (error) => `The value ${error.value} is not <= ${error.max}.`,
2111
2265
  );
2112
2266
 
2113
2267
  /**
2114
2268
  * Number that is not NaN.
2115
2269
  *
2116
- * @category Number
2270
+ * @group Number
2117
2271
  */
2118
2272
  export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
2119
2273
  brand("NonNaN", parent, (value) =>
@@ -2124,18 +2278,19 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
2124
2278
 
2125
2279
  export interface NonNaNError extends TypeError<"NonNaN"> {}
2126
2280
 
2127
- export const formatNonNaNError = createTypeErrorFormatter<NonNaNError>(
2128
- () => `The value must not be NaN.`,
2129
- );
2281
+ export const formatNonNaNError =
2282
+ /*#__PURE__*/ createTypeErrorFormatter<NonNaNError>(
2283
+ () => `The value must not be NaN.`,
2284
+ );
2130
2285
 
2131
- /** @category Number */
2132
- export const NonNaNNumber = nonNaN(Number);
2286
+ /** @group Number */
2287
+ export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
2133
2288
  export type NonNaNNumber = typeof NonNaNNumber.Type;
2134
2289
 
2135
2290
  /**
2136
2291
  * Finite number.
2137
2292
  *
2138
- * @category Number
2293
+ * @group Number
2139
2294
  */
2140
2295
  export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
2141
2296
  brand("Finite", parent, (value) =>
@@ -2146,9 +2301,10 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
2146
2301
 
2147
2302
  export interface FiniteError extends TypeError<"Finite"> {}
2148
2303
 
2149
- export const formatFiniteError = createTypeErrorFormatter<FiniteError>(
2150
- (error) => `The value ${error.value} must be finite.`,
2151
- );
2304
+ export const formatFiniteError =
2305
+ /*#__PURE__*/ createTypeErrorFormatter<FiniteError>(
2306
+ (error) => `The value ${error.value} must be finite.`,
2307
+ );
2152
2308
 
2153
2309
  /**
2154
2310
  * Finite number.
@@ -2161,15 +2317,17 @@ export const formatFiniteError = createTypeErrorFormatter<FiniteError>(
2161
2317
  * finite (e.g., `Infinity`, `-Infinity`, or `NaN`). Using `FiniteNumber` helps
2162
2318
  * prevent these unexpected behaviors when working with JSON serialization.
2163
2319
  *
2164
- * @category Number
2320
+ * @group Number
2165
2321
  */
2166
- export const FiniteNumber = finite(Number);
2322
+ export const FiniteNumber = /*#__PURE__*/ finite(Number);
2167
2323
  export type FiniteNumber = typeof FiniteNumber.Type;
2168
2324
 
2169
2325
  /**
2170
2326
  * Number that is a multiple of a divisor.
2171
2327
  *
2172
- * @category Number
2328
+ * Use numeric literal, not expression. See {@link BrandFactory}.
2329
+ *
2330
+ * @group Number
2173
2331
  */
2174
2332
  export const multipleOf: <Divisor extends number>(
2175
2333
  divisor: Divisor,
@@ -2187,13 +2345,17 @@ export interface MultipleOfError<
2187
2345
  readonly divisor: Divisor;
2188
2346
  }
2189
2347
 
2190
- export const formatMultipleOfError = createTypeErrorFormatter<MultipleOfError>(
2191
- (error) => `The value ${error.value} is not a multiple of ${error.divisor}.`,
2192
- );
2348
+ export const formatMultipleOfError =
2349
+ /*#__PURE__*/ createTypeErrorFormatter<MultipleOfError>(
2350
+ (error) =>
2351
+ `The value ${error.value} is not a multiple of ${error.divisor}.`,
2352
+ );
2193
2353
 
2194
2354
  /**
2195
2355
  * Number within a range, inclusive.
2196
2356
  *
2357
+ * Use numeric literal, not expression. See {@link BrandFactory}.
2358
+ *
2197
2359
  * ### Example
2198
2360
  *
2199
2361
  * ```ts
@@ -2202,7 +2364,7 @@ export const formatMultipleOfError = createTypeErrorFormatter<MultipleOfError>(
2202
2364
  * const errorResult = Between1And10.from(11); // err
2203
2365
  * ```
2204
2366
  *
2205
- * @category Number
2367
+ * @group Number
2206
2368
  */
2207
2369
  export const between: <Min extends number, Max extends number>(
2208
2370
  min: Min,
@@ -2223,10 +2385,11 @@ export interface BetweenError<
2223
2385
  readonly max: Max;
2224
2386
  }
2225
2387
 
2226
- export const formatBetweenError = createTypeErrorFormatter<BetweenError>(
2227
- (error) =>
2228
- `The value ${error.value} is not between ${error.min} and ${error.max}, inclusive.`,
2229
- );
2388
+ export const formatBetweenError =
2389
+ /*#__PURE__*/ createTypeErrorFormatter<BetweenError>(
2390
+ (error) =>
2391
+ `The value ${error.value} is not between ${error.min} and ${error.max}, inclusive.`,
2392
+ );
2230
2393
 
2231
2394
  /**
2232
2395
  * Literal {@link Type}.
@@ -2243,7 +2406,7 @@ export const formatBetweenError = createTypeErrorFormatter<BetweenError>(
2243
2406
  *
2244
2407
  * TODO: Add JsonValue
2245
2408
  *
2246
- * @category Base Factories
2409
+ * @group Base factories
2247
2410
  */
2248
2411
  export const literal = <T extends Literal>(expected: T): LiteralType<T> => {
2249
2412
  const fromUnknown = (value: unknown): Result<T, LiteralError<T>> =>
@@ -2275,17 +2438,18 @@ export interface LiteralError<
2275
2438
  readonly expected: T;
2276
2439
  }
2277
2440
 
2278
- export const formatLiteralError = createTypeErrorFormatter<LiteralError>(
2279
- (error) =>
2280
- `The value ${error.value} is not strictly equal to the expected literal: ${globalThis.String(
2281
- error.expected,
2282
- )}.`,
2283
- );
2441
+ export const formatLiteralError =
2442
+ /*#__PURE__*/ createTypeErrorFormatter<LiteralError>(
2443
+ (error) =>
2444
+ `The value ${error.value} is not strictly equal to the expected literal: ${globalThis.String(
2445
+ error.expected,
2446
+ )}.`,
2447
+ );
2284
2448
 
2285
2449
  /**
2286
2450
  * Array of a specific {@link Type}.
2287
2451
  *
2288
- * Usage:
2452
+ * ### Example
2289
2453
  *
2290
2454
  * ```ts
2291
2455
  * const NumberArray = array(Number);
@@ -2294,8 +2458,8 @@ export const formatLiteralError = createTypeErrorFormatter<LiteralError>(
2294
2458
  * const result2 = NumberArray.from(["a", "b"]); // err(...)
2295
2459
  * ```
2296
2460
  *
2297
- * @category Base Factories
2298
- * @category Array
2461
+ * @group Base factories
2462
+ * @group Array
2299
2463
  */
2300
2464
  export const array = <ElementType extends AnyType>(
2301
2465
  element: ElementType,
@@ -2413,7 +2577,7 @@ export const formatArrayError = <Error extends TypeError>(
2413
2577
  * const result2 = NumberSet.from(new Set(["a", "b"])); // err(...)
2414
2578
  * ```
2415
2579
  *
2416
- * @category Base Factories
2580
+ * @group Base factories
2417
2581
  */
2418
2582
  export const set = <ElementType extends AnyType>(
2419
2583
  element: ElementType,
@@ -2543,8 +2707,8 @@ export const formatSetError = <Error extends TypeError>(
2543
2707
  * StringToNumberRecord.from({ a: "x", b: 2 });
2544
2708
  * ```
2545
2709
  *
2546
- * @category Base Factories
2547
- * @category Object
2710
+ * @group Base factories
2711
+ * @group Object
2548
2712
  */
2549
2713
  export const record = <
2550
2714
  KeyName extends TypeName,
@@ -2591,7 +2755,7 @@ export const record = <
2591
2755
  type: "Record",
2592
2756
  value,
2593
2757
  reason: { kind: "Key", key: rawKey, error: keyResult.error },
2594
- } as IntentionalNever);
2758
+ } as never);
2595
2759
  }
2596
2760
 
2597
2761
  const valueResult = valueType.fromUnknown(rawValue);
@@ -2602,12 +2766,12 @@ export const record = <
2602
2766
  reason: {
2603
2767
  kind: "Value",
2604
2768
  key: rawKey,
2605
- error: valueResult.error as IntentionalNever,
2769
+ error: valueResult.error as never,
2606
2770
  },
2607
2771
  });
2608
2772
  }
2609
2773
 
2610
- result[keyResult.value] = valueResult.value as IntentionalNever;
2774
+ result[keyResult.value] = valueResult.value as never;
2611
2775
  }
2612
2776
 
2613
2777
  return ok(result);
@@ -2664,7 +2828,7 @@ export const record = <
2664
2828
  }),
2665
2829
  key: keyType,
2666
2830
  value: valueType,
2667
- } as IntentionalNever;
2831
+ } as never;
2668
2832
  };
2669
2833
 
2670
2834
  /**
@@ -2748,22 +2912,22 @@ export const formatRecordError = <Error extends TypeError>(
2748
2912
  *
2749
2913
  * https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures
2750
2914
  *
2751
- * ### Examples
2915
+ * ### Example
2752
2916
  *
2753
- * #### Basic Object Validation
2917
+ * ## Basic Object Validation
2754
2918
  *
2755
2919
  * ```ts
2756
2920
  * const User = object({
2757
2921
  * name: NonEmptyTrimmedString,
2758
2922
  * age: PositiveNumber,
2759
2923
  * });
2760
- * type User = typeof User.Type;
2924
+ * interface User extends InferType<typeof User> {}
2761
2925
  *
2762
2926
  * const result = User.from({ name: "John", age: 30 }); // ok({ name: "John", age: 30 })
2763
2927
  * const error = User.from({ name: "John", age: -5 }); // err
2764
2928
  * ```
2765
2929
  *
2766
- * #### Optional Properties
2930
+ * ## Optional Properties
2767
2931
  *
2768
2932
  * In this example the `age` property is marked as optional using
2769
2933
  * {@link optional}.
@@ -2773,10 +2937,10 @@ export const formatRecordError = <Error extends TypeError>(
2773
2937
  * name: NonEmptyString, // Required
2774
2938
  * age: optional(PositiveNumber), // Optional
2775
2939
  * });
2776
- * type User = typeof User.Type;
2940
+ * interface User extends InferType<typeof User> {}
2777
2941
  * ```
2778
2942
  *
2779
- * #### Allowing Additional Properties
2943
+ * ## Allowing Additional Properties
2780
2944
  *
2781
2945
  * ```ts
2782
2946
  * const UserWithAnyExtraProperties = object(
@@ -2795,7 +2959,7 @@ export const formatRecordError = <Error extends TypeError>(
2795
2959
  * });
2796
2960
  * ```
2797
2961
  *
2798
- * #### Combining Fixed and Flexible Properties
2962
+ * ## Combining Fixed and Flexible Properties
2799
2963
  *
2800
2964
  * ```ts
2801
2965
  * const NumberDictionary = object(
@@ -2832,13 +2996,14 @@ export const formatRecordError = <Error extends TypeError>(
2832
2996
  * );
2833
2997
  * ```
2834
2998
  *
2835
- * @category Base Factories
2836
- * @category Object
2999
+ * @group Base factories
3000
+ * @group Object
2837
3001
  */
2838
3002
  export function object<Props extends Record<string, AnyType>>(
2839
3003
  props: Props,
2840
3004
  ): ObjectType<Props>;
2841
3005
 
3006
+ /** With additional record properties. */
2842
3007
  export function object<
2843
3008
  Props extends Record<string, AnyType>,
2844
3009
  KeyName extends TypeName,
@@ -2901,9 +3066,9 @@ export function object(
2901
3066
 
2902
3067
  const propResult = props[key].fromUnknown(value[key]);
2903
3068
  if (!propResult.ok) {
2904
- errors[key] = propResult.error as IntentionalNever;
3069
+ errors[key] = propResult.error as never;
2905
3070
  } else {
2906
- result[key] = propResult.value as IntentionalNever;
3071
+ result[key] = propResult.value as never;
2907
3072
  }
2908
3073
  }
2909
3074
 
@@ -2921,7 +3086,7 @@ export function object(
2921
3086
  reason: {
2922
3087
  kind: "IndexKey",
2923
3088
  key,
2924
- error: keyResult.error as IntentionalNever,
3089
+ error: keyResult.error as never,
2925
3090
  },
2926
3091
  });
2927
3092
  }
@@ -2934,12 +3099,12 @@ export function object(
2934
3099
  reason: {
2935
3100
  kind: "IndexValue",
2936
3101
  key,
2937
- error: valueResult.error as IntentionalNever,
3102
+ error: valueResult.error as never,
2938
3103
  },
2939
3104
  });
2940
3105
  }
2941
3106
 
2942
- result[keyResult.value] = valueResult.value as IntentionalNever;
3107
+ result[keyResult.value] = valueResult.value as never;
2943
3108
  }
2944
3109
  } else if (extraKeys.length > 0) {
2945
3110
  return err({
@@ -3092,7 +3257,7 @@ export interface ObjectError<
3092
3257
  * Merge Error and ParentError into one ObjectError so tooltips and error
3093
3258
  * messages are easier to read.
3094
3259
  *
3095
- * @category Utilities
3260
+ * @group Utilities
3096
3261
  */
3097
3262
  export type MergeObjectTypeErrors<T extends ObjectType<any>> =
3098
3263
  T extends ObjectType<infer Props>
@@ -3202,6 +3367,187 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
3202
3367
  }
3203
3368
  });
3204
3369
 
3370
+ /**
3371
+ * Base interface for objects with a discriminant `type` property.
3372
+ *
3373
+ * This enables
3374
+ * {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions | discriminated unions}
3375
+ * (also known as tagged unions) — a pattern where TypeScript uses a literal
3376
+ * `type` field to narrow union types automatically.
3377
+ *
3378
+ * ## Why Discriminated Unions?
3379
+ *
3380
+ * Discriminated unions model states that are **mutually exclusive**. Instead of
3381
+ * optional fields and boolean flags that can combine into invalid
3382
+ * configurations, each variant is a distinct type. This makes illegal states
3383
+ * unrepresentable — invalid combinations cannot exist, so bugs cannot create
3384
+ * them.
3385
+ *
3386
+ * Benefits:
3387
+ *
3388
+ * - **Self-documenting** — Union cases immediately show all possible states
3389
+ * - **Compile-time safety** — TypeScript enforces handling all cases
3390
+ * - **Refactoring-friendly** — Adding a new state breaks code that doesn't handle
3391
+ * it
3392
+ *
3393
+ * ### Example
3394
+ *
3395
+ * ```ts
3396
+ * // Bad: optional fields allow invalid states (no contact info at all)
3397
+ * interface Contact {
3398
+ * readonly email?: Email;
3399
+ * readonly phone?: Phone;
3400
+ * }
3401
+ *
3402
+ * // Good: discriminated union makes "at least one" explicit
3403
+ * interface EmailOnly extends Typed<"EmailOnly"> {
3404
+ * readonly email: Email;
3405
+ * }
3406
+ * interface PhoneOnly extends Typed<"PhoneOnly"> {
3407
+ * readonly phone: Phone;
3408
+ * }
3409
+ * interface EmailAndPhone extends Typed<"EmailAndPhone"> {
3410
+ * readonly email: Email;
3411
+ * readonly phone: Phone;
3412
+ * }
3413
+ *
3414
+ * type ContactInfo = EmailOnly | PhoneOnly | EmailAndPhone;
3415
+ * ```
3416
+ *
3417
+ * ```ts
3418
+ * interface Pending extends Typed<"Pending"> {
3419
+ * readonly createdAt: DateIso;
3420
+ * }
3421
+ * interface Shipped extends Typed<"Shipped"> {
3422
+ * readonly trackingNumber: TrackingNumber;
3423
+ * }
3424
+ * interface Delivered extends Typed<"Delivered"> {
3425
+ * readonly deliveredAt: DateIso;
3426
+ * }
3427
+ * interface Cancelled extends Typed<"Cancelled"> {
3428
+ * readonly reason: CancellationReason;
3429
+ * }
3430
+ *
3431
+ * type OrderState = Pending | Shipped | Delivered | Cancelled;
3432
+ *
3433
+ * // TypeScript enforces exhaustiveness via return type
3434
+ * const getStatusMessage = (state: OrderState): string => {
3435
+ * switch (state.type) {
3436
+ * case "Pending":
3437
+ * return "Order placed";
3438
+ * case "Shipped":
3439
+ * return `Shipped: ${state.trackingNumber}`;
3440
+ * case "Delivered":
3441
+ * return `Delivered on ${state.deliveredAt.toLocaleDateString()}`;
3442
+ * case "Cancelled":
3443
+ * return `Cancelled: ${state.reason}`;
3444
+ * }
3445
+ * };
3446
+ *
3447
+ * // For void functions, use exhaustiveCheck to ensure all cases are handled
3448
+ * const logState = (state: OrderState): void => {
3449
+ * switch (state.type) {
3450
+ * case "Pending":
3451
+ * console.log("Order placed");
3452
+ * break;
3453
+ * case "Shipped":
3454
+ * console.log(`Shipped: ${state.trackingNumber}`);
3455
+ * break;
3456
+ * case "Delivered":
3457
+ * console.log(
3458
+ * `Delivered on ${state.deliveredAt.toLocaleDateString()}`,
3459
+ * );
3460
+ * break;
3461
+ * case "Cancelled":
3462
+ * console.log(`Cancelled: ${state.reason}`);
3463
+ * break;
3464
+ * default:
3465
+ * exhaustiveCheck(state);
3466
+ * }
3467
+ * };
3468
+ * ```
3469
+ *
3470
+ * ## Why `type` (and not e.g. `_tag`)?
3471
+ *
3472
+ * Underscore-prefixing is meant to avoid clashing with domain properties, but
3473
+ * proper discriminated union design means the discriminant IS the domain
3474
+ * concept — there's no clash to avoid. The `type` prop name also aligns with
3475
+ * {@link Type}'s name. If an entity has a meaningful "type" (like product
3476
+ * category), model it as the discriminant itself:
3477
+ *
3478
+ * ```ts
3479
+ * interface Electronics extends Typed<"Electronics"> {
3480
+ * voltage: Voltage;
3481
+ * }
3482
+ * interface Clothing extends Typed<"Clothing"> {
3483
+ * size: Size;
3484
+ * }
3485
+ * type Product = Electronics | Clothing;
3486
+ * ```
3487
+ *
3488
+ * @see {@link exhaustiveCheck} to ensure all cases are handled in void functions.
3489
+ * @see {@link typed} for runtime-validated typed objects.
3490
+ */
3491
+ export interface Typed<T extends TypeName> {
3492
+ readonly type: T;
3493
+ }
3494
+
3495
+ /**
3496
+ * Creates a runtime-validated typed object with a `type` discriminant.
3497
+ *
3498
+ * ### Example
3499
+ *
3500
+ * ```ts
3501
+ * const Card = typed("Card", {
3502
+ * cardNumber: CardNumber,
3503
+ * expiry: DateIso,
3504
+ * });
3505
+ *
3506
+ * const Cash = typed("Cash", {
3507
+ * currency: NonEmptyTrimmedString,
3508
+ * });
3509
+ *
3510
+ * const Payment = union(Card, Cash);
3511
+ * type Payment = typeof Payment.Type;
3512
+ *
3513
+ * const result = Payment.fromUnknown(data);
3514
+ * if (result.ok) {
3515
+ * switch (result.value.type) {
3516
+ * case "Card":
3517
+ * console.log(result.value.cardNumber);
3518
+ * break;
3519
+ * case "Cash":
3520
+ * console.log(result.value.currency);
3521
+ * break;
3522
+ * }
3523
+ * }
3524
+ * ```
3525
+ *
3526
+ * @see {@link Typed} for type-only discrimination.
3527
+ */
3528
+ export function typed<Tag extends TypeName>(tag: Tag): TypedType<Tag>;
3529
+ /** With additional properties. */
3530
+ export function typed<
3531
+ Tag extends TypeName,
3532
+ Props extends Record<string, AnyType>,
3533
+ >(tag: Tag, props: Props): TypedType<Tag, Props>;
3534
+ export function typed<
3535
+ Tag extends TypeName,
3536
+ Props extends Record<string, AnyType>,
3537
+ >(tag: Tag, props?: Props): ObjectType<{ type: LiteralType<Tag> } & Props> {
3538
+ return object({ type: literal(tag), ...props } as {
3539
+ type: LiteralType<Tag>;
3540
+ } & Props);
3541
+ }
3542
+
3543
+ /** Return type of {@link typed}. */
3544
+ export type TypedType<
3545
+ Tag extends TypeName,
3546
+ Props extends Record<string, AnyType> = Record<never, never>,
3547
+ > = ObjectType<{ type: LiteralType<Tag> } & Props>;
3548
+
3549
+ // TODO: TypeError with "Error" suffix.
3550
+
3205
3551
  /**
3206
3552
  * Union {@link Type}.
3207
3553
  *
@@ -3224,12 +3570,13 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
3224
3570
  * const result3 = StringOrNumber.from(42); // ok(42)
3225
3571
  * ```
3226
3572
  *
3227
- * @category Base Factories
3573
+ * @group Base factories
3228
3574
  */
3229
3575
  export function union<
3230
3576
  Members extends [AnyType, AnyType, ...ReadonlyArray<AnyType>],
3231
3577
  >(...members: Members): UnionType<Members>;
3232
3578
 
3579
+ /** With literal values. */
3233
3580
  export function union<
3234
3581
  Literals extends [Literal, Literal, ...ReadonlyArray<Literal>],
3235
3582
  >(
@@ -3319,6 +3666,110 @@ export const isUnionType = (
3319
3666
  ): t is UnionType<[AnyType, ...ReadonlyArray<AnyType>]> =>
3320
3667
  t.name === "Union" && Array.isArray((t as { members?: unknown }).members);
3321
3668
 
3669
+ /**
3670
+ * Creates a {@link Type} for {@link Result} values.
3671
+ *
3672
+ * Use for validating serialized Results from storage, APIs, or message passing.
3673
+ *
3674
+ * ### Example
3675
+ *
3676
+ * ```ts
3677
+ * const SyncResponse = result(
3678
+ * object({ timestamp: NonNegativeInt }),
3679
+ * typed("SyncError", { message: String }),
3680
+ * );
3681
+ *
3682
+ * // Validate response from worker or API
3683
+ * const validated = SyncResponse.from(JSON.parse(message));
3684
+ * if (!validated.ok) return validated; // validation error
3685
+ * // validated.value is Result<{ timestamp }, SyncError>
3686
+ * ```
3687
+ *
3688
+ * @group Composite factories
3689
+ */
3690
+ export const result = <OkType extends AnyType, ErrType extends AnyType>(
3691
+ okType: OkType,
3692
+ errType: ErrType,
3693
+ ): UnionType<
3694
+ [
3695
+ ObjectType<{ ok: LiteralType<true>; value: OkType }>,
3696
+ ObjectType<{ ok: LiteralType<false>; error: ErrType }>,
3697
+ ]
3698
+ > =>
3699
+ union(
3700
+ object({ ok: literal(true), value: okType }),
3701
+ object({ ok: literal(false), error: errType }),
3702
+ );
3703
+
3704
+ /**
3705
+ * A {@link result} type for `Result<unknown, unknown>`.
3706
+ *
3707
+ * Useful for serializing Results where the value and error types are unknown.
3708
+ *
3709
+ * @group Composite factories
3710
+ */
3711
+ export const UnknownResult = /*#__PURE__*/ result(Unknown, Unknown);
3712
+ export type UnknownResult = typeof UnknownResult.Type;
3713
+
3714
+ /**
3715
+ * Creates a {@link Type} for {@link NextResult} with three outcomes.
3716
+ *
3717
+ * Validates results where the producer responds with:
3718
+ *
3719
+ * - `Ok<A>` — produced a value
3720
+ * - `Err<Done<D>>` — completed normally with a done value
3721
+ * - `Err<E>` — failed with an error
3722
+ *
3723
+ * ### Example
3724
+ *
3725
+ * ```ts
3726
+ * const MyNextResult = nextResult(Item, MyError, Summary);
3727
+ *
3728
+ * const validated = MyNextResult.fromUnknown(data);
3729
+ * if (!validated.ok) return validated;
3730
+ *
3731
+ * const result = validated.value;
3732
+ * if (result.ok) {
3733
+ * console.log(result.value);
3734
+ * } else if (result.error.type === "Done") {
3735
+ * console.log("Done:", result.error.done);
3736
+ * } else {
3737
+ * console.error(result.error);
3738
+ * }
3739
+ * ```
3740
+ *
3741
+ * @group Composite factories
3742
+ */
3743
+ export const nextResult = <
3744
+ ValueType extends AnyType,
3745
+ ErrorType extends AnyType,
3746
+ DoneType extends AnyType,
3747
+ >(
3748
+ valueType: ValueType,
3749
+ errorType: ErrorType,
3750
+ doneType: DoneType,
3751
+ ): ReturnType<
3752
+ typeof result<
3753
+ ValueType,
3754
+ UnionType<[ErrorType, TypedType<"Done", { done: DoneType }>]>
3755
+ >
3756
+ > => result(valueType, union(errorType, typed("Done", { done: doneType })));
3757
+
3758
+ /**
3759
+ * A {@link nextResult} type for `NextResult<unknown, unknown, unknown>`.
3760
+ *
3761
+ * Useful for checking if a value is a {@link NextResult} via
3762
+ * `UnknownNextResult.is(value)`.
3763
+ *
3764
+ * @group Composite factories
3765
+ */
3766
+ export const UnknownNextResult = /*#__PURE__*/ nextResult(
3767
+ Unknown,
3768
+ Unknown,
3769
+ Unknown,
3770
+ );
3771
+ export type UnknownNextResult = typeof UnknownNextResult.Type;
3772
+
3322
3773
  /**
3323
3774
  * Recursive {@link Type}.
3324
3775
  *
@@ -3352,7 +3803,7 @@ export const isUnionType = (
3352
3803
  * );
3353
3804
  * ```
3354
3805
  *
3355
- * @category Base Factories
3806
+ * @group Base factories
3356
3807
  */
3357
3808
  export const recursive = <ParentType extends AnyType>(
3358
3809
  create: () => ParentType,
@@ -3412,7 +3863,7 @@ export interface RecursiveType<ParentType extends AnyType> extends Type<
3412
3863
  * NullOrString.from(42); // err(...)
3413
3864
  * ```
3414
3865
  *
3415
- * @category Base Factories
3866
+ * @group Base factories
3416
3867
  */
3417
3868
  export const nullOr = <T extends AnyType>(
3418
3869
  type: T,
@@ -3430,7 +3881,7 @@ export const nullOr = <T extends AnyType>(
3430
3881
  * UndefinedOrString.from(42); // err(...)
3431
3882
  * ```
3432
3883
  *
3433
- * @category Base Factories
3884
+ * @group Base factories
3434
3885
  */
3435
3886
  export const undefinedOr = <T extends AnyType>(
3436
3887
  type: T,
@@ -3452,7 +3903,7 @@ export const undefinedOr = <T extends AnyType>(
3452
3903
  * NullishOrString.from(42); // err(...)
3453
3904
  * ```
3454
3905
  *
3455
- * @category Base Factories
3906
+ * @group Base factories
3456
3907
  */
3457
3908
  export const nullishOr = <T extends AnyType>(
3458
3909
  type: T,
@@ -3473,7 +3924,7 @@ export const nullishOr = <T extends AnyType>(
3473
3924
  * const error = NameAndAge.from(["Alice", -10]); // err
3474
3925
  * ```
3475
3926
  *
3476
- * @category Base Factories
3927
+ * @group Base factories
3477
3928
  */
3478
3929
  export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
3479
3930
  ...elements: Elements
@@ -3503,7 +3954,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
3503
3954
  reason: {
3504
3955
  kind: "Element",
3505
3956
  index: i,
3506
- error: elementResult.error as IntentionalNever,
3957
+ error: elementResult.error as never,
3507
3958
  },
3508
3959
  });
3509
3960
  }
@@ -3534,7 +3985,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
3534
3985
  reason: {
3535
3986
  kind: "Element",
3536
3987
  index: i,
3537
- error: elementResult.error as IntentionalNever,
3988
+ error: elementResult.error as never,
3538
3989
  },
3539
3990
  });
3540
3991
  }
@@ -3550,7 +4001,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
3550
4001
  fromParent,
3551
4002
  }),
3552
4003
  elements,
3553
- } as IntentionalNever;
4004
+ } as never;
3554
4005
  };
3555
4006
 
3556
4007
  /**
@@ -3609,9 +4060,9 @@ export const formatTupleError = <Error extends TypeError>(
3609
4060
  *
3610
4061
  * https://www.sqlite.org/c3ref/int64.html
3611
4062
  *
3612
- * @category Number
4063
+ * @group Number
3613
4064
  */
3614
- export const Int64 = brand("Int64", BigInt, (value) =>
4065
+ export const Int64 = /*#__PURE__*/ brand("Int64", BigInt, (value) =>
3615
4066
  value >= -9223372036854775808n && value <= 9223372036854775807n
3616
4067
  ? ok(value)
3617
4068
  : err<Int64Error>({ type: "Int64", value }),
@@ -3619,25 +4070,29 @@ export const Int64 = brand("Int64", BigInt, (value) =>
3619
4070
  export type Int64 = typeof Int64.Type;
3620
4071
  export interface Int64Error extends TypeError<"Int64"> {}
3621
4072
 
3622
- export const formatInt64Error = createTypeErrorFormatter<Int64Error>(
3623
- (error) =>
3624
- `The value ${error.value} is not a valid 64-bit signed integer (Int64).`,
3625
- );
4073
+ export const formatInt64Error =
4074
+ /*#__PURE__*/ createTypeErrorFormatter<Int64Error>(
4075
+ (error) =>
4076
+ `The value ${error.value} is not a valid 64-bit signed integer (Int64).`,
4077
+ );
3626
4078
 
3627
4079
  /**
3628
4080
  * Stringified {@link Int64}.
3629
4081
  *
3630
- * @category String
4082
+ * @group String
3631
4083
  */
3632
- export const Int64String = brand("Int64", NonEmptyTrimmedString, (value) =>
3633
- trySync(
3634
- () => {
3635
- const maybeInt = globalThis.BigInt(value);
3636
- Int64.orThrow(maybeInt);
3637
- return value;
3638
- },
3639
- (): Int64StringError => ({ type: "Int64String", value }),
3640
- ),
4084
+ export const Int64String = /*#__PURE__*/ brand(
4085
+ "Int64",
4086
+ NonEmptyTrimmedString,
4087
+ (value) =>
4088
+ trySync(
4089
+ () => {
4090
+ const maybeInt = globalThis.BigInt(value);
4091
+ Int64.orThrow(maybeInt);
4092
+ return value;
4093
+ },
4094
+ (): Int64StringError => ({ type: "Int64String", value }),
4095
+ ),
3641
4096
  );
3642
4097
 
3643
4098
  export type Int64String = typeof Int64String.Type;
@@ -3645,10 +4100,20 @@ export type Int64String = typeof Int64String.Type;
3645
4100
  export interface Int64StringError extends TypeError<"Int64String"> {}
3646
4101
 
3647
4102
  export const formatInt64StringError =
3648
- createTypeErrorFormatter<Int64StringError>(
4103
+ /*#__PURE__*/ createTypeErrorFormatter<Int64StringError>(
3649
4104
  (error) => `The value ${error.value} is not a valid Int64 string.`,
3650
4105
  );
3651
4106
 
4107
+ /**
4108
+ * Validated JSON-compatible value.
4109
+ *
4110
+ * This is the output side of JSON data in Evolu. It uses {@link FiniteNumber}
4111
+ * instead of `number` because JSON numbers are expected to be finite once the
4112
+ * value has been parsed or validated.
4113
+ *
4114
+ * Compare with {@link JsonValueInput}, which represents caller-provided input
4115
+ * before validation.
4116
+ */
3652
4117
  export type JsonValue =
3653
4118
  | string
3654
4119
  | FiniteNumber
@@ -3657,6 +4122,19 @@ export type JsonValue =
3657
4122
  | JsonArray
3658
4123
  | JsonObject;
3659
4124
 
4125
+ /**
4126
+ * JSON-compatible input value before validation.
4127
+ *
4128
+ * This is broader than {@link JsonValue} because inputs arrive as ordinary
4129
+ * JavaScript values, so numbers are typed as `number` before validation can
4130
+ * narrow them to {@link FiniteNumber}.
4131
+ *
4132
+ * That means `JsonValueInput` can temporarily contain numbers that are lossy in
4133
+ * JSON serialization. For example, `JSON.stringify(NaN)` and
4134
+ * `JSON.stringify(Infinity)` produce `null`, and `JSON.stringify(-0)` produces
4135
+ * `0`. Use {@link JsonValue} when the value must already satisfy JSON numeric
4136
+ * constraints.
4137
+ */
3660
4138
  export type JsonValueInput =
3661
4139
  | string
3662
4140
  | number
@@ -3690,9 +4168,9 @@ export type JsonArrayInput = ReadonlyArray<JsonValueInput>;
3690
4168
  * JSON-compatible value: string, {@link FiniteNumber}, boolean, null,
3691
4169
  * {@link JsonArray}, or {@link JsonObject}.
3692
4170
  *
3693
- * @category Base Types
4171
+ * @group Base Types
3694
4172
  */
3695
- export const JsonValue = recursive(
4173
+ export const JsonValue = /*#__PURE__*/ recursive(
3696
4174
  (): UnionType<
3697
4175
  [
3698
4176
  typeof String,
@@ -3732,16 +4210,16 @@ export const JsonValue = recursive(
3732
4210
  /**
3733
4211
  * JSON-compatible array of {@link JsonValue} elements.
3734
4212
  *
3735
- * @category Array
4213
+ * @group Array
3736
4214
  */
3737
- export const JsonArray = array(JsonValue);
4215
+ export const JsonArray = /*#__PURE__*/ array(JsonValue);
3738
4216
 
3739
4217
  /**
3740
4218
  * JSON-compatible object with string keys and {@link JsonValue} values.
3741
4219
  *
3742
- * @category Object
4220
+ * @group Object
3743
4221
  */
3744
- export const JsonObject = record(String, JsonValue);
4222
+ export const JsonObject = /*#__PURE__*/ record(String, JsonValue);
3745
4223
 
3746
4224
  export const parseJson = (value: string): Result<JsonValue, JsonError> =>
3747
4225
  trySync(
@@ -3763,9 +4241,9 @@ export const parseJson = (value: string): Result<JsonValue, JsonError> =>
3763
4241
  * const error = Json.from("invalid json"); // err
3764
4242
  * ```
3765
4243
  *
3766
- * @category String
4244
+ * @group String
3767
4245
  */
3768
- export const Json = brand("Json", String, (value) => {
4246
+ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
3769
4247
  const result = parseJson(value);
3770
4248
  if (!result.ok) return result;
3771
4249
  return ok(value);
@@ -3777,9 +4255,10 @@ export interface JsonError extends TypeError<"Json"> {
3777
4255
  readonly message: string;
3778
4256
  }
3779
4257
 
3780
- export const formatJsonError = createTypeErrorFormatter<JsonError>(
3781
- (error) => `Invalid JSON: ${error.value}. Error: ${error.message}`,
3782
- );
4258
+ export const formatJsonError =
4259
+ /*#__PURE__*/ createTypeErrorFormatter<JsonError>(
4260
+ (error) => `Invalid JSON: ${error.value}. Error: ${error.message}`,
4261
+ );
3783
4262
 
3784
4263
  export const jsonValueToJson = (value: JsonValue): Json =>
3785
4264
  JSON.stringify(value) as Json;
@@ -3807,7 +4286,7 @@ export const jsonToJsonValue = (value: Json): JsonValue =>
3807
4286
  * name: NonEmptyString100,
3808
4287
  * age: FiniteNumber,
3809
4288
  * });
3810
- * type Person = typeof Person.Type;
4289
+ * interface Person extends InferType<typeof Person> {}
3811
4290
  *
3812
4291
  * const [PersonJson, personToPersonJson, personJsonToPerson] = json(
3813
4292
  * Person,
@@ -3848,11 +4327,7 @@ export const json = <T extends AnyType, Name extends TypeName>(
3848
4327
  return ok(value);
3849
4328
  }) as BrandType<typeof String, Name, JsonError | InferErrors<T>, StringError>;
3850
4329
 
3851
- return [
3852
- BrandedJsonType,
3853
- jsonValueToJson as IntentionalNever,
3854
- jsonToJsonValue as IntentionalNever,
3855
- ];
4330
+ return [BrandedJsonType, jsonValueToJson as never, jsonToJsonValue as never];
3856
4331
  };
3857
4332
 
3858
4333
  /**
@@ -3924,7 +4399,7 @@ export const isOptionalType = (x: unknown): x is OptionalType<any> =>
3924
4399
  * PartialUser.from({ age: -5 });
3925
4400
  * ```
3926
4401
  *
3927
- * @category Object
4402
+ * @group Object
3928
4403
  */
3929
4404
  export const partial = <Props extends Record<string, AnyType>>(
3930
4405
  props: Props,
@@ -3944,7 +4419,7 @@ export const partial = <Props extends Record<string, AnyType>>(
3944
4419
  * entirely, or set it to `null`, or set it to the non-null member of the
3945
4420
  * union.
3946
4421
  *
3947
- * @category Object
4422
+ * @group Object
3948
4423
  */
3949
4424
  export const nullableToOptional = <Props extends Record<string, AnyType>>(
3950
4425
  props: Props,
@@ -3990,12 +4465,12 @@ export type NullTypeInMembers<Members extends [AnyType, ...Array<AnyType>]> =
3990
4465
  /**
3991
4466
  * Create a new `object` {@link Type} by omitting some keys.
3992
4467
  *
3993
- * @category Object
4468
+ * @group Object
3994
4469
  */
3995
- export function omit<T extends ObjectType<any>, Keys extends keyof T["props"]>(
4470
+ export const omit = <T extends ObjectType<any>, Keys extends keyof T["props"]>(
3996
4471
  objectType: T,
3997
4472
  ...keys: ReadonlyArray<Keys>
3998
- ): ObjectType<Omit<T["props"], Keys>> {
4473
+ ): ObjectType<Omit<T["props"], Keys>> => {
3999
4474
  const newProps = {} as Omit<T["props"], Keys>;
4000
4475
 
4001
4476
  for (const key in objectType.props) {
@@ -4005,40 +4480,7 @@ export function omit<T extends ObjectType<any>, Keys extends keyof T["props"]>(
4005
4480
  }
4006
4481
  }
4007
4482
  return object(newProps);
4008
- }
4009
-
4010
- export const maxMutationSize = 655360;
4011
-
4012
- /**
4013
- * Evolu has to limit the maximum mutation size. Otherwise, sync couldn't use
4014
- * the `maxProtocolMessageRangesSize`. The max size is 640KB in bytes, measured
4015
- * via MessagePack. Evolu Protocol DbChange will be smaller thanks to various
4016
- * optimizations.
4017
- */
4018
- export const validMutationSize = <T extends AnyType>(
4019
- type: T,
4020
- ): BrandType<T, "ValidMutationSize", ValidMutationSizeError, InferErrors<T>> =>
4021
- brand("ValidMutationSize", type, (value) =>
4022
- pack(value).byteLength <= maxMutationSize
4023
- ? ok(value)
4024
- : err<ValidMutationSizeError>({ type: "ValidMutationSize", value }),
4025
- );
4026
-
4027
- export interface ValidMutationSizeError extends TypeError<"ValidMutationSize"> {}
4028
-
4029
- export const formatValidMutationSizeError =
4030
- createTypeErrorFormatter<ValidMutationSizeError>(
4031
- (error) =>
4032
- `The mutation size exceeds the maximum limit of ${maxMutationSize} bytes. The provided mutation has a size of ${pack(error.value).byteLength} bytes.`,
4033
- );
4034
-
4035
- export type ValidMutationSize<Props extends Record<string, AnyType>> =
4036
- BrandType<
4037
- ObjectType<Props>,
4038
- "ValidMutationSize",
4039
- ValidMutationSizeError,
4040
- InferErrors<ObjectType<Props>>
4041
- >;
4483
+ };
4042
4484
 
4043
4485
  /**
4044
4486
  * Union of all `TypeError`s defined in the `Type.ts` file, including base type
@@ -4051,7 +4493,7 @@ export type ValidMutationSize<Props extends Record<string, AnyType>> =
4051
4493
  * Used by {@link createFormatTypeError} to generate human-readable error
4052
4494
  * messages.
4053
4495
  *
4054
- * @category Utilities
4496
+ * @group Utilities
4055
4497
  */
4056
4498
  export type TypeErrors<ExtraErrors extends TypeError = never> =
4057
4499
  | StringError
@@ -4092,7 +4534,6 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
4092
4534
  | Int64Error
4093
4535
  | Int64StringError
4094
4536
  | JsonError
4095
- | ValidMutationSizeError
4096
4537
  | ExtraErrors
4097
4538
  // Composite errors
4098
4539
  | ArrayError<TypeErrors<ExtraErrors>>
@@ -4185,7 +4626,7 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
4185
4626
  * };
4186
4627
  * ```
4187
4628
  *
4188
- * @category Utilities
4629
+ * @group Utilities
4189
4630
  */
4190
4631
  export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
4191
4632
  extraFormatter?: TypeErrorFormatter<ExtraErrors>,
@@ -4274,8 +4715,6 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
4274
4715
  return formatInt64StringError(error);
4275
4716
  case "Json":
4276
4717
  return formatJsonError(error);
4277
- case "ValidMutationSize":
4278
- return formatValidMutationSizeError(error);
4279
4718
  // Composite Types
4280
4719
  case "SimplePassword":
4281
4720
  return formatSimplePasswordError(formatTypeError)(error);
@@ -4308,25 +4747,25 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
4308
4747
  * Converts an Evolu {@link TypeError} to Standard Schema V1 issues format.
4309
4748
  *
4310
4749
  * This function recursively converts Evolu's typed errors into the Standard
4311
- * Schema issue format with proper path tracking for nested structures.
4750
+ * Schema issue format with proper path tracking for nested structures. The
4751
+ * `message` field contains the JSON-serialized typed error, which users can
4752
+ * deserialize and format using appropriate {@link TypeErrorFormatter}s.
4312
4753
  *
4313
- * @category Utilities
4754
+ * @group Utilities
4314
4755
  */
4315
4756
  export const typeErrorToStandardSchemaIssues = <
4316
4757
  ExtraErrors extends TypeError = never,
4317
4758
  >(
4318
4759
  error: TypeErrors<ExtraErrors>,
4319
- formatTypeError: TypeErrorFormatter<TypeErrors<ExtraErrors>>,
4320
4760
  path: ReadonlyArray<PropertyKey> = [],
4321
4761
  ): ReadonlyArray<StandardSchemaV1.Issue> => {
4322
4762
  if (error.type === "Array") {
4323
4763
  const arrayError = error as ArrayError;
4324
4764
  if (arrayError.reason.kind === "NotArray") {
4325
- return [{ message: formatTypeError(error), path }];
4765
+ return [{ message: JSON.stringify(error), path }];
4326
4766
  }
4327
4767
  return typeErrorToStandardSchemaIssues(
4328
4768
  arrayError.reason.error as TypeErrors<ExtraErrors>,
4329
- formatTypeError,
4330
4769
  [...path, arrayError.reason.index],
4331
4770
  );
4332
4771
  }
@@ -4334,11 +4773,10 @@ export const typeErrorToStandardSchemaIssues = <
4334
4773
  if (error.type === "Set") {
4335
4774
  const setError = error as SetError;
4336
4775
  if (setError.reason.kind === "NotSet") {
4337
- return [{ message: formatTypeError(error), path }];
4776
+ return [{ message: JSON.stringify(error), path }];
4338
4777
  }
4339
4778
  return typeErrorToStandardSchemaIssues(
4340
4779
  setError.reason.error as TypeErrors<ExtraErrors>,
4341
- formatTypeError,
4342
4780
  [...path, setError.reason.index],
4343
4781
  );
4344
4782
  }
@@ -4349,14 +4787,13 @@ export const typeErrorToStandardSchemaIssues = <
4349
4787
  objectError.reason.kind === "NotObject" ||
4350
4788
  objectError.reason.kind === "ExtraKeys"
4351
4789
  ) {
4352
- return [{ message: formatTypeError(error), path }];
4790
+ return [{ message: JSON.stringify(error), path }];
4353
4791
  }
4354
4792
  const issues: Array<StandardSchemaV1.Issue> = [];
4355
4793
  for (const [key, propError] of Object.entries(objectError.reason.errors)) {
4356
4794
  issues.push(
4357
4795
  ...typeErrorToStandardSchemaIssues(
4358
4796
  propError as TypeErrors<ExtraErrors>,
4359
- formatTypeError,
4360
4797
  [...path, key],
4361
4798
  ),
4362
4799
  );
@@ -4367,7 +4804,7 @@ export const typeErrorToStandardSchemaIssues = <
4367
4804
  if (error.type === "ObjectWithRecord") {
4368
4805
  const objectWithRecordError = error as ObjectWithRecordError;
4369
4806
  if (objectWithRecordError.reason.kind === "NotObject") {
4370
- return [{ message: formatTypeError(error), path }];
4807
+ return [{ message: JSON.stringify(error), path }];
4371
4808
  }
4372
4809
  if (
4373
4810
  objectWithRecordError.reason.kind === "IndexKey" ||
@@ -4375,7 +4812,6 @@ export const typeErrorToStandardSchemaIssues = <
4375
4812
  ) {
4376
4813
  return typeErrorToStandardSchemaIssues(
4377
4814
  objectWithRecordError.reason.error as TypeErrors<ExtraErrors>,
4378
- formatTypeError,
4379
4815
  [...path, objectWithRecordError.reason.key as PropertyKey],
4380
4816
  );
4381
4817
  }
@@ -4386,7 +4822,6 @@ export const typeErrorToStandardSchemaIssues = <
4386
4822
  issues.push(
4387
4823
  ...typeErrorToStandardSchemaIssues(
4388
4824
  propError as TypeErrors<ExtraErrors>,
4389
- formatTypeError,
4390
4825
  [...path, key],
4391
4826
  ),
4392
4827
  );
@@ -4397,11 +4832,10 @@ export const typeErrorToStandardSchemaIssues = <
4397
4832
  if (error.type === "Record") {
4398
4833
  const recordError = error as RecordError;
4399
4834
  if (recordError.reason.kind === "NotRecord") {
4400
- return [{ message: formatTypeError(error), path }];
4835
+ return [{ message: JSON.stringify(error), path }];
4401
4836
  }
4402
4837
  return typeErrorToStandardSchemaIssues(
4403
4838
  recordError.reason.error as TypeErrors<ExtraErrors>,
4404
- formatTypeError,
4405
4839
  [...path, recordError.reason.key as PropertyKey],
4406
4840
  );
4407
4841
  }
@@ -4409,11 +4843,10 @@ export const typeErrorToStandardSchemaIssues = <
4409
4843
  if (error.type === "Tuple") {
4410
4844
  const tupleError = error as TupleError;
4411
4845
  if (tupleError.reason.kind === "InvalidLength") {
4412
- return [{ message: formatTypeError(error), path }];
4846
+ return [{ message: JSON.stringify(error), path }];
4413
4847
  }
4414
4848
  return typeErrorToStandardSchemaIssues(
4415
4849
  tupleError.reason.error as TypeErrors<ExtraErrors>,
4416
- formatTypeError,
4417
4850
  [...path, tupleError.reason.index],
4418
4851
  );
4419
4852
  }
@@ -4421,11 +4854,7 @@ export const typeErrorToStandardSchemaIssues = <
4421
4854
  if (error.type === "Union") {
4422
4855
  const unionError = error as UnionError;
4423
4856
  return unionError.errors.flatMap((err) =>
4424
- typeErrorToStandardSchemaIssues(
4425
- err as TypeErrors<ExtraErrors>,
4426
- formatTypeError,
4427
- path,
4428
- ),
4857
+ typeErrorToStandardSchemaIssues(err as TypeErrors<ExtraErrors>, path),
4429
4858
  );
4430
4859
  }
4431
4860
 
@@ -4434,14 +4863,13 @@ export const typeErrorToStandardSchemaIssues = <
4434
4863
  if ("parentError" in brandError) {
4435
4864
  return typeErrorToStandardSchemaIssues(
4436
4865
  brandError.parentError as TypeErrors<ExtraErrors>,
4437
- formatTypeError,
4438
4866
  path,
4439
4867
  );
4440
4868
  }
4441
- return [{ message: formatTypeError(error), path }];
4869
+ return [{ message: JSON.stringify(error), path }];
4442
4870
  }
4443
4871
 
4444
- return [{ message: formatTypeError(error), path }];
4872
+ return [{ message: JSON.stringify(error), path }];
4445
4873
  };
4446
4874
 
4447
4875
  /** The Standard Schema interface. */
@@ -4515,10 +4943,3 @@ export declare namespace StandardSchemaV1 {
4515
4943
  Schema["~standard"]["types"]
4516
4944
  >["output"];
4517
4945
  }
4518
-
4519
- /**
4520
- * Shared formatter cache for Standard Schema integration - avoids circular
4521
- * dependency by lazily creating the formatter on first use rather than during
4522
- * module initialization.
4523
- */
4524
- let cachedStandardSchemaFormatTypeError: TypeErrorFormatter<any> | undefined;