flowx-control 1.0.4 → 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 -274
  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,267 +73,37 @@ await limiter.execute(() => callExternalApi());
79
73
 
80
74
  ### 🛡️ Resilience
81
75
 
82
- #### retry — Retry with backoff & jitter
83
-
84
- ```ts
85
- import { retry } from 'flowx-control/retry';
86
-
87
- const data = await retry(() => fetch('/api'), {
88
- maxAttempts: 5,
89
- delay: 1000,
90
- backoff: 'exponential', // 'fixed' | 'linear' | 'exponential' | custom fn
91
- jitter: true,
92
- retryIf: (err) => err.status !== 404,
93
- onRetry: (err, attempt) => console.log(`Attempt ${attempt}`),
94
- signal: abortController.signal,
95
- });
96
- ```
97
- #### circuitBreaker — Stop cascading failures
98
-
99
- ```ts
100
- import { createCircuitBreaker } from 'flowx-control/circuit-breaker';
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
101
80
 
102
- const breaker = createCircuitBreaker(callApi, {
103
- failureThreshold: 5,
104
- resetTimeout: 30000,
105
- halfOpenLimit: 1,
106
- successThreshold: 2,
107
- shouldTrip: (err) => err.status >= 500,
108
- onStateChange: (from, to) => log(`${from} → ${to}`),
109
- });
110
-
111
- const result = await breaker.fire(args);
112
- console.log(breaker.state); // 'closed' | 'open' | 'half-open'
113
- console.log(breaker.failureCount);
114
- breaker.reset();
115
- ```
116
- #### fallback — Graceful degradation
117
-
118
- ```ts
119
- import { withFallback, fallbackChain } from 'flowx-control/fallback';
120
-
121
- const data = await withFallback(
122
- () => fetchFromPrimary(),
123
- 'default-value',
124
- { onFallback: (err, idx) => console.warn(err) }
125
- );
126
-
127
- const result = await fallbackChain([
128
- () => fetchFromPrimary(),
129
- () => fetchFromCache(),
130
- () => fetchFromFallback(),
131
- ]);
132
- ```
133
- #### timeout — Never wait forever
134
-
135
- ```ts
136
- import { withTimeout } from 'flowx-control/timeout';
137
-
138
- const result = await withTimeout(() => fetch('/slow-api'), 5000, {
139
- fallback: () => cachedData,
140
- message: 'API took too long',
141
- signal: controller.signal,
142
- });
143
- ```
144
81
  ### 🚦 Concurrency
145
82
 
146
- #### bulkhead — Isolate concurrent operations
147
-
148
- ```ts
149
- import { createBulkhead } from 'flowx-control/bulkhead';
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
150
87
 
151
- const bulkhead = createBulkhead({
152
- maxConcurrent: 10,
153
- maxQueue: 100,
154
- queueTimeout: 5000,
155
- });
156
-
157
- const result = await bulkhead.execute(() => processRequest());
158
- console.log(bulkhead.activeCount, bulkhead.queueSize);
159
- ```
160
- #### queue — Priority async task queue
161
-
162
- ```ts
163
- import { createQueue } from 'flowx-control/queue';
164
-
165
- const queue = createQueue({ concurrency: 5, timeout: 10000 });
166
-
167
- const result = await queue.add(() => processJob(), { priority: 1 });
168
- const results = await queue.addAll(tasks.map(t => () => process(t)));
169
-
170
- await queue.onIdle(); // wait until all done
171
- queue.pause();
172
- queue.resume();
173
- ```
174
- #### semaphore — Counting resource lock
175
-
176
- ```ts
177
- import { createSemaphore } from 'flowx-control/semaphore';
178
-
179
- const sem = createSemaphore(3); // max 3 concurrent
180
- const release = await sem.acquire();
181
- try {
182
- await doWork();
183
- } finally {
184
- release();
185
- }
186
- ```
187
- #### mutex — Mutual exclusion lock
188
-
189
- ```ts
190
- import { createMutex } from 'flowx-control/mutex';
191
-
192
- const mutex = createMutex();
193
- const release = await mutex.acquire();
194
- try {
195
- await criticalSection();
196
- } finally {
197
- release();
198
- }
199
- ```
200
88
  ### 🎛️ Flow Control
201
89
 
