@asaidimu/utils-sync 2.1.0 → 2.2.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/README.md CHANGED
@@ -70,7 +70,7 @@ yarn add @asaidimu/utils-sync
70
70
  After installation, you can test that the library works correctly:
71
71
 
72
72
  ```typescript
73
- import { Mutex } from '@asaidimu/utils-sync';
73
+ import { Mutex } from "@asaidimu/utils-sync";
74
74
 
75
75
  const mutex = new Mutex();
76
76
  console.log(mutex.locked()); // false
@@ -85,13 +85,13 @@ If the import runs without errors, the package is ready.
85
85
  All examples assume ES module import syntax:
86
86
 
87
87
  ```typescript
88
- import { Mutex, Once, Serializer } from '@asaidimu/utils-sync';
88
+ import { Mutex, Once, Serializer } from "@asaidimu/utils-sync";
89
89
  ```
90
90
 
91
91
  For CommonJS:
92
92
 
93
93
  ```javascript
94
- const { Mutex, Once, Serializer } = require('@asaidimu/utils-sync');
94
+ const { Mutex, Once, Serializer } = require("@asaidimu/utils-sync");
95
95
  ```
96
96
 
97
97
  ---
@@ -125,7 +125,7 @@ try {
125
125
  mutex.unlock();
126
126
  } catch (err) {
127
127
  if (err instanceof TimeoutError) {
128
- console.log('Could not acquire lock in time');
128
+ console.log("Could not acquire lock in time");
129
129
  }
130
130
  }
131
131
  ```
