@asaidimu/utils-sync 2.2.3 → 2.3.1

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.ts CHANGED
@@ -1,43 +1,46 @@
1
+ import { SystemError } from "@asaidimu/utils-error";
2
+
3
+ //#region src/sync/debouncer.d.ts
1
4
  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;
5
+ /**
6
+ * Quiet period in milliseconds. The last-enqueued function runs after
7
+ * no new calls have arrived for this duration.
8
+ * @default 300
9
+ */
10
+ delay?: number;
11
+ /**
12
+ * If true, also fires immediately on the leading edge (first call in a
13
+ * quiet period), then debounces subsequent calls normally.
14
+ *
15
+ * Leading-edge semantics: the leading call fires immediately and clears
16
+ * _pendingFn. Any calls arriving *while* the leading execution is in flight
17
+ * register as a new trailing call and will be resolved by the trailing timer
18
+ * when the quiet period expires. Calls arriving during the leading execution
19
+ * are never lost.
20
+ *
21
+ * @default false
22
+ */
23
+ leading?: boolean;
21
24
  }
22
25
  /**
23
26
  * Result when the debounced function successfully executed.
24
27
  */
25
28
  type DebouncerOk<T> = {
26
- status: "ok";
27
- value: T;
29
+ status: "ok";
30
+ value: T;
28
31
  };
29
32
  /**
30
33
  * Result when the debounced function threw an error.
31
34
  */
32
35
  type DebouncerError = {
33
- status: "error";
34
- error: unknown;
36
+ status: "error";
37
+ error: unknown;
35
38
  };
36
39
  /**
37
40
  * Result when the debounced call was cancelled before execution.
38
41
  */
39
42
  type DebouncerCancelled = {
40
- status: "cancelled";
43
+ status: "cancelled";
41
44
  };
42
45
  /**
43
46
  * Discriminated union of all possible Debouncer outcomes.
@@ -67,179 +70,77 @@ type DebouncerResult<T> = DebouncerOk<T> | DebouncerError | DebouncerCancelled;
67
70
  * // b and c share the same result as a.
68
71
  */
69
72
  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
- /**
126
- * Severity levels for structured issues and errors.
127
- */
128
- type Severity = "error" | "warning" | "info";
129
- /**
130
- * Represents a detailed validation or operational problem.
131
- * Designed to be machine-readable while remaining human-friendly.
132
- */
133
- interface Issue {
134
- /** Machine-readable identifier (e.g., "REQUIRED_FIELD_MISSING"). */
135
- readonly code: string;
136
- /** Human-readable description of the problem. */
137
- readonly message: string;
138
- /** Field path in a document or state (e.g., "users.0.email"). */
139
- readonly path?: string;
140
- /** Optional array index when the issue relates to a specific element. */
141
- readonly index?: number;
142
- /** Seriousness of the issue. Defaults to "error". */
143
- readonly severity?: Severity;
144
- /** Optional detailed explanation, can be multi-line. */
145
- readonly description?: string;
146
- /** Nested issues that caused this one. */
147
- readonly cause?: readonly Issue[];
148
- }
149
- /**
150
- * Standardized error code format: CATEGORY-XXX[-SUFFIX]
151
- * Examples:
152
- * - VAL-001 (Validation: required field missing)
153
- * - DB-002-NF (Database: not found)
154
- * - AUTH-003-DENIED (Auth: permission denied)
155
- */
156
- interface ErrorCodeMetadata {
157
- /** The formal error code (e.g., "VAL-001") */
158
- readonly code: string;
159
- /** Human-readable name for the error type */
160
- readonly name: string;
161
- /** Detailed description of when this error occurs */
162
- readonly description: string;
163
- /** Suggested action for handling or resolving the error */
164
- readonly action?: string;
165
- /** HTTP status code mapping (if applicable) */
166
- readonly httpStatus?: number;
167
- /** Category of the error */
168
- readonly category: ErrorCategory;
169
- /** Whether this error should be logged as warning vs error */
170
- readonly logLevel?: "debug" | "info" | "warn" | "error";
171
- }
172
- type ErrorCategory = "validation" | "database" | "auth" | "business" | "system" | "network" | "concurrency" | "custom";
173
- /**
174
- * SystemError is the primary error type for all operational and validation errors.
175
- * Supports both predefined and custom error codes with full metadata.
176
- */
177
- declare class SystemError extends Error {
178
- readonly code: string;
179
- readonly codeMetadata: ErrorCodeMetadata;
180
- readonly severity: Severity;
181
- readonly path?: string;
182
- readonly operation?: string;
183
- readonly issues: readonly Issue[];
184
- readonly cause?: unknown;
185
- constructor(params: {
186
- code: string;
187
- message?: string;
188
- severity?: Severity;
189
- path?: string;
190
- operation?: string;
191
- issues?: readonly Issue[];
192
- cause?: unknown;
193
- metadata?: Partial<Omit<ErrorCodeMetadata, "code">>;
194
- });
195
- /**
196
- * Returns a new SystemError with the specified path.
197
- */
198
- withPath(path: string): SystemError;
199
- /**
200
- * Returns a new SystemError with the specified operation name.
201
- */
202
- withOperation(operation: string): SystemError;
203
- /**
204
- * Returns a new SystemError with the specified issue appended.
205
- */
206
- withIssue(issue: Issue): SystemError;
207
- /**
208
- * Returns a new SystemError with multiple issues appended.
209
- */
210
- withIssues(issues: readonly Issue[]): SystemError;
211
- /**
212
- * Returns a new SystemError wrapping the specified cause.
213
- */
214
- withCause(cause: unknown): SystemError;
215
- /**
216
- * Returns the HTTP status code for this error (if applicable).
217
- */
218
- getHttpStatus(): number | undefined;
219
- /**
220
- * Formats the error for logging with structured metadata.
221
- */
222
- toLogEntry(): Record<string, unknown>;
223
- /**
224
- * Produces a human-readable representation suitable for logging.
225
- */
226
- toString(): string;
73
+ private _delay;
74
+ private _leading;
75
+ private _timer;
76
+ private _pendingFn;
77
+ private _pendingResolvers;
78
+ private _leadingFired;
79
+ constructor(options?: DebouncerOptions);
80
+ /**
81
+ * Enqueues a function and returns a promise that resolves with its result.
82
+ *
83
+ * All callers within the same quiet window share the same result — the
84
+ * function that actually runs is the *last* one enqueued before the delay
85
+ * expires. Use this when you need the return value of the debounced call.
86
+ *
87
+ * For fire-and-forget use (void fns, event handlers), prefer `fire()` to
88
+ * avoid unhandled-promise-rejection warnings and make intent explicit.
89
+ *
90
+ * @param fn - The function to debounce.
91
+ * @returns A promise resolving with a DebouncerResult discriminated union.
92
+ */
93
+ do(fn: () => Promise<T> | T): Promise<DebouncerResult<T>>;
94
+ /**
95
+ * Enqueues a function without returning a promise (fire-and-forget).
96
+ *
97
+ * Identical debounce semantics to `do()` — the last-enqueued function runs
98
+ * after the quiet period. Use this for void callbacks, DOM event handlers,
99
+ * or any caller that doesn't care about the result. No dangling promise,
100
+ * no unhandled-rejection risk.
101
+ *
102
+ * @param fn - The function to debounce.
103
+ */
104
+ fire(fn: () => Promise<T> | T): void;
105
+ /**
106
+ * Shared enqueue logic for both `do()` and `fire()`.
107
+ *
108
+ */
109
+ private _enqueue;
110
+ /**
111
+ * Immediately cancels any pending debounced execution.
112
+ * All waiting callers resolve with `{ status: "cancelled" }`.
113
+ */
114
+ cancel(): void;
115
+ /**
116
+ * Immediately executes the pending function (if any), bypassing the remaining delay.
117
+ * All waiting callers receive the result.
118
+ */
119
+ flush(): Promise<DebouncerResult<T> | null>;
120
+ /** Returns true if a debounced call is currently pending. */
121
+ pending(): boolean;
122
+ /**
123
+ * Executes the latest pending function and resolves all waiting callers.
124
+ */
125
+ private _fire;
227
126
  }
