native-document 1.0.95 → 1.0.98

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 (43) hide show
  1. package/{src/devtools/hrm → devtools}/ComponentRegistry.js +2 -2
  2. package/devtools/index.js +8 -0
  3. package/{src/devtools/plugin.js → devtools/plugin/dev-tools-plugin.js} +2 -2
  4. package/{src/devtools/hrm/nd-vite-hot-reload.js → devtools/transformers/nd-vite-devtools.js} +16 -6
  5. package/devtools/transformers/src/transformComponentForHrm.js +74 -0
  6. package/devtools/transformers/src/transformJsFile.js +9 -0
  7. package/devtools/transformers/src/utils.js +79 -0
  8. package/devtools/widget/Widget.js +48 -0
  9. package/devtools/widget/widget.css +81 -0
  10. package/devtools/widget.js +23 -0
  11. package/dist/native-document.components.min.js +1922 -1277
  12. package/dist/native-document.dev.js +1985 -1401
  13. package/dist/native-document.dev.js.map +1 -1
  14. package/dist/native-document.devtools.min.js +1 -1
  15. package/dist/native-document.min.js +1 -1
  16. package/docs/cache.md +1 -1
  17. package/docs/core-concepts.md +1 -1
  18. package/docs/native-document-element.md +51 -15
  19. package/docs/observables.md +310 -306
  20. package/docs/state-management.md +198 -193
  21. package/package.json +1 -1
  22. package/readme.md +1 -1
  23. package/src/core/data/ObservableChecker.js +2 -0
  24. package/src/core/data/ObservableItem.js +97 -0
  25. package/src/core/data/ObservableObject.js +182 -0
  26. package/src/core/data/Store.js +364 -34
  27. package/src/core/data/observable-helpers/object.js +2 -166
  28. package/src/core/utils/formatters.js +91 -0
  29. package/src/core/utils/localstorage.js +57 -0
  30. package/src/core/utils/validator.js +0 -2
  31. package/src/devtools.js +9 -0
  32. package/src/fetch/NativeFetch.js +5 -2
  33. package/types/observable.d.ts +71 -15
  34. package/types/plugins-manager.d.ts +1 -1
  35. package/types/store.d.ts +33 -6
  36. package/hrm.js +0 -7
  37. package/src/devtools/app/App.js +0 -66
  38. package/src/devtools/app/app.css +0 -0
  39. package/src/devtools/hrm/transformComponent.js +0 -129
  40. package/src/devtools/index.js +0 -18
  41. package/src/devtools/widget/DevToolsWidget.js +0 -26
  42. /package/{src/devtools/hrm → devtools/transformers/templates}/hrm.hook.template.js +0 -0
  43. /package/{src/devtools/hrm → devtools/transformers/templates}/hrm.orbservable.hook.template.js +0 -0
@@ -53,23 +53,158 @@ const cartStore = Store.create('cart', {
53
53
  });
54
54
  ```
55
55
 
56
- ### Store with Computed Values
56
+ ### Resettable Store
57
57
 
58
+ Use `createResettable()` when the store needs to return to its initial value — for example on logout or route change.
58
59
  ```javascript
