@chriscdn/promise-semaphore 2.0.12 → 3.0.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
@@ -1,6 +1,7 @@
1
1
  # @chriscdn/promise-semaphore
2
2
 
3
- Limit or throttle the simultaneous execution of asynchronous code in separate iterations of the event loop.
3
+ Limit or throttle the concurrent execution of asynchronous code in separate
4
+ iterations of the event loop.
4
5
 
5
6
  ## Installing
6
7
 
@@ -16,16 +17,40 @@ Using yarn:
16
17
  yarn add @chriscdn/promise-semaphore
17
18
  ```
18
19
 
19
- ## API
20
+ ## Version 3
21
+
22
+ Version 3 introduces two main changes:
23
+
24
+ - A new `GroupSemaphore` class has been added. It allows multiple tasks within
25
+ the same group (identified by a key) to run concurrently while ensuring that
26
+ only one group's tasks are active at a time. See below for documentation.
27
+ - The default export has been replaced with a named export.
28
+
29
+ Change:
30
+
31
+ ```ts
32
+ import Semaphore from "@chriscdn/promise-semaphore";
33
+ ```
34
+
35
+ to:
36
+
37
+ ```ts
38
+ import { Semaphore } from "@chriscdn/promise-semaphore";
39
+ ```
40
+
41
+ ## API - Semaphore
20
42
 
21
43
  ### Create an instance
22
44
 
23
45
  ```js
24
- import Semaphore from "@chriscdn/promise-semaphore";
46
+ import { Semaphore } from "@chriscdn/promise-semaphore";
25
47
  const semaphore = new Semaphore([maxConcurrent]);
26
48
  ```
27
49
 
28
- The `maxConcurrent` parameter is optional and defaults to `1` (making it an exclusive lock or _binary semaphore_). An integer greater than `1` can be used to allow multiple concurrent executions from separate iterations of the event loop.
50
+ The `maxConcurrent` parameter is optional and defaults to `1` (making it an
51
+ exclusive lock or _binary semaphore_). An integer greater than `1` can be used
52
+ to allow multiple concurrent executions from separate iterations of the event
53
+ loop.
29
54
 
30
55
  ### Acquire a lock
31
56
 
@@ -33,7 +58,9 @@ The `maxConcurrent` parameter is optional and defaults to `1` (making it an excl
33
58
  semaphore.acquire([key]);
34
59
  ```
35
60
 
36
- This returns a `Promise` that resolves once a lock is acquired. The `key` parameter is optional and allows the same `Semaphore` instance to manage locks in different contexts. Additional details are provided in the second example.
61
+ This returns a `Promise` that resolves once a lock is acquired. The `key`
62
+ parameter is optional and allows the same `Semaphore` instance to manage locks
63
+ in different contexts. Additional details are provided in the second example.
37
64
 
38
65
  ### Release a lock
39
66
 
@@ -41,7 +68,8 @@ This returns a `Promise` that resolves once a lock is acquired. The `key` parame
41
68
  semaphore.release([key]);
42
69
  ```
43
70
 
44
- The `release` method should be called within a `finally` block (whether using promises or a `try/catch` block) to ensure the lock is released.
71
+ The `release` method should be called within a `finally` block (whether using
72
+ promises or a `try/catch` block) to ensure the lock is released.
45
73
 
46
74
  ### Check if a lock can be acquired
47
75
 
@@ -49,7 +77,8 @@ The `release` method should be called within a `finally` block (whether using pr
49
77
  semaphore.canAcquire([key]);
50
78
  ```
51
79
 
52
- This synchronous method returns `true` if a lock can be immediately acquired, and `false` otherwise.
80
+ This synchronous method returns `true` if a lock can be immediately acquired,
81
+ and `false` otherwise.
53
82
 
54
83
  ### `count`
55
84
 
@@ -57,15 +86,7 @@ This synchronous method returns `true` if a lock can be immediately acquired, an
57
86
  semaphore.count([key]);
58
87
  ```
59
88
 
60
- This synchronous function returns the current number of locks.
61
-
62
- ### `wait` method
63
-
64
- ```js
65
- semaphore.wait([key]);
66
- ```
67
-
68
- This asynchronous function resolves immediately if `count` is 0; otherwise, it resolves once `count` reaches 0.
89
+ This function is synchronous, and returns the current number of locks.
69
90
 
70
91
  ### `request` method
71
92
 
@@ -73,12 +94,13 @@ This asynchronous function resolves immediately if `count` is 0; otherwise, it r
73
94
  const results = await semaphore.request(fn [, key]);
74
95
  ```
75
96
 
76
- This function reduces boilerplate when using `acquire` and `release`. It returns a promise that resolves when `fn` completes. It is functionally equivalent to:
97
+ This function reduces boilerplate when using `acquire` and `release`. It returns
98
+ a promise that resolves when `fn` completes. It is functionally equivalent to:
77
99
 
78
100
  ```js
79
101
  try {
80
102
  await semaphore.acquire([key]);
81
- const results = await fn();
103
+ return await fn();
82
104
  } finally {
83
105
  semaphore.release([key]);
84
106
  }
@@ -93,17 +115,17 @@ const results = await semaphore.requestIfAvailable(fn [, key]);
93
115
  This is functionally equivalent to:
94
116
 
95
117
  ```js
96
- const results = semaphore.canAcquire([key])
97
- ? await semaphore.request(fn, [key])
98
- : null;
118
+ return semaphore.canAcquire([key]) ? await semaphore.request(fn, [key]) : null;
99
119
  ```
100
120
 
101
- This is useful in scenarios where only one instance of a function block should run while discarding additional attempts. For example, handling repeated button clicks.
121
+ This is useful in scenarios where only one instance of a function block should
122
+ run while discarding additional attempts. For example, handling repeated button
123
+ clicks.
102
124
 
103
125
  ## Example 1
104
126
 
105
127
  ```js
106
- import Semaphore from "@chriscdn/promise-semaphore";
128
+ import { Semaphore } from "@chriscdn/promise-semaphore";
107
129
  const semaphore = new Semaphore();
108
130
 
109
131
  // Using promises
@@ -113,7 +135,7 @@ semaphore
113
135
  // This block executes once a lock is acquired.
114
136
  // If already locked, it waits and executes after all preceding locks are released.
115
137
  //
116
- // Critical operations are performed here.
138
+ // Critical operations
117
139
  })
