@evolu/common 8.2.0 → 8.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/dist/src/Function.d.ts +1 -0
  2. package/dist/src/Function.d.ts.map +1 -1
  3. package/dist/src/Http.d.ts +9 -0
  4. package/dist/src/Http.d.ts.map +1 -1
  5. package/dist/src/Object.d.ts +1 -0
  6. package/dist/src/Object.d.ts.map +1 -1
  7. package/dist/src/RefCount.d.ts +1 -0
  8. package/dist/src/RefCount.d.ts.map +1 -1
  9. package/dist/src/Relation.d.ts +2 -0
  10. package/dist/src/Relation.d.ts.map +1 -1
  11. package/dist/src/Resource.d.ts +1 -0
  12. package/dist/src/Resource.d.ts.map +1 -1
  13. package/dist/src/Result.d.ts +383 -548
  14. package/dist/src/Result.d.ts.map +1 -1
  15. package/dist/src/Result.js +296 -46
  16. package/dist/src/StackTrace.d.ts +1 -0
  17. package/dist/src/StackTrace.d.ts.map +1 -1
  18. package/dist/src/Task.d.ts +985 -1069
  19. package/dist/src/Task.d.ts.map +1 -1
  20. package/dist/src/Task.js +1007 -156
  21. package/dist/src/Time.d.ts +1 -0
  22. package/dist/src/Time.d.ts.map +1 -1
  23. package/dist/src/Type.d.ts +1322 -793
  24. package/dist/src/Type.d.ts.map +1 -1
  25. package/dist/src/Type.js +1192 -244
  26. package/dist/src/intl/_en.d.ts +126 -0
  27. package/dist/src/intl/_en.d.ts.map +1 -0
  28. package/dist/src/intl/_en.js +267 -0
  29. package/dist/src/intl/ar.d.ts +117 -0
  30. package/dist/src/intl/ar.d.ts.map +1 -0
  31. package/dist/src/intl/ar.js +245 -0
  32. package/dist/src/intl/bn.d.ts +117 -0
  33. package/dist/src/intl/bn.d.ts.map +1 -0
  34. package/dist/src/intl/bn.js +258 -0
  35. package/dist/src/intl/ca.d.ts +117 -0
  36. package/dist/src/intl/ca.d.ts.map +1 -0
  37. package/dist/src/intl/ca.js +258 -0
  38. package/dist/src/intl/cs.d.ts +20 -7
  39. package/dist/src/intl/cs.d.ts.map +1 -1
  40. package/dist/src/intl/cs.js +54 -27
  41. package/dist/src/intl/da.d.ts +117 -0
  42. package/dist/src/intl/da.d.ts.map +1 -0
  43. package/dist/src/intl/da.js +258 -0
  44. package/dist/src/intl/de.d.ts +117 -0
  45. package/dist/src/intl/de.d.ts.map +1 -0
  46. package/dist/src/intl/de.js +258 -0
  47. package/dist/src/intl/el.d.ts +117 -0
  48. package/dist/src/intl/el.d.ts.map +1 -0
  49. package/dist/src/intl/el.js +265 -0
  50. package/dist/src/intl/es.d.ts +117 -0
  51. package/dist/src/intl/es.d.ts.map +1 -0
  52. package/dist/src/intl/es.js +258 -0
  53. package/dist/src/intl/fa.d.ts +117 -0
  54. package/dist/src/intl/fa.d.ts.map +1 -0
  55. package/dist/src/intl/fa.js +258 -0
  56. package/dist/src/intl/fi.d.ts +117 -0
  57. package/dist/src/intl/fi.d.ts.map +1 -0
  58. package/dist/src/intl/fi.js +258 -0
  59. package/dist/src/intl/fil.d.ts +117 -0
  60. package/dist/src/intl/fil.d.ts.map +1 -0
  61. package/dist/src/intl/fil.js +258 -0
  62. package/dist/src/intl/fr.d.ts +117 -0
  63. package/dist/src/intl/fr.d.ts.map +1 -0
  64. package/dist/src/intl/fr.js +258 -0
  65. package/dist/src/intl/he.d.ts +117 -0
  66. package/dist/src/intl/he.d.ts.map +1 -0
  67. package/dist/src/intl/he.js +258 -0
  68. package/dist/src/intl/hi.d.ts +117 -0
  69. package/dist/src/intl/hi.d.ts.map +1 -0
  70. package/dist/src/intl/hi.js +258 -0
  71. package/dist/src/intl/hr.d.ts +117 -0
  72. package/dist/src/intl/hr.d.ts.map +1 -0
  73. package/dist/src/intl/hr.js +265 -0
  74. package/dist/src/intl/hu.d.ts +63 -0
  75. package/dist/src/intl/hu.d.ts.map +1 -0
  76. package/dist/src/intl/hu.js +204 -0
  77. package/dist/src/intl/id.d.ts +117 -0
  78. package/dist/src/intl/id.d.ts.map +1 -0
  79. package/dist/src/intl/id.js +258 -0
  80. package/dist/src/intl/index.d.ts +42 -0
  81. package/dist/src/intl/index.d.ts.map +1 -1
  82. package/dist/src/intl/index.js +42 -0
  83. package/dist/src/intl/it.d.ts +117 -0
  84. package/dist/src/intl/it.d.ts.map +1 -0
  85. package/dist/src/intl/it.js +258 -0
  86. package/dist/src/intl/ja.d.ts +117 -0
  87. package/dist/src/intl/ja.d.ts.map +1 -0
  88. package/dist/src/intl/ja.js +258 -0
  89. package/dist/src/intl/ko.d.ts +117 -0
  90. package/dist/src/intl/ko.d.ts.map +1 -0
  91. package/dist/src/intl/ko.js +258 -0
  92. package/dist/src/intl/ml.d.ts +117 -0
  93. package/dist/src/intl/ml.d.ts.map +1 -0
  94. package/dist/src/intl/ml.js +258 -0
  95. package/dist/src/intl/mr.d.ts +117 -0
  96. package/dist/src/intl/mr.d.ts.map +1 -0
  97. package/dist/src/intl/mr.js +258 -0
  98. package/dist/src/intl/ms.d.ts +117 -0
  99. package/dist/src/intl/ms.d.ts.map +1 -0
  100. package/dist/src/intl/ms.js +245 -0
  101. package/dist/src/intl/nb.d.ts +63 -0
  102. package/dist/src/intl/nb.d.ts.map +1 -0
  103. package/dist/src/intl/nb.js +191 -0
  104. package/dist/src/intl/nl.d.ts +117 -0
  105. package/dist/src/intl/nl.d.ts.map +1 -0
  106. package/dist/src/intl/nl.js +258 -0
  107. package/dist/src/intl/pa.d.ts +117 -0
  108. package/dist/src/intl/pa.d.ts.map +1 -0
  109. package/dist/src/intl/pa.js +258 -0
  110. package/dist/src/intl/pl.d.ts +117 -0
  111. package/dist/src/intl/pl.d.ts.map +1 -0
  112. package/dist/src/intl/pl.js +242 -0
  113. package/dist/src/intl/pt-BR.d.ts +117 -0
  114. package/dist/src/intl/pt-BR.d.ts.map +1 -0
  115. package/dist/src/intl/pt-BR.js +258 -0
  116. package/dist/src/intl/pt.d.ts +117 -0
  117. package/dist/src/intl/pt.d.ts.map +1 -0
  118. package/dist/src/intl/pt.js +246 -0
  119. package/dist/src/intl/ro.d.ts +117 -0
  120. package/dist/src/intl/ro.d.ts.map +1 -0
  121. package/dist/src/intl/ro.js +258 -0
  122. package/dist/src/intl/sk.d.ts +117 -0
  123. package/dist/src/intl/sk.d.ts.map +1 -0
  124. package/dist/src/intl/sk.js +244 -0
  125. package/dist/src/intl/sl.d.ts +117 -0
  126. package/dist/src/intl/sl.d.ts.map +1 -0
  127. package/dist/src/intl/sl.js +258 -0
  128. package/dist/src/intl/sv.d.ts +117 -0
  129. package/dist/src/intl/sv.d.ts.map +1 -0
  130. package/dist/src/intl/sv.js +258 -0
  131. package/dist/src/intl/sw.d.ts +63 -0
  132. package/dist/src/intl/sw.d.ts.map +1 -0
  133. package/dist/src/intl/sw.js +191 -0
  134. package/dist/src/intl/ta.d.ts +117 -0
  135. package/dist/src/intl/ta.d.ts.map +1 -0
  136. package/dist/src/intl/ta.js +258 -0
  137. package/dist/src/intl/te.d.ts +117 -0
  138. package/dist/src/intl/te.d.ts.map +1 -0
  139. package/dist/src/intl/te.js +258 -0
  140. package/dist/src/intl/th.d.ts +117 -0
  141. package/dist/src/intl/th.d.ts.map +1 -0
  142. package/dist/src/intl/th.js +258 -0
  143. package/dist/src/intl/tr.d.ts +117 -0
  144. package/dist/src/intl/tr.d.ts.map +1 -0
  145. package/dist/src/intl/tr.js +255 -0
  146. package/dist/src/intl/uk.d.ts +117 -0
  147. package/dist/src/intl/uk.d.ts.map +1 -0
  148. package/dist/src/intl/uk.js +258 -0
  149. package/dist/src/intl/ur.d.ts +117 -0
  150. package/dist/src/intl/ur.d.ts.map +1 -0
  151. package/dist/src/intl/ur.js +245 -0
  152. package/dist/src/intl/vi.d.ts +117 -0
  153. package/dist/src/intl/vi.d.ts.map +1 -0
  154. package/dist/src/intl/vi.js +255 -0
  155. package/dist/src/intl/zh-CN.d.ts +117 -0
  156. package/dist/src/intl/zh-CN.d.ts.map +1 -0
  157. package/dist/src/intl/zh-CN.js +258 -0
  158. package/dist/src/intl/zh-TW.d.ts +117 -0
  159. package/dist/src/intl/zh-TW.d.ts.map +1 -0
  160. package/dist/src/intl/zh-TW.js +258 -0
  161. package/dist/src/local-first/Owner.d.ts +156 -62
  162. package/dist/src/local-first/Owner.d.ts.map +1 -1
  163. package/dist/src/local-first/Owner.js +119 -20
  164. package/package.json +1 -1
  165. package/src/Function.ts +2 -0
  166. package/src/Http.ts +9 -0
  167. package/src/Object.ts +2 -0
  168. package/src/RefCount.ts +2 -0
  169. package/src/Relation.ts +4 -0
  170. package/src/Resource.ts +2 -0
  171. package/src/Result.ts +397 -560
  172. package/src/StackTrace.ts +2 -0
  173. package/src/Task.ts +1143 -1222
  174. package/src/Time.ts +2 -0
  175. package/src/Type.ts +1759 -794
  176. package/src/intl/_en.ts +497 -0
  177. package/src/intl/ar.ts +417 -0
  178. package/src/intl/bn.ts +486 -0
  179. package/src/intl/ca.ts +488 -0
  180. package/src/intl/cs.ts +95 -31
  181. package/src/intl/da.ts +488 -0
  182. package/src/intl/de.ts +488 -0
  183. package/src/intl/el.ts +494 -0
  184. package/src/intl/es.ts +488 -0
  185. package/src/intl/fa.ts +487 -0
  186. package/src/intl/fi.ts +487 -0
  187. package/src/intl/fil.ts +488 -0
  188. package/src/intl/fr.ts +488 -0
  189. package/src/intl/he.ts +486 -0
  190. package/src/intl/hi.ts +487 -0
  191. package/src/intl/hr.ts +494 -0
  192. package/src/intl/hu.ts +387 -0
  193. package/src/intl/id.ts +487 -0
  194. package/src/intl/index.ts +42 -0
  195. package/src/intl/it.ts +488 -0
  196. package/src/intl/ja.ts +488 -0
  197. package/src/intl/ko.ts +487 -0
  198. package/src/intl/ml.ts +487 -0
  199. package/src/intl/mr.ts +487 -0
  200. package/src/intl/ms.ts +410 -0
  201. package/src/intl/nb.ts +364 -0
  202. package/src/intl/nl.ts +488 -0
  203. package/src/intl/pa.ts +487 -0
  204. package/src/intl/pl.ts +450 -0
  205. package/src/intl/pt-BR.ts +487 -0
  206. package/src/intl/pt.ts +424 -0
  207. package/src/intl/ro.ts +488 -0
  208. package/src/intl/sk.ts +412 -0
  209. package/src/intl/sl.ts +487 -0
  210. package/src/intl/sv.ts +488 -0
  211. package/src/intl/sw.ts +324 -0
  212. package/src/intl/ta.ts +487 -0
  213. package/src/intl/te.ts +487 -0
  214. package/src/intl/th.ts +484 -0
  215. package/src/intl/tr.ts +487 -0
  216. package/src/intl/uk.ts +488 -0
  217. package/src/intl/ur.ts +410 -0
  218. package/src/intl/vi.ts +435 -0
  219. package/src/intl/zh-CN.ts +482 -0
  220. package/src/intl/zh-TW.ts +482 -0
  221. package/src/local-first/Owner.ts +156 -62
package/src/Type.ts CHANGED
@@ -1,176 +1,208 @@
1
1
  /**
2
- * Runtime types.
2
+ * ## Intro
3
3
  *
4
- * @module
5
- */
6
-
7
- import { utf8ToBytes } from "@noble/ciphers/utils.js";
8
- import { sha256 } from "@noble/hashes/sha2.js";
9
- import * as bip39 from "@scure/bip39";
10
- import { wordlist } from "@scure/bip39/wordlists/english.js";
11
- import type { StandardSchemaV1 } from "@standard-schema/spec";
12
- import type {
13
- AtLeastTwoReadonlyArray,
14
- NonEmptyReadonlyArray,
15
- } from "./Array.ts";
16
- import { assert, assertNonNullable } from "./Assert.ts";
17
- import type { Brand } from "./Brand.ts";
18
- import type { RandomBytesDep } from "./Crypto.ts";
19
- import { identity, type Thunk } from "./Function.ts";
20
- import { createMutableRecord } from "./Object.ts";
21
- import { hasNodeBuffer } from "./Platform.ts";
22
- import {
23
- err,
24
- flatMapResult,
25
- getOk,
26
- getOrNull,
27
- getOrThrow,
28
- ok,
29
- trySync,
30
- type Result,
31
- } from "./Result.ts";
32
- import { safelyStringifyUnknownValue } from "./String.ts";
33
- import type { Task } from "./Task.ts";
34
- import type { TimeDep } from "./Time.ts";
35
- import {
36
- instance,
37
- isInstance,
38
- type CompileTimeError,
39
- type Instance,
40
- type IsUnion,
41
- type Literal,
42
- type Simplify,
43
- type ValueWithLength,
44
- type WidenLiteral,
45
- } from "./Types.ts";
46
-
47
- /**
48
- * A runtime representation of a TypeScript type with typed structured errors.
49
- *
50
- * Evolu Type reports expected decoding failures through {@link Result} rather
51
- * than exceptions. It represents both an encoded `Input` and its semantic
52
- * `Output`, supporting validation, transformation, and canonical encoding.
53
- *
54
- * Evolu Type is designed for correctness and developer experience. Correctness
55
- * is especially important for local-first data: application authors cannot
56
- * inspect or repair a user's data on a server because they do not have access
57
- * to it. Type declarations must reject invalid data at system boundaries, then
58
- * preserve those guarantees wherever the data travels.
59
- *
60
- * To make correct code the easiest code to write, Evolu Type preserves as much
61
- * information as TypeScript can express. Invalid declarations produce readable
62
- * {@link CompileTimeError} types when the compiler can detect them, while
63
- * runtime assertions enforce construction contracts it cannot prove. Together,
64
- * these choices create a pit of success.
65
- *
66
- * The implementation is optimized for minimal bundle size. Less descriptive
67
- * assertion messages could make it even smaller, but Evolu keeps actionable
68
- * messages as a deliberate tradeoff for developer experience.
69
- *
70
- * The main properties of Evolu Type are:
71
- *
72
- * - **Result-based error handling** – expected failures are explicit values.
73
- * - **Typed errors with decoupled formatters** – validation logic stays
74
- * independent of user-facing messages, and errors can be handled
75
- * exhaustively.
76
- * - **Type-safe, tree-shakeable localization** – formatter requirements are
77
- * inferred from selected Types, while apps bundle exactly the locales they
78
- * support so users can change language offline.
79
- * - **Consistent constraints through {@link Brand}** – every refinement constraint
80
- * is represented in the TypeScript type, so an unconstrained parent value
81
- * cannot be used where the constrained value is required.
82
- * - **Typed inputs** – prefer `from` and its `.parent` entry points to connect
83
- * precise producer and consumer contracts while preserving typed remaining
84
- * errors; reserve `fromUnknown` for genuinely unknown values.
85
- * - **Lawful codecs** – Types partially decode `Input` to `Output` and totally
86
- * encode every legitimate `Output` to `CanonicalInput`, the statically known
87
- * subtype of `Input` returned by complete encoding.
88
- * - **A top-down implementation** – the source is intended to be read from
89
- * beginning to end.
90
- *
91
- * Evolu Type supports [Standard Schema](https://standardschema.dev/) for
92
- * interoperability with compatible tools and frameworks while preserving each
93
- * Type's exact Input and Output.
94
- *
95
- * Evolu Type assumes that all executing code, including third-party
96
- * dependencies, has been audited and is trusted. It validates data contracts
97
- * under that assumption. Trusting code does not require trusting every value it
98
- * returns, so uncertain values from legacy code or another realm can still be
99
- * decoded at an explicit boundary. It does not protect against hostile
100
- * executable behavior such as sabotaged Proxies or throwing traps; Type
101
- * validation is not a security boundary for untrusted JavaScript.
102
- *
103
- * Type declarations and their callbacks are trusted construction code. Evolu
104
- * Type leverages that trust for better developer experience and does not try to
105
- * recover from code that defeats the type system with `any` or casts, including
106
- * fabricating an `Err` for `Result<_, never>`. Runtime assertions still enforce
107
- * contracts TypeScript cannot express, and every Type declaration must be
108
- * tested for its expected successes and failures.
109
- *
110
- * `fromUnknown` validates untyped input through the complete pipeline. `from`
111
- * and its `.parent` operations use their declared boundary to determine which
112
- * remaining stages can return validation errors, but assert that boundary at
113
- * runtime. `orThrow` and `orNull` reuse the deepest `from` operation accepting
114
- * `Input`, while `to` asserts its Output boundary. A failed assertion means
115
- * application code violated its static contract; it is a developer error, not
116
- * an expected validation failure. Its message identifies the expected Type and
117
- * its cause preserves the exact structured Output validation error.
118
- *
119
- * Prefer the most precise typed boundary available. A value is not unknown
120
- * merely because it originated outside the application: forms, components, and
121
- * other producers often expose a `string` or a branded value that can connect
122
- * directly to a matching `from` boundary. Reserve `fromUnknown` for values
123
- * whose TypeScript type is genuinely `unknown`. `is` means exact membership in
124
- * the output domain, not merely that output-side parsing could succeed. A
125
- * successful `fromUnknown` result always satisfies `is`.
126
- *
127
- * Decoding accepts a representation outside the Output domain only when the
128
- * Type explicitly declares that representation, such as a transformation Input.
129
- * Structural Types do not implicitly repair another JavaScript representation.
130
- * In particular, {@link array} and {@link tuple} require dense own data elements,
131
- * while the predefined {@link Object}, {@link object}, and {@link record} require
132
- * plain objects with own enumerable data properties. They do not invoke
133
- * accessors or materialize inherited and non-enumerable properties.
134
- *
135
- * TypeScript object types are structural and do not encode JavaScript realm
136
- * identity. Structural Types therefore accept legitimate representations from
137
- * other realms. Prototype checks remain when the prototype defines the semantic
138
- * domain. Plain-object Types accept a `null` prototype or an immediate root
139
- * prototype whose own prototype is `null`; ordinary class instances and deeper
140
- * prototype chains are rejected. When Record decoding must construct a
141
- * normalized value, it uses a `null` prototype so every string key remains
142
- * ordinary data.
143
- *
144
- * Evolu Type requires TypeScript 7 or newer and expects the
145
- * `exactOptionalPropertyTypes` compiler option to be enabled.
146
- *
147
- * Predefined Types intentionally use the names of corresponding JavaScript
148
- * built-ins because they represent those familiar value categories. If an
149
- * imported Type shadows a built-in in the same scope, access the built-in
150
- * through `globalThis`, JavaScript's standard cross-environment global object,
151
- * such as `globalThis.String` or `globalThis.Date`.
4
+ * Runtime validation with precise TypeScript types and structured errors.
152
5
  *
153
- * ### Example
6
+ * Evolu {@link Type} is a pure, synchronous codec for defining semantic domains.
7
+ * It partially decodes an `Input` into an `Output` and totally encodes every
8
+ * valid `Output` into a `CanonicalInput`. Types can validate, refine,
9
+ * transform, and compose without losing the contracts TypeScript can express.
10
+ *
11
+ * Decoding failures are explicit {@link Result} values, and their structured
12
+ * errors preserve the exact error types each Type can return.
13
+ *
14
+ * Evolu Type is designed to make correct code the easiest code to write:
15
+ *
16
+ * - Predefined constraints add a {@link Brand} to their Output.
17
+ * - Invalid declarations produce readable {@link CompileTimeError} types when the
18
+ * compiler can detect them.
19
+ * - Evolu Type uses runtime {@link assert | assertions} to detect developer errors
20
+ * that TypeScript cannot express, such as excess properties and sparse
21
+ * arrays.
22
+ * - Typed `from` boundaries allow connecting value producers to domain fields
23
+ * through their exact TypeScript types, so incompatible contract changes are
24
+ * compile-time errors rather than runtime validation errors.
25
+ * - Lawful codecs compose without creating unencodable values: every valid Output
26
+ * has a canonical Input representation and round-trips to the same semantic
27
+ * value.
28
+ * - Type-safe localization infers the required error formatters from selected
29
+ * Types, so missing validation messages are compile-time errors.
30
+ *
31
+ * Correctness is especially important for local-first data: application authors
32
+ * cannot inspect or repair a user's data.
33
+ *
34
+ * Evolu Type is optimized for small real-world bundles: composed Types share
35
+ * runtime code, while unused validators and formatters are tree-shaken. It
36
+ * could be smaller with less descriptive assertion messages, but Evolu favors
37
+ * actionable diagnostics over micro-optimizing isolated Types.
38
+ *
39
+ * Predefined Types use the names of corresponding JavaScript built-ins. When a
40
+ * Type shadows one, access the JavaScript built-in through `globalThis`, such
41
+ * as `globalThis.String` or `globalThis.Date`.
42
+ *
43
+ * Evolu Type supports [Standard Schema](https://standardschema.dev/) and
44
+ * requires TypeScript 7+ with `exactOptionalPropertyTypes` enabled.
45
+ *
46
+ * ## Examples
47
+ *
48
+ * Define a domain object with a custom `Age` Type, then validate unknown input:
154
49
  *
155
50
  * ```ts
156
- * import { String, type Result } from "@evolu/common";
51
+ * import {
52
+ * Number,
53
+ * NonEmptyTrimmedString100,
54
+ * brand,
55
+ * finite,
56
+ * int,
57
+ * lessThan,
58
+ * nonNaN,
59
+ * nonNegative,
60
+ * object,
61
+ * type Brand,
62
+ * type InferErrors,
63
+ * type InferType,
64
+ * } from "@evolu/common";
157
65
  *
158
- * const value: unknown = "hello";
159
- * const result = String.fromUnknown(value);
66
+ * // Age and its parent Types are predefined by Evolu. They are reconstructed
67
+ * // here to reveal every constraint behind a seemingly simple domain value.
68
+ * const NonNaNNumber = nonNaN(Number);
69
+ * const FiniteNumber = finite(NonNaNNumber);
70
+ * const Int = int(FiniteNumber);
71
+ * const NonNegativeInt = nonNegative(Int);
160
72
  *
161
- * expectTypeOf(result).toEqualTypeOf<
73
+ * const Age = brand("Age", lessThan(200)(NonNegativeInt));
74
+ * type Age = typeof Age.Output;
75
+ *
76
+ * expectTypeOf<Age>().toEqualTypeOf<
77
+ * number &
78
+ * Brand<"NonNaN"> &
79
+ * Brand<"Finite"> &
80
+ * Brand<"Int"> &
81
+ * Brand<"NonNegative"> &
82
+ * Brand<"LessThan200"> &
83
+ * Brand<"Age">
84
+ * >();
85
+ *
86
+ * const User = object({
87
+ * name: NonEmptyTrimmedString100,
88
+ * age: Age,
89
+ * });
90
+ * interface User extends InferType<typeof User> {}
91
+ *
92
+ * const value: unknown = { name: "Ada", age: 37 };
93
+ * const user = User.fromUnknown(value);
94
+ *
95
+ * expectOk(user, { name: "Ada", age: 37 });
96
+ * expectTypeOf(user.value).toExtend<User>();
97
+ *
98
+ * const invalidUser = User.fromUnknown({ name: "Ada", age: 37.5 });
99
+ *
100
+ * expectErr(invalidUser, {
101
+ * type: "Object",
102
+ * reason: {
103
+ * kind: "Properties",
104
+ * errors: {
105
+ * age: { type: "Int", value: 37.5 },
106
+ * },
107
+ * },
108
+ * });
109
+ *
110
+ * // InferErrors includes every structured error User.fromUnknown can return.
111
+ * expectTypeOf(invalidUser.error).toEqualTypeOf<
112
+ * InferErrors<typeof User>
113
+ * >();
114
+ * ```
115
+ *
116
+ * A Type can format its structured errors into user-facing messages:
117
+ *
118
+ * ```ts
119
+ * import { Age } from "@evolu/common";
120
+ *
121
+ * const age = Age.fromUnknown(37.5);
122
+ *
123
+ * expectErr(age, { type: "Int", value: 37.5 });
124
+ * expect(Age.formatError(age.error)).toBe(
125
+ * "The value 37.5 must be a safe integer.",
126
+ * );
127
+ * ```
128
+ *
129
+ * Use {@link localizeTypes} to derive Types with localized messages without
130
+ * changing validation behavior.
131
+ *
132
+ * One of Evolu Type's strongest features is typed `from` boundaries. A value
133
+ * producer, such as a form input, carries the precise constraints it
134
+ * guarantees, and TypeScript checks them against the consuming domain field.
135
+ * Unlike validation from `unknown` or `string`, this checks the contract
136
+ * between the producer and consumer, not merely whether the current value
137
+ * passes:
138
+ *
139
+ * ```ts
140
+ * import {
141
+ * NonEmptyTrimmedString100,
142
+ * NonEmptyTrimmedString1000,
143
+ * object,
144
+ * trim,
145
+ * type MaxLengthError,
146
+ * type MinLengthError,
147
+ * type Result,
148
+ * type TrimmedString,
149
+ * } from "@evolu/common";
150
+ *
151
+ * const Todo = object({ title: NonEmptyTrimmedString100 });
152
+ *
153
+ * // This is type-checked: Todo.from expects NonEmptyTrimmedString100.
154
+ * const title = NonEmptyTrimmedString100.orThrow("Buy milk");
155
+ * expectOk(Todo.from({ title }), { title });
156
+ *
157
+ * // Imagine the UI input component is changed to allow longer titles.
158
+ * // TypeScript rejects the mismatch, so users never see a save error
159
+ * // for a title the UI accepts but the domain cannot save.
160
+ * const longerTitle = NonEmptyTrimmedString1000.orThrow("Buy milk");
161
+ * // @ts-expect-error MaxLength1000 does not guarantee MaxLength100.
162
+ * Todo.from({ title: longerTitle });
163
+ *
164
+ * // Imagine a UI input component that returns TrimmedString.
165
+ * // from.parent.parent connects it to the domain field and validates the
166
+ * // remaining constraints.
167
+ * const titleFromTrimmingInput: TrimmedString = trim(" Buy milk ");
168
+ * const validatedTitle = Todo.props.title.from.parent.parent(
169
+ * titleFromTrimmingInput,
170
+ * );
171
+ *
172
+ * // No "not a string" or "not trimmed" errors: the input guarantees both.
173
+ * expectTypeOf(validatedTitle).toEqualTypeOf<
162
174
  * Result<
163
- * string,
164
- * {
165
- * readonly type: "TypeOf";
166
- * readonly expected: "String";
167
- * readonly value: unknown;
168
- * }
175
+ * NonEmptyTrimmedString100,
176
+ * MaxLengthError<100> | MinLengthError<1>
169
177
  * >
170
178
  * >();
171
- * expectOk(result, "hello");
179
+ * expectOk(validatedTitle, "Buy milk");
172
180
  * ```
173
181
  *
182
+ * Evolu includes dozens of predefined Types and Type factories. Use Types such
183
+ * as {@link Age}, {@link PositiveInt}, {@link DateIso},
184
+ * {@link NonEmptyTrimmedString100}, {@link Base64Url}, and {@link Json} directly.
185
+ * Build domain Types with factories such as {@link brand}, {@link typed},
186
+ * {@link minLength}, {@link maxLength}, {@link array}, {@link object},
187
+ * {@link union}, {@link templateLiteral}, {@link transform},
188
+ * {@link discriminatedUnion}, and {@link json}.
189
+ *
190
+ * ## Guarantees
191
+ *
192
+ * Evolu Type validates values; it does not defend against adversarial
193
+ * JavaScript such as malicious Proxies, mutation during validation, throwing
194
+ * traps, forged built-ins, or code deliberately bypassing TypeScript with `any`
195
+ * or casts.
196
+ *
197
+ * Evolu Type trusts application code and audited dependencies. Untrusted code
198
+ * can cause harm far beyond validation and must not run in the application.
199
+ * Defending against it would add complexity without creating a meaningful
200
+ * security boundary.
201
+ *
202
+ * Runtime assertions still detect accidental developer errors that TypeScript
203
+ * cannot express. They are correctness checks, not defenses against malicious
204
+ * code.
205
+ *
174
206
  * ## FAQ
175
207
  *
176
208
  * ### What does a Type represent?
@@ -287,10 +319,10 @@ import {
287
319
  * is own, enumerable, or a data property. It also permits a wider object with
288
320
  * excess properties where a narrower object type is expected.
289
321
  *
290
- * `fromUnknown` treats such invalid external values as expected data and
291
- * returns a typed error. Typed boundaries instead assert the domain promised by
292
- * their parameter type. If application code claims an accessor-backed object or
293
- * an object with excess properties is an Object Output, the assertion throws
322
+ * `fromUnknown` treats such invalid external values as input data and returns a
323
+ * typed error. Typed boundaries instead assert the domain promised by their
324
+ * parameter type. If application code claims an accessor-backed object or an
325
+ * object with excess properties is an Object Output, the assertion throws
294
326
  * because the application contract is broken. `orThrow` and `orNull` preserve
295
327
  * the assertion at their typed `Input` boundary, then apply {@link getOrThrow}
296
328
  * or {@link getOrNull} only to validation failures returned by the remaining
@@ -319,21 +351,17 @@ import {
319
351
  *
320
352
  * ### How should values from another realm be handled?
321
353
  *
322
- * Code trust and data validation are separate decisions. Values returned by
323
- * trusted legacy code or another realm can still be uncertain and should be
324
- * validated. Realm-neutral Types accept an otherwise legitimate representation
325
- * without requiring conversion merely because its built-ins belong to another
326
- * realm.
354
+ * Values returned by legacy code or another realm can still be uncertain and
355
+ * should be validated. Realm-neutral Types accept an otherwise legitimate
356
+ * representation without requiring conversion merely because its JavaScript
357
+ * built-ins belong to another realm.
327
358
  *
328
- * When an application trusts both the producer and its return contract, it can
329
- * cast the boundary API's `unknown` because validation is redundant. Use a
330
- * specialized Type or explicit transformation when the producer actually uses a
331
- * different representation that needs adaptation or normalization.
332
- *
333
- * All executing JavaScript remains trusted. Deliberately forged built-ins,
334
- * hostile Proxies, throwing traps, or sabotaged executable behavior can throw;
335
- * Evolu Type does not selectively contain them or claim to be a security
336
- * boundary for untrusted code.
359
+ * When an application trusts both the producer and its return contract, expose
360
+ * that contract as an accurate TypeScript type and use the typed value
361
+ * directly. If the boundary returns `unknown`, validate it instead of bypassing
362
+ * the boundary with a cast. Use a specialized Type or explicit transformation
363
+ * when the producer uses a different representation that needs adaptation or
364
+ * normalization.
337
365
  *
338
366
  * ### Why doesn't Evolu Type extract data from rich objects?
339
367
  *
@@ -384,8 +412,8 @@ import {
384
412
  * value to a Task. A pure synchronous conversion that can fail can be an
385
413
  * ordinary function returning Result.
386
414
  *
387
- * Keeping those responsibilities separate prevents an Evolu Type from becoming
388
- * a hidden application workflow. It also keeps validation deterministic,
415
+ * Keeping those responsibilities separate prevents Evolu Type from becoming a
416
+ * hidden application workflow. It also keeps validation deterministic,
389
417
  * dependency-free, immediately composable, and straightforward to test.
390
418
  *
391
419
  * ### What if only decoding is needed?
@@ -397,7 +425,55 @@ import {
397
425
  * irreversible operation is a separate function or Task, not a Type
398
426
  * transformation.
399
427
  *
400
- * @group Core Types
428
+ * @module
429
+ */
430
+ import { utf8ToBytes } from "@noble/ciphers/utils.js";
431
+ import { sha256 } from "@noble/hashes/sha2.js";
432
+ import * as bip39 from "@scure/bip39";
433
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
434
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
435
+ import type {
436
+ AtLeastTwoReadonlyArray,
437
+ NonEmptyReadonlyArray,
438
+ } from "./Array.ts";
439
+ import { assert, assertNonNullable } from "./Assert.ts";
440
+ import type { Brand } from "./Brand.ts";
441
+ import type { RandomBytesDep } from "./Crypto.ts";
442
+ import { identity, type Thunk } from "./Function.ts";
443
+ import { createMutableRecord } from "./Object.ts";
444
+ import { hasNodeBuffer } from "./Platform.ts";
445
+ import {
446
+ err,
447
+ flatMapResult,
448
+ getOk,
449
+ getOrNull,
450
+ getOrThrow,
451
+ ok,
452
+ trySync,
453
+ type Result,
454
+ } from "./Result.ts";
455
+ import { safelyStringifyUnknownValue } from "./String.ts";
456
+ import type { Task } from "./Task.ts";
457
+ import type { TimeDep } from "./Time.ts";
458
+ import {
459
+ instance,
460
+ isInstance,
461
+ type CompileTimeError,
462
+ type Instance,
463
+ type IsUnion,
464
+ type Literal,
465
+ type Simplify,
466
+ type ValueWithLength,
467
+ type WidenLiteral,
468
+ } from "./Types.ts";
469
+
470
+ /**
471
+ * A runtime representation of a TypeScript type, including its encoded input,
472
+ * semantic output, structured errors, and canonical encoding.
473
+ *
474
+ * See the {@link @evolu/common!Type | Type overview}.
475
+ *
476
+ * @group Core
401
477
  */