228
-
127
+ //#endregion
128
+ //#region src/sync/errors.d.ts
229
129
  /** Represents all errors within the sync system */
230
130
  declare class SyncError extends SystemError {
231
- constructor(message: string, cause?: unknown);
131
+ constructor(message: string, cause?: unknown);
232
132
  }
233
133
  declare class TimeoutError extends SyncError {
234
- constructor(message?: string);
134
+ constructor(message?: string);
235
135
  }
236
136
  declare class OnceExecutionConflict extends SyncError {
237
- constructor(cause?: unknown);
137
+ constructor(cause?: unknown);
238
138
  }
239
139
  declare class SerializerExecutionDone extends SyncError {
240
- constructor(cause?: unknown);
140
+ constructor(cause?: unknown);
241
141
  }
242
-
142
+ //#endregion
143
+ //#region src/sync/latch.d.ts
243
144
  /**
244
145
  * A one-shot gate that starts closed and opens exactly once.
245
146
  *
@@ -258,52 +159,53 @@ declare class SerializerExecutionDone extends SyncError {
258
159
  * ready.open();
259
160
  */
260
161
  declare class Latch {
261
- private _open;
262
- private _resolve;
263
- private _promise;
264
- constructor();
265
- /**
266
- * Opens the latch. All current and future waiters resolve immediately.
267
- * Calling open() more than once is a no-op.
268
- */
269
- open(): void;
270
- /**
271
- * Returns a promise that resolves when the latch is opened.
272
- * If already open, resolves on the next microtask checkpoint.
273
- *
274
- * @param timeout - Optional maximum wait time in milliseconds.
275
- * @throws {TimeoutError} If the latch does not open within the timeout.
276
- */
277
- wait(timeout?: number): Promise<void>;
278
- /**
279
- * Synchronously checks whether the latch has been opened.
280
- */
281
- isOpen(): boolean;
162
+ private _open;
163
+ private _resolve;
164
+ private _promise;
165
+ constructor();
166
+ /**
167
+ * Opens the latch. All current and future waiters resolve immediately.
168
+ * Calling open() more than once is a no-op.
169
+ */
170
+ open(): void;
171
+ /**
172
+ * Returns a promise that resolves when the latch is opened.
173
+ * If already open, resolves on the next microtask checkpoint.
174
+ *
175
+ * @param timeout - Optional maximum wait time in milliseconds.
176
+ * @throws {TimeoutError} If the latch does not open within the timeout.
177
+ */
178
+ wait(timeout?: number): Promise<void>;
179
+ /**
180
+ * Synchronously checks whether the latch has been opened.
181
+ */
182
+ isOpen(): boolean;
282
183
  }
