native-document 1.0.92 → 1.0.93
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/dist/native-document.components.min.js +1088 -65
- package/dist/native-document.dev.js +695 -142
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.devtools.min.js +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/advanced-components.md +814 -0
- package/docs/anchor.md +71 -11
- package/docs/cache.md +888 -0
- package/docs/conditional-rendering.md +91 -1
- package/docs/core-concepts.md +9 -2
- package/docs/elements.md +127 -2
- package/docs/extending-native-document-element.md +7 -1
- package/docs/filters.md +1216 -0
- package/docs/getting-started.md +12 -3
- package/docs/lifecycle-events.md +10 -2
- package/docs/list-rendering.md +453 -54
- package/docs/memory-management.md +9 -7
- package/docs/native-document-element.md +30 -9
- package/docs/native-fetch.md +744 -0
- package/docs/observables.md +135 -6
- package/docs/routing.md +7 -1
- package/docs/state-management.md +7 -1
- package/docs/validation.md +8 -1
- package/eslint.config.js +3 -3
- package/package.json +3 -2
- package/readme.md +53 -14
- package/src/components/$traits/HasItems.js +42 -1
- package/src/components/BaseComponent.js +4 -1
- package/src/components/accordion/Accordion.js +112 -8
- package/src/components/accordion/AccordionItem.js +93 -4
- package/src/components/alert/Alert.js +164 -4
- package/src/components/avatar/Avatar.js +236 -22
- package/src/components/menu/index.js +1 -2
- package/src/core/data/ObservableArray.js +120 -2
- package/src/core/data/ObservableChecker.js +50 -0
- package/src/core/data/ObservableItem.js +124 -4
- package/src/core/data/ObservableWhen.js +36 -6
- package/src/core/data/observable-helpers/array.js +12 -3
- package/src/core/data/observable-helpers/computed.js +17 -4
- package/src/core/data/observable-helpers/object.js +19 -3
- package/src/core/elements/control/for-each-array.js +20 -2
- package/src/core/elements/control/for-each.js +17 -5
- package/src/core/elements/control/show-if.js +31 -15
- package/src/core/elements/control/show-when.js +23 -0
- package/src/core/elements/control/switch.js +40 -10
- package/src/core/utils/cache.js +5 -0
- package/src/core/utils/memoize.js +25 -16
- package/src/core/utils/prototypes.js +3 -2
- package/src/core/wrappers/AttributesWrapper.js +1 -1
- package/src/core/wrappers/NDElement.js +41 -1
- package/src/core/wrappers/NdPrototype.js +4 -0
- package/src/core/wrappers/TemplateCloner.js +13 -10
- package/src/core/wrappers/prototypes/bind-class-extensions.js +1 -1
- package/src/core/wrappers/prototypes/nd-element-extensions.js +3 -0
- package/src/router/Route.js +9 -4
- package/src/router/Router.js +28 -9
- package/src/router/errors/RouterError.js +0 -1
- package/types/control-flow.d.ts +9 -6
- package/types/elements.d.ts +6 -3
- package/types/filters/index.d.ts +4 -0
- package/types/nd-element.d.ts +5 -238
- package/types/observable.d.ts +9 -3
- package/types/router.d.ts +5 -1
- package/types/template-cloner.ts +1 -0
- package/types/validator.ts +11 -1
- package/utils.d.ts +2 -1
- package/utils.js +4 -4
- package/src/core/utils/service.js +0 -6
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
# NativeFetch
|
|
2
|
+
|
|
3
|
+
NativeFetch is a powerful HTTP client built on top of the native Fetch API, providing a clean interface with interceptors, automatic request/response handling, and advanced features for modern web applications.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
NativeFetch enhances the standard Fetch API with:
|
|
8
|
+
- **Interceptors** - Request and response transformation
|
|
9
|
+
- **Base URL** - Centralized endpoint configuration
|
|
10
|
+
- **Automatic JSON handling** - Parse and stringify automatically
|
|
11
|
+
- **Error handling** - Consistent error responses
|
|
12
|
+
- **Request cancellation** - AbortController integration
|
|
13
|
+
- **TypeScript-friendly** - Clean, predictable API
|
|
14
|
+
|
|
15
|
+
## Import
|
|
16
|
+
```javascript
|
|
17
|
+
import { NativeFetch } from 'native-document/utils';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Basic Usage
|
|
21
|
+
|
|
22
|
+
### Creating an Instance
|
|
23
|
+
```javascript
|
|
24
|
+
import { NativeFetch } from 'native-document/utils';
|
|
25
|
+
|
|
26
|
+
// Create client with base URL
|
|
27
|
+
const api = new NativeFetch('https://api.example.com');
|
|
28
|
+
|
|
29
|
+
// Make requests
|
|
30
|
+
const users = await api.get('/users');
|
|
31
|
+
const user = await api.get('/users/123');
|
|
32
|
+
const newUser = await api.post('/users', { name: 'Alice', email: 'alice@example.com' });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### HTTP Methods
|
|
36
|
+
```javascript
|
|
37
|
+
import { NativeFetch } from 'native-document/utils';
|
|
38
|
+
|
|
39
|
+
const api = new NativeFetch('https://api.example.com');
|
|
40
|
+
|
|
41
|
+
// GET request
|
|
42
|
+
const data = await api.get('/endpoint');
|
|
43
|
+
const dataWithParams = await api.get('/endpoint', { page: 1, limit: 10 });
|
|
44
|
+
|
|
45
|
+
// POST request
|
|
46
|
+
const created = await api.post('/endpoint', {
|
|
47
|
+
name: 'New Item',
|
|
48
|
+
description: 'Description'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// PUT request
|
|
52
|
+
const updated = await api.put('/endpoint/123', {
|
|
53
|
+
name: 'Updated Item'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// PATCH request
|
|
57
|
+
const patched = await api.patch('/endpoint/123', {
|
|
58
|
+
status: 'active'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// DELETE request
|
|
62
|
+
const deleted = await api.delete('/endpoint/123');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Query Parameters
|
|
66
|
+
```javascript
|
|
67
|
+
import { NativeFetch } from 'native-document/utils';
|
|
68
|
+
|
|
69
|
+
const api = new NativeFetch('https://api.example.com');
|
|
70
|
+
|
|
71
|
+
// Pass query parameters as object
|
|
72
|
+
const users = await api.get('/users', {
|
|
73
|
+
page: 1,
|
|
74
|
+
limit: 20,
|
|
75
|
+
sort: 'name',
|
|
76
|
+
filter: 'active'
|
|
77
|
+
});
|
|
78
|
+
// Request: GET /users?page=1&limit=20&sort=name&filter=active
|
|
79
|
+
|
|
80
|
+
// Array parameters
|
|
81
|
+
const posts = await api.get('/posts', {
|
|
82
|
+
tags: ['javascript', 'tutorial'],
|
|
83
|
+
categories: ['tech', 'programming']
|
|
84
|
+
});
|
|
85
|
+
// Request: GET /posts?tags=javascript,tutorial&categories=tech,programming
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Interceptors
|
|
89
|
+
|
|
90
|
+
Interceptors allow you to transform requests before they're sent and responses before they're returned.
|
|
91
|
+
|
|
92
|
+
### Request Interceptors
|
|
93
|
+
```javascript
|
|
94
|
+
import { NativeFetch } from 'native-document/utils';
|
|
95
|
+
|
|
96
|
+
const api = new NativeFetch('https://api.example.com');
|
|
97
|
+
|
|
98
|
+
// Add authentication header
|
|
99
|
+
api.interceptors.request((config) => {
|
|
100
|
+
const token = localStorage.getItem('auth_token');
|
|
101
|
+
if (token) {
|
|
102
|
+
config.headers['Authorization'] = `Bearer ${token}`;
|
|
103
|
+
}
|
|
104
|
+
return config;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Add custom headers
|
|
108
|
+
api.interceptors.request((config) => {
|
|
109
|
+
config.headers['X-App-Version'] = '1.0.0';
|
|
110
|
+
config.headers['X-Request-ID'] = generateRequestId();
|
|
111
|
+
return config;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Log requests
|
|
115
|
+
api.interceptors.request((config) => {
|
|
116
|
+
console.log(`[${config.method}] ${config.url}`);
|
|
117
|
+
return config;
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Response Interceptors
|
|
122
|
+
```javascript
|
|
123
|
+
import { NativeFetch } from 'native-document/utils';
|
|
124
|
+
|
|
125
|
+
const api = new NativeFetch('https://api.example.com');
|
|
126
|
+
|
|
127
|
+
// Handle authentication errors
|
|
128
|
+
api.interceptors.response((response) => {
|
|
129
|
+
if (response.status === 401) {
|
|
130
|
+
// Redirect to login
|
|
131
|
+
window.location.href = '/login';
|
|
132
|
+
throw new Error('Unauthorized');
|
|
133
|
+
}
|
|
134
|
+
return response;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Transform response data
|
|
138
|
+
api.interceptors.response((response) => {
|
|
139
|
+
// Unwrap data from envelope
|
|
140
|
+
if (response.data && response.data.result) {
|
|
141
|
+
return {
|
|
142
|
+
...response,
|
|
143
|
+
data: response.data.result
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return response;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Log responses
|
|
150
|
+
api.interceptors.response((response) => {
|
|
151
|
+
console.log(`[${response.status}] ${response.url}`);
|
|
152
|
+
return response;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Handle errors globally
|
|
156
|
+
api.interceptors.response((response) => {
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
console.error(`Error: ${response.status} - ${response.statusText}`);
|
|
159
|
+
}
|
|
160
|
+
return response;
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Multiple Interceptors
|
|
165
|
+
```javascript
|
|
166
|
+
import { NativeFetch } from 'native-document/utils';
|
|
167
|
+
|
|
168
|
+
const api = new NativeFetch('https://api.example.com');
|
|
169
|
+
|
|
170
|
+
// Interceptors are executed in order
|
|
171
|
+
api.interceptors.request((config) => {
|
|
172
|
+
console.log('Interceptor 1');
|
|
173
|
+
return config;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
api.interceptors.request((config) => {
|
|
177
|
+
console.log('Interceptor 2');
|
|
178
|
+
return config;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// When making a request:
|
|
182
|
+
await api.get('/users');
|
|
183
|
+
// Logs: "Interceptor 1"
|
|
184
|
+
// Logs: "Interceptor 2"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Advanced Features
|
|
188
|
+
|
|
189
|
+
### Custom Headers
|
|
190
|
+
```javascript
|
|
191
|
+
import { NativeFetch } from 'native-document/utils';
|
|
192
|
+
|
|
193
|
+
const api = new NativeFetch('https://api.example.com');
|
|
194
|
+
|
|
195
|
+
// Set default headers
|
|
196
|
+
api.interceptors.request((config) => {
|
|
197
|
+
config.headers['Content-Type'] = 'application/json';
|
|
198
|
+
config.headers['Accept'] = 'application/json';
|
|
199
|
+
return config;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Per-request headers
|
|
203
|
+
const data = await api.get('/endpoint', {}, {
|
|
204
|
+
headers: {
|
|
205
|
+
'X-Custom-Header': 'value'
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Retry Logic
|
|
211
|
+
```javascript
|
|
212
|
+
import { NativeFetch } from 'native-document/utils';
|
|
213
|
+
|
|
214
|
+
const api = new NativeFetch('https://api.example.com');
|
|
215
|
+
|
|
216
|
+
// Add retry logic
|
|
217
|
+
api.interceptors.response(async (response) => {
|
|
218
|
+
if (response.status === 429) {
|
|
219
|
+
// Rate limited - wait and retry
|
|
220
|
+
const retryAfter = response.headers.get('Retry-After') || 1;
|
|
221
|
+
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
|
|
222
|
+
|
|
223
|
+
// Retry the request
|
|
224
|
+
return fetch(response.url, response.config);
|
|
225
|
+
}
|
|
226
|
+
return response;
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Practical Examples
|
|
231
|
+
|
|
232
|
+
### Authentication Flow
|
|
233
|
+
```javascript
|
|
234
|
+
import { NativeFetch } from 'native-document/utils';
|
|
235
|
+
import { Store } from 'native-document';
|
|
236
|
+
|
|
237
|
+
const api = new NativeFetch('https://api.example.com');
|
|
238
|
+
|
|
239
|
+
// Create auth store
|
|
240
|
+
Store.create('auth', {
|
|
241
|
+
user: null,
|
|
242
|
+
token: null,
|
|
243
|
+
isAuthenticated: false
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Add token to requests
|
|
247
|
+
api.interceptors.request((config) => {
|
|
248
|
+
const auth = Store.get('auth').val();
|
|
249
|
+
if (auth.token) {
|
|
250
|
+
config.headers['Authorization'] = `Bearer ${auth.token}`;
|
|
251
|
+
}
|
|
252
|
+
return config;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Handle unauthorized
|
|
256
|
+
api.interceptors.response((response) => {
|
|
257
|
+
if (response.status === 401) {
|
|
258
|
+
const auth = Store.get('auth');
|
|
259
|
+
auth.set({
|
|
260
|
+
user: null,
|
|
261
|
+
token: null,
|
|
262
|
+
isAuthenticated: false
|
|
263
|
+
});
|
|
264
|
+
window.location.href = '/login';
|
|
265
|
+
}
|
|
266
|
+
return response;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Login function
|
|
270
|
+
async function login(email, password) {
|
|
271
|
+
const { data } = await api.post('/auth/login', { email, password });
|
|
272
|
+
|
|
273
|
+
const auth = Store.get('auth');
|
|
274
|
+
auth.set({
|
|
275
|
+
user: data.user,
|
|
276
|
+
token: data.token,
|
|
277
|
+
isAuthenticated: true
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return data;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Logout function
|
|
284
|
+
async function logout() {
|
|
285
|
+
await api.post('/auth/logout');
|
|
286
|
+
|
|
287
|
+
const auth = Store.get('auth');
|
|
288
|
+
auth.set({
|
|
289
|
+
user: null,
|
|
290
|
+
token: null,
|
|
291
|
+
isAuthenticated: false
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### API Service Layer
|
|
297
|
+
```javascript
|
|
298
|
+
import { NativeFetch } from 'native-document/utils';
|
|
299
|
+
import { Cache } from 'native-document/utils';
|
|
300
|
+
|
|
301
|
+
// Create API client singleton
|
|
302
|
+
const getAPI = Cache.singleton(() => {
|
|
303
|
+
const client = new NativeFetch('https://api.example.com');
|
|
304
|
+
|
|
305
|
+
// Add interceptors
|
|
306
|
+
client.interceptors.request((config) => {
|
|
307
|
+
config.headers['Content-Type'] = 'application/json';
|
|
308
|
+
const token = localStorage.getItem('token');
|
|
309
|
+
if (token) {
|
|
310
|
+
config.headers['Authorization'] = `Bearer ${token}`;
|
|
311
|
+
}
|
|
312
|
+
return config;
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return client;
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// API endpoints by resource
|
|
319
|
+
export const API = Cache.memoize((resource) => {
|
|
320
|
+
const client = getAPI();
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
list: (params = {}) => client.get(`/${resource}`, params),
|
|
324
|
+
get: (id) => client.get(`/${resource}/${id}`),
|
|
325
|
+
create: (data) => client.post(`/${resource}`, data),
|
|
326
|
+
update: (id, data) => client.put(`/${resource}/${id}`, data),
|
|
327
|
+
patch: (id, data) => client.patch(`/${resource}/${id}`, data),
|
|
328
|
+
delete: (id) => client.delete(`/${resource}/${id}`)
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Usage
|
|
333
|
+
const users = await API.users.list({ page: 1, limit: 10 });
|
|
334
|
+
const user = await API.users.get('123');
|
|
335
|
+
const newUser = await API.users.create({ name: 'Alice' });
|
|
336
|
+
await API.users.update('123', { name: 'Alice Updated' });
|
|
337
|
+
await API.users.delete('123');
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Loading States
|
|
341
|
+
```javascript
|
|
342
|
+
import { NativeFetch } from 'native-document/utils';
|
|
343
|
+
import { Observable } from 'native-document';
|
|
344
|
+
|
|
345
|
+
const api = new NativeFetch('https://api.example.com');
|
|
346
|
+
|
|
347
|
+
const isLoading = Observable(false);
|
|
348
|
+
const loadingCount = Observable(0);
|
|
349
|
+
|
|
350
|
+
// Track loading state
|
|
351
|
+
api.interceptors.request((config) => {
|
|
352
|
+
loadingCount.set(loadingCount.val() + 1);
|
|
353
|
+
isLoading.set(true);
|
|
354
|
+
return config;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
api.interceptors.response((response) => {
|
|
358
|
+
const count = loadingCount.val() - 1;
|
|
359
|
+
loadingCount.set(count);
|
|
360
|
+
if (count === 0) {
|
|
361
|
+
isLoading.set(false);
|
|
362
|
+
}
|
|
363
|
+
return response;
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Use in UI
|
|
367
|
+
import { Div } from 'native-document/src/elements';
|
|
368
|
+
import { ShowIf } from 'native-document';
|
|
369
|
+
|
|
370
|
+
const LoadingIndicator = Div({ class: 'loading-indicator' }, [
|
|
371
|
+
ShowIf(isLoading, () =>
|
|
372
|
+
Div({ class: 'spinner' }, 'Loading...')
|
|
373
|
+
)
|
|
374
|
+
]);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Error Handling
|
|
378
|
+
```javascript
|
|
379
|
+
import { NativeFetch } from 'native-document/utils';
|
|
380
|
+
import { Observable } from 'native-document';
|
|
381
|
+
|
|
382
|
+
const api = new NativeFetch('https://api.example.com');
|
|
383
|
+
const globalError = Observable(null);
|
|
384
|
+
|
|
385
|
+
// Global error handler
|
|
386
|
+
api.interceptors.response((response) => {
|
|
387
|
+
if (!response.ok) {
|
|
388
|
+
const error = {
|
|
389
|
+
status: response.status,
|
|
390
|
+
message: response.statusText,
|
|
391
|
+
url: response.url
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
globalError.set(error);
|
|
395
|
+
|
|
396
|
+
// Auto-clear after 5 seconds
|
|
397
|
+
setTimeout(() => globalError.set(null), 5000);
|
|
398
|
+
}
|
|
399
|
+
return response;
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Error display component
|
|
403
|
+
import { Div } from 'native-document/src/elements';
|
|
404
|
+
import { ShowIf } from 'native-document';
|
|
405
|
+
|
|
406
|
+
const ErrorNotification = Div([
|
|
407
|
+
ShowIf(globalError, () =>
|
|
408
|
+
Div({
|
|
409
|
+
class: 'error-notification',
|
|
410
|
+
style: {
|
|
411
|
+
position: 'fixed',
|
|
412
|
+
top: '20px',
|
|
413
|
+
right: '20px',
|
|
414
|
+
background: '#ff4444',
|
|
415
|
+
color: 'white',
|
|
416
|
+
padding: '16px',
|
|
417
|
+
borderRadius: '8px'
|
|
418
|
+
}
|
|
419
|
+
}, [
|
|
420
|
+
globalError.check(err => err ? err.message : '')
|
|
421
|
+
])
|
|
422
|
+
)
|
|
423
|
+
]);
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Request Logging
|
|
427
|
+
```javascript
|
|
428
|
+
import { NativeFetch } from 'native-document/utils';
|
|
429
|
+
|
|
430
|
+
const api = new NativeFetch('https://api.example.com');
|
|
431
|
+
|
|
432
|
+
// Log all requests
|
|
433
|
+
api.interceptors.request((config) => {
|
|
434
|
+
console.group(`🔵 ${config.method} ${config.url}`);
|
|
435
|
+
console.log('Headers:', config.headers);
|
|
436
|
+
console.log('Body:', config.body);
|
|
437
|
+
console.log('Timestamp:', new Date().toISOString());
|
|
438
|
+
console.groupEnd();
|
|
439
|
+
return config;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Log all responses
|
|
443
|
+
api.interceptors.response((response) => {
|
|
444
|
+
const color = response.ok ? '🟢' : '🔴';
|
|
445
|
+
console.group(`${color} ${response.status} ${response.url}`);
|
|
446
|
+
console.log('Status:', response.statusText);
|
|
447
|
+
console.log('Data:', response.data);
|
|
448
|
+
console.log('Duration:', /* calculate duration */);
|
|
449
|
+
console.groupEnd();
|
|
450
|
+
return response;
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Analytics Tracking
|
|
455
|
+
```javascript
|
|
456
|
+
import { NativeFetch } from 'native-document/utils';
|
|
457
|
+
|
|
458
|
+
const api = new NativeFetch('https://api.example.com');
|
|
459
|
+
|
|
460
|
+
// Track API calls
|
|
461
|
+
api.interceptors.request((config) => {
|
|
462
|
+
config._startTime = Date.now();
|
|
463
|
+
return config;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
api.interceptors.response((response) => {
|
|
467
|
+
const duration = Date.now() - response.config._startTime;
|
|
468
|
+
|
|
469
|
+
// Send to analytics
|
|
470
|
+
if (window.analytics) {
|
|
471
|
+
window.analytics.track('api_request', {
|
|
472
|
+
method: response.config.method,
|
|
473
|
+
endpoint: response.url,
|
|
474
|
+
status: response.status,
|
|
475
|
+
duration: duration,
|
|
476
|
+
success: response.ok
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return response;
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## Response Format
|
|
485
|
+
|
|
486
|
+
NativeFetch automatically parses JSON responses and provides a consistent response object:
|
|
487
|
+
```javascript
|
|
488
|
+
{
|
|
489
|
+
ok: boolean, // true if status 200-299
|
|
490
|
+
status: number, // HTTP status code
|
|
491
|
+
statusText: string, // Status message
|
|
492
|
+
headers: Headers, // Response headers
|
|
493
|
+
data: any, // Parsed JSON data
|
|
494
|
+
url: string, // Request URL
|
|
495
|
+
config: object // Original request config
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Handling Different Response Types
|
|
500
|
+
```javascript
|
|
501
|
+
import { NativeFetch } from 'native-document/utils';
|
|
502
|
+
|
|
503
|
+
const api = new NativeFetch('https://api.example.com');
|
|
504
|
+
|
|
505
|
+
// JSON response (default)
|
|
506
|
+
const json = await api.get('/data');
|
|
507
|
+
console.log(json.data);
|
|
508
|
+
|
|
509
|
+
// Text response
|
|
510
|
+
const response = await fetch('https://api.example.com/text');
|
|
511
|
+
const text = await response.text();
|
|
512
|
+
|
|
513
|
+
// Blob response (files, images)
|
|
514
|
+
const blobResponse = await fetch('https://api.example.com/image.png');
|
|
515
|
+
const blob = await blobResponse.blob();
|
|
516
|
+
const imageUrl = URL.createObjectURL(blob);
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Error Handling
|
|
520
|
+
|
|
521
|
+
### Try-Catch Pattern
|
|
522
|
+
```javascript
|
|
523
|
+
import { NativeFetch } from 'native-document/utils';
|
|
524
|
+
|
|
525
|
+
const api = new NativeFetch('https://api.example.com');
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const data = await api.get('/endpoint');
|
|
529
|
+
console.log('Success:', data);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (error.response) {
|
|
532
|
+
// Server responded with error
|
|
533
|
+
console.error('Server error:', error.response.status);
|
|
534
|
+
console.error('Message:', error.response.data);
|
|
535
|
+
} else if (error.request) {
|
|
536
|
+
// Request made but no response
|
|
537
|
+
console.error('Network error:', error.message);
|
|
538
|
+
} else {
|
|
539
|
+
// Other errors
|
|
540
|
+
console.error('Error:', error.message);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Async Error Boundaries
|
|
546
|
+
```javascript
|
|
547
|
+
import { NativeFetch } from 'native-document/utils';
|
|
548
|
+
import { Observable } from 'native-document';
|
|
549
|
+
|
|
550
|
+
const api = new NativeFetch('https://api.example.com');
|
|
551
|
+
|
|
552
|
+
async function fetchData() {
|
|
553
|
+
const data = Observable(null);
|
|
554
|
+
const error = Observable(null);
|
|
555
|
+
const loading = Observable(true);
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
const response = await api.get('/data');
|
|
559
|
+
data.set(response.data);
|
|
560
|
+
} catch (err) {
|
|
561
|
+
error.set(err.message);
|
|
562
|
+
} finally {
|
|
563
|
+
loading.set(false);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return { data, error, loading };
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## Best Practices
|
|
571
|
+
|
|
572
|
+
### 1. Create Singleton API Client
|
|
573
|
+
```javascript
|
|
574
|
+
import { Cache } from 'native-document/utils';
|
|
575
|
+
import { NativeFetch } from 'native-document/utils';
|
|
576
|
+
|
|
577
|
+
// ✅ Good: Single instance with interceptors
|
|
578
|
+
const getAPI = Cache.singleton(() => {
|
|
579
|
+
const client = new NativeFetch('https://api.example.com');
|
|
580
|
+
|
|
581
|
+
client.interceptors.request((config) => {
|
|
582
|
+
// Add auth header
|
|
583
|
+
return config;
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
return client;
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// ❌ Bad: New instance every time
|
|
590
|
+
function makeRequest() {
|
|
591
|
+
const api = new NativeFetch('https://api.example.com');
|
|
592
|
+
return api.get('/data');
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### 2. Use Resource-Based Endpoints
|
|
597
|
+
```javascript
|
|
598
|
+
import { Cache } from 'native-document/utils';
|
|
599
|
+
|
|
600
|
+
// ✅ Good: Organized by resource
|
|
601
|
+
const API = Cache.memoize((resource) => ({
|
|
602
|
+
list: () => api.get(`/${resource}`),
|
|
603
|
+
get: (id) => api.get(`/${resource}/${id}`),
|
|
604
|
+
create: (data) => api.post(`/${resource}`, data)
|
|
605
|
+
}));
|
|
606
|
+
|
|
607
|
+
// ❌ Bad: Scattered API calls
|
|
608
|
+
async function getUsers() {
|
|
609
|
+
return api.get('/users');
|
|
610
|
+
}
|
|
611
|
+
async function getUser(id) {
|
|
612
|
+
return api.get('/users/' + id);
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### 3. Handle Errors Consistently
|
|
617
|
+
```javascript
|
|
618
|
+
import { NativeFetch } from 'native-document/utils';
|
|
619
|
+
|
|
620
|
+
const api = new NativeFetch('https://api.example.com');
|
|
621
|
+
|
|
622
|
+
// ✅ Good: Centralized error handling
|
|
623
|
+
api.interceptors.response((response) => {
|
|
624
|
+
if (!response.ok) {
|
|
625
|
+
handleError(response);
|
|
626
|
+
}
|
|
627
|
+
return response;
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// ❌ Bad: Error handling in every request
|
|
631
|
+
try {
|
|
632
|
+
await api.get('/endpoint1');
|
|
633
|
+
} catch (error) {
|
|
634
|
+
showError(error);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
await api.get('/endpoint2');
|
|
639
|
+
} catch (error) {
|
|
640
|
+
showError(error);
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### 4. Use Query Parameters Object
|
|
645
|
+
```javascript
|
|
646
|
+
import { NativeFetch } from 'native-document/utils';
|
|
647
|
+
|
|
648
|
+
const api = new NativeFetch('https://api.example.com');
|
|
649
|
+
|
|
650
|
+
// ✅ Good: Clean object syntax
|
|
651
|
+
const users = await api.get('/users', {
|
|
652
|
+
page: 1,
|
|
653
|
+
limit: 20,
|
|
654
|
+
sort: 'name'
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// ❌ Bad: Manual query string
|
|
658
|
+
const users = await api.get('/users?page=1&limit=20&sort=name');
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### 5. Type Your Responses
|
|
662
|
+
```javascript
|
|
663
|
+
import { NativeFetch } from 'native-document/utils';
|
|
664
|
+
|
|
665
|
+
const api = new NativeFetch('https://api.example.com');
|
|
666
|
+
|
|
667
|
+
// ✅ Good: Document expected response
|
|
668
|
+
/**
|
|
669
|
+
* @returns {Promise<{data: User[]}>}
|
|
670
|
+
*/
|
|
671
|
+
async function getUsers() {
|
|
672
|
+
return await api.get('/users');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Use with confidence
|
|
676
|
+
const { data } = await getUsers();
|
|
677
|
+
data.forEach(user => console.log(user.name));
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
## Performance Tips
|
|
681
|
+
|
|
682
|
+
### 1. Reuse Client Instances
|
|
683
|
+
```javascript
|
|
684
|
+
import { Cache } from 'native-document/utils';
|
|
685
|
+
|
|
686
|
+
// ✅ Singleton - created once
|
|
687
|
+
const getAPI = Cache.singleton(() => new NativeFetch('https://api.example.com'));
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### 2. Cancel Unnecessary Requests
|
|
691
|
+
```javascript
|
|
692
|
+
let controller = new AbortController();
|
|
693
|
+
|
|
694
|
+
async function search(query) {
|
|
695
|
+
// Cancel previous request
|
|
696
|
+
controller.abort();
|
|
697
|
+
controller = new AbortController();
|
|
698
|
+
|
|
699
|
+
return await api.get('/search', { q: query }, {
|
|
700
|
+
signal: controller.signal
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
### 3. Batch Related Requests
|
|
706
|
+
```javascript
|
|
707
|
+
// ✅ Good: Batch requests
|
|
708
|
+
const [users, posts, comments] = await Promise.all([
|
|
709
|
+
api.get('/users'),
|
|
710
|
+
api.get('/posts'),
|
|
711
|
+
api.get('/comments')
|
|
712
|
+
]);
|
|
713
|
+
|
|
714
|
+
// ❌ Bad: Sequential requests
|
|
715
|
+
const users = await api.get('/users');
|
|
716
|
+
const posts = await api.get('/posts');
|
|
717
|
+
const comments = await api.get('/comments');
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
## Next Steps
|
|
721
|
+
|
|
722
|
+
Explore related utilities and concepts:
|
|
723
|
+
|
|
724
|
+
## Next Steps
|
|
725
|
+
|
|
726
|
+
- **[Getting Started](getting-started.md)** - Installation and first steps
|
|
727
|
+
- **[Core Concepts](core-concepts.md)** - Understanding the fundamentals
|
|
728
|
+
- **[Observables](observables.md)** - Reactive state management
|
|
729
|
+
- **[Elements](elements.md)** - Creating and composing UI
|
|
730
|
+
- **[Conditional Rendering](conditional-rendering.md)** - Dynamic content
|
|
731
|
+
- **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
|
|
732
|
+
- **[Routing](routing.md)** - Navigation and URL management
|
|
733
|
+
- **[State Management](state-management.md)** - Global state patterns
|
|
734
|
+
- **[NDElement](native-document-element.md)** - Native Document Element
|
|
735
|
+
- **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
|
|
736
|
+
- **[Advanced Components](advanced-components.md)** - Template caching and singleton views
|
|
737
|
+
- **[Args Validation](validation.md)** - Function Argument Validation
|
|
738
|
+
- **[Memory Management](memory-management.md)** - Memory management
|
|
739
|
+
|
|
740
|
+
## Utilities
|
|
741
|
+
|
|
742
|
+
- **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
|
|
743
|
+
- **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
|
|
744
|
+
- **[Filters](docs/utils/filters.md)** - Data filtering helpers
|