vigor-fetch 2.2.8 → 3.0.0

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.
package/README.md CHANGED
@@ -1,466 +1,1160 @@
1
1
  # vigor-fetch
2
2
 
3
- **Vigor** is a lightweight (minified + gzipped ~4.3kb) TypeScript HTTP / Retry utility library.
4
- Vigor provides a fluent, chainable API for building robust network logic with built-in retry, backoff, interceptors, parsing, and concurrency control.
3
+ ## Vigor is a composable, lightweighted (gzipped ~10kb) network workflow toolkit built on top of native Fetch.
4
+
5
+ > Vigor provides a fluent, chainable API for building robust network logic with built-in retry, backoff, interceptors, parsing, and concurrency control.
5
6
 
6
7
  ---
7
8
 
8
9
  ## Features
9
10
 
10
11
  - 🧩 **Fluent & Immutable API** — Fully composable, side-effect-free chaining
11
- - 🔁 **Advanced Retry System** — Exponential backoff with jitter support.
12
+ - 🔁 **Advanced Retry System** — Constant, linear, backoff, custom delay with jitter support.
12
13
  - 🌐 **Smart Fetch Layer** — Automatic 429 handling & configurable retry rules
13
14
  - ⚡ **Parallel Requests** — Concurrency-limited task runner
14
- - 🔌 **Smart Response Parsing** — Auto parsing based on Content-Type
15
+ - 🔌 **Smart Response Parsing** — Auto parsing based on Content-Type, Sniffing
15
16
  - ⚡ **Zero Dependencies** — Built on native Fetch + AbortController
16
17
  - 🪝 **Powerful Interceptors** — Lifecycle hooks for full control flow
17
18
  - 🧠 **TypeScript First** — Fully typed inference across all modules
18
19
 
19
-
20
20
  ---
21
21
 
22
-
23
22
  ## Installation
24
23
 
25
24
  ```bash
26
- npm install vigor-fetch
25
+
26
+ npm install vigor-fetch
27
+
27
28
  ```
28
29
 
29
30
  ## Why Vigor?
30
-
31
- | Feature | Vigor | native fetch | ky | axios |
31
+
32
+ | Feature | Vigor | Axios | Ky | Got |
32
33
  |---|:---:|:---:|:---:|:---:|
33
- | Zero dependencies | | | | |
34
- | 429 rate-limit handling | ✅ | ❌ | ✅(manual/config-based) | ❌ |
35
- | Retry with jitter | ✅ | ❌ | | |
36
- | Exponential backoff | ✅ | | ✅ | |
37
- | Auto response parsing | ✅ | ❌ | | |
38
- | Fluent chaining API | ✅ | ❌ | | ❌ |
39
- | Concurrency control | ✅ | ❌ | | |
40
- | Lifecycle interceptors | ✅ | | partial | ✅ |
41
- | Plugin system | ✅ | ❌ | ❌ | ❌ |
34
+ | Runtime | Browser + Node | Browser + Node | Browser + Node | Primarily Node.js |
35
+ | Built on Fetch | ✅ Native Fetch-based | ❌ Custom adapter-based | ✅ | ❌ |
36
+ | Immutable Fluent Builder | ✅ | ❌ | ⚠️ Partial | ⚠️ Partial |
37
+ | Built-in Retry Engine | ✅ Advanced | ⚠️ Limited | ✅ | |
38
+ | Custom Retry Algorithms | ✅ | ❌ | ⚠️ Limited | ⚠️ Limited |
39
+ | Retry Interceptors | ✅ | ❌ | | ❌ |
40
+ | Automatic RateLimit Handling (`429`) | ✅ | ❌ | ⚠️ Partial | |
41
+ | Automatic Content-Type Parsing | ✅ | ⚠️ Mostly JSON | | ✅ |
42
+ | Standalone Parse Engine | ✅ | ❌ | ❌ | ❌ |
43
+ | Custom Parsing Strategies | ✅ | ❌ | ❌ | ⚠️ Hook-level |
44
+ | Lifecycle Interceptors | ✅ Full lifecycle | ✅ | ⚠️ Hook-based | ✅ Hook-based |
45
+ | Concurrency Queue Engine | ✅ | ❌ | ❌ | ❌ |
46
+ | Default Fallback Values | ✅ | ❌ | ❌ | ❌ |
42
47
 
43
48
  ## Quick Start
44
49
 
50
+ ### Fetch
51
+ ```ts
52
+ import vigor from "vigor-fetch";
53
+
54
+ const data = await vigor
55
+ .fetch("https://api.example.com", "api")
56
+ .path("v1", "main")
57
+ .request();
58
+ // -> https://api.example.com/api/v1/main
59
+ ```
60
+ #### Advanced
45
61
  ```ts
46
- import vigor from "vigor-fetch";
47
62
  const data = await vigor
48
63
  .fetch("https://api.example.com")
49
- .path("api", "v1", "main")
50
- .request()
64
+ .path("users")
65
+ .retryConfig(r => r
66
+ .settings(s => s.attempt(5))
67
+ )
68
+ .parseConfig(p => p
69
+ .strategies(s => s.sniff())
70
+ )
71
+ .interceptors(i => i
72
+ .onError((ctx, api) => {
73
+ api.retry();
74
+ })
75
+ )
76
+ .request();
77
+ ```
78
+
79
+ ### Retry
80
+ ```ts
81
+ import vigor from "vigor-fetch";
82
+
83
+ const data = await vigor
84
+ .retry(async(ctx, {signal, abort}) => {
85
+ return await db.select(~)
86
+ })
87
+ .request()
51
88
  ```