59
- const cartStore = Store.create('cart', {
60
- items: [],
61
- taxRate: 0.08
60
+ const userStore = Store.createResettable('user', {
61
+ id: null,
62
+ name: '',
63
+ email: '',
64
+ isLoggedIn: false
62
65
  });
63
66
 
64
- // Computed total that updates automatically
67
+ // Reset to initial value at any time
68
+ Store.reset('user');
69
+ // -> { id: null, name: '', email: '', isLoggedIn: false }
70
+ // -> all subscribers are notified automatically
71
+
72
+ // Standard create() does not support reset
73
+ Store.reset('theme'); // ❌ throws : this store is not resettable
74
+ ```
75
+
76
+ ### Store with Computed Values
77
+
78
+ For a computed value that stays local, use `Observable.computed()` :
79
+ ```javascript
65
80
  const cartTotal = Observable.computed(() => {
66
- const cart = cartStore.$value;
81
+ const cart = cartStore.val();
67
82
  const subtotal = cart.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
68
- const tax = subtotal * cart.taxRate;
69
- return subtotal + tax;
83
+ return subtotal + (subtotal * cart.taxRate);
70
84
  }, [cartStore]);
71
85
  ```
72
86
 
87
+ For a computed value that must be accessible globally via the Store registry, use `createComposed()` :
88
+ ```javascript
89
+ Store.create('products', [{ id: 1, price: 10 }]);
90
+ Store.create('cart', [{ productId: 1, quantity: 2 }]);
91
+
92
+ Store.createComposed('total', () => {
93
+ const products = Store.get('products').val();
94
+ const cart = Store.get('cart').val();
95
+ return cart.reduce((sum, item) => {
96
+ const product = products.find(p => p.id === item.productId);
97
+ return sum + (product.price * item.quantity);
98
+ }, 0);
99
+ }, ['products', 'cart']);
100
+
101
+ // Access like any other store — read-only
102
+ const total = Store.follow('total');
103
+ total.val(); // -> 20
104
+
105
+ // Store.use('total') -> ❌ throws : composed stores are read-only
106
+ // Store.reset('total') -> ❌ throws : composed stores cannot be reset
107
+ ```
108
+
109
+ ### Persistent Store
110
+
111
+ Use `createPersistent()` when the store needs to survive page reloads. The value is automatically restored from localStorage on load and saved on every change.
112
+ ```javascript
113
+ const theme = Store.createPersistent('theme', 'light');
114
+ theme.set('dark'); // saved to localStorage automatically
115
+
116
+ // On next page load — restored automatically
117
+ Store.get('theme').val(); // "dark"
118
+ ```
119
+
120
+ Use `createPersistentResettable()` when you need both persistence and reset capability. Reset clears the localStorage entry and restores the default value.
121
+ ```javascript
122
+ const session = Store.createPersistentResettable('session', { id: null, name: '' });
123
+ session.set({ id: 1, name: 'John' }); // saved
124
+
125
+ Store.reset('session');
126
+ // -> { id: null, name: '' }
127
+ // -> localStorage entry removed
128
+ ```
129
+
130
+ Both methods accept an optional custom localStorage key — useful when the store name conflicts with existing keys:
131
+ ```javascript
132
+ Store.createPersistent('theme', 'light', 'app:theme');
133
+ Store.createPersistentResettable('session', null, 'app:session');
134
+ ```
135
+
136
+ ### Store Groups
137
+
138
+ Use `Store.group()` to create an isolated store namespace. Each group is a fully independent `StoreFactory` instance — no key conflicts, no shared state with the parent store.
139
+ ```javascript
140
+ const EventStore = Store.group('events', (group) => {
141
+ group.create('catalog', []);
142
+ group.create('filters', { category: null, date: null, city: null });
143
+
144
+ group.createResettable('selected', null);
145
+
146
+ group.createComposed('filtered', () => {
147
+ const catalog = EventStore.get('catalog').val();
148
+ const filters = EventStore.get('filters').val();
149
+ return catalog.filter(event => {
150
+ if (filters.category && event.category !== filters.category) return false;
151
+ if (filters.city && event.city !== filters.city) return false;
152
+ return true;
153
+ });
154
+ }, ['catalog', 'filters']);
155
+ });
156
+
157
+ // Usage — same API as Store
158
+ EventStore.use('catalog'); // two-way follower
159
+ EventStore.follow('filtered'); // read-only follower
160
+ EventStore.get('filters'); // raw observable
161
+
162
+ // Direct property access — raw observable, always read-only
163
+ EventStore.catalog;
164
+ EventStore.filters;
165
+ ```
166
+
167
+ Groups can reference each other in `createComposed()` for cross-group derived state:
168
+ ```javascript
169
+ const CartStore = Store.group('cart', (group) => {
170
+ group.create('items', []);
171
+
172
+ group.createComposed('total', () => {
173
+ return CartStore.get('items').val()
174
+ .reduce((sum, item) => sum + item.price * item.qty, 0);
175
+ }, ['items']);
176
+ });
177
+
178
+ const OrderStore = Store.group('orders', (group) => {
179
+ group.createComposed('summary', () => {
180
+ const items = CartStore.get('items').val();
181
+ const events = EventStore.get('catalog').val();
182
+ return { items, events };
183
+ }, [CartStore.get('items'), EventStore.get('catalog')]);
184
+ });
185
+ ```
186
+
187
+ Groups can also be created without a name:
188
+ ```javascript
189
+ const CartStore = Store.group((group) => {
190
+ group.create('items', []);
191
+ });
192
+ ```
193
+
194
+ ### Use Groups to Organize Domain State
195
+ ```javascript
196
+ // Good: domain-driven grouping
197
+ const UserStore = Store.group('user', (g) => { g.create('session', null); });
198
+ const EventStore = Store.group('events', (g) => { g.create('catalog', []); });
199
+ const CartStore = Store.group('cart', (g) => { g.create('items', []); });
200
+
201
+ // Avoid: flat global stores for everything
202
+ Store.create('userSession', null);
203
+ Store.create('eventCatalog', []);
204
+ Store.create('cartItems', []);
205
+ ```
206
+
207
+
73
208
  ## Using Stores
74
209
 
75
210
  Access and react to store changes using Store.use() or Store.follow():
@@ -82,8 +217,8 @@ const UserProfile = () => {
82
217
  const user = Store.use('user');
83
218
 
84
219
  return Div([
85
- H1(['User Profile: ', user.check(u => u.name)]),
86
- P(['Email: ', user.check(u => u.email)]),
220
+ H1(['User Profile: ', user.name]),
221
+ P(['Email: ', user.email]),
87
222
  P(['Status: ', user.check(u => u.isLoggedIn ? 'Online' : 'Offline')])
88
223
  ]);
89
224
  };
@@ -103,12 +238,15 @@ const UserMenu = () => {
103
238
  };
104
239
  ```
105
240
 
106
- ### Store.follow() - Alternative Access
241
+ ### Store.follow() - Read-only Access
107
242
 
108
243
  ```javascript
109
244
  const NotificationBadge = () => {
110
- // Follow is alias for use
245
+ // Follow returns a read-only reference — any attempt to call
246
+ // .set(), .toggle() or .reset() will throw a NativeDocumentError.
247
+ // Use this when a component should only read from the store.
111
248
  const notifications = Store.follow('notifications');
249
+ // notifications.set(...) -> ❌ throws NativeDocumentError
112
250
 
113
251
  return ShowIf(notifications.check(list => list.length > 0),
114
252
  () => Span({ class: 'badge' }, notifications.$value.length)
@@ -116,6 +254,38 @@ const NotificationBadge = () => {
116
254
  };
117
255
  ```
118
256
 
257
+ ### Store.get() - Raw Access
258
+
259
+ Returns the raw store observable directly — no follower, no cleanup contract. Use this for direct read access when you don't need to unsubscribe.
260
+
261
+ > **Warning:** mutations on this observer impact all subscribers immediately.
262
+ ```javascript
263
+ const userStore = Store.get('user');
264
+
265
+ if (userStore.$value.isLoggedIn) {
266
+ console.log('User is logged in');
267
+ }
268
+
269
+ userStore.subscribe(newUser => {
270
+ console.log('User changed:', newUser);
271
+ });
272
+ ```
273
+
274
+ ### Direct Property Access
275
+
276
+ Stores and groups expose their observables as direct properties via a Proxy. Property access returns the raw observable — equivalent to calling `Store.get()`. Any attempt to assign or delete a property will throw.
277
+ ```javascript
278
+ // Equivalent to Store.get('catalog')
279
+ EventStore.catalog;
280
+
281
+ // For a read-only follower, use follow() explicitly
282
+ EventStore.follow('catalog');
283
+
284
+ // Direct assignment is forbidden
285
+ EventStore.catalog = []; // ❌ throws : Store structure is immutable
286
+ delete EventStore.catalog; // ❌ throws : Store keys cannot be deleted
287
+ ```
288
+
119
289
  ## Updating Store State
120
290
 
121
291
  Modify store state using the returned observable's methods:
@@ -147,7 +317,7 @@ const LoginForm = () => {
147
317
  ...user.$value,
148
318
  email: email.$value,
149
319
  isLoggedIn: true,
150
- name: 'User Name' // This would come from API
320
+ name: 'User Name' // ...
151
321
  });
152
322
  };
