@uistate/core 5.1.0 → 5.1.1
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 +74 -11
- 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 {
|
|
@@ -89,22 +90,25 @@ export function createEventState(initial = {}) {
|
|
|
89
90
|
if (!destroyed) {
|
|
90
91
|
const detail = { path, value, oldValue };
|
|
91
92
|
|
|
92
|
-
// Notify exact path subscribers
|
|
93
|
+
// Notify exact path subscribers
|
|
93
94
|
const exactListeners = listeners.get(path);
|
|
94
95
|
if (exactListeners) {
|
|
95
|
-
exactListeners.forEach(cb => cb(value));
|
|
96
|
+
exactListeners.forEach(cb => cb(value, detail));
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
// Notify wildcard subscribers for
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
wildcardListeners
|
|
99
|
+
// Notify wildcard subscribers for parent paths
|
|
100
|
+
if (parts.length) {
|
|
101
|
+
let parent = "";
|
|
102
|
+
for (const p of parts) {
|
|
103
|
+
parent = parent ? `${parent}.${p}` : p;
|
|
104
|
+
const wildcardListeners = listeners.get(`${parent}.*`);
|
|
105
|
+
if (wildcardListeners) {
|
|
106
|
+
wildcardListeners.forEach(cb => cb(detail));
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
// Notify global subscribers
|
|
111
|
+
// Notify global subscribers
|
|
108
112
|
const globalListeners = listeners.get('*');
|
|
109
113
|
if (globalListeners) {
|
|
110
114
|
globalListeners.forEach(cb => cb(detail));
|
|
@@ -114,10 +118,67 @@ export function createEventState(initial = {}) {
|
|
|
114
118
|
return value;
|
|
115
119
|
},
|
|
116
120
|
|
|
121
|
+
async setAsync(path, fetcher) {
|
|
122
|
+
if (destroyed) throw new Error('Cannot setAsync on destroyed store');
|
|
123
|
+
if (!path) throw new TypeError('setAsync requires a path');
|
|
124
|
+
if (typeof fetcher !== 'function') {
|
|
125
|
+
throw new TypeError('setAsync(path, fetcher) requires a function fetcher');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (asyncOps.has(path)) {
|
|
129
|
+
asyncOps.get(path).controller.abort();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
asyncOps.set(path, { controller });
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
this.set(`${path}.status`, 'loading');
|
|
137
|
+
this.set(`${path}.error`, null);
|
|
138
|
+
|
|
139
|
+
const data = await fetcher(controller.signal);
|
|
140
|
+
|
|
141
|
+
if (destroyed) throw new Error('Cannot setAsync on destroyed store');
|
|
142
|
+
|
|
143
|
+
this.set(path, data);
|
|
144
|
+
this.set(`${path}.status`, 'success');
|
|
145
|
+
return data;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (err?.name === 'AbortError') {
|
|
148
|
+
this.set(`${path}.status`, 'cancelled');
|
|
149
|
+
const cancelErr = new Error('Request cancelled');
|
|
150
|
+
cancelErr.name = 'AbortError';
|
|
151
|
+
throw cancelErr;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.set(`${path}.status`, 'error');
|
|
155
|
+
this.set(`${path}.error`, err?.message ?? String(err));
|
|
156
|
+
throw err;
|
|
157
|
+
} finally {
|
|
158
|
+
const op = asyncOps.get(path);
|
|
159
|
+
if (op?.controller === controller) {
|
|
160
|
+
asyncOps.delete(path);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
cancel(path) {
|
|
166
|
+
if (destroyed) throw new Error('Cannot cancel on destroyed store');
|
|
167
|
+
if (!path) throw new TypeError('cancel requires a path');
|
|
168
|
+
|
|
169
|
+
if (asyncOps.has(path)) {
|
|
170
|
+
asyncOps.get(path).controller.abort();
|
|
171
|
+
asyncOps.delete(path);
|
|
172
|
+
this.set(`${path}.status`, 'cancelled');
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
117
176
|
/**
|
|
118
177
|
* Subscribe to changes at path
|
|
119
178
|
* @param {string} path - Path to subscribe to (supports wildcards: 'user.*', '*')
|
|
120
|
-
* @param {Function} handler - Callback function
|
|
179
|
+
* @param {Function} handler - Callback function.
|
|
180
|
+
* - Exact path subscriptions: (value, meta) => void
|
|
181
|
+
* - Wildcard/global subscriptions: (meta) => void
|
|
121
182
|
* @returns {Function} Unsubscribe function
|
|
122
183
|
*/
|
|
123
184
|
subscribe(path, handler) {
|
|
@@ -140,6 +201,8 @@ export function createEventState(initial = {}) {
|
|
|
140
201
|
destroy() {
|
|
141
202
|
if (!destroyed) {
|
|
142
203
|
destroyed = true;
|
|
204
|
+
asyncOps.forEach(({ controller }) => controller.abort());
|
|
205
|
+
asyncOps.clear();
|
|
143
206
|
listeners.clear();
|
|
144
207
|
}
|
|
145
208
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uistate/core",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.1",
|
|
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",
|