native-document 1.0.92 → 1.0.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/native-document.components.min.js +1088 -65
  2. package/dist/native-document.dev.js +695 -142
  3. package/dist/native-document.dev.js.map +1 -1
  4. package/dist/native-document.devtools.min.js +1 -1
  5. package/dist/native-document.min.js +1 -1
  6. package/docs/advanced-components.md +814 -0
  7. package/docs/anchor.md +71 -11
  8. package/docs/cache.md +888 -0
  9. package/docs/conditional-rendering.md +91 -1
  10. package/docs/core-concepts.md +9 -2
  11. package/docs/elements.md +127 -2
  12. package/docs/extending-native-document-element.md +7 -1
  13. package/docs/filters.md +1216 -0
  14. package/docs/getting-started.md +12 -3
  15. package/docs/lifecycle-events.md +10 -2
  16. package/docs/list-rendering.md +453 -54
  17. package/docs/memory-management.md +9 -7
  18. package/docs/native-document-element.md +30 -9
  19. package/docs/native-fetch.md +744 -0
  20. package/docs/observables.md +135 -6
  21. package/docs/routing.md +7 -1
  22. package/docs/state-management.md +7 -1
  23. package/docs/validation.md +8 -1
  24. package/elements.js +1 -0
  25. package/eslint.config.js +3 -3
  26. package/index.def.js +350 -0
  27. package/package.json +3 -2
  28. package/readme.md +53 -14
  29. package/src/components/$traits/HasItems.js +42 -1
  30. package/src/components/BaseComponent.js +4 -1
  31. package/src/components/accordion/Accordion.js +112 -8
  32. package/src/components/accordion/AccordionItem.js +93 -4
  33. package/src/components/alert/Alert.js +164 -4
  34. package/src/components/avatar/Avatar.js +236 -22
  35. package/src/components/menu/index.js +1 -2
  36. package/src/core/data/ObservableArray.js +120 -2
  37. package/src/core/data/ObservableChecker.js +50 -0
  38. package/src/core/data/ObservableItem.js +124 -4
  39. package/src/core/data/ObservableWhen.js +36 -6
  40. package/src/core/data/observable-helpers/array.js +12 -3
  41. package/src/core/data/observable-helpers/computed.js +17 -4
  42. package/src/core/data/observable-helpers/object.js +19 -3
  43. package/src/core/elements/content-formatter.js +138 -1
  44. package/src/core/elements/control/for-each-array.js +20 -2
  45. package/src/core/elements/control/for-each.js +17 -5
  46. package/src/core/elements/control/show-if.js +31 -15
  47. package/src/core/elements/control/show-when.js +23 -0
  48. package/src/core/elements/control/switch.js +40 -10
  49. package/src/core/elements/description-list.js +14 -0
  50. package/src/core/elements/form.js +188 -4
  51. package/src/core/elements/html5-semantics.js +44 -1
  52. package/src/core/elements/img.js +22 -10
  53. package/src/core/elements/index.js +5 -0
  54. package/src/core/elements/interactive.js +19 -1
  55. package/src/core/elements/list.js +28 -1
  56. package/src/core/elements/medias.js +29 -0
  57. package/src/core/elements/meta-data.js +34 -0
  58. package/src/core/elements/table.js +59 -0
  59. package/src/core/utils/cache.js +5 -0
  60. package/src/core/utils/helpers.js +7 -2
  61. package/src/core/utils/memoize.js +25 -16
  62. package/src/core/utils/prototypes.js +3 -2
  63. package/src/core/wrappers/AttributesWrapper.js +1 -1
  64. package/src/core/wrappers/HtmlElementWrapper.js +2 -2
  65. package/src/core/wrappers/NDElement.js +42 -2
  66. package/src/core/wrappers/NdPrototype.js +4 -0
  67. package/src/core/wrappers/TemplateCloner.js +14 -11
  68. package/src/core/wrappers/prototypes/bind-class-extensions.js +1 -1
  69. package/src/core/wrappers/prototypes/nd-element-extensions.js +3 -0
  70. package/src/router/Route.js +9 -4
  71. package/src/router/Router.js +28 -9
  72. package/src/router/errors/RouterError.js +0 -1
  73. package/types/control-flow.d.ts +9 -6
  74. package/types/elements.d.ts +496 -111
  75. package/types/filters/index.d.ts +4 -0
  76. package/types/forms.d.ts +85 -48
  77. package/types/images.d.ts +16 -9
  78. package/types/nd-element.d.ts +5 -238
  79. package/types/observable.d.ts +9 -3
  80. package/types/router.d.ts +5 -1
  81. package/types/template-cloner.ts +1 -0
  82. package/types/validator.ts +11 -1
  83. package/utils.d.ts +2 -1
  84. package/utils.js +4 -4
  85. package/src/core/utils/service.js +0 -6
