@asaidimu/utils-sync 2.0.2 → 2.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.
package/index.d.mts CHANGED
@@ -1,3 +1,182 @@
1
+ interface DebouncerOptions {
2
+ /**
3
+ * Quiet period in milliseconds. The last-enqueued function runs after
4
+ * no new calls have arrived for this duration.
5
+ * @default 300
6
+ */
7
+ delay?: number;
8
+ /**
9
+ * If true, also fires immediately on the leading edge (first call in a
10
+ * quiet period), then debounces subsequent calls normally.
11
+ *
12
+ * Leading-edge semantics: the leading call fires immediately and clears
13
+ * _pendingFn. Any calls arriving *while* the leading execution is in flight
14
+ * register as a new trailing call and will be resolved by the trailing timer
15
+ * when the quiet period expires. Calls arriving during the leading execution
16
+ * are never lost.
17
+ *
18
+ * @default false
19
+ */
20
+ leading?: boolean;
21
+ }
22
+ /**
23
+ * Result when the debounced function successfully executed.
24
+ */
25
+ type DebouncerOk<T> = {
26
+ status: "ok";
27
+ value: T;
28
+ };
29
+ /**
30
+ * Result when the debounced function threw an error.
31
+ */
32
+ type DebouncerError = {
33
+ status: "error";
34
+ error: unknown;
35
+ };
36
+ /**
37
+ * Result when the debounced call was cancelled before execution.
38
+ */
39
+ type DebouncerCancelled = {
40
+ status: "cancelled";
41
+ };
42
+ /**
43
+ * Discriminated union of all possible Debouncer outcomes.
44
+ *
45
+ * - `DebouncerOk<T>`: function ran and returned a value.
46
+ * - `DebouncerError`: function ran and threw an error.
47
+ * - `DebouncerCancelled`: call was cancelled before it ran.
48
+ */
49
+ type DebouncerResult<T> = DebouncerOk<T> | DebouncerError | DebouncerCancelled;
50
+ /**
51
+ * Collapses rapid successive calls into a single execution.
52
+ *
53
+ * Each call to `do()` returns a promise that resolves with the result of the
54
+ * debounced execution — i.e. all callers within the same quiet window share
55
+ * the same result. The function that actually runs is always the *last* one
56
+ * enqueued before the delay expires.
57
+ *
58
+ * @example
59
+ * const debounced = new Debouncer({ delay: 200 });
60
+ * // Called rapidly three times — only the last fn runs.
61
+ * const [a, b, c] = await Promise.all([
62
+ * debounced.do(() => fetch("/api/search?q=h")),
63
+ * debounced.do(() => fetch("/api/search?q=he")),
64
+ * debounced.do(() => fetch("/api/search?q=hel")),
65
+ * ]);
66
+ * // a.status === "ok" and a.value is the result of the third fetch.
67
+ * // b and c share the same result as a.
68
+ */
69
+ declare class Debouncer<T = void> {
70
+ private _delay;
71
+ private _leading;
72
+ private _timer;
73
+ private _pendingFn;
74
+ private _pendingResolvers;
75
+ private _leadingFired;
76
+ constructor(options?: DebouncerOptions);
77
+ /**
78
+ * Enqueues a function and returns a promise that resolves with its result.
79
+ *
80
+ * All callers within the same quiet window share the same result — the
81
+ * function that actually runs is the *last* one enqueued before the delay
82
+ * expires. Use this when you need the return value of the debounced call.
83
+ *
84
+ * For fire-and-forget use (void fns, event handlers), prefer `fire()` to
85
+ * avoid unhandled-promise-rejection warnings and make intent explicit.
86
+ *
87
+ * @param fn - The function to debounce.
88
+ * @returns A promise resolving with a DebouncerResult discriminated union.
89
+ */
90
+ do(fn: () => Promise<T> | T): Promise<DebouncerResult<T>>;
91
+ /**
92
+ * Enqueues a function without returning a promise (fire-and-forget).
93
+ *
94
+ * Identical debounce semantics to `do()` — the last-enqueued function runs
95
+ * after the quiet period. Use this for void callbacks, DOM event handlers,
96
+ * or any caller that doesn't care about the result. No dangling promise,
97
+ * no unhandled-rejection risk.
98
+ *
99
+ * @param fn - The function to debounce.
100
+ */
101
+ fire(fn: () => Promise<T> | T): void;
102
+ /**
103
+ * Shared enqueue logic for both `do()` and `fire()`.
104
+ *
105
+ */
106
+ private _enqueue;
107
+ /**
108
+ * Immediately cancels any pending debounced execution.
109
+ * All waiting callers resolve with `{ status: "cancelled" }`.
110
+ */
111
+ cancel(): void;
112
+ /**
113
+ * Immediately executes the pending function (if any), bypassing the remaining delay.
114
+ * All waiting callers receive the result.
115
+ */
116
+ flush(): Promise<DebouncerResult<T> | null>;
117
+ /** Returns true if a debounced call is currently pending. */
118
+ pending(): boolean;
119
+ /**
120
+ * Executes the latest pending function and resolves all waiting callers.
121
+ */
122
+ private _fire;
123
+ }
124
+
125
+ /** Represents all errors within the sync system */
126
+ declare class SyncError extends Error {
127
+ constructor(message: string, cause?: unknown);
128
+ }
129
+ declare class TimeoutError extends SyncError {
130
+ constructor(message?: string);
131
+ }
132
+ declare class OnceExecutionConflict extends SyncError {
133
+ constructor(cause?: unknown);
134
+ }
135
+ declare class SerializerExecutionDone extends SyncError {
136
+ constructor(cause?: unknown);
137
+ }
138
+
139
+ /**
140
+ * A one-shot gate that starts closed and opens exactly once.
141
+ *
142
+ * All current and future `wait()` callers resolve immediately once `open()` is
143
+ * called. Unlike Once, Latch carries no return value and has no retry logic —
144
+ * it is a pure signalling primitive.
145
+ *
146
+ * Typical use: coordinate startup, signal that an initialisation phase is done,
147
+ * or gate downstream work on a single event.
148
+ *
149
+ * @example
150
+ * const ready = new Latch();
151
+ * // Consumer
152
+ * await ready.wait();
153
+ * // Producer (elsewhere)
154
+ * ready.open();
155
+ */
156
+ declare class Latch {
157
+ private _open;
158
+ private _resolve;
159
+ private _promise;
160
+ constructor();
161
+ /**
162
+ * Opens the latch. All current and future waiters resolve immediately.
163
+ * Calling open() more than once is a no-op.
164
+ */
165
+ open(): void;
166
+ /**
167
+ * Returns a promise that resolves when the latch is opened.
168
+ * If already open, resolves on the next microtask checkpoint.
169
+ *
170
+ * @param timeout - Optional maximum wait time in milliseconds.
171
+ * @throws {TimeoutError} If the latch does not open within the timeout.
172
+ */
173
+ wait(timeout?: number): Promise<void>;
174
+ /**
175
+ * Synchronously checks whether the latch has been opened.
176
+ */
177
+ isOpen(): boolean;
178
+ }
179
+
1
180
  interface MutexOptions {
2
181
  /**
3
182
  * Maximum number of pending requests allowed in the queue.
@@ -64,61 +243,7 @@ declare class Mutex {
64
243
  /** Returns the number of operations waiting for the lock. */
65
244
  pending(): number;
66
245
  }
