@uistate/core 5.6.0 → 5.6.2
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 +4 -2
- package/eventState.js +1 -1
- package/index.js +1 -1
- package/package.json +9 -3
- package/queryClient.js +0 -1
- package/self-test.js +216 -0
package/README.md
CHANGED
|
@@ -127,10 +127,12 @@ qc.invalidate('users');
|
|
|
127
127
|
| Package | Description | License |
|
|
128
128
|
|---|---|---|
|
|
129
129
|
| [@uistate/core](https://www.npmjs.com/package/@uistate/core) | Path-based state management with wildcard subscriptions and async support | MIT |
|
|
130
|
+
| [@uistate/view](https://www.npmjs.com/package/@uistate/view) | State-driven view: DOMless resolve + surgical DOM projector. View tree as first-class state | MIT |
|
|
130
131
|
| [@uistate/react](https://www.npmjs.com/package/@uistate/react) | React adapter — `usePath`, `useIntent`, `useAsync` hooks and `EventStateProvider` | MIT |
|
|
131
|
-
| [@uistate/css](https://www.npmjs.com/package/@uistate/css) |
|
|
132
|
+
| [@uistate/css](https://www.npmjs.com/package/@uistate/css) | Reactive CSSOM engine — design tokens, typed validation, WCAG enforcement, all via path-based state | MIT |
|
|
132
133
|
| [@uistate/event-test](https://www.npmjs.com/package/@uistate/event-test) | Event-sequence testing for UIstate stores | Proprietary |
|
|
133
134
|
| [@uistate/examples](https://www.npmjs.com/package/@uistate/examples) | Example applications and patterns | MIT |
|
|
135
|
+
| [@uistate/renderer](https://www.npmjs.com/package/@uistate/renderer) | Direct-binding reactive renderer: `bind-*`, `set`, `each` attributes. Zero build step | Proprietary |
|
|
134
136
|
| [@uistate/aliases](https://www.npmjs.com/package/@uistate/aliases) | Ergonomic single-character and short-name DOM aliases for vanilla JS | MIT |
|
|
135
137
|
|
|
136
138
|
📖 **Documentation:** [uistate.com](https://uistate.com)
|
|
@@ -139,7 +141,7 @@ qc.invalidate('users');
|
|
|
139
141
|
|
|
140
142
|
MIT — see [LICENSE](./LICENSE).
|
|
141
143
|
|
|
142
|
-
Copyright © 2025 Ajdin Imsirovic
|
|
144
|
+
Copyright © 2025–2026 Ajdin Imsirovic
|
|
143
145
|
|
|
144
146
|
## Links
|
|
145
147
|
|
package/eventState.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EventState
|
|
2
|
+
* EventState v5.6.2 - Optimized Path-Based State Management
|
|
3
3
|
*
|
|
4
4
|
* A lightweight, performant state management library using path-based subscriptions.
|
|
5
5
|
* Optimized for selective notifications and granular updates.
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uistate/core",
|
|
3
|
-
"version": "5.6.
|
|
3
|
+
"version": "5.6.2",
|
|
4
4
|
"description": "Lightweight event-driven state management with path-based subscriptions, wildcards, and async support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
8
|
+
"postinstall": "node self-test.js",
|
|
9
|
+
"test": "node tests/core.test.js",
|
|
10
|
+
"self-test": "node self-test.js"
|
|
9
11
|
},
|
|
10
12
|
"exports": {
|
|
11
13
|
".": "./index.js",
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
"index.js",
|
|
17
19
|
"eventState.js",
|
|
18
20
|
"queryClient.js",
|
|
21
|
+
"self-test.js",
|
|
19
22
|
"LICENSE"
|
|
20
23
|
],
|
|
21
24
|
"keywords": [
|
|
@@ -25,7 +28,10 @@
|
|
|
25
28
|
"zero-dependency",
|
|
26
29
|
"micro-framework",
|
|
27
30
|
"async-state",
|
|
28
|
-
"query-client"
|
|
31
|
+
"query-client",
|
|
32
|
+
"domless-testing",
|
|
33
|
+
"view-as-state",
|
|
34
|
+
"state-as-view"
|
|
29
35
|
],
|
|
30
36
|
"author": "Ajdin Imsirovic",
|
|
31
37
|
"license": "MIT",
|
package/queryClient.js
CHANGED
package/self-test.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uistate/core — self-test
|
|
3
|
+
*
|
|
4
|
+
* Standalone test of core EventState functionality. No dependencies beyond eventState.js.
|
|
5
|
+
* Runs on `node self-test.js` or as a postinstall hook.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createEventState } from './eventState.js';
|
|
9
|
+
|
|
10
|
+
let passed = 0;
|
|
11
|
+
let failed = 0;
|
|
12
|
+
|
|
13
|
+
function assert(name, condition) {
|
|
14
|
+
if (condition) {
|
|
15
|
+
passed++;
|
|
16
|
+
console.log(` ✓ ${name}`);
|
|
17
|
+
} else {
|
|
18
|
+
failed++;
|
|
19
|
+
console.error(` ✗ ${name}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log('\n1. get / set');
|
|
24
|
+
const s1 = createEventState({ count: 0, user: { name: 'Alice', age: 30 } });
|
|
25
|
+
|
|
26
|
+
assert('get: primitive', s1.get('count') === 0);
|
|
27
|
+
assert('get: nested', s1.get('user.name') === 'Alice');
|
|
28
|
+
assert('get: object', s1.get('user')?.name === 'Alice');
|
|
29
|
+
assert('get: entire state', s1.get('')?.count === 0);
|
|
30
|
+
assert('get: missing path → undefined', s1.get('nonexistent') === undefined);
|
|
31
|
+
assert('get: deep missing → undefined', s1.get('user.email') === undefined);
|
|
32
|
+
|
|
33
|
+
s1.set('count', 5);
|
|
34
|
+
assert('set: primitive', s1.get('count') === 5);
|
|
35
|
+
|
|
36
|
+
s1.set('user.name', 'Bob');
|
|
37
|
+
assert('set: nested', s1.get('user.name') === 'Bob');
|
|
38
|
+
|
|
39
|
+
s1.set('user.email', 'bob@example.com');
|
|
40
|
+
assert('set: auto-creates path', s1.get('user.email') === 'bob@example.com');
|
|
41
|
+
|
|
42
|
+
s1.set('deep.nested.path', 'value');
|
|
43
|
+
assert('set: deep auto-create', s1.get('deep.nested.path') === 'value');
|
|
44
|
+
|
|
45
|
+
s1.destroy();
|
|
46
|
+
|
|
47
|
+
console.log('\n2. subscribe: exact path');
|
|
48
|
+
const s2 = createEventState({ count: 0 });
|
|
49
|
+
let lastValue = null;
|
|
50
|
+
let fireCount = 0;
|
|
51
|
+
|
|
52
|
+
const unsub = s2.subscribe('count', (value) => {
|
|
53
|
+
lastValue = value;
|
|
54
|
+
fireCount++;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
s2.set('count', 1);
|
|
58
|
+
assert('subscribe: fires on change', fireCount === 1);
|
|
59
|
+
assert('subscribe: receives value', lastValue === 1);
|
|
60
|
+
|
|
61
|
+
s2.set('count', 2);
|
|
62
|
+
assert('subscribe: fires again', fireCount === 2);
|
|
63
|
+
assert('subscribe: receives new value', lastValue === 2);
|
|
64
|
+
|
|
65
|
+
unsub();
|
|
66
|
+
s2.set('count', 3);
|
|
67
|
+
assert('unsubscribe: stops firing', fireCount === 2);
|
|
68
|
+
assert('unsubscribe: value unchanged in handler', lastValue === 2);
|
|
69
|
+
assert('set still works after unsub', s2.get('count') === 3);
|
|
70
|
+
|
|
71
|
+
s2.destroy();
|
|
72
|
+
|
|
73
|
+
console.log('\n3. subscribe: wildcard');
|
|
74
|
+
const s3 = createEventState({ user: { name: 'Alice', age: 30 } });
|
|
75
|
+
let wildcardFires = 0;
|
|
76
|
+
let wildcardDetail = null;
|
|
77
|
+
|
|
78
|
+
s3.subscribe('user.*', (detail) => {
|
|
79
|
+
wildcardFires++;
|
|
80
|
+
wildcardDetail = detail;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
s3.set('user.name', 'Bob');
|
|
84
|
+
assert('wildcard: fires on child change', wildcardFires === 1);
|
|
85
|
+
assert('wildcard: detail has path', wildcardDetail?.path === 'user.name');
|
|
86
|
+
assert('wildcard: detail has value', wildcardDetail?.value === 'Bob');
|
|
87
|
+
|
|
88
|
+
s3.set('user.age', 31);
|
|
89
|
+
assert('wildcard: fires on other child', wildcardFires === 2);
|
|
90
|
+
|
|
91
|
+
s3.destroy();
|
|
92
|
+
|
|
93
|
+
console.log('\n4. subscribe: global');
|
|
94
|
+
const s4 = createEventState({ a: 1, b: { c: 2 } });
|
|
95
|
+
let globalFires = 0;
|
|
96
|
+
|
|
97
|
+
s4.subscribe('*', () => { globalFires++; });
|
|
98
|
+
|
|
99
|
+
s4.set('a', 10);
|
|
100
|
+
assert('global: fires on root path', globalFires === 1);
|
|
101
|
+
|
|
102
|
+
s4.set('b.c', 20);
|
|
103
|
+
assert('global: fires on nested path', globalFires === 2);
|
|
104
|
+
|
|
105
|
+
s4.destroy();
|
|
106
|
+
|
|
107
|
+
console.log('\n5. batch');
|
|
108
|
+
const s5 = createEventState({ x: 0, y: 0 });
|
|
109
|
+
let batchFires = 0;
|
|
110
|
+
s5.subscribe('*', () => { batchFires++; });
|
|
111
|
+
|
|
112
|
+
s5.batch(() => {
|
|
113
|
+
s5.set('x', 1);
|
|
114
|
+
s5.set('y', 2);
|
|
115
|
+
});
|
|
116
|
+
assert('batch: fires after (not during)', batchFires === 2);
|
|
117
|
+
assert('batch: x set', s5.get('x') === 1);
|
|
118
|
+
assert('batch: y set', s5.get('y') === 2);
|
|
119
|
+
|
|
120
|
+
// Deduplication
|
|
121
|
+
batchFires = 0;
|
|
122
|
+
s5.batch(() => {
|
|
123
|
+
s5.set('x', 10);
|
|
124
|
+
s5.set('x', 20);
|
|
125
|
+
s5.set('x', 30);
|
|
126
|
+
});
|
|
127
|
+
assert('batch: deduplicates same path (1 fire)', batchFires === 1);
|
|
128
|
+
assert('batch: last write wins', s5.get('x') === 30);
|
|
129
|
+
|
|
130
|
+
// Nested batch
|
|
131
|
+
batchFires = 0;
|
|
132
|
+
s5.batch(() => {
|
|
133
|
+
s5.set('x', 100);
|
|
134
|
+
s5.batch(() => {
|
|
135
|
+
s5.set('y', 200);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
assert('nested batch: both fire after outermost', batchFires === 2);
|
|
139
|
+
assert('nested batch: x', s5.get('x') === 100);
|
|
140
|
+
assert('nested batch: y', s5.get('y') === 200);
|
|
141
|
+
|
|
142
|
+
// No notifications during batch
|
|
143
|
+
const seen = [];
|
|
144
|
+
const s5b = createEventState({ v: 0 });
|
|
145
|
+
s5b.subscribe('v', (val) => seen.push(val));
|
|
146
|
+
s5b.batch(() => {
|
|
147
|
+
s5b.set('v', 1);
|
|
148
|
+
s5b.set('v', 2);
|
|
149
|
+
});
|
|
150
|
+
assert('batch: no mid-batch notifications', seen.length === 1);
|
|
151
|
+
assert('batch: final value delivered', seen[0] === 2);
|
|
152
|
+
|
|
153
|
+
s5.destroy();
|
|
154
|
+
s5b.destroy();
|
|
155
|
+
|
|
156
|
+
console.log('\n6. setMany');
|
|
157
|
+
const s6 = createEventState({});
|
|
158
|
+
|
|
159
|
+
// Plain object
|
|
160
|
+
s6.setMany({ 'a.b': 1, 'a.c': 2 });
|
|
161
|
+
assert('setMany object: a.b', s6.get('a.b') === 1);
|
|
162
|
+
assert('setMany object: a.c', s6.get('a.c') === 2);
|
|
163
|
+
|
|
164
|
+
// Array of pairs
|
|
165
|
+
s6.setMany([['x.y', 'hello'], ['x.z', 'world']]);
|
|
166
|
+
assert('setMany array: x.y', s6.get('x.y') === 'hello');
|
|
167
|
+
assert('setMany array: x.z', s6.get('x.z') === 'world');
|
|
168
|
+
|
|
169
|
+
// Map
|
|
170
|
+
s6.setMany(new Map([['m.a', true], ['m.b', false]]));
|
|
171
|
+
assert('setMany Map: m.a', s6.get('m.a') === true);
|
|
172
|
+
assert('setMany Map: m.b', s6.get('m.b') === false);
|
|
173
|
+
|
|
174
|
+
s6.destroy();
|
|
175
|
+
|
|
176
|
+
console.log('\n7. destroy');
|
|
177
|
+
const s7 = createEventState({ z: 0 });
|
|
178
|
+
s7.destroy();
|
|
179
|
+
|
|
180
|
+
let threw = false;
|
|
181
|
+
try { s7.get('z'); } catch { threw = true; }
|
|
182
|
+
assert('destroy: get throws', threw);
|
|
183
|
+
|
|
184
|
+
threw = false;
|
|
185
|
+
try { s7.set('z', 1); } catch { threw = true; }
|
|
186
|
+
assert('destroy: set throws', threw);
|
|
187
|
+
|
|
188
|
+
threw = false;
|
|
189
|
+
try { s7.batch(() => {}); } catch { threw = true; }
|
|
190
|
+
assert('destroy: batch throws', threw);
|
|
191
|
+
|
|
192
|
+
threw = false;
|
|
193
|
+
try { s7.setMany({ a: 1 }); } catch { threw = true; }
|
|
194
|
+
assert('destroy: setMany throws', threw);
|
|
195
|
+
|
|
196
|
+
threw = false;
|
|
197
|
+
try { s7.subscribe('z', () => {}); } catch { threw = true; }
|
|
198
|
+
assert('destroy: subscribe throws', threw);
|
|
199
|
+
|
|
200
|
+
console.log('\n8. subscribe: detail object');
|
|
201
|
+
const s8 = createEventState({ count: 10 });
|
|
202
|
+
let detail = null;
|
|
203
|
+
s8.subscribe('count', (value, d) => { detail = d; });
|
|
204
|
+
s8.set('count', 20);
|
|
205
|
+
assert('detail: has path', detail?.path === 'count');
|
|
206
|
+
assert('detail: has value', detail?.value === 20);
|
|
207
|
+
assert('detail: has oldValue', detail?.oldValue === 10);
|
|
208
|
+
|
|
209
|
+
s8.destroy();
|
|
210
|
+
|
|
211
|
+
// Results
|
|
212
|
+
|
|
213
|
+
console.log(`\n@uistate/core v5.6.1 — self-test`);
|
|
214
|
+
console.log(`✓ ${passed} assertions passed${failed ? `, ✗ ${failed} failed` : ''}\n`);
|
|
215
|
+
|
|
216
|
+
if (failed > 0) process.exit(1);
|