283
-
184
+ //#endregion
185
+ //#region src/sync/mutex.d.ts
284
186
  interface MutexOptions {
285
- /**
286
- * Maximum number of pending requests allowed in the queue.
287
- * If exceeded, tryLock/lock will fail.
288
- * @default Infinity
289
- */
290
- capacity?: number;
291
- /**
292
- * Controls how lock handoff is scheduled when a waiter is unblocked.
293
- *
294
- * - `"macrotask"` (default): Uses setTimeout(fn, 0) to yield to the event
295
- * loop between handoffs. Prevents microtask starvation under heavy
296
- * contention — I/O, rendering, and other macrotasks can run between
297
- * lock acquisitions. Use for Serializer and other coarse-grained
298
- * serializers.
299
- *
300
- * - `"microtask"`: Uses queueMicrotask(fn) for handoff. Near-zero latency
301
- * between acquisitions — no macrotask delay. Safe when you need
302
- * back-to-back operations to complete as fast as possible and starvation
303
- * is not a concern (e.g. Once, Semaphore where builds are infrequent
304
- * and latency matters more than fairness).
305
- */
306
- yieldMode?: "macrotask" | "microtask";
187
+ /**
188
+ * Maximum number of pending requests allowed in the queue.
189
+ * If exceeded, tryLock/lock will fail.
190
+ * @default Infinity
191
+ */
192
+ capacity?: number;
193
+ /**
194
+ * Controls how lock handoff is scheduled when a waiter is unblocked.
195
+ *
196
+ * - `"macrotask"` (default): Uses setTimeout(fn, 0) to yield to the event
197
+ * loop between handoffs. Prevents microtask starvation under heavy
198
+ * contention — I/O, rendering, and other macrotasks can run between
199
+ * lock acquisitions. Use for Serializer and other coarse-grained
200
+ * serializers.
201
+ *
202
+ * - `"microtask"`: Uses queueMicrotask(fn) for handoff. Near-zero latency
203
+ * between acquisitions — no macrotask delay. Safe when you need
204
+ * back-to-back operations to complete as fast as possible and starvation
205
+ * is not a concern (e.g. Once, Semaphore where builds are infrequent
206
+ * and latency matters more than fairness).
207
+ */
208
+ yieldMode?: "macrotask" | "microtask";
307
209
  }
308
210
  /**
309
211
  * A mutual exclusion lock.
@@ -314,43 +216,44 @@ interface MutexOptions {
314
216
  * - "microtask": zero-delay handoff for latency-sensitive paths.
315
217
  */
316
218
  declare class Mutex {
317
- private _locked;
318
- private _capacity;
319
- private _yieldMode;
320
- private waiters;
321
- constructor(options?: MutexOptions);
322
- /**
323
- * Acquires the lock. If already held, waits until released or timeout reached.
324
- *
325
- * @param timeout - Optional maximum wait time in milliseconds.
326
- * @throws {TimeoutError} If the lock cannot be acquired within the timeout.
327
- * @throws {Error} If the wait queue is full (backpressure).
328
- */
329
- lock(timeout?: number): Promise<void>;
330
- /**
331
- * Attempts to acquire the lock without waiting.
332
- * @returns `true` if the lock was acquired, `false` otherwise.
333
- */
334
- tryLock(): boolean;
335
- /**
336
- * Releases the lock, scheduling the next waiter according to yieldMode.
337
- *
338
- * When a waiter exists, `_locked` intentionally remains `true` — ownership
339
- * transfers directly to the next waiter without ever clearing the flag.
340
- * Only when the queue is empty is `_locked` set to false.
341
- *
342
- * @throws {Error} If the mutex is not currently locked.
343
- */
344
- unlock(): void;
345
- /** Returns true if the mutex is currently locked. */
346
- locked(): boolean;
347
- /** Returns the number of operations waiting for the lock. */
348
- pending(): number;
219
+ private _locked;
220
+ private _capacity;
221
+ private _yieldMode;
222
+ private waiters;
223
+ constructor(options?: MutexOptions);
224
+ /**
225
+ * Acquires the lock. If already held, waits until released or timeout reached.
226
+ *
227
+ * @param timeout - Optional maximum wait time in milliseconds.
228
+ * @throws {TimeoutError} If the lock cannot be acquired within the timeout.
229
+ * @throws {Error} If the wait queue is full (backpressure).
230
+ */
231
+ lock(timeout?: number): Promise<void>;
232
+ /**
233
+ * Attempts to acquire the lock without waiting.
234
+ * @returns `true` if the lock was acquired, `false` otherwise.
235
+ */
236
+ tryLock(): boolean;
237
+ /**
238
+ * Releases the lock, scheduling the next waiter according to yieldMode.
239
+ *
240
+ * When a waiter exists, `_locked` intentionally remains `true` — ownership
241
+ * transfers directly to the next waiter without ever clearing the flag.
242
+ * Only when the queue is empty is `_locked` set to false.
243
+ *
244
+ * @throws {Error} If the mutex is not currently locked.
245
+ */
246
+ unlock(): void;
247
+ /** Returns true if the mutex is currently locked. */
248
+ locked(): boolean;
249
+ /** Returns the number of operations waiting for the lock. */
250
+ pending(): number;
349
251
  }
350
-
252
+ //#endregion
253
+ //#region src/sync/once.d.ts
351
254
  type OnceResult<T> = {
352
- value: T | null;
353
- error?: unknown;
255
+ value: T | null;
256
+ error?: unknown;
354
257
  };
355
258
  /**
356
259
  * Ensures a specific task is executed exactly once, regardless of concurrent callers.
@@ -362,97 +265,101 @@ type OnceResult<T> = {
362
265
  * @template T - The type of value returned by the execution.
363
266
  */
