kimu-core 0.4.1
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 +30 -0
- package/.gitattributes +11 -0
- package/.github/FUNDING.yml +8 -0
- package/.github/copilot-instructions.md +103 -0
- package/.github/kimu-copilot-instructions.md +3779 -0
- package/.github/workflows/deploy-demo.yml +39 -0
- package/AUTHORS.md +20 -0
- package/CHANGELOG.md +20 -0
- package/CODE_GUIDELINES.md +165 -0
- package/CODE_OF_CONDUCT.md +47 -0
- package/CONTRIBUTING.md +62 -0
- package/FUNDING.md +31 -0
- package/ISSUE_GUIDELINES.md +74 -0
- package/LICENSE +17 -0
- package/LICENSE.it.md +17 -0
- package/MPL-2.0.txt +373 -0
- package/NOTICE +65 -0
- package/README-KIMU.md +40 -0
- package/README.it.md +208 -0
- package/README.md +266 -0
- package/SECURITY.md +64 -0
- package/docs/get-started-en.md +207 -0
- package/docs/images/icon.svg +64 -0
- package/docs/images/logo_kimu.png +0 -0
- package/docs/index.md +29 -0
- package/env/dev.config.json +6 -0
- package/env/local.config.json +6 -0
- package/env/prod.config.json +6 -0
- package/env/staging.config.json +6 -0
- package/env/test.config.json +4 -0
- package/icon.svg +10 -0
- package/logo_kimu.png +0 -0
- package/package.json +79 -0
- package/public/favicon.svg +64 -0
- package/public/logo_kimu.svg +1 -0
- package/scripts/build-all-config.js +59 -0
- package/scripts/build-all-core.js +65 -0
- package/scripts/build-all-extensions.js +64 -0
- package/scripts/build-all-modules.js +99 -0
- package/scripts/build-extension.js +60 -0
- package/scripts/clear-kimu-build.js +31 -0
- package/scripts/generate-kimu-build-config.js +79 -0
- package/scripts/install-module.js +162 -0
- package/scripts/list-modules.js +109 -0
- package/scripts/minify-css-assets.js +82 -0
- package/scripts/remove-module.js +122 -0
- package/scripts/utils/fix-imports.js +85 -0
- package/src/assets/index.css +43 -0
- package/src/assets/kimu-style.css +84 -0
- package/src/assets/style.css +116 -0
- package/src/config/kimu-base-config.json +5 -0
- package/src/core/index.ts +47 -0
- package/src/core/kimu-app.ts +76 -0
- package/src/core/kimu-asset-manager.ts +167 -0
- package/src/core/kimu-component-element.ts +325 -0
- package/src/core/kimu-component.ts +33 -0
- package/src/core/kimu-engine.ts +188 -0
- package/src/core/kimu-extension-manager.ts +281 -0
- package/src/core/kimu-global-styles.ts +136 -0
- package/src/core/kimu-module-manager.ts +69 -0
- package/src/core/kimu-module.ts +21 -0
- package/src/core/kimu-path-config.ts +127 -0
- package/src/core/kimu-reactive.ts +196 -0
- package/src/core/kimu-render.ts +91 -0
- package/src/core/kimu-store.ts +147 -0
- package/src/core/kimu-types.ts +65 -0
- package/src/extensions/.gitkeep +0 -0
- package/src/extensions/extensions-manifest.json +13 -0
- package/src/extensions/kimu-home/component.ts +80 -0
- package/src/extensions/kimu-home/lang/en.json +5 -0
- package/src/extensions/kimu-home/lang/it.json +5 -0
- package/src/extensions/kimu-home/style.css +61 -0
- package/src/extensions/kimu-home/view.html +51 -0
- package/src/index.html +26 -0
- package/src/main.ts +68 -0
- package/src/modules/.gitkeep +0 -0
- package/src/modules/README.md +79 -0
- package/src/modules/i18n/README.it.md +63 -0
- package/src/modules/i18n/README.md +63 -0
- package/src/modules/i18n/kimu-global-lang.ts +26 -0
- package/src/modules/i18n/kimu-i18n-service.ts +108 -0
- package/src/modules/i18n/manifest.json +22 -0
- package/src/modules/i18n/module.ts +39 -0
- package/src/modules/modules-manifest.json +12 -0
- package/src/modules-repository/README.md +108 -0
- package/src/modules-repository/api-axios/CHANGELOG.md +48 -0
- package/src/modules-repository/api-axios/QUICK-REFERENCE.md +178 -0
- package/src/modules-repository/api-axios/README.md +304 -0
- package/src/modules-repository/api-axios/api-axios-service.ts +355 -0
- package/src/modules-repository/api-axios/examples.ts +293 -0
- package/src/modules-repository/api-axios/index.ts +19 -0
- package/src/modules-repository/api-axios/interfaces.ts +71 -0
- package/src/modules-repository/api-axios/module.ts +41 -0
- package/src/modules-repository/api-core/CHANGELOG.md +42 -0
- package/src/modules-repository/api-core/QUICK-REFERENCE.md +192 -0
- package/src/modules-repository/api-core/README.md +435 -0
- package/src/modules-repository/api-core/api-core-service.ts +289 -0
- package/src/modules-repository/api-core/examples.ts +432 -0
- package/src/modules-repository/api-core/index.ts +8 -0
- package/src/modules-repository/api-core/interfaces.ts +83 -0
- package/src/modules-repository/api-core/module.ts +30 -0
- package/src/modules-repository/event-bus/README.md +273 -0
- package/src/modules-repository/event-bus/event-bus-service.ts +176 -0
- package/src/modules-repository/event-bus/module.ts +30 -0
- package/src/modules-repository/i18n/README.it.md +63 -0
- package/src/modules-repository/i18n/README.md +63 -0
- package/src/modules-repository/i18n/kimu-global-lang.ts +26 -0
- package/src/modules-repository/i18n/kimu-i18n-service.ts +108 -0
- package/src/modules-repository/i18n/manifest.json +22 -0
- package/src/modules-repository/i18n/module.ts +39 -0
- package/src/modules-repository/notification/README.md +423 -0
- package/src/modules-repository/notification/module.ts +30 -0
- package/src/modules-repository/notification/notification-service.ts +436 -0
- package/src/modules-repository/router/README.it.md +39 -0
- package/src/modules-repository/router/README.md +39 -0
- package/src/modules-repository/router/manifest.json +21 -0
- package/src/modules-repository/router/module.ts +23 -0
- package/src/modules-repository/router/router.ts +144 -0
- package/src/modules-repository/state/README.md +409 -0
- package/src/modules-repository/state/module.ts +30 -0
- package/src/modules-repository/state/state-service.ts +296 -0
- package/src/modules-repository/theme/README.md +267 -0
- package/src/modules-repository/theme/module.ts +30 -0
- package/src/modules-repository/theme/pre-build.js +40 -0
- package/src/modules-repository/theme/theme-service.ts +389 -0
- package/src/modules-repository/theme/themes/theme-cherry-blossom.css +78 -0
- package/src/modules-repository/theme/themes/theme-cozy.css +111 -0
- package/src/modules-repository/theme/themes/theme-cyberpunk.css +150 -0
- package/src/modules-repository/theme/themes/theme-dark.css +79 -0
- package/src/modules-repository/theme/themes/theme-forest.css +171 -0
- package/src/modules-repository/theme/themes/theme-gold.css +100 -0
- package/src/modules-repository/theme/themes/theme-high-contrast.css +126 -0
- package/src/modules-repository/theme/themes/theme-lava.css +101 -0
- package/src/modules-repository/theme/themes/theme-lavender.css +90 -0
- package/src/modules-repository/theme/themes/theme-light.css +79 -0
- package/src/modules-repository/theme/themes/theme-matrix.css +103 -0
- package/src/modules-repository/theme/themes/theme-midnight.css +81 -0
- package/src/modules-repository/theme/themes/theme-nord.css +94 -0
- package/src/modules-repository/theme/themes/theme-ocean.css +84 -0
- package/src/modules-repository/theme/themes/theme-retro80s.css +343 -0
- package/src/modules-repository/theme/themes/theme-sunset.css +62 -0
- package/src/modules-repository/theme/themes-config.d.ts +27 -0
- package/src/modules-repository/theme/themes-config.json +213 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +33 -0
- package/vite.config.ts +99 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# KIMU API Core Module
|
|
2
|
+
|
|
3
|
+
Modulo minimale per la gestione di richieste HTTP verso API esterne, basato su `fetch` nativo.
|
|
4
|
+
|
|
5
|
+
## Caratteristiche
|
|
6
|
+
|
|
7
|
+
- ✅ Zero dipendenze esterne
|
|
8
|
+
- ✅ Basato su fetch API nativa
|
|
9
|
+
- ✅ Promise-based (async/await)
|
|
10
|
+
- ✅ TypeScript-first con type safety completo
|
|
11
|
+
- ✅ Supporto per tutti i metodi HTTP (GET, POST, PUT, PATCH, DELETE)
|
|
12
|
+
- ✅ Gestione automatica di query parameters, headers, body
|
|
13
|
+
- ✅ Interceptor per request/response/error
|
|
14
|
+
- ✅ Timeout configurabile
|
|
15
|
+
- ✅ Gestione errori strutturata
|
|
16
|
+
- ✅ Supporto per diversi tipi di risposta (JSON, text, blob, arraybuffer)
|
|
17
|
+
- ✅ BaseURL globale configurabile
|
|
18
|
+
- ✅ Lightweight (~2KB minified)
|
|
19
|
+
|
|
20
|
+
## Installazione
|
|
21
|
+
|
|
22
|
+
Il modulo è incluso nel repository `kimu-modules`. Per installarlo:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Usando kimu-cli (quando disponibile)
|
|
26
|
+
kimu install module api-core
|
|
27
|
+
|
|
28
|
+
# Manualmente
|
|
29
|
+
# Copia la cartella src/modules/api-core nel tuo progetto
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Utilizzo Base
|
|
33
|
+
|
|
34
|
+
### Inizializzazione
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { KimuModuleManager } from '@kimu/core';
|
|
38
|
+
import ApiCoreModule from './modules/api-core/module';
|
|
39
|
+
|
|
40
|
+
// Inizializza il modulo
|
|
41
|
+
const moduleManager = KimuModuleManager.getInstance();
|
|
42
|
+
const apiModule = new ApiCoreModule();
|
|
43
|
+
await moduleManager.loadModule(apiModule);
|
|
44
|
+
|
|
45
|
+
// Ottieni il servizio
|
|
46
|
+
const api = moduleManager.getService('api-core');
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Configurazione Globale
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { apiCoreService } from './modules/api-core/api-core-service';
|
|
53
|
+
|
|
54
|
+
// Configura baseURL e headers di default
|
|
55
|
+
apiCoreService.configure({
|
|
56
|
+
baseURL: 'https://api.example.com',
|
|
57
|
+
timeout: 10000, // 10 secondi
|
|
58
|
+
headers: {
|
|
59
|
+
'Authorization': 'Bearer YOUR_TOKEN',
|
|
60
|
+
'X-Custom-Header': 'value'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Richieste GET
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// GET semplice
|
|
69
|
+
const response = await api.get('/users');
|
|
70
|
+
console.log(response.data); // Array di utenti
|
|
71
|
+
|
|
72
|
+
// GET con query parameters
|
|
73
|
+
const response = await api.get('/users', {
|
|
74
|
+
params: {
|
|
75
|
+
page: 1,
|
|
76
|
+
limit: 10,
|
|
77
|
+
sort: 'name'
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// GET con headers custom
|
|
82
|
+
const response = await api.get('/users', {
|
|
83
|
+
headers: {
|
|
84
|
+
'Accept-Language': 'it-IT'
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Richieste POST
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// POST con JSON body
|
|
93
|
+
const response = await api.post('/users', {
|
|
94
|
+
name: 'Mario Rossi',
|
|
95
|
+
email: 'mario@example.com',
|
|
96
|
+
age: 30
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// POST con FormData
|
|
100
|
+
const formData = new FormData();
|
|
101
|
+
formData.append('file', fileInput.files[0]);
|
|
102
|
+
formData.append('title', 'My File');
|
|
103
|
+
|
|
104
|
+
const response = await api.post('/upload', formData);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Altri Metodi HTTP
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// PUT - aggiorna risorsa
|
|
111
|
+
const response = await api.put('/users/123', {
|
|
112
|
+
name: 'Mario Rossi Updated'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// PATCH - aggiorna parzialmente
|
|
116
|
+
const response = await api.patch('/users/123', {
|
|
117
|
+
age: 31
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// DELETE - elimina risorsa
|
|
121
|
+
const response = await api.delete('/users/123');
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Richiesta Generica
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const response = await api.request('/custom-endpoint', {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
body: { key: 'value' },
|
|
130
|
+
headers: { 'X-Custom': 'header' },
|
|
131
|
+
params: { filter: 'active' },
|
|
132
|
+
timeout: 5000,
|
|
133
|
+
responseType: 'json'
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Gestione Errori
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
try {
|
|
141
|
+
const response = await api.get('/users');
|
|
142
|
+
console.log(response.data);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('Error:', error.message);
|
|
145
|
+
console.error('Status:', error.status);
|
|
146
|
+
console.error('Status Text:', error.statusText);
|
|
147
|
+
console.error('Response Data:', error.data);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Interceptor
|
|
152
|
+
|
|
153
|
+
### Request Interceptor
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
apiCoreService.configure({
|
|
157
|
+
requestInterceptor: (config) => {
|
|
158
|
+
// Aggiungi token dinamicamente
|
|
159
|
+
const token = localStorage.getItem('token');
|
|
160
|
+
if (token) {
|
|
161
|
+
config.headers = {
|
|
162
|
+
...config.headers,
|
|
163
|
+
'Authorization': `Bearer ${token}`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Log della richiesta
|
|
168
|
+
console.log('Making request:', config);
|
|
169
|
+
|
|
170
|
+
return config;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Response Interceptor
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
apiCoreService.configure({
|
|
179
|
+
responseInterceptor: (response) => {
|
|
180
|
+
// Log della risposta
|
|
181
|
+
console.log('Response received:', response.status);
|
|
182
|
+
|
|
183
|
+
// Trasforma i dati
|
|
184
|
+
response.data = {
|
|
185
|
+
...response.data,
|
|
186
|
+
receivedAt: new Date().toISOString()
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return response;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Error Interceptor
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
apiCoreService.configure({
|
|
198
|
+
errorInterceptor: async (error) => {
|
|
199
|
+
// Gestione errore 401: refresh token
|
|
200
|
+
if (error.status === 401) {
|
|
201
|
+
const newToken = await refreshToken();
|
|
202
|
+
localStorage.setItem('token', newToken);
|
|
203
|
+
|
|
204
|
+
// Puoi anche rilanciare una richiesta o modificare l'errore
|
|
205
|
+
error.message = 'Session expired, please login again';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Log errori
|
|
209
|
+
console.error('API Error:', error);
|
|
210
|
+
|
|
211
|
+
return error;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Timeout e Cancellazione
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Timeout automatico
|
|
220
|
+
const response = await api.get('/slow-endpoint', {
|
|
221
|
+
timeout: 3000 // 3 secondi
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Cancellazione manuale con AbortController
|
|
225
|
+
const controller = new AbortController();
|
|
226
|
+
|
|
227
|
+
const request = api.get('/data', {
|
|
228
|
+
signal: controller.signal
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Cancella dopo 2 secondi
|
|
232
|
+
setTimeout(() => controller.abort(), 2000);
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const response = await request;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (error.message.includes('timeout')) {
|
|
238
|
+
console.log('Request was cancelled');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Tipi di Risposta
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// JSON (default)
|
|
247
|
+
const response = await api.get('/users', {
|
|
248
|
+
responseType: 'json'
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Testo
|
|
252
|
+
const response = await api.get('/readme.txt', {
|
|
253
|
+
responseType: 'text'
|
|
254
|
+
});
|
|
255
|
+
console.log(response.data); // stringa
|
|
256
|
+
|
|
257
|
+
// Blob (per file)
|
|
258
|
+
const response = await api.get('/image.png', {
|
|
259
|
+
responseType: 'blob'
|
|
260
|
+
});
|
|
261
|
+
const imageUrl = URL.createObjectURL(response.data);
|
|
262
|
+
|
|
263
|
+
// ArrayBuffer (per dati binari)
|
|
264
|
+
const response = await api.get('/binary-data', {
|
|
265
|
+
responseType: 'arraybuffer'
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Uso in Componenti KIMU
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { KimuComponentElement } from '@kimu/core';
|
|
273
|
+
import { KimuModuleManager } from '@kimu/core';
|
|
274
|
+
|
|
275
|
+
export class UserListComponent extends KimuComponentElement {
|
|
276
|
+
private api: any;
|
|
277
|
+
private users: any[] = [];
|
|
278
|
+
|
|
279
|
+
async onInit() {
|
|
280
|
+
// Ottieni il servizio API
|
|
281
|
+
this.api = KimuModuleManager.getInstance().getService('api-core');
|
|
282
|
+
|
|
283
|
+
// Carica i dati
|
|
284
|
+
await this.loadUsers();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async loadUsers() {
|
|
288
|
+
try {
|
|
289
|
+
const response = await this.api.get('/users');
|
|
290
|
+
this.users = response.data;
|
|
291
|
+
this.onRender();
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Failed to load users:', error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async createUser(userData: any) {
|
|
298
|
+
try {
|
|
299
|
+
const response = await this.api.post('/users', userData);
|
|
300
|
+
this.users.push(response.data);
|
|
301
|
+
this.onRender();
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error('Failed to create user:', error);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## TypeScript Support
|
|
310
|
+
|
|
311
|
+
Il modulo è completamente tipizzato:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
interface User {
|
|
315
|
+
id: number;
|
|
316
|
+
name: string;
|
|
317
|
+
email: string;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Type-safe request
|
|
321
|
+
const response = await api.get<User[]>('/users');
|
|
322
|
+
response.data.forEach(user => {
|
|
323
|
+
console.log(user.name); // Type-safe access
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Type-safe POST
|
|
327
|
+
const newUser: Omit<User, 'id'> = {
|
|
328
|
+
name: 'Mario',
|
|
329
|
+
email: 'mario@example.com'
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const response = await api.post<User>('/users', newUser);
|
|
333
|
+
console.log(response.data.id); // Type-safe
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Best Practices
|
|
337
|
+
|
|
338
|
+
### 1. Configura baseURL globalmente
|
|
339
|
+
```typescript
|
|
340
|
+
apiCoreService.configure({
|
|
341
|
+
baseURL: process.env.API_URL || 'https://api.example.com'
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 2. Usa interceptor per autenticazione
|
|
346
|
+
```typescript
|
|
347
|
+
apiCoreService.configure({
|
|
348
|
+
requestInterceptor: (config) => {
|
|
349
|
+
const token = getAuthToken();
|
|
350
|
+
if (token) {
|
|
351
|
+
config.headers = {
|
|
352
|
+
...config.headers,
|
|
353
|
+
'Authorization': `Bearer ${token}`
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return config;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 3. Gestisci errori globalmente
|
|
362
|
+
```typescript
|
|
363
|
+
apiCoreService.configure({
|
|
364
|
+
errorInterceptor: (error) => {
|
|
365
|
+
if (error.status === 401) {
|
|
366
|
+
// Redirect to login
|
|
367
|
+
window.location.href = '/login';
|
|
368
|
+
} else if (error.status >= 500) {
|
|
369
|
+
// Show error notification
|
|
370
|
+
showNotification('Server error, please try again later');
|
|
371
|
+
}
|
|
372
|
+
return error;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 4. Crea wrapper service per API specifiche
|
|
378
|
+
```typescript
|
|
379
|
+
class UserService {
|
|
380
|
+
private api = apiCoreService;
|
|
381
|
+
|
|
382
|
+
async getUsers() {
|
|
383
|
+
return this.api.get<User[]>('/users');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async getUser(id: number) {
|
|
387
|
+
return this.api.get<User>(`/users/${id}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async createUser(user: Omit<User, 'id'>) {
|
|
391
|
+
return this.api.post<User>('/users', user);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async updateUser(id: number, user: Partial<User>) {
|
|
395
|
+
return this.api.patch<User>(`/users/${id}`, user);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async deleteUser(id: number) {
|
|
399
|
+
return this.api.delete(`/users/${id}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Confronto con Altri Framework
|
|
405
|
+
|
|
406
|
+
| Feature | KIMU api-core | Angular HttpClient | Axios | Fetch nativo |
|
|
407
|
+
|---------|---------------|-------------------|-------|--------------|
|
|
408
|
+
| Bundle Size | ~2KB | ~40KB | ~13KB | 0KB (nativo) |
|
|
409
|
+
| Dependencies | 0 | RxJS required | 0 | 0 |
|
|
410
|
+
| TypeScript | ✅ Native | ✅ Native | ✅ Native | ⚠️ Basic |
|
|
411
|
+
| Interceptors | ✅ | ✅ | ✅ | ❌ |
|
|
412
|
+
| Timeout | ✅ | ✅ | ✅ | ⚠️ Manual |
|
|
413
|
+
| Query Params | ✅ Auto | ✅ Auto | ✅ Auto | ❌ Manual |
|
|
414
|
+
| Error Handling | ✅ Structured | ✅ Structured | ✅ Structured | ⚠️ Basic |
|
|
415
|
+
| Promise/Observable | Promise | Observable | Promise | Promise |
|
|
416
|
+
|
|
417
|
+
## Roadmap
|
|
418
|
+
|
|
419
|
+
- [ ] Support per retry automatico
|
|
420
|
+
- [ ] Caching integrato delle richieste
|
|
421
|
+
- [ ] Request deduplication
|
|
422
|
+
- [ ] Upload progress tracking
|
|
423
|
+
- [ ] Download progress tracking
|
|
424
|
+
- [ ] Request queue con priorità
|
|
425
|
+
- [ ] Mock/test utilities
|
|
426
|
+
|
|
427
|
+
## License
|
|
428
|
+
|
|
429
|
+
Questo modulo fa parte di KIMU-Core ed è distribuito sotto licenza MPL-2.0.
|
|
430
|
+
|
|
431
|
+
## Supporto
|
|
432
|
+
|
|
433
|
+
Per issue, feature request o domande:
|
|
434
|
+
- GitHub Issues: [kimu-core/issues](https://github.com/UnicoVerso/kimu-core/issues)
|
|
435
|
+
- Documentazione: [kimu-docs](https://github.com/UnicoVerso/kimu-docs)
|
|
@@ -0,0 +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();
|