@djangocfg/monitor 2.1.216

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 (62) hide show
  1. package/README.md +341 -0
  2. package/dist/client.cjs +1273 -0
  3. package/dist/client.cjs.map +1 -0
  4. package/dist/client.d.cts +123 -0
  5. package/dist/client.d.ts +123 -0
  6. package/dist/client.mjs +1243 -0
  7. package/dist/client.mjs.map +1 -0
  8. package/dist/index.cjs +18 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +101 -0
  11. package/dist/index.d.ts +101 -0
  12. package/dist/index.mjs +1 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/server.cjs +947 -0
  15. package/dist/server.cjs.map +1 -0
  16. package/dist/server.d.cts +117 -0
  17. package/dist/server.d.ts +117 -0
  18. package/dist/server.mjs +917 -0
  19. package/dist/server.mjs.map +1 -0
  20. package/package.json +82 -0
  21. package/src/.claude/.sidecar/activity.jsonl +1 -0
  22. package/src/.claude/.sidecar/map_cache.json +38 -0
  23. package/src/.claude/.sidecar/usage.json +5 -0
  24. package/src/.claude/project-map.md +29 -0
  25. package/src/_api/BaseClient.ts +18 -0
  26. package/src/_api/generated/cfg_monitor/CLAUDE.md +60 -0
  27. package/src/_api/generated/cfg_monitor/_utils/fetchers/index.ts +30 -0
  28. package/src/_api/generated/cfg_monitor/_utils/fetchers/monitor.ts +51 -0
  29. package/src/_api/generated/cfg_monitor/_utils/hooks/index.ts +30 -0
  30. package/src/_api/generated/cfg_monitor/_utils/hooks/monitor.ts +43 -0
  31. package/src/_api/generated/cfg_monitor/_utils/schemas/FrontendEventIngestRequest.schema.ts +34 -0
  32. package/src/_api/generated/cfg_monitor/_utils/schemas/IngestBatchRequest.schema.ts +20 -0
  33. package/src/_api/generated/cfg_monitor/_utils/schemas/index.ts +22 -0
  34. package/src/_api/generated/cfg_monitor/api-instance.ts +181 -0
  35. package/src/_api/generated/cfg_monitor/client.ts +322 -0
  36. package/src/_api/generated/cfg_monitor/enums.ts +36 -0
  37. package/src/_api/generated/cfg_monitor/errors.ts +118 -0
  38. package/src/_api/generated/cfg_monitor/http.ts +137 -0
  39. package/src/_api/generated/cfg_monitor/index.ts +317 -0
  40. package/src/_api/generated/cfg_monitor/logger.ts +261 -0
  41. package/src/_api/generated/cfg_monitor/monitor/client.ts +25 -0
  42. package/src/_api/generated/cfg_monitor/monitor/index.ts +4 -0
  43. package/src/_api/generated/cfg_monitor/monitor/models.ts +48 -0
  44. package/src/_api/generated/cfg_monitor/retry.ts +177 -0
  45. package/src/_api/generated/cfg_monitor/schema.json +184 -0
  46. package/src/_api/generated/cfg_monitor/storage.ts +163 -0
  47. package/src/_api/generated/cfg_monitor/validation-events.ts +135 -0
  48. package/src/_api/index.ts +6 -0
  49. package/src/client/capture/console.ts +72 -0
  50. package/src/client/capture/fingerprint.ts +27 -0
  51. package/src/client/capture/js-errors.ts +70 -0
  52. package/src/client/capture/network.ts +47 -0
  53. package/src/client/capture/session.ts +33 -0
  54. package/src/client/capture/validation.ts +38 -0
  55. package/src/client/index.ts +72 -0
  56. package/src/client/store/index.ts +41 -0
  57. package/src/client/transport/ingest.ts +31 -0
  58. package/src/index.ts +12 -0
  59. package/src/server/index.ts +85 -0
  60. package/src/types/config.ts +33 -0
  61. package/src/types/events.ts +5 -0
  62. package/src/types/index.ts +6 -0