402
478
  export interface Type<
403
479
  Name extends TypeName,
@@ -489,7 +565,20 @@ export interface Type<
489
565
  /** The one preceding Type node, or `null` for a root Type. */
490
566
  readonly parent: Parent;
491
567
 
492
- /** Decodes an unknown value through the complete Type pipeline. */
568
+ /**
569
+ * Decodes an unknown value through the complete Type pipeline.
570
+ *
571
+ * ### Example
572
+ *
573
+ * ```ts
574
+ * import { PositiveInt } from "@evolu/common";
575
+ *
576
+ * const value: unknown = 42;
577
+ * const result = PositiveInt.fromUnknown(value);
578
+ *
579
+ * expectOk(result, 42);
580
+ * ```
581
+ */
493
582
  readonly fromUnknown: (
494
583
  value: unknown,
495
584
  options?: ValidationOptions,
@@ -497,12 +586,25 @@ export interface Type<
497
586
 
498
587
  /**
499
588
  * Formats an error returned by `fromUnknown` or `from` as one human-readable
500
- * message. Built-in Types use English; {@link localizeTypes} derives Types
589
+ * message. Predefined Types use English; {@link localizeTypes} derives Types
501
590
  * with localized formatters.
502
591
  *
503
592
  * Structural errors retain nested errors and their locations in the typed
504
593
  * error value. This formatter does not encode paths or enumerate nested
505
594
  * errors in its message.
595
+ *
596
+ * ### Example
597
+ *
598
+ * ```ts
599
+ * import { String } from "@evolu/common";
600
+ *
601
+ * const result = String.fromUnknown(42);
602
+ *
603
+ * expectErr(result, { type: "TypeOf", expected: "String", value: 42 });
604
+ * expect(String.formatError(result.error)).toBe(
605
+ * "A value 42 is not a string.",
606
+ * );
607
+ * ```
506
608
  */
507
609
  readonly formatError: TypeErrorFormatter<Errors>;
508
610
 
@@ -540,6 +642,52 @@ export interface Type<
540
642
  * developer error. The Error message identifies the expected boundary Type,
541
643
  * and its cause preserves the structured validation error. Only failures
542
644
  * introduced after that boundary are returned through `Result`.
645
+ *
646
+ * ### Example
647
+ *
648
+ * A form already guarantees every constraint on a title, while a new note
649
+ * input guarantees only trimmed text. The note's parent boundary validates
650
+ * only the constraints that the form does not guarantee:
651
+ *
652
+ * ```ts
653
+ * import {
654
+ * flatMapResult,
655
+ * NonEmptyTrimmedString100,
656
+ * object,
657
+ * TrimmedString,
658
+ * type MaxLengthError,
659
+ * type MinLengthError,
660
+ * type Result,
661
+ * } from "@evolu/common";
662
+ *
663
+ * const Todo = object({
664
+ * title: NonEmptyTrimmedString100,
665
+ * note: NonEmptyTrimmedString100,
666
+ * });
667
+ *
668
+ * const saveTodo = (
669
+ * title: NonEmptyTrimmedString100,
670
+ * note: TrimmedString,
671
+ * ) => {
672
+ * // @ts-expect-error TrimmedString does not guarantee a non-empty value
673
+ * // with at most 100 characters.
674
+ * Todo.from({ title, note });
675
+ *
676
+ * return flatMapResult(
677
+ * Todo.props.note.from.parent.parent(note),
678
+ * (note) => Todo.from({ title, note }),
679
+ * );
680
+ * };
681
+ *
682
+ * const title = NonEmptyTrimmedString100.orThrow("Buy milk");
683
+ * const note = TrimmedString.orThrow("Remember oat milk");
684
+ * const result = saveTodo(title, note);
685
+ *
686
+ * expectTypeOf(result).toEqualTypeOf<
687
+ * Result<typeof Todo.Output, MaxLengthError<100> | MinLengthError<1>>
688
+ * >();
689
+ * expectOk(result, { title, note });
690
+ * ```
543
691
  */
544
692
  readonly from: [CustomFrom] extends [never]
545
693
  ? [Parent] extends [infer P extends TypeNode]
@@ -554,6 +702,16 @@ export interface Type<
554
702
  * `to` runs the complete encoding pipeline. Its first `.parent` stops at the
555
703
  * immediate parent Output, and each additional suffix stops one Type closer
556
704
  * to the root. Every entry point accepts this Type's `Output`.
705
+ *
706
+ * ### Example
707
+ *
708
+ * ```ts
709
+ * import { Int64, Int64FromInt64String } from "@evolu/common";
710
+ *
711
+ * const value = Int64.orThrow(42n);
712
+ *
713
+ * expect(Int64FromInt64String.to(value)).toBe("42");
714
+ * ```
557
715
  */
558
716
  readonly to: [Parent] extends [infer P extends TypeNode]
559
717
  ? ToOperation<Output, CanonicalInput, P>
@@ -586,6 +744,9 @@ export interface Type<
586
744
  *
587
745
  * // Equivalent because `from.parent` is this Type's deepest `from` operation:
588
746
  * const sameValue = getOrThrow(NonEmptyString.from.parent("Evolu"));
747
+ *
748
+ * expect(value).toBe("Evolu");
749
+ * expect(sameValue).toBe(value);
589
750
  * ```
590
751
  */
591
752
  readonly orThrow: TypeOperationFn<"orThrow", Input, Output, never>;
@@ -617,12 +778,20 @@ export interface Type<
617
778
  *
618
779
  * // Equivalent because `from.parent` is this Type's deepest `from` operation:
619
780
  * const sameValue = getOrNull(NonEmptyString.from.parent("Evolu"));
781
+ *
782
+ * expect(value).toBe("Evolu");
783
+ * expect(sameValue).toBe(value);
784
+ * expect(NonEmptyString.orNull("")).toBeNull();
620
785
  * ```
621
786
  */
622
787
  readonly orNull: TypeOperationFn<"orNull", Input, Output, never>;
623
788
  }
624
789
 
625
- /** @group Core Types */
790
+ /**
791
+ * A capitalized name identifying a {@link Type} node.
792
+ *
793
+ * @group Core
794
+ */
626
795
  export type TypeName = Capitalize<string>;
627
796
 
628
797
  /**
@@ -635,7 +804,7 @@ export type TypeName = Capitalize<string>;
635
804
  * only when it intentionally represents the same error contract. Accidental
636
805
  * reuse prevents reliable discriminated-union narrowing.
637
806
  *
638
- * @group Core Types
807
+ * @group Core
639
808
  */
640
809
  export interface TypeError<Name extends TypeName = TypeName> {
641
810
  readonly type: Name;
@@ -653,9 +822,9 @@ interface TransparentTypeError {
653
822
  * Structural errors such as Array and Union errors extend {@link TypeError}
654
823
  * instead because they locate nested errors rather than own one value.
655
824
  *
656
- * @group Core Types
825
+ * @group Core
657
826
  */
658
- // Built-in errors intentionally repeat narrower `value` properties. Making
827
+ // Predefined errors intentionally repeat narrower `value` properties. Making
659
828
  // `value` generic here and sharing this base regresses `pnpm bench:type`.
660
829
  export interface TypeValueError<
661
830
  Name extends TypeName = TypeName,
@@ -666,13 +835,18 @@ export interface TypeValueError<
666
835
  /**
667
836
  * Formats a structured {@link TypeError} as a human-readable message.
668
837
  *
669
- * @group Core Types
838
+ * @group Core
670
839
  */
671
840
  export type TypeErrorFormatter<Error extends TypeError> = (
672
841
  error: Error,
673
842
  ) => string;
674
843
 
675
- /** @group Core Types */
844
+ /**
845
+ * The common structural shape of every {@link Type}, with its specific type
846
+ * parameters erased.
847
+ *
848
+ * @group Core
849
+ */
676
850
  export interface TypeNode {
677
851
  readonly name: TypeName;
678
852
  readonly "~standard": StandardSchemaV1.Props<unknown, unknown>;
@@ -804,7 +978,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
804
978
  * Asserts that a value belongs to a {@link Type} Output domain.
805
979
  *
806
980
  * Use this for internal invariants, not external input. Validate external input
807
- * with `Type.fromUnknown` so expected failures remain typed values. A failed
981
+ * with `Type.fromUnknown` so validation failures remain typed values. A failed
808
982
  * assertion uses the Type name for its message and preserves the exact Output
809
983
  * validation error as the thrown Error's cause.
810
984
  *
@@ -824,7 +998,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
824
998
  * >();
825
999
  * ```
826
1000
  *
827
- * @group Core Types
1001
+ * @group Core
828
1002
  */
829
1003
  export const assertType: <T extends TypeNode>(
830
1004
  type: T,
@@ -860,34 +1034,32 @@ const assertTypeOutput = <Error extends TypeError>(
860
1034
  };
861
1035
 
862
1036
  /**
863
- * Localized copies of selected {@link Type} declarations for every locale.
1037
+ * Creates localized copies of selected {@link Type} declarations.
864
1038
  *
865
- * Each locale supplies one formatter for every Type that can own a formatted
866
- * error. Structural Types use their own formatter for structural failures and
867
- * delegate contained failures to the Type that produced them. A Union owns its
868
- * complete failure because no member matched. Formatter requirements are
869
- * inferred recursively from structured errors, including mutually recursive
870
- * Lazy error interfaces.
1039
+ * Pass the Types used together in one localization scope and formatter maps
1040
+ * keyed by locale. TypeScript infers every formatter required by the selected
1041
+ * Types, including errors from nested structural Types and recursive Lazy
1042
+ * Types. Every locale must provide the complete inferred formatter set; missing
1043
+ * and unrelated formatters are compile-time errors.
871
1044
  *
872
- * Parents and Types exposed through public reflection are localized recursively
873
- * through one shared cache. Lazy definitions stay opaque and are not evaluated;
874
- * their declared error types provide the formatter requirements instead.
1045
+ * The result preserves the locale names, selected Type names, and exact
1046
+ * TypeScript types. A localized Type validates exactly like its source Type;
1047
+ * only its human-readable `formatError` and Standard Schema messages change.
1048
+ * The source Types remain unchanged.
875
1049
  *
876
- * The selected Type map, locale map, and each formatter map must be plain
877
- * objects whose entries are own enumerable string-keyed data properties.
1050
+ * Parents and reflected child Types are localized with the same formatter set.
1051
+ * Structural Types retain error paths and delegate nested messages to the Type
1052
+ * that produced them. Different localized Type sets can coexist in separate
1053
+ * application or dependency-injection scopes.
878
1054
  *
879
- * All localized Types for one locale share the same formatter set. The result
880
- * preserves the selected names and exact TypeScript types under every locale
881
- * key, making one localized set easy to provide through dependency injection or
882
- * application context. Canonical Types remain unchanged.
1055
+ * Localization is scoped to the selected Types instead of a package-wide
1056
+ * translation registry. Static imports give bundlers an explicit dependency
1057
+ * graph, so unrelated Types, locales, and formatters can be removed. Bundling
1058
+ * every locale an app supports also allows language changes without a network
1059
+ * connection.
883
1060
  *
884
- * Localization is scoped to the Types an app imports instead of a package-wide
885
- * translation registry. Ordinary static imports give bundlers an explicit
886
- * dependency graph from those Types to their formatters, so unrelated Type and
887
- * localization code can be removed. An app supplies all locales it supports in
888
- * the same self-contained bundle, allowing users to change language without a
889
- * network connection. Different localized Type sets can coexist on one page or
890
- * in separate dependency-injection scopes.
1061
+ * The selected Type map, locale map, and formatter maps must be plain objects
1062
+ * with own enumerable string-keyed data properties.
891
1063
  *
892
1064
  * ### Example
893
1065
  *
@@ -897,7 +1069,7 @@ const assertTypeOutput = <Error extends TypeError>(
897
1069
  *
898
1070
  * const Label = minLength(1)(String);
899
1071
  *
900
- * const TypesByLocale = localizeTypes(
1072
+ * const typesByLocale = localizeTypes(
901
1073
  * { Label },
902
1074
  * {
903
1075
  * cs: {
@@ -907,11 +1079,66 @@ const assertTypeOutput = <Error extends TypeError>(
907
1079
  * },
908
1080
  * );
909
1081
  *
910
- * expectTypeOf<typeof TypesByLocale.cs.Label>().toEqualTypeOf<
1082
+ * expectTypeOf<typeof typesByLocale.cs.Label>().toEqualTypeOf<
911
1083
  * typeof Label
912
1084
  * >();
1085
+ *
1086
+ * const result = typesByLocale.cs.Label.fromUnknown("");
1087
+ * expectErr(result, { type: "MinLength1", min: 1, value: "" });
1088
+ * expect(typesByLocale.cs.Label.formatError(result.error)).toBe(
1089
+ * "Text nesmí být prázdný.",
1090
+ * );
913
1091
  * ```
914
1092
  *
1093
+ * ### Supported locales
1094
+ *
1095
+ * English is built in; use {@link Type} directly for its default formatters. The
1096
+ * following additional locales are available:
1097
+ *
1098
+ * - Arabic (`ar`)
1099
+ * - Bengali (`bn`)
1100
+ * - Catalan (`ca`)
1101
+ * - Chinese, Simplified (`zhCN`)
1102
+ * - Chinese, Traditional (`zhTW`)
1103
+ * - Croatian (`hr`)
1104
+ * - Czech (`cs`)
1105
+ * - Danish (`da`)
1106
+ * - Dutch (`nl`)
1107
+ * - Filipino (`fil`)
1108
+ * - Finnish (`fi`)
1109
+ * - French (`fr`)
1110
+ * - German (`de`)
1111
+ * - Greek (`el`)
1112
+ * - Hebrew (`he`)
1113
+ * - Hindi (`hi`)
1114
+ * - Hungarian (`hu`)
1115
+ * - Indonesian (`id`)
1116
+ * - Italian (`it`)
1117
+ * - Japanese (`ja`)
1118
+ * - Korean (`ko`)
1119
+ * - Malay (`ms`)
1120
+ * - Malayalam (`ml`)
1121
+ * - Marathi (`mr`)
1122
+ * - Norwegian Bokmål (`nb`)
1123
+ * - Persian (`fa`)
1124
+ * - Polish (`pl`)
1125
+ * - Portuguese (`pt`)
1126
+ * - Portuguese, Brazilian (`ptBR`)
1127
+ * - Punjabi (`pa`)
1128
+ * - Romanian (`ro`)
1129
+ * - Slovak (`sk`)
1130
+ * - Slovenian (`sl`)
1131
+ * - Spanish (`es`)
1132
+ * - Swahili (`sw`)
1133
+ * - Swedish (`sv`)
1134
+ * - Tamil (`ta`)
1135
+ * - Telugu (`te`)
1136
+ * - Thai (`th`)
1137
+ * - Turkish (`tr`)
1138
+ * - Ukrainian (`uk`)
1139
+ * - Urdu (`ur`)
1140
+ * - Vietnamese (`vi`)
1141
+ *
915
1142
  * @group Localization
916
1143
  */
917
1144
  export const localizeTypes = ((
@@ -1277,16 +1504,21 @@ declare const identityEncodingSymbol: unique symbol;
1277
1504
  * age: optional(PositiveInt),
1278
1505
  * });
1279
1506
  * interface User extends InferType<typeof User> {}
1507
+ *
1508
+ * const user = User.orThrow({ name: "Ada", age: 37 });
1509
+ *
1510
+ * expectTypeOf(user).toExtend<User>();
1511
+ * expect(user.name).toBe("Ada");
1280
1512
  * ```
1281
1513
  *
1282
- * @group Type utilities
1514
+ * @group Core
1283
1515
  */
1284
1516
  export type InferType<T extends TypeNode> = T["Output"];
1285
1517
 
1286
1518
  /**
1287
1519
  * The union of errors a {@link Type} can return from `fromUnknown`.
1288
1520
  *
1289
- * @group Type utilities
1521
+ * @group Core
1290
1522
  */
1291
1523
  export type InferErrors<T extends TypeNode> = T[typeof errorsSymbol];
1292
1524
 
@@ -1501,7 +1733,7 @@ type TypeOperationFn<
1501
1733
  /**
1502
1734
  * Configures how container {@link Type} operations report errors.
1503
1735
  *
1504
- * @group Core Types
1736
+ * @group Core
1505
1737
  */
1506
1738
  export interface ValidationOptions {
1507
1739
  /** Controls whether container {@link Type} operations return one or all errors. */
@@ -1637,7 +1869,7 @@ type ConcreteChildTypeNameError = CompileTimeError<
1637
1869
  * expectErr(Text.fromUnknown(42), { type: "Text", value: 42 });
1638
1870
  * ```
1639
1871
  *
1640
- * @group Type construction
1872
+ * @group Construction
1641
1873
  */
1642
1874
  export function createType<
1643
1875
  Name extends TypeName,
@@ -1649,6 +1881,8 @@ export function createType<
1649
1881
  // Validation alone determines Error; broad formatters must not widen it.
1650
1882
  formatError: TypeErrorFormatter<NoInfer<Error>>,
1651
1883
  ): Type<Name, Output, Output, Error, null, Error, never, Output>;
1884
+
1885
+ /** Creates an infallible child Type that preserves its parent's Output. */
1652
1886
  export function createType<
1653
1887
  Name extends TypeName,
1654
1888
  ParentType extends ConcreteTypeNode,
@@ -1669,6 +1903,8 @@ export function createType<
1669
1903
  CanonicalInputForChild<ParentType, ParentType["Output"]>,
1670
1904
  IdentityEncodingOf<ParentType>
1671
1905
  >;
1906
+
1907
+ /** Creates a fallible child Type that narrows its parent's Output. */
1672
1908
  export function createType<
1673
1909
  Name extends TypeName,
1674
1910
  ParentType extends ConcreteTypeNode,
@@ -1887,12 +2123,19 @@ const createChildType = <
1887
2123
  * `from` accepts the semantic Output. `from.parent` converts the parent Output
1888
2124
  * to the output Type Input, then runs the complete output Type pipeline. `to`
1889
2125
  * canonically encodes every output Type value before converting it back through
1890
- * the parent Type. Transformation callbacks are Type construction code. Their
1891
- * successful results are asserted against the declared boundary so a broken
1892
- * callback fails as a developer error rather than becoming a validation error.
1893
- * Like all Type-construction callbacks, they are trusted to follow their
1894
- * declared TypeScript types. A `Result<_, never>` callback is therefore trusted
1895
- * never to return an `Err`.
2126
+ * the parent Type.
2127
+ *
2128
+ * The callbacks must form a lawful codec: decoding `to(output)` must reproduce
2129
+ * the same semantic Output for every valid output value. Encoding may
2130
+ * canonicalize multiple parent representations, but it must be total and must
2131
+ * not lose distinctions present in the Output domain.
2132
+ *
2133
+ * Transformation callbacks are Type construction code. Their successful results
2134
+ * are asserted against the declared boundary so a broken callback fails as a
2135
+ * developer error rather than becoming a validation error. Like all
2136
+ * Type-construction callbacks, they are trusted to follow their declared
2137
+ * TypeScript types. A `Result<_, never>` callback is therefore trusted never to
2138
+ * return an `Err`.
1896
2139
  *
1897
2140
  * Errors from the parent and the forward callback remain unchanged. A forward
1898
2141
  * callback error must use the transformation name as its type. Errors from the
@@ -1905,18 +2148,24 @@ const createChildType = <
1905
2148
  * ### Example
1906
2149
  *
1907
2150
  * ```ts
1908
- * import { Number, String, ok, transform } from "@evolu/common";
2151
+ * import { Boolean, literal, ok, transform, union } from "@evolu/common";
1909
2152
  *
1910
- * const NumberFromString = transform("NumberFromString", String, Number, {
1911
- * from: (value) => ok(globalThis.Number(value)),
1912
- * to: globalThis.String,
1913
- * });
2153
+ * const BooleanString = union(literal("false"), literal("true"));
2154
+ * const BooleanFromString = transform(
2155
+ * "BooleanFromString",
2156
+ * BooleanString,
2157
+ * Boolean,
2158
+ * {
2159
+ * from: (value) => ok(value === "true"),
2160
+ * to: (value) => (value ? "true" : "false"),
2161
+ * },
2162
+ * );
1914
2163
  *
1915
- * expectOk(NumberFromString.from.parent("42"), 42);
1916
- * assert(NumberFromString.to(42) === "42");
2164
+ * expectOk(BooleanFromString.from.parent("true"), true);
2165
+ * expect(BooleanFromString.to(false)).toBe("false");
1917
2166
  * ```
1918
2167
  *
1919
- * @group Type construction
2168
+ * @group Construction
1920
2169
  */
1921
2170
  export function transform<
1922
2171
  Name extends TypeName,
@@ -1934,6 +2183,8 @@ export function transform<
1934
2183
  readonly to: (value: CanonicalInputOf<OutputType>) => ToOutput;
1935
2184
  },
1936
2185
  ): TransformType<ParentType, OutputType, Name, never, ToOutput>;
