@evolu/common 8.1.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) 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/Number.d.ts +66 -6
  6. package/dist/src/Number.d.ts.map +1 -1
  7. package/dist/src/Number.js +11 -3
  8. package/dist/src/Object.d.ts +1 -0
  9. package/dist/src/Object.d.ts.map +1 -1
  10. package/dist/src/RefCount.d.ts +1 -0
  11. package/dist/src/RefCount.d.ts.map +1 -1
  12. package/dist/src/Relation.d.ts +2 -0
  13. package/dist/src/Relation.d.ts.map +1 -1
  14. package/dist/src/Resource.d.ts +1 -0
  15. package/dist/src/Resource.d.ts.map +1 -1
  16. package/dist/src/Result.d.ts +383 -548
  17. package/dist/src/Result.d.ts.map +1 -1
  18. package/dist/src/Result.js +296 -46
  19. package/dist/src/Schedule.d.ts +2 -2
  20. package/dist/src/Schedule.d.ts.map +1 -1
  21. package/dist/src/StackTrace.d.ts +1 -0
  22. package/dist/src/StackTrace.d.ts.map +1 -1
  23. package/dist/src/Task.d.ts +978 -1061
  24. package/dist/src/Task.d.ts.map +1 -1
  25. package/dist/src/Task.js +1010 -159
  26. package/dist/src/Time.d.ts +71 -34
  27. package/dist/src/Time.d.ts.map +1 -1
  28. package/dist/src/Time.js +77 -1
  29. package/dist/src/Type.d.ts +1740 -795
  30. package/dist/src/Type.d.ts.map +1 -1
  31. package/dist/src/Type.js +1634 -211
  32. package/dist/src/Types.d.ts +0 -24
  33. package/dist/src/Types.d.ts.map +1 -1
  34. package/dist/src/intl/_en.d.ts +126 -0
  35. package/dist/src/intl/_en.d.ts.map +1 -0
  36. package/dist/src/intl/_en.js +267 -0
  37. package/dist/src/intl/ar.d.ts +117 -0
  38. package/dist/src/intl/ar.d.ts.map +1 -0
  39. package/dist/src/intl/ar.js +245 -0
  40. package/dist/src/intl/bn.d.ts +117 -0
  41. package/dist/src/intl/bn.d.ts.map +1 -0
  42. package/dist/src/intl/bn.js +258 -0
  43. package/dist/src/intl/ca.d.ts +117 -0
  44. package/dist/src/intl/ca.d.ts.map +1 -0
  45. package/dist/src/intl/ca.js +258 -0
  46. package/dist/src/intl/cs.d.ts +30 -7
  47. package/dist/src/intl/cs.d.ts.map +1 -1
  48. package/dist/src/intl/cs.js +65 -28
  49. package/dist/src/intl/da.d.ts +117 -0
  50. package/dist/src/intl/da.d.ts.map +1 -0
  51. package/dist/src/intl/da.js +258 -0
  52. package/dist/src/intl/de.d.ts +117 -0
  53. package/dist/src/intl/de.d.ts.map +1 -0
  54. package/dist/src/intl/de.js +258 -0
  55. package/dist/src/intl/el.d.ts +117 -0
  56. package/dist/src/intl/el.d.ts.map +1 -0
  57. package/dist/src/intl/el.js +265 -0
  58. package/dist/src/intl/es.d.ts +117 -0
  59. package/dist/src/intl/es.d.ts.map +1 -0
  60. package/dist/src/intl/es.js +258 -0
  61. package/dist/src/intl/fa.d.ts +117 -0
  62. package/dist/src/intl/fa.d.ts.map +1 -0
  63. package/dist/src/intl/fa.js +258 -0
  64. package/dist/src/intl/fi.d.ts +117 -0
  65. package/dist/src/intl/fi.d.ts.map +1 -0
  66. package/dist/src/intl/fi.js +258 -0
  67. package/dist/src/intl/fil.d.ts +117 -0
  68. package/dist/src/intl/fil.d.ts.map +1 -0
  69. package/dist/src/intl/fil.js +258 -0
  70. package/dist/src/intl/fr.d.ts +117 -0
  71. package/dist/src/intl/fr.d.ts.map +1 -0
  72. package/dist/src/intl/fr.js +258 -0
  73. package/dist/src/intl/he.d.ts +117 -0
  74. package/dist/src/intl/he.d.ts.map +1 -0
  75. package/dist/src/intl/he.js +258 -0
  76. package/dist/src/intl/hi.d.ts +117 -0
  77. package/dist/src/intl/hi.d.ts.map +1 -0
  78. package/dist/src/intl/hi.js +258 -0
  79. package/dist/src/intl/hr.d.ts +117 -0
  80. package/dist/src/intl/hr.d.ts.map +1 -0
  81. package/dist/src/intl/hr.js +265 -0
  82. package/dist/src/intl/hu.d.ts +63 -0
  83. package/dist/src/intl/hu.d.ts.map +1 -0
  84. package/dist/src/intl/hu.js +204 -0
  85. package/dist/src/intl/id.d.ts +117 -0
  86. package/dist/src/intl/id.d.ts.map +1 -0
  87. package/dist/src/intl/id.js +258 -0
  88. package/dist/src/intl/index.d.ts +42 -0
  89. package/dist/src/intl/index.d.ts.map +1 -1
  90. package/dist/src/intl/index.js +42 -0
  91. package/dist/src/intl/it.d.ts +117 -0
  92. package/dist/src/intl/it.d.ts.map +1 -0
  93. package/dist/src/intl/it.js +258 -0
  94. package/dist/src/intl/ja.d.ts +117 -0
  95. package/dist/src/intl/ja.d.ts.map +1 -0
  96. package/dist/src/intl/ja.js +258 -0
  97. package/dist/src/intl/ko.d.ts +117 -0
  98. package/dist/src/intl/ko.d.ts.map +1 -0
  99. package/dist/src/intl/ko.js +258 -0
  100. package/dist/src/intl/ml.d.ts +117 -0
  101. package/dist/src/intl/ml.d.ts.map +1 -0
  102. package/dist/src/intl/ml.js +258 -0
  103. package/dist/src/intl/mr.d.ts +117 -0
  104. package/dist/src/intl/mr.d.ts.map +1 -0
  105. package/dist/src/intl/mr.js +258 -0
  106. package/dist/src/intl/ms.d.ts +117 -0
  107. package/dist/src/intl/ms.d.ts.map +1 -0
  108. package/dist/src/intl/ms.js +245 -0
  109. package/dist/src/intl/nb.d.ts +63 -0
  110. package/dist/src/intl/nb.d.ts.map +1 -0
  111. package/dist/src/intl/nb.js +191 -0
  112. package/dist/src/intl/nl.d.ts +117 -0
  113. package/dist/src/intl/nl.d.ts.map +1 -0
  114. package/dist/src/intl/nl.js +258 -0
  115. package/dist/src/intl/pa.d.ts +117 -0
  116. package/dist/src/intl/pa.d.ts.map +1 -0
  117. package/dist/src/intl/pa.js +258 -0
  118. package/dist/src/intl/pl.d.ts +117 -0
  119. package/dist/src/intl/pl.d.ts.map +1 -0
  120. package/dist/src/intl/pl.js +242 -0
  121. package/dist/src/intl/pt-BR.d.ts +117 -0
  122. package/dist/src/intl/pt-BR.d.ts.map +1 -0
  123. package/dist/src/intl/pt-BR.js +258 -0
  124. package/dist/src/intl/pt.d.ts +117 -0
  125. package/dist/src/intl/pt.d.ts.map +1 -0
  126. package/dist/src/intl/pt.js +246 -0
  127. package/dist/src/intl/ro.d.ts +117 -0
  128. package/dist/src/intl/ro.d.ts.map +1 -0
  129. package/dist/src/intl/ro.js +258 -0
  130. package/dist/src/intl/sk.d.ts +117 -0
  131. package/dist/src/intl/sk.d.ts.map +1 -0
  132. package/dist/src/intl/sk.js +244 -0
  133. package/dist/src/intl/sl.d.ts +117 -0
  134. package/dist/src/intl/sl.d.ts.map +1 -0
  135. package/dist/src/intl/sl.js +258 -0
  136. package/dist/src/intl/sv.d.ts +117 -0
  137. package/dist/src/intl/sv.d.ts.map +1 -0
  138. package/dist/src/intl/sv.js +258 -0
  139. package/dist/src/intl/sw.d.ts +63 -0
  140. package/dist/src/intl/sw.d.ts.map +1 -0
  141. package/dist/src/intl/sw.js +191 -0
  142. package/dist/src/intl/ta.d.ts +117 -0
  143. package/dist/src/intl/ta.d.ts.map +1 -0
  144. package/dist/src/intl/ta.js +258 -0
  145. package/dist/src/intl/te.d.ts +117 -0
  146. package/dist/src/intl/te.d.ts.map +1 -0
  147. package/dist/src/intl/te.js +258 -0
  148. package/dist/src/intl/th.d.ts +117 -0
  149. package/dist/src/intl/th.d.ts.map +1 -0
  150. package/dist/src/intl/th.js +258 -0
  151. package/dist/src/intl/tr.d.ts +117 -0
  152. package/dist/src/intl/tr.d.ts.map +1 -0
  153. package/dist/src/intl/tr.js +255 -0
  154. package/dist/src/intl/uk.d.ts +117 -0
  155. package/dist/src/intl/uk.d.ts.map +1 -0
  156. package/dist/src/intl/uk.js +258 -0
  157. package/dist/src/intl/ur.d.ts +117 -0
  158. package/dist/src/intl/ur.d.ts.map +1 -0
  159. package/dist/src/intl/ur.js +245 -0
  160. package/dist/src/intl/vi.d.ts +117 -0
  161. package/dist/src/intl/vi.d.ts.map +1 -0
  162. package/dist/src/intl/vi.js +255 -0
  163. package/dist/src/intl/zh-CN.d.ts +117 -0
  164. package/dist/src/intl/zh-CN.d.ts.map +1 -0
  165. package/dist/src/intl/zh-CN.js +258 -0
  166. package/dist/src/intl/zh-TW.d.ts +117 -0
  167. package/dist/src/intl/zh-TW.d.ts.map +1 -0
  168. package/dist/src/intl/zh-TW.js +258 -0
  169. package/dist/src/local-first/Owner.d.ts +156 -62
  170. package/dist/src/local-first/Owner.d.ts.map +1 -1
  171. package/dist/src/local-first/Owner.js +119 -20
  172. package/package.json +1 -1
  173. package/src/Function.ts +2 -0
  174. package/src/Http.ts +9 -0
  175. package/src/Number.ts +85 -14
  176. package/src/Object.ts +2 -0
  177. package/src/RefCount.ts +2 -0
  178. package/src/Relation.ts +4 -0
  179. package/src/Resource.ts +2 -0
  180. package/src/Result.ts +397 -560
  181. package/src/Schedule.ts +1 -1
  182. package/src/StackTrace.ts +2 -0
  183. package/src/Task.ts +1145 -1225
  184. package/src/Time.ts +110 -62
  185. package/src/Type.ts +2842 -835
  186. package/src/Types.ts +0 -49
  187. package/src/intl/_en.ts +497 -0
  188. package/src/intl/ar.ts +417 -0
  189. package/src/intl/bn.ts +486 -0
  190. package/src/intl/ca.ts +488 -0
  191. package/src/intl/cs.ts +131 -32
  192. package/src/intl/da.ts +488 -0
  193. package/src/intl/de.ts +488 -0
  194. package/src/intl/el.ts +494 -0
  195. package/src/intl/es.ts +488 -0
  196. package/src/intl/fa.ts +487 -0
  197. package/src/intl/fi.ts +487 -0
  198. package/src/intl/fil.ts +488 -0
  199. package/src/intl/fr.ts +488 -0
  200. package/src/intl/he.ts +486 -0
  201. package/src/intl/hi.ts +487 -0
  202. package/src/intl/hr.ts +494 -0
  203. package/src/intl/hu.ts +387 -0
  204. package/src/intl/id.ts +487 -0
  205. package/src/intl/index.ts +42 -0
  206. package/src/intl/it.ts +488 -0
  207. package/src/intl/ja.ts +488 -0
  208. package/src/intl/ko.ts +487 -0
  209. package/src/intl/ml.ts +487 -0
  210. package/src/intl/mr.ts +487 -0
  211. package/src/intl/ms.ts +410 -0
  212. package/src/intl/nb.ts +364 -0
  213. package/src/intl/nl.ts +488 -0
  214. package/src/intl/pa.ts +487 -0
  215. package/src/intl/pl.ts +450 -0
  216. package/src/intl/pt-BR.ts +487 -0
  217. package/src/intl/pt.ts +424 -0
  218. package/src/intl/ro.ts +488 -0
  219. package/src/intl/sk.ts +412 -0
  220. package/src/intl/sl.ts +487 -0
  221. package/src/intl/sv.ts +488 -0
  222. package/src/intl/sw.ts +324 -0
  223. package/src/intl/ta.ts +487 -0
  224. package/src/intl/te.ts +487 -0
  225. package/src/intl/th.ts +484 -0
  226. package/src/intl/tr.ts +487 -0
  227. package/src/intl/uk.ts +488 -0
  228. package/src/intl/ur.ts +410 -0
  229. package/src/intl/vi.ts +435 -0
  230. package/src/intl/zh-CN.ts +482 -0
  231. package/src/intl/zh-TW.ts +482 -0
  232. package/src/local-first/Owner.ts +156 -62
