flowx-control 1.0.3 → 1.0.5

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +54 -324
  3. package/package.json +3 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to **flowx-control** will be documented here.
4
+
5
+ ---
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Planned
10
+ - Actor model support
11
+ - Distributed rate limiting
12
+
13
+ ---
14
+
15
+ ## [1.0.0] - 2026-03-31
16
+
17
+ ### Added
18
+ - Initial release
19
+ - Retry with backoff, Circuit Breaker, Bulkhead
20
+ - Rate Limiter, Priority Queue, Semaphore, Mutex
21
+ - Debounce, Throttle, Timeout, Hedge, Poll, Batch, Pipeline
22
+ - 100% test coverage, tree-shakable
package/README.md CHANGED
@@ -32,13 +32,7 @@
32
32
 
33
33
  ```bash
34
34
  npm install flowx-control
35
- ```
36
-
37
- ```bash
38
35
  yarn add flowx-control
39
- ```
40
-
41
- ```bash
42
36
  pnpm add flowx-control
43
37
  ```
44
38
 
@@ -56,19 +50,19 @@ const data = await retry(() => fetch('/api/data'), {
56
50
  backoff: 'exponential',
57
51
  });
58
52
 
59
- // Circuit breaker — stop cascading failures
53
+ // Circuit breaker
60
54
  const breaker = createCircuitBreaker(fetchUser, {
61
55
  failureThreshold: 5,
62
56
  resetTimeout: 30_000,
63
57
  });
64
58
  const user = await breaker.fire(userId);
65
59
 
66
- // Timeout — never wait forever
60
+ // Timeout
67
61
  const result = await withTimeout(() => fetch('/slow'), 5000, {
68
62
  fallback: () => cachedResponse,
69
63
  });
70
64
 
71
- // Rate limiter — respect API limits
65
+ // Rate limiter
72
66
  const limiter = createRateLimiter({ limit: 10, interval: 1000 });
73
67
  await limiter.execute(() => callExternalApi());