2186
+
2187
+ /** Creates a fallible transformed Type with its own error formatter. */
1937
2188
  export function transform<
1938
2189
  Name extends TypeName,
1939
2190
  ParentType extends ConcreteTypeNode,
@@ -2038,7 +2289,11 @@ export function transform(
2038
2289
  );
2039
2290
  }
2040
2291
 
2041
- /** @group Type construction */
2292
+ /**
2293
+ * The {@link Type} returned by {@link transform}.
2294
+ *
2295
+ * @group Construction
2296
+ */
2042
2297
  export interface TransformType<
2043
2298
  ParentType extends TypeNode,
2044
2299
  OutputType extends TypeNode,
@@ -2065,7 +2320,12 @@ export interface TransformType<
2065
2320
  readonly output: OutputType;
2066
2321
  }
2067
2322
 
2068
- /** @group Type construction */
2323
+ /**
2324
+ * An error produced by {@link transform} while decoding or validating its
2325
+ * output.
2326
+ *
2327
+ * @group Construction
2328
+ */
2069
2329
  export type TransformError<
2070
2330
  Name extends TypeName,
2071
2331
  OwnError extends TypeError<Name>,
@@ -2079,7 +2339,7 @@ export type TransformError<
2079
2339
  /**
2080
2340
  * Wraps an error produced by the output {@link Type} of {@link transform}.
2081
2341
  *
2082
- * @group Type construction
2342
+ * @group Construction
2083
2343
  */
2084
2344
  export interface TransformOutputError<
2085
2345
  Name extends TypeName,
@@ -2345,14 +2605,22 @@ const addRuntimeAssertions = (
2345
2605
  return asserted;
2346
2606
  };
2347
2607
 
2348
- /** @group Base Types */
2608
+ /**
2609
+ * An infallible {@link Type} accepting every value.
2610
+ *
2611
+ * @group Base
2612
+ */
2349
2613
  export const Unknown = /*#__PURE__*/ createRootType<"Unknown", unknown, never>(
2350
2614
  "Unknown",
2351
2615
  ok,
2352
2616
  identity,
2353
2617
  );
2354
2618
 
