@coherent.js/state 1.1.2 → 2.0.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/README.md +84 -15
- package/dist/index.js +922 -333
- package/dist/index.js.map +3 -3
- package/dist/reactive-state.js +468 -111
- package/dist/reactive-state.js.map +2 -2
- package/dist/state-manager.js +141 -38
- package/dist/state-manager.js.map +3 -3
- package/dist/state-persistence.js +239 -143
- package/dist/state-persistence.js.map +2 -2
- package/dist/state-validation.js +63 -41
- package/dist/state-validation.js.map +2 -2
- package/package.json +1 -4
- package/types/index.d.ts +143 -36
package/dist/reactive-state.js
CHANGED
|
@@ -14,11 +14,107 @@ var globalErrorHandler = {
|
|
|
14
14
|
console.error("State Error:", error.message, context);
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
-
var
|
|
17
|
+
var MAX_FLUSH_ITERATIONS = 100;
|
|
18
|
+
var activeComputed = null;
|
|
19
|
+
var batchDepth = 0;
|
|
20
|
+
var flushing = false;
|
|
21
|
+
var pendingObservables = /* @__PURE__ */ new Map();
|
|
22
|
+
var pendingComputeds = /* @__PURE__ */ new Set();
|
|
23
|
+
var globalVersion = 0;
|
|
24
|
+
function reportError(source, error, type, context = {}) {
|
|
25
|
+
const onError = source?._options?.onError;
|
|
26
|
+
if (typeof onError === "function") {
|
|
27
|
+
try {
|
|
28
|
+
onError(error, { type, ...context });
|
|
29
|
+
return;
|
|
30
|
+
} catch (handlerError) {
|
|
31
|
+
error = handlerError;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
globalErrorHandler.handle(error, { type, context });
|
|
35
|
+
}
|
|
36
|
+
function runObserver(source, observer, newValue, oldValue) {
|
|
37
|
+
try {
|
|
38
|
+
observer.callback(newValue, oldValue, observer.unwatch);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
reportError(source, error, "watcher-error", { newValue, oldValue });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function flush() {
|
|
44
|
+
flushing = true;
|
|
45
|
+
let iterations = 0;
|
|
46
|
+
try {
|
|
47
|
+
while (pendingObservables.size > 0 || pendingComputeds.size > 0) {
|
|
48
|
+
if (++iterations > MAX_FLUSH_ITERATIONS) {
|
|
49
|
+
const culprits = [...pendingObservables.keys(), ...pendingComputeds];
|
|
50
|
+
pendingObservables.clear();
|
|
51
|
+
pendingComputeds.clear();
|
|
52
|
+
reportError(
|
|
53
|
+
culprits[0],
|
|
54
|
+
new StateError(
|
|
55
|
+
`Watchers kept changing state after ${MAX_FLUSH_ITERATIONS} rounds; a watcher probably writes a new value to a state it (indirectly) watches.`,
|
|
56
|
+
{ type: "update-depth" }
|
|
57
|
+
),
|
|
58
|
+
"update-depth"
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const observables = [...pendingObservables];
|
|
63
|
+
pendingObservables.clear();
|
|
64
|
+
for (const [source, oldValue] of observables) {
|
|
65
|
+
const newValue = source._value;
|
|
66
|
+
if (!source._changed(oldValue, newValue)) continue;
|
|
67
|
+
for (const observer of [...source._observers]) {
|
|
68
|
+
if (source._observers.has(observer)) {
|
|
69
|
+
runObserver(source, observer, newValue, oldValue);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const computeds = [...pendingComputeds];
|
|
74
|
+
pendingComputeds.clear();
|
|
75
|
+
for (const source of computeds) {
|
|
76
|
+
if (source._observers.size === 0) continue;
|
|
77
|
+
try {
|
|
78
|
+
source._refresh();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
reportError(source, error, "computed-error");
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const newValue = source._value;
|
|
84
|
+
const oldValue = source._lastNotified;
|
|
85
|
+
if (Object.is(newValue, oldValue)) continue;
|
|
86
|
+
source._lastNotified = newValue;
|
|
87
|
+
for (const observer of [...source._observers]) {
|
|
88
|
+
if (source._observers.has(observer)) {
|
|
89
|
+
runObserver(source, observer, newValue, oldValue);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} finally {
|
|
95
|
+
flushing = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function scheduleFlush() {
|
|
99
|
+
if (batchDepth === 0 && !flushing) {
|
|
100
|
+
flush();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function batch(fn) {
|
|
104
|
+
batchDepth++;
|
|
105
|
+
try {
|
|
106
|
+
return fn();
|
|
107
|
+
} finally {
|
|
108
|
+
batchDepth--;
|
|
109
|
+
scheduleFlush();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
var Observable = class {
|
|
18
113
|
constructor(value, options = {}) {
|
|
19
114
|
this._value = value;
|
|
115
|
+
this._version = 0;
|
|
20
116
|
this._observers = /* @__PURE__ */ new Set();
|
|
21
|
-
this.
|
|
117
|
+
this._subscribers = /* @__PURE__ */ new Set();
|
|
22
118
|
this._options = {
|
|
23
119
|
deep: options.deep !== false,
|
|
24
120
|
immediate: options.immediate !== false,
|
|
@@ -26,107 +122,281 @@ var Observable = class _Observable {
|
|
|
26
122
|
};
|
|
27
123
|
}
|
|
28
124
|
get value() {
|
|
29
|
-
|
|
30
|
-
this._computedDependents.add(_Observable._currentComputed);
|
|
31
|
-
}
|
|
125
|
+
activeComputed?._track(this);
|
|
32
126
|
return this._value;
|
|
33
127
|
}
|
|
34
128
|
set value(newValue) {
|
|
35
|
-
|
|
129
|
+
this._write(newValue);
|
|
130
|
+
}
|
|
131
|
+
/** Read without registering a dependency. */
|
|
132
|
+
peek() {
|
|
133
|
+
return this._value;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Whether a write from `oldValue` to `newValue` is a change. Identical
|
|
137
|
+
* primitives never are; with `deep` (the default) re-assigning the same
|
|
138
|
+
* object is, since it may have been mutated in place.
|
|
139
|
+
* @private
|
|
140
|
+
*/
|
|
141
|
+
_changed(oldValue, newValue) {
|
|
142
|
+
if (!Object.is(oldValue, newValue)) return true;
|
|
143
|
+
return this._options.deep && newValue !== null && typeof newValue === "object";
|
|
144
|
+
}
|
|
145
|
+
/** @private */
|
|
146
|
+
_write(newValue) {
|
|
147
|
+
const oldValue = this._value;
|
|
148
|
+
if (!this._changed(oldValue, newValue)) {
|
|
36
149
|
return;
|
|
37
150
|
}
|
|
38
|
-
const oldValue = this._value;
|
|
39
151
|
this._value = newValue;
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
152
|
+
this._version++;
|
|
153
|
+
globalVersion++;
|
|
154
|
+
for (const subscriber of [...this._subscribers]) {
|
|
155
|
+
subscriber._markDirty();
|
|
156
|
+
}
|
|
157
|
+
if (this._observers.size > 0 && !pendingObservables.has(this)) {
|
|
158
|
+
pendingObservables.set(this, oldValue);
|
|
159
|
+
}
|
|
160
|
+
scheduleFlush();
|
|
161
|
+
}
|
|
162
|
+
/** @private */
|
|
163
|
+
_addSubscriber(computed2) {
|
|
164
|
+
this._subscribers.add(computed2);
|
|
165
|
+
}
|
|
166
|
+
/** @private */
|
|
167
|
+
_removeSubscriber(computed2) {
|
|
168
|
+
this._subscribers.delete(computed2);
|
|
169
|
+
}
|
|
170
|
+
/** @private */
|
|
171
|
+
_addObserver(observer) {
|
|
172
|
+
this._observers.add(observer);
|
|
173
|
+
}
|
|
174
|
+
/** @private */
|
|
175
|
+
_removeObserver(observer) {
|
|
176
|
+
this._observers.delete(observer);
|
|
53
177
|
}
|
|
54
178
|
watch(callback, options = {}) {
|
|
55
179
|
if (typeof callback !== "function") {
|
|
56
180
|
throw new StateError("Watch callback must be a function");
|
|
57
181
|
}
|
|
58
|
-
const observer =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this._observers.add(observer);
|
|
182
|
+
const observer = { callback, unwatch: null };
|
|
183
|
+
observer.unwatch = () => this._removeObserver(observer);
|
|
184
|
+
this._addObserver(observer);
|
|
62
185
|
if (options.immediate !== false) {
|
|
63
|
-
|
|
186
|
+
runObserver(this, observer, this._value, void 0);
|
|
64
187
|
}
|
|
65
|
-
return
|
|
188
|
+
return observer.unwatch;
|
|
66
189
|
}
|
|
67
|
-
|
|
68
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Remove a watcher, by the callback passed to watch()
|
|
192
|
+
* @param {Function} callback
|
|
193
|
+
*/
|
|
194
|
+
unwatch(callback) {
|
|
195
|
+
for (const observer of [...this._observers]) {
|
|
196
|
+
if (observer.callback === callback || observer.unwatch === callback) {
|
|
197
|
+
this._removeObserver(observer);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
69
200
|
}
|
|
201
|
+
/** Remove every watcher. */
|
|
70
202
|
unwatchAll() {
|
|
71
|
-
this._observers
|
|
72
|
-
|
|
203
|
+
for (const observer of [...this._observers]) {
|
|
204
|
+
this._removeObserver(observer);
|
|
205
|
+
}
|
|
73
206
|
}
|
|
74
207
|
};
|
|
75
|
-
var Computed = class extends Observable {
|
|
208
|
+
var Computed = class _Computed extends Observable {
|
|
76
209
|
constructor(getter, options = {}) {
|
|
77
|
-
super(void 0, options);
|
|
78
|
-
this._getter = getter;
|
|
79
|
-
this._cached = false;
|
|
80
|
-
this._dirty = true;
|
|
81
210
|
if (typeof getter !== "function") {
|
|
82
211
|
throw new StateError("Computed getter must be a function");
|
|
83
212
|
}
|
|
213
|
+
super(void 0, options);
|
|
214
|
+
this._getter = getter;
|
|
215
|
+
this._deps = /* @__PURE__ */ new Map();
|
|
216
|
+
this._dirty = true;
|
|
217
|
+
this._computing = false;
|
|
218
|
+
this._globalVersionSeen = -1;
|
|
219
|
+
this._lastNotified = void 0;
|
|
84
220
|
}
|
|
85
221
|
get value() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
222
|
+
this._refresh();
|
|
223
|
+
activeComputed?._track(this);
|
|
89
224
|
return this._value;
|
|
90
225
|
}
|
|
91
|
-
set value(
|
|
226
|
+
set value(_newValue) {
|
|
92
227
|
throw new StateError("Cannot set value on computed property");
|
|
93
228
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
229
|
+
peek() {
|
|
230
|
+
this._refresh();
|
|
231
|
+
return this._value;
|
|
232
|
+
}
|
|
233
|
+
/** Watched, or a dependency of a live computed. @private */
|
|
234
|
+
get _live() {
|
|
235
|
+
return this._observers.size > 0 || this._subscribers.size > 0;
|
|
236
|
+
}
|
|
237
|
+
/** @private */
|
|
238
|
+
_track(source) {
|
|
239
|
+
if (!this._deps.has(source)) {
|
|
240
|
+
this._deps.set(source, source._version);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** Bring the cached value up to date. @private */
|
|
244
|
+
_refresh() {
|
|
245
|
+
if (this._computing) {
|
|
246
|
+
throw new StateError("Circular dependency between computed properties", {
|
|
247
|
+
type: "computed-cycle",
|
|
248
|
+
context: { getter: this._getter.name || "anonymous" }
|
|
112
249
|
});
|
|
250
|
+
}
|
|
251
|
+
if (!this._dirty) {
|
|
252
|
+
if (this._live || this._globalVersionSeen === globalVersion) return;
|
|
253
|
+
if (!this._dependenciesChanged()) {
|
|
254
|
+
this._globalVersionSeen = globalVersion;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
this._recompute();
|
|
259
|
+
}
|
|
260
|
+
/** @private */
|
|
261
|
+
_dependenciesChanged() {
|
|
262
|
+
for (const [source, version] of this._deps) {
|
|
263
|
+
if (source instanceof _Computed) {
|
|
264
|
+
source._refresh();
|
|
265
|
+
}
|
|
266
|
+
if (source._version !== version) return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
/** @private */
|
|
271
|
+
_recompute() {
|
|
272
|
+
const previousDeps = this._deps;
|
|
273
|
+
const previousActive = activeComputed;
|
|
274
|
+
this._deps = /* @__PURE__ */ new Map();
|
|
275
|
+
this._computing = true;
|
|
276
|
+
activeComputed = this;
|
|
277
|
+
let newValue;
|
|
278
|
+
try {
|
|
279
|
+
newValue = this._getter();
|
|
280
|
+
} catch (error) {
|
|
281
|
+
this._deps = previousDeps;
|
|
282
|
+
this._dirty = true;
|
|
283
|
+
throw error;
|
|
113
284
|
} finally {
|
|
114
|
-
|
|
285
|
+
this._computing = false;
|
|
286
|
+
activeComputed = previousActive;
|
|
287
|
+
}
|
|
288
|
+
if (this._live) {
|
|
289
|
+
for (const source of previousDeps.keys()) {
|
|
290
|
+
if (!this._deps.has(source)) source._removeSubscriber(this);
|
|
291
|
+
}
|
|
292
|
+
for (const source of this._deps.keys()) {
|
|
293
|
+
if (!previousDeps.has(source)) source._addSubscriber(this);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
this._dirty = false;
|
|
297
|
+
this._globalVersionSeen = globalVersion;
|
|
298
|
+
if (!Object.is(newValue, this._value)) {
|
|
299
|
+
this._value = newValue;
|
|
300
|
+
this._version++;
|
|
115
301
|
}
|
|
116
302
|
}
|
|
117
|
-
|
|
303
|
+
/** A dependency changed. @private */
|
|
304
|
+
_markDirty() {
|
|
305
|
+
if (this._dirty) return;
|
|
118
306
|
this._dirty = true;
|
|
119
|
-
this.
|
|
120
|
-
|
|
121
|
-
}
|
|
307
|
+
if (this._observers.size > 0) {
|
|
308
|
+
pendingComputeds.add(this);
|
|
309
|
+
}
|
|
310
|
+
for (const subscriber of [...this._subscribers]) {
|
|
311
|
+
subscriber._markDirty();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** Subscribe to dependencies. @private */
|
|
315
|
+
_goLive() {
|
|
316
|
+
this._refresh();
|
|
317
|
+
for (const source of this._deps.keys()) {
|
|
318
|
+
source._addSubscriber(this);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** Unsubscribe from dependencies. @private */
|
|
322
|
+
_goLazy() {
|
|
323
|
+
for (const source of this._deps.keys()) {
|
|
324
|
+
source._removeSubscriber(this);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/** @private */
|
|
328
|
+
_addSubscriber(computed2) {
|
|
329
|
+
const wasLive = this._live;
|
|
330
|
+
this._subscribers.add(computed2);
|
|
331
|
+
if (!wasLive) this._goLive();
|
|
332
|
+
}
|
|
333
|
+
/** @private */
|
|
334
|
+
_removeSubscriber(computed2) {
|
|
335
|
+
this._subscribers.delete(computed2);
|
|
336
|
+
if (!this._live) this._goLazy();
|
|
337
|
+
}
|
|
338
|
+
/** @private */
|
|
339
|
+
_addObserver(observer) {
|
|
340
|
+
const hadObservers = this._observers.size > 0;
|
|
341
|
+
const wasLive = this._live;
|
|
342
|
+
this._observers.add(observer);
|
|
343
|
+
try {
|
|
344
|
+
if (wasLive) {
|
|
345
|
+
this._refresh();
|
|
346
|
+
} else {
|
|
347
|
+
this._goLive();
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this._observers.delete(observer);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
if (!hadObservers) {
|
|
354
|
+
this._lastNotified = this._value;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/** @private */
|
|
358
|
+
_removeObserver(observer) {
|
|
359
|
+
if (!this._observers.delete(observer)) return;
|
|
360
|
+
if (!this._live) this._goLazy();
|
|
122
361
|
}
|
|
123
362
|
};
|
|
124
|
-
|
|
363
|
+
var ABSENT = /* @__PURE__ */ Symbol("absent");
|
|
364
|
+
function isPath(key) {
|
|
365
|
+
return typeof key === "string" && key.includes(".");
|
|
366
|
+
}
|
|
367
|
+
function readPath(value, segments) {
|
|
368
|
+
let current = value;
|
|
369
|
+
for (const segment of segments) {
|
|
370
|
+
if (current === null || current === void 0) return void 0;
|
|
371
|
+
current = current[segment];
|
|
372
|
+
}
|
|
373
|
+
return current;
|
|
374
|
+
}
|
|
375
|
+
function writePath(target, segments, value) {
|
|
376
|
+
const [head, ...rest] = segments;
|
|
377
|
+
const base = target !== null && typeof target === "object" ? Array.isArray(target) ? [...target] : { ...target } : {};
|
|
378
|
+
const next = rest.length === 0 ? value : writePath(base[head], rest, value);
|
|
379
|
+
Object.defineProperty(base, head, { value: next, enumerable: true, writable: true, configurable: true });
|
|
380
|
+
return base;
|
|
381
|
+
}
|
|
382
|
+
function deletePath(target, segments) {
|
|
383
|
+
if (target === null || typeof target !== "object") return target;
|
|
384
|
+
const [head, ...rest] = segments;
|
|
385
|
+
if (!Object.prototype.hasOwnProperty.call(target, head)) return target;
|
|
386
|
+
const base = Array.isArray(target) ? [...target] : { ...target };
|
|
387
|
+
if (rest.length === 0) {
|
|
388
|
+
delete base[head];
|
|
389
|
+
} else {
|
|
390
|
+
base[head] = deletePath(base[head], rest);
|
|
391
|
+
}
|
|
392
|
+
return base;
|
|
393
|
+
}
|
|
125
394
|
var ReactiveState = class {
|
|
126
395
|
constructor(initialState = {}, options = {}) {
|
|
127
396
|
this._state = /* @__PURE__ */ new Map();
|
|
128
397
|
this._computed = /* @__PURE__ */ new Map();
|
|
129
398
|
this._watchers = /* @__PURE__ */ new Map();
|
|
399
|
+
this._expressionWatchers = /* @__PURE__ */ new Set();
|
|
130
400
|
this._middleware = [];
|
|
131
401
|
this._history = [];
|
|
132
402
|
this._options = {
|
|
@@ -140,57 +410,115 @@ var ReactiveState = class {
|
|
|
140
410
|
this.set(key, value);
|
|
141
411
|
});
|
|
142
412
|
}
|
|
413
|
+
/** Observable for a key, created as an absent placeholder if needed. @private */
|
|
414
|
+
_observable(key) {
|
|
415
|
+
let observable2 = this._state.get(key);
|
|
416
|
+
if (!observable2) {
|
|
417
|
+
observable2 = new Observable(ABSENT, this._options);
|
|
418
|
+
this._state.set(key, observable2);
|
|
419
|
+
}
|
|
420
|
+
return observable2;
|
|
421
|
+
}
|
|
422
|
+
/** Whether a key is stored under its full name. @private */
|
|
423
|
+
_hasOwnKey(key) {
|
|
424
|
+
const observable2 = this._state.get(key);
|
|
425
|
+
return Boolean(observable2) && observable2._value !== ABSENT;
|
|
426
|
+
}
|
|
427
|
+
/** [rootKey, pathSegments] for a dot path, or null for a plain key. @private */
|
|
428
|
+
_splitPath(key) {
|
|
429
|
+
if (!isPath(key)) return null;
|
|
430
|
+
const [root, ...segments] = key.split(".");
|
|
431
|
+
return [root, segments];
|
|
432
|
+
}
|
|
143
433
|
/**
|
|
144
434
|
* Get reactive state value
|
|
145
435
|
*/
|
|
146
436
|
get(key) {
|
|
147
|
-
const
|
|
148
|
-
|
|
437
|
+
const path = this._splitPath(key);
|
|
438
|
+
if (path) {
|
|
439
|
+
return readPath(this.get(path[0]), path[1]);
|
|
440
|
+
}
|
|
441
|
+
const observable2 = activeComputed ? this._observable(key) : this._state.get(key);
|
|
442
|
+
if (!observable2) return void 0;
|
|
443
|
+
const value = observable2.value;
|
|
444
|
+
return value === ABSENT ? void 0 : value;
|
|
149
445
|
}
|
|
150
446
|
/**
|
|
151
447
|
* Set reactive state value
|
|
152
448
|
*/
|
|
153
449
|
set(key, value, options = {}) {
|
|
154
450
|
const config = { ...this._options, ...options };
|
|
451
|
+
const oldValue = this.get(key);
|
|
155
452
|
if (config.enableMiddleware) {
|
|
156
|
-
const middlewareResult = this._runMiddleware("set", { key, value, oldValue
|
|
453
|
+
const middlewareResult = this._runMiddleware("set", { key, value, oldValue });
|
|
157
454
|
if (middlewareResult.cancelled) {
|
|
158
455
|
return false;
|
|
159
456
|
}
|
|
160
457
|
value = middlewareResult.value !== void 0 ? middlewareResult.value : value;
|
|
161
458
|
}
|
|
162
|
-
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
this._state.set(key, observable2);
|
|
166
|
-
} else {
|
|
459
|
+
const path = this._splitPath(key);
|
|
460
|
+
if (path) {
|
|
461
|
+
const [root, segments] = path;
|
|
167
462
|
if (config.enableHistory) {
|
|
168
|
-
this._addToHistory("set", key,
|
|
463
|
+
this._addToHistory("set", key, oldValue, value);
|
|
169
464
|
}
|
|
170
|
-
|
|
465
|
+
this._writeKey(root, writePath(this.get(root), segments, value));
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
if (config.enableHistory && this._hasOwnKey(key)) {
|
|
469
|
+
this._addToHistory("set", key, oldValue, value);
|
|
171
470
|
}
|
|
471
|
+
this._writeKey(key, value);
|
|
172
472
|
return true;
|
|
173
473
|
}
|
|
474
|
+
/** @private */
|
|
475
|
+
_writeKey(key, value) {
|
|
476
|
+
this._observable(key)._write(value);
|
|
477
|
+
}
|
|
174
478
|
/**
|
|
175
479
|
* Check if state has a key
|
|
176
480
|
*/
|
|
177
481
|
has(key) {
|
|
178
|
-
|
|
482
|
+
const path = this._splitPath(key);
|
|
483
|
+
if (path) {
|
|
484
|
+
const parent = readPath(this.get(path[0]), path[1].slice(0, -1));
|
|
485
|
+
return parent !== null && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, path[1][path[1].length - 1]);
|
|
486
|
+
}
|
|
487
|
+
return this._hasOwnKey(key);
|
|
179
488
|
}
|
|
180
489
|
/**
|
|
181
|
-
* Delete state key
|
|
490
|
+
* Delete state key. Its watchers are removed; computed properties that
|
|
491
|
+
* read it update.
|
|
182
492
|
*/
|
|
183
493
|
delete(key) {
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
494
|
+
const path = this._splitPath(key);
|
|
495
|
+
if (path) {
|
|
496
|
+
if (!this.has(key)) return false;
|
|
186
497
|
if (this._options.enableHistory) {
|
|
187
|
-
this._addToHistory("delete", key,
|
|
498
|
+
this._addToHistory("delete", key, this.get(key), void 0);
|
|
188
499
|
}
|
|
189
|
-
|
|
190
|
-
this._state.delete(key);
|
|
500
|
+
this._writeKey(path[0], deletePath(this.get(path[0]), path[1]));
|
|
191
501
|
return true;
|
|
192
502
|
}
|
|
193
|
-
|
|
503
|
+
if (!this._hasOwnKey(key)) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
const observable2 = this._state.get(key);
|
|
507
|
+
if (this._options.enableHistory) {
|
|
508
|
+
this._addToHistory("delete", key, observable2._value, void 0);
|
|
509
|
+
}
|
|
510
|
+
observable2.unwatchAll();
|
|
511
|
+
this._releaseKeyWatchers(key);
|
|
512
|
+
observable2._write(ABSENT);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
/** @private */
|
|
516
|
+
_releaseKeyWatchers(key) {
|
|
517
|
+
const unwatchers = this._watchers.get(key);
|
|
518
|
+
if (unwatchers) {
|
|
519
|
+
for (const unwatch of unwatchers) unwatch();
|
|
520
|
+
this._watchers.delete(key);
|
|
521
|
+
}
|
|
194
522
|
}
|
|
195
523
|
/**
|
|
196
524
|
* Clear all state
|
|
@@ -199,10 +527,13 @@ var ReactiveState = class {
|
|
|
199
527
|
if (this._options.enableHistory) {
|
|
200
528
|
this._addToHistory("clear", null, this.toObject(), {});
|
|
201
529
|
}
|
|
202
|
-
|
|
203
|
-
observable2.
|
|
204
|
-
|
|
205
|
-
|
|
530
|
+
batch(() => {
|
|
531
|
+
for (const [key, observable2] of this._state) {
|
|
532
|
+
observable2.unwatchAll();
|
|
533
|
+
this._releaseKeyWatchers(key);
|
|
534
|
+
observable2._write(ABSENT);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
206
537
|
this._computed.clear();
|
|
207
538
|
this._watchers.clear();
|
|
208
539
|
}
|
|
@@ -225,40 +556,62 @@ var ReactiveState = class {
|
|
|
225
556
|
return computed2 ? computed2.value : void 0;
|
|
226
557
|
}
|
|
227
558
|
/**
|
|
228
|
-
* Watch state changes
|
|
559
|
+
* Watch state changes: a key, a dot path into a key, or a getter
|
|
560
|
+
* expression (re-evaluated whenever what it reads changes).
|
|
229
561
|
*/
|
|
230
562
|
watch(key, callback, options = {}) {
|
|
231
563
|
if (typeof key === "function") {
|
|
232
564
|
return this._watchComputed(key, callback, options);
|
|
233
565
|
}
|
|
234
|
-
const
|
|
235
|
-
if (
|
|
566
|
+
const path = this._splitPath(key);
|
|
567
|
+
if (path) {
|
|
568
|
+
if (!this._hasOwnKey(path[0])) {
|
|
569
|
+
throw new StateError(`Cannot watch undefined state key: ${path[0]}`);
|
|
570
|
+
}
|
|
571
|
+
const computed2 = new Computed(() => this.get(key), { ...this._options, ...options });
|
|
572
|
+
return this._track(key, computed2.watch(callback, options));
|
|
573
|
+
}
|
|
574
|
+
if (!this._hasOwnKey(key)) {
|
|
236
575
|
throw new StateError(`Cannot watch undefined state key: ${key}`);
|
|
237
576
|
}
|
|
238
|
-
|
|
577
|
+
return this._track(key, this._state.get(key).watch(callback, options));
|
|
578
|
+
}
|
|
579
|
+
/** Remember an unwatch function for cleanup. @private */
|
|
580
|
+
_track(key, unwatch) {
|
|
239
581
|
if (!this._watchers.has(key)) {
|
|
240
582
|
this._watchers.set(key, /* @__PURE__ */ new Set());
|
|
241
583
|
}
|
|
242
|
-
this._watchers.get(key)
|
|
243
|
-
|
|
584
|
+
const unwatchers = this._watchers.get(key);
|
|
585
|
+
const release = () => {
|
|
586
|
+
unwatch();
|
|
587
|
+
unwatchers.delete(release);
|
|
588
|
+
};
|
|
589
|
+
unwatchers.add(release);
|
|
590
|
+
return release;
|
|
244
591
|
}
|
|
245
592
|
/**
|
|
246
593
|
* Watch computed expression
|
|
247
594
|
*/
|
|
248
595
|
_watchComputed(expression, callback, options = {}) {
|
|
249
|
-
const computed2 = new Computed(expression, options);
|
|
596
|
+
const computed2 = new Computed(expression, { ...this._options, ...options });
|
|
250
597
|
const unwatch = computed2.watch(callback, options);
|
|
251
|
-
|
|
598
|
+
const release = () => {
|
|
599
|
+
unwatch();
|
|
600
|
+
this._expressionWatchers.delete(release);
|
|
601
|
+
};
|
|
602
|
+
this._expressionWatchers.add(release);
|
|
603
|
+
return release;
|
|
252
604
|
}
|
|
253
605
|
/**
|
|
254
|
-
* Batch state updates
|
|
606
|
+
* Batch state updates: watchers run once, after every update, with the
|
|
607
|
+
* final values.
|
|
255
608
|
*/
|
|
256
609
|
batch(updates) {
|
|
257
610
|
if (typeof updates === "function") {
|
|
258
611
|
const oldEnableHistory = this._options.enableHistory;
|
|
259
612
|
this._options.enableHistory = false;
|
|
260
613
|
try {
|
|
261
|
-
const result = updates(this);
|
|
614
|
+
const result = batch(() => updates(this));
|
|
262
615
|
if (oldEnableHistory) {
|
|
263
616
|
this._addToHistory("batch", null, null, this.toObject());
|
|
264
617
|
}
|
|
@@ -318,11 +671,8 @@ var ReactiveState = class {
|
|
|
318
671
|
break;
|
|
319
672
|
}
|
|
320
673
|
}
|
|
321
|
-
} catch (
|
|
322
|
-
|
|
323
|
-
type: "middleware-_error",
|
|
324
|
-
context: { action, middleware: middleware.toString() }
|
|
325
|
-
});
|
|
674
|
+
} catch (error) {
|
|
675
|
+
reportError(this, error, "middleware-error", { action });
|
|
326
676
|
}
|
|
327
677
|
}
|
|
328
678
|
return result;
|
|
@@ -386,28 +736,28 @@ var ReactiveState = class {
|
|
|
386
736
|
* Convert state to plain object
|
|
387
737
|
*/
|
|
388
738
|
toObject() {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
return result;
|
|
739
|
+
return Object.fromEntries(
|
|
740
|
+
[...this._state].filter(([, observable2]) => observable2._value !== ABSENT).map(([key, observable2]) => [key, observable2._value])
|
|
741
|
+
);
|
|
394
742
|
}
|
|
395
743
|
/**
|
|
396
744
|
* Convert computed properties to object
|
|
397
745
|
*/
|
|
398
746
|
getComputedValues() {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
return result;
|
|
747
|
+
return Object.fromEntries(
|
|
748
|
+
[...this._computed].map(([key, computed2]) => [key, computed2.value])
|
|
749
|
+
);
|
|
404
750
|
}
|
|
405
751
|
/**
|
|
406
752
|
* Get state statistics
|
|
407
753
|
*/
|
|
408
754
|
getStats() {
|
|
755
|
+
let stateKeys = 0;
|
|
756
|
+
for (const observable2 of this._state.values()) {
|
|
757
|
+
if (observable2._value !== ABSENT) stateKeys++;
|
|
758
|
+
}
|
|
409
759
|
return {
|
|
410
|
-
stateKeys
|
|
760
|
+
stateKeys,
|
|
411
761
|
computedKeys: this._computed.size,
|
|
412
762
|
watcherKeys: this._watchers.size,
|
|
413
763
|
historyLength: this._history.length,
|
|
@@ -418,6 +768,12 @@ var ReactiveState = class {
|
|
|
418
768
|
* Cleanup and destroy
|
|
419
769
|
*/
|
|
420
770
|
destroy() {
|
|
771
|
+
for (const key of [...this._watchers.keys()]) {
|
|
772
|
+
this._releaseKeyWatchers(key);
|
|
773
|
+
}
|
|
774
|
+
for (const release of [...this._expressionWatchers]) {
|
|
775
|
+
release();
|
|
776
|
+
}
|
|
421
777
|
for (const observable2 of this._state.values()) {
|
|
422
778
|
observable2.unwatchAll();
|
|
423
779
|
}
|
|
@@ -502,6 +858,7 @@ export {
|
|
|
502
858
|
Observable,
|
|
503
859
|
ReactiveState,
|
|
504
860
|
StateError,
|
|
861
|
+
batch,
|
|
505
862
|
computed,
|
|
506
863
|
createReactiveState,
|
|
507
864
|
reactive_state_default as default,
|