118
140
  .finally(() => {
119
141
  // The lock is released, allowing the next queued block to proceed.
@@ -124,14 +146,14 @@ semaphore
124
146
  try {
125
147
  await semaphore.acquire();
126
148
 
127
- // Critical operations are performed here.
149
+ // Critical operations
128
150
  } finally {
129
151
  semaphore.release();
130
152
  }
131
153
 
132
154
  // Using the request function
133
155
  await semaphore.request(() => {
134
- // Critical operations are performed here.
156
+ // Critical operations
135
157
  });
136
158
  ```
137
159
 
@@ -153,12 +175,15 @@ const downloadAndSave = async (url) => {
153
175
  };
154
176
  ```
155
177
 
156
- This approach works as expected until `downloadAndSave()` is called multiple times with the same `url` in quick succession. Without control, it could initiate simultaneous downloads that attempt to write to the same file at the same time.
178
+ This approach works as expected until `downloadAndSave()` is called multiple
179
+ times with the same `url` in quick succession. Without control, it could
180
+ initiate simultaneous downloads that attempt to write to the same file at the
181
+ same time.
157
182
 
158
183
  This issue can be resolved by using a `Semaphore` with the `key` parameter:
159
184
 
160
185
  ```js
161
- import Semaphore from "@chriscdn/promise-semaphore";
186
+ import { Semaphore } from "@chriscdn/promise-semaphore";
162
187
  const semaphore = new Semaphore();
163
188
 
164
189
  const downloadAndSave = async (url) => {
@@ -166,7 +191,7 @@ const downloadAndSave = async (url) => {
166
191
  await semaphore.acquire(url);
167
192
 
168
193
  // This block continues once a lock on url is acquired. This
169
- // permits multiple simulataneous downloads for different urls.
194
+ // permits multiple simultaneous downloads for different urls.
170
195
 
171
196
  const filePath = urlToFilePath(url);
172
197
 
@@ -192,16 +217,58 @@ const downloadAndSave = (url) => {
192
217
 
193
218
  if (await pathExists(filePath)) {
194
219
  // The file is already on disk, so no action is required.
195
- return filePath;
220
+ } else {
221
+ await downloadAndSaveToFilepath(url, filePath);
196
222
  }
197
-
198
- await downloadAndSaveToFilepath(url, filePath);
199
-
200
223
  return filePath;
201
224
  }, url);
202
225
  };
203
226
  ```
204
227
 
228
+ ## API - GroupSemaphore
229
+
230
+ The `GroupSemaphore` class manages a semaphore for different groups of tasks. A
231
+ group is identified by a key, and the semaphore ensures that only one group can
232
+ run its tasks at a time. The tasks within a group can run concurrently.
233
+
234
+ The `GroupSemaphore` class exposes `acquire` and `release` methods, which have
235
+ the same interface as `Semaphore`. The only difference is that the `key`
236
+ parameter is required.
237
+
238
+ ### Example
239
+
240
+ ```ts
241
+ import { GroupSemaphore } from "@chriscdn/promise-semaphore";
242
+
243
+ const groupSemaphore = new GroupSemaphore();
244
+
245
+ const RunA = async () => {
246
+ try {
247
+ await groupSemaphore.acquire("GroupA");
248
+
249
+ // Perform asynchronous operations for group A
250
+ } finally {
251
+ groupSemaphore.release("GroupA");
252
+ }
253
+ };
254
+
255
+ const RunB = async () => {
256
+ try {
257
+ await groupSemaphore.acquire("GroupB");
258
+
259
+ // Perform asynchronous operations for group B
260
+ } finally {
261
+ groupSemaphore.release("GroupB");
262
+ }
263
+ };
264
+ ```
265
+
266
+ This setup allows `RunA` to be called multiple times, and will run concurrently.
267
+ However, calling `RunB` will wait until all `GroupA` tasks are completed before
268
+ acquiring the lock for `GroupB`. As soon as `GroupB` acquires the lock, any
269
+ subsequent calls to `RunA` will wait until `GroupB` releases the lock before it
270
+ executes.
271
+
205
272
  ## License
206
273
 
207
274
  [MIT](LICENSE)
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
- import Semaphore from "../src/index";
3
+ import { Semaphore } from "../src/index";
4
4
 
5
5
  const pause = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6
6
 
@@ -145,26 +145,3 @@ describe("All test", () => {
145
145
  });
146
146
  });
147
147
  });
