@everystate/core 1.0.5 → 1.0.7
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/README.md +17 -3
- package/everyState.js +51 -0
- package/index.d.ts +12 -0
- package/package.json +1 -1
- package/self-test.js +64 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @everystate/core v1.0.
|
|
1
|
+
# @everystate/core v1.0.7
|
|
2
2
|
|
|
3
3
|
**EveryState: Observable state management with dot-path addressing**
|
|
4
4
|
|
|
@@ -35,6 +35,14 @@ store.subscribe('user.*', ({ path, value }) => {
|
|
|
35
35
|
console.log(`User field ${path} changed to:`, value);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
// Check if a path exists (handles intentional undefined)
|
|
39
|
+
store.has('count'); // true
|
|
40
|
+
store.has('nonexistent'); // false
|
|
41
|
+
|
|
42
|
+
// List all leaf paths under a prefix
|
|
43
|
+
store.keys('user'); // ['user.name']
|
|
44
|
+
store.keys(); // ['count', 'user.name']
|
|
45
|
+
|
|
38
46
|
// Cleanup
|
|
39
47
|
unsub();
|
|
40
48
|
```
|
|
@@ -112,6 +120,7 @@ EveryState is a reactive state management library where:
|
|
|
112
120
|
- **Path-based subscriptions**: Subscribe to exactly what you need
|
|
113
121
|
- **Wildcard support**: `user.*` catches all user changes
|
|
114
122
|
- **Atomic batching**: Multiple writes, single notification per path
|
|
123
|
+
- **Path introspection**: `has()` and `keys()` for runtime path discovery
|
|
115
124
|
- **Zero dependencies**: ~2KB minified
|
|
116
125
|
- **Framework-agnostic**: Works with React, Vue, Angular, Svelte, or vanilla JS
|
|
117
126
|
|
|
@@ -134,10 +143,12 @@ EveryState makes state **addressable, observable, and testable** without special
|
|
|
134
143
|
| [@everystate/examples](https://www.npmjs.com/package/@everystate/examples) | Example applications and patterns | MIT |
|
|
135
144
|
| [@everystate/perf](https://www.npmjs.com/package/@everystate/perf) | Performance monitoring overlay | MIT |
|
|
136
145
|
| [@everystate/react](https://www.npmjs.com/package/@everystate/react) | React hooks adapter: `usePath`, `useIntent`, `useAsync` hooks and `EventStateProvider` | MIT |
|
|
137
|
-
| [@everystate/renderer](https://www.npmjs.com/package/@everystate/renderer) | Direct-binding reactive renderer: `bind-*`, `set`, `each` attributes. Zero build step |
|
|
146
|
+
| [@everystate/renderer](https://www.npmjs.com/package/@everystate/renderer) | Direct-binding reactive renderer: `bind-*`, `set`, `each` attributes. Zero build step | MIT |
|
|
138
147
|
| [@everystate/router](https://www.npmjs.com/package/@everystate/router) | SPA routing as state | MIT |
|
|
139
|
-
| [@everystate/test](https://www.npmjs.com/package/@everystate/test) | Event-sequence testing for UIstate stores. Zero dependency. |
|
|
148
|
+
| [@everystate/test](https://www.npmjs.com/package/@everystate/test) | Event-sequence testing for UIstate stores. Zero dependency. | MIT |
|
|
140
149
|
| [@everystate/view](https://www.npmjs.com/package/@everystate/view) | State-driven view: DOMless resolve + surgical DOM projector. View tree as first-class state | MIT |
|
|
150
|
+
| [@everystate/vue](https://www.npmjs.com/package/@everystate/vue) | Vue 3 composables adapter: `provideStore`, `usePath`, `useIntent`, `useWildcard`, `useAsync` | MIT |
|
|
151
|
+
| [@everystate/types](https://www.npmjs.com/package/@everystate/types) | Typed dot-path autocomplete for EveryState stores (you are here) | MIT |
|
|
141
152
|
|
|
142
153
|
## Documentation
|
|
143
154
|
|
|
@@ -146,3 +157,6 @@ Full documentation: [https://github.com/ImsirovicAjdin/everystate-core](https://
|
|
|
146
157
|
## License
|
|
147
158
|
|
|
148
159
|
MIT © Ajdin Imsirovic
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
package/everyState.js
CHANGED
|
@@ -142,6 +142,57 @@ export function createEveryState(initial = {}) {
|
|
|
142
142
|
return cur;
|
|
143
143
|
},
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Check if a path exists in the store.
|
|
147
|
+
* Unlike get(path) !== undefined, this correctly handles
|
|
148
|
+
* paths whose value is intentionally set to undefined.
|
|
149
|
+
* @param {string} path - Dot-separated path
|
|
150
|
+
* @returns {boolean} true if the path exists
|
|
151
|
+
*/
|
|
152
|
+
has(path) {
|
|
153
|
+
if (destroyed) throw new Error('Cannot check destroyed store');
|
|
154
|
+
if (!path) return true;
|
|
155
|
+
const parts = path.split('.');
|
|
156
|
+
let cur = state;
|
|
157
|
+
for (const p of parts) {
|
|
158
|
+
if (cur == null || typeof cur !== 'object' || !(p in cur)) return false;
|
|
159
|
+
cur = cur[p];
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* List all leaf paths under a prefix.
|
|
166
|
+
* @param {string} [prefix] - Dot-separated prefix (e.g., 'css').
|
|
167
|
+
* If omitted, lists all paths in the entire store.
|
|
168
|
+
* @returns {string[]} Array of dot-separated leaf paths
|
|
169
|
+
*/
|
|
170
|
+
keys(prefix) {
|
|
171
|
+
if (destroyed) throw new Error('Cannot list keys of destroyed store');
|
|
172
|
+
let root = state;
|
|
173
|
+
let base = '';
|
|
174
|
+
if (prefix) {
|
|
175
|
+
base = prefix;
|
|
176
|
+
const parts = prefix.split('.');
|
|
177
|
+
for (const p of parts) {
|
|
178
|
+
if (root == null || typeof root !== 'object' || !(p in root)) return [];
|
|
179
|
+
root = root[p];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const result = [];
|
|
183
|
+
function walk(obj, path) {
|
|
184
|
+
if (obj == null || typeof obj !== 'object') {
|
|
185
|
+
result.push(path);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (const k of Object.keys(obj)) {
|
|
189
|
+
walk(obj[k], path ? `${path}.${k}` : k);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
walk(root, base);
|
|
193
|
+
return result;
|
|
194
|
+
},
|
|
195
|
+
|
|
145
196
|
/**
|
|
146
197
|
* Set value at path and notify subscribers
|
|
147
198
|
* @param {string} path - Dot-separated path (e.g., 'user.profile.name')
|
package/index.d.ts
CHANGED
|
@@ -22,6 +22,18 @@ export interface EveryStateStore {
|
|
|
22
22
|
*/
|
|
23
23
|
get(path?: string): any;
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Check if a path exists in the store.
|
|
27
|
+
* Unlike `get(path) !== undefined`, correctly handles intentionally-stored undefined.
|
|
28
|
+
*/
|
|
29
|
+
has(path: string): boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* List all leaf paths under a prefix.
|
|
33
|
+
* If no prefix is provided, lists all paths in the entire store.
|
|
34
|
+
*/
|
|
35
|
+
keys(prefix?: string): string[];
|
|
36
|
+
|
|
25
37
|
/**
|
|
26
38
|
* Set value at a dot-separated path and notify subscribers.
|
|
27
39
|
* @returns The value that was set
|
package/package.json
CHANGED
package/self-test.js
CHANGED
|
@@ -284,9 +284,72 @@ assert('wildcard-only: detail.oldValue correct', s12detail?.oldValue === 'Alice'
|
|
|
284
284
|
|
|
285
285
|
s12.destroy();
|
|
286
286
|
|
|
287
|
+
console.log('\n13. has()');
|
|
288
|
+
const s13 = createEveryState({ count: 0, user: { name: 'Alice' }, empty: undefined });
|
|
289
|
+
|
|
290
|
+
assert('has: existing primitive', s13.has('count') === true);
|
|
291
|
+
assert('has: existing nested', s13.has('user.name') === true);
|
|
292
|
+
assert('has: existing parent', s13.has('user') === true);
|
|
293
|
+
assert('has: missing path', s13.has('nonexistent') === false);
|
|
294
|
+
assert('has: deep missing', s13.has('user.email') === false);
|
|
295
|
+
assert('has: deep missing parent', s13.has('a.b.c') === false);
|
|
296
|
+
assert('has: no path returns true', s13.has('') === true);
|
|
297
|
+
|
|
298
|
+
// set undefined intentionally, then check
|
|
299
|
+
s13.set('maybe', undefined);
|
|
300
|
+
assert('has: intentionally undefined', s13.has('maybe') === true);
|
|
301
|
+
|
|
302
|
+
// destroyed store
|
|
303
|
+
s13.destroy();
|
|
304
|
+
let threwHas = false;
|
|
305
|
+
try { s13.has('count'); } catch { threwHas = true; }
|
|
306
|
+
assert('has: destroyed throws', threwHas);
|
|
307
|
+
|
|
308
|
+
console.log('\n14. keys()');
|
|
309
|
+
const s14 = createEveryState({ count: 0, user: { name: 'Alice', age: 30 }, flag: true });
|
|
310
|
+
|
|
311
|
+
// All keys
|
|
312
|
+
const allKeys = s14.keys();
|
|
313
|
+
assert('keys: returns array', Array.isArray(allKeys));
|
|
314
|
+
assert('keys: has count', allKeys.includes('count'));
|
|
315
|
+
assert('keys: has user.name', allKeys.includes('user.name'));
|
|
316
|
+
assert('keys: has user.age', allKeys.includes('user.age'));
|
|
317
|
+
assert('keys: has flag', allKeys.includes('flag'));
|
|
318
|
+
assert('keys: no parent objects', !allKeys.includes('user'));
|
|
319
|
+
assert('keys: correct count', allKeys.length === 4);
|
|
320
|
+
|
|
321
|
+
// Prefix
|
|
322
|
+
const userKeys = s14.keys('user');
|
|
323
|
+
assert('keys(prefix): returns children', userKeys.length === 2);
|
|
324
|
+
assert('keys(prefix): has user.name', userKeys.includes('user.name'));
|
|
325
|
+
assert('keys(prefix): has user.age', userKeys.includes('user.age'));
|
|
326
|
+
|
|
327
|
+
// Missing prefix
|
|
328
|
+
const missing = s14.keys('nonexistent');
|
|
329
|
+
assert('keys(missing): empty array', missing.length === 0);
|
|
330
|
+
|
|
331
|
+
// Deep prefix
|
|
332
|
+
s14.set('a.b.c', 1);
|
|
333
|
+
s14.set('a.b.d', 2);
|
|
334
|
+
s14.set('a.e', 3);
|
|
335
|
+
const aKeys = s14.keys('a');
|
|
336
|
+
assert('keys(deep prefix): a has 3 leaves', aKeys.length === 3);
|
|
337
|
+
assert('keys(deep prefix): a.b.c', aKeys.includes('a.b.c'));
|
|
338
|
+
assert('keys(deep prefix): a.b.d', aKeys.includes('a.b.d'));
|
|
339
|
+
assert('keys(deep prefix): a.e', aKeys.includes('a.e'));
|
|
340
|
+
|
|
341
|
+
const abKeys = s14.keys('a.b');
|
|
342
|
+
assert('keys(deep prefix): a.b has 2 leaves', abKeys.length === 2);
|
|
343
|
+
|
|
344
|
+
// Destroyed store
|
|
345
|
+
s14.destroy();
|
|
346
|
+
let threwKeys = false;
|
|
347
|
+
try { s14.keys(); } catch { threwKeys = true; }
|
|
348
|
+
assert('keys: destroyed throws', threwKeys);
|
|
349
|
+
|
|
287
350
|
// Results
|
|
288
351
|
|
|
289
|
-
console.log(`\n@everystate/core v1.0.
|
|
352
|
+
console.log(`\n@everystate/core v1.0.6 self-test`);
|
|
290
353
|
console.log(`✓ ${passed} assertions passed${failed ? `, ✗ ${failed} failed` : ''}\n`);
|
|
291
354
|
|
|
292
355
|
if (failed > 0) process.exit(1);
|