package/docs/cache.md ADDED
@@ -0,0 +1,888 @@
1
+ # Cache Utilities
2
+
3
+ NativeDocument provides utility functions for optimizing function execution through lazy initialization, memoization, and singleton patterns. These utilities help improve performance by deferring execution and caching results.
4
+
5
+ ## Overview
6
+
7
+ The Cache utilities include:
8
+ - **`Cache.once(fn)`** - Lazy execution (via `autoOnce`) - executes on first property access
9
+ - **`Cache.singleton(fn)`** - Eager singleton (via `once`) - executes immediately on first call
10
+ - **`Cache.memoize(fn)`** - Lazy memoization (via `autoMemoize`) - proxy-based caching
11
+
12
+ ## Import
13
+ ```javascript
14
+ import { Cache } from 'native-document/utils';
15
+
16
+ // Use Cache methods
17
+ const lazyInit = Cache.once(() => { /* ... */ });
18
+ const singleton = Cache.singleton(() => { /* ... */ });
19
+ const memoized = Cache.memoize((key) => { /* ... */ });
20
+ ```
21
+
22
+ ## Cache.once() - Lazy Initialization
23
+
24
+ Lazy execution using `autoOnce`. The function is **not executed immediately** - it only runs when you access a property on the returned object.
25
+
26
+ ### Basic Usage
27
+ ```javascript
28
+ import { Cache } from 'native-document/utils';
29
+
30
+ const LazyConfig = Cache.once(() => {
31
+ console.log('Loading config...');
32
+ return {
33
+ apiUrl: 'https://api.example.com',
34
+ timeout: 5000,
35
+ maxRetries: 3
36
+ };
37
+ });
38
+
39
+ // Function not executed yet
40
+ console.log('Config created');
41
+
42
+ // First property access triggers execution
43
+ console.log(LazyConfig.apiUrl);
44
+ // Logs: "Loading config..."
45
+ // Returns: "https://api.example.com"
46
+
47
+ // Subsequent accesses use cached result
48
+ console.log(LazyConfig.timeout); // No log, returns 5000
49
+ console.log(LazyConfig.maxRetries); // No log, returns 3
50
+ ```
51
+
52
+ ### Lazy Module Loading
53
+ ```javascript
54
+ import { Cache } from 'native-document/utils';
55
+
56
+ const Utils = Cache.once(() => {
57
+ console.log('Initializing utils...');
58
+ return {
59
+ formatDate: (date) => new Date(date).toLocaleDateString(),
60
+ capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
61
+ truncate: (str, len) => str.length > len ? str.slice(0, len) + '...' : str,
62
+ slugify: (str) => str.toLowerCase().replace(/\s+/g, '-')
63
+ };
64
+ });
65
+
66
+ // Utils not initialized yet
67
+ console.log('App started');
68
+
69
+ // First use initializes the module
70
+ const formatted = Utils.formatDate(new Date());
71
+ // Logs: "Initializing utils..."
72
+
73
+ // Already initialized - no log
74
+ const capitalized = Utils.capitalize('hello');
75
+ const slug = Utils.slugify('Hello World');
76
+ ```
77
+
78
+ ### Lazy API Client
79
+ ```javascript
80
+ import { Cache } from 'native-document/utils';
81
+ import { NativeFetch } from 'native-document/utils';
82
+
83
+ const API = Cache.once(() => {
84
+ console.log('Setting up API client...');
85
+ const client = new NativeFetch('https://api.example.com');
86
+
87
+ client.interceptors.request((config) => {
88
+ config.headers['Authorization'] = `Bearer ${getToken()}`;
89
+ return config;
90
+ });
91
+
92
+ return {
93
+ users: {
94
+ get: (id) => client.get(`/users/${id}`),
95
+ list: () => client.get('/users')
96
+ },
97
+ posts: {
98
+ get: (id) => client.get(`/posts/${id}`),
99
+ create: (data) => client.post('/posts', data)
100
+ }
101
+ };
102
+ });
103
+
104
+ // Client not created yet
105
+ console.log('Starting app...');
106
+
107
+ // Client created on first API call
108
+ const users = await API.users.list();
109
+ // Logs: "Setting up API client..."
110
+
111
+ // Client already created
112
+ const user = await API.users.get('123');
113
+ ```
114
+
115
+ ## Cache.singleton() - Eager Singleton
116
+
117
+ Eager execution using `once`. The function executes **on the first call** and caches the result for subsequent calls.
118
+
119
+ ### Basic Usage
120
+ ```javascript
121
+ import { Cache } from 'native-document/utils';
122
+
123
+ const getLogger = Cache.singleton(() => {
124
+ console.log('Creating logger instance...');
125
+ return {
126
+ log: (msg) => console.log(`[LOG] ${msg}`),
127
+ error: (msg) => console.error(`[ERROR] ${msg}`),
128
+ warn: (msg) => console.warn(`[WARN] ${msg}`),
129
+ info: (msg) => console.info(`[INFO] ${msg}`)
130
+ };
131
+ });
132
+
133
+ // First call creates the logger
134
+ const logger = getLogger();
135
+ // Logs: "Creating logger instance..."
136
+
137
+ logger.log('Application started');
138
+
139
+ // Subsequent calls return cached instance
140
+ const logger2 = getLogger();
141
+ // No log - returns cached instance
142
+
143
+ console.log(logger === logger2); // true - same reference
144
+ ```
145
+
146
+ ### Application Configuration
147
+ ```javascript
148
+ import { Cache } from 'native-document/utils';
149
+
150
+ const getConfig = Cache.singleton(() => {
151
+ console.log('Loading configuration...');
152
+ return {
153
+ apiUrl: import.meta.env.VITE_API_URL || 'https://api.example.com',
154
+ debug: import.meta.env.DEV,
155
+ version: '1.0.0',
156
+ features: {
157
+ analytics: true,
158
+ darkMode: true
159
+ }
160
+ };
161
+ });
162
+
163
+ // First call loads config
164
+ const config = getConfig();
165
+ // Logs: "Loading configuration..."
166
+
167
+ console.log(config.apiUrl);
168
+
169
+ // Returns same config instance
170
+ const config2 = getConfig();
171
+ console.log(config === config2); // true
172
+ ```
173
+
174
+ ### Event Bus Singleton
175
+ ```javascript
176
+ import { Cache } from 'native-document/utils';
177
+
178
+ const getEventBus = Cache.singleton(() => {
179
+ console.log('Creating event bus...');
180
+ const listeners = new Map();
181
+
182
+ return {
183
+ on: (event, callback) => {
184
+ if (!listeners.has(event)) {
185
+ listeners.set(event, []);
186
+ }
187
+ listeners.get(event).push(callback);
188
+ },
189
+ off: (event, callback) => {
190
+ const eventListeners = listeners.get(event);
191
+ if (eventListeners) {
192
+ const index = eventListeners.indexOf(callback);
193
+ if (index > -1) {
194
+ eventListeners.splice(index, 1);
195
+ }
196
+ }
197
+ },
198
+ emit: (event, data) => {
199
+ const eventListeners = listeners.get(event);
200
+ if (eventListeners) {
201
+ eventListeners.forEach(callback => callback(data));
202
+ }
203
+ }
204
+ };
205
+ });
206
+
207
+ // Create event bus
208
+ const bus = getEventBus();
209
+ // Logs: "Creating event bus..."
210
+
211
+ bus.on('user:login', (user) => console.log('User logged in:', user));
212
+ bus.emit('user:login', { id: 1, name: 'John' });
213
+
214
+ // Same event bus everywhere
215
+ const bus2 = getEventBus();
216
+ console.log(bus === bus2); // true
217
+ ```
218
+
219
+ ## Cache.memoize() - Lazy Memoization with Key-Based Instances
220
+
221
+ Lazy memoization using `autoMemoize`. Each property access creates and caches a **separate instance** of the function result, using the property name as the key parameter.
222
+
223
+ ### How It Works
224
+ ```javascript
225
+ import { Cache } from 'native-document/utils';
226
+
227
+ const API = Cache.memoize((key) => {
228
+ console.log(`Creating API for: ${key}`);
229
+ return {
230
+ async list() {
231
+ return await fetch('/' + key);
232
+ },
233
+ async get(id) {
234
+ return await fetch('/' + key + '/' + id);
235
+ }
236
+ };
237
+ });
238
+
239
+ // First access to 'users' - executes function with key='users'
240
+ await API.users.list();
241
+ // Logs: "Creating API for: users"
242
+ // Fetch: '/users'
243
+
244
+ // Second access to 'users' - returns cached instance
245
+ await API.users.get('123');
246
+ // No log - cached instance
247
+ // Fetch: '/users/123'
248
+
249
+ // First access to 'posts' - executes function with key='posts'
250
+ await API.posts.list();
251
+ // Logs: "Creating API for: posts"
252
+ // Fetch: '/posts'
253
+
254
+ // Cached instance for 'posts'
255
+ await API.posts.get('456');
256
+ // No log - cached instance
257
+ // Fetch: '/posts/456'
258
+ ```
259
+
260
+ ### Key Concepts
261
+
262
+ 1. **Property name becomes the key**: `API.users` → `key = 'users'`
263
+ 2. **Function executed per unique key**: First access creates instance
264
+ 3. **Results cached by key**: Subsequent accesses return same instance
265
+ 4. **Each key has its own instance**: `API.users` ≠ `API.posts`
266
+
267
+ ### Basic Resource Providers
268
+ ```javascript
269
+ import { Cache } from 'native-document/utils';
270
+
271
+ const Resources = Cache.memoize((resource) => {
272
+ console.log(`Loading ${resource}...`);
273
+
274
+ return {
275
+ data: `${resource} data`,
276
+ load: () => console.log(`Reloading ${resource}`),
277
+ save: (content) => console.log(`Saving to ${resource}:`, content)
278
+ };
279
+ });
280
+
281
+ // First access - creates 'icons' instance
282
+ Resources.icons.load();
283
+ // Logs: "Loading icons..."
284
+ // Logs: "Reloading icons"
285
+
286
+ // Cached instance
287
+ Resources.icons.save('new-icon.svg');
288
+ // Logs: "Saving to icons: new-icon.svg"
289
+
290
+ // Different key - creates 'fonts' instance
291
+ Resources.fonts.load();
292
+ // Logs: "Loading fonts..."
293
+ // Logs: "Reloading fonts"
294
+ ```
295
+
296
+ ### API Endpoint Collections
297
+ ```javascript
298
+ import { Cache } from 'native-document/utils';
299
+ import { NativeFetch } from 'native-document/utils';
300
+
301
+ const api = new NativeFetch('https://api.example.com');
302
+
303
+ const Endpoints = Cache.memoize((resource) => {
304
+ console.log(`Creating endpoint for: ${resource}`);
305
+
306
+ return {
307
+ list: () => api.get(`/${resource}`),
308
+ get: (id) => api.get(`/${resource}/${id}`),
309
+ create: (data) => api.post(`/${resource}`, data),
310
+ update: (id, data) => api.put(`/${resource}/${id}`, data),
311
+ delete: (id) => api.delete(`/${resource}/${id}`)
312
+ };
313
+ });
314
+
315
+ // Create 'users' endpoint instance
316
+ const users = await Endpoints.users.list();
317
+ // Logs: "Creating endpoint for: users"
318
+ // GET: /users
319
+
320
+ const user = await Endpoints.users.get('123');
321
+ // GET: /users/123 (cached instance)
322
+
323
+ // Create 'posts' endpoint instance
324
+ const posts = await Endpoints.posts.list();
325
+ // Logs: "Creating endpoint for: posts"
326
+ // GET: /posts
327
+
328
+ await Endpoints.posts.create({ title: 'New Post' });
329
+ // POST: /posts (cached instance)
330
+ ```
331
+
332
+ ### Store Providers
333
+ ```javascript
334
+ import { Cache } from 'native-document/utils';
335
+ import { Store } from 'native-document';
336
+
337
+ const Stores = Cache.memoize((storeName) => {
338
+ console.log(`Creating store: ${storeName}`);
339
+
340
+ // Create store if it doesn't exist
341
+ if (!Store.get(storeName)) {
342
+ Store.create(storeName, {
343
+ items: [],
344
+ loading: false,
345
+ error: null
346
+ });
347
+ }
348
+
349
+ return {
350
+ get: () => Store.use(storeName),
351
+ setLoading: (value) => {
352
+ /*...*/
353
+ },
354
+ addItem: (items) => {
355
+ /*...*/
356
+ },
357
+ setItems: (items) => {
358
+ /*...*/
359
+ },
360
+ setError: (error) => {
361
+ /*...*/
362
+ }
363
+ };
364
+ });
365
+
366
+ // Create 'products' store
367
+ const productsStore = Stores.products.get();
368
+ // Logs: "Creating store: products"
369
+
370
+ Stores.products.setLoading(true);
371
+ Stores.products.addItem({ id: 1, name: 'Product 1' });
372
+
373
+ // Create 'users' store
374
+ const usersStore = Stores.users.get();
375
+ // Logs: "Creating store: users"
376
+
377
+ Stores.users.setItems([{ id: 1, name: 'Alice' }]);
378
+ ```
379
+
380
+ ### LocalStorage Namespaces
381
+ ```javascript
382
+ import { Cache } from 'native-document/utils';
383
+
384
+ const Storage = Cache.memoize((namespace) => {
385
+ console.log(`Creating storage for namespace: ${namespace}`);
386
+
387
+ const prefix = `${namespace}:`;
388
+
389
+ return {
390
+ get: (key) => {
391
+ try {
392
+ const item = localStorage.getItem(prefix + key);
393
+ return item ? JSON.parse(item) : null;
394
+ } catch {
395
+ return null;
396
+ }
397
+ },
398
+ set: (key, value) => {
399
+ try {
400
+ localStorage.setItem(prefix + key, JSON.stringify(value));
401
+ return true;
402
+ } catch {
403
+ return false;
404
+ }
405
+ },
406
+ remove: (key) => {
407
+ localStorage.removeItem(prefix + key);
408
+ },
409
+ clear: () => {
410
+ // Clear all keys with this namespace
411
+ Object.keys(localStorage)
412
+ .filter(key => key.startsWith(prefix))
413
+ .forEach(key => localStorage.removeItem(key));
414
+ }
415
+ };
416
+ });
417
+
418
+ // User preferences namespace
419
+ Storage.user.set('theme', 'dark');
420
+ // Logs: "Creating storage for namespace: user"
421
+ // localStorage: "user:theme" = "dark"
422
+
423
+ Storage.user.set('language', 'en');
424
+ // localStorage: "user:language" = "en"
425
+
426
+ const theme = Storage.user.get('theme');
427
+ // Returns: "dark"
428
+
429
+ // App settings namespace (different instance)
430
+ Storage.app.set('version', '1.0.0');
431
+ // Logs: "Creating storage for namespace: app"
432
+ // localStorage: "app:version" = "1.0.0"
433
+ ```
434
+
435
+ ### Event Handlers by Type
436
+ ```javascript
437
+ import { Cache } from 'native-document/utils';
438
+
439
+ const EventHandlers = Cache.memoize((eventType) => {
440
+ console.log(`Creating handler for: ${eventType}`);
441
+ const listeners = [];
442
+
443
+ return {
444
+ add: (callback) => {
445
+ listeners.push(callback);
446
+ },
447
+ remove: (callback) => {
448
+ const index = listeners.indexOf(callback);
449
+ if (index > -1) {
450
+ listeners.splice(index, 1);
451
+ }
452
+ },
453
+ emit: (data) => {
454
+ listeners.forEach(callback => callback(data));
455
+ },
456
+ count: () => listeners.length
457
+ };
458
+ });
459
+
460
+ // Create 'click' event handler
461
+ EventHandlers.click.add((data) => console.log('Clicked:', data));
462
+ // Logs: "Creating handler for: click"
463
+
464
+ EventHandlers.click.emit({ x: 100, y: 200 });
465
+ // Logs: "Clicked: { x: 100, y: 200 }"
466
+
467
+ // Create 'scroll' event handler (different instance)
468
+ EventHandlers.scroll.add((data) => console.log('Scrolled:', data));
469
+ // Logs: "Creating handler for: scroll"
470
+
471
+ EventHandlers.scroll.emit({ top: 500 });
472
+ // Logs: "Scrolled: { top: 500 }"
473
+
474
+ console.log(EventHandlers.click.count()); // 1
475
+ console.log(EventHandlers.scroll.count()); // 1
476
+ ```
477
+
478
+ ### Validation Rules by Type
479
+ ```javascript
480
+ import { Cache } from 'native-document/utils';
481
+
482
+ const Validators = Cache.memoize((fieldType) => {
483
+ console.log(`Creating validators for: ${fieldType}`);
484
+
485
+ const rules = {
486
+ email: {
487
+ required: (value) => !!value || 'Email is required',
488
+ format: (value) => /\S+@\S+\.\S+/.test(value) || 'Invalid email format'
489
+ },
490
+ password: {
491
+ required: (value) => !!value || 'Password is required',
492
+ minLength: (value) => value.length >= 8 || 'Password must be at least 8 characters',
493
+ hasNumber: (value) => /\d/.test(value) || 'Password must contain a number'
494
+ },
495
+ phone: {
496
+ required: (value) => !!value || 'Phone is required',
497
+ format: (value) => /^\d{10}$/.test(value) || 'Phone must be 10 digits'
498
+ }
499
+ };
500
+
501
+ return {
502
+ validate: (value) => {
503
+ const fieldRules = rules[fieldType];
504
+ if (!fieldRules) return [];
505
+
506
+ const errors = [];
507
+ for (const [ruleName, rule] of Object.entries(fieldRules)) {
508
+ const result = rule(value);
509
+ if (result !== true) {
510
+ errors.push(result);
511
+ }
512
+ }
513
+ return errors;
514
+ },
515
+ isValid: (value) => {
516
+ return Validators[fieldType].validate(value).length === 0;
517
+ }
518
+ };
519
+ });
520
+
521
+ // Validate email
522
+ const emailErrors = Validators.email.validate('invalid-email');
523
+ // Logs: "Creating validators for: email"
524
+ // Returns: ['Invalid email format']
525
+
526
+ const isEmailValid = Validators.email.isValid('test@example.com');
527
+ // Returns: true (cached instance)
528
+
529
+ // Validate password
530
+ const passwordErrors = Validators.password.validate('weak');
531
+ // Logs: "Creating validators for: password"
532
+ // Returns: ['Password must be at least 8 characters', 'Password must contain a number']
533
+ ```
534
+
535
+ ### Chart Instances by Type
536
+ ```javascript
537
+ import { Cache } from 'native-document/utils';
538
+
539
+ const Charts = Cache.memoize((chartType) => {
540
+ console.log(`Creating ${chartType} chart instance...`);
541
+
542
+ return {
543
+ render: (container, data, options = {}) => {
544
+ console.log(`Rendering ${chartType} chart`);
545
+
546
+ // Simplified chart rendering
547
+ const canvas = document.createElement('canvas');
548
+ container.appendChild(canvas);
549
+
550
+ // Chart-specific rendering logic
551
+ switch (chartType) {
552
+ case 'bar':
553
+ renderBarChart(canvas, data, options);
554
+ break;
555
+ case 'line':
556
+ renderLineChart(canvas, data, options);
557
+ break;
558
+ case 'pie':
559
+ renderPieChart(canvas, data, options);
560
+ break;
561
+ }
562
+
563
+ return canvas;
564
+ },
565
+ update: (canvas, data) => {
566
+ console.log(`Updating ${chartType} chart`);
567
+ // Update logic
568
+ }
569
+ };
570
+ });
571
+
572
+ // Create bar chart instance
573
+ const barContainer = document.querySelector('#bar-chart');
574
+ Charts.bar.render(barContainer, [10, 20, 30]);
575
+ // Logs: "Creating bar chart instance..."
576
+ // Logs: "Rendering bar chart"
577
+
578
+ // Create line chart instance (different from bar)
579
+ const lineContainer = document.querySelector('#line-chart');
580
+ Charts.line.render(lineContainer, [5, 15, 25]);
581
+ // Logs: "Creating line chart instance..."
582
+ // Logs: "Rendering line chart"
583
+
584
+ // Reuse bar chart instance
585
+ Charts.bar.update(barCanvas, [15, 25, 35]);
586
+ // Logs: "Updating bar chart"
587
+ ```
588
+
589
+ ### Form Field Managers
590
+ ```javascript
591
+ import { Cache } from 'native-document/utils';
592
+ import { Observable } from 'native-document';
593
+
594
+ const FormFields = Cache.memoize((fieldName) => {
595
+ console.log(`Creating field manager for: ${fieldName}`);
596
+
597
+ const value = Observable('');
598
+ const errors = Observable([]);
599
+ const touched = Observable(false);
600
+
601
+ return {
602
+ value,
603
+ errors,
604
+ touched,
605
+ setValue: (newValue) => {
606
+ value.set(newValue);
607
+ touched.set(true);
608
+ },
609
+ setErrors: (newErrors) => {
610
+ errors.set(newErrors);
611
+ },
612
+ reset: () => {
613
+ value.set('');
614
+ errors.set([]);
615
+ touched.set(false);
616
+ },
617
+ isValid: () => errors.val().length === 0
618
+ };
619
+ });
620
+
621
+ // Create email field
622
+ FormFields.email.setValue('test@example.com');
623
+ // Logs: "Creating field manager for: email"
624
+
625
+ FormFields.email.setErrors([]);
626
+ console.log(FormFields.email.isValid()); // true
627
+
628
+ // Create password field (different instance)
629
+ FormFields.password.setValue('weak');
630
+ // Logs: "Creating field manager for: password"
631
+
632
+ FormFields.password.setErrors(['Too short']);
633
+ console.log(FormFields.password.isValid()); // false
634
+ ```
635
+
636
+ ## Comparison: once vs singleton vs memoize
637
+
638
+ | Feature | `Cache.once()` | `Cache.singleton()` | `Cache.memoize()` |
639
+ |---------|------------------|------------------------|---------------------|
640
+ | **Execution** | Lazy (on property access) | Eager (on first call) | Lazy (per key on property access) |
641
+ | **Implementation** | `autoOnce` | `once` | `autoMemoize` |
642
+ | **Access Pattern** | `obj.property` | `fn()` | `obj[key].method()` |
643
+ | **Instances Created** | 1 (single instance) | 1 (single instance) | N (one per key) |
644
+ | **Cache Strategy** | Properties from result | Entire result | Result per property key |
645
+ | **Use Case** | Lazy modules | Eager singletons | Multiple instances by key |
646
+
647
+ ### Visual Comparison
648
+ ```javascript
649
+ import { Cache } from 'native-document/utils';
650
+
651
+ // Cache.once() - Single lazy instance
652
+ const LazyUtils = Cache.once(() => {
653
+ console.log('Init utils');
654
+ return {
655
+ format: () => 'formatted',
656
+ parse: () => 'parsed'
657
+ };
658
+ });
659
+
660
+ LazyUtils.format(); // Logs: "Init utils"
661
+ LazyUtils.parse(); // No log - same instance
662
+
663
+ // Cache.singleton() - Single eager instance
664
+ const getConfig = Cache.singleton(() => {
665
+ console.log('Init config');
666
+ return { api: 'url', debug: true };
667
+ });
668
+
669
+ const config = getConfig(); // Logs: "Init config"
670
+ const config2 = getConfig(); // No log - same instance
671
+
672
+ // Cache.memoize() - Multiple instances by key
673
+ const API = Cache.memoize((resource) => {
674
+ console.log(`Init ${resource}`);
675
+ return {
676
+ list: () => `List ${resource}`,
677
+ get: (id) => `Get ${resource}/${id}`
678
+ };
679
+ });
680
+
681
+ API.users.list(); // Logs: "Init users"
682
+ API.users.get(1); // No log - cached 'users' instance
683
+ API.posts.list(); // Logs: "Init posts" - new key, new instance
684
+ API.posts.get(2); // No log - cached 'posts' instance
685
+ ```
686
+
687
+ ## When to Use Cache.memoize()
688
+
689
+ ### ✅ Good Use Cases
690
+ ```javascript
691
+ import { Cache } from 'native-document/utils';
692
+
693
+ // Multiple API endpoints with same structure
694
+ const API = Cache.memoize((resource) => ({
695
+ list: () => fetch(`/${resource}`),
696
+ get: (id) => fetch(`/${resource}/${id}`)
697
+ }));
698
+
699
+ // Multiple storage namespaces
700
+ const Storage = Cache.memoize((namespace) => ({
701
+ get: (key) => localStorage.getItem(`${namespace}:${key}`),
702
+ set: (key, val) => localStorage.setItem(`${namespace}:${key}`, val)
703
+ }));
704
+
705
+ // Multiple event types with same handler structure
706
+ const Events = Cache.memoize((type) => {
707
+ const listeners = [];
708
+ return {
709
+ on: (cb) => listeners.push(cb),
710
+ emit: (data) => listeners.forEach(cb => cb(data))
711
+ };
712
+ });
713
+
714
+ // Multiple form fields with same structure
715
+ const Fields = Cache.memoize((name) => ({
716
+ value: Observable(''),
717
+ error: Observable(null),
718
+ validate: () => { /* ... */ }
719
+ }));
720
+ ```
721
+
722
+ ### ❌ Bad Use Cases
723
+ ```javascript
724
+ import { Cache } from 'native-document/utils';
725
+
726
+ // ❌ Don't use for single instance
727
+ const Config = Cache.memoize(() => loadConfig());
728
+ // Use Cache.singleton() instead
729
+
730
+ // ❌ Don't use when keys are unpredictable
731
+ const RandomData = Cache.memoize((timestamp) => generateData());
732
+ // Each call has unique timestamp - cache never hits
733
+
734
+ // ❌ Don't use for simple property access
735
+ const Constants = Cache.memoize(() => ({
736
+ PI: 3.14159,
737
+ E: 2.71828
738
+ }));
739
+ // Use Cache.once() instead
740
+ ```
741
+
742
+ ## Best Practices
743
+
744
+ ### 1. Use Descriptive Keys
745
+ ```javascript
746
+ import { Cache } from 'native-document/utils';
747
+
748
+ // ✅ Good: Clear, predictable keys
749
+ const Endpoints = Cache.memoize((resource) => createAPI(resource));
750
+ Endpoints.users.list();
751
+ Endpoints.posts.list();
752
+
753
+ // ❌ Bad: Dynamic, unpredictable keys
754
+ const DynamicAPI = Cache.memoize((timestamp) => createAPI(timestamp));
755
+ DynamicAPI[Date.now()].list(); // New instance every time
756
+ ```
757
+
758
+ ### 2. Document Key-Based Behavior
759
+ ```javascript
760
+ import { Cache } from 'native-document/utils';
761
+
762
+ /**
763
+ * API endpoints by resource type
764
+ * @memoized Each resource type gets its own instance
765
+ * @param {string} resource - Resource name (e.g., 'users', 'posts')
766
+ */
767
+ const API = Cache.memoize((resource) => ({
768
+ list: () => fetch(`/${resource}`),
769
+ get: (id) => fetch(`/${resource}/${id}`)
770
+ }));
771
+ ```
772
+
773
+ ### 3. Keep Keys Simple
774
+ ```javascript
775
+ import { Cache } from 'native-document/utils';
776
+
777
+ // ✅ Good: Simple string keys
778
+ const Stores = Cache.memoize((name) => Store.create(name, {}));
779
+ Stores.user;
780
+ Stores.settings;
781
+
782
+ // ❌ Bad: Complex keys (won't work as expected)
783
+ const BadCache = Cache.memoize((config) => createThing(config));
784
+ BadCache[{ type: 'user' }]; // Object as key - problematic
785
+ ```
786
+
787
+ ### 4. Combine with Other Patterns
788
+ ```javascript
789
+ import { Cache } from 'native-document/utils';
790
+
791
+ // Eager config + Memoized resources
792
+ const getConfig = Cache.singleton(() => loadConfig());
793
+
794
+ const Resources = Cache.memoize((type) => {
795
+ const config = getConfig(); // Config already loaded
796
+ return createResource(type, config);
797
+ });
798
+ ```
799
+
800
+ ## Performance Considerations
801
+
802
+ ### Memory Usage
803
+ ```javascript
804
+ import { Cache } from 'native-document/utils';
805
+
806
+ // ⚠️ Each key creates a new cached instance
807
+ const API = Cache.memoize((resource) => createAPI(resource));
808
+
809
+ API.users; // Instance 1
810
+ API.posts; // Instance 2
811
+ API.comments; // Instance 3
812
+ // All instances stay in memory
813
+
814
+ // Consider: Do you need separate instances per key?
815
+ ```
816
+
817
+ ### Cache Growth
818
+ ```javascript
819
+ import { Cache } from 'native-document/utils';
820
+
821
+ // ⚠️ Warning: Unbounded cache growth
822
+ const DynamicCache = Cache.memoize((id) => createInstance(id));
823
+
824
+ // If IDs keep changing, cache grows indefinitely
825
+ for (let i = 0; i < 1000; i++) {
826
+ DynamicCache[`user-${i}`]; // 1000 cached instances
827
+ }
828
+
829
+ // ✅ Better: Limited, predictable keys
830
+ const ResourceCache = Cache.memoize((type) => createResource(type));
831
+ ResourceCache.users; // Only a few known types
832
+ ResourceCache.posts;
833
+ ResourceCache.comments;
834
+ ```
835
+
836
+ ## Summary
837
+
838
+ ### Quick Reference
839
+
840
+ | Pattern | Syntax | When to Use |
841
+ |---------|--------|-------------|
842
+ | **Lazy Module** | `Cache.once(() => {...})` | Optional features, heavy resources |
843
+ | **Eager Singleton** | `Cache.singleton(() => {...})` | Core services, configuration |
844
+ | **Multi-Instance** | `Cache.memoize((key) => {...})` | Resources by type, namespaced data |
845
+
846
+ ### Key Differences
847
+ ```javascript
848
+ import { Cache } from 'native-document/utils';
849
+
850
+ // Cache.once() - Single lazy instance via autoOnce
851
+ const Lazy = Cache.once(() => ({ value: 1 }));
852
+ Lazy.value; // Creates instance on property access
853
+
854
+ // Cache.singleton() - Single eager instance via once
855
+ const Eager = Cache.singleton(() => ({ value: 1 }));
856
+ Eager(); // Creates instance on function call
857
+
858
+ // Cache.memoize() - Multiple instances by key via autoMemoize
859
+ const Multi = Cache.memoize((key) => ({ value: key }));
860
+ Multi.a; // Creates instance for key 'a'
861
+ Multi.b; // Creates instance for key 'b'
862
+ ```
863
+
864
+ ## Next Steps
865
+
866
+ Explore related utilities and concepts:
867
+
868
+ ## Next Steps
869
+
870
+ - **[Getting Started](getting-started.md)** - Installation and first steps
871
+ - **[Core Concepts](core-concepts.md)** - Understanding the fundamentals
872
+ - **[Observables](observables.md)** - Reactive state management
873
+ - **[Elements](elements.md)** - Creating and composing UI
874
+ - **[Conditional Rendering](conditional-rendering.md)** - Dynamic content
875
+ - **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
876
+ - **[Routing](routing.md)** - Navigation and URL management
877
+ - **[State Management](state-management.md)** - Global state patterns
878
+ - **[NDElement](native-document-element.md)** - Native Document Element
879
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
880
+ - **[Advanced Components](advanced-components.md)** - Template caching and singleton views
881
+ - **[Args Validation](validation.md)** - Function Argument Validation
882
+ - **[Memory Management](memory-management.md)** - Memory management
883
+
884
+ ## Utilities
885
+
886
+ - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
887
+ - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
888
+ - **[Filters](docs/utils/filters.md)** - Data filtering helpers