52
89
 
53
- # 🛠️ Vigor API Reference
90
+ ### Parse
91
+ ```ts
92
+ import vigor from "vigor-fetch";
93
+
94
+ const response = await fetch("https://api.example.com");
95
+ const data = await vigor
96
+ .parse(response)
97
+ .request();
98
+ ```
54
99
 
100
+ ### Concurrency
101
+ ```ts
102
+ import vigor from "vigor-fetch";
103
+
104
+ const data = await vigor
105
+ .all(
106
+ async () => fetch("/api/1"),
107
+ async () => fetch("/api/2")
108
+ )
109
+ .request();
110
+ ```
55
111
 
112
+ ## vigor.retry
113
+
114
+ ### Methods
115
+
116
+ | Method | Description |
117
+ |---|---|
118
+ | target(fn) | Sets retry target function |
119
+ | settings(fn \| config) | Configures retry settings |
120
+ | interceptors(fn \| config) | Configures retry interceptors |
121
+ | algorithms(fn) | Configures retry delay algorithm |
122
+ | abortSignals(...signals) | Attaches external AbortSignals |
123
+ | request(config?) | Executes retry pipeline |
124
+
125
+ ---
126
+
127
+ ### target
128
+
129
+ Sets the retry target.
130
+
131
+ ```ts
132
+ vigor.retry(async () => {
133
+ return await fetch("/api");
134
+ })
135
+ ```
136
+
137
+ ---
138
+
139
+ ### settings
140
+
141
+ Configures retry behavior.
142
+
143
+ ```ts
144
+ .settings(s => s
145
+ .attempt(10)
146
+ .timeout(5000)
147
+ .jitter(1000)
148
+ )
149
+ ```
150
+
151
+ #### Settings API
152
+
153
+ | Method | Description | Default |
154
+ |---|---|---|
155
+ | attempt(number) | Maximum retry attempts | `5` |
156
+ | timeout(ms) | Timeout per attempt | `20000` |
157
+ | jitter(ms) | Random retry jitter | `1000` |
158
+ | default(value) | Fallback return value | `throws` |
159
+
160
+ ---
161
+
162
+ ### algorithms
163
+
164
+ Configures retry delay algorithm.
165
+
166
+ ```ts
167
+ .algorithms(a => a
168
+ .backoff()
169
+ .initial(1000)
170
+ .multiplier(2)
171
+ )
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Retry Algorithms
177
+
178
+ ### constant
179
+
180
+ Fixed retry delay.
181
+
182
+ ```ts
183
+ .algorithms(a => a
184
+ .constant()
185
+ .interval(2000)
186
+ )
187
+ ```
188
+
189
+ #### API
190
+
191
+ | Method | Description |
192
+ |---|---|
193
+ | interval(ms) | Fixed retry interval |
194
+
195
+ ---
196
+
197
+ ### linear
198
+
199
+ Linearly increasing delay.
200
+
201
+ ```ts
202
+ .algorithms(a => a
203
+ .linear()
204
+ .initial(1000)
205
+ .increment(1000)
206
+ )
207
+ ```
208
+
209
+ #### API
210
+
211
+ | Method | Description |
212
+ |---|---|
213
+ | initial(ms) | Initial delay |
214
+ | increment(ms) | Delay increment |
215
+ | minDelay(ms) | Minimum delay |
216
+ | maxDelay(ms) | Maximum delay |
217
+
218
+ ---
219
+
220
+ ### backoff
221
+
222
+ Exponential backoff delay.
223
+
224
+ ```ts
225
+ .algorithms(a => a
226
+ .backoff()
227
+ .initial(1000)
228
+ .multiplier(1.7)
229
+ )
230
+ ```
231
+
232
+ #### API
233
+
234
+ | Method | Description |
235
+ |---|---|
236
+ | initial(ms) | Initial delay |
237
+ | multiplier(number) | Exponential multiplier |
238
+ | unit(ms) | Delay unit |
239
+ | minDelay(ms) | Minimum delay |
240
+ | maxDelay(ms) | Maximum delay |
241
+
242
+ ---
243
+
244
+ ### custom
245
+
246
+ Fully custom retry algorithm.
247
+
248
+ ```ts
249
+ .algorithms(a => a
250
+ .custom()
251
+ .func(attempt => {
252
+ return attempt * 3000;
253
+ })
254
+ )
255
+ ```
256
+
257
+ #### API
258
+
259
+ | Method | Description |
260
+ |---|---|
261
+ | func(fn) | Custom delay calculator |
262
+
263
+ ---
264
+
265
+ ### abortSignals
266
+
267
+ Attaches external AbortSignals.
268
+
269
+ ```ts
270
+ const controller = new AbortController();
271
+
272
+ await vigor
273
+ .retry(task)
274
+ .abortSignals(controller.signal)
275
+ .request();
276
+ ```
277
+
278
+ ---
279
+
280
+ ### request
281
+
282
+ Executes retry workflow.
283
+
284
+ ```ts
285
+ const result = await vigor
286
+ .retry(task)
287
+ .request();
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Interceptors
293
+
294
+ | Name | API |
295
+ |---|:---:|
296
+ | before | throwError / breakRetry / abort |
297
+ | after | setResult / throwError / breakRetry |
298
+ | result | setResult / throwError |
299
+ | retryIf | proceedRetry / cancelRetry |
300
+ | onRetry | throwError / setDelay / setAttempt |
301
+ | onError | setResult / throwError / restart |
302
+
303
+ ---
304
+
305
+ ### before
306
+
307
+ Runs before each retry attempt.
308
+
309
+ ```ts
310
+ .before(async (ctx, api) => {
311
+ console.log(ctx.attempt);
312
+ })
313
+ ```
314
+
315
+ #### Available APIs
316
+
317
+ | API | Description |
318
+ |---|---|
319
+ | throwError(error) | Immediately throws an error |
320
+ | breakRetry(error) | Stops retry loop immediately |
321
+ | abort(error) | Aborts current request |
322
+
323
+ ---
324
+
325
+ ### after
326
+
327
+ Runs after successful task execution.
328
+
329
+ ```ts
330
+ .after(async (ctx, api) => {
331
+ api.setResult({
332
+ wrapped: ctx.result
333
+ });
334
+ })
335
+ ```
336
+
337
+ #### Available APIs
338
+
339
+ | API | Description |
340
+ |---|---|
341
+ | setResult(value) | Replaces current result |
342
+ | throwError(error) | Throws an error |
343
+ | breakRetry(error) | Stops retry loop |
344
+
345
+ ---
346
+
347
+ ### result
348
+
349
+ Runs before returning final result.
350
+
351
+ ```ts
352
+ .result(async (ctx, api) => {
353
+ api.setResult(transform(ctx.result));
354
+ })
355
+ ```
356
+
357
+ #### Available APIs
358
+
359
+ | API | Description |
360
+ |---|---|
361
+ | setResult(value) | Replaces current result |
362
+ | throwError(error) | Throws an error |
363
+
364
+ ---
365
+
366
+ ### retryIf
367
+
368
+ Controls retry continuation.
369
+
370
+ ```ts
371
+ .retryIf(async (ctx, api) => {
372
+ if (ctx.error instanceof TypeError) {
373
+ api.proceedRetry();
374
+ }
375
+ else {
376
+ api.cancelRetry();
377
+ }
378
+ })
379
+ ```
380
+
381
+ #### Available APIs
382
+
383
+ | API | Description |
384
+ |---|---|
385
+ | proceedRetry() | Continues retry |
386
+ | cancelRetry() | Cancels retry |
387
+
388
+ ---
389
+
390
+ ### onRetry
391
+
392
+ Runs before retry delay.
393
+
394
+ ```ts
395
+ .onRetry(async (ctx, api) => {
396
+ api.setDelay(5000);
397
+ })
398
+ ```
399
+
400
+ #### Available APIs
401
+
402
+ | API | Description |
403
+ |---|---|
404
+ | throwError(error) | Throws an error |
405
+ | setDelay(ms) | Overrides retry delay |
406
+ | setAttempt(num) | Overrides attempt count |
407
+
408
+ ---
409
+
410
+ ### onError
411
+
412
+ Runs after retry exhaustion.
413
+
414
+ ```ts
415
+ .onError(async (ctx, api) => {
416
+ console.error(ctx.error);
417
+ api.restart();
418
+ })
419
+ ```
420
+
421
+ #### Available APIs
422
+
423
+ | API | Description |
424
+ |---|---|
425
+ | setResult(value) | Returns fallback value |
426
+ | throwError(error) | Throws an error |
427
+ | restart() | Restarts retry pipeline |
428
+
56
429
  ---