74
68
  ```
@@ -79,318 +73,37 @@ await limiter.execute(() => callExternalApi());
79
73
 
80
74
  ### 🛡️ Resilience
81
75
 
82
- <details>
83
- <summary><strong>retry</strong>Retry with backoff & jitter</summary>
84
-
85
- ```ts
86
- import { retry } from 'flowx-control/retry';
87
-
88
- const data = await retry(() => fetch('/api'), {
89
- maxAttempts: 5,
90
- delay: 1000,
91
- backoff: 'exponential', // 'fixed' | 'linear' | 'exponential' | custom fn
92
- jitter: true,
93
- retryIf: (err) => err.status !== 404,
94
- onRetry: (err, attempt) => console.log(`Attempt ${attempt}`),
95
- signal: abortController.signal,
96
- });
97
- ```
98
- </details>
99
-
100
- <details>
101
- <summary><strong>circuitBreaker</strong> — Stop cascading failures</summary>
102
-
103
- ```ts
104
- import { createCircuitBreaker } from 'flowx-control/circuit-breaker';
105
-
106
- const breaker = createCircuitBreaker(callApi, {
107
- failureThreshold: 5,
108
- resetTimeout: 30000,
109
- halfOpenLimit: 1,
110
- successThreshold: 2,
111
- shouldTrip: (err) => err.status >= 500,
112
- onStateChange: (from, to) => log(`${from} → ${to}`),
113
- });
114
-
115
- const result = await breaker.fire(args);
116
- console.log(breaker.state); // 'closed' | 'open' | 'half-open'
117
- console.log(breaker.failureCount);
118
- breaker.reset();
119
- ```
120
- </details>
121
-
122
- <details>
123
- <summary><strong>fallback</strong> — Graceful degradation</summary>
124
-
125
- ```ts
126
- import { withFallback, fallbackChain } from 'flowx-control/fallback';
127
-
128
- const data = await withFallback(
129
- () => fetchFromPrimary(),
130
- 'default-value',
131
- { onFallback: (err, idx) => console.warn(err) }
132
- );
133
-
134
- const result = await fallbackChain([
135
- () => fetchFromPrimary(),
136
- () => fetchFromCache(),
137
- () => fetchFromFallback(),
138
- ]);
139
- ```
140
- </details>
141
-
142
- <details>
143
- <summary><strong>timeout</strong> — Never wait forever</summary>
144
-
145
- ```ts
146
- import { withTimeout } from 'flowx-control/timeout';
147
-
148
- const result = await withTimeout(() => fetch('/slow-api'), 5000, {
149
- fallback: () => cachedData,
150
- message: 'API took too long',
151
- signal: controller.signal,
152
- });
153
- ```
154
- </details>
76
+ - **retry** — Exponential backoff, jitter, abort signal, custom retry predicates
77
+ - **circuitBreaker** Closed/Open/Half-open state machine, trip hooks
78
+ - **fallback** — Graceful degradation with fallback chains
79
+ - **timeout** — Hard deadline + optional fallback value
155
80
 
156
81
  ### 🚦 Concurrency
157
82
 
158
- <details>
159
- <summary><strong>bulkhead</strong>Isolate concurrent operations</summary>
160
-
161
- ```ts
162
- import { createBulkhead } from 'flowx-control/bulkhead';
163
-
164
- const bulkhead = createBulkhead({
165
- maxConcurrent: 10,
166
- maxQueue: 100,
167
- queueTimeout: 5000,
168
- });
169
-
170
- const result = await bulkhead.execute(() => processRequest());
171
- console.log(bulkhead.activeCount, bulkhead.queueSize);
172
- ```
173
- </details>
174
-
175
- <details>
176
- <summary><strong>queue</strong> — Priority async task queue</summary>
177
-
178
- ```ts
179
- import { createQueue } from 'flowx-control/queue';
180
-
181
- const queue = createQueue({ concurrency: 5, timeout: 10000 });
182
-
183
- const result = await queue.add(() => processJob(), { priority: 1 });
184
- const results = await queue.addAll(tasks.map(t => () => process(t)));
185
-
186
- await queue.onIdle(); // wait until all done
187
- queue.pause();
188
- queue.resume();
189
- ```
190
- </details>
191
-
192
- <details>
193
- <summary><strong>semaphore</strong> — Counting resource lock</summary>
194
-
195
- ```ts
196
- import { createSemaphore } from 'flowx-control/semaphore';
197
-
198
- const sem = createSemaphore(3); // max 3 concurrent
199
- const release = await sem.acquire();
200
- try {
201
- await doWork();
202
- } finally {
203
- release();
204
- }
205
- ```
206
- </details>
207
-
208
- <details>
209
- <summary><strong>mutex</strong> — Mutual exclusion lock</summary>
210
-
211
- ```ts
212
- import { createMutex } from 'flowx-control/mutex';
213
-
214
- const mutex = createMutex();
215
- const release = await mutex.acquire();
216
- try {
217
- await criticalSection();
218
- } finally {
219
- release();
220
- }
221
- ```
222
- </details>
83
+ - **bulkhead** — Max concurrent + max queue isolation
84
+ - **queue** Priority async task queue with concurrency
85
+ - **semaphore** — Counting resource lock (acquire/release)
86
+ - **mutex** — Mutual exclusion for critical sections
223
87
 
224
88
  ### 🎛️ Flow Control
225
89
 
226
- <details>
227
- <summary><strong>rateLimit</strong>Token bucket rate limiting</summary>
228
-
229
- ```ts
230
- import { createRateLimiter } from 'flowx-control/rate-limit';
231
-
232
- const limiter = createRateLimiter({
233
- limit: 100,
234
- interval: 60_000,
235
- strategy: 'queue', // 'queue' | 'reject'
236
- });
237
-
238
- await limiter.execute(() => callApi());
239
- console.log(limiter.remaining);
240
- limiter.reset();
241
- ```
242
- </details>
243
-
244
- <details>
245
- <summary><strong>throttle</strong> — Rate-limit function calls</summary>
246
-
247
- ```ts
248
- import { throttle } from 'flowx-control/throttle';
249
-
250
- const save = throttle(saveToDb, 1000, {
251
- leading: true,
252
- trailing: true,
253
- });
254
-
255
- await save(data);
256
- save.cancel();
257
- ```
258
- </details>
259
-
260
- <details>
261
- <summary><strong>debounce</strong> — Delay until activity pauses</summary>
262
-
263
- ```ts
264
- import { debounce } from 'flowx-control/debounce';
265
-
266
- const search = debounce(searchApi, 300, {
267
- leading: false,
268
- trailing: true,
269
- maxWait: 1000,
270
- });
271
-
272
- await search(query);
273
- await search.flush();
274
- search.cancel();
275
- ```
276
- </details>
277
-
278
- <details>
279
- <summary><strong>batch</strong> — Process collections in chunks</summary>
280
-
281
- ```ts
282
- import { batch } from 'flowx-control/batch';
283
-
284
- const result = await batch(urls, async (url, i) => {
285
- return fetch(url).then(r => r.json());
286
- }, {
287
- batchSize: 10,
288
- concurrency: 3,
289
- onProgress: (done, total) => console.log(`${done}/${total}`),
290
- signal: controller.signal,
291
- });
292
-
293
- console.log(result.succeeded, result.failed, result.errors);
294
- ```
295
- </details>
296
-
297
- <details>
298
- <summary><strong>pipeline</strong> — Compose async operations</summary>
299
-
300
- ```ts
301
- import { pipeline, pipe } from 'flowx-control/pipeline';
302
-
303
- const transform = pipe(
304
- (input: string) => input.trim(),
305
- (str) => str.toUpperCase(),
306
- async (str) => await translate(str),
307
- );
308
-
309
- const result = await transform(' hello world ');
310
- ```
311
- </details>
90
+ - **rateLimit** — Token bucket with queue/reject strategies
91
+ - **throttle** Leading/trailing edge, cancellable
92
+ - **debounce** — maxWait support, flush/cancel
93
+ - **batch** — Process collections in chunks with progress
94
+ - **pipeline** Compose sync/async operations
312
95
 
313
96
  ### 🛠️ Utilities
314
97
 
315
- <details>
316
- <summary><strong>poll</strong>Repeated polling with backoff</summary>
317
-
318
- ```ts
319
- import { poll } from 'flowx-control/poll';
320
-
321
- const { result, stop } = poll(() => checkJobStatus(jobId), {
322
- interval: 2000,
323
- until: (status) => status === 'complete',
324
- maxAttempts: 30,
325
- backoff: 'exponential',
326
- signal: controller.signal,
327
- });
328
-
329
- const finalStatus = await result;
330
- ```
331
- </details>
332
-
333
- <details>
334
- <summary><strong>hedge</strong> — Hedged/speculative requests</summary>
335
-
336
- ```ts
337
- import { hedge } from 'flowx-control/hedge';
338
-
339
- // If primary doesn't respond in 200ms, fire a parallel request
340
- const data = await hedge(() => fetch('/api'), {
341
- delay: 200,
342
- maxHedges: 2,
343
- });
344
- ```
345
- </details>
346
-
347
- <details>
348
- <summary><strong>memo</strong> — Async memoization with TTL</summary>
349
-
350
- ```ts
351
- import { memo } from 'flowx-control/memo';
352
-
353
- const cachedFetch = memo(fetchUserById, {
354
- ttl: 60_000,
355
- maxSize: 1000,
356
- key: (id) => `user:${id}`,
357
- });
358
-
359
- const user = await cachedFetch(123);
360
- cachedFetch.clear();
361
- ```
362
- </details>
363
-
364
- <details>
365
- <summary><strong>deferred</strong> — Externally resolvable promise</summary>
366
-
367
- ```ts
368
- import { deferred } from 'flowx-control/deferred';
369
-
370
- const d = deferred<string>();
371
- setTimeout(() => d.resolve('hello'), 1000);
372
- const value = await d.promise; // 'hello'
373
- ```
374
- </details>
375
-
376
- ---
377
-
378
- ## Deep Imports (Tree-shaking)
379
-
380
- Import only what you need — zero unused code in your bundle:
381
-
382
- ```ts
383
- // Only pulls in ~2KB instead of the full 28KB
384
- import { retry } from 'flowx-control/retry';
385
- import { createQueue } from 'flowx-control/queue';
386
- ```
98
+ - **poll** — Repeated polling with backoff until condition
99
+ - **hedge** Speculative parallel requests
100
+ - **memo** — Async memoization with TTL + max size
101
+ - **deferred** — Externally resolvable promise
387
102
 
388
103
  ---
389
104
 
390
105
  ## Error Hierarchy
391
106
 
392
- All errors extend `FlowXError` with a machine-readable `code`:
393
-
394
107
  | Error | Code | Thrown by |
395
108
  |-------|------|----------|
396
109
  | `TimeoutError` | `ERR_TIMEOUT` | `withTimeout` |
@@ -399,18 +112,6 @@ All errors extend `FlowXError` with a machine-readable `code`:
399
112
  | `AbortError` | `ERR_ABORTED` | `poll`, `batch`, `timeout` |
400
113
  | `RateLimitError` | `ERR_RATE_LIMIT` | `rateLimit` |
401
114
 
402
- ```ts
403
- import { TimeoutError, FlowXError } from 'flowx-control';
404
-
405
- try {
406
- await withTimeout(fn, 1000);
407
- } catch (err) {
408
- if (err instanceof TimeoutError) {
409
- console.log(err.code); // 'ERR_TIMEOUT'
410
- }
411
- }
412
- ```
413
-
414
115
  ---
415
116
 
416
117
  ## Compatibility
@@ -429,11 +130,9 @@ try {
429
130
 
430
131
  ```bash
