@evolu/common 6.0.1-preview.2 → 6.0.1-preview.21

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 (154) hide show
  1. package/dist/src/Assert.d.ts +6 -3
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +7 -4
  4. package/dist/src/Brand.d.ts +75 -0
  5. package/dist/src/Brand.d.ts.map +1 -0
  6. package/dist/src/Brand.js +1 -0
  7. package/dist/src/Buffer.d.ts +1 -1
  8. package/dist/src/Buffer.d.ts.map +1 -1
  9. package/dist/src/Buffer.js +1 -1
  10. package/dist/src/CallbackRegistry.d.ts +53 -0
  11. package/dist/src/CallbackRegistry.d.ts.map +1 -0
  12. package/dist/src/CallbackRegistry.js +25 -0
  13. package/dist/src/Console.d.ts +31 -6
  14. package/dist/src/Console.d.ts.map +1 -1
  15. package/dist/src/Console.js +72 -9
  16. package/dist/src/Crypto.d.ts +61 -34
  17. package/dist/src/Crypto.d.ts.map +1 -1
  18. package/dist/src/Crypto.js +29 -42
  19. package/dist/src/Evolu/Db.d.ts +159 -66
  20. package/dist/src/Evolu/Db.d.ts.map +1 -1
  21. package/dist/src/Evolu/Db.js +300 -705
  22. package/dist/src/Evolu/Diff.d.ts +3 -3
  23. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  24. package/dist/src/Evolu/Diff.js +7 -5
  25. package/dist/src/Evolu/Evolu.d.ts +200 -115
  26. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  27. package/dist/src/Evolu/Evolu.js +287 -157
  28. package/dist/src/Evolu/Internal.d.ts +0 -2
  29. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  30. package/dist/src/Evolu/Internal.js +0 -2
  31. package/dist/src/Evolu/LocalAuth.d.ts +144 -0
  32. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  33. package/dist/src/Evolu/LocalAuth.js +171 -0
  34. package/dist/src/Evolu/Owner.d.ts +209 -124
  35. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  36. package/dist/src/Evolu/Owner.js +118 -104
  37. package/dist/src/Evolu/Platform.d.ts +9 -7
  38. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  39. package/dist/src/Evolu/Protocol.d.ts +256 -211
  40. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  41. package/dist/src/Evolu/Protocol.js +558 -359
  42. package/dist/src/Evolu/Public.d.ts +6 -8
  43. package/dist/src/Evolu/Public.d.ts.map +1 -1
  44. package/dist/src/Evolu/Public.js +2 -3
  45. package/dist/src/Evolu/PublicKysely.js +3 -3
  46. package/dist/src/Evolu/Query.d.ts +2 -1
  47. package/dist/src/Evolu/Query.d.ts.map +1 -1
  48. package/dist/src/Evolu/Relay.d.ts +72 -4
  49. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  50. package/dist/src/Evolu/Relay.js +124 -13
  51. package/dist/src/Evolu/Schema.d.ts +129 -73
  52. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  53. package/dist/src/Evolu/Schema.js +169 -89
  54. package/dist/src/Evolu/Storage.d.ts +159 -14
  55. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  56. package/dist/src/Evolu/Storage.js +42 -32
  57. package/dist/src/Evolu/Sync.d.ts +68 -13
  58. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  59. package/dist/src/Evolu/Sync.js +414 -20
  60. package/dist/src/Evolu/Timestamp.d.ts +35 -27
  61. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  62. package/dist/src/Evolu/Timestamp.js +27 -18
  63. package/dist/src/ManyToManyMap.d.ts +74 -10
  64. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  65. package/dist/src/ManyToManyMap.js +41 -6
  66. package/dist/src/Number.d.ts +2 -1
  67. package/dist/src/Number.d.ts.map +1 -1
  68. package/dist/src/Random.d.ts +3 -2
  69. package/dist/src/Random.d.ts.map +1 -1
  70. package/dist/src/RefCountedResourceManager.d.ts +119 -0
  71. package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
  72. package/dist/src/RefCountedResourceManager.js +197 -0
  73. package/dist/src/Result.d.ts +176 -51
  74. package/dist/src/Result.d.ts.map +1 -1
  75. package/dist/src/Result.js +30 -241
  76. package/dist/src/Sqlite.d.ts +21 -4
  77. package/dist/src/Sqlite.d.ts.map +1 -1
  78. package/dist/src/Sqlite.js +50 -8
  79. package/dist/src/Task.d.ts +511 -0
  80. package/dist/src/Task.d.ts.map +1 -0
  81. package/dist/src/Task.js +410 -0
  82. package/dist/src/Time.d.ts +59 -0
  83. package/dist/src/Time.d.ts.map +1 -1
  84. package/dist/src/Time.js +87 -4
  85. package/dist/src/Type.d.ts +474 -327
  86. package/dist/src/Type.d.ts.map +1 -1
  87. package/dist/src/Type.js +502 -436
  88. package/dist/src/Types.d.ts +0 -74
  89. package/dist/src/Types.d.ts.map +1 -1
  90. package/dist/src/WebSocket.d.ts +5 -2
  91. package/dist/src/WebSocket.d.ts.map +1 -1
  92. package/dist/src/WebSocket.js +12 -13
  93. package/dist/src/Worker.d.ts +39 -11
  94. package/dist/src/Worker.d.ts.map +1 -1
  95. package/dist/src/Worker.js +22 -4
  96. package/dist/src/index.d.ts +3 -3
  97. package/dist/src/index.d.ts.map +1 -1
  98. package/dist/src/index.js +3 -3
  99. package/package.json +14 -13
  100. package/src/Assert.ts +8 -7
  101. package/src/Brand.ts +75 -0
  102. package/src/Buffer.ts +1 -1
  103. package/src/CallbackRegistry.ts +84 -0
  104. package/src/Console.ts +91 -11
  105. package/src/Crypto.ts +94 -79
  106. package/src/Evolu/Db.ts +528 -1027
  107. package/src/Evolu/Diff.ts +7 -5
  108. package/src/Evolu/Evolu.ts +682 -329
  109. package/src/Evolu/Internal.ts +0 -2
  110. package/src/Evolu/LocalAuth.ts +422 -0
  111. package/src/Evolu/Owner.ts +282 -227
  112. package/src/Evolu/Platform.ts +9 -9
  113. package/src/Evolu/Protocol.ts +788 -640
  114. package/src/Evolu/Public.ts +7 -14
  115. package/src/Evolu/PublicKysely.ts +3 -3
  116. package/src/Evolu/Query.ts +2 -1
  117. package/src/Evolu/Relay.ts +238 -18
  118. package/src/Evolu/Schema.ts +391 -191
  119. package/src/Evolu/Storage.ts +275 -55
  120. package/src/Evolu/Sync.ts +708 -37
  121. package/src/Evolu/Timestamp.ts +38 -35
  122. package/src/ManyToManyMap.ts +127 -24
  123. package/src/Number.ts +2 -6
  124. package/src/Random.ts +3 -2
  125. package/src/RefCountedResourceManager.ts +368 -0
  126. package/src/Result.ts +183 -53
  127. package/src/Sqlite.ts +59 -23
  128. package/src/Task.ts +779 -0
  129. package/src/Time.ts +168 -4
  130. package/src/Type.ts +708 -660
  131. package/src/Types.ts +0 -76
  132. package/src/WebSocket.ts +23 -17
  133. package/src/Worker.ts +72 -23
  134. package/src/index.ts +3 -3
  135. package/dist/src/Callbacks.d.ts +0 -20
  136. package/dist/src/Callbacks.d.ts.map +0 -1
  137. package/dist/src/Callbacks.js +0 -18
  138. package/dist/src/Evolu/Config.d.ts +0 -69
  139. package/dist/src/Evolu/Config.d.ts.map +0 -1
  140. package/dist/src/Evolu/Config.js +0 -9
  141. package/dist/src/Evolu/Kysely.d.ts +0 -6
  142. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  143. package/dist/src/Evolu/Kysely.js +0 -21
  144. package/dist/src/NanoId.d.ts +0 -27
  145. package/dist/src/NanoId.d.ts.map +0 -1
  146. package/dist/src/NanoId.js +0 -6
  147. package/dist/src/Promise.d.ts +0 -180
  148. package/dist/src/Promise.d.ts.map +0 -1
  149. package/dist/src/Promise.js +0 -176
  150. package/src/Callbacks.ts +0 -43
  151. package/src/Evolu/Config.ts +0 -83
  152. package/src/Evolu/Kysely.ts +0 -38
  153. package/src/NanoId.ts +0 -39
  154. 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 a reference-counted resource manager.
