native-document 1.0.9 → 1.0.11

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.
@@ -0,0 +1,423 @@
1
+ # State Management
2
+
3
+ NativeDocument's state management system provides tools for managing application-wide state that persists across components and route changes. The Store system enables shared state with reactive updates, while Observables handle local component state.
4
+
5
+ ## Understanding State Management
6
+
7
+ State management in NativeDocument operates on two levels: local component state using Observables, and global application state using the Store system. The Store allows multiple components to share and react to the same state changes.
8
+
9
+ ```javascript
10
+ import { Store, Observable } from 'native-document';
11
+
12
+ // Create global state
13
+ const userStore = Store.create('user', {
14
+ id: null,
15
+ name: '',
16
+ email: '',
17
+ isLoggedIn: false
18
+ });
19
+
20
+ // Components automatically update when store changes
21
+ const UserGreeting = () => {
22
+ const user = Store.use('user');
23
+
24
+ return ShowIf(user.check(u => u.isLoggedIn),
25
+ () => Div(['Welcome back, ', user.$value.name, '!'])
26
+ );
27
+ };
28
+ ```
29
+
30
+ ## Store Creation
31
+
32
+ Create named stores that can be accessed from anywhere in your application:
33
+
34
+ ### Basic Store Creation
35
+
36
+ ```javascript
37
+ // Create a simple store
38
+ const themeStore = Store.create('theme', 'light');
39
+
40
+ // Create an object store
41
+ const appStore = Store.create('app', {
42
+ currentPage: 'home',
43
+ sidebarOpen: false,
44
+ notifications: []
45
+ });
46
+
47
+ // Create with initial complex data
48
+ const cartStore = Store.create('cart', {
49
+ items: [],
50
+ total: 0,
51
+ currency: 'USD',
52
+ discountCode: null
53
+ });
54
+ ```
55
+
56
+ ### Store with Computed Values
57
+
58
+ ```javascript
59
+ const cartStore = Store.create('cart', {
60
+ items: [],
61
+ taxRate: 0.08
62
+ });
63
+
64
+ // Computed total that updates automatically
65
+ const cartTotal = Observable.computed(() => {
66
+ const cart = cartStore.$value;
67
+ const subtotal = cart.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
68
+ const tax = subtotal * cart.taxRate;
69
+ return subtotal + tax;
70
+ }, [cartStore]);
71
+ ```
72
+
73
+ ## Using Stores
74
+
75
+ Access and react to store changes using Store.use() or Store.follow():
76
+
77
+ ### Store.use() - Primary Access Method
78
+
79
+ ```javascript
80
+ const UserProfile = () => {
81
+ // Get reactive reference to store
82
+ const user = Store.use('user');
83
+
84
+ return Div([
85
+ H1(['User Profile: ', user.check(u => u.name)]),
86
+ P(['Email: ', user.check(u => u.email)]),
87
+ P(['Status: ', user.check(u => u.isLoggedIn ? 'Online' : 'Offline')])
88
+ ]);
89
+ };
90
+
91
+ // Multiple components can use the same store
92
+ const UserMenu = () => {
93
+ const user = Store.use('user');
94
+
95
+ return Nav([
96
+ ShowIf(user.check(u => u.isLoggedIn), [
97
+ Link({ to: '/profile' }, 'My Profile'),
98
+ Button('Logout').nd.on.click(() => {
99
+ user.set({ ...user.$value, isLoggedIn: false });
100
+ })
101
+ ])
102
+ ]);
103
+ };
104
+ ```
105
+
106
+ ### Store.follow() - Alternative Access
107
+
108
+ ```javascript
109
+ const NotificationBadge = () => {
110
+ // Follow is alias for use
111
+ const notifications = Store.follow('notifications');
112
+
113
+ return ShowIf(notifications.check(list => list.length > 0),
114
+ () => Span({ class: 'badge' }, notifications.$value.length)
115
+ );
116
+ };
117
+ ```
118
+
119
+ ## Updating Store State
120
+
121
+ Modify store state using the returned observable's methods:
122
+
123
+ ### Direct Updates
124
+
125
+ ```javascript
126
+ const ThemeToggle = () => {
127
+ const theme = Store.use('theme');
128
+
129
+ return Button('Toggle Theme').nd.on.click(() => {
130
+ const current = theme.$value;
131
+ theme.set(current === 'light' ? 'dark' : 'light');
132
+ });
133
+ };
134
+ ```
135
+
136
+ ### Object Store Updates
137
+
138
+ ```javascript
139
+ const LoginForm = () => {
140
+ const user = Store.use('user');
141
+ const email = Observable('');
142
+ const password = Observable('');
143
+
144
+ const handleLogin = () => {
145
+ // Update multiple properties
146
+ user.set({
147
+ ...user.$value,
148
+ email: email.$value,
149
+ isLoggedIn: true,
150
+ name: 'User Name' // This would come from API
151
+ });
152
+ };
153
+
154
+ return Form([
155
+ Input({ type: 'email', value: email, placeholder: 'Email' }),
156
+ Input({ type: 'password', value: password, placeholder: 'Password' }),
157
+ Button('Login').nd.on.click(handleLogin)
158
+ ]);
159
+ };
160
+ ```
161
+
162
+ ### Partial Updates
163
+
164
+ ```javascript
165
+ const UserSettings = () => {
166
+ const user = Store.use('user');
167
+
168
+ const updateName = (newName) => {
169
+ // Only update specific fields
170
+ user.set({
171
+ ...user.$value,
172
+ name: newName
173
+ });
174
+ };
175
+
176
+ const updatePreferences = (prefs) => {
177
+ user.set({
178
+ ...user.$value,
179
+ preferences: {
180
+ ...user.$value.preferences,
181
+ ...prefs
182
+ }
183
+ });
184
+ };
185
+
186
+ return Div([
187
+ Input({
188
+ value: user.check(u => u.name),
189
+ placeholder: 'Name'
190
+ }).nd.on.input(e => updateName(e.target.value)),
191
+
192
+ Button('Dark Mode').nd.on.click(() =>
193
+ updatePreferences({ theme: 'dark' })
194
+ )
195
+ ]);
196
+ };
197
+ ```
198
+
199
+ ## Store Access Patterns
200
+
201
+ ### Direct Store Access
202
+
203
+ ```javascript
204
+ // Get store reference without reactivity
205
+ const userStore = Store.get('user');
206
+
207
+ // Check current value
208
+ if (userStore.$value.isLoggedIn) {
209
+ console.log('User is logged in');
210
+ }
211
+
212
+ // Subscribe to changes manually
213
+ userStore.subscribe(newUser => {
214
+ console.log('User changed:', newUser);
215
+ });
216
+ ```
217
+
218
+ ## Real-World Examples
219
+
220
+ ## Store Cleanup
221
+
222
+ Properly clean up stores when they're no longer needed:
223
+
224
+ ### Manual Cleanup
225
+
226
+ ```javascript
227
+ // Remove store and all its subscribers
228
+ Store.delete('temporaryStore');
229
+
230
+ // Clean up specific subscriber
231
+ const userStore = Store.use('user');
232
+ userStore.destroy(); // Clean up this subscriber instance
233
+ ```
234
+
235
+ ## Performance Considerations
236
+
237
+ ### Efficient Store Updates
238
+
239
+ ```javascript
240
+ // Good: Batch related updates
241
+ const updateUserProfile = (name, email, preferences) => {
242
+ const user = Store.use('user');
243
+ user.set({
244
+ ...user.$value,
245
+ name,
246
+ email,
247
+ preferences: {
248
+ ...user.$value.preferences,
249
+ ...preferences
250
+ }
251
+ });
252
+ };
253
+
254
+ // Less efficient: Multiple separate updates
255
+ const updateUserProfileSlow = (name, email, preferences) => {
256
+ const user = Store.use('user');
257
+ user.set({ ...user.$value, name }); // Triggers update
258
+ user.set({ ...user.$value, email }); // Triggers update
259
+ user.set({ ...user.$value, preferences }); // Triggers update
260
+ };
261
+ ```
262
+
263
+ ### Selective Subscriptions
264
+
265
+ ```javascript
266
+ // Subscribe to specific parts of store
267
+ const UserName = () => {
268
+ const user = Store.use('user');
269
+
270
+ // Only re-render when name changes
271
+ const userName = user.check(u => u.name);
272
+
273
+ return Span(userName);
274
+ };
275
+
276
+ // More efficient than re-rendering entire user profile
277
+ // when only name is needed
278
+ ```
279
+
280
+ ## Best Practices
281
+
282
+ ### 1. Use Descriptive Store Names
283
+
284
+ ```javascript
285
+ // Good: Clear, descriptive names
286
+ const userAuthStore = Store.create('userAuth', defaultUser);
287
+ const shoppingCartStore = Store.create('shoppingCart', defaultCart);
288
+ const appSettingsStore = Store.create('appSettings', defaultSettings);
289
+
290
+ // Less clear: Generic names
291
+ const store1 = Store.create('data', {});
292
+ const state = Store.create('state', {});
293
+ ```
294
+
295
+ ### 2. Initialize with Complete Data Structures
296
+
297
+ ```javascript
298
+ // Good: Complete initial structure
299
+ const userStore = Store.create('user', {
300
+ id: null,
301
+ name: '',
302
+ email: '',
303
+ preferences: {
304
+ theme: 'light',
305
+ notifications: true
306
+ },
307
+ isLoggedIn: false
308
+ });
309
+
310
+ // Problematic: Incomplete structure
311
+ const userStore = Store.create('user', {});
312
+ // Later code might fail when accessing user.preferences.theme
313
+ ```
314
+
315
+ ### 3. Create Service Objects for Complex Operations
316
+
317
+ ```javascript
318
+ // Good: Organized actions
319
+ const AuthService= (function() {
320
+ const authUser = Store.create('user', {});
321
+
322
+ return {
323
+ login: (credentials) => { /* ... */ },
324
+ logout: () => { /* ... */ },
325
+ updateProfile: (data) => { /* ... */ },
326
+ updatePreferences: (prefs) => { /* ... */ }
327
+ };
328
+ }());
329
+
330
+ // Instead of scattered update logic throughout components
331
+ ```
332
+
333
+ ### 4. Use Store for Cross-Component State Only
334
+
335
+ ```javascript
336
+ // Good: Local state for component-specific data
337
+ const ContactForm = () => {
338
+ const name = Observable(''); // Local state
339
+ const email = Observable(''); // Local state
340
+ const user = Store.use('user'); // Global state
341
+
342
+ return Form([/* ... */]);
343
+ };
344
+
345
+ // Avoid: Putting everything in stores
346
+ // Don't store form input state globally unless shared
347
+ ```
348
+
349
+ ## Debugging Stores
350
+
351
+ ### Store State Inspection
352
+
353
+ ```javascript
354
+ // Log current store state
355
+ console.log('Current user:', Store.get('user').$value);
356
+
357
+ // Monitor store changes
358
+ Store.get('user').subscribe(newUser => {
359
+ console.log('User changed:', newUser);
360
+ });
361
+
362
+ // Check all followers of a store
363
+ const userData = Store.getWithSubscribers('user');
364
+ console.log('Store value:', userData.observer.$value);
365
+ console.log('Followers count:', userData.subscribers.size);
366
+ ```
367
+
368
+ ## Common Patterns
369
+
370
+ ### Persistent Store
371
+
372
+ ```javascript
373
+ const createPersistentStore = (name, defaultValue, storageKey) => {
374
+ // Load from localStorage
375
+ const saved = localStorage.getItem(storageKey);
376
+ const initialValue = saved ? JSON.parse(saved) : defaultValue;
377
+
378
+ const store = Store.create(name, initialValue);
379
+
380
+ // Save changes to localStorage
381
+ store.subscribe(newValue => {
382
+ localStorage.setItem(storageKey, JSON.stringify(newValue));
383
+ });
384
+
385
+ return store;
386
+ };
387
+
388
+ // Usage
389
+ createPersistentStore('userPreferences', {
390
+ theme: 'light',
391
+ language: 'en'
392
+ }, 'app_preferences');
393
+ ```
394
+
395
+ ### Store Composition
396
+
397
+ ```javascript
398
+ // Combine multiple stores for complex state
399
+ const createAppState = () => {
400
+ const auth = Store.create('auth', defaultAuth);
401
+ const cart = Store.create('cart', defaultCart);
402
+ const settings = Store.create('settings', defaultSettings);
403
+
404
+ // Computed store that combines others
405
+ const appStatus = Observable.computed(() => {
406
+ return {
407
+ isLoggedIn: auth.$value.user !== null,
408
+ cartItems: cart.$value.items.length,
409
+ theme: settings.$value.theme
410
+ };
411
+ }, [auth, cart, settings]);
412
+
413
+ return { auth, cart, settings, appStatus };
414
+ };
415
+ ```
416
+
417
+ ## Next Steps
418
+
419
+ Now that you understand state management, explore these related topics:
420
+
421
+ - **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
422
+ - **[Memory Management](docs/memory-management.md)** - Memory management
423
+ - **[Anchor](docs/anchor.md)** - Anchor
@@ -0,0 +1,193 @@
1
+ # Validation
2
+
3
+ NativeDocument provides a comprehensive runtime validation system that helps catch errors early and ensures function arguments meet expected types and constraints.
4
+
5
+ ## Function Argument Validation
6
+
7
+ ### Using .args() Method
8
+
9
+ ```javascript
10
+ // Original function
11
+ function createUser(name, age, email) {
12
+ console.log(`Creating user: ${name}, ${age}, ${email}`);
13
+ }
14
+
15
+ // .args() returns a function that validates arguments before calling original
16
+ const createUserWithValidation = createUser.args(
17
+ ArgTypes.string('name'),
18
+ ArgTypes.number('age'),
19
+ ArgTypes.string('email')
20
+ );
21
+
22
+ // Usage
23
+ createUserWithValidation("John", 25, "john@example.com"); // Valid - calls original function
24
+ createUserWithValidation("John", "25", "john@example.com"); // Throws validation error
25
+ ```
26
+
27
+ ### WithValidation Wrapper (Equivalent)
28
+
29
+ ```javascript
30
+ // These two approaches are equivalent:
31
+
32
+ // Method 1: Using .args()
33
+ const createUserWithValidation1 = createUser.args(
34
+ ArgTypes.string('name'),
35
+ ArgTypes.number('age'),
36
+ ArgTypes.string('email')
37
+ );
38
+
39
+ // Method 2: Using withValidation()
40
+ const createUserWithValidation2 = withValidation(createUser, [
41
+ ArgTypes.string('name'),
42
+ ArgTypes.number('age'),
43
+ ArgTypes.string('email')
44
+ ], 'createUser');
45
+
46
+ // Both return a function that validates arguments before calling the original
47
+ ```
48
+
49
+ ## ArgTypes Reference
50
+
51
+ ### Basic Types
52
+
53
+ ```javascript
54
+ ArgTypes.string('name') // Must be string
55
+ ArgTypes.number('age') // Must be number
56
+ ArgTypes.boolean('isActive') // Must be boolean
57
+ ArgTypes.function('callback') // Must be function
58
+ ArgTypes.object('config') // Must be object
59
+ ArgTypes.objectNotNull('data') // Must be object and not null
60
+ ```
61
+
62
+ ### NativeDocument Types
63
+
64
+ ```javascript
65
+ ArgTypes.observable('state') // Must be Observable instance
66
+ ArgTypes.element('domNode') // Must be HTML element
67
+ ArgTypes.children('content') // Valid children (elements, strings, numbers, observables)
68
+ ArgTypes.attributes('attrs') // Valid attributes object
69
+ ```
70
+
71
+ ### Optional Arguments
72
+
73
+ ```javascript
74
+ function greet(name, greeting) {
75
+ return `${greeting || 'Hello'} ${name}`;
76
+ }
77
+
78
+ // Create function with argument validation
79
+ const greetWithValidation = greet.args(
80
+ ArgTypes.string('name'),
81
+ ArgTypes.optional(ArgTypes.string('greeting'))
82
+ );
83
+
84
+ greetWithValidation("John"); // Valid - greeting is optional
85
+ greetWithValidation("John", "Hi"); // Valid
86
+ greetWithValidation("John", 123); // Error - greeting must be string if provided
87
+ ```
88
+
89
+ ### OneOf Validation
90
+
91
+ ```javascript
92
+ function setTheme(theme, config) {
93
+ // Implementation
94
+ }
95
+
96
+ // Create function with argument validation
97
+ const setThemeWithValidation = setTheme.args(
98
+ ArgTypes.oneOf('theme',
99
+ ArgTypes.string('theme'),
100
+ ArgTypes.object('theme')
101
+ ),
102
+ ArgTypes.object('config')
103
+ );
104
+
105
+ setThemeWithValidation("dark", {}); // Valid - string theme
106
+ setThemeWithValidation({name: "custom"}, {}); // Valid - object theme
107
+ setThemeWithValidation(123, {}); // Error - must be string or object
108
+ ```
109
+
110
+ ## Error Handling
111
+
112
+ ### Validation Errors
113
+
114
+ ```javascript
115
+ function processData(items, callback) {
116
+ // Implementation
117
+ }
118
+
119
+ // Create function with argument validation
120
+ const processDataWithValidation = processData.args(
121
+ ArgTypes.children('items'),
122
+ ArgTypes.function('callback')
123
+ );
124
+
125
+ try {
126
+ processDataWithValidation("invalid", "not a function");
127
+ } catch (error) {
128
+ console.log(error.message);
129
+ // "Argument validation failed
130
+ // processData: Invalid argument 'items' at position 1, expected children, got String
131
+ // processData: Invalid argument 'callback' at position 2, expected function, got String"
132
+ }
133
+ ```
134
+
135
+ ### Error Boundary Pattern
136
+
137
+ ```javascript
138
+ // Original function with argument validation
139
+ const createUserWithValidation = createUser.args(
140
+ ArgTypes.string('name'),
141
+ ArgTypes.number('age'),
142
+ ArgTypes.string('email')
143
+ );
144
+
145
+ // Add error boundary to validation function
146
+ const safeCreateUser = createUserWithValidation.errorBoundary((error) => {
147
+ console.error("User creation failed:", error.message);
148
+ return null; // Return fallback value
149
+ });
150
+
151
+ // Won't throw, returns null on validation error
152
+ const result = safeCreateUser("John", "invalid age", "email");
153
+ ```
154
+
155
+ ## Custom Validation
156
+
157
+ ### Creating Custom ArgTypes
158
+
159
+ ```javascript
160
+ // Custom validator for email format
161
+ const emailType = (name) => ({
162
+ name,
163
+ type: 'email',
164
+ validate: (value) => {
165
+ return typeof value === 'string' && /\S+@\S+\.\S+/.test(value);
166
+ }
167
+ });
168
+
169
+ function registerUser(email, password) {
170
+ // Implementation
171
+ }
172
+
173
+ registerUser.args(
174
+ emailType('email'),
175
+ ArgTypes.string('password')
176
+ );
177
+ ```
178
+
179
+ ## Best Practices
180
+
181
+ 1. **Validate early** - Add validation to public functions and components
182
+ 2. **Use descriptive names** - Make error messages clear with good argument names
183
+ 3. **Combine with error boundaries** - Handle validation errors gracefully
184
+ 4. **Validate complex objects** - Use ArgTypes.objectNotNull for required objects
185
+ 5. **Make optional explicit** - Use ArgTypes.optional() for clarity
186
+ 6. **Custom validators** - Create reusable validators for domain-specific types
187
+
188
+ ## Next Steps
189
+
190
+ - **[Advanced Features](advanced-features.md)** - Error boundaries and debugging tools
191
+ - **[API Reference](api-reference.md)** - Complete ArgTypes and validation API
192
+ - **[Memory Management](memory-management.md)** - Debugging memory issues with validation
193
+ - **[Lifecycle Events](lifecycle-events.md)** - Validate lifecycle callback arguments
package/elements.js CHANGED
@@ -1,2 +1,4 @@
1
-
1
+ import Anchor from "./src/elements/anchor";
2
2
  export * from './src/elements/index';
3
+
4
+ export { Anchor };
package/index.js CHANGED
@@ -2,7 +2,9 @@ export { default as HtmlElementWrapper, ElementCreator } from './src/wrappers/Ht
2
2
 
3
3
  import './src/utils/prototypes.js';
4
4
 
5
+ export * from './src/utils/plugins-manager';
5
6
  export * from './src/utils/args-types';
7
+ export * from './src/utils/validator'
6
8
  export * from './src/data/Observable';
7
9
  export * from './src/data/Store';
8
10
  import * as elements from './elements';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {