@evolu/common 6.0.1-preview.8 → 7.0.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 (184) hide show
  1. package/README.md +2 -2
  2. package/dist/src/Array.d.ts +256 -12
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +215 -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 +44 -0
  15. package/dist/src/Cache.d.ts.map +1 -0
  16. package/dist/src/Cache.js +52 -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 +31 -6
  21. package/dist/src/Console.d.ts.map +1 -1
  22. package/dist/src/Console.js +72 -9
  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 +40 -53
  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 +214 -134
  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 +0 -2
  36. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  37. package/dist/src/Evolu/Internal.js +0 -2
  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 +600 -454
  49. package/dist/src/Evolu/Public.d.ts +6 -8
  50. package/dist/src/Evolu/Public.d.ts.map +1 -1
  51. package/dist/src/Evolu/Public.js +2 -3
  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/Platform.d.ts +20 -0
  85. package/dist/src/Platform.d.ts.map +1 -0
  86. package/dist/src/Platform.js +22 -0
  87. package/dist/src/Random.d.ts +3 -2
  88. package/dist/src/Random.d.ts.map +1 -1
  89. package/dist/src/Relation.d.ts +79 -0
  90. package/dist/src/Relation.d.ts.map +1 -0
  91. package/dist/src/Relation.js +127 -0
  92. package/dist/src/Resources.d.ts +118 -0
  93. package/dist/src/Resources.d.ts.map +1 -0
  94. package/dist/src/Resources.js +197 -0
  95. package/dist/src/Result.d.ts +184 -52
  96. package/dist/src/Result.d.ts.map +1 -1
  97. package/dist/src/Result.js +30 -241
  98. package/dist/src/Skiplist.js +2 -1
  99. package/dist/src/Sqlite.d.ts +89 -5
  100. package/dist/src/Sqlite.d.ts.map +1 -1
  101. package/dist/src/Sqlite.js +136 -9
  102. package/dist/src/Task.d.ts +586 -0
  103. package/dist/src/Task.d.ts.map +1 -0
  104. package/dist/src/Task.js +469 -0
  105. package/dist/src/Time.d.ts +66 -1
  106. package/dist/src/Time.d.ts.map +1 -1
  107. package/dist/src/Time.js +99 -5
  108. package/dist/src/Type.d.ts +676 -343
  109. package/dist/src/Type.d.ts.map +1 -1
  110. package/dist/src/Type.js +718 -467
  111. package/dist/src/Types.d.ts +1 -75
  112. package/dist/src/Types.d.ts.map +1 -1
  113. package/dist/src/WebSocket.d.ts +5 -2
  114. package/dist/src/WebSocket.d.ts.map +1 -1
  115. package/dist/src/WebSocket.js +12 -18
  116. package/dist/src/Worker.d.ts +39 -11
  117. package/dist/src/Worker.d.ts.map +1 -1
  118. package/dist/src/Worker.js +22 -4
  119. package/dist/src/index.d.ts +8 -3
  120. package/dist/src/index.d.ts.map +1 -1
  121. package/dist/src/index.js +8 -3
  122. package/package.json +15 -14
  123. package/src/Array.ts +301 -19
  124. package/src/Assert.ts +0 -21
  125. package/src/Brand.ts +75 -0
  126. package/src/Buffer.ts +7 -7
  127. package/src/Cache.ts +85 -0
  128. package/src/Callbacks.ts +62 -22
  129. package/src/Console.ts +91 -11
  130. package/src/Crypto.ts +94 -90
  131. package/src/Evolu/Db.ts +519 -1026
  132. package/src/Evolu/Diff.ts +7 -5
  133. package/src/Evolu/Evolu.ts +477 -361
  134. package/src/Evolu/Internal.ts +0 -2
  135. package/src/Evolu/LocalAuth.ts +463 -0
  136. package/src/Evolu/Owner.ts +355 -228
  137. package/src/Evolu/Platform.ts +9 -9
  138. package/src/Evolu/Protocol.ts +842 -742
  139. package/src/Evolu/Public.ts +7 -14
  140. package/src/Evolu/PublicKysely.ts +4 -5
  141. package/src/Evolu/Query.ts +3 -2
  142. package/src/Evolu/Relay.ts +406 -103
  143. package/src/Evolu/Schema.ts +323 -91
  144. package/src/Evolu/Storage.ts +559 -137
  145. package/src/Evolu/Sync.ts +819 -36
  146. package/src/Evolu/Timestamp.ts +90 -58
  147. package/src/Identicon.ts +197 -0
  148. package/src/Instances.ts +90 -0
  149. package/src/Number.ts +6 -10
  150. package/src/Object.ts +13 -5
  151. package/src/Platform.ts +26 -0
  152. package/src/Random.ts +3 -2
  153. package/src/Relation.ts +234 -0
  154. package/src/Resources.ts +367 -0
  155. package/src/Result.ts +191 -54
  156. package/src/Skiplist.ts +1 -1
  157. package/src/Sqlite.ts +152 -17
  158. package/src/Task.ts +901 -0
  159. package/src/Time.ts +180 -5
  160. package/src/Type.ts +1135 -730
  161. package/src/Types.ts +1 -77
  162. package/src/WebSocket.ts +27 -25
  163. package/src/Worker.ts +72 -23
  164. package/src/index.ts +8 -3
  165. package/dist/src/Evolu/Config.d.ts +0 -69
  166. package/dist/src/Evolu/Config.d.ts.map +0 -1
  167. package/dist/src/Evolu/Config.js +0 -9
  168. package/dist/src/Evolu/Kysely.d.ts +0 -6
  169. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  170. package/dist/src/Evolu/Kysely.js +0 -21
  171. package/dist/src/ManyToManyMap.d.ts +0 -26
  172. package/dist/src/ManyToManyMap.d.ts.map +0 -1
  173. package/dist/src/ManyToManyMap.js +0 -92
  174. package/dist/src/NanoId.d.ts +0 -27
  175. package/dist/src/NanoId.d.ts.map +0 -1
  176. package/dist/src/NanoId.js +0 -6
  177. package/dist/src/Promise.d.ts +0 -180
  178. package/dist/src/Promise.d.ts.map +0 -1
  179. package/dist/src/Promise.js +0 -176
  180. package/src/Evolu/Config.ts +0 -83
  181. package/src/Evolu/Kysely.ts +0 -38
  182. package/src/ManyToManyMap.ts +0 -140
  183. package/src/NanoId.ts +0 -39
  184. package/src/Promise.ts +0 -295