@@ -1,26 +1,27 @@
1
1
  /**
2
- * Type-safe error handling with the Result type.
2
+ * ## Intro
3
3
  *
4
- * @module
5
- */
6
- import { type NonEmptyReadonlyArray } from "./Array.ts";
7
- import { type Thunk } from "./Function.ts";
8
- import type { Typed } from "./Type.ts";
9
- import type { Awaitable } from "./Types.ts";
10
- /**
11
- * The problem with `throw` in JavaScript is that the caught value is always of
4
+ * Type-safe error handling with {@link Result}.
5
+ *
6
+ * The problem with exceptions in JavaScript is that a caught value is always of
12
7
  * unknown type. We can't be sure all errors have been handled because the
13
- * TypeScript compiler can't tell us what was thrown — we can't use
8
+ * TypeScript compiler can't tell us what might be thrown — we can't use
14
9
  * {@link exhaustiveCheck}.
15
10
  *
16
- * Languages like Rust and Haskell solve this with types like `Result` or
17
- * `Either` where errors are part of the return type. TypeScript can do the
18
- * same.
11
+ * Languages like Rust and Haskell model recoverable failures with types such as
12
+ * `Result` and `Either`, making errors part of the return type.
19
13
  *
20
- * ## Example
14
+ * TypeScript can express the same pattern with discriminated unions of plain
15
+ * objects. In Evolu, domain error interfaces extend {@link Typed} to avoid
16
+ * repeating the literal `type` discriminant.
21
17
  *
22
18
  * ```ts
23
- * import { err, exhaustiveCheck, type Result } from "@evolu/common";
19
+ * import {
20
+ * err,
21
+ * exhaustiveCheck,
22
+ * type Result,
23
+ * type Typed,
24
+ * } from "@evolu/common";
24
25
  *
25
26
  * // TypeScript can't know what was thrown.
26
27
  * try {
@@ -32,16 +33,12 @@ import type { Awaitable } from "./Types.ts";
32
33
  * // With Result, errors are part of the return type.
33
34
  * const doSomething = (): Result<
34
35
  * number,
35
- * NotFoundError | InvalidInputError
36
+ * InvalidInputError | NotFoundError
36
37
  * > => err({ type: "NotFound" });
37
38
  *
38
- * interface NotFoundError {
39
- * readonly type: "NotFound";
40
- * }
39
+ * interface InvalidInputError extends Typed<"InvalidInput"> {}
41
40
  *
42
- * interface InvalidInputError {
43
- * readonly type: "InvalidInput";
44
- * }
41
+ * interface NotFoundError extends Typed<"NotFound"> {}
45
42
  *
46
43
  * // With Result, the error type is known and exhaustiveCheck works.
47
44
  * // If we add another error type, TypeScript tells us it isn't handled.
@@ -78,41 +75,37 @@ import type { Awaitable } from "./Types.ts";
78
75
  * }
79
76
  * ```
80
77
  *
81
- * Use {@link trySync} and {@link tryAsync} to intentionally convert thrown or
82
- * rejected errors into typed, recoverable `Result` values.
78
+ * Use {@link trySync} and {@link tryAsync} to intentionally convert thrown values
79
+ * and Promise rejections into typed, recoverable errors represented by
80
+ * `Result`.
83
81
  *
84
- * Do not wrap every throwing API in `Result`. If an error is unrecoverable and
85
- * the caller has no meaningful fallback, let it throw.
86
- *
87
- * ## Composition
82
+ * Do not wrap every API that throws or rejects in `Result`. If an error is
83
+ * unrecoverable and the caller has no meaningful fallback, let it propagate.
88
84
  *
89
85
  * Since `Result` is a plain object, imperative code works naturally.
90
86
  *
91
87
  * ```ts
92
- * import { ok, type Result } from "@evolu/common";
88
+ * import { ok, type Result, type Typed } from "@evolu/common";
93
89
  *
94
90
  * interface User {
95
91
  * readonly id: string;
96
92
  * }
97
93
  *
98
- * interface UserNotFoundError {
99
- * readonly type: "UserNotFoundError";
100
- * }
94
+ * const getUser = (): Result<User, UserNotFoundError> =>
95
+ * ok({ id: "user-1" });
96
+ *
97
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {}
101
98
  *
102
99
  * interface Profile {
103
100
  * readonly userId: string;
104
101
  * }
105
102
  *
106
- * interface ProfileNotFoundError {
107
- * readonly type: "ProfileNotFoundError";
108
- * }
109
- *
110
- * const getUser = (): Result<User, UserNotFoundError> =>
111
- * ok({ id: "user-1" });
112
103
  * const getProfile = (
113
104
  * userId: string,
114
105
  * ): Result<Profile, ProfileNotFoundError> => ok({ userId });
115
106
  *
107
+ * interface ProfileNotFoundError extends Typed<"ProfileNotFound"> {}
108
+ *
116
109
  * const getCurrentProfile = (): Result<
117
110
  * Profile,
118
111
  * UserNotFoundError | ProfileNotFoundError
@@ -130,211 +123,26 @@ import type { Awaitable } from "./Types.ts";
130
123
  * expectOk(profile, { userId: "user-1" });
131
124
  * ```
132
125
  *
133
- * {@link flatMapResult} is for a single local composition.
134
- *
135
- * ```ts
136
- * import { flatMapResult, ok, type Result } from "@evolu/common";
137
- *
138
- * interface User {
139
- * readonly id: string;
140
- * }
141
- *
142
- * interface UserNotFoundError {
143
- * readonly type: "UserNotFoundError";
144
- * }
145
- *
146
- * interface Profile {
147
- * readonly userId: string;
148
- * }
149
- *
150
- * interface ProfileNotFoundError {
151
- * readonly type: "ProfileNotFoundError";
152
- * }
153
- *
154
- * const getUser = (): Result<User, UserNotFoundError> =>
155
- * ok({ id: "user-1" });
156
- * const getProfile = (
157
- * userId: string,
158
- * ): Result<Profile, ProfileNotFoundError> => ok({ userId });
159
- *
160
- * const profile = flatMapResult(getUser(), (user) => getProfile(user.id));
161
- * expectTypeOf(profile).toEqualTypeOf<
162
- * Result<Profile, UserNotFoundError | ProfileNotFoundError>
163
- * >();
164
- * expectOk(profile, { userId: "user-1" });
165
- * ```
166
- *
167
- * Use {@link allResult} to collect the Ok values from a collection of Results,
168
- * or return the first Err.
169
- *
170
- * ```ts
171
- * import { allResult, err, ok, type Result } from "@evolu/common";
172
- *
173
- * interface User {
174
- * readonly id: string;
175
- * }
176
- *
177
- * interface Preferences {
178
- * readonly theme: "dark" | "light";
179
- * }
180
- *
181
- * interface LoadAccountError {
182
- * readonly type: "LoadAccountError";
183
- * readonly resource: "user" | "preferences";
184
- * }
185
- *
186
- * const user: Result<User, LoadAccountError> = ok({ id: "user-1" });
187
- * const preferences: Result<Preferences, LoadAccountError> = ok({
188
- * theme: "dark",
189
- * });
190
- * const account = allResult([user, preferences]);
191
- * expectTypeOf(account).toEqualTypeOf<
192
- * Result<readonly [User, Preferences], LoadAccountError>
193
- * >();
194
- * expectOk(account, [{ id: "user-1" }, { theme: "dark" }]);
195
- *
196
- * const userError: Result<never, LoadAccountError> = err({
197
- * type: "LoadAccountError",
198
- * resource: "user",
199
- * });
200
- * const preferencesError: Result<never, LoadAccountError> = err({
201
- * type: "LoadAccountError",
202
- * resource: "preferences",
203
- * });
204
- * const failedAccount = allResult([userError, preferencesError]);
205
- * expectErr(failedAccount, {
206
- * type: "LoadAccountError",
207
- * resource: "user",
208
- * });
209
- * ```
210
- *
211
- * `allResult` can also map collection values to Results before collecting their
212
- * Ok values. Mapping stops on the first Err.
213
- *
214
- * ```ts
215
- * import { allResult, err, ok, type Result } from "@evolu/common";
216
- *
217
- * interface OrderLine {
218
- * readonly productId: string;
219
- * readonly quantity: number;
220
- * }
221
- *
222
- * interface ProductUnavailableError {
223
- * readonly type: "ProductUnavailableError";
224
- * readonly productId: string;
225
- * }
226
- *
227
- * const priceByProductId = new Map([["book", 20]]);
228
- * const priceOrderLine = ({
229
- * productId,
230
- * quantity,
231
- * }: OrderLine): Result<number, ProductUnavailableError> => {
232
- * const price = priceByProductId.get(productId);
233
- * if (price == null) {
234
- * return err({ type: "ProductUnavailableError", productId });
235
- * }
236
- * return ok(price * quantity);
237
- * };
238
- *
239
- * const orderLines: ReadonlyArray<OrderLine> = [
240
- * { productId: "book", quantity: 2 },
241
- * { productId: "sold-out", quantity: 1 },
242
- * ];
243
- * const prices = allResult(orderLines, priceOrderLine);
244
- * expectTypeOf(prices).toEqualTypeOf<
245
- * Result<ReadonlyArray<number>, ProductUnavailableError>
246
- * >();
247
- * expectErr(prices, {
248
- * type: "ProductUnavailableError",
249
- * productId: "sold-out",
250
- * });
251
- * ```
252
- *
253
- * For side effects, or whenever the success values aren't needed, disable
254
- * collection with `{ collect: false }`. `allResult` then returns `Result<void,
255
- * E>` without storing the successful values and still stops on the first Err.
126
+ * Note `user` and `profile` are named after their success values, not after the
127
+ * Result (`userResult`, `profileResult`).
256
128
  *
257
- * ```ts
258
- * import { allResult, err, ok, type Result } from "@evolu/common";
259
- *
260
- * interface User {
261
- * readonly id: string;
262
- * }
263
- *
264
- * interface SaveUserError {
265
- * readonly type: "SaveUserError";
266
- * }
267
- *
268
- * interface SendWelcomeEmailError {
269
- * readonly type: "SendWelcomeEmailError";
270
- * }
271
- *
272
- * const saveUser = (_user: User): Result<number, SaveUserError> =>
273
- * err({ type: "SaveUserError" });
274
- *
275
- * let welcomeEmailSent = false;
276
- * const sendWelcomeEmail = (
277
- * _user: User,
278
- * ): Result<void, SendWelcomeEmailError> => {
279
- * welcomeEmailSent = true;
280
- * return ok();
281
- * };
282
- *
283
- * const registerUser = (
284
- * user: User,
285
- * ): Result<void, SaveUserError | SendWelcomeEmailError> =>
286
- * allResult(
287
- * [saveUser, sendWelcomeEmail],
288
- * (operation) => operation(user),
289
- * { collect: false },
290
- * );
291
- *
292
- * const result = registerUser({ id: "user-1" });
293
- * expectTypeOf(result).toEqualTypeOf<
294
- * Result<void, SaveUserError | SendWelcomeEmailError>
295
- * >();
296
- * expectErr(result, { type: "SaveUserError" });
297
- * expect(welcomeEmailSent).toBe(false);
298
- * ```
299
- *
300
- * For the first success, {@link anyResult} returns the first Ok or the last
301
- * error if all fail:
302
- *
303
- * ```ts
304
- * import { anyResult, err, ok, type Result } from "@evolu/common";
305
- *
306
- * interface CacheMissError {
307
- * readonly type: "CacheMissError";
308
- * }
309
- *
310
- * const getCachedPrice = (): Result<number, CacheMissError> =>
311
- * err({ type: "CacheMissError" });
312
- *
313
- * const prices = [getCachedPrice(), ok(20)] as const;
314
- * const price = anyResult(prices);
315
- * expectTypeOf(price).toEqualTypeOf<Result<number, CacheMissError>>();
316
- * expectOk(price, 20);
317
- * ```
318
- *
319
- * ## Naming convention
320
- *
321
- * Name a Result after its value (`user`, `config`), not after the wrapper
322
- * (`userResult`, `configResult`). If it has no success value, name it `result`.
323
- * For several such operations, use `allResult` with `{ collect: false }` as
324
- * shown above.
129
+ * If a Result has no success value, name it `result`. For several such
130
+ * operations, use {@link allResult} with `{ collect: false }`.
325
131
  *
326
132
  * ## Unrecoverable errors
327
133
  *
328
- * Some errors can't be handled locally they must propagate to the top level.
329
- * These are unrecoverable errors: expected (you know they can happen) but only
330
- * handleable at the app level.
134
+ * Unrecoverable errors are failures the application can't handle meaningfully.
135
+ * For example, a database failure is unrecoverable when the application has no
136
+ * useful fallback, retry, or degraded mode.
331
137
  *
332
- * Do not force these errors into `Result` just because the underlying API
333
- * throws. If the local caller cannot recover, let the error propagate to a
334
- * global handler or other app boundary.
138
+ * Do not turn such failures into `Result` merely because the underlying API
139
+ * throws or rejects. Let the exception or Promise rejection propagate to the
140
+ * top-level handler so the application stops instead of continuing in a
141
+ * potentially invalid state.
335
142
  *
336
- * In Evolu apps, the root Run reports defects and the platform lifecycle API
337
- * owns application shutdown. For example, `@evolu/nodejs` provides `runMain`.
143
+ * In Evolu apps, the root {@link Run} reports defects and the platform lifecycle
144
+ * API owns application shutdown. For example, `@evolu/nodejs` provides
145
+ * {@link @evolu/nodejs!runMain | runMain}.
338
146
  *
339
147
  * ## FAQ
340
148
  *
@@ -377,15 +185,46 @@ import type { Awaitable } from "./Types.ts";
377
185
  * Use `Result<void, E>` and return `ok()` (no argument). Don't return
378
186
  * `ok(true)`, `ok("success")`, or `ok("done")` — `ok()` already signals
379
187
  * success; redundant values add noise.
188
+ *
189
+ * @module
190
+ */
191
+ import { type NonEmptyReadonlyArray } from "./Array.ts";
192
+ import { type Thunk } from "./Function.ts";
193
+ import type { Typed } from "./Type.ts";
194
+ import type { Awaitable } from "./Types.ts";
195
+ /**
196
+ * A discriminated success or failure value: either {@link Ok} or {@link Err}.
197
+ *
198
+ * See the {@link @evolu/common!Result | Result overview}.
199
+ *
200
+ * @group Core
380
201
  */
