{"_id":"@andrew-nazin/single-flight","_rev":"4-5e0f6f525d85942854532865c9ed4676","name":"@andrew-nazin/single-flight","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@andrew-nazin/single-flight","version":"1.0.0","keywords":["single-flight","singleflight","promise","deduplication","request-coalescing","async","typescript","cache","cooldown"],"author":{"name":"Andrew Nazin"},"license":"MIT","_id":"@andrew-nazin/single-flight@1.0.0","maintainers":[{"name":"andrew-nazin","email":"mysquab@hotmail.com"}],"homepage":"https://github.com/andrew-nazin/single-flight#readme","bugs":{"url":"https://github.com/andrew-nazin/single-flight/issues"},"dist":{"shasum":"0b19d30165d78ec09de12e714cf139b92cee761b","tarball":"https://registry.npmjs.org/@andrew-nazin/single-flight/-/single-flight-1.0.0.tgz","fileCount":12,"integrity":"sha512-FGMLkUh16uYpL5CoFIKB/pFDCNlbKnNkOjpkDDzKtHi2AVxKskBfpzRsByCvBjXIQCZoqpDSCIQm1xM5ucj0AA==","signatures":[{"sig":"MEQCIDlBFipFADnlSprqdht+u+p55IqPdeFYoYlnwlnz5mm7AiBKAywU+NtExnFjPk1xtmCazVNXgKv2GsPBVCNofAsDiA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":26561},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"3de9547368709673822783d2d7ccae6289fa1a78","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","check":"npm run build && npm run test","clean":"rimraf dist coverage","pack:check":"npm pack --dry-run","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run clean && npm run check"},"_npmUser":{"name":"andrew-nazin","email":"mysquab@hotmail.com"},"repository":{"url":"git+https://github.com/andrew-nazin/single-flight.git","type":"git"},"_npmVersion":"11.12.1","description":"TypeScript single-flight implementation for deduplicating concurrent async operations by key.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","_hasShrinkwrap":false,"devDependencies":{"rimraf":"^6.1.3","vitest":"^4.1.6","typescript":"^5.4.5","@types/node":"^20.11.30","@vitest/coverage-v8":"^4.1.6"},"_npmOperationalInternal":{"tmp":"tmp/single-flight_1.0.0_1779189503423_0.44770347918778985","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@andrew-nazin/single-flight","version":"1.0.1","description":"TypeScript single-flight implementation for deduplicating concurrent async operations by key.","keywords":["single-flight","singleflight","promise","deduplication","request-coalescing","async","typescript","cache","cooldown"],"homepage":"https://github.com/andrew-nazin/single-flight#readme","bugs":{"url":"https://github.com/andrew-nazin/single-flight/issues"},"repository":{"type":"git","url":"git+https://github.com/andrew-nazin/single-flight.git"},"license":"MIT","author":{"name":"Andrew Nazin"},"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"sideEffects":false,"scripts":{"clean":"rimraf dist coverage","build":"tsc -p tsconfig.json","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","check":"npm run build && npm run test","pack:check":"npm pack --dry-run","prepublishOnly":"npm run clean && npm run check"},"devDependencies":{"@types/node":"^20.11.30","@vitest/coverage-v8":"^4.1.6","rimraf":"^6.1.3","typescript":"^5.4.5","vitest":"^4.1.6"},"engines":{"node":">=18"},"gitHead":"b064607fe0c2e43054a0d2c0a5bffd614e535b1f","_id":"@andrew-nazin/single-flight@1.0.1","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-9yu2VxDsGRDNqdrDp+or08j8rlyW0U0OSKXPOl53fZ4Sceue/6k1CotmApcnYXnFZZ75YuAo29eiGRTGfoNVcg==","shasum":"0ecdd09b4a3f651b3ece28427ae6c99ecd64eac9","tarball":"https://registry.npmjs.org/@andrew-nazin/single-flight/-/single-flight-1.0.1.tgz","fileCount":12,"unpackedSize":27173,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIF7RVL5RFiJLFXBfQkUGiJyryAlL5MFmOdXYpt+qk7vwAiAWtT+s5cLLzaZTR0eumQ4aeT16T6WelkbqTrKCXkul5w=="}]},"_npmUser":{"name":"andrew-nazin","email":"mysquab@hotmail.com"},"directories":{},"maintainers":[{"name":"andrew-nazin","email":"mysquab@hotmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/single-flight_1.0.1_1779996850801_0.36278997455110407"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-19T11:18:23.340Z","modified":"2026-05-28T19:34:11.047Z","0.1.0":"2026-05-19T10:57:57.735Z","1.0.0":"2026-05-19T11:18:23.572Z","1.0.1":"2026-05-28T19:34:10.954Z"},"bugs":{"url":"https://github.com/andrew-nazin/single-flight/issues"},"author":{"name":"Andrew Nazin"},"license":"MIT","homepage":"https://github.com/andrew-nazin/single-flight#readme","keywords":["single-flight","singleflight","promise","deduplication","request-coalescing","async","typescript","cache","cooldown"],"repository":{"type":"git","url":"git+https://github.com/andrew-nazin/single-flight.git"},"description":"TypeScript single-flight implementation for deduplicating concurrent async operations by key.","maintainers":[{"name":"andrew-nazin","email":"mysquab@hotmail.com"}],"readme":"# single-flight\n\nTypeScript single-flight implementation for deduplicating concurrent asynchronous operations by key.\n\n`single-flight` prevents duplicate in-flight async operations. If multiple calls with the same key happen at the same time, only one underlying operation is executed and all callers receive the same Promise result.\n\n## Features\n\n- Deduplicates concurrent async operations by key\n- Supports string keys and composite string-array keys\n- Shares the same in-flight Promise between concurrent callers\n- Supports error cooldown\n- Supports custom error normalization\n- Supports retry decision logic\n- Supports success and error lifecycle hooks\n- Written in TypeScript\n- Ships with TypeScript declarations\n- No runtime dependencies\n\n## Installation\n\n```bash\nnpm i @andrew-nazin/single-flight\n```\n\n## Usage\n\n```typescript\nimport { SingleFlight } from \"@andrew-nazin/single-flight\";\n\nconst singleFlight = new SingleFlight();\n\nconst result = await singleFlight.exec(\"user:1\", async () => { return \"user-data\"; });\n\nconsole.log(result);\n\nsingleFlight.dispose();\n```\n\n## Deduplicating concurrent calls\n\n```typescript\nimport { SingleFlight } from \"@andrew-nazin/single-flight\";\n\nconst singleFlight = new SingleFlight();\nlet calls = 0;\n\nconst operation = async () => {\n    calls += 1;\n    await new Promise((resolve) => setTimeout(resolve, 100));\n    return \"result\";\n};\n\nconst promise1 = singleFlight.exec('my-key', operation);\nconst promise2 = singleFlight.exec('my-key', operation);\nconst promise3 = singleFlight.exec('my-key', operation);\n\nconst [first, second, third] = await Promise.all([promise1, promise2, promise3]);\n\nconsole.log(first);  // \"result\"\nconsole.log(second); // \"result\"\nconsole.log(third);  // \"result\"\nconsole.log(calls);  // 1 ← the operation was performed only once!\n\nsingleFlight.dispose();\n```\n\nOnly one underlying operation is executed while all concurrent callers receive the same result.\n\n## Error cooldown\n\n```typescript\nimport { SingleFlight } from \"@andrew-nazin/single-flight\";\n\nconst singleFlight = new SingleFlight({ cooldownMs: 5000 });\n\ntry {\n    await singleFlight.exec(\"resource\", async () => { throw new Error(\"Temporary failure\"); });\n} catch (error) {\n    console.error(error.message);\n}\n\ntry {\n    const result = await singleFlight.exec(\"resource\", async () => { return \"this will not run during cooldown\"; });\n    console.log(result);\n} catch (error) {\n    console.error(error.message);\n}\n\nsingleFlight.dispose();\n```\n\n## API\n\n### `SingleFlight<TValue, TError>`\n\nMain class used to deduplicate concurrent async operations.\n\n```typescript\nconst singleFlight = new SingleFlight<TValue, TError>(options);\n```\n\n### `exec(key, operation, options?)`\n\nExecutes an async operation with single-flight deduplication.\n\n```typescript\nexec( key: Key, operation: () => Promise , options?: SingleFlightOptions ): Promise\n```\n\n### `dispose()`\n\nStops the internal cleanup timer.\n\n```typescript\nsingleFlight.dispose();\n```\n\nCall this method when the instance is no longer needed, especially in tests, CLI tools, or short-lived scripts.\n\n## Types\n\n```typescript\ntype Key = string | string[];\n\ninterface SingleFlightOptions<TValue, TError extends Error = Error> { \n    cooldownMs?: number; \n    normalizeError?: (error: unknown) => TError; \n    shouldRetry?: (error: TError) => boolean; \n    onSuccess?: (value: TValue) => void | Promise ; \n    onError?: (error: TError) => void | Promise; \n}\n\ninterface SingleFlightState<TValue, TError extends Error = Error> { \n    inFlight: Promise| null; \n    lastError: TError | null; \n    errorCacheExpiresAt: number; \n}\n```\n\n## Development\n\nInstall dependencies:\n\n```bash\nnpm install\n```\n\nRun tests:\n\n```bash\nnpm test\n```\n\nRun tests with coverage:\n\n```bash\nnpm run test:coverage\n```\n\nBuild package:\n\n```bash\nnpm run build\n```\n\nCheck package contents before publishing:\n\n```bash\nnpm run pack:check\n```\n\n## License\n\nMIT","readmeFilename":"README.md"}