@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.
- package/dist/src/Assert.d.ts +6 -3
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +7 -4
- package/dist/src/Brand.d.ts +75 -0
- package/dist/src/Brand.d.ts.map +1 -0
- package/dist/src/Brand.js +1 -0
- package/dist/src/Buffer.d.ts +1 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +1 -1
- package/dist/src/CallbackRegistry.d.ts +53 -0
- package/dist/src/CallbackRegistry.d.ts.map +1 -0
- package/dist/src/CallbackRegistry.js +25 -0
- package/dist/src/Console.d.ts +31 -6
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +72 -9
- package/dist/src/Crypto.d.ts +61 -34
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +29 -42
- package/dist/src/Evolu/Db.d.ts +159 -66
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +300 -705
- package/dist/src/Evolu/Diff.d.ts +3 -3
- package/dist/src/Evolu/Diff.d.ts.map +1 -1
- package/dist/src/Evolu/Diff.js +7 -5
- package/dist/src/Evolu/Evolu.d.ts +200 -115
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +287 -157
- package/dist/src/Evolu/Internal.d.ts +0 -2
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +0 -2
- package/dist/src/Evolu/LocalAuth.d.ts +144 -0
- package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
- package/dist/src/Evolu/LocalAuth.js +171 -0
- package/dist/src/Evolu/Owner.d.ts +209 -124
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +118 -104
- package/dist/src/Evolu/Platform.d.ts +9 -7
- package/dist/src/Evolu/Platform.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.d.ts +256 -211
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +558 -359
- package/dist/src/Evolu/Public.d.ts +6 -8
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +2 -3
- package/dist/src/Evolu/PublicKysely.js +3 -3
- package/dist/src/Evolu/Query.d.ts +2 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.d.ts +72 -4
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +124 -13
- package/dist/src/Evolu/Schema.d.ts +129 -73
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +169 -89
- package/dist/src/Evolu/Storage.d.ts +159 -14
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +42 -32
- package/dist/src/Evolu/Sync.d.ts +68 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +414 -20
- package/dist/src/Evolu/Timestamp.d.ts +35 -27
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +27 -18
- package/dist/src/ManyToManyMap.d.ts +74 -10
- package/dist/src/ManyToManyMap.d.ts.map +1 -1
- package/dist/src/ManyToManyMap.js +41 -6
- package/dist/src/Number.d.ts +2 -1
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Random.d.ts +3 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/RefCountedResourceManager.d.ts +119 -0
- package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
- package/dist/src/RefCountedResourceManager.js +197 -0
- package/dist/src/Result.d.ts +176 -51
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +30 -241
- package/dist/src/Sqlite.d.ts +21 -4
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +50 -8
- package/dist/src/Task.d.ts +511 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +410 -0
- package/dist/src/Time.d.ts +59 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +87 -4
- package/dist/src/Type.d.ts +474 -327
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +502 -436
- package/dist/src/Types.d.ts +0 -74
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts +5 -2
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -13
- package/dist/src/Worker.d.ts +39 -11
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -4
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/package.json +14 -13
- package/src/Assert.ts +8 -7
- package/src/Brand.ts +75 -0
- package/src/Buffer.ts +1 -1
- package/src/CallbackRegistry.ts +84 -0
- package/src/Console.ts +91 -11
- package/src/Crypto.ts +94 -79
- package/src/Evolu/Db.ts +528 -1027
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +682 -329
- package/src/Evolu/Internal.ts +0 -2
- package/src/Evolu/LocalAuth.ts +422 -0
- package/src/Evolu/Owner.ts +282 -227
- package/src/Evolu/Platform.ts +9 -9
- package/src/Evolu/Protocol.ts +788 -640
- package/src/Evolu/Public.ts +7 -14
- package/src/Evolu/PublicKysely.ts +3 -3
- package/src/Evolu/Query.ts +2 -1
- package/src/Evolu/Relay.ts +238 -18
- package/src/Evolu/Schema.ts +391 -191
- package/src/Evolu/Storage.ts +275 -55
- package/src/Evolu/Sync.ts +708 -37
- package/src/Evolu/Timestamp.ts +38 -35
- package/src/ManyToManyMap.ts +127 -24
- package/src/Number.ts +2 -6
- package/src/Random.ts +3 -2
- package/src/RefCountedResourceManager.ts +368 -0
- package/src/Result.ts +183 -53
- package/src/Sqlite.ts +59 -23
- package/src/Task.ts +779 -0
- package/src/Time.ts +168 -4
- package/src/Type.ts +708 -660
- package/src/Types.ts +0 -76
- package/src/WebSocket.ts +23 -17
- package/src/Worker.ts +72 -23
- package/src/index.ts +3 -3
- package/dist/src/Callbacks.d.ts +0 -20
- package/dist/src/Callbacks.d.ts.map +0 -1
- package/dist/src/Callbacks.js +0 -18
- package/dist/src/Evolu/Config.d.ts +0 -69
- package/dist/src/Evolu/Config.d.ts.map +0 -1
- package/dist/src/Evolu/Config.js +0 -9
- package/dist/src/Evolu/Kysely.d.ts +0 -6
- package/dist/src/Evolu/Kysely.d.ts.map +0 -1
- package/dist/src/Evolu/Kysely.js +0 -21
- package/dist/src/NanoId.d.ts +0 -27
- package/dist/src/NanoId.d.ts.map +0 -1
- package/dist/src/NanoId.js +0 -6
- package/dist/src/Promise.d.ts +0 -180
- package/dist/src/Promise.d.ts.map +0 -1
- package/dist/src/Promise.js +0 -176
- package/src/Callbacks.ts +0 -43
- package/src/Evolu/Config.ts +0 -83
- package/src/Evolu/Kysely.ts +0 -38
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
|
@@ -0,0 +1,511 @@
|
|
|
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
|
+
//# 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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"}
|