@evolu/common 7.4.0 → 8.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +622 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +36 -26
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +415 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +465 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +1064 -630
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -823
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +209 -282
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -529
  246. package/src/Instances.ts +0 -90
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -960
package/src/Result.ts CHANGED
@@ -1,25 +1,77 @@
1
- import type { Task } from "./Task.js";
1
+ /**
2
+ * Type-safe error handling with Result types.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import {
8
+ arrayFrom,
9
+ emptyArray,
10
+ isNonEmptyArray,
11
+ type NonEmptyReadonlyArray,
12
+ } from "./Array.js";
13
+ import { assert } from "./Assert.js";
14
+ import type { Lazy } from "./Function.js";
15
+ import { exhaustiveCheck } from "./Function.js";
16
+ import { createRecord, emptyRecord, isIterable } from "./Object.js";
17
+ import type { Typed } from "./Type.js";
2
18
 
3
19
  /**
4
- * The problem with throwing an exception in JavaScript is that the caught error
5
- * is always of an unknown type. The unknown type is a problem because we can't
6
- * be sure all errors have been handled because the TypeScript compiler can't
7
- * tell us.
20
+ * The problem with `throw` in JavaScript is that the caught value is always of
21
+ * unknown type. We can't be sure all errors have been handled because the
22
+ * TypeScript compiler can't tell us what was thrown we can't use
23
+ * {@link exhaustiveCheck}.
8
24
  *
9
- * Languages like Rust or Haskell use a type-safe approach to error handling,
10
- * where errors are explicitly represented as part of the return type, such as
11
- * Result or Either, allowing the developer to handle errors safely. TypeScript
12
- * can have this too via the `Result` type.
25
+ * Languages like Rust and Haskell solve this with types like `Result` or
26
+ * `Either` where errors are part of the return type. TypeScript can do the
27
+ * same.
13
28
  *
14
- * The `Result` type can be either {@link Ok} (success) or {@link Err} (error).
15
- * Use {@link ok} to create a successful result and {@link err} to create an error
16
- * result.
29
+ * ```ts
30
+ * // With throw - caught value is unknown, can't use exhaustiveCheck
31
+ * try {
32
+ * doSomething();
33
+ * } catch (error) {
34
+ * // error is unknown - TypeScript can't help us here
35
+ * }
17
36
  *
18
- * Now let's look at how `Result` can be used for safe JSON parsing:
37
+ * // With Result - error type is known, exhaustiveCheck works
38
+ * const result = doSomething();
39
+ * if (!result.ok) {
40
+ * switch (result.error.type) {
41
+ * case "NotFound":
42
+ * // handle not found
43
+ * break;
44
+ * case "InvalidInput":
45
+ * // handle invalid input
46
+ * break;
47
+ * default:
48
+ * exhaustiveCheck(result.error); // TypeScript ensures all cases handled
49
+ * }
50
+ * }
51
+ * ```
52
+ *
53
+ * A `Result` is either {@link Ok} (success with a value) or {@link Err} (failure
54
+ * with an error). Create them with {@link ok} and {@link err}.
19
55
  *
20
56
  * ```ts
21
- * interface ParseJsonError {
22
- * readonly type: "ParseJsonError";
57
+ * type Result<T, E = never> = Ok<T> | Err<E>;
58
+ *
59
+ * interface Ok<T> {
60
+ * readonly ok: true;
61
+ * readonly value: T;
62
+ * }
63
+ *
64
+ * interface Err<E> {
65
+ * readonly ok: false;
66
+ * readonly error: E;
67
+ * }
68
+ * ```
69
+ *
70
+ * ## Example
71
+ *
72
+ * ```ts
73
+ * // Typed<T> adds { type: T } for discriminated unions
74
+ * interface ParseJsonError extends Typed<"ParseJsonError"> {
23
75
  * readonly message: string;
24
76
  * }
25
77
  *
@@ -31,21 +83,21 @@ import type { Task } from "./Task.js";
31
83
  * }
32
84
  * };
33
85
  *
34
- * // Result<unknown, ParseJsonError>
35
- * const json = parseJson('{"key": "value"}');
86
+ * const json = parseJson('{"name": "Alice"}');
87
+ * if (!json.ok) return json; // short-circuit on error
36
88
  *
37
- * // Fail fast to handle errors early.
38
- * if (!json.ok) return json; // Err<ParseJsonError>
39
- *
40
- * // Now, we have access to the json.value.
41
- * expectTypeOf(json.value).toBeUnknown();
89
+ * // Now we have access to json.value (type: unknown)
90
+ * console.log(json.value);
42
91
  * ```
43
92
  *
44
- * Note how we didn't have to use the try/catch, just `if (!json.ok)`, and how
45
- * the error isn't unknown but has a type.
93
+ * The caller doesn't need `try/catch`, just `if (!json.ok)`, and the error is
94
+ * `ParseJsonError`, not `unknown`.
95
+ *
96
+ * Use this pattern when the caller can recover or choose a different flow.
97
+ * Parsing unknown JSON is a good fit because invalid input is expected and
98
+ * actionable.
46
99
  *
47
- * But we had to use `try/catch` in the `parseJson` function. For such a case,
48
- * wrapping unsafe code, Evolu provides the {@link trySync} helper:
100
+ * To avoid `try/catch` inside `parseJson` too, use {@link trySync}:
49
101
  *
50
102
  * ```ts
51
103
  * const parseJson = (value: string): Result<unknown, ParseJsonError> =>
@@ -55,297 +107,137 @@ import type { Task } from "./Task.js";
55
107
  * );
56
108
  * ```
57
109
  *
58
- * {@link trySync} helper makes unsafe (can throw) synchronous code safe; for
59
- * unsafe asynchronous code, use {@link tryAsync}.
110
+ * `trySync` and {@link tryAsync} are for intentionally converting thrown errors
111
+ * into typed, recoverable {@link Result} values.
60
112
  *
61
- * Let's summarize it:
113
+ * Do not wrap every throwing API in {@link Result}. If an error is unrecoverable
114
+ * and the caller has no meaningful fallback, let it throw and handle it at the
115
+ * app boundary.
62
116
  *
63
- * - For safe code, use `ok` and `err`.
64
- * - For unsafe code, use `trySync` or `tryAsync`.
117
+ * Since `Result` is a plain object, imperative code works naturally:
65
118
  *
66
- * Safe asynchronous code (using Result with a Promise):
119
+ * ### Stop on error, map on success
67
120
  *
68
121
  * ```ts
69
- * const fetchUser = async (
70
- * userId: string,
71
- * ): Promise<Result<User, FetchUserError>> => {
72
- * // Simulate an API call
73
- * return new Promise((resolve) => {
74
- * setTimeout(() => {
75
- * if (userId === "1") {
76
- * resolve(ok({ id: "1", name: "Alice" }));
77
- * } else {
78
- * resolve(
79
- * err({ type: "FetchUserError", reason: "user not found" }),
80
- * );
81
- * }
82
- * }, 1000);
83
- * });
84
- * };
122
+ * const users = getActiveUsers();
123
+ * if (!users.ok) return users;
124
+ * const usernames = mapArray(users.value, (u) => u.username);
85
125
  * ```
86
126
  *
87
- * For lazy, cancellable async operations, see {@link Task}.
88
- *
89
- * ### Naming convention
90
- *
91
- * - **For values you need:** use a name without Result suffix (`user`, `config`)
92
- * - **For void operations:** use `result` (no value to name)
93
- *
94
- * For multiple void operations, use block scopes to avoid potentially long
95
- * names like `createBaseTablesResult`, `createRelayTablesResult`, or counters
96
- * like `result1`, `result2`:
127
+ * ### Iterate array, stop on first error
97
128
  *
98
129
  * ```ts
99
- * const processUser = () => {
100
- * const user = getUser();
101
- * if (!user.ok) return user;
102
- *
103
- * const result = saveToDatabase(user.value);
130
+ * for (const user of users) {
131
+ * const result = validateUser(user); // Result<ValidUser, ValidateUserError>
104
132
  * if (!result.ok) return result;
105
- *
106
- * return ok();
107
- * };
108
- *
109
- * const setupDatabase = () => {
110
- * // Multiple void operations - use block scopes to avoid name clash
111
- * {
112
- * const result = createBaseTables();
113
- * if (!result.ok) return result;
114
- * }
115
- * {
116
- * const result = createRelayTables();
117
- * if (!result.ok) return result;
118
- * }
119
- *
120
- * return ok();
121
- * };
133
+ * }
122
134
  * ```
123
135
  *
124
- * ### Examples
125
- *
126
- * #### Sequential operations with short-circuiting
127
- *
128
- * When performing a sequence of operations where any failure should stop
129
- * further processing, use the `Result` type with early returns.
136
+ * ## Style
130
137
  *
131
- * Here's an example of a database reset operation that drops tables, restores a
132
- * schema, and initializes the database, stopping on the first error:
138
+ * Imperative code is the preferred way to compose sequential {@link Result}
139
+ * operations.
133
140
  *
134
141
  * ```ts
135
- * const result = deps.sqlite.transaction(() => {
136
- * const result = dropAllTables(deps);
137
- * if (!result.ok) return result;
142
+ * const user = getUser();
143
+ * if (!user.ok) return user;
138
144
  *
139
- * if (message.restore) {
140
- * const dbSchema = getDbSchema(deps)();
141
- * if (!dbSchema.ok) return dbSchema;
142
- *
143
- * {
144
- * const result = ensureDbSchema(deps)(
145
- * message.restore.dbSchema,
146
- * dbSchema.value,
147
- * );
148
- * if (!result.ok) return result;
149
- * }
150
- * {
151
- * const result = initializeDb(deps)(message.restore.mnemonic);
152
- * if (!result.ok) return result;
153
- * }
154
- * }
155
- * return ok();
156
- * });
145
+ * const profile = getProfile(user.value.id);
146
+ * if (!profile.ok) return profile;
157
147
  *
158
- * if (!result.ok) {
159
- * deps.postMessage({ type: "onError", error: result.error });
160
- * return;
161
- * }
148
+ * return ok({ user: user.value, profile: profile.value });
162
149
  * ```
163
150
  *
164
- * In this pattern:
165
- *
166
- * - Each operation returns a `Result` (e.g., `Result<void, E>` or `Result<T,
167
- * E>`).
168
- * - After each operation, check `if (!result.ok)` and return the `Err` to
169
- * short-circuit.
170
- * - If all operations succeed, return `ok()` (or another value if needed).
171
- * - Outside the transaction, handle the final `Result` to report success or
172
- * failure.
151
+ * This is an intentional style choice. Evolu does not provide helper
152
+ * combinators for every sequential pattern because that would duplicate plain
153
+ * control flow and create API ambiguity. Use helpers when they add semantics
154
+ * over ordinary control flow, such as operating on collections of results.
155
+ * While it may seem verbose, it is explicit, transparent, and avoids pipes and
156
+ * nested helpers, which are harder to debug.
173
157
  *
174
- * This approach ensures type-safe error handling, avoids nested try/catch
175
- * blocks, and clearly communicates the control flow.
158
+ * ## Composition
176
159
  *
177
- * #### A function with two different errors:
160
+ * Some patterns are common enough that deserve helpers. The previous example
161
+ * can be written with {@link mapResult}:
178
162
  *
179
163
  * ```ts
180
- * const example = (value: string): Result<number, FooError | BarError> => {
181
- * const foo = getFoo(value);
182
- * if (!foo.ok) return foo;
183
- *
184
- * const bar = getBar(foo.value);
185
- * if (!bar.ok) return bar;
186
- *
187
- * return ok(barToNumber(bar.value));
188
- * };
164
+ * const result = mapResult(users, validateUser);
165
+ * // Result<ValidUser[], ValidateUserError>
189
166
  * ```
190
167
  *
191
- * ### Handling unexpected errors
192
- *
193
- * Even with disciplined use of `trySync` and `tryAsync`, unexpected errors can
194
- * still occur due to programming mistakes, third-party library bugs, or edge
195
- * cases. These should be logged for debugging, but **unexpected errors are not
196
- * recoverable** - they represent bugs that must be fixed.
197
- *
198
- * **Important**: "Graceful shutdown" and error recovery can only come from
199
- * expected errors handled via the `Result` type. Unexpected errors should fail
200
- * fast - the operation fails immediately and the error bubbles up.
201
- *
202
- * #### In browser environments
168
+ * For an array of results, {@link allResult} extracts all values or returns the
169
+ * first error:
203
170
  *
204
171
  * ```ts
205
- * // Global error handler for unexpected errors
206
- * window.addEventListener("error", (event) => {
207
- * console.error("Uncaught error:", event.error);
208
- * // Send to error reporting service
209
- * errorReportingService.report(event.error);
210
- * });
211
- *
212
- * // For unhandled promise rejections
213
- * window.addEventListener("unhandledrejection", (event) => {
214
- * console.error("Unhandled promise rejection:", event.reason);
215
- * errorReportingService.report(event.reason);
216
- * });
172
+ * const result = allResult(validationResults);
217
173
  * ```
218
174
  *
219
- * #### In Node.js environments
175
+ * For the first success, {@link anyResult} returns the first Ok or the last
176
+ * error if all fail:
220
177
  *
221
178
  * ```ts
222
- * // Handle uncaught exceptions - log and fail fast
223
- * process.on("uncaughtException", (error) => {
224
- * console.error("Uncaught exception:", error);
225
- * errorReportingService.report(error);
226
- * // Exit immediately - unexpected errors are not recoverable
227
- * process.exit(1);
228
- * });
229
- *
230
- * // Handle unhandled promise rejections
231
- * process.on("unhandledRejection", (reason) => {
232
- * console.error("Unhandled promise rejection:", reason);
233
- * errorReportingService.report(reason);
234
- * });
179
+ * const result = anyResult(parserResults);
235
180
  * ```
236
181
  *
237
- * These global handlers serve as a safety net to log and report unexpected
238
- * errors for debugging purposes. They do not attempt recovery - unexpected
239
- * errors represent bugs that must be fixed. The discipline of explicit error
240
- * handling through the `Result` pattern remains the primary approach for all
241
- * recoverable scenarios.
242
- *
243
- * ### FAQ
244
- *
245
- * #### When should a function return a plain value instead of `Result<T, E>`?
246
- *
247
- * Use `Result<T, E>` only when a function can fail with **known, expected
248
- * errors** that callers need to handle. If a function cannot fail with a known
249
- * error, return the value directly.
182
+ * ## Naming convention
250
183
  *
251
- * - Return `Result<User, UserNotFoundError>` - can fail with a known error
252
- * - Return `User` - cannot fail with a known error
253
- * - ❌ Don't return `Result<User, never>` - unnecessary wrapper
254
- *
255
- * This keeps the codebase clean and makes error handling intentional. The type
256
- * system communicates which operations can fail and which cannot.
257
- *
258
- * Unsafe code from external libraries (not under our control) should be wrapped
259
- * with `trySync` or `tryAsync` at the boundaries. Once wrapped, if the error is
260
- * not important to callers, functions can safely return plain values. If the
261
- * error matters, use `Result` with a typed error.
184
+ * - Result with a value: name it after the value (`user`, `config`)
185
+ * - Result without a value: name it `result`
262
186
  *
263
187
  * ```ts
264
- * // Safe to return void - unsafe code is wrapped and error is handled
265
- * const processData = (data: string): void => {
266
- * const result = trySync(
267
- * () => JSON.parse(data),
268
- * (error) => ({ type: "ParseError", message: String(error) }),
269
- * );
188
+ * const processUser = (): Result<
189
+ * void,
190
+ * GetUserError | SaveToDatabaseError | SendWelcomeEmailError
191
+ * > => {
192
+ * const user = getUser();
193
+ * if (!user.ok) return user;
194
+ *
195
+ * const result = saveToDatabase(user.value);
196
+ * if (!result.ok) return result;
270
197
  *
271
- * if (!result.ok) {
272
- * logError(result.error);
273
- * return;
198
+ * // To avoid a clash with the previous `result`, use a block scope.
199
+ * {
200
+ * const result = sendWelcomeEmail(user.value);
201
+ * if (!result.ok) return result;
274
202
  * }
275
203
  *
276
- * // Continue with safe operations...
204
+ * return ok();
277
205
  * };
278
- *
279
- * // ✅ Can call without try-catch since it returns void
280
- * processData(jsonString);
281
- * ```
282
- *
283
- * #### What if my function doesn't return a value on success?
284
- *
285
- * If your function performs an operation but doesn't need to return a value on
286
- * success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
287
- * than using `Result<true, E>` or `Result<null, E>` because it communicates
288
- * that the function doesn't produce a value but can produce errors.
289
- *
290
- * #### How do I short-circuit processing of an array on the first error?
291
- *
292
- * If you want to stop processing as soon as an error occurs (short-circuit),
293
- * you should produce and check each `Result` inside a loop:
294
- *
295
- * ```ts
296
- * for (const query of [
297
- * sql`drop table evolu_config;`,
298
- * sql`drop table evolu_message;`,
299
- * ]) {
300
- * const result = deps.sqlite.exec(query);
301
- * if (!result.ok) return result;
302
- * }
303
- * // All queries succeeded
304
206
  * ```
305
207
  *
306
- * #### How do I handle an array of operations and short-circuit on the first error?
307
- *
308
- * If you have an array of operations (not results), you should make them
309
- * _lazy_—that is, represent each operation as a function. This way, you only
310
- * execute each operation as needed, and can stop on the first error:
208
+ * ## Unrecoverable errors
311
209
  *
312
- * ```ts
313
- * import type { LazyValue } from "./Function";
210
+ * Some errors can't be handled locally — they must propagate to the top level.
211
+ * These are unrecoverable errors: expected (you know they can happen) but only
212
+ * handleable at the app level.
314
213
  *
315
- * const operations: LazyValue<Result<void, MyError>>[] = [
316
- * () => doSomething(),
317
- * () => doSomethingElse(),
318
- * ];
214
+ * Do not force these errors into {@link Result} just because the underlying API
215
+ * throws. If the local caller cannot recover, let the error propagate to a
216
+ * global handler or other app boundary.
319
217
  *
320
- * for (const op of operations) {
321
- * const result = op();
322
- * if (!result.ok) return result;
323
- * }
324
- * // All operations succeeded
325
- * ```
218
+ * In Evolu apps, that boundary is typically a platform `createRun` adapter such
219
+ * as `@evolu/web`, `@evolu/nodejs`, or `@evolu/react-native`, which add
220
+ * platform-specific global error handling.
326
221
  *
327
- * If you already have an array of `Result`s, the processing has already
328
- * happened, so you can't short-circuit. In that case, you can check for the
329
- * first error:
222
+ * ## FAQ
330
223
  *
331
- * ```ts
332
- * const firstError = results.find((r) => !r.ok);
333
- * if (firstError) return firstError;
334
- * // All results are Ok
335
- * ```
224
+ * ### What if a function doesn't return a value on success?
336
225
  *
337
- * ### Why doesn't Evolu provide "handy helpers"?
226
+ * Use `Result<void, E>` and return `ok()` (no argument). Don't return
227
+ * `ok(true)`, `ok("success")`, or `ok("done")` — `ok()` already signals
228
+ * success; redundant values add noise.
229
+ */
230
+ export type Result<T, E = never> = Ok<T> | Err<E>;
231
+
232
+ /**
233
+ * Shorthand for a {@link Result} with `any` type parameters.
338
234
  *
339
- * Evolu intentionally favors imperative patterns (like the `for...of` loop
340
- * above) over monadic helpers. Imperative code is generally more readable,
341
- * easier to debug, and more familiar to most JavaScript and TypeScript
342
- * developers. While monads and functional helpers can be powerful, they often
343
- * obscure control flow and make debugging harder.
235
+ * @group Utilities
344
236
  */