57
430
 
431
+ ## vigor.parse
432
+
433
+ ### Methods
434
+
435
+ | Method | Description |
436
+ |---|---|
437
+ | target(response) | Sets target Response object |
438
+ | settings(fn \| config) | Configures parser settings |
439
+ | strategies(fn) | Configures parser strategies |
440
+ | parsers(fn \| config) | Registers custom parsers |
441
+ | interceptors(fn \| config) | Configures parser interceptors |
442
+ | request(config?) | Executes parse pipeline |
58
443
 
59
- # 📡 vigor.fetch(origin)
444
+ ---
60
445
 
61
- vigor.fetch(origin: string)
446
+ ### target
62
447
 
63
- ## Chain Methods
448
+ Sets the target `Response`.
64
449
 
65
- | Method | Type | Description |
66
- |--------|------|-------------|
67
- | origin | (string) => VigorFetch | Set base URL |
68
- | path | (...string[]) => VigorFetch | Append URL path segments |
69
- | query | (object) => VigorFetch | Set query parameters |
70
- | method | (VigorFetchMethods) => VigorFetch | Set HTTP method |
71
- | headers | (HeadersInit) => VigorFetch | Set request headers |
72
- | body | (any) => VigorFetch | Set request body |
73
- | options | (object) => VigorFetch | Merge fetch options |
74
- | setting | (fn: (s: VigorFetchSettings) => VigorFetchSettings) => VigorFetch | Settings pipeline |
75
- | retryConfig | (fn: (r: VigorRetry) => VigorRetry) => VigorFetch | Retry engine config |
76
- | parseConfig | (fn: (p: VigorParse) => VigorParse) => VigorFetch | Response parser config |
77
- | interceptors | (fn: (i: VigorFetchInterceptors) => VigorFetchInterceptors) => VigorFetch | Lifecycle hooks |
78
- | request | () => Promise<T> | Execute request |
450
+ ```ts
451
+ const response = await fetch("/api");
79
452
 
453
+ await vigor
454
+ .parse(response)
455
+ .request();
456
+ ```
80
457
 
81
458
  ---
82
459
 
460
+ ### settings
461
+
462
+ Configures parser behavior.
83
463
 
84
- ## ⚙️ fetch().setting(s => s)
464
+ ```ts
465
+ .settings(s =>
466
+ s
467
+ .original(false)
468
+ .fallback(null)
469
+ )
470
+ ```
85
471
 
86
- | Field | Type | Description |
87
- |------|------|-------------|
88
- | origin | string | Base URL |
89
- | path | string[] | URL segments |
90
- | query | object | Query params |
91
- | unretry | number[] | Non-retry status codes |
92
- | retryHeaders | string[] | Retry-related headers |
93
- | method | string | HTTP method |
94
- | headers | object | Request headers |
95
- | body | any | Request body |
96
- | options | object | Fetch options |
97
- | default | T | Fallback value |
472
+ #### Settings API
98
473
 
474
+ | Method | Description | Default |
475
+ |---|---|---|
476
+ | original(boolean) | Returns original Response object | `false` |
477
+ | fallback(value) | Fallback return value on parse failure | `throws` |
99
478
 
100
479
  ---
101
480
 
481
+ ### strategies
102
482
 
103
- ## 🧩 fetch().interceptors(i => i)
483
+ Configures parser strategy.
104
484
 
105
- | Hook | Signature | Description |
106
- |------|----------|-------------|
107
- | before | (ctx, { setOptions, throwError }) => void | Before request |
108
- | after | (ctx, { throwError }) => void | After success |
109
- | onError | (ctx, { setResult, throwError }) => void | Error handler |
110
- | result | (ctx, { setResult, throwError }) => void | Final result hook |
485
+ ```ts
486
+ .strategies(s =>
487
+ s.contentType()
488
+ )
489
+ ```
490
+
491
+ ---
492
+
493
+ ## Parse Strategies
494
+
495
+ ### contentType
496
+
497
+ Parses response using `content-type` header.
111
498
 
499
+ ```ts
500
+ .strategies(s =>
501
+ s.contentType()
502
+ )
503
+ ```
504
+
505
+ Supported:
506
+
507
+ - JSON
508
+ - Text
509
+ - Blob
510
+ - FormData
511
+ - ArrayBuffer
512
+ - Audio
513
+ - Video
514
+ - Image
112
515
 
113
516
  ---
114
517
 
518
+ ### sniff
115
519
 
116
- # 🔁 vigor.retry(task)
520
+ Attempts all parsers until one succeeds.
117
521
 
118
- vigor.retry(task: VigorRetryTask<T>)
522
+ ```ts
523
+ .strategies(s =>
524
+ s.sniff()
525
+ )
526
+ ```
119
527
 
120
- ## Methods
528
+ Useful when:
121
529
 
122
- | Method | Type | Description |
123
- |--------|------|-------------|
124
- | target | (fn: VigorRetryTask<T>) => VigorRetry | Set retry function |
125
- | setting | (fn: (s: VigorRetrySettings) => VigorRetrySettings) => VigorRetry | Retry settings |
126
- | backoff | (fn: (b: VigorRetryBackoff) => VigorRetryBackoff) => VigorRetry | Backoff strategy |
127
- | interceptors | (fn: (i: VigorRetryInterceptors) => VigorRetryInterceptors) => VigorRetry | Lifecycle hooks |
128
- | request | () => Promise<T> | Execute retry flow |
129
- | createController | () => (error: Error) => void | Abort controller |
530
+ - content-type is invalid
531
+ - server responses are inconsistent
532
+ - APIs return malformed headers
130
533
 
534
+ ---
535
+
536
+ ### custom
537
+
538
+ Uses custom parser strategy.
539
+
540
+ ```ts
541
+ .strategies(s =>
542
+ s.custom()
543
+ .func(async ({ response, parsers }) => {
544
+ return await parsers.json();
545
+ })
546
+ )
547
+ ```
548
+
549
+ #### API
550
+
551
+ | Method | Description |
552
+ |---|---|
553
+ | func(fn) | Custom parse resolver |
131
554
 
132
555
  ---
133
556
 
557
+ ### parsers
134
558
 
135
- ## ⚙️ retry().setting(s => s)
559
+ Registers custom parsers.
560
+
561
+ ```ts
562
+ .parsers(p =>
563
+ p.add("csv", async response => {
564
+ return parseCSV(await response.text());
565
+ })
566
+ )
567
+ ```
136
568
 
137
- | Field | Type | Description |
138
- |------|------|-------------|
139
- | count | number | Max retry attempts |
140
- | limit | number | Timeout per attempt |
141
- | maxDelay | number | Max delay cap |
142
- | default | T | Fallback value |
569
+ #### Parser API
143
570
 
571
+ | Method | Description |
572
+ |---|---|
573
+ | add(name, parser) | Adds custom parser |
574
+ | remove(name) | Removes parser |
575
+ | clear() | Removes all parsers |
144
576
 
145
577
  ---
146
578
 
579
+ ### request
147
580
 
148
- ## 📈 retry().backoff(b => b)
581
+ Executes parse workflow.
149
582
 
150
- | Field | Type | Description |
151
- |------|------|-------------|
152
- | initialDelay | number | Initial delay |
153
- | baseDelay | number | Base delay |
154
- | factor | number | Exponential multiplier |
155
- | jitter | number | Random noise |
583
+ ```ts
584
+ const result = await vigor
585
+ .parse(response)
586
+ .request();
587
+ ```
588
+
589
+ ---
156
590
 
591
+ ## Interceptors
592
+
593
+ | Name | API |
594
+ |---|:---:|
595
+ | before | throwError |
596
+ | after | setResult / throwError |
597
+ | result | setResult / throwError |
598
+ | onError | setResult / throwError |
157
599
 
158
600
  ---
159
601
 
602
+ ### before
603
+
604
+ Runs before parsing starts.
160
605
 
161
- ## 🧩 retry().interceptors(i => i)
606
+ ```ts
607
+ .before(async (ctx, api) => {
608
+ console.log(ctx.response.headers);
609
+ })
610
+ ```
162
611
 
163
- | Hook | Signature | Description |
164
- |------|----------|-------------|
165
- | before | (ctx, { setAttempt, throwError, abort }) => void | Before execution |
166
- | after | (ctx, { setAttempt, setResult, throwError }) => void | After success |
167
- | onError | (ctx, { setResult, throwError }) => void | Error handling |
168
- | onRetry | (ctx, { setDelay }) => void | Retry event |
169
- | retryIf | (ctx, { proceedRetry, cancelRetry }) => void | Retry decision |
612
+ #### Available APIs
170
613
 
614
+ | API | Description |
615
+ |---|---|
616
+ | throwError(error) | Immediately throws error |
171
617
 
172
618
  ---
173
619
 
620
+ ### after
621
+
622
+ Runs after parser succeeds.
623
+
624
+ ```ts
625
+ .after(async (ctx, api) => {
626
+ api.setResult({
627
+ wrapped: ctx.result
628
+ });
629
+ })
630
+ ```
174
631
 
175
- # vigor.all(tasks)
632
+ #### Available APIs
176
633
 
177
- vigor.all(tasks: VigorAllTask<T>[])
634
+ | API | Description |
635
+ |---|---|
636
+ | setResult(value) | Replaces parsed result |
637
+ | throwError(error) | Throws error |
178
638
 
179
- ## Methods
639
+ ---
180
640
 
181
- | Method | Type | Description |
182
- |--------|------|-------------|
183
- | target | (...tasks) => VigorAll | Set tasks |
184
- | setting | (fn: (s: VigorAllSettings) => VigorAllSettings) => VigorAll | Concurrency config |
185
- | interceptors | (fn: (i: VigorAllInterceptors) => VigorAllInterceptors) => VigorAll | Hooks |
186
- | request | () => Promise<Array<T | Error>> | Execute all tasks |
641
+ ### result
187
642
 
643
+ Runs before returning final result.
644
+
645
+ ```ts
646
+ .result(async (ctx, api) => {
647
+ api.setResult(transform(ctx.result));
648
+ })
649
+ ```
650
+
651
+ #### Available APIs
652
+
653
+ | API | Description |
654
+ |---|---|
655
+ | setResult(value) | Replaces final result |
656
+ | throwError(error) | Throws error |
188
657
 
189
658
  ---
190
659
 
660
+ ### onError
191
661
 
192
- ## ⚙️ all().setting(s => s)
662
+ Runs when parsing fails.
193
663
 
194
- | Field | Type | Description |
195
- |------|------|-------------|
196
- | concurrency | number | Max parallel tasks |
197
- | jitter | number | Delay randomness |
198
- | onlySuccess | boolean | Filters Success |
664
+ ```ts
665
+ .onError(async (ctx, api) => {
666
+ api.setResult(null);
667
+ })
668
+ ```
199
669
 
670
+ #### Available APIs
671
+
672
+ | API | Description |
673
+ |---|---|
674
+ | setResult(value) | Returns fallback value |
675
+ | throwError(error) | Throws error |
200
676
 
201
677
  ---
202
678
 
679
+ ## Example
680
+
681
+ ```ts
682
+ const response = await fetch("/api");
683
+
684
+ const data = await vigor
685
+ .parse(response)
686
+ .strategies(s =>
687
+ s.sniff()
688
+ )
689
+ .interceptors(i =>
690
+ i.onError((ctx, api) => {
691
+ console.log(ctx.error);
692
+
693
+ api.setResult(null);
694
+ })
695
+ )
696
+ .request();
697
+ ```
203
698
 
204
- ## 🧩 all().interceptors(i => i)
699
+ ## vigor.fetch
205
700
 
206
- | Hook | Signature | Description |
207
- |------|----------|-------------|
208
- | before | (ctx) => void | Before each task |
209
- | after | (ctx, { setResult }) => void | After success |
210
- | onError | (ctx, { setResult }) => void | Error handling |
211
- | result | (ctx, { setResult }) => void | Final aggregation |
701
+ ### Methods
212
702
 
703
+ | Method | Description |
704
+ |---|---|
705
+ | origin(...paths) | Sets base URL and origin paths |
706
+ | path(...paths) | Appends request paths |
707
+ | query(params) | Sets query parameters |
708
+ | headers(headers) | Sets request headers |
709
+ | options(options) | Sets fetch options |
710
+ | retryConfig(fn \| config) | Configures retry engine |
711
+ | parseConfig(fn \| config) | Configures parse engine |
712
+ | interceptors(fn \| config) | Configures fetch interceptors |
713
+ | abortSignals(...signals) | Attaches external AbortSignals |
714
+ | request(config?) | Executes fetch pipeline |
213
715
 
214
716
  ---
215
717
 
718
+ ### origin
216
719
 
217
- # 🧪 vigor.parse(response)
720
+ Sets base URL and origin paths.
218
721
 
219
- vigor.parse(response: Response)
722
+ ```ts
723
+ .fetch("https://api.example.com/", "/api")
724
+ ```
220
725
 
221
- | Method | Type | Description |
222
- |--------|------|-------------|
223
- | target | Response | Set response |
224
- | original | boolean | Return raw response |
225
- | type | keyof Response | Force parse type |
226
- | request | () => Promise<T> | Execute parsing |
726
+ Produces:
227
727
 
728
+ ```txt
729
+ https://api.example.com/api
730
+ ```
228
731
 
229
732
  ---
230
733
 
734
+ ### path
231
735
 
232
- # 🚀 vigor.fetch examples
736
+ Appends request paths.
233
737
 
234
- ## GET request
235
738
  ```ts
236
- vigor.fetch("https://api.example.com")
237
- .path("users", "profile")
238
- .query({ id: 123 })
239
- .method("GET")
240
- .request()
739
+ .path("v1", "users")
740
+ ```
741
+
742
+ Produces:
743
+
744
+ ```txt
745
+ https://api.example.com/api/v1/users
241
746
  ```
747
+
242
748
  ---
243
749
 
244
- ## POST request
750
+ ### query
751
+
752
+ Adds query parameters.
753
+
245
754
  ```ts
246
- vigor.fetch("https://api.example.com")
247
- .path("users")
248
- .method("POST")
249
- .headers({
250
- Authorization: "Bearer TOKEN"
251
- })
252
- .body({
253
- name: "John",
254
- age: 30
255
- })
256
- .request()
755
+ .query({
756
+ page: 1,
757
+ limit: 10
758
+ })
759
+ ```
760
+
761
+ Produces:
762
+
763
+ ```txt
764
+ ?page=1&limit=10
257
765
  ```
766
+
258
767
  ---
259
768
 
260
- ## fetch + retry + backoff + parse
769
+ ### headers
770
+
771
+ Sets request headers.
772
+
261
773
  ```ts
262
- vigor.fetch("https://api.example.com")
263
- .path("data")
264
- .retryConfig(r =>
265
- r
266
- .setting(s =>
267
- s
268
- .count(3)
269
- .limit(5000)
270
- )
271
- .backoff(b =>
272
- b
273
- .factor(2)
274
- .jitter(300)
275
- )
276
- )
277
- .parseConfig(p =>
278
- p.original(false)
279
- )
280
- .request()
774
+ .headers({
775
+ Authorization: "Bearer token"
776
+ })
281
777
  ```
778
+
282
779
  ---
283
780
 
284
- # 🔁 vigor.retry examples
781
+ ### options
782
+
783
+ Sets native fetch options.
285
784
 
286
- ## basic retry
287
785
  ```ts
288
- vigor.retry(async (ctx, { signal }) => {
289
- const res = await fetch("https://api.example.com/data", { signal })
290
- if (!res.ok) throw new Error("failed")
291
- return res.json()
786
+ .options({
787
+ method: "POST",
788
+ body: JSON.stringify({
789
+ username: "john"
790
+ })
292
791
  })
293
- .setting(s =>
294
- s
295
- .count(5)
296
- .limit(3000)
792
+ ```
793
+
794
+ ---
795
+
796
+ ### retryConfig
797
+
798
+ Configures internal retry engine.
799
+
800
+ ```ts
801
+ .retryConfig(r => r
802
+ .settings(s => s
803
+ .attempt(5)
804
+ )
297
805
  )
298
- .backoff(b =>
299
- b
300
- .baseDelay(500)
301
- .factor(2)
806
+ ```
807
+
808
+ ---
809
+
810
+ ### parseConfig
811
+
812
+ Configures internal parse engine.
813
+
814
+ ```ts
815
+ .parseConfig(p => p
816
+ .strategies(s => s
817
+ .sniff()
818
+ )
302
819
  )
303
- .request()
304
820
  ```
305
821
 
822
+ ---
823
+
824
+ ### abortSignals
825
+
826
+ Attaches external AbortSignals.
827
+
828
+ ```ts
829
+ const controller = new AbortController();
830
+
831
+ await vigor
832
+ .fetch("/api")
833
+ .abortSignals(controller.signal)
834
+ .request();
835
+ ```
306
836
 
307
837
  ---
308
838
 
839
+ ### request
840
+
841
+ Executes fetch workflow.
309
842
 
310
- ## retryIf control
311
843
  ```ts
312
- vigor.retry(async () => {
313
- const res = await fetch("https://api.example.com")
314
- return res.json()
844
+ const data = await vigor
845
+ .fetch("https://api.example.com")
846
+ .request();
847
+ ```
848
+
849
+ ---
850
+
851
+ ## Interceptors
852
+
853
+ | Name | API |
854
+ |---|:---:|
855
+ | before | throwError / abort |
856
+ | after | setResponse / throwError |
857
+ | result | setResult / throwError |
858
+ | onError | setResult / throwError / retry |
859
+
860
+ ---
861
+
862
+ ### before
863
+
864
+ Runs before fetch execution.
865
+
866
+ ```ts
867
+ .before(async (ctx, api) => {
868
+ console.log(ctx.url);
315
869
  })
316
- .interceptors(i =>
317
- i.retryIf((ctx, { cancelRetry }) => {
318
- const result = ctx.runtime.result
870
+ ```
319
871
 
320
- if (result?.error === "fatal") {
321
- cancelRetry()
322
- }
323
- })
324
- )
325
- .request()
872
+ #### Available APIs
873
+
874
+ | API | Description |
875
+ |---|---|
876
+ | throwError(error) | Immediately throws error |
877
+ | abort(error) | Aborts request |
878
+
879
+ ---
880
+
881
+ ### after
882
+
883
+ Runs after receiving Response.
884
+
885
+ ```ts
886
+ .after(async (ctx, api) => {
887
+ console.log(ctx.response.status);
888
+ })
326
889
  ```
327
890
 
891
+ #### Available APIs
892
+
893
+ | API | Description |
894
+ |---|---|
895
+ | setResponse(response) | Replaces Response object |
896
+ | throwError(error) | Throws error |
328
897
 
329
898
  ---
330
899
 
900
+ ### result
901
+
902
+ Runs before returning parsed result.
331
903
 
332
- ## abort controller
333
904
  ```ts
334
- const retry = vigor.retry(async (ctx, { signal }) => {
335
- const res = await fetch("https://api.example.com", { signal })
336
- return res.json()
905
+ .result(async (ctx, api) => {
906
+ api.setResult({
907
+ data: ctx.result
908
+ });
337
909
  })
910
+ ```
911
+
912
+ #### Available APIs
913
+
914
+ | API | Description |
915
+ |---|---|
916
+ | setResult(value) | Replaces final result |
917
+ | throwError(error) | Throws error |
918
+
919
+ ---
338
920
 
339
- const abort = retry.createController()
921
+ ### onError
340
922
 
341
- setTimeout(() => {
342
- abort(new Error("manual abort"))
343
- }, 2000)
923
+ Runs when fetch fails.
344
924
 
345
- await retry.request()
925
+ ```ts
926
+ .onError(async (ctx, api) => {
927
+ api.retry();
928
+ })
346
929
  ```
347
930
 
931
+ #### Available APIs
932
+
933
+ | API | Description |
934
+ |---|---|
935
+ | setResult(value) | Returns fallback value |
936
+ | throwError(error) | Throws error |
937
+ | retry() | Re-executes fetch pipeline |
348
938
 
349
939
  ---
350
940
 
941
+ ## Example
351
942
 
352
- # ⚡ vigor.all examples
353
943
  ```ts
354
- vigor.all([
355
- async () => fetch("https://api.com/a").then(r => r.json()),
356
- async () => fetch("https://api.com/b").then(r => r.json()),
357
- async () => fetch("https://api.com/c").then(r => r.json())
358
- ]).request()
944
+ const data = await vigor
945
+ .fetch("https://api.example.com", "api")
946
+ .path("v1", "users")
947
+ .query({
948
+ page: 1
949
+ })
950
+ .headers({
951
+ Authorization: "Bearer token"
952
+ })
953
+ .retryConfig(r =>
954
+ r.settings(s =>
955
+ s.attempt(5)
956
+ )
957
+ )
958
+ .request();
359
959
  ```
360
960
 
961
+ ## vigor.all
962
+
963
+ ### Methods
964
+
965
+ | Method | Description |
966
+ |---|---|
967
+ | target(...tasks) | Sets async task list |
968
+ | settings(fn \| config) | Configures concurrency settings |
969
+ | interceptors(fn \| config) | Configures concurrency interceptors |
970
+ | abortSignals(...signals) | Attaches external AbortSignals |
971
+ | request(config?) | Executes concurrency pipeline |
361
972
 
362
973
  ---
363
974
 
975
+ ### target
976
+
977
+ Sets async task list.
364
978
 
365
979
  ```ts
366
- vigor.all([
367
- async () => "A",
368
- async () => "B",
369
- async () => "C",
370
- async () => "D"
371
- ])
372
- .setting(s =>
373
- s
980
+ .all(
981
+ async () => fetch("/api/1"),
982
+ async () => fetch("/api/2")
983
+ )
984
+ ```
985
+
986
+ ---
987
+
988
+ ### settings
989
+
990
+ Configures concurrency behavior.
991
+
992
+ ```ts
993
+ .settings(s => s
374
994
  .concurrency(2)
375
- .jitter(500)
995
+ .onlySuccess(true)
376
996
  )
377
- .request()
378
997
  ```
379
998
 
999
+ #### Settings API
1000
+
1001
+ | Method | Description | Default |
1002
+ |---|---|---|
1003
+ | concurrency(number) | Maximum concurrent tasks | `Infinity` |
1004
+ | onlySuccess(boolean) | Returns only successful results | `false` |
1005
+ | fallback(value) | Fallback return value | `throws` |
380
1006
 
381
1007
  ---
382
1008
 
1009
+ ### abortSignals
1010
+
1011
+ Attaches external AbortSignals.
383
1012
 
384
1013
  ```ts
385
- vigor.all([
386
- async () => "ok1",
387
- async () => { throw new Error("fail") },
388
- async () => "ok2"
389
- ]).request()
1014
+ const controller = new AbortController();
1015
+
1016
+ await vigor
1017
+ .all(task1, task2)
1018
+ .abortSignals(controller.signal)
1019
+ .request();
390
1020
  ```
391
1021
 
392
1022
  ---
393
1023
 
1024
+ ### request
394
1025
 
395
- # 🧪 vigor.parse examples
396
- ```ts
397
- const res = await fetch("https://api.com/data")
1026
+ Executes concurrency workflow.
398
1027
 
399
- vigor.parse(res).request()
1028
+ ```ts
1029
+ const results = await vigor
1030
+ .all(task1, task2)
1031
+ .request();
400
1032
  ```
401
1033
 
1034
+ ---
1035
+
1036
+ ## Interceptors
1037
+
1038
+ | Name | API |
1039
+ |---|:---:|
1040
+ | before | throwError / abort |
1041
+ | afterEach | setResult / throwError |
1042
+ | after | setResults / throwError |
1043
+ | result | setResults / throwError |
1044
+ | onError | setResults / throwError |
402
1045
 
403
1046
  ---
404
1047
 
1048
+ ### before
1049
+
1050
+ Runs before task execution starts.
405
1051
 
406
1052
  ```ts
407
- const img = await fetch("https://api.com/image.png")
1053
+ .before(async (ctx, api) => {
1054
+ console.log(ctx.tasks.length);
1055
+ })
1056
+ ```
1057
+
1058
+ #### Available APIs
1059
+
1060
+ | API | Description |
1061
+ |---|---|
1062
+ | throwError(error) | Immediately throws error |
1063
+ | abort(error) | Aborts execution |
1064
+
1065
+ ---
1066
+
1067
+ ### afterEach
1068
+
1069
+ Runs after each task resolves.
408
1070
 
409
- vigor.parse(img)
410
- .type("blob")
411
- .request()
1071
+ ```ts
1072
+ .afterEach(async (ctx, api) => {
1073
+ console.log(ctx.result);
1074
+ })
412
1075
  ```
413
1076
 
1077
+ #### Available APIs
1078
+
1079
+ | API | Description |
1080
+ |---|---|
1081
+ | setResult(value) | Replaces task result |
1082
+ | throwError(error) | Throws error |
414
1083
 
415
1084
  ---
416
1085
 
1086
+ ### after
1087
+
1088
+ Runs after all tasks complete.
417
1089
 
418
1090
  ```ts
419
- const raw = await fetch("https://api.com")
1091
+ .after(async (ctx, api) => {
1092
+ console.log(ctx.results);
1093
+ })
1094
+ ```
1095
+
1096
+ #### Available APIs
1097
+
1098
+ | API | Description |
1099
+ |---|---|
1100
+ | setResults(value) | Replaces result array |
1101
+ | throwError(error) | Throws error |
420
1102
 
421
- vigor.parse(raw)
422
- .original(true)
423
- .request()
1103
+ ---
1104
+
1105
+ ### result
1106
+
1107
+ Runs before returning final results.
1108
+
1109
+ ```ts
1110
+ .result(async (ctx, api) => {
1111
+ api.setResults(
1112
+ ctx.results.filter(Boolean)
1113
+ );
1114
+ })
424
1115
  ```
425
1116
 
1117
+ #### Available APIs
1118
+
1119
+ | API | Description |
1120
+ |---|---|
1121
+ | setResults(value) | Replaces final results |
1122
+ | throwError(error) | Throws error |
426
1123
 
427
1124
  ---
428
1125
 
1126
+ ### onError
1127
+
1128
+ Runs when concurrency execution fails.
429
1129
 
430
- # 🔥 full pipeline example
431
1130
  ```ts
432
- vigor.fetch("https://api.example.com")
433
- .path("users")
434
- .query({ page: 1 })
435
- .method("GET")
436
- .retryConfig(r =>
437
- r
438
- .setting(s =>
439
- s
440
- .count(3)
441
- .limit(5000)
442
- )
443
- .backoff(b =>
444
- b
445
- .factor(2)
446
- .jitter(200)
447
- )
448
- .interceptors(i =>
449
- i.onRetry((ctx, { setDelay }) => {
450
- setDelay(1000)
451
- })
452
- )
453
- )
454
- .parseConfig(p =>
455
- p.original(false)
456
- )
457
- .interceptors(i =>
458
- i.result(() => {
459
- console.log("done")
460
- })
461
- )
462
- .request()
1131
+ .onError(async (ctx, api) => {
1132
+ api.setResults([]);
1133
+ })
463
1134
  ```
464
1135
 
1136
+ #### Available APIs
465
1137
 
466
- ---
1138
+ | API | Description |
1139
+ |---|---|
1140
+ | setResults(value) | Returns fallback results |
1141
+ | throwError(error) | Throws error |
1142
+
1143
+ ---
1144
+
1145
+ ## Example
1146
+
1147
+ ```ts
1148
+ const results = await vigor
1149
+ .all(
1150
+ async () => fetch("/api/1"),
1151
+ async () => fetch("/api/2"),
1152
+ async () => fetch("/api/3")
1153
+ )
1154
+ .settings(s =>
1155
+ s
1156
+ .concurrency(2)
1157
+ .onlySuccess(true)
1158
+ )
1159
+ .request();
1160
+ ```