@evolu/common 6.0.1-preview.9 → 7.1.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 (195) hide show
  1. package/README.md +27 -29
  2. package/dist/src/Array.d.ts +317 -12
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +219 -9
  5. package/dist/src/Assert.d.ts +0 -13
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +0 -15
  8. package/dist/src/Brand.d.ts +75 -0
  9. package/dist/src/Brand.d.ts.map +1 -0
  10. package/dist/src/Brand.js +1 -0
  11. package/dist/src/Buffer.d.ts +1 -1
  12. package/dist/src/Buffer.d.ts.map +1 -1
  13. package/dist/src/Buffer.js +8 -7
  14. package/dist/src/Cache.d.ts +39 -0
  15. package/dist/src/Cache.d.ts.map +1 -0
  16. package/dist/src/Cache.js +47 -0
  17. package/dist/src/Callbacks.d.ts +45 -12
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Callbacks.js +14 -7
  20. package/dist/src/Console.d.ts +32 -7
  21. package/dist/src/Console.d.ts.map +1 -1
  22. package/dist/src/Console.js +73 -10
  23. package/dist/src/Crypto.d.ts +56 -42
  24. package/dist/src/Crypto.d.ts.map +1 -1
  25. package/dist/src/Crypto.js +45 -54
  26. package/dist/src/Evolu/Db.d.ts +162 -74
  27. package/dist/src/Evolu/Db.d.ts.map +1 -1
  28. package/dist/src/Evolu/Db.js +284 -702
  29. package/dist/src/Evolu/Diff.d.ts +3 -3
  30. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  31. package/dist/src/Evolu/Diff.js +7 -5
  32. package/dist/src/Evolu/Evolu.d.ts +213 -133
  33. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  34. package/dist/src/Evolu/Evolu.js +189 -180
  35. package/dist/src/Evolu/Internal.d.ts +2 -4
  36. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  37. package/dist/src/Evolu/Internal.js +2 -4
  38. package/dist/src/Evolu/LocalAuth.d.ts +150 -0
  39. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  40. package/dist/src/Evolu/LocalAuth.js +174 -0
  41. package/dist/src/Evolu/Owner.d.ts +273 -120
  42. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  43. package/dist/src/Evolu/Owner.js +130 -104
  44. package/dist/src/Evolu/Platform.d.ts +9 -7
  45. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  46. package/dist/src/Evolu/Protocol.d.ts +268 -240
  47. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  48. package/dist/src/Evolu/Protocol.js +595 -447
  49. package/dist/src/Evolu/Public.d.ts +7 -9
  50. package/dist/src/Evolu/Public.d.ts.map +1 -1
  51. package/dist/src/Evolu/Public.js +3 -4
  52. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
  53. package/dist/src/Evolu/PublicKysely.js +3 -4
  54. package/dist/src/Evolu/Query.d.ts +2 -1
  55. package/dist/src/Evolu/Query.d.ts.map +1 -1
  56. package/dist/src/Evolu/Query.js +1 -1
  57. package/dist/src/Evolu/Relay.d.ts +91 -8
  58. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  59. package/dist/src/Evolu/Relay.js +214 -88
  60. package/dist/src/Evolu/Schema.d.ts +125 -47
  61. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  62. package/dist/src/Evolu/Schema.js +175 -31
  63. package/dist/src/Evolu/Storage.d.ts +249 -27
  64. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  65. package/dist/src/Evolu/Storage.js +198 -92
  66. package/dist/src/Evolu/Sync.d.ts +68 -13
  67. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  68. package/dist/src/Evolu/Sync.js +469 -20
  69. package/dist/src/Evolu/Timestamp.d.ts +83 -30
  70. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  71. package/dist/src/Evolu/Timestamp.js +79 -34
  72. package/dist/src/Identicon.d.ts +35 -0
  73. package/dist/src/Identicon.d.ts.map +1 -0
  74. package/dist/src/Identicon.js +143 -0
  75. package/dist/src/Instances.d.ts +34 -0
  76. package/dist/src/Instances.d.ts.map +1 -0
  77. package/dist/src/Instances.js +44 -0
  78. package/dist/src/Number.d.ts +4 -3
  79. package/dist/src/Number.d.ts.map +1 -1
  80. package/dist/src/Number.js +5 -4
  81. package/dist/src/Object.d.ts +10 -4
  82. package/dist/src/Object.d.ts.map +1 -1
  83. package/dist/src/Object.js +9 -3
  84. package/dist/src/Order.d.ts +0 -5
  85. package/dist/src/Order.d.ts.map +1 -1
  86. package/dist/src/Order.js +0 -5
  87. package/dist/src/Platform.d.ts +15 -0
  88. package/dist/src/Platform.d.ts.map +1 -0
  89. package/dist/src/Platform.js +17 -0
  90. package/dist/src/Random.d.ts +3 -7
  91. package/dist/src/Random.d.ts.map +1 -1
  92. package/dist/src/Random.js +0 -5
  93. package/dist/src/Ref.d.ts.map +1 -1
  94. package/dist/src/Relation.d.ts +79 -0
  95. package/dist/src/Relation.d.ts.map +1 -0
  96. package/dist/src/Relation.js +127 -0
  97. package/dist/src/Resources.d.ts +118 -0
  98. package/dist/src/Resources.d.ts.map +1 -0
  99. package/dist/src/Resources.js +197 -0
  100. package/dist/src/Result.d.ts +189 -59
  101. package/dist/src/Result.d.ts.map +1 -1
  102. package/dist/src/Result.js +30 -241
  103. package/dist/src/Skiplist.js +2 -1
  104. package/dist/src/Sqlite.d.ts +89 -5
  105. package/dist/src/Sqlite.d.ts.map +1 -1
  106. package/dist/src/Sqlite.js +136 -9
  107. package/dist/src/Store.d.ts +0 -5
  108. package/dist/src/Store.d.ts.map +1 -1
  109. package/dist/src/Store.js +0 -5
  110. package/dist/src/Task.d.ts +580 -0
  111. package/dist/src/Task.d.ts.map +1 -0
  112. package/dist/src/Task.js +464 -0
  113. package/dist/src/Time.d.ts +66 -6
  114. package/dist/src/Time.d.ts.map +1 -1
  115. package/dist/src/Time.js +96 -7
  116. package/dist/src/Type.d.ts +677 -344
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +718 -467
  119. package/dist/src/Types.d.ts +38 -75
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/WebSocket.d.ts +5 -7
  122. package/dist/src/WebSocket.d.ts.map +1 -1
  123. package/dist/src/WebSocket.js +12 -23
  124. package/dist/src/Worker.d.ts +39 -16
  125. package/dist/src/Worker.d.ts.map +1 -1
  126. package/dist/src/Worker.js +22 -9
  127. package/dist/src/index.d.ts +8 -3
  128. package/dist/src/index.d.ts.map +1 -1
  129. package/dist/src/index.js +8 -3
  130. package/package.json +14 -13
  131. package/src/Array.ts +396 -19
  132. package/src/Assert.ts +0 -35
  133. package/src/Brand.ts +75 -0
  134. package/src/Buffer.ts +7 -7
  135. package/src/Cache.ts +79 -0
  136. package/src/Callbacks.ts +62 -22
  137. package/src/Console.ts +92 -12
  138. package/src/Crypto.ts +99 -91
  139. package/src/Evolu/Db.ts +519 -1026
  140. package/src/Evolu/Diff.ts +7 -5
  141. package/src/Evolu/Evolu.ts +476 -360
  142. package/src/Evolu/Internal.ts +2 -4
  143. package/src/Evolu/LocalAuth.ts +463 -0
  144. package/src/Evolu/Owner.ts +355 -228
  145. package/src/Evolu/Platform.ts +9 -9
  146. package/src/Evolu/Protocol.ts +846 -742
  147. package/src/Evolu/Public.ts +8 -15
  148. package/src/Evolu/PublicKysely.ts +4 -5
  149. package/src/Evolu/Query.ts +3 -2
  150. package/src/Evolu/Relay.ts +406 -103
  151. package/src/Evolu/Schema.ts +323 -91
  152. package/src/Evolu/Storage.ts +559 -137
  153. package/src/Evolu/Sync.ts +819 -36
  154. package/src/Evolu/Timestamp.ts +90 -58
  155. package/src/Identicon.ts +197 -0
  156. package/src/Instances.ts +90 -0
  157. package/src/Number.ts +6 -10
  158. package/src/Object.ts +13 -5
  159. package/src/Order.ts +0 -6
  160. package/src/Platform.ts +20 -0
  161. package/src/Random.ts +3 -8
  162. package/src/Ref.ts +0 -5
  163. package/src/Relation.ts +234 -0
  164. package/src/Resources.ts +367 -0
  165. package/src/Result.ts +196 -61
  166. package/src/Skiplist.ts +1 -1
  167. package/src/Sqlite.ts +152 -17
  168. package/src/Store.ts +0 -5
  169. package/src/Task.ts +894 -0
  170. package/src/Time.ts +179 -10
  171. package/src/Type.ts +1136 -731
  172. package/src/Types.ts +43 -77
  173. package/src/WebSocket.ts +27 -31
  174. package/src/Worker.ts +72 -29
  175. package/src/index.ts +8 -3
  176. package/dist/src/Evolu/Config.d.ts +0 -69
  177. package/dist/src/Evolu/Config.d.ts.map +0 -1
  178. package/dist/src/Evolu/Config.js +0 -9
  179. package/dist/src/Evolu/Kysely.d.ts +0 -6
  180. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  181. package/dist/src/Evolu/Kysely.js +0 -21
  182. package/dist/src/ManyToManyMap.d.ts +0 -26
  183. package/dist/src/ManyToManyMap.d.ts.map +0 -1
  184. package/dist/src/ManyToManyMap.js +0 -92
  185. package/dist/src/NanoId.d.ts +0 -27
  186. package/dist/src/NanoId.d.ts.map +0 -1
  187. package/dist/src/NanoId.js +0 -6
  188. package/dist/src/Promise.d.ts +0 -180
  189. package/dist/src/Promise.d.ts.map +0 -1
  190. package/dist/src/Promise.js +0 -176
  191. package/src/Evolu/Config.ts +0 -83
  192. package/src/Evolu/Kysely.ts +0 -38
  193. package/src/ManyToManyMap.ts +0 -140
  194. package/src/NanoId.ts +0 -39
  195. package/src/Promise.ts +0 -295