@@ -0,0 +1,137 @@
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
+
22
+ export interface HttpResponse<T = any> {
23
+ data: T;
24
+ status: number;
25
+ statusText: string;
26
+ headers: Record<string, string>;
27
+ }
28
+
29
+ /**
30
+ * HTTP Client Adapter Interface.
31
+ * Implement this to use custom HTTP clients (axios, httpx, etc.)
32
+ */
33
+ export interface HttpClientAdapter {
34
+ request<T = any>(request: HttpRequest): Promise<HttpResponse<T>>;
35
+ }
36
+
37
+ /**
38
+ * Default Fetch API adapter.
39
+ * Uses native browser fetch() with proper error handling.
40
+ */
41
+ export class FetchAdapter implements HttpClientAdapter {
42
+ async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
43
+ const { method, url, headers, body, params, formData, binaryBody } = request;
44
+
45
+ // Build URL with query params
46
+ let finalUrl = url;
47
+ if (params) {
48
+ const searchParams = new URLSearchParams();
49
+ Object.entries(params).forEach(([key, value]) => {
50
+ if (value !== null && value !== undefined) {
51
+ searchParams.append(key, String(value));
52
+ }
53
+ });
54
+ const queryString = searchParams.toString();
55
+ if (queryString) {
56
+ finalUrl = url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`;
57
+ }
58
+ }
59
+
60
+ // Build headers
61
+ const finalHeaders: Record<string, string> = { ...headers };
62
+
63
+ // Determine body and content-type
64
+ let requestBody: string | FormData | Blob | ArrayBuffer | undefined;
65
+
66
+ if (formData) {
67
+ // For multipart/form-data, let browser set Content-Type with boundary
68
+ requestBody = formData;
69
+ // Don't set Content-Type - browser will set it with boundary
70
+ } else if (binaryBody) {
71
+ // Binary upload (application/octet-stream)
72
+ finalHeaders['Content-Type'] = 'application/octet-stream';
73
+ requestBody = binaryBody;
74
+ } else if (body) {
75
+ // JSON request
76
+ finalHeaders['Content-Type'] = 'application/json';
77
+ requestBody = JSON.stringify(body);
78
+ }
79
+
80
+ // Make request
81
+ const response = await fetch(finalUrl, {
82
+ method,
83
+ headers: finalHeaders,
84
+ body: requestBody,
85
+ credentials: 'include', // Include Django session cookies
86
+ });
87
+
88
+ // Parse response
89
+ let data: any = null;
90
+ const contentType = response.headers.get('content-type');
91
+
92
+ if (response.status !== 204 && contentType?.includes('application/json')) {
93
+ data = await response.json();
94
+ } else if (response.status !== 204) {
95
+ data = await response.text();
96
+ }
97
+
98
+ // Convert Headers to plain object
99
+ const responseHeaders: Record<string, string> = {};
100
+ response.headers.forEach((value, key) => {
101
+ responseHeaders[key] = value;
102
+ });
103
+
104
+ return {
105
+ data,
106
+ status: response.status,
107
+ statusText: response.statusText,
108
+ headers: responseHeaders,
109
+ };
110
+ }
111
+ }
112
+
113
+ /**
114
+ * FetchAdapter with keepalive:true.
115
+ *
116
+ * Use this adapter when you need requests to survive page unload
117
+ * (visibilitychange / beforeunload) — the browser will complete the request
118
+ * even after the page is navigated away. Typical use: monitor / analytics flush.
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * import { APIClient, KeepAliveFetchAdapter } from './client';
123
+ * const client = new APIClient(baseUrl, { httpClient: new KeepAliveFetchAdapter() });
124
+ * ```
125
+ */
126
+ export class KeepAliveFetchAdapter extends FetchAdapter {
127
+ async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
128
+ const origFetch = globalThis.fetch;
129
+ globalThis.fetch = (input: RequestInfo | URL, init?: RequestInit) =>
130
+ origFetch(input, { ...init, keepalive: true });
131
+ try {
132
+ return await super.request<T>(request);
133
+ } finally {
134
+ globalThis.fetch = origFetch;
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,317 @@
1
+ // @ts-nocheck
2
+ // Auto-generated by DjangoCFG - see CLAUDE.md
3
+ /**
4
+ * Django CFG API - API Client with JWT Management
5
+ *
6
+ * Usage:
7
+ * ```typescript
8
+ * import { API } from './api';
9
+ *
10
+ * const api = new API('https://api.example.com');
11
+ *
12
+ * // Set JWT token
13
+ * api.setToken('your-jwt-token', 'refresh-token');
14
+ *
15
+ * // Use API
16
+ * const posts = await api.posts.list();
17
+ * const user = await api.users.retrieve(1);
18
+ *
19
+ * // Check authentication
20
+ * if (api.isAuthenticated()) {
21
+ * // ...
22
+ * }
23
+ *
24
+ * // Custom storage with logging (for Electron/Node.js)
25
+ * import { MemoryStorageAdapter, APILogger } from './storage';
26
+ * const logger = new APILogger({ enabled: true, logLevel: 'debug' });
27
+ * const api = new API('https://api.example.com', {
28
+ * storage: new MemoryStorageAdapter(logger),
29
+ * loggerConfig: { enabled: true, logLevel: 'debug' }
30
+ * });
31
+ *
32
+ * // Get OpenAPI schema
33
+ * const schema = api.getSchema();
34
+ * ```
35
+ */
36
+
37
+ import { APIClient } from "./client";
38
+ import {
39
+ StorageAdapter,
40
+ LocalStorageAdapter,
41
+ CookieStorageAdapter,
42
+ MemoryStorageAdapter
43
+ } from "./storage";
44
+ import type { RetryConfig } from "./retry";
45
+ import type { LoggerConfig } from "./logger";
46
+ import { APILogger } from "./logger";
47
+ import { Monitor } from "./monitor/client";
48
+ export * as MonitorTypes from "./monitor/models";
49
+ // Note: Direct exports (export * from) are removed to avoid duplicate type conflicts
50
+ // Use namespace exports like CfgAccountsTypes.User or import from specific modules
51
+ export * as Enums from "./enums";
52
+
53
+ // Re-export Zod schemas for runtime validation
54
+ export * as Schemas from "./_utils/schemas";
55
+ // Also export all schemas directly for convenience
56
+ export * from "./_utils/schemas";
57
+
58
+ // Re-export Zod validation events for browser integration
59
+ export type { ValidationErrorDetail, ValidationErrorEvent } from "./validation-events";
60
+ export { dispatchValidationError, onValidationError, formatZodError } from "./validation-events";
61
+
62
+ // Re-export typed fetchers for universal usage
63
+ export * as Fetchers from "./_utils/fetchers";
64
+ export * from "./_utils/fetchers";
65
+
66
+ // Re-export API instance configuration functions
67
+ export {
68
+ configureAPI,
69
+ getAPIInstance,
70
+ reconfigureAPI,
71
+ clearAPITokens,
72
+ resetAPI,
73
+ isAPIConfigured
74
+ } from "./api-instance";
75
+ // NOTE: SWR hooks are generated in ./_utils/hooks/ but NOT exported here to keep
76
+ // the main bundle server-safe. Import hooks directly from the hooks directory:
77
+ // import { useUsers } from './_utils/hooks';
78
+ // Or use a separate entry point like '@djangocfg/api/hooks' for client components.
79
+
80
+ // Re-export core client
81
+ export { APIClient };
82
+
83
+ // Re-export storage adapters for convenience
84
+ export type { StorageAdapter };
85
+ export { LocalStorageAdapter, CookieStorageAdapter, MemoryStorageAdapter };
86
+
87
+ // Re-export error classes for convenience
88
+ export { APIError, NetworkError } from "./errors";
89
+
90
+ // Re-export HTTP adapters for custom implementations
91
+ export type { HttpClientAdapter, HttpRequest, HttpResponse } from "./http";
92
+ export { FetchAdapter, KeepAliveFetchAdapter } from "./http";
93
+
94
+ // Re-export logger types and classes
95
+ export type { LoggerConfig, RequestLog, ResponseLog, ErrorLog } from "./logger";
96
+ export { APILogger } from "./logger";
97
+
98
+ // Re-export retry configuration and utilities
99
+ export type { RetryConfig, FailedAttemptInfo } from "./retry";
100
+ export { withRetry, shouldRetry, DEFAULT_RETRY_CONFIG } from "./retry";
101
+
102
+ export const TOKEN_KEY = "auth_token";
103
+ export const REFRESH_TOKEN_KEY = "refresh_token";
104
+
105
+ /** Auto-detect locale from cookie NEXT_LOCALE or navigator.language */
106
+ function detectLocale(): string | null {
107
+ try {
108
+ if (typeof document !== 'undefined') {
109
+ const match = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]*)/);
110
+ if (match) return match[1];
111
+ }
112
+ if (typeof navigator !== 'undefined' && navigator.language) {
113
+ return navigator.language;
114
+ }
115
+ } catch {}
116
+ return null;
117
+ }
118
+
119
+ export interface APIOptions {
120
+ /** Custom storage adapter (defaults to LocalStorageAdapter) */
121
+ storage?: StorageAdapter;
122
+ /** Custom HTTP client adapter (defaults to FetchAdapter) */
123
+ httpClient?: HttpClientAdapter;
124
+ /** Retry configuration for failed requests */
125
+ retryConfig?: RetryConfig;
126
+ /** Logger configuration */
127
+ loggerConfig?: Partial<LoggerConfig>;
128
+ /** Locale for Accept-Language header (e.g. 'en', 'ko', 'ru') */
129
+ locale?: string;
130
+ }
131
+
132
+ export class API {
133
+ private baseUrl: string;
134
+ private _client: APIClient;
135
+ private _token: string | null = null;
136
+ private _refreshToken: string | null = null;
137
+ private _locale: string | null = null;
138
+ private storage: StorageAdapter;
139
+ private options?: APIOptions;
140
+
141
+ // Sub-clients
142
+ public monitor!: Monitor;
143
+
144
+ constructor(baseUrl: string, options?: APIOptions) {
145
+ this.baseUrl = baseUrl;
146
+ this.options = options;
147
+
148
+ // Create logger if config provided
149
+ const logger = options?.loggerConfig ? new APILogger(options.loggerConfig) : undefined;
150
+
151
+ // Initialize storage with logger
152
+ this.storage = options?.storage || new LocalStorageAdapter(logger);
153
+
154
+ this._locale = options?.locale || null;
155
+
156
+ this._loadTokensFromStorage();
157
+
158
+ // Initialize APIClient with token getter for URL authentication
159
+ this._client = new APIClient(this.baseUrl, {
160
+ httpClient: this.options?.httpClient,
161
+ retryConfig: this.options?.retryConfig,
162
+ loggerConfig: this.options?.loggerConfig,
163
+ tokenGetter: () => this.getToken(),
164
+ });
165
+
166
+ // Always inject auth header wrapper (reads token dynamically from storage)
167
+ this._injectAuthHeader();
168
+
169
+ // Initialize sub-clients from APIClient
170
+ this.monitor = this._client.monitor;
171
+ }
172
+
173
+ private _loadTokensFromStorage(): void {
174
+ this._token = this.storage.getItem(TOKEN_KEY);
175
+ this._refreshToken = this.storage.getItem(REFRESH_TOKEN_KEY);
176
+ }
177
+
178
+ private _reinitClients(): void {
179
+ this._client = new APIClient(this.baseUrl, {
180
+ httpClient: this.options?.httpClient,
181
+ retryConfig: this.options?.retryConfig,
182
+ loggerConfig: this.options?.loggerConfig,
183
+ tokenGetter: () => this.getToken(),
184
+ });
185
+
186
+ // Always inject auth header wrapper (reads token dynamically from storage)
187
+ this._injectAuthHeader();
188
+
189
+ // Reinitialize sub-clients
190
+ this.monitor = this._client.monitor;
191
+ }
192
+
193
+ private _injectAuthHeader(): void {
194
+ // Override request method to inject auth header
195
+ const originalRequest = this._client.request.bind(this._client);
196
+ this._client.request = async <T>(
197
+ method: string,
198
+ path: string,
199
+ options?: { params?: Record<string, any>; body?: any; formData?: FormData; headers?: Record<string, string> }
200
+ ): Promise<T> => {
201
+ // Read token from storage dynamically (supports JWT injection after instantiation)
202
+ const token = this.getToken();
203
+ const locale = this._locale || detectLocale();
204
+ const mergedOptions = {
205
+ ...options,
206
+ headers: {
207
+ ...(options?.headers || {}),
208
+ ...(token ? { 'Authorization': `Bearer ${token}` } : {}),
209
+ ...(locale ? { 'Accept-Language': locale } : {}),
210
+ },
211
+ };
212
+
213
+ return originalRequest(method, path, mergedOptions);
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Get current JWT token
219
+ */
220
+ getToken(): string | null {
221
+ return this.storage.getItem(TOKEN_KEY);
222
+ }
223
+
224
+ /**
225
+ * Get current refresh token
226
+ */
227
+ getRefreshToken(): string | null {
228
+ return this.storage.getItem(REFRESH_TOKEN_KEY);
229
+ }
230
+
231
+ /**
232
+ * Set JWT token and refresh token
233
+ * @param token - JWT access token
234
+ * @param refreshToken - JWT refresh token (optional)
235
+ */
236
+ setToken(token: string, refreshToken?: string): void {
237
+ this._token = token;
238
+ this.storage.setItem(TOKEN_KEY, token);
239
+
240
+ if (refreshToken) {
241
+ this._refreshToken = refreshToken;
242
+ this.storage.setItem(REFRESH_TOKEN_KEY, refreshToken);
243
+ }
244
+
245
+ // Reinitialize clients with new token
246
+ this._reinitClients();
247
+ }
248
+
249
+ /**
250
+ * Clear all tokens
251
+ */
252
+ clearTokens(): void {
253
+ this._token = null;
254
+ this._refreshToken = null;
255
+ this.storage.removeItem(TOKEN_KEY);
256
+ this.storage.removeItem(REFRESH_TOKEN_KEY);
257
+
258
+ // Reinitialize clients without token
259
+ this._reinitClients();
260
+ }
261
+
262
+ /**
263
+ * Check if user is authenticated
264
+ */
265
+ isAuthenticated(): boolean {
266
+ return !!this.getToken();
267
+ }
268
+
269
+ /**
270
+ * Update base URL and reinitialize clients
271
+ * @param url - New base URL
272
+ */
273
+ setBaseUrl(url: string): void {
274
+ this.baseUrl = url;
275
+ this._reinitClients();
276
+ }
277
+
278
+ /**
279
+ * Get current base URL
280
+ */
281
+ getBaseUrl(): string {
282
+ return this.baseUrl;
283
+ }
284
+
285
+ /**
286
+ * Set locale for Accept-Language header
287
+ * @param locale - Locale string (e.g. 'en', 'ko', 'ru') or null to clear
288
+ */
289
+ setLocale(locale: string | null): void {
290
+ this._locale = locale;
291
+ }
292
+
293
+ /**
294
+ * Get current locale
295
+ */
296
+ getLocale(): string | null {
297
+ return this._locale;
298
+ }
299
+
300
+ /**
301
+ * Get OpenAPI schema path
302
+ * @returns Path to the OpenAPI schema JSON file
303
+ *
304
+ * Note: The OpenAPI schema is available in the schema.json file.
305
+ * You can load it dynamically using:
306
+ * ```typescript
307
+ * const schema = await fetch('./schema.json').then(r => r.json());
308
+ * // or using fs in Node.js:
309
+ * // const schema = JSON.parse(fs.readFileSync('./schema.json', 'utf-8'));
310
+ * ```
311
+ */
312
+ getSchemaPath(): string {
313
+ return './schema.json';
314
+ }
315
+ }
316
+
317
+ export default API;
@@ -0,0 +1,261 @@
1
+ // @ts-nocheck
2
+ // Auto-generated by DjangoCFG - see CLAUDE.md
3
+ /**
4
+ * API Logger with Consola
5
+ * Beautiful console logging for API requests and responses
6
+ *
7
+ * Installation:
8
+ * npm install consola
9
+ */
10
+
11
+ import { type ConsolaInstance, createConsola } from 'consola';
12
+
13
+ /**
14
+ * Request log data
15
+ */
16
+ export interface RequestLog {
17
+ method: string;
18
+ url: string;
19
+ headers?: Record<string, string>;
20
+ body?: any;
21
+ timestamp: number;
22
+ }
23
+
24
+ /**
25
+ * Response log data
26
+ */
27
+ export interface ResponseLog {
28
+ status: number;
29
+ statusText: string;
30
+ data?: any;
31
+ duration: number;
32
+ timestamp: number;
33
+ }
34
+
35
+ /**
36
+ * Error log data
37
+ */
38
+ export interface ErrorLog {
39
+ message: string;
40
+ statusCode?: number;
41
+ fieldErrors?: Record<string, string[]>;
42
+ duration: number;
43
+ timestamp: number;
44
+ }
45
+
46
+ /**
47
+ * Logger configuration
48
+ */
49
+ export interface LoggerConfig {
50
+ /** Enable logging */
51
+ enabled: boolean;
52
+ /** Log requests */
53
+ logRequests: boolean;
54
+ /** Log responses */
55
+ logResponses: boolean;
56
+ /** Log errors */
57
+ logErrors: boolean;
58
+ /** Log request/response bodies */
59
+ logBodies: boolean;
60
+ /** Log headers (excluding sensitive ones) */
61
+ logHeaders: boolean;
62
+ /** Custom consola instance */
63
+ consola?: ConsolaInstance;
64
+ }
65
+
66
+ /**
67
+ * Default logger configuration
68
+ */
69
+ const DEFAULT_CONFIG: LoggerConfig = {
70
+ enabled: process.env.NODE_ENV !== 'production',
71
+ logRequests: true,
72
+ logResponses: true,
73
+ logErrors: true,
74
+ logBodies: true,
75
+ logHeaders: false,
76
+ };
77
+
78
+ /**
79
+ * Sensitive header names to filter out
80
+ */
81
+ const SENSITIVE_HEADERS = [
82
+ 'authorization',
83
+ 'cookie',
84
+ 'set-cookie',
85
+ 'x-api-key',
86
+ 'x-csrf-token',
87
+ ];
88
+
89
+ /**
90
+ * API Logger class
91
+ */
92
+ export class APILogger {
93
+ private config: LoggerConfig;
94
+ private consola: ConsolaInstance;
95
+
96
+ constructor(config: Partial<LoggerConfig> = {}) {
97
+ this.config = { ...DEFAULT_CONFIG, ...config };
98
+ this.consola = config.consola || createConsola({
99
+ level: this.config.enabled ? 4 : 0,
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Enable logging
105
+ */
106
+ enable(): void {
107
+ this.config.enabled = true;
108
+ }
109
+
110
+ /**
111
+ * Disable logging
112
+ */
113
+ disable(): void {
114
+ this.config.enabled = false;
115
+ }
116
+
117
+ /**
118
+ * Update configuration
119
+ */
120
+ setConfig(config: Partial<LoggerConfig>): void {
121
+ this.config = { ...this.config, ...config };
122
+ }
123
+
124
+ /**
125
+ * Filter sensitive headers
126
+ */
127
+ private filterHeaders(headers?: Record<string, string>): Record<string, string> {
128
+ if (!headers) return {};
129
+
130
+ const filtered: Record<string, string> = {};
131
+ Object.keys(headers).forEach((key) => {
132
+ const lowerKey = key.toLowerCase();
133
+ if (SENSITIVE_HEADERS.includes(lowerKey)) {
134
+ filtered[key] = '***';
135
+ } else {
136
+ filtered[key] = headers[key] || '';
137
+ }
138
+ });
139
+
140
+ return filtered;
141
+ }
142
+
143
+ /**
144
+ * Log request
145
+ */
146
+ logRequest(request: RequestLog): void {
147
+ if (!this.config.enabled || !this.config.logRequests) return;
148
+
149
+ const { method, url, headers, body } = request;
150
+
151
+ this.consola.start(`${method} ${url}`);
152
+
153
+ if (this.config.logHeaders && headers) {
154
+ this.consola.debug('Headers:', this.filterHeaders(headers));
155
+ }
156
+
157
+ if (this.config.logBodies && body) {
158
+ this.consola.debug('Body:', body);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Log response
164
+ */
165
+ logResponse(request: RequestLog, response: ResponseLog): void {
166
+ if (!this.config.enabled || !this.config.logResponses) return;
167
+
168
+ const { method, url } = request;
169
+ const { status, statusText, data, duration } = response;
170
+
171
+ const statusColor = status >= 500 ? 'red'
172
+ : status >= 400 ? 'yellow'
173
+ : status >= 300 ? 'cyan'
174
+ : 'green';
175
+
176
+ this.consola.success(
177
+ `${method} ${url} ${status} ${statusText} (${duration}ms)`
178
+ );
179
+
180
+ if (this.config.logBodies && data) {
181
+ this.consola.debug('Response:', data);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Log error
187
+ */
188
+ logError(request: RequestLog, error: ErrorLog): void {
189
+ if (!this.config.enabled || !this.config.logErrors) return;
190
+
191
+ const { method, url } = request;
192
+ const { message, statusCode, fieldErrors, duration } = error;
193
+
194
+ this.consola.error(
195
+ `${method} ${url} ${statusCode || 'Network'} Error (${duration}ms)`
196
+ );
197
+
198
+ this.consola.error('Message:', message);
199
+
200
+ if (fieldErrors && Object.keys(fieldErrors).length > 0) {
201
+ this.consola.error('Field Errors:');
202
+ Object.entries(fieldErrors).forEach(([field, errors]) => {
203
+ errors.forEach((err) => {
204
+ this.consola.error(` • ${field}: ${err}`);
205
+ });
206
+ });
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Log general info
212
+ */
213
+ info(message: string, ...args: any[]): void {
214
+ if (!this.config.enabled) return;
215
+ this.consola.info(message, ...args);
216
+ }
217
+
218
+ /**
219
+ * Log warning
220
+ */
221
+ warn(message: string, ...args: any[]): void {
222
+ if (!this.config.enabled) return;
223
+ this.consola.warn(message, ...args);
224
+ }
225
+
226
+ /**
227
+ * Log error
228
+ */
229
+ error(message: string, ...args: any[]): void {
230
+ if (!this.config.enabled) return;
231
+ this.consola.error(message, ...args);
232
+ }
233
+
234
+ /**
235
+ * Log debug
236
+ */
237
+ debug(message: string, ...args: any[]): void {
238
+ if (!this.config.enabled) return;
239
+ this.consola.debug(message, ...args);
240
+ }
241
+
242
+ /**
243
+ * Log success
244
+ */
245
+ success(message: string, ...args: any[]): void {
246
+ if (!this.config.enabled) return;
247
+ this.consola.success(message, ...args);
248
+ }
249
+
250
+ /**
251
+ * Create a sub-logger with prefix
252
+ */
253
+ withTag(tag: string): ConsolaInstance {
254
+ return this.consola.withTag(tag);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Default logger instance
260
+ */
261
+ export const defaultLogger = new APILogger();