2355
- /** @group Base Types */
2619
+ /**
2620
+ * A {@link Type} rejecting every value.
2621
+ *
2622
+ * @group Base
2623
+ */
2356
2624
  export const Never = /*#__PURE__*/ createRootType(
2357
2625
  "Never",
2358
2626
  (value): Result<never, NeverError> => err({ type: "Never", value }),
@@ -2360,6 +2628,11 @@ export const Never = /*#__PURE__*/ createRootType(
2360
2628
  `A value ${safelyStringifyUnknownValue(error.value)} is not valid for type Never.`,
2361
2629
  );
2362
2630
 
2631
+ /**
2632
+ * Error returned by {@link Never} for every value.
2633
+ *
2634
+ * @group Base
2635
+ */
2363
2636
  export interface NeverError extends TypeError<"Never"> {
2364
2637
  readonly value: unknown;
2365
2638
  }
@@ -2398,7 +2671,11 @@ interface TypeOfOutputByName {
2398
2671
  readonly Function: globalThis.Function;
2399
2672
  }
2400
2673
 
2401
- /** @group Base Types */
2674
+ /**
2675
+ * Error returned when `typeof` does not match the expected JavaScript type.
2676
+ *
2677
+ * @group Base
2678
+ */
2402
2679
  export interface TypeOfError<
2403
2680
  Name extends keyof TypeOfOutputByName,
2404
2681
  > extends TypeError<"TypeOf"> {
@@ -2429,6 +2706,8 @@ export interface TypeOfError<
2429
2706
  * expectTypeOf<WireValue100>().toEqualTypeOf<
2430
2707
  * string & Brand<"MaxLength100">
2431
2708
  * >();
2709
+ * expectOk(WireValue100.fromUnknown(""), "");
2710
+ * expectOk(WireValue100.fromUnknown(" value "), " value ");
2432
2711
  * ```
2433
2712
  *
2434
2713
  * @group String
@@ -2505,25 +2784,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
2505
2784
  */
2506
2785
  export const Number = /*#__PURE__*/ createTypeOfType("Number");
2507
2786
 
2508
- /** @group Base Types */
2787
+ /**
2788
+ * A JavaScript bigint {@link Type}.
2789
+ *
2790
+ * @group Base
2791
+ */
2509
2792
  export const BigInt = /*#__PURE__*/ createTypeOfType("BigInt");
2510
2793
 
2511
- /** @group Base Types */
2794
+ /**
2795
+ * A JavaScript boolean {@link Type}.
2796
+ *
2797
+ * @group Base
2798
+ */
2512
2799
  export const Boolean = /*#__PURE__*/ createTypeOfType("Boolean");
2513
2800
 
2514
- /** @group Base Types */
2801
+ /**
2802
+ * A JavaScript symbol {@link Type}.
2803
+ *
2804
+ * @group Base
2805
+ */
2515
2806
  export const Symbol = /*#__PURE__*/ createTypeOfType("Symbol");
2516
2807
 
2517
- /** @group Base Types */
2808
+ /**
2809
+ * A JavaScript function {@link Type}.
2810
+ *
2811
+ * @group Base
2812
+ */
2518
2813
  export const Function = /*#__PURE__*/ createTypeOfType("Function");
2519
2814
 
2520
2815
  /**
2521
- * An Evolu {@link Type} validating other Evolu Types.
2816
+ * A {@link Type} validating Evolu Type declarations.
2522
2817
  *
2523
2818
  * This is useful when a Type itself crosses an unknown boundary or must be
2524
2819
  * asserted with {@link assertType}.
2525
2820
  *
2526
- * @group Core Types
2821
+ * @group Core
2527
2822
  */
2528
2823
  export const EvoluType = /*#__PURE__*/ createType(
2529
2824
  "EvoluType",
@@ -2545,23 +2840,23 @@ export const EvoluType = /*#__PURE__*/ createType(
2545
2840
  * the recursive `TypeNode` shape so composing Types does not repeatedly add its
2546
2841
  * compiler cost; {@link EvoluType} bridges that runtime evidence to this type.
2547
2842
  *
2548
- * @group Core Types
2843
+ * @group Core
2549
2844
  */
2550
2845
  export interface AnyType extends TypeNode {
2551
2846
  readonly [concreteTypeSymbol]: true;
2552
2847
  }
2553
2848
 
2554
2849
  /**
2555
- * Error returned when a value is not an Evolu {@link Type}.
2850
+ * Error returned when {@link EvoluType} rejects a value.
2556
2851
  *
2557
- * @group Core Types
2852
+ * @group Core
2558
2853
  */
2559
2854
  export interface EvoluTypeError extends TypeValueError<"EvoluType"> {}
2560
2855
 
2561
2856
  /**
2562
2857
  * Nominal evidence that a value has one object tag.
2563
2858
  *
2564
- * @group Base Types
2859
+ * @group Base
2565
2860
  */
2566
2861
  export interface ObjectTag<Name extends TypeName> {
2567
2862
  readonly [objectTagSymbol]: Name;
@@ -2570,7 +2865,7 @@ export interface ObjectTag<Name extends TypeName> {
2570
2865
  /**
2571
2866
  * The {@link Type} returned by {@link objectTag}.
2572
2867
  *
2573
- * @group Base Types
2868
+ * @group Base
2574
2869
  */
2575
2870
  export interface ObjectTagType<
2576
2871
  Name extends TypeName,
@@ -2597,7 +2892,7 @@ export interface ObjectTagType<
2597
2892
  /**
2598
2893
  * An error returned when an object does not report the expected tag.
2599
2894
  *
2600
- * @group Base Types
2895
+ * @group Base
2601
2896
  */
2602
2897
  export interface ObjectTagError<
2603
2898
  Expected extends TypeName = TypeName,
@@ -2615,20 +2910,31 @@ interface ObjectTagOutputByName {
2615
2910
  /**
2616
2911
  * Realm-neutral {@link Type} trusting an object's reported tag.
2617
2912
  *
2618
- * Predefined built-in tags expose their native Output type under the assumption
2619
- * that trusted code does not forge their tags. They do not verify native
2620
- * internal slots. A custom tag refines the supplied Type and adds nominal
2621
- * evidence to its Output, so only a value validated by the resulting Type is
2622
- * accepted by its typed operations.
2913
+ * Predefined tags for JavaScript built-ins expose their native Output type
2914
+ * under the assumption that trusted code does not forge their tags. They do not
2915
+ * verify native internal slots. A custom tag refines the supplied Type and adds
2916
+ * nominal evidence to its Output, so only a value validated by the resulting
2917
+ * Type is accepted by its typed operations.
2918
+ *
2919
+ * `Object.prototype.toString` recognizes legitimate JavaScript built-ins from
2920
+ * another realm, but any object can customize the result with
2921
+ * `Symbol.toStringTag`. Types returned by this factory therefore classify
2922
+ * trusted values; they are not security boundaries. Passing a forged JavaScript
2923
+ * built-in tag violates the trust assumption of the predefined Type. Primitive
2924
+ * Outputs are rejected at compile time.
2925
+ *
2926
+ * ### Example
2927
+ *
2928
+ * ```ts
2929
+ * import { objectTag } from "@evolu/common";
2930
+ *
2931
+ * const DateType = objectTag("Date");
2932
+ * const date = new globalThis.Date("2025-01-01T00:00:00.000Z");
2623
2933
  *
2624
- * `Object.prototype.toString` recognizes legitimate built-ins from another
2625
- * realm, but any object can customize the result with `Symbol.toStringTag`.
2626
- * Types returned by this factory therefore classify trusted values; they are
2627
- * not security boundaries. Passing a forged built-in tag violates the trust
2628
- * assumption of the predefined Type. Primitive Outputs are rejected at compile
2629
- * time.
2934
+ * expectOk(DateType.fromUnknown(date), date);
2935
+ * ```
2630
2936
  *
2631
- * @group Base Types
2937
+ * @group Base
2632
2938
  */
2633
2939
  export function objectTag<Name extends keyof ObjectTagOutputByName>(
2634
2940
  name: ValidateConcreteTypeName<Name>,
@@ -2642,6 +2948,8 @@ export function objectTag<Name extends keyof ObjectTagOutputByName>(
2642
2948
  never,
2643
2949
  ObjectTagOutputByName[Name]
2644
2950
  >;
2951
+
2952
+ /** Creates an object-tag Type by refining an existing object Type. */
2645
2953
  export function objectTag<
2646
2954
  Name extends TypeName,
2647
2955
  OutputType extends ConcreteTypeNode & { readonly Output: object },
@@ -2656,6 +2964,9 @@ export function objectTag(
2656
2964
  name: TypeName,
2657
2965
  outputType?: ConcreteTypeNode & { readonly Output: object },
2658
2966
  ): TypeNode {
2967
+ const formatError: TypeErrorFormatter<ObjectTagError> = (error) =>
2968
+ `A value ${safelyStringifyUnknownValue(error.value)} does not have the expected object tag ${safelyStringifyUnknownValue(error.expected)}.`;
2969
+
2659
2970
  if (outputType === undefined) {
2660
2971
  return createRootType(
2661
2972
  name,
@@ -2663,7 +2974,7 @@ export function objectTag(
2663
2974
  hasObjectTag(value, name)
2664
2975
  ? ok(value as object)
2665
2976
  : err({ type: "ObjectTag", expected: name, value }),
2666
- formatObjectTagError,
2977
+ formatError,
2667
2978
  );
2668
2979
  }
2669
2980
 
@@ -2675,7 +2986,7 @@ export function objectTag(
2675
2986
  hasObjectTag(value, name)
2676
2987
  ? ok(value as object & ObjectTag<TypeName>)
2677
2988
  : err({ type: "ObjectTag", expected: name, value }),
2678
- formatObjectTagError,
2989
+ formatError,
2679
2990
  ),
2680
2991
  { expected: name },
2681
2992
  );
@@ -2683,9 +2994,6 @@ export function objectTag(
2683
2994
 
2684
2995
  declare const objectTagSymbol: unique symbol;
2685
2996
 
2686
- const formatObjectTagError: TypeErrorFormatter<ObjectTagError> = (error) =>
2687
- `A value ${safelyStringifyUnknownValue(error.value)} does not have the object tag ${safelyStringifyUnknownValue(error.expected)}.`;
2688
-
2689
2997
  const hasObjectTag = (value: unknown, expected: string): boolean =>
2690
2998
  value !== null &&
2691
2999
  (typeof value === "object" || typeof value === "function") &&
@@ -2696,7 +3004,7 @@ const hasObjectTag = (value: unknown, expected: string): boolean =>
2696
3004
  *
2697
3005
  * It trusts the reported object tag and does not verify Date internal slots.
2698
3006
  *
2699
- * @group Base Types
3007
+ * @group Base
2700
3008
  */
2701
3009
  export const Date = /*#__PURE__*/ objectTag("Date");
2702
3010
 
@@ -2706,7 +3014,7 @@ export const Date = /*#__PURE__*/ objectTag("Date");
2706
3014
  * It trusts the reported object tag and does not verify Uint8Array internal
2707
3015
  * slots.
2708
3016
  *
2709
- * @group Base Types
3017
+ * @group Base
2710
3018
  */
2711
3019
  export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
2712
3020
 
@@ -2716,7 +3024,7 @@ export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
2716
3024
  * It trusts the reported object tag and does not verify ArrayBuffer internal
2717
3025
  * slots.
2718
3026
  *
2719
- * @group Base Types
3027
+ * @group Base
2720
3028
  */
2721
3029
  export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
2722
3030
 
@@ -2748,7 +3056,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
2748
3056
  * assert(!UserInstance.is({ name: "Ada" }));
2749
3057
  * ```
2750
3058
  *
2751
- * @group Base Types
3059
+ * @group Base
2752
3060
  */
2753
3061
  export const instanceOf = <Constructor extends InstanceConstructor>(
2754
3062
  constructor: ValidateInstanceConstructor<Constructor>,
@@ -2779,16 +3087,20 @@ export const instanceOf = <Constructor extends InstanceConstructor>(
2779
3087
  /**
2780
3088
  * A JavaScript class constructor accepted by {@link instanceOf}.
2781
3089
  *
2782
- * @group Base Types
3090
+ * @group Base
2783
3091
  */
2784
3092
  export type InstanceConstructor<Instance extends object = object> =
2785
3093
  (abstract new (...args: ReadonlyArray<never>) => Instance) & {
2786
3094
  readonly name: string;
2787
3095
  };
2788
3096
 
2789
- /** @group Base Types */
2790
- export interface InstanceOfType<
2791
- Constructor extends InstanceConstructor,
3097
+ /**
3098
+ * The {@link Type} returned by {@link instanceOf}.
3099
+ *
3100
+ * @group Base
3101
+ */
3102
+ export interface InstanceOfType<
3103
+ Constructor extends InstanceConstructor,
2792
3104
  > extends Type<
2793
3105
  "InstanceOf",
2794
3106
  InstanceOfOutput<Constructor>,
@@ -2807,7 +3119,11 @@ type InstanceOfOutput<Constructor extends InstanceConstructor> =
2807
3119
  ? Output
2808
3120
  : InstanceType<Constructor>;
2809
3121
 
2810
- /** @group Base Types */
3122
+ /**
3123
+ * Error returned when a value is not an instance of the expected constructor.
3124
+ *
3125
+ * @group Base
3126
+ */
2811
3127
  export interface InstanceOfError extends TypeValueError<"InstanceOf"> {
2812
3128
  readonly constructorName: string;
2813
3129
  }
@@ -2895,7 +3211,11 @@ export const literal = <const Expected extends Literal>(
2895
3211
  ) as unknown as LiteralType<Expected>;
2896
3212
  };
2897
3213
 
2898
- /** @group Unions */
3214
+ /**
3215
+ * The {@link Type} returned by {@link literal}.
3216
+ *
3217
+ * @group Unions
3218
+ */
2899
3219
  export interface LiteralType<Expected extends Literal> extends Type<
2900
3220
  "Literal",
2901
3221
  WidenLiteral<Expected>,
@@ -2941,7 +3261,11 @@ type LiteralCompileTimeError = CompileTimeError<
2941
3261
  "Expected must be one concrete literal value."
2942
3262
  >;
2943
3263
 
2944
- /** @group Unions */
3264
+ /**
3265
+ * Error returned when a value does not equal the expected literal.
3266
+ *
3267
+ * @group Unions
3268
+ */
2945
3269
  export interface LiteralError<
2946
3270
  Expected extends Literal = Literal,
2947
3271
  > extends TypeError<"Literal"> {
@@ -2949,10 +3273,18 @@ export interface LiteralError<
2949
3273
  readonly value: unknown;
2950
3274
  }
2951
3275
 
2952
- /** @group Unions */
3276
+ /**
3277
+ * Literal {@link Type} accepting only `undefined`.
3278
+ *
3279
+ * @group Unions
3280
+ */
2953
3281
  export const Undefined = /*#__PURE__*/ literal(undefined);
2954
3282
 
2955
- /** @group Unions */
3283
+ /**
3284
+ * Literal {@link Type} accepting only `null`.
3285
+ *
3286
+ * @group Unions
3287
+ */
2956
3288
  export const Null = /*#__PURE__*/ literal(null);
2957
3289
 
2958
3290
  /**
@@ -2978,7 +3310,10 @@ export const Null = /*#__PURE__*/ literal(null);
2978
3310
  * within each member.
2979
3311
  *
2980
3312
  * Member order matters when multiple members accept the same value: validation
2981
- * and encoding use the first matching member.
3313
+ * and encoding use the first matching member. When member Inputs overlap,
3314
+ * decoding the value emitted by the first member selected for an Output must
3315
+ * reproduce that semantic Output; otherwise the Union violates the round-trip
3316
+ * law.
2982
3317
  *
2983
3318
  * ### Example
2984
3319
  *
@@ -3017,11 +3352,15 @@ export function union<const Expected extends AtLeastTwoReadonlyArray<Literal>>(
3017
3352
  ): UnionType<{
3018
3353
  readonly [Index in keyof Expected]: LiteralType<Expected[Index]>;
3019
3354
  }>;
3355
+
3356
+ /** Creates a Union Type from Type members. */
3020
3357
  export function union<const Members extends AtLeastTwoReadonlyArray<TypeNode>>(
3021
3358
  ...members: {
3022
3359
  readonly [Index in keyof Members]: ValidateUnionTypeMember<Members[Index]>;
3023
3360
  }
3024
3361
  ): UnionType<Members>;
3362
+
3363
+ /** Creates a Union Type from Type and literal members. */
3025
3364
  export function union<
3026
3365
  const Members extends AtLeastTwoReadonlyArray<TypeNode | Literal>,
3027
3366
  >(
@@ -3046,7 +3385,8 @@ export function union(
3046
3385
  inputMembers,
3047
3386
  (member, value, options) => member[outputValidationSymbol](value, options),
3048
3387
  );
3049
- const defaultFormatter = formatUnionError as TypeErrorFormatter<TypeError>;
3388
+ const defaultFormatter = (() =>
3389
+ "A value does not match any allowed variant.") as TypeErrorFormatter<TypeError>;
3050
3390
  const getTypeIssues: RuntimeGetTypeIssues = (error) =>
3051
3391
  singleRuntimeTypeIssue("Union", error, defaultFormatter);
3052
3392
  const input = createTypeNode<
@@ -3101,9 +3441,6 @@ export function union(
3101
3441
  );
3102
3442
  }
3103
3443
 
3104
- const formatUnionError: TypeErrorFormatter<UnionErrorValue<TypeError>> = () =>
3105
- "A value does not match any union member.";
3106
-
3107
3444
  const createUnionValidation =
3108
3445
  (
3109
3446
  members: ReadonlyArray<RuntimeTypeNode>,
@@ -3146,6 +3483,16 @@ const createUnionValidation =
3146
3483
  * This does not make an object property optional. It changes only the values
3147
3484
  * accepted when the property is present.
3148
3485
  *
3486
+ * ### Example
3487
+ *
3488
+ * ```ts
3489
+ * import { String, undefinedOr } from "@evolu/common";
3490
+ *
3491
+ * const StringOrUndefined = undefinedOr(String);
3492
+ *
3493
+ * expectOk(StringOrUndefined.fromUnknown(undefined), undefined);
3494
+ * ```
3495
+ *
3149
3496
  * @group Unions
3150
3497
  */
3151
3498
  export const undefinedOr = <ValueType extends TypeNode>(
@@ -3155,6 +3502,16 @@ export const undefinedOr = <ValueType extends TypeNode>(
3155
3502
  /**
3156
3503
  * Union {@link Type} containing the supplied Type and `null`.
3157
3504
  *
3505
+ * ### Example
3506
+ *
3507
+ * ```ts
3508
+ * import { String, nullOr } from "@evolu/common";
3509
+ *
3510
+ * const NullableString = nullOr(String);
3511
+ *
3512
+ * expectOk(NullableString.fromUnknown(null), null);
3513
+ * ```
3514
+ *
3158
3515
  * @group Unions
3159
3516
  */
3160
3517
  export const nullOr = <ValueType extends TypeNode>(
@@ -3164,6 +3521,17 @@ export const nullOr = <ValueType extends TypeNode>(
3164
3521
  /**
3165
3522
  * Union {@link Type} containing the supplied Type, `null`, and `undefined`.
3166
3523
  *
3524
+ * ### Example
3525
+ *
3526
+ * ```ts
3527
+ * import { String, nullishOr } from "@evolu/common";
3528
+ *
3529
+ * const NullishString = nullishOr(String);
3530
+ *
3531
+ * expectOk(NullishString.fromUnknown(undefined), undefined);
3532
+ * expectOk(NullishString.fromUnknown(null), null);
3533
+ * ```
3534
+ *
3167
3535
  * @group Unions
3168
3536
  */
3169
3537
  export const nullishOr = <ValueType extends TypeNode>(
@@ -3205,7 +3573,11 @@ type NormalizeUnionMembers<
3205
3573
  ? Normalized
3206
3574
  : never;
3207
3575
 
3208
- /** @group Unions */
3576
+ /**
3577
+ * The {@link Type} returned by {@link union}.
3578
+ *
3579
+ * @group Unions
3580
+ */
3209
3581
  export interface UnionType<
3210
3582
  Members extends AtLeastTwoReadonlyArray<TypeNode>,
3211
3583
  > extends Type<
@@ -3283,13 +3655,21 @@ type RootUnionMembers<Members extends AtLeastTwoReadonlyArray<TypeNode>> = {
3283
3655
  ? RootMembers
3284
3656
  : never;
3285
3657
 
3286
- /** @group Unions */
3658
+ /**
3659
+ * Error returned when every member of a {@link union} rejects an input.
3660
+ *
3661
+ * @group Unions
3662
+ */
3287
3663
  export type UnionError<
3288
3664
  Error extends TypeError = TypeError,
3289
3665
  MemberError extends UnionMemberError<Error> = UnionMemberError<Error>,
3290
3666
  > = [Error] extends [never] ? never : UnionErrorValue<Error, MemberError>;
3291
3667
 
3292
- /** @group Unions */
3668
+ /**
3669
+ * An error returned by one {@link union} member and its index.
3670
+ *
3671
+ * @group Unions
3672
+ */
3293
3673
  export interface UnionMemberError<
3294
3674
  Error extends TypeError,
3295
3675
  Index extends number = number,
@@ -3353,9 +3733,7 @@ interface UnionErrorValue<
3353
3733
  }
3354
3734
 
3355
3735
  /**
3356
- * Template literal {@link Type} for validation and parsing.
3357
- *
3358
- * Parses and creates structured strings.
3736
+ * Template literal {@link Type} that parses canonical strings into Tuples.
3359
3737
  *
3360
3738
  * Accepts the same template parts as {@link templateLiteral}: fixed string
3361
3739
  * literals and Types canonically encoded as strings. Instead of keeping Output
@@ -3371,10 +3749,10 @@ interface UnionErrorValue<
3371
3749
  * reversibility and keeps capture boundaries unambiguous. Different capture
3372
3750
  * Tuples must never encode to the same string. The parser provides predictable
3373
3751
  * parsing without pathological backtracking and decodes each capture once, so
3374
- * adversarial input cannot trigger exponential parser work. Fixed-width captures
3375
- * may be adjacent, but only one variable-width capture is allowed. Declarations
3376
- * that could join UTF-16 surrogate halves across parts are rejected during
3377
- * construction.
3752
+ * adversarial input cannot trigger exponential parser work. Fixed-width
3753
+ * captures may be adjacent, but only one variable-width capture is allowed.
3754
+ * Declarations that could join UTF-16 surrogate halves across parts are
3755
+ * rejected during construction.
3378
3756
  *
3379
3757
  * Keep capture unions reasonably small to avoid excessive compiler work.
3380
3758
  *
@@ -3411,10 +3789,9 @@ interface UnionErrorValue<
3411
3789
  *
3412
3790
  * // Parse an unknown string into structured data.
3413
3791
  * const result = SupportedLocale.fromUnknown("cs-CZ");
3414
- * assert(result.ok);
3792
+ * expectOk(result, ["cs", "CZ"]);
3415
3793
  * const locale = result.value;
3416
3794
  * expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
3417
- * expect(locale).toEqual(["cs", "CZ"]);
3418
3795
  * expectErr(SupportedLocale.fromUnknown("cs/CZ"), {
3419
3796
  * type: "TemplateLiteral",
3420
3797
  * value: "cs/CZ",
@@ -3473,10 +3850,9 @@ interface UnionErrorValue<
3473
3850
  *
3474
3851
  * // Decode the string into structured data.
3475
3852
  * const result = ItemId.fromUnknown("item-42");
3476
- * assert(result.ok);
3853
+ * expectOk(result, [42n]);
3477
3854
  * const itemId = result.value;
3478
3855
  * expectTypeOf(itemId).toEqualTypeOf<ItemId>();
3479
- * expect(itemId).toEqual([42n]);
3480
3856
  *
3481
3857
  * // Encode the structured data into its canonical string.
3482
3858
  * const itemIdLiteral = ItemId.to(itemId);
@@ -3569,12 +3945,14 @@ const createTemplateLiteralParserType = <
3569
3945
  options,
3570
3946
  false,
3571
3947
  );
3572
- return (outputResult.ok
3573
- ? outputResult
3574
- : err({
3575
- type: "TemplateLiteral",
3576
- outputError: outputResult.error,
3577
- })) as Result<
3948
+ return (
3949
+ outputResult.ok
3950
+ ? outputResult
3951
+ : err({
3952
+ type: "TemplateLiteral",
3953
+ outputError: outputResult.error,
3954
+ })
3955
+ ) as Result<
3578
3956
  TemplateLiteralCaptureTuple<Parts>["Output"],
3579
3957
  TemplateLiteralRuntimeParseError<Parts>
3580
3958
  >;
@@ -3635,7 +4013,8 @@ const createTemplateLiteralParserType = <
3635
4013
  return singleRuntimeTypeIssue(
3636
4014
  "TemplateLiteral",
3637
4015
  error,
3638
- formatTemplateLiteralError as TypeErrorFormatter<TypeError>,
4016
+ ((error: TemplateLiteralError) =>
4017
+ `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`) as TypeErrorFormatter<TypeError>,
3639
4018
  );
3640
4019
  };
3641
4020
  const canonicalStringFromUnknown = (
@@ -3726,7 +4105,11 @@ const createTemplateLiteralParserType = <
3726
4105
  return type;
3727
4106
  };
3728
4107
 
3729
- /** @group Template literals */
4108
+ /**
4109
+ * The parsing {@link Type} returned by {@link templateLiteralParser}.
4110
+ *
4111
+ * @group Template literals
4112
+ */
3730
4113
  export interface TemplateLiteralParserType<
3731
4114
  Parts extends TemplateLiteralParts,
3732
4115
  > extends Type<
@@ -3746,7 +4129,11 @@ export interface TemplateLiteralParserType<
3746
4129
  readonly parts: Parts;
3747
4130
  }
3748
4131
 
3749
- /** @group Template literals */
4132
+ /**
4133
+ * The validating string {@link Type} returned by {@link templateLiteral}.
4134
+ *
4135
+ * @group Template literals
4136
+ */
3750
4137
  export interface TemplateLiteralType<
3751
4138
  Parts extends TemplateLiteralParts,
3752
4139
  > extends Type<
@@ -3772,8 +4159,8 @@ export interface TemplateLiteralType<
3772
4159
  * Creates a canonical string Type from fixed strings and string-encoded Types.
3773
4160
  *
3774
4161
  * Use this factory when Output should remain a string. Switch to
3775
- * {@link templateLiteralParser} when the individual Type parts should be
3776
- * decoded into a Tuple.
4162
+ * {@link templateLiteralParser} when the individual Type parts should be decoded
4163
+ * into a Tuple.
3777
4164
  *
3778
4165
  * ### Example
3779
4166
  *
@@ -3830,15 +4217,15 @@ type TemplateLiteralCaptureFromStringError<T extends TypeNode> =
3830
4217
  : InferErrors<T>
3831
4218
  : never;
3832
4219
 
3833
- /** @group Template literals */
4220
+ /**
4221
+ * Error returned when a string does not match a template literal declaration.
4222
+ *
4223
+ * @group Template literals
4224
+ */
3834
4225
  export interface TemplateLiteralError extends TypeError<"TemplateLiteral"> {
3835
4226
  readonly value: string;
3836
4227
  }
3837
4228
 
3838
- const formatTemplateLiteralError: TypeErrorFormatter<TemplateLiteralError> =
3839
- (error) =>
3840
- `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`;
3841
-
3842
4229
  declare const templateLiteralStringBrandSymbol: unique symbol;
3843
4230
 
3844
4231
  interface TemplateLiteralStringBrand<Parts extends TemplateLiteralParts> {
@@ -4084,9 +4471,7 @@ const compileTemplateLiteralParser = <Parts extends TemplateLiteralParts>(
4084
4471
  }
4085
4472
 
4086
4473
  return position === inputCodePoints.length
4087
- ? ok(
4088
- captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"],
4089
- )
4474
+ ? ok(captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"])
4090
4475
  : err({ type: "TemplateLiteral", value: input });
4091
4476
  };
4092
4477
  };
@@ -4254,68 +4639,10 @@ const getTemplateLiteralPartFraming = (
4254
4639
  * });
4255
4640
  * ```
4256
4641
  *
4257
- * To reuse a Brand constraint with different parent Types, define a
4258
- * {@link BrandFactory}. Brand Factories can then be composed:
4259
- *
4260
- * ```ts
4261
- * import {
4262
- * String,
4263
- * brand,
4264
- * err,
4265
- * minLength,
4266
- * ok,
4267
- * type Brand,
4268
- * type BrandFactory,
4269
- * type TypeError,
4270
- * } from "@evolu/common";
4271
- *
4272
- * const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
4273
- * parent,
4274
- * ) =>
4275
- * brand(
4276
- * "Trimmed",
4277
- * parent,
4278
- * (value) =>
4279
- * value === value.trim()
4280
- * ? ok()
4281
- * : err<TrimmedError>({ type: "Trimmed", value }),
4282
- * () => "Expected a string without surrounding whitespace.",
4283
- * );
4284
- *
4285
- * const TrimmedString = trimmed(String);
4286
- * type TrimmedString = typeof TrimmedString.Output;
4287
- *
4288
- * expectTypeOf<TrimmedString>().toEqualTypeOf<string & Brand<"Trimmed">>();
4289
- *
4290
- * const NonEmptyString = minLength(1)(String);
4291
- * type NonEmptyString = typeof NonEmptyString.Output;
4292
- *
4293
- * expectTypeOf<NonEmptyString>().toEqualTypeOf<
4294
- * string & Brand<"MinLength1">
4295
- * >();
4296
- *
4297
- * const NonEmptyTrimmedString = minLength(1)(TrimmedString);
4298
- * type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
4299
- *
4300
- * expectTypeOf<NonEmptyTrimmedString>().toEqualTypeOf<
4301
- * string & Brand<"Trimmed"> & Brand<"MinLength1">
4302
- * >();
4303
- *
4304
- * interface TrimmedError extends TypeError<"Trimmed"> {
4305
- * readonly value: string;
4306
- * }
4307
- *
4308
- * // Validation from unknown.
4309
- * expectOk(NonEmptyTrimmedString.fromUnknown("Evolu"), "Evolu");
4310
- *
4311
- * // The typed input selects TrimmedString as the validated boundary.
4312
- * expectOk(
4313
- * NonEmptyTrimmedString.from.parent(TrimmedString.orThrow("Evolu")),
4314
- * "Evolu",
4315
- * );
4316
- * ```
4642
+ * To reuse and compose a Brand constraint with different parent Types, define a
4643
+ * {@link BrandFactory}.
4317
4644
  *
4318
- * @group Type construction
4645
+ * @group Construction
4319
4646
  */
4320
4647
  export function brand<
4321
4648
  Name extends TypeName,
@@ -4325,6 +4652,8 @@ export function brand<
4325
4652
  parent: ValidateParent<ParentType>,
4326
4653
  validate?: (value: ParentType["Output"]) => Result<void, never>,
4327
4654
  ): BrandType<ParentType, Name, never>;
4655
+
4656
+ /** Creates a validated Brand Type with its own error formatter. */
4328
4657
  export function brand<
4329
4658
  Name extends TypeName,
4330
4659
  ParentType extends ConcreteTypeNode,
@@ -4352,7 +4681,11 @@ export function brand(
4352
4681
  );
4353
4682
  }
4354
4683
 
4355
- /** @group Type construction */
4684
+ /**
4685
+ * The {@link Type} returned by {@link brand}.
4686
+ *
4687
+ * @group Construction
4688
+ */
4356
4689
  export interface BrandType<
4357
4690
  ParentType extends TypeNode,
4358
4691
  Name extends TypeName,
@@ -4409,7 +4742,11 @@ export const DateIso = /*#__PURE__*/ brand(
4409
4742
  );
4410
4743
  export type DateIso = typeof DateIso.Output;
4411
4744
 
4412
- /** @group String */
4745
+ /**
4746
+ * Error returned when a string is not a canonical {@link DateIso}.
4747
+ *
4748
+ * @group String
4749
+ */
4413
4750
  export interface DateIsoError extends TypeError<"DateIso"> {
4414
4751
  readonly value: string;
4415
4752
  }
@@ -4417,6 +4754,18 @@ export interface DateIsoError extends TypeError<"DateIso"> {
4417
4754
  /**
4418
4755
  * Safely transforms a {@link Date} into a canonical {@link DateIso}.
4419
4756
  *
4757
+ * ### Example
4758
+ *
4759
+ * ```ts
4760
+ * import { DateIsoFromDate } from "@evolu/common";
4761
+ *
4762
+ * const date = new globalThis.Date("2025-01-01T12:00:00.000Z");
4763
+ * const result = DateIsoFromDate.fromUnknown(date);
4764
+ *
4765
+ * expectOk(result, "2025-01-01T12:00:00.000Z");
4766
+ * expect(DateIsoFromDate.to(result.value)).toEqual(date);
4767
+ * ```
4768
+ *
4420
4769
  * @group String
4421
4770
  */
4422
4771
  export const DateIsoFromDate = /*#__PURE__*/ transform(
@@ -4434,7 +4783,11 @@ export const DateIsoFromDate = /*#__PURE__*/ transform(
4434
4783
  () => "The Date cannot be represented as DateIso.",
4435
4784
  );
4436
4785
 
4437
- /** @group String */
4786
+ /**
4787
+ * Error returned when a {@link Date} cannot be represented as {@link DateIso}.
4788
+ *
4789
+ * @group String
4790
+ */
4438
4791
  export interface DateIsoFromDateError extends TypeError<"DateIsoFromDate"> {
4439
4792
  readonly value: globalThis.Date;
4440
4793
  }
@@ -4456,7 +4809,11 @@ export const Int64 = /*#__PURE__*/ brand(
4456
4809
  );
4457
4810
  export type Int64 = typeof Int64.Output;
4458
4811
 
4459
- /** @group Number */
4812
+ /**
4813
+ * Error returned when a bigint is outside the signed 64-bit {@link Int64} range.
4814
+ *
4815
+ * @group Number
4816
+ */
4460
4817
  export interface Int64Error extends TypeError<"Int64"> {
4461
4818
  readonly value: bigint;
4462
4819
  }
@@ -4478,7 +4835,12 @@ export const UInt64 = /*#__PURE__*/ brand(
4478
4835
  );
4479
4836
  export type UInt64 = typeof UInt64.Output;
4480
4837
 
4481
- /** @group Number */
4838
+ /**
4839
+ * Error returned when a bigint is outside the unsigned 64-bit {@link UInt64}
4840
+ * range.
4841
+ *
4842
+ * @group Number
4843
+ */
4482
4844
  export interface UInt64Error extends TypeError<"UInt64"> {
4483
4845
  readonly value: bigint;
4484
4846
  }
@@ -4535,7 +4897,7 @@ export interface UInt64Error extends TypeError<"UInt64"> {
4535
4897
  * For numeric parameters encoded in a Brand name, use
4536
4898
  * {@link ValidateBrandFactoryNumber}.
4537
4899
  *
4538
- * @group Type construction
4900
+ * @group Construction
4539
4901
  */
4540
4902
  export type BrandFactory<
4541
4903
  Name extends TypeName,
@@ -4605,7 +4967,7 @@ export type BrandFactory<
4605
4967
  * lessThan(100 - 1)(Number);
4606
4968
  * ```
4607
4969
  *
4608
- * @group Type utilities
4970
+ * @group Construction
4609
4971
  */
4610
4972
  export type ValidateBrandFactoryNumber<Value extends number> =
4611
4973
  IsUnion<Value> extends false
@@ -4664,7 +5026,11 @@ export const capitalized: BrandFactory<
4664
5026
  `The value ${safelyStringifyUnknownValue(error.value)} must be capitalized.`,
4665
5027
  );
4666
5028
 
4667
- /** @group String */
5029
+ /**
5030
+ * Error returned when {@link capitalized} rejects a string.
5031
+ *
5032
+ * @group String
5033
+ */
4668
5034
  export interface CapitalizedError extends TypeError<"Capitalized"> {
4669
5035
  readonly value: string;
4670
5036
  }
@@ -4680,6 +5046,16 @@ export type CapitalizedString = typeof CapitalizedString.Output;
4680
5046
  /**
4681
5047
  * String {@link Brand} without surrounding whitespace.
4682
5048
  *
5049
+ * ### Example
5050
+ *
5051
+ * ```ts
5052
+ * import { String, trimmed } from "@evolu/common";
5053
+ *
5054
+ * const Trimmed = trimmed(String);
5055
+ *
5056
+ * expectOk(Trimmed.fromUnknown("Evolu"), "Evolu");
5057
+ * ```
5058
+ *
4683
5059
  * @group String
4684
5060
  */
4685
5061
  export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
@@ -4696,7 +5072,11 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
4696
5072
  `The value ${safelyStringifyUnknownValue(error.value)} must be trimmed.`,
4697
5073
  );
4698
5074
 
4699
- /** @group String */
5075
+ /**
5076
+ * Error returned when {@link trimmed} rejects a string.
5077
+ *
5078
+ * @group String
5079
+ */
4700
5080
  export interface TrimmedError extends TypeError<"Trimmed"> {
4701
5081
  readonly value: string;
4702
5082
  }
@@ -4718,13 +5098,33 @@ export type TrimmedString = typeof TrimmedString.Output;
4718
5098
  /**
4719
5099
  * Trims a string and returns a {@link TrimmedString}.
4720
5100
  *
5101
+ * ### Example
5102
+ *
5103
+ * ```ts
5104
+ * import { trim } from "@evolu/common";
5105
+ *
5106
+ * expect(trim(" Evolu ")).toBe("Evolu");
5107
+ * ```
5108
+ *
4721
5109
  * @group String
4722
5110
  */
4723
5111
  export const trim = (value: string): TrimmedString =>
4724
5112
  value.trim() as TrimmedString;
4725
5113
 
4726
5114
  /**
4727
- * Minimum-length {@link Brand} requiring a value to have at least `min` items.
5115
+ * Minimum-length {@link Brand} for values whose `length` is at least `min`.
5116
+ *
5117
+ * ### Example
5118
+ *
5119
+ * ```ts
5120
+ * import { String, array, minLength } from "@evolu/common";
5121
+ *
5122
+ * const AtLeastThreeCharacters = minLength(3)(String);
5123
+ * const AtLeastTwoItems = minLength(2)(array(String));
5124
+ *
5125
+ * expectOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
5126
+ * expectOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
5127
+ * ```
4728
5128
  *
4729
5129
  * @group String
4730
5130
  * @group Collection
@@ -4748,7 +5148,11 @@ export const minLength =
4748
5148
  );
4749
5149
  };
4750
5150
 
4751
- /** @group Collection */
5151
+ /**
5152
+ * Error returned when {@link minLength} rejects a value.
5153
+ *
5154
+ * @group Collection
5155
+ */
4752
5156
  export interface MinLengthError<
4753
5157
  Min extends number = number,
4754
5158
  > extends TypeError<`MinLength${Min}`> {
@@ -4774,7 +5178,19 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
4774
5178
  export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
4775
5179
 
4776
5180
  /**
4777
- * Maximum-length {@link Brand} requiring a value to have at most `max` items.
5181
+ * Maximum-length {@link Brand} for values whose `length` is at most `max`.
5182
+ *
5183
+ * ### Example
5184
+ *
5185
+ * ```ts
5186
+ * import { String, array, maxLength } from "@evolu/common";
5187
+ *
5188
+ * const AtMostThreeCharacters = maxLength(3)(String);
5189
+ * const AtMostTwoItems = maxLength(2)(array(String));
5190
+ *
5191
+ * expectOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
5192
+ * expectOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
5193
+ * ```
4778
5194
  *
4779
5195
  * @group String
4780
5196
  * @group Collection
@@ -4798,7 +5214,11 @@ export const maxLength =
4798
5214
  );
4799
5215
  };
4800
5216
 
4801
- /** @group Collection */
5217
+ /**
5218
+ * Error returned when {@link maxLength} rejects a value.
5219
+ *
5220
+ * @group Collection
5221
+ */
4802
5222
  export interface MaxLengthError<
4803
5223
  Max extends number = number,
4804
5224
  > extends TypeError<`MaxLength${Max}`> {
@@ -4827,7 +5247,19 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(
4827
5247
  export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Output;
4828
5248
 
4829
5249
  /**
4830
- * Exact-length {@link Brand} requiring a value to have exactly `exact` items.
5250
+ * Exact-length {@link Brand} for values whose `length` equals `exact`.
5251
+ *
5252
+ * ### Example
5253
+ *
5254
+ * ```ts
5255
+ * import { String, array, length } from "@evolu/common";
5256
+ *
5257
+ * const ThreeCharacters = length(3)(String);
5258
+ * const TwoItems = length(2)(array(String));
5259
+ *
5260
+ * expectOk(ThreeCharacters.fromUnknown("abc"), "abc");
5261
+ * expectOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
5262
+ * ```
4831
5263
  *
4832
5264
  * @group String
4833
5265
  * @group Collection
@@ -4851,7 +5283,11 @@ export const length =
4851
5283
  );
4852
5284
  };
4853
5285
 
4854
- /** @group Collection */
5286
+ /**
5287
+ * Error returned when {@link length} rejects a value.
5288
+ *
5289
+ * @group Collection
5290
+ */
4855
5291
  export interface LengthError<
4856
5292
  Exact extends number = number,
4857
5293
  > extends TypeError<`Length${Exact}`> {
@@ -4917,7 +5353,12 @@ export const regex = <const Name extends TypeName>(
4917
5353
  );
4918
5354
  };
4919
5355
 
4920
- /** @group String */
5356
+ /**
5357
+ * Error returned when a string does not match the regular expression supplied
5358
+ * to {@link regex}.
5359
+ *
5360
+ * @group String
5361
+ */
4921
5362
  export interface RegexError<
4922
5363
  Name extends TypeName = TypeName,
4923
5364
  > extends TypeError<Name> {
@@ -4985,8 +5426,8 @@ const base64UrlStringToUint8Array = (value: string): Uint8Array => {
4985
5426
  /**
4986
5427
  * Base64Url text without padding.
4987
5428
  *
4988
- * Encode bytes with {@link uint8ArrayToBase64Url} and decode them with
4989
- * {@link base64UrlToUint8Array}.
5429
+ * Convert bytes to Base64Url with {@link uint8ArrayToBase64Url} and convert
5430
+ * Base64Url to bytes with {@link base64UrlToUint8Array}.
4990
5431
  *
4991
5432
  * @group String
4992
5433
  */
@@ -5005,13 +5446,27 @@ export const Base64Url = /*#__PURE__*/ brand(
5005
5446
  );
5006
5447
  export type Base64Url = typeof Base64Url.Output;
5007
5448
 
5008
- /** @group String */
5449
+ /**
5450
+ * Error returned when a string is not valid {@link Base64Url} text.
5451
+ *
5452
+ * @group String
5453
+ */
5009
5454
  export interface Base64UrlError extends TypeError<"Base64Url"> {
5010
5455
  readonly value: string;
5011
5456
  }
5012
5457
 
5013
5458
  /**
5014
- * Encodes bytes as {@link Base64Url}.
5459
+ * Converts bytes to {@link Base64Url}.
5460
+ *
5461
+ * ### Example
5462
+ *
5463
+ * ```ts
5464
+ * import { uint8ArrayToBase64Url } from "@evolu/common";
5465
+ *
5466
+ * expect(uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255]))).toBe(
5467
+ * "AAEC_w",
5468
+ * );
5469
+ * ```
5015
5470
  *
5016
5471
  * @group String
5017
5472
  */
@@ -5019,7 +5474,19 @@ export const uint8ArrayToBase64Url = (bytes: Uint8Array): Base64Url =>
5019
5474
  uint8ArrayToBase64UrlString(bytes) as Base64Url;
5020
5475
 
5021
5476
  /**
5022
- * Decodes {@link Base64Url} as bytes.
5477
+ * Converts {@link Base64Url} to bytes.
5478
+ *
5479
+ * ### Example
5480
+ *
5481
+ * ```ts
5482
+ * import { Base64Url, base64UrlToUint8Array } from "@evolu/common";
5483
+ *
5484
+ * const value = Base64Url.orThrow("AAEC_w");
5485
+ *
5486
+ * expect(base64UrlToUint8Array(value)).toEqual(
5487
+ * new Uint8Array([0, 1, 2, 255]),
5488
+ * );
5489
+ * ```
5023
5490
  *
5024
5491
  * @group String
5025
5492
  */
@@ -5041,7 +5508,11 @@ export const Name = /*#__PURE__*/ brand(
5041
5508
  );
5042
5509
  export type Name = typeof Name.Output;
5043
5510
 
5044
- /** @group String */
5511
+ /**
5512
+ * Error returned when a string is not a valid {@link Name}.
5513
+ *
5514
+ * @group String
5515
+ */
5045
5516
  export interface NameError extends TypeError<"Name"> {
5046
5517
  readonly value: string;
5047
5518
  }
@@ -5077,11 +5548,15 @@ export const Mnemonic = /*#__PURE__*/ brand(
5077
5548
  ? ok()
5078
5549
  : err<MnemonicError>({ type: "Mnemonic", value }),
5079
5550
  (error) =>
5080
- `The value ${safelyStringifyUnknownValue(error.value)} is not a valid BIP39 mnemonic.`,
5551
+ `The value ${safelyStringifyUnknownValue(error.value)} is not a valid English BIP39 mnemonic.`,
5081
5552
  );
5082
5553
  export type Mnemonic = typeof Mnemonic.Output;
5083
5554
 
5084
- /** @group String */
5555
+ /**
5556
+ * Error returned when a string is not a valid English BIP39 {@link Mnemonic}.
5557
+ *
5558
+ * @group String
5559
+ */
5085
5560
  export interface MnemonicError extends TypeError<"Mnemonic"> {
5086
5561
  readonly value: string;
5087
5562
  }
@@ -5108,7 +5583,11 @@ export const Id = /*#__PURE__*/ brand(
5108
5583
  );
5109
5584
  export type Id = typeof Id.Output;
5110
5585
 
5111
- /** @group String */
5586
+ /**
5587
+ * Error returned when a string is not a valid {@link Id}.
5588
+ *
5589
+ * @group String
5590
+ */
5112
5591
  export interface IdError extends TypeError<"Id"> {
5113
5592
  readonly value: string;
5114
5593
  }
@@ -5119,6 +5598,22 @@ export interface IdError extends TypeError<"Id"> {
5119
5598
  * This is the recommended default because it does not encode creation time.
5120
5599
  * Pass a Brand name when the returned Id belongs to one domain entity.
5121
5600
  *
5601
+ * ### Example
5602
+ *
5603
+ * ```ts
5604
+ * import {
5605
+ * Id,
5606
+ * createId,
5607
+ * createRandomBytes,
5608
+ * type Brand,
5609
+ * } from "@evolu/common";
5610
+ *
5611
+ * const userId = createId<"User">({ randomBytes: createRandomBytes() });
5612
+ *
5613
+ * expect(Id.is(userId)).toBe(true);
5614
+ * expectTypeOf(userId).toEqualTypeOf<Id & Brand<"User">>();
5615
+ * ```
5616
+ *
5122
5617
  * @group String
5123
5618
  */
5124
5619
  export const createId = <B extends string = never>(
@@ -5165,13 +5660,33 @@ export const createIdFromString = <B extends string = never>(
5165
5660
  * logs, URLs, and exports. Prefer {@link createId} unless that tradeoff is
5166
5661
  * deliberate.
5167
5662
  *
5168
- * @group String
5169
- */
5170
- export const createIdAsUuidv7 = <B extends string = never>(
5171
- deps: RandomBytesDep & TimeDep,
5172
- ..._validation: IdBrandValidation<B>
5173
- ): CreatedId<B> => {
5174
- const bytes = deps.randomBytes.create(16);
5663
+ * ### Example
5664
+ *
5665
+ * ```ts
5666
+ * import {
5667
+ * createIdAsUuidv7,
5668
+ * createRandomBytes,
5669
+ * createTime,
5670
+ * idToIdBytes,
5671
+ * } from "@evolu/common";
5672
+ *
5673
+ * const value = createIdAsUuidv7({
5674
+ * randomBytes: createRandomBytes(),
5675
+ * time: createTime(),
5676
+ * });
5677
+ * const bytes = idToIdBytes(value);
5678
+ *
5679
+ * expect(bytes[6] >> 4).toBe(0x7);
5680
+ * expect(bytes[8] & 0xc0).toBe(0x80);
5681
+ * ```
5682
+ *
5683
+ * @group String
5684
+ */
5685
+ export const createIdAsUuidv7 = <B extends string = never>(
5686
+ deps: RandomBytesDep & TimeDep,
5687
+ ..._validation: IdBrandValidation<B>
5688
+ ): CreatedId<B> => {
5689
+ const bytes = deps.randomBytes.create(16);
5175
5690
  const timestamp = globalThis.BigInt(deps.time.now());
5176
5691
 
5177
5692
  bytes[0] = globalThis.Number((timestamp >> 40n) & 0xffn);
@@ -5189,6 +5704,22 @@ export const createIdAsUuidv7 = <B extends string = never>(
5189
5704
  /**
5190
5705
  * Table-specific {@link Id} Type.
5191
5706
  *
5707
+ * ### Example
5708
+ *
5709
+ * ```ts
5710
+ * import {
5711
+ * createIdFromString,
5712
+ * id,
5713
+ * type Brand,
5714
+ * type Id,
5715
+ * } from "@evolu/common";
5716
+ *
5717
+ * const TodoId = id("Todo");
5718
+ * const todoId = TodoId.orThrow(createIdFromString("todo"));
5719
+ *
5720
+ * expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
5721
+ * ```
5722
+ *
5192
5723
  * @group String
5193
5724
  */
5194
5725
  export const id = <Table extends TypeName>(
@@ -5211,7 +5742,11 @@ export const id = <Table extends TypeName>(
5211
5742
  );
5212
5743
  };
5213
5744
 
5214
- /** @group String */
5745
+ /**
5746
+ * The {@link Type} returned by {@link id} for one table.
5747
+ *
5748
+ * @group String
5749
+ */
5215
5750
  export interface TableId<Table extends TypeName> extends Type<
5216
5751
  "TableId",
5217
5752
  string,
@@ -5226,7 +5761,11 @@ export interface TableId<Table extends TypeName> extends Type<
5226
5761
  readonly table: Table;
5227
5762
  }
5228
5763
 
5229
- /** @group String */
5764
+ /**
5765
+ * Error returned when a string is not a valid {@link Id} for the expected table.
5766
+ *
5767
+ * @group String
5768
+ */
5230
5769
  export interface TableIdError<
5231
5770
  Table extends TypeName = TypeName,
5232
5771
  > extends TypeError<"TableId"> {
@@ -5278,14 +5817,50 @@ export const IdBytes = /*#__PURE__*/ brand(
5278
5817
  );
5279
5818
  export type IdBytes = typeof IdBytes.Output;
5280
5819
 
5281
- /** @group String */
5820
+ /**
5821
+ * Byte length of an {@link IdBytes} value.
5822
+ *
5823
+ * @group String
5824
+ */
5282
5825
  export const idBytesTypeValueLength = 16 as NonNegativeInt;
5283
5826
 
5284
- /** @group String */
5827
+ /**
5828
+ * Converts an {@link Id} to {@link IdBytes}.
5829
+ *
5830
+ * ### Example
5831
+ *
5832
+ * ```ts
5833
+ * import { createIdFromString, idToIdBytes } from "@evolu/common";
5834
+ *
5835
+ * const bytes = idToIdBytes(createIdFromString("todo"));
5836
+ *
5837
+ * expect(bytes).toHaveLength(16);
5838
+ * ```
5839
+ *
5840
+ * @group String
5841
+ */
5285
5842
  export const idToIdBytes = (value: Id): IdBytes =>
5286
5843
  base64UrlToUint8Array(value as unknown as Base64Url) as IdBytes;
5287
5844
 
5288
- /** @group String */
5845
+ /**
5846
+ * Converts {@link IdBytes} to an {@link Id}.
5847
+ *
5848
+ * ### Example
5849
+ *
5850
+ * ```ts
5851
+ * import {
5852
+ * createIdFromString,
5853
+ * idBytesToId,
5854
+ * idToIdBytes,
5855
+ * } from "@evolu/common";
5856
+ *
5857
+ * const value = createIdFromString("todo");
5858
+ *
5859
+ * expect(idBytesToId(idToIdBytes(value))).toBe(value);
5860
+ * ```
5861
+ *
5862
+ * @group String
5863
+ */
5289
5864
  export const idBytesToId = (value: IdBytes): Id =>
5290
5865
  uint8ArrayToBase64Url(value) as unknown as Id;
5291
5866
 
@@ -5319,7 +5894,11 @@ export const Int64String = /*#__PURE__*/ brand(
5319
5894
  );
5320
5895
  export type Int64String = typeof Int64String.Output;
5321
5896
 
5322
- /** @group Number */
5897
+ /**
5898
+ * Error returned when a string is not a canonical {@link Int64String}.
5899
+ *
5900
+ * @group Number
5901
+ */
5323
5902
  export interface Int64StringError extends TypeError<"Int64String"> {
5324
5903
  readonly value: string;
5325
5904
  }
@@ -5331,6 +5910,19 @@ export interface Int64StringError extends TypeError<"Int64String"> {
5331
5910
  * text, including SQLite queries that cast INTEGER values to TEXT to avoid a
5332
5911
  * lossy JavaScript number conversion.
5333
5912
  *
5913
+ * ### Example
5914
+ *
5915
+ * ```ts
5916
+ * import { Int64FromInt64String } from "@evolu/common";
5917
+ *
5918
+ * const result = Int64FromInt64String.fromUnknown("9223372036854775807");
5919
+ *
5920
+ * expectOk(result, 9223372036854775807n);
5921
+ * expect(Int64FromInt64String.to(result.value)).toBe(
5922
+ * "9223372036854775807",
5923
+ * );
5924
+ * ```
5925
+ *
5334
5926
  * @group Number
5335
5927
  */
5336
5928
  export const Int64FromInt64String = /*#__PURE__*/ transform(
@@ -5346,6 +5938,16 @@ export const Int64FromInt64String = /*#__PURE__*/ transform(
5346
5938
  /**
5347
5939
  * Number {@link Brand} requiring a value greater than or equal to zero.
5348
5940
  *
5941
+ * ### Example
5942
+ *
5943
+ * ```ts
5944
+ * import { Number, nonNegative } from "@evolu/common";
5945
+ *
5946
+ * const NonNegative = nonNegative(Number);
5947
+ *
5948
+ * expectOk(NonNegative.fromUnknown(0), 0);
5949
+ * ```
5950
+ *
5349
5951
  * @group Number
5350
5952
  */
5351
5953
  export const nonNegative: BrandFactory<
@@ -5362,7 +5964,11 @@ export const nonNegative: BrandFactory<
5362
5964
  `The value ${safelyStringifyUnknownValue(error.value)} must be non-negative (>= 0).`,
5363
5965
  );
5364
5966
 
5365
- /** @group Number */
5967
+ /**
5968
+ * Error returned when {@link nonNegative} rejects a number.
5969
+ *
5970
+ * @group Number
5971
+ */
5366
5972
  export interface NonNegativeError extends TypeError<"NonNegative"> {
5367
5973
  readonly value: number;
5368
5974
  }
@@ -5378,6 +5984,16 @@ export type NonNegativeNumber = typeof NonNegativeNumber.Output;
5378
5984
  /**
5379
5985
  * Number {@link Brand} requiring a value greater than zero.
5380
5986
  *
5987
+ * ### Example
5988
+ *
5989
+ * ```ts
5990
+ * import { Number, positive } from "@evolu/common";
5991
+ *
5992
+ * const Positive = positive(Number);
5993
+ *
5994
+ * expectOk(Positive.fromUnknown(1), 1);
5995
+ * ```
5996
+ *
5381
5997
  * @group Number
5382
5998
  */
5383
5999
  export const positive: BrandFactory<"Positive", number, PositiveError> = (
@@ -5392,7 +6008,11 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
5392
6008
  `The value ${safelyStringifyUnknownValue(error.value)} must be positive (> 0).`,
5393
6009
  );
5394
6010
 
5395
- /** @group Number */
6011
+ /**
6012
+ * Error returned when {@link positive} rejects a number.
6013
+ *
6014
+ * @group Number
6015
+ */
5396
6016
  export interface PositiveError extends TypeError<"Positive"> {
5397
6017
  readonly value: number;
5398
6018
  }
@@ -5411,6 +6031,16 @@ export type PositiveNumber = typeof PositiveNumber.Output;
5411
6031
  /**
5412
6032
  * Number {@link Brand} requiring a value less than or equal to zero.
5413
6033
  *
6034
+ * ### Example
6035
+ *
6036
+ * ```ts
6037
+ * import { Number, nonPositive } from "@evolu/common";
6038
+ *
6039
+ * const NonPositive = nonPositive(Number);
6040
+ *
6041
+ * expectOk(NonPositive.fromUnknown(0), 0);
6042
+ * ```
6043
+ *
5414
6044
  * @group Number
5415
6045
  */
5416
6046
  export const nonPositive: BrandFactory<
@@ -5427,7 +6057,11 @@ export const nonPositive: BrandFactory<
5427
6057
  `The value ${safelyStringifyUnknownValue(error.value)} must be non-positive (<= 0).`,
5428
6058
  );
5429
6059
 
5430
- /** @group Number */
6060
+ /**
6061
+ * Error returned when {@link nonPositive} rejects a number.
6062
+ *
6063
+ * @group Number
6064
+ */
5431
6065
  export interface NonPositiveError extends TypeError<"NonPositive"> {
5432
6066
  readonly value: number;
5433
6067
  }
@@ -5443,6 +6077,16 @@ export type NonPositiveNumber = typeof NonPositiveNumber.Output;
5443
6077
  /**
5444
6078
  * Number {@link Brand} requiring a value less than zero.
5445
6079
  *
6080
+ * ### Example
6081
+ *
6082
+ * ```ts
6083
+ * import { Number, negative } from "@evolu/common";
6084
+ *
6085
+ * const Negative = negative(Number);
6086
+ *
6087
+ * expectOk(Negative.fromUnknown(-1), -1);
6088
+ * ```
6089
+ *
5446
6090
  * @group Number
5447
6091
  */
5448
6092
  export const negative: BrandFactory<"Negative", number, NegativeError> = (
@@ -5457,7 +6101,11 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
5457
6101
  `The value ${safelyStringifyUnknownValue(error.value)} must be negative (< 0).`,
5458
6102
  );
5459
6103
 
5460
- /** @group Number */
6104
+ /**
6105
+ * Error returned when {@link negative} rejects a number.
6106
+ *
6107
+ * @group Number
6108
+ */
5461
6109
  export interface NegativeError extends TypeError<"Negative"> {
5462
6110
  readonly value: number;
5463
6111
  }
@@ -5476,6 +6124,16 @@ export type NegativeNumber = typeof NegativeNumber.Output;
5476
6124
  /**
5477
6125
  * Number {@link Brand} requiring a value other than `NaN`.
5478
6126
  *
6127
+ * ### Example
6128
+ *
6129
+ * ```ts
6130
+ * import { Number, nonNaN } from "@evolu/common";
6131
+ *
6132
+ * const NonNaN = nonNaN(Number);
6133
+ *
6134
+ * expectOk(NonNaN.fromUnknown(Infinity), Infinity);
6135
+ * ```
6136
+ *
5479
6137
  * @group Number
5480
6138
  */
5481
6139
  export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
@@ -5489,7 +6147,11 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
5489
6147
  () => "The value must not be NaN.",
5490
6148
  );
5491
6149
 
5492
- /** @group Number */
6150
+ /**
6151
+ * Error returned when {@link nonNaN} rejects `NaN`.
6152
+ *
6153
+ * @group Number
6154
+ */
5493
6155
  export interface NonNaNError extends TypeError<"NonNaN"> {
5494
6156
  readonly value: number;
5495
6157
  }
@@ -5510,6 +6172,16 @@ export type NonNaNNumber = typeof NonNaNNumber.Output;
5510
6172
  /**
5511
6173
  * Number {@link Brand} requiring a finite value.
5512
6174
  *
6175
+ * ### Example
6176
+ *
6177
+ * ```ts
6178
+ * import { Number, finite } from "@evolu/common";
6179
+ *
6180
+ * const Finite = finite(Number);
6181
+ *
6182
+ * expectOk(Finite.fromUnknown(42), 42);
6183
+ * ```
6184
+ *
5513
6185
  * @group Number
5514
6186
  */
5515
6187
  export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
@@ -5524,7 +6196,11 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
5524
6196
  `The value ${safelyStringifyUnknownValue(error.value)} must be finite.`,
5525
6197
  );
5526
6198
 
5527
- /** @group Number */
6199
+ /**
6200
+ * Error returned when {@link finite} rejects a non-finite number.
6201
+ *
6202
+ * @group Number
6203
+ */
5528
6204
  export interface FiniteError extends TypeError<"Finite"> {
5529
6205
  readonly value: number;
5530
6206
  }
@@ -5589,7 +6265,11 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
5589
6265
  `The value ${safelyStringifyUnknownValue(error.value)} must be a safe integer.`,
5590
6266
  );
5591
6267
 
5592
- /** @group Number */
6268
+ /**
6269
+ * Error returned when {@link int} rejects a number that is not a safe integer.
6270
+ *
6271
+ * @group Number
6272
+ */
5593
6273
  export interface IntError extends TypeError<"Int"> {
5594
6274
  readonly value: number;
5595
6275
  }
@@ -5665,6 +6345,16 @@ export type NegativeInt = typeof NegativeInt.Output;
5665
6345
  /**
5666
6346
  * Number {@link Brand} requiring a value greater than `min`.
5667
6347
  *
6348
+ * ### Example
6349
+ *
6350
+ * ```ts
6351
+ * import { Number, greaterThan } from "@evolu/common";
6352
+ *
6353
+ * const GreaterThanTen = greaterThan(10)(Number);
6354
+ *
6355
+ * expectOk(GreaterThanTen.fromUnknown(11), 11);
6356
+ * ```
6357
+ *
5668
6358
  * @group Number
5669
6359
  */
5670
6360
  export const greaterThan =
@@ -5686,7 +6376,11 @@ export const greaterThan =
5686
6376
  );
5687
6377
  };
5688
6378
 
5689
- /** @group Number */
6379
+ /**
6380
+ * Error returned when {@link greaterThan} rejects a number.
6381
+ *
6382
+ * @group Number
6383
+ */
5690
6384
  export interface GreaterThanError<
5691
6385
  Min extends number = number,
5692
6386
  > extends TypeError<`GreaterThan${Min}`> {
@@ -5697,6 +6391,16 @@ export interface GreaterThanError<
5697
6391
  /**
5698
6392
  * Number {@link Brand} requiring a value greater than or equal to `min`.
5699
6393
  *
6394
+ * ### Example
6395
+ *
6396
+ * ```ts
6397
+ * import { Number, greaterThanOrEqualTo } from "@evolu/common";
6398
+ *
6399
+ * const AtLeastTen = greaterThanOrEqualTo(10)(Number);
6400
+ *
6401
+ * expectOk(AtLeastTen.fromUnknown(10), 10);
6402
+ * ```
6403
+ *
5700
6404
  * @group Number
5701
6405
  */
5702
6406
  export const greaterThanOrEqualTo =
@@ -5726,7 +6430,11 @@ export const greaterThanOrEqualTo =
5726
6430
  );
5727
6431
  };
5728
6432
 
5729
- /** @group Number */
6433
+ /**
6434
+ * Error returned when {@link greaterThanOrEqualTo} rejects a number.
6435
+ *
6436
+ * @group Number
6437
+ */
5730
6438
  export interface GreaterThanOrEqualToError<
5731
6439
  Min extends number = number,
5732
6440
  > extends TypeError<`GreaterThanOrEqualTo${Min}`> {
@@ -5737,6 +6445,16 @@ export interface GreaterThanOrEqualToError<
5737
6445
  /**
5738
6446
  * Number {@link Brand} requiring a value less than `max`.
5739
6447
  *
6448
+ * ### Example
6449
+ *
6450
+ * ```ts
6451
+ * import { Number, lessThan } from "@evolu/common";
6452
+ *
6453
+ * const LessThanTen = lessThan(10)(Number);
6454
+ *
6455
+ * expectOk(LessThanTen.fromUnknown(9), 9);
6456
+ * ```
6457
+ *
5740
6458
  * @group Number
5741
6459
  */
5742
6460
  export const lessThan =
@@ -5758,7 +6476,11 @@ export const lessThan =
5758
6476
  );
5759
6477
  };
5760
6478
 
5761
- /** @group Number */
6479
+ /**
6480
+ * Error returned when {@link lessThan} rejects a number.
6481
+ *
6482
+ * @group Number
6483
+ */
5762
6484
  export interface LessThanError<
5763
6485
  Max extends number = number,
5764
6486
  > extends TypeError<`LessThan${Max}`> {
@@ -5780,6 +6502,16 @@ export type Age = typeof Age.Output;
5780
6502
  /**
5781
6503
  * Number {@link Brand} requiring a value less than or equal to `max`.
5782
6504
  *
6505
+ * ### Example
6506
+ *
6507
+ * ```ts
6508
+ * import { Number, lessThanOrEqualTo } from "@evolu/common";
6509
+ *
6510
+ * const AtMostTen = lessThanOrEqualTo(10)(Number);
6511
+ *
6512
+ * expectOk(AtMostTen.fromUnknown(10), 10);
6513
+ * ```
6514
+ *
5783
6515
  * @group Number
5784
6516
  */
5785
6517
  export const lessThanOrEqualTo =
@@ -5805,7 +6537,11 @@ export const lessThanOrEqualTo =
5805
6537
  );
5806
6538
  };
5807
6539
 
5808
- /** @group Number */
6540
+ /**
6541
+ * Error returned when {@link lessThanOrEqualTo} rejects a number.
6542
+ *
6543
+ * @group Number
6544
+ */
5809
6545
  export interface LessThanOrEqualToError<
5810
6546
  Max extends number = number,
5811
6547
  > extends TypeError<`LessThanOrEqualTo${Max}`> {
@@ -5879,7 +6615,11 @@ export const DecimalString = /*#__PURE__*/ brand(
5879
6615
  );
5880
6616
  export type DecimalString = typeof DecimalString.Output;
5881
6617
 
5882
- /** @group Number */
6618
+ /**
6619
+ * Error returned when a string is not a canonical {@link DecimalString}.
6620
+ *
6621
+ * @group Number
6622
+ */
5883
6623
  export interface DecimalStringError extends TypeError<"DecimalString"> {
5884
6624
  readonly value: string;
5885
6625
  }
@@ -5887,6 +6627,16 @@ export interface DecimalStringError extends TypeError<"DecimalString"> {
5887
6627
  /**
5888
6628
  * {@link DecimalString} Brand requiring a value greater than or equal to zero.
5889
6629
  *
6630
+ * ### Example
6631
+ *
6632
+ * ```ts
6633
+ * import { DecimalString, nonNegativeDecimalString } from "@evolu/common";
6634
+ *
6635
+ * const NonNegative = nonNegativeDecimalString(DecimalString);
6636
+ *
6637
+ * expectOk(NonNegative.fromUnknown("0.5"), "0.5");
6638
+ * ```
6639
+ *
5890
6640
  * @group Number
5891
6641
  */
5892
6642
  export const nonNegativeDecimalString: BrandFactory<
@@ -5908,7 +6658,11 @@ export const nonNegativeDecimalString: BrandFactory<
5908
6658
  `The value ${safelyStringifyUnknownValue(error.value)} must be a non-negative decimal string.`,
5909
6659
  );
5910
6660
 
5911
- /** @group Number */
6661
+ /**
6662
+ * Error returned when {@link nonNegativeDecimalString} rejects a decimal string.
6663
+ *
6664
+ * @group Number
6665
+ */
5912
6666
  export interface NonNegativeDecimalStringError extends TypeError<"NonNegativeDecimalString"> {
5913
6667
  readonly value: string;
5914
6668
  }
@@ -5925,6 +6679,16 @@ export type NonNegativeDecimalString = typeof NonNegativeDecimalString.Output;
5925
6679
  /**
5926
6680
  * {@link DecimalString} Brand requiring a value greater than zero.
5927
6681
  *
6682
+ * ### Example
6683
+ *
6684
+ * ```ts
6685
+ * import { DecimalString, positiveDecimalString } from "@evolu/common";
6686
+ *
6687
+ * const Positive = positiveDecimalString(DecimalString);
6688
+ *
6689
+ * expectOk(Positive.fromUnknown("0.5"), "0.5");
6690
+ * ```
6691
+ *
5928
6692
  * @group Number
5929
6693
  */
5930
6694
  export const positiveDecimalString: BrandFactory<
@@ -5946,7 +6710,11 @@ export const positiveDecimalString: BrandFactory<
5946
6710
  `The value ${safelyStringifyUnknownValue(error.value)} must be a positive decimal string.`,
5947
6711
  );
5948
6712
 
5949
- /** @group Number */
6713
+ /**
6714
+ * Error returned when {@link positiveDecimalString} rejects a decimal string.
6715
+ *
6716
+ * @group Number
6717
+ */
5950
6718
  export interface PositiveDecimalStringError extends TypeError<"PositiveDecimalString"> {
5951
6719
  readonly value: string;
5952
6720
  }
@@ -5967,6 +6735,16 @@ export type PositiveDecimalString = typeof PositiveDecimalString.Output;
5967
6735
  /**
5968
6736
  * {@link DecimalString} Brand requiring a value less than or equal to zero.
5969
6737
  *
6738
+ * ### Example
6739
+ *
6740
+ * ```ts
6741
+ * import { DecimalString, nonPositiveDecimalString } from "@evolu/common";
6742
+ *
6743
+ * const NonPositive = nonPositiveDecimalString(DecimalString);
6744
+ *
6745
+ * expectOk(NonPositive.fromUnknown("-0.5"), "-0.5");
6746
+ * ```
6747
+ *
5970
6748
  * @group Number
5971
6749
  */
5972
6750
  export const nonPositiveDecimalString: BrandFactory<
@@ -5988,7 +6766,11 @@ export const nonPositiveDecimalString: BrandFactory<
5988
6766
  `The value ${safelyStringifyUnknownValue(error.value)} must be a non-positive decimal string.`,
5989
6767
  );
5990
6768
 
5991
- /** @group Number */
6769
+ /**
6770
+ * Error returned when {@link nonPositiveDecimalString} rejects a decimal string.
6771
+ *
6772
+ * @group Number
6773
+ */
5992
6774
  export interface NonPositiveDecimalStringError extends TypeError<"NonPositiveDecimalString"> {
5993
6775
  readonly value: string;
5994
6776
  }
@@ -6005,6 +6787,16 @@ export type NonPositiveDecimalString = typeof NonPositiveDecimalString.Output;
6005
6787
  /**
6006
6788
  * {@link DecimalString} Brand requiring a value less than zero.
6007
6789
  *
6790
+ * ### Example
6791
+ *
6792
+ * ```ts
6793
+ * import { DecimalString, negativeDecimalString } from "@evolu/common";
6794
+ *
6795
+ * const Negative = negativeDecimalString(DecimalString);
6796
+ *
6797
+ * expectOk(Negative.fromUnknown("-0.5"), "-0.5");
6798
+ * ```
6799
+ *
6008
6800
  * @group Number
6009
6801
  */
6010
6802
  export const negativeDecimalString: BrandFactory<
@@ -6026,7 +6818,11 @@ export const negativeDecimalString: BrandFactory<
6026
6818
  `The value ${safelyStringifyUnknownValue(error.value)} must be a negative decimal string.`,
6027
6819
  );
6028
6820
 
6029
- /** @group Number */
6821
+ /**
6822
+ * Error returned when {@link negativeDecimalString} rejects a decimal string.
6823
+ *
6824
+ * @group Number
6825
+ */
6030
6826
  export interface NegativeDecimalStringError extends TypeError<"NegativeDecimalString"> {
6031
6827
  readonly value: string;
6032
6828
  }
@@ -6118,7 +6914,11 @@ export const multipleOf = <const Divisor extends string>(
6118
6914
  );
6119
6915
  };
6120
6916
 
6121
- /** @group Number */
6917
+ /**
6918
+ * Error returned when {@link multipleOf} rejects a number.
6919
+ *
6920
+ * @group Number
6921
+ */
6122
6922
  export interface MultipleOfError<
6123
6923
  Divisor extends string = string,
6124
6924
  > extends TypeError<`MultipleOf${Divisor}`> {
@@ -6197,6 +6997,16 @@ const decimalStringToParts = (value: string): DecimalParts => {
6197
6997
  /**
6198
6998
  * Number {@link Brand} requiring a value within an inclusive range.
6199
6999
  *
7000
+ * ### Example
7001
+ *
7002
+ * ```ts
7003
+ * import { Number, between } from "@evolu/common";
7004
+ *
7005
+ * const Percentage = between(0, 100)(Number);
7006
+ *
7007
+ * expectOk(Percentage.fromUnknown(75), 75);
7008
+ * ```
7009
+ *
6200
7010
  * @group Number
6201
7011
  */
6202
7012
  export const between =
@@ -6224,7 +7034,11 @@ export const between =
6224
7034
  );
6225
7035
  };
6226
7036
 
6227
- /** @group Number */
7037
+ /**
7038
+ * Error returned when {@link between} rejects a number.
7039
+ *
7040
+ * @group Number
7041
+ */
6228
7042
  export interface BetweenError<
6229
7043
  Min extends number = number,
6230
7044
  Max extends number = number,
@@ -6292,7 +7106,11 @@ export const array = <ElementType extends ConcreteTypeNode>(
6292
7106
  arrayRuntimeConfig,
6293
7107
  ) as ArrayType<ElementType>;
6294
7108
 
6295
- /** @group Collection */
7109
+ /**
7110
+ * The homogeneous readonly-array {@link Type} returned by {@link array}.
7111
+ *
7112
+ * @group Collection
7113
+ */
6296
7114
  export interface ArrayType<ElementType extends TypeNode> extends Type<
6297
7115
  "Array",
6298
7116
  ReadonlyArray<ElementType["Input"]>,
@@ -6371,11 +7189,19 @@ type ArrayNodeError<ElementType extends TypeNode> = [
6371
7189
  ? ArrayElementsError<ElementType["Error"]>
6372
7190
  : ArrayError<ElementType["Error"]>;
6373
7191
 
6374
- /** @group Collection */
7192
+ /**
7193
+ * Error returned by {@link array} for a non-array value or invalid array items.
7194
+ *
7195
+ * @group Collection
7196
+ */
6375
7197
  export type ArrayError<Error extends TypeError = TypeError> =
6376
7198
  ArrayNotArrayError | ArrayItemsErrorValue<Error, true>;
6377
7199
 
6378
- /** @group Collection */
7200
+ /**
7201
+ * Error returned when an {@link array} input is not an array.
7202
+ *
7203
+ * @group Collection
7204
+ */
6379
7205
  export interface ArrayNotArrayError extends TypeError<"Array"> {
6380
7206
  readonly reason: {
6381
7207
  readonly kind: "NotArray";
@@ -6383,29 +7209,49 @@ export interface ArrayNotArrayError extends TypeError<"Array"> {
6383
7209
  };
6384
7210
  }
6385
7211
 
6386
- /** @group Collection */
7212
+ /**
7213
+ * An {@link array} error containing structural or element issues.
7214
+ *
7215
+ * @group Collection
7216
+ */
6387
7217
  export type ArrayItemsError<Error extends TypeError> = ArrayItemsErrorValue<
6388
7218
  Error,
6389
7219
  true
6390
7220
  >;
6391
7221
 
6392
- /** @group Collection */
7222
+ /**
7223
+ * One structural or element issue found by {@link array}.
7224
+ *
7225
+ * @group Collection
7226
+ */
6393
7227
  export type ArrayIssue<Error extends TypeError> =
6394
7228
  ArrayStructuralIssue | ArrayElementIssue<Error>;
6395
7229
 
6396
- /** @group Collection */
7230
+ /**
7231
+ * A missing array element.
7232
+ *
7233
+ * @group Collection
7234
+ */
6397
7235
  export interface ArrayHoleIssue {
6398
7236
  readonly kind: "Hole";
6399
7237
  readonly index: number;
6400
7238
  }
6401
7239
 
6402
- /** @group Collection */
7240
+ /**
7241
+ * An array element defined by an accessor instead of a data property.
7242
+ *
7243
+ * @group Collection
7244
+ */
6403
7245
  export interface ArrayAccessorIssue {
6404
7246
  readonly kind: "Accessor";
6405
7247
  readonly index: number;
6406
7248
  }
6407
7249
 
6408
- /** @group Collection */
7250
+ /**
7251
+ * An own array property other than `length` or an indexed element.
7252
+ *
7253
+ * @group Collection
7254
+ */
6409
7255
  export interface ArrayExcessPropertyIssue {
6410
7256
  readonly kind: "ExcessProperty";
6411
7257
  readonly key: string | symbol;
@@ -6414,7 +7260,11 @@ export interface ArrayExcessPropertyIssue {
6414
7260
  type ArrayStructuralIssue =
6415
7261
  ArrayHoleIssue | ArrayAccessorIssue | ArrayExcessPropertyIssue;
6416
7262
 
6417
- /** @group Collection */
7263
+ /**
7264
+ * An invalid array element and its index.
7265
+ *
7266
+ * @group Collection
7267
+ */
6418
7268
  export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
6419
7269
  ? {
6420
7270
  readonly kind: "Element";
@@ -6423,7 +7273,11 @@ export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
6423
7273
  }
6424
7274
  : never;
6425
7275
 
6426
- /** @group Collection */
7276
+ /**
7277
+ * An {@link array} error containing element errors from a typed boundary.
7278
+ *
7279
+ * @group Collection
7280
+ */
6427
7281
  export type ArrayElementsError<Error extends TypeError> = [Error] extends [
6428
7282
  never,
6429
7283
  ]
@@ -6494,23 +7348,6 @@ const isArrayCollection = (
6494
7348
  return true;
6495
7349
  };
6496
7350
 
6497
- const formatArrayError: TypeErrorFormatter<ArrayError> = (error) => {
6498
- if (error.reason.kind === "NotArray") {
6499
- return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
6500
- }
6501
-
6502
- const issue = error.reason.issues[0] as ArrayStructuralIssue;
6503
-
6504
- switch (issue.kind) {
6505
- case "Hole":
6506
- return `An array element at index ${issue.index} is missing.`;
6507
- case "Accessor":
6508
- return `An array element at index ${issue.index} must be a data property.`;
6509
- case "ExcessProperty":
6510
- return "An excess Array property is not allowed. Remove it or use a different Type.";
6511
- }
6512
- };
6513
-
6514
7351
  const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
6515
7352
  ReadonlyArray<unknown>
6516
7353
  > = {
@@ -6521,7 +7358,23 @@ const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
6521
7358
  validateArrayItems(value, validateElement, options, false),
6522
7359
  encode: encodeArrayCollection,
6523
7360
  is: isArrayCollection,
6524
- formatError: formatArrayError as TypeErrorFormatter<TypeError>,
7361
+ formatError: ((error: ArrayError) => {
7362
+ if (error.reason.kind === "NotArray") {
7363
+ return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
7364
+ }
7365
+ const issue = error.reason.issues[0];
7366
+
7367
+ switch (issue.kind) {
7368
+ case "Hole":
7369
+ return `An array element at index ${issue.index} is missing.`;
7370
+ case "Accessor":
7371
+ return `An array element at index ${issue.index} must be a data property.`;
7372
+ case "ExcessProperty":
7373
+ return "An excess Array property is not allowed. Remove it or use a different Type.";
7374
+ case "Element":
7375
+ return `An array element at index ${issue.index} is invalid.`;
7376
+ }
7377
+ }) as TypeErrorFormatter<TypeError>,
6525
7378
  };
6526
7379
 
6527
7380
  const validateArrayItems = (
@@ -6658,6 +7511,17 @@ const copyArrayPrefix = (
6658
7511
  * iteration order. Classification uses the realm-neutral object tag and
6659
7512
  * prototype structure under Evolu Type's trusted JavaScript policy.
6660
7513
  *
7514
+ * ### Example
7515
+ *
7516
+ * ```ts
7517
+ * import { String, set } from "@evolu/common";
7518
+ *
7519
+ * const Tags = set(String);
7520
+ * const tags = new Set(["local-first", "offline"]);
7521
+ *
7522
+ * expectOk(Tags.fromUnknown(tags), tags);
7523
+ * ```
7524
+ *
6661
7525
  * @group Collection
6662
7526
  */
6663
7527
  export const set = <ElementType extends ConcreteTypeNode>(
@@ -6668,7 +7532,11 @@ export const set = <ElementType extends ConcreteTypeNode>(
6668
7532
  setRuntimeConfig,
6669
7533
  ) as SetType<ElementType>;
6670
7534
 
6671
- /** @group Collection */
7535
+ /**
7536
+ * The homogeneous readonly-set {@link Type} returned by {@link set}.
7537
+ *
7538
+ * @group Collection
7539
+ */
6672
7540
  export interface SetType<ElementType extends TypeNode> extends Type<
6673
7541
  "Set",
6674
7542
  ReadonlySet<ElementType["Input"]>,
@@ -6739,13 +7607,22 @@ type SetNodeError<ElementType extends TypeNode> = [
6739
7607
  ? SetElementsError<ElementType["Error"]>
6740
7608
  : SetError<ElementType["Error"]>;
6741
7609
 
6742
- /** @group Collection */
7610
+ /**
7611
+ * Error returned by {@link set} for a non-Set value, an invalid Set prototype,
7612
+ * or invalid Set items.
7613
+ *
7614
+ * @group Collection
7615
+ */
6743
7616
  export type SetError<Error extends TypeError = TypeError> =
6744
7617
  | SetNotSetError
6745
7618
  | SetUnexpectedPrototypeError
6746
7619
  | SetItemsErrorValue<Error, true>;
6747
7620
 
6748
- /** @group Collection */
7621
+ /**
7622
+ * Error returned when a {@link set} input is not a Set.
7623
+ *
7624
+ * @group Collection
7625
+ */
6749
7626
  export interface SetNotSetError extends TypeError<"Set"> {
6750
7627
  readonly reason: {
6751
7628
  readonly kind: "NotSet";
@@ -6765,7 +7642,11 @@ export interface SetUnexpectedPrototypeError extends TypeError<"Set"> {
6765
7642
  };
6766
7643
  }
6767
7644
 
6768
- /** @group Collection */
7645
+ /**
7646
+ * An own property found on a Set value.
7647
+ *
7648
+ * @group Collection
7649
+ */
6769
7650
  export interface SetExcessPropertyIssue {
6770
7651
  readonly kind: "ExcessProperty";
6771
7652
  readonly key: string | symbol;
@@ -6773,7 +7654,11 @@ export interface SetExcessPropertyIssue {
6773
7654
 
6774
7655
  type SetStructuralIssue = SetExcessPropertyIssue;
6775
7656
 
6776
- /** @group Collection */
7657
+ /**
7658
+ * An invalid Set element and its iteration index.
7659
+ *
7660
+ * @group Collection
7661
+ */
6777
7662
  export type SetElementIssue<Error extends TypeError> = Error extends TypeError
6778
7663
  ? {
6779
7664
  readonly kind: "Element";
@@ -6782,13 +7667,21 @@ export type SetElementIssue<Error extends TypeError> = Error extends TypeError
6782
7667
  }
6783
7668
  : never;
6784
7669
 
6785
- /** @group Collection */
7670
+ /**
7671
+ * A {@link set} error containing structural or element issues.
7672
+ *
7673
+ * @group Collection
7674
+ */
6786
7675
  export type SetItemsError<Error extends TypeError> = SetItemsErrorValue<
6787
7676
  Error,
6788
7677
  true
6789
7678
  >;
6790
7679
 
6791
- /** @group Collection */
7680
+ /**
7681
+ * A {@link set} error containing element errors from a typed boundary.
7682
+ *
7683
+ * @group Collection
7684
+ */
6792
7685
  export type SetElementsError<Error extends TypeError> = [Error] extends [never]
6793
7686
  ? never
6794
7687
  : SetItemsErrorValue<Error, false>;
@@ -6960,18 +7853,6 @@ const isSetCollection = (
6960
7853
  return true;
6961
7854
  };
6962
7855
 
6963
- const formatSetError: TypeErrorFormatter<SetError> = (error) => {
6964
- if (error.reason.kind === "NotSet") {
6965
- return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
6966
- }
6967
- if (error.reason.kind === "UnexpectedPrototype") {
6968
- return "The value is a Set subclass, but a Set Output must be a direct Set.";
6969
- }
6970
-
6971
- const issue = error.reason.issues[0] as SetStructuralIssue;
6972
- return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
6973
- };
6974
-
6975
7856
  const hasDirectSetPrototype = (value: object): boolean => {
6976
7857
  const prototype: unknown = globalThis.Object.getPrototypeOf(value);
6977
7858
  if (prototype === null) return false;
@@ -6994,7 +7875,19 @@ const setRuntimeConfig: HomogeneousCollectionRuntimeConfig<
6994
7875
  validateSetItems(value, validateElement, options, false),
6995
7876
  encode: encodeSetCollection,
6996
7877
  is: isSetCollection,
6997
- formatError: formatSetError as TypeErrorFormatter<TypeError>,
7878
+ formatError: ((error: SetError) => {
7879
+ if (error.reason.kind === "NotSet")
7880
+ return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
7881
+ if (error.reason.kind === "UnexpectedPrototype")
7882
+ return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
7883
+ const issue = error.reason.issues[0];
7884
+ switch (issue.kind) {
7885
+ case "ExcessProperty":
7886
+ return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
7887
+ case "Element":
7888
+ return `A Set element at index ${issue.index} is invalid.`;
7889
+ }
7890
+ }) as TypeErrorFormatter<TypeError>,
6998
7891
  };
6999
7892
 
7000
7893
  const validateSetItems = (
@@ -7067,17 +7960,12 @@ const validateSetItems = (
7067
7960
  * ### Example
7068
7961
  *
7069
7962
  * ```ts
7070
- * import { Number, String, ok, transform, tuple } from "@evolu/common";
7071
- *
7072
- * const NumberFromString = transform("NumberFromString", String, Number, {
7073
- * from: (value) => ok(globalThis.Number(value)),
7074
- * to: globalThis.String,
7075
- * });
7963
+ * import { Int64FromInt64String, String, tuple } from "@evolu/common";
7076
7964
  *
7077
- * const Entry = tuple(String, NumberFromString);
7965
+ * const Entry = tuple(String, Int64FromInt64String);
7078
7966
  *
7079
- * expectOk(Entry.fromUnknown(["count", "1"]), ["count", 1]);
7080
- * expectOk(Entry.from.parent(["count", "1"]), ["count", 1]);
7967
+ * expectOk(Entry.fromUnknown(["count", "1"]), ["count", 1n]);
7968
+ * expectOk(Entry.from.parent(["count", "1"]), ["count", 1n]);
7081
7969
  * ```
7082
7970
  *
7083
7971
  * @group Collection
@@ -7148,14 +8036,11 @@ const createTupleType = (
7148
8036
  options,
7149
8037
  );
7150
8038
  const formatError: TypeErrorFormatter<TupleError> = (error) => {
7151
- if (error.reason.kind === "NotArray") {
8039
+ if (error.reason.kind === "NotArray")
7152
8040
  return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a tuple.`;
7153
- }
7154
- if (error.reason.kind === "InvalidLength") {
8041
+ if (error.reason.kind === "InvalidLength")
7155
8042
  return `A Tuple must contain exactly ${error.reason.expected} elements, but the value contains ${error.reason.actual}.`;
7156
- }
7157
- const issue = error.reason.issues[0] as TupleStructuralIssue;
7158
-
8043
+ const issue = error.reason.issues[0];
7159
8044
  switch (issue.kind) {
7160
8045
  case "Hole":
7161
8046
  return `A Tuple element at index ${issue.index} is missing.`;
@@ -7163,6 +8048,8 @@ const createTupleType = (
7163
8048
  return `A Tuple element at index ${issue.index} must be a data property.`;
7164
8049
  case "ExcessProperty":
7165
8050
  return "An excess Tuple property is not allowed. Remove it or use a different Type.";
8051
+ case "Element":
8052
+ return `A Tuple element at index ${issue.index} is invalid.`;
7166
8053
  }
7167
8054
  };
7168
8055
  const rootElements = typeElements.map(
@@ -7538,10 +8425,7 @@ export type Digit1To6 = typeof Digit1To6.Output;
7538
8425
  export const Digit1To23 = /*#__PURE__*/ union(
7539
8426
  Digit1To9,
7540
8427
  /*#__PURE__*/ templateLiteral("1", Digit),
7541
- /*#__PURE__*/ templateLiteral(
7542
- "2",
7543
- /*#__PURE__*/ union("0", "1", "2", "3"),
7544
- ),
8428
+ /*#__PURE__*/ templateLiteral("2", /*#__PURE__*/ union("0", "1", "2", "3")),
7545
8429
  );
7546
8430
  export type Digit1To23 = typeof Digit1To23.Output;
7547
8431
 
@@ -7552,10 +8436,7 @@ export type Digit1To23 = typeof Digit1To23.Output;
7552
8436
  */
7553
8437
  export const Digit1To51 = /*#__PURE__*/ union(
7554
8438
  Digit1To9,
7555
- /*#__PURE__*/ templateLiteral(
7556
- /*#__PURE__*/ union("1", "2", "3", "4"),
7557
- Digit,
7558
- ),
8439
+ /*#__PURE__*/ templateLiteral(/*#__PURE__*/ union("1", "2", "3", "4"), Digit),
7559
8440
  /*#__PURE__*/ templateLiteral("5", /*#__PURE__*/ union("0", "1")),
7560
8441
  );
7561
8442
  export type Digit1To51 = typeof Digit1To51.Output;
@@ -7654,31 +8535,19 @@ const createObjectRuntimeTypeIssues =
7654
8535
  }) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
7655
8536
  };
7656
8537
 
8538
+ const formatPlainObjectRootError = (
8539
+ reason:
8540
+ ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
8541
+ ): string =>
8542
+ reason.kind === "NotObject"
8543
+ ? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
8544
+ : "The value is an object, but an Object Output must be a plain object or have a null prototype.";
8545
+
7657
8546
  type PlainObjectError = ObjectError<
7658
8547
  Readonly<Record<never, never>>,
7659
8548
  ObjectPropertyAccessError | ObjectExcessPropertyError
7660
8549
  >;
7661
8550
 
7662
- const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
7663
- error,
7664
- ) => {
7665
- if (error.reason.kind !== "Properties") {
7666
- return formatPlainObjectRootError(error.reason);
7667
- }
7668
-
7669
- const key = Reflect.ownKeys(error.reason.errors).at(0);
7670
- assertNonNullable(key);
7671
- const propertyError = error.reason.errors[key];
7672
- assertNonNullable(propertyError);
7673
-
7674
- if (propertyError.type === "ObjectPropertyAccess") {
7675
- return propertyError.reason === "Accessor"
7676
- ? "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type."
7677
- : "An Object property must be enumerable. Make it enumerable or use a different Type.";
7678
- }
7679
- return "An Object property key must be a string. Remove it or use a different Type.";
7680
- };
7681
-
7682
8551
  /**
7683
8552
  * A {@link Type} for readonly plain objects with unknown property values.
7684
8553
  *
@@ -7693,7 +8562,7 @@ const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
7693
8562
  * values have their own Types, and {@link instanceOf} when an instance belongs
7694
8563
  * to the domain.
7695
8564
  *
7696
- * @group Base Types
8565
+ * @group Base
7697
8566
  */
7698
8567
  export const Object: Type<
7699
8568
  "Object",
@@ -7768,10 +8637,52 @@ export const Object: Type<
7768
8637
  reason: { kind: "Properties", errors },
7769
8638
  } as PlainObjectError);
7770
8639
  },
7771
- formatPlainObjectError,
7772
- /*#__PURE__*/ createObjectRuntimeTypeIssues(
7773
- formatPlainObjectError as TypeErrorFormatter<TypeError>,
7774
- ),
8640
+ (error: ObjectError) => {
8641
+ if (error.reason.kind !== "Properties")
8642
+ return formatPlainObjectRootError(error.reason);
8643
+ const key = Reflect.ownKeys(error.reason.errors).at(0);
8644
+ assertNonNullable(key);
8645
+ const propertyError = error.reason.errors[key];
8646
+ assertNonNullable(propertyError);
8647
+ if (propertyError.type === "ObjectPropertyAccess") {
8648
+ switch ((propertyError as ObjectPropertyAccessError).reason) {
8649
+ case "Accessor":
8650
+ return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
8651
+ case "NonEnumerable":
8652
+ return "An Object property must be enumerable. Make it enumerable or use a different Type.";
8653
+ }
8654
+ }
8655
+ if (propertyError.type === "ObjectMissingProperty")
8656
+ return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
8657
+ if (typeof key === "symbol")
8658
+ return "An Object property key must be a string. Remove the symbol property or use a different Type.";
8659
+ if (propertyError.type === "ObjectExcessProperty")
8660
+ return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
8661
+ return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
8662
+ },
8663
+ /*#__PURE__*/ createObjectRuntimeTypeIssues(((error: ObjectError) => {
8664
+ if (error.reason.kind !== "Properties")
8665
+ return formatPlainObjectRootError(error.reason);
8666
+ const key = Reflect.ownKeys(error.reason.errors).at(0);
8667
+ assertNonNullable(key);
8668
+ const propertyError = error.reason.errors[key];
8669
+ assertNonNullable(propertyError);
8670
+ if (propertyError.type === "ObjectPropertyAccess") {
8671
+ switch ((propertyError as ObjectPropertyAccessError).reason) {
8672
+ case "Accessor":
8673
+ return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
8674
+ case "NonEnumerable":
8675
+ return "An Object property must be enumerable. Make it enumerable or use a different Type.";
8676
+ }
8677
+ }
8678
+ if (propertyError.type === "ObjectMissingProperty")
8679
+ return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
8680
+ if (typeof key === "symbol")
8681
+ return "An Object property key must be a string. Remove the symbol property or use a different Type.";
8682
+ if (propertyError.type === "ObjectExcessProperty")
8683
+ return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
8684
+ return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
8685
+ }) as TypeErrorFormatter<TypeError>),
7775
8686
  );
7776
8687
 
7777
8688
  const isPlainObject = (value: object): boolean => {
@@ -7781,14 +8692,6 @@ const isPlainObject = (value: object): boolean => {
7781
8692
  );
7782
8693
  };
7783
8694
 
7784
- const formatPlainObjectRootError = (
7785
- reason:
7786
- ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
7787
- ): string =>
7788
- reason.kind === "NotObject"
7789
- ? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
7790
- : "The value is an object, but an Object Output must be a plain object or have a null prototype.";
7791
-
7792
8695
  /**
7793
8696
  * Record {@link Type}.
7794
8697
  *
@@ -7819,20 +8722,13 @@ const formatPlainObjectRootError = (
7819
8722
  *
7820
8723
  * ```ts
7821
8724
  * import {
7822
- * Number,
8725
+ * Int64FromInt64String,
7823
8726
  * String,
7824
- * ok,
7825
8727
  * record,
7826
- * transform,
7827
- * type Result,
8728
+ * type Int64,
7828
8729
  * } from "@evolu/common";
7829
8730
  *
7830
- * const ScoreFromString = transform("ScoreFromString", String, Number, {
7831
- * from: (value) => ok(globalThis.Number(value)),
7832
- * to: globalThis.String,
7833
- * });
7834
- *
7835
- * const ScoresByUser = record(String, ScoreFromString);
8731
+ * const ScoresByUser = record(String, Int64FromInt64String);
7836
8732
  * type ScoresByUser = typeof ScoresByUser.Output;
7837
8733
  *
7838
8734
  * // Validate an unknown value.
@@ -7841,7 +8737,7 @@ const formatPlainObjectRootError = (
7841
8737
  * grace: "20",
7842
8738
  * });
7843
8739
  *
7844
- * expectOk(scoresFromUnknown, { ada: 10, grace: 20 });
8740
+ * expectOk(scoresFromUnknown, { ada: 10n, grace: 20n });
7845
8741
  *
7846
8742
  * // Validate keys and values with their root Types.
7847
8743
  * const scoresInput = ScoresByUser.parent.fromUnknown({
@@ -7853,11 +8749,10 @@ const formatPlainObjectRootError = (
7853
8749
  * // Run the remaining key and value stages.
7854
8750
  * const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
7855
8751
  *
7856
- * expectTypeOf(scoresFromInput).toEqualTypeOf<
7857
- * Result<Readonly<Partial<Record<string, number>>>, never>
8752
+ * expectOk(scoresFromInput, { ada: 10n, grace: 20n });
8753
+ * expectTypeOf(scoresFromInput.value).toEqualTypeOf<
8754
+ * Readonly<Partial<Record<string, Int64>>>
7858
8755
  * >();
7859
- *
7860
- * expectOk(scoresFromInput, { ada: 10, grace: 20 });
7861
8756
  * ```
7862
8757
  *
7863
8758
  * Note that TypeScript does not model an object's runtime prototype. This can
@@ -7954,20 +8849,20 @@ export const record = <
7954
8849
  options,
7955
8850
  );
7956
8851
  const formatError: TypeErrorFormatter<RecordError> = (error) => {
7957
- if (error.reason.kind === "NotRecord") {
7958
- return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a record.`;
7959
- }
7960
- if (error.reason.kind === "NotPlainRecord") {
8852
+ if (error.reason.kind === "NotRecord")
8853
+ return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Record.`;
8854
+ if (error.reason.kind === "NotPlainRecord")
7961
8855
  return "The value is an object, but a Record Output must be a plain object or have a null prototype.";
7962
- }
7963
-
7964
- const issue = error.reason.issues[0] as RecordStructuralIssue;
7965
-
8856
+ const issue = error.reason.issues[0];
7966
8857
  switch (issue.kind) {
8858
+ case "Key":
8859
+ return `Property key ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
8860
+ case "Value":
8861
+ return `The value of property ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
7967
8862
  case "Accessor":
7968
- return `A record property ${safelyStringifyUnknownValue(issue.key)} must be a data property.`;
8863
+ return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be a data property.`;
7969
8864
  case "NonEnumerable":
7970
- return `A record property ${safelyStringifyUnknownValue(issue.key)} must be enumerable.`;
8865
+ return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be enumerable.`;
7971
8866
  case "Collision":
7972
8867
  return `Record keys ${safelyStringifyUnknownValue(issue.previousKey)} and ${safelyStringifyUnknownValue(issue.key)} decode to the same key ${safelyStringifyUnknownValue(issue.outputKey)}.`;
7973
8868
  }
@@ -8073,7 +8968,11 @@ export const record = <
8073
8968
  );
8074
8969
  };
8075
8970
 
8076
- /** @group Objects */
8971
+ /**
8972
+ * The {@link Type} returned by {@link record}.
8973
+ *
8974
+ * @group Objects
8975
+ */
8077
8976
  export interface RecordType<
8078
8977
  KeyType extends TypeNode,
8079
8978
  ValueType extends TypeNode,
@@ -8197,7 +9096,11 @@ type RecordKeyStringTypeError = CompileTimeError<
8197
9096
  "Record key Type Input and Output must extend string."
8198
9097
  >;
8199
9098
 
8200
- /** @group Objects */
9099
+ /**
9100
+ * Error returned while validating a {@link record} and its entries.
9101
+ *
9102
+ * @group Objects
9103
+ */
8201
9104
  export type RecordError<
8202
9105
  KeyError extends TypeError = TypeError,
8203
9106
  ValueError extends TypeError = TypeError,
@@ -8211,7 +9114,11 @@ export type RecordError<
8211
9114
  Collision | RecordAccessorIssue | RecordNonEnumerableIssue
8212
9115
  >;
8213
9116
 
8214
- /** @group Objects */
9117
+ /**
9118
+ * Error returned when a {@link record} input is not an object.
9119
+ *
9120
+ * @group Objects
9121
+ */
8215
9122
  export interface RecordNotRecordError extends TypeError<"Record"> {
8216
9123
  readonly reason: {
8217
9124
  readonly kind: "NotRecord";
@@ -8219,7 +9126,11 @@ export interface RecordNotRecordError extends TypeError<"Record"> {
8219
9126
  };
8220
9127
  }
8221
9128
 
8222
- /** @group Objects */
9129
+ /**
9130
+ * Error returned when a {@link record} input is not a plain object.
9131
+ *
9132
+ * @group Objects
9133
+ */
8223
9134
  export interface RecordNotPlainRecordError extends TypeError<"Record"> {
8224
9135
  readonly reason: {
8225
9136
  readonly kind: "NotPlainRecord";
@@ -8244,14 +9155,22 @@ export type RecordEntriesError<
8244
9155
  ? never
8245
9156
  : RecordEntriesErrorValue<KeyError, ValueError, StructuralIssue>;
8246
9157
 
8247
- /** @group Objects */
9158
+ /**
9159
+ * An invalid key, value, or property structure in a {@link record}.
9160
+ *
9161
+ * @group Objects
9162
+ */
8248
9163
  export type RecordIssue<
8249
9164
  KeyError extends TypeError,
8250
9165
  ValueError extends TypeError,
8251
9166
  StructuralIssue extends RecordStructuralIssue = RecordCollisionIssue,
8252
9167
  > = RecordKeyIssue<KeyError> | RecordValueIssue<ValueError> | StructuralIssue;
8253
9168
 
8254
- /** @group Objects */
9169
+ /**
9170
+ * A property-structure issue returned by {@link record}.
9171
+ *
9172
+ * @group Objects
9173
+ */
8255
9174
  export type RecordStructuralIssue =
8256
9175
  RecordAccessorIssue | RecordCollisionIssue | RecordNonEnumerableIssue;
8257
9176
 
@@ -8275,7 +9194,11 @@ export interface RecordNonEnumerableIssue {
8275
9194
  readonly key: string | symbol;
8276
9195
  }
8277
9196
 
8278
- /** @group Objects */
9197
+ /**
9198
+ * An invalid key and its source property key in a {@link record}.
9199
+ *
9200
+ * @group Objects
9201
+ */
8279
9202
  export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
8280
9203
  ? {
8281
9204
  readonly kind: "Key";
@@ -8284,7 +9207,11 @@ export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
8284
9207
  }
8285
9208
  : never;
8286
9209
 
8287
- /** @group Objects */
9210
+ /**
9211
+ * An invalid value and its property key in a {@link record}.
9212
+ *
9213
+ * @group Objects
9214
+ */
8288
9215
  export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
8289
9216
  ? {
8290
9217
  readonly kind: "Value";
@@ -8293,7 +9220,11 @@ export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
8293
9220
  }
8294
9221
  : never;
8295
9222
 
8296
- /** @group Objects */
9223
+ /**
9224
+ * Two {@link record} keys that decode to the same output key.
9225
+ *
9226
+ * @group Objects
9227
+ */
8297
9228
  export interface RecordCollisionIssue {
8298
9229
  readonly kind: "Collision";
8299
9230
  readonly key: string | symbol;
@@ -8520,48 +9451,38 @@ type ObjectProperty = ObjectProps[string];
8520
9451
  *
8521
9452
  * ```ts
8522
9453
  * import {
8523
- * Number,
9454
+ * Int64FromInt64String,
8524
9455
  * String,
8525
9456
  * object,
8526
- * ok,
8527
- * transform,
8528
9457
  * type InferType,
8529
- * type Result,
8530
9458
  * } from "@evolu/common";
8531
9459
  *
8532
- * const AgeFromString = transform("AgeFromString", String, Number, {
8533
- * from: (value) => ok(globalThis.Number(value)),
8534
- * to: globalThis.String,
8535
- * });
8536
- *
8537
9460
  * const User = object({
8538
9461
  * name: String,
8539
- * age: AgeFromString,
9462
+ * loginCount: Int64FromInt64String,
8540
9463
  * });
8541
9464
  * interface User extends InferType<typeof User> {}
8542
9465
  *
8543
9466
  * // Validate an unknown value.
8544
- * const userFromUnknown = User.fromUnknown({ name: "Ada", age: "42" });
9467
+ * const userFromUnknown = User.fromUnknown({
9468
+ * name: "Ada",
9469
+ * loginCount: "42",
9470
+ * });
8545
9471
  *
8546
- * expectOk(userFromUnknown, { name: "Ada", age: 42 });
9472
+ * expectOk(userFromUnknown, { name: "Ada", loginCount: 42n });
8547
9473
  *
8548
9474
  * // Validate the object and root property Types.
8549
9475
  * const userInput = User.parent.fromUnknown({
8550
9476
  * name: "Ada",
8551
- * age: "42",
9477
+ * loginCount: "42",
8552
9478
  * });
8553
- * expectOk(userInput, { name: "Ada", age: "42" });
9479
+ * expectOk(userInput, { name: "Ada", loginCount: "42" });
8554
9480
  *
8555
9481
  * // Run the remaining property stages.
8556
- * const userFromInput: Result<
8557
- * { readonly name: string; readonly age: number },
8558
- * never
8559
- * > = User.from.parent(userInput.value);
9482
+ * const userFromInput = User.from.parent(userInput.value);
8560
9483
  *
8561
- * expectTypeOf(userFromInput).toEqualTypeOf<
8562
- * Result<{ readonly name: string; readonly age: number }, never>
8563
- * >();
8564
- * expectOk(userFromInput, { name: "Ada", age: 42 });
9484
+ * expectOk(userFromInput, { name: "Ada", loginCount: 42n });
9485
+ * expectTypeOf(userFromInput.value).toExtend<User>();
8565
9486
  * ```
8566
9487
  *
8567
9488
  * Note that TypeScript does not model an object's runtime prototype. This can
@@ -8614,6 +9535,8 @@ export function object<const Props extends ObjectProps>(
8614
9535
  ? []
8615
9536
  : [ValidationFailure<ObjectValidationError<Props>>]
8616
9537
  ): StrictObjectType<Props>;
9538
+
9539
+ /** Creates an Object Type with additional record properties. */
8617
9540
  export function object<
8618
9541
  const Props extends ObjectProps,
8619
9542
  const Rest extends RecordTypeNode & ConcreteTypeNode,
@@ -8818,31 +9741,27 @@ const createObjectType = (
8818
9741
  options: ValidationOptions = firstValidationOptions,
8819
9742
  ) => validate(value, options, true);
8820
9743
  const formatError: TypeErrorFormatter<ObjectError> = (error) => {
8821
- if (error.reason.kind !== "Properties") {
9744
+ if (error.reason.kind !== "Properties")
8822
9745
  return formatPlainObjectRootError(error.reason);
8823
- }
8824
-
8825
9746
  const key = Reflect.ownKeys(error.reason.errors).at(0);
8826
9747
  assertNonNullable(key);
8827
- const propertyError = (
8828
- error.reason.errors as Readonly<Partial<Record<PropertyKey, TypeError>>>
8829
- )[key]!;
8830
-
9748
+ const propertyError = error.reason.errors[key];
9749
+ assertNonNullable(propertyError);
8831
9750
  if (propertyError.type === "ObjectPropertyAccess") {
8832
9751
  switch ((propertyError as ObjectPropertyAccessError).reason) {
8833
9752
  case "Accessor":
8834
9753
  return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
8835
9754
  case "NonEnumerable":
8836
- return "An Object property must be enumerable.";
9755
+ return "An Object property must be enumerable. Make it enumerable or use a different Type.";
8837
9756
  }
8838
9757
  }
8839
- if (
8840
- typeof key === "string" &&
8841
- globalThis.Object.hasOwn(runtimeProps, key)
8842
- ) {
8843
- return "A required property is missing.";
8844
- }
8845
- return "An excess property is not allowed. Remove it or use a different Type.";
9758
+ if (propertyError.type === "ObjectMissingProperty")
9759
+ return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
9760
+ if (typeof key === "symbol")
9761
+ return "An Object property key must be a string. Remove the symbol property or use a different Type.";
9762
+ if (propertyError.type === "ObjectExcessProperty")
9763
+ return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
9764
+ return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
8846
9765
  };
8847
9766
  const rootProps = createMutableRecord<string, RuntimeObjectProperty>();
8848
9767
  let hasNonRootType = false;
@@ -9214,7 +10133,11 @@ type ObjectRecordCanonicalInputTypeError = CompileTimeError<
9214
10133
  "Every declared property Type CanonicalInput must extend the Object Record value Type CanonicalInput."
9215
10134
  >;
9216
10135
 
9217
- /** @group Objects */
10136
+ /**
10137
+ * The {@link Type} returned by {@link object}.
10138
+ *
10139
+ * @group Objects
10140
+ */
9218
10141
  export type ObjectType<
9219
10142
  Props extends ObjectProps,
9220
10143
  Rest extends ObjectRecordTypeNode | undefined = undefined,
@@ -9454,10 +10377,10 @@ export interface ObjectNotObjectError extends TypeError<"Object"> {
9454
10377
  *
9455
10378
  * Object Types accept a `null` prototype or a prototype whose own prototype is
9456
10379
  * `null`. This includes ordinary and cross-realm plain objects as well as
9457
- * objects created from an immediate root prototype. Arrays, built-in objects,
9458
- * class instances, and objects with deeper custom prototype chains return this
9459
- * error instead of having their prototype or inherited state discarded.
9460
- * `reason.value` is the rejected object.
10380
+ * objects created from an immediate root prototype. Arrays, JavaScript built-in
10381
+ * objects, class instances, and objects with deeper custom prototype chains
10382
+ * return this error instead of having their prototype or inherited state
10383
+ * discarded. `reason.value` is the rejected object.
9461
10384
  *
9462
10385
  * @group Objects
9463
10386
  */
@@ -9649,7 +10572,11 @@ export const partial = <const Props extends ObjectProps>(
9649
10572
  >;
9650
10573
  };
9651
10574
 
9652
- /** @group Objects */
10575
+ /**
10576
+ * Maps every required object property Type to an optional property.
10577
+ *
10578
+ * @group Objects
10579
+ */
9653
10580
  export type PartialObjectProps<Props extends ObjectProps> = {
9654
10581
  readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
9655
10582
  ? Props[Key]
@@ -9719,7 +10646,12 @@ export const nullableToOptional = <const Props extends ObjectProps>(
9719
10646
  >;
9720
10647
  };
9721
10648
 
9722
- /** @group Objects */
10649
+ /**
10650
+ * Maps object properties whose Union Type includes {@link Null} to optional
10651
+ * properties.
10652
+ *
10653
+ * @group Objects
10654
+ */
9723
10655
  export type NullableToOptionalProps<Props extends ObjectProps> = {
9724
10656
  readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
9725
10657
  ? Props[Key]
@@ -9733,6 +10665,17 @@ export type NullableToOptionalProps<Props extends ObjectProps> = {
9733
10665
  /**
9734
10666
  * Object {@link Type} without the selected declared properties.
9735
10667
  *
10668
+ * ### Example
10669
+ *
10670
+ * ```ts
10671
+ * import { String, object, omit } from "@evolu/common";
10672
+ *
10673
+ * const User = object({ name: String, password: String });
10674
+ * const PublicUser = omit(User, "password");
10675
+ *
10676
+ * expectOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
10677
+ * ```
10678
+ *
9736
10679
  * @group Objects
9737
10680
  */
9738
10681
  export const omit = <
@@ -9785,10 +10728,10 @@ type OmitKeyConcreteTypeError = CompileTimeError<
9785
10728
  >;
9786
10729
 
9787
10730
  /**
9788
- * {@link Result} {@link Type} for typed success and error values.
10731
+ * Creates a {@link Type} for {@link Result} values.
9789
10732
  *
9790
10733
  * Use this to validate Results crossing a storage, worker, API, or other
9791
- * serialization boundary. The operation returns an outer validation Result. Its
10734
+ * serialization boundary. `fromUnknown` returns an outer validation Result. Its
9792
10735
  * successful value is the inner domain Result described by `okType` and
9793
10736
  * `errorType`.
9794
10737
  *
@@ -9805,34 +10748,16 @@ type OmitKeyConcreteTypeError = CompileTimeError<
9805
10748
  *
9806
10749
  * const SyncResponse = result(
9807
10750
  * object({ timestamp: NonNegativeInt }),
9808
- * typed("SyncError", { message: String }),
10751
+ * typed("SyncFailed", { message: String }),
9809
10752
  * );
9810
10753
  *
9811
- * const describeResponse = (input: unknown): string => {
9812
- * const validated = SyncResponse.fromUnknown(input);
9813
- * if (!validated.ok) return "Invalid response";
9814
- *
9815
- * const response = validated.value;
9816
- * return response.ok
9817
- * ? `Synced at ${response.value.timestamp}`
9818
- * : response.error.message;
9819
- * };
10754
+ * const validated = SyncResponse.fromUnknown({
10755
+ * ok: true,
10756
+ * value: { timestamp: 42 },
10757
+ * });
9820
10758
  *
9821
- * expect(
9822
- * describeResponse({
9823
- * ok: true,
9824
- * value: { timestamp: 42 },
9825
- * }),
9826
- * ).toBe("Synced at 42");
9827
- * expect(
9828
- * describeResponse({
9829
- * ok: false,
9830
- * error: { type: "SyncError", message: "Offline" },
9831
- * }),
9832
- * ).toBe("Offline");
9833
- * expect(describeResponse({ ok: true, value: { timestamp: -1 } })).toBe(
9834
- * "Invalid response",
9835
- * );
10759
+ * expectOk(validated, { ok: true, value: { timestamp: 42 } });
10760
+ * expectOk(validated.value, { timestamp: 42 });
9836
10761
  * ```
9837
10762
  *
9838
10763
  * @group Results
@@ -9887,7 +10812,7 @@ export const UnknownResult = /*#__PURE__*/ result(Unknown, Unknown);
9887
10812
  export type UnknownResult = typeof UnknownResult.Output;
9888
10813
 
9889
10814
  /**
9890
- * Tagged {@link ObjectType}.
10815
+ * Creates an {@link ObjectType} with a literal `type` property.
9891
10816
  *
9892
10817
  * The discriminator belongs to `typed`, so additional properties cannot declare
9893
10818
  * `type`. The discriminator Input is `string`, inherited from {@link String},
@@ -9899,22 +10824,16 @@ export type UnknownResult = typeof UnknownResult.Output;
9899
10824
  * ### Example
9900
10825
  *
9901
10826
  * ```ts
9902
- * import { String, discriminatedUnion, typed } from "@evolu/common";
10827
+ * import { String, typed } from "@evolu/common";
9903
10828
  *
9904
10829
  * const Pending = typed("Pending", {
9905
10830
  * label: String,
9906
10831
  * });
9907
10832
  *
9908
- * const Completed = typed("Completed");
9909
- * const Status = discriminatedUnion(Pending, Completed);
9910
- *
9911
- * expectOk(Status.fromUnknown({ type: "Pending", label: "Waiting" }), {
10833
+ * expectOk(Pending.fromUnknown({ type: "Pending", label: "Waiting" }), {
9912
10834
  * type: "Pending",
9913
10835
  * label: "Waiting",
9914
10836
  * });
9915
- * expectOk(Status.fromUnknown({ type: "Completed" }), {
9916
- * type: "Completed",
9917
- * });
9918
10837
  * ```
9919
10838
  *
9920
10839
  * @group Discriminated unions
@@ -9922,6 +10841,8 @@ export type UnknownResult = typeof UnknownResult.Output;
9922
10841
  export function typed<const Tag extends TypeName>(
9923
10842
  tag: ValidateTypedTag<Tag>,
9924
10843
  ): TypedType<Tag>;
10844
+
10845
+ /** Creates a Tagged Object Type with declared properties. */
9925
10846
  export function typed<
9926
10847
  const Tag extends TypeName,
9927
10848
  const Props extends ObjectProps,
@@ -9932,6 +10853,8 @@ export function typed<
9932
10853
  ? []
9933
10854
  : [ValidationFailure<TypedValidationError<Props>>]
9934
10855
  ): TypedType<Tag, Props>;
10856
+
10857
+ /** Creates a Tagged Object Type with additional record properties. */
9935
10858
  export function typed<
9936
10859
  const Tag extends TypeName,
9937
10860
  const Props extends ObjectProps,
@@ -9971,7 +10894,14 @@ export function typed(
9971
10894
  }
9972
10895
 
9973
10896
  /**
9974
- * A structurally tagged value created by {@link typed}.
10897
+ * A TypeScript interface with a literal `type` property.
10898
+ *
10899
+ * Use `Typed` for both domain objects in discriminated unions and plain domain
10900
+ * errors returned by {@link Result}. Name a domain error interface `XError`.
10901
+ * When `X` already describes a failure, use `X` for its `type` discriminant
10902
+ * because `Error` describes the interface's role rather than the runtime error
10903
+ * kind. Keep `Error` when it is needed to make the discriminant unambiguous,
10904
+ * such as `TimeoutError`.
9975
10905
  *
9976
10906
  * Typed unions model mutually exclusive states as separate variants instead of
9977
10907
  * combinations of flags and optional properties. TypeScript narrows a union by
@@ -9982,20 +10912,23 @@ export function typed(
9982
10912
  * ### Example
9983
10913
  *
9984
10914
  * ```ts
9985
- * import { type Typed } from "@evolu/common";
10915
+ * import { err, ok, type Result, type Typed } from "@evolu/common";
9986
10916
  *
9987
- * type Status = Typed<"Pending"> | Typed<"Completed">;
10917
+ * interface User extends Typed<"User"> {
10918
+ * readonly id: string;
10919
+ * }
9988
10920
  *
9989
- * const getStatusMessage = (status: Status): string => {
9990
- * switch (status.type) {
9991
- * case "Pending":
9992
- * return "Waiting";
9993
- * case "Completed":
9994
- * return "Done";
9995
- * }
9996
- * };
10921
+ * const getUser = (id: string): Result<User, UserNotFoundError> =>
10922
+ * id === "user-1"
10923
+ * ? ok({ type: "User", id })
10924
+ * : err({ type: "UserNotFound", id });
9997
10925
  *
9998
- * expect(getStatusMessage({ type: "Pending" })).toBe("Waiting");
10926
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {
10927
+ * readonly id: string;
10928
+ * }
10929
+ *
10930
+ * expectOk(getUser("user-1"), { type: "User", id: "user-1" });
10931
+ * expectErr(getUser("missing"), { type: "UserNotFound", id: "missing" });
9999
10932
  * ```
10000
10933
  *
10001
10934
  * @group Discriminated unions
@@ -10007,7 +10940,7 @@ export interface Typed<Tag extends TypeName> {
10007
10940
  /**
10008
10941
  * Extracts members of a {@link Typed} Output union by their `type` literal.
10009
10942
  *
10010
- * The requested name is constrained to the union's actual discriminator values,
10943
+ * The requested tag is constrained to the union's actual discriminator values,
10011
10944
  * so a misspelling is a TypeScript error instead of silently producing
10012
10945
  * `never`.
10013
10946
  *
@@ -10079,7 +11012,8 @@ type TypedTypePropertyError = CompileTimeError<
10079
11012
  >;
10080
11013
 
10081
11014
  /**
10082
- * Producer-result {@link Type} for value, error, or done outcomes.
11015
+ * Creates a {@link Type} for producer Results with value, error, or done
11016
+ * outcomes.
10083
11017
  *
10084
11018
  * The three outcomes are `Ok<Value>`, `Err<Error>`, and `Err<Typed<"Done"> & {
10085
11019
  * done: Done }>`. This keeps normal completion distinct from failure while
@@ -10092,7 +11026,7 @@ type TypedTypePropertyError = CompileTimeError<
10092
11026
  *
10093
11027
  * const StringNextResult = nextResult(
10094
11028
  * String,
10095
- * typed("ProducerError", { message: String }),
11029
+ * typed("ReadFailed", { message: String }),
10096
11030
  * String,
10097
11031
  * );
10098
11032
  *
@@ -10116,7 +11050,7 @@ type TypedTypePropertyError = CompileTimeError<
10116
11050
  * expect(
10117
11051
  * describeNext({
10118
11052
  * ok: false,
10119
- * error: { type: "ProducerError", message: "Offline" },
11053
+ * error: { type: "ReadFailed", message: "Offline" },
10120
11054
  * }),
10121
11055
  * ).toBe("Error: Offline");
10122
11056
  * ```
@@ -10178,7 +11112,7 @@ export const UnknownNextResult = /*#__PURE__*/ nextResult(
10178
11112
  export type UnknownNextResult = typeof UnknownNextResult.Output;
10179
11113
 
10180
11114
  /**
10181
- * Discriminated Union {@link Type}.
11115
+ * Discriminated union {@link Type}.
10182
11116
  *
10183
11117
  * With no explicit key, the conventional `type` property created by
10184
11118
  * {@link typed} is used. Pass a key first to discriminate
@@ -10213,6 +11147,35 @@ export function discriminatedUnion<
10213
11147
  >(
10214
11148
  ...members: Members & DiscriminatedUnionValidation<"type", Members>
10215
11149
  ): DiscriminatedUnionType<"type", Members>;
11150
+
11151
+ /**
11152
+ * Creates a Discriminated Union Type with an explicit discriminator key.
11153
+ *
11154
+ * ### Example
11155
+ *
11156
+ * ```ts
11157
+ * import {
11158
+ * Number,
11159
+ * String,
11160
+ * discriminatedUnion,
11161
+ * literal,
11162
+ * object,
11163
+ * } from "@evolu/common";
11164
+ *
11165
+ * const Added = object({ kind: literal("added"), value: String });
11166
+ * const Removed = object({ kind: literal("removed"), id: Number });
11167
+ * const Event = discriminatedUnion("kind", Added, Removed);
11168
+ *
11169
+ * expectOk(Event.fromUnknown({ kind: "added", value: "Evolu" }), {
11170
+ * kind: "added",
11171
+ * value: "Evolu",
11172
+ * });
11173
+ * expectOk(Event.fromUnknown({ kind: "removed", id: 1 }), {
11174
+ * kind: "removed",
11175
+ * id: 1,
11176
+ * });
11177
+ * ```
11178
+ */
10216
11179
  export function discriminatedUnion<
10217
11180
  const Key extends string,
10218
11181
  const Members extends DiscriminatedUnionMembers,
@@ -10354,26 +11317,21 @@ export function discriminatedUnion(
10354
11317
  Reflect.get(value as Readonly<Record<string, unknown>>, key),
10355
11318
  )!;
10356
11319
  const formatError: TypeErrorFormatter<DiscriminatedUnionError> = (error) => {
10357
- const reason = error.reason as Exclude<
10358
- DiscriminatedUnionError["reason"],
10359
- DiscriminatedUnionMemberIssue
10360
- >;
10361
-
10362
- switch (reason.kind) {
11320
+ switch (error.reason.kind) {
10363
11321
  case "Object":
10364
- return formatPlainObjectRootError(reason.error.reason);
11322
+ return formatPlainObjectRootError(error.reason.error.reason);
10365
11323
  case "PropertyAccess": {
10366
- const property = `The discriminator property ${safelyStringifyUnknownValue(reason.key)}`;
10367
- if (reason.reason === "Accessor") {
11324
+ const property = `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)}`;
11325
+ if (error.reason.reason === "Accessor")
10368
11326
  return `${property} must be a data property.`;
10369
- }
10370
- if (reason.reason === "Inherited") {
11327
+ if (error.reason.reason === "Inherited")
10371
11328
  return `${property} must be an own property.`;
10372
- }
10373
11329
  return `${property} must be enumerable.`;
10374
11330
  }
10375
11331
  case "Discriminator":
10376
- return `The discriminator property ${safelyStringifyUnknownValue(reason.key)} has an unexpected value ${safelyStringifyUnknownValue(reason.value)}.`;
11332
+ return `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)} has an unexpected value ${safelyStringifyUnknownValue(error.reason.value)}.`;
11333
+ case "Member":
11334
+ return `The selected variant ${safelyStringifyUnknownValue(error.reason.discriminator)} is invalid.`;
10377
11335
  }
10378
11336
  };
10379
11337
  const defaultFormatter = formatError as TypeErrorFormatter<TypeError>;
@@ -10799,10 +11757,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
10799
11757
  };
10800
11758
 
10801
11759
  /**
10802
- * Lazy {@link Type} for recursive definitions.
11760
+ * Creates a lazy {@link Type} for recursive definitions.
10803
11761
  *
10804
- * Lazy defers and caches a Type definition, allowing recursive data such as
10805
- * trees and mutually recursive models.
11762
+ * The definition is evaluated on first use and then cached, allowing recursive
11763
+ * data such as trees and mutually recursive models.
10806
11764
  *
10807
11765
  * A recursive declaration refers to its own variable while that variable is
10808
11766
  * being initialized, so TypeScript cannot infer it reliably. Getter-based
@@ -10822,12 +11780,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
10822
11780
  *
10823
11781
  * ```ts
10824
11782
  * import {
10825
- * Number,
10826
11783
  * String,
10827
11784
  * array,
10828
11785
  * lazy,
10829
11786
  * object,
10830
- * optional,
10831
11787
  * type ArrayError,
10832
11788
  * type LazyType,
10833
11789
  * type ObjectError,
@@ -10848,35 +11804,19 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
10848
11804
  * () => object({ value: String, children: array(Tree) }),
10849
11805
  * );
10850
11806
  *
10851
- * interface Left {
10852
- * readonly label: string;
10853
- * readonly right?: Right;
10854
- * }
10855
- *
10856
- * interface Right {
10857
- * readonly count: number;
10858
- * readonly left?: Left;
10859
- * }
10860
- *
10861
- * interface LeftError extends ObjectError<{
10862
- * readonly label: TypeOfError<"String">;
10863
- * readonly right?: RightError;
10864
- * }> {}
10865
- *
10866
- * interface RightError extends ObjectError<{
10867
- * readonly count: TypeOfError<"Number">;
10868
- * readonly left?: LeftError;
10869
- * }> {}
10870
- *
10871
- * const Left: LazyType<Left, Left, never, LeftError, LeftError> = lazy(
10872
- * () => object({ label: String, right: optional(Right) }),
10873
- * );
11807
+ * const result = Tree.fromUnknown({
11808
+ * value: "root",
11809
+ * children: [{ value: "leaf", children: [] }],
11810
+ * });
10874
11811
  *
10875
- * const Right: LazyType<Right, Right, never, RightError, RightError> =
10876
- * lazy(() => object({ count: Number, left: optional(Left) }));
11812
+ * expectOk(result, {
11813
+ * value: "root",
11814
+ * children: [{ value: "leaf", children: [] }],
11815
+ * });
11816
+ * expectTypeOf(result.value).toEqualTypeOf<Tree>();
10877
11817
  * ```
10878
11818
  *
10879
- * @group Recursive Types
11819
+ * @group Recursive
10880
11820
  */
10881
11821
  export function lazy<Target extends ConcreteTypeNode>(
10882
11822
  getType: Thunk<ValidateLazyTarget<Target>>,
@@ -10995,7 +11935,7 @@ export function lazy(getType: Thunk<TypeNode>): TypeNode {
10995
11935
  * recursive declaration finite for TypeScript while preserving structured
10996
11936
  * errors at every boundary.
10997
11937
  *
10998
- * @group Recursive Types
11938
+ * @group Recursive
10999
11939
  */
11000
11940
  export interface LazyType<
11001
11941
  // Explicit invariance prevents recursive comparisons from repeatedly
@@ -11123,7 +12063,7 @@ export interface JsonObject {
11123
12063
  }
11124
12064
 
11125
12065
  /**
11126
- * One issue found while validating an exact {@link JsonValue}.
12066
+ * One issue found while validating a candidate as an exact {@link JsonValue}.
11127
12067
  *
11128
12068
  * @group JSON
11129
12069
  */
@@ -11171,7 +12111,8 @@ export type JsonValueIssue =
11171
12111
  };
11172
12112
 
11173
12113
  /**
11174
- * An error containing one or more issues found in a {@link JsonValue}.
12114
+ * An error containing one or more issues found while validating a candidate as
12115
+ * an exact {@link JsonValue}.
11175
12116
  *
11176
12117
  * @group JSON
11177
12118
  */
@@ -11500,31 +12441,6 @@ const validateJsonValue = (
11500
12441
  });
11501
12442
  };
11502
12443
 
11503
- const formatJsonValueError: TypeErrorFormatter<JsonValueError> = (error) => {
11504
- const issue = error.reason.issues[0];
11505
-
11506
- switch (issue.kind) {
11507
- case "InvalidType":
11508
- return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
11509
- case "NonFiniteNumber":
11510
- return "A JSON number must be finite.";
11511
- case "UnexpectedPrototype":
11512
- return "The value is an object, but a JsonValue Object must be a plain object or have a null prototype.";
11513
- case "Accessor":
11514
- return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
11515
- case "NonEnumerable":
11516
- return "A JSON Object property must be enumerable. Remove it or use a different Type.";
11517
- case "SymbolProperty":
11518
- return "A JSON Object property key must be a string. Remove the symbol property or use a different Type.";
11519
- case "Hole":
11520
- return "A JSON Array element is missing.";
11521
- case "ExcessProperty":
11522
- return "An excess JSON Array property is not allowed. Remove it or use a different Type.";
11523
- case "CircularReference":
11524
- return "A JsonValue must not contain circular references.";
11525
- }
11526
- };
11527
-
11528
12444
  const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
11529
12445
  const jsonValueError = error as JsonValueError;
11530
12446
  const issues =
@@ -11542,7 +12458,29 @@ const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
11542
12458
  reason: { kind: "Issues", issues: [issue] },
11543
12459
  },
11544
12460
  path: issue.path,
11545
- formatError: formatJsonValueError as TypeErrorFormatter<TypeError>,
12461
+ formatError: ((error: JsonValueError) => {
12462
+ const issue = error.reason.issues[0];
12463
+ switch (issue.kind) {
12464
+ case "InvalidType":
12465
+ return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
12466
+ case "NonFiniteNumber":
12467
+ return "A JSON number must be finite.";
12468
+ case "UnexpectedPrototype":
12469
+ return "The value is an object, but a JsonValue object must be a plain object or have a null prototype.";
12470
+ case "Accessor":
12471
+ return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
12472
+ case "NonEnumerable":
12473
+ return "A JSON object property must be enumerable. Remove it or use a different Type.";
12474
+ case "SymbolProperty":
12475
+ return "A JSON object property key must be a string. Remove the symbol property or use a different Type.";
12476
+ case "Hole":
12477
+ return "A JSON array element is missing.";
12478
+ case "ExcessProperty":
12479
+ return "An excess JSON array property is not allowed. Remove it or use a different Type.";
12480
+ case "CircularReference":
12481
+ return "A JsonValue must not contain circular references.";
12482
+ }
12483
+ }) as TypeErrorFormatter<TypeError>,
11546
12484
  })) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
11547
12485
  };
11548
12486
 
@@ -11627,6 +12565,21 @@ const stringifyJsonValue = (value: JsonValue): Json => {
11627
12565
  /**
11628
12566
  * Exact root Type for {@link JsonValue} data trees.
11629
12567
  *
12568
+ * ### Example
12569
+ *
12570
+ * ```ts
12571
+ * import { JsonValue, type JsonValueInput } from "@evolu/common";
12572
+ *
12573
+ * const input: JsonValueInput = {
12574
+ * name: "Ada",
12575
+ * scores: [10, 20],
12576
+ * };
12577
+ * const result = JsonValue.fromUnknown(input);
12578
+ *
12579
+ * expectOk(result, input);
12580
+ * expectTypeOf(result.value).toEqualTypeOf<JsonValue>();
12581
+ * ```
12582
+ *
11630
12583
  * @group JSON
11631
12584
  */
11632
12585
  export const JsonValue: JsonValueType =
@@ -11662,7 +12615,7 @@ export const JsonObject = /*#__PURE__*/ record(
11662
12615
  * A {@link String} Brand proving that its exact text parses to {@link JsonValue}.
11663
12616
  *
11664
12617
  * The Brand preserves whitespace, property order, and number spelling. Convert
11665
- * it totally to {@link JsonValue} through {@link JsonValueFromJson} or
12618
+ * it to {@link JsonValue} through {@link JsonValueFromJson} or
11666
12619
  * {@link jsonToJsonValue}.
11667
12620
  *
11668
12621
  * @group JSON
@@ -11681,14 +12634,34 @@ export const Json = /*#__PURE__*/ brand(
11681
12634
  export type Json = typeof Json.Output;
11682
12635
 
11683
12636
  /**
11684
- * Totally parses proven {@link Json} text into an exact {@link JsonValue}.
12637
+ * Converts proven {@link Json} text to an exact {@link JsonValue}.
12638
+ *
12639
+ * ### Example
12640
+ *
12641
+ * ```ts
12642
+ * import { Json, jsonToJsonValue } from "@evolu/common";
12643
+ *
12644
+ * const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
12645
+ *
12646
+ * expect(value).toEqual({ name: "Ada" });
12647
+ * ```
11685
12648
  *
11686
12649
  * @group JSON
11687
12650
  */
11688
12651
  export const jsonToJsonValue = (value: Json): JsonValue => parseJson(value);
11689
12652
 
11690
12653
  /**
11691
- * Totally encodes an exact {@link JsonValue} as canonical {@link Json} text.
12654
+ * Converts an exact {@link JsonValue} to canonical {@link Json} text.
12655
+ *
12656
+ * ### Example
12657
+ *
12658
+ * ```ts
12659
+ * import { jsonValueToJson, type JsonValue } from "@evolu/common";
12660
+ *
12661
+ * const value: JsonValue = { name: "Ada" };
12662
+ *
12663
+ * expect(jsonValueToJson(value)).toBe('{"name":"Ada"}');
12664
+ * ```
11692
12665
  *
11693
12666
  * @group JSON
11694
12667
  */
@@ -11696,11 +12669,22 @@ export const jsonValueToJson = (value: JsonValue): Json =>
11696
12669
  stringifyJsonValue(JsonValue.to(value));
11697
12670
 
11698
12671
  /**
11699
- * {@link Json} to {@link JsonValue} transformation.
12672
+ * Transformation {@link Type} that parses {@link Json} into {@link JsonValue}.
11700
12673
  *
11701
12674
  * Decoding unknown input first validates the Json Brand. Starting from the
11702
12675
  * typed Json parent is infallible. Encoding canonicalizes JSON text.
11703
12676
  *
12677
+ * ### Example
12678
+ *
12679
+ * ```ts
12680
+ * import { JsonValueFromJson } from "@evolu/common";
12681
+ *
12682
+ * const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
12683
+ *
12684
+ * expectOk(result, { name: "Ada" });
12685
+ * expect(JsonValueFromJson.to(result.value)).toBe('{"name":"Ada"}');
12686
+ * ```
12687
+ *
11704
12688
  * @group JSON
11705
12689
  */
11706
12690
  export const JsonValueFromJson = /*#__PURE__*/ transform(
@@ -11714,67 +12698,48 @@ export const JsonValueFromJson = /*#__PURE__*/ transform(
11714
12698
  );
11715
12699
 
11716
12700
  /**
11717
- * Branded {@link Json} Type and total conversions for another Type.
11718
- *
11719
- * Use this factory when a domain value must be stored as JSON text while its
11720
- * exact Type remains visible to TypeScript, such as a JSON column in an Evolu
11721
- * Schema. The returned tuple contains the branded Json Type, an encoder from
11722
- * the supplied Type's Output to Json, and a decoder from Json back to that
11723
- * Output.
11724
- *
11725
- * The supplied Type's `CanonicalInput` must be JSON-compatible. The encoder
11726
- * first uses the Type's canonical `to` operation, then encodes that
11727
- * representation as canonical Json. Runtime representation constraints
11728
- * TypeScript cannot prove, such as dense Arrays and enumerable data properties,
11729
- * are asserted as developer errors.
11730
- *
11731
- * The branded Json Type is the validation boundary for unknown JSON text. It
11732
- * grants its {@link Brand} only when the text is valid Json and decoding it
11733
- * through the supplied Type succeeds. The supplied Type is responsible for
11734
- * preserving semantic Outputs across canonical JSON encoding and decoding. This
11735
- * law cannot be checked generically because Types do not define semantic
11736
- * equality. Before granting the Brand, the encoder asserts the weaker runtime
11737
- * guarantee that the final Json successfully decodes through the supplied Type.
11738
- * Failed decodability therefore throws as a developer error.
11739
- *
11740
- * Consequently, the two typed conversions return their values directly without
11741
- * exposing a validation {@link Result}: an Output satisfying the JSON
11742
- * representation contract of a correctly declared Type can always be encoded,
11743
- * and the branded Json proves decoding will succeed. Decoding still runs the
11744
- * Type pipeline because transformations may need to construct different Output
11745
- * values.
12701
+ * Branded {@link Json} Type and conversions for another {@link Type}.
12702
+ *
12703
+ * Use this when a value must be stored as JSON text, such as in a JSON column
12704
+ * in an Evolu Schema. It returns a branded Json Type and functions for
12705
+ * converting the supplied Type's Output to and from that branded JSON
12706
+ * representation.
11746
12707
  *
11747
12708
  * ### Example
11748
12709
  *
11749
12710
  * ```ts
11750
12711
  * import {
11751
12712
  * Age,
12713
+ * NonEmptyTrimmedString100,
11752
12714
  * json,
11753
12715
  * object,
11754
- * String,
11755
12716
  * type Brand,
11756
- * type InferType,
11757
- * type Json,
11758
12717
  * } from "@evolu/common";
11759
12718
  *
11760
- * const Person = object({ name: String, age: Age });
11761
- * interface Person extends InferType<typeof Person> {}
12719
+ * const User = object({
12720
+ * name: NonEmptyTrimmedString100,
12721
+ * age: Age,
12722
+ * });
11762
12723
  *
11763
- * const [PersonJson, personToPersonJson, personJsonToPerson] = json(
11764
- * Person,
11765
- * "PersonJson",
12724
+ * const [UserJson, userToUserJson, userJsonToUser] = json(
12725
+ * User,
12726
+ * "UserJson",
11766
12727
  * );
11767
- * type PersonJson = typeof PersonJson.Output;
11768
- *
11769
- * expectTypeOf<PersonJson>().toEqualTypeOf<Json & Brand<"PersonJson">>();
11770
12728
  *
11771
- * const person = Person.orThrow({ name: "Ada", age: 42 });
11772
- * const personJson = personToPersonJson(person);
11773
- * const decodedPerson = personJsonToPerson(personJson);
12729
+ * const user = User.orThrow({ name: "Ada", age: 37 });
12730
+ * const userJson = userToUserJson(user);
11774
12731
  *
11775
- * expect(decodedPerson).toEqual(person);
12732
+ * expectTypeOf(userJson).toEqualTypeOf<
12733
+ * string & Brand<"Json"> & Brand<"UserJson">
12734
+ * >();
12735
+ * expect(userJson).toBe('{"name":"Ada","age":37}');
12736
+ * expect(userJsonToUser(userJson)).toEqual(user);
11776
12737
  * ```
11777
12738
  *
12739
+ * The supplied Type must have a JSON-compatible `CanonicalInput`. The branded
12740
+ * Json Type accepts only valid JSON text whose parsed value can be decoded by
12741
+ * the supplied Type.
12742
+ *
11778
12743
  * @group JSON
11779
12744
  */
11780
12745
  export const json = <T extends ConcreteTypeNode, Name extends TypeName>(