@@ -0,0 +1,197 @@
1
+ import { err, ok } from "./Result.js";
2
+ import { PositiveInt } from "./Type.js";
3
+ /**
4
+ * Creates {@link Resources}.
5
+ *
6
+ * This tracks which consumers are using which resources and maintains reference
7
+ * counts to know when it's safe to dispose resources. Resources are created
8
+ * on-demand and disposed with a configurable delay to avoid churn.
9
+ *
10
+ * ### Example Usage
11
+ *
12
+ * ```ts
13
+ * // WebSocket connections
14
+ * interface WebSocketConfig {
15
+ * readonly url: WebSocketUrl;
16
+ * }
17
+ *
18
+ * type WebSocketUrl = string & Brand<"WebSocketUrl">;
19
+ * type UserId = string & Brand<"UserId">;
20
+ *
21
+ * const webSockets = createResources<
22
+ * WebSocket,
23
+ * WebSocketUrl,
24
+ * WebSocketConfig,
25
+ * User,
26
+ * UserId
27
+ * >({
28
+ * createResource: (config) => new WebSocket(config.url),
29
+ * getResourceKey: (config) => config.url,
30
+ * getConsumerId: (user) => user.id,
31
+ * disposalDelay: 1000,
32
+ * });
33
+ *
34
+ * // Add users to WebSocket connections
35
+ * webSockets.addConsumer(user1, [
36
+ * { url: "ws://server1.com" as WebSocketUrl },
37
+ * { url: "ws://server2.com" as WebSocketUrl },
38
+ * ]);
39
+ * webSockets.addConsumer(user2, [
40
+ * { url: "ws://server1.com" as WebSocketUrl },
41
+ * ]);
42
+ *
43
+ * // Remove users - server1 stays alive (user2 still using it)
44
+ * webSockets.removeConsumer(user1, [
45
+ * { url: "ws://server1.com" as WebSocketUrl },
46
+ * { url: "ws://server2.com" as WebSocketUrl },
47
+ * ]);
48
+ *
49
+ * // server2 gets disposed after delay, server1 stays alive
50
+ * ```
51
+ */
52
+ export const createResources = (config) => {
53
+ let isDisposed = false;
54
+ const resourcesMap = new Map();
55
+ const consumerCounts = new Map();
56
+ const consumers = new Map();
57
+ const disposalTimeouts = new Map();
58
+ const disposalDelay = config.disposalDelay ?? 100;
59
+ const ensureResource = (resourceConfig) => {
60
+ const key = config.getResourceKey(resourceConfig);
61
+ const timeout = disposalTimeouts.get(key);
62
+ if (timeout) {
63
+ clearTimeout(timeout);
64
+ disposalTimeouts.delete(key);
65
+ }
66
+ if (!resourcesMap.has(key)) {
67
+ const resource = config.createResource(resourceConfig);
68
+ resourcesMap.set(key, resource);
69
+ }
70
+ };
71
+ const scheduleDisposal = (key) => {
72
+ const timeout = setTimeout(() => {
73
+ const resource = resourcesMap.get(key);
74
+ if (resource) {
75
+ resource[Symbol.dispose]();
76
+ resourcesMap.delete(key);
77
+ }
78
+ disposalTimeouts.delete(key);
79
+ }, disposalDelay);
80
+ disposalTimeouts.set(key, timeout);
81
+ };
82
+ const resources = {
83
+ addConsumer: (consumer, resourceConfigs) => {
84
+ if (isDisposed)
85
+ return;
86
+ const consumerId = config.getConsumerId(consumer);
87
+ // Store consumer (last added consumer for this ID)
88
+ consumers.set(consumerId, consumer);
89
+ for (const resourceConfig of resourceConfigs) {
90
+ ensureResource(resourceConfig);
91
+ const resourceKey = config.getResourceKey(resourceConfig);
92
+ let counts = consumerCounts.get(resourceKey);
93
+ if (!counts) {
94
+ counts = new Map();
95
+ consumerCounts.set(resourceKey, counts);
96
+ }
97
+ const currentCount = counts.get(consumerId) ?? 0;
98
+ const newCount = currentCount + 1;
99
+ counts.set(consumerId, PositiveInt.orThrow(newCount));
100
+ // Call onConsumerAdded callback only when consumer is added for the first time (0 -> 1)
101
+ if (currentCount === 0 && config.onConsumerAdded) {
102
+ const resource = resourcesMap.get(resourceKey);
103
+ if (resource) {
104
+ config.onConsumerAdded(consumer, resource, resourceKey);
105
+ }
106
+ }
107
+ }
108
+ },
109
+ removeConsumer: (consumer, resourceConfigs) => {
110
+ if (isDisposed)
111
+ return ok();
112
+ const consumerId = config.getConsumerId(consumer);
113
+ for (const resourceConfig of resourceConfigs) {
114
+ const key = config.getResourceKey(resourceConfig);
115
+ const counts = consumerCounts.get(key);
116
+ if (!counts) {
117
+ return err({ type: "ResourceNotFoundError", resourceKey: key });
118
+ }
119
+ const currentCount = counts.get(consumerId);
120
+ if (currentCount == null) {
121
+ return err({
122
+ type: "ConsumerNotFoundError",
123
+ consumerId: consumerId,
124
+ resourceKey: key,
125
+ });
126
+ }
127
+ if (currentCount === 1) {
128
+ counts.delete(consumerId);
129
+ // Call onConsumerRemoved callback only when consumer is completely removed (1 -> 0)
130
+ if (config.onConsumerRemoved) {
131
+ const resource = resourcesMap.get(key);
132
+ if (resource) {
133
+ config.onConsumerRemoved(consumer, resource, key);
134
+ }
135
+ }
136
+ if (counts.size === 0) {
137
+ consumerCounts.delete(key);
138
+ scheduleDisposal(key);
139
+ }
140
+ }
141
+ else {
142
+ counts.set(consumerId, PositiveInt.orThrow(currentCount - 1));
143
+ }
144
+ }
145
+ if (!resources.hasConsumerAnyResource(consumer)) {
146
+ consumers.delete(consumerId);
147
+ }
148
+ return ok();
149
+ },
150
+ getResource: (key) => {
151
+ if (isDisposed)
152
+ return null;
153
+ return resourcesMap.get(key) ?? null;
154
+ },
155
+ getConsumersForResource: (key) => {
156
+ if (isDisposed)
157
+ return [];
158
+ const counts = consumerCounts.get(key);
159
+ return counts ? Array.from(counts.keys()) : [];
160
+ },
161
+ hasConsumerAnyResource: (consumer) => {
162
+ if (isDisposed)
163
+ return false;
164
+ const consumerId = config.getConsumerId(consumer);
165
+ // If slow, can be optimized with reverse index
166
+ return Array.from(consumerCounts.values()).some((counts) => counts.has(consumerId));
167
+ },
168
+ getConsumer: (consumerId) => {
169
+ if (isDisposed)
170
+ return null;
171
+ const consumer = consumers.get(consumerId);
172
+ if (!consumer)
173
+ return null;
174
+ // Only return consumer if it's currently using any resources
175
+ if (!resources.hasConsumerAnyResource(consumer)) {
176
+ return null;
177
+ }
178
+ return consumer;
179
+ },
180
+ [Symbol.dispose]: () => {
181
+ if (isDisposed)
182
+ return;
183
+ isDisposed = true;
184
+ for (const timeout of disposalTimeouts.values()) {
185
+ clearTimeout(timeout);
186
+ }
187
+ disposalTimeouts.clear();
188
+ for (const resource of resourcesMap.values()) {
189
+ resource[Symbol.dispose]();
190
+ }
191
+ resourcesMap.clear();
192
+ consumerCounts.clear();
193
+ consumers.clear();
194
+ },
195
+ };
196
+ return resources;
197
+ };
@@ -1,36 +1,17 @@
1
1
  /**
2
- * 🛡️ Type-safe errors
3
- *
4
- * ## Intro
5
- *
6
2
  * The problem with throwing an exception in JavaScript is that the caught error
7
3
  * is always of an unknown type. The unknown type is a problem because we can't
8
4
  * be sure all errors have been handled because the TypeScript compiler can't
9
- * help us.
10
- *
11
- * Some other languages like Rust 🦀 or Haskell 📚 use a type-safe approach to
12
- * error handling, where errors are explicitly represented as part of the return
13
- * type, such as Result or Either, allowing the developer to handle all errors
14
- * safely. ✅
15
- *
16
- * ✨ Evolu uses {@link Result}, and it looks like this:
17
- *
18
- * ```ts
19
- * type Result<T, E> = Ok<T> | Err<E>;
5
+ * tell us.
20
6
  *
21
- * interface Ok<T> {
22
- * readonly ok: true;
23
- * readonly value: T;
24
- * }
25
- *
26
- * interface Err<E> {
27
- * readonly ok: false;
28
- * readonly error: E;
29
- * }
7
+ * Languages like Rust or Haskell use a type-safe approach to error handling,
8
+ * where errors are explicitly represented as part of the return type, such as
9
+ * Result or Either, allowing the developer to handle errors safely. TypeScript
10
+ * can have this too via the `Result` type.
30
11
  *
31
- * const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
32
- * const err = <E>(error: E): Err<E> => ({ ok: false, error });
33
- * ```
12
+ * The `Result` type can be either {@link Ok} (success) or {@link Err} (error).
13
+ * Use {@link ok} to create a successful result and {@link err} to create an error
14
+ * result.
34
15
  *
35
16
  * Now let's look at how `Result` can be used for safe JSON parsing:
36
17
  *
@@ -77,12 +58,10 @@
77
58
  *
78
59
  * Let's summarize it:
79
60
  *
80
- * - For synchronous safe code, use `ok` and `err`.
61
+ * - For safe code, use `ok` and `err`.
81
62
  * - For unsafe code, use `trySync` or `tryAsync`.
82
- * - For asynchronous safe code, use `Promise` with {@link Result}.
83
63
  *
84
- * Asynchronous safe (because of a Promise using Result) code is
85
- * straightforward:
64
+ * Safe asynchronous code (using Result with a Promise):
86
65
  *
87
66
  * ```ts
88
67
  * const fetchUser = async (
@@ -103,9 +82,40 @@
103
82
  * };
104
83
  * ```
105
84
  *
106
- * ## Examples
85
+ * ### Naming convention
107
86
  *
108
- * ### Sequential Operations with Short-Circuiting
87
+ * - For values: `const user = getUser()`
88
+ * - For a single void operation: `const result = foo()`
89
+ * - For multiple void operations: use descriptive names for all
90
+ *
91
+ * ```ts
92
+ * const processUser = () => {
93
+ * // we have a value
94
+ * const user = getUser();
95
+ * if (!user.ok) return user;
96
+ *
97
+ * // single void operation
98
+ * const result = saveToDatabase(user.value);
99
+ * if (!result.ok) return result;
100
+ *
101
+ * return ok();
102
+ * };
103
+ *
104
+ * const setupDatabase = () => {
105
+ * // multiple void operations - use descriptive names
106
+ * const baseTables = createBaseTables();
107
+ * if (!baseTables.ok) return baseTables;
108
+ *
109
+ * const relayTables = createRelayTables();
110
+ * if (!relayTables.ok) return relayTables;
111
+ *
112
+ * return ok();
113
+ * };
114
+ * ```
115
+ *
116
+ * ### Examples
117
+ *
118
+ * #### Sequential operations with short-circuiting
109
119
  *
110
120
  * When performing a sequence of operations where any failure should stop
111
121
  * further processing, use the `Result` type with early returns.
@@ -155,37 +165,127 @@
155
165
  * This approach ensures type-safe error handling, avoids nested try/catch
156
166
  * blocks, and clearly communicates the control flow.
157
167
  *
158
- * ### A function with two different errors:
168
+ * #### A function with two different errors:
159
169
  *
160
170
  * ```ts
161
171
  * const example = (value: string): Result<number, FooError | BarError> => {
162
172
  * const foo = getFoo(value);
163
173
  * if (!foo.ok) return foo;
164
174
  *
165
- * const bar = barize(foo.value);
175
+ * const bar = getBar(foo.value);
166
176
  * if (!bar.ok) return bar;
167
177
  *
168
178
  * return ok(barToNumber(bar.value));
169
179
  * };
170
180
  * ```
171
181
  *
172
- * ## FAQ
182
+ * ### Handling unexpected errors
183
+ *
184
+ * Even with disciplined use of `trySync` and `tryAsync`, unexpected errors can
185
+ * still occur due to programming mistakes, third-party library bugs, or edge
186
+ * cases. These should be logged for debugging, but **unexpected errors are not
187
+ * recoverable** - they represent bugs that must be fixed.
188
+ *
189
+ * **Important**: "Graceful shutdown" and error recovery can only come from
190
+ * expected errors handled via the `Result` type. Unexpected errors should fail
191
+ * fast - the operation fails immediately and the error bubbles up.
192
+ *
193
+ * #### In browser environments
194
+ *
195
+ * ```ts
196
+ * // Global error handler for unexpected errors
197
+ * window.addEventListener("error", (event) => {
198
+ * console.error("Uncaught error:", event.error);
199
+ * // Send to error reporting service
200
+ * errorReportingService.report(event.error);
201
+ * });
202
+ *
203
+ * // For unhandled promise rejections
204
+ * window.addEventListener("unhandledrejection", (event) => {
205
+ * console.error("Unhandled promise rejection:", event.reason);
206
+ * errorReportingService.report(event.reason);
207
+ * });
208
+ * ```
209
+ *
210
+ * #### In Node.js environments
211
+ *
212
+ * ```ts
213
+ * // Handle uncaught exceptions - log and fail fast
214
+ * process.on("uncaughtException", (error) => {
215
+ * console.error("Uncaught exception:", error);
216
+ * errorReportingService.report(error);
217
+ * // Exit immediately - unexpected errors are not recoverable
218
+ * process.exit(1);
219
+ * });
220
+ *
221
+ * // Handle unhandled promise rejections
222
+ * process.on("unhandledRejection", (reason) => {
223
+ * console.error("Unhandled promise rejection:", reason);
224
+ * errorReportingService.report(reason);
225
+ * });
226
+ * ```
227
+ *
228
+ * These global handlers serve as a safety net to log and report unexpected
229
+ * errors for debugging purposes. They do not attempt recovery - unexpected
230
+ * errors represent bugs that must be fixed. The discipline of explicit error
231
+ * handling through the `Result` pattern remains the primary approach for all
232
+ * recoverable scenarios.
173
233
  *
174
- * ### What if my function doesn't return a value on success?
234
+ * ### FAQ
235
+ *
236
+ * #### When should a function return a plain value instead of `Result<T, E>`?
237
+ *
238
+ * Use `Result<T, E>` only when a function can fail with **known, expected
239
+ * errors** that callers need to handle. If a function cannot fail with a known
240
+ * error, return the value directly.
241
+ *
242
+ * - ✅ Return `Result<User, UserNotFoundError>` - can fail with a known error
243
+ * - ✅ Return `User` - cannot fail with a known error
244
+ * - ❌ Don't return `Result<User, never>` - unnecessary wrapper
245
+ *
246
+ * This keeps the codebase clean and makes error handling intentional. The type
247
+ * system communicates which operations can fail and which cannot.
248
+ *
249
+ * Unsafe code from external libraries (not under our control) should be wrapped
250
+ * with `trySync` or `tryAsync` at the boundaries. Once wrapped, if the error is
251
+ * not important to callers, functions can safely return plain values. If the
252
+ * error matters, use `Result` with a typed error.
253
+ *
254
+ * ```ts
255
+ * // ✅ Safe to return void - unsafe code is wrapped and error is handled
256
+ * const processData = (data: string): void => {
257
+ * const parseResult = trySync(
258
+ * () => JSON.parse(data),
259
+ * (error) => ({ type: "ParseError", message: String(error) }),
260
+ * );
261
+ *
262
+ * if (!parseResult.ok) {
263
+ * logError(parseResult.error);
264
+ * return;
265
+ * }
266
+ *
267
+ * // Continue with safe operations...
268
+ * };
269
+ *
270
+ * // ✅ Can call without try-catch since it returns void
271
+ * processData(jsonString);
272
+ * ```
273
+ *
274
+ * #### What if my function doesn't return a value on success?
175
275
  *
176
276
  * If your function performs an operation but doesn't need to return a value on
177
277
  * success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
178
278
  * than using `Result<true, E>` or `Result<null, E>` because it communicates
179
279
  * that the function doesn't produce a value but can produce errors.
180
280
  *
181
- * ### How do I short-circuit processing of an array on the first error?
281
+ * #### How do I short-circuit processing of an array on the first error?
182
282
  *
183
283
  * If you want to stop processing as soon as an error occurs (short-circuit),
184
284
  * you should produce and check each `Result` inside a loop:
185
285
  *
186
286
  * ```ts
187
287
  * for (const query of [
188
- * sql`drop table evolu_owner;`,
288
+ * sql`drop table evolu_config;`,
189
289
  * sql`drop table evolu_message;`,
190
290
  * ]) {
191
291
  * const result = deps.sqlite.exec(query);
@@ -194,12 +294,11 @@
194
294
  * // All queries succeeded
195
295
  * ```
196
296
  *
197
- * ### How do I handle an array of operations and short-circuit on the first error?
297
+ * #### How do I handle an array of operations and short-circuit on the first error?
198
298
  *
199
299
  * If you have an array of operations (not results), you should make them
200
- * _lazy_—that is, represent each operation as a function (see `LazyValue` in
201
- * `Function.ts`). This way, you only execute each operation as needed, and can
202
- * stop on the first error:
300
+ * _lazy_—that is, represent each operation as a function. This way, you only
301
+ * execute each operation as needed, and can stop on the first error:
203
302
  *
204
303
  * ```ts
205
304
  * import type { LazyValue } from "./Function";
@@ -232,16 +331,7 @@
232
331
  * above) over monadic helpers. Imperative code is generally more readable,
233
332
  * easier to debug, and more familiar to most JavaScript and TypeScript
234
333
  * developers. While monads and functional helpers can be powerful, they often
235
- * obscure control flow and make debugging harder. Evolu's approach keeps error
236
- * handling explicit and straightforward.
237
- *
238
- * @module
239
- */
240
- /**
241
- * A `Result` can be either {@link Ok} (success) or {@link Err} (error).
242
- *
243
- * Use {@link ok} to create a successful result and {@link err} to create an error
244
- * result.
334
+ * obscure control flow and make debugging harder.
245
335
  */