5
+ *
6
+ * This manager tracks which consumers are using which resources and maintains
7
+ * reference counts to know when it's safe to dispose resources. Resources are
8
+ * created on-demand and disposed with a configurable delay to avoid churn.
9
+ *
10
+ * ### Example Usage
11
+ *
12
+ * ```ts
13
+ * // WebSocket connections manager
14
+ * interface WebSocketConfig {
15
+ * readonly url: WebSocketUrl;
16
+ * }
17
+ *
18
+ * type WebSocketUrl = string & Brand<"WebSocketUrl">;
19
+ * type UserId = string & Brand<"UserId">;
20
+ *
21
+ * const wsManager = createRefCountedResourceManager<
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
+ * wsManager.addConsumer(user1, [
36
+ * { url: "ws://server1.com" as WebSocketUrl },
37
+ * { url: "ws://server2.com" as WebSocketUrl },
38
+ * ]);
39
+ * wsManager.addConsumer(user2, [
40
+ * { url: "ws://server1.com" as WebSocketUrl },
41
+ * ]);
42
+ *
43
+ * // Remove users - server1 stays alive (user2 still using it)
44
+ * wsManager.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 createRefCountedResourceManager = (config) => {
53
+ let isDisposed = false;
54
+ const resources = 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 (!resources.has(key)) {
67
+ const resource = config.createResource(resourceConfig);
68
+ resources.set(key, resource);
69
+ }
70
+ };
71
+ const scheduleDisposal = (key) => {
72
+ const timeout = setTimeout(() => {
73
+ const resource = resources.get(key);
74
+ if (resource) {
75
+ resource[Symbol.dispose]();
76
+ resources.delete(key);
77
+ }
78
+ disposalTimeouts.delete(key);
79
+ }, disposalDelay);
80
+ disposalTimeouts.set(key, timeout);
81
+ };
82
+ const manager = {
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 = resources.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 = resources.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 (!manager.hasConsumerAnyResource(consumer)) {
146
+ consumers.delete(consumerId);
147
+ }
148
+ return ok();
149
+ },
150
+ getResource: (key) => {
151
+ if (isDisposed)
152
+ return null;
153
+ return resources.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 (!manager.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 resources.values()) {
189
+ resource[Symbol.dispose]();
190
+ }
191
+ resources.clear();
192
+ consumerCounts.clear();
193
+ consumers.clear();
194
+ },
195
+ };
196
+ return manager;
197
+ };
@@ -1,36 +1,19 @@
1
1
  /**
2
2
  * 🛡️ Type-safe errors
3
3
  *
4
- * ## Intro
5
- *
6
4
  * The problem with throwing an exception in JavaScript is that the caught error
7
5
  * is always of an unknown type. The unknown type is a problem because we can't
8
6
  * 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:
7
+ * tell us.
17
8
  *
18
- * ```ts
19
- * type Result<T, E> = Ok<T> | Err<E>;
20
- *
21
- * interface Ok<T> {
22
- * readonly ok: true;
23
- * readonly value: T;
24
- * }
9
+ * Languages like Rust 🦀 or Haskell 📚 use a type-safe approach to error
10
+ * handling, where errors are explicitly represented as part of the return type,
11
+ * such as Result or Either, allowing the developer to handle errors safely.
12
+ * TypeScript can have this too via the `Result` type.
25
13
  *