431
132
  git clone https://github.com/Avinashvelu03/flowx-control.git
432
- cd flowx-control
433
- npm install
434
- npm test # Run tests with 100% coverage
435
- npm run lint # ESLint
436
- npm run build # Build ESM + CJS + DTS
133
+ cd flowx-control && npm install
134
+ npm test
135
+ npm run build
437
136
  ```
438
137
 
439
138
  ---
@@ -441,3 +140,34 @@ npm run build # Build ESM + CJS + DTS
441
140
  ## License
442
141
 
443
142
  MIT © [Avinash](https://github.com/Avinashvelu03)
143
+
144
+ ---
145
+
146
+ ## ⚡ Fuel the Flow
147
+
148
+ <div align="center">
149
+
150
+ ```
151
+ · · · · · · · · · · · · · · · · · · · · · · · ·
152
+ · ·
153
+ · FlowX handles your retries, ·
154
+ · your circuit breakers, ·
155
+ · your race conditions, ·
156
+ · and your 3 AM production fires. ·
157
+ · ·
158
+ · If it earned your trust — fuel it. ·
159
+ · ·
160
+ · · · · · · · · · · · · · · · · · · · · · · · ·
161
+ ```
162
+
163
+ [![Ko-fi](https://img.shields.io/badge/☕_Ko--fi-Fuel_the_Flow-FF5E5B?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/avinashvelu)
164
+ [![GitHub Sponsors](https://img.shields.io/badge/⚡_Sponsor-Power_Up-EA4AAA?style=for-the-badge&logo=github&logoColor=white)](https://github.com/sponsors/Avinashvelu03)
165
+
166
+ **No budget? No problem:**
167
+ - ⭐ [Star FlowX](https://github.com/Avinashvelu03/flowx-control) — boosts discovery
168
+ - 🐛 [Open an issue](https://github.com/Avinashvelu03/flowx-control/issues) — shape the roadmap
169
+ - 🗣️ Tell a dev who ships async code
170
+
171
+ *Built solo, shipped free — by [Avinash Velu](https://github.com/Avinashvelu03)*
172
+
173
+ </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowx-control",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Production-grade, zero-dependency TypeScript resilience & async flow control library. 100% test coverage. Retry with backoff, Circuit Breaker, Bulkhead, Rate Limiter, Priority Queue, Semaphore, Mutex, Debounce, Throttle, Timeout, Hedge, Poll, Batch, Pipeline — all tree-shakable & composable.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -108,8 +108,9 @@
108
108
  "test": "jest --coverage",
109
109
  "test:ci": "jest --coverage",
110
110
  "lint": "eslint src/ --ext .ts",
111
+ "typecheck": "tsc --noEmit",
111
112
  "format": "prettier --write \"src/**/*.ts\" \"__tests__/**/*.ts\"",
112
- "typecheck": "tsc --noEmit"
113
+ "prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build"
113
114
  },
114
115
  "keywords": [
115
116
  "async",