@djangocfg/monitor 2.1.322 → 2.1.327

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.
Files changed (74) hide show
  1. package/dist/client.cjs +956 -1126
  2. package/dist/client.cjs.map +1 -1
  3. package/dist/client.d.cts +25 -39
  4. package/dist/client.d.ts +25 -39
  5. package/dist/client.mjs +956 -1137
  6. package/dist/client.mjs.map +1 -1
  7. package/dist/index.cjs +934 -1092
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +25 -39
  10. package/dist/index.d.ts +25 -39
  11. package/dist/index.mjs +927 -1096
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/server.cjs +915 -1092
  14. package/dist/server.cjs.map +1 -1
  15. package/dist/server.d.cts +25 -39
  16. package/dist/server.d.ts +25 -39
  17. package/dist/server.mjs +907 -1095
  18. package/dist/server.mjs.map +1 -1
  19. package/package.json +7 -2
  20. package/src/_api/generated/_shared/errors.ts +70 -0
  21. package/src/_api/generated/_shared/index.ts +25 -0
  22. package/src/_api/generated/_shared/logger.ts +123 -0
  23. package/src/_api/generated/_shared/storage.ts +83 -0
  24. package/src/_api/generated/_shared/validation-events.ts +52 -0
  25. package/src/_api/generated/cfg_monitor/api.ts +122 -0
  26. package/src/_api/generated/cfg_monitor/client/client.gen.ts +280 -0
  27. package/src/_api/generated/cfg_monitor/client/index.ts +25 -0
  28. package/src/_api/generated/cfg_monitor/client/types.gen.ts +217 -0
  29. package/src/_api/generated/cfg_monitor/client/utils.gen.ts +318 -0
  30. package/src/_api/generated/cfg_monitor/client.gen.ts +16 -0
  31. package/src/_api/generated/cfg_monitor/core/auth.gen.ts +41 -0
  32. package/src/_api/generated/cfg_monitor/core/bodySerializer.gen.ts +82 -0
  33. package/src/_api/generated/cfg_monitor/core/params.gen.ts +169 -0
  34. package/src/_api/generated/cfg_monitor/core/pathSerializer.gen.ts +171 -0
  35. package/src/_api/generated/cfg_monitor/core/queryKeySerializer.gen.ts +117 -0
  36. package/src/_api/generated/cfg_monitor/core/serverSentEvents.gen.ts +242 -0
  37. package/src/_api/generated/cfg_monitor/core/types.gen.ts +104 -0
  38. package/src/_api/generated/cfg_monitor/core/utils.gen.ts +140 -0
  39. package/src/_api/generated/cfg_monitor/events.ts +198 -0
  40. package/src/_api/generated/cfg_monitor/hooks/index.ts +4 -0
  41. package/src/_api/generated/cfg_monitor/hooks/useCfgMonitorIngestCreate.ts +24 -0
  42. package/src/_api/generated/cfg_monitor/index.ts +26 -314
  43. package/src/_api/generated/cfg_monitor/schemas/EventTypeEnum.ts +9 -0
  44. package/src/_api/generated/cfg_monitor/{_utils/schemas/FrontendEventIngestRequest.schema.ts → schemas/FrontendEventIngestRequest.ts} +12 -19
  45. package/src/_api/generated/cfg_monitor/schemas/IngestBatchRequest.ts +12 -0
  46. package/src/_api/generated/cfg_monitor/schemas/LevelEnum.ts +9 -0
  47. package/src/_api/generated/cfg_monitor/schemas/index.ts +7 -0
  48. package/src/_api/generated/cfg_monitor/sdk.gen.ts +55 -0
  49. package/src/_api/generated/cfg_monitor/types.gen.ts +79 -0
  50. package/src/_api/generated/index.ts +22 -0
  51. package/src/_api/index.ts +2 -2
  52. package/src/client/capture/console.ts +2 -3
  53. package/src/client/transport/ingest.ts +21 -17
  54. package/src/server/index.ts +3 -2
  55. package/src/_api/generated/cfg_monitor/CLAUDE.md +0 -60
  56. package/src/_api/generated/cfg_monitor/_utils/fetchers/index.ts +0 -30
  57. package/src/_api/generated/cfg_monitor/_utils/fetchers/monitor.ts +0 -51
  58. package/src/_api/generated/cfg_monitor/_utils/hooks/index.ts +0 -30
  59. package/src/_api/generated/cfg_monitor/_utils/hooks/monitor.ts +0 -43
  60. package/src/_api/generated/cfg_monitor/_utils/schemas/IngestBatchRequest.schema.ts +0 -20
  61. package/src/_api/generated/cfg_monitor/_utils/schemas/index.ts +0 -22
  62. package/src/_api/generated/cfg_monitor/api-instance.ts +0 -181
  63. package/src/_api/generated/cfg_monitor/client.ts +0 -330
  64. package/src/_api/generated/cfg_monitor/enums.ts +0 -34
  65. package/src/_api/generated/cfg_monitor/errors.ts +0 -123
  66. package/src/_api/generated/cfg_monitor/http.ts +0 -160
  67. package/src/_api/generated/cfg_monitor/logger.ts +0 -261
  68. package/src/_api/generated/cfg_monitor/monitor/client.ts +0 -26
  69. package/src/_api/generated/cfg_monitor/monitor/index.ts +0 -4
  70. package/src/_api/generated/cfg_monitor/monitor/models.ts +0 -47
  71. package/src/_api/generated/cfg_monitor/retry.ts +0 -177
  72. package/src/_api/generated/cfg_monitor/schema.json +0 -181
  73. package/src/_api/generated/cfg_monitor/storage.ts +0 -163
  74. package/src/_api/generated/cfg_monitor/validation-events.ts +0 -135