26
- * interface Err<E> {
27
- * readonly ok: false;
28
- * readonly error: E;
29
- * }
30
- *
31
- * const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
32
- * const err = <E>(error: E): Err<E> => ({ ok: false, error });
33
- * ```
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.
34
17
  *
35
18
  * Now let's look at how `Result` can be used for safe JSON parsing:
36
19
  *
@@ -77,12 +60,10 @@
77
60
  *
78
61
  * Let's summarize it:
79
62
  *
80
- * - For synchronous safe code, use `ok` and `err`.
63
+ * - For safe code, use `ok` and `err`.
81
64
  * - For unsafe code, use `trySync` or `tryAsync`.
82
- * - For asynchronous safe code, use `Promise` with {@link Result}.
83
65
  *
84
- * Asynchronous safe (because of a Promise using Result) code is
85
- * straightforward:
66
+ * Asynchronous safe (because of a Promise using Result) code:
86
67
  *
87
68
  * ```ts
88
69
  * const fetchUser = async (
@@ -103,9 +84,33 @@
103
84
  * };
104
85
  * ```
105
86
  *
106
- * ## Examples
87
+ * ### Naming Convention
88
+ *
89
+ * - For values: `const user = getUser()`
90
+ * - For void operations: `const result = foo()` (unless it would clash)
91
+ * - For clashes, suffix the name: `const saveResult = save()`
92
+ *
93
+ * ```ts
94
+ * const processUser = () => {
95
+ * // we have a value
96
+ * const user = getUser();
97
+ * if (!user.ok) return user;
98
+ *
99
+ * // void operation
100
+ * const result = saveToDatabase(user.value);
101
+ * if (!result.ok) return result;
102
+ *
103
+ * // avoiding clash
104
+ * const deleteFromCacheResult = deleteFromCache();
105
+ * if (!deleteFromCacheResult.ok) return deleteFromCacheResult;
106
+ *
107
+ * return ok();
108
+ * };
109
+ * ```
110
+ *
111
+ * ### Examples
107
112
  *
108
- * ### Sequential Operations with Short-Circuiting
113
+ * #### Sequential Operations with Short-Circuiting
109
114
  *
110
115
  * When performing a sequence of operations where any failure should stop
111
116
  * further processing, use the `Result` type with early returns.
@@ -155,30 +160,120 @@
155
160
  * This approach ensures type-safe error handling, avoids nested try/catch
156
161
  * blocks, and clearly communicates the control flow.
157
162
  *
158
- * ### A function with two different errors:
163
+ * #### A function with two different errors:
159
164
  *
160
165
  * ```ts
