@zogjs/http 0.4.8
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 +850 -0
- package/dist/zog-http.es.js +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
# ZogHttp Plugin v1.0.0
|
|
2
|
+
|
|
3
|
+
A powerful, production-ready HTTP client plugin for Zog.js with full support for all HTTP methods, file uploads with progress tracking, request/response interceptors, and reactive state management.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
|
|
8
|
+
- ✅ Request/Response interceptors (before/after hooks)
|
|
9
|
+
- ✅ File upload with real-time progress tracking
|
|
10
|
+
- ✅ File download with progress tracking
|
|
11
|
+
- ✅ Authentication token management (Bearer, Basic)
|
|
12
|
+
- ✅ Custom headers support
|
|
13
|
+
- ✅ Automatic JSON parsing
|
|
14
|
+
- ✅ Request cancellation via AbortController
|
|
15
|
+
- ✅ Reactive loading/error states
|
|
16
|
+
- ✅ Timeout configuration
|
|
17
|
+
- ✅ Base URL configuration
|
|
18
|
+
- ✅ Automatic retry mechanism
|
|
19
|
+
- ✅ TypeScript-friendly API
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import { createApp } from 'zogjs';
|
|
26
|
+
import { ZogHttpPlugin } from '@zogjs/http';
|
|
27
|
+
|
|
28
|
+
createApp(() => ({
|
|
29
|
+
// your data
|
|
30
|
+
}))
|
|
31
|
+
.use(ZogHttpPlugin, {
|
|
32
|
+
baseURL: 'https://api.example.com',
|
|
33
|
+
timeout: 30000,
|
|
34
|
+
})
|
|
35
|
+
.mount('#app');
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<div id="app">
|
|
43
|
+
<div z-if="$http.state.loading">Loading...</div>
|
|
44
|
+
<div z-if="$http.state.error">{{ $http.state.error }}</div>
|
|
45
|
+
|
|
46
|
+
<ul>
|
|
47
|
+
<li z-for="user in users" :key="user.id">{{ user.name }}</li>
|
|
48
|
+
</ul>
|
|
49
|
+
|
|
50
|
+
<button @click="loadUsers">Load Users</button>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<script type="module">
|
|
54
|
+
import { createApp, reactive } from './zog.js';
|
|
55
|
+
import { ZogHttpPlugin } from './zog-http.js';
|
|
56
|
+
|
|
57
|
+
createApp(() => {
|
|
58
|
+
const users = reactive([]);
|
|
59
|
+
|
|
60
|
+
async function loadUsers() {
|
|
61
|
+
try {
|
|
62
|
+
const response = await this.$http.get('/users');
|
|
63
|
+
users.splice(0, users.length, ...response.data);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Failed to load users:', error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { users, loadUsers };
|
|
70
|
+
})
|
|
71
|
+
.use(ZogHttpPlugin, { baseURL: 'https://api.example.com' })
|
|
72
|
+
.mount('#app');
|
|
73
|
+
</script>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Configuration Options
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
.use(ZogHttpPlugin, {
|
|
80
|
+
// Base URL for all requests
|
|
81
|
+
baseURL: 'https://api.example.com',
|
|
82
|
+
|
|
83
|
+
// Default timeout in milliseconds
|
|
84
|
+
timeout: 30000,
|
|
85
|
+
|
|
86
|
+
// Default headers for all requests
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
'X-Custom-Header': 'value',
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Include credentials (cookies) in requests
|
|
93
|
+
withCredentials: false,
|
|
94
|
+
|
|
95
|
+
// Number of automatic retries on failure
|
|
96
|
+
retries: 0,
|
|
97
|
+
|
|
98
|
+
// Delay between retries in milliseconds
|
|
99
|
+
retryDelay: 1000,
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API Reference
|
|
104
|
+
|
|
105
|
+
### HTTP Methods
|
|
106
|
+
|
|
107
|
+
All methods return a Promise that resolves to a response object:
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Response object structure
|
|
111
|
+
{
|
|
112
|
+
data: any, // Parsed response body
|
|
113
|
+
status: number, // HTTP status code
|
|
114
|
+
statusText: string, // HTTP status text
|
|
115
|
+
headers: object, // Response headers
|
|
116
|
+
config: object, // Request configuration
|
|
117
|
+
request: object, // Original request info
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### GET Request
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// Simple GET
|
|
125
|
+
const response = await this.$http.get('/users');
|
|
126
|
+
|
|
127
|
+
// GET with query parameters
|
|
128
|
+
const response = await this.$http.get('/users', {
|
|
129
|
+
params: { page: 1, limit: 10, status: 'active' }
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// GET with custom headers
|
|
133
|
+
const response = await this.$http.get('/users', {
|
|
134
|
+
headers: { 'X-Request-ID': '123' }
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### POST Request
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Simple POST
|
|
142
|
+
const response = await this.$http.post('/users', {
|
|
143
|
+
name: 'John Doe',
|
|
144
|
+
email: 'john@example.com'
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// POST with additional options
|
|
148
|
+
const response = await this.$http.post('/users',
|
|
149
|
+
{ name: 'John' },
|
|
150
|
+
{ headers: { 'X-Custom': 'value' } }
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### PUT Request
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const response = await this.$http.put('/users/123', {
|
|
158
|
+
name: 'Jane Doe',
|
|
159
|
+
email: 'jane@example.com'
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### PATCH Request
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const response = await this.$http.patch('/users/123', {
|
|
167
|
+
name: 'Updated Name'
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### DELETE Request
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
const response = await this.$http.delete('/users/123');
|
|
175
|
+
|
|
176
|
+
// DELETE with body
|
|
177
|
+
const response = await this.$http.delete('/users', {
|
|
178
|
+
body: { ids: [1, 2, 3] }
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### HEAD & OPTIONS
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const headResponse = await this.$http.head('/users');
|
|
186
|
+
const optionsResponse = await this.$http.options('/users');
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Shorthand Methods
|
|
190
|
+
|
|
191
|
+
For convenience, shorthand methods are also injected into scope:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
await this.$get('/users');
|
|
195
|
+
await this.$post('/users', data);
|
|
196
|
+
await this.$put('/users/1', data);
|
|
197
|
+
await this.$patch('/users/1', data);
|
|
198
|
+
await this.$delete('/users/1');
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Authentication
|
|
202
|
+
|
|
203
|
+
#### Bearer Token
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
// Set token
|
|
207
|
+
this.$http.setAuthToken('your-jwt-token');
|
|
208
|
+
|
|
209
|
+
// Clear token
|
|
210
|
+
this.$http.clearAuth();
|
|
211
|
+
|
|
212
|
+
// Or set directly
|
|
213
|
+
this.$http.setHeader('Authorization', 'Bearer your-token');
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Basic Authentication
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
this.$http.setBasicAuth('username', 'password');
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Headers Management
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
// Set single header
|
|
226
|
+
this.$http.setHeader('X-API-Key', 'your-api-key');
|
|
227
|
+
|
|
228
|
+
// Set multiple headers
|
|
229
|
+
this.$http.setHeaders({
|
|
230
|
+
'X-API-Key': 'key',
|
|
231
|
+
'X-Client-Version': '1.0.0'
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Remove header
|
|
235
|
+
this.$http.removeHeader('X-API-Key');
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Base URL
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// Change base URL at runtime
|
|
242
|
+
this.$http.setBaseURL('https://api.newdomain.com');
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Timeout
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// Set global timeout
|
|
249
|
+
this.$http.setTimeout(60000); // 60 seconds
|
|
250
|
+
|
|
251
|
+
// Per-request timeout
|
|
252
|
+
await this.$http.get('/slow-endpoint', { timeout: 120000 });
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Interceptors
|
|
256
|
+
|
|
257
|
+
Interceptors allow you to run code before requests are sent and after responses are received.
|
|
258
|
+
|
|
259
|
+
### Request Interceptors
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
// Add request interceptor
|
|
263
|
+
const interceptorId = this.$http.addRequestInterceptor(
|
|
264
|
+
// Success handler (called before each request)
|
|
265
|
+
async (config) => {
|
|
266
|
+
// Add timestamp to all requests
|
|
267
|
+
config.headers['X-Request-Time'] = Date.now();
|
|
268
|
+
|
|
269
|
+
// Add token from storage
|
|
270
|
+
const token = localStorage.getItem('token');
|
|
271
|
+
if (token) {
|
|
272
|
+
config.headers['Authorization'] = `Bearer ${token}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log('Request:', config.method, config.url);
|
|
276
|
+
return config; // Must return config
|
|
277
|
+
},
|
|
278
|
+
// Error handler (optional)
|
|
279
|
+
async (error) => {
|
|
280
|
+
console.error('Request error:', error);
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Remove interceptor later
|
|
286
|
+
this.$http.removeRequestInterceptor(interceptorId);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Response Interceptors
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
// Add response interceptor
|
|
293
|
+
const interceptorId = this.$http.addResponseInterceptor(
|
|
294
|
+
// Success handler
|
|
295
|
+
async (response) => {
|
|
296
|
+
console.log('Response:', response.status, response.data);
|
|
297
|
+
|
|
298
|
+
// Transform response data
|
|
299
|
+
if (response.data?.items) {
|
|
300
|
+
response.data = response.data.items;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return response;
|
|
304
|
+
},
|
|
305
|
+
// Error handler
|
|
306
|
+
async (error) => {
|
|
307
|
+
if (error.status === 401) {
|
|
308
|
+
// Handle unauthorized - redirect to login
|
|
309
|
+
window.location.href = '/login';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (error.status === 429) {
|
|
313
|
+
// Handle rate limiting - wait and retry
|
|
314
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
315
|
+
return this.$http.request(error.request);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Clear All Interceptors
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
this.$http.clearInterceptors();
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## File Upload
|
|
330
|
+
|
|
331
|
+
The upload method provides real-time progress tracking and supports single/multiple files.
|
|
332
|
+
|
|
333
|
+
### Basic Upload
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
// Single file upload
|
|
337
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
338
|
+
const file = fileInput.files[0];
|
|
339
|
+
|
|
340
|
+
const { promise, tracker, abort } = this.$http.upload('/upload', file);
|
|
341
|
+
|
|
342
|
+
// Access reactive progress state
|
|
343
|
+
console.log(tracker.progress); // 0-100
|
|
344
|
+
console.log(tracker.status); // 'idle' | 'uploading' | 'completed' | 'error'
|
|
345
|
+
|
|
346
|
+
const response = await promise;
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Upload with Progress Callbacks
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const { promise, tracker, abort } = this.$http.upload('/upload', file, {
|
|
353
|
+
// Field name for the file
|
|
354
|
+
fieldName: 'document',
|
|
355
|
+
|
|
356
|
+
// Additional form data
|
|
357
|
+
additionalData: {
|
|
358
|
+
description: 'My file',
|
|
359
|
+
category: 'documents'
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
// Custom headers
|
|
363
|
+
headers: {
|
|
364
|
+
'X-Upload-ID': 'unique-id'
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
// Progress callback
|
|
368
|
+
onProgress: (info) => {
|
|
369
|
+
console.log(`Progress: ${info.progress}%`);
|
|
370
|
+
console.log(`Speed: ${(info.speed / 1024).toFixed(2)} KB/s`);
|
|
371
|
+
console.log(`Remaining: ${info.remainingTime}s`);
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
// Completion callback
|
|
375
|
+
onComplete: (response) => {
|
|
376
|
+
console.log('Upload complete!', response.data);
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
// Error callback
|
|
380
|
+
onError: (error) => {
|
|
381
|
+
console.error('Upload failed:', error.message);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Multiple Files Upload
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
const files = document.querySelector('input[type="file"]').files;
|
|
390
|
+
|
|
391
|
+
const { promise, tracker } = this.$http.upload('/upload', Array.from(files), {
|
|
392
|
+
fieldName: 'files',
|
|
393
|
+
additionalData: { albumId: '123' }
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Upload with FormData
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
const formData = new FormData();
|
|
401
|
+
formData.append('file', file);
|
|
402
|
+
formData.append('name', 'custom name');
|
|
403
|
+
|
|
404
|
+
const { promise } = this.$http.upload('/upload', formData);
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Cancel Upload
|
|
408
|
+
|
|
409
|
+
```javascript
|
|
410
|
+
const { promise, abort, requestId } = this.$http.upload('/upload', file);
|
|
411
|
+
|
|
412
|
+
// Cancel by abort function
|
|
413
|
+
abort();
|
|
414
|
+
|
|
415
|
+
// Or cancel by request ID
|
|
416
|
+
this.$http.cancelRequest(requestId);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Reactive Upload State in Template
|
|
420
|
+
|
|
421
|
+
```html
|
|
422
|
+
<div id="app">
|
|
423
|
+
<input type="file" @change="handleFileSelect" />
|
|
424
|
+
|
|
425
|
+
<div z-if="uploadState.status === 'uploading'">
|
|
426
|
+
<div class="progress-bar">
|
|
427
|
+
<div :style="{ width: uploadState.progress + '%' }"></div>
|
|
428
|
+
</div>
|
|
429
|
+
<p>{{ uploadState.progress }}% - {{ formatSpeed(uploadState.speed) }}</p>
|
|
430
|
+
<p>Remaining: {{ uploadState.remainingTime }}s</p>
|
|
431
|
+
<button @click="cancelUpload">Cancel</button>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<div z-if="uploadState.status === 'completed'">
|
|
435
|
+
Upload complete! ✓
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<div z-if="uploadState.status === 'error'">
|
|
439
|
+
Error: {{ uploadState.error }}
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<script type="module">
|
|
444
|
+
createApp(() => {
|
|
445
|
+
const uploadState = reactive({
|
|
446
|
+
progress: 0,
|
|
447
|
+
status: 'idle',
|
|
448
|
+
speed: 0,
|
|
449
|
+
remainingTime: 0,
|
|
450
|
+
error: null
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
let abortFn = null;
|
|
454
|
+
|
|
455
|
+
async function handleFileSelect(e) {
|
|
456
|
+
const file = e.target.files[0];
|
|
457
|
+
if (!file) return;
|
|
458
|
+
|
|
459
|
+
const { promise, tracker, abort } = this.$upload('/upload', file);
|
|
460
|
+
|
|
461
|
+
// Sync tracker state with local state
|
|
462
|
+
Object.assign(uploadState, tracker);
|
|
463
|
+
abortFn = abort;
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
await promise;
|
|
467
|
+
} catch (err) {
|
|
468
|
+
// Error already handled via tracker
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function cancelUpload() {
|
|
473
|
+
if (abortFn) abortFn();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function formatSpeed(bytesPerSec) {
|
|
477
|
+
return (bytesPerSec / 1024).toFixed(2) + ' KB/s';
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return { uploadState, handleFileSelect, cancelUpload, formatSpeed };
|
|
481
|
+
}).use(ZogHttpPlugin).mount('#app');
|
|
482
|
+
</script>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## File Download
|
|
486
|
+
|
|
487
|
+
Download files with progress tracking and optional auto-save.
|
|
488
|
+
|
|
489
|
+
### Basic Download
|
|
490
|
+
|
|
491
|
+
```javascript
|
|
492
|
+
const { promise, tracker, abort } = this.$http.download('/files/report.pdf', {
|
|
493
|
+
filename: 'report.pdf', // Auto-triggers download
|
|
494
|
+
|
|
495
|
+
onProgress: (info) => {
|
|
496
|
+
console.log(`Downloaded: ${info.progress}%`);
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
onComplete: ({ blob, filename, size }) => {
|
|
500
|
+
console.log(`Downloaded ${filename} (${size} bytes)`);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const { blob } = await promise;
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Download without Auto-Save
|
|
508
|
+
|
|
509
|
+
```javascript
|
|
510
|
+
const { promise } = this.$http.download('/files/image.jpg');
|
|
511
|
+
const { blob } = await promise;
|
|
512
|
+
|
|
513
|
+
// Process blob manually
|
|
514
|
+
const imageUrl = URL.createObjectURL(blob);
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## Reactive State
|
|
518
|
+
|
|
519
|
+
The plugin provides reactive global state:
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
// Access in JavaScript
|
|
523
|
+
this.$http.state.loading // true when any request is pending
|
|
524
|
+
this.$http.state.error // Last error message
|
|
525
|
+
this.$http.state.pendingRequests // Number of pending requests
|
|
526
|
+
this.$http.state.lastRequest // Info about last request
|
|
527
|
+
|
|
528
|
+
// Access in template
|
|
529
|
+
<div z-show="$http.state.loading">Loading...</div>
|
|
530
|
+
<div z-if="$http.state.pendingRequests > 0">
|
|
531
|
+
{{ $http.state.pendingRequests }} requests in progress
|
|
532
|
+
</div>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## Request Cancellation
|
|
536
|
+
|
|
537
|
+
### Cancel Single Request
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
const { requestId } = this.$http.upload('/upload', file);
|
|
541
|
+
|
|
542
|
+
// Cancel by ID
|
|
543
|
+
this.$http.cancelRequest(requestId);
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Cancel All Requests
|
|
547
|
+
|
|
548
|
+
```javascript
|
|
549
|
+
// Cancel all pending requests
|
|
550
|
+
this.$http.cancelAll();
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## Error Handling
|
|
554
|
+
|
|
555
|
+
### HttpError Object
|
|
556
|
+
|
|
557
|
+
```javascript
|
|
558
|
+
try {
|
|
559
|
+
await this.$http.get('/users');
|
|
560
|
+
} catch (error) {
|
|
561
|
+
if (error.isHttpError) {
|
|
562
|
+
console.log(error.status); // 404, 500, etc.
|
|
563
|
+
console.log(error.message); // Error message
|
|
564
|
+
console.log(error.response); // Full response object
|
|
565
|
+
console.log(error.request); // Original request info
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### HTTP Status Codes
|
|
571
|
+
|
|
572
|
+
```javascript
|
|
573
|
+
import { HttpStatus } from './zog-http.js';
|
|
574
|
+
|
|
575
|
+
if (error.status === HttpStatus.NOT_FOUND) {
|
|
576
|
+
// Handle 404
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (error.status === HttpStatus.UNAUTHORIZED) {
|
|
580
|
+
// Handle 401
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Creating Instances
|
|
585
|
+
|
|
586
|
+
Create multiple HTTP clients with different configurations:
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
// Create a new instance
|
|
590
|
+
const adminApi = this.$http.create({
|
|
591
|
+
baseURL: 'https://admin.api.com',
|
|
592
|
+
headers: { 'X-Admin-Key': 'secret' }
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
await adminApi.get('/dashboard');
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## Standalone Usage
|
|
599
|
+
|
|
600
|
+
Use without Zog.js:
|
|
601
|
+
|
|
602
|
+
```javascript
|
|
603
|
+
import { createHttpClient } from './zog-http.js';
|
|
604
|
+
|
|
605
|
+
const http = createHttpClient({
|
|
606
|
+
baseURL: 'https://api.example.com',
|
|
607
|
+
timeout: 30000
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// Add interceptor
|
|
611
|
+
http.addRequestInterceptor(config => {
|
|
612
|
+
config.headers['Authorization'] = 'Bearer token';
|
|
613
|
+
return config;
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Make requests
|
|
617
|
+
const users = await http.get('/users');
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Complete Example
|
|
621
|
+
|
|
622
|
+
```html
|
|
623
|
+
<!DOCTYPE html>
|
|
624
|
+
<html>
|
|
625
|
+
<head>
|
|
626
|
+
<title>ZogHttp Demo</title>
|
|
627
|
+
<style>
|
|
628
|
+
.loading { opacity: 0.5; pointer-events: none; }
|
|
629
|
+
.progress-bar { width: 100%; height: 20px; background: #eee; }
|
|
630
|
+
.progress-fill { height: 100%; background: #4caf50; transition: width 0.3s; }
|
|
631
|
+
.error { color: red; }
|
|
632
|
+
</style>
|
|
633
|
+
</head>
|
|
634
|
+
<body>
|
|
635
|
+
<div id="app">
|
|
636
|
+
<!-- Global loading indicator -->
|
|
637
|
+
<div z-show="$http.state.loading" class="loading-overlay">
|
|
638
|
+
Loading...
|
|
639
|
+
</div>
|
|
640
|
+
|
|
641
|
+
<!-- User list -->
|
|
642
|
+
<section>
|
|
643
|
+
<h2>Users</h2>
|
|
644
|
+
<button @click="fetchUsers" :disabled="$http.state.loading">
|
|
645
|
+
Refresh Users
|
|
646
|
+
</button>
|
|
647
|
+
<ul>
|
|
648
|
+
<li z-for="user in users" :key="user.id">
|
|
649
|
+
{{ user.name }} ({{ user.email }})
|
|
650
|
+
<button @click="deleteUser(user.id)">Delete</button>
|
|
651
|
+
</li>
|
|
652
|
+
</ul>
|
|
653
|
+
</section>
|
|
654
|
+
|
|
655
|
+
<!-- Add user form -->
|
|
656
|
+
<section>
|
|
657
|
+
<h2>Add User</h2>
|
|
658
|
+
<input z-model="newUser.name" placeholder="Name" />
|
|
659
|
+
<input z-model="newUser.email" placeholder="Email" />
|
|
660
|
+
<button @click="addUser">Add User</button>
|
|
661
|
+
</section>
|
|
662
|
+
|
|
663
|
+
<!-- File upload -->
|
|
664
|
+
<section>
|
|
665
|
+
<h2>Upload Avatar</h2>
|
|
666
|
+
<input type="file" @change="handleUpload" accept="image/*" />
|
|
667
|
+
|
|
668
|
+
<div z-if="upload.status === 'uploading'">
|
|
669
|
+
<div class="progress-bar">
|
|
670
|
+
<div class="progress-fill" :style="{ width: upload.progress + '%' }"></div>
|
|
671
|
+
</div>
|
|
672
|
+
<span>{{ upload.progress }}%</span>
|
|
673
|
+
<button @click="cancelUpload">Cancel</button>
|
|
674
|
+
</div>
|
|
675
|
+
|
|
676
|
+
<div z-if="upload.status === 'completed'" style="color: green;">
|
|
677
|
+
Upload complete! ✓
|
|
678
|
+
</div>
|
|
679
|
+
|
|
680
|
+
<div z-if="upload.status === 'error'" class="error">
|
|
681
|
+
{{ upload.error }}
|
|
682
|
+
</div>
|
|
683
|
+
</section>
|
|
684
|
+
|
|
685
|
+
<!-- Error display -->
|
|
686
|
+
<div z-if="error" class="error">
|
|
687
|
+
{{ error }}
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
<script type="module">
|
|
692
|
+
import { createApp, ref, reactive } from './zog.js';
|
|
693
|
+
import { ZogHttpPlugin } from './zog-http.js';
|
|
694
|
+
|
|
695
|
+
createApp(() => {
|
|
696
|
+
// Reactive data
|
|
697
|
+
const users = reactive([]);
|
|
698
|
+
const newUser = reactive({ name: '', email: '' });
|
|
699
|
+
const error = ref('');
|
|
700
|
+
const upload = reactive({
|
|
701
|
+
progress: 0,
|
|
702
|
+
status: 'idle',
|
|
703
|
+
error: null
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
let abortUpload = null;
|
|
707
|
+
|
|
708
|
+
// Fetch users
|
|
709
|
+
async function fetchUsers() {
|
|
710
|
+
try {
|
|
711
|
+
error.value = '';
|
|
712
|
+
const response = await this.$http.get('/users');
|
|
713
|
+
users.splice(0, users.length, ...response.data);
|
|
714
|
+
} catch (e) {
|
|
715
|
+
error.value = e.message;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Add user
|
|
720
|
+
async function addUser() {
|
|
721
|
+
if (!newUser.name || !newUser.email) return;
|
|
722
|
+
|
|
723
|
+
try {
|
|
724
|
+
error.value = '';
|
|
725
|
+
const response = await this.$http.post('/users', {
|
|
726
|
+
name: newUser.name,
|
|
727
|
+
email: newUser.email
|
|
728
|
+
});
|
|
729
|
+
users.push(response.data);
|
|
730
|
+
newUser.name = '';
|
|
731
|
+
newUser.email = '';
|
|
732
|
+
} catch (e) {
|
|
733
|
+
error.value = e.message;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Delete user
|
|
738
|
+
async function deleteUser(id) {
|
|
739
|
+
try {
|
|
740
|
+
error.value = '';
|
|
741
|
+
await this.$http.delete(`/users/${id}`);
|
|
742
|
+
const index = users.findIndex(u => u.id === id);
|
|
743
|
+
if (index > -1) users.splice(index, 1);
|
|
744
|
+
} catch (e) {
|
|
745
|
+
error.value = e.message;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Handle file upload
|
|
750
|
+
function handleUpload(event) {
|
|
751
|
+
const file = event.target.files[0];
|
|
752
|
+
if (!file) return;
|
|
753
|
+
|
|
754
|
+
const { promise, tracker, abort } = this.$upload('/avatar', file, {
|
|
755
|
+
fieldName: 'avatar',
|
|
756
|
+
additionalData: { userId: 1 },
|
|
757
|
+
onProgress: (info) => {
|
|
758
|
+
upload.progress = info.progress;
|
|
759
|
+
},
|
|
760
|
+
onComplete: () => {
|
|
761
|
+
upload.status = 'completed';
|
|
762
|
+
},
|
|
763
|
+
onError: (err) => {
|
|
764
|
+
upload.status = 'error';
|
|
765
|
+
upload.error = err.message;
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
upload.status = 'uploading';
|
|
770
|
+
upload.progress = 0;
|
|
771
|
+
upload.error = null;
|
|
772
|
+
abortUpload = abort;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Cancel upload
|
|
776
|
+
function cancelUpload() {
|
|
777
|
+
if (abortUpload) {
|
|
778
|
+
abortUpload();
|
|
779
|
+
upload.status = 'idle';
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return {
|
|
784
|
+
users,
|
|
785
|
+
newUser,
|
|
786
|
+
error,
|
|
787
|
+
upload,
|
|
788
|
+
fetchUsers,
|
|
789
|
+
addUser,
|
|
790
|
+
deleteUser,
|
|
791
|
+
handleUpload,
|
|
792
|
+
cancelUpload
|
|
793
|
+
};
|
|
794
|
+
})
|
|
795
|
+
.use(ZogHttpPlugin, {
|
|
796
|
+
baseURL: 'https://jsonplaceholder.typicode.com',
|
|
797
|
+
timeout: 30000,
|
|
798
|
+
retries: 2,
|
|
799
|
+
retryDelay: 1000,
|
|
800
|
+
})
|
|
801
|
+
.mount('#app');
|
|
802
|
+
</script>
|
|
803
|
+
</body>
|
|
804
|
+
</html>
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
## TypeScript Support
|
|
808
|
+
|
|
809
|
+
The plugin is written in vanilla JavaScript but includes JSDoc comments for IDE support. For full TypeScript support, type definitions can be added:
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
interface HttpResponse<T = any> {
|
|
813
|
+
data: T;
|
|
814
|
+
status: number;
|
|
815
|
+
statusText: string;
|
|
816
|
+
headers: Record<string, string>;
|
|
817
|
+
config: RequestConfig;
|
|
818
|
+
request: RequestInfo;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
interface UploadProgress {
|
|
822
|
+
loaded: number;
|
|
823
|
+
total: number;
|
|
824
|
+
progress: number;
|
|
825
|
+
speed: number;
|
|
826
|
+
remainingTime: number;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
interface UploadOptions {
|
|
830
|
+
fieldName?: string;
|
|
831
|
+
additionalData?: Record<string, any>;
|
|
832
|
+
headers?: Record<string, string>;
|
|
833
|
+
onProgress?: (info: UploadProgress) => void;
|
|
834
|
+
onComplete?: (response: HttpResponse) => void;
|
|
835
|
+
onError?: (error: HttpError) => void;
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
## Browser Support
|
|
840
|
+
|
|
841
|
+
- Chrome 66+
|
|
842
|
+
- Firefox 57+
|
|
843
|
+
- Safari 11.1+
|
|
844
|
+
- Edge 79+
|
|
845
|
+
|
|
846
|
+
Requires: `fetch`, `AbortController`, `FormData`, `Blob`, `URL.createObjectURL`
|
|
847
|
+
|
|
848
|
+
## License
|
|
849
|
+
|
|
850
|
+
MIT License - feel free to use in any project.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t=Object.defineProperty,e=Object.defineProperties,s=Object.getOwnPropertyDescriptors,r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,n=Object.prototype.propertyIsEnumerable,a=(e,s,r)=>s in e?t(e,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[s]=r,i=(t,e)=>{for(var s in e||(e={}))o.call(e,s)&&a(t,s,e[s]);if(r)for(var s of r(e))n.call(e,s)&&a(t,s,e[s]);return t},l=(t,r)=>e(t,s(r)),d=(t,e)=>{var s={};for(var a in t)o.call(t,a)&&e.indexOf(a)<0&&(s[a]=t[a]);if(null!=t&&r)for(var a of r(t))e.indexOf(a)<0&&n.call(t,a)&&(s[a]=t[a]);return s};const h={baseURL:"",timeout:3e4,headers:{"Content-Type":"application/json"},withCredentials:!1,retries:0,retryDelay:1e3},c={OK:200,CREATED:201,NO_CONTENT:204,BAD_REQUEST:400,UNAUTHORIZED:401,FORBIDDEN:403,NOT_FOUND:404,UNPROCESSABLE_ENTITY:422,INTERNAL_SERVER_ERROR:500,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503};class u extends Error{constructor(t,e,s,r){super(t),this.name="HttpError",this.status=e,this.response=s,this.request=r,this.isHttpError=!0}}class p{constructor(t){this.state=t({progress:0,loaded:0,total:0,status:"idle",error:null,startTime:null,speed:0,remainingTime:0})}start(t){this.state.progress=0,this.state.loaded=0,this.state.total=t,this.state.status="uploading",this.state.error=null,this.state.startTime=Date.now(),this.state.speed=0,this.state.remainingTime=0}update(t,e){this.state.loaded=t,this.state.total=e,this.state.progress=e>0?Math.round(t/e*100):0;const s=(Date.now()-this.state.startTime)/1e3;if(s>0){this.state.speed=Math.round(t/s);const r=e-t;this.state.remainingTime=this.state.speed>0?Math.round(r/this.state.speed):0}}complete(){this.state.progress=100,this.state.status="completed"}fail(t){this.state.status="error",this.state.error=t.message||"Upload failed"}reset(){this.state.progress=0,this.state.loaded=0,this.state.total=0,this.state.status="idle",this.state.error=null,this.state.startTime=null,this.state.speed=0,this.state.remainingTime=0}}class f{constructor(t,e,s){this.config=i(i({},h),t),this.reactive=e,this.ref=s,this.interceptors={request:[],response:[]},this.state=e({loading:!1,error:null,lastRequest:null,pendingRequests:0}),this.abortControllers=/* @__PURE__ */new Map}setBaseURL(t){return this.config.baseURL=t,this}setHeader(t,e){return this.config.headers[t]=e,this}setHeaders(t){return this.config.headers=i(i({},this.config.headers),t),this}removeHeader(t){return delete this.config.headers[t],this}setAuthToken(t){return t?this.config.headers.Authorization=`Bearer ${t}`:delete this.config.headers.Authorization,this}setBasicAuth(t,e){const s=btoa(`${t}:${e}`);return this.config.headers.Authorization=`Basic ${s}`,this}clearAuth(){return delete this.config.headers.Authorization,this}setTimeout(t){return this.config.timeout=t,this}addRequestInterceptor(t,e){const s=this.interceptors.request.length;return this.interceptors.request.push({fulfilled:t,rejected:e,id:s}),s}addResponseInterceptor(t,e){const s=this.interceptors.response.length;return this.interceptors.response.push({fulfilled:t,rejected:e,id:s}),s}removeRequestInterceptor(t){const e=this.interceptors.request.findIndex(e=>e.id===t);-1!==e&&this.interceptors.request.splice(e,1)}removeResponseInterceptor(t){const e=this.interceptors.response.findIndex(e=>e.id===t);-1!==e&&this.interceptors.response.splice(e,1)}clearInterceptors(){this.interceptors.request=[],this.interceptors.response=[]}buildURL(t,e={}){let s=t.startsWith("http")?t:`${this.config.baseURL}${t}`;const r=new URLSearchParams;for(const[n,a]of Object.entries(e))null!=a&&(Array.isArray(a)?a.forEach(t=>r.append(n,t)):r.append(n,a));const o=r.toString();return o&&(s+=(s.includes("?")?"&":"?")+o),s}async runRequestInterceptors(t){let e=i({},t);for(const r of this.interceptors.request)try{r.fulfilled&&(e=await r.fulfilled(e)||e)}catch(s){if(!r.rejected)throw s;e=await r.rejected(s)||e}return e}async runResponseInterceptors(t){let e=t;for(const r of this.interceptors.response)try{r.fulfilled&&(e=await r.fulfilled(e)||e)}catch(s){if(!r.rejected)throw s;e=await r.rejected(s)}return e}async runResponseErrorInterceptors(t){for(const s of this.interceptors.response)if(s.rejected)try{const e=await s.rejected(t);if(void 0!==e)return e}catch(e){t=e}throw t}generateRequestId(){return`req_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}async request(t){const e=this.generateRequestId(),s=new AbortController;this.abortControllers.set(e,s);let r=i({method:"GET",headers:i({},this.config.headers),timeout:this.config.timeout,withCredentials:this.config.withCredentials,retries:this.config.retries,retryDelay:this.config.retryDelay},t);t.headers&&(r.headers=i(i({},r.headers),t.headers));try{r=await this.runRequestInterceptors(r)}catch(d){throw this.abortControllers.delete(e),d}const o=this.buildURL(r.url,r.params),n={method:r.method.toUpperCase(),headers:r.headers,signal:s.signal,credentials:r.withCredentials?"include":"same-origin"};void 0===r.body||["GET","HEAD"].includes(n.method)||(r.body instanceof FormData?(delete n.headers["Content-Type"],n.body=r.body):"object"==typeof r.body?n.body=JSON.stringify(r.body):n.body=r.body),this.state.loading=!0,this.state.pendingRequests++,this.state.lastRequest={url:o,method:n.method,time:Date.now()},this.state.error=null;const a=setTimeout(()=>{s.abort()},r.timeout),l=async t=>{try{const s=await fetch(o,n);let a;const d=s.headers.get("content-type");if(d&&d.includes("application/json"))a=await s.json();else if(d&&d.includes("text/"))a=await s.text();else{const t=await s.text();try{a=JSON.parse(t)}catch(e){a=t}}const h={data:a,status:s.status,statusText:s.statusText,headers:Object.fromEntries(s.headers.entries()),config:r,request:i({url:o},n)};if(!s.ok){const e=new u((null==a?void 0:a.message)||s.statusText||`Request failed with status ${s.status}`,s.status,h,i({url:o},n));if(s.status>=500&&t>0)return await new Promise(t=>setTimeout(t,r.retryDelay)),l(t-1);throw e}return await this.runResponseInterceptors(h)}catch(d){if("AbortError"===d.name)throw new u("Request timeout",408,null,i({url:o},n));if("TypeError"===d.name&&t>0)return await new Promise(t=>setTimeout(t,r.retryDelay)),l(t-1);try{return await this.runResponseErrorInterceptors(d)}catch(e){throw e}}};try{return await l(r.retries)}finally{clearTimeout(a),this.abortControllers.delete(e),this.state.pendingRequests--,this.state.loading=this.state.pendingRequests>0}}async get(t,e={}){return this.request(l(i({},e),{method:"GET",url:t}))}async post(t,e={},s={}){return this.request(l(i({},s),{method:"POST",url:t,body:e}))}async put(t,e={},s={}){return this.request(l(i({},s),{method:"PUT",url:t,body:e}))}async patch(t,e={},s={}){return this.request(l(i({},s),{method:"PATCH",url:t,body:e}))}async delete(t,e={}){return this.request(l(i({},e),{method:"DELETE",url:t}))}async head(t,e={}){return this.request(l(i({},e),{method:"HEAD",url:t}))}async options(t,e={}){return this.request(l(i({},e),{method:"OPTIONS",url:t}))}upload(t,e,s={}){const r=new p(this.reactive),o=new AbortController,n=this.generateRequestId();this.abortControllers.set(n,o);const a=s,{fieldName:l="file",additionalData:h={},headers:c={},onProgress:f,onComplete:m,onError:g}=a,b=d(a,["fieldName","additionalData","headers","onProgress","onComplete","onError"]);let w;if(e instanceof FormData)w=e;else{w=new FormData,Array.isArray(e)?e.forEach((t,e)=>{w.append(`${l}[${e}]`,t)}):w.append(l,e);for(const[t,e]of Object.entries(h))null!=e&&w.append(t,"object"==typeof e?JSON.stringify(e):e)}let y=0;for(const[,i]of w.entries())i instanceof File?y+=i.size:"string"==typeof i&&(y+=new Blob([i]).size);r.start(y);return{promise:new Promise((e,s)=>{var a;const l=new XMLHttpRequest;l.upload.addEventListener("progress",t=>{t.lengthComputable&&(r.update(t.loaded,t.total),f&&f({loaded:t.loaded,total:t.total,progress:r.state.progress,speed:r.state.speed,remainingTime:r.state.remainingTime}))}),l.addEventListener("load",async()=>{let o;this.abortControllers.delete(n);try{o=JSON.parse(l.responseText)}catch(i){o=l.responseText}const a={data:o,status:l.status,statusText:l.statusText,headers:this.parseXHRHeaders(l.getAllResponseHeaders())};if(l.status>=200&&l.status<300){r.complete();try{const t=await this.runResponseInterceptors(a);m&&m(t),e(t)}catch(d){r.fail(d),g&&g(d),s(d)}}else{const e=new u((null==o?void 0:o.message)||l.statusText||"Upload failed",l.status,a,{url:t,method:"POST"});r.fail(e);try{await this.runResponseErrorInterceptors(e)}catch(i){g&&g(i),s(i)}}}),l.addEventListener("error",()=>{this.abortControllers.delete(n);const e=new u("Network error during upload",0,null,{url:t,method:"POST"});r.fail(e),g&&g(e),s(e)}),l.addEventListener("abort",()=>{this.abortControllers.delete(n);const e=new u("Upload cancelled",0,null,{url:t,method:"POST"});r.fail(e),g&&g(e),s(e)}),l.addEventListener("timeout",()=>{this.abortControllers.delete(n);const e=new u("Upload timeout",408,null,{url:t,method:"POST"});r.fail(e),g&&g(e),s(e)}),o.signal.addEventListener("abort",()=>{l.abort()});const d=this.buildURL(t);l.open("POST",d);const h=i(i({},this.config.headers),c);delete h["Content-Type"];for(const[t,r]of Object.entries(h))l.setRequestHeader(t,r);l.timeout=b.timeout||this.config.timeout,l.withCredentials=null!=(a=b.withCredentials)?a:this.config.withCredentials,l.send(w)}),tracker:r.state,abort:()=>o.abort(),requestId:n}}parseXHRHeaders(t){const e={};return t?(t.split("\r\n").forEach(t=>{const[s,...r]=t.split(":");s&&r.length&&(e[s.trim().toLowerCase()]=r.join(":").trim())}),e):e}download(t,e={}){const s=new p(this.reactive),r=new AbortController,o=this.generateRequestId();this.abortControllers.set(o,r);const n=e,{filename:a,onProgress:l,onComplete:h,onError:c}=n;d(n,["filename","onProgress","onComplete","onError"]);return{promise:(async()=>{try{const n=await fetch(this.buildURL(t),{method:"GET",headers:i(i({},this.config.headers),e.headers),signal:r.signal,credentials:this.config.withCredentials?"include":"same-origin"});if(!n.ok)throw new u(`Download failed with status ${n.status}`,n.status,null,{url:t,method:"GET"});const d=n.headers.get("content-length"),c=d?parseInt(d,10):0;s.start(c);const p=n.body.getReader(),f=[];let m=0;for(;;){const{done:t,value:e}=await p.read();if(t)break;f.push(e),m+=e.length,c>0&&(s.update(m,c),l&&l({loaded:m,total:c,progress:s.state.progress,speed:s.state.speed,remainingTime:s.state.remainingTime}))}s.complete();const g=new Blob(f);if(a){const t=URL.createObjectURL(g),e=document.createElement("a");e.href=t,e.download=a,document.body.appendChild(e),e.click(),document.body.removeChild(e),URL.revokeObjectURL(t)}const b={blob:g,filename:a,size:g.size};return h&&h(b),this.abortControllers.delete(o),b}catch(n){if(this.abortControllers.delete(o),"AbortError"===n.name){const e=new u("Download cancelled",0,null,{url:t,method:"GET"});throw s.fail(e),c&&c(e),e}throw s.fail(n),c&&c(n),n}})(),tracker:s.state,abort:()=>r.abort(),requestId:o}}cancelRequest(t){const e=this.abortControllers.get(t);e&&(e.abort(),this.abortControllers.delete(t))}cancelAll(){for(const[t,e]of this.abortControllers)e.abort();this.abortControllers.clear(),this.state.loading=!1,this.state.pendingRequests=0}create(t={}){return new f(i(i({},this.config),t),this.reactive,this.ref)}}const m={install(t,e={}){const{reactive:s,ref:r}=t,o=new f(e,s,r);m._instance=o,t.onHook("afterCompile",(t,e,s)=>{e.$http||(e.$http=o,e.$get=o.get.bind(o),e.$post=o.post.bind(o),e.$put=o.put.bind(o),e.$patch=o.patch.bind(o),e.$delete=o.delete.bind(o),e.$upload=o.upload.bind(o),e.$download=o.download.bind(o))}),t.onHook("onError",(t,e,s)=>{t.isHttpError})},getInstance:()=>m._instance||null};function g(t={}){return new f(t,t=>t,t=>({value:t}))}export{u as HttpError,c as HttpStatus,p as UploadTracker,f as ZogHttpClient,m as ZogHttpPlugin,g as createHttpClient,m as default};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zogjs/http",
|
|
3
|
+
"version": "0.4.8",
|
|
4
|
+
"description": "Powerful HTTP client plugin for Zog.js - Full-featured request library with interceptors, file upload/download progress tracking, and reactive state management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/zog-http.es.js",
|
|
7
|
+
"module": "dist/zog-http.es.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/zog-http.es.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "vite",
|
|
21
|
+
"build": "vite build",
|
|
22
|
+
"preview": "vite preview",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/zogjs/zog-http.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/zogjs/zog-http#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/zogjs/zog-http/issues"
|
|
32
|
+
},
|
|
33
|
+
"author": "ZogJS Team",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"keywords": [
|
|
36
|
+
"zogjs",
|
|
37
|
+
"zog",
|
|
38
|
+
"zog-http",
|
|
39
|
+
"plugin",
|
|
40
|
+
"utilities",
|
|
41
|
+
"http-client",
|
|
42
|
+
"performance",
|
|
43
|
+
"reactive",
|
|
44
|
+
"frontend",
|
|
45
|
+
"javascript"
|
|
46
|
+
],
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"zogjs": "^0.4.8"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"terser": "^5.44.1",
|
|
52
|
+
"vite": "^7.0.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=16.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|