345
- export type Result<T, E> = Ok<T> | Err<E>;
237
+ export type AnyResult = Result<any, any>;
346
238
 
347
239
  /** A successful {@link Result}. */
348
- export interface Ok<T> {
240
+ export interface Ok<out T> {
349
241
  readonly ok: true;
350
242
  readonly value: T;
351
243
  }
@@ -353,48 +245,24 @@ export interface Ok<T> {
353
245
  /**
354
246
  * An error {@link Result}.
355
247
  *
356
- * The `error` property can be any type that describes the error. For normal
357
- * domain logic, use a plain object. This allows us to structure errors with
358
- * custom fields (e.g., `{ type: "MyError", code: 123 }`). Messages for users
359
- * belong to translations, not to error objects.
360
- *
361
- * If you need a stack trace for debugging, use an `Error` instance or a custom
362
- * error class to include additional metadata.
363
- *
364
- * ### Examples
365
- *
366
- * #### Domain logic error (plain object, recommended)
248
+ * The `error` property can be any type that describes the error. For domain
249
+ * errors, use a plain object with a `type` field for discrimination.
367
250
  *
368
- * ```ts
369
- * const failure = err({
370
- * type: "ParseJsonError",
371
- * code: 1001,
372
- * input: "foo",
373
- * });
374
- * ```
375
- *
376
- * #### Debugging with stack trace (error instance)
377
- *
378
- * ```ts
379
- * const failure = err(new Error("Something went wrong"));
380
- * ```
381
- *
382
- * #### Custom error class
251
+ * ### Example
383
252
  *
384
253
  * ```ts
385
- * class MyCustomError extends Error {
386
- * constructor(
387
- * public code: number,
388
- * public input: string,
389
- * ) {
390
- * super(`Error ${code} on input: ${input}`);
391
- * this.name = "MyCustomError";
392
- * }
254
+ * interface NotFoundError extends Typed<"NotFoundError"> {
255
+ * readonly id: string;
393
256
  * }
394
- * const failure = err(new MyCustomError(404, "bad-input"));
257
+ *
258
+ * const findUser = (id: string): Result<User, NotFoundError> => {
259
+ * const user = users.get(id);
260
+ * if (user == null) return err({ type: "NotFoundError", id });
261
+ * return ok(user);
262
+ * };
395
263
  * ```
396
264
  */
397
- export interface Err<E> {
265
+ export interface Err<out E> {
398
266
  readonly ok: false;
399
267
  readonly error: E;
400
268
  }
@@ -402,7 +270,7 @@ export interface Err<E> {
402
270
  /**
403
271
  * Extracts the value type from a {@link Result}.
404
272
  *
405
- * @category Utilities
273
+ * @group Utilities
406
274
  */
407
275
  export type InferOk<R extends Result<any, any>> =
408
276
  R extends Ok<infer T> ? T : never;
@@ -410,7 +278,7 @@ export type InferOk<R extends Result<any, any>> =
410
278
  /**
411
279
  * Extracts the error type from a {@link Result}.
412
280
  *
413
- * @category Utilities
281
+ * @group Utilities
414
282
  */
415
283
  export type InferErr<R extends Result<any, any>> =
416
284
  R extends Err<infer E> ? E : never;
@@ -418,9 +286,9 @@ export type InferErr<R extends Result<any, any>> =
418
286
  /**
419
287
  * Creates an {@link Ok} result.
420
288
  *
421
- * - `ok()` creates an `Ok<void>` for operations that succeed without producing a
422
- * value.
423
- * - `ok(value)` creates an `Ok<T>` containing the specified value.
289
+ * - `ok()` creates a `Result<void, never>` for operations that succeed without
290
+ * producing a value.
291
+ * - `ok(value)` creates a `Result<T, never>` containing the specified value.
424
292
  *
425
293
  * ### Example
426
294
  *
@@ -432,35 +300,43 @@ export type InferErr<R extends Result<any, any>> =
432
300
  * console.log(success); // { ok: true, value: 42 }
433
301
  * ```
434
302
  */
435
- export function ok(): Ok<void>;
303
+ export function ok(): Result<void>;
436
304
  /** Creates an {@link Ok} result with a specified value. */
437
- export function ok<T>(value: T): Ok<T>;
438
- export function ok<T>(value = undefined): Ok<T> {
305
+ export function ok<T>(value: T): Result<T>;
306
+ export function ok<T>(value?: T): Result<T> {
307
+ if (value === undefined) return okVoid as Result<T>;
439
308
  return { ok: true, value: value as T };
440
309
  }
441
310
 
311
+ /** Cache ok() and ok(undefined) to avoid repeated allocations. */
312
+ const okVoid: Result<void> = { ok: true, value: undefined };
313
+
314
+ /** Creates an {@link Err} result. */
315
+ export const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
316
+
317
+ /** Type guard for {@link Ok} results. */
318
+ export const isOk = <T, E>(result: Result<T, E>): result is Ok<T> => result.ok;
319
+
320
+ /** Type guard for {@link Err} results. */
321
+ export const isErr = <T, E>(result: Result<T, E>): result is Err<E> =>
322
+ !result.ok;
323
+
442
324
  /**
443
- * Creates an {@link Err} result.
325
+ * Returns the value from an `Ok` {@link Result}, or throws if it is an `Err`.
444
326
  *
445
- * ### Example
327
+ * Use this where failure should crash the current flow instead of being handled
328
+ * locally.
446
329
  *
447
- * ```ts
448
- * const failure = err("Something went wrong");
449
- * console.log(failure); // { ok: false, error: "Something went wrong" }
450
- * ```
451
- */
452
- export const err = <E>(error: E): Err<E> => ({ ok: false, error });
453
-
454
- /**
455
- * Extracts the value from a {@link Result} if it is an `Ok`, or throws an error
456
- * if it is an `Err`.
330
+ * **When to use:**
457
331
  *
458
- * **Intended usage:**
332
+ * - Application startup or composition-root setup where errors must stop the
333
+ * program immediately. In Evolu apps, errors are handled by platform-specific
334
+ * `createRun` adapters at the app boundary.
335
+ * - Module-level constants
336
+ * - Test setup with values that are expected to be valid
459
337
  *
460
- * - For critical code paths (e.g., app startup, config values) where failure
461
- * should crash the app.
462
- * - Not recommended for general error handling in application logic—prefer
463
- * explicit checks.
338
+ * Prefer an explicit `if (!result.ok)` check in ordinary application logic
339
+ * where the caller can recover, retry, or choose a different flow.
464
340
  *
465
341
  * ### Example
466
342
  *
@@ -493,42 +369,39 @@ export const getOrThrow = <T, E>(result: Result<T, E>): T => {
493
369
  * ### Example
494
370
  *
495
371
  * ```ts
496
- * const parseResult = parseJson('{"key": "value"}');
497
- * const value = getOrNull(parseResult);
498
- * // value is unknown | null
499
- *
500
- * if (value != null) {
501
- * console.log("Parsed value:", value);
502
- * }
372
+ * // For APIs that expect T | null
373
+ * const user = getOrNull(findUser(id));
503
374
  * ```
504
375
  */
505
376
  export const getOrNull = <T, E>(result: Result<T, E>): T | null =>
506
377
  result.ok ? result.value : null;
507
378
 
508
379
  /**
509
- * Wraps synchronous functions that may throw exceptions, returning a
510
- * {@link Result}.
380
+ * Extracts the value from a {@link Result} whose error type is `never`.
381
+ *
382
+ * This is useful when the type system guarantees the result cannot fail (for
383
+ * example `Result<T, never>`), avoiding impossible `if (!result.ok)` branches
384
+ * at call sites.
385
+ */
386
+ export const getOk = <T>(result: Result<T>): T => {
387
+ assert(result.ok, "Expected Ok result.");
388
+ return result.value;
389
+ };
390
+
391
+ /**
392
+ * Wraps a synchronous function that may throw, returning a {@link Result}.
511
393
  *
512
- * The `trySync` function is designed to handle synchronous code safely by
513
- * wrapping the execution in a try-catch block. If the function succeeds, it
514
- * returns an `Ok` result. If an exception is thrown, it maps the error to a
515
- * custom type and returns an `Err` result.
394
+ * Use this when the thrown value should become a typed, recoverable error for
395
+ * the caller. Do not use it for failures that should terminate the current flow
396
+ * and propagate to a global handler.
516
397
  *
517
398
  * ### Example
518
399
  *
519
400
  * ```ts
520
- * interface ParseJsonError {
521
- * readonly type: "ParseJsonError";
522
- * readonly message: string;
523
- * }
524
- *
525
401
  * const parseJson = (value: string): Result<unknown, ParseJsonError> =>
526
402
  * trySync(
527
403
  * () => JSON.parse(value) as unknown,
528
- * (error): ParseJsonError => ({
529
- * type: "ParseJsonError",
530
- * message: String(error),
531
- * }),
404
+ * (error) => ({ type: "ParseJsonError", message: String(error) }),
532
405
  * );
533
406
  * ```
534
407
  */
@@ -544,54 +417,348 @@ export const trySync = <T, E>(
544
417
  };
545
418
 
546
419
  /**
547
- * Wraps async functions or any operation returning a promise, returning a
548
- * {@link Result}.
420
+ * Wraps an async function that may throw, returning a {@link Result}.
549
421
  *
550
- * The `tryAsync` function provides a way to handle asynchronous code safely by
551
- * catching any rejected promises and mapping errors to a custom type. If the
552
- * promise resolves, it returns an `Ok` result. If the promise rejects, it maps
553
- * the error and returns an `Err` result.
422
+ * Use this when the rejection should become a typed, recoverable error for the
423
+ * caller. Do not use it for failures that should terminate the current flow and
424
+ * propagate to a global handler.
554
425
  *
555
426
  * ### Example
556
427
  *
557
428
  * ```ts
558
- * interface FetchError {
559
- * readonly type: "FetchError";
560
- * readonly message: string;
561
- * }
562
- *
563
- * const tryFetch = async (
564
- * url: string,
565
- * ): Promise<Result<unknown, FetchError>> =>
429
+ * const fetchJson = (url: string): Promise<Result<unknown, FetchError>> =>
566
430
  * tryAsync(
567
431
  * async () => {
568
432
  * const response = await fetch(url);
569
- * if (!response.ok) {
570
- * throw new Error(`Request failed with status ${response.status}`);
571
- * }
433
+ * if (!response.ok) throw new Error(`Status ${response.status}`);
572
434
  * return response.json();
573
435
  * },
574
- * (error) => ({
575
- * type: "FetchError",
576
- * message: String(error),
577
- * }),
436
+ * (error) => ({ type: "FetchError", message: String(error) }),
578
437
  * );
579
- *
580
- * const result = await tryFetch(
581
- * "https://jsonplaceholder.typicode.com/posts/1",
582
- * );
583
- * if (result.ok) {
584
- * console.log("Data:", result.value);
585
- * } else {
586
- * console.error("Error:", result.error);
587
- * }
588
438
  * ```
589
439
  */
590
- export const tryAsync = async <T, E>(
591
- promiseFn: () => Promise<T>,
440
+ export const tryAsync = <T, E>(
441
+ lazyPromise: Lazy<Promise<T>>,
592
442
  mapError: (error: unknown) => E,
593
443
  ): Promise<Result<T, E>> =>
594
- promiseFn().then(
444
+ Promise.try(lazyPromise).then(
595
445
  (value) => ok(value),
596
446
  (error: unknown) => err(mapError(error)),
597
447
  );
448
+
449
+ /**
450
+ * A result for a pull-based protocol with three outcomes.
451
+ *
452
+ * The consumer requests the next value (e.g. via `next()`), and the producer
453
+ * responds with one of:
454
+ *
455
+ * - `Ok<A>` — produced a value
456
+ * - `Err<Done<D>>` — completed normally with a done value
457
+ * - `Err<E>` — failed with an error
458
+ *
459
+ * Inspired by JavaScript's `Iterator.next()`, which returns `{ value, done }`.
460
+ */
461
+ export type NextResult<A, E = never, D = void> = Result<A, E | Done<D>>;
462
+
463
+ /**
464
+ * A signal indicating normal completion of a pull-based protocol.
465
+ *
466
+ * This is not a failure — it is a control signal that carries an optional
467
+ * "done" value (often `void`, but can be a final summary or leftover).
468
+ *
469
+ * Inspired by JavaScript's `IteratorResult` where `{ done: true }` signals
470
+ * completion.
471
+ */
472
+ export interface Done<out D = unknown> extends Typed<"Done"> {
473
+ readonly done: D;
474
+ }
475
+
476
+ /**
477
+ * Constructs a {@link Done} value.
478
+ *
479
+ * - `done()` creates a `Done<void>` for protocols that don't need a done value.
480
+ * - `done(value)` creates a `Done<D>` containing the specified value.
481
+ */
482
+ export function done(): Done<void>;
483
+ /** With a done value. */
484
+ export function done<D>(value: D): Done<D>;
485
+ export function done<D>(value?: D): Done<D> {
486
+ return {
487
+ type: "Done",
488
+ done: value as D,
489
+ };
490
+ }
491
+
492
+ /**
493
+ * Removes {@link Done} from an error union.
494
+ *
495
+ * Useful for pull-based protocols where completion is encoded in the error
496
+ * channel (for example {@link NextResult}).
497
+ *
498
+ * @group Utilities
499
+ */
500
+ export type ExcludeDone<E> = Exclude<E, Done<any>>;
501
+
502
+ /**
503
+ * Extracts only {@link Done} from an error union.
504
+ *
505
+ * Useful for pull-based protocols where completion is encoded in the error
506
+ * channel (for example {@link NextResult}).
507
+ *
508
+ * @group Utilities
509
+ */
510
+ export type OnlyDone<E> = Extract<E, Done<any>>;
511
+
512
+ /**
513
+ * Extracts the done value type from a {@link NextResult}.
514
+ *
515
+ * @group Utilities
516
+ */
517
+ export type InferDone<R extends Result<any, any>> =
518
+ InferErr<R> extends infer Errors
519
+ ? Errors extends Done<infer D>
520
+ ? D
521
+ : never
522
+ : never;
523
+
524
+ /**
525
+ * Extracts all values from an array of {@link Result}s.
526
+ *
527
+ * Returns the first error if any result fails.
528
+ *
529
+ * ### Example
530
+ *
531
+ * ```ts
532
+ * const results = [ok(1), ok(2), ok(3)];
533
+ * const all = allResult(results);
534
+ * // ok([1, 2, 3])
535
+ *
536
+ * const withError = [ok(1), err("fail"), ok(3)];
537
+ * const failed = allResult(withError);
538
+ * // err("fail")
539
+ * ```
540
+ *
541
+ * @group Composition
542
+ */
543
+ export function allResult<
544
+ const T extends readonly [AnyResult, ...ReadonlyArray<AnyResult>],
545
+ >(results: T): Result<{ [K in keyof T]: InferOk<T[K]> }, InferErr<T[number]>>;
546
+
547
+ /**
548
+ * Returns object with same keys.
549
+ *
550
+ * ```ts
551
+ * const result = allResult({ a: ok(1), b: ok(2) });
552
+ * // ok({ a: 1, b: 2 })
553
+ * ```
554
+ */
555
+ export function allResult<T extends Readonly<Record<string, AnyResult>>>(
556
+ results: T,
557
+ ): Result<
558
+ { [P in keyof T]: InferOk<T[P]> },
559
+ [keyof T] extends [never] ? never : InferErr<T[keyof T]>
560
+ >;
561
+
562
+ /**
563
+ * For dynamic or generated result lists.
564
+ *
565
+ * ```ts
566
+ * const results: ReadonlyArray<Result<number, Error>> = getResults();
567
+ * const all = allResult(results);
568
+ * // Result<ReadonlyArray<number>, Error>
569
+ * ```
570
+ */
571
+ export function allResult<T, E>(
572
+ results: Iterable<Result<T, E>>,
573
+ ): Result<ReadonlyArray<T>, E>;
574
+
575
+ /**
576
+ * Guarantees non-empty result.
577
+ *
578
+ * ```ts
579
+ * const results: NonEmptyReadonlyArray<Result<number, Error>> = [
580
+ * ok(1),
581
+ * ok(2),
582
+ * ];
583
+ * const all = allResult(results);
584
+ * // Result<NonEmptyReadonlyArray<number>, Error>
585
+ * ```
586
+ */
587
+ export function allResult<T, E>(
588
+ results: NonEmptyReadonlyArray<Result<T, E>>,
589
+ ): Result<NonEmptyReadonlyArray<T>, E>;
590
+
591
+ export function allResult(
592
+ input: Iterable<AnyResult> | Readonly<Record<string, AnyResult>>,
593
+ ): AnyResult {
594
+ if (isIterable(input)) {
595
+ const array = arrayFrom(input);
596
+ if (!isNonEmptyArray(array)) return ok(emptyArray);
597
+
598
+ const length = array.length;
599
+ const values = new Array<unknown>(length);
600
+ for (let i = 0; i < length; i++) {
601
+ const result = array[i];
602
+ if (!result.ok) return result;
603
+ values[i] = result.value;
604
+ }
605
+ return ok(values);
606
+ }
607
+
608
+ const length = Object.keys(input).length;
609
+ if (length === 0) return ok(emptyRecord);
610
+
611
+ const keys = new Array<string>(length);
612
+ const results = new Array<AnyResult>(length);
613
+ let index = 0;
614
+ for (const key in input) {
615
+ keys[index] = key;
616
+ results[index] = (input as Record<string, AnyResult>)[key];
617
+ index++;
618
+ }
619
+
620
+ const record = createRecord();
621
+ for (let i = 0; i < length; i++) {
622
+ const result = results[i];
623
+ if (!result.ok) return result;
624
+ record[keys[i]] = result.value;
625
+ }
626
+ return ok(record);
627
+ }
628
+
629
+ /**
630
+ * Maps items to {@link Result}s and extracts all values.
631
+ *
632
+ * Returns the first error if any result fails.
633
+ *
634
+ * ### Example
635
+ *
636
+ * ```ts
637
+ * const users = [{ id: 1 }, { id: 2 }];
638
+ * const result = mapResult(users, validateUser);
639
+ * // Result<ReadonlyArray<ValidUser>, ValidateUserError>
640
+ * ```
641
+ *
642
+ * @group Composition
643
+ */
644
+ export function mapResult<
645
+ const A extends readonly [unknown, ...Array<unknown>],
646
+ T,
647
+ E,
648
+ >(
649
+ items: A,
650
+ fn: (a: A[number]) => Result<T, E>,
651
+ ): Result<{ [K in keyof A]: T }, E>;
652
+
653
+ /**
654
+ * For dynamic or generated item lists.
655
+ *
656
+ * ```ts
657
+ * const users = [{ id: 1 }, { id: 2 }];
658
+ * const result = mapResult(users, validateUser);
659
+ * // Result<ReadonlyArray<ValidUser>, ValidateUserError>
660
+ * ```
661
+ */
662
+ export function mapResult<A, T, E>(
663
+ items: Iterable<A>,
664
+ fn: (a: A) => Result<T, E>,
665
+ ): Result<ReadonlyArray<T>, E>;
666
+
667
+ /**
668
+ * Returns object with same keys.
669
+ *
670
+ * ```ts
671
+ * const result = mapResult({ a: 1, b: 2 }, double);
672
+ * // Result<{ a: number, b: number }, DoubleError>
673
+ * ```
674
+ */
675
+ export function mapResult<A, T, E, K extends string>(
676
+ items: Readonly<Record<K, A>>,
677
+ fn: (a: A) => Result<T, E>,
678
+ ): Result<Readonly<Record<K, T>>, E>;
679
+
680
+ export function mapResult(
681
+ input: Iterable<unknown> | Readonly<Record<string, unknown>>,
682
+ fn: (a: unknown) => AnyResult,
683
+ ): AnyResult {
684
+ if (isIterable(input)) {
685
+ const array = arrayFrom(input);
686
+ if (!isNonEmptyArray(array)) return ok(emptyArray);
687
+
688
+ const length = array.length;
689
+ const values = new Array<unknown>(length);
690
+ for (let i = 0; i < length; i++) {
691
+ const result = fn(array[i]);
692
+ if (!result.ok) return result;
693
+ values[i] = result.value;
694
+ }
695
+ return ok(values);
696
+ }
697
+
698
+ const length = Object.keys(input).length;
699
+ if (length === 0) return ok(emptyRecord);
700
+
701
+ const keys = new Array<string>(length);
702
+ const items = new Array<unknown>(length);
703
+ let index = 0;
704
+ for (const key in input) {
705
+ keys[index] = key;
706
+ items[index] = (input as Record<string, unknown>)[key];
707
+ index++;
708
+ }
709
+
710
+ const record = createRecord();
711
+ for (let i = 0; i < length; i++) {
712
+ const result = fn(items[i]);
713
+ if (!result.ok) return result;
714
+ record[keys[i]] = result.value;
715
+ }
716
+ return ok(record);
717
+ }
718
+
719
+ /**
720
+ * Returns the first successful {@link Result}.
721
+ *
722
+ * If all results fail, returns the last error.
723
+ *
724
+ * Requires a non-empty array — there's no "first success" with zero
725
+ * participants. Use {@link isNonEmptyArray} to guard:
726
+ *
727
+ * ```ts
728
+ * if (isNonEmptyArray(results)) {
729
+ * const result = anyResult(results);
730
+ * }
731
+ * ```
732
+ *
733
+ * ### Example
734
+ *
735
+ * ```ts
736
+ * const results = [err("fail1"), ok(42), err("fail2")];
737
+ * if (isNonEmptyArray(results)) {
738
+ * const result = anyResult(results);
739
+ * // ok(42)
740
+ * }
741
+ *
742
+ * const allFailed = [err("a"), err("b"), err("c")];
743
+ * if (isNonEmptyArray(allFailed)) {
744
+ * const result = anyResult(allFailed);
745
+ * // err("c") — last error
746
+ * }
747
+ * ```
748
+ *
749
+ * @group Composition
750
+ */
751
+ export function anyResult<
752
+ const T extends readonly [AnyResult, ...ReadonlyArray<AnyResult>],
753
+ >(results: T): Result<InferOk<T[number]>, InferErr<T[number]>>;
754
+
755
+ export function anyResult(
756
+ results: NonEmptyReadonlyArray<AnyResult>,
757
+ ): AnyResult {
758
+ let lastError: Err<unknown> | null = null;
759
+ for (const result of results) {
760
+ if (result.ok) return result;
761
+ lastError = result;
762
+ }
763
+ return lastError!;
764
+ }