@hashrytech/quick-components-kit 0.20.6 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @hashrytech/quick-components-kit
2
2
 
3
+ ## 0.20.8
4
+
5
+ ### Patch Changes
6
+
7
+ - patch: making beging and end request protected
8
+
9
+ ## 0.20.7
10
+
11
+ ### Patch Changes
12
+
13
+ - Adding requests in progress to fetch client
14
+
3
15
  ## 0.20.6
4
16
 
5
17
  ### Patch Changes
@@ -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
+ protected beginRequest(): void;
156
+ protected endRequest(): void;
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
- const xhr = new XMLHttpRequest();
447
- xhr.open(method, url, true);
448
- // Add Authorization if not skipped
449
- if (!options.skipAuth && token) {
450
- xhr.setRequestHeader('Authorization', `Bearer ${token}`);
451
- }
452
- // Add any remaining headers
453
- headers.forEach((value, key) => {
454
- // Let the browser set Content-Type for FormData
455
- if (key.toLowerCase() !== 'content-type') {
456
- xhr.setRequestHeader(key, value);
457
- }
458
- });
459
- xhr.upload.onprogress = (event) => {
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
- xhr.onload = () => {
465
- if (xhr.status >= 200 && xhr.status < 300) {
466
- const contentType = xhr.getResponseHeader('Content-Type') || '';
467
- if (contentType.includes('application/json')) {
468
- try {
469
- const json = JSON.parse(xhr.responseText);
470
- resolve(json);
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
- catch {
473
- reject(new Error('Failed to parse JSON response'));
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
- // If not JSON, cast explicitly to unknown first, then to T
478
- resolve(xhr.responseText);
524
+ rejectOnce(new FetchError(xhr.statusText, xhr.status));
479
525
  }
480
- }
481
- else {
482
- reject(new FetchError(xhr.statusText, xhr.status));
483
- }
484
- };
485
- xhr.onerror = () => reject(new Error('Upload failed'));
486
- xhr.send(data);
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
  /**
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/hashrytech/quick-components-kit.git"
7
7
  },
8
- "version": "0.20.6",
8
+ "version": "0.20.8",
9
9
  "license": "MIT",
10
10
  "author": "Hashry Tech",
11
11
  "files": [