67
- interface SemaphoreOptions extends MutexOptions {
68
- /**
69
- * Number of concurrent holders allowed.
70
- * Inherits `capacity` and `yieldMode` from MutexOptions.
71
- * @default 1 (equivalent to a Mutex)
72
- */
73
- slots?: number;
74
- }
75
- /**
76
- * A counting semaphore — generalises Mutex to allow up to N concurrent holders.
77
- *
78
- * Useful for rate-limiting concurrency: e.g. "at most 3 in-flight HTTP requests".
79
- * With slots=1 it behaves identically to Mutex.
80
- *
81
- * Accepts the same `capacity` and `yieldMode` options as Mutex, plus `slots`.
82
- */
83
- declare class Semaphore {
84
- private _slots;
85
- private _available;
86
- private _capacity;
87
- private _yieldMode;
88
- private waiters;
89
- constructor(options?: SemaphoreOptions);
90
- /**
91
- * Acquires one slot. If all slots are taken, waits until one is released.
92
- *
93
- * @param timeout - Optional maximum wait time in milliseconds.
94
- * @throws {TimeoutError} If a slot cannot be acquired within the timeout.
95
- * @throws {Error} If the wait queue is full.
96
- */
97
- acquire(timeout?: number): Promise<void>;
98
- /**
99
- * Attempts to acquire a slot without waiting.
100
- * @returns `true` if a slot was acquired, `false` otherwise.
101
- */
102
- tryAcquire(): boolean;
103
- /**
104
- * Releases one slot. If waiters are queued, the next one is scheduled
105
- * according to yieldMode.
106
- *
107
- * @throws {Error} If no slot is currently held (release without acquire).
108
- */
109
- release(): void;
110
- /**
111
- * Runs a function with one acquired slot, releasing it when done.
112
- * Convenience wrapper — prefer this over manual acquire/release.
113
- */
114
- run<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
115
- /** Number of slots currently free. */
116
- available(): number;
117
- /** Number of callers waiting for a slot. */
118
- pending(): number;
119
- /** Total slot count (as configured). */
120
- slots(): number;
121
- }
246
+
122
247
  type OnceResult<T> = {
123
248
  value: T | null;
124
249
  error?: unknown;
@@ -161,13 +286,6 @@ declare class Once<T = void> {
161
286
  * @returns The result of the execution.
162
287
  */
163
288
  doSync(fn: () => T): OnceResult<T>;
164
- /**
165
- * Returns true if the operation has completed (success or non-retryable failure)
166
- * and the internal promise has been cleared.
167
- *
168
- * This is the canonical "safe to read" check — prefer it over `done()`.
169
- */
170
- ready(): boolean;
171
289
  /**
172
290
  * Returns true if the operation is currently executing.
173
291
  *
@@ -210,116 +328,18 @@ declare class Once<T = void> {
210
328
  */
211
329
  private _awaitWithTimeout;
212
330
  }
213
- type SerializerResult<T> = {
214
- value: T | null;
215
- error?: unknown;
216
- };
217
- interface SerializerOptions {
218
- /**
219
- * Max items in queue. If full, .do() returns an error immediately.
220
- * @default 1000
221
- */
222
- capacity?: number;
331
+
332
+ interface RWMutexOptions {
223
333
  /**
224
- * Yield mode for the internal mutex. Defaults to "macrotask" for
225
- * coarse-grained serializers. Use "microtask" for fine-grained
226
- * latency-sensitive serializers.
227
- */
228
- yieldMode?: "macrotask" | "microtask";
229
- }
230
- /**
231
- * Ensures tasks are executed sequentially (FIFO).
232
- * Maintains the result of the last successful execution.
233
- * Includes backpressure protection via configurable queue capacity.
234
- */
235
- declare class Serializer<T = void> {
236
- private mutex;
237
- private _done;
238
- private _lastValue;
239
- private _lastError;
240
- private _hasRun;
241
- constructor(options?: SerializerOptions);
242
- /**
243
- * Enqueue a function to be executed after all previous tasks complete.
244
- *
245
- * @param fn - The function to execute.
246
- * @param timeout - Max time to wait to acquire the lock.
247
- * @returns Object containing the value or error.
248
- */
249
- do(fn: () => Promise<T> | T, timeout?: number): Promise<SerializerResult<T | null>>;
250
- /**
251
- * Returns the result of the last execution.
252
- *
253
- * Returns `{ value: null, error: undefined }` both when the serializer has
254
- * never run and when it ran and returned null — use `hasRun()` to distinguish
255
- * these two cases.
256
- */
257
- peek(): SerializerResult<T | null>;
258
- /**
259
- * Returns true if at least one task has been executed (successfully or not).
260
- */
261
- hasRun(): boolean;
262
- /**
263
- * Permanently closes the serializer.
264
- * Subsequent calls to `do()` will fail immediately with SerializerExecutionDone.
265
- */
266
- close(): void;
267
- /** Returns the number of tasks currently waiting. */
268
- pending(): number;
269
- /** Returns true if a task is currently executing. */
270
- running(): boolean;
271
- }
272
- /**
273
- * A one-shot gate that starts closed and opens exactly once.
274
- *
275
- * All current and future `wait()` callers resolve immediately once `open()` is
276
- * called. Unlike Once, Latch carries no return value and has no retry logic —
277
- * it is a pure signalling primitive.
278
- *
279
- * Typical use: coordinate startup, signal that an initialisation phase is done,
280
- * or gate downstream work on a single event.
281
- *
282
- * @example
283
- * const ready = new Latch();
284
- * // Consumer
285
- * await ready.wait();
286
- * // Producer (elsewhere)
287
- * ready.open();
288
- */
289
- declare class Latch {
290
- private _open;
291
- private _resolve;
292
- private _promise;
293
- constructor();
294
- /**
295
- * Opens the latch. All current and future waiters resolve immediately.
296
- * Calling open() more than once is a no-op.
297
- */
298
- open(): void;
299
- /**
300
- * Returns a promise that resolves when the latch is opened.
301
- * If already open, resolves on the next microtask checkpoint.
302
- *
303
- * @param timeout - Optional maximum wait time in milliseconds.
304
- * @throws {TimeoutError} If the latch does not open within the timeout.
305
- */
306
- wait(timeout?: number): Promise<void>;
307
- /**
308
- * Synchronously checks whether the latch has been opened.
309
- */
310
- isOpen(): boolean;
311
- }
312
- interface RWMutexOptions {
313
- /**
314
- * Controls how lock handoff is scheduled when a waiter is unblocked.
315
- *
316
- * - `"microtask"` (default): Uses queueMicrotask(fn). Near-zero latency
317
- * between handoffs. Appropriate for RWMutex because readers are woken in
318
- * bulk and writers are rarely contended enough to cause starvation.
319
- *
320
- * - `"macrotask"`: Uses setTimeout(fn, 0) to yield to the event loop between
321
- * handoffs. Use if you observe microtask starvation under extreme write
322
- * contention on this specific lock.
334
+ * Controls how lock handoff is scheduled when a waiter is unblocked.
335
+ *
336
+ * - `"microtask"` (default): Uses queueMicrotask(fn). Near-zero latency
337
+ * between handoffs. Appropriate for RWMutex because readers are woken in
338
+ * bulk and writers are rarely contended enough to cause starvation.
339
+ *
340
+ * - `"macrotask"`: Uses setTimeout(fn, 0) to yield to the event loop between
341
+ * handoffs. Use if you observe microtask starvation under extreme write
342
+ * contention on this specific lock.
323
343
  */
324
344
  yieldMode?: "macrotask" | "microtask";
325
345
  }
@@ -402,128 +422,121 @@ declare class RWMutex {
402
422
  */
403
423
  private _schedule;
404
424
  }
405
- interface DebouncerOptions {
406
- /**
407
- * Quiet period in milliseconds. The last-enqueued function runs after
408
- * no new calls have arrived for this duration.
409
- * @default 300
410
- */
411
- delay?: number;
425
+
426
+ interface SemaphoreOptions extends MutexOptions {
412
427
  /**
413
- * If true, also fires immediately on the leading edge (first call in a
414
- * quiet period), then debounces subsequent calls normally.
415
- *
416
- * Leading-edge semantics: the leading call fires immediately and clears
417
- * _pendingFn. Any calls arriving *while* the leading execution is in flight
418
- * register as a new trailing call and will be resolved by the trailing timer
419
- * when the quiet period expires. Calls arriving during the leading execution
420
- * are never lost.
421
- *
422
- * @default false
428
+ * Number of concurrent holders allowed.
429
+ * Inherits `capacity` and `yieldMode` from MutexOptions.
430
+ * @default 1 (equivalent to a Mutex)
423
431
  */
424
- leading?: boolean;
432
+ slots?: number;
425
433
  }
426
434
  /**
427
- * Result when the debounced function successfully executed.
428
- */
429
- type DebouncerOk<T> = {
430
- status: "ok";
431
- value: T;
432
- };
433
- /**
434
- * Result when the debounced function threw an error.
435
- */
436
- type DebouncerError = {
437
- status: "error";
438
- error: unknown;
439
- };
440
- /**
441
- * Result when the debounced call was cancelled before execution.
442
- */
443
- type DebouncerCancelled = {
444
- status: "cancelled";
445
- };
446
- /**
447
- * Discriminated union of all possible Debouncer outcomes.
448
- *
449
- * - `DebouncerOk<T>`: function ran and returned a value.
450
- * - `DebouncerError`: function ran and threw an error.
451
- * - `DebouncerCancelled`: call was cancelled before it ran.
452
- */
453
- type DebouncerResult<T> = DebouncerOk<T> | DebouncerError | DebouncerCancelled;
454
- /**
455
- * Collapses rapid successive calls into a single execution.
435
+ * A counting semaphore generalises Mutex to allow up to N concurrent holders.
456
436
  *
457
- * Each call to `do()` returns a promise that resolves with the result of the
458
- * debounced execution i.e. all callers within the same quiet window share
459
- * the same result. The function that actually runs is always the *last* one
460
- * enqueued before the delay expires.
437
+ * Useful for rate-limiting concurrency: e.g. "at most 3 in-flight HTTP requests".
438
+ * With slots=1 it behaves identically to Mutex.
461
439
  *
462
- * @example
463
- * const debounced = new Debouncer({ delay: 200 });
464
- * // Called rapidly three times — only the last fn runs.
465
- * const [a, b, c] = await Promise.all([
466
- * debounced.do(() => fetch("/api/search?q=h")),
467
- * debounced.do(() => fetch("/api/search?q=he")),
468
- * debounced.do(() => fetch("/api/search?q=hel")),
469
- * ]);
470
- * // a.status === "ok" and a.value is the result of the third fetch.
471
- * // b and c share the same result as a.
440
+ * Accepts the same `capacity` and `yieldMode` options as Mutex, plus `slots`.
472
441
  */
473
- declare class Debouncer<T = void> {
474
- private _delay;
475
- private _leading;
476
- private _timer;
477
- private _pendingFn;
478
- private _pendingResolvers;
479
- private _leadingFired;
480
- constructor(options?: DebouncerOptions);
442
+ declare class Semaphore {
443
+ private _slots;
444
+ private _available;
445
+ private _capacity;
446
+ private _yieldMode;
447
+ private waiters;
448
+ constructor(options?: SemaphoreOptions);
481
449
  /**
482
- * Enqueues a function and returns a promise that resolves with its result.
483
- *
484
- * All callers within the same quiet window share the same result — the
485
- * function that actually runs is the *last* one enqueued before the delay
486
- * expires. Use this when you need the return value of the debounced call.
487
- *
488
- * For fire-and-forget use (void fns, event handlers), prefer `fire()` to
489
- * avoid unhandled-promise-rejection warnings and make intent explicit.
450
+ * Acquires one slot. If all slots are taken, waits until one is released.
490
451
  *
491
- * @param fn - The function to debounce.
492
- * @returns A promise resolving with a DebouncerResult discriminated union.
452
+ * @param timeout - Optional maximum wait time in milliseconds.
453
+ * @throws {TimeoutError} If a slot cannot be acquired within the timeout.
454
+ * @throws {Error} If the wait queue is full.
493
455
  */
494
- do(fn: () => Promise<T> | T): Promise<DebouncerResult<T>>;
456
+ acquire(timeout?: number): Promise<void>;
495
457
  /**
496
- * Enqueues a function without returning a promise (fire-and-forget).
497
- *
498
- * Identical debounce semantics to `do()` — the last-enqueued function runs
499
- * after the quiet period. Use this for void callbacks, DOM event handlers,
500
- * or any caller that doesn't care about the result. No dangling promise,
501
- * no unhandled-rejection risk.
458
+ * Attempts to acquire a slot without waiting.
459
+ * @returns `true` if a slot was acquired, `false` otherwise.
460
+ */
461
+ tryAcquire(): boolean;
462
+ /**
463
+ * Releases one slot. If waiters are queued, the next one is scheduled
464
+ * according to yieldMode.
502
465
  *
503
- * @param fn - The function to debounce.
466
+ * @throws {Error} If no slot is currently held (release without acquire).
504
467
  */
505
- fire(fn: () => Promise<T> | T): void;
468
+ release(): void;
506
469
  /**
507
- * Shared enqueue logic for both `do()` and `fire()`.
470
+ * Runs a function with one acquired slot, releasing it when done.
471
+ * Convenience wrapper — prefer this over manual acquire/release.
472
+ */
473
+ run<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
474
+ /** Number of slots currently free. */
475
+ available(): number;
476
+ /** Number of callers waiting for a slot. */
477
+ pending(): number;
478
+ /** Total slot count (as configured). */
479
+ slots(): number;
480
+ }
481
+
482
+ type SerializerResult<T> = {
483
+ value: T | null;
484
+ error?: unknown;
485
+ };
486
+ interface SerializerOptions {
487
+ /**
488
+ * Max items in queue. If full, .do() returns an error immediately.
489
+ * @default 1000
490
+ */
491
+ capacity?: number;
492
+ /**
493
+ * Yield mode for the internal mutex. Defaults to "macrotask" for
494
+ * coarse-grained serializers. Use "microtask" for fine-grained
495
+ * latency-sensitive serializers.
496
+ */
497
+ yieldMode?: "macrotask" | "microtask";
498
+ }
499
+ /**
500
+ * Ensures tasks are executed sequentially (FIFO).
501
+ * Maintains the result of the last successful execution.
502
+ * Includes backpressure protection via configurable queue capacity.
503
+ */
504
+ declare class Serializer<T = void> {
505
+ private mutex;
506
+ private _done;
507
+ private _lastValue;
508
+ private _lastError;
509
+ private _hasRun;
510
+ constructor(options?: SerializerOptions);
511
+ /**
512
+ * Enqueue a function to be executed after all previous tasks complete.
508
513
  *
514
+ * @param fn - The function to execute.
515
+ * @param timeout - Max time to wait to acquire the lock.
516
+ * @returns Object containing the value or error.
509
517
  */
510
- private _enqueue;
518
+ do(fn: () => Promise<T> | T, timeout?: number): Promise<SerializerResult<T | null>>;
511
519
  /**
512
- * Immediately cancels any pending debounced execution.
513
- * All waiting callers resolve with `{ status: "cancelled" }`.
520
+ * Returns the result of the last execution.
521
+ *
522
+ * Returns `{ value: null, error: undefined }` both when the serializer has
523
+ * never run and when it ran and returned null — use `hasRun()` to distinguish
524
+ * these two cases.
514
525
  */
515
- cancel(): void;
526
+ peek(): SerializerResult<T | null>;
516
527
  /**
517
- * Immediately executes the pending function (if any), bypassing the remaining delay.
518
- * All waiting callers receive the result.
528
+ * Returns true if at least one task has been executed (successfully or not).
519
529
  */
520
- flush(): Promise<DebouncerResult<T> | null>;
521
- /** Returns true if a debounced call is currently pending. */
522
- pending(): boolean;
530
+ hasRun(): boolean;
523
531
  /**
524
- * Executes the latest pending function and resolves all waiting callers.
532
+ * Permanently closes the serializer.
533
+ * Subsequent calls to `do()` will fail immediately with SerializerExecutionDone.
525
534
  */
526
- private _fire;
535
+ close(): void;
536
+ /** Returns the number of tasks currently waiting. */
537
+ pending(): number;
538
+ /** Returns true if a task is currently executing. */
539
+ running(): boolean;
527
540
  }
528
541
 
529
- export { Debouncer, type DebouncerCancelled, type DebouncerError, type DebouncerOk, type DebouncerOptions, type DebouncerResult, Latch, Mutex, type MutexOptions, Once, type OnceResult, RWMutex, type RWMutexOptions, Semaphore, type SemaphoreOptions, Serializer, type SerializerOptions, type SerializerResult };
542
+ export { Debouncer, type DebouncerCancelled, type DebouncerError, type DebouncerOk, type DebouncerOptions, type DebouncerResult, Latch, Mutex, type MutexOptions, Once, OnceExecutionConflict, type OnceResult, RWMutex, type RWMutexOptions, Semaphore, type SemaphoreOptions, Serializer, SerializerExecutionDone, type SerializerOptions, type SerializerResult, SyncError, TimeoutError };
package/index.d.ts CHANGED
@@ -1,3 +1,182 @@
1
+ interface DebouncerOptions {
2
+ /**
3
+ * Quiet period in milliseconds. The last-enqueued function runs after
4
+ * no new calls have arrived for this duration.
5
+ * @default 300
6
+ */
7
+ delay?: number;
8
+ /**
9
+ * If true, also fires immediately on the leading edge (first call in a
10
+ * quiet period), then debounces subsequent calls normally.
11
+ *
12
+ * Leading-edge semantics: the leading call fires immediately and clears
13
+ * _pendingFn. Any calls arriving *while* the leading execution is in flight
14
+ * register as a new trailing call and will be resolved by the trailing timer
15
+ * when the quiet period expires. Calls arriving during the leading execution
16
+ * are never lost.
17
+ *
18
+ * @default false
19
+ */
20
+ leading?: boolean;
21
+ }
22
+ /**
23
+ * Result when the debounced function successfully executed.
24
+ */
25
+ type DebouncerOk<T> = {
26
+ status: "ok";
27
+ value: T;
28
+ };
29
+ /**
30
+ * Result when the debounced function threw an error.
31
+ */
32
+ type DebouncerError = {
33
+ status: "error";
34
+ error: unknown;
35
+ };
36
+ /**
37
+ * Result when the debounced call was cancelled before execution.
38
+ */
39
+ type DebouncerCancelled = {
40
+ status: "cancelled";
41
+ };
42
+ /**
43
+ * Discriminated union of all possible Debouncer outcomes.
44
+ *
45
+ * - `DebouncerOk<T>`: function ran and returned a value.
46
+ * - `DebouncerError`: function ran and threw an error.
47
+ * - `DebouncerCancelled`: call was cancelled before it ran.
48
+ */
49
+ type DebouncerResult<T> = DebouncerOk<T> | DebouncerError | DebouncerCancelled;
50
+ /**
51
+ * Collapses rapid successive calls into a single execution.
52
+ *
53
+ * Each call to `do()` returns a promise that resolves with the result of the
54
+ * debounced execution — i.e. all callers within the same quiet window share
55
+ * the same result. The function that actually runs is always the *last* one
56
+ * enqueued before the delay expires.
57
+ *
58
+ * @example
59
+ * const debounced = new Debouncer({ delay: 200 });
60
+ * // Called rapidly three times — only the last fn runs.
61
+ * const [a, b, c] = await Promise.all([
62
+ * debounced.do(() => fetch("/api/search?q=h")),
63
+ * debounced.do(() => fetch("/api/search?q=he")),
64
+ * debounced.do(() => fetch("/api/search?q=hel")),
65
+ * ]);
66
+ * // a.status === "ok" and a.value is the result of the third fetch.
67
+ * // b and c share the same result as a.
68
+ */
69
+ declare class Debouncer<T = void> {
70
+ private _delay;
71
+ private _leading;
72
+ private _timer;
73
+ private _pendingFn;
74
+ private _pendingResolvers;
75
+ private _leadingFired;
76
+ constructor(options?: DebouncerOptions);
77
+ /**
78
+ * Enqueues a function and returns a promise that resolves with its result.
79
+ *
80
+ * All callers within the same quiet window share the same result — the
81
+ * function that actually runs is the *last* one enqueued before the delay
82
+ * expires. Use this when you need the return value of the debounced call.
83
+ *
84
+ * For fire-and-forget use (void fns, event handlers), prefer `fire()` to
85
+ * avoid unhandled-promise-rejection warnings and make intent explicit.
86
+ *
87
+ * @param fn - The function to debounce.
88
+ * @returns A promise resolving with a DebouncerResult discriminated union.
89
+ */
90
+ do(fn: () => Promise<T> | T): Promise<DebouncerResult<T>>;
91
+ /**
92
+ * Enqueues a function without returning a promise (fire-and-forget).
93
+ *
94
+ * Identical debounce semantics to `do()` — the last-enqueued function runs
95
+ * after the quiet period. Use this for void callbacks, DOM event handlers,
96
+ * or any caller that doesn't care about the result. No dangling promise,
97
+ * no unhandled-rejection risk.
98
+ *
99
+ * @param fn - The function to debounce.
100
+ */
101
+ fire(fn: () => Promise<T> | T): void;
102
+ /**
103
+ * Shared enqueue logic for both `do()` and `fire()`.
104
+ *
105
+ */
106
+ private _enqueue;
107
+ /**
108
+ * Immediately cancels any pending debounced execution.
109
+ * All waiting callers resolve with `{ status: "cancelled" }`.
110
+ */
111
+ cancel(): void;
112
+ /**
113
+ * Immediately executes the pending function (if any), bypassing the remaining delay.
114
+ * All waiting callers receive the result.
115
+ */
116
+ flush(): Promise<DebouncerResult<T> | null>;
117
+ /** Returns true if a debounced call is currently pending. */
118
+ pending(): boolean;
119
+ /**
120
+ * Executes the latest pending function and resolves all waiting callers.
121
+ */
122
+ private _fire;
123
+ }
124
+
125
+ /** Represents all errors within the sync system */
126
+ declare class SyncError extends Error {
127
+ constructor(message: string, cause?: unknown);
128
+ }
129
+ declare class TimeoutError extends SyncError {
130
+ constructor(message?: string);
131
+ }
132
+ declare class OnceExecutionConflict extends SyncError {
133
+ constructor(cause?: unknown);
134
+ }
135
+ declare class SerializerExecutionDone extends SyncError {
136
+ constructor(cause?: unknown);
137
+ }
138
+
139
+ /**
140
+ * A one-shot gate that starts closed and opens exactly once.
141
+ *
142
+ * All current and future `wait()` callers resolve immediately once `open()` is
143
+ * called. Unlike Once, Latch carries no return value and has no retry logic —
144
+ * it is a pure signalling primitive.
145
+ *
146
+ * Typical use: coordinate startup, signal that an initialisation phase is done,
147
+ * or gate downstream work on a single event.
148
+ *
149
+ * @example
150
+ * const ready = new Latch();
151
+ * // Consumer
152
+ * await ready.wait();
153
+ * // Producer (elsewhere)
154
+ * ready.open();
155
+ */
156
+ declare class Latch {
157
+ private _open;
158
+ private _resolve;
159
+ private _promise;
160
+ constructor();
161
+ /**
162
+ * Opens the latch. All current and future waiters resolve immediately.
163
+ * Calling open() more than once is a no-op.
164
+ */
165
+ open(): void;
166
+ /**
167
+ * Returns a promise that resolves when the latch is opened.
168
+ * If already open, resolves on the next microtask checkpoint.
169
+ *
170
+ * @param timeout - Optional maximum wait time in milliseconds.
171
+ * @throws {TimeoutError} If the latch does not open within the timeout.
172
+ */
173
+ wait(timeout?: number): Promise<void>;
174
+ /**
175
+ * Synchronously checks whether the latch has been opened.
176
+ */
177
+ isOpen(): boolean;
178
+ }
179
+
1
180
  interface MutexOptions {
2
181
  /**
3
182
  * Maximum number of pending requests allowed in the queue.
@@ -64,61 +243,7 @@ declare class Mutex {
64
243
  /** Returns the number of operations waiting for the lock. */
65
244
  pending(): number;
66
245
  }
67
- interface SemaphoreOptions extends MutexOptions {
68
- /**
69
- * Number of concurrent holders allowed.
70
- * Inherits `capacity` and `yieldMode` from MutexOptions.
71
- * @default 1 (equivalent to a Mutex)
72
- */
73
- slots?: number;
74
- }
75
- /**
76
- * A counting semaphore — generalises Mutex to allow up to N concurrent holders.
77
- *
78
- * Useful for rate-limiting concurrency: e.g. "at most 3 in-flight HTTP requests".
79
- * With slots=1 it behaves identically to Mutex.
80
- *
81
- * Accepts the same `capacity` and `yieldMode` options as Mutex, plus `slots`.
82
- */
83
- declare class Semaphore {
84
- private _slots;
85
- private _available;
86
- private _capacity;
87
- private _yieldMode;
88
- private waiters;
89
- constructor(options?: SemaphoreOptions);
90
- /**
91
- * Acquires one slot. If all slots are taken, waits until one is released.
92
- *
93
- * @param timeout - Optional maximum wait time in milliseconds.
94
- * @throws {TimeoutError} If a slot cannot be acquired within the timeout.
95
- * @throws {Error} If the wait queue is full.
96
- */
97
- acquire(timeout?: number): Promise<void>;
98
- /**
99
- * Attempts to acquire a slot without waiting.
100
- * @returns `true` if a slot was acquired, `false` otherwise.
101
- */
102
- tryAcquire(): boolean;
103
- /**
104
- * Releases one slot. If waiters are queued, the next one is scheduled
105
- * according to yieldMode.
106
- *
107
- * @throws {Error} If no slot is currently held (release without acquire).
108
- */
109
- release(): void;
110
- /**
111
- * Runs a function with one acquired slot, releasing it when done.
112
- * Convenience wrapper — prefer this over manual acquire/release.
113
- */
114
- run<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
115
- /** Number of slots currently free. */
116
- available(): number;
117
- /** Number of callers waiting for a slot. */
118
- pending(): number;
119
- /** Total slot count (as configured). */
120
- slots(): number;
121
- }
246
+
122
247
  type OnceResult<T> = {
123
248
  value: T | null;
124
249
  error?: unknown;
@@ -161,13 +286,6 @@ declare class Once<T = void> {
161
286
  * @returns The result of the execution.
162
287
  */
163
288
  doSync(fn: () => T): OnceResult<T>;
164
- /**
165
- * Returns true if the operation has completed (success or non-retryable failure)
166
- * and the internal promise has been cleared.
167
- *
168
- * This is the canonical "safe to read" check — prefer it over `done()`.
169
- */
170
- ready(): boolean;
171
289
  /**
172
290
  * Returns true if the operation is currently executing.
173
291
  *
@@ -210,116 +328,18 @@ declare class Once<T = void> {
210
328
  */
211
329
  private _awaitWithTimeout;
212
330
  }
213
- type SerializerResult<T> = {
214
- value: T | null;
215
- error?: unknown;
216
- };
217
- interface SerializerOptions {
218
- /**
219
- * Max items in queue. If full, .do() returns an error immediately.
220
- * @default 1000
221
- */
222
- capacity?: number;
331
+
332
+ interface RWMutexOptions {
223
333
  /**
224
- * Yield mode for the internal mutex. Defaults to "macrotask" for
225
- * coarse-grained serializers. Use "microtask" for fine-grained
226
- * latency-sensitive serializers.
227
- */
228
- yieldMode?: "macrotask" | "microtask";
229
- }
230
- /**
231
- * Ensures tasks are executed sequentially (FIFO).
232
- * Maintains the result of the last successful execution.
233
- * Includes backpressure protection via configurable queue capacity.
234
- */
235
- declare class Serializer<T = void> {
236
- private mutex;
237
- private _done;
238
- private _lastValue;
239
- private _lastError;
240
- private _hasRun;
241
- constructor(options?: SerializerOptions);
242
- /**
243
- * Enqueue a function to be executed after all previous tasks complete.
244
- *
245
- * @param fn - The function to execute.
246
- * @param timeout - Max time to wait to acquire the lock.
247
- * @returns Object containing the value or error.
248
- */
249
- do(fn: () => Promise<T> | T, timeout?: number): Promise<SerializerResult<T | null>>;
250
- /**
251
- * Returns the result of the last execution.
252
- *
253
- * Returns `{ value: null, error: undefined }` both when the serializer has
254
- * never run and when it ran and returned null — use `hasRun()` to distinguish
255
- * these two cases.
256
- */
257
- peek(): SerializerResult<T | null>;
258
- /**
259
- * Returns true if at least one task has been executed (successfully or not).
260
- */
261
- hasRun(): boolean;
262
- /**
263
- * Permanently closes the serializer.
264
- * Subsequent calls to `do()` will fail immediately with SerializerExecutionDone.
265
- */
266
- close(): void;
267
- /** Returns the number of tasks currently waiting. */
268
- pending(): number;
269
- /** Returns true if a task is currently executing. */
270
- running(): boolean;
271
- }
272
- /**
273
- * A one-shot gate that starts closed and opens exactly once.
274
- *
275
- * All current and future `wait()` callers resolve immediately once `open()` is
276
- * called. Unlike Once, Latch carries no return value and has no retry logic —
277
- * it is a pure signalling primitive.
278
- *
279
- * Typical use: coordinate startup, signal that an initialisation phase is done,
280
- * or gate downstream work on a single event.
281
- *
282
- * @example
283
- * const ready = new Latch();
284
- * // Consumer
285
- * await ready.wait();
286
- * // Producer (elsewhere)
287
- * ready.open();
288
- */
289
- declare class Latch {
290
- private _open;
291
- private _resolve;
292
- private _promise;
293
- constructor();
294
- /**
295
- * Opens the latch. All current and future waiters resolve immediately.
296
- * Calling open() more than once is a no-op.
297
- */
298
- open(): void;
299
- /**
300
- * Returns a promise that resolves when the latch is opened.
301
- * If already open, resolves on the next microtask checkpoint.
302
- *
303
- * @param timeout - Optional maximum wait time in milliseconds.
304
- * @throws {TimeoutError} If the latch does not open within the timeout.
305
- */
306
- wait(timeout?: number): Promise<void>;
307
- /**
308
- * Synchronously checks whether the latch has been opened.
309
- */
310
- isOpen(): boolean;
311
- }
312
- interface RWMutexOptions {
313
- /**
314
- * Controls how lock handoff is scheduled when a waiter is unblocked.
315
- *
316
- * - `"microtask"` (default): Uses queueMicrotask(fn). Near-zero latency
317
- * between handoffs. Appropriate for RWMutex because readers are woken in
318
- * bulk and writers are rarely contended enough to cause starvation.
319
- *
320
- * - `"macrotask"`: Uses setTimeout(fn, 0) to yield to the event loop between
321
- * handoffs. Use if you observe microtask starvation under extreme write
322
- * contention on this specific lock.
334
+ * Controls how lock handoff is scheduled when a waiter is unblocked.
335
+ *
336
+ * - `"microtask"` (default): Uses queueMicrotask(fn). Near-zero latency
337
+ * between handoffs. Appropriate for RWMutex because readers are woken in
338
+ * bulk and writers are rarely contended enough to cause starvation.
339
+ *
340
+ * - `"macrotask"`: Uses setTimeout(fn, 0) to yield to the event loop between
341
+ * handoffs. Use if you observe microtask starvation under extreme write
342
+ * contention on this specific lock.
323
343
  */
324
344
  yieldMode?: "macrotask" | "microtask";
325
345
  }
@@ -402,128 +422,121 @@ declare class RWMutex {
402
422
  */
403
423
  private _schedule;
404
424
  }
405
- interface DebouncerOptions {
406
- /**
407
- * Quiet period in milliseconds. The last-enqueued function runs after
408
- * no new calls have arrived for this duration.
409
- * @default 300
410
- */
411
- delay?: number;
425
+
426
+ interface SemaphoreOptions extends MutexOptions {
412
427
  /**
413
- * If true, also fires immediately on the leading edge (first call in a
414
- * quiet period), then debounces subsequent calls normally.
415
- *
416
- * Leading-edge semantics: the leading call fires immediately and clears
417
- * _pendingFn. Any calls arriving *while* the leading execution is in flight
418
- * register as a new trailing call and will be resolved by the trailing timer
419
- * when the quiet period expires. Calls arriving during the leading execution
420
- * are never lost.
421
- *
422
- * @default false
428
+ * Number of concurrent holders allowed.
429
+ * Inherits `capacity` and `yieldMode` from MutexOptions.
430
+ * @default 1 (equivalent to a Mutex)
423
431
  */
424
- leading?: boolean;
432
+ slots?: number;
425
433
  }
426
434
  /**
427
- * Result when the debounced function successfully executed.
428
- */
429
- type DebouncerOk<T> = {
430
- status: "ok";
431
- value: T;
432
- };
433
- /**
434
- * Result when the debounced function threw an error.
435
- */
436
- type DebouncerError = {
437
- status: "error";
438
- error: unknown;
439
- };
440
- /**
441
- * Result when the debounced call was cancelled before execution.
442
- */
443
- type DebouncerCancelled = {
444
- status: "cancelled";
445
- };
446
- /**
447
- * Discriminated union of all possible Debouncer outcomes.
448
- *
449
- * - `DebouncerOk<T>`: function ran and returned a value.
450
- * - `DebouncerError`: function ran and threw an error.
451
- * - `DebouncerCancelled`: call was cancelled before it ran.
452
- */
453
- type DebouncerResult<T> = DebouncerOk<T> | DebouncerError | DebouncerCancelled;
454
- /**
455
- * Collapses rapid successive calls into a single execution.
435
+ * A counting semaphore generalises Mutex to allow up to N concurrent holders.
456
436
  *
457
- * Each call to `do()` returns a promise that resolves with the result of the
458
- * debounced execution i.e. all callers within the same quiet window share
459
- * the same result. The function that actually runs is always the *last* one
460
- * enqueued before the delay expires.
437
+ * Useful for rate-limiting concurrency: e.g. "at most 3 in-flight HTTP requests".
438
+ * With slots=1 it behaves identically to Mutex.
461
439
  *
462
- * @example
463
- * const debounced = new Debouncer({ delay: 200 });
464
- * // Called rapidly three times — only the last fn runs.
465
- * const [a, b, c] = await Promise.all([
466
- * debounced.do(() => fetch("/api/search?q=h")),
467
- * debounced.do(() => fetch("/api/search?q=he")),
468
- * debounced.do(() => fetch("/api/search?q=hel")),
469
- * ]);
470
- * // a.status === "ok" and a.value is the result of the third fetch.
471
- * // b and c share the same result as a.
440
+ * Accepts the same `capacity` and `yieldMode` options as Mutex, plus `slots`.
472
441
  */
473
- declare class Debouncer<T = void> {
474
- private _delay;
475
- private _leading;
476
- private _timer;
477
- private _pendingFn;
478
- private _pendingResolvers;
479
- private _leadingFired;
480
- constructor(options?: DebouncerOptions);
442
+ declare class Semaphore {
443
+ private _slots;
444
+ private _available;
445
+ private _capacity;
446
+ private _yieldMode;
447
+ private waiters;
448
+ constructor(options?: SemaphoreOptions);
481
449
  /**
482
- * Enqueues a function and returns a promise that resolves with its result.
483
- *
484
- * All callers within the same quiet window share the same result — the
485
- * function that actually runs is the *last* one enqueued before the delay
486
- * expires. Use this when you need the return value of the debounced call.
487
- *
488
- * For fire-and-forget use (void fns, event handlers), prefer `fire()` to
489
- * avoid unhandled-promise-rejection warnings and make intent explicit.
450
+ * Acquires one slot. If all slots are taken, waits until one is released.
490
451
  *
491
- * @param fn - The function to debounce.
492
- * @returns A promise resolving with a DebouncerResult discriminated union.
452
+ * @param timeout - Optional maximum wait time in milliseconds.
453
+ * @throws {TimeoutError} If a slot cannot be acquired within the timeout.
454
+ * @throws {Error} If the wait queue is full.
493
455
  */
494
- do(fn: () => Promise<T> | T): Promise<DebouncerResult<T>>;
456
+ acquire(timeout?: number): Promise<void>;
495
457
  /**
496
- * Enqueues a function without returning a promise (fire-and-forget).
497
- *
498
- * Identical debounce semantics to `do()` — the last-enqueued function runs
499
- * after the quiet period. Use this for void callbacks, DOM event handlers,
500
- * or any caller that doesn't care about the result. No dangling promise,
501
- * no unhandled-rejection risk.
458
+ * Attempts to acquire a slot without waiting.
459
+ * @returns `true` if a slot was acquired, `false` otherwise.
460
+ */
461
+ tryAcquire(): boolean;
462
+ /**
463
+ * Releases one slot. If waiters are queued, the next one is scheduled
464
+ * according to yieldMode.
502
465
  *
503
- * @param fn - The function to debounce.
466
+ * @throws {Error} If no slot is currently held (release without acquire).
504
467
  */
505
- fire(fn: () => Promise<T> | T): void;
468
+ release(): void;
506
469
  /**
507
- * Shared enqueue logic for both `do()` and `fire()`.
470
+ * Runs a function with one acquired slot, releasing it when done.
471
+ * Convenience wrapper — prefer this over manual acquire/release.
472
+ */
473
+ run<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
474
+ /** Number of slots currently free. */
475
+ available(): number;
476
+ /** Number of callers waiting for a slot. */
477
+ pending(): number;
478
+ /** Total slot count (as configured). */
479
+ slots(): number;
480
+ }
481
+
482
+ type SerializerResult<T> = {
483
+ value: T | null;
484
+ error?: unknown;
485
+ };
486
+ interface SerializerOptions {
487
+ /**
488
+ * Max items in queue. If full, .do() returns an error immediately.
489
+ * @default 1000
490
+ */
491
+ capacity?: number;
492
+ /**
493
+ * Yield mode for the internal mutex. Defaults to "macrotask" for
494
+ * coarse-grained serializers. Use "microtask" for fine-grained
495
+ * latency-sensitive serializers.
496
+ */
497
+ yieldMode?: "macrotask" | "microtask";
498
+ }
499
+ /**
500
+ * Ensures tasks are executed sequentially (FIFO).
501
+ * Maintains the result of the last successful execution.
502
+ * Includes backpressure protection via configurable queue capacity.
503
+ */
504
+ declare class Serializer<T = void> {
505
+ private mutex;
506
+ private _done;
507
+ private _lastValue;
508
+ private _lastError;
509
+ private _hasRun;
510
+ constructor(options?: SerializerOptions);
511
+ /**
512
+ * Enqueue a function to be executed after all previous tasks complete.
508
513
  *
514
+ * @param fn - The function to execute.
515
+ * @param timeout - Max time to wait to acquire the lock.
516
+ * @returns Object containing the value or error.
509
517
  */
510
- private _enqueue;
518
+ do(fn: () => Promise<T> | T, timeout?: number): Promise<SerializerResult<T | null>>;
511
519
  /**
512
- * Immediately cancels any pending debounced execution.
513
- * All waiting callers resolve with `{ status: "cancelled" }`.
520
+ * Returns the result of the last execution.
521
+ *
522
+ * Returns `{ value: null, error: undefined }` both when the serializer has
523
+ * never run and when it ran and returned null — use `hasRun()` to distinguish
524
+ * these two cases.
514
525
  */
515
- cancel(): void;
526
+ peek(): SerializerResult<T | null>;
516
527
  /**
517
- * Immediately executes the pending function (if any), bypassing the remaining delay.
518
- * All waiting callers receive the result.
528
+ * Returns true if at least one task has been executed (successfully or not).
519
529
  */
520
- flush(): Promise<DebouncerResult<T> | null>;
521
- /** Returns true if a debounced call is currently pending. */
522
- pending(): boolean;
530
+ hasRun(): boolean;
523
531
  /**
524
- * Executes the latest pending function and resolves all waiting callers.
532
+ * Permanently closes the serializer.
533
+ * Subsequent calls to `do()` will fail immediately with SerializerExecutionDone.
525
534
  */
526
- private _fire;
535
+ close(): void;
536
+ /** Returns the number of tasks currently waiting. */
537
+ pending(): number;
538
+ /** Returns true if a task is currently executing. */
539
+ running(): boolean;
527
540
  }
528
541
 
529
- export { Debouncer, type DebouncerCancelled, type DebouncerError, type DebouncerOk, type DebouncerOptions, type DebouncerResult, Latch, Mutex, type MutexOptions, Once, type OnceResult, RWMutex, type RWMutexOptions, Semaphore, type SemaphoreOptions, Serializer, type SerializerOptions, type SerializerResult };
542
+ export { Debouncer, type DebouncerCancelled, type DebouncerError, type DebouncerOk, type DebouncerOptions, type DebouncerResult, Latch, Mutex, type MutexOptions, Once, OnceExecutionConflict, type OnceResult, RWMutex, type RWMutexOptions, Semaphore, type SemaphoreOptions, Serializer, SerializerExecutionDone, type SerializerOptions, type SerializerResult, SyncError, TimeoutError };
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},t=class extends e{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},i=class extends e{constructor(e){super("[Serializer] The serializer has been marked as done!",e)}},r=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let i;const r=new Promise((e=>i=e));if(this.waiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(i);-1!==e&&this.waiters.splice(e,1),o(new t("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}};exports.Debouncer=class{_delay;_leading;_timer;_pendingFn;_pendingResolvers=[];_leadingFired=!1;constructor(e){this._delay=e?.delay??300,this._leading=e?.leading??!1}do(e){return new Promise((t=>{this._enqueue(e,t)}))}fire(e){this._enqueue(e,void 0)}_enqueue(e,t){if(this._pendingFn=e,t&&this._pendingResolvers.push(t),this._leading&&!this._leadingFired)return this._leadingFired=!0,this._fire(),clearTimeout(this._timer),void(this._timer=setTimeout((()=>{this._leadingFired=!1,void 0!==this._pendingFn&&this._fire()}),this._delay));clearTimeout(this._timer),this._timer=setTimeout((()=>{this._leadingFired=!1,this._fire()}),this._delay)}cancel(){clearTimeout(this._timer),this._timer=void 0,this._pendingFn=void 0,this._leadingFired=!1;const e=this._pendingResolvers.splice(0);for(const t of e)t({status:"cancelled"})}async flush(){return this._pendingFn?(clearTimeout(this._timer),this._timer=void 0,this._leadingFired=!1,this._fire()):null}pending(){return void 0!==this._pendingFn}async _fire(){const e=this._pendingFn,t=this._pendingResolvers.splice(0);let i;this._pendingFn=void 0;try{i={status:"ok",value:await e()}}catch(e){i={status:"error",error:e}}for(const e of t)e(i);return i}},exports.Latch=class{_open=!1;_resolve;_promise;constructor(){this._promise=new Promise((e=>{this._resolve=e}))}open(){this._open||(this._open=!0,this._resolve())}async wait(e){if(this._open)return;if(null==e)return this._promise;let i;await Promise.race([this._promise.then((()=>clearTimeout(i))),new Promise(((r,s)=>{i=setTimeout((()=>s(new t("Latch timed out"))),e)}))])}isOpen(){return this._open}},exports.Mutex=r,exports.Once=class{mutex=new r({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{const t=await e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.promise=null}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}ready(){return this._done}running(){return null!==this.promise&&!this.ready()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,i,r="Operation timed out"){if(null==i)return e;let s;return Promise.race([e.then((e=>(clearTimeout(s),e))),new Promise(((e,o)=>{s=setTimeout((()=>o(new t(r))),i)}))])}},exports.RWMutex=class{_readers=0;_writeLocked=!1;_pendingWriters=0;_yieldMode;readerWaiters=[];writerWaiters=[];constructor(e){this._yieldMode=e?.yieldMode??"microtask"}async rlock(e){if(!this._writeLocked&&0===this._pendingWriters)return void this._readers++;let i;const r=new Promise((e=>i=e));if(this.readerWaiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.readerWaiters.indexOf(i);-1!==e&&this.readerWaiters.splice(e,1),o(new t("RWMutex rlock timed out"))}),e)}))])}runlock(){if(this._readers<=0)throw new Error("RWMutex: runlock called without a matching rlock");this._readers--,this._tryWakeWriter()}async lock(e){if(!this._writeLocked&&0===this._readers)return void(this._writeLocked=!0);let i;this._pendingWriters++;const r=new Promise((e=>i=e));if(this.writerWaiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.writerWaiters.indexOf(i);-1!==e&&(this.writerWaiters.splice(e,1),this._pendingWriters--),o(new t("RWMutex lock timed out"))}),e)}))])}unlock(){if(!this._writeLocked)throw new Error("RWMutex: unlock called without a matching lock");this._writeLocked=!1,this._tryWakeWriter()||this._wakeAllReaders()}async read(e,t){await this.rlock(t);try{return await e()}finally{this.runlock()}}async write(e,t){await this.lock(t);try{return await e()}finally{this.unlock()}}writeLocked(){return this._writeLocked}readers(){return this._readers}pendingWriters(){return this._pendingWriters}pendingReaders(){return this.readerWaiters.length}_tryWakeWriter(){if(this._writeLocked||this._readers>0)return!1;const e=this.writerWaiters.shift();return!!e&&(this._writeLocked=!0,this._pendingWriters--,this._schedule(e),!0)}_wakeAllReaders(){const e=this.readerWaiters.splice(0);if(0!==e.length){this._readers+=e.length;for(const t of e)this._schedule(t)}}_schedule(e){"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0)}},exports.Semaphore=class{_slots;_available;_capacity;_yieldMode;waiters=[];constructor(e){if(this._slots=e?.slots??1,this._available=this._slots,this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask",this._slots<1)throw new Error("Semaphore slots must be >= 1")}async acquire(e){if(this._available>0)return void this._available--;if(this.waiters.length>=this._capacity)throw new Error(`Semaphore queue is full (capacity: ${this._capacity})`);let i;const r=new Promise((e=>i=e));if(this.waiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(i);-1!==e&&this.waiters.splice(e,1),o(new t("Semaphore acquire timed out"))}),e)}))])}tryAcquire(){return this._available>0&&(this._available--,!0)}release(){if(this._available>=this._slots)throw new Error("Semaphore released more times than acquired");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._available++}async run(e,t){await this.acquire(t);try{return await e()}finally{this.release()}}available(){return this._available}pending(){return this.waiters.length}slots(){return this._slots}},exports.Serializer=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;_hasRun=!1;constructor(e){this.mutex=new r({capacity:e?.capacity??1e3,yieldMode:e?.yieldMode??"macrotask"})}async do(e,t){if(this._done)return{value:null,error:new i};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let r,s=null;try{if(this._done)throw new i;s=await e(),this._lastValue=s,this._lastError=void 0,this._hasRun=!0}catch(e){r=e,this._lastError=e,this._hasRun=!0}finally{this.mutex.unlock()}return{value:s,error:r}}peek(){return{value:this._lastValue,error:this._lastError}}hasRun(){return this._hasRun}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}};
1
+ "use strict";var e=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},t=class extends e{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},i=class extends e{constructor(e){super("[Serializer] The serializer has been marked as done!",e)}},r=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let i;const r=new Promise((e=>i=e));if(this.waiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(i);-1!==e&&this.waiters.splice(e,1),o(new t("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}};exports.Debouncer=class{_delay;_leading;_timer;_pendingFn;_pendingResolvers=[];_leadingFired=!1;constructor(e){this._delay=e?.delay??300,this._leading=e?.leading??!1}do(e){return new Promise((t=>{this._enqueue(e,t)}))}fire(e){this._enqueue(e,void 0)}_enqueue(e,t){if(this._pendingFn=e,t&&this._pendingResolvers.push(t),this._leading&&!this._leadingFired)return this._leadingFired=!0,this._fire(),clearTimeout(this._timer),void(this._timer=setTimeout((()=>{this._leadingFired=!1,void 0!==this._pendingFn&&this._fire()}),this._delay));clearTimeout(this._timer),this._timer=setTimeout((()=>{this._leadingFired=!1,this._fire()}),this._delay)}cancel(){clearTimeout(this._timer),this._timer=void 0,this._pendingFn=void 0,this._leadingFired=!1;const e=this._pendingResolvers.splice(0);for(const t of e)t({status:"cancelled"})}async flush(){return this._pendingFn?(clearTimeout(this._timer),this._timer=void 0,this._leadingFired=!1,this._fire()):null}pending(){return void 0!==this._pendingFn}async _fire(){const e=this._pendingFn,t=this._pendingResolvers.splice(0);let i;this._pendingFn=void 0;try{i={status:"ok",value:await e()}}catch(e){i={status:"error",error:e}}for(const e of t)e(i);return i}},exports.Latch=class{_open=!1;_resolve;_promise;constructor(){this._promise=new Promise((e=>{this._resolve=e}))}open(){this._open||(this._open=!0,this._resolve())}async wait(e){if(this._open)return;if(null==e)return this._promise;let i;await Promise.race([this._promise.then((()=>clearTimeout(i))),new Promise(((r,s)=>{i=setTimeout((()=>s(new t("Latch timed out"))),e)}))])}isOpen(){return this._open}},exports.Mutex=r,exports.Once=class{mutex=new r({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{this._value=await e(),this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}running(){return null!==this.promise&&!this.done()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,i,r="Operation timed out"){if(null==i)return e;let s;return Promise.race([e.then((e=>(clearTimeout(s),e))),new Promise(((e,o)=>{s=setTimeout((()=>o(new t(r))),i)}))])}},exports.OnceExecutionConflict=class extends e{constructor(e){super("[Once] A method has already been registered!",e)}},exports.RWMutex=class{_readers=0;_writeLocked=!1;_pendingWriters=0;_yieldMode;readerWaiters=[];writerWaiters=[];constructor(e){this._yieldMode=e?.yieldMode??"microtask"}async rlock(e){if(!this._writeLocked&&0===this._pendingWriters)return void this._readers++;let i;const r=new Promise((e=>i=e));if(this.readerWaiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.readerWaiters.indexOf(i);-1!==e&&this.readerWaiters.splice(e,1),o(new t("RWMutex rlock timed out"))}),e)}))])}runlock(){if(this._readers<=0)throw new Error("RWMutex: runlock called without a matching rlock");this._readers--,this._tryWakeWriter()}async lock(e){if(!this._writeLocked&&0===this._readers)return void(this._writeLocked=!0);let i;this._pendingWriters++;const r=new Promise((e=>i=e));if(this.writerWaiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.writerWaiters.indexOf(i);-1!==e&&(this.writerWaiters.splice(e,1),this._pendingWriters--),o(new t("RWMutex lock timed out"))}),e)}))])}unlock(){if(!this._writeLocked)throw new Error("RWMutex: unlock called without a matching lock");this._writeLocked=!1,this._tryWakeWriter()||this._wakeAllReaders()}async read(e,t){await this.rlock(t);try{return await e()}finally{this.runlock()}}async write(e,t){await this.lock(t);try{return await e()}finally{this.unlock()}}writeLocked(){return this._writeLocked}readers(){return this._readers}pendingWriters(){return this._pendingWriters}pendingReaders(){return this.readerWaiters.length}_tryWakeWriter(){if(this._writeLocked||this._readers>0)return!1;const e=this.writerWaiters.shift();return!!e&&(this._writeLocked=!0,this._pendingWriters--,this._schedule(e),!0)}_wakeAllReaders(){const e=this.readerWaiters.splice(0);if(0!==e.length){this._readers+=e.length;for(const t of e)this._schedule(t)}}_schedule(e){"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0)}},exports.Semaphore=class{_slots;_available;_capacity;_yieldMode;waiters=[];constructor(e){if(this._slots=e?.slots??1,this._available=this._slots,this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask",this._slots<1)throw new Error("Semaphore slots must be >= 1")}async acquire(e){if(this._available>0)return void this._available--;if(this.waiters.length>=this._capacity)throw new Error(`Semaphore queue is full (capacity: ${this._capacity})`);let i;const r=new Promise((e=>i=e));if(this.waiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(i);-1!==e&&this.waiters.splice(e,1),o(new t("Semaphore acquire timed out"))}),e)}))])}tryAcquire(){return this._available>0&&(this._available--,!0)}release(){if(this._available>=this._slots)throw new Error("Semaphore released more times than acquired");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._available++}async run(e,t){await this.acquire(t);try{return await e()}finally{this.release()}}available(){return this._available}pending(){return this.waiters.length}slots(){return this._slots}},exports.Serializer=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;_hasRun=!1;constructor(e){this.mutex=new r({capacity:e?.capacity??1e3,yieldMode:e?.yieldMode??"macrotask"})}async do(e,t){if(this._done)return{value:null,error:new i};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let r,s=null;try{if(this._done)throw new i;s=await e(),this._lastValue=s,this._lastError=void 0,this._hasRun=!0}catch(e){r=e,this._lastError=e,this._hasRun=!0}finally{this.mutex.unlock()}return{value:s,error:r}}peek(){return{value:this._lastValue,error:this._lastError}}hasRun(){return this._hasRun}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}},exports.SerializerExecutionDone=i,exports.SyncError=e,exports.TimeoutError=t;
package/index.mjs CHANGED
@@ -1 +1 @@
1
- var e=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},t=class extends e{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},i=class extends e{constructor(e){super("[Serializer] The serializer has been marked as done!",e)}},r=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let i;const r=new Promise((e=>i=e));if(this.waiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(i);-1!==e&&this.waiters.splice(e,1),o(new t("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},s=class{_slots;_available;_capacity;_yieldMode;waiters=[];constructor(e){if(this._slots=e?.slots??1,this._available=this._slots,this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask",this._slots<1)throw new Error("Semaphore slots must be >= 1")}async acquire(e){if(this._available>0)return void this._available--;if(this.waiters.length>=this._capacity)throw new Error(`Semaphore queue is full (capacity: ${this._capacity})`);let i;const r=new Promise((e=>i=e));if(this.waiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(i);-1!==e&&this.waiters.splice(e,1),o(new t("Semaphore acquire timed out"))}),e)}))])}tryAcquire(){return this._available>0&&(this._available--,!0)}release(){if(this._available>=this._slots)throw new Error("Semaphore released more times than acquired");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._available++}async run(e,t){await this.acquire(t);try{return await e()}finally{this.release()}}available(){return this._available}pending(){return this.waiters.length}slots(){return this._slots}},o=class{mutex=new r({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{const t=await e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.promise=null}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}ready(){return this._done}running(){return null!==this.promise&&!this.ready()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,i,r="Operation timed out"){if(null==i)return e;let s;return Promise.race([e.then((e=>(clearTimeout(s),e))),new Promise(((e,o)=>{s=setTimeout((()=>o(new t(r))),i)}))])}},n=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;_hasRun=!1;constructor(e){this.mutex=new r({capacity:e?.capacity??1e3,yieldMode:e?.yieldMode??"macrotask"})}async do(e,t){if(this._done)return{value:null,error:new i};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let r,s=null;try{if(this._done)throw new i;s=await e(),this._lastValue=s,this._lastError=void 0,this._hasRun=!0}catch(e){r=e,this._lastError=e,this._hasRun=!0}finally{this.mutex.unlock()}return{value:s,error:r}}peek(){return{value:this._lastValue,error:this._lastError}}hasRun(){return this._hasRun}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}},a=class{_open=!1;_resolve;_promise;constructor(){this._promise=new Promise((e=>{this._resolve=e}))}open(){this._open||(this._open=!0,this._resolve())}async wait(e){if(this._open)return;if(null==e)return this._promise;let i;await Promise.race([this._promise.then((()=>clearTimeout(i))),new Promise(((r,s)=>{i=setTimeout((()=>s(new t("Latch timed out"))),e)}))])}isOpen(){return this._open}},h=class{_readers=0;_writeLocked=!1;_pendingWriters=0;_yieldMode;readerWaiters=[];writerWaiters=[];constructor(e){this._yieldMode=e?.yieldMode??"microtask"}async rlock(e){if(!this._writeLocked&&0===this._pendingWriters)return void this._readers++;let i;const r=new Promise((e=>i=e));if(this.readerWaiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.readerWaiters.indexOf(i);-1!==e&&this.readerWaiters.splice(e,1),o(new t("RWMutex rlock timed out"))}),e)}))])}runlock(){if(this._readers<=0)throw new Error("RWMutex: runlock called without a matching rlock");this._readers--,this._tryWakeWriter()}async lock(e){if(!this._writeLocked&&0===this._readers)return void(this._writeLocked=!0);let i;this._pendingWriters++;const r=new Promise((e=>i=e));if(this.writerWaiters.push(i),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.writerWaiters.indexOf(i);-1!==e&&(this.writerWaiters.splice(e,1),this._pendingWriters--),o(new t("RWMutex lock timed out"))}),e)}))])}unlock(){if(!this._writeLocked)throw new Error("RWMutex: unlock called without a matching lock");this._writeLocked=!1,this._tryWakeWriter()||this._wakeAllReaders()}async read(e,t){await this.rlock(t);try{return await e()}finally{this.runlock()}}async write(e,t){await this.lock(t);try{return await e()}finally{this.unlock()}}writeLocked(){return this._writeLocked}readers(){return this._readers}pendingWriters(){return this._pendingWriters}pendingReaders(){return this.readerWaiters.length}_tryWakeWriter(){if(this._writeLocked||this._readers>0)return!1;const e=this.writerWaiters.shift();return!!e&&(this._writeLocked=!0,this._pendingWriters--,this._schedule(e),!0)}_wakeAllReaders(){const e=this.readerWaiters.splice(0);if(0!==e.length){this._readers+=e.length;for(const t of e)this._schedule(t)}}_schedule(e){"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0)}},l=class{_delay;_leading;_timer;_pendingFn;_pendingResolvers=[];_leadingFired=!1;constructor(e){this._delay=e?.delay??300,this._leading=e?.leading??!1}do(e){return new Promise((t=>{this._enqueue(e,t)}))}fire(e){this._enqueue(e,void 0)}_enqueue(e,t){if(this._pendingFn=e,t&&this._pendingResolvers.push(t),this._leading&&!this._leadingFired)return this._leadingFired=!0,this._fire(),clearTimeout(this._timer),void(this._timer=setTimeout((()=>{this._leadingFired=!1,void 0!==this._pendingFn&&this._fire()}),this._delay));clearTimeout(this._timer),this._timer=setTimeout((()=>{this._leadingFired=!1,this._fire()}),this._delay)}cancel(){clearTimeout(this._timer),this._timer=void 0,this._pendingFn=void 0,this._leadingFired=!1;const e=this._pendingResolvers.splice(0);for(const t of e)t({status:"cancelled"})}async flush(){return this._pendingFn?(clearTimeout(this._timer),this._timer=void 0,this._leadingFired=!1,this._fire()):null}pending(){return void 0!==this._pendingFn}async _fire(){const e=this._pendingFn,t=this._pendingResolvers.splice(0);let i;this._pendingFn=void 0;try{i={status:"ok",value:await e()}}catch(e){i={status:"error",error:e}}for(const e of t)e(i);return i}};export{l as Debouncer,a as Latch,r as Mutex,o as Once,h as RWMutex,s as Semaphore,n as Serializer};
1
+ var e=class{_delay;_leading;_timer;_pendingFn;_pendingResolvers=[];_leadingFired=!1;constructor(e){this._delay=e?.delay??300,this._leading=e?.leading??!1}do(e){return new Promise((t=>{this._enqueue(e,t)}))}fire(e){this._enqueue(e,void 0)}_enqueue(e,t){if(this._pendingFn=e,t&&this._pendingResolvers.push(t),this._leading&&!this._leadingFired)return this._leadingFired=!0,this._fire(),clearTimeout(this._timer),void(this._timer=setTimeout((()=>{this._leadingFired=!1,void 0!==this._pendingFn&&this._fire()}),this._delay));clearTimeout(this._timer),this._timer=setTimeout((()=>{this._leadingFired=!1,this._fire()}),this._delay)}cancel(){clearTimeout(this._timer),this._timer=void 0,this._pendingFn=void 0,this._leadingFired=!1;const e=this._pendingResolvers.splice(0);for(const t of e)t({status:"cancelled"})}async flush(){return this._pendingFn?(clearTimeout(this._timer),this._timer=void 0,this._leadingFired=!1,this._fire()):null}pending(){return void 0!==this._pendingFn}async _fire(){const e=this._pendingFn,t=this._pendingResolvers.splice(0);let i;this._pendingFn=void 0;try{i={status:"ok",value:await e()}}catch(e){i={status:"error",error:e}}for(const e of t)e(i);return i}},t=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},i=class extends t{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},r=class extends t{constructor(e){super("[Once] A method has already been registered!",e)}},s=class extends t{constructor(e){super("[Serializer] The serializer has been marked as done!",e)}},o=class{_open=!1;_resolve;_promise;constructor(){this._promise=new Promise((e=>{this._resolve=e}))}open(){this._open||(this._open=!0,this._resolve())}async wait(e){if(this._open)return;if(null==e)return this._promise;let t;await Promise.race([this._promise.then((()=>clearTimeout(t))),new Promise(((r,s)=>{t=setTimeout((()=>s(new i("Latch timed out"))),e)}))])}isOpen(){return this._open}},n=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const r=new Promise((e=>t=e));if(this.waiters.push(t),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),o(new i("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},a=class{mutex=new n({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{this._value=await e(),this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}running(){return null!==this.promise&&!this.done()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,t,r="Operation timed out"){if(null==t)return e;let s;return Promise.race([e.then((e=>(clearTimeout(s),e))),new Promise(((e,o)=>{s=setTimeout((()=>o(new i(r))),t)}))])}},h=class{_readers=0;_writeLocked=!1;_pendingWriters=0;_yieldMode;readerWaiters=[];writerWaiters=[];constructor(e){this._yieldMode=e?.yieldMode??"microtask"}async rlock(e){if(!this._writeLocked&&0===this._pendingWriters)return void this._readers++;let t;const r=new Promise((e=>t=e));if(this.readerWaiters.push(t),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.readerWaiters.indexOf(t);-1!==e&&this.readerWaiters.splice(e,1),o(new i("RWMutex rlock timed out"))}),e)}))])}runlock(){if(this._readers<=0)throw new Error("RWMutex: runlock called without a matching rlock");this._readers--,this._tryWakeWriter()}async lock(e){if(!this._writeLocked&&0===this._readers)return void(this._writeLocked=!0);let t;this._pendingWriters++;const r=new Promise((e=>t=e));if(this.writerWaiters.push(t),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.writerWaiters.indexOf(t);-1!==e&&(this.writerWaiters.splice(e,1),this._pendingWriters--),o(new i("RWMutex lock timed out"))}),e)}))])}unlock(){if(!this._writeLocked)throw new Error("RWMutex: unlock called without a matching lock");this._writeLocked=!1,this._tryWakeWriter()||this._wakeAllReaders()}async read(e,t){await this.rlock(t);try{return await e()}finally{this.runlock()}}async write(e,t){await this.lock(t);try{return await e()}finally{this.unlock()}}writeLocked(){return this._writeLocked}readers(){return this._readers}pendingWriters(){return this._pendingWriters}pendingReaders(){return this.readerWaiters.length}_tryWakeWriter(){if(this._writeLocked||this._readers>0)return!1;const e=this.writerWaiters.shift();return!!e&&(this._writeLocked=!0,this._pendingWriters--,this._schedule(e),!0)}_wakeAllReaders(){const e=this.readerWaiters.splice(0);if(0!==e.length){this._readers+=e.length;for(const t of e)this._schedule(t)}}_schedule(e){"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0)}},l=class{_slots;_available;_capacity;_yieldMode;waiters=[];constructor(e){if(this._slots=e?.slots??1,this._available=this._slots,this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask",this._slots<1)throw new Error("Semaphore slots must be >= 1")}async acquire(e){if(this._available>0)return void this._available--;if(this.waiters.length>=this._capacity)throw new Error(`Semaphore queue is full (capacity: ${this._capacity})`);let t;const r=new Promise((e=>t=e));if(this.waiters.push(t),null==e)return void await r;let s;await Promise.race([r.then((()=>clearTimeout(s))),new Promise(((r,o)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),o(new i("Semaphore acquire timed out"))}),e)}))])}tryAcquire(){return this._available>0&&(this._available--,!0)}release(){if(this._available>=this._slots)throw new Error("Semaphore released more times than acquired");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._available++}async run(e,t){await this.acquire(t);try{return await e()}finally{this.release()}}available(){return this._available}pending(){return this.waiters.length}slots(){return this._slots}},c=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;_hasRun=!1;constructor(e){this.mutex=new n({capacity:e?.capacity??1e3,yieldMode:e?.yieldMode??"macrotask"})}async do(e,t){if(this._done)return{value:null,error:new s};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let i,r=null;try{if(this._done)throw new s;r=await e(),this._lastValue=r,this._lastError=void 0,this._hasRun=!0}catch(e){i=e,this._lastError=e,this._hasRun=!0}finally{this.mutex.unlock()}return{value:r,error:i}}peek(){return{value:this._lastValue,error:this._lastError}}hasRun(){return this._hasRun}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}};export{e as Debouncer,o as Latch,n as Mutex,a as Once,r as OnceExecutionConflict,h as RWMutex,l as Semaphore,c as Serializer,s as SerializerExecutionDone,t as SyncError,i as TimeoutError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaidimu/utils-sync",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "A collection of sync utilities.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",