@uistate/core 5.1.0 → 5.2.0
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/eventStateNew.js +81 -12
- package/package.json +1 -1
package/eventStateNew.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* @example
|
|
21
21
|
* const store = createEventState({ count: 0, user: { name: 'Alice' } });
|
|
22
22
|
*
|
|
23
|
-
* // Subscribe to specific path
|
|
23
|
+
* // Subscribe to specific path
|
|
24
24
|
* const unsub = store.subscribe('count', (value) => {
|
|
25
25
|
* console.log('Count changed:', value);
|
|
26
26
|
* });
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
export function createEventState(initial = {}) {
|
|
50
50
|
const state = JSON.parse(JSON.stringify(initial));
|
|
51
51
|
const listeners = new Map();
|
|
52
|
+
const asyncOps = new Map();
|
|
52
53
|
let destroyed = false;
|
|
53
54
|
|
|
54
55
|
return {
|
|
@@ -60,7 +61,13 @@ export function createEventState(initial = {}) {
|
|
|
60
61
|
get(path) {
|
|
61
62
|
if (destroyed) throw new Error('Cannot get from destroyed store');
|
|
62
63
|
if (!path) return state;
|
|
63
|
-
|
|
64
|
+
const parts = path.split('.');
|
|
65
|
+
let cur = state;
|
|
66
|
+
for (const p of parts) {
|
|
67
|
+
if (cur == null) return undefined;
|
|
68
|
+
cur = cur[p];
|
|
69
|
+
}
|
|
70
|
+
return cur;
|
|
64
71
|
},
|
|
65
72
|
|
|
66
73
|
/**
|
|
@@ -89,22 +96,25 @@ export function createEventState(initial = {}) {
|
|
|
89
96
|
if (!destroyed) {
|
|
90
97
|
const detail = { path, value, oldValue };
|
|
91
98
|
|
|
92
|
-
// Notify exact path subscribers
|
|
99
|
+
// Notify exact path subscribers
|
|
93
100
|
const exactListeners = listeners.get(path);
|
|
94
101
|
if (exactListeners) {
|
|
95
|
-
exactListeners.forEach(cb => cb(value));
|
|
102
|
+
exactListeners.forEach(cb => cb(value, detail));
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
// Notify wildcard subscribers for
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
wildcardListeners
|
|
105
|
+
// Notify wildcard subscribers for parent paths
|
|
106
|
+
if (parts.length) {
|
|
107
|
+
let parent = "";
|
|
108
|
+
for (const p of parts) {
|
|
109
|
+
parent = parent ? `${parent}.${p}` : p;
|
|
110
|
+
const wildcardListeners = listeners.get(`${parent}.*`);
|
|
111
|
+
if (wildcardListeners) {
|
|
112
|
+
wildcardListeners.forEach(cb => cb(detail));
|
|
113
|
+
}
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
// Notify global subscribers
|
|
117
|
+
// Notify global subscribers
|
|
108
118
|
const globalListeners = listeners.get('*');
|
|
109
119
|
if (globalListeners) {
|
|
110
120
|
globalListeners.forEach(cb => cb(detail));
|
|
@@ -114,10 +124,67 @@ export function createEventState(initial = {}) {
|
|
|
114
124
|
return value;
|
|
115
125
|
},
|
|
116
126
|
|
|
127
|
+
async setAsync(path, fetcher) {
|
|
128
|
+
if (destroyed) throw new Error('Cannot setAsync on destroyed store');
|
|
129
|
+
if (!path) throw new TypeError('setAsync requires a path');
|
|
130
|
+
if (typeof fetcher !== 'function') {
|
|
131
|
+
throw new TypeError('setAsync(path, fetcher) requires a function fetcher');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (asyncOps.has(path)) {
|
|
135
|
+
asyncOps.get(path).controller.abort();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
asyncOps.set(path, { controller });
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
this.set(`${path}.status`, 'loading');
|
|
143
|
+
this.set(`${path}.error`, null);
|
|
144
|
+
|
|
145
|
+
const data = await fetcher(controller.signal);
|
|
146
|
+
|
|
147
|
+
if (destroyed) throw new Error('Cannot setAsync on destroyed store');
|
|
148
|
+
|
|
149
|
+
this.set(`${path}.data`, data);
|
|
150
|
+
this.set(`${path}.status`, 'success');
|
|
151
|
+
return data;
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (err?.name === 'AbortError') {
|
|
154
|
+
this.set(`${path}.status`, 'cancelled');
|
|
155
|
+
const cancelErr = new Error('Request cancelled');
|
|
156
|
+
cancelErr.name = 'AbortError';
|
|
157
|
+
throw cancelErr;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.set(`${path}.status`, 'error');
|
|
161
|
+
this.set(`${path}.error`, err?.message ?? String(err));
|
|
162
|
+
throw err;
|
|
163
|
+
} finally {
|
|
164
|
+
const op = asyncOps.get(path);
|
|
165
|
+
if (op?.controller === controller) {
|
|
166
|
+
asyncOps.delete(path);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
cancel(path) {
|
|
172
|
+
if (destroyed) throw new Error('Cannot cancel on destroyed store');
|
|
173
|
+
if (!path) throw new TypeError('cancel requires a path');
|
|
174
|
+
|
|
175
|
+
if (asyncOps.has(path)) {
|
|
176
|
+
asyncOps.get(path).controller.abort();
|
|
177
|
+
asyncOps.delete(path);
|
|
178
|
+
this.set(`${path}.status`, 'cancelled');
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
|
|
117
182
|
/**
|
|
118
183
|
* Subscribe to changes at path
|
|
119
184
|
* @param {string} path - Path to subscribe to (supports wildcards: 'user.*', '*')
|
|
120
|
-
* @param {Function} handler - Callback function
|
|
185
|
+
* @param {Function} handler - Callback function.
|
|
186
|
+
* - Exact path subscriptions: (value, meta) => void
|
|
187
|
+
* - Wildcard/global subscriptions: (meta) => void
|
|
121
188
|
* @returns {Function} Unsubscribe function
|
|
122
189
|
*/
|
|
123
190
|
subscribe(path, handler) {
|
|
@@ -140,6 +207,8 @@ export function createEventState(initial = {}) {
|
|
|
140
207
|
destroy() {
|
|
141
208
|
if (!destroyed) {
|
|
142
209
|
destroyed = true;
|
|
210
|
+
asyncOps.forEach(({ controller }) => controller.abort());
|
|
211
|
+
asyncOps.clear();
|
|
143
212
|
listeners.clear();
|
|
144
213
|
}
|
|
145
214
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uistate/core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Lightweight event-driven state management with slot orchestration and experimental event-sequence testing (eventTest.js available under dual license)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|