@ooneex/fetcher 0.0.1
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/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +10 -0
- package/dist/ooneex-fetcher-0.0.1.tgz +0 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ooneex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
# @ooneex/fetcher
|
|
2
|
+
|
|
3
|
+
A powerful and flexible TypeScript/JavaScript HTTP client library built on top of the native Fetch API. This package provides a comprehensive set of methods for making HTTP requests with automatic JSON handling, header management, file uploads, and response status detection.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
✅ **Native Fetch Based** - Built on top of the modern Fetch API
|
|
15
|
+
|
|
16
|
+
✅ **Type-Safe** - Full TypeScript support with proper type definitions
|
|
17
|
+
|
|
18
|
+
✅ **HTTP Methods** - Support for GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
|
|
19
|
+
|
|
20
|
+
✅ **File Upload** - Built-in file and blob upload functionality
|
|
21
|
+
|
|
22
|
+
✅ **Header Management** - Comprehensive header manipulation methods
|
|
23
|
+
|
|
24
|
+
✅ **Authentication** - Bearer token and basic authentication support
|
|
25
|
+
|
|
26
|
+
✅ **Request Aborting** - Built-in AbortController support
|
|
27
|
+
|
|
28
|
+
✅ **Status Detection** - Automatic HTTP status code classification
|
|
29
|
+
|
|
30
|
+
✅ **JSON Handling** - Automatic JSON serialization and deserialization
|
|
31
|
+
|
|
32
|
+
✅ **Error Handling** - Comprehensive error handling and response parsing
|
|
33
|
+
|
|
34
|
+
✅ **Cross-Platform** - Works in Browser, Node.js, Bun, and Deno
|
|
35
|
+
|
|
36
|
+
✅ **Zero Dependencies** - Minimal external dependencies
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
### Bun
|
|
41
|
+
```bash
|
|
42
|
+
bun add @ooneex/fetcher
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### pnpm
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add @ooneex/fetcher
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Yarn
|
|
51
|
+
```bash
|
|
52
|
+
yarn add @ooneex/fetcher
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### npm
|
|
56
|
+
```bash
|
|
57
|
+
npm install @ooneex/fetcher
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### Basic Usage
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Fetcher } from '@ooneex/fetcher';
|
|
66
|
+
|
|
67
|
+
const api = new Fetcher('https://api.example.com');
|
|
68
|
+
|
|
69
|
+
// GET request
|
|
70
|
+
const response = await api.get('/users');
|
|
71
|
+
console.log(response.data); // parsed JSON data
|
|
72
|
+
console.log(response.isSuccessful); // true for 2xx status codes
|
|
73
|
+
|
|
74
|
+
// POST request with JSON data
|
|
75
|
+
const newUser = { name: 'John Doe', email: 'john@example.com' };
|
|
76
|
+
const createResponse = await api.post('/users', newUser);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Authentication
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { Fetcher } from '@ooneex/fetcher';
|
|
83
|
+
|
|
84
|
+
const api = new Fetcher('https://api.example.com');
|
|
85
|
+
|
|
86
|
+
// Bearer token authentication
|
|
87
|
+
api.setBearerToken('your-jwt-token');
|
|
88
|
+
|
|
89
|
+
// Basic authentication
|
|
90
|
+
api.setBasicToken('base64-encoded-credentials');
|
|
91
|
+
|
|
92
|
+
// Make authenticated requests
|
|
93
|
+
const response = await api.get('/protected-resource');
|
|
94
|
+
|
|
95
|
+
// Clear authentication
|
|
96
|
+
api.clearBearerToken();
|
|
97
|
+
api.clearBasicToken();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Header Management
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { Fetcher } from '@ooneex/fetcher';
|
|
104
|
+
|
|
105
|
+
const api = new Fetcher('https://api.example.com');
|
|
106
|
+
|
|
107
|
+
// Set content type
|
|
108
|
+
api.setContentType('application/json');
|
|
109
|
+
|
|
110
|
+
// Set language
|
|
111
|
+
api.setLang('en-US');
|
|
112
|
+
|
|
113
|
+
// Chain header methods
|
|
114
|
+
api.setBearerToken('token')
|
|
115
|
+
.setContentType('application/json')
|
|
116
|
+
.setLang('fr-FR');
|
|
117
|
+
|
|
118
|
+
// Access header object directly
|
|
119
|
+
api.header.set('X-Custom-Header', 'custom-value');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### File Upload
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { Fetcher } from '@ooneex/fetcher';
|
|
126
|
+
|
|
127
|
+
const api = new Fetcher('https://api.example.com');
|
|
128
|
+
|
|
129
|
+
// Upload a file
|
|
130
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
131
|
+
const file = fileInput.files[0];
|
|
132
|
+
|
|
133
|
+
const uploadResponse = await api.upload('/upload', file, 'document');
|
|
134
|
+
|
|
135
|
+
// Upload a blob
|
|
136
|
+
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
|
|
137
|
+
const blobResponse = await api.upload('/upload-text', blob, 'textFile');
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Request Aborting
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { Fetcher } from '@ooneex/fetcher';
|
|
144
|
+
|
|
145
|
+
const api = new Fetcher('https://api.example.com');
|
|
146
|
+
|
|
147
|
+
// Start a request
|
|
148
|
+
const requestPromise = api.get('/slow-endpoint');
|
|
149
|
+
|
|
150
|
+
// Abort the request
|
|
151
|
+
api.abort();
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const response = await requestPromise;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.log('Request was aborted');
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Advanced Usage
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { Fetcher } from '@ooneex/fetcher';
|
|
164
|
+
|
|
165
|
+
const api = new Fetcher('https://api.example.com');
|
|
166
|
+
|
|
167
|
+
// Configure headers and authentication
|
|
168
|
+
api.setBearerToken('jwt-token')
|
|
169
|
+
.setContentType('application/json')
|
|
170
|
+
.setLang('en-US');
|
|
171
|
+
|
|
172
|
+
// Make various HTTP requests
|
|
173
|
+
const users = await api.get('/users');
|
|
174
|
+
const user = await api.post('/users', { name: 'Jane Doe' });
|
|
175
|
+
const updated = await api.put('/users/1', { name: 'Jane Smith' });
|
|
176
|
+
const patched = await api.patch('/users/1', { email: 'jane@example.com' });
|
|
177
|
+
await api.delete('/users/1');
|
|
178
|
+
|
|
179
|
+
// Check response status
|
|
180
|
+
if (user.isSuccessful) {
|
|
181
|
+
console.log('User created:', user.data);
|
|
182
|
+
} else if (user.isClientError) {
|
|
183
|
+
console.log('Client error:', user.message);
|
|
184
|
+
} else if (user.isServerError) {
|
|
185
|
+
console.log('Server error:', user.message);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Clone fetcher for different configurations
|
|
189
|
+
const authenticatedApi = api.clone().setBearerToken('different-token');
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## API Reference
|
|
193
|
+
|
|
194
|
+
### `Fetcher` Class
|
|
195
|
+
|
|
196
|
+
The main class for making HTTP requests.
|
|
197
|
+
|
|
198
|
+
#### Constructor
|
|
199
|
+
|
|
200
|
+
##### `new Fetcher(baseURL: string)`
|
|
201
|
+
Creates a new Fetcher instance with the specified base URL.
|
|
202
|
+
|
|
203
|
+
**Parameters:**
|
|
204
|
+
- `baseURL` - The base URL for all requests
|
|
205
|
+
|
|
206
|
+
**Example:**
|
|
207
|
+
```typescript
|
|
208
|
+
const api = new Fetcher('https://api.example.com');
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### Authentication Methods
|
|
212
|
+
|
|
213
|
+
##### `setBearerToken(token: string): Fetcher`
|
|
214
|
+
Sets the Authorization header with a Bearer token.
|
|
215
|
+
|
|
216
|
+
**Parameters:**
|
|
217
|
+
- `token` - The Bearer token
|
|
218
|
+
|
|
219
|
+
**Returns:** The Fetcher instance for method chaining
|
|
220
|
+
|
|
221
|
+
**Example:**
|
|
222
|
+
```typescript
|
|
223
|
+
api.setBearerToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
##### `setBasicToken(token: string): Fetcher`
|
|
227
|
+
Sets the Authorization header with Basic authentication.
|
|
228
|
+
|
|
229
|
+
**Parameters:**
|
|
230
|
+
- `token` - The base64-encoded credentials
|
|
231
|
+
|
|
232
|
+
**Returns:** The Fetcher instance for method chaining
|
|
233
|
+
|
|
234
|
+
**Example:**
|
|
235
|
+
```typescript
|
|
236
|
+
api.setBasicToken(btoa('username:password'));
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
##### `clearBearerToken(): Fetcher`
|
|
240
|
+
Removes the Authorization header.
|
|
241
|
+
|
|
242
|
+
**Returns:** The Fetcher instance for method chaining
|
|
243
|
+
|
|
244
|
+
##### `clearBasicToken(): Fetcher`
|
|
245
|
+
Removes the Authorization header.
|
|
246
|
+
|
|
247
|
+
**Returns:** The Fetcher instance for method chaining
|
|
248
|
+
|
|
249
|
+
#### Header Management Methods
|
|
250
|
+
|
|
251
|
+
##### `setContentType(contentType: MimeType): Fetcher`
|
|
252
|
+
Sets the Content-Type header.
|
|
253
|
+
|
|
254
|
+
**Parameters:**
|
|
255
|
+
- `contentType` - The MIME type for the content
|
|
256
|
+
|
|
257
|
+
**Returns:** The Fetcher instance for method chaining
|
|
258
|
+
|
|
259
|
+
**Example:**
|
|
260
|
+
```typescript
|
|
261
|
+
api.setContentType('application/json');
|
|
262
|
+
api.setContentType('multipart/form-data');
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
##### `setLang(lang: string): Fetcher`
|
|
266
|
+
Sets the Accept-Language header.
|
|
267
|
+
|
|
268
|
+
**Parameters:**
|
|
269
|
+
- `lang` - The language code
|
|
270
|
+
|
|
271
|
+
**Returns:** The Fetcher instance for method chaining
|
|
272
|
+
|
|
273
|
+
**Example:**
|
|
274
|
+
```typescript
|
|
275
|
+
api.setLang('en-US');
|
|
276
|
+
api.setLang('fr-FR');
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Request Control Methods
|
|
280
|
+
|
|
281
|
+
##### `abort(): Fetcher`
|
|
282
|
+
Aborts the current request and creates a new AbortController.
|
|
283
|
+
|
|
284
|
+
**Returns:** The Fetcher instance for method chaining
|
|
285
|
+
|
|
286
|
+
**Example:**
|
|
287
|
+
```typescript
|
|
288
|
+
api.abort(); // Cancels any ongoing requests
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
##### `clone(): Fetcher`
|
|
292
|
+
Creates a new Fetcher instance with the same base URL.
|
|
293
|
+
|
|
294
|
+
**Returns:** A new Fetcher instance
|
|
295
|
+
|
|
296
|
+
**Example:**
|
|
297
|
+
```typescript
|
|
298
|
+
const newApi = api.clone();
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
#### HTTP Method Shortcuts
|
|
302
|
+
|
|
303
|
+
##### `get<T = unknown>(path: string): Promise<FetcherResponseType<T>>`
|
|
304
|
+
Performs a GET request.
|
|
305
|
+
|
|
306
|
+
**Parameters:**
|
|
307
|
+
- `path` - The endpoint path
|
|
308
|
+
|
|
309
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
310
|
+
|
|
311
|
+
**Example:**
|
|
312
|
+
```typescript
|
|
313
|
+
const users = await api.get<User[]>('/users');
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
##### `post<T = unknown>(path: string, data?: unknown): Promise<FetcherResponseType<T>>`
|
|
317
|
+
Performs a POST request.
|
|
318
|
+
|
|
319
|
+
**Parameters:**
|
|
320
|
+
- `path` - The endpoint path
|
|
321
|
+
- `data` - Optional request body data
|
|
322
|
+
|
|
323
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
324
|
+
|
|
325
|
+
**Example:**
|
|
326
|
+
```typescript
|
|
327
|
+
const newUser = await api.post<User>('/users', { name: 'John' });
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
##### `put<T = unknown>(path: string, data?: unknown): Promise<FetcherResponseType<T>>`
|
|
331
|
+
Performs a PUT request.
|
|
332
|
+
|
|
333
|
+
**Parameters:**
|
|
334
|
+
- `path` - The endpoint path
|
|
335
|
+
- `data` - Optional request body data
|
|
336
|
+
|
|
337
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
338
|
+
|
|
339
|
+
**Example:**
|
|
340
|
+
```typescript
|
|
341
|
+
const updatedUser = await api.put<User>('/users/1', { name: 'Jane' });
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
##### `patch<T = unknown>(path: string, data?: unknown): Promise<FetcherResponseType<T>>`
|
|
345
|
+
Performs a PATCH request.
|
|
346
|
+
|
|
347
|
+
**Parameters:**
|
|
348
|
+
- `path` - The endpoint path
|
|
349
|
+
- `data` - Optional request body data
|
|
350
|
+
|
|
351
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
352
|
+
|
|
353
|
+
**Example:**
|
|
354
|
+
```typescript
|
|
355
|
+
const patchedUser = await api.patch<User>('/users/1', { email: 'new@email.com' });
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
##### `delete<T = unknown>(path: string): Promise<FetcherResponseType<T>>`
|
|
359
|
+
Performs a DELETE request.
|
|
360
|
+
|
|
361
|
+
**Parameters:**
|
|
362
|
+
- `path` - The endpoint path
|
|
363
|
+
|
|
364
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
365
|
+
|
|
366
|
+
**Example:**
|
|
367
|
+
```typescript
|
|
368
|
+
await api.delete('/users/1');
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
##### `head<T = unknown>(path: string): Promise<FetcherResponseType<T>>`
|
|
372
|
+
Performs a HEAD request.
|
|
373
|
+
|
|
374
|
+
**Parameters:**
|
|
375
|
+
- `path` - The endpoint path
|
|
376
|
+
|
|
377
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
378
|
+
|
|
379
|
+
**Example:**
|
|
380
|
+
```typescript
|
|
381
|
+
const headers = await api.head('/users/1');
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
##### `options<T = unknown>(path: string): Promise<FetcherResponseType<T>>`
|
|
385
|
+
Performs an OPTIONS request.
|
|
386
|
+
|
|
387
|
+
**Parameters:**
|
|
388
|
+
- `path` - The endpoint path
|
|
389
|
+
|
|
390
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
391
|
+
|
|
392
|
+
**Example:**
|
|
393
|
+
```typescript
|
|
394
|
+
const options = await api.options('/users');
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
##### `request<T = unknown>(method: HttpMethodType, path: string, data?: unknown): Promise<FetcherResponseType<T>>`
|
|
398
|
+
Performs a custom HTTP request.
|
|
399
|
+
|
|
400
|
+
**Parameters:**
|
|
401
|
+
- `method` - The HTTP method
|
|
402
|
+
- `path` - The endpoint path
|
|
403
|
+
- `data` - Optional request body data
|
|
404
|
+
|
|
405
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
406
|
+
|
|
407
|
+
**Example:**
|
|
408
|
+
```typescript
|
|
409
|
+
const response = await api.request('PATCH', '/users/1', { status: 'active' });
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
##### `upload<T = unknown>(path: string, file: File | Blob, name?: string): Promise<FetcherResponseType<T>>`
|
|
413
|
+
Uploads a file or blob.
|
|
414
|
+
|
|
415
|
+
**Parameters:**
|
|
416
|
+
- `path` - The endpoint path
|
|
417
|
+
- `file` - The file or blob to upload
|
|
418
|
+
- `name` - Optional field name (defaults to "file")
|
|
419
|
+
|
|
420
|
+
**Returns:** Promise resolving to a FetcherResponseType
|
|
421
|
+
|
|
422
|
+
**Example:**
|
|
423
|
+
```typescript
|
|
424
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
425
|
+
const file = fileInput.files[0];
|
|
426
|
+
const response = await api.upload('/upload', file, 'document');
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### Properties
|
|
430
|
+
|
|
431
|
+
##### `header: Header`
|
|
432
|
+
Read-only access to the Header instance for direct header manipulation.
|
|
433
|
+
|
|
434
|
+
**Example:**
|
|
435
|
+
```typescript
|
|
436
|
+
api.header.set('X-API-Key', 'your-api-key');
|
|
437
|
+
const contentType = api.header.get('Content-Type');
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Types
|
|
441
|
+
|
|
442
|
+
#### `FetcherResponseType<T>`
|
|
443
|
+
The response object returned by all HTTP methods.
|
|
444
|
+
|
|
445
|
+
**Properties:**
|
|
446
|
+
- `data: T | null` - The parsed response data
|
|
447
|
+
- `message: string | null` - Error message if parsing failed
|
|
448
|
+
- `header: ReadonlyHeader` - Response headers
|
|
449
|
+
- `isInformational: boolean` - True for 1xx status codes
|
|
450
|
+
- `isSuccessful: boolean` - True for 2xx status codes
|
|
451
|
+
- `isRedirect: boolean` - True for 3xx status codes
|
|
452
|
+
- `isClientError: boolean` - True for 4xx status codes
|
|
453
|
+
- `isServerError: boolean` - True for 5xx status codes
|
|
454
|
+
- `isError: boolean` - True for 4xx or 5xx status codes
|
|
455
|
+
|
|
456
|
+
**Example:**
|
|
457
|
+
```typescript
|
|
458
|
+
const response = await api.get<User[]>('/users');
|
|
459
|
+
|
|
460
|
+
if (response.isSuccessful) {
|
|
461
|
+
console.log('Users:', response.data);
|
|
462
|
+
} else if (response.isClientError) {
|
|
463
|
+
console.error('Client error:', response.message);
|
|
464
|
+
} else if (response.isServerError) {
|
|
465
|
+
console.error('Server error:', response.message);
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
#### `IFetcher`
|
|
470
|
+
Interface defining the Fetcher contract.
|
|
471
|
+
|
|
472
|
+
**Example:**
|
|
473
|
+
```typescript
|
|
474
|
+
import { IFetcher } from '@ooneex/fetcher';
|
|
475
|
+
|
|
476
|
+
class CustomFetcher implements IFetcher {
|
|
477
|
+
// Implement all required methods
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Error Handling
|
|
482
|
+
|
|
483
|
+
The fetcher automatically handles various error scenarios:
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
import { Fetcher } from '@ooneex/fetcher';
|
|
487
|
+
|
|
488
|
+
const api = new Fetcher('https://api.example.com');
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
const response = await api.get('/users');
|
|
492
|
+
|
|
493
|
+
if (response.isSuccessful) {
|
|
494
|
+
// Handle successful response
|
|
495
|
+
console.log('Data:', response.data);
|
|
496
|
+
} else if (response.isClientError) {
|
|
497
|
+
// Handle 4xx errors
|
|
498
|
+
console.error('Client error:', response.message);
|
|
499
|
+
} else if (response.isServerError) {
|
|
500
|
+
// Handle 5xx errors
|
|
501
|
+
console.error('Server error:', response.message);
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
// Handle network errors or other exceptions
|
|
505
|
+
console.error('Request failed:', error);
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Advanced Features
|
|
510
|
+
|
|
511
|
+
### URL Building
|
|
512
|
+
The fetcher automatically handles URL building:
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
const api = new Fetcher('https://api.example.com');
|
|
516
|
+
|
|
517
|
+
// These all work correctly
|
|
518
|
+
await api.get('/users'); // -> https://api.example.com/users
|
|
519
|
+
await api.get('users'); // -> https://api.example.com/users
|
|
520
|
+
await api.get('https://other.com/api'); // -> https://other.com/api (absolute URL)
|
|
521
|
+
|
|
522
|
+
// Base URL with trailing slash
|
|
523
|
+
const api2 = new Fetcher('https://api.example.com/');
|
|
524
|
+
await api2.get('/users'); // -> https://api.example.com/users
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Content Type Handling
|
|
528
|
+
The fetcher automatically sets appropriate Content-Type headers:
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
const api = new Fetcher('https://api.example.com');
|
|
532
|
+
|
|
533
|
+
// Automatic JSON content type for objects
|
|
534
|
+
await api.post('/users', { name: 'John' }); // Content-Type: application/json
|
|
535
|
+
|
|
536
|
+
// FormData handling
|
|
537
|
+
const formData = new FormData();
|
|
538
|
+
formData.append('name', 'John');
|
|
539
|
+
await api.post('/users', formData); // Content-Type: multipart/form-data
|
|
540
|
+
|
|
541
|
+
// String data
|
|
542
|
+
await api.post('/users', 'raw string data');
|
|
543
|
+
|
|
544
|
+
// Blob/ArrayBuffer data
|
|
545
|
+
const blob = new Blob(['data'], { type: 'text/plain' });
|
|
546
|
+
await api.post('/upload', blob);
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## License
|
|
550
|
+
|
|
551
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
552
|
+
|
|
553
|
+
## Contributing
|
|
554
|
+
|
|
555
|
+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
556
|
+
|
|
557
|
+
### Development Setup
|
|
558
|
+
|
|
559
|
+
1. Clone the repository
|
|
560
|
+
2. Install dependencies: `bun install`
|
|
561
|
+
3. Run tests: `bun run test`
|
|
562
|
+
4. Build the project: `bun run build`
|
|
563
|
+
|
|
564
|
+
### Guidelines
|
|
565
|
+
|
|
566
|
+
- Write tests for new features
|
|
567
|
+
- Follow the existing code style
|
|
568
|
+
- Update documentation for API changes
|
|
569
|
+
- Ensure all tests pass before submitting PR
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
Made with ❤️ by the Ooneex team
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Header as Header2 } from "@ooneex/http-header";
|
|
2
|
+
import { MimeType as MimeType2 } from "@ooneex/http-mimes";
|
|
3
|
+
import { ResponseDataType as ResponseDataType2 } from "@ooneex/http-response";
|
|
4
|
+
import { HttpMethodType as HttpMethodType2 } from "@ooneex/types";
|
|
5
|
+
import { Header } from "@ooneex/http-header";
|
|
6
|
+
import { MimeType } from "@ooneex/http-mimes";
|
|
7
|
+
import { ResponseDataType } from "@ooneex/http-response";
|
|
8
|
+
import { HttpMethodType } from "@ooneex/types";
|
|
9
|
+
interface IFetcher {
|
|
10
|
+
readonly header: Header;
|
|
11
|
+
setBearerToken(token: string): IFetcher;
|
|
12
|
+
setBasicToken(token: string): IFetcher;
|
|
13
|
+
clearBearerToken(): IFetcher;
|
|
14
|
+
clearBasicToken(): IFetcher;
|
|
15
|
+
setContentType(contentType: MimeType): IFetcher;
|
|
16
|
+
setLang(lang: string): IFetcher;
|
|
17
|
+
abort(): IFetcher;
|
|
18
|
+
clone(): IFetcher;
|
|
19
|
+
get<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType<T>>;
|
|
20
|
+
post<T extends Record<string, unknown> = Record<string, unknown>>(path: string, data?: unknown): Promise<ResponseDataType<T>>;
|
|
21
|
+
put<T extends Record<string, unknown> = Record<string, unknown>>(path: string, data?: unknown): Promise<ResponseDataType<T>>;
|
|
22
|
+
patch<T extends Record<string, unknown> = Record<string, unknown>>(path: string, data?: unknown): Promise<ResponseDataType<T>>;
|
|
23
|
+
delete<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType<T>>;
|
|
24
|
+
head<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType<T>>;
|
|
25
|
+
options<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType<T>>;
|
|
26
|
+
request<T extends Record<string, unknown> = Record<string, unknown>>(method: HttpMethodType, path: string, data?: unknown): Promise<ResponseDataType<T>>;
|
|
27
|
+
upload<T extends Record<string, unknown> = Record<string, unknown>>(path: string, file: File | Blob, name?: string): Promise<ResponseDataType<T>>;
|
|
28
|
+
}
|
|
29
|
+
declare class Fetcher implements IFetcher {
|
|
30
|
+
private baseURL;
|
|
31
|
+
private abortController;
|
|
32
|
+
readonly header: Header2;
|
|
33
|
+
constructor(baseURL: string);
|
|
34
|
+
setBearerToken(token: string): this;
|
|
35
|
+
setBasicToken(token: string): this;
|
|
36
|
+
clearBearerToken(): this;
|
|
37
|
+
clearBasicToken(): this;
|
|
38
|
+
setContentType(contentType: MimeType2): this;
|
|
39
|
+
setLang(lang: string): this;
|
|
40
|
+
abort(): this;
|
|
41
|
+
clone(): Fetcher;
|
|
42
|
+
get<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType2<T>>;
|
|
43
|
+
post<T extends Record<string, unknown> = Record<string, unknown>>(path: string, data?: unknown): Promise<ResponseDataType2<T>>;
|
|
44
|
+
put<T extends Record<string, unknown> = Record<string, unknown>>(path: string, data?: unknown): Promise<ResponseDataType2<T>>;
|
|
45
|
+
patch<T extends Record<string, unknown> = Record<string, unknown>>(path: string, data?: unknown): Promise<ResponseDataType2<T>>;
|
|
46
|
+
delete<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType2<T>>;
|
|
47
|
+
head<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType2<T>>;
|
|
48
|
+
options<T extends Record<string, unknown> = Record<string, unknown>>(path: string): Promise<ResponseDataType2<T>>;
|
|
49
|
+
request<T extends Record<string, unknown> = Record<string, unknown>>(method: HttpMethodType2, path: string, data?: unknown): Promise<ResponseDataType2<T>>;
|
|
50
|
+
upload<T extends Record<string, unknown> = Record<string, unknown>>(path: string, file: File | Blob, name?: string): Promise<ResponseDataType2<T>>;
|
|
51
|
+
private buildURL;
|
|
52
|
+
private buildRequestOptions;
|
|
53
|
+
}
|
|
54
|
+
export { IFetcher, Fetcher };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{Header as c}from"@ooneex/http-header";class T{baseURL;abortController;header=new c;constructor(e){this.baseURL=e;this.abortController=new AbortController}setBearerToken(e){return this.header.setBearerToken(e),this}setBasicToken(e){return this.header.setBasicAuth(e),this}clearBearerToken(){return this.header.remove("Authorization"),this}clearBasicToken(){return this.header.remove("Authorization"),this}setContentType(e){return this.header.contentType(e),this}setLang(e){return this.header.setLang(e),this}abort(){return this.abortController.abort(),this.abortController=new AbortController,this}clone(){return new T(this.baseURL)}async get(e){return this.request("GET",e)}async post(e,n){return this.request("POST",e,n)}async put(e,n){return this.request("PUT",e,n)}async patch(e,n){return this.request("PATCH",e,n)}async delete(e){return this.request("DELETE",e)}async head(e){return this.request("HEAD",e)}async options(e){return this.request("OPTIONS",e)}async request(e,n,r){let s=this.buildURL(n),o=this.buildRequestOptions(e,r),i=await fetch(s,o);try{return await i.json()}catch(t){throw Error(t instanceof Error?t.message:"Failed to parse JSON response")}}async upload(e,n,r="file"){let s=new FormData;s.append(r,n);let o=this.header.get("Content-Type");this.header.remove("Content-Type");let i=await this.request("POST",e,s);if(o)this.header.set("Content-Type",o);return i}buildURL(e){if(e.startsWith("http://")||e.startsWith("https://"))return e;let n=e.startsWith("/")?e:`/${e}`;return`${this.baseURL.endsWith("/")?this.baseURL.slice(0,-1):this.baseURL}${n}`}buildRequestOptions(e,n){let r=this.header.native,s;if(n!==void 0&&e!=="GET"&&e!=="HEAD"&&e!=="OPTIONS"){if(n instanceof FormData)s=n,this.header.clearContentType();else if(typeof n==="string")s=n;else if(n instanceof Blob||n instanceof ArrayBuffer)s=n;else if(s=JSON.stringify(n),!this.header.has("Content-Type"))this.header.setJson()}return{method:e,headers:r,...s!==void 0&&{body:s},signal:this.abortController.signal}}}export{T as Fetcher};
|
|
2
|
+
|
|
3
|
+
//# debugId=8AF8D142203AB99D64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["src/Fetcher.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { Header } from \"@ooneex/http-header\";\nimport type { MimeType } from \"@ooneex/http-mimes\";\nimport type { ResponseDataType } from \"@ooneex/http-response\";\nimport type { HttpMethodType } from \"@ooneex/types\";\nimport type { IFetcher } from \"./types\";\n\nexport class Fetcher implements IFetcher {\n private abortController: AbortController;\n public readonly header: Header = new Header();\n\n constructor(private baseURL: string) {\n this.abortController = new AbortController();\n }\n\n public setBearerToken(token: string): this {\n this.header.setBearerToken(token);\n\n return this;\n }\n\n public setBasicToken(token: string): this {\n this.header.setBasicAuth(token);\n\n return this;\n }\n\n public clearBearerToken(): this {\n this.header.remove(\"Authorization\");\n\n return this;\n }\n\n public clearBasicToken(): this {\n this.header.remove(\"Authorization\");\n\n return this;\n }\n\n public setContentType(contentType: MimeType): this {\n this.header.contentType(contentType);\n\n return this;\n }\n\n public setLang(lang: string): this {\n this.header.setLang(lang);\n\n return this;\n }\n\n public abort(): this {\n this.abortController.abort();\n this.abortController = new AbortController();\n\n return this;\n }\n\n public clone(): Fetcher {\n return new Fetcher(this.baseURL);\n }\n\n public async get<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n ): Promise<ResponseDataType<T>> {\n return this.request<T>(\"GET\", path);\n }\n\n public async post<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n data?: unknown,\n ): Promise<ResponseDataType<T>> {\n return this.request<T>(\"POST\", path, data);\n }\n\n public async put<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n data?: unknown,\n ): Promise<ResponseDataType<T>> {\n return this.request<T>(\"PUT\", path, data);\n }\n\n public async patch<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n data?: unknown,\n ): Promise<ResponseDataType<T>> {\n return this.request<T>(\"PATCH\", path, data);\n }\n\n public async delete<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n ): Promise<ResponseDataType<T>> {\n return this.request<T>(\"DELETE\", path);\n }\n\n public async head<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n ): Promise<ResponseDataType<T>> {\n return this.request<T>(\"HEAD\", path);\n }\n\n public async options<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n ): Promise<ResponseDataType<T>> {\n return this.request<T>(\"OPTIONS\", path);\n }\n\n public async request<T extends Record<string, unknown> = Record<string, unknown>>(\n method: HttpMethodType,\n path: string,\n data?: unknown,\n ): Promise<ResponseDataType<T>> {\n const fullURL = this.buildURL(path);\n const requestOptions = this.buildRequestOptions(method, data);\n const response = await fetch(fullURL, requestOptions);\n\n try {\n return await response.json();\n } catch (error) {\n throw new Error(error instanceof Error ? error.message : \"Failed to parse JSON response\");\n }\n }\n\n public async upload<T extends Record<string, unknown> = Record<string, unknown>>(\n path: string,\n file: File | Blob,\n name = \"file\",\n ): Promise<ResponseDataType<T>> {\n const formData = new FormData();\n formData.append(name, file);\n\n // Clear any existing Content-Type to let browser set multipart/form-data boundary\n const originalContentType = this.header.get(\"Content-Type\");\n this.header.remove(\"Content-Type\");\n\n const result = await this.request<T>(\"POST\", path, formData);\n\n // Restore original Content-Type if it existed\n if (originalContentType) {\n this.header.set(\"Content-Type\", originalContentType);\n }\n\n return result;\n }\n\n private buildURL(url: string): string {\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {\n return url;\n }\n\n const path = url.startsWith(\"/\") ? url : `/${url}`;\n const baseURL = this.baseURL.endsWith(\"/\") ? this.baseURL.slice(0, -1) : this.baseURL;\n\n return `${baseURL}${path}`;\n }\n\n private buildRequestOptions(method: string, data?: unknown): RequestInit {\n const headers = this.header.native;\n\n let body: BodyInit | undefined;\n\n if (data !== undefined && method !== \"GET\" && method !== \"HEAD\" && method !== \"OPTIONS\") {\n if (data instanceof FormData) {\n body = data;\n this.header.clearContentType();\n } else if (typeof data === \"string\") {\n body = data;\n } else if (data instanceof Blob || data instanceof ArrayBuffer) {\n body = data;\n } else {\n body = JSON.stringify(data);\n // Set Content-Type to application/json if not already set\n if (!this.header.has(\"Content-Type\")) {\n this.header.setJson();\n }\n }\n }\n\n const requestOptions: RequestInit = {\n method,\n headers,\n ...(body !== undefined && { body }),\n signal: this.abortController.signal,\n };\n\n return requestOptions;\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "8AF8D142203AB99D64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ooneex/fetcher",
|
|
3
|
+
"description": "",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"README.md",
|
|
10
|
+
"package.json"
|
|
11
|
+
],
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "bun test tests",
|
|
26
|
+
"build": "bunup",
|
|
27
|
+
"lint": "tsgo --noEmit && bunx biome lint",
|
|
28
|
+
"publish:prod": "bun publish --tolerate-republish --access public",
|
|
29
|
+
"publish:pack": "bun pm pack --destination ./dist",
|
|
30
|
+
"publish:dry": "bun publish --dry-run"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@ooneex/http-header": "0.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@ooneex/http-response": "0.0.1",
|
|
37
|
+
"@ooneex/http-mimes": "0.0.1",
|
|
38
|
+
"@ooneex/types": "0.0.1"
|
|
39
|
+
}
|
|
40
|
+
}
|