148
-
149
- describe("Wait TEST", () => {
150
- const semaphore = new Semaphore(2);
151
-
152
- it("Acquire & Release Basic", async () => {
153
- let tester = 0;
154
-
155
- semaphore
156
- .acquire()
157
- .then(() => pause(1000))
158
- .then(() => (tester = 20))
159
- .finally(() => semaphore.release());
160
-
161
- semaphore
162
- .acquire()
163
- .then(() => pause(500))
164
- .then(() => (tester = 10))
165
- .finally(() => semaphore.release());
166
-
167
- await semaphore.wait(); // WAIT for all tasks to finish
168
- expect(tester).toBe(20);
169
- });
170
- });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * GroupSemaphore manages a shared semaphore for different groups of tasks. Each
3
+ * group is identified by a unique key, and the semaphore ensures only one group
4
+ * can run its tasks concurrently.
5
+ *
6
+ * - acquire(key): Increments the active count for the given group. If it's the
7
+ * first task for the group (active count is 0), it acquires the global
8
+ * semaphore, ensuring only one group's tasks can proceed at a time.
9
+ * Subsequent calls in the group increment the count and are permitted to run.
10
+ * - release(key): Decrements the active count for the group. If the last task
11
+ * for that group is released, it releases the global semaphore, allowing
12
+ * other groups to proceed.
13
+ *
14
+ * This ensures that only one group can execute concurrently, but multiple tasks
15
+ * within the same group can run as long as no other tasks from different groups
16
+ * are active.
17
+ */
18
+ declare class GroupSemaphore {
19
+ private _semaphore;
20
+ private _activeCounts;
21
+ private _groupWaiters;
22
+ acquire(key: string): Promise<void>;
23
+ release(key: string): void;
24
+ }
25
+ export { GroupSemaphore };
package/lib/index.d.ts CHANGED
@@ -1,65 +1,2 @@
1
- declare class Semaphore {
2
- private semaphoreInstances;
3
- private maxConcurrent;
4
- /**
5
- * @param {number} [maxConcurrent] The maximum number of concurrent locks.
6
- */
7
- constructor(maxConcurrent?: number);
8
- private hasSemaphoreInstance;
9
- private getSemaphoreInstance;
10
- /**
11
- * @param {string | number} [key]- Optional, the semaphore key.
12
- */
13
- private tidy;
14
- /**
15
- * A synchronous function to determine whether a lock can be acquired.
16
- *
17
- * @param {string | number} [key]- Optional, the semaphore key.
18
- * @returns {boolean} Returns true if the lock on `key` can be acquired, false
19
- * otherwise.
20
- */
21
- canAcquire(key?: string | number): boolean;
22
- /**
23
- * @param {string | number} [key]- Optional, the semaphore key.
24
- */
25
- acquire(key?: string | number): Promise<void>;
26
- /**
27
- * @param {string | number} [key]- Optional, the semaphore key.
28
- */
29
- release(key?: string | number): void;
30
- /**
31
- * The number of active locks. Will always be less or equal to `max`.
32
- *
33
- * @param {string | number} [key]- Optional, the semaphore key.
34
- */
35
- count(key?: string | number): number;
36
- /**
37
- * @param {string | number} [key]- Optional, the semaphore key.
38
- * @returns {boolean} True if the semaphore and key has locks, false otherwise.
39
- */
40
- hasTasks(key?: string | number): boolean;
41
- /**
42
- * @param {Function<T>} fn The function to execute.
43
- * @param {string | number} [key]- Optional, the semaphore key.
44
- * @returns {Promise<T>}
45
- */
46
- request<T>(fn: Function, key?: string | number): Promise<T>;
47
- /**
48
- * Asynchronously executes `fn` if a lock can be immediately acquired.
49
- * Otherwise, returns null.
50
- *
51
- * @param {Function<T>} fn The function to execute.
52
- * @param {string | number} [key]- Optional, the semaphore key.
53
- * @returns {Promise<T>}
54
- */
55
- requestIfAvailable<T>(fn: Function, key?: string | number): Promise<T | null>;
56
- /**
57
- * Wait until the count on `key` is 0 and then resolve.
58
- *
59
- * @param key
60
- * @returns
61
- */
62
- wait(key?: string | number): Promise<void>;
63
- }
64
- export default Semaphore;
65
- export { Semaphore };
1
+ export { Semaphore } from "./semaphore";
2
+ export { GroupSemaphore } from "./group-semaphore";
@@ -1,2 +1,2 @@
1
- function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.waitQueue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.waitQueue=[],this.maxConcurrent=e,this.count=0}var n,r,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--,0===this.count&&(this.waitQueue.forEach(function(e){return e()}),this.waitQueue=[])},i.acquire=function(){var e=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(t){return e.queue.push(t)})},i.release=function(){var e=this.queue.shift();e?setTimeout(e,0):this.decrementCount()},i.wait=function(){var e=this;return new Promise(function(t){return e.waitQueue.push(t)})},n=t,(r=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,n){for(var r=0;r<n.length;r++){var i=n[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(n.prototype,r),Object.defineProperty(n,"prototype",{writable:!1}),n}(),n="_default",r=/*#__PURE__*/function(){function e(e){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}var r=e.prototype;return r.hasSemaphoreInstance=function(e){return void 0===e&&(e=n),Boolean(this.semaphoreInstances[e])},r.getSemaphoreInstance=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new t(this.maxConcurrent)),this.semaphoreInstances[e]},r.tidy=function(e){void 0===e&&(e=n),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},r.canAcquire=function(e){return void 0===e&&(e=n),this.getSemaphoreInstance(e).canAcquire},r.acquire=function(e){return void 0===e&&(e=n),this.getSemaphoreInstance(e).acquire()},r.release=function(e){void 0===e&&(e=n),this.getSemaphoreInstance(e).release(),this.tidy(e)},r.count=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)?this.getSemaphoreInstance(e).count:0},r.hasTasks=function(e){return void 0===e&&(e=n),this.count(e)>0},r.request=function(e,t){void 0===t&&(t=n);try{var r=this;return Promise.resolve(function(n,i){try{var o=Promise.resolve(r.acquire(t)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,n){if(r.release(t),e)throw n;return n}))}catch(e){return Promise.reject(e)}},r.requestIfAvailable=function(e,t){void 0===t&&(t=n);try{return this.canAcquire(t)?Promise.resolve(this.request(e,t)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},r.wait=function(e){void 0===e&&(e=n);try{return this.hasTasks(e)?Promise.resolve(this.getSemaphoreInstance(e).wait()):Promise.resolve()}catch(e){return Promise.reject(e)}},e}();exports.Semaphore=r,exports.default=r;
1
+ function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var n,r,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(){var e=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(t){return e.queue.push(t)})},i.release=function(){var e=this.queue.shift();e?setTimeout(e,0):this.decrementCount()},n=t,(r=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,n){for(var r=0;r<n.length;r++){var i=n[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(n.prototype,r),Object.defineProperty(n,"prototype",{writable:!1}),n}(),n="_default",r=/*#__PURE__*/function(){function e(e){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}var r=e.prototype;return r.hasSemaphoreInstance=function(e){return void 0===e&&(e=n),Boolean(this.semaphoreInstances[e])},r.getSemaphoreInstance=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new t(this.maxConcurrent)),this.semaphoreInstances[e]},r.tidy=function(e){void 0===e&&(e=n),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},r.canAcquire=function(e){return void 0===e&&(e=n),!this.hasSemaphoreInstance(e)||this.getSemaphoreInstance(e).canAcquire},r.acquire=function(e){return void 0===e&&(e=n),this.getSemaphoreInstance(e).acquire()},r.release=function(e){void 0===e&&(e=n),this.getSemaphoreInstance(e).release(),this.tidy(e)},r.count=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)?this.getSemaphoreInstance(e).count:0},r.hasTasks=function(e){return void 0===e&&(e=n),this.count(e)>0},r.request=function(e,t){void 0===t&&(t=n);try{var r=this;return Promise.resolve(function(n,i){try{var o=Promise.resolve(r.acquire(t)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,n){if(r.release(t),e)throw n;return n}))}catch(e){return Promise.reject(e)}},r.requestIfAvailable=function(e,t){void 0===t&&(t=n);try{return this.canAcquire(t)?Promise.resolve(this.request(e,t)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}();exports.GroupSemaphore=/*#__PURE__*/function(){function e(){this._semaphore=new r,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,n,r=this,i=null!=(t=r._activeCounts[e])?t:0;r._activeCounts[e]=i+1;var o=null!=(n=r._groupWaiters[e])?n:r._semaphore.acquire();return r._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}(),exports.Semaphore=r;
2
2
  //# sourceMappingURL=promise-semaphore.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"promise-semaphore.cjs","sources":["../src/index.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Array<Function>;\n private waitQueue: Array<Function>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.waitQueue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n\n if (this.count === 0) {\n this.waitQueue.forEach((resolve) => resolve());\n this.waitQueue = [];\n }\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n\n wait(): Promise<void> {\n return new Promise((resolve) => this.waitQueue.push(resolve));\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(this.maxConcurrent);\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n\n /**\n * Wait until the count on `key` is 0 and then resolve.\n *\n * @param key\n * @returns\n */\n async wait(key: string | number = defaultKey) {\n if (this.hasTasks(key)) {\n return this.getSemaphoreInstance(key).wait();\n } else {\n return Promise.resolve();\n }\n }\n\n // globalCount() {\n // return Object.values(this.semaphoreInstances).reduce(\n // (a, instance) => a + instance.count,\n // 0,\n // );\n // }\n}\n\nexport default Semaphore;\nexport { Semaphore };\n"],"names":["SemaphoreItem","maxConcurrent","queue","waitQueue","count","this","_proto","prototype","incrementCount","decrementCount","forEach","resolve","acquire","_this","canAcquire","Promise","push","release","resolveFunc","shift","setTimeout","wait","_this2","key","get","defaultKey","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","_this3","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable"],"mappings":"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"}
1
+ {"version":3,"file":"promise-semaphore.cjs","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Function[];\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return !this.hasSemaphoreInstance(key) ||\n this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","incrementCount","decrementCount","acquire","_this","canAcquire","Promise","resolve","push","release","resolveFunc","shift","setTimeout","key","get","defaultKey","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- class e{constructor(e){this.queue=void 0,this.waitQueue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.waitQueue=[],this.maxConcurrent=e,this.count=0}get canAcquire(){return this.count<this.maxConcurrent}incrementCount(){this.count++}decrementCount(){this.count--,0===this.count&&(this.waitQueue.forEach(e=>e()),this.waitQueue=[])}acquire(){return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(e=>this.queue.push(e))}release(){const e=this.queue.shift();e?setTimeout(e,0):this.decrementCount()}wait(){return new Promise(e=>this.waitQueue.push(e))}}const t="_default";class s{constructor(e=1){this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}hasSemaphoreInstance(e=t){return Boolean(this.semaphoreInstances[e])}getSemaphoreInstance(s=t){return this.hasSemaphoreInstance(s)||(this.semaphoreInstances[s]=new e(this.maxConcurrent)),this.semaphoreInstances[s]}tidy(e=t){this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]}canAcquire(e=t){return this.getSemaphoreInstance(e).canAcquire}acquire(e=t){return this.getSemaphoreInstance(e).acquire()}release(e=t){this.getSemaphoreInstance(e).release(),this.tidy(e)}count(e=t){return this.hasSemaphoreInstance(e)?this.getSemaphoreInstance(e).count:0}hasTasks(e=t){return this.count(e)>0}async request(e,s=t){try{return await this.acquire(s),await e()}finally{this.release(s)}}async requestIfAvailable(e,s=t){return this.canAcquire(s)?this.request(e,s):null}async wait(e=t){return this.hasTasks(e)?this.getSemaphoreInstance(e).wait():Promise.resolve()}}export{s as Semaphore,s as default};
1
+ class e{constructor(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}get canAcquire(){return this.count<this.maxConcurrent}incrementCount(){this.count++}decrementCount(){this.count--}acquire(){return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(e=>this.queue.push(e))}release(){const e=this.queue.shift();e?setTimeout(e,0):this.decrementCount()}}const t="_default";class s{constructor(e=1){this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}hasSemaphoreInstance(e=t){return Boolean(this.semaphoreInstances[e])}getSemaphoreInstance(s=t){return this.hasSemaphoreInstance(s)||(this.semaphoreInstances[s]=new e(this.maxConcurrent)),this.semaphoreInstances[s]}tidy(e=t){this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]}canAcquire(e=t){return!this.hasSemaphoreInstance(e)||this.getSemaphoreInstance(e).canAcquire}acquire(e=t){return this.getSemaphoreInstance(e).acquire()}release(e=t){this.getSemaphoreInstance(e).release(),this.tidy(e)}count(e=t){return this.hasSemaphoreInstance(e)?this.getSemaphoreInstance(e).count:0}hasTasks(e=t){return this.count(e)>0}async request(e,s=t){try{return await this.acquire(s),await e()}finally{this.release(s)}}async requestIfAvailable(e,s=t){return this.canAcquire(s)?this.request(e,s):null}}class n{constructor(){this._semaphore=new s,this._activeCounts={},this._groupWaiters={}}async acquire(e){var t,s;const n=null!=(t=this._activeCounts[e])?t:0;this._activeCounts[e]=n+1;const r=null!=(s=this._groupWaiters[e])?s:this._semaphore.acquire();this._groupWaiters[e]=r,await r}release(e){const t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1}}export{n as GroupSemaphore,s as Semaphore};
2
2
  //# sourceMappingURL=promise-semaphore.modern.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"promise-semaphore.modern.js","sources":["../src/index.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Array<Function>;\n private waitQueue: Array<Function>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.waitQueue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n\n if (this.count === 0) {\n this.waitQueue.forEach((resolve) => resolve());\n this.waitQueue = [];\n }\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n\n wait(): Promise<void> {\n return new Promise((resolve) => this.waitQueue.push(resolve));\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(this.maxConcurrent);\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n\n /**\n * Wait until the count on `key` is 0 and then resolve.\n *\n * @param key\n * @returns\n */\n async wait(key: string | number = defaultKey) {\n if (this.hasTasks(key)) {\n return this.getSemaphoreInstance(key).wait();\n } else {\n return Promise.resolve();\n }\n }\n\n // globalCount() {\n // return Object.values(this.semaphoreInstances).reduce(\n // (a, instance) => a + instance.count,\n // 0,\n // );\n // }\n}\n\nexport default Semaphore;\nexport { Semaphore };\n"],"names":["SemaphoreItem","constructor","maxConcurrent","this","queue","waitQueue","count","canAcquire","incrementCount","decrementCount","forEach","resolve","acquire","Promise","push","release","resolveFunc","shift","setTimeout","wait","defaultKey","Semaphore","semaphoreInstances","hasSemaphoreInstance","key","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","requestIfAvailable"],"mappings":"AAAA,MAAMA,EAUJC,WAAAA,CAAYC,GAAqBC,KATzBC,WAAK,EAAAD,KACLE,eAAS,EAAAF,KACTD,mBAKDI,EAAAA,KAAAA,WAGL,EAAAH,KAAKC,MAAQ,GACbD,KAAKE,UAAY,GACjBF,KAAKD,cAAgBA,EACrBC,KAAKG,MAAQ,CACf,CAEA,cAAIC,GACF,OAAOJ,KAAKG,MAAQH,KAAKD,aAC3B,CAEQM,cAAAA,GACNL,KAAKG,OACP,CAEQG,cAAAA,GACNN,KAAKG,QAEc,IAAfH,KAAKG,QACPH,KAAKE,UAAUK,QAASC,GAAYA,KACpCR,KAAKE,UAAY,GAErB,CAEAO,OAAAA,GACE,OAAIT,KAAKI,YACPJ,KAAKK,iBACEK,QAAQF,eAEJE,QAASF,GAAYR,KAAKC,MAAMU,KAAKH,GAEpD,CAEAI,OAAAA,GACE,MAAMC,EAAcb,KAAKC,MAAMa,QAE3BD,EAEFE,WAAWF,EAAa,GAExBb,KAAKM,gBAET,CAEAU,IAAAA,GACE,OAAW,IAAAN,QAASF,GAAYR,KAAKE,UAAUS,KAAKH,GACtD,EAGF,MAAMS,EAAa,WAEnB,MAAMC,EAOJpB,WAAAA,CAAYC,EAAwB,GAN5BoB,KAAAA,wBACApB,EAAAA,KAAAA,mBAMN,EAAAC,KAAKmB,mBAAqB,GAC1BnB,KAAKD,cAAgBA,CACvB,CAEQqB,oBAAAA,CAAqBC,EAAuBJ,GAClD,OAAOK,QAAQtB,KAAKmB,mBAAmBE,GACzC,CAEQE,oBAAAA,CAAqBF,EAAuBJ,GAIlD,OAHKjB,KAAKoB,qBAAqBC,KAC7BrB,KAAKmB,mBAAmBE,GAAO,IAAIxB,EAAcG,KAAKD,gBAE7CC,KAACmB,mBAAmBE,EACjC,CAKQG,IAAAA,CAAKH,EAAuBJ,GAEhCjB,KAAKoB,qBAAqBC,IACe,IAAzCrB,KAAKuB,qBAAqBF,GAAKlB,cAExBH,KAAKmB,mBAAmBE,EAEnC,CASAjB,UAAAA,CAAWiB,EAAuBJ,GAChC,YAAYM,qBAAqBF,GAAKjB,UACxC,CAKAK,OAAAA,CAAQY,EAAuBJ,GAC7B,OAAOjB,KAAKuB,qBAAqBF,GAAKZ,SACxC,CAKAG,OAAAA,CAAQS,EAAuBJ,GAC7BjB,KAAKuB,qBAAqBF,GAAKT,UAC/BZ,KAAKwB,KAAKH,EACZ,CAOAlB,KAAAA,CAAMkB,EAAuBJ,GAC3B,OAAIjB,KAAKoB,qBAAqBC,GACrBrB,KAAKuB,qBAAqBF,GAAKlB,MAE/B,CAEX,CAMAsB,QAAAA,CAASJ,EAAuBJ,GAC9B,OAAWjB,KAACG,MAAMkB,GAAO,CAC3B,CAOA,aAAMK,CACJC,EACAN,EAAuBJ,GAEvB,IAEE,aADUjB,KAACS,QAAQY,SACNM,GACd,CAAA,QACC3B,KAAKY,QAAQS,EACd,CACH,CAUA,wBAAMO,CACJD,EACAN,EAAuBJ,GAEvB,OAAIjB,KAAKI,WAAWiB,GACXrB,KAAK0B,QAAQC,EAAIN,GAGzB,IACH,CAQA,UAAML,CAAKK,EAAuBJ,GAChC,OAAIjB,KAAKyB,SAASJ,QACJE,qBAAqBF,GAAKL,OAE/BN,QAAQF,SAEnB"}
1
+ {"version":3,"file":"promise-semaphore.modern.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Function[];\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return !this.hasSemaphoreInstance(key) ||\n this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["SemaphoreItem","constructor","maxConcurrent","this","queue","count","canAcquire","incrementCount","decrementCount","acquire","Promise","resolve","push","release","resolveFunc","shift","setTimeout","defaultKey","Semaphore","semaphoreInstances","hasSemaphoreInstance","key","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"AAAA,MAAMA,EASFC,WAAAA,CAAYC,GAAqBC,KARzBC,WAAK,EAAAD,KACLD,mBAAa,EAAAC,KAKdE,WAGH,EAAAF,KAAKC,MAAQ,GACbD,KAAKD,cAAgBA,EACrBC,KAAKE,MAAQ,CACjB,CAEA,cAAIC,GACA,OAAOH,KAAKE,MAAQF,KAAKD,aAC7B,CAEQK,cAAAA,GACJJ,KAAKE,OACT,CAEQG,cAAAA,GACJL,KAAKE,OACT,CAEAI,OAAAA,GACI,OAAIN,KAAKG,YACLH,KAAKI,iBACEG,QAAQC,WAER,IAAID,QAASC,GAAYR,KAAKC,MAAMQ,KAAKD,GAExD,CAEAE,OAAAA,GACI,MAAMC,EAAcX,KAAKC,MAAMW,QAE3BD,EAEAE,WAAWF,EAAa,GAExBX,KAAKK,gBAEb,EAGJ,MAAMS,EAAa,WAEnB,MAAMC,EAOFjB,WAAAA,CAAYC,EAAwB,GAACC,KAN7BgB,wBAAkB,EAAAhB,KAClBD,mBAAa,EAMjBC,KAAKgB,mBAAqB,CAAA,EAC1BhB,KAAKD,cAAgBA,CACzB,CAEQkB,oBAAAA,CAAqBC,EAAuBJ,GAChD,OAAOK,QAAQnB,KAAKgB,mBAAmBE,GAC3C,CAEQE,oBAAAA,CAAqBF,EAAuBJ,GAMhD,OALKd,KAAKiB,qBAAqBC,KAC3BlB,KAAKgB,mBAAmBE,GAAO,IAAIrB,EAC/BG,KAAKD,gBAGFC,KAACgB,mBAAmBE,EACnC,CAKQG,IAAAA,CAAKH,EAAuBJ,GAE5Bd,KAAKiB,qBAAqBC,IACe,IAAzClB,KAAKoB,qBAAqBF,GAAKhB,cAExBF,KAAKgB,mBAAmBE,EAEvC,CASAf,UAAAA,CAAWe,EAAuBJ,GAC9B,OAAQd,KAAKiB,qBAAqBC,IAC9BlB,KAAKoB,qBAAqBF,GAAKf,UACvC,CAKAG,OAAAA,CAAQY,EAAuBJ,GAC3B,OAAWd,KAACoB,qBAAqBF,GAAKZ,SAC1C,CAKAI,OAAAA,CAAQQ,EAAuBJ,GAC3Bd,KAAKoB,qBAAqBF,GAAKR,UAC/BV,KAAKqB,KAAKH,EACd,CAOAhB,KAAAA,CAAMgB,EAAuBJ,GACzB,OAAId,KAAKiB,qBAAqBC,GACnBlB,KAAKoB,qBAAqBF,GAAKhB,MAGzC,CACL,CAMAoB,QAAAA,CAASJ,EAAuBJ,GAC5B,OAAWd,KAACE,MAAMgB,GAAO,CAC7B,CAOA,aAAMK,CACFC,EACAN,EAAuBJ,GAEvB,IAEI,aADUd,KAACM,QAAQY,SACNM,GAChB,CAAA,QACGxB,KAAKU,QAAQQ,EAChB,CACL,CAUA,wBAAMO,CACFD,EACAN,EAAuBJ,GAEvB,OAAId,KAAKG,WAAWe,GACLlB,KAACuB,QAAQC,EAAIN,GAEjB,IAEf,ECtJJ,MAAMQ,EAAc5B,WAAAA,GAAAE,KACR2B,WAAa,IAAIZ,OACjBa,cAAwC,CAAE,OAC1CC,cAA+C,CAAA,CAAE,CAEzD,aAAMvB,CAAQY,GAAW,IAAAY,EAAAC,EACrB,MAAMC,EAAqCF,OAA1BA,EAAG9B,KAAK4B,cAAcV,IAAIY,EAAI,EAC/C9B,KAAK4B,cAAcV,GAAOc,EAAc,EACxC,MAAMC,EAAgC,OAA1BF,EAAG/B,KAAK6B,cAAcX,IAAIa,EAAI/B,KAAK2B,WAAWrB,UAC1DN,KAAK6B,cAAcX,GAAOe,QACpBA,CACV,CAEAvB,OAAAA,CAAQQ,GACJ,MAAMc,EAAchC,KAAK4B,cAAcV,GAEnB,IAAhBc,GACAhC,KAAK2B,WAAWjB,iBACTV,KAAK4B,cAAcV,UACnBlB,KAAK6B,cAAcX,IAE1BlB,KAAK4B,cAAcV,GAAOc,EAAc,CAEhD"}
@@ -1,2 +1,2 @@
1
- function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.waitQueue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.waitQueue=[],this.maxConcurrent=e,this.count=0}var n,r,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--,0===this.count&&(this.waitQueue.forEach(function(e){return e()}),this.waitQueue=[])},i.acquire=function(){var e=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(t){return e.queue.push(t)})},i.release=function(){var e=this.queue.shift();e?setTimeout(e,0):this.decrementCount()},i.wait=function(){var e=this;return new Promise(function(t){return e.waitQueue.push(t)})},n=t,(r=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,n){for(var r=0;r<n.length;r++){var i=n[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(n.prototype,r),Object.defineProperty(n,"prototype",{writable:!1}),n}(),n="_default",r=/*#__PURE__*/function(){function e(e){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}var r=e.prototype;return r.hasSemaphoreInstance=function(e){return void 0===e&&(e=n),Boolean(this.semaphoreInstances[e])},r.getSemaphoreInstance=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new t(this.maxConcurrent)),this.semaphoreInstances[e]},r.tidy=function(e){void 0===e&&(e=n),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},r.canAcquire=function(e){return void 0===e&&(e=n),this.getSemaphoreInstance(e).canAcquire},r.acquire=function(e){return void 0===e&&(e=n),this.getSemaphoreInstance(e).acquire()},r.release=function(e){void 0===e&&(e=n),this.getSemaphoreInstance(e).release(),this.tidy(e)},r.count=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)?this.getSemaphoreInstance(e).count:0},r.hasTasks=function(e){return void 0===e&&(e=n),this.count(e)>0},r.request=function(e,t){void 0===t&&(t=n);try{var r=this;return Promise.resolve(function(n,i){try{var o=Promise.resolve(r.acquire(t)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,n){if(r.release(t),e)throw n;return n}))}catch(e){return Promise.reject(e)}},r.requestIfAvailable=function(e,t){void 0===t&&(t=n);try{return this.canAcquire(t)?Promise.resolve(this.request(e,t)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},r.wait=function(e){void 0===e&&(e=n);try{return this.hasTasks(e)?Promise.resolve(this.getSemaphoreInstance(e).wait()):Promise.resolve()}catch(e){return Promise.reject(e)}},e}();export{r as Semaphore,r as default};
1
+ function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var n,r,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(){var e=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(t){return e.queue.push(t)})},i.release=function(){var e=this.queue.shift();e?setTimeout(e,0):this.decrementCount()},n=t,(r=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,n){for(var r=0;r<n.length;r++){var i=n[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(n.prototype,r),Object.defineProperty(n,"prototype",{writable:!1}),n}(),n="_default",r=/*#__PURE__*/function(){function e(e){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}var r=e.prototype;return r.hasSemaphoreInstance=function(e){return void 0===e&&(e=n),Boolean(this.semaphoreInstances[e])},r.getSemaphoreInstance=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new t(this.maxConcurrent)),this.semaphoreInstances[e]},r.tidy=function(e){void 0===e&&(e=n),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},r.canAcquire=function(e){return void 0===e&&(e=n),!this.hasSemaphoreInstance(e)||this.getSemaphoreInstance(e).canAcquire},r.acquire=function(e){return void 0===e&&(e=n),this.getSemaphoreInstance(e).acquire()},r.release=function(e){void 0===e&&(e=n),this.getSemaphoreInstance(e).release(),this.tidy(e)},r.count=function(e){return void 0===e&&(e=n),this.hasSemaphoreInstance(e)?this.getSemaphoreInstance(e).count:0},r.hasTasks=function(e){return void 0===e&&(e=n),this.count(e)>0},r.request=function(e,t){void 0===t&&(t=n);try{var r=this;return Promise.resolve(function(n,i){try{var o=Promise.resolve(r.acquire(t)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,n){if(r.release(t),e)throw n;return n}))}catch(e){return Promise.reject(e)}},r.requestIfAvailable=function(e,t){void 0===t&&(t=n);try{return this.canAcquire(t)?Promise.resolve(this.request(e,t)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}(),i=/*#__PURE__*/function(){function e(){this._semaphore=new r,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,n,r=this,i=null!=(t=r._activeCounts[e])?t:0;r._activeCounts[e]=i+1;var o=null!=(n=r._groupWaiters[e])?n:r._semaphore.acquire();return r._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}();export{i as GroupSemaphore,r as Semaphore};
2
2
  //# sourceMappingURL=promise-semaphore.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"promise-semaphore.module.js","sources":["../src/index.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Array<Function>;\n private waitQueue: Array<Function>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.waitQueue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n\n if (this.count === 0) {\n this.waitQueue.forEach((resolve) => resolve());\n this.waitQueue = [];\n }\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n\n wait(): Promise<void> {\n return new Promise((resolve) => this.waitQueue.push(resolve));\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(this.maxConcurrent);\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n\n /**\n * Wait until the count on `key` is 0 and then resolve.\n *\n * @param key\n * @returns\n */\n async wait(key: string | number = defaultKey) {\n if (this.hasTasks(key)) {\n return this.getSemaphoreInstance(key).wait();\n } else {\n return Promise.resolve();\n }\n }\n\n // globalCount() {\n // return Object.values(this.semaphoreInstances).reduce(\n // (a, instance) => a + instance.count,\n // 0,\n // );\n // }\n}\n\nexport default Semaphore;\nexport { Semaphore };\n"],"names":["SemaphoreItem","maxConcurrent","queue","waitQueue","count","this","_proto","prototype","incrementCount","decrementCount","forEach","resolve","acquire","_this","canAcquire","Promise","push","release","resolveFunc","shift","setTimeout","wait","_this2","key","get","defaultKey","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","_this3","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable"],"mappings":"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"}
1
+ {"version":3,"file":"promise-semaphore.module.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["class SemaphoreItem {\n private queue: Function[];\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => this.queue.push(resolve));\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nconst defaultKey = \"_default\";\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: string | number = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: string | number = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: string | number = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: string | number = defaultKey): boolean {\n return !this.hasSemaphoreInstance(key) ||\n this.getSemaphoreInstance(key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: string | number = defaultKey) {\n return this.getSemaphoreInstance(key).acquire();\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: string | number = defaultKey): void {\n this.getSemaphoreInstance(key).release();\n this.tidy(key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: string | number = defaultKey): number {\n if (this.hasSemaphoreInstance(key)) {\n return this.getSemaphoreInstance(key).count;\n } else {\n return 0;\n }\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: string | number = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: string | number = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","incrementCount","decrementCount","acquire","_this","canAcquire","Promise","resolve","push","release","resolveFunc","shift","setTimeout","key","get","defaultKey","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ declare class Semaphore {
2
+ private semaphoreInstances;
3
+ private maxConcurrent;
4
+ /**
5
+ * @param {number} [maxConcurrent] The maximum number of concurrent locks.
6
+ */
7
+ constructor(maxConcurrent?: number);
8
+ private hasSemaphoreInstance;
9
+ private getSemaphoreInstance;
10
+ /**
11
+ * @param {string | number} [key]- Optional, the semaphore key.
12
+ */
13
+ private tidy;
14
+ /**
15
+ * A synchronous function to determine whether a lock can be acquired.
16
+ *
17
+ * @param {string | number} [key]- Optional, the semaphore key.
18
+ * @returns {boolean} Returns true if the lock on `key` can be acquired, false
19
+ * otherwise.
20
+ */
21
+ canAcquire(key?: string | number): boolean;
22
+ /**
23
+ * @param {string | number} [key]- Optional, the semaphore key.
24
+ */
25
+ acquire(key?: string | number): Promise<void>;
26
+ /**
27
+ * @param {string | number} [key]- Optional, the semaphore key.
28
+ */
29
+ release(key?: string | number): void;
30
+ /**
31
+ * The number of active locks. Will always be less or equal to `max`.
32
+ *
33
+ * @param {string | number} [key]- Optional, the semaphore key.
34
+ */
35
+ count(key?: string | number): number;
36
+ /**
37
+ * @param {string | number} [key]- Optional, the semaphore key.
38
+ * @returns {boolean} True if the semaphore and key has locks, false otherwise.
39
+ */
40
+ hasTasks(key?: string | number): boolean;
41
+ /**
42
+ * @param {Function<T>} fn The function to execute.
43
+ * @param {string | number} [key]- Optional, the semaphore key.
44
+ * @returns {Promise<T>}
45
+ */
46
+ request<T>(fn: Function, key?: string | number): Promise<T>;
47
+ /**
48
+ * Asynchronously executes `fn` if a lock can be immediately acquired.
49
+ * Otherwise, returns null.
50
+ *
51
+ * @param {Function<T>} fn The function to execute.
52
+ * @param {string | number} [key]- Optional, the semaphore key.
53
+ * @returns {Promise<T>}
54
+ */
55
+ requestIfAvailable<T>(fn: Function, key?: string | number): Promise<T | null>;
56
+ }
57
+ export { Semaphore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chriscdn/promise-semaphore",
3
- "version": "2.0.12",
3
+ "version": "3.0.1",
4
4
  "description": "Limit or throttle the simultaneous execution of asynchronous code in separate iterations of the event loop.",
5
5
  "repository": "https://github.com/chriscdn/promise-semaphore",
6
6
  "author": "Christopher Meyer <chris@schwiiz.org>",
@@ -0,0 +1,46 @@
1
+ import { Semaphore } from "./semaphore";
2
+
3
+ /**
4
+ * GroupSemaphore manages a shared semaphore for different groups of tasks. Each
5
+ * group is identified by a unique key, and the semaphore ensures only one group
6
+ * can run its tasks concurrently.
7
+ *
8
+ * - acquire(key): Increments the active count for the given group. If it's the
9
+ * first task for the group (active count is 0), it acquires the global
10
+ * semaphore, ensuring only one group's tasks can proceed at a time.
11
+ * Subsequent calls in the group increment the count and are permitted to run.
12
+ * - release(key): Decrements the active count for the group. If the last task
13
+ * for that group is released, it releases the global semaphore, allowing
14
+ * other groups to proceed.
15
+ *
16
+ * This ensures that only one group can execute concurrently, but multiple tasks
17
+ * within the same group can run as long as no other tasks from different groups
18
+ * are active.
19
+ */
20
+ class GroupSemaphore {
21
+ private _semaphore = new Semaphore();
22
+ private _activeCounts: Record<string, number> = {};
23
+ private _groupWaiters: Record<string, Promise<void>> = {};
24
+
25
+ async acquire(key: string) {
26
+ const activeCount = this._activeCounts[key] ?? 0;
27
+ this._activeCounts[key] = activeCount + 1;
28
+ const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();
29
+ this._groupWaiters[key] = waiter;
30
+ await waiter;
31
+ }
32
+
33
+ release(key: string) {
34
+ const activeCount = this._activeCounts[key];
35
+
36
+ if (activeCount === 1) {
37
+ this._semaphore.release();
38
+ delete this._activeCounts[key];
39
+ delete this._groupWaiters[key];
40
+ } else {
41
+ this._activeCounts[key] = activeCount - 1;
42
+ }
43
+ }
44
+ }
45
+
46
+ export { GroupSemaphore };
package/src/index.ts CHANGED
@@ -1,203 +1,2 @@
1
- class SemaphoreItem {
2
- private queue: Array<Function>;
3
- private waitQueue: Array<Function>;
4
- private maxConcurrent: number;
5
-
6
- /**
7
- * The number of locks.
8
- */
9
- public count: number;
10
-
11
- constructor(maxConcurrent: number) {
12
- this.queue = [];
13
- this.waitQueue = [];
14
- this.maxConcurrent = maxConcurrent;
15
- this.count = 0;
16
- }
17
-
18
- get canAcquire(): boolean {
19
- return this.count < this.maxConcurrent;
20
- }
21
-
22
- private incrementCount() {
23
- this.count++;
24
- }
25
-
26
- private decrementCount() {
27
- this.count--;
28
-
29
- if (this.count === 0) {
30
- this.waitQueue.forEach((resolve) => resolve());
31
- this.waitQueue = [];
32
- }
33
- }
34
-
35
- acquire(): Promise<void> {
36
- if (this.canAcquire) {
37
- this.incrementCount();
38
- return Promise.resolve();
39
- } else {
40
- return new Promise((resolve) => this.queue.push(resolve));
41
- }
42
- }
43
-
44
- release(): void {
45
- const resolveFunc = this.queue.shift();
46
-
47
- if (resolveFunc) {
48
- // Give the micro task queue a small break instead of calling resolveFunc() directly
49
- setTimeout(resolveFunc, 0);
50
- } else {
51
- this.decrementCount();
52
- }
53
- }
54
-
55
- wait(): Promise<void> {
56
- return new Promise((resolve) => this.waitQueue.push(resolve));
57
- }
58
- }
59
-
60
- const defaultKey = "_default";
61
-
62
- class Semaphore {
63
- private semaphoreInstances: Record<string | number, SemaphoreItem>;
64
- private maxConcurrent: number;
65
-
66
- /**
67
- * @param {number} [maxConcurrent] The maximum number of concurrent locks.
68
- */
69
- constructor(maxConcurrent: number = 1) {
70
- this.semaphoreInstances = {};
71
- this.maxConcurrent = maxConcurrent;
72
- }
73
-
74
- private hasSemaphoreInstance(key: string | number = defaultKey) {
75
- return Boolean(this.semaphoreInstances[key]);
76
- }
77
-
78
- private getSemaphoreInstance(key: string | number = defaultKey) {
79
- if (!this.hasSemaphoreInstance(key)) {
80
- this.semaphoreInstances[key] = new SemaphoreItem(this.maxConcurrent);
81
- }
82
- return this.semaphoreInstances[key];
83
- }
84
-
85
- /**
86
- * @param {string | number} [key]- Optional, the semaphore key.
87
- */
88
- private tidy(key: string | number = defaultKey): void {
89
- if (
90
- this.hasSemaphoreInstance(key) &&
91
- this.getSemaphoreInstance(key).count === 0
92
- ) {
93
- delete this.semaphoreInstances[key];
94
- }
95
- }
96
-
97
- /**
98
- * A synchronous function to determine whether a lock can be acquired.
99
- *
100
- * @param {string | number} [key]- Optional, the semaphore key.
101
- * @returns {boolean} Returns true if the lock on `key` can be acquired, false
102
- * otherwise.
103
- */
104
- canAcquire(key: string | number = defaultKey): boolean {
105
- return this.getSemaphoreInstance(key).canAcquire;
106
- }
107
-
108
- /**
109
- * @param {string | number} [key]- Optional, the semaphore key.
110
- */
111
- acquire(key: string | number = defaultKey) {
112
- return this.getSemaphoreInstance(key).acquire();
113
- }
114
-
115
- /**
116
- * @param {string | number} [key]- Optional, the semaphore key.
117
- */
118
- release(key: string | number = defaultKey): void {
119
- this.getSemaphoreInstance(key).release();
120
- this.tidy(key);
121
- }
122
-
123
- /**
124
- * The number of active locks. Will always be less or equal to `max`.
125
- *
126
- * @param {string | number} [key]- Optional, the semaphore key.
127
- */
128
- count(key: string | number = defaultKey): number {
129
- if (this.hasSemaphoreInstance(key)) {
130
- return this.getSemaphoreInstance(key).count;
131
- } else {
132
- return 0;
133
- }
134
- }
135
-
136
- /**
137
- * @param {string | number} [key]- Optional, the semaphore key.
138
- * @returns {boolean} True if the semaphore and key has locks, false otherwise.
139
- */
140
- hasTasks(key: string | number = defaultKey): boolean {
141
- return this.count(key) > 0;
142
- }
143
-
144
- /**
145
- * @param {Function<T>} fn The function to execute.
146
- * @param {string | number} [key]- Optional, the semaphore key.
147
- * @returns {Promise<T>}
148
- */
149
- async request<T>(
150
- fn: Function,
151
- key: string | number = defaultKey,
152
- ): Promise<T> {
153
- try {
154
- await this.acquire(key);
155
- return await fn();
156
- } finally {
157
- this.release(key);
158
- }
159
- }
160
-
161
- /**
162
- * Asynchronously executes `fn` if a lock can be immediately acquired.
163
- * Otherwise, returns null.
164
- *
165
- * @param {Function<T>} fn The function to execute.
166
- * @param {string | number} [key]- Optional, the semaphore key.
167
- * @returns {Promise<T>}
168
- */
169
- async requestIfAvailable<T>(
170
- fn: Function,
171
- key: string | number = defaultKey,
172
- ): Promise<T | null> {
173
- if (this.canAcquire(key)) {
174
- return this.request(fn, key);
175
- } else {
176
- return null;
177
- }
178
- }
179
-
180
- /**
181
- * Wait until the count on `key` is 0 and then resolve.
182
- *
183
- * @param key
184
- * @returns
185
- */
186
- async wait(key: string | number = defaultKey) {
187
- if (this.hasTasks(key)) {
188
- return this.getSemaphoreInstance(key).wait();
189
- } else {
190
- return Promise.resolve();
191
- }
192
- }
193
-
194
- // globalCount() {
195
- // return Object.values(this.semaphoreInstances).reduce(
196
- // (a, instance) => a + instance.count,
197
- // 0,
198
- // );
199
- // }
200
- }
201
-
202
- export default Semaphore;
203
- export { Semaphore };
1
+ export { Semaphore } from "./semaphore";
2
+ export { GroupSemaphore } from "./group-semaphore";
@@ -0,0 +1,173 @@
1
+ class SemaphoreItem {
2
+ private queue: Function[];
3
+ private maxConcurrent: number;
4
+
5
+ /**
6
+ * The number of locks.
7
+ */
8
+ public count: number;
9
+
10
+ constructor(maxConcurrent: number) {
11
+ this.queue = [];
12
+ this.maxConcurrent = maxConcurrent;
13
+ this.count = 0;
14
+ }
15
+
16
+ get canAcquire(): boolean {
17
+ return this.count < this.maxConcurrent;
18
+ }
19
+
20
+ private incrementCount() {
21
+ this.count++;
22
+ }
23
+
24
+ private decrementCount() {
25
+ this.count--;
26
+ }
27
+
28
+ acquire(): Promise<void> {
29
+ if (this.canAcquire) {
30
+ this.incrementCount();
31
+ return Promise.resolve();
32
+ } else {
33
+ return new Promise((resolve) => this.queue.push(resolve));
34
+ }
35
+ }
36
+
37
+ release(): void {
38
+ const resolveFunc = this.queue.shift();
39
+
40
+ if (resolveFunc) {
41
+ // Give the micro task queue a small break instead of calling resolveFunc() directly
42
+ setTimeout(resolveFunc, 0);
43
+ } else {
44
+ this.decrementCount();
45
+ }
46
+ }
47
+ }
48
+
49
+ const defaultKey = "_default";
50
+
51
+ class Semaphore {
52
+ private semaphoreInstances: Record<string | number, SemaphoreItem>;
53
+ private maxConcurrent: number;
54
+
55
+ /**
56
+ * @param {number} [maxConcurrent] The maximum number of concurrent locks.
57
+ */
58
+ constructor(maxConcurrent: number = 1) {
59
+ this.semaphoreInstances = {};
60
+ this.maxConcurrent = maxConcurrent;
61
+ }
62
+
63
+ private hasSemaphoreInstance(key: string | number = defaultKey) {
64
+ return Boolean(this.semaphoreInstances[key]);
65
+ }
66
+
67
+ private getSemaphoreInstance(key: string | number = defaultKey) {
68
+ if (!this.hasSemaphoreInstance(key)) {
69
+ this.semaphoreInstances[key] = new SemaphoreItem(
70
+ this.maxConcurrent,
71
+ );
72
+ }
73
+ return this.semaphoreInstances[key];
74
+ }
75
+
76
+ /**
77
+ * @param {string | number} [key]- Optional, the semaphore key.
78
+ */
79
+ private tidy(key: string | number = defaultKey): void {
80
+ if (
81
+ this.hasSemaphoreInstance(key) &&
82
+ this.getSemaphoreInstance(key).count === 0
83
+ ) {
84
+ delete this.semaphoreInstances[key];
85
+ }
86
+ }
87
+
88
+ /**
89
+ * A synchronous function to determine whether a lock can be acquired.
90
+ *
91
+ * @param {string | number} [key]- Optional, the semaphore key.
92
+ * @returns {boolean} Returns true if the lock on `key` can be acquired, false
93
+ * otherwise.
94
+ */
95
+ canAcquire(key: string | number = defaultKey): boolean {
96
+ return !this.hasSemaphoreInstance(key) ||
97
+ this.getSemaphoreInstance(key).canAcquire;
98
+ }
99
+
100
+ /**
101
+ * @param {string | number} [key]- Optional, the semaphore key.
102
+ */
103
+ acquire(key: string | number = defaultKey) {
104
+ return this.getSemaphoreInstance(key).acquire();
105
+ }
106
+
107
+ /**
108
+ * @param {string | number} [key]- Optional, the semaphore key.
109
+ */
110
+ release(key: string | number = defaultKey): void {
111
+ this.getSemaphoreInstance(key).release();
112
+ this.tidy(key);
113
+ }
114
+
115
+ /**
116
+ * The number of active locks. Will always be less or equal to `max`.
117
+ *
118
+ * @param {string | number} [key]- Optional, the semaphore key.
119
+ */
120
+ count(key: string | number = defaultKey): number {
121
+ if (this.hasSemaphoreInstance(key)) {
122
+ return this.getSemaphoreInstance(key).count;
123
+ } else {
124
+ return 0;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * @param {string | number} [key]- Optional, the semaphore key.
130
+ * @returns {boolean} True if the semaphore and key has locks, false otherwise.
131
+ */
132
+ hasTasks(key: string | number = defaultKey): boolean {
133
+ return this.count(key) > 0;
134
+ }
135
+
136
+ /**
137
+ * @param {Function<T>} fn The function to execute.
138
+ * @param {string | number} [key]- Optional, the semaphore key.
139
+ * @returns {Promise<T>}
140
+ */
141
+ async request<T>(
142
+ fn: Function,
143
+ key: string | number = defaultKey,
144
+ ): Promise<T> {
145
+ try {
146
+ await this.acquire(key);
147
+ return await fn();
148
+ } finally {
149
+ this.release(key);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Asynchronously executes `fn` if a lock can be immediately acquired.
155
+ * Otherwise, returns null.
156
+ *
157
+ * @param {Function<T>} fn The function to execute.
158
+ * @param {string | number} [key]- Optional, the semaphore key.
159
+ * @returns {Promise<T>}
160
+ */
161
+ async requestIfAvailable<T>(
162
+ fn: Function,
163
+ key: string | number = defaultKey,
164
+ ): Promise<T | null> {
165
+ if (this.canAcquire(key)) {
166
+ return this.request(fn, key);
167
+ } else {
168
+ return null;
169
+ }
170
+ }
171
+ }
172
+
173
+ export { Semaphore };