364
267
  declare class Once<T = void> {
365
- private mutex;
366
- private promise;
367
- private _value;
368
- private _error;
369
- private _done;
370
- private retry;
371
- private throws;
372
- constructor({ retry, throws }?: {
373
- retry?: boolean;
374
- throws?: boolean;
375
- });
376
- /**
377
- * Manually sets a value, marking the operation as done.
378
- * Throws if the operation is already done or currently running.
379
- *
380
- * @param value - The value to resolve to.
381
- */
382
- resolve(value: T): void;
383
- /**
384
- * Execute the function if it hasn't been executed yet.
385
- * Subsequent calls return the result of the first execution.
386
- *
387
- * @param fn - The function to execute.
388
- * @param timeout - Max wait time in ms (includes lock wait + execution time).
389
- */
390
- do(fn: () => Promise<T> | T, timeout?: number): Promise<OnceResult<T>>;
391
- /**
392
- * Executes the function synchronously if it hasn't been executed yet.
393
- * If an async operation is pending or the lock is contended, it will fail immediately.
394
- * The failure is returned as an error state, OR thrown if `throws: true` is configured.
395
- *
396
- * @param fn - The synchronous function to execute.
397
- * @returns The result of the execution.
398
- */
399
- doSync(fn: () => T): OnceResult<T>;
400
- /**
401
- * Returns true if the operation is currently executing.
402
- *
403
- * Uses ready() as the canonical check: running is its logical complement
404
- * when a promise is in flight. This correctly handles the brief window where
405
- * _done=true but the promise hasn't been cleared yet in finally.
406
- */
407
- running(): boolean;
408
- /**
409
- * Returns the current state without waiting.
410
- */
411
- peek(): OnceResult<T>;
412
- /**
413
- * Returns the stored value if successful, otherwise throws the stored error.
414
- * Throws if the operation is not yet complete.
415
- */
416
- get(): T | null;
417
- /**
418
- * Resets the instance, allowing the action to run again on the next call.
419
- *
420
- * @throws {Error} If called while an operation is currently executing,
421
- * as this would leave the in-flight promise in an inconsistent state.
422
- */
423
- reset(): void;
424
- /**
425
- * Returns true if the operation has finished (success or final failure).
426
- */
427
- done(): boolean;
428
- /**
429
- * Returns the in-flight execution promise if one is currently running, null otherwise.
430
- *
431
- * Use this to join an in-progress operation without triggering a new one.
432
- */
433
- current(): Promise<OnceResult<T>> | null;
434
- /**
435
- * Awaits a promise with an optional timeout.
436
- *
437
- * We clear the timer on the winning branch, consistent with the
438
- * Mutex/Semaphore/Latch timeout pattern throughout this module.
439
- */
440
- private _awaitWithTimeout;
268
+ private mutex;
269
+ private promise;
270
+ private _value;
271
+ private _error;
272
+ private _done;
273
+ private retry;
274
+ private throws;
275
+ constructor({
276
+ retry,
277
+ throws
278
+ }?: {
279
+ retry?: boolean;
280
+ throws?: boolean;
281
+ });
282
+ /**
283
+ * Manually sets a value, marking the operation as done.
284
+ * Throws if the operation is already done or currently running.
285
+ *
286
+ * @param value - The value to resolve to.
287
+ */
288
+ resolve(value: T): void;
289
+ /**
290
+ * Execute the function if it hasn't been executed yet.
291
+ * Subsequent calls return the result of the first execution.
292
+ *
293
+ * @param fn - The function to execute.
294
+ * @param timeout - Max wait time in ms (includes lock wait + execution time).
295
+ */
296
+ do(fn: () => Promise<T> | T, timeout?: number): Promise<OnceResult<T>>;
297
+ /**
298
+ * Executes the function synchronously if it hasn't been executed yet.
299
+ * If an async operation is pending or the lock is contended, it will fail immediately.
300
+ * The failure is returned as an error state, OR thrown if `throws: true` is configured.
301
+ *
302
+ * @param fn - The synchronous function to execute.
303
+ * @returns The result of the execution.
304
+ */
305
+ doSync(fn: () => T): OnceResult<T>;
306
+ /**
307
+ * Returns true if the operation is currently executing.
308
+ *
309
+ * Uses ready() as the canonical check: running is its logical complement
310
+ * when a promise is in flight. This correctly handles the brief window where
311
+ * _done=true but the promise hasn't been cleared yet in finally.
312
+ */
313
+ running(): boolean;
314
+ /**
315
+ * Returns the current state without waiting.
316
+ */
317
+ peek(): OnceResult<T>;
318
+ /**
319
+ * Returns the stored value if successful, otherwise throws the stored error.
320
+ * Throws if the operation is not yet complete.
321
+ */
322
+ get(): T | null;
323
+ /**
324
+ * Resets the instance, allowing the action to run again on the next call.
325
+ *
326
+ * @throws {Error} If called while an operation is currently executing,
327
+ * as this would leave the in-flight promise in an inconsistent state.
328
+ */
329
+ reset(): void;
330
+ /**
331
+ * Returns true if the operation has finished (success or final failure).
332
+ */
333
+ done(): boolean;
334
+ /**
335
+ * Returns the in-flight execution promise if one is currently running, null otherwise.
336
+ *
337
+ * Use this to join an in-progress operation without triggering a new one.
338
+ */
339
+ current(): Promise<OnceResult<T>> | null;
340
+ /**
341
+ * Awaits a promise with an optional timeout.
342
+ *
343
+ * We clear the timer on the winning branch, consistent with the
344
+ * Mutex/Semaphore/Latch timeout pattern throughout this module.
345
+ */
346
+ private _awaitWithTimeout;
441
347
  }