246
336
  export type Result<T, E> = Ok<T> | Err<E>;
247
337
  /** A successful {@link Result}. */
@@ -253,16 +343,16 @@ export interface Ok<T> {
253
343
  * An error {@link Result}.
254
344
  *
255
345
  * The `error` property can be any type that describes the error. For normal
256
- * business logic, use a plain object. This allows us to structure errors with
346
+ * domain logic, use a plain object. This allows us to structure errors with
257
347
  * custom fields (e.g., `{ type: "MyError", code: 123 }`). Messages for users
258
348
  * belong to translations, not to error objects.
259
349
  *
260
- * If you need a stacktrace for debugging, use an `Error` instance or a custom
350
+ * If you need a stack trace for debugging, use an `Error` instance or a custom
261
351
  * error class to include additional metadata.
262
352
  *
263
353
  * ### Examples
264
354
  *
265
- * #### Business Logic Error (Plain Object, Recommended)
355
+ * #### Domain logic error (plain object, recommended)
266
356
  *
267
357
  * ```ts
268
358
  * const failure = err({
@@ -272,13 +362,13 @@ export interface Ok<T> {
272
362
  * });
273
363
  * ```
274
364
  *
275
- * #### Debugging with Stack Trace (Error Instance)
365
+ * #### Debugging with stack trace (error instance)
276
366
  *
277
367
  * ```ts
278
368
  * const failure = err(new Error("Something went wrong"));
279
369
  * ```
280
370
  *
281
- * #### Custom Error Class
371
+ * #### Custom error class
282
372
  *
283
373
  * ```ts
284
374
  * class MyCustomError extends Error {
@@ -297,6 +387,18 @@ export interface Err<E> {
297
387
  readonly ok: false;
298
388
  readonly error: E;
299
389
  }
390
+ /**
391
+ * Extracts the value type from a {@link Result}.
392
+ *
393
+ * @category Utilities
394
+ */
395
+ export type InferOk<R extends Result<any, any>> = R extends Ok<infer T> ? T : never;
396
+ /**
397
+ * Extracts the error type from a {@link Result}.
398
+ *
399
+ * @category Utilities
400
+ */
401
+ export type InferErr<R extends Result<any, any>> = R extends Err<infer E> ? E : never;
300
402
  /**
301
403
  * Creates an {@link Ok} result.
302
404
  *
@@ -346,8 +448,33 @@ export declare const err: <E>(error: E) => Err<E>;
346
448
  * const config = getOrThrow(loadConfig());
347
449
  * // Safe to use config here
348
450
  * ```
451
+ *
452
+ * Throws: `Error` with the original error attached as `cause`.
349
453
  */
350
454
  export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
455
+ /**
456
+ * Extracts the value from a {@link Result} if it is an `Ok`, or returns `null`
457
+ * if it is an `Err`.
458
+ *
459
+ * **Intended usage:**
460
+ *
461
+ * - When you need to convert a `Result` to a nullable value for APIs that expect
462
+ * `T | null`.
463
+ * - When the error is not important and you just want the value or nothing.
464
+ *
465
+ * ### Example
466
+ *
467
+ * ```ts
468
+ * const parseResult = parseJson('{"key": "value"}');
469
+ * const value = getOrNull(parseResult);
470
+ * // value is unknown | null
471
+ *
472
+ * if (value != null) {
473
+ * console.log("Parsed value:", value);
474
+ * }
475
+ * ```
476
+ */
477
+ export declare const getOrNull: <T, E>(result: Result<T, E>) => T | null;
351
478
  /**
352
479
  * Wraps synchronous functions that may throw exceptions, returning a
353
480
  * {@link Result}.
@@ -368,7 +495,10 @@ export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
368
495
  * const parseJson = (value: string): Result<unknown, ParseJsonError> =>
369
496
  * trySync(
370
497
  * () => JSON.parse(value) as unknown,
371
- * (error) => ({ type: "ParseJsonError", message: String(error) }),
498
+ * (error): ParseJsonError => ({
499
+ * type: "ParseJsonError",
500
+ * message: String(error),
501
+ * }),
372
502
  * );
373
503
  * ```
374
504
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Result.d.ts","sourceRoot":"","sources":["../../src/Result.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8OG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAE1C,mCAAmC;AACnC,MAAM,WAAW,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,GAAG,CAAC,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;AAC/B,2DAA2D;AAC3D,wBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAKvC;;;;;;;;;GASG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG,GAAG,CAAC,CAAC,CAA2B,CAAC;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,CAMvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,CAAC,EAC1B,IAAI,MAAM,CAAC,EACX,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,KAC9B,MAAM,CAAC,CAAC,EAAE,CAAC,CAMb,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,QAAQ,GAAU,CAAC,EAAE,CAAC,EACjC,WAAW,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,KAC9B,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAIpB,CAAC"}
1
+ {"version":3,"file":"Result.d.ts","sourceRoot":"","sources":["../../src/Result.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8UG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAE1C,mCAAmC;AACnC,MAAM,WAAW,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,GAAG,CAAC,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAC5C,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEpC;;;;GAIG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAC7C,CAAC,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAErC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;AAC/B,2DAA2D;AAC3D,wBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAKvC;;;;;;;;;GASG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG,GAAG,CAAC,CAAC,CAA2B,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,CAMvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,CAAC,GAAG,IAC1B,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,CAAC,EAC1B,IAAI,MAAM,CAAC,EACX,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,KAC9B,MAAM,CAAC,CAAC,EAAE,CAAC,CAMb,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,QAAQ,GAAU,CAAC,EAAE,CAAC,EACjC,WAAW,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,KAC9B,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAIpB,CAAC"}