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