202
- #### rateLimit — Token bucket rate limiting
203
-
204
- ```ts
205
- import { createRateLimiter } from 'flowx-control/rate-limit';
206
-
207
- const limiter = createRateLimiter({
208
- limit: 100,
209
- interval: 60_000,
210
- strategy: 'queue', // 'queue' | 'reject'
211
- });
212
-
213
- await limiter.execute(() => callApi());
214
- console.log(limiter.remaining);
215
- limiter.reset();
216
- ```
217
- #### throttle — Rate-limit function calls
218
-
219
- ```ts
220
- import { throttle } from 'flowx-control/throttle';
221
-
222
- const save = throttle(saveToDb, 1000, {
223
- leading: true,
224
- trailing: true,
225
- });
226
-
227
- await save(data);
228
- save.cancel();
229
- ```
230
- #### debounce — Delay until activity pauses
231
-
232
- ```ts
233
- import { debounce } from 'flowx-control/debounce';
234
-
235
- const search = debounce(searchApi, 300, {
236
- leading: false,
237
- trailing: true,
238
- maxWait: 1000,
239
- });
240
-
241
- await search(query);
242
- await search.flush();
243
- search.cancel();
244
- ```
245
- #### batch — Process collections in chunks
246
-
247
- ```ts
248
- import { batch } from 'flowx-control/batch';
249
-
250
- const result = await batch(urls, async (url, i) => {
251
- return fetch(url).then(r => r.json());
252
- }, {
253
- batchSize: 10,
254
- concurrency: 3,
255
- onProgress: (done, total) => console.log(`${done}/${total}`),
256
- signal: controller.signal,
257
- });
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
258
95
 
259
- console.log(result.succeeded, result.failed, result.errors);
260
- ```
261
- #### pipeline — Compose async operations
262
-
263
- ```ts
264
- import { pipeline, pipe } from 'flowx-control/pipeline';
265
-
266
- const transform = pipe(
267
- (input: string) => input.trim(),
268
- (str) => str.toUpperCase(),
269
- async (str) => await translate(str),
270
- );
271
-
272
- const result = await transform(' hello world ');
273
- ```
274
96
  ### 🛠️ Utilities
275
97
 
276
- #### poll — Repeated polling with backoff
277
-
278
- ```ts
279
- import { poll } from 'flowx-control/poll';
280
-
281
- const { result, stop } = poll(() => checkJobStatus(jobId), {
282
- interval: 2000,
283
- until: (status) => status === 'complete',
284
- maxAttempts: 30,
285
- backoff: 'exponential',
286
- signal: controller.signal,
287
- });
288
-
289
- const finalStatus = await result;
290
- ```
291
- #### hedge — Hedged/speculative requests
292
-
293
- ```ts
294
- import { hedge } from 'flowx-control/hedge';
295
-
296
- // If primary doesn't respond in 200ms, fire a parallel request
297
- const data = await hedge(() => fetch('/api'), {
298
- delay: 200,
299
- maxHedges: 2,
300
- });
301
- ```
302
- #### memo — Async memoization with TTL
303
-
304
- ```ts
305
- import { memo } from 'flowx-control/memo';
306
-
307
- const cachedFetch = memo(fetchUserById, {
308
- ttl: 60_000,
309
- maxSize: 1000,
310
- key: (id) => `user:${id}`,
311
- });
312
-
313
- const user = await cachedFetch(123);
314
- cachedFetch.clear();
315
- ```
316
- #### deferred — Externally resolvable promise
317
-
318
- ```ts
319
- import { deferred } from 'flowx-control/deferred';
320
-
321
- const d = deferred<string>();
322
- setTimeout(() => d.resolve('hello'), 1000);
323
- const value = await d.promise; // 'hello'
324
- ```
325
- ---
326
-
327
- ## Deep Imports (Tree-shaking)
328
-
329
- Import only what you need — zero unused code in your bundle:
330
-
331
- ```ts
332
- // Only pulls in ~2KB instead of the full 28KB
333
- import { retry } from 'flowx-control/retry';
334
- import { createQueue } from 'flowx-control/queue';
335
- ```
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
336
102
 
337
103
  ---
338
104
 
339
105
  ## Error Hierarchy
340
106
 
341
- All errors extend `FlowXError` with a machine-readable `code`:
342
-
343
107
  | Error | Code | Thrown by |
344
108
  |-------|------|----------|
345
109
  | `TimeoutError` | `ERR_TIMEOUT` | `withTimeout` |
@@ -348,18 +112,6 @@ All errors extend `FlowXError` with a machine-readable `code`:
348
112
  | `AbortError` | `ERR_ABORTED` | `poll`, `batch`, `timeout` |
349
113
  | `RateLimitError` | `ERR_RATE_LIMIT` | `rateLimit` |
350
114
 
351
- ```ts
352
- import { TimeoutError, FlowXError } from 'flowx-control';
353
-
354
- try {
355
- await withTimeout(fn, 1000);
356
- } catch (err) {
357
- if (err instanceof TimeoutError) {
358
- console.log(err.code); // 'ERR_TIMEOUT'
359
- }
360
- }
361
- ```
362
-
363
115
  ---
364
116
 
365
117
  ## Compatibility
@@ -378,11 +130,9 @@ try {
378
130
 
379
131
  ```bash
380
132
  git clone https://github.com/Avinashvelu03/flowx-control.git
381
- cd flowx-control
382
- npm install
383
- npm test # Run tests with 100% coverage
384
- npm run lint # ESLint
385
- npm run build # Build ESM + CJS + DTS
133
+ cd flowx-control && npm install
134
+ npm test
135
+ npm run build
386
136
  ```
387
137
 
388
138
  ---
@@ -390,4 +140,34 @@ npm run build # Build ESM + CJS + DTS
390
140
  ## License
391
141
 
392
142
  MIT © [Avinash](https://github.com/Avinashvelu03)
393
-
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.4",
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",