{"_id":"@dmytromykhailiuk/execution-blocker","_rev":"2-b214f315e6929a836226f0ef641b1ba6","name":"@dmytromykhailiuk/execution-blocker","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@dmytromykhailiuk/execution-blocker","version":"1.0.0","keywords":["mutex","lock","async-lock","queue","fifo","sequential","serialize","critical-section","concurrency","async","promise","typescript","zero-dependencies"],"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","_id":"@dmytromykhailiuk/execution-blocker@1.0.0","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"homepage":"https://github.com/dmytromykhailiuk/execution-blocker#readme","bugs":{"url":"https://github.com/dmytromykhailiuk/execution-blocker/issues"},"dist":{"shasum":"0490a48538862fdf7e8b8d9a71ec2396aff26b7e","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/execution-blocker/-/execution-blocker-1.0.0.tgz","fileCount":9,"integrity":"sha512-uBJvx/RlUwPTcvc2j8wOEgwZHsETJo17w86CP3f9vXVMGuR88i8ZNj8bIM7H1TZQ+DR2UnHh/r1G1fg3haiirw==","signatures":[{"sig":"MEQCIEdwFkOYnary2+gOQkC0ygYO9qo3GYLxuvjG+nqn3ddDAiAlvpvo1DWJ8tfU3SifBuSixS1C7X+cwu9zCf0y4kbORQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24277},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"0c83cfa9c9431597be004bf2180cf74328ef91a0","scripts":{"dev":"tsup --watch","lint":"biome check .","test":"vitest run","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","playground":"vite --config vite.playground.config.ts","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"repository":{"url":"git+https://github.com/dmytromykhailiuk/execution-blocker.git","type":"git"},"_npmVersion":"11.6.2","description":"Promise-based FIFO execution lock with independent queues per id — run async logic strictly one at a time. Zero dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vite":"^5.4.11","vitest":"^2.1.8","typescript":"^5.7.3","@types/node":"^22.10.5","@biomejs/biome":"^1.9.4"},"_npmOperationalInternal":{"tmp":"tmp/execution-blocker_1.0.0_1784995967773_0.29367801353496903","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@dmytromykhailiuk/execution-blocker","version":"1.0.1","description":"Promise-based FIFO execution lock with independent queues per id — run async logic strictly one at a time. Zero dependencies.","type":"module","sideEffects":false,"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","keywords":["mutex","lock","async-lock","queue","fifo","sequential","serialize","critical-section","concurrency","async","promise","typescript","zero-dependencies"],"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"build":"tsup","dev":"tsup --watch","playground":"vite --config vite.playground.config.ts","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","lint":"biome check .","lint:fix":"biome check --write .","format":"biome format --write .","prepublishOnly":"npm run build"},"engines":{"node":">=18"},"devDependencies":{"@biomejs/biome":"^1.9.4","@types/node":"^22.10.5","tsup":"^8.3.5","typescript":"^5.7.3","vite":"^5.4.11","vitest":"^2.1.8"},"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/execution-blocker.git"},"bugs":{"url":"https://github.com/dmytromykhailiuk/execution-blocker/issues"},"homepage":"https://dmytromykhailiuk.github.io/execution-blocker/","gitHead":"12fd8532e4162103c8c009a99f32eb9e97d8f3fe","_id":"@dmytromykhailiuk/execution-blocker@1.0.1","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-t2dmb43NSVBLm3Y6ABKhY8kFzdoi2/3jwGw4LQsOyvqMshr7iriNMbeI5uJDKyR3Ipkwq3amkVtiYo0Y0+MV0A==","shasum":"34600ec7a61d25088c619bd39848e8fa03c4d154","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/execution-blocker/-/execution-blocker-1.0.1.tgz","fileCount":9,"unpackedSize":24270,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDSb5aX0UTUx1PqRdPV0P6qaprKtdDwDDZOQ4NKhGthtAIhANO3GqBDwQBPhqBfQUB4RqfE57j7Ojz/SW01DK11saoR"}]},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"directories":{},"maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/execution-blocker_1.0.1_1786638433465_0.49703982845177497"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-25T16:12:47.679Z","modified":"2026-08-13T16:27:13.820Z","1.0.0":"2026-07-25T16:12:47.909Z","1.0.1":"2026-08-13T16:27:13.644Z"},"bugs":{"url":"https://github.com/dmytromykhailiuk/execution-blocker/issues"},"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","homepage":"https://dmytromykhailiuk.github.io/execution-blocker/","keywords":["mutex","lock","async-lock","queue","fifo","sequential","serialize","critical-section","concurrency","async","promise","typescript","zero-dependencies"],"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/execution-blocker.git"},"description":"Promise-based FIFO execution lock with independent queues per id — run async logic strictly one at a time. Zero dependencies.","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"readme":"# @dmytromykhailiuk/execution-blocker\n\nPromise-based FIFO execution lock with independent queues per id — run async logic strictly one\nat a time. Zero dependencies, works in any browser and in Node.\n\n> **Full documentation:** open [Docs](https://dmytromykhailiuk.github.io/execution-blocker/) in a\n> browser — every method, with examples, a table of contents and cross-links. This README is the\n> short form.\n\n> ⚠️ **The rule that makes it work:** a lock taken with `block()` **must** be released — call the\n> returned function in a `finally`, or every later caller of that queue waits forever. `run()` is\n> the safe counterpart: it acquires, executes and releases even when the task throws. Reach for\n> `block()` only when acquire and release genuinely live in different places.\n\nBuilt for the async logic that must not overlap: refreshing an auth token once instead of five\ntimes in parallel, serializing writes to a file or IndexedDB, keeping \"read, then update\" atomic,\ndraining actions against one resource in order. JavaScript won't interleave your *statements* —\nbut every `await` is a door for another caller to walk through. An execution blocker closes it:\ncallers of the same queue line up and run strictly one after another, in the order they arrived.\n\n## Install\n\n```sh\nnpm i @dmytromykhailiuk/execution-blocker\n```\n\n## Quick start\n\n```ts\nimport { createExecutionBlocker } from \"@dmytromykhailiuk/execution-blocker\";\n\nconst blocker = createExecutionBlocker();\n\n// Ten parallel calls — the body still runs strictly one at a time.\nconst refreshToken = () =>\n  blocker.run(\"auth\", async () => {\n    if (!isExpired(token)) return token;   // later callers see the fresh token\n    token = await api.refresh();           // executed once, not ten times\n    return token;\n  });\n```\n\n- `run(id, fn)` acquires the `id` queue, runs `fn`, and releases — even when `fn` throws. It\n  resolves with `fn`'s result.\n- Tasks on the **same id** run one after another, FIFO. Tasks on **different ids** don't wait for\n  each other.\n- Omit the id (`run(fn)`, `block()`) to use the shared `\"default\"` queue.\n\n## API\n\n```ts\nconst blocker = createExecutionBlocker();\n\nblocker.run(id?, fn);        // acquire → fn() → release; resolves with fn's result\nblocker.block(id?);          // resolves with release() once every earlier holder is done\nblocker.isLocked(id?);       // is anything holding or waiting for this queue?\nblocker.pending(id?);        // holders + waiters currently in this queue\n```\n\nEach `createExecutionBlocker()` call is an isolated world — two blockers never see each other's\nqueues. Create one per domain and share it via a module export.\n\n## `block()` — manual acquire / release\n\nFor the rare case where acquire and release live in different places (a stream that opens here\nand closes in a callback there):\n\n```ts\nconst release = await blocker.block(\"file:write\");\ntry {\n  await stream.write(chunk);\n} finally {\n  release(); // idempotent — a second call is a safe no-op\n}\n```\n\n`release` is idempotent, so calling it twice can't free the next waiter early. But **not** calling\nit deadlocks the queue — which is why `run()` is the default choice.\n\n## Independent queues\n\nThe id picks the queue, and only same-id callers line up:\n\n```ts\nblocker.run(\"user:42\", updateProfile);   // ┐ run one after another\nblocker.run(\"user:42\", updateSettings);  // ┘\nblocker.run(\"user:7\", updateProfile);    // runs immediately — different queue\n```\n\nIds are plain strings — build them from your domain: `` `user:${id}` ``, `` `file:${path}` ``.\nA queue that empties is deleted internally; there is no cleanup to do.\n\n## Error handling\n\nA task that throws does not poison the queue: `run()` releases the lock in a `finally`, rejects\nwith the original error, and the next task starts as usual.\n\n```ts\nawait blocker.run(\"q\", async () => { throw new Error(\"boom\"); }).catch(() => {});\nawait blocker.run(\"q\", async () => \"still works\"); // \"still works\"\n```\n\n## TypeScript\n\n```ts\nconst n = await blocker.run(\"q\", async () => 42);        // number\nconst s = await blocker.run(() => \"sync works too\");     // string\n\nconst release: Release = await blocker.block(\"q\");\n```\n\n`run()` infers its result type from the task; synchronous tasks are supported. The blocker object\nis frozen — its methods cannot be reassigned.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}