kimu-core 0.4.1 → 0.4.2
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/.editorconfig +116 -30
- package/.gitattributes +81 -11
- package/.github/FUNDING.yml +8 -8
- package/.github/kimu-copilot-instructions.md +3779 -3779
- package/.github/workflows/deploy-demo.yml +39 -39
- package/.nvmrc +1 -0
- package/.prettierignore +44 -0
- package/.prettierrc +16 -0
- package/FUNDING.md +31 -31
- package/icon.svg +10 -10
- package/package.json +9 -2
- package/scripts/minify-css-assets.js +82 -82
- package/src/core/index.ts +47 -47
- package/src/core/kimu-global-styles.ts +136 -136
- package/src/core/kimu-reactive.ts +196 -196
- package/src/modules-repository/api-axios/CHANGELOG.md +48 -48
- package/src/modules-repository/api-axios/QUICK-REFERENCE.md +178 -178
- package/src/modules-repository/api-axios/README.md +304 -304
- package/src/modules-repository/api-axios/api-axios-service.ts +355 -355
- package/src/modules-repository/api-axios/examples.ts +293 -293
- package/src/modules-repository/api-axios/index.ts +19 -19
- package/src/modules-repository/api-axios/interfaces.ts +71 -71
- package/src/modules-repository/api-axios/module.ts +41 -41
- package/src/modules-repository/api-core/CHANGELOG.md +42 -42
- package/src/modules-repository/api-core/QUICK-REFERENCE.md +192 -192
- package/src/modules-repository/api-core/README.md +435 -435
- package/src/modules-repository/api-core/api-core-service.ts +289 -289
- package/src/modules-repository/api-core/examples.ts +432 -432
- package/src/modules-repository/api-core/index.ts +8 -8
- package/src/modules-repository/api-core/interfaces.ts +83 -83
- package/src/modules-repository/api-core/module.ts +30 -30
- package/src/modules-repository/event-bus/README.md +273 -273
- package/src/modules-repository/event-bus/event-bus-service.ts +176 -176
- package/src/modules-repository/event-bus/module.ts +30 -30
- package/src/modules-repository/notification/README.md +423 -423
- package/src/modules-repository/notification/module.ts +30 -30
- package/src/modules-repository/notification/notification-service.ts +436 -436
- package/src/modules-repository/router/README.it.md +61 -10
- package/src/modules-repository/router/README.md +61 -10
- package/src/modules-repository/router/router-config.ts.example +61 -0
- package/src/modules-repository/router/router.ts +18 -0
- package/src/modules-repository/state/README.md +409 -409
- package/src/modules-repository/state/module.ts +30 -30
- package/src/modules-repository/state/state-service.ts +296 -296
- package/src/modules-repository/theme/README.md +311 -267
- package/src/modules-repository/theme/module.ts +30 -30
- package/src/modules-repository/theme/pre-build.js +40 -40
- package/src/modules-repository/theme/theme-service.ts +411 -389
- package/src/modules-repository/theme/themes/theme-cherry-blossom.css +78 -78
- package/src/modules-repository/theme/themes/theme-cozy.css +111 -111
- package/src/modules-repository/theme/themes/theme-cyberpunk.css +150 -150
- package/src/modules-repository/theme/themes/theme-dark.css +79 -79
- package/src/modules-repository/theme/themes/theme-forest.css +171 -171
- package/src/modules-repository/theme/themes/theme-gold.css +100 -100
- package/src/modules-repository/theme/themes/theme-high-contrast.css +126 -126
- package/src/modules-repository/theme/themes/theme-lava.css +101 -101
- package/src/modules-repository/theme/themes/theme-lavender.css +90 -90
- package/src/modules-repository/theme/themes/theme-light.css +79 -79
- package/src/modules-repository/theme/themes/theme-matrix.css +103 -103
- package/src/modules-repository/theme/themes/theme-midnight.css +81 -81
- package/src/modules-repository/theme/themes/theme-nord.css +94 -94
- package/src/modules-repository/theme/themes/theme-ocean.css +84 -84
- package/src/modules-repository/theme/themes/theme-retro80s.css +343 -343
- package/src/modules-repository/theme/themes/theme-sunset.css +62 -62
- package/src/modules-repository/theme/themes-config-default.json +19 -0
- package/src/modules-repository/theme/themes-config.d.ts +27 -27
- package/src/modules-repository/theme/{themes-config.json → themes-config.json.example} +223 -213
|
@@ -1,355 +1,355 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Axios Service
|
|
3
|
-
*
|
|
4
|
-
* Advanced HTTP client based on Axios library.
|
|
5
|
-
* Provides additional features over api-core:
|
|
6
|
-
* - Request/response interceptors
|
|
7
|
-
* - Automatic retries with exponential backoff
|
|
8
|
-
* - Request cancellation
|
|
9
|
-
* - Upload/download progress tracking
|
|
10
|
-
* - Better error handling and transformation
|
|
11
|
-
* - Request/response caching
|
|
12
|
-
*
|
|
13
|
-
* @module api-axios
|
|
14
|
-
* @author UnicòVerso
|
|
15
|
-
* @version 1.0.0
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource, InternalAxiosRequestConfig } from 'axios';
|
|
19
|
-
import {
|
|
20
|
-
ApiRequestConfig,
|
|
21
|
-
ApiResponse,
|
|
22
|
-
ApiError,
|
|
23
|
-
HttpMethod,
|
|
24
|
-
RequestInterceptor,
|
|
25
|
-
ResponseInterceptor,
|
|
26
|
-
ErrorInterceptor
|
|
27
|
-
} from './interfaces';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* ApiAxiosService - Advanced HTTP client with Axios
|
|
31
|
-
*
|
|
32
|
-
* Features:
|
|
33
|
-
* - Interceptors for requests and responses
|
|
34
|
-
* - Automatic retry logic with exponential backoff
|
|
35
|
-
* - Request cancellation support
|
|
36
|
-
* - Progress tracking for uploads/downloads
|
|
37
|
-
* - Response caching
|
|
38
|
-
* - Better error handling
|
|
39
|
-
*/
|
|
40
|
-
export class ApiAxiosService {
|
|
41
|
-
private axiosInstance: AxiosInstance;
|
|
42
|
-
private requestInterceptors: RequestInterceptor[] = [];
|
|
43
|
-
private responseInterceptors: ResponseInterceptor[] = [];
|
|
44
|
-
private errorInterceptors: ErrorInterceptor[] = [];
|
|
45
|
-
private cache: Map<string, { data: any; timestamp: number }> = new Map();
|
|
46
|
-
private cacheTTL: number = 5 * 60 * 1000; // 5 minutes default
|
|
47
|
-
|
|
48
|
-
constructor() {
|
|
49
|
-
this.axiosInstance = axios.create({
|
|
50
|
-
timeout: 30000,
|
|
51
|
-
headers: {
|
|
52
|
-
'Content-Type': 'application/json'
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
this.setupInterceptors();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Setup default Axios interceptors
|
|
61
|
-
*/
|
|
62
|
-
private setupInterceptors(): void {
|
|
63
|
-
// Request interceptor
|
|
64
|
-
this.axiosInstance.interceptors.request.use(
|
|
65
|
-
async (config: InternalAxiosRequestConfig) => {
|
|
66
|
-
// Apply custom request interceptors
|
|
67
|
-
for (const interceptor of this.requestInterceptors) {
|
|
68
|
-
const modifiedConfig = await interceptor(config as any);
|
|
69
|
-
Object.assign(config, modifiedConfig);
|
|
70
|
-
}
|
|
71
|
-
return config;
|
|
72
|
-
},
|
|
73
|
-
(error: any) => Promise.reject(error)
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// Response interceptor
|
|
77
|
-
this.axiosInstance.interceptors.response.use(
|
|
78
|
-
async (response: AxiosResponse) => {
|
|
79
|
-
const apiResponse = this.transformResponse(response);
|
|
80
|
-
|
|
81
|
-
// Apply custom response interceptors
|
|
82
|
-
for (const interceptor of this.responseInterceptors) {
|
|
83
|
-
await interceptor(apiResponse);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return response;
|
|
87
|
-
},
|
|
88
|
-
async (error: any) => {
|
|
89
|
-
const apiError = this.transformError(error);
|
|
90
|
-
|
|
91
|
-
// Apply custom error interceptors
|
|
92
|
-
for (const interceptor of this.errorInterceptors) {
|
|
93
|
-
await interceptor(apiError);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return Promise.reject(apiError);
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Add a request interceptor
|
|
103
|
-
*/
|
|
104
|
-
addRequestInterceptor(interceptor: RequestInterceptor): void {
|
|
105
|
-
this.requestInterceptors.push(interceptor);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Add a response interceptor
|
|
110
|
-
*/
|
|
111
|
-
addResponseInterceptor(interceptor: ResponseInterceptor): void {
|
|
112
|
-
this.responseInterceptors.push(interceptor);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Add an error interceptor
|
|
117
|
-
*/
|
|
118
|
-
addErrorInterceptor(interceptor: ErrorInterceptor): void {
|
|
119
|
-
this.errorInterceptors.push(interceptor);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Transform Axios response to ApiResponse format
|
|
124
|
-
*/
|
|
125
|
-
private transformResponse(response: AxiosResponse): ApiResponse {
|
|
126
|
-
return {
|
|
127
|
-
data: response.data,
|
|
128
|
-
status: response.status,
|
|
129
|
-
statusText: response.statusText,
|
|
130
|
-
headers: response.headers as Record<string, string>,
|
|
131
|
-
config: response.config as ApiRequestConfig
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Transform Axios error to ApiError format
|
|
137
|
-
*/
|
|
138
|
-
private transformError(error: any): ApiError {
|
|
139
|
-
if (error.response) {
|
|
140
|
-
return {
|
|
141
|
-
message: error.message || 'Request failed',
|
|
142
|
-
status: error.response.status,
|
|
143
|
-
data: error.response.data,
|
|
144
|
-
originalError: error
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
message: error.message || 'Network error',
|
|
150
|
-
originalError: error
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Generate cache key from URL and config
|
|
156
|
-
*/
|
|
157
|
-
private getCacheKey(url: string, config?: ApiRequestConfig): string {
|
|
158
|
-
const params = config?.params ? JSON.stringify(config.params) : '';
|
|
159
|
-
return `${url}${params}`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Check if cached response is still valid
|
|
164
|
-
*/
|
|
165
|
-
private isCacheValid(timestamp: number): boolean {
|
|
166
|
-
return Date.now() - timestamp < this.cacheTTL;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Set cache TTL (time-to-live) in milliseconds
|
|
171
|
-
*/
|
|
172
|
-
setCacheTTL(ttl: number): void {
|
|
173
|
-
this.cacheTTL = ttl;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Clear all cached responses
|
|
178
|
-
*/
|
|
179
|
-
clearCache(): void {
|
|
180
|
-
this.cache.clear();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Create a cancel token for request cancellation
|
|
185
|
-
*/
|
|
186
|
-
createCancelToken(): CancelTokenSource {
|
|
187
|
-
return axios.CancelToken.source();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Perform HTTP GET request
|
|
192
|
-
*/
|
|
193
|
-
async get<T = any>(url: string, config?: ApiRequestConfig & { useCache?: boolean }): Promise<ApiResponse<T>> {
|
|
194
|
-
const useCache = config?.useCache ?? false;
|
|
195
|
-
const cacheKey = this.getCacheKey(url, config);
|
|
196
|
-
|
|
197
|
-
// Check cache
|
|
198
|
-
if (useCache && this.cache.has(cacheKey)) {
|
|
199
|
-
const cached = this.cache.get(cacheKey)!;
|
|
200
|
-
if (this.isCacheValid(cached.timestamp)) {
|
|
201
|
-
return {
|
|
202
|
-
data: cached.data,
|
|
203
|
-
status: 200,
|
|
204
|
-
statusText: 'OK (cached)',
|
|
205
|
-
headers: {},
|
|
206
|
-
config: config || {}
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const axiosConfig: AxiosRequestConfig = {
|
|
212
|
-
...config?.axiosConfig,
|
|
213
|
-
headers: config?.headers,
|
|
214
|
-
params: config?.params,
|
|
215
|
-
timeout: config?.timeout,
|
|
216
|
-
withCredentials: config?.withCredentials,
|
|
217
|
-
responseType: config?.responseType,
|
|
218
|
-
onUploadProgress: config?.onUploadProgress,
|
|
219
|
-
onDownloadProgress: config?.onDownloadProgress
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const response = await this.axiosInstance.get<T>(url, axiosConfig);
|
|
223
|
-
const apiResponse = this.transformResponse(response);
|
|
224
|
-
|
|
225
|
-
// Store in cache if enabled
|
|
226
|
-
if (useCache) {
|
|
227
|
-
this.cache.set(cacheKey, {
|
|
228
|
-
data: apiResponse.data,
|
|
229
|
-
timestamp: Date.now()
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return apiResponse;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Perform HTTP POST request
|
|
238
|
-
*/
|
|
239
|
-
async post<T = any>(url: string, data?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
240
|
-
const axiosConfig: AxiosRequestConfig = {
|
|
241
|
-
...config?.axiosConfig,
|
|
242
|
-
headers: config?.headers,
|
|
243
|
-
params: config?.params,
|
|
244
|
-
timeout: config?.timeout,
|
|
245
|
-
withCredentials: config?.withCredentials,
|
|
246
|
-
responseType: config?.responseType,
|
|
247
|
-
onUploadProgress: config?.onUploadProgress,
|
|
248
|
-
onDownloadProgress: config?.onDownloadProgress
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const response = await this.axiosInstance.post<T>(url, data, axiosConfig);
|
|
252
|
-
return this.transformResponse(response);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Perform HTTP PUT request
|
|
257
|
-
*/
|
|
258
|
-
async put<T = any>(url: string, data?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
259
|
-
const axiosConfig: AxiosRequestConfig = {
|
|
260
|
-
...config?.axiosConfig,
|
|
261
|
-
headers: config?.headers,
|
|
262
|
-
params: config?.params,
|
|
263
|
-
timeout: config?.timeout,
|
|
264
|
-
withCredentials: config?.withCredentials,
|
|
265
|
-
responseType: config?.responseType,
|
|
266
|
-
onUploadProgress: config?.onUploadProgress,
|
|
267
|
-
onDownloadProgress: config?.onDownloadProgress
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const response = await this.axiosInstance.put<T>(url, data, axiosConfig);
|
|
271
|
-
return this.transformResponse(response);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Perform HTTP PATCH request
|
|
276
|
-
*/
|
|
277
|
-
async patch<T = any>(url: string, data?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
278
|
-
const axiosConfig: AxiosRequestConfig = {
|
|
279
|
-
...config?.axiosConfig,
|
|
280
|
-
headers: config?.headers,
|
|
281
|
-
params: config?.params,
|
|
282
|
-
timeout: config?.timeout,
|
|
283
|
-
withCredentials: config?.withCredentials,
|
|
284
|
-
responseType: config?.responseType,
|
|
285
|
-
onUploadProgress: config?.onUploadProgress,
|
|
286
|
-
onDownloadProgress: config?.onDownloadProgress
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const response = await this.axiosInstance.patch<T>(url, data, axiosConfig);
|
|
290
|
-
return this.transformResponse(response);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Perform HTTP DELETE request
|
|
295
|
-
*/
|
|
296
|
-
async delete<T = any>(url: string, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
297
|
-
const axiosConfig: AxiosRequestConfig = {
|
|
298
|
-
...config?.axiosConfig,
|
|
299
|
-
headers: config?.headers,
|
|
300
|
-
params: config?.params,
|
|
301
|
-
timeout: config?.timeout,
|
|
302
|
-
withCredentials: config?.withCredentials,
|
|
303
|
-
responseType: config?.responseType
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const response = await this.axiosInstance.delete<T>(url, axiosConfig);
|
|
307
|
-
return this.transformResponse(response);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Perform a generic HTTP request with automatic retry
|
|
312
|
-
*/
|
|
313
|
-
async request<T = any>(
|
|
314
|
-
method: HttpMethod,
|
|
315
|
-
url: string,
|
|
316
|
-
data?: any,
|
|
317
|
-
config?: ApiRequestConfig & { retries?: number; retryDelay?: number }
|
|
318
|
-
): Promise<ApiResponse<T>> {
|
|
319
|
-
const retries = config?.retries ?? 0;
|
|
320
|
-
const retryDelay = config?.retryDelay ?? 1000;
|
|
321
|
-
|
|
322
|
-
let lastError: ApiError | null = null;
|
|
323
|
-
|
|
324
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
325
|
-
try {
|
|
326
|
-
switch (method) {
|
|
327
|
-
case 'GET':
|
|
328
|
-
return await this.get<T>(url, config);
|
|
329
|
-
case 'POST':
|
|
330
|
-
return await this.post<T>(url, data, config);
|
|
331
|
-
case 'PUT':
|
|
332
|
-
return await this.put<T>(url, data, config);
|
|
333
|
-
case 'PATCH':
|
|
334
|
-
return await this.patch<T>(url, data, config);
|
|
335
|
-
case 'DELETE':
|
|
336
|
-
return await this.delete<T>(url, config);
|
|
337
|
-
default:
|
|
338
|
-
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
339
|
-
}
|
|
340
|
-
} catch (error: any) {
|
|
341
|
-
lastError = error as ApiError;
|
|
342
|
-
|
|
343
|
-
if (attempt < retries) {
|
|
344
|
-
// Exponential backoff
|
|
345
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay * Math.pow(2, attempt)));
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
throw lastError;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Singleton instance
|
|
355
|
-
export const apiAxiosService = new ApiAxiosService();
|
|
1
|
+
/**
|
|
2
|
+
* API Axios Service
|
|
3
|
+
*
|
|
4
|
+
* Advanced HTTP client based on Axios library.
|
|
5
|
+
* Provides additional features over api-core:
|
|
6
|
+
* - Request/response interceptors
|
|
7
|
+
* - Automatic retries with exponential backoff
|
|
8
|
+
* - Request cancellation
|
|
9
|
+
* - Upload/download progress tracking
|
|
10
|
+
* - Better error handling and transformation
|
|
11
|
+
* - Request/response caching
|
|
12
|
+
*
|
|
13
|
+
* @module api-axios
|
|
14
|
+
* @author UnicòVerso
|
|
15
|
+
* @version 1.0.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource, InternalAxiosRequestConfig } from 'axios';
|
|
19
|
+
import {
|
|
20
|
+
ApiRequestConfig,
|
|
21
|
+
ApiResponse,
|
|
22
|
+
ApiError,
|
|
23
|
+
HttpMethod,
|
|
24
|
+
RequestInterceptor,
|
|
25
|
+
ResponseInterceptor,
|
|
26
|
+
ErrorInterceptor
|
|
27
|
+
} from './interfaces';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ApiAxiosService - Advanced HTTP client with Axios
|
|
31
|
+
*
|
|
32
|
+
* Features:
|
|
33
|
+
* - Interceptors for requests and responses
|
|
34
|
+
* - Automatic retry logic with exponential backoff
|
|
35
|
+
* - Request cancellation support
|
|
36
|
+
* - Progress tracking for uploads/downloads
|
|
37
|
+
* - Response caching
|
|
38
|
+
* - Better error handling
|
|
39
|
+
*/
|
|
40
|
+
export class ApiAxiosService {
|
|
41
|
+
private axiosInstance: AxiosInstance;
|
|
42
|
+
private requestInterceptors: RequestInterceptor[] = [];
|
|
43
|
+
private responseInterceptors: ResponseInterceptor[] = [];
|
|
44
|
+
private errorInterceptors: ErrorInterceptor[] = [];
|
|
45
|
+
private cache: Map<string, { data: any; timestamp: number }> = new Map();
|
|
46
|
+
private cacheTTL: number = 5 * 60 * 1000; // 5 minutes default
|
|
47
|
+
|
|
48
|
+
constructor() {
|
|
49
|
+
this.axiosInstance = axios.create({
|
|
50
|
+
timeout: 30000,
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json'
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this.setupInterceptors();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Setup default Axios interceptors
|
|
61
|
+
*/
|
|
62
|
+
private setupInterceptors(): void {
|
|
63
|
+
// Request interceptor
|
|
64
|
+
this.axiosInstance.interceptors.request.use(
|
|
65
|
+
async (config: InternalAxiosRequestConfig) => {
|
|
66
|
+
// Apply custom request interceptors
|
|
67
|
+
for (const interceptor of this.requestInterceptors) {
|
|
68
|
+
const modifiedConfig = await interceptor(config as any);
|
|
69
|
+
Object.assign(config, modifiedConfig);
|
|
70
|
+
}
|
|
71
|
+
return config;
|
|
72
|
+
},
|
|
73
|
+
(error: any) => Promise.reject(error)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Response interceptor
|
|
77
|
+
this.axiosInstance.interceptors.response.use(
|
|
78
|
+
async (response: AxiosResponse) => {
|
|
79
|
+
const apiResponse = this.transformResponse(response);
|
|
80
|
+
|
|
81
|
+
// Apply custom response interceptors
|
|
82
|
+
for (const interceptor of this.responseInterceptors) {
|
|
83
|
+
await interceptor(apiResponse);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return response;
|
|
87
|
+
},
|
|
88
|
+
async (error: any) => {
|
|
89
|
+
const apiError = this.transformError(error);
|
|
90
|
+
|
|
91
|
+
// Apply custom error interceptors
|
|
92
|
+
for (const interceptor of this.errorInterceptors) {
|
|
93
|
+
await interceptor(apiError);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Promise.reject(apiError);
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Add a request interceptor
|
|
103
|
+
*/
|
|
104
|
+
addRequestInterceptor(interceptor: RequestInterceptor): void {
|
|
105
|
+
this.requestInterceptors.push(interceptor);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Add a response interceptor
|
|
110
|
+
*/
|
|
111
|
+
addResponseInterceptor(interceptor: ResponseInterceptor): void {
|
|
112
|
+
this.responseInterceptors.push(interceptor);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Add an error interceptor
|
|
117
|
+
*/
|
|
118
|
+
addErrorInterceptor(interceptor: ErrorInterceptor): void {
|
|
119
|
+
this.errorInterceptors.push(interceptor);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Transform Axios response to ApiResponse format
|
|
124
|
+
*/
|
|
125
|
+
private transformResponse(response: AxiosResponse): ApiResponse {
|
|
126
|
+
return {
|
|
127
|
+
data: response.data,
|
|
128
|
+
status: response.status,
|
|
129
|
+
statusText: response.statusText,
|
|
130
|
+
headers: response.headers as Record<string, string>,
|
|
131
|
+
config: response.config as ApiRequestConfig
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Transform Axios error to ApiError format
|
|
137
|
+
*/
|
|
138
|
+
private transformError(error: any): ApiError {
|
|
139
|
+
if (error.response) {
|
|
140
|
+
return {
|
|
141
|
+
message: error.message || 'Request failed',
|
|
142
|
+
status: error.response.status,
|
|
143
|
+
data: error.response.data,
|
|
144
|
+
originalError: error
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
message: error.message || 'Network error',
|
|
150
|
+
originalError: error
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Generate cache key from URL and config
|
|
156
|
+
*/
|
|
157
|
+
private getCacheKey(url: string, config?: ApiRequestConfig): string {
|
|
158
|
+
const params = config?.params ? JSON.stringify(config.params) : '';
|
|
159
|
+
return `${url}${params}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if cached response is still valid
|
|
164
|
+
*/
|
|
165
|
+
private isCacheValid(timestamp: number): boolean {
|
|
166
|
+
return Date.now() - timestamp < this.cacheTTL;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Set cache TTL (time-to-live) in milliseconds
|
|
171
|
+
*/
|
|
172
|
+
setCacheTTL(ttl: number): void {
|
|
173
|
+
this.cacheTTL = ttl;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Clear all cached responses
|
|
178
|
+
*/
|
|
179
|
+
clearCache(): void {
|
|
180
|
+
this.cache.clear();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create a cancel token for request cancellation
|
|
185
|
+
*/
|
|
186
|
+
createCancelToken(): CancelTokenSource {
|
|
187
|
+
return axios.CancelToken.source();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Perform HTTP GET request
|
|
192
|
+
*/
|
|
193
|
+
async get<T = any>(url: string, config?: ApiRequestConfig & { useCache?: boolean }): Promise<ApiResponse<T>> {
|
|
194
|
+
const useCache = config?.useCache ?? false;
|
|
195
|
+
const cacheKey = this.getCacheKey(url, config);
|
|
196
|
+
|
|
197
|
+
// Check cache
|
|
198
|
+
if (useCache && this.cache.has(cacheKey)) {
|
|
199
|
+
const cached = this.cache.get(cacheKey)!;
|
|
200
|
+
if (this.isCacheValid(cached.timestamp)) {
|
|
201
|
+
return {
|
|
202
|
+
data: cached.data,
|
|
203
|
+
status: 200,
|
|
204
|
+
statusText: 'OK (cached)',
|
|
205
|
+
headers: {},
|
|
206
|
+
config: config || {}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const axiosConfig: AxiosRequestConfig = {
|
|
212
|
+
...config?.axiosConfig,
|
|
213
|
+
headers: config?.headers,
|
|
214
|
+
params: config?.params,
|
|
215
|
+
timeout: config?.timeout,
|
|
216
|
+
withCredentials: config?.withCredentials,
|
|
217
|
+
responseType: config?.responseType,
|
|
218
|
+
onUploadProgress: config?.onUploadProgress,
|
|
219
|
+
onDownloadProgress: config?.onDownloadProgress
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const response = await this.axiosInstance.get<T>(url, axiosConfig);
|
|
223
|
+
const apiResponse = this.transformResponse(response);
|
|
224
|
+
|
|
225
|
+
// Store in cache if enabled
|
|
226
|
+
if (useCache) {
|
|
227
|
+
this.cache.set(cacheKey, {
|
|
228
|
+
data: apiResponse.data,
|
|
229
|
+
timestamp: Date.now()
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return apiResponse;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Perform HTTP POST request
|
|
238
|
+
*/
|
|
239
|
+
async post<T = any>(url: string, data?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
240
|
+
const axiosConfig: AxiosRequestConfig = {
|
|
241
|
+
...config?.axiosConfig,
|
|
242
|
+
headers: config?.headers,
|
|
243
|
+
params: config?.params,
|
|
244
|
+
timeout: config?.timeout,
|
|
245
|
+
withCredentials: config?.withCredentials,
|
|
246
|
+
responseType: config?.responseType,
|
|
247
|
+
onUploadProgress: config?.onUploadProgress,
|
|
248
|
+
onDownloadProgress: config?.onDownloadProgress
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const response = await this.axiosInstance.post<T>(url, data, axiosConfig);
|
|
252
|
+
return this.transformResponse(response);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Perform HTTP PUT request
|
|
257
|
+
*/
|
|
258
|
+
async put<T = any>(url: string, data?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
259
|
+
const axiosConfig: AxiosRequestConfig = {
|
|
260
|
+
...config?.axiosConfig,
|
|
261
|
+
headers: config?.headers,
|
|
262
|
+
params: config?.params,
|
|
263
|
+
timeout: config?.timeout,
|
|
264
|
+
withCredentials: config?.withCredentials,
|
|
265
|
+
responseType: config?.responseType,
|
|
266
|
+
onUploadProgress: config?.onUploadProgress,
|
|
267
|
+
onDownloadProgress: config?.onDownloadProgress
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const response = await this.axiosInstance.put<T>(url, data, axiosConfig);
|
|
271
|
+
return this.transformResponse(response);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Perform HTTP PATCH request
|
|
276
|
+
*/
|
|
277
|
+
async patch<T = any>(url: string, data?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
278
|
+
const axiosConfig: AxiosRequestConfig = {
|
|
279
|
+
...config?.axiosConfig,
|
|
280
|
+
headers: config?.headers,
|
|
281
|
+
params: config?.params,
|
|
282
|
+
timeout: config?.timeout,
|
|
283
|
+
withCredentials: config?.withCredentials,
|
|
284
|
+
responseType: config?.responseType,
|
|
285
|
+
onUploadProgress: config?.onUploadProgress,
|
|
286
|
+
onDownloadProgress: config?.onDownloadProgress
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const response = await this.axiosInstance.patch<T>(url, data, axiosConfig);
|
|
290
|
+
return this.transformResponse(response);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Perform HTTP DELETE request
|
|
295
|
+
*/
|
|
296
|
+
async delete<T = any>(url: string, config?: ApiRequestConfig): Promise<ApiResponse<T>> {
|
|
297
|
+
const axiosConfig: AxiosRequestConfig = {
|
|
298
|
+
...config?.axiosConfig,
|
|
299
|
+
headers: config?.headers,
|
|
300
|
+
params: config?.params,
|
|
301
|
+
timeout: config?.timeout,
|
|
302
|
+
withCredentials: config?.withCredentials,
|
|
303
|
+
responseType: config?.responseType
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const response = await this.axiosInstance.delete<T>(url, axiosConfig);
|
|
307
|
+
return this.transformResponse(response);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Perform a generic HTTP request with automatic retry
|
|
312
|
+
*/
|
|
313
|
+
async request<T = any>(
|
|
314
|
+
method: HttpMethod,
|
|
315
|
+
url: string,
|
|
316
|
+
data?: any,
|
|
317
|
+
config?: ApiRequestConfig & { retries?: number; retryDelay?: number }
|
|
318
|
+
): Promise<ApiResponse<T>> {
|
|
319
|
+
const retries = config?.retries ?? 0;
|
|
320
|
+
const retryDelay = config?.retryDelay ?? 1000;
|
|
321
|
+
|
|
322
|
+
let lastError: ApiError | null = null;
|
|
323
|
+
|
|
324
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
325
|
+
try {
|
|
326
|
+
switch (method) {
|
|
327
|
+
case 'GET':
|
|
328
|
+
return await this.get<T>(url, config);
|
|
329
|
+
case 'POST':
|
|
330
|
+
return await this.post<T>(url, data, config);
|
|
331
|
+
case 'PUT':
|
|
332
|
+
return await this.put<T>(url, data, config);
|
|
333
|
+
case 'PATCH':
|
|
334
|
+
return await this.patch<T>(url, data, config);
|
|
335
|
+
case 'DELETE':
|
|
336
|
+
return await this.delete<T>(url, config);
|
|
337
|
+
default:
|
|
338
|
+
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
339
|
+
}
|
|
340
|
+
} catch (error: any) {
|
|
341
|
+
lastError = error as ApiError;
|
|
342
|
+
|
|
343
|
+
if (attempt < retries) {
|
|
344
|
+
// Exponential backoff
|
|
345
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay * Math.pow(2, attempt)));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
throw lastError;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Singleton instance
|
|
355
|
+
export const apiAxiosService = new ApiAxiosService();
|