{"_id":"@henrygd/semaphore","_rev":"4-6d9963c9228eee0b0553d8aadc8cccdc","name":"@henrygd/semaphore","dist-tags":{"latest":"0.1.0"},"versions":{"0.0.1-rc.1":{"name":"@henrygd/semaphore","version":"0.0.1-rc.1","keywords":["mutex","semaphore","lock","async","await","concurrency"],"author":{"url":"https://henrygd.me","name":"Hank Dollman","email":"hank@henrygd.me"},"license":"MIT","_id":"@henrygd/semaphore@0.0.1-rc.1","homepage":"https://github.com/henrygd/semaphore#readme","bugs":{"url":"https://github.com/henrygd/semaphore/issues"},"dist":{"shasum":"2a982a60c77b7ca2352a241e2ee1de6241735cca","tarball":"https://registry.npmjs.org/@henrygd/semaphore/-/semaphore-0.0.1-rc.1.tgz","fileCount":13,"integrity":"sha512-fz7NKDhHKMsgPECPysc9s2gN1VLSta0o4VzYSk2rpDrYuOSrTChhgGyQ/Kihf/0acOQnX10Ky5BYTBTXd9wo5A==","signatures":[{"sig":"MEUCIQCAS7/VDeVCoUgUihXYMd8MMj/Gb5QdT+RxcxVuLY9sVQIgbVeik7ipcrp2SRIaFIz5yj8FSzIa6qgYCWqCAU5J+NI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":23141},"type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"25c06b6e6b0f587614655d50c13d55d6bdd4eb57","scripts":{"test":"bun run test:dev && bun test:dist","bench":"node bench/bench.js","build":"bun run build.ts && ls -l dist/index.min.js && bun run generate-types","test:dev":"bun test test/bun.test.ts","bench:bun":"bun run bench/bench.js","test:deno":"bun run test:deno:dev && bun run test:deno:dist","test:dist":"DIST=true bun test test/bun.test.ts","bench:deno":"deno run --allow-env --allow-hrtime --allow-sys bench/bench.js","test:deno:dev":"deno test test/deno.test.ts --allow-env","generate-types":"tsc -d index.ts --outDir dist --emitDeclarationOnly > /dev/null","test:deno:dist":"DIST=true deno test test/deno.test.ts --allow-env","build:skip-types":"bun run build.ts && ls -l dist/index.min.js"},"_npmUser":{"name":"henrygd","email":"hank@henrygd.me"},"repository":{"url":"git+https://github.com/henrygd/semaphore.git","type":"git"},"_npmVersion":"10.8.1","description":"The fastest javascript inline semaphores and mutexes.","directories":{},"_nodeVersion":"22.3.0","_hasShrinkwrap":false,"devDependencies":{"mitata":"^0.1.11","esbuild":"^0.21.5","@types/bun":"^1.1.5","async-sema":"^3.1.1","typescript":"^5.5.2","async-mutex":"^0.5.0","await-semaphore":"^0.1.3","@shopify/semaphore":"^3.1.0"},"_npmOperationalInternal":{"tmp":"tmp/semaphore_0.0.1-rc.1_1719533361591_0.5246735304587062","host":"s3://npm-registry-packages"}},"0.0.1":{"name":"@henrygd/semaphore","version":"0.0.1","keywords":["mutex","semaphore","lock","async","await","concurrency"],"author":{"url":"https://henrygd.me","name":"Hank Dollman","email":"hank@henrygd.me"},"license":"MIT","_id":"@henrygd/semaphore@0.0.1","homepage":"https://github.com/henrygd/semaphore#readme","bugs":{"url":"https://github.com/henrygd/semaphore/issues"},"dist":{"shasum":"a57de3607a3c7f7e6889c1ace4411474ffd2ab06","tarball":"https://registry.npmjs.org/@henrygd/semaphore/-/semaphore-0.0.1.tgz","fileCount":13,"integrity":"sha512-crDmv7WlD7LPxGmWO8HLPCKPz2RgqG8NOx0XftLSXz6izTe569m0JW9iBg0Ms4/tJ3idvoDvCBI3lfOMh4TfZg==","signatures":[{"sig":"MEUCIHGRQQV6JPH6ZrlrB6FA3iS5idGxzGtn79KDHLtIx5D/AiEA3tezVg+mSq/k/inQ7szt0S5ZTgVDgCOE8m7lSzfwoJE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":23856},"type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"cbe32babf383ec321b6b331264a501b0ef9acfb0","scripts":{"test":"bun run test:dev && bun test:dist","bench":"node bench/bench.js","build":"bun run build.ts && ls -l dist/index.min.js && bun run generate-types","test:dev":"bun test test/bun.test.ts","bench:bun":"bun run bench/bench.js","test:deno":"bun run test:deno:dev && bun run test:deno:dist","test:dist":"DIST=true bun test test/bun.test.ts","bench:deno":"deno run --allow-env --allow-hrtime --allow-sys bench/bench.js","test:deno:dev":"deno test test/deno.test.ts --allow-env","generate-types":"tsc -d index.ts --outDir dist --emitDeclarationOnly > /dev/null","test:deno:dist":"DIST=true deno test test/deno.test.ts --allow-env","build:skip-types":"bun run build.ts && ls -l dist/index.min.js"},"_npmUser":{"name":"henrygd","email":"hank@henrygd.me"},"repository":{"url":"git+https://github.com/henrygd/semaphore.git","type":"git"},"_npmVersion":"10.8.1","description":"The fastest javascript inline semaphores and mutexes.","directories":{},"_nodeVersion":"22.3.0","_hasShrinkwrap":false,"devDependencies":{"mitata":"^0.1.11","esbuild":"^0.21.5","@types/bun":"^1.1.5","async-sema":"^3.1.1","typescript":"^5.5.2","async-mutex":"^0.5.0","await-semaphore":"^0.1.3","@shopify/semaphore":"^3.1.0"},"_npmOperationalInternal":{"tmp":"tmp/semaphore_0.0.1_1719536821962_0.5451962888169681","host":"s3://npm-registry-packages"}},"0.0.2":{"name":"@henrygd/semaphore","version":"0.0.2","keywords":["mutex","semaphore","lock","async","await","concurrency","promise"],"author":{"url":"https://henrygd.me","name":"Hank Dollman","email":"hank@henrygd.me"},"license":"MIT","_id":"@henrygd/semaphore@0.0.2","homepage":"https://github.com/henrygd/semaphore#readme","bugs":{"url":"https://github.com/henrygd/semaphore/issues"},"dist":{"shasum":"58a08b9411aeaeae4d87c34629d55fefc1ffab8e","tarball":"https://registry.npmjs.org/@henrygd/semaphore/-/semaphore-0.0.2.tgz","fileCount":13,"integrity":"sha512-N3W7MKwTRmAxOjeG0NAT18oe2Xn3KdjkpMR6crbkF1UDamMGPjyigqEsefiv+qTaxibtc1a+zXCVzb9YXANVqw==","signatures":[{"sig":"MEYCIQDZx3+plQzNf1qioeUA05lw4EePTzWKGgiA9YdUDJDBSAIhANJb02sc09MLMOjAFkVeTntNbrIO6b4zyd4Vb4w/txKp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":24671},"type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"e3d54ce9568c7f19c391e68ae76f2bf70565a521","scripts":{"test":"bun run test:dev && bun test:dist","bench":"node bench/bench.js","build":"bun run build.ts && ls -l dist/index.min.js && bun run generate-types","test:dev":"bun test test/bun.test.ts","bench:bun":"bun run bench/bench.js","test:deno":"bun run test:deno:dev && bun run test:deno:dist","test:dist":"DIST=true bun test test/bun.test.ts","bench:deno":"deno run --allow-env --allow-hrtime --allow-sys bench/bench.js","test:deno:dev":"deno test test/deno.test.ts --allow-env","generate-types":"tsc -d index.ts --outDir dist --emitDeclarationOnly > /dev/null","test:deno:dist":"DIST=true deno test test/deno.test.ts --allow-env","build:skip-types":"bun run build.ts && ls -l dist/index.min.js"},"_npmUser":{"name":"henrygd","email":"hank@henrygd.me"},"repository":{"url":"git+https://github.com/henrygd/semaphore.git","type":"git"},"_npmVersion":"10.8.1","description":"Fast inline semaphores and mutexes","directories":{},"_nodeVersion":"22.3.0","_hasShrinkwrap":false,"devDependencies":{"mitata":"^0.1.11","esbuild":"^0.21.5","@types/bun":"^1.1.5","async-sema":"^3.1.1","typescript":"^5.5.2","async-mutex":"^0.5.0","await-semaphore":"^0.1.3","@shopify/semaphore":"^3.1.0"},"_npmOperationalInternal":{"tmp":"tmp/semaphore_0.0.2_1719757969558_0.11795696933641642","host":"s3://npm-registry-packages"}},"0.1.0":{"name":"@henrygd/semaphore","version":"0.1.0","license":"MIT","type":"module","description":"Fast inline semaphores and mutexes","author":{"name":"Hank Dollman","email":"hank@henrygd.me","url":"https://henrygd.me"},"repository":{"type":"git","url":"git+https://github.com/henrygd/semaphore.git"},"bugs":{"url":"https://github.com/henrygd/semaphore/issues"},"exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs","types":"./dist/index.d.ts"}},"types":"./dist/index.d.ts","scripts":{"build":"bun run build.ts && ls -l dist/index.min.js && bun run generate-types","build:skip-types":"bun run build.ts && ls -l dist/index.min.js","generate-types":"tsc -d index.ts --outDir dist --emitDeclarationOnly > /dev/null","test":"bun run test:dev && bun test:dist","test:dev":"bun test test/bun.test.ts","test:dist":"DIST=true bun test test/bun.test.ts","test:deno":"bun run test:deno:dev && bun run test:deno:dist","test:deno:dist":"DIST=true deno test test/deno.test.ts --allow-env","test:deno:dev":"deno test test/deno.test.ts --allow-env","bench":"node bench/bench.js","bench:bun":"bun run bench/bench.js","bench:deno":"deno run --allow-env --allow-hrtime --allow-sys bench/bench.js"},"keywords":["mutex","semaphore","lock","async","await","concurrency","promise"],"devDependencies":{"@shopify/semaphore":"^3.1.0","@types/bun":"^1.1.5","async-mutex":"^0.5.0","async-sema":"^3.1.1","await-semaphore":"^0.1.3","esbuild":"^0.21.5","mitata":"^0.1.11","typescript":"^5.5.2"},"_id":"@henrygd/semaphore@0.1.0","gitHead":"eea78347cdbac04ccc7e8e0d6644d7f2fc787526","homepage":"https://github.com/henrygd/semaphore#readme","_nodeVersion":"22.13.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-5wRPsfP9iWnOTFRd7QKdHlWTgY9xw9GAHSkQP/Q05ZTKUBdfTj44BhsnADPfvBoUQVGDtP2Nd0UShVdeTLuLwA==","shasum":"92edf8e441b4f565d046d5de8c8d92437b5df26d","tarball":"https://registry.npmjs.org/@henrygd/semaphore/-/semaphore-0.1.0.tgz","fileCount":13,"unpackedSize":24670,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC35p0VlM6sGbQlbDFwgjUrh9gvlaGLMEg0/BlHsmqPFAIhAMGocotPk6d+pyjplOdujBm4tymuNAsrNpXQhhLQKaTx"}]},"_npmUser":{"name":"henrygd","email":"hank@henrygd.me"},"directories":{},"maintainers":[{"name":"henrygd","email":"hank@henrygd.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/semaphore_0.1.0_1757866447494_0.23317051418892287"},"_hasShrinkwrap":false}},"time":{"created":"2024-06-28T00:09:21.454Z","modified":"2025-09-14T16:14:07.927Z","0.0.1-rc.1":"2024-06-28T00:09:21.750Z","0.0.1":"2024-06-28T01:07:02.151Z","0.0.2":"2024-06-30T14:32:49.722Z","0.1.0":"2025-09-14T16:14:07.733Z"},"bugs":{"url":"https://github.com/henrygd/semaphore/issues"},"author":{"name":"Hank Dollman","email":"hank@henrygd.me","url":"https://henrygd.me"},"license":"MIT","homepage":"https://github.com/henrygd/semaphore#readme","keywords":["mutex","semaphore","lock","async","await","concurrency","promise"],"repository":{"type":"git","url":"git+https://github.com/henrygd/semaphore.git"},"description":"Fast inline semaphores and mutexes","maintainers":[{"name":"henrygd","email":"hank@henrygd.me"}],"readme":"[size-image]: https://img.shields.io/github/size/henrygd/semaphore/dist/index.min.js?style=flat\n[license-image]: https://img.shields.io/github/license/henrygd/semaphore?style=flat&color=%2349ac0c\n[license-url]: /LICENSE\n\n# @henrygd/semaphore\n\n[![File Size][size-image]](https://github.com/henrygd/semaphore/blob/main/dist/index.min.js) [![MIT license][license-image]][license-url] [![JSR Score 100%](https://jsr.io/badges/@henrygd/semaphore/score?v1)](https://jsr.io/@henrygd/semaphore)\n\nFast inline semaphores and mutexes. See [comparisons and benchmarks](#comparisons-and-benchmarks).\n\nSemaphores limit simultaneous access to code and resources (e.g. a file) among multiple concurrent tasks.\n\nWorks with: <img alt=\"browsers\" title=\"This package works with browsers.\" height=\"16px\" src=\"https://jsr.io/logos/browsers.svg\" /> <img alt=\"Deno\" title=\"This package works with Deno.\" height=\"16px\" src=\"https://jsr.io/logos/deno.svg\" /> <img alt=\"Node.js\" title=\"This package works with Node.js\" height=\"16px\" src=\"https://jsr.io/logos/node.svg\" /> <img alt=\"Cloudflare Workers\" title=\"This package works with Cloudflare Workers.\" height=\"16px\" src=\"https://jsr.io/logos/cloudflare-workers.svg\" /> <img alt=\"Bun\" title=\"This package works with Bun.\" height=\"16px\" src=\"https://jsr.io/logos/bun.svg\" />\n\n## Usage\n\nCreate or retrieve a semaphore by calling `getSemaphore` with optional key and concurrency limit.\n\n```js\nconst sem = getSemaphore('key', 1)\n```\n\nUse the `acquire` and `release` methods to limit access.\n\n```js\nawait sem.acquire()\n// access here is limited to one task at a time\nsem.release()\n```\n\n## Full example\n\nWe use semaphores here to prevent multiple requests to an API for the same resource.\n\nThe first calls to `fetchPokemon` will acquire access to the protected code. Subsequent calls will wait, then return the data from the cache.\n\nWe use a key to allow access based on the name. This lets `ditto` and `snorlax` run simultaneously.\n\n<!-- prettier-ignore -->\n```js\nimport { getSemaphore } from '@henrygd/semaphore'\n\nconst cache = new Map()\n\nfor (let i = 0; i < 5; i++) {\n    fetchPokemon('ditto')\n    fetchPokemon('snorlax')\n}\n\nasync function fetchPokemon(name) {\n    // get semaphore with key based on name\n    const sem = getSemaphore(name)\n    // acquire access from the semaphore\n    await sem.acquire()\n    try {\n        // return data from cache if available\n        if (cache.has(name)) {\n            console.log('Cache hit:', name)\n            return cache.get(name)\n        }\n        // otherwise fetch from API\n        console.warn('Fetching from API:', name)\n        const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`)\n        const json = await res.json()\n        cache.set(name, json)\n        return json\n    } finally {\n        // release access when done\n        sem.release()\n    }\n}\n```\n\n## Interface\n\n<!-- prettier-ignore -->\n```ts\n/**\n * Creates or retrieves existing semaphore with optional key and concurrency level.\n *\n * key - Key used to identify the semaphore. Defaults to `Symbol()`.\n * concurrency - Maximum concurrent tasks allowed access. Defaults to `1`.\n */\nfunction getSemaphore(key?: any, concurrency?: number): Semaphore\n\ninterface Semaphore {\n    /** Returns a promise that resolves when access is acquired */\n    acquire(): Promise<void>\n    /** Release access to the semaphore */\n    release(): void\n    /** Returns the total number of tasks active or waiting for access */\n    size(): number\n}\n```\n\n### Keys and persistence\n\nKeyed semaphores are held in a `Map` and deleted from the `Map` once they've been acquired and fully released (no waiting tasks).\n\nIf you need to reuse the same semaphore even after deletion from the `Map`, use a persistent variable instead of calling `getSemaphore` again.\n\n### Concurrency\n\nConcurrency is set for each semaphore on first creation via `getSemaphore`. If called again using the key for an active semaphore, the concurrency argument is ignored and the existing semaphore is returned.\n\n## Comparisons and benchmarks\n\nNote that we're looking at libraries which provide a promise-based locking mechanism, not callbacks.\n\n| Library                                                                | Version | Bundle size (B) | Keys | Weekly Downloads |\n| :--------------------------------------------------------------------- | :------ | :-------------- | :--- | :--------------- |\n| @henrygd/semaphore                                                     | 0.0.1   | 267             | yes  | ¯\\\\\\_(ツ)\\_/¯    |\n| [async-mutex](https://www.npmjs.com/package/async-mutex)               | 0.5.0   | 4,758           | no   | 1,639,071        |\n| [async-sema](https://www.npmjs.com/package/async-sema)                 | 3.1.1   | 3,532           | no   | 1,258,877        |\n| [await-semaphore](https://www.npmjs.com/package/await-semaphore)       | 0.1.3   | 1,184           | no   | 60,449           |\n| [@shopify/semaphore](https://www.npmjs.com/package/@shopify/semaphore) | 3.1.0   | 604             | no   | 29,089           |\n\n> If there's a library you'd like added to the table or benchmarks, please open an issue.\n\n## Benchmarks\n\nAll libraries run the same test. Each operation measures how long it takes a binary semaphore with 1,000 queued `acquire` requests to allow and release all requests.\n\n### Browser benchmark\n\nThis test was run in Chromium. Chrome and Edge are the same. Safari is more lopsided with Vercel's `async-sema` dropping to third. Firefox, though I love and respect it, seems to be hard capped by slow promise handling, with `async-mutex` not far behind.\n\nYou can run or tweak for yourself here: https://jsbm.dev/8bBxR1pBLw0TM\n\n![@henrygd/queue - 13,665 Ops/s. async-sema - 8,077 Ops/s. async-mutex - 5,576 Ops/s. @shopify/semaphore - 4,099 Ops/s.](https://henrygd-assets.b-cdn.net/semaphore/browser.png)\n\n> Note: `await-semaphore` is extremely slow for some reason and I didn't want to include it in the image because it seems excessive. Not sure what's happening there.\n\n### Node.js benchmark\n\n![@henrygd/queue - 1.7x faster than async-sema. 2.66x async-mutex. 3.08x async-semaphore. 3.47x @shopify/semaphore.](https://henrygd-assets.b-cdn.net/semaphore/node.png)\n\n### Bun benchmark\n\n![@henrygd/queue - 2x faster than async-semaphore 2.63x asynsc-mutex. 2.68x async-sema. 3.77x @shopify/semaphore.](https://henrygd-assets.b-cdn.net/semaphore/bun-bench.png)\n\n### Deno benchmark\n\n![@henrygd/queue - 1.7x faster than async-sema. 2.7x async-mutex. 2.72x await-semaphore. 4.01x @shopify/semaphore.](https://henrygd-assets.b-cdn.net/semaphore/deno-bench.png)\n\n### Cloudflare Workers benchmark\n\nUses [oha](https://github.com/hatoo/oha) to make 1,000 requests to each worker. Each request creates a semaphore and resolves 5,000 acquisitions / releases.\n\nThis was run locally using [Wrangler](https://developers.cloudflare.com/workers/get-started/guide/). Wrangler uses the same [workerd](https://github.com/cloudflare/workerd) runtime as workers deployed to Cloudflare, so the relative difference should be accurate. Here's the [repo for this benchmark](https://github.com/henrygd/semaphore-wrangler-benchmark).\n\n| Library            | Requests/sec | Total (sec) | Average | Slowest |\n| :----------------- | :----------- | :---------- | :------ | :------ |\n| @henrygd/semaphore | 941.8135     | 1.0618      | 0.0521  | 0.0788  |\n| async-mutex        | 569.5130     | 1.7559      | 0.0862  | 0.1251  |\n| async-sema         | 375.7332     | 2.6615      | 0.1308  | 0.1818  |\n| @shopify/semaphore | 167.8239     | 5.9586      | 0.2925  | 0.4063  |\n| await-semaphore\\*  | n/a          | n/a         | n/a     | n/a     |\n\n> \\* `await-semaphore` does not work with concurrent requests.\n\n## Related\n\n[`@henrygd/queue`](https://github.com/henrygd/queue) - Tiny async queue with concurrency control. Like p-limit or fastq, but smaller and faster.\n\n## License\n\n[MIT license](/LICENSE)\n","readmeFilename":"readme.md"}