153
323
 
@@ -215,181 +385,18 @@ userStore.subscribe(newUser => {
215
385
  });
216
386
  ```
217
387
 
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
-
388
+ ### Checking Store Existence
372
389
  ```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
- };
390
+ // Check if a store exists before accessing it
391
+ if (Store.has('cart')) {
392
+ const cart = Store.use('cart');
393
+ // ...
394
+ }
387
395
 
388
- // Usage
389
- createPersistentStore('userPreferences', {
390
- theme: 'light',
391
- language: 'en'
392
- }, 'app_preferences');
396
+ // Typical use case : dynamic module initialization
397
+ if (!Store.has('cart')) {
398
+ Store.create('cart', { items: [], total: 0 });
399
+ }
393
400
  ```
394
401
 
395
402
  ### Store Composition
@@ -402,13 +409,11 @@ const createAppState = () => {
402
409
  const settings = Store.create('settings', defaultSettings);
403
410
 
404
411
  // 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
+ Store.createComposed('appStatus', () => ({
413
+ isLoggedIn: Store.get('auth').val().user !== null,
414
+ cartItems: Store.get('cart').val().items.length,
415
+ theme: Store.get('settings').val().theme
416
+ }), ['auth', 'cart', 'settings']);
412
417
 
413
418
  return { auth, cart, settings, appStatus };
414
419
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.95",
3
+ "version": "1.0.98",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -317,4 +317,4 @@ Thanks to all contributors and the JavaScript community for inspiration.
317
317
 
318
318
  ---
319
319
 
320
- **Ready to build with native simplicity?** [Get Started ](docs/getting-started.md)
320
+ **Ready to build with native simplicity?** [Get Started ->](docs/getting-started.md)
@@ -10,6 +10,8 @@ export default function ObservableChecker($observable, $checker) {
10
10
  this.unSubscriptions = [];
11
11
  }
12
12
 
13
+ export const ObservablePipe = ObservableChecker;
14
+
13
15
  ObservableChecker.prototype.__$isObservableChecker = true;
14
16
 
15
17
  /**
@@ -6,6 +6,10 @@ import PluginsManager from "../../core/utils/plugins-manager";
6
6
  import Validator from "../../core/utils/validator";
7
7
  import {ObservableWhen} from "./ObservableWhen";
8
8
  import {deepClone} from "../utils/helpers";
9
+ import {Store} from "./Store";
10
+ import { Formatters} from "../utils/formatters";
11
+ import {Observable} from "./Observable";
12
+ import {$getFromStorage, $saveToStorage} from "../utils/localstorage";
9
13
 
10
14
  /**
11
15
  *
@@ -368,6 +372,11 @@ ObservableItem.prototype.check = function(callback) {
368
372
  return new ObservableChecker(this, callback)
369
373
  };
370
374
 
375
+ ObservableItem.prototype.transform = ObservableItem.prototype.check;
376
+ ObservableItem.prototype.pluck = ObservableItem.prototype.check;
377
+ ObservableItem.prototype.is = ObservableItem.prototype.check;
378
+ ObservableItem.prototype.select = ObservableItem.prototype.check;
379
+
371
380
  /**
372
381
  * Gets a property value from the observable's current value.
373
382
  * If the property is an observable, returns its value.
@@ -483,4 +492,92 @@ ObservableItem.prototype.toString = function() {
483
492
  */
484
493
  ObservableItem.prototype.valueOf = function() {
485
494
  return this.$currentValue;
495
+ };
496
+
497
+
498
+ /**
499
+ * Creates a derived observable that formats the current value using Intl.
500
+ * Automatically reacts to both value changes and locale changes (Store.__nd.locale).
501
+ *
502
+ * @param {string | Function} type - Format type or custom formatter function
503
+ * @param {Object} [options={}] - Options passed to the formatter
504
+ * @returns {ObservableItem<string>}
505
+ *
506
+ * @example
507
+ * // Currency
508
+ * price.format('currency') // "15 000 FCFA"
509
+ * price.format('currency', { currency: 'EUR' }) // "15 000,00 €"
510
+ * price.format('currency', { notation: 'compact' }) // "15 K FCFA"
511
+ *
512
+ * // Number
513
+ * count.format('number') // "15 000"
514
+ *
515
+ * // Percent
516
+ * rate.format('percent') // "15,0 %"
517
+ * rate.format('percent', { decimals: 2 }) // "15,00 %"
518
+ *
519
+ * // Date
520
+ * date.format('date') // "3 mars 2026"
521
+ * date.format('date', { dateStyle: 'full' }) // "mardi 3 mars 2026"
522
+ * date.format('date', { format: 'DD/MM/YYYY' }) // "03/03/2026"
523
+ * date.format('date', { format: 'DD MMM YYYY' }) // "03 mar 2026"
524
+ * date.format('date', { format: 'DD MMMM YYYY' }) // "03 mars 2026"
525
+ *
526
+ * // Time
527
+ * date.format('time') // "20:30"
528
+ * date.format('time', { second: '2-digit' }) // "20:30:00"
529
+ * date.format('time', { format: 'HH:mm:ss' }) // "20:30:00"
530
+ *
531
+ * // Datetime
532
+ * date.format('datetime') // "3 mars 2026, 20:30"
533
+ * date.format('datetime', { dateStyle: 'full' }) // "mardi 3 mars 2026, 20:30"
534
+ * date.format('datetime', { format: 'DD/MM/YYYY HH:mm' }) // "03/03/2026 20:30"
535
+ *
536
+ * // Relative
537
+ * date.format('relative') // "dans 11 jours"
538
+ * date.format('relative', { unit: 'month' }) // "dans 1 mois"
539
+ *
540
+ * // Plural
541
+ * count.format('plural', { singular: 'billet', plural: 'billets' }) // "3 billets"
542
+ *
543
+ * // Custom formatter
544
+ * price.format(value => `${value.toLocaleString()} FCFA`)
545
+ *
546
+ * // Reacts to locale changes automatically
547
+ * Store.setLocale('en-US');
548
+ */
549
+ ObservableItem.prototype.format = function(type, options = {}) {
550
+ const self = this;
551
+
552
+ if (typeof type === 'function') {
553
+ return new ObservableChecker(self, type);
554
+ }
555
+
556
+ if (process.env.NODE_ENV === 'development') {
557
+ if (!Formatters[type]) {
558
+ throw new NativeDocumentError(
559
+ `Observable.format : unknown type '${type}'. Available : ${Object.keys(Formatters).join(', ')}.`
560
+ );
561
+ }
562
+ }
563
+
564
+ const formatter = Formatters[type];
565
+ const localeObservable = Store.follow('locale');
566
+
567
+ return Observable.computed(() => formatter(self.val(), localeObservable.val(), options),
568
+ [self, localeObservable]
569
+ );
570
+ };
571
+
572
+ ObservableItem.prototype.persist = function(key, options = {}) {
573
+ let value = $getFromStorage(key, this.$currentValue);
574
+ if(options.get) {
575
+ value = options.get(value);
576
+ }
577
+ this.set(value);
578
+ const saver = $saveToStorage(this.$currentValue);
579
+ this.subscribe((newValue) => {
580
+ saver(key, options.set ? options.set(newValue) : newValue);
581
+ });
582
+ return this;
486
583
  };