{"_id":"@glitchpad/throttler","_rev":"5-096979cd4520945cc0a9c98ad238d2b3","name":"@glitchpad/throttler","dist-tags":{"latest":"2.2.4"},"versions":{"2.1.1":{"name":"@glitchpad/throttler","version":"2.1.1","keywords":["throttle","debounce","rate-limit","esm","tiny"],"author":{"url":"https://github.com/glitchpad","name":"glitchpad","email":"glitchpad@pm.me"},"license":"MIT","_id":"@glitchpad/throttler@2.1.1","maintainers":[{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"}],"homepage":"https://github.com/glitchpad/throttler#readme","bugs":{"url":"https://github.com/glitchpad/throttler/issues"},"dist":{"shasum":"28a97fa0ecd7449fae0fc83ea830f8f1e42eb809","tarball":"https://registry.npmjs.org/@glitchpad/throttler/-/throttler-2.1.1.tgz","fileCount":18,"integrity":"sha512-07lSrlnTckSXOq8inRdNKlute/U6Xo6m5vWf/W2/BHvE4UkiG+EAQ4aAjdMqS5OmssL0He9x7M00cSBjWXwlpA==","signatures":[{"sig":"MEUCIQCU9DUfvPhUp8EFkBEvnylrPIEWyoffYkzqfM60Za0EJgIgHPAs2DnaSKxaVVcR6XXn997+tV3DffeSBt7RPHkYu5g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14721},"main":"dist/umd/index.js","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=14"},"gitHead":"e5a4fb2ba8662034007da9c85eccef0bfa90cf18","scripts":{"postinstall":"node dist/primer.js"},"_npmUser":{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"},"repository":{"url":"git+https://github.com/glitchpad/throttler.git","type":"git"},"_npmVersion":"11.11.0","description":"Tiny throttle and debounce with start/middle/once options","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/throttler_2.1.1_1780558382114_0.37088068649203954","host":"s3://npm-registry-packages-npm-production"}},"2.2.1":{"name":"@glitchpad/throttler","version":"2.2.1","keywords":["async","throttle","cancel","flush","dedup","deduplication","metrics","backpressure","retry","jitter","queue","concurrency","rate-limit","typescript"],"author":{"url":"https://github.com/glitchpad","name":"glitchpad","email":"glitchpad@pm.me"},"license":"MIT","_id":"@glitchpad/throttler@2.2.1","maintainers":[{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"}],"homepage":"https://github.com/glitchpad/throttler#readme","bugs":{"url":"https://github.com/glitchpad/throttler/issues"},"dist":{"shasum":"4d2ed8b8aef1c21f21df178845e57b99a3c56620","tarball":"https://registry.npmjs.org/@glitchpad/throttler/-/throttler-2.2.1.tgz","fileCount":9,"integrity":"sha512-spz5fVJwA8CZZQcPbpcSGAuLb7r91dywrG4nlej5OhThtrfB7pyr9Eul4XU4Se6ChSPQ1e/rcGkQdGVXy4RwYA==","signatures":[{"sig":"MEUCIQD1t2WVBlIQquOS/QWtxUHJl+IfJak+eo+ZTuQCuDv8kQIgNbflopISgTtlL9Ne8uwy96m7z5oStzeiaRCg8FQ0swM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":175562},"main":"./index.cjs","type":"module","types":"./index.d.ts","module":"./index.js","engines":{"node":">=14"},"exports":{".":{"import":{"types":"./index.d.ts","default":"./index.js"},"require":{"types":"./index.d.cts","default":"./index.cjs"}},"./package.json":"./package.json"},"gitHead":"b71e61e3dc8927aea53b5e8e458160b915a0a109","scripts":{"postinstall":"node ./primer.cjs"},"_npmUser":{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"},"repository":{"url":"git+https://github.com/glitchpad/throttler.git","type":"git"},"_npmVersion":"11.11.0","description":"Async throttle with cancel/flush, deduplication, backpressure strategies, retry/jitter, and per-task metrics. RxJS-friendly.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/throttler_2.2.1_1781415189872_0.19672052165278608","host":"s3://npm-registry-packages-npm-production"}},"2.2.2":{"name":"@glitchpad/throttler","version":"2.2.2","keywords":["async","throttle","cancel","flush","dedup","deduplication","metrics","backpressure","retry","jitter","queue","concurrency","rate-limit","typescript"],"author":{"url":"https://github.com/glitchpad","name":"glitchpad","email":"glitchpad@pm.me"},"license":"MIT","_id":"@glitchpad/throttler@2.2.2","maintainers":[{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"}],"homepage":"https://github.com/bsreeram08/experiments/tree/main/packages/async-throttle#readme","bugs":{"url":"https://github.com/bsreeram08/experiments/issues"},"dist":{"shasum":"b5677435f7c3712b08df9c18bac61efb779be566","tarball":"https://registry.npmjs.org/@glitchpad/throttler/-/throttler-2.2.2.tgz","fileCount":9,"integrity":"sha512-Jb1GvgZGkxS56q21SXazsD9+sYRPWVdwZ7F1+x2cNS8cHcmPjhbeioK7kCkzXKGhTnG6eeko+WK4RhzaHwnJYA==","signatures":[{"sig":"MEQCIEOwnOuRqUN93+Ux7Ve6GDyQzeeO/m4Vrd6hkoyaf6sqAiBogaVJjN43x9IGUgC+SZK3oLNjWe6Z7MP8HzUFjXmTww==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":175609},"main":"./index.cjs","type":"module","types":"./index.d.ts","module":"./index.js","engines":{"node":">=14"},"exports":{".":{"import":{"types":"./index.d.ts","default":"./index.js"},"require":{"types":"./index.d.cts","default":"./index.cjs"}},"./package.json":"./package.json"},"gitHead":"c864885188b5474cfc3ac609ca20896f1a4abf82","scripts":{"postinstall":"node ./primer.cjs"},"_npmUser":{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"},"repository":{"url":"git+https://github.com/bsreeram08/experiments.git","type":"git"},"_npmVersion":"11.11.0","description":"Async throttle with cancel/flush, deduplication, backpressure strategies, retry/jitter, and per-task metrics. RxJS-friendly.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/throttler_2.2.2_1781420295618_0.4857213270749965","host":"s3://npm-registry-packages-npm-production"}},"2.2.3":{"name":"@glitchpad/throttler","version":"2.2.3","keywords":["async","throttle","cancel","flush","dedup","deduplication","metrics","backpressure","retry","jitter","queue","concurrency","rate-limit","typescript"],"author":{"url":"https://github.com/glitchpad","name":"glitchpad","email":"glitchpad@pm.me"},"license":"MIT","_id":"@glitchpad/throttler@2.2.3","maintainers":[{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"}],"homepage":"https://github.com/bsreeram08/experiments/tree/main/packages/async-throttle#readme","bugs":{"url":"https://github.com/bsreeram08/experiments/issues"},"dist":{"shasum":"7ca52c3a0116f7739fbcf01cb86751f4bf916c06","tarball":"https://registry.npmjs.org/@glitchpad/throttler/-/throttler-2.2.3.tgz","fileCount":9,"integrity":"sha512-reZk26XZmYaYic4KJP+d27A4ag4Q8GSh2gmpKMZynhipAHNqQLhWbF9OtJtZFzs7NpwK6Njn/Np4O1c8UiSiZw==","signatures":[{"sig":"MEQCID7rX+vINDofx99iHQ4ck3GIwWiqIa4yH7IgE52JWCq2AiASPhtjLNmucp3fG2pWG/vgbfhSwZOflDMUqKAKUReUBw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":696652},"main":"./index.cjs","type":"module","types":"./index.d.ts","module":"./index.js","engines":{"node":">=14"},"exports":{".":{"import":{"types":"./index.d.ts","default":"./index.js"},"require":{"types":"./index.d.cts","default":"./index.cjs"}},"./package.json":"./package.json"},"gitHead":"dbf2fbe7804a692acbba55f7487c66cd1f27a7fe","scripts":{"postinstall":"node ./primer.cjs"},"_npmUser":{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"},"repository":{"url":"git+https://github.com/bsreeram08/experiments.git","type":"git"},"_npmVersion":"11.11.0","description":"Async throttle with cancel/flush, deduplication, backpressure strategies, retry/jitter, and per-task metrics. RxJS-friendly.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/throttler_2.2.3_1781765253197_0.04825196751114613","host":"s3://npm-registry-packages-npm-production"}},"2.2.4":{"name":"@glitchpad/throttler","version":"2.2.4","type":"module","description":"Async throttle with cancel/flush, deduplication, backpressure strategies, retry/jitter, and per-task metrics. RxJS-friendly.","license":"MIT","author":{"name":"glitchpad","email":"glitchpad@pm.me","url":"https://github.com/glitchpad"},"repository":{"type":"git","url":"git+https://github.com/bsreeram08/experiments.git"},"bugs":{"url":"https://github.com/bsreeram08/experiments/issues"},"homepage":"https://github.com/bsreeram08/experiments/tree/main/packages/async-throttle#readme","keywords":["async","throttle","cancel","flush","dedup","deduplication","metrics","backpressure","retry","jitter","queue","concurrency","rate-limit","typescript"],"exports":{".":{"import":{"types":"./index.d.ts","default":"./index.js"},"require":{"types":"./index.d.cts","default":"./index.cjs"}},"./package.json":"./package.json"},"main":"./index.cjs","module":"./index.js","types":"./index.d.ts","sideEffects":false,"engines":{"node":">=14"},"gitHead":"ae4b2cd180188d18baa3783b8ff867dbc7e15440","_id":"@glitchpad/throttler@2.2.4","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-edGm/f/5gIerS/SczFpSdZmP/kc2Wi2t32BTbzaVPLh0a5OINFPEju3J5df5lssPwSNrr6i99bjKbAb4M+3pFQ==","shasum":"81e74aa5926bbed9a77a74d66f571c30560aacad","tarball":"https://registry.npmjs.org/@glitchpad/throttler/-/throttler-2.2.4.tgz","fileCount":9,"unpackedSize":696593,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD0hkOcHFmQu926cHlCndXgXz91c7JLbhyQfMbpxZR9lwIgcXGY/3Gz6PkVKs2KbR3PZNma9e0nla5xVee+Bsjb9MU="}]},"_npmUser":{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"},"directories":{},"maintainers":[{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/throttler_2.2.4_1781770055010_0.03603490692643496"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-04T07:33:01.873Z","modified":"2026-06-18T08:07:35.304Z","2.1.1":"2026-06-04T07:33:02.252Z","2.2.1":"2026-06-14T05:33:10.009Z","2.2.2":"2026-06-14T06:58:15.784Z","2.2.3":"2026-06-18T06:47:33.353Z","2.2.4":"2026-06-18T08:07:35.190Z"},"bugs":{"url":"https://github.com/bsreeram08/experiments/issues"},"author":{"name":"glitchpad","email":"glitchpad@pm.me","url":"https://github.com/glitchpad"},"license":"MIT","homepage":"https://github.com/bsreeram08/experiments/tree/main/packages/async-throttle#readme","keywords":["async","throttle","cancel","flush","dedup","deduplication","metrics","backpressure","retry","jitter","queue","concurrency","rate-limit","typescript"],"repository":{"type":"git","url":"git+https://github.com/bsreeram08/experiments.git"},"description":"Async throttle with cancel/flush, deduplication, backpressure strategies, retry/jitter, and per-task metrics. RxJS-friendly.","maintainers":[{"name":"glitchpad","email":"glitchpad-revqgz@wshu.net"}],"readme":"# @glitchpad/throttler\n\nAsync throttle for promise-returning functions. Concurrency cap, priority queue, pause/resume, cancel, flush, deduplication, backpressure strategies, retry with configurable strategy + jitter, and per-task metrics with latency percentiles. Zero runtime deps. RxJS-friendly.\n\n## Install\n\n```bash\nnpm install @glitchpad/throttler\n```\n\n## Quick start\n\n```typescript\nimport { AsyncThrottle, TAsyncThrottleFunction } from '@glitchpad/throttler';\n\nconst throttle = new AsyncThrottle({\n    maxThreshold: 3,\n    delayExecutions: 1000,\n});\n\nconst task: TAsyncThrottleFunction<[number, number], string> = {\n    args: [1, 2000],\n    function: async (taskId: number, delay: number) => {\n        await new Promise((r) => setTimeout(r, delay));\n        return `Task ${taskId} completed`;\n    },\n};\n\nthrottle.addToQueue(task);\n```\n\n## Priority queue\n\nHigher numeric priority runs first. Default priority is `0`.\n\n```typescript\nthrottle.addToQueue(highPriorityTask, 'high-1', 10);\nthrottle.addToQueue(normalTask);\nthrottle.addToQueue(lowPriorityTask, 'low-1', -5);\n```\n\n## Pause / resume / stop\n\n```typescript\nthrottle.pause();\nif (throttle.isPaused) {\n    // ...\n}\nthrottle.resume();\n\nthrottle.clearQueue();\nthrottle.stop();\n```\n\n## Cancel + flush + drain\n\n```typescript\n// Abort everything in flight or queued\nthrottle.cancelAll('user navigated away');\n\n// Wait until the queue drains; throws if timeout exceeded\nawait throttle.flush(5000);\n\n// Stop accepting new work and wait for in-flight tasks\nawait throttle.drain(5000);\n```\n\n## Scheduling\n\n```typescript\n// Run at a specific Date\nawait throttle.scheduleAt(task, new Date(Date.now() + 60_000));\n\n// Run after N ms\nawait throttle.scheduleIn(task, 2500);\n\n// Batch insertion\nawait throttle.addMany([taskA, taskB, taskC], 'batch-1', 0);\n```\n\n## Backpressure\n\n```typescript\nconst throttle = new AsyncThrottle({\n    maxThreshold: 4,\n    delayExecutions: 100,\n    maxQueueSize: 100,\n    backpressureStrategy: 'reject', // 'reject' | 'drop-oldest' | 'block'\n});\n```\n\nWhen the queue is at capacity, `reject` throws `QueueFullError`, `drop-oldest` evicts the head, and `block` is reserved for explicit handling on the caller side.\n\n## Deduplication\n\nUse the default hash (`function.name + JSON.stringify(args)`) or supply your own.\n\n```typescript\nconst throttle = new AsyncThrottle({\n    maxThreshold: 4,\n    delayExecutions: 100,\n    enableDeduplication: true,\n    deduplicationHashFn: (task) => `${task.function.name}:${task.args[0]}`,\n});\n```\n\nA duplicate `addToQueue` call resolves immediately with `success: false` and `error: Error('Duplicate task')`.\n\n## Retry, strategies, jitter\n\nConfigure retry on the throttle and override per task:\n\n```typescript\nconst throttle = new AsyncThrottle({\n    maxThreshold: 4,\n    delayExecutions: 100,\n    retry: {\n        attempts: 5,\n        delay: 500,\n        strategy: 'exponential', // 'exponential' | 'linear' | 'fibonacci' | 'fixed'\n        jitter: 'full',          // 'full' | 'equal' | 'decorrelated' | 'none'\n        maxDelay: 30_000,\n    },\n});\n\nthrottle.addToQueue(task, 'override-1', 0, {\n    retryAttempts: 2,\n    retryDelay: 250,\n    retryStrategy: 'linear',\n    jitterStrategy: 'equal',\n    maxRetryDelay: 5_000,\n});\n```\n\n## Per-task timeout\n\n```typescript\nconst throttle = new AsyncThrottle({\n    maxThreshold: 4,\n    delayExecutions: 100,\n    defaultTimeout: 10_000,\n});\n\nthrottle.addToQueue(task, 'with-timeout', 0, { timeout: 2_000 });\n```\n\nA timed-out task throws `TimeoutError` and emits `taskTimeout`. Cancelled tasks throw `TaskCancelledError` and emit `taskCancelled`.\n\n## AbortSignal\n\n```typescript\nconst controller = new AbortController();\nthrottle.addToQueue(task, 'abortable', 0, { signal: controller.signal });\n\ncontroller.abort('component unmounted');\n```\n\nThe throttle also exposes its global signal via `throttle.signal` and `throttle.cancelAll(reason)`.\n\n## Metrics\n\n```typescript\nconst throttle = new AsyncThrottle({\n    maxThreshold: 4,\n    delayExecutions: 100,\n    metricsConfig: { buckets: [10, 50, 100, 250, 500, 1000, 5000] },\n});\n\nconst m = throttle.getMetrics();\n// {\n//   totalTasks, queued, active, succeeded, failed, cancelled, timedOut,\n//   avgLatency, p50, p95, p99, latencyHistogram\n// }\n\nthrottle.resetMetrics();\n```\n\n## Events\n\n```typescript\nthrottle.on('result',         (r) => { /* { id, value, success, retries, duration, metadata } */ });\nthrottle.on('resultError',    (r) => { /* { id, error, success: false, retries, duration, metadata } */ });\nthrottle.on('taskTimeout',    ({ id, timeout }) => {});\nthrottle.on('taskCancelled',  ({ id, reason }) => {});\nthrottle.on('empty',          () => {});\nthrottle.on('add',            () => {});\nthrottle.on('pause',          () => {});\nthrottle.on('resume',         () => {});\nthrottle.on('stop',           () => {});\nthrottle.on('clear',          () => {});\nthrottle.on('log',            (msg) => {});\n\n// Cleanup\nthrottle.off('result', handler);\nthrottle.removeAllListeners('result');\nthrottle.removeAllListeners();\n```\n\n## API\n\n### Constructor\n\n```typescript\nnew AsyncThrottle({\n    maxThreshold: number;\n    delayExecutions: number;\n    maxQueueSize?: number;\n    backpressureStrategy?: 'reject' | 'drop-oldest' | 'block';\n    defaultTimeout?: number;\n    enableDeduplication?: boolean;\n    deduplicationHashFn?: (task) => string;\n    retry?: {\n        attempts: number;\n        delay: number;\n        strategy?: 'exponential' | 'linear' | 'fibonacci' | 'fixed';\n        jitter?: 'full' | 'equal' | 'decorrelated' | 'none';\n        maxDelay?: number;\n    };\n    metricsConfig?: { buckets?: number[] };\n});\n```\n\n### Methods\n\n- `addToQueue(task, id?, priority?, options?)`\n- `addMany(tasks, idPrefix?, priority?, options?)`\n- `scheduleAt(task, date, id?, priority?, options?)`\n- `scheduleIn(task, delayMs, id?, priority?, options?)`\n- `clearQueue()`\n- `flush(timeoutMs?)`\n- `drain(timeoutMs?)`\n- `cancelAll(reason?)`\n- `pause()` / `resume()` / `stop()`\n- `getMetrics()` / `resetMetrics()`\n- `on(evt, fn)` / `off(evt, fn)` / `removeAllListeners(evt?)`\n\n### Errors\n\n- `TimeoutError`\n- `TaskCancelledError`\n- `QueueFullError`\n\n### Properties\n\n- `currentStatus` — `{ queueSize, currentlyQueued, maxThreshold }`\n- `isPaused`\n- `signal` — global `AbortSignal`\n\n## Error handling\n\n```typescript\nthrottle.on('resultError', ({ id, error }) => {\n    console.error(`task ${id} failed`, error);\n});\n\ntry {\n    throttle.addToQueue(task);\n} catch (err) {\n    // Synchronous backpressure rejection lands here\n}\n```\n\n## Patterns\n\n### Rate-limited API calls\n\n```typescript\nimport { AsyncThrottle, TAsyncThrottleFunction } from '@glitchpad/throttler';\n\nconst api = new AsyncThrottle({\n    maxThreshold: 5,\n    delayExecutions: 1000,\n    retry: { attempts: 3, delay: 500, strategy: 'exponential', jitter: 'full' },\n    defaultTimeout: 8_000,\n});\n\nconst fetchUser = async (id: number) => {\n    const res = await fetch(`https://api.example.com/users/${id}`);\n    if (!res.ok) throw new Error(`HTTP ${res.status}`);\n    return res.json();\n};\n\nfor (let i = 1; i <= 20; i++) {\n    const task: TAsyncThrottleFunction<[number], unknown> = {\n        args: [i],\n        function: fetchUser,\n    };\n    api.addToQueue(task, `user-${i}`);\n}\n\napi.on('result',      (r) => console.log('ok', r.id, r.duration));\napi.on('resultError', (r) => console.error('fail', r.id, r.error));\napi.on('empty',       () => api.stop());\n```\n\n### Stale-resource pattern\n\nWhen working with database connections or any resource that may not be ready at enqueue time, close over it inside `function`, do not pass it through `args`:\n\n```typescript\nconst task: TAsyncThrottleFunction<[], void> = {\n    args: [],\n    function: async () => {\n        const conn = await getConnection();\n        await runQuery(conn);\n    },\n};\n```\n\nThis evaluates the resource at execution time, not at enqueue time.\n\n## TypeScript\n\nType definitions ship in the package. `.d.ts` for ESM consumers and `.d.cts` for CommonJS consumers.\n\n## Runtime\n\nDesigned for Node.js 14+. Works in modern browsers that support `AbortController` and `AbortSignal.any` (or via polyfill). Bundle with your tool of choice.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}