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,289 +1,289 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KIMU API Core Service
|
|
3
|
-
* Lightweight HTTP client based on native fetch API
|
|
4
|
-
* Zero dependencies, Promise-based, TypeScript-first
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
ApiRequestOptions,
|
|
9
|
-
ApiResponse,
|
|
10
|
-
ApiError,
|
|
11
|
-
ApiConfig
|
|
12
|
-
} from './interfaces';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Main API service class for making HTTP requests
|
|
16
|
-
*/
|
|
17
|
-
export class ApiCoreService {
|
|
18
|
-
private config: ApiConfig = {};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Configure the API service with global settings
|
|
22
|
-
* @param config - Global configuration options
|
|
23
|
-
*/
|
|
24
|
-
configure(config: ApiConfig): void {
|
|
25
|
-
this.config = { ...this.config, ...config };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Make a GET request
|
|
30
|
-
* @param url - Request URL (relative to baseURL if configured)
|
|
31
|
-
* @param options - Request options
|
|
32
|
-
* @returns Promise with response data
|
|
33
|
-
*/
|
|
34
|
-
async get<T = any>(url: string, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
35
|
-
return this.request<T>(url, { ...options, method: 'GET' });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Make a POST request
|
|
40
|
-
* @param url - Request URL
|
|
41
|
-
* @param data - Request body data
|
|
42
|
-
* @param options - Request options
|
|
43
|
-
* @returns Promise with response data
|
|
44
|
-
*/
|
|
45
|
-
async post<T = any>(url: string, data?: any, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
46
|
-
return this.request<T>(url, { ...options, method: 'POST', body: data });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Make a PUT request
|
|
51
|
-
* @param url - Request URL
|
|
52
|
-
* @param data - Request body data
|
|
53
|
-
* @param options - Request options
|
|
54
|
-
* @returns Promise with response data
|
|
55
|
-
*/
|
|
56
|
-
async put<T = any>(url: string, data?: any, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
57
|
-
return this.request<T>(url, { ...options, method: 'PUT', body: data });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Make a PATCH request
|
|
62
|
-
* @param url - Request URL
|
|
63
|
-
* @param data - Request body data
|
|
64
|
-
* @param options - Request options
|
|
65
|
-
* @returns Promise with response data
|
|
66
|
-
*/
|
|
67
|
-
async patch<T = any>(url: string, data?: any, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
68
|
-
return this.request<T>(url, { ...options, method: 'PATCH', body: data });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Make a DELETE request
|
|
73
|
-
* @param url - Request URL
|
|
74
|
-
* @param options - Request options
|
|
75
|
-
* @returns Promise with response data
|
|
76
|
-
*/
|
|
77
|
-
async delete<T = any>(url: string, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
78
|
-
return this.request<T>(url, { ...options, method: 'DELETE' });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Make a generic HTTP request
|
|
83
|
-
* @param url - Request URL
|
|
84
|
-
* @param options - Request options
|
|
85
|
-
* @returns Promise with response data
|
|
86
|
-
*/
|
|
87
|
-
async request<T = any>(url: string, options: ApiRequestOptions = {}): Promise<ApiResponse<T>> {
|
|
88
|
-
try {
|
|
89
|
-
// Apply request interceptor if configured
|
|
90
|
-
let finalOptions = options;
|
|
91
|
-
if (this.config.requestInterceptor) {
|
|
92
|
-
finalOptions = await this.config.requestInterceptor(options);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Build final URL with baseURL and query params
|
|
96
|
-
const finalUrl = this.buildUrl(url, finalOptions.params);
|
|
97
|
-
|
|
98
|
-
// Build headers
|
|
99
|
-
const headers = this.buildHeaders(finalOptions);
|
|
100
|
-
|
|
101
|
-
// Build request body
|
|
102
|
-
const body = this.buildBody(finalOptions.body, headers);
|
|
103
|
-
|
|
104
|
-
// Setup timeout if specified
|
|
105
|
-
const controller = new AbortController();
|
|
106
|
-
const signal = finalOptions.signal || controller.signal;
|
|
107
|
-
const timeout = finalOptions.timeout || this.config.timeout;
|
|
108
|
-
|
|
109
|
-
let timeoutId: NodeJS.Timeout | undefined;
|
|
110
|
-
if (timeout) {
|
|
111
|
-
timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Make fetch request
|
|
115
|
-
const response = await fetch(finalUrl, {
|
|
116
|
-
method: finalOptions.method || 'GET',
|
|
117
|
-
headers,
|
|
118
|
-
body,
|
|
119
|
-
credentials: finalOptions.credentials,
|
|
120
|
-
cache: finalOptions.cache,
|
|
121
|
-
signal
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Clear timeout
|
|
125
|
-
if (timeoutId) {
|
|
126
|
-
clearTimeout(timeoutId);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Parse response
|
|
130
|
-
const responseData = await this.parseResponse<T>(response, finalOptions.responseType);
|
|
131
|
-
|
|
132
|
-
// Build API response object
|
|
133
|
-
let apiResponse: ApiResponse<T> = {
|
|
134
|
-
data: responseData,
|
|
135
|
-
status: response.status,
|
|
136
|
-
statusText: response.statusText,
|
|
137
|
-
headers: response.headers,
|
|
138
|
-
raw: response
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Apply response interceptor if configured
|
|
142
|
-
if (this.config.responseInterceptor) {
|
|
143
|
-
apiResponse = await this.config.responseInterceptor(apiResponse);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Check if response is ok
|
|
147
|
-
if (!response.ok) {
|
|
148
|
-
throw this.createError(
|
|
149
|
-
`HTTP Error ${response.status}: ${response.statusText}`,
|
|
150
|
-
response.status,
|
|
151
|
-
response.statusText,
|
|
152
|
-
responseData
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return apiResponse;
|
|
157
|
-
|
|
158
|
-
} catch (error: any) {
|
|
159
|
-
// Create API error
|
|
160
|
-
let apiError: ApiError;
|
|
161
|
-
|
|
162
|
-
if (error.name === 'AbortError') {
|
|
163
|
-
apiError = this.createError('Request timeout', undefined, undefined, undefined, error);
|
|
164
|
-
} else if (error.message?.includes('HTTP Error')) {
|
|
165
|
-
apiError = error;
|
|
166
|
-
} else {
|
|
167
|
-
apiError = this.createError(
|
|
168
|
-
error.message || 'Network error',
|
|
169
|
-
undefined,
|
|
170
|
-
undefined,
|
|
171
|
-
undefined,
|
|
172
|
-
error
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Apply error interceptor if configured
|
|
177
|
-
if (this.config.errorInterceptor) {
|
|
178
|
-
apiError = await this.config.errorInterceptor(apiError);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
throw apiError;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Build final URL with baseURL and query parameters
|
|
187
|
-
*/
|
|
188
|
-
private buildUrl(url: string, params?: Record<string, string | number | boolean>): string {
|
|
189
|
-
let finalUrl = url;
|
|
190
|
-
|
|
191
|
-
// Add baseURL if configured and URL is relative
|
|
192
|
-
if (this.config.baseURL && !url.startsWith('http')) {
|
|
193
|
-
finalUrl = `${this.config.baseURL.replace(/\/$/, '')}/${url.replace(/^\//, '')}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Add query parameters
|
|
197
|
-
if (params) {
|
|
198
|
-
const queryString = Object.entries(params)
|
|
199
|
-
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
|
200
|
-
.join('&');
|
|
201
|
-
|
|
202
|
-
finalUrl += (finalUrl.includes('?') ? '&' : '?') + queryString;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return finalUrl;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Build request headers
|
|
210
|
-
*/
|
|
211
|
-
private buildHeaders(options: ApiRequestOptions): HeadersInit {
|
|
212
|
-
const headers: Record<string, string> = {
|
|
213
|
-
...this.config.headers,
|
|
214
|
-
...options.headers
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// Auto-add Content-Type for JSON body if not specified
|
|
218
|
-
if (options.body && typeof options.body === 'object' && !headers['Content-Type']) {
|
|
219
|
-
headers['Content-Type'] = 'application/json';
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return headers;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Build request body
|
|
227
|
-
*/
|
|
228
|
-
private buildBody(body: any, _headers: HeadersInit): BodyInit | undefined {
|
|
229
|
-
if (!body) {
|
|
230
|
-
return undefined;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// If body is already a string, FormData, Blob, etc., return as-is
|
|
234
|
-
if (typeof body === 'string' || body instanceof FormData || body instanceof Blob) {
|
|
235
|
-
return body;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Otherwise, stringify as JSON
|
|
239
|
-
return JSON.stringify(body);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Parse response based on response type
|
|
244
|
-
*/
|
|
245
|
-
private async parseResponse<T>(response: Response, responseType?: string): Promise<T> {
|
|
246
|
-
const type = responseType || 'json';
|
|
247
|
-
|
|
248
|
-
switch (type) {
|
|
249
|
-
case 'json':
|
|
250
|
-
try {
|
|
251
|
-
return await response.json();
|
|
252
|
-
} catch {
|
|
253
|
-
return null as T;
|
|
254
|
-
}
|
|
255
|
-
case 'text':
|
|
256
|
-
return await response.text() as T;
|
|
257
|
-
case 'blob':
|
|
258
|
-
return await response.blob() as T;
|
|
259
|
-
case 'arraybuffer':
|
|
260
|
-
return await response.arrayBuffer() as T;
|
|
261
|
-
default:
|
|
262
|
-
return await response.json();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Create an API error object
|
|
268
|
-
*/
|
|
269
|
-
private createError(
|
|
270
|
-
message: string,
|
|
271
|
-
status?: number,
|
|
272
|
-
statusText?: string,
|
|
273
|
-
data?: any,
|
|
274
|
-
originalError?: Error
|
|
275
|
-
): ApiError {
|
|
276
|
-
return {
|
|
277
|
-
message,
|
|
278
|
-
status,
|
|
279
|
-
statusText,
|
|
280
|
-
data,
|
|
281
|
-
originalError: originalError || new Error(message)
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Singleton instance of API Core Service
|
|
288
|
-
*/
|
|
289
|
-
export const apiCoreService = new ApiCoreService();
|
|
1
|
+
/**
|
|
2
|
+
* KIMU API Core Service
|
|
3
|
+
* Lightweight HTTP client based on native fetch API
|
|
4
|
+
* Zero dependencies, Promise-based, TypeScript-first
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ApiRequestOptions,
|
|
9
|
+
ApiResponse,
|
|
10
|
+
ApiError,
|
|
11
|
+
ApiConfig
|
|
12
|
+
} from './interfaces';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Main API service class for making HTTP requests
|
|
16
|
+
*/
|
|
17
|
+
export class ApiCoreService {
|
|
18
|
+
private config: ApiConfig = {};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configure the API service with global settings
|
|
22
|
+
* @param config - Global configuration options
|
|
23
|
+
*/
|
|
24
|
+
configure(config: ApiConfig): void {
|
|
25
|
+
this.config = { ...this.config, ...config };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Make a GET request
|
|
30
|
+
* @param url - Request URL (relative to baseURL if configured)
|
|
31
|
+
* @param options - Request options
|
|
32
|
+
* @returns Promise with response data
|
|
33
|
+
*/
|
|
34
|
+
async get<T = any>(url: string, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
35
|
+
return this.request<T>(url, { ...options, method: 'GET' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Make a POST request
|
|
40
|
+
* @param url - Request URL
|
|
41
|
+
* @param data - Request body data
|
|
42
|
+
* @param options - Request options
|
|
43
|
+
* @returns Promise with response data
|
|
44
|
+
*/
|
|
45
|
+
async post<T = any>(url: string, data?: any, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
46
|
+
return this.request<T>(url, { ...options, method: 'POST', body: data });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Make a PUT request
|
|
51
|
+
* @param url - Request URL
|
|
52
|
+
* @param data - Request body data
|
|
53
|
+
* @param options - Request options
|
|
54
|
+
* @returns Promise with response data
|
|
55
|
+
*/
|
|
56
|
+
async put<T = any>(url: string, data?: any, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
57
|
+
return this.request<T>(url, { ...options, method: 'PUT', body: data });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Make a PATCH request
|
|
62
|
+
* @param url - Request URL
|
|
63
|
+
* @param data - Request body data
|
|
64
|
+
* @param options - Request options
|
|
65
|
+
* @returns Promise with response data
|
|
66
|
+
*/
|
|
67
|
+
async patch<T = any>(url: string, data?: any, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
68
|
+
return this.request<T>(url, { ...options, method: 'PATCH', body: data });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Make a DELETE request
|
|
73
|
+
* @param url - Request URL
|
|
74
|
+
* @param options - Request options
|
|
75
|
+
* @returns Promise with response data
|
|
76
|
+
*/
|
|
77
|
+
async delete<T = any>(url: string, options?: ApiRequestOptions): Promise<ApiResponse<T>> {
|
|
78
|
+
return this.request<T>(url, { ...options, method: 'DELETE' });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Make a generic HTTP request
|
|
83
|
+
* @param url - Request URL
|
|
84
|
+
* @param options - Request options
|
|
85
|
+
* @returns Promise with response data
|
|
86
|
+
*/
|
|
87
|
+
async request<T = any>(url: string, options: ApiRequestOptions = {}): Promise<ApiResponse<T>> {
|
|
88
|
+
try {
|
|
89
|
+
// Apply request interceptor if configured
|
|
90
|
+
let finalOptions = options;
|
|
91
|
+
if (this.config.requestInterceptor) {
|
|
92
|
+
finalOptions = await this.config.requestInterceptor(options);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Build final URL with baseURL and query params
|
|
96
|
+
const finalUrl = this.buildUrl(url, finalOptions.params);
|
|
97
|
+
|
|
98
|
+
// Build headers
|
|
99
|
+
const headers = this.buildHeaders(finalOptions);
|
|
100
|
+
|
|
101
|
+
// Build request body
|
|
102
|
+
const body = this.buildBody(finalOptions.body, headers);
|
|
103
|
+
|
|
104
|
+
// Setup timeout if specified
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const signal = finalOptions.signal || controller.signal;
|
|
107
|
+
const timeout = finalOptions.timeout || this.config.timeout;
|
|
108
|
+
|
|
109
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
110
|
+
if (timeout) {
|
|
111
|
+
timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Make fetch request
|
|
115
|
+
const response = await fetch(finalUrl, {
|
|
116
|
+
method: finalOptions.method || 'GET',
|
|
117
|
+
headers,
|
|
118
|
+
body,
|
|
119
|
+
credentials: finalOptions.credentials,
|
|
120
|
+
cache: finalOptions.cache,
|
|
121
|
+
signal
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Clear timeout
|
|
125
|
+
if (timeoutId) {
|
|
126
|
+
clearTimeout(timeoutId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Parse response
|
|
130
|
+
const responseData = await this.parseResponse<T>(response, finalOptions.responseType);
|
|
131
|
+
|
|
132
|
+
// Build API response object
|
|
133
|
+
let apiResponse: ApiResponse<T> = {
|
|
134
|
+
data: responseData,
|
|
135
|
+
status: response.status,
|
|
136
|
+
statusText: response.statusText,
|
|
137
|
+
headers: response.headers,
|
|
138
|
+
raw: response
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Apply response interceptor if configured
|
|
142
|
+
if (this.config.responseInterceptor) {
|
|
143
|
+
apiResponse = await this.config.responseInterceptor(apiResponse);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check if response is ok
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw this.createError(
|
|
149
|
+
`HTTP Error ${response.status}: ${response.statusText}`,
|
|
150
|
+
response.status,
|
|
151
|
+
response.statusText,
|
|
152
|
+
responseData
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return apiResponse;
|
|
157
|
+
|
|
158
|
+
} catch (error: any) {
|
|
159
|
+
// Create API error
|
|
160
|
+
let apiError: ApiError;
|
|
161
|
+
|
|
162
|
+
if (error.name === 'AbortError') {
|
|
163
|
+
apiError = this.createError('Request timeout', undefined, undefined, undefined, error);
|
|
164
|
+
} else if (error.message?.includes('HTTP Error')) {
|
|
165
|
+
apiError = error;
|
|
166
|
+
} else {
|
|
167
|
+
apiError = this.createError(
|
|
168
|
+
error.message || 'Network error',
|
|
169
|
+
undefined,
|
|
170
|
+
undefined,
|
|
171
|
+
undefined,
|
|
172
|
+
error
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Apply error interceptor if configured
|
|
177
|
+
if (this.config.errorInterceptor) {
|
|
178
|
+
apiError = await this.config.errorInterceptor(apiError);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
throw apiError;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Build final URL with baseURL and query parameters
|
|
187
|
+
*/
|
|
188
|
+
private buildUrl(url: string, params?: Record<string, string | number | boolean>): string {
|
|
189
|
+
let finalUrl = url;
|
|
190
|
+
|
|
191
|
+
// Add baseURL if configured and URL is relative
|
|
192
|
+
if (this.config.baseURL && !url.startsWith('http')) {
|
|
193
|
+
finalUrl = `${this.config.baseURL.replace(/\/$/, '')}/${url.replace(/^\//, '')}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Add query parameters
|
|
197
|
+
if (params) {
|
|
198
|
+
const queryString = Object.entries(params)
|
|
199
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
|
200
|
+
.join('&');
|
|
201
|
+
|
|
202
|
+
finalUrl += (finalUrl.includes('?') ? '&' : '?') + queryString;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return finalUrl;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Build request headers
|
|
210
|
+
*/
|
|
211
|
+
private buildHeaders(options: ApiRequestOptions): HeadersInit {
|
|
212
|
+
const headers: Record<string, string> = {
|
|
213
|
+
...this.config.headers,
|
|
214
|
+
...options.headers
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Auto-add Content-Type for JSON body if not specified
|
|
218
|
+
if (options.body && typeof options.body === 'object' && !headers['Content-Type']) {
|
|
219
|
+
headers['Content-Type'] = 'application/json';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return headers;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Build request body
|
|
227
|
+
*/
|
|
228
|
+
private buildBody(body: any, _headers: HeadersInit): BodyInit | undefined {
|
|
229
|
+
if (!body) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// If body is already a string, FormData, Blob, etc., return as-is
|
|
234
|
+
if (typeof body === 'string' || body instanceof FormData || body instanceof Blob) {
|
|
235
|
+
return body;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Otherwise, stringify as JSON
|
|
239
|
+
return JSON.stringify(body);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Parse response based on response type
|
|
244
|
+
*/
|
|
245
|
+
private async parseResponse<T>(response: Response, responseType?: string): Promise<T> {
|
|
246
|
+
const type = responseType || 'json';
|
|
247
|
+
|
|
248
|
+
switch (type) {
|
|
249
|
+
case 'json':
|
|
250
|
+
try {
|
|
251
|
+
return await response.json();
|
|
252
|
+
} catch {
|
|
253
|
+
return null as T;
|
|
254
|
+
}
|
|
255
|
+
case 'text':
|
|
256
|
+
return await response.text() as T;
|
|
257
|
+
case 'blob':
|
|
258
|
+
return await response.blob() as T;
|
|
259
|
+
case 'arraybuffer':
|
|
260
|
+
return await response.arrayBuffer() as T;
|
|
261
|
+
default:
|
|
262
|
+
return await response.json();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Create an API error object
|
|
268
|
+
*/
|
|
269
|
+
private createError(
|
|
270
|
+
message: string,
|
|
271
|
+
status?: number,
|
|
272
|
+
statusText?: string,
|
|
273
|
+
data?: any,
|
|
274
|
+
originalError?: Error
|
|
275
|
+
): ApiError {
|
|
276
|
+
return {
|
|
277
|
+
message,
|
|
278
|
+
status,
|
|
279
|
+
statusText,
|
|
280
|
+
data,
|
|
281
|
+
originalError: originalError || new Error(message)
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Singleton instance of API Core Service
|
|
288
|
+
*/
|
|
289
|
+
export const apiCoreService = new ApiCoreService();
|