442
-
348
+ //#endregion
349
+ //#region src/sync/rwmutex.d.ts
443
350
  interface RWMutexOptions {
444
- /**
445
- * Controls how lock handoff is scheduled when a waiter is unblocked.
446
- *
447
- * - `"microtask"` (default): Uses queueMicrotask(fn). Near-zero latency
448
- * between handoffs. Appropriate for RWMutex because readers are woken in
449
- * bulk and writers are rarely contended enough to cause starvation.
450
- *
451
- * - `"macrotask"`: Uses setTimeout(fn, 0) to yield to the event loop between
452
- * handoffs. Use if you observe microtask starvation under extreme write
453
- * contention on this specific lock.
454
- */
455
- yieldMode?: "macrotask" | "microtask";
351
+ /**
352
+ * Controls how lock handoff is scheduled when a waiter is unblocked.
353
+ *
354
+ * - `"microtask"` (default): Uses queueMicrotask(fn). Near-zero latency
355
+ * between handoffs. Appropriate for RWMutex because readers are woken in
356
+ * bulk and writers are rarely contended enough to cause starvation.
357
+ *
358
+ * - `"macrotask"`: Uses setTimeout(fn, 0) to yield to the event loop between
359
+ * handoffs. Use if you observe microtask starvation under extreme write
360
+ * contention on this specific lock.
361
+ */
362
+ yieldMode?: "macrotask" | "microtask";
456
363
  }
457
364
  /**
458
365
  * A read-write mutex with writer preference.
@@ -467,80 +374,81 @@ interface RWMutexOptions {
467
374
  * Prefer the `read()` and `write()` convenience wrappers to avoid lock leaks.
468
375
  */
469
376
  declare class RWMutex {
470
- private _readers;
471
- private _writeLocked;
472
- private _pendingWriters;
473
- private _yieldMode;
474
- private readerWaiters;
475
- private writerWaiters;
476
- constructor(options?: RWMutexOptions);
477
- /**
478
- * Acquires a read lock. Blocks if a writer holds or is waiting for the lock.
479
- *
480
- * @param timeout - Optional maximum wait time in milliseconds.
481
- * @throws {TimeoutError} If the read lock cannot be acquired within the timeout.
482
- */
483
- rlock(timeout?: number): Promise<void>;
484
- /**
485
- * Releases a read lock.
486
- * @throws {Error} If no read lock is currently held.
487
- */
488
- runlock(): void;
489
- /**
490
- * Acquires the write lock. Blocks until all current readers and any prior
491
- * writers have finished.
492
- *
493
- * @param timeout - Optional maximum wait time in milliseconds.
494
- * @throws {TimeoutError} If the write lock cannot be acquired within the timeout.
495
- */
496
- lock(timeout?: number): Promise<void>;
497
- /**
498
- * Releases the write lock.
499
- * Wakes the next pending writer if one exists; otherwise wakes all pending readers.
500
- *
501
- * @throws {Error} If no write lock is currently held.
502
- */
503
- unlock(): void;
504
- /**
505
- * Runs a function under a read lock, releasing it when done.
506
- * Prefer this over manual rlock/runlock to avoid lock leaks.
507
- */
508
- read<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
509
- /**
510
- * Runs a function under the write lock, releasing it when done.
511
- * Prefer this over manual lock/unlock to avoid lock leaks.
512
- */
513
- write<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
514
- /** Returns true if the write lock is currently held. */
515
- writeLocked(): boolean;
516
- /** Returns the number of active read lock holders. */
517
- readers(): number;
518
- /** Returns the number of writers currently waiting for the lock. */
519
- pendingWriters(): number;
520
- /** Returns the number of readers currently waiting for the lock. */
521
- pendingReaders(): number;
522
- /**
523
- * Wakes the next writer if the lock is free and a writer is waiting.
524
- * Returns true if a writer was woken, false otherwise.
525
- */
526
- private _tryWakeWriter;
527
- /**
528
- * Wakes all pending readers (called when a writer releases and no writers are queued).
529
- */
530
- private _wakeAllReaders;
531
- /**
532
- * Schedules a waiter callback according to the configured yieldMode.
533
- */
534
- private _schedule;
377
+ private _readers;
378
+ private _writeLocked;
379
+ private _pendingWriters;
380
+ private _yieldMode;
381
+ private readerWaiters;
382
+ private writerWaiters;
383
+ constructor(options?: RWMutexOptions);
384
+ /**
385
+ * Acquires a read lock. Blocks if a writer holds or is waiting for the lock.
386
+ *
387
+ * @param timeout - Optional maximum wait time in milliseconds.
388
+ * @throws {TimeoutError} If the read lock cannot be acquired within the timeout.
389
+ */
390
+ rlock(timeout?: number): Promise<void>;
391
+ /**
392
+ * Releases a read lock.
393
+ * @throws {Error} If no read lock is currently held.
394
+ */
395
+ runlock(): void;
396
+ /**
397
+ * Acquires the write lock. Blocks until all current readers and any prior
398
+ * writers have finished.
399
+ *
400
+ * @param timeout - Optional maximum wait time in milliseconds.
401
+ * @throws {TimeoutError} If the write lock cannot be acquired within the timeout.
402
+ */
403
+ lock(timeout?: number): Promise<void>;
404
+ /**
405
+ * Releases the write lock.
406
+ * Wakes the next pending writer if one exists; otherwise wakes all pending readers.
407
+ *
408
+ * @throws {Error} If no write lock is currently held.
409
+ */
410
+ unlock(): void;
411
+ /**
412
+ * Runs a function under a read lock, releasing it when done.
413
+ * Prefer this over manual rlock/runlock to avoid lock leaks.
414
+ */
415
+ read<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
416
+ /**
417
+ * Runs a function under the write lock, releasing it when done.
418
+ * Prefer this over manual lock/unlock to avoid lock leaks.
419
+ */
420
+ write<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
421
+ /** Returns true if the write lock is currently held. */
422
+ writeLocked(): boolean;
423
+ /** Returns the number of active read lock holders. */
424
+ readers(): number;
425
+ /** Returns the number of writers currently waiting for the lock. */
426
+ pendingWriters(): number;
427
+ /** Returns the number of readers currently waiting for the lock. */
428
+ pendingReaders(): number;
429
+ /**
430
+ * Wakes the next writer if the lock is free and a writer is waiting.
431
+ * Returns true if a writer was woken, false otherwise.
432
+ */
433
+ private _tryWakeWriter;
434
+ /**
435
+ * Wakes all pending readers (called when a writer releases and no writers are queued).
436
+ */
437
+ private _wakeAllReaders;
438
+ /**
439
+ * Schedules a waiter callback according to the configured yieldMode.
440
+ */
441
+ private _schedule;
535
442
  }