@@ -1,330 +0,0 @@
1
- import { Monitor } from "./monitor";
2
- import { HttpClientAdapter, FetchAdapter } from "./http";
3
- import { APIError, NetworkError } from "./errors";
4
- import { APILogger, type LoggerConfig } from "./logger";
5
- import { withRetry, type RetryConfig } from "./retry";
6
-
7
-
8
- /**
9
- * Async API client for Django CFG API.
10
- *
11
- * Usage:
12
- * ```typescript
13
- * const client = new APIClient('https://api.example.com');
14
- * const users = await client.users.list();
15
- * const post = await client.posts.create(newPost);
16
- *
17
- * // Custom HTTP adapter (e.g., Axios)
18
- * const client = new APIClient('https://api.example.com', {
19
- * httpClient: new AxiosAdapter()
20
- * });
21
- * ```
22
- */
23
- export class APIClient {
24
- private baseUrl: string;
25
- private httpClient: HttpClientAdapter;
26
- private logger: APILogger | null = null;
27
- private retryConfig: RetryConfig | null = null;
28
- private tokenGetter: (() => string | null) | null = null;
29
-
30
- // Sub-clients
31
- public monitor: Monitor;
32
-
33
- constructor(
34
- baseUrl: string,
35
- options?: {
36
- httpClient?: HttpClientAdapter;
37
- loggerConfig?: Partial<LoggerConfig>;
38
- retryConfig?: RetryConfig;
39
- tokenGetter?: () => string | null;
40
- }
41
- ) {
42
- this.baseUrl = baseUrl.replace(/\/$/, '');
43
- this.httpClient = options?.httpClient || new FetchAdapter();
44
- this.tokenGetter = options?.tokenGetter || null;
45
-
46
- // Initialize logger if config provided
47
- if (options?.loggerConfig !== undefined) {
48
- this.logger = new APILogger(options.loggerConfig);
49
- }
50
-
51
- // Store retry configuration
52
- if (options?.retryConfig !== undefined) {
53
- this.retryConfig = options.retryConfig;
54
- }
55
-
56
- // Initialize sub-clients
57
- this.monitor = new Monitor(this);
58
- }
59
-
60
- /**
61
- * Get CSRF token from cookies (for SessionAuthentication).
62
- *
63
- * Returns null if cookie doesn't exist (JWT-only auth).
64
- */
65
- getCsrfToken(): string | null {
66
- const name = 'csrftoken';
67
- const value = `; ${document.cookie}`;
68
- const parts = value.split(`; ${name}=`);
69
- if (parts.length === 2) {
70
- return parts.pop()?.split(';').shift() || null;
71
- }
72
- return null;
73
- }
74
-
75
- /**
76
- * Get the base URL for building streaming/download URLs.
77
- */
78
- getBaseUrl(): string {
79
- return this.baseUrl;
80
- }
81
-
82
- /**
83
- * Get JWT token for URL authentication (used in streaming endpoints).
84
- * Returns null if no token getter is configured or no token is available.
85
- */
86
- getToken(): string | null {
87
- return this.tokenGetter ? this.tokenGetter() : null;
88
- }
89
-
90
- /**
91
- * Make HTTP request with Django CSRF and session handling.
92
- * Automatically retries on network errors and 5xx server errors.
93
- */
94
- async request<T>(
95
- method: string,
96
- path: string,
97
- options?: {
98
- params?: Record<string, any>;
99
- body?: any;
100
- formData?: FormData;
101
- binaryBody?: Blob | ArrayBuffer;
102
- headers?: Record<string, string>;
103
- responseType?: 'json' | 'text' | 'blob';
104
- }
105
- ): Promise<T> {
106
- // Wrap request in retry logic if configured
107
- if (this.retryConfig) {
108
- return withRetry(() => this._makeRequest<T>(method, path, options), {
109
- ...this.retryConfig,
110
- onFailedAttempt: (info) => {
111
- // Log retry attempts
112
- if (this.logger) {
113
- this.logger.warn(
114
- `Retry attempt ${info.attemptNumber}/${info.retriesLeft + info.attemptNumber} ` +
115
- `for ${method} ${path}: ${info.error.message}`
116
- );
117
- }
118
- // Call user's onFailedAttempt if provided
119
- this.retryConfig?.onFailedAttempt?.(info);
120
- },
121
- });
122
- }
123
-
124
- // No retry configured, make request directly
125
- return this._makeRequest<T>(method, path, options);
126
- }
127
-
128
- /**
129
- * Internal request method (without retry wrapper).
130
- * Used by request() method with optional retry logic.
131
- */
132
- private async _makeRequest<T>(
133
- method: string,
134
- path: string,
135
- options?: {
136
- params?: Record<string, any>;
137
- body?: any;
138
- formData?: FormData;
139
- binaryBody?: Blob | ArrayBuffer;
140
- headers?: Record<string, string>;
141
- responseType?: 'json' | 'text' | 'blob';
142
- }
143
- ): Promise<T> {
144
- // Build URL - handle both absolute and relative paths
145
- // When baseUrl is empty (static builds), path is used as-is (relative to current origin)
146
- const url = this.baseUrl ? `${this.baseUrl}${path}` : path;
147
- const startTime = Date.now();
148
-
149
- // Build headers - start with custom headers from options
150
- const headers: Record<string, string> = {
151
- ...(options?.headers || {})
152
- };
153
-
154
- // Don't set Content-Type for FormData/binaryBody (browser will set it with boundary)
155
- if (!options?.formData && !options?.binaryBody && !headers['Content-Type']) {
156
- headers['Content-Type'] = 'application/json';
157
- }
158
-
159
- // CSRF not needed - SessionAuthentication not enabled in DRF config
160
- // Your API uses JWT/Token authentication (no CSRF required)
161
-
162
- // Add Authorization header from tokenGetter (Bearer token / API key)
163
- if (!headers['Authorization']) {
164
- const token = this.getToken();
165
- if (token) {
166
- headers['Authorization'] = `Bearer ${token}`;
167
- }
168
- }
169
-
170
- // Log request
171
- if (this.logger) {
172
- this.logger.logRequest({
173
- method,
174
- url: url,
175
- headers,
176
- body: options?.formData || options?.body,
177
- timestamp: startTime,
178
- });
179
- }
180
-
181
- try {
182
- // Make request via HTTP adapter
183
- const response = await this.httpClient.request<T>({
184
- method,
185
- url: url,
186
- headers,
187
- params: options?.params,
188
- body: options?.body,
189
- formData: options?.formData,
190
- binaryBody: options?.binaryBody,
191
- responseType: options?.responseType,
192
- });
193
-
194
- const duration = Date.now() - startTime;
195
-
196
- // Check for HTTP errors
197
- if (response.status >= 400) {
198
- const error = new APIError(
199
- response.status,
200
- response.statusText,
201
- response.data,
202
- url
203
- );
204
-
205
- // Log error
206
- if (this.logger) {
207
- this.logger.logError(
208
- {
209
- method,
210
- url: url,
211
- headers,
212
- body: options?.formData || options?.body,
213
- timestamp: startTime,
214
- },
215
- {
216
- message: error.message,
217
- statusCode: response.status,
218
- duration,
219
- timestamp: Date.now(),
220
- }
221
- );
222
- }
223
-
224
- throw error;
225
- }
226
-
227
- // Log successful response
228
- if (this.logger) {
229
- this.logger.logResponse(
230
- {
231
- method,
232
- url: url,
233
- headers,
234
- body: options?.formData || options?.body,
235
- timestamp: startTime,
236
- },
237
- {
238
- status: response.status,
239
- statusText: response.statusText,
240
- data: response.data,
241
- duration,
242
- timestamp: Date.now(),
243
- }
244
- );
245
- }
246
-
247
- return response.data as T;
248
- } catch (error) {
249
- const duration = Date.now() - startTime;
250
-
251
- // Re-throw APIError as-is
252
- if (error instanceof APIError) {
253
- throw error;
254
- }
255
-
256
- // Classify network error using PerformanceResourceTiming.
257
- // Browser intentionally makes CORS errors indistinguishable from network failures
258
- // (same TypeError: "Failed to fetch") for security reasons. We use PerformanceResourceTiming
259
- // as the best available heuristic:
260
- // - Entry exists with responseStatus === 0 → request reached server, JS blocked → likely CORS
261
- // - No entry / entry missing → connection never established → server unavailable / DNS / offline
262
- // All cases are dispatched as 'network-error' with a `possibly_cors` flag.
263
- let possiblyCors = false;
264
- if (error instanceof TypeError && typeof window !== 'undefined') {
265
- try {
266
- const isCrossOrigin = (() => {
267
- try { return new URL(url).origin !== window.location.origin; } catch { return false; }
268
- })();
269
- if (isCrossOrigin) {
270
- const entries = performance.getEntriesByName(url, 'resource');
271
- if (entries.length > 0) {
272
- const last = entries[entries.length - 1] as PerformanceResourceTiming;
273
- possiblyCors = 'responseStatus' in last && (last as any).responseStatus === 0;
274
- }
275
- }
276
- } catch { /* ignore — PerformanceResourceTiming not available */ }
277
- }
278
-
279
- if (this.logger) {
280
- this.logger.error(`⚠️ Network Error: ${method} ${url}`);
281
- this.logger.error(` → ${error instanceof Error ? error.message : String(error)}`);
282
- if (possiblyCors) {
283
- this.logger.error(` → Possibly blocked by CORS policy (configure CORS on the server)`);
284
- }
285
- }
286
-
287
- // Dispatch network-error event with possibly_cors hint
288
- if (typeof window !== 'undefined') {
289
- try {
290
- window.dispatchEvent(new CustomEvent('network-error', {
291
- detail: {
292
- url: url,
293
- method: method,
294
- error: error instanceof Error ? error.message : String(error),
295
- possibly_cors: possiblyCors,
296
- timestamp: new Date(),
297
- },
298
- bubbles: true,
299
- cancelable: false,
300
- }));
301
- } catch { /* silently ignore — event dispatch must never crash the app */ }
302
- }
303
-
304
- // Wrap other errors as NetworkError
305
- const networkError = error instanceof Error
306
- ? new NetworkError(error.message, url, error)
307
- : new NetworkError('Unknown error', url);
308
-
309
- // Detailed logging via logger.logError
310
- if (this.logger) {
311
- this.logger.logError(
312
- {
313
- method,
314
- url: url,
315
- headers,
316
- body: options?.formData || options?.body,
317
- timestamp: startTime,
318
- },
319
- {
320
- message: networkError.message,
321
- duration,
322
- timestamp: Date.now(),
323
- }
324
- );
325
- }
326
-
327
- throw networkError;
328
- }
329
- }
330
- }
@@ -1,34 +0,0 @@
1
- // @ts-nocheck
2
- // Auto-generated by DjangoCFG - see CLAUDE.md
3
- /**
4
- * * `JS_ERROR` - JS_ERROR
5
- * * `NETWORK_ERROR` - NETWORK_ERROR
6
- * * `ERROR` - ERROR
7
- * * `WARNING` - WARNING
8
- * * `PAGE_VIEW` - PAGE_VIEW
9
- * * `PERFORMANCE` - PERFORMANCE
10
- * * `CONSOLE` - CONSOLE
11
- */
12
- export enum FrontendEventIngestRequestEventType {
13
- JS_ERROR = "JS_ERROR",
14
- NETWORK_ERROR = "NETWORK_ERROR",
15
- ERROR = "ERROR",
16
- WARNING = "WARNING",
17
- PAGE_VIEW = "PAGE_VIEW",
18
- PERFORMANCE = "PERFORMANCE",
19
- CONSOLE = "CONSOLE",
20
- }
21
-
22
- /**
23
- * * `error` - error
24
- * * `warning` - warning
25
- * * `info` - info
26
- * * `debug` - debug
27
- */
28
- export enum FrontendEventIngestRequestLevel {
29
- ERROR = "error",
30
- WARNING = "warning",
31
- INFO = "info",
32
- DEBUG = "debug",
33
- }
34
-
@@ -1,123 +0,0 @@
1
- // @ts-nocheck
2
- // Auto-generated by DjangoCFG - see CLAUDE.md
3
- /**
4
- * API Error Classes
5
- *
6
- * Typed error classes with Django REST Framework support.
7
- */
8
-
9
- /**
10
- * HTTP API Error with DRF field-specific validation errors.
11
- *
12
- * Usage:
13
- * ```typescript
14
- * try {
15
- * await api.users.create(userData);
16
- * } catch (error) {
17
- * if (error instanceof APIError) {
18
- * if (error.isValidationError) {
19
- * console.log('Field errors:', error.fieldErrors);
20
- * // { "email": ["Email already exists"], "username": ["Required"] }
21
- * }
22
- * }
23
- * }
24
- * ```
25
- */
26
- export class APIError extends Error {
27
- constructor(
28
- public statusCode: number,
29
- public statusText: string,
30
- public response: any,
31
- public url: string,
32
- message?: string
33
- ) {
34
- super(message || `HTTP ${statusCode}: ${statusText}`);
35
- this.name = 'APIError';
36
- }
37
-
38
- /**
39
- * Get error details from response.
40
- * DRF typically returns: { "detail": "Error message" } or { "field": ["error1", "error2"] }
41
- */
42
- get details(): Record<string, any> | null {
43
- if (typeof this.response === 'object' && this.response !== null) {
44
- return this.response;
45
- }
46
- return null;
47
- }
48
-
49
- /**
50
- * Get field-specific validation errors from DRF.
51
- * Returns: { "field_name": ["error1", "error2"], ... }
52
- */
53
- get fieldErrors(): Record<string, string[]> | null {
54
- const details = this.details;
55
- if (!details) return null;
56
-
57
- // DRF typically returns: { "field": ["error1", "error2"] }
58
- const fieldErrors: Record<string, string[]> = {};
59
- for (const [key, value] of Object.entries(details)) {
60
- if (Array.isArray(value)) {
61
- fieldErrors[key] = value;
62
- }
63
- }
64
-
65
- return Object.keys(fieldErrors).length > 0 ? fieldErrors : null;
66
- }
67
-
68
- /**
69
- * Get single error message from DRF.
70
- * Checks for "detail", "message", or first field error.
71
- */
72
- get errorMessage(): string {
73
- const details = this.details;
74
- if (!details) return this.message;
75
-
76
- // Check for "detail" field (common in DRF)
77
- if (details.detail) {
78
- return Array.isArray(details.detail) ? details.detail.join(', ') : String(details.detail);
79
- }
80
-
81
- // Check for "error" field (common in custom DRF views)
82
- if (details.error) {
83
- return String(details.error);
84
- }
85
-
86
- // Check for "message" field
87
- if (details.message) {
88
- return String(details.message);
89
- }
90
-
91
- // Return first field error
92
- const fieldErrors = this.fieldErrors;
93
- if (fieldErrors) {
94
- const firstField = Object.keys(fieldErrors)[0];
95
- if (firstField) {
96
- return `${firstField}: ${fieldErrors[firstField]?.join(', ')}`;
97
- }
98
- }
99
-
100
- return this.message;
101
- }
102
-
103
- // Helper methods for common HTTP status codes
104
- get isValidationError(): boolean { return this.statusCode === 400; }
105
- get isAuthError(): boolean { return this.statusCode === 401; }
106
- get isPermissionError(): boolean { return this.statusCode === 403; }
107
- get isNotFoundError(): boolean { return this.statusCode === 404; }
108
- get isServerError(): boolean { return this.statusCode >= 500 && this.statusCode < 600; }
109
- }
110
-
111
- /**
112
- * Network Error (connection failed, timeout, etc.)
113
- */
114
- export class NetworkError extends Error {
115
- constructor(
116
- message: string,
117
- public url: string,
118
- public originalError?: Error
119
- ) {
120
- super(message);
121
- this.name = 'NetworkError';
122
- }
123
- }
@@ -1,160 +0,0 @@
1
- // @ts-nocheck
2
- // Auto-generated by DjangoCFG - see CLAUDE.md
3
- /**
4
- * HTTP Client Adapter Pattern
5
- *
6
- * Allows switching between fetch/axios/httpx without changing generated code.
7
- * Provides unified interface for making HTTP requests.
8
- */
9
-
10
- export interface HttpRequest {
11
- method: string;
12
- url: string;
13
- headers?: Record<string, string>;
14
- body?: any;
15
- params?: Record<string, any>;
16
- /** FormData for file uploads (multipart/form-data) */
17
- formData?: FormData;
18
- /** Binary data for octet-stream uploads */
19
- binaryBody?: Blob | ArrayBuffer;
20
- /**
21
- * Force a specific response parser. When set, overrides Content-Type sniffing.
22
- * Generated for endpoints whose primary response is binary (file downloads).
23
- */
24
- responseType?: 'json' | 'text' | 'blob';
25
- }
26
-
27
- export interface HttpResponse<T = any> {
28
- data: T;
29
- status: number;
30
- statusText: string;
31
- headers: Record<string, string>;
32
- }
33
-
34
- /**
35
- * HTTP Client Adapter Interface.
36
- * Implement this to use custom HTTP clients (axios, httpx, etc.)
37
- */
38
- export interface HttpClientAdapter {
39
- request<T = any>(request: HttpRequest): Promise<HttpResponse<T>>;
40
- }
41
-
42
- /**
43
- * Default Fetch API adapter.
44
- * Uses native browser fetch() with proper error handling.
45
- */
46
- export class FetchAdapter implements HttpClientAdapter {
47
- async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
48
- const { method, url, headers, body, params, formData, binaryBody, responseType } = request;
49
-
50
- // Build URL with query params
51
- let finalUrl = url;
52
- if (params) {
53
- const searchParams = new URLSearchParams();
54
- Object.entries(params).forEach(([key, value]) => {
55
- if (value !== null && value !== undefined) {
56
- searchParams.append(key, String(value));
57
- }
58
- });
59
- const queryString = searchParams.toString();
60
- if (queryString) {
61
- finalUrl = url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`;
62
- }
63
- }
64
-
65
- // Build headers
66
- const finalHeaders: Record<string, string> = { ...headers };
67
-
68
- // Determine body and content-type
69
- let requestBody: string | FormData | Blob | ArrayBuffer | undefined;
70
-
71
- if (formData) {
72
- // For multipart/form-data, let browser set Content-Type with boundary
73
- requestBody = formData;
74
- // Don't set Content-Type - browser will set it with boundary
75
- } else if (binaryBody) {
76
- // Binary upload (application/octet-stream)
77
- finalHeaders['Content-Type'] = 'application/octet-stream';
78
- requestBody = binaryBody;
79
- } else if (body) {
80
- // JSON request
81
- finalHeaders['Content-Type'] = 'application/json';
82
- requestBody = JSON.stringify(body);
83
- }
84
-
85
- // Make request
86
- const response = await fetch(finalUrl, {
87
- method,
88
- headers: finalHeaders,
89
- body: requestBody,
90
- credentials: 'include', // Include Django session cookies
91
- });
92
-
93
- // Parse response. Explicit `responseType` (set by the generator for binary
94
- // endpoints) wins over Content-Type sniffing — backends often serve CSV/JSON
95
- // file downloads with `text/csv` or `application/json`, which would otherwise
96
- // be parsed as string/object instead of the Blob the caller expects.
97
- //
98
- // responseType set → use it directly
99
- // application/json → JSON
100
- // text/* → string
101
- // anything else → Blob (file downloads, octet-stream, …)
102
- let data: any = null;
103
- const contentType = response.headers.get('content-type') ?? '';
104
-
105
- if (response.status !== 204) {
106
- if (responseType === 'blob') {
107
- data = await response.blob();
108
- } else if (responseType === 'text') {
109
- data = await response.text();
110
- } else if (responseType === 'json') {
111
- data = await response.json();
112
- } else if (contentType.includes('application/json')) {
113
- data = await response.json();
114
- } else if (contentType.startsWith('text/')) {
115
- data = await response.text();
116
- } else {
117
- data = await response.blob();
118
- }
119
- }
120
-
121
- // Convert Headers to plain object
122
- const responseHeaders: Record<string, string> = {};
123
- response.headers.forEach((value, key) => {
124
- responseHeaders[key] = value;
125
- });
126
-
127
- return {
128
- data,
129
- status: response.status,
130
- statusText: response.statusText,
131
- headers: responseHeaders,
132
- };
133
- }
134
- }
135
-
136
- /**
137
- * FetchAdapter with keepalive:true.
138
- *
139
- * Use this adapter when you need requests to survive page unload
140
- * (visibilitychange / beforeunload) — the browser will complete the request
141
- * even after the page is navigated away. Typical use: monitor / analytics flush.
142
- *
143
- * @example
144
- * ```typescript
145
- * import { APIClient, KeepAliveFetchAdapter } from './client';
146
- * const client = new APIClient(baseUrl, { httpClient: new KeepAliveFetchAdapter() });
147
- * ```
148
- */
149
- export class KeepAliveFetchAdapter extends FetchAdapter {
150
- async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
151
- const origFetch = globalThis.fetch;
152
- globalThis.fetch = (input: RequestInfo | URL, init?: RequestInit) =>
153
- origFetch(input, { ...init, keepalive: true });
154
- try {
155
- return await super.request<T>(request);
156
- } finally {
157
- globalThis.fetch = origFetch;
158
- }
159
- }
160
- }