zlient 3.0.0 → 3.1.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 +116 -29
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/endpoint/base-endpoint.d.ts.map +1 -1
- package/dist/http/http-client.d.ts +2 -13
- package/dist/http/http-client.d.ts.map +1 -1
- package/dist/index.cjs +48 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +48 -55
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +16 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,28 +6,36 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
Build robust, type-safe API clients with
|
|
9
|
+
Build robust, type-safe API clients with runtime validation, retry logic, and zero boilerplate. Use **any** [Standard Schema](https://standardschema.dev) library — Zod, Valibot, ArkType, and more.
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
13
|
+
- **Standard Schema**: Use Zod, Valibot, ArkType, or any compatible validator. No lock-in.
|
|
14
|
+
- **Functional API**: Define endpoints with pure functions and automatic type inference.
|
|
15
|
+
- **Type-Safe**: Full TypeScript support. Arguments and responses are strictly typed.
|
|
16
|
+
- **Runtime Validation**: Validate requests, responses, query params, and path params.
|
|
17
|
+
- **Resilience**: Built-in exponential backoff retries and timeouts.
|
|
18
|
+
- **Auth**: Logic-safe authentication providers (Bearer, API Key, Custom).
|
|
19
|
+
- **Observability**: Hooks for structured logging and metrics.
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
24
25
|
```bash
|
|
25
|
-
npm install zlient
|
|
26
|
+
npm install zlient
|
|
26
27
|
# or
|
|
27
|
-
bun add zlient
|
|
28
|
+
bun add zlient
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Then install your preferred validation library:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Pick one (or more!)
|
|
35
|
+
npm install zod # Zod
|
|
36
|
+
npm install valibot # Valibot
|
|
37
|
+
npm install arktype # ArkType
|
|
38
|
+
```
|
|
31
39
|
|
|
32
40
|
---
|
|
33
41
|
|
|
@@ -42,21 +50,22 @@ const client = new HttpClient({
|
|
|
42
50
|
baseUrls: {
|
|
43
51
|
default: 'https://api.example.com',
|
|
44
52
|
},
|
|
45
|
-
retry: {
|
|
53
|
+
retry: { maxAttempts: 3, baseDelayMs: 1000 },
|
|
46
54
|
});
|
|
47
55
|
```
|
|
48
56
|
|
|
49
57
|
### 2. Define Endpoint
|
|
50
58
|
|
|
51
|
-
Use `createEndpoint`
|
|
59
|
+
Use `createEndpoint` with your favorite schema library:
|
|
52
60
|
|
|
61
|
+
<!-- tabs:start -->
|
|
62
|
+
#### **Zod**
|
|
53
63
|
```typescript
|
|
54
64
|
import { z } from 'zod';
|
|
55
65
|
|
|
56
66
|
const getUser = client.createEndpoint({
|
|
57
67
|
method: 'GET',
|
|
58
68
|
path: (params) => `/users/${params.id}`,
|
|
59
|
-
// Strict schemas for all inputs
|
|
60
69
|
pathParams: z.object({ id: z.string() }),
|
|
61
70
|
response: z.object({
|
|
62
71
|
id: z.string(),
|
|
@@ -66,6 +75,39 @@ const getUser = client.createEndpoint({
|
|
|
66
75
|
});
|
|
67
76
|
```
|
|
68
77
|
|
|
78
|
+
#### **Valibot**
|
|
79
|
+
```typescript
|
|
80
|
+
import * as v from 'valibot';
|
|
81
|
+
|
|
82
|
+
const getUser = client.createEndpoint({
|
|
83
|
+
method: 'GET',
|
|
84
|
+
path: (params) => `/users/${params.id}`,
|
|
85
|
+
pathParams: v.object({ id: v.string() }),
|
|
86
|
+
response: v.object({
|
|
87
|
+
id: v.string(),
|
|
88
|
+
name: v.string(),
|
|
89
|
+
email: v.pipe(v.string(), v.email()),
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### **ArkType**
|
|
95
|
+
```typescript
|
|
96
|
+
import { type } from 'arktype';
|
|
97
|
+
|
|
98
|
+
const getUser = client.createEndpoint({
|
|
99
|
+
method: 'GET',
|
|
100
|
+
path: (params) => `/users/${params.id}`,
|
|
101
|
+
pathParams: type({ id: 'string' }),
|
|
102
|
+
response: type({
|
|
103
|
+
id: 'string',
|
|
104
|
+
name: 'string',
|
|
105
|
+
email: 'string.email',
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
<!-- tabs:end -->
|
|
110
|
+
|
|
69
111
|
### 3. Call It
|
|
70
112
|
|
|
71
113
|
TypeScript will enforce inputs and infer the response type automatically.
|
|
@@ -83,6 +125,23 @@ console.log(user.name);
|
|
|
83
125
|
|
|
84
126
|
## Advanced Usage
|
|
85
127
|
|
|
128
|
+
### Retry Configuration
|
|
129
|
+
|
|
130
|
+
Zlient automatically retries failed requests with exponential backoff. Customize the retry behavior:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const client = new HttpClient({
|
|
134
|
+
baseUrls: { default: 'https://api.example.com' },
|
|
135
|
+
retry: {
|
|
136
|
+
maxAttempts: 3, // Total attempts (including initial request)
|
|
137
|
+
baseDelayMs: 1000, // Base delay for exponential backoff
|
|
138
|
+
retryMethods: ['GET', 'POST', 'PUT'], // Methods to retry
|
|
139
|
+
retryStatusCodes: [500, 502, 503, 504], // Status codes to retry
|
|
140
|
+
respectRetryAfter: true, // Honor Retry-After header
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
86
145
|
### Authentication
|
|
87
146
|
|
|
88
147
|
Zlient provides built-in auth providers that safely handle headers.
|
|
@@ -104,6 +163,8 @@ client.setAuth(new ApiKeyAuth({ header: 'X-API-KEY', value: 'secret' }));
|
|
|
104
163
|
Handle different responses for different status codes.
|
|
105
164
|
|
|
106
165
|
```typescript
|
|
166
|
+
import { z } from 'zod';
|
|
167
|
+
|
|
107
168
|
const createPost = client.createEndpoint({
|
|
108
169
|
method: 'POST',
|
|
109
170
|
path: '/posts',
|
|
@@ -118,40 +179,46 @@ const result = await createPost({ data: { title: 'Hello' } });
|
|
|
118
179
|
// `result` type is the union of the 201 and 400 schemas
|
|
119
180
|
```
|
|
120
181
|
|
|
182
|
+
### Error Handling
|
|
183
|
+
|
|
184
|
+
Validation errors are thrown as `ApiError` with detailed issues:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { ApiError } from 'zlient';
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
await getUser({ pathParams: { id: '123' } });
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error instanceof ApiError && error.validationIssues) {
|
|
193
|
+
// Handle validation error
|
|
194
|
+
console.log(error.validationIssues);
|
|
195
|
+
// [{ message: 'Expected string, got number', path: ['id'] }]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
121
200
|
### FormData Support
|
|
122
201
|
|
|
123
|
-
Upload files and send multipart form data seamlessly.
|
|
202
|
+
Upload files and send multipart form data seamlessly.
|
|
124
203
|
|
|
125
204
|
```typescript
|
|
126
|
-
|
|
205
|
+
import { z } from 'zod';
|
|
206
|
+
|
|
127
207
|
const uploadFile = client.createEndpoint({
|
|
128
208
|
method: 'POST',
|
|
129
209
|
path: '/upload',
|
|
130
210
|
response: z.object({ fileId: z.string(), url: z.string() }),
|
|
131
211
|
advanced: {
|
|
132
|
-
skipRequestValidation: true, // FormData can't be validated
|
|
212
|
+
skipRequestValidation: true, // FormData can't be validated
|
|
133
213
|
},
|
|
134
214
|
});
|
|
135
215
|
|
|
136
216
|
const formData = new FormData();
|
|
137
217
|
formData.append('file', fileBlob, 'document.pdf');
|
|
138
|
-
formData.append('description', 'My document');
|
|
139
218
|
|
|
140
219
|
const result = await uploadFile({ data: formData });
|
|
141
|
-
console.log(result.url);
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
You can also use the low-level `request` method directly:
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
const formData = new FormData();
|
|
148
|
-
formData.append('avatar', imageFile);
|
|
149
|
-
|
|
150
|
-
const { data } = await client.post('/users/avatar', formData);
|
|
151
220
|
```
|
|
152
221
|
|
|
153
|
-
> **Note**: When using `FormData`, the `Content-Type` header is automatically removed so the browser can set it with the proper multipart boundary.
|
|
154
|
-
|
|
155
222
|
### Metrics & Logging
|
|
156
223
|
|
|
157
224
|
Integrate with any monitoring stack (Datadog, Prometheus, etc.).
|
|
@@ -168,6 +235,26 @@ const client = new HttpClient({
|
|
|
168
235
|
|
|
169
236
|
---
|
|
170
237
|
|
|
238
|
+
## Migration from v2
|
|
239
|
+
|
|
240
|
+
v3 introduces Standard Schema support. Key changes:
|
|
241
|
+
|
|
242
|
+
```diff
|
|
243
|
+
- import { z } from 'zod'; // Required peer dependency
|
|
244
|
+
+ // Use any Standard Schema library (Zod, Valibot, ArkType)
|
|
245
|
+
|
|
246
|
+
- catch (e) { if (e instanceof ZodError) { ... } }
|
|
247
|
+
+ catch (e) { if (e instanceof ApiError && e.validationIssues) { ... } }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Documentation
|
|
253
|
+
|
|
254
|
+
📖 [Full Documentation](https://zlient.dev)
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
171
258
|
## License
|
|
172
259
|
|
|
173
260
|
MIT © [Emirhan Gumus](https://github.com/emirhangumus)
|
package/dist/auth.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface AuthProvider {
|
|
|
36
36
|
* Use this when you don't need authentication.
|
|
37
37
|
*/
|
|
38
38
|
export declare class NoAuth implements AuthProvider {
|
|
39
|
-
apply(): Promise<void>;
|
|
39
|
+
apply(_req: AuthContext): Promise<void>;
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* API Key authentication provider.
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC/C;AAED;;;GAGG;AACH,qBAAa,MAAO,YAAW,YAAY;IACnC,KAAK;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC/C;AAED;;;GAGG;AACH,qBAAa,MAAO,YAAW,YAAY;IACnC,KAAK,CAAC,IAAI,EAAE,WAAW;CAG9B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAW,YAAW,YAAY;IACjC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAQ5E,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,WAAW;CAgBjC;AACD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAgB,YAAW,YAAY;IACtC,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM;IACtD,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,WAAW;CAclC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-endpoint.d.ts","sourceRoot":"","sources":["../../lib/endpoint/base-endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,
|
|
1
|
+
{"version":3,"file":"base-endpoint.d.ts","sourceRoot":"","sources":["../../lib/endpoint/base-endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import type { AuthProvider } from '../auth';
|
|
|
2
2
|
import { EndpointCall, EndpointConfig } from '../endpoint/base-endpoint';
|
|
3
3
|
import { ClientOptions, HTTPMethod, HTTPStatusCodeNumber, RequestOptions, ResponseSchema, StandardSchemaV1 } from '../types';
|
|
4
4
|
/**
|
|
5
|
-
* HTTP client with built-in
|
|
5
|
+
* HTTP client with built-in authentication, and interceptors.
|
|
6
6
|
* Supports multiple base URLs, type-safe requests, and comprehensive error handling.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
@@ -10,7 +10,6 @@ import { ClientOptions, HTTPMethod, HTTPStatusCodeNumber, RequestOptions, Respon
|
|
|
10
10
|
* const client = new HttpClient({
|
|
11
11
|
* baseUrls: { default: 'https://api.example.com' },
|
|
12
12
|
* headers: { 'Content-Type': 'application/json' },
|
|
13
|
-
* retry: { maxRetries: 3, baseDelayMs: 1000 },
|
|
14
13
|
* timeout: { requestTimeoutMs: 30000 }
|
|
15
14
|
* });
|
|
16
15
|
*
|
|
@@ -22,7 +21,7 @@ export declare class HttpClient {
|
|
|
22
21
|
private baseUrls;
|
|
23
22
|
private headers;
|
|
24
23
|
private interceptors;
|
|
25
|
-
private
|
|
24
|
+
private retryPolicy;
|
|
26
25
|
private timeoutMs?;
|
|
27
26
|
private auth;
|
|
28
27
|
private logger;
|
|
@@ -46,16 +45,6 @@ export declare class HttpClient {
|
|
|
46
45
|
*/
|
|
47
46
|
setAuth(auth: AuthProvider): void;
|
|
48
47
|
private resolveBaseUrl;
|
|
49
|
-
/**
|
|
50
|
-
* Sleep for a specified duration (used for retry backoff).
|
|
51
|
-
* @private
|
|
52
|
-
*/
|
|
53
|
-
private sleep;
|
|
54
|
-
/**
|
|
55
|
-
* Execute a function with retry logic and exponential backoff.
|
|
56
|
-
* @private
|
|
57
|
-
*/
|
|
58
|
-
private withRetry;
|
|
59
48
|
/**
|
|
60
49
|
* Run all registered before-request hooks.
|
|
61
50
|
* @private
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../lib/http/http-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAgB,MAAM,2BAA2B,CAAC;AAGvF,OAAO,
|
|
1
|
+
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../lib/http/http-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
5
5
|
* Use this when you don't need authentication.
|
|
6
6
|
*/
|
|
7
7
|
var NoAuth = class {
|
|
8
|
-
async apply() {}
|
|
8
|
+
async apply(_req) {}
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* API Key authentication provider.
|
|
@@ -539,7 +539,7 @@ var ConsoleMetricsCollector = class {
|
|
|
539
539
|
//#endregion
|
|
540
540
|
//#region lib/http/http-client.ts
|
|
541
541
|
/**
|
|
542
|
-
* HTTP client with built-in
|
|
542
|
+
* HTTP client with built-in authentication, and interceptors.
|
|
543
543
|
* Supports multiple base URLs, type-safe requests, and comprehensive error handling.
|
|
544
544
|
*
|
|
545
545
|
* @example
|
|
@@ -547,7 +547,6 @@ var ConsoleMetricsCollector = class {
|
|
|
547
547
|
* const client = new HttpClient({
|
|
548
548
|
* baseUrls: { default: 'https://api.example.com' },
|
|
549
549
|
* headers: { 'Content-Type': 'application/json' },
|
|
550
|
-
* retry: { maxRetries: 3, baseDelayMs: 1000 },
|
|
551
550
|
* timeout: { requestTimeoutMs: 30000 }
|
|
552
551
|
* });
|
|
553
552
|
*
|
|
@@ -569,19 +568,12 @@ var HttpClient = class {
|
|
|
569
568
|
this.baseUrls = opts.baseUrls;
|
|
570
569
|
this.headers = opts.headers ?? { "Content-Type": "application/json" };
|
|
571
570
|
this.interceptors = opts.interceptors ?? {};
|
|
572
|
-
this.
|
|
573
|
-
|
|
574
|
-
baseDelayMs:
|
|
575
|
-
jitter: .2,
|
|
576
|
-
retryMethods: ["GET", "HEAD"]
|
|
571
|
+
this.retryPolicy = opts.retry ?? {
|
|
572
|
+
maxAttempts: 0,
|
|
573
|
+
baseDelayMs: 1e3
|
|
577
574
|
};
|
|
578
|
-
if (!this.
|
|
579
|
-
|
|
580
|
-
return typeof code === "number" && code >= 500;
|
|
581
|
-
});
|
|
582
|
-
if (this.retry.maxRetries < 0) throw new Error("retry.maxRetries must be non-negative");
|
|
583
|
-
if (this.retry.baseDelayMs < 0) throw new Error("retry.baseDelayMs must be non-negative");
|
|
584
|
-
if (this.retry.jitter !== void 0 && (this.retry.jitter < 0 || this.retry.jitter > 1)) throw new Error("retry.jitter must be between 0 and 1");
|
|
575
|
+
if (!Number.isFinite(this.retryPolicy.maxAttempts) || this.retryPolicy.maxAttempts < 0) throw new Error("retry.maxAttempts must be a non-negative finite number");
|
|
576
|
+
if (this.retryPolicy.baseDelayMs < 0) throw new Error("retry.baseDelayMs must be non-negative");
|
|
585
577
|
this.timeoutMs = opts.timeout?.requestTimeoutMs;
|
|
586
578
|
if (this.timeoutMs !== void 0 && this.timeoutMs < 0) throw new Error("timeout.requestTimeoutMs must be non-negative");
|
|
587
579
|
this.auth = opts["auth"] ?? new NoAuth();
|
|
@@ -611,33 +603,6 @@ var HttpClient = class {
|
|
|
611
603
|
return url.replace(/\/$/, "");
|
|
612
604
|
}
|
|
613
605
|
/**
|
|
614
|
-
* Sleep for a specified duration (used for retry backoff).
|
|
615
|
-
* @private
|
|
616
|
-
*/
|
|
617
|
-
sleep(ms) {
|
|
618
|
-
return new Promise((res) => setTimeout(res, ms));
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Execute a function with retry logic and exponential backoff.
|
|
622
|
-
* @private
|
|
623
|
-
*/
|
|
624
|
-
async withRetry(fn, canRetry) {
|
|
625
|
-
let attempt = 0;
|
|
626
|
-
const { maxRetries, baseDelayMs, jitter = .2 } = this.retry;
|
|
627
|
-
while (true) try {
|
|
628
|
-
return await fn();
|
|
629
|
-
} catch (err) {
|
|
630
|
-
if (attempt >= maxRetries || !canRetry({
|
|
631
|
-
attempt,
|
|
632
|
-
error: err
|
|
633
|
-
})) throw err;
|
|
634
|
-
const backoff = baseDelayMs * 2 ** attempt;
|
|
635
|
-
const j = 1 + (Math.random() * 2 - 1) * jitter;
|
|
636
|
-
await this.sleep(backoff * j);
|
|
637
|
-
attempt++;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
606
|
* Run all registered before-request hooks.
|
|
642
607
|
* @private
|
|
643
608
|
*/
|
|
@@ -729,6 +694,7 @@ var HttpClient = class {
|
|
|
729
694
|
if (init.__urlOverride) url = init.__urlOverride;
|
|
730
695
|
await this.runBeforeHooks(url, init);
|
|
731
696
|
let refreshAttempted = false;
|
|
697
|
+
let retryAttempt = 0;
|
|
732
698
|
const doFetch = async () => {
|
|
733
699
|
while (true) {
|
|
734
700
|
let timeoutId;
|
|
@@ -739,7 +705,10 @@ var HttpClient = class {
|
|
|
739
705
|
}, this.timeoutMs);
|
|
740
706
|
try {
|
|
741
707
|
if (refreshAttempted && !options?.skipAuth) {
|
|
742
|
-
const freshInit = {
|
|
708
|
+
const freshInit = {
|
|
709
|
+
...init,
|
|
710
|
+
headers: typeof init.headers === "object" && !(init.headers instanceof Headers) && !Array.isArray(init.headers) ? { ...init.headers } : init.headers
|
|
711
|
+
};
|
|
743
712
|
await this.auth.apply({
|
|
744
713
|
url,
|
|
745
714
|
init: freshInit,
|
|
@@ -758,6 +727,41 @@ var HttpClient = class {
|
|
|
758
727
|
}
|
|
759
728
|
const status = res.status;
|
|
760
729
|
const contentType = res.headers.get("content-type") || "";
|
|
730
|
+
if (!res.ok) {
|
|
731
|
+
if (!options?.skipRetry && this.retryPolicy.maxAttempts > 0 && retryAttempt < this.retryPolicy.maxAttempts && this.retryPolicy.retryStatusCodes?.includes(status) && this.retryPolicy.retryMethods?.includes(method)) {
|
|
732
|
+
let shouldRetry = true;
|
|
733
|
+
if (this.retryPolicy.shouldRetry) shouldRetry = await this.retryPolicy.shouldRetry({
|
|
734
|
+
url,
|
|
735
|
+
method,
|
|
736
|
+
status,
|
|
737
|
+
attempt: retryAttempt,
|
|
738
|
+
response: res.clone()
|
|
739
|
+
});
|
|
740
|
+
if (shouldRetry) {
|
|
741
|
+
retryAttempt++;
|
|
742
|
+
let delay = this.retryPolicy.baseDelayMs * 2 ** (retryAttempt - 1);
|
|
743
|
+
if (this.retryPolicy.respectRetryAfter) {
|
|
744
|
+
const retryAfter = res.headers.get("Retry-After") || res.headers.get("retry-after");
|
|
745
|
+
if (retryAfter) {
|
|
746
|
+
delay = parseInt(retryAfter, 10) * 1e3;
|
|
747
|
+
this.logger.warn(`Request failed with status ${status}. Retrying after ${delay}ms due to Retry-After header...`, {
|
|
748
|
+
method,
|
|
749
|
+
url,
|
|
750
|
+
status,
|
|
751
|
+
retryAttempt: retryAttempt + 1
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
} else this.logger.warn(`Request failed with status ${status}. Retrying attempt ${retryAttempt} after ${delay}ms...`, {
|
|
755
|
+
method,
|
|
756
|
+
url,
|
|
757
|
+
status,
|
|
758
|
+
retryAttempt
|
|
759
|
+
});
|
|
760
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
761
765
|
let data;
|
|
762
766
|
if (contentType.includes("json")) data = await res.json();
|
|
763
767
|
else if (contentType.includes("application/octet-stream") || contentType.includes("application/pdf") || contentType.includes("image/") || contentType.includes("video/") || contentType.includes("audio/") || contentType.startsWith("application/zip") || contentType.startsWith("application/x-")) data = await res.blob();
|
|
@@ -804,18 +808,7 @@ var HttpClient = class {
|
|
|
804
808
|
}
|
|
805
809
|
}
|
|
806
810
|
};
|
|
807
|
-
|
|
808
|
-
if (error && typeof error === "object" && "name" in error) {
|
|
809
|
-
const errorName = error.name;
|
|
810
|
-
if (errorName === "AbortError" || errorName === "TimeoutError") return false;
|
|
811
|
-
}
|
|
812
|
-
if (error instanceof ApiError && error.status) {
|
|
813
|
-
if (this.retry.retryStatusCodes?.some((codeKey) => HTTPStatusCode[codeKey] === error.status)) return true;
|
|
814
|
-
}
|
|
815
|
-
return false;
|
|
816
|
-
};
|
|
817
|
-
if (options?.skipRetry || !this.retry.retryMethods?.includes(method)) return doFetch();
|
|
818
|
-
return this.withRetry(doFetch, canRetry);
|
|
811
|
+
return doFetch();
|
|
819
812
|
}
|
|
820
813
|
/**
|
|
821
814
|
* Convenience method for GET requests.
|