536
-
443
+ //#endregion
444
+ //#region src/sync/semaphore.d.ts
537
445
  interface SemaphoreOptions extends MutexOptions {
538
- /**
539
- * Number of concurrent holders allowed.
540
- * Inherits `capacity` and `yieldMode` from MutexOptions.
541
- * @default 1 (equivalent to a Mutex)
542
- */
543
- slots?: number;
446
+ /**
447
+ * Number of concurrent holders allowed.
448
+ * Inherits `capacity` and `yieldMode` from MutexOptions.
449
+ * @default 1 (equivalent to a Mutex)
450
+ */
451
+ slots?: number;
544
452
  }
545
453
  /**
546
454
  * A counting semaphore — generalises Mutex to allow up to N concurrent holders.
@@ -551,114 +459,137 @@ interface SemaphoreOptions extends MutexOptions {
551
459
  * Accepts the same `capacity` and `yieldMode` options as Mutex, plus `slots`.
552
460
  */
553
461
  declare class Semaphore {
554
- private _slots;
555
- private _available;
556
- private _capacity;
557
- private _yieldMode;
558
- private waiters;
559
- constructor(options?: SemaphoreOptions);
560
- /**
561
- * Acquires one slot. If all slots are taken, waits until one is released.
562
- *
563
- * @param timeout - Optional maximum wait time in milliseconds.
564
- * @throws {TimeoutError} If a slot cannot be acquired within the timeout.
565
- * @throws {Error} If the wait queue is full.
566
- */
567
- acquire(timeout?: number): Promise<void>;
568
- /**
569
- * Attempts to acquire a slot without waiting.
570
- * @returns `true` if a slot was acquired, `false` otherwise.
571
- */
572
- tryAcquire(): boolean;
573
- /**
574
- * Releases one slot. If waiters are queued, the next one is scheduled
575
- * according to yieldMode.
576
- *
577
- * @throws {Error} If no slot is currently held (release without acquire).
578
- */
579
- release(): void;
580
- /**
581
- * Runs a function with one acquired slot, releasing it when done.
582
- * Convenience wrapper — prefer this over manual acquire/release.
583
- */
584
- run<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
585
- /** Number of slots currently free. */
586
- available(): number;
587
- /** Number of callers waiting for a slot. */
588
- pending(): number;
589
- /** Total slot count (as configured). */
590
- slots(): number;
462
+ private _slots;
463
+ private _available;
464
+ private _capacity;
465
+ private _yieldMode;
466
+ private waiters;
467
+ constructor(options?: SemaphoreOptions);
468
+ /**
469
+ * Acquires one slot. If all slots are taken, waits until one is released.
470
+ *
471
+ * @param timeout - Optional maximum wait time in milliseconds.
472
+ * @throws {TimeoutError} If a slot cannot be acquired within the timeout.
473
+ * @throws {Error} If the wait queue is full.
474
+ */
475
+ acquire(timeout?: number): Promise<void>;
476
+ /**
477
+ * Attempts to acquire a slot without waiting.
478
+ * @returns `true` if a slot was acquired, `false` otherwise.
479
+ */
480
+ tryAcquire(): boolean;
481
+ /**
482
+ * Releases one slot. If waiters are queued, the next one is scheduled
483
+ * according to yieldMode.
484
+ *
485
+ * @throws {Error} If no slot is currently held (release without acquire).
486
+ */
487
+ release(): void;
488
+ /**
489
+ * Runs a function with one acquired slot, releasing it when done.
490
+ * Convenience wrapper — prefer this over manual acquire/release.
491
+ */
492
+ run<T>(fn: () => Promise<T> | T, timeout?: number): Promise<T>;
493
+ /** Number of slots currently free. */
494
+ available(): number;
495
+ /** Number of callers waiting for a slot. */
496
+ pending(): number;
497
+ /** Total slot count (as configured). */
498
+ slots(): number;
591
499
  }
592
-
500
+ //#endregion
501
+ //#region src/sync/serializer.d.ts
593
502
  type SerializerResult<T> = {
594
- value: T | null;
595
- error?: unknown;
503
+ value: T | null;
504
+ error?: unknown;
596
505
  };
597
506
  interface SerializerOptions {
598
- /**
599
- * Max items in queue. If full, .do() returns an error immediately.
600
- * @default 1000
601
- */
602
- capacity?: number;
603
- /**
604
- * Yield mode for the internal mutex. Defaults to "macrotask" for
605
- * coarse-grained serializers. Use "microtask" for fine-grained
606
- * latency-sensitive serializers.
607
- */
608
- yieldMode?: "macrotask" | "microtask";
507
+ /**
508
+ * Max items in queue. If full, .do() returns an error immediately.
509
+ * @default 1000
510
+ */
511
+ capacity?: number;
512
+ /**
513
+ * Yield mode for the internal mutex. Defaults to "macrotask" for
514
+ * coarse-grained serializers. Use "microtask" for fine-grained
515
+ * latency-sensitive serializers.
516
+ */
517
+ yieldMode?: "macrotask" | "microtask";
609
518
  }
610
519
  /**
611
520
  * Ensures tasks are executed sequentially (FIFO).
612
521
  * Maintains the result of the last successful execution.
613
522
  * Includes backpressure protection via configurable queue capacity.
614
523
  */