161
166
  * const example = (value: string): Result<number, FooError | BarError> => {
162
167
  * const foo = getFoo(value);
163
168
  * if (!foo.ok) return foo;
164
169
  *
165
- * const bar = barize(foo.value);
170
+ * const bar = getBar(foo.value);
166
171
  * if (!bar.ok) return bar;
167
172
  *
168
173
  * return ok(barToNumber(bar.value));
169
174
  * };
170
175
  * ```
171
176
  *
172
- * ## FAQ
177
+ * ### Handling Unexpected Errors
178
+ *
179
+ * Even with disciplined use of `trySync` and `tryAsync`, unexpected errors can
180
+ * still occur due to programming mistakes, third-party library bugs, or edge
181
+ * cases. These should be logged for debugging, but **unexpected errors are not
182
+ * recoverable** - they represent bugs that must be fixed.
183
+ *
184
+ * **Important**: "Graceful shutdown" and error recovery can only come from
185
+ * expected errors handled via the `Result` type. Unexpected errors should fail
186
+ * fast - the operation fails immediately and the error bubbles up.
187
+ *
188
+ * #### In Browser Environments
189
+ *
190
+ * ```ts
191
+ * // Global error handler for unexpected errors
192
+ * window.addEventListener("error", (event) => {
193
+ * console.error("Uncaught error:", event.error);
194
+ * // Send to error reporting service
195
+ * errorReportingService.report(event.error);
196
+ * });
197
+ *
198
+ * // For unhandled promise rejections
199
+ * window.addEventListener("unhandledrejection", (event) => {
200
+ * console.error("Unhandled promise rejection:", event.reason);
201
+ * errorReportingService.report(event.reason);
202
+ * });
203
+ * ```
204
+ *
205
+ * #### In Node.js Environments
206
+ *
207
+ * ```ts
208
+ * // Handle uncaught exceptions - log and fail fast
209
+ * process.on("uncaughtException", (error) => {
210
+ * console.error("Uncaught exception:", error);
211
+ * errorReportingService.report(error);
212
+ * // Exit immediately - unexpected errors are not recoverable
213
+ * process.exit(1);
214
+ * });
215
+ *
216
+ * // Handle unhandled promise rejections
217
+ * process.on("unhandledRejection", (reason) => {
218
+ * console.error("Unhandled promise rejection:", reason);
219
+ * errorReportingService.report(reason);
220
+ * });
221
+ * ```
222
+ *
223
+ * These global handlers serve as a safety net to log and report unexpected
224
+ * errors for debugging purposes. They do not attempt recovery - unexpected
225
+ * errors represent bugs that must be fixed. The discipline of explicit error
226
+ * handling through the `Result` pattern remains the primary approach for all
227
+ * recoverable scenarios.
228
+ *
229
+ * ### FAQ
230
+ *
231
+ * #### When should a function return a plain value instead of `Result<T, E>`?
173
232
  *
174
- * ### What if my function doesn't return a value on success?
233
+ * Use `Result<T, E>` only when a function can fail with **known, expected
234
+ * errors** that callers need to handle. If a function cannot fail with a known
235
+ * error, return the value directly.
236
+ *
237
+ * - ✅ Return `Result<User, UserNotFoundError>` - can fail with a known error
238
+ * - ✅ Return `User` - cannot fail with a known error
239
+ * - ❌ Don't return `Result<User, never>` - unnecessary wrapper
240
+ *
241
+ * This keeps the codebase clean and makes error handling intentional. The type
242
+ * system communicates which operations can fail and which cannot.
243
+ *
244
+ * Unsafe code from external libraries (not under our control) should be wrapped
245
+ * with `trySync` or `tryAsync` at the boundaries. Once wrapped, if the error is
246
+ * not important to callers, functions can safely return plain values. If the
247
+ * error matters, use `Result` with a typed error.
248
+ *
249
+ * ```ts
250
+ * // ✅ Safe to return void - unsafe code is wrapped and error is handled
251
+ * const processData = (data: string): void => {
252
+ * const parseResult = trySync(
253
+ * () => JSON.parse(data),
254
+ * (error) => ({ type: "ParseError", message: String(error) }),
255
+ * );
256
+ *
257
+ * if (!parseResult.ok) {
258
+ * logError(parseResult.error);
259
+ * return;
260
+ * }
261
+ *
262
+ * // Continue with safe operations...
263
+ * };
264
+ *
265
+ * // ✅ Can call without try-catch since it returns void
266
+ * processData(jsonString);
267
+ * ```
268
+ *
269
+ * #### What if my function doesn't return a value on success?
175
270
  *
176
271
  * If your function performs an operation but doesn't need to return a value on
177
272
  * success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
178
273
  * than using `Result<true, E>` or `Result<null, E>` because it communicates
179
274
  * that the function doesn't produce a value but can produce errors.
180
275
  *
181
- * ### How do I short-circuit processing of an array on the first error?
276
+ * #### How do I short-circuit processing of an array on the first error?
182
277
  *
183
278
  * If you want to stop processing as soon as an error occurs (short-circuit),
184
279
  * you should produce and check each `Result` inside a loop:
@@ -194,12 +289,11 @@
194
289
  * // All queries succeeded
195
290
  * ```
