@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/index.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
|
}
|
|
@@ -545,147 +901,199 @@ var globalStateManager = {
|
|
|
545
901
|
return createState();
|
|
546
902
|
}
|
|
547
903
|
};
|
|
548
|
-
var
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
904
|
+
var EMPTY_SCOPE = /* @__PURE__ */ new Map();
|
|
905
|
+
function createAsyncStorage() {
|
|
906
|
+
try {
|
|
907
|
+
const asyncHooks = globalThis.process?.getBuiltinModule?.("node:async_hooks");
|
|
908
|
+
const AsyncLocalStorage = asyncHooks?.AsyncLocalStorage;
|
|
909
|
+
return typeof AsyncLocalStorage === "function" ? new AsyncLocalStorage() : null;
|
|
910
|
+
} catch {
|
|
911
|
+
return null;
|
|
552
912
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
913
|
+
}
|
|
914
|
+
var asyncStorage = createAsyncStorage();
|
|
915
|
+
var syncHolder = { scope: EMPTY_SCOPE, owned: true };
|
|
916
|
+
function currentHolder() {
|
|
917
|
+
return asyncStorage ? asyncStorage.getStore() : syncHolder;
|
|
918
|
+
}
|
|
919
|
+
function currentScope() {
|
|
920
|
+
return currentHolder()?.scope ?? EMPTY_SCOPE;
|
|
921
|
+
}
|
|
922
|
+
function setScope(scope, holder = currentHolder()) {
|
|
923
|
+
if (!holder?.owned) {
|
|
924
|
+
throw new Error(
|
|
925
|
+
"Context can only be provided inside runWithContext() on the server: outside it the value would leak into other requests. Wrap each request or render: runWithContext(() => ...)."
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
holder.scope = scope;
|
|
929
|
+
}
|
|
930
|
+
function runInScope(scope, fn, args) {
|
|
931
|
+
if (asyncStorage) {
|
|
932
|
+
return asyncStorage.run({ scope, owned: true }, fn, ...args);
|
|
933
|
+
}
|
|
934
|
+
const previous = syncHolder.scope;
|
|
935
|
+
syncHolder.scope = scope;
|
|
936
|
+
try {
|
|
937
|
+
return fn(...args);
|
|
938
|
+
} finally {
|
|
939
|
+
syncHolder.scope = previous;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function withValue(scope, key, value) {
|
|
943
|
+
const next = new Map(scope);
|
|
944
|
+
next.set(key, { value, previous: scope.get(key) });
|
|
945
|
+
return next;
|
|
946
|
+
}
|
|
947
|
+
function runWithContext(fn, values) {
|
|
948
|
+
if (typeof fn !== "function") {
|
|
949
|
+
throw new TypeError(`runWithContext() requires a function, received: ${typeof fn}`);
|
|
950
|
+
}
|
|
951
|
+
let scope = EMPTY_SCOPE;
|
|
952
|
+
if (values && typeof values === "object") {
|
|
953
|
+
for (const [key, value] of Object.entries(values)) {
|
|
954
|
+
scope = withValue(scope, key, value);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return runInScope(scope, fn, []);
|
|
958
|
+
}
|
|
959
|
+
function provideContext(key, value) {
|
|
960
|
+
setScope(withValue(currentScope(), key, value));
|
|
557
961
|
}
|
|
558
962
|
function createContextProvider(key, value, children) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
} else {
|
|
565
|
-
return children;
|
|
566
|
-
}
|
|
567
|
-
} finally {
|
|
568
|
-
restoreContext(key);
|
|
963
|
+
function contextProvider(...args) {
|
|
964
|
+
const renderFunction = args[0];
|
|
965
|
+
const scope = withValue(currentScope(), key, value);
|
|
966
|
+
if (typeof renderFunction === "function") {
|
|
967
|
+
return runInScope(scope, renderFunction, [children]);
|
|
569
968
|
}
|
|
969
|
+
return runInScope(scope, resolveComponents, [children]);
|
|
970
|
+
}
|
|
971
|
+
return contextProvider;
|
|
972
|
+
}
|
|
973
|
+
var TAG_NAME = /^[a-zA-Z][a-zA-Z0-9-]*$/;
|
|
974
|
+
function resolveComponents(node, depth = 0) {
|
|
975
|
+
if (depth > 1e3) return node;
|
|
976
|
+
if (typeof node === "function") {
|
|
977
|
+
return resolveComponents(node(), depth + 1);
|
|
978
|
+
}
|
|
979
|
+
if (Array.isArray(node)) {
|
|
980
|
+
let changed2 = false;
|
|
981
|
+
const resolved2 = node.map((child) => {
|
|
982
|
+
const next = resolveComponents(child, depth + 1);
|
|
983
|
+
if (next !== child) changed2 = true;
|
|
984
|
+
return next;
|
|
985
|
+
});
|
|
986
|
+
return changed2 ? resolved2 : node;
|
|
987
|
+
}
|
|
988
|
+
if (!node || typeof node !== "object") return node;
|
|
989
|
+
if (node.__isLazy === true && typeof node.evaluate === "function") {
|
|
990
|
+
return resolveComponents(node.evaluate(), depth + 1);
|
|
991
|
+
}
|
|
992
|
+
const tags = Object.keys(node);
|
|
993
|
+
if (tags.length === 0 || !tags.every((tag) => TAG_NAME.test(tag))) return node;
|
|
994
|
+
let changed = false;
|
|
995
|
+
const resolved = {};
|
|
996
|
+
for (const tag of tags) {
|
|
997
|
+
let content = node[tag];
|
|
998
|
+
if (typeof content === "function") {
|
|
999
|
+
content = resolveComponents(content(), depth + 1);
|
|
1000
|
+
}
|
|
1001
|
+
if (content && typeof content === "object" && !Array.isArray(content) && !isTrusted(content)) {
|
|
1002
|
+
content = resolveProps(content, depth);
|
|
1003
|
+
}
|
|
1004
|
+
if (content !== node[tag]) changed = true;
|
|
1005
|
+
resolved[tag] = content;
|
|
1006
|
+
}
|
|
1007
|
+
return changed ? resolved : node;
|
|
1008
|
+
}
|
|
1009
|
+
function isTrusted(value) {
|
|
1010
|
+
return value[/* @__PURE__ */ Symbol.for("coherent.js.trustedContent")] === true;
|
|
1011
|
+
}
|
|
1012
|
+
function resolveProps(props, depth) {
|
|
1013
|
+
let next = props;
|
|
1014
|
+
const set = (key, value) => {
|
|
1015
|
+
if (next === props) next = { ...props };
|
|
1016
|
+
next[key] = value;
|
|
570
1017
|
};
|
|
1018
|
+
for (const key of Object.keys(props)) {
|
|
1019
|
+
const value = props[key];
|
|
1020
|
+
if (key === "children") {
|
|
1021
|
+
if (value !== void 0 && value !== null) {
|
|
1022
|
+
const children = resolveComponents(value, depth + 1);
|
|
1023
|
+
if (children !== value) set(key, children);
|
|
1024
|
+
}
|
|
1025
|
+
} else if (typeof value === "function" && key !== "key") {
|
|
1026
|
+
if (key === "text" || key === "html") {
|
|
1027
|
+
set(key, value());
|
|
1028
|
+
} else if (!key.startsWith("on")) {
|
|
1029
|
+
try {
|
|
1030
|
+
set(key, value());
|
|
1031
|
+
} catch {
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return next;
|
|
571
1037
|
}
|
|
572
1038
|
function restoreContext(key) {
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
} else {
|
|
580
|
-
globalState.set(key, previousValue);
|
|
581
|
-
}
|
|
582
|
-
contextStacks.delete(key);
|
|
1039
|
+
const scope = currentScope();
|
|
1040
|
+
const entry = scope.get(key);
|
|
1041
|
+
if (!entry) return;
|
|
1042
|
+
const next = new Map(scope);
|
|
1043
|
+
if (entry.previous) {
|
|
1044
|
+
next.set(key, entry.previous);
|
|
583
1045
|
} else {
|
|
584
|
-
|
|
1046
|
+
next.delete(key);
|
|
585
1047
|
}
|
|
1048
|
+
setScope(next);
|
|
586
1049
|
}
|
|
587
1050
|
function clearAllContexts() {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if (beforeFirstProvide === void 0) {
|
|
591
|
-
globalState.delete(key);
|
|
592
|
-
} else {
|
|
593
|
-
globalState.set(key, beforeFirstProvide);
|
|
594
|
-
}
|
|
1051
|
+
if (currentScope().size > 0) {
|
|
1052
|
+
setScope(EMPTY_SCOPE);
|
|
595
1053
|
}
|
|
596
|
-
contextStacks.clear();
|
|
597
1054
|
}
|
|
598
1055
|
function useContext(key) {
|
|
599
|
-
|
|
1056
|
+
const entry = currentScope().get(key);
|
|
1057
|
+
return entry ? entry.value : globalState.get(key);
|
|
600
1058
|
}
|
|
601
1059
|
|
|
602
1060
|
// src/state-persistence.js
|
|
603
|
-
var
|
|
604
|
-
constructor() {
|
|
605
|
-
this.
|
|
1061
|
+
var WebStorageAdapter = class {
|
|
1062
|
+
constructor(storageName) {
|
|
1063
|
+
this.storageName = storageName;
|
|
1064
|
+
this.available = typeof globalThis[storageName] !== "undefined" && globalThis[storageName] !== null;
|
|
1065
|
+
}
|
|
1066
|
+
get storage() {
|
|
1067
|
+
return globalThis[this.storageName];
|
|
606
1068
|
}
|
|
607
1069
|
async get(key) {
|
|
608
1070
|
if (!this.available) return null;
|
|
609
|
-
|
|
610
|
-
return localStorage.getItem(key);
|
|
611
|
-
} catch (error) {
|
|
612
|
-
console.error("LocalStorage get error:", error);
|
|
613
|
-
return null;
|
|
614
|
-
}
|
|
1071
|
+
return this.storage.getItem(key);
|
|
615
1072
|
}
|
|
616
1073
|
async set(key, value) {
|
|
617
1074
|
if (!this.available) return false;
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
return true;
|
|
621
|
-
} catch (error) {
|
|
622
|
-
console.error("LocalStorage set error:", error);
|
|
623
|
-
return false;
|
|
624
|
-
}
|
|
1075
|
+
this.storage.setItem(key, value);
|
|
1076
|
+
return true;
|
|
625
1077
|
}
|
|
626
1078
|
async remove(key) {
|
|
627
1079
|
if (!this.available) return false;
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
return true;
|
|
631
|
-
} catch (error) {
|
|
632
|
-
console.error("LocalStorage remove error:", error);
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
1080
|
+
this.storage.removeItem(key);
|
|
1081
|
+
return true;
|
|
635
1082
|
}
|
|
636
1083
|
async clear() {
|
|
637
1084
|
if (!this.available) return false;
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
return true;
|
|
641
|
-
} catch (error) {
|
|
642
|
-
console.error("LocalStorage clear error:", error);
|
|
643
|
-
return false;
|
|
644
|
-
}
|
|
1085
|
+
this.storage.clear();
|
|
1086
|
+
return true;
|
|
645
1087
|
}
|
|
646
1088
|
};
|
|
647
|
-
var
|
|
1089
|
+
var LocalStorageAdapter = class extends WebStorageAdapter {
|
|
648
1090
|
constructor() {
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
async get(key) {
|
|
652
|
-
if (!this.available) return null;
|
|
653
|
-
try {
|
|
654
|
-
return sessionStorage.getItem(key);
|
|
655
|
-
} catch (error) {
|
|
656
|
-
console.error("SessionStorage get error:", error);
|
|
657
|
-
return null;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
async set(key, value) {
|
|
661
|
-
if (!this.available) return false;
|
|
662
|
-
try {
|
|
663
|
-
sessionStorage.setItem(key, value);
|
|
664
|
-
return true;
|
|
665
|
-
} catch (error) {
|
|
666
|
-
console.error("SessionStorage set error:", error);
|
|
667
|
-
return false;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
async remove(key) {
|
|
671
|
-
if (!this.available) return false;
|
|
672
|
-
try {
|
|
673
|
-
sessionStorage.removeItem(key);
|
|
674
|
-
return true;
|
|
675
|
-
} catch (error) {
|
|
676
|
-
console.error("SessionStorage remove error:", error);
|
|
677
|
-
return false;
|
|
678
|
-
}
|
|
1091
|
+
super("localStorage");
|
|
679
1092
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
return true;
|
|
685
|
-
} catch (error) {
|
|
686
|
-
console.error("SessionStorage clear error:", error);
|
|
687
|
-
return false;
|
|
688
|
-
}
|
|
1093
|
+
};
|
|
1094
|
+
var SessionStorageAdapter = class extends WebStorageAdapter {
|
|
1095
|
+
constructor() {
|
|
1096
|
+
super("sessionStorage");
|
|
689
1097
|
}
|
|
690
1098
|
};
|
|
691
1099
|
var IndexedDBAdapter = class {
|
|
@@ -694,19 +1102,21 @@ var IndexedDBAdapter = class {
|
|
|
694
1102
|
this.storeName = storeName;
|
|
695
1103
|
this.available = typeof indexedDB !== "undefined";
|
|
696
1104
|
this.db = null;
|
|
1105
|
+
this.opening = null;
|
|
697
1106
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1107
|
+
/**
|
|
1108
|
+
* Open the database, creating the store in an upgrade. Without `version`,
|
|
1109
|
+
* opens the current version (creating version 1 for a new database).
|
|
1110
|
+
*/
|
|
1111
|
+
open(version) {
|
|
701
1112
|
return new Promise((resolve, reject) => {
|
|
702
|
-
const request = indexedDB.open(this.dbName,
|
|
1113
|
+
const request = version === void 0 ? indexedDB.open(this.dbName) : indexedDB.open(this.dbName, version);
|
|
703
1114
|
request.onerror = () => {
|
|
704
1115
|
console.error("IndexedDB open error:", request.error);
|
|
705
1116
|
reject(request.error);
|
|
706
1117
|
};
|
|
707
1118
|
request.onsuccess = () => {
|
|
708
|
-
|
|
709
|
-
resolve(true);
|
|
1119
|
+
resolve(request.result);
|
|
710
1120
|
};
|
|
711
1121
|
request.onupgradeneeded = (event) => {
|
|
712
1122
|
const db = event.target.result;
|
|
@@ -716,6 +1126,27 @@ var IndexedDBAdapter = class {
|
|
|
716
1126
|
};
|
|
717
1127
|
});
|
|
718
1128
|
}
|
|
1129
|
+
async init() {
|
|
1130
|
+
if (!this.available) return false;
|
|
1131
|
+
if (this.db) return true;
|
|
1132
|
+
this.opening ??= (async () => {
|
|
1133
|
+
let db = await this.open();
|
|
1134
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
1135
|
+
const version = db.version + 1;
|
|
1136
|
+
db.close();
|
|
1137
|
+
db = await this.open(version);
|
|
1138
|
+
}
|
|
1139
|
+
db.onversionchange = () => {
|
|
1140
|
+
db.close();
|
|
1141
|
+
if (this.db === db) this.db = null;
|
|
1142
|
+
};
|
|
1143
|
+
this.db = db;
|
|
1144
|
+
return true;
|
|
1145
|
+
})().finally(() => {
|
|
1146
|
+
this.opening = null;
|
|
1147
|
+
});
|
|
1148
|
+
return this.opening;
|
|
1149
|
+
}
|
|
719
1150
|
async get(key) {
|
|
720
1151
|
if (!this.available) return null;
|
|
721
1152
|
await this.init();
|
|
@@ -801,47 +1232,85 @@ var MemoryAdapter = class {
|
|
|
801
1232
|
return true;
|
|
802
1233
|
}
|
|
803
1234
|
};
|
|
804
|
-
var
|
|
805
|
-
constructor(
|
|
806
|
-
this.
|
|
1235
|
+
var ServerAdapter = class {
|
|
1236
|
+
constructor() {
|
|
1237
|
+
this.available = false;
|
|
1238
|
+
}
|
|
1239
|
+
async get() {
|
|
1240
|
+
return null;
|
|
807
1241
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1242
|
+
async set() {
|
|
1243
|
+
return false;
|
|
1244
|
+
}
|
|
1245
|
+
async remove() {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
async clear() {
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
function toBase64(bytes) {
|
|
1253
|
+
let binary = "";
|
|
1254
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
1255
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
1256
|
+
}
|
|
1257
|
+
return btoa(binary);
|
|
1258
|
+
}
|
|
1259
|
+
function fromBase64(encoded) {
|
|
1260
|
+
const binary = atob(encoded);
|
|
1261
|
+
const bytes = new Uint8Array(binary.length);
|
|
1262
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1263
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1264
|
+
}
|
|
1265
|
+
return bytes;
|
|
1266
|
+
}
|
|
1267
|
+
var XorObfuscation = class {
|
|
1268
|
+
constructor(key) {
|
|
1269
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
1270
|
+
throw new TypeError(
|
|
1271
|
+
"createPersistentState: `encrypt: true` requires a non-empty `encryptionKey`. It is XOR obfuscation, not encryption; there is no default key."
|
|
813
1272
|
);
|
|
814
1273
|
}
|
|
815
|
-
|
|
1274
|
+
this.keyBytes = new globalThis.TextEncoder().encode(key);
|
|
816
1275
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
for (let i = 0; i < text.length; i++) {
|
|
821
|
-
result += String.fromCharCode(
|
|
822
|
-
text.charCodeAt(i) ^ this.key.charCodeAt(i % this.key.length)
|
|
823
|
-
);
|
|
1276
|
+
xor(bytes) {
|
|
1277
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1278
|
+
bytes[i] ^= this.keyBytes[i % this.keyBytes.length];
|
|
824
1279
|
}
|
|
825
|
-
return
|
|
1280
|
+
return bytes;
|
|
1281
|
+
}
|
|
1282
|
+
encode(text) {
|
|
1283
|
+
return toBase64(this.xor(new globalThis.TextEncoder().encode(text)));
|
|
1284
|
+
}
|
|
1285
|
+
decode(encoded) {
|
|
1286
|
+
return new globalThis.TextDecoder().decode(this.xor(fromBase64(encoded)));
|
|
826
1287
|
}
|
|
827
1288
|
};
|
|
828
|
-
function createStorageAdapter(type) {
|
|
1289
|
+
function createStorageAdapter(type, options = {}) {
|
|
829
1290
|
switch (type) {
|
|
830
1291
|
case "localStorage":
|
|
831
1292
|
return new LocalStorageAdapter();
|
|
832
1293
|
case "sessionStorage":
|
|
833
1294
|
return new SessionStorageAdapter();
|
|
834
1295
|
case "indexedDB":
|
|
835
|
-
return new IndexedDBAdapter();
|
|
1296
|
+
return new IndexedDBAdapter(options.dbName ?? void 0, options.storeName ?? void 0);
|
|
836
1297
|
case "memory":
|
|
837
1298
|
return new MemoryAdapter();
|
|
838
1299
|
default:
|
|
839
1300
|
return new LocalStorageAdapter();
|
|
840
1301
|
}
|
|
841
1302
|
}
|
|
1303
|
+
function createInstanceId() {
|
|
1304
|
+
const cryptoApi = globalThis.crypto;
|
|
1305
|
+
if (cryptoApi && typeof cryptoApi.randomUUID === "function") {
|
|
1306
|
+
return cryptoApi.randomUUID();
|
|
1307
|
+
}
|
|
1308
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
1309
|
+
}
|
|
842
1310
|
function createPersistentState(initialState = {}, options = {}) {
|
|
843
1311
|
const opts = {
|
|
844
1312
|
storage: "localStorage",
|
|
1313
|
+
adapter: null,
|
|
845
1314
|
key: "coherent-state",
|
|
846
1315
|
debounce: true,
|
|
847
1316
|
debounceDelay: 300,
|
|
@@ -861,11 +1330,30 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
861
1330
|
crossTab: false,
|
|
862
1331
|
...options
|
|
863
1332
|
};
|
|
864
|
-
const
|
|
865
|
-
const
|
|
1333
|
+
const onServer = typeof window === "undefined";
|
|
1334
|
+
const obfuscation = opts.encrypt ? new XorObfuscation(opts.encryptionKey) : null;
|
|
1335
|
+
let adapter;
|
|
1336
|
+
if (opts.adapter) {
|
|
1337
|
+
adapter = opts.adapter;
|
|
1338
|
+
} else if (onServer && opts.storage !== "memory") {
|
|
1339
|
+
adapter = new ServerAdapter();
|
|
1340
|
+
} else {
|
|
1341
|
+
adapter = createStorageAdapter(opts.storage, opts);
|
|
1342
|
+
}
|
|
1343
|
+
const instanceId = createInstanceId();
|
|
866
1344
|
let state = { ...initialState };
|
|
867
1345
|
let saveTimeout = null;
|
|
1346
|
+
let destroyed = false;
|
|
868
1347
|
const listeners = /* @__PURE__ */ new Set();
|
|
1348
|
+
let initialRestorePending = false;
|
|
1349
|
+
const touchedKeys = /* @__PURE__ */ new Set();
|
|
1350
|
+
function reportError2(error) {
|
|
1351
|
+
if (opts.onError) {
|
|
1352
|
+
opts.onError(error);
|
|
1353
|
+
} else {
|
|
1354
|
+
console.error("State persistence error:", error);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
869
1357
|
function filterKeys(obj) {
|
|
870
1358
|
if (!obj || typeof obj !== "object") return obj;
|
|
871
1359
|
if (opts.include && Array.isArray(opts.include)) {
|
|
@@ -886,11 +1374,31 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
886
1374
|
}
|
|
887
1375
|
return obj;
|
|
888
1376
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1377
|
+
let channel = null;
|
|
1378
|
+
if (opts.crossTab && !onServer && typeof BroadcastChannel !== "undefined") {
|
|
1379
|
+
channel = new BroadcastChannel(`coherent-state-sync:${opts.key}`);
|
|
1380
|
+
channel.onmessage = (event) => {
|
|
1381
|
+
const message = event.data;
|
|
1382
|
+
if (destroyed || !message || message.type !== "state-update" || message.source === instanceId) {
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
const oldState = { ...state };
|
|
1386
|
+
state = { ...state, ...message.state };
|
|
1387
|
+
notifyListeners(oldState, state);
|
|
1388
|
+
};
|
|
1389
|
+
channel.unref?.();
|
|
1390
|
+
}
|
|
1391
|
+
function broadcast(filteredState) {
|
|
1392
|
+
if (!channel) return;
|
|
1393
|
+
try {
|
|
1394
|
+
channel.postMessage({ type: "state-update", source: instanceId, state: filteredState });
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
reportError2(error);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
async function write() {
|
|
1400
|
+
if (adapter.available === false) {
|
|
1401
|
+
return false;
|
|
894
1402
|
}
|
|
895
1403
|
try {
|
|
896
1404
|
const filteredState = filterKeys(state);
|
|
@@ -902,31 +1410,45 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
902
1410
|
ttl: opts.ttl
|
|
903
1411
|
};
|
|
904
1412
|
let dataString = JSON.stringify(data);
|
|
905
|
-
if (
|
|
906
|
-
dataString =
|
|
1413
|
+
if (obfuscation) {
|
|
1414
|
+
dataString = obfuscation.encode(dataString);
|
|
1415
|
+
}
|
|
1416
|
+
const stored = await adapter.set(opts.key, dataString);
|
|
1417
|
+
if (stored === false) {
|
|
1418
|
+
throw new Error(`State "${opts.key}" could not be written to storage`);
|
|
907
1419
|
}
|
|
908
|
-
await adapter.set(opts.key, dataString);
|
|
909
1420
|
if (opts.onSave) {
|
|
910
1421
|
opts.onSave(filteredState);
|
|
911
1422
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
channel.postMessage({ type: "state-update", state: filteredState });
|
|
915
|
-
channel.close();
|
|
916
|
-
}
|
|
1423
|
+
broadcast(filteredState);
|
|
1424
|
+
return true;
|
|
917
1425
|
} catch (error) {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
opts.onError(error);
|
|
921
|
-
}
|
|
1426
|
+
reportError2(error);
|
|
1427
|
+
return false;
|
|
922
1428
|
}
|
|
923
1429
|
}
|
|
1430
|
+
function save(immediate = false) {
|
|
1431
|
+
if (destroyed) {
|
|
1432
|
+
return Promise.resolve(false);
|
|
1433
|
+
}
|
|
1434
|
+
if (opts.debounce && !immediate) {
|
|
1435
|
+
clearTimeout(saveTimeout);
|
|
1436
|
+
saveTimeout = setTimeout(() => {
|
|
1437
|
+
saveTimeout = null;
|
|
1438
|
+
write();
|
|
1439
|
+
}, opts.debounceDelay);
|
|
1440
|
+
return void 0;
|
|
1441
|
+
}
|
|
1442
|
+
clearTimeout(saveTimeout);
|
|
1443
|
+
saveTimeout = null;
|
|
1444
|
+
return write();
|
|
1445
|
+
}
|
|
924
1446
|
async function load() {
|
|
925
1447
|
try {
|
|
926
1448
|
let dataString = await adapter.get(opts.key);
|
|
927
1449
|
if (!dataString) return null;
|
|
928
|
-
if (
|
|
929
|
-
dataString =
|
|
1450
|
+
if (obfuscation) {
|
|
1451
|
+
dataString = obfuscation.decode(dataString);
|
|
930
1452
|
}
|
|
931
1453
|
const data = JSON.parse(dataString);
|
|
932
1454
|
if (data.ttl && data.timestamp) {
|
|
@@ -949,10 +1471,7 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
949
1471
|
}
|
|
950
1472
|
return loadedState;
|
|
951
1473
|
} catch (error) {
|
|
952
|
-
|
|
953
|
-
if (opts.onError) {
|
|
954
|
-
opts.onError(error);
|
|
955
|
-
}
|
|
1474
|
+
reportError2(error);
|
|
956
1475
|
return null;
|
|
957
1476
|
}
|
|
958
1477
|
}
|
|
@@ -969,6 +1488,20 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
969
1488
|
}
|
|
970
1489
|
});
|
|
971
1490
|
}
|
|
1491
|
+
function applyLoaded(loaded, skipTouched) {
|
|
1492
|
+
if (!loaded || typeof loaded !== "object") {
|
|
1493
|
+
return false;
|
|
1494
|
+
}
|
|
1495
|
+
const updates = Object.fromEntries(
|
|
1496
|
+
Object.entries(loaded).filter(([key]) => !skipTouched || !touchedKeys.has(key))
|
|
1497
|
+
);
|
|
1498
|
+
if (Object.keys(updates).length > 0) {
|
|
1499
|
+
const oldState = { ...state };
|
|
1500
|
+
state = { ...state, ...updates };
|
|
1501
|
+
notifyListeners(oldState, state);
|
|
1502
|
+
}
|
|
1503
|
+
return true;
|
|
1504
|
+
}
|
|
972
1505
|
function getState(key) {
|
|
973
1506
|
return key ? state[key] : { ...state };
|
|
974
1507
|
}
|
|
@@ -977,6 +1510,9 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
977
1510
|
if (typeof updates === "function") {
|
|
978
1511
|
updates = updates(oldState);
|
|
979
1512
|
}
|
|
1513
|
+
if (initialRestorePending && updates && typeof updates === "object") {
|
|
1514
|
+
for (const key of Object.keys(updates)) touchedKeys.add(key);
|
|
1515
|
+
}
|
|
980
1516
|
state = { ...state, ...updates };
|
|
981
1517
|
notifyListeners(oldState, state);
|
|
982
1518
|
if (persist2) {
|
|
@@ -985,6 +1521,10 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
985
1521
|
}
|
|
986
1522
|
function resetState(persist2 = true) {
|
|
987
1523
|
const oldState = { ...state };
|
|
1524
|
+
if (initialRestorePending) {
|
|
1525
|
+
for (const key of Object.keys(oldState)) touchedKeys.add(key);
|
|
1526
|
+
for (const key of Object.keys(initialState)) touchedKeys.add(key);
|
|
1527
|
+
}
|
|
988
1528
|
state = { ...initialState };
|
|
989
1529
|
notifyListeners(oldState, state);
|
|
990
1530
|
if (persist2) {
|
|
@@ -992,33 +1532,44 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
992
1532
|
}
|
|
993
1533
|
}
|
|
994
1534
|
async function clearStorage() {
|
|
995
|
-
|
|
1535
|
+
try {
|
|
1536
|
+
await adapter.remove(opts.key);
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
reportError2(error);
|
|
1539
|
+
}
|
|
996
1540
|
}
|
|
997
1541
|
async function persist() {
|
|
998
|
-
|
|
1542
|
+
return save(true);
|
|
999
1543
|
}
|
|
1000
1544
|
async function restore() {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
channel
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1545
|
+
return applyLoaded(await load(), false);
|
|
1546
|
+
}
|
|
1547
|
+
async function destroy() {
|
|
1548
|
+
if (destroyed) return;
|
|
1549
|
+
const pending = saveTimeout !== null;
|
|
1550
|
+
clearTimeout(saveTimeout);
|
|
1551
|
+
saveTimeout = null;
|
|
1552
|
+
if (pending) {
|
|
1553
|
+
await write();
|
|
1554
|
+
}
|
|
1555
|
+
destroyed = true;
|
|
1556
|
+
channel?.close();
|
|
1557
|
+
channel = null;
|
|
1558
|
+
listeners.clear();
|
|
1559
|
+
}
|
|
1560
|
+
let ready;
|
|
1561
|
+
if (opts.storage !== "memory" || opts.adapter) {
|
|
1562
|
+
initialRestorePending = true;
|
|
1563
|
+
ready = load().then((loaded) => {
|
|
1564
|
+
initialRestorePending = false;
|
|
1565
|
+
if (destroyed) return false;
|
|
1566
|
+
const restored = applyLoaded(loaded, true);
|
|
1567
|
+
if (restored && touchedKeys.size > 0) save();
|
|
1568
|
+
touchedKeys.clear();
|
|
1569
|
+
return restored;
|
|
1570
|
+
});
|
|
1571
|
+
} else {
|
|
1572
|
+
ready = Promise.resolve(false);
|
|
1022
1573
|
}
|
|
1023
1574
|
return {
|
|
1024
1575
|
getState,
|
|
@@ -1030,6 +1581,9 @@ function createPersistentState(initialState = {}, options = {}) {
|
|
|
1030
1581
|
clearStorage,
|
|
1031
1582
|
load,
|
|
1032
1583
|
save: () => save(true),
|
|
1584
|
+
destroy,
|
|
1585
|
+
/** Settles once the automatic restore on creation is done */
|
|
1586
|
+
ready,
|
|
1033
1587
|
get adapter() {
|
|
1034
1588
|
return adapter;
|
|
1035
1589
|
}
|
|
@@ -1063,6 +1617,61 @@ var EMAIL_MAX_LENGTH = 254;
|
|
|
1063
1617
|
function isEmailShaped(value) {
|
|
1064
1618
|
return typeof value === "string" && value.length <= EMAIL_MAX_LENGTH && EMAIL_PATTERN.test(value);
|
|
1065
1619
|
}
|
|
1620
|
+
function matchesType(value, type) {
|
|
1621
|
+
switch (type) {
|
|
1622
|
+
case "array":
|
|
1623
|
+
return Array.isArray(value);
|
|
1624
|
+
case "null":
|
|
1625
|
+
return value === null;
|
|
1626
|
+
case "object":
|
|
1627
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1628
|
+
case "integer":
|
|
1629
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
1630
|
+
case "number":
|
|
1631
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
1632
|
+
default:
|
|
1633
|
+
return typeof value === type;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function describeValue(value) {
|
|
1637
|
+
if (value === null) return "null";
|
|
1638
|
+
if (Array.isArray(value)) return "array";
|
|
1639
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
1640
|
+
return typeof value === "object" ? "object" : String(value);
|
|
1641
|
+
}
|
|
1642
|
+
function coerceTo(value, type) {
|
|
1643
|
+
switch (type) {
|
|
1644
|
+
case "string":
|
|
1645
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1646
|
+
return { ok: !Number.isNaN(value), value: String(value) };
|
|
1647
|
+
}
|
|
1648
|
+
return { ok: false };
|
|
1649
|
+
case "number":
|
|
1650
|
+
case "integer": {
|
|
1651
|
+
let number;
|
|
1652
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
1653
|
+
number = Number(value);
|
|
1654
|
+
} else if (typeof value === "boolean") {
|
|
1655
|
+
number = value ? 1 : 0;
|
|
1656
|
+
} else {
|
|
1657
|
+
return { ok: false };
|
|
1658
|
+
}
|
|
1659
|
+
const valid = type === "integer" ? Number.isInteger(number) : !Number.isNaN(number);
|
|
1660
|
+
return valid ? { ok: true, value: number } : { ok: false };
|
|
1661
|
+
}
|
|
1662
|
+
case "boolean":
|
|
1663
|
+
if (typeof value === "string") {
|
|
1664
|
+
const normalized = value.trim().toLowerCase();
|
|
1665
|
+
if (normalized === "true" || normalized === "1") return { ok: true, value: true };
|
|
1666
|
+
if (normalized === "false" || normalized === "0" || normalized === "") return { ok: true, value: false };
|
|
1667
|
+
return { ok: false };
|
|
1668
|
+
}
|
|
1669
|
+
if (value === 1 || value === 0) return { ok: true, value: value === 1 };
|
|
1670
|
+
return { ok: false };
|
|
1671
|
+
default:
|
|
1672
|
+
return { ok: false };
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1066
1675
|
var SchemaValidator = class {
|
|
1067
1676
|
constructor(schema, options = {}) {
|
|
1068
1677
|
this.schema = schema;
|
|
@@ -1085,10 +1694,7 @@ var SchemaValidator = class {
|
|
|
1085
1694
|
if (schema.type) {
|
|
1086
1695
|
const typeResult = this.validateType(value, schema.type, path);
|
|
1087
1696
|
if (!typeResult.valid) {
|
|
1088
|
-
errors
|
|
1089
|
-
if (!this.options.coerce) {
|
|
1090
|
-
return { valid: false, errors, value };
|
|
1091
|
-
}
|
|
1697
|
+
return { valid: false, errors: typeResult.errors, value };
|
|
1092
1698
|
}
|
|
1093
1699
|
coercedValue = typeResult.value;
|
|
1094
1700
|
}
|
|
@@ -1146,47 +1752,17 @@ var SchemaValidator = class {
|
|
|
1146
1752
|
const errors = [];
|
|
1147
1753
|
let coercedValue = value;
|
|
1148
1754
|
const types = Array.isArray(type) ? type : [type];
|
|
1149
|
-
const isValid = types.some((t) =>
|
|
1150
|
-
if (t === "array") return Array.isArray(value);
|
|
1151
|
-
if (t === "null") return value === null;
|
|
1152
|
-
if (t === "integer") return typeof value === "number" && Number.isInteger(value);
|
|
1153
|
-
return typeof value === t;
|
|
1154
|
-
});
|
|
1755
|
+
const isValid = types.some((t) => matchesType(value, t));
|
|
1155
1756
|
if (!isValid) {
|
|
1156
1757
|
if (this.options.coerce) {
|
|
1157
1758
|
const primaryType = types[0];
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
coercedValue = Number(value);
|
|
1163
|
-
if (isNaN(coercedValue)) {
|
|
1164
|
-
errors.push({
|
|
1165
|
-
path,
|
|
1166
|
-
message: `Cannot coerce "${value}" to number`,
|
|
1167
|
-
type: "type",
|
|
1168
|
-
value,
|
|
1169
|
-
expected: primaryType
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
} else if (primaryType === "boolean") {
|
|
1173
|
-
coercedValue = Boolean(value);
|
|
1174
|
-
} else if (primaryType === "integer") {
|
|
1175
|
-
coercedValue = parseInt(value, 10);
|
|
1176
|
-
if (isNaN(coercedValue)) {
|
|
1177
|
-
errors.push({
|
|
1178
|
-
path,
|
|
1179
|
-
message: `Cannot coerce "${value}" to integer`,
|
|
1180
|
-
type: "type",
|
|
1181
|
-
value,
|
|
1182
|
-
expected: primaryType
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
} catch {
|
|
1759
|
+
const coerced = coerceTo(value, primaryType);
|
|
1760
|
+
if (coerced.ok) {
|
|
1761
|
+
coercedValue = coerced.value;
|
|
1762
|
+
} else {
|
|
1187
1763
|
errors.push({
|
|
1188
1764
|
path,
|
|
1189
|
-
message: `Cannot coerce value to ${primaryType}`,
|
|
1765
|
+
message: `Cannot coerce ${describeValue(value)} to ${primaryType}`,
|
|
1190
1766
|
type: "type",
|
|
1191
1767
|
value,
|
|
1192
1768
|
expected: primaryType
|
|
@@ -1407,7 +1983,7 @@ var SchemaValidator = class {
|
|
|
1407
1983
|
}
|
|
1408
1984
|
});
|
|
1409
1985
|
}
|
|
1410
|
-
if (schema.additionalProperties === false
|
|
1986
|
+
if (schema.additionalProperties === false || !this.options.allowUnknown && schema.properties) {
|
|
1411
1987
|
const allowedProps = new Set(Object.keys(schema.properties || {}));
|
|
1412
1988
|
Object.keys(value).forEach((prop) => {
|
|
1413
1989
|
if (!allowedProps.has(prop)) {
|
|
@@ -1889,7 +2465,16 @@ var ModalState = class {
|
|
|
1889
2465
|
this._currentId = 0;
|
|
1890
2466
|
}
|
|
1891
2467
|
// OOP methods for modal control
|
|
2468
|
+
/**
|
|
2469
|
+
* Open the modal with `data`. Resolves with the value passed to close().
|
|
2470
|
+
* Opening again while open replaces the modal: the earlier promise
|
|
2471
|
+
* resolves with `null`, as if it had been closed without a result.
|
|
2472
|
+
*/
|
|
1892
2473
|
async open(data) {
|
|
2474
|
+
for (const [id, resolver] of this._resolvers) {
|
|
2475
|
+
resolver(null);
|
|
2476
|
+
this._resolvers.delete(id);
|
|
2477
|
+
}
|
|
1893
2478
|
return new Promise((resolve) => {
|
|
1894
2479
|
const id = ++this._currentId;
|
|
1895
2480
|
this._resolvers.set(id, resolve);
|
|
@@ -1991,6 +2576,7 @@ var index_default = {
|
|
|
1991
2576
|
createReactiveState,
|
|
1992
2577
|
observable,
|
|
1993
2578
|
computed,
|
|
2579
|
+
batch,
|
|
1994
2580
|
// SSR-compatible state management
|
|
1995
2581
|
createState,
|
|
1996
2582
|
globalStateManager,
|
|
@@ -1999,6 +2585,7 @@ var index_default = {
|
|
|
1999
2585
|
restoreContext,
|
|
2000
2586
|
clearAllContexts,
|
|
2001
2587
|
useContext,
|
|
2588
|
+
runWithContext,
|
|
2002
2589
|
// Persistence utilities
|
|
2003
2590
|
createPersistentState,
|
|
2004
2591
|
withLocalStorage,
|
|
@@ -2023,6 +2610,7 @@ export {
|
|
|
2023
2610
|
ReactiveState,
|
|
2024
2611
|
RouterState,
|
|
2025
2612
|
StateError,
|
|
2613
|
+
batch,
|
|
2026
2614
|
clearAllContexts,
|
|
2027
2615
|
computed,
|
|
2028
2616
|
createContextProvider,
|
|
@@ -2040,6 +2628,7 @@ export {
|
|
|
2040
2628
|
observable,
|
|
2041
2629
|
provideContext,
|
|
2042
2630
|
restoreContext,
|
|
2631
|
+
runWithContext,
|
|
2043
2632
|
stateUtils,
|
|
2044
2633
|
useContext,
|
|
2045
2634
|
validators,
|