615
- declare class Serializer<T = void> {
616
- private mutex;
617
- private _done;
618
- private _lastValue;
619
- private _lastError;
620
- private _hasRun;
621
- constructor(options?: SerializerOptions);
622
- /**
623
- * Enqueue a function to be executed after all previous tasks complete.
624
- *
625
- * @param fn - The function to execute.
626
- * @param timeout - Max time to wait to acquire the lock.
627
- * @returns Object containing the value or error.
628
- */
629
- do(fn: () => Promise<T> | T, timeout?: number): Promise<SerializerResult<T | null>>;
630
- /**
631
- * Returns the result of the last execution.
632
- *
633
- * Returns `{ value: null, error: undefined }` both when the serializer has
634
- * never run and when it ran and returned null — use `hasRun()` to distinguish
635
- * these two cases.
636
- */
637
- peek(): SerializerResult<T | null>;
638
- /**
639
- * Returns true if at least one task has been executed (successfully or not).
640
- */
641
- hasRun(): boolean;
642
- /**
643
- * Permanently closes the serializer.
644
- * Subsequent calls to `do()` will fail immediately with SerializerExecutionDone.
645
- */
646
- close(): void;
647
- /** Returns the number of tasks currently waiting. */
648
- pending(): number;
649
- /** Returns true if a task is currently executing. */
650
- running(): boolean;
524
+ declare class Serializer<T = any> {
525
+ private mutex;
526
+ private _closed;
527
+ private _lastValue;
528
+ private _lastError;
529
+ private _hasRun;
530
+ private _closeOnce;
531
+ constructor(options?: SerializerOptions);
532
+ /**
533
+ * Enqueue a function to be executed after all previous tasks complete.
534
+ *
535
+ * @param fn - The function to execute.
536
+ * @param timeout - Max time to wait to acquire the lock.
537
+ * @returns Object containing the value or error.
538
+ */
539
+ do(fn: () => Promise<T> | T, timeout?: number): Promise<SerializerResult<T | null>>;
540
+ /**
541
+ * Permanently closes the serializer, preventing new tasks from being enqueued,
542
+ * and returns a promise that resolves once all previously enqueued tasks have
543
+ * finished executing.
544
+ *
545
+ * - The seal (`_closed = true`) happens synchronously on the first call, so any
546
+ * concurrent or subsequent `.do()` calls are rejected immediately.
547
+ * - The drain sentinel is enqueued exactly once via `Once`, so concurrent
548
+ * `close()` callers all join the same promise rather than each enqueuing
549
+ * their own sentinel.
550
+ *
551
+ * @returns A promise that resolves when the serializer is fully drained.
552
+ */
553
+ close(): Promise<void>;
554
+ /**
555
+ * Returns true if the serializer has been closed and fully drained —
556
+ * i.e., close() has been called and the drain sentinel has completed.
557
+ */
558
+ done(): boolean;
559
+ /**
560
+ * Returns the result of the last execution.
561
+ *
562
+ * Returns `{ value: null, error: undefined }` both when the serializer has
563
+ * never run and when it ran and returned null — use `hasRun()` to distinguish
564
+ * these two cases.
565
+ */
566
+ peek(): SerializerResult<T | null>;
567
+ /**
568
+ * Returns true if at least one task has been executed (successfully or not).
569
+ */
570
+ hasRun(): boolean;
571
+ /** Returns the number of tasks currently waiting. */
572
+ pending(): number;
573
+ /** Returns true if a task is currently executing. */
574
+ running(): boolean;
575
+ /**
576
+ * Raw lock → execute → unlock path. No `_closed` guard.
577
+ * Used by both `do()` (after the guard passes) and the drain sentinel
578
+ * inside `close()`.
579
+ */
580
+ private _enqueue;
651
581
  }
652
-
582
+ //#endregion
583
+ //#region src/sync/shared-resource.d.ts
653
584
  interface SharedResourceOptions {
654
- /**
655
- * How long to wait after the last subscriber leaves before cleaning up.
656
- * - "microtask": (Default) Waits until the end of the current synchronous execution tick.
657
- * Perfect for React StrictMode or concurrent rendering.
658
- * - number: Waits for the specified milliseconds. Useful for debouncing rapid reconnects.
659
- * - "sync": Cleans up immediately (bypasses the grace period entirely).
660
- */
661
- gracePeriod?: "microtask" | "sync" | number;
585
+ /**
586
+ * How long to wait after the last subscriber leaves before cleaning up.
587
+ * - "microtask": (Default) Waits until the end of the current synchronous execution tick.
588
+ * Perfect for React StrictMode or concurrent rendering.
589
+ * - number: Waits for the specified milliseconds. Useful for debouncing rapid reconnects.
590
+ * - "sync": Cleans up immediately (bypasses the grace period entirely).
591
+ */
592
+ gracePeriod?: "microtask" | "sync" | number;
662
593
  }
663
594
  /**
664
595
  * Manages the lifecycle of a reference-counted shared resource.
@@ -666,53 +597,104 @@ interface SharedResourceOptions {
666
597
  * and cleaned up only when the last subscriber releases it (after an optional grace period).
667
598
  */
668
599
  declare class SharedResource<T> {
669
- private readonly factory;
670
- private readonly onCleanup;
671
- private readonly options;
672
- private _count;
673
- private readonly init;
674
- private pendingMicrotask;
675
- private cleanupTimer?;
676
- constructor(factory: () => Promise<T> | T, onCleanup: (value: T | null) => void | Promise<void>, options?: SharedResourceOptions);
677
- /**
678
- * The current number of active subscribers.
679
- */
680
- get subscribers(): number;
681
- /**
682
- * Increments the reference count and ensures the resource is initialized.
683
- * If a cleanup was scheduled but hasn't executed yet, it is cancelled.
684
- *
685
- * @returns The initialized resource.
686
- */
687
- acquire(): Promise<T>;
688
- /**
689
- * Decrements the reference count.
690
- * If the count reaches zero, the cleanup process is scheduled according to the grace period.
691
- */
692
- release(): void;
693
- /**
694
- * Returns the current value synchronously if it has been successfully initialized.
695
- * Returns null if initialization is pending, failed, or hasn't started.
696
- */
697
- peek(): T | null;
698
- /**
699
- * Forcibly tears down the resource, ignoring the reference count and grace period.
700
- * Useful for emergency teardowns (e.g., container disposal).
701
- */
702
- forceCleanup(): void;
703
- /**
704
- * Cancels any scheduled cleanup tasks.
705
- * This is the core mechanism that prevents React StrictMode from destroying the resource.
706
- */
707
- private cancelPendingCleanup;
708
- /**
709
- * Determines how to schedule the cleanup based on the configured options.
710
- */
711
- private scheduleCleanup;
712
- /**
713
- * Performs the actual teardown of the resource and resets the initialization state.
714
- */
715
- private executeCleanup;
600
+ private readonly factory;
601
+ private readonly onCleanup;
602
+ private readonly options;
603
+ private _count;
604
+ private readonly init;
605
+ private pendingMicrotask;
606
+ private cleanupTimer?;
607
+ constructor(factory: () => Promise<T> | T, onCleanup: (value: T | null) => void | Promise<void>, options?: SharedResourceOptions);
608
+ /**
609
+ * The current number of active subscribers.
610
+ */
611
+ get subscribers(): number;
612
+ /**
613
+ * Increments the reference count and ensures the resource is initialized.
614
+ * If a cleanup was scheduled but hasn't executed yet, it is cancelled.
615
+ *
616
+ * @returns The initialized resource.
617
+ */
618
+ acquire(): Promise<T>;
619
+ /**
620
+ * Decrements the reference count.
621
+ * If the count reaches zero, the cleanup process is scheduled according to the grace period.
622
+ */
623
+ release(): void;
624
+ /**
625
+ * Returns the current value synchronously if it has been successfully initialized.
626
+ * Returns null if initialization is pending, failed, or hasn't started.
627
+ */
628
+ peek(): T | null;
629
+ /**
630
+ * Forcibly tears down the resource, ignoring the reference count and grace period.
631
+ * Useful for emergency teardowns (e.g., container disposal).
632
+ */
633
+ forceCleanup(): void;
634
+ /**
635
+ * Cancels any scheduled cleanup tasks.
636
+ * This is the core mechanism that prevents React StrictMode from destroying the resource.
637
+ */
638
+ private cancelPendingCleanup;
639
+ /**
640
+ * Determines how to schedule the cleanup based on the configured options.
641
+ */
642
+ private scheduleCleanup;
643
+ /**
644
+ * Performs the actual teardown of the resource and resets the initialization state.
645
+ */
646
+ private executeCleanup;
716
647
  }
717
-
718
- 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, SharedResource, type SharedResourceOptions, SyncError, TimeoutError };
648
+ //#endregion
649
+ //#region src/sync/registry.d.ts
650
+ interface ResourceRegistryOptions<T, K, O> {
651
+ /**
652
+ * Async factory: receives the key and per-call options.
653
+ * Only invoked when a resource does not already exist.
654
+ */
655
+ onCreate?: (key: K, options: O) => Promise<T>;
656
+ /**
657
+ * Sync factory: receives the key and per-call options.
658
+ * Only invoked when a resource does not already exist.
659
+ */
660
+ onCreateSync?: (key: K, options: O) => T;
661
+ /**
662
+ * Optional disposer for explicit cleanup (release/clear).
663
+ */
664
+ onDispose?: (resource: T) => void | Promise<void>;
665
+ /**
666
+ * Optional callback fired when the resource is GC'd.
667
+ */
668
+ onEvict?: (key: K) => void;
669
+ }
670
+ declare class ResourceRegistry<R extends object, K extends string | number = string, O = void> {
671
+ private readonly entries;
672
+ private readonly resourceToOnce;
673
+ private readonly finalizer;
674
+ private readonly onCreate?;
675
+ private readonly onCreateSync?;
676
+ private readonly onDispose?;
677
+ private readonly onEvict?;
678
+ constructor(options: ResourceRegistryOptions<R, K, O>);
679
+ /**
680
+ * Get or create a resource.
681
+ *
682
+ * IMPORTANT: The `options` are ONLY used if the resource does NOT exist yet.
683
+ * If the resource already exists (or is being created by another concurrent call),
684
+ * the `options` are ignored and the existing instance is returned.
685
+ *
686
+ * This matches the original `registry.ts` behavior.
687
+ */
688
+ get(key: K, options: O, timeout?: number): Promise<R>;
689
+ /**
690
+ * Synchronous version of `get`.
691
+ * Options are only used on the first creation.
692
+ */
693
+ getSync(key: K, options: O): R;
694
+ release(key: K): Promise<boolean>;
695
+ clear(): Promise<void>;
696
+ has(key: K): boolean;
697
+ get size(): number;
698
+ }
699
+ //#endregion
700
+ export { Debouncer, DebouncerCancelled, DebouncerError, DebouncerOk, DebouncerOptions, DebouncerResult, Latch, Mutex, MutexOptions, Once, OnceExecutionConflict, OnceResult, RWMutex, RWMutexOptions, ResourceRegistry, ResourceRegistryOptions, Semaphore, SemaphoreOptions, Serializer, SerializerExecutionDone, SerializerOptions, SerializerResult, SharedResource, SharedResourceOptions, SyncError, TimeoutError };