196
291
  *
197
- * ### How do I handle an array of operations and short-circuit on the first error?
292
+ * #### How do I handle an array of operations and short-circuit on the first error?
198
293
  *
199
294
  * 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:
295
+ * _lazy_—that is, represent each operation as a function. This way, you only
296
+ * execute each operation as needed, and can stop on the first error:
203
297
  *
204
298
  * ```ts
205
299
  * import type { LazyValue } from "./Function";
@@ -232,16 +326,7 @@
232
326
  * above) over monadic helpers. Imperative code is generally more readable,
233
327
  * easier to debug, and more familiar to most JavaScript and TypeScript
234
328
  * 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.
329
+ * obscure control flow and make debugging harder.
245
330
  */
246
331
  export type Result<T, E> = Ok<T> | Err<E>;
247
332
  /** A successful {@link Result}. */
@@ -297,6 +382,18 @@ export interface Err<E> {
297
382
  readonly ok: false;
298
383
  readonly error: E;
299
384
  }
385
+ /**
386
+ * Extracts the value type from a {@link Result}.
387
+ *
388
+ * @category Utilities
389
+ */
390
+ export type InferOk<R extends Result<any, any>> = R extends Ok<infer T> ? T : never;
391
+ /**
392
+ * Extracts the error type from a {@link Result}.
393
+ *
394
+ * @category Utilities
395
+ */
396
+ export type InferErr<R extends Result<any, any>> = R extends Err<infer E> ? E : never;
300
397
  /**
301
398
  * Creates an {@link Ok} result.
302
399
  *
@@ -346,8 +443,33 @@ export declare const err: <E>(error: E) => Err<E>;
346
443
  * const config = getOrThrow(loadConfig());
347
444
  * // Safe to use config here
348
445
  * ```
