@hashrytech/quick-components-kit 0.20.6 → 0.20.7
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/CHANGELOG.md +6 -0
- package/dist/modules/fetch-client.d.ts +11 -0
- package/dist/modules/fetch-client.js +81 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -91,6 +91,7 @@ export declare class FetchClient {
|
|
|
91
91
|
private responseInterceptors;
|
|
92
92
|
private errorHandlers;
|
|
93
93
|
private events;
|
|
94
|
+
private activeRequests;
|
|
94
95
|
/**
|
|
95
96
|
* Creates an instance of ApiClient.
|
|
96
97
|
* @param config - Configuration for the API client.
|
|
@@ -126,6 +127,14 @@ export declare class FetchClient {
|
|
|
126
127
|
*/
|
|
127
128
|
addErrorHandler(handler: ErrorHandler): void;
|
|
128
129
|
setEventHooks(events: Partial<FetchClientEvents>): void;
|
|
130
|
+
/**
|
|
131
|
+
* True when one or more requests are currently in progress.
|
|
132
|
+
*/
|
|
133
|
+
get requestsInProgress(): boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Number of active in-flight requests.
|
|
136
|
+
*/
|
|
137
|
+
get activeRequestCount(): number;
|
|
129
138
|
/**
|
|
130
139
|
* Emits a typed event to a single-argument handler (if registered).
|
|
131
140
|
*
|
|
@@ -143,6 +152,8 @@ export declare class FetchClient {
|
|
|
143
152
|
* (e.g. `type EventArgsMap = { onRequest: [Request]; ... }`) to keep spreads type-safe.
|
|
144
153
|
*/
|
|
145
154
|
private emit;
|
|
155
|
+
private beginRequest;
|
|
156
|
+
private endRequest;
|
|
146
157
|
/**
|
|
147
158
|
* Processes the request, applying default headers, auth token, and request interceptors.
|
|
148
159
|
* @param endpoint - The API endpoint (e.g., '/products', '/users/123').
|
|
@@ -40,6 +40,7 @@ export class FetchClient {
|
|
|
40
40
|
responseInterceptors = [];
|
|
41
41
|
errorHandlers = [];
|
|
42
42
|
events = {};
|
|
43
|
+
activeRequests = 0;
|
|
43
44
|
/**
|
|
44
45
|
* Creates an instance of ApiClient.
|
|
45
46
|
* @param config - Configuration for the API client.
|
|
@@ -91,6 +92,18 @@ export class FetchClient {
|
|
|
91
92
|
setEventHooks(events) {
|
|
92
93
|
Object.assign(this.events, events);
|
|
93
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* True when one or more requests are currently in progress.
|
|
97
|
+
*/
|
|
98
|
+
get requestsInProgress() {
|
|
99
|
+
return this.activeRequests > 0;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Number of active in-flight requests.
|
|
103
|
+
*/
|
|
104
|
+
get activeRequestCount() {
|
|
105
|
+
return this.activeRequests;
|
|
106
|
+
}
|
|
94
107
|
/**
|
|
95
108
|
* Emits a typed event to a single-argument handler (if registered).
|
|
96
109
|
*
|
|
@@ -119,6 +132,14 @@ export class FetchClient {
|
|
|
119
132
|
console.warn(`FetchClient ${String(name)} handler threw`, e);
|
|
120
133
|
}
|
|
121
134
|
}
|
|
135
|
+
beginRequest() {
|
|
136
|
+
this.activeRequests += 1;
|
|
137
|
+
}
|
|
138
|
+
endRequest() {
|
|
139
|
+
if (this.activeRequests > 0) {
|
|
140
|
+
this.activeRequests -= 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
122
143
|
/**
|
|
123
144
|
* Processes the request, applying default headers, auth token, and request interceptors.
|
|
124
145
|
* @param endpoint - The API endpoint (e.g., '/products', '/users/123').
|
|
@@ -264,6 +285,7 @@ export class FetchClient {
|
|
|
264
285
|
if (this.debug)
|
|
265
286
|
console.debug(`Fetch Client: ${options.fetchInstance || this.fetchInstance ? "Using sveltekit fetch instance..." : "Using default fetch instance"}`);
|
|
266
287
|
const currentFetch = options.fetchInstance || this.fetchInstance || fetch; // Use provided fetch first then the class instance fetch or the global fetch if not specified.
|
|
288
|
+
this.beginRequest();
|
|
267
289
|
try {
|
|
268
290
|
const request = await this.processRequest(endpoint, method, body, options);
|
|
269
291
|
const response = await currentFetch(request);
|
|
@@ -292,6 +314,9 @@ export class FetchClient {
|
|
|
292
314
|
error: problemError
|
|
293
315
|
};
|
|
294
316
|
}
|
|
317
|
+
finally {
|
|
318
|
+
this.endRequest();
|
|
319
|
+
}
|
|
295
320
|
}
|
|
296
321
|
evaluateRedirect(errorObj, status) {
|
|
297
322
|
for (const rule of this.autoRedirects) {
|
|
@@ -437,53 +462,74 @@ export class FetchClient {
|
|
|
437
462
|
const url = this.baseURL ? new URL(endpoint, this.baseURL).toString() : endpoint; // Resolve endpoint relative to baseURL
|
|
438
463
|
const token = this.accessToken;
|
|
439
464
|
const headers = new Headers(this.defaultHeaders);
|
|
465
|
+
this.beginRequest();
|
|
440
466
|
// Merge user-supplied headers
|
|
441
467
|
if (options.headers) {
|
|
442
468
|
const userHeaders = new Headers(options.headers);
|
|
443
469
|
userHeaders.forEach((value, key) => headers.set(key, value));
|
|
444
470
|
}
|
|
445
471
|
return new Promise((resolve, reject) => {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (event.lengthComputable) {
|
|
461
|
-
onProgress?.(Math.round((event.loaded / event.total) * 100));
|
|
462
|
-
}
|
|
472
|
+
let settled = false;
|
|
473
|
+
const resolveOnce = (value) => {
|
|
474
|
+
if (settled)
|
|
475
|
+
return;
|
|
476
|
+
settled = true;
|
|
477
|
+
this.endRequest();
|
|
478
|
+
resolve(value);
|
|
479
|
+
};
|
|
480
|
+
const rejectOnce = (reason) => {
|
|
481
|
+
if (settled)
|
|
482
|
+
return;
|
|
483
|
+
settled = true;
|
|
484
|
+
this.endRequest();
|
|
485
|
+
reject(reason);
|
|
463
486
|
};
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
487
|
+
try {
|
|
488
|
+
const xhr = new XMLHttpRequest();
|
|
489
|
+
xhr.open(method, url, true);
|
|
490
|
+
// Add Authorization if not skipped
|
|
491
|
+
if (!options.skipAuth && token) {
|
|
492
|
+
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
|
493
|
+
}
|
|
494
|
+
// Add any remaining headers
|
|
495
|
+
headers.forEach((value, key) => {
|
|
496
|
+
// Let the browser set Content-Type for FormData
|
|
497
|
+
if (key.toLowerCase() !== 'content-type') {
|
|
498
|
+
xhr.setRequestHeader(key, value);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
xhr.upload.onprogress = (event) => {
|
|
502
|
+
if (event.lengthComputable) {
|
|
503
|
+
onProgress?.(Math.round((event.loaded / event.total) * 100));
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
xhr.onload = () => {
|
|
507
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
508
|
+
const contentType = xhr.getResponseHeader('Content-Type') || '';
|
|
509
|
+
if (contentType.includes('application/json')) {
|
|
510
|
+
try {
|
|
511
|
+
const json = JSON.parse(xhr.responseText);
|
|
512
|
+
resolveOnce(json);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
rejectOnce(new Error('Failed to parse JSON response'));
|
|
516
|
+
}
|
|
471
517
|
}
|
|
472
|
-
|
|
473
|
-
|
|
518
|
+
else {
|
|
519
|
+
// If not JSON, cast explicitly to unknown first, then to T
|
|
520
|
+
resolveOnce(xhr.responseText);
|
|
474
521
|
}
|
|
475
522
|
}
|
|
476
523
|
else {
|
|
477
|
-
|
|
478
|
-
resolve(xhr.responseText);
|
|
524
|
+
rejectOnce(new FetchError(xhr.statusText, xhr.status));
|
|
479
525
|
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
526
|
+
};
|
|
527
|
+
xhr.onerror = () => rejectOnce(new Error('Upload failed'));
|
|
528
|
+
xhr.send(data);
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
rejectOnce(error);
|
|
532
|
+
}
|
|
487
533
|
});
|
|
488
534
|
}
|
|
489
535
|
/**
|