@@ -0,0 +1,586 @@
1
+ /**
2
+ * ⚡ Lazy, cancellable Promise that returns Result instead of throwing
3
+ *
4
+ * @module
5
+ */
6
+ import { Result } from "./Result.js";
7
+ import { Duration } from "./Time.js";
8
+ import { PositiveInt } from "./Type.js";
9
+ /**
10
+ * `Task` is a lazy, cancellable Promise that returns {@link Result} instead of
11
+ * throwing.
12
+ *
13
+ * In other words, Task is a function that creates a Promise when it's called.
14
+ * This laziness allows safe composition, e.g. retry logic because it prevents
15
+ * eager execution.
16
+ *
17
+ * ### Cancellation
18
+ *
19
+ * Tasks support optional cancellation via signal in {@link TaskContext}. When a
20
+ * Task is called without a signal, it cannot be cancelled and {@link AbortError}
21
+ * will never be returned. When called with a signal, the Task can be cancelled
22
+ * and AbortError is added to the error union with precise type safety.
23
+ *
24
+ * When composing Tasks, we typically have context and want to abort ASAP by
25
+ * passing it through. However, there are valid cases where we don't want to
26
+ * abort because we need some atomic unit to complete. For simple scripts and
27
+ * tests, omitting context is fine.
28
+ *
29
+ * ### Task Helpers
30
+ *
31
+ * - {@link toTask} - Convert async function to Task
32
+ * - {@link wait} - Delay execution for a specified {@link Duration}
33
+ * - {@link timeout} - Add timeout to any Task
34
+ * - {@link retry} - Retry failed Tasks with configurable backoff
35
+ *
36
+ * ### Example
37
+ *
38
+ * ```ts
39
+ * interface FetchError {
40
+ * readonly type: "FetchError";
41
+ * readonly error: unknown;
42
+ * }
43
+ *
44
+ * // Task version of fetch with proper error handling and cancellation support.
45
+ * const fetch = (url: string) =>
46
+ * toTask((context) =>
47
+ * tryAsync(
48
+ * () => globalThis.fetch(url, { signal: context?.signal ?? null }),
49
+ * (error): FetchError => ({ type: "FetchError", error }),
50
+ * ),
51
+ * );
52
+ *
53
+ * // `satisfies` shows the expected type signature.
54
+ * fetch satisfies (url: string) => Task<Response, FetchError>;
55
+ *
56
+ * // Add timeout to prevent hanging
57
+ * const fetchWithTimeout = (url: string) => timeout("30s", fetch(url));
58
+ *
59
+ * fetchWithTimeout satisfies (
60
+ * url: string,
61
+ * ) => Task<Response, TimeoutError | FetchError>;
62
+ *
63
+ * // Add retry for resilience
64
+ * const fetchWithRetry = (url: string) =>
65
+ * retry(
66
+ * {
67
+ * retries: PositiveInt.orThrow(3),
68
+ * initialDelay: "100ms",
69
+ * },
70
+ * fetchWithTimeout(url),
71
+ * );
72
+ *
73
+ * fetchWithRetry satisfies (
74
+ * url: string,
75
+ * ) => Task<
76
+ * Response,
77
+ * TimeoutError | FetchError | RetryError<TimeoutError | FetchError>
78
+ * >;
79
+ *
80
+ * const semaphore = createSemaphore(PositiveInt.orThrow(2));
81
+ *
82
+ * // Control concurrency with semaphore
83
+ * const fetchWithPermit = (url: string) =>
84
+ * semaphore.withPermit(fetchWithRetry(url));
85
+ *
86
+ * fetchWithPermit satisfies (url: string) => Task<
87
+ * Response,
88
+ * | TimeoutError
89
+ * | FetchError
90
+ * | AbortError // Semaphore dispose aborts Tasks
91
+ * | RetryError<TimeoutError | FetchError>
92
+ * >;
93
+ *
94
+ * // Usage
95
+ * const results = await Promise.all(
96
+ * [
97
+ * "https://api.example.com/users",
98
+ * "https://api.example.com/posts",
99
+ * "https://api.example.com/comments",
100
+ * ]
101
+ * .map(fetchWithPermit)
102
+ * .map((task) => task()),
103
+ * );
104
+ *
105
+ * results satisfies Array<
106
+ * Result<
107
+ * Response,
108
+ * | AbortError
109
+ * | TimeoutError
110
+ * | FetchError
111
+ * | RetryError<TimeoutError | FetchError>
112
+ * >
113
+ * >;
114
+ *
115
+ * // Handle results
116
+ * for (const result of results) {
117
+ * if (result.ok) {
118
+ * // Process successful response
119
+ * const response = result.value;
120
+ * expect(response).toBeInstanceOf(Response);
121
+ * } else {
122
+ * // Handle error (TimeoutError, FetchError, RetryError, or AbortError)
123
+ * expect(result.error).toBeDefined();
124
+ * }
125
+ * }
126
+ *
127
+ * // Cancellation support
128
+ * const controller = new AbortController();
129
+ * const cancelableTask = fetchWithPermit("https://api.example.com/data");
130
+ *
131
+ * // Start task
132
+ * const promise = cancelableTask(controller);
133
+ *
134
+ * // Cancel after some time
135
+ * setTimeout(() => {
136
+ * controller.abort("User cancelled");
137
+ * }, 1000);
138
+ *
139
+ * const _result = await promise;
140
+ * // Result will be AbortError if cancelled
141
+ * ```
142
+ *
143
+ * ### Dependency Injection Integration
144
+ *
145
+ * Tasks integrate naturally with Evolu's DI pattern. Use `deps` for static
146
+ * dependencies and `TaskContext` for execution context like cancellation. Usage
147
+ * follows the pattern: deps → arguments → execution context.
148
+ */
149
+ export interface Task<T, E> {
150
+ /**
151
+ * Invoke the Task.
152
+ *
153
+ * Provide a context with an AbortSignal to enable cancellation. When called
154
+ * without a signal, {@link AbortError} cannot occur and the error type narrows
155
+ * accordingly.
156
+ *
157
+ * ### Example
158
+ *
159
+ * ```ts
160
+ * interface FetchError {
161
+ * readonly type: "FetchError";
162
+ * readonly error: unknown;
163
+ * }
164
+ *
165
+ * // Task version of fetch with proper error handling and cancellation support.
166
+ * const fetch = (url: string) =>
167
+ * toTask((context) =>
168
+ * tryAsync(
169
+ * () => globalThis.fetch(url, { signal: context?.signal ?? null }),
170
+ * (error): FetchError => ({ type: "FetchError", error }),
171
+ * ),
172
+ * );
173
+ *
174
+ * // `satisfies` shows the expected type signature.
175
+ * fetch satisfies (url: string) => Task<Response, FetchError>;
176
+ *
177
+ * const result1 = await fetch("https://api.example.com/data")();
178
+ * expectTypeOf(result1).toEqualTypeOf<Result<Response, FetchError>>();
179
+ *
180
+ * // With AbortController
181
+ * const controller = new AbortController();
182
+ * const result2 = await fetch("https://api.example.com/data")(
183
+ * controller,
184
+ * );
185
+ * expectTypeOf(result2).toEqualTypeOf<
186
+ * Result<Response, FetchError | AbortError>
187
+ * >();
188
+ * ```
189
+ */
190
+ <TContext extends TaskContext | undefined = undefined>(context?: TContext): Promise<Result<T, TContext extends {
191
+ signal: AbortSignal;
192
+ } ? E | AbortError : E>>;
193
+ }
194
+ /** Context passed to {@link Task}s for cancellation. */
195
+ export interface TaskContext {
196
+ /** Signal for cancellation */
197
+ readonly signal?: AbortSignal;
198
+ }
199
+ /** Error returned when a {@link Task} is cancelled via AbortSignal. */
200
+ export interface AbortError {
201
+ readonly type: "AbortError";
202
+ readonly reason?: unknown;
203
+ }
204
+ /**
205
+ * Converts async function returning {@link Result} to a {@link Task}.
206
+ *
207
+ * ### Example
208
+ *
209
+ * ```ts
210
+ * interface FetchError {
211
+ * readonly type: "FetchError";
212
+ * readonly error: unknown;
213
+ * }
214
+ *
215
+ * // Task version of fetch with proper error handling and cancellation support.
216
+ * const fetch = (url: string) =>
217
+ * toTask((context) =>
218
+ * tryAsync(
219
+ * () => globalThis.fetch(url, { signal: context?.signal ?? null }),
220
+ * (error): FetchError => ({ type: "FetchError", error }),
221
+ * ),
222
+ * );
223
+ *
224
+ * // `satisfies` shows the expected type signature.
225
+ * fetch satisfies (url: string) => Task<Response, FetchError>;
226
+ *
227
+ * const result1 = await fetch("https://api.example.com/data")();
228
+ * result1 satisfies Result<Response, FetchError>;
229
+ *
230
+ * // With AbortController
231
+ * const controller = new AbortController();
232
+ * const result2 = await fetch("https://api.example.com/data")(controller);
233
+ * result2 satisfies Result<Response, FetchError | AbortError>;
234
+ * ```
235
+ */
236
+ export declare const toTask: <T, E>(fn: (context?: TaskContext) => Promise<Result<T, E>>) => Task<T, E>;
237
+ /**
238
+ * Creates a {@link Task} that waits for the specified duration.
239
+ *
240
+ * ### Example
241
+ *
242
+ * ```ts
243
+ * const result1 = await wait("10ms")();
244
+ * result1 satisfies Result<void, never>;
245
+ *
246
+ * // With AbortController
247
+ * const controller = new AbortController();
248
+ * const result2 = await wait("10ms")(controller);
249
+ * result2 satisfies Result<void, AbortError>;
250
+ * ```
251
+ */
252
+ export declare const wait: (duration: Duration) => Task<void, never>;
253
+ /** Error returned when {@link timeout} exceeds the specified duration. */
254
+ export interface TimeoutError {
255
+ readonly type: "TimeoutError";
256
+ readonly timeoutMs: number;
257
+ }
258
+ /**
259
+ * Adds timeout behavior to a {@link Task}.
260
+ *
261
+ * ### Example
262
+ *
263
+ * ```ts
264
+ * interface FetchError {
265
+ * readonly type: "FetchError";
266
+ * readonly error: unknown;
267
+ * }
268
+ *
269
+ * // Task version of fetch with proper error handling and cancellation support.
270
+ * const fetch = (url: string) =>
271
+ * toTask((context) =>
272
+ * tryAsync(
273
+ * () => globalThis.fetch(url, { signal: context?.signal ?? null }),
274
+ * (error): FetchError => ({ type: "FetchError", error }),
275
+ * ),
276
+ * );
277
+ *
278
+ * // `satisfies` shows the expected type signature.
279
+ * fetch satisfies (url: string) => Task<Response, FetchError>;
280
+ *
281
+ * const fetchWithTimeout = (url: string) => timeout("2m", fetch(url));
282
+ *
283
+ * const result1 = await fetchWithTimeout("https://api.example.com/data")();
284
+ * result1 satisfies Result<Response, FetchError | TimeoutError>;
285
+ *
286
+ * // With AbortController
287
+ * const controller = new AbortController();
288
+ * const result2 = await fetchWithTimeout("https://api.example.com/data")(
289
+ * controller,
290
+ * );
291
+ * result2 satisfies Result<
292
+ * Response,
293
+ * FetchError | TimeoutError | AbortError
294
+ * >;
295
+ * ```
296
+ */
297
+ export declare const timeout: <T, E>(duration: Duration, task: Task<T, E>) => Task<T, E | TimeoutError>;
298
+ /** Options for configuring {@link retry} behavior. */
299
+ export interface RetryOptions<E> {
300
+ /** Number of retry attempts after the initial failure. */
301
+ readonly retries: PositiveInt;
302
+ /**
303
+ * Initial delay for exponential backoff (1st retry uses this, 2nd uses
304
+ * this×factor, 3rd uses this×factor², etc.). Actual delays are randomized by
305
+ * {@link RetryOptions.jitter}.
306
+ */
307
+ readonly initialDelay?: Duration;
308
+ /** Maximum delay between retries. */
309
+ readonly maxDelay?: Duration;
310
+ /** Exponential backoff multiplier. */
311
+ readonly factor?: number;
312
+ /** Random jitter factor (0-1) to prevent thundering herd. */
313
+ readonly jitter?: number;
314
+ /**
315
+ * Predicate to determine if error should trigger retry. Receives AbortError
316
+ * too.
317
+ */
318
+ readonly retryable?: (error: E | AbortError) => boolean;
319
+ /** Callback invoked before each retry attempt. */
320
+ readonly onRetry?: (error: E, attempt: number, delay: number) => void;
321
+ }
322
+ /** Error returned when {@link retry} exhausts all retry attempts. */
323
+ export interface RetryError<E> {
324
+ readonly type: "RetryError";
325
+ readonly cause: E;
326
+ readonly attempts: number;
327
+ }
328
+ /**
329
+ * Adds retry logic with exponential backoff and jitter to a {@link Task}.
330
+ *
331
+ * ### Example
332
+ *
333
+ * ```ts
334
+ * interface FetchError {
335
+ * readonly type: "FetchError";
336
+ * readonly error: unknown;
337
+ * }
338
+ *
339
+ * // Task version of fetch with proper error handling and cancellation support.
340
+ * const fetch = (url: string) =>
341
+ * toTask((context) =>
342
+ * tryAsync(
343
+ * () => globalThis.fetch(url, { signal: context?.signal ?? null }),
344
+ * (error): FetchError => ({ type: "FetchError", error }),
345
+ * ),
346
+ * );
347
+ *
348
+ * // `satisfies` shows the expected type signature.
349
+ * fetch satisfies (url: string) => Task<Response, FetchError>;
350
+ *
351
+ * const fetchWithRetry = (url: string) =>
352
+ * retry({ retries: PositiveInt.orThrow(3) }, fetch(url));
353
+ *
354
+ * const result1 = await fetchWithRetry("https://api.example.com/data")();
355
+ * result1 satisfies Result<Response, FetchError | RetryError<FetchError>>;
356
+ *
357
+ * // With AbortController
358
+ * const controller = new AbortController();
359
+ * const result2 = await fetchWithRetry("https://api.example.com/data")(
360
+ * controller,
361
+ * );
362
+ * result2 satisfies Result<
363
+ * Response,
364
+ * FetchError | RetryError<FetchError> | AbortError
365
+ * >;
366
+ * ```
367
+ */
368
+ export declare const retry: <T, E>({ retries, initialDelay, maxDelay, factor, jitter, retryable, onRetry, }: RetryOptions<E>, task: Task<T, E>) => Task<T, E | RetryError<E>>;
369
+ /**
370
+ * A semaphore that limits the number of concurrent async Tasks.
371
+ *
372
+ * For mutual exclusion (limiting to exactly one Task), consider using
373
+ * {@link Mutex} instead.
374
+ *
375
+ * @see {@link createSemaphore} to create a semaphore instance.
376
+ */
377
+ export interface Semaphore extends Disposable {
378
+ /**
379
+ * Executes a Task while holding a semaphore permit.
380
+ *
381
+ * The Task will wait until a permit is available before executing. Supports
382
+ * cancellation via AbortSignal - if the signal is aborted while waiting for a
383
+ * permit or during execution, the Task is cancelled and permits are properly
384
+ * released.
385
+ */
386
+ readonly withPermit: <T, E>(task: Task<T, E>) => Task<T, E | AbortError>;
387
+ }
388
+ /**
389
+ * Creates a semaphore that limits concurrent async Tasks to the specified
390
+ * count.
391
+ *
392
+ * A semaphore controls access to a resource by maintaining a count of available
393
+ * permits. Tasks acquire a permit before executing and release it when
394
+ * complete.
395
+ *
396
+ * For mutual exclusion (exactly one Task at a time), consider using
397
+ * {@link createMutex} instead.
398
+ *
399
+ * ### Example
400
+ *
401
+ * ```ts
402
+ * // Allow maximum 3 concurrent Tasks
403
+ * const semaphore = createSemaphore(PositiveInt.orThrow(3));
404
+ *
405
+ * let currentConcurrent = 0;
406
+ * const events: Array<string> = [];
407
+ *
408
+ * const fetchData = (id: number) =>
409
+ * toTask<number, never>(async (context) => {
410
+ * currentConcurrent++;
411
+ * events.push(`start ${id} (concurrent: ${currentConcurrent})`);
412
+ *
413
+ * await wait("10ms")(context);
414
+ *
415
+ * currentConcurrent--;
416
+ * events.push(`end ${id} (concurrent: ${currentConcurrent})`);
417
+ * return ok(id * 10);
418
+ * });
419
+ *
420
+ * // These will execute with at most 3 running concurrently
421
+ * const results = await Promise.all([
422
+ * semaphore.withPermit(fetchData(1))(),
423
+ * semaphore.withPermit(fetchData(2))(),
424
+ * semaphore.withPermit(fetchData(3))(),
425
+ * semaphore.withPermit(fetchData(4))(), // waits for one above to complete
426
+ * semaphore.withPermit(fetchData(5))(), // waits for permit
427
+ * ]);
428
+ *
429
+ * expect(results.map(getOrThrow)).toEqual([10, 20, 30, 40, 50]);
430
+ * expect(events).toMatchInlineSnapshot(`
431
+ * [
432
+ * "start 1 (concurrent: 1)",
433
+ * "start 2 (concurrent: 2)",
434
+ * "start 3 (concurrent: 3)",
435
+ * "end 1 (concurrent: 2)",
436
+ * "start 4 (concurrent: 3)",
437
+ * "end 2 (concurrent: 2)",
438
+ * "start 5 (concurrent: 3)",
439
+ * "end 3 (concurrent: 2)",
440
+ * "end 4 (concurrent: 1)",
441
+ * "end 5 (concurrent: 0)",
442
+ * ]
443
+ * `);
444
+ * ```
445
+ */
446
+ export declare const createSemaphore: (maxConcurrent: PositiveInt) => Semaphore;
447
+ /**
448
+ * A mutex (mutual exclusion) that ensures only one Task runs at a time.
449
+ *
450
+ * This is a specialized version of a {@link Semaphore} with a permit count of 1.
451
+ *
452
+ * @see {@link createMutex} to create a mutex instance.
453
+ */
454
+ export interface Mutex extends Disposable {
455
+ /**
456
+ * Executes a Task while holding the mutex lock.
457
+ *
458
+ * Only one Task can hold the lock at a time. Other Tasks will wait until the
459
+ * lock is released. Supports cancellation via AbortSignal.
460
+ */
461
+ readonly withLock: <T, E>(task: Task<T, E>) => Task<T, E | AbortError>;
462
+ }
463
+ /**
464
+ * Creates a new mutex for ensuring mutual exclusion.
465
+ *
466
+ * A mutex is a {@link createSemaphore} with exactly one permit, ensuring that
467
+ * only one Task can execute at a time.
468
+ *
469
+ * ### Example
470
+ *
471
+ * ```ts
472
+ * const mutex = createMutex();
473
+ *
474
+ * const updateTask = (id: number) =>
475
+ * toTask((context) =>
476
+ * tryAsync(
477
+ * () => updateSharedResource(id, context),
478
+ * (error): UpdateError => ({ type: "UpdateError", error }),
479
+ * ),
480
+ * );
481
+ *
482
+ * // These Tasks will execute one at a time
483
+ * const results = await Promise.all([
484
+ * mutex.withLock(updateTask(1))(),
485
+ * mutex.withLock(updateTask(2))(),
486
+ * mutex.withLock(updateTask(3))(),
487
+ * ]);
488
+ * ```
489
+ */
490
+ export declare const createMutex: () => Mutex;
491
+ /**
492
+ * Schedule a task to run after all interactions (animations, gestures,
493
+ * navigation) have completed.
494
+ *
495
+ * This uses `requestIdleCallback` when available, otherwise falls back to
496
+ * `setTimeout(0)` for cross-platform compatibility.
497
+ *
498
+ * ### Example
499
+ *
500
+ * ```ts
501
+ * const processDataTask: Task<void, ProcessError> = toTask(async () => {
502
+ * // Heavy processing work
503
+ * return ok();
504
+ * });
505
+ *
506
+ * // Schedule the task to run when idle
507
+ * void requestIdleTask(processDataTask)();
508
+ * ```
509
+ */
510
+ export declare const requestIdleTask: <T, E>(task: Task<T, E>) => Task<T, E>;
511
+ /**
512
+ * Represents a value that can be either synchronous or asynchronous.
513
+ *
514
+ * This type is useful for functions that may complete synchronously or
515
+ * asynchronously depending on runtime conditions (e.g., cache hit vs network
516
+ * fetch).
517
+ *
518
+ * ### Why MaybeAsync?
519
+ *
520
+ * When a function can be sync or async, the typical approaches are:
521
+ *
522
+ * 1. **Always return Promise** - Simple but forces microtask overhead even for
523
+ * sync values (see "await always adds microtask" test in Task.test.ts)
524
+ * 2. **Use callbacks** - Can avoid microtask, but calling code must still `await`
525
+ * for sane composition, which adds microtask anyway
526
+ * 3. **Return `T | PromiseLike<T>`** - Calling code can check the value and only
527
+ * `await` when needed, avoiding microtask overhead for sync cases
528
+ *
529
+ * The third approach (MaybeAsync) provides:
530
+ *
531
+ * - **Performance**: No microtask overhead for synchronous operations
532
+ * - **Reliability**: No interleaving via microtask queue when operations are
533
+ * _synchronous_, reducing need for mutexes to protect shared state
534
+ *
535
+ * ### Example
536
+ *
537
+ * ```ts
538
+ * // Function that may be sync or async
539
+ * const getData = (id: string): MaybeAsync<Data> => {
540
+ * const cached = cache.get(id);
541
+ * if (cached) return cached; // Sync path
542
+ * return fetchData(id); // Async path
543
+ * };
544
+ *
545
+ * // Caller can optimize based on actual behavior
546
+ * const result = getData(id);
547
+ * const data = isAsync(result) ? await result : result;
548
+ * ```
549
+ *
550
+ * ### Alternative Approaches
551
+ *
552
+ * It's possible to eliminate the sync/async distinction using complex
553
+ * frameworks with custom schedulers. However, such frameworks require depending
554
+ * on other people's code that controls how your code executes, resulting in
555
+ * more complex stack traces and debugging experiences. With MaybeAsync, we
556
+ * don't need that machinery - it works directly with JavaScript's native
557
+ * primitives and TypeScript's type system.
558
+ *
559
+ * ### TODO: Consider
560
+ *
561
+ * Use MaybeAsync in Task and Task helpers to preserve synchronous execution
562
+ * when possible (e.g., mutex with available permit, retry on first success).
563
+ */
564
+ export type MaybeAsync<T> = T | PromiseLike<T>;
565
+ /**
566
+ * Type guard to check if a {@link MaybeAsync} value is async (a promise).
567
+ *
568
+ * This function narrows the type of a {@link MaybeAsync} value, allowing you to
569
+ * conditionally `await` only when necessary.
570
+ *
571
+ * ### Example
572
+ *
573
+ * ```ts
574
+ * const getData = (id: string): MaybeAsync<Data> => {
575
+ * const cached = cache.get(id);
576
+ * if (cached) return cached; // Sync path
577
+ * return fetchData(id); // Async path
578
+ * };
579
+ *
580
+ * const result = getData(id);
581
+ * const data = isAsync(result) ? await result : result;
582
+ * // No microtask overhead when cached!
583
+ * ```
584
+ */
585
+ export declare const isAsync: <T>(value: MaybeAsync<T>) => value is T extends PromiseLike<unknown> ? never : PromiseLike<T>;
586
+ //# sourceMappingURL=Task.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Task.d.ts","sourceRoot":"","sources":["../../src/Task.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,MAAM,EAAW,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAA4B,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAkB,WAAW,EAAE,MAAM,WAAW,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2IG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC;IACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IAEH,CAAC,QAAQ,SAAS,WAAW,GAAG,SAAS,GAAG,SAAS,EACnD,OAAO,CAAC,EAAE,QAAQ,GACjB,OAAO,CACR,MAAM,CAAC,CAAC,EAAE,QAAQ,SAAS;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,GAAG,CAAC,GAAG,UAAU,GAAG,CAAC,CAAC,CACzE,CAAC;CACH;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAgDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,CAAC,EACzB,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KACnD,IAAI,CAAC,CAAC,EAAE,CAAC,CAsCM,CAAC;AAiBnB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,IAAI,GAAI,UAAU,QAAQ,KAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAkBvD,CAAC;AAEJ,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,CAAC,EAC1B,UAAU,QAAQ,EAClB,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KACf,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,YAAY,CAcvB,CAAC;AAEL,sDAAsD;AACtD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAE9B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC;IAEjC,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAE7B,sCAAsC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,UAAU,KAAK,OAAO,CAAC;IAExD,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvE;AAED,qEAAqE;AACrE,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EACxB,0EAQG,YAAY,CAAC,CAAC,CAAC,EAClB,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KACf,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAkDxB,CAAC;AAEL;;;;;;;GAOG;AACH,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC;CAC1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,eAAe,GAAI,eAAe,WAAW,KAAG,SA4D5D,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,KAAM,SAAQ,UAAU;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC;CACxE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,WAAW,QAAO,KAO9B,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAQ/D,CAAC;AAOJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EACvB,OAAO,UAAU,CAAC,CAAC,CAAC,KACnB,KAAK,IAAI,CAAC,SAAS,WAAW,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,WAAW,CAAC,CAAC,CAEtB,CAAC"}