446
+ *
447
+ * Throws: `Error` with the original error attached as `cause`.
349
448
  */
350
449
  export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
450
+ /**
451
+ * Extracts the value from a {@link Result} if it is an `Ok`, or returns `null`
452
+ * if it is an `Err`.
453
+ *
454
+ * **Intended usage:**
455
+ *
456
+ * - When you need to convert a `Result` to a nullable value for APIs that expect
457
+ * `T | null`.
458
+ * - When the error is not important and you just want the value or nothing.
459
+ *
460
+ * ### Example
461
+ *
462
+ * ```ts
463
+ * const parseResult = parseJson('{"key": "value"}');
464
+ * const value = getOrNull(parseResult);
465
+ * // value is unknown | null
466
+ *
467
+ * if (value != null) {
468
+ * console.log("Parsed value:", value);
469
+ * }
470
+ * ```
471
+ */
472
+ export declare const getOrNull: <T, E>(result: Result<T, E>) => T | null;
351
473
  /**
352
474
  * Wraps synchronous functions that may throw exceptions, returning a
353
475
  * {@link Result}.
@@ -368,7 +490,10 @@ export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
368
490
  * const parseJson = (value: string): Result<unknown, ParseJsonError> =>
369
491
  * trySync(
370
492
  * () => JSON.parse(value) as unknown,
371
- * (error) => ({ type: "ParseJsonError", message: String(error) }),
493
+ * (error): ParseJsonError => ({
494
+ * type: "ParseJsonError",
495
+ * message: String(error),
496
+ * }),
372
497
  * );
373
498
  * ```
374
499
  */
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyUG;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"}