@@ -146,20 +146,20 @@ if (mutex.tryLock()) {
146
146
 
147
147
  #### Options
148
148
 
149
- | Option | Type | Default | Description |
150
- |--------------|--------------------------|---------------|-----------------------------------------------------------------------------------------------|
151
- | `capacity` | `number` | `Infinity` | Max pending waiters. If exceeded, `lock()` throws an error. |
152
- | `yieldMode` | `"macrotask"` \| `"microtask"` | `"macrotask"` | `"macrotask"` yields via `setTimeout(…,0)` (prevents starvation). `"microtask"` uses `queueMicrotask` for lower latency. |
149
+ | Option | Type | Default | Description |
150
+ | ----------- | ------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------ |
151
+ | `capacity` | `number` | `Infinity` | Max pending waiters. If exceeded, `lock()` throws an error. |
152
+ | `yieldMode` | `"macrotask"` \| `"microtask"` | `"macrotask"` | `"macrotask"` yields via `setTimeout(…,0)` (prevents starvation). `"microtask"` uses `queueMicrotask` for lower latency. |
153
153
 
154
154
  #### API
155
155
 
156
- | Method | Return type | Description |
157
- |------------------------------|-----------------------|---------------------------------------------------------------------------------------------------------|
158
- | `lock(timeout?: number)` | `Promise<void>` | Acquire lock, waiting if necessary. Throws `TimeoutError` if timeout elapses or queue is full. |
159
- | `tryLock()` | `boolean` | Attempt to acquire lock without waiting. Returns `true` if acquired. |
160
- | `unlock()` | `void` | Release the lock. Throws if not locked. Schedules next waiter according to `yieldMode`. |
161
- | `locked()` | `boolean` | Returns `true` if the lock is currently held. |
162
- | `pending()` | `number` | Number of tasks waiting for the lock. |
156
+ | Method | Return type | Description |
157
+ | ------------------------ | --------------- | ---------------------------------------------------------------------------------------------- |
158
+ | `lock(timeout?: number)` | `Promise<void>` | Acquire lock, waiting if necessary. Throws `TimeoutError` if timeout elapses or queue is full. |
159
+ | `tryLock()` | `boolean` | Attempt to acquire lock without waiting. Returns `true` if acquired. |
160
+ | `unlock()` | `void` | Release the lock. Throws if not locked. Schedules next waiter according to `yieldMode`. |
161
+ | `locked()` | `boolean` | Returns `true` if the lock is currently held. |
162
+ | `pending()` | `number` | Number of tasks waiting for the lock. |
163
163
 
164
164
  ---
165
165
 
@@ -174,7 +174,7 @@ const once = new Once<string>();
174
174
 
175
175
  async function getConfig() {
176
176
  const result = await once.do(async () => {
177
- const res = await fetch('/api/config');
177
+ const res = await fetch("/api/config");
178
178
  return res.json();
179
179
  });
180
180
  // result.value contains the config, or result.error if failed
@@ -210,23 +210,23 @@ if (once.ready()) {
210
210
 
211
211
  #### Options
212
212
 
213
- | Option | Type | Default | Description |
214
- |-----------|-----------|---------|----------------------------------------------------------------------------------|
215
- | `retry` | `boolean` | `false` | If `true`, a failed execution does **not** mark the instance as done – next call will retry. |
216
- | `throws` | `boolean` | `false` | If `true`, the `do()` method will **throw** the error instead of returning it in the result object. |
213
+ | Option | Type | Default | Description |
214
+ | -------- | --------- | ------- | --------------------------------------------------------------------------------------------------- |
215
+ | `retry` | `boolean` | `false` | If `true`, a failed execution does **not** mark the instance as done – next call will retry. |
216
+ | `throws` | `boolean` | `false` | If `true`, the `do()` method will **throw** the error instead of returning it in the result object. |
217
217
 
218
218
  #### API
219
219
 
220
- | Method | Return type | Description |
221
- |-----------------------------------|-------------------------------------|---------------------------------------------------------------------------------------------------|
222
- | `do(fn, timeout?)` | `Promise<OnceResult<T>>` | Executes `fn` once. Returns `{ value, error }` (unless `throws:true`). Timeout covers lock + execution. |
223
- | `ready()` | `boolean` | `true` if operation has completed (success or non‑retryable failure) and no execution is running. |
224
- | `running()` | `boolean` | `true` if the operation is currently executing. |
225
- | `peek()` | `OnceResult<T>` | Returns current cached `{ value, error }` without waiting. |
226
- | `get()` | `T \| null` | Returns cached value if done, otherwise throws. Throws cached error if present. |
227
- | `reset()` | `void` | Clears state – next `do()` will run again. |
228
- | `done()` | `boolean` | `true` if finished (success or final failure). |
229
- | `current()` | `Promise<OnceResult<T>> \| null` | Returns the underlying promise if running, otherwise `null`. |
220
+ | Method | Return type | Description |
221
+ | ------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
222
+ | `do(fn, timeout?)` | `Promise<OnceResult<T>>` | Executes `fn` once. Returns `{ value, error }` (unless `throws:true`). Timeout covers lock + execution. |
223
+ | `ready()` | `boolean` | `true` if operation has completed (success or non‑retryable failure) and no execution is running. |
224
+ | `running()` | `boolean` | `true` if the operation is currently executing. |
225
+ | `peek()` | `OnceResult<T>` | Returns current cached `{ value, error }` without waiting. |
226
+ | `get()` | `T \| null` | Returns cached value if done, otherwise throws. Throws cached error if present. |
227
+ | `reset()` | `void` | Clears state – next `do()` will run again. |
228
+ | `done()` | `boolean` | `true` if finished (success or final failure). |
229
+ | `current()` | `Promise<OnceResult<T>> \| null` | Returns the underlying promise if running, otherwise `null`. |
230
230
 
231
231
  ---
232
232
 
@@ -241,7 +241,7 @@ const serializer = new Serializer<string>();
241
241
 
242
242
  async function log(message: string) {
243
243
  const result = await serializer.do(async () => {
244
- await appendToFile('log.txt', message);
244
+ await appendToFile("log.txt", message);
245
245
  return message;
246
246
  });
247
247
  return result.value; // last successful result
@@ -253,8 +253,8 @@ async function log(message: string) {
253
253
  Even if a task fails, the serializer continues processing the next queued tasks:
254
254
 
255
255
  ```typescript
256
- await serializer.do(failingFn); // returns { error: ... }
257
- await serializer.do(successfulFn); // still runs
256
+ await serializer.do(failingFn); // returns { error: ... }
257
+ await serializer.do(successfulFn); // still runs
258
258
  ```
259
259
 
260
260
  #### Peeking at the last result
@@ -273,20 +273,20 @@ const result = await serializer.do(anyFn);
273
273
 
274
274
  #### Options
275
275
 
276
- | Option | Type | Default | Description |
277
- |--------------|--------------------------|------------|------------------------------------------------------------------|
278
- | `capacity` | `number` | `1000` | Max pending tasks. When full, `do()` returns an error immediately. |
279
- | `yieldMode` | `"macrotask"` \| `"microtask"` | `"macrotask"` | Handoff scheduling for the internal mutex. Default prevents microtask starvation. |
276
+ | Option | Type | Default | Description |
277
+ | ----------- | ------------------------------ | ------------- | --------------------------------------------------------------------------------- |
278
+ | `capacity` | `number` | `1000` | Max pending tasks. When full, `do()` returns an error immediately. |
279
+ | `yieldMode` | `"macrotask"` \| `"microtask"` | `"macrotask"` | Handoff scheduling for the internal mutex. Default prevents microtask starvation. |
280
280
 
281
281
  #### API
282
282
 
283
- | Method | Return type | Description |
284
- |-------------------------------|--------------------------------------|------------------------------------------------------------------------------------------------------|
285
- | `do(fn, timeout?)` | `Promise<SerializerResult<T\|null>>` | Enqueues `fn`. Returns `{ value, error }`. If closed or queue full, error is `SerializerExecutionDone`. |
286
- | `peek()` | `SerializerResult<T\|null>` | Returns the last successful result or last error. |
287
- | `close()` | `void` | Permanently closes the serializer. All subsequent `do()` calls fail immediately. |
288
- | `pending()` | `number` | Number of tasks waiting in the queue. |
289
- | `running()` | `boolean` | `true` if a task is currently executing. |
283
+ | Method | Return type | Description |
284
+ | ------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------- |
285
+ | `do(fn, timeout?)` | `Promise<SerializerResult<T\|null>>` | Enqueues `fn`. Returns `{ value, error }`. If closed or queue full, error is `SerializerExecutionDone`. |
286
+ | `peek()` | `SerializerResult<T\|null>` | Returns the last successful result or last error. |
287
+ | `close()` | `void` | Permanently closes the serializer. All subsequent `do()` calls fail immediately. |
288
+ | `pending()` | `number` | Number of tasks waiting in the queue. |
289
+ | `running()` | `boolean` | `true` if a task is currently executing. |
290
290
 
291
291
  ---
292
292
 
@@ -328,11 +328,11 @@ npm install
328
328
 
329
329
  ### Scripts
330
330
 
331
- | Command | Description |
332
- |----------------------|--------------------------------------------------|
333
- | `npm test` | Run tests once (Vitest) |
334
- | `npm run test:watch` | Run tests in watch mode |
335
- | `npm run test:browser` | Run tests in a browser environment (Vitest) |
331
+ | Command | Description |
332
+ | ---------------------- | ------------------------------------------- |
333
+ | `npm test` | Run tests once (Vitest) |
334
+ | `npm run test:watch` | Run tests in watch mode |
335
+ | `npm run test:browser` | Run tests in a browser environment (Vitest) |
336
336
 
337
337
  ### Testing
338
338
 
@@ -372,7 +372,7 @@ Report bugs or request features via [GitHub Issues](https://github.com/asaidimu/
372
372
  ### Troubleshooting
373
373
 
374
374
  | Problem | Possible solution |
375
- |----------------------------------------------|-------------------------------------------------------------------------------------------------------|
375
+ | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
376
376
  | `Mutex` lock never resolves | Check that `unlock()` is always called (e.g., use `try/finally`). |
377
377
  | `Serializer` tasks stop running | Did you call `close()`? Once closed, all new tasks fail immediately. |
378
378
  | `Once` returns stale error even after retry | Ensure `retry: true` is set. Without it, a failure marks `_done = true` and never retries. |
package/index.d.mts CHANGED
@@ -539,4 +539,69 @@ declare class Serializer<T = void> {
539
539
  running(): boolean;
540
540
  }
541
541
 
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 };
542
+ interface SharedResourceOptions {
543
+ /**
544
+ * How long to wait after the last subscriber leaves before cleaning up.
545
+ * - "microtask": (Default) Waits until the end of the current synchronous execution tick.
546
+ * Perfect for React StrictMode or concurrent rendering.
547
+ * - number: Waits for the specified milliseconds. Useful for debouncing rapid reconnects.
548
+ * - "sync": Cleans up immediately (bypasses the grace period entirely).
549
+ */
550
+ gracePeriod?: "microtask" | "sync" | number;
551
+ }
552
+ /**
553
+ * Manages the lifecycle of a reference-counted shared resource.
554
+ * Ensures the resource is created exactly once on the first acquisition,
555
+ * and cleaned up only when the last subscriber releases it (after an optional grace period).
556
+ */
557
+ declare class SharedResource<T> {
558
+ private readonly factory;
559
+ private readonly onCleanup;
560
+ private readonly options;
561
+ private _count;
562
+ private readonly init;
563
+ private pendingMicrotask;
564
+ private cleanupTimer?;
565
+ constructor(factory: () => Promise<T> | T, onCleanup: (value: T | null) => void | Promise<void>, options?: SharedResourceOptions);
566
+ /**
567
+ * The current number of active subscribers.
568
+ */
569
+ get subscribers(): number;
570
+ /**
571
+ * Increments the reference count and ensures the resource is initialized.
572
+ * If a cleanup was scheduled but hasn't executed yet, it is cancelled.
573
+ *
574
+ * @returns The initialized resource.
575
+ */
576
+ acquire(): Promise<T>;
577
+ /**
578
+ * Decrements the reference count.
579
+ * If the count reaches zero, the cleanup process is scheduled according to the grace period.
580
+ */
581
+ release(): void;
582
+ /**
583
+ * Returns the current value synchronously if it has been successfully initialized.
584
+ * Returns null if initialization is pending, failed, or hasn't started.
585
+ */
586
+ peek(): T | null;
587
+ /**
588
+ * Forcibly tears down the resource, ignoring the reference count and grace period.
589
+ * Useful for emergency teardowns (e.g., container disposal).
590
+ */
591
+ forceCleanup(): void;
592
+ /**
593
+ * Cancels any scheduled cleanup tasks.
594
+ * This is the core mechanism that prevents React StrictMode from destroying the resource.
595
+ */
596
+ private cancelPendingCleanup;
597
+ /**
598
+ * Determines how to schedule the cleanup based on the configured options.
599
+ */
600
+ private scheduleCleanup;
601
+ /**
602
+ * Performs the actual teardown of the resource and resets the initialization state.
603
+ */
604
+ private executeCleanup;
605
+ }
606
+
607
+ 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 };
package/index.d.ts CHANGED
@@ -539,4 +539,69 @@ declare class Serializer<T = void> {
539
539
  running(): boolean;
540
540
  }
541
541
 
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 };
542
+ interface SharedResourceOptions {
543
+ /**
544
+ * How long to wait after the last subscriber leaves before cleaning up.
545
+ * - "microtask": (Default) Waits until the end of the current synchronous execution tick.
546
+ * Perfect for React StrictMode or concurrent rendering.
547
+ * - number: Waits for the specified milliseconds. Useful for debouncing rapid reconnects.
548
+ * - "sync": Cleans up immediately (bypasses the grace period entirely).
549
+ */
550
+ gracePeriod?: "microtask" | "sync" | number;
551
+ }
552
+ /**
553
+ * Manages the lifecycle of a reference-counted shared resource.
554
+ * Ensures the resource is created exactly once on the first acquisition,
555
+ * and cleaned up only when the last subscriber releases it (after an optional grace period).
556
+ */
557
+ declare class SharedResource<T> {
558
+ private readonly factory;
559
+ private readonly onCleanup;
560
+ private readonly options;
561
+ private _count;
562
+ private readonly init;
563
+ private pendingMicrotask;
564
+ private cleanupTimer?;
565
+ constructor(factory: () => Promise<T> | T, onCleanup: (value: T | null) => void | Promise<void>, options?: SharedResourceOptions);
566
+ /**
567
+ * The current number of active subscribers.
568
+ */
569
+ get subscribers(): number;
570
+ /**
571
+ * Increments the reference count and ensures the resource is initialized.
572
+ * If a cleanup was scheduled but hasn't executed yet, it is cancelled.
573
+ *
574
+ * @returns The initialized resource.
575
+ */
576
+ acquire(): Promise<T>;
577
+ /**
578
+ * Decrements the reference count.
579
+ * If the count reaches zero, the cleanup process is scheduled according to the grace period.
580
+ */
581
+ release(): void;
582
+ /**
583
+ * Returns the current value synchronously if it has been successfully initialized.
584
+ * Returns null if initialization is pending, failed, or hasn't started.
585
+ */
586
+ peek(): T | null;
587
+ /**
588
+ * Forcibly tears down the resource, ignoring the reference count and grace period.
589
+ * Useful for emergency teardowns (e.g., container disposal).
590
+ */
591
+ forceCleanup(): void;
592
+ /**
593
+ * Cancels any scheduled cleanup tasks.
594
+ * This is the core mechanism that prevents React StrictMode from destroying the resource.
595
+ */
596
+ private cancelPendingCleanup;
597
+ /**
598
+ * Determines how to schedule the cleanup based on the configured options.
599
+ */
600
+ private scheduleCleanup;
601
+ /**
602
+ * Performs the actual teardown of the resource and resets the initialization state.
603
+ */
604
+ private executeCleanup;
605
+ }
606
+
607
+ 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 };
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{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;
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}},s=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.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=s,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.SharedResource=class{constructor(e,t,i={}){this.factory=e,this.onCleanup=t,this.options=i}_count=0;init=new s({retry:!1,throws:!1});pendingMicrotask=!1;cleanupTimer;get subscribers(){return this._count}async acquire(){this.cancelPendingCleanup(),this._count++;const e=await this.init.do(this.factory);if(e.error)throw e.error;return e.value}release(){this._count<=0?console.warn("SharedResource.release() called, but count is already 0."):(this._count--,0===this._count&&this.scheduleCleanup())}peek(){const e=this.init.peek();return e.error?null:e.value}forceCleanup(){this.cancelPendingCleanup(),this._count=0,this.executeCleanup()}cancelPendingCleanup(){this.pendingMicrotask=!1,void 0!==this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=void 0)}scheduleCleanup(){const e=this.options.gracePeriod??"microtask";if("sync"!==e)return"microtask"===e?(this.pendingMicrotask=!0,void queueMicrotask((()=>{!this.pendingMicrotask||this._count>0||(this.pendingMicrotask=!1,this.executeCleanup())}))):void(this.cleanupTimer=setTimeout((()=>{this.cleanupTimer=void 0,this._count>0||this.executeCleanup()}),e));this.executeCleanup()}executeCleanup(){const e=this.init.peek();try{this.onCleanup(e.value)}catch(e){console.error("[SharedResource] Error during cleanup callback:",e)}this.init.running()||this.init.reset()}},exports.SyncError=e,exports.TimeoutError=t;
package/index.mjs CHANGED
@@ -1 +1 @@
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};
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)}},n=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}},o=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,n)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),n(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 o({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,n)=>{s=setTimeout((()=>n(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,n)=>{s=setTimeout((()=>{const e=this.readerWaiters.indexOf(t);-1!==e&&this.readerWaiters.splice(e,1),n(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,n)=>{s=setTimeout((()=>{const e=this.writerWaiters.indexOf(t);-1!==e&&(this.writerWaiters.splice(e,1),this._pendingWriters--),n(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,n)=>{s=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),n(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 o({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()}},u=class{constructor(e,t,i={}){this.factory=e,this.onCleanup=t,this.options=i}_count=0;init=new a({retry:!1,throws:!1});pendingMicrotask=!1;cleanupTimer;get subscribers(){return this._count}async acquire(){this.cancelPendingCleanup(),this._count++;const e=await this.init.do(this.factory);if(e.error)throw e.error;return e.value}release(){this._count<=0?console.warn("SharedResource.release() called, but count is already 0."):(this._count--,0===this._count&&this.scheduleCleanup())}peek(){const e=this.init.peek();return e.error?null:e.value}forceCleanup(){this.cancelPendingCleanup(),this._count=0,this.executeCleanup()}cancelPendingCleanup(){this.pendingMicrotask=!1,void 0!==this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=void 0)}scheduleCleanup(){const e=this.options.gracePeriod??"microtask";if("sync"!==e)return"microtask"===e?(this.pendingMicrotask=!0,void queueMicrotask((()=>{!this.pendingMicrotask||this._count>0||(this.pendingMicrotask=!1,this.executeCleanup())}))):void(this.cleanupTimer=setTimeout((()=>{this.cleanupTimer=void 0,this._count>0||this.executeCleanup()}),e));this.executeCleanup()}executeCleanup(){const e=this.init.peek();try{this.onCleanup(e.value)}catch(e){console.error("[SharedResource] Error during cleanup callback:",e)}this.init.running()||this.init.reset()}};export{e as Debouncer,n as Latch,o as Mutex,a as Once,r as OnceExecutionConflict,h as RWMutex,l as Semaphore,c as Serializer,s as SerializerExecutionDone,u as SharedResource,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.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "A collection of sync utilities.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",