381
202
  export type Result<T, E = never> = Ok<T> | Err<E>;
382
203
  /**
383
204
  * Shorthand for a {@link Result} with `any` type parameters.
384
205
  *
385
- * @group Utilities
206
+ * @group Core
386
207
  */
387
208
  export type AnyResult = Result<any, any>;
388
- /** A successful {@link Result}. */
209
+ /**
210
+ * A successful {@link Result}.
211
+ *
212
+ * ### Example
213
+ *
214
+ * ```ts
215
+ * import { ok, type Result } from "@evolu/common";
216
+ *
217
+ * const result = ok();
218
+ * const count = ok(42);
219
+ *
220
+ * expectTypeOf(result).toEqualTypeOf<Result<void>>();
221
+ * expectTypeOf(count).toEqualTypeOf<Result<number>>();
222
+ * expectOk(result, undefined);
223
+ * expectOk(count, 42);
224
+ * ```
225
+ *
226
+ * @group Core
227
+ */
389
228
  export interface Ok<out T> {
390
229
  readonly ok: true;
391
230
  readonly value: T;
@@ -394,7 +233,7 @@ export interface Ok<out T> {
394
233
  * An error {@link Result}.
395
234
  *
396
235
  * The `error` property can be any type that describes the error. For domain
397
- * errors, use a plain object with a `type` field for discrimination.
236
+ * errors, define a plain interface extending {@link Typed}.
398
237
  *
399
238
  * ### Example
400
239
  *
@@ -405,23 +244,25 @@ export interface Ok<out T> {
405
244
  * readonly id: string;
406
245
  * }
407
246
  *
408
- * interface NotFoundError extends Typed<"NotFoundError"> {
409
- * readonly id: string;
410
- * }
411
- *
412
247
  * const users = new Map<string, User>([["user-1", { id: "user-1" }]]);
413
248
  * const findUser = (id: string): Result<User, NotFoundError> => {
414
249
  * const user = users.get(id);
415
- * if (user == null) return err({ type: "NotFoundError", id });
250
+ * if (user == null) return err({ type: "NotFound", id });
416
251
  * return ok(user);
417
252
  * };
418
253
  *
254
+ * interface NotFoundError extends Typed<"NotFound"> {
255
+ * readonly id: string;
256
+ * }
257
+ *
419
258
  * expectOk(findUser("user-1"), { id: "user-1" });
420
259
  * expectErr(findUser("missing"), {
421
- * type: "NotFoundError",
260
+ * type: "NotFound",
422
261
  * id: "missing",
423
262
  * });
424
263
  * ```
264
+ *
265
+ * @group Core
425
266
  */
426
267
  export interface Err<out E> {
427
268
  readonly ok: false;
@@ -430,13 +271,13 @@ export interface Err<out E> {
430
271
  /**
431
272
  * Infers the success value type from a {@link Result}.
432
273
  *
433
- * @group Utilities
274
+ * @group Core
434
275
  */
435
276
  export type InferOk<R extends Result<any, any>> = R extends Ok<infer T> ? T : never;
436
277
  /**
437
278
  * Infers the error type from a {@link Result}.
438
279
  *
439
- * @group Utilities
280
+ * @group Core
440
281
  */
441
282
  export type InferErr<R extends Result<any, any>> = R extends Err<infer E> ? E : never;
442
283
  /**
@@ -446,31 +287,76 @@ export type InferErr<R extends Result<any, any>> = R extends Err<infer E> ? E :
446
287
  * producing a value.
447
288
  * - `ok(value)` creates a `Result<T, never>` containing the specified value.
448
289
  *
290
+ * @group Core
291
+ */
292
+ export declare function ok(): Result<void>;
293
+ /** Creates an {@link Ok} result with a specified value. */
294
+ export declare function ok<T>(value: T): Result<T>;
295
+ /**
296
+ * Creates an {@link Err} result.
297
+ *
298
+ * @group Core
299
+ */
300
+ export declare const err: <E>(error: E) => Result<never, E>;
301
+ /**
302
+ * Type guard for {@link Ok} results.
303
+ *
449
304
  * ### Example
450
305
  *
451
306
  * ```ts
452
- * import { ok, type Result } from "@evolu/common";
307
+ * import {
308
+ * isOk,
309
+ * ok,
310
+ * type Ok,
311
+ * type Result,
312
+ * type Typed,
313
+ * } from "@evolu/common";
314
+ *
315
+ * const getCount = (): Result<number, CountUnavailableError> => ok(2);
453
316
  *
454
- * const noValue = ok();
455
- * const success = ok(42);
317
+ * interface CountUnavailableError extends Typed<"CountUnavailable"> {}
456
318
  *
457
- * expectTypeOf(noValue).toEqualTypeOf<Result<void>>();
458
- * expectTypeOf(success).toEqualTypeOf<Result<number>>();
459
- * expectOk(noValue, undefined);
460
- * expectOk(success, 42);
319
+ * const count = getCount();
320
+ * if (isOk(count)) {
321
+ * expectTypeOf(count).toEqualTypeOf<Ok<number>>();
322
+ * expect(count.value).toBe(2);
323
+ * }
461
324
  * ```
325
+ *
326
+ * @group Guards
462
327
  */
463
- export declare function ok(): Result<void>;
464
- /** Creates an {@link Ok} result with a specified value. */
465
- export declare function ok<T>(value: T): Result<T>;
466
- /** Creates an {@link Err} result. */
467
- export declare const err: <E>(error: E) => Result<never, E>;
468
- /** Type guard for {@link Ok} results. */
469
328
  export declare const isOk: <T, E>(result: Result<T, E>) => result is Ok<T>;
470
- /** Type guard for {@link Err} results. */
329
+ /**
330
+ * Type guard for {@link Err} results.
331
+ *
332
+ * ### Example
333
+ *
334
+ * ```ts
335
+ * import {
336
+ * err,
337
+ * isErr,
338
+ * type Err,
339
+ * type Result,
340
+ * type Typed,
341
+ * } from "@evolu/common";
342
+ *
343
+ * const getCount = (): Result<number, CountUnavailableError> =>
344
+ * err({ type: "CountUnavailable" });
345
+ *
346
+ * interface CountUnavailableError extends Typed<"CountUnavailable"> {}
347
+ *
348
+ * const count = getCount();
349
+ * if (isErr(count)) {
350
+ * expectTypeOf(count).toEqualTypeOf<Err<CountUnavailableError>>();
351
+ * expect(count.error).toEqual({ type: "CountUnavailable" });
352
+ * }
353
+ * ```
354
+ *
355
+ * @group Guards
356
+ */
471
357
  export declare const isErr: <T, E>(result: Result<T, E>) => result is Err<E>;
472
358
  /**
473
- * Gets the value from an `Ok` {@link Result}, or throws if it is an `Err`.
359
+ * Gets the value from an {@link Ok}, or throws for an {@link Err}.
474
360
  *
475
361
  * Use this where failure should crash the current flow instead of being handled
476
362
  * locally.
@@ -478,8 +364,8 @@ export declare const isErr: <T, E>(result: Result<T, E>) => result is Err<E>;
478
364
  * **When to use:**
479
365
  *
480
366
  * - Application startup or composition-root setup where errors must stop the
481
- * program immediately. In Evolu apps, the root Run reports the defect and the
482
- * platform lifecycle API handles shutdown.
367
+ * program immediately. In Evolu apps, the root {@link Run} reports the defect
368
+ * and the platform lifecycle API handles shutdown.
483
369
  * - Module-level constants
484
370
  * - Test setup with values that are expected to be valid
485
371
  *
@@ -489,38 +375,45 @@ export declare const isErr: <T, E>(result: Result<T, E>) => result is Err<E>;
489
375
  * ### Example
490
376
  *
491
377
  * ```ts
492
- * import { err, getOrThrow, ok, type Result } from "@evolu/common";
378
+ * import {
379
+ * err,
380
+ * getOrThrow,
381
+ * ok,
382
+ * type Result,
383
+ * type Typed,
384
+ * } from "@evolu/common";
493
385
  *
494
386
  * interface Config {
495
387
  * readonly port: number;
496
388
  * }
497
- * interface InvalidConfigError {
498
- * readonly type: "InvalidConfigError";
499
- * }
389
+ *
500
390
  * const loadConfig = (): Result<Config, InvalidConfigError> =>
501
391
  * ok({ port: 3000 });
502
392
  *
393
+ * interface InvalidConfigError extends Typed<"InvalidConfig"> {}
394
+ *
503
395
  * // At app startup, crash if the config is invalid.
504
396
  * const config = getOrThrow(loadConfig());
505
397
  * expectTypeOf(config).toEqualTypeOf<Config>();
506
398
  * expect(config).toEqual({ port: 3000 });
507
399
  *
508
400
  * try {
509
- * getOrThrow(err({ type: "InvalidConfigError" }));
401
+ * getOrThrow(err({ type: "InvalidConfig" }));
510
402
  * assert.fail("Expected getOrThrow to throw");
511
403
  * } catch (error) {
512
404
  * assert(error instanceof Error);
513
405
  * expect(error.message).toBe("getOrThrow");
514
- * expect(error.cause).toEqual({ type: "InvalidConfigError" });
406
+ * expect(error.cause).toEqual({ type: "InvalidConfig" });
515
407
  * }
516
408
  * ```
517
409
  *
518
410
  * Throws: `Error` with the original error attached as `cause`.
411
+ *
412
+ * @group Unwrapping
519
413
  */
520
414
  export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
521
415
  /**
522
- * Gets the value from an `Ok` {@link Result}, or returns `null` if it is an
523
- * `Err`.
416
+ * Gets the value from an {@link Ok}, or returns `null` for an {@link Err}.
524
417
  *
525
418
  * **Intended usage:**
526
419
  *
@@ -531,16 +424,22 @@ export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
531
424
  * ### Example
532
425
  *
533
426
  * ```ts
534
- * import { err, getOrNull, ok, type Result } from "@evolu/common";
427
+ * import {
428
+ * err,
429
+ * getOrNull,
430
+ * ok,
431
+ * type Result,
432
+ * type Typed,
433
+ * } from "@evolu/common";
535
434
  *
536
435
  * interface User {
537
436
  * readonly id: string;
538
437
  * }
539
- * interface UserNotFoundError {
540
- * readonly type: "UserNotFoundError";
541
- * }
438
+ *
542
439
  * const findUser = (id: string): Result<User, UserNotFoundError> =>
543
- * id === "user-1" ? ok({ id }) : err({ type: "UserNotFoundError" });
440
+ * id === "user-1" ? ok({ id }) : err({ type: "UserNotFound" });
441
+ *
442
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {}
544
443
  *
545
444
  * // For APIs that expect T | null.
546
445
  * const user = getOrNull(findUser("user-1"));
@@ -550,6 +449,8 @@ export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
550
449
  * expect(user).toEqual({ id: "user-1" });
551
450
  * expect(missingUser).toBeNull();
552
451
  * ```
452
+ *
453
+ * @group Unwrapping
553
454
  */
554
455
  export declare const getOrNull: <T, E>(result: Result<T, E>) => T | null;
555
456
  /**
@@ -569,108 +470,114 @@ export declare const getOrNull: <T, E>(result: Result<T, E>) => T | null;
569
470
  * expectTypeOf(count).toEqualTypeOf<number>();
570
471
  * expect(count).toBe(2);
571
472
  * ```
473
+ *
474
+ * @group Unwrapping
572
475
  */
573
476
  export declare const getOk: <T>(result: Result<T>) => T;
574
477
  /**
575
478
  * Wraps a synchronous function that may throw, returning a {@link Result}.
576
479
  *
577
- * `mapError` converts the caught `unknown` value into a typed domain error.
480
+ * When provided, `mapError` converts the caught `unknown` value into a typed
481
+ * domain error.
578
482
  *
579
- * Some APIs use exceptions for both recoverable and unrecoverable failures. In
580
- * that case, convert only the failures the caller can recover from and rethrow
581
- * the rest. `trySync` propagates rethrown failures instead of converting them
582
- * to Err.
483
+ * Some APIs throw for both recoverable and unrecoverable errors. In that case,
484
+ * convert only the errors the caller can recover from and rethrow the rest. The
485
+ * `trySync` error mapper propagates rethrown values instead of converting them
486
+ * to {@link Err}.
583
487
  *
584
488
  * ### Example
585
489
  *
586
490
  * ```ts
587
- * import { trySync, type Result } from "@evolu/common";
588
- *
589
- * interface ReserveSeatError {
590
- * readonly type: "ReserveSeatError";
591
- * readonly seat: string;
592
- * }
491
+ * import { trySync, type Result, type Typed } from "@evolu/common";
593
492
  *
594
493
  * class LegacySeatUnavailableError extends Error {}
595
494
  *
596
495
  * const legacyReserveSeat = (seat: string): void => {
597
496
  * if (seat === "A1") throw new LegacySeatUnavailableError();
598
- * if (seat === "B1") throw new Error("Unexpected database error");
497
+ * if (seat === "B1") throw new Error("Database error");
599
498
  * };
600
499
  *
601
- * const reserveSeat = (seat: string): Result<void, ReserveSeatError> =>
500
+ * const reserveSeat = (seat: string): Result<void, SeatUnavailableError> =>
602
501
  * trySync(
603
502
  * () => legacyReserveSeat(seat),
604
503
  * (error) => {
605
504
  * if (error instanceof LegacySeatUnavailableError) {
606
- * return { type: "ReserveSeatError", seat };
505
+ * return { type: "SeatUnavailable", seat };
607
506
  * }
608
507
  * throw error;
609
508
  * },
610
509
  * );
611
510
  *
511
+ * interface SeatUnavailableError extends Typed<"SeatUnavailable"> {
512
+ * readonly seat: string;
513
+ * }
514
+ *
612
515
  * const result = reserveSeat("B2");
613
- * expectTypeOf(result).toEqualTypeOf<Result<void, ReserveSeatError>>();
516
+ * expectTypeOf(result).toEqualTypeOf<Result<void, SeatUnavailableError>>();
614
517
  * expectOk(result, undefined);
615
- * expectErr(reserveSeat("A1"), { type: "ReserveSeatError", seat: "A1" });
616
- * expect(() => reserveSeat("B1")).toThrow("Unexpected database error");
518
+ * expectErr(reserveSeat("A1"), { type: "SeatUnavailable", seat: "A1" });
519
+ * expect(() => reserveSeat("B1")).toThrow("Database error");
617
520
  * ```
521
+ *
522
+ * @group Exception interop
618
523
  */
619
524
  export declare function trySync<T>(fn: () => T): Result<T, unknown>;
525
+ /** Maps caught exceptions to a typed error. */
620
526
  export declare function trySync<T, E>(fn: () => T, mapError: (error: unknown) => E): Result<T, E>;
621
527
  /**
622
528
  * Wraps an async function that may throw or reject, returning a {@link Result}.
623
529
  *
624
- * `mapError` converts the caught `unknown` value into a typed domain error.
530
+ * When provided, `mapError` converts the caught `unknown` value into a typed
531
+ * domain error.
625
532
  *
626
- * Some APIs use exceptions for both recoverable and unrecoverable failures. In
627
- * that case, convert only the failures the caller can recover from and rethrow
628
- * the rest. `tryAsync` rejects with rethrown failures instead of converting
629
- * them to Err.
533
+ * Some async APIs throw or reject for both recoverable and unrecoverable
534
+ * errors. In that case, convert only the errors the caller can recover from and
535
+ * rethrow the rest. The `tryAsync` error mapper leaves rethrown values as
536
+ * Promise rejections instead of converting them to {@link Err}.
630
537
  *
631
538
  * ### Example
632
539
  *
633
540
  * ```ts
634
- * import { tryAsync, type Result } from "@evolu/common";
635
- *
636
- * interface ReserveSeatError {
637
- * readonly type: "ReserveSeatError";
638
- * readonly seat: string;
639
- * }
541
+ * import { tryAsync, type Result, type Typed } from "@evolu/common";
640
542
  *
641
543
  * class LegacySeatUnavailableError extends Error {}
642
544
  *
643
545
  * const legacyReserveSeat = async (seat: string): Promise<void> => {
644
546
  * if (seat === "A1") throw new LegacySeatUnavailableError();
645
- * if (seat === "B1") throw new Error("Unexpected database error");
547
+ * if (seat === "B1") throw new Error("Database error");
646
548
  * };
647
549
  *
648
550
  * const reserveSeat = (
649
551
  * seat: string,
650
- * ): Promise<Result<void, ReserveSeatError>> =>
552
+ * ): Promise<Result<void, SeatUnavailableError>> =>
651
553
  * tryAsync(
652
554
  * () => legacyReserveSeat(seat),
653
555
  * (error) => {
654
556
  * if (error instanceof LegacySeatUnavailableError) {
655
- * return { type: "ReserveSeatError", seat };
557
+ * return { type: "SeatUnavailable", seat };
656
558
  * }
657
559
  * throw error;
658
560
  * },
659
561
  * );
660
562
  *
563
+ * interface SeatUnavailableError extends Typed<"SeatUnavailable"> {
564
+ * readonly seat: string;
565
+ * }
566
+ *
661
567
  * const result = await reserveSeat("B2");
662
- * expectTypeOf(result).toEqualTypeOf<Result<void, ReserveSeatError>>();
568
+ * expectTypeOf(result).toEqualTypeOf<Result<void, SeatUnavailableError>>();
663
569
  * expectOk(result, undefined);
664
570
  * expectErr(await reserveSeat("A1"), {
665
- * type: "ReserveSeatError",
571
+ * type: "SeatUnavailable",
666
572
  * seat: "A1",
667
573
  * });
668
- * await expect(reserveSeat("B1")).rejects.toThrow(
669
- * "Unexpected database error",
670
- * );
574
+ * await expect(reserveSeat("B1")).rejects.toThrow("Database error");
671
575
  * ```
576
+ *
577
+ * @group Exception interop
672
578
  */
673
579
  export declare function tryAsync<T>(promiseThunk: Thunk<Awaitable<T>>): Promise<Result<T, unknown>>;
580
+ /** Maps thrown or rejected values to a typed error. */
674
581
  export declare function tryAsync<T, E>(promiseThunk: Thunk<Awaitable<T>>, mapError: (error: unknown) => E): Promise<Result<T, E>>;
675
582
  /**
676
583
  * A result for a pull-based protocol with three outcomes.
@@ -678,35 +585,43 @@ export declare function tryAsync<T, E>(promiseThunk: Thunk<Awaitable<T>>, mapErr
678
585
  * The consumer requests the next value (e.g. via `next()`), and the producer
679
586
  * responds with one of:
680
587
  *
681
- * - `Ok<A>` — produced a value
682
- * - `Err<Done<D>>` — completed normally with a done value
683
- * - `Err<E>` — failed with an error
588
+ * - An {@link Ok} containing `A` — produced a value
589
+ * - An {@link Err} containing {@link Done} — completed normally with a done value
590
+ * - An {@link Err} containing `E` — failed with an error
684
591
  *
685
592
  * Inspired by JavaScript's `Iterator.next()`, which returns `{ value, done }`.
686
593
  *
687
594
  * ### Example
688
595
  *
689
596
  * ```ts
690
- * import { done, err, ok, type NextResult } from "@evolu/common";
691
- *
692
- * interface ReadError {
693
- * readonly type: "ReadError";
694
- * }
597
+ * import {
598
+ * done,
599
+ * err,
600
+ * ok,
601
+ * type NextResult,
602
+ * type Typed,
603
+ * } from "@evolu/common";
695
604
  *
696
- * const next = (index: number): NextResult<string, ReadError, number> => {
605
+ * const next = (
606
+ * index: number,
607
+ * ): NextResult<string, ReadFailedError, number> => {
697
608
  * if (index === 0) return ok("first");
698
- * if (index === 1) return err({ type: "ReadError" });
609
+ * if (index === 1) return err({ type: "ReadFailed" });
699
610
  * return err(done(index));
700
611
  * };
701
612
  *
613
+ * interface ReadFailedError extends Typed<"ReadFailed"> {}
614
+ *
702
615
  * const value = next(0);
703
616
  * expectTypeOf(value).toEqualTypeOf<
704
- * NextResult<string, ReadError, number>
617
+ * NextResult<string, ReadFailedError, number>
705
618
  * >();
706
619
  * expectOk(value, "first");
707
- * expectErr(next(1), { type: "ReadError" });
620
+ * expectErr(next(1), { type: "ReadFailed" });
708
621
  * expectErr(next(2), { type: "Done", done: 2 });
709
622
  * ```
623
+ *
624
+ * @group Pull
710
625
  */
711
626
  export type NextResult<A, E = never, D = void> = Result<A, E | Done<D>>;
712
627
  /**
@@ -717,15 +632,6 @@ export type NextResult<A, E = never, D = void> = Result<A, E | Done<D>>;
717
632
  *
718
633
  * Inspired by JavaScript's `IteratorResult` where `{ done: true }` signals
719
634
  * completion.
720
- */
721
- export interface Done<out D = unknown> extends Typed<"Done"> {
722
- readonly done: D;
723
- }
724
- /**
725
- * Constructs a {@link Done} value.
726
- *
727
- * - `done()` creates a `Done<void>` for protocols that don't need a done value.
728
- * - `done(value)` creates a `Done<D>` containing the specified value.
729
635
  *
730
636
  * ### Example
731
637
  *
@@ -739,6 +645,19 @@ export interface Done<out D = unknown> extends Typed<"Done"> {
739
645
  * expect(withoutValue).toEqual({ type: "Done", done: undefined });
740
646
  * expect(withValue).toEqual({ type: "Done", done: 42 });
741
647
  * ```
648
+ *
649
+ * @group Pull
650
+ */
651
+ export interface Done<out D = unknown> extends Typed<"Done"> {
652
+ readonly done: D;
653
+ }
654
+ /**
655
+ * Constructs a {@link Done} value.
656
+ *
657
+ * - `done()` creates a `Done<void>` for protocols that don't need a done value.
658
+ * - `done(value)` creates a `Done<D>` containing the specified value.
659
+ *
660
+ * @group Pull
742
661
  */
743
662
  export declare function done(): Done<void>;
744
663
  /** With a done value. */
@@ -752,13 +671,16 @@ export declare function done<D>(value: D): Done<D>;
752
671
  * ### Example
753
672
  *
754
673
  * ```ts
755
- * import { type Done, type ExcludeDone } from "@evolu/common";
674
+ * import { type Done, type ExcludeDone, type Typed } from "@evolu/common";
675
+ *
676
+ * type Errors = ReadFailedError | Done<number>;
756
677
  *
757
- * type Errors = "ReadError" | Done<number>;
758
- * expectTypeOf<ExcludeDone<Errors>>().toEqualTypeOf<"ReadError">();
678
+ * interface ReadFailedError extends Typed<"ReadFailed"> {}
679
+ *
680
+ * expectTypeOf<ExcludeDone<Errors>>().toEqualTypeOf<ReadFailedError>();
759
681
  * ```
760
682
  *
761
- * @group Utilities
683
+ * @group Pull
762
684
  */
763
685
  export type ExcludeDone<E> = Exclude<E, Done<any>>;
764
686
  /**
@@ -770,13 +692,16 @@ export type ExcludeDone<E> = Exclude<E, Done<any>>;
770
692
  * ### Example
771
693
  *
772
694
  * ```ts
773
- * import { type Done, type OnlyDone } from "@evolu/common";
695
+ * import { type Done, type OnlyDone, type Typed } from "@evolu/common";
696
+ *
697
+ * type Errors = ReadFailedError | Done<number>;
698
+ *
699
+ * interface ReadFailedError extends Typed<"ReadFailed"> {}
774
700
  *
775
- * type Errors = "ReadError" | Done<number>;
776
701
  * expectTypeOf<OnlyDone<Errors>>().toEqualTypeOf<Done<number>>();
777
702
  * ```
778
703
  *
779
- * @group Utilities
704
+ * @group Pull
780
705
  */
781
706
  export type OnlyDone<E> = Extract<E, Done<any>>;
782
707
  /**
@@ -785,13 +710,20 @@ export type OnlyDone<E> = Extract<E, Done<any>>;
785
710
  * ### Example
786
711
  *
787
712
  * ```ts
788
- * import { type InferDone, type NextResult } from "@evolu/common";
713
+ * import {
714
+ * type InferDone,
715
+ * type NextResult,
716
+ * type Typed,
717
+ * } from "@evolu/common";
718
+ *
719
+ * type ReadResult = NextResult<string, ReadFailedError, number>;
720
+ *
721
+ * interface ReadFailedError extends Typed<"ReadFailed"> {}
789
722
  *
790
- * type ReadResult = NextResult<string, "ReadError", number>;
791
723
  * expectTypeOf<InferDone<ReadResult>>().toEqualTypeOf<number>();
792
724
  * ```
793
725
  *
794
- * @group Utilities
726
+ * @group Pull
795
727
  */
796
728
  export type InferDone<R extends Result<any, any>> = InferErr<R> extends infer Errors ? Errors extends Done<infer D> ? D : never : never;
797
729
  /**
@@ -806,85 +738,71 @@ export type InferDone<R extends Result<any, any>> = InferErr<R> extends infer Er
806
738
  * ### Example
807
739
  *
808
740
  * ```ts
809
- * import { err, flatMapResult, ok, type Result } from "@evolu/common";
741
+ * import {
742
+ * flatMapResult,
743
+ * ok,
744
+ * type Result,
745
+ * type Typed,
746
+ * } from "@evolu/common";
810
747
  *
811
748
  * interface User {
812
749
  * readonly id: string;
813
750
  * }
814
- * interface UserError {
815
- * readonly type: "UserError";
816
- * }
751
+ *
752
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {}
817
753
  *
818
754
  * interface Profile {
819
755
  * readonly userId: string;
820
756
  * }
821
- * interface ProfileError {
822
- * readonly type: "ProfileError";
823
- * }
824
- * const getProfile = (userId: string): Result<Profile, ProfileError> =>
825
- * ok({ userId });
826
- *
827
- * let getProfileCalls = 0;
828
- * const getProfileForUser = (
829
- * user: User,
830
- * ): Result<Profile, ProfileError> => {
831
- * getProfileCalls++;
832
- * return getProfile(user.id);
833
- * };
834
757
  *
835
- * const user: Result<User, UserError> = ok({ id: "user-1" });
836
- * const profile = flatMapResult(user, getProfileForUser);
758
+ * const getProfile = (
759
+ * userId: string,
760
+ * ): Result<Profile, ProfileNotFoundError> => ok({ userId });
761
+ *
762
+ * interface ProfileNotFoundError extends Typed<"ProfileNotFound"> {}
763
+ *
764
+ * const user: Result<User, UserNotFoundError> = ok({ id: "user-1" });
765
+ * const profile = flatMapResult(user, ({ id }) => getProfile(id));
837
766
  * expectTypeOf(profile).toEqualTypeOf<
838
- * Result<Profile, UserError | ProfileError>
767
+ * Result<Profile, UserNotFoundError | ProfileNotFoundError>
839
768
  * >();
840
769
  * expectOk(profile, { userId: "user-1" });
841
- * expect(getProfileCalls).toBe(1);
842
- *
843
- * const missingUser: Result<User, UserError> = err({ type: "UserError" });
844
- * const missingProfile = flatMapResult(missingUser, getProfileForUser);
845
- * expectErr(missingProfile, { type: "UserError" });
846
- * expect(getProfileCalls).toBe(1);
847
770
  * ```
848
771
  *
849
772
  * @group Composition
850
773
  */
851
774
  export declare const flatMapResult: <T, E, U, F>(result: Result<T, E>, fn: (value: T) => Result<U, F>) => Result<U, E | F>;
852
775
  /**
853
- * Collects the Ok values from a collection of {@link Result}s, or maps
854
- * collection values to Results and collects their Ok values.
776
+ * Collects successful values from {@link Result}s.
855
777
  *
856
- * Returns the first error if any result fails.
778
+ * Stops processing the input at the first {@link Err}. Mapping overloads do not
779
+ * call the mapper for the remaining input values.
857
780
  *
858
- * Pass `{ collect: false }` to return `Result<void, E>` without storing the Ok
859
- * values in an output collection.
781
+ * Pass `{ collect: false }` to return `Result<void, E>` without storing the
782
+ * {@link Ok} values in an output collection.
860
783
  *
861
784
  * ### Example
862
785
  *
863
786
  * ```ts
864
- * import { allResult, err, ok, type Result } from "@evolu/common";
787
+ * import { allResult, ok, type Result, type Typed } from "@evolu/common";
865
788
  *
866
- * interface CountError {
867
- * readonly type: "CountError";
868
- * }
869
- * interface LabelError {
870
- * readonly type: "LabelError";
871
- * }
789
+ * const getCount = (): Result<number, CountUnavailableError> => ok(2);
790
+ *
791
+ * interface CountUnavailableError extends Typed<"CountUnavailable"> {}
792
+ *
793
+ * const getLabel = (): Result<string, LabelUnavailableError> =>
794
+ * ok("books");
872
795
  *
873
- * const getCount = (): Result<number, CountError> => ok(2);
874
- * const getLabel = (): Result<string, LabelError> => ok("books");
796
+ * interface LabelUnavailableError extends Typed<"LabelUnavailable"> {}
875
797
  *
876
798
  * const values = allResult([getCount(), getLabel()]);
877
799
  * expectTypeOf(values).toEqualTypeOf<
878
- * Result<readonly [number, string], CountError | LabelError>
800
+ * Result<
801
+ * readonly [number, string],
802
+ * CountUnavailableError | LabelUnavailableError
803
+ * >
879
804
  * >();
880
805
  * expectOk(values, [2, "books"]);
881
- *
882
- * const numbers = allResult([
883
- * ok(1),
884
- * err({ type: "CountError" }),
885
- * err({ type: "LabelError" }),
886
- * ]);
887
- * expectErr(numbers, { type: "CountError" });
888
806
  * ```
889
807
  *
890
808
  * @group Composition
@@ -916,26 +834,15 @@ export declare function allResult<T extends Readonly<Record<string, AnyResult>>>
916
834
  /**
917
835
  * For dynamic or generated result lists.
918
836
  *
919
- * Stops consuming the iterable on the first Err.
920
- *
921
837
  * ### Example
922
838
  *
923
839
  * ```ts
924
- * import { allResult, err, ok, type Result } from "@evolu/common";
925
- *
926
- * interface LoadNumberError {
927
- * readonly type: "LoadNumberError";
928
- * }
840
+ * import { allResult, ok, type Result } from "@evolu/common";
929
841
  *
930
- * const results: ReadonlyArray<Result<number, LoadNumberError>> = [
931
- * ok(1),
932
- * err({ type: "LoadNumberError" }),
933
- * ];
842
+ * const results: ReadonlyArray<Result<number>> = [ok(1), ok(2)];
934
843
  * const numbers = allResult(results);
935
- * expectTypeOf(numbers).toEqualTypeOf<
936
- * Result<ReadonlyArray<number>, LoadNumberError>
937
- * >();
938
- * expectErr(numbers, { type: "LoadNumberError" });
844
+ * expectTypeOf(numbers).toEqualTypeOf<Result<ReadonlyArray<number>>>();
845
+ * expectOk(numbers, [1, 2]);
939
846
  * ```
940
847
  */
941
848
  export declare function allResult<T, E>(results: Iterable<Result<T, E>>): Result<ReadonlyArray<T>, E>;
@@ -961,7 +868,19 @@ export declare function allResult<T, E>(results: Iterable<Result<T, E>>): Result
961
868
  * ```
962
869
  */
963
870
  export declare function allResult<T, E>(results: NonEmptyReadonlyArray<Result<T, E>>): Result<NonEmptyReadonlyArray<T>, E>;
964
- /** Does not collect the Ok values from a record of Results. */
871
+ /**
872
+ * Does not collect the Ok values from a record of Results.
873
+ *
874
+ * ### Example
875
+ *
876
+ * ```ts
877
+ * import { allResult, ok, type Result } from "@evolu/common";
878
+ *
879
+ * const result = allResult({ a: ok(1), b: ok(2) }, { collect: false });
880
+ * expectTypeOf(result).toEqualTypeOf<Result<void>>();
881
+ * expectOk(result, undefined);
882
+ * ```
883
+ */
965
884
  export declare function allResult<T extends Readonly<Record<string, AnyResult>>>(results: T, options: {
966
885
  readonly collect: false;
967
886
  }): Result<void, [keyof T] extends [never] ? never : InferErr<T[keyof T]>>;
@@ -969,25 +888,17 @@ export declare function allResult<T extends Readonly<Record<string, AnyResult>>>
969
888
  * Does not collect the Ok values from an iterable of Results.
970
889
  *
971
890
  * Use this overload when the Results already exist and only their collective
972
- * success or failure matters. To stop invoking operations after the first Err,
973
- * use the mapping overload instead.
891
+ * success or failure matters.
974
892
  *
975
893
  * ### Example
976
894
  *
977
895
  * ```ts
978
- * import { allResult, err, ok, type Result } from "@evolu/common";
979
- *
980
- * interface SaveError {
981
- * readonly type: "SaveError";
982
- * }
896
+ * import { allResult, ok, type Result } from "@evolu/common";
983
897
  *
984
- * const results: ReadonlyArray<Result<number, SaveError>> = [
985
- * ok(1),
986
- * err({ type: "SaveError" }),
987
- * ];
898
+ * const results: ReadonlyArray<Result<number>> = [ok(1), ok(2)];
988
899
  * const result = allResult(results, { collect: false });
989
- * expectTypeOf(result).toEqualTypeOf<Result<void, SaveError>>();
990
- * expectErr(result, { type: "SaveError" });
900
+ * expectTypeOf(result).toEqualTypeOf<Result<void>>();
901
+ * expectOk(result, undefined);
991
902
  * ```
992
903
  */
993
904
  export declare function allResult<R extends AnyResult>(results: Iterable<R>, options: {
@@ -1006,35 +917,16 @@ export declare function allResult<R extends AnyResult>(results: Iterable<R>, opt
1006
917
  * type Result,
1007
918
  * } from "@evolu/common";
1008
919
  *
1009
- * interface Order {
1010
- * readonly id: string;
1011
- * }
1012
- * interface Invoice {
1013
- * readonly orderId: string;
1014
- * }
1015
- * interface CreateInvoiceError {
1016
- * readonly type: "CreateInvoiceError";
1017
- * }
1018
- *
1019
- * const orders: NonEmptyReadonlyArray<Order> = [{ id: "order-1" }];
1020
- * const createInvoice = (
1021
- * order: Order,
1022
- * ): Result<Invoice, CreateInvoiceError> => ok({ orderId: order.id });
1023
- *
1024
- * const invoices = allResult(orders, createInvoice);
1025
- * expectTypeOf(invoices).toEqualTypeOf<
1026
- * Result<NonEmptyReadonlyArray<Invoice>, CreateInvoiceError>
920
+ * const numbers: NonEmptyReadonlyArray<number> = [1, 2];
921
+ * const doubled = allResult(numbers, (number) => ok(number * 2));
922
+ * expectTypeOf(doubled).toEqualTypeOf<
923
+ * Result<NonEmptyReadonlyArray<number>>
1027
924
  * >();
1028
- * expectOk(invoices, [{ orderId: "order-1" }]);
925
+ * expectOk(doubled, [2, 4]);
1029
926
  *
1030
- * const invoiceTuple = allResult(
1031
- * [{ id: "order-1" }, { id: "order-2" }] as const,
1032
- * createInvoice,
1033
- * );
1034
- * expectTypeOf(invoiceTuple).toEqualTypeOf<
1035
- * Result<readonly [Invoice, Invoice], CreateInvoiceError>
1036
- * >();
1037
- * expectOk(invoiceTuple, [{ orderId: "order-1" }, { orderId: "order-2" }]);
927
+ * const tuple = allResult([1, 2] as const, (number) => ok(number * 2));
928
+ * expectTypeOf(tuple).toEqualTypeOf<Result<readonly [number, number]>>();
929
+ * expectOk(tuple, [2, 4]);
1038
930
  * ```
1039
931
  */
1040
932
  export declare function allResult<const A extends readonly [unknown, ...Array<unknown>], R extends AnyResult>(values: A, fn: (value: A[number]) => R): Result<{
@@ -1043,37 +935,15 @@ export declare function allResult<const A extends readonly [unknown, ...Array<un
1043
935
  /**
1044
936
  * Maps a dynamic or generated value collection to Results.
1045
937
  *
1046
- * Stops consuming and mapping the iterable on the first Err.
1047
- *
1048
938
  * ### Example
1049
939
  *
1050
940
  * ```ts
1051
- * import { allResult, err, ok, type Result } from "@evolu/common";
1052
- *
1053
- * interface ProductNotFoundError {
1054
- * readonly type: "ProductNotFoundError";
1055
- * readonly productId: string;
1056
- * }
1057
- *
1058
- * const pricesByProductId = new Map([
1059
- * ["book", 20],
1060
- * ["pen", 5],
1061
- * ]);
1062
- * const getPrice = (
1063
- * productId: string,
1064
- * ): Result<number, ProductNotFoundError> => {
1065
- * const price = pricesByProductId.get(productId);
1066
- * return price == null
1067
- * ? err({ type: "ProductNotFoundError", productId })
1068
- * : ok(price);
1069
- * };
941
+ * import { allResult, ok, type Result } from "@evolu/common";
1070
942
  *
1071
- * const productIds: Iterable<string> = new Set(["book", "pen"]);
1072
- * const prices = allResult(productIds, getPrice);
1073
- * expectTypeOf(prices).toEqualTypeOf<
1074
- * Result<ReadonlyArray<number>, ProductNotFoundError>
1075
- * >();
1076
- * expectOk(prices, [20, 5]);
943
+ * const numbers: Iterable<number> = new Set([1, 2]);
944
+ * const doubled = allResult(numbers, (number) => ok(number * 2));
945
+ * expectTypeOf(doubled).toEqualTypeOf<Result<ReadonlyArray<number>>>();
946
+ * expectOk(doubled, [2, 4]);
1077
947
  * ```
1078
948
  */
1079
949
  export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, fn: (value: A) => R): Result<ReadonlyArray<InferOk<R>>, InferErr<R>>;
@@ -1083,32 +953,18 @@ export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, f
1083
953
  * ### Example
1084
954
  *
1085
955
  * ```ts
1086
- * import { allResult, err, ok, type Result } from "@evolu/common";
956
+ * import { allResult, ok, type Result } from "@evolu/common";
1087
957
  *
1088
958
  * interface User {
1089
959
  * readonly id: string;
1090
960
  * }
1091
961
  *
1092
- * interface UserNotFoundError {
1093
- * readonly type: "UserNotFoundError";
1094
- * readonly userId: string;
1095
- * }
1096
- *
1097
- * const usersById = new Map<string, User>([
1098
- * ["user-1", { id: "user-1" }],
1099
- * ["user-2", { id: "user-2" }],
1100
- * ]);
1101
- * const findUser = (userId: string): Result<User, UserNotFoundError> => {
1102
- * const user = usersById.get(userId);
1103
- * return user == null
1104
- * ? err({ type: "UserNotFoundError", userId })
1105
- * : ok(user);
1106
- * };
962
+ * const toUser = (id: string): Result<User> => ok({ id });
1107
963
  *
1108
964
  * const userIdsByRole = { owner: "user-1", reviewer: "user-2" } as const;
1109
- * const usersByRole = allResult(userIdsByRole, findUser);
965
+ * const usersByRole = allResult(userIdsByRole, toUser);
1110
966
  * expectTypeOf(usersByRole).toEqualTypeOf<
1111
- * Result<Readonly<Record<"owner" | "reviewer", User>>, UserNotFoundError>
967
+ * Result<Readonly<Record<"owner" | "reviewer", User>>>
1112
968
  * >();
1113
969
  * expectOk(usersByRole, {
1114
970
  * owner: { id: "user-1" },
@@ -1128,28 +984,40 @@ export declare function allResult<A, R extends AnyResult, K extends string>(valu
1128
984
  * ```ts
1129
985
  * import { allResult, ok, type Result } from "@evolu/common";
1130
986
  *
1131
- * interface UpdateItemError {
1132
- * readonly type: "UpdateItemError";
1133
- * }
987
+ * const visited: Array<number> = [];
988
+ * const result = allResult(
989
+ * [1, 2],
990
+ * (number) => {
991
+ * visited.push(number);
992
+ * return ok(number * 2);
993
+ * },
994
+ * {
995
+ * collect: false,
996
+ * },
997
+ * );
998
+ * expectTypeOf(result).toEqualTypeOf<Result<void>>();
999
+ * expectOk(result, undefined);
1000
+ * expect(visited).toEqual([1, 2]);
1001
+ * ```
1002
+ */
1003
+ export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, fn: (value: A) => R, options: {
1004
+ readonly collect: false;
1005
+ }): Result<void, InferErr<R>>;
1006
+ /**
1007
+ * Maps record values to Results without collecting their Ok values.
1134
1008
  *
1135
- * const updatedItemIds: Array<string> = [];
1136
- * const updateItem = (itemId: string): Result<number, UpdateItemError> => {
1137
- * updatedItemIds.push(itemId);
1138
- * return ok(1);
1139
- * };
1009
+ * ### Example
1140
1010
  *
1141
- * const result = allResult(["item-1", "item-2"], updateItem, {
1011
+ * ```ts
1012
+ * import { allResult, ok, type Result } from "@evolu/common";
1013
+ *
1014
+ * const result = allResult({ a: 1, b: 2 }, (number) => ok(number * 2), {
1142
1015
  * collect: false,
1143
1016
  * });
1144
- * expectTypeOf(result).toEqualTypeOf<Result<void, UpdateItemError>>();
1017
+ * expectTypeOf(result).toEqualTypeOf<Result<void>>();
1145
1018
  * expectOk(result, undefined);
1146
- * expect(updatedItemIds).toEqual(["item-1", "item-2"]);
1147
1019
  * ```
1148
1020
  */
1149
- export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, fn: (value: A) => R, options: {
1150
- readonly collect: false;
1151
- }): Result<void, InferErr<R>>;
1152
- /** Maps record values to Results without collecting their Ok values. */
1153
1021
  export declare function allResult<A, R extends AnyResult, K extends string>(values: Readonly<Record<K, A>>, fn: (value: A) => R, options: {
1154
1022
  readonly collect: false;
1155
1023
  }): Result<void, InferErr<R>>;
@@ -1167,52 +1035,19 @@ export declare function allResult<A, R extends AnyResult, K extends string>(valu
1167
1035
  * import {
1168
1036
  * anyResult,
1169
1037
  * err,
1170
- * isNonEmptyArray,
1171
1038
  * ok,
1172
1039
  * type Result,
1040
+ * type Typed,
1173
1041
  * } from "@evolu/common";
1174
1042
  *
1175
- * interface LookupError {
1176
- * readonly type: "LookupError";
1177
- * }
1178
- *
1179
- * const results: ReadonlyArray<Result<number, LookupError>> = [
1180
- * err({ type: "LookupError" }),
1181
- * ok(42),
1182
- * ];
1183
- * if (isNonEmptyArray(results)) {
1184
- * const number = anyResult(results);
1185
- * expectTypeOf(number).toEqualTypeOf<Result<number, LookupError>>();
1186
- * expectOk(number, 42);
1187
- * }
1188
- * ```
1189
- *
1190
- * A statically non-empty tuple needs no guard:
1191
- *
1192
- * ```ts
1193
- * import { anyResult, err, ok, type Result } from "@evolu/common";
1194
- *
1195
- * interface LookupError {
1196
- * readonly type: "LookupError";
1197
- * readonly source: string;
1198
- * }
1043
+ * const getCachedPrice = (): Result<number, CacheMissError> =>
1044
+ * err({ type: "CacheMiss" });
1199
1045
  *
1200
- * const lookupError = (source: string): Result<never, LookupError> =>
1201
- * err({ type: "LookupError", source });
1046
+ * interface CacheMissError extends Typed<"CacheMiss"> {}
1202
1047
  *
1203
- * const results = [
1204
- * lookupError("first"),
1205
- * ok(42),
1206
- * lookupError("second"),
1207
- * ] as const;
1208
- * const number = anyResult(results);
1209
- * expectTypeOf(number).toEqualTypeOf<Result<number, LookupError>>();
1048
+ * const number = anyResult([getCachedPrice(), ok(42)]);
1049
+ * expectTypeOf(number).toEqualTypeOf<Result<number, CacheMissError>>();
1210
1050
  * expectOk(number, 42);
1211
- *
1212
- * const allFailed = [lookupError("a"), lookupError("b")] as const;
1213
- * const result = anyResult(allFailed);
1214
- * expectTypeOf(result).toEqualTypeOf<Result<never, LookupError>>();
1215
- * expectErr(result, { type: "LookupError", source: "b" });
1216
1051
  * ```
1217
1052
  *
1218
1053
  * @group Composition