@coherent.js/state 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/index.js +1696 -0
- package/package.json +44 -0
- package/types/index.d.ts +140 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1696 @@
|
|
|
1
|
+
// src/reactive-state.js
|
|
2
|
+
import { globalErrorHandler, StateError } from "@coherent.js/core/src/utils/_error-handler.js";
|
|
3
|
+
var Observable = class _Observable {
|
|
4
|
+
constructor(value, options = {}) {
|
|
5
|
+
this._value = value;
|
|
6
|
+
this._observers = /* @__PURE__ */ new Set();
|
|
7
|
+
this._computedDependents = /* @__PURE__ */ new Set();
|
|
8
|
+
this._options = {
|
|
9
|
+
deep: options.deep !== false,
|
|
10
|
+
immediate: options.immediate !== false,
|
|
11
|
+
...options
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
get value() {
|
|
15
|
+
if (_Observable._currentComputed) {
|
|
16
|
+
this._computedDependents.add(_Observable._currentComputed);
|
|
17
|
+
}
|
|
18
|
+
return this._value;
|
|
19
|
+
}
|
|
20
|
+
set value(newValue) {
|
|
21
|
+
if (this._value === newValue && !this._options.deep) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const oldValue = this._value;
|
|
25
|
+
this._value = newValue;
|
|
26
|
+
this._observers.forEach((observer) => {
|
|
27
|
+
try {
|
|
28
|
+
observer(newValue, oldValue);
|
|
29
|
+
} catch (_error) {
|
|
30
|
+
globalErrorHandler.handle(_error, {
|
|
31
|
+
type: "watcher-_error",
|
|
32
|
+
context: { newValue, oldValue }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
this._computedDependents.forEach((computed2) => {
|
|
37
|
+
computed2._invalidate();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
watch(callback, options = {}) {
|
|
41
|
+
if (typeof callback !== "function") {
|
|
42
|
+
throw new StateError("Watch callback must be a function");
|
|
43
|
+
}
|
|
44
|
+
const observer = (newValue, oldValue) => {
|
|
45
|
+
callback(newValue, oldValue, () => this.unwatch(observer));
|
|
46
|
+
};
|
|
47
|
+
this._observers.add(observer);
|
|
48
|
+
if (options.immediate !== false) {
|
|
49
|
+
observer(this._value, void 0);
|
|
50
|
+
}
|
|
51
|
+
return () => this.unwatch(observer);
|
|
52
|
+
}
|
|
53
|
+
unwatch(observer) {
|
|
54
|
+
this._observers.delete(observer);
|
|
55
|
+
}
|
|
56
|
+
unwatchAll() {
|
|
57
|
+
this._observers.clear();
|
|
58
|
+
this._computedDependents.clear();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var Computed = class extends Observable {
|
|
62
|
+
constructor(getter, options = {}) {
|
|
63
|
+
super(void 0, options);
|
|
64
|
+
this._getter = getter;
|
|
65
|
+
this._cached = false;
|
|
66
|
+
this._dirty = true;
|
|
67
|
+
if (typeof getter !== "function") {
|
|
68
|
+
throw new StateError("Computed getter must be a function");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
get value() {
|
|
72
|
+
if (this._dirty || !this._cached) {
|
|
73
|
+
this._compute();
|
|
74
|
+
}
|
|
75
|
+
return this._value;
|
|
76
|
+
}
|
|
77
|
+
set value(newValue) {
|
|
78
|
+
throw new StateError("Cannot set value on computed property");
|
|
79
|
+
}
|
|
80
|
+
_compute() {
|
|
81
|
+
const prevComputed = Observable._currentComputed;
|
|
82
|
+
Observable._currentComputed = this;
|
|
83
|
+
try {
|
|
84
|
+
const newValue = this._getter();
|
|
85
|
+
if (newValue !== this._value) {
|
|
86
|
+
const oldValue = this._value;
|
|
87
|
+
this._value = newValue;
|
|
88
|
+
this._observers.forEach((observer) => {
|
|
89
|
+
observer(newValue, oldValue);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
this._cached = true;
|
|
93
|
+
this._dirty = false;
|
|
94
|
+
} catch (_error) {
|
|
95
|
+
globalErrorHandler.handle(_error, {
|
|
96
|
+
type: "computed-_error",
|
|
97
|
+
context: { getter: this._getter.toString() }
|
|
98
|
+
});
|
|
99
|
+
} finally {
|
|
100
|
+
Observable._currentComputed = prevComputed;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
_invalidate() {
|
|
104
|
+
this._dirty = true;
|
|
105
|
+
this._computedDependents.forEach((computed2) => {
|
|
106
|
+
computed2._invalidate();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
Observable._currentComputed = null;
|
|
111
|
+
var ReactiveState = class {
|
|
112
|
+
constructor(initialState = {}, options = {}) {
|
|
113
|
+
this._state = /* @__PURE__ */ new Map();
|
|
114
|
+
this._computed = /* @__PURE__ */ new Map();
|
|
115
|
+
this._watchers = /* @__PURE__ */ new Map();
|
|
116
|
+
this._middleware = [];
|
|
117
|
+
this._history = [];
|
|
118
|
+
this._options = {
|
|
119
|
+
enableHistory: options.enableHistory !== false,
|
|
120
|
+
maxHistorySize: options.maxHistorySize || 50,
|
|
121
|
+
enableMiddleware: options.enableMiddleware !== false,
|
|
122
|
+
deep: options.deep !== false,
|
|
123
|
+
...options
|
|
124
|
+
};
|
|
125
|
+
Object.entries(initialState).forEach(([key, value]) => {
|
|
126
|
+
this.set(key, value);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get reactive state value
|
|
131
|
+
*/
|
|
132
|
+
get(key) {
|
|
133
|
+
const observable2 = this._state.get(key);
|
|
134
|
+
return observable2 ? observable2.value : void 0;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Set reactive state value
|
|
138
|
+
*/
|
|
139
|
+
set(key, value, options = {}) {
|
|
140
|
+
const config = { ...this._options, ...options };
|
|
141
|
+
if (config.enableMiddleware) {
|
|
142
|
+
const middlewareResult = this._runMiddleware("set", { key, value, oldValue: this.get(key) });
|
|
143
|
+
if (middlewareResult.cancelled) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
value = middlewareResult.value !== void 0 ? middlewareResult.value : value;
|
|
147
|
+
}
|
|
148
|
+
let observable2 = this._state.get(key);
|
|
149
|
+
if (!observable2) {
|
|
150
|
+
observable2 = new Observable(value, config);
|
|
151
|
+
this._state.set(key, observable2);
|
|
152
|
+
} else {
|
|
153
|
+
if (config.enableHistory) {
|
|
154
|
+
this._addToHistory("set", key, observable2.value, value);
|
|
155
|
+
}
|
|
156
|
+
observable2.value = value;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if state has a key
|
|
162
|
+
*/
|
|
163
|
+
has(key) {
|
|
164
|
+
return this._state.has(key);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Delete state key
|
|
168
|
+
*/
|
|
169
|
+
delete(key) {
|
|
170
|
+
const observable2 = this._state.get(key);
|
|
171
|
+
if (observable2) {
|
|
172
|
+
if (this._options.enableHistory) {
|
|
173
|
+
this._addToHistory("delete", key, observable2.value, void 0);
|
|
174
|
+
}
|
|
175
|
+
observable2.unwatchAll();
|
|
176
|
+
this._state.delete(key);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Clear all state
|
|
183
|
+
*/
|
|
184
|
+
clear() {
|
|
185
|
+
if (this._options.enableHistory) {
|
|
186
|
+
this._addToHistory("clear", null, this.toObject(), {});
|
|
187
|
+
}
|
|
188
|
+
for (const observable2 of this._state.values()) {
|
|
189
|
+
observable2.unwatchAll();
|
|
190
|
+
}
|
|
191
|
+
this._state.clear();
|
|
192
|
+
this._computed.clear();
|
|
193
|
+
this._watchers.clear();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Create computed property
|
|
197
|
+
*/
|
|
198
|
+
computed(key, getter, options = {}) {
|
|
199
|
+
if (typeof getter !== "function") {
|
|
200
|
+
throw new StateError(`Computed property '${key}' getter must be a function`);
|
|
201
|
+
}
|
|
202
|
+
const computed2 = new Computed(getter, { ...this._options, ...options });
|
|
203
|
+
this._computed.set(key, computed2);
|
|
204
|
+
return computed2;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get computed property value
|
|
208
|
+
*/
|
|
209
|
+
getComputed(key) {
|
|
210
|
+
const computed2 = this._computed.get(key);
|
|
211
|
+
return computed2 ? computed2.value : void 0;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Watch state changes
|
|
215
|
+
*/
|
|
216
|
+
watch(key, callback, options = {}) {
|
|
217
|
+
if (typeof key === "function") {
|
|
218
|
+
return this._watchComputed(key, callback, options);
|
|
219
|
+
}
|
|
220
|
+
const observable2 = this._state.get(key);
|
|
221
|
+
if (!observable2) {
|
|
222
|
+
throw new StateError(`Cannot watch undefined state key: ${key}`);
|
|
223
|
+
}
|
|
224
|
+
const unwatch = observable2.watch(callback, options);
|
|
225
|
+
if (!this._watchers.has(key)) {
|
|
226
|
+
this._watchers.set(key, /* @__PURE__ */ new Set());
|
|
227
|
+
}
|
|
228
|
+
this._watchers.get(key).add(unwatch);
|
|
229
|
+
return unwatch;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Watch computed expression
|
|
233
|
+
*/
|
|
234
|
+
_watchComputed(expression, callback, options = {}) {
|
|
235
|
+
const computed2 = new Computed(expression, options);
|
|
236
|
+
const unwatch = computed2.watch(callback, options);
|
|
237
|
+
return unwatch;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Batch state updates
|
|
241
|
+
*/
|
|
242
|
+
batch(updates) {
|
|
243
|
+
if (typeof updates === "function") {
|
|
244
|
+
const oldEnableHistory = this._options.enableHistory;
|
|
245
|
+
this._options.enableHistory = false;
|
|
246
|
+
try {
|
|
247
|
+
const result = updates(this);
|
|
248
|
+
if (oldEnableHistory) {
|
|
249
|
+
this._addToHistory("batch", null, null, this.toObject());
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
} finally {
|
|
253
|
+
this._options.enableHistory = oldEnableHistory;
|
|
254
|
+
}
|
|
255
|
+
} else if (typeof updates === "object") {
|
|
256
|
+
return this.batch(() => {
|
|
257
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
258
|
+
this.set(key, value);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Subscribe to multiple state changes
|
|
265
|
+
*/
|
|
266
|
+
subscribe(keys, callback, options = {}) {
|
|
267
|
+
if (!Array.isArray(keys)) {
|
|
268
|
+
keys = [keys];
|
|
269
|
+
}
|
|
270
|
+
const unwatchers = keys.map((key) => {
|
|
271
|
+
return this.watch(key, (newValue, oldValue) => {
|
|
272
|
+
callback({
|
|
273
|
+
key,
|
|
274
|
+
newValue,
|
|
275
|
+
oldValue,
|
|
276
|
+
state: this.toObject()
|
|
277
|
+
});
|
|
278
|
+
}, options);
|
|
279
|
+
});
|
|
280
|
+
return () => {
|
|
281
|
+
unwatchers.forEach((unwatch) => unwatch());
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Add middleware for state changes
|
|
286
|
+
*/
|
|
287
|
+
use(middleware) {
|
|
288
|
+
if (typeof middleware !== "function") {
|
|
289
|
+
throw new StateError("Middleware must be a function");
|
|
290
|
+
}
|
|
291
|
+
this._middleware.push(middleware);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Run middleware chain
|
|
295
|
+
*/
|
|
296
|
+
_runMiddleware(action, context) {
|
|
297
|
+
let result = { ...context, cancelled: false };
|
|
298
|
+
for (const middleware of this._middleware) {
|
|
299
|
+
try {
|
|
300
|
+
const middlewareResult = middleware(action, result);
|
|
301
|
+
if (middlewareResult) {
|
|
302
|
+
result = { ...result, ...middlewareResult };
|
|
303
|
+
if (result.cancelled) {
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} catch (_error) {
|
|
308
|
+
globalErrorHandler.handle(_error, {
|
|
309
|
+
type: "middleware-_error",
|
|
310
|
+
context: { action, middleware: middleware.toString() }
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Add action to history
|
|
318
|
+
*/
|
|
319
|
+
_addToHistory(action, key, oldValue, newValue) {
|
|
320
|
+
if (!this._options.enableHistory) return;
|
|
321
|
+
this._history.unshift({
|
|
322
|
+
action,
|
|
323
|
+
key,
|
|
324
|
+
oldValue,
|
|
325
|
+
newValue,
|
|
326
|
+
timestamp: Date.now()
|
|
327
|
+
});
|
|
328
|
+
if (this._history.length > this._options.maxHistorySize) {
|
|
329
|
+
this._history = this._history.slice(0, this._options.maxHistorySize);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get state history
|
|
334
|
+
*/
|
|
335
|
+
getHistory(limit = 10) {
|
|
336
|
+
return this._history.slice(0, limit);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Undo last action
|
|
340
|
+
*/
|
|
341
|
+
undo() {
|
|
342
|
+
const lastAction = this._history.shift();
|
|
343
|
+
if (!lastAction) return false;
|
|
344
|
+
const { action, key, oldValue } = lastAction;
|
|
345
|
+
const oldEnableHistory = this._options.enableHistory;
|
|
346
|
+
this._options.enableHistory = false;
|
|
347
|
+
try {
|
|
348
|
+
switch (action) {
|
|
349
|
+
case "set":
|
|
350
|
+
if (oldValue === void 0) {
|
|
351
|
+
this.delete(key);
|
|
352
|
+
} else {
|
|
353
|
+
this.set(key, oldValue);
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
case "delete":
|
|
357
|
+
this.set(key, oldValue);
|
|
358
|
+
break;
|
|
359
|
+
case "clear":
|
|
360
|
+
this.clear();
|
|
361
|
+
Object.entries(oldValue || {}).forEach(([k, v]) => {
|
|
362
|
+
this.set(k, v);
|
|
363
|
+
});
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
} finally {
|
|
368
|
+
this._options.enableHistory = oldEnableHistory;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Convert state to plain object
|
|
373
|
+
*/
|
|
374
|
+
toObject() {
|
|
375
|
+
const result = {};
|
|
376
|
+
for (const [key, observable2] of this._state.entries()) {
|
|
377
|
+
result[key] = observable2.value;
|
|
378
|
+
}
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Convert computed properties to object
|
|
383
|
+
*/
|
|
384
|
+
getComputedValues() {
|
|
385
|
+
const result = {};
|
|
386
|
+
for (const [key, computed2] of this._computed.entries()) {
|
|
387
|
+
result[key] = computed2.value;
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get state statistics
|
|
393
|
+
*/
|
|
394
|
+
getStats() {
|
|
395
|
+
return {
|
|
396
|
+
stateKeys: this._state.size,
|
|
397
|
+
computedKeys: this._computed.size,
|
|
398
|
+
watcherKeys: this._watchers.size,
|
|
399
|
+
historyLength: this._history.length,
|
|
400
|
+
middlewareCount: this._middleware.length
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Cleanup and destroy
|
|
405
|
+
*/
|
|
406
|
+
destroy() {
|
|
407
|
+
for (const observable2 of this._state.values()) {
|
|
408
|
+
observable2.unwatchAll();
|
|
409
|
+
}
|
|
410
|
+
for (const computed2 of this._computed.values()) {
|
|
411
|
+
computed2.unwatchAll();
|
|
412
|
+
}
|
|
413
|
+
this._state.clear();
|
|
414
|
+
this._computed.clear();
|
|
415
|
+
this._watchers.clear();
|
|
416
|
+
this._middleware.length = 0;
|
|
417
|
+
this._history.length = 0;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
function createReactiveState(initialState, options = {}) {
|
|
421
|
+
return new ReactiveState(initialState, options);
|
|
422
|
+
}
|
|
423
|
+
function observable(value, options = {}) {
|
|
424
|
+
return new Observable(value, options);
|
|
425
|
+
}
|
|
426
|
+
function computed(getter, options = {}) {
|
|
427
|
+
return new Computed(getter, options);
|
|
428
|
+
}
|
|
429
|
+
var stateUtils = {
|
|
430
|
+
/**
|
|
431
|
+
* Create a toggle state
|
|
432
|
+
*/
|
|
433
|
+
toggle(initialValue = false) {
|
|
434
|
+
const obs = observable(initialValue);
|
|
435
|
+
obs.toggle = () => {
|
|
436
|
+
obs.value = !obs.value;
|
|
437
|
+
};
|
|
438
|
+
return obs;
|
|
439
|
+
},
|
|
440
|
+
/**
|
|
441
|
+
* Create a counter state
|
|
442
|
+
*/
|
|
443
|
+
counter(initialValue = 0) {
|
|
444
|
+
const obs = observable(initialValue);
|
|
445
|
+
obs.increment = (by = 1) => {
|
|
446
|
+
obs.value += by;
|
|
447
|
+
};
|
|
448
|
+
obs.decrement = (by = 1) => {
|
|
449
|
+
obs.value -= by;
|
|
450
|
+
};
|
|
451
|
+
obs.reset = () => {
|
|
452
|
+
obs.value = initialValue;
|
|
453
|
+
};
|
|
454
|
+
return obs;
|
|
455
|
+
},
|
|
456
|
+
/**
|
|
457
|
+
* Create an array state with utilities
|
|
458
|
+
*/
|
|
459
|
+
array(initialArray = []) {
|
|
460
|
+
const obs = observable([...initialArray]);
|
|
461
|
+
obs.push = (...items) => {
|
|
462
|
+
obs.value = [...obs.value, ...items];
|
|
463
|
+
};
|
|
464
|
+
obs.pop = () => {
|
|
465
|
+
const newArray = [...obs.value];
|
|
466
|
+
const result = newArray.pop();
|
|
467
|
+
obs.value = newArray;
|
|
468
|
+
return result;
|
|
469
|
+
};
|
|
470
|
+
obs.filter = (predicate) => {
|
|
471
|
+
obs.value = obs.value.filter(predicate);
|
|
472
|
+
};
|
|
473
|
+
obs.clear = () => {
|
|
474
|
+
obs.value = [];
|
|
475
|
+
};
|
|
476
|
+
return obs;
|
|
477
|
+
},
|
|
478
|
+
/**
|
|
479
|
+
* Create object state with deep reactivity
|
|
480
|
+
*/
|
|
481
|
+
object(initialObject = {}) {
|
|
482
|
+
const state = createReactiveState(initialObject, { deep: true });
|
|
483
|
+
return state;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/state-manager.js
|
|
488
|
+
var globalState = /* @__PURE__ */ new Map();
|
|
489
|
+
function createState(initialState = {}) {
|
|
490
|
+
const state = new Map(Object.entries(initialState));
|
|
491
|
+
return {
|
|
492
|
+
get(key) {
|
|
493
|
+
return state.get(key);
|
|
494
|
+
},
|
|
495
|
+
set(key, value) {
|
|
496
|
+
state.set(key, value);
|
|
497
|
+
return this;
|
|
498
|
+
},
|
|
499
|
+
has(key) {
|
|
500
|
+
return state.has(key);
|
|
501
|
+
},
|
|
502
|
+
delete(key) {
|
|
503
|
+
return state.delete(key);
|
|
504
|
+
},
|
|
505
|
+
clear() {
|
|
506
|
+
state.clear();
|
|
507
|
+
return this;
|
|
508
|
+
},
|
|
509
|
+
toObject() {
|
|
510
|
+
return Object.fromEntries(state);
|
|
511
|
+
},
|
|
512
|
+
// For debugging
|
|
513
|
+
_internal: state
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
var globalStateManager = {
|
|
517
|
+
set(key, value) {
|
|
518
|
+
globalState.set(key, value);
|
|
519
|
+
},
|
|
520
|
+
get(key) {
|
|
521
|
+
return globalState.get(key);
|
|
522
|
+
},
|
|
523
|
+
has(key) {
|
|
524
|
+
return globalState.has(key);
|
|
525
|
+
},
|
|
526
|
+
clear() {
|
|
527
|
+
globalState.clear();
|
|
528
|
+
},
|
|
529
|
+
// Create isolated state for each request
|
|
530
|
+
createRequestState() {
|
|
531
|
+
return createState();
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
var contextStacks = /* @__PURE__ */ new Map();
|
|
535
|
+
function provideContext(key, value) {
|
|
536
|
+
if (!contextStacks.has(key)) {
|
|
537
|
+
contextStacks.set(key, []);
|
|
538
|
+
}
|
|
539
|
+
const stack = contextStacks.get(key);
|
|
540
|
+
const previousValue = globalState.get(key);
|
|
541
|
+
stack.push(previousValue);
|
|
542
|
+
globalState.set(key, value);
|
|
543
|
+
}
|
|
544
|
+
function createContextProvider(key, value, children) {
|
|
545
|
+
return (renderFunction) => {
|
|
546
|
+
try {
|
|
547
|
+
provideContext(key, value);
|
|
548
|
+
if (renderFunction && typeof renderFunction === "function") {
|
|
549
|
+
return renderFunction(children);
|
|
550
|
+
} else {
|
|
551
|
+
return children;
|
|
552
|
+
}
|
|
553
|
+
} finally {
|
|
554
|
+
restoreContext(key);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function restoreContext(key) {
|
|
559
|
+
if (!contextStacks.has(key)) return;
|
|
560
|
+
const stack = contextStacks.get(key);
|
|
561
|
+
const previousValue = stack.pop();
|
|
562
|
+
if (stack.length === 0) {
|
|
563
|
+
if (previousValue === void 0) {
|
|
564
|
+
globalState.delete(key);
|
|
565
|
+
} else {
|
|
566
|
+
globalState.set(key, previousValue);
|
|
567
|
+
}
|
|
568
|
+
contextStacks.delete(key);
|
|
569
|
+
} else {
|
|
570
|
+
globalState.set(key, previousValue);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function clearAllContexts() {
|
|
574
|
+
contextStacks.clear();
|
|
575
|
+
}
|
|
576
|
+
function useContext(key) {
|
|
577
|
+
return globalState.get(key);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/state-persistence.js
|
|
581
|
+
var LocalStorageAdapter = class {
|
|
582
|
+
constructor() {
|
|
583
|
+
this.available = typeof localStorage !== "undefined";
|
|
584
|
+
}
|
|
585
|
+
async get(key) {
|
|
586
|
+
if (!this.available) return null;
|
|
587
|
+
try {
|
|
588
|
+
return localStorage.getItem(key);
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.error("LocalStorage get error:", error);
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async set(key, value) {
|
|
595
|
+
if (!this.available) return false;
|
|
596
|
+
try {
|
|
597
|
+
localStorage.setItem(key, value);
|
|
598
|
+
return true;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.error("LocalStorage set error:", error);
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async remove(key) {
|
|
605
|
+
if (!this.available) return false;
|
|
606
|
+
try {
|
|
607
|
+
localStorage.removeItem(key);
|
|
608
|
+
return true;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.error("LocalStorage remove error:", error);
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async clear() {
|
|
615
|
+
if (!this.available) return false;
|
|
616
|
+
try {
|
|
617
|
+
localStorage.clear();
|
|
618
|
+
return true;
|
|
619
|
+
} catch (error) {
|
|
620
|
+
console.error("LocalStorage clear error:", error);
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
var SessionStorageAdapter = class {
|
|
626
|
+
constructor() {
|
|
627
|
+
this.available = typeof sessionStorage !== "undefined";
|
|
628
|
+
}
|
|
629
|
+
async get(key) {
|
|
630
|
+
if (!this.available) return null;
|
|
631
|
+
try {
|
|
632
|
+
return sessionStorage.getItem(key);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
console.error("SessionStorage get error:", error);
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async set(key, value) {
|
|
639
|
+
if (!this.available) return false;
|
|
640
|
+
try {
|
|
641
|
+
sessionStorage.setItem(key, value);
|
|
642
|
+
return true;
|
|
643
|
+
} catch (error) {
|
|
644
|
+
console.error("SessionStorage set error:", error);
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async remove(key) {
|
|
649
|
+
if (!this.available) return false;
|
|
650
|
+
try {
|
|
651
|
+
sessionStorage.removeItem(key);
|
|
652
|
+
return true;
|
|
653
|
+
} catch (error) {
|
|
654
|
+
console.error("SessionStorage remove error:", error);
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async clear() {
|
|
659
|
+
if (!this.available) return false;
|
|
660
|
+
try {
|
|
661
|
+
sessionStorage.clear();
|
|
662
|
+
return true;
|
|
663
|
+
} catch (error) {
|
|
664
|
+
console.error("SessionStorage clear error:", error);
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
var IndexedDBAdapter = class {
|
|
670
|
+
constructor(dbName = "coherent-db", storeName = "state") {
|
|
671
|
+
this.dbName = dbName;
|
|
672
|
+
this.storeName = storeName;
|
|
673
|
+
this.available = typeof indexedDB !== "undefined";
|
|
674
|
+
this.db = null;
|
|
675
|
+
}
|
|
676
|
+
async init() {
|
|
677
|
+
if (!this.available) return false;
|
|
678
|
+
if (this.db) return true;
|
|
679
|
+
return new Promise((resolve, reject) => {
|
|
680
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
681
|
+
request.onerror = () => {
|
|
682
|
+
console.error("IndexedDB open error:", request.error);
|
|
683
|
+
reject(request.error);
|
|
684
|
+
};
|
|
685
|
+
request.onsuccess = () => {
|
|
686
|
+
this.db = request.result;
|
|
687
|
+
resolve(true);
|
|
688
|
+
};
|
|
689
|
+
request.onupgradeneeded = (event) => {
|
|
690
|
+
const db = event.target.result;
|
|
691
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
692
|
+
db.createObjectStore(this.storeName);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
async get(key) {
|
|
698
|
+
if (!this.available) return null;
|
|
699
|
+
await this.init();
|
|
700
|
+
return new Promise((resolve, reject) => {
|
|
701
|
+
const transaction = this.db.transaction([this.storeName], "readonly");
|
|
702
|
+
const store = transaction.objectStore(this.storeName);
|
|
703
|
+
const request = store.get(key);
|
|
704
|
+
request.onerror = () => {
|
|
705
|
+
console.error("IndexedDB get error:", request.error);
|
|
706
|
+
reject(request.error);
|
|
707
|
+
};
|
|
708
|
+
request.onsuccess = () => {
|
|
709
|
+
resolve(request.result || null);
|
|
710
|
+
};
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
async set(key, value) {
|
|
714
|
+
if (!this.available) return false;
|
|
715
|
+
await this.init();
|
|
716
|
+
return new Promise((resolve, reject) => {
|
|
717
|
+
const transaction = this.db.transaction([this.storeName], "readwrite");
|
|
718
|
+
const store = transaction.objectStore(this.storeName);
|
|
719
|
+
const request = store.put(value, key);
|
|
720
|
+
request.onerror = () => {
|
|
721
|
+
console.error("IndexedDB set error:", request.error);
|
|
722
|
+
reject(request.error);
|
|
723
|
+
};
|
|
724
|
+
request.onsuccess = () => {
|
|
725
|
+
resolve(true);
|
|
726
|
+
};
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
async remove(key) {
|
|
730
|
+
if (!this.available) return false;
|
|
731
|
+
await this.init();
|
|
732
|
+
return new Promise((resolve, reject) => {
|
|
733
|
+
const transaction = this.db.transaction([this.storeName], "readwrite");
|
|
734
|
+
const store = transaction.objectStore(this.storeName);
|
|
735
|
+
const request = store.delete(key);
|
|
736
|
+
request.onerror = () => {
|
|
737
|
+
console.error("IndexedDB remove error:", request.error);
|
|
738
|
+
reject(request.error);
|
|
739
|
+
};
|
|
740
|
+
request.onsuccess = () => {
|
|
741
|
+
resolve(true);
|
|
742
|
+
};
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
async clear() {
|
|
746
|
+
if (!this.available) return false;
|
|
747
|
+
await this.init();
|
|
748
|
+
return new Promise((resolve, reject) => {
|
|
749
|
+
const transaction = this.db.transaction([this.storeName], "readwrite");
|
|
750
|
+
const store = transaction.objectStore(this.storeName);
|
|
751
|
+
const request = store.clear();
|
|
752
|
+
request.onerror = () => {
|
|
753
|
+
console.error("IndexedDB clear error:", request.error);
|
|
754
|
+
reject(request.error);
|
|
755
|
+
};
|
|
756
|
+
request.onsuccess = () => {
|
|
757
|
+
resolve(true);
|
|
758
|
+
};
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
var MemoryAdapter = class {
|
|
763
|
+
constructor() {
|
|
764
|
+
this.storage = /* @__PURE__ */ new Map();
|
|
765
|
+
this.available = true;
|
|
766
|
+
}
|
|
767
|
+
async get(key) {
|
|
768
|
+
return this.storage.get(key) || null;
|
|
769
|
+
}
|
|
770
|
+
async set(key, value) {
|
|
771
|
+
this.storage.set(key, value);
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
async remove(key) {
|
|
775
|
+
return this.storage.delete(key);
|
|
776
|
+
}
|
|
777
|
+
async clear() {
|
|
778
|
+
this.storage.clear();
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
var SimpleEncryption = class {
|
|
783
|
+
constructor(key) {
|
|
784
|
+
this.key = key || "default-key";
|
|
785
|
+
}
|
|
786
|
+
encrypt(text) {
|
|
787
|
+
let result = "";
|
|
788
|
+
for (let i = 0; i < text.length; i++) {
|
|
789
|
+
result += String.fromCharCode(
|
|
790
|
+
text.charCodeAt(i) ^ this.key.charCodeAt(i % this.key.length)
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
return btoa(result);
|
|
794
|
+
}
|
|
795
|
+
decrypt(encrypted) {
|
|
796
|
+
const text = atob(encrypted);
|
|
797
|
+
let result = "";
|
|
798
|
+
for (let i = 0; i < text.length; i++) {
|
|
799
|
+
result += String.fromCharCode(
|
|
800
|
+
text.charCodeAt(i) ^ this.key.charCodeAt(i % this.key.length)
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
function createStorageAdapter(type) {
|
|
807
|
+
switch (type) {
|
|
808
|
+
case "localStorage":
|
|
809
|
+
return new LocalStorageAdapter();
|
|
810
|
+
case "sessionStorage":
|
|
811
|
+
return new SessionStorageAdapter();
|
|
812
|
+
case "indexedDB":
|
|
813
|
+
return new IndexedDBAdapter();
|
|
814
|
+
case "memory":
|
|
815
|
+
return new MemoryAdapter();
|
|
816
|
+
default:
|
|
817
|
+
return new LocalStorageAdapter();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
function createPersistentState(initialState = {}, options = {}) {
|
|
821
|
+
const opts = {
|
|
822
|
+
storage: "localStorage",
|
|
823
|
+
key: "coherent-state",
|
|
824
|
+
debounce: true,
|
|
825
|
+
debounceDelay: 300,
|
|
826
|
+
serialize: JSON.stringify,
|
|
827
|
+
deserialize: JSON.parse,
|
|
828
|
+
include: null,
|
|
829
|
+
exclude: null,
|
|
830
|
+
encrypt: false,
|
|
831
|
+
encryptionKey: null,
|
|
832
|
+
onSave: null,
|
|
833
|
+
onLoad: null,
|
|
834
|
+
onError: null,
|
|
835
|
+
versioning: false,
|
|
836
|
+
version: "1.0.0",
|
|
837
|
+
migrate: null,
|
|
838
|
+
ttl: null,
|
|
839
|
+
crossTab: false,
|
|
840
|
+
...options
|
|
841
|
+
};
|
|
842
|
+
const adapter = createStorageAdapter(opts.storage);
|
|
843
|
+
const encryption = opts.encrypt ? new SimpleEncryption(opts.encryptionKey) : null;
|
|
844
|
+
let state = { ...initialState };
|
|
845
|
+
let saveTimeout = null;
|
|
846
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
847
|
+
function filterKeys(obj) {
|
|
848
|
+
if (!obj || typeof obj !== "object") return obj;
|
|
849
|
+
if (opts.include && Array.isArray(opts.include)) {
|
|
850
|
+
const filtered = {};
|
|
851
|
+
opts.include.forEach((key) => {
|
|
852
|
+
if (key in obj) {
|
|
853
|
+
filtered[key] = obj[key];
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
return filtered;
|
|
857
|
+
}
|
|
858
|
+
if (opts.exclude && Array.isArray(opts.exclude)) {
|
|
859
|
+
const filtered = { ...obj };
|
|
860
|
+
opts.exclude.forEach((key) => {
|
|
861
|
+
delete filtered[key];
|
|
862
|
+
});
|
|
863
|
+
return filtered;
|
|
864
|
+
}
|
|
865
|
+
return obj;
|
|
866
|
+
}
|
|
867
|
+
async function save(immediate = false) {
|
|
868
|
+
if (opts.debounce && !immediate) {
|
|
869
|
+
clearTimeout(saveTimeout);
|
|
870
|
+
saveTimeout = setTimeout(() => save(true), opts.debounceDelay);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
const filteredState = filterKeys(state);
|
|
875
|
+
const serialized = opts.serialize(filteredState);
|
|
876
|
+
const data = {
|
|
877
|
+
state: serialized,
|
|
878
|
+
version: opts.version,
|
|
879
|
+
timestamp: Date.now(),
|
|
880
|
+
ttl: opts.ttl
|
|
881
|
+
};
|
|
882
|
+
let dataString = JSON.stringify(data);
|
|
883
|
+
if (encryption) {
|
|
884
|
+
dataString = encryption.encrypt(dataString);
|
|
885
|
+
}
|
|
886
|
+
await adapter.set(opts.key, dataString);
|
|
887
|
+
if (opts.onSave) {
|
|
888
|
+
opts.onSave(filteredState);
|
|
889
|
+
}
|
|
890
|
+
if (opts.crossTab && typeof BroadcastChannel !== "undefined") {
|
|
891
|
+
const channel = new BroadcastChannel("coherent-state-sync");
|
|
892
|
+
channel.postMessage({ type: "state-update", state: filteredState });
|
|
893
|
+
channel.close();
|
|
894
|
+
}
|
|
895
|
+
} catch (error) {
|
|
896
|
+
console.error("State save error:", error);
|
|
897
|
+
if (opts.onError) {
|
|
898
|
+
opts.onError(error);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async function load() {
|
|
903
|
+
try {
|
|
904
|
+
let dataString = await adapter.get(opts.key);
|
|
905
|
+
if (!dataString) return null;
|
|
906
|
+
if (encryption) {
|
|
907
|
+
dataString = encryption.decrypt(dataString);
|
|
908
|
+
}
|
|
909
|
+
const data = JSON.parse(dataString);
|
|
910
|
+
if (data.ttl && data.timestamp) {
|
|
911
|
+
const age = Date.now() - data.timestamp;
|
|
912
|
+
if (age > data.ttl) {
|
|
913
|
+
await adapter.remove(opts.key);
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (opts.versioning && data.version !== opts.version) {
|
|
918
|
+
if (opts.migrate) {
|
|
919
|
+
const migrated = opts.migrate(data.state, data.version, opts.version);
|
|
920
|
+
return opts.deserialize(migrated);
|
|
921
|
+
}
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
const loadedState = opts.deserialize(data.state);
|
|
925
|
+
if (opts.onLoad) {
|
|
926
|
+
opts.onLoad(loadedState);
|
|
927
|
+
}
|
|
928
|
+
return loadedState;
|
|
929
|
+
} catch (error) {
|
|
930
|
+
console.error("State load error:", error);
|
|
931
|
+
if (opts.onError) {
|
|
932
|
+
opts.onError(error);
|
|
933
|
+
}
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function subscribe(listener) {
|
|
938
|
+
listeners.add(listener);
|
|
939
|
+
return () => listeners.delete(listener);
|
|
940
|
+
}
|
|
941
|
+
function notifyListeners(oldState, newState) {
|
|
942
|
+
listeners.forEach((listener) => {
|
|
943
|
+
try {
|
|
944
|
+
listener(newState, oldState);
|
|
945
|
+
} catch (error) {
|
|
946
|
+
console.error("Listener error:", error);
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
function getState(key) {
|
|
951
|
+
return key ? state[key] : { ...state };
|
|
952
|
+
}
|
|
953
|
+
function setState(updates, persist2 = true) {
|
|
954
|
+
const oldState = { ...state };
|
|
955
|
+
if (typeof updates === "function") {
|
|
956
|
+
updates = updates(oldState);
|
|
957
|
+
}
|
|
958
|
+
state = { ...state, ...updates };
|
|
959
|
+
notifyListeners(oldState, state);
|
|
960
|
+
if (persist2) {
|
|
961
|
+
save();
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function resetState(persist2 = true) {
|
|
965
|
+
const oldState = { ...state };
|
|
966
|
+
state = { ...initialState };
|
|
967
|
+
notifyListeners(oldState, state);
|
|
968
|
+
if (persist2) {
|
|
969
|
+
save(true);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
async function clearStorage() {
|
|
973
|
+
await adapter.remove(opts.key);
|
|
974
|
+
}
|
|
975
|
+
async function persist() {
|
|
976
|
+
await save(true);
|
|
977
|
+
}
|
|
978
|
+
async function restore() {
|
|
979
|
+
const loaded = await load();
|
|
980
|
+
if (loaded) {
|
|
981
|
+
const oldState = { ...state };
|
|
982
|
+
state = { ...state, ...loaded };
|
|
983
|
+
notifyListeners(oldState, state);
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
if (opts.crossTab && typeof BroadcastChannel !== "undefined") {
|
|
989
|
+
const channel = new BroadcastChannel("coherent-state-sync");
|
|
990
|
+
channel.onmessage = (event) => {
|
|
991
|
+
if (event.data.type === "state-update") {
|
|
992
|
+
const oldState = { ...state };
|
|
993
|
+
state = { ...state, ...event.data.state };
|
|
994
|
+
notifyListeners(oldState, state);
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
if (opts.storage !== "memory") {
|
|
999
|
+
restore();
|
|
1000
|
+
}
|
|
1001
|
+
return {
|
|
1002
|
+
getState,
|
|
1003
|
+
setState,
|
|
1004
|
+
resetState,
|
|
1005
|
+
subscribe,
|
|
1006
|
+
persist,
|
|
1007
|
+
restore,
|
|
1008
|
+
clearStorage,
|
|
1009
|
+
load,
|
|
1010
|
+
save: () => save(true),
|
|
1011
|
+
get adapter() {
|
|
1012
|
+
return adapter;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
function withLocalStorage(initialState = {}, key = "coherent-state", options = {}) {
|
|
1017
|
+
return createPersistentState(initialState, {
|
|
1018
|
+
...options,
|
|
1019
|
+
storage: "localStorage",
|
|
1020
|
+
key
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
function withSessionStorage(initialState = {}, key = "coherent-state", options = {}) {
|
|
1024
|
+
return createPersistentState(initialState, {
|
|
1025
|
+
...options,
|
|
1026
|
+
storage: "sessionStorage",
|
|
1027
|
+
key
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
function withIndexedDB(initialState = {}, key = "coherent-state", options = {}) {
|
|
1031
|
+
return createPersistentState(initialState, {
|
|
1032
|
+
...options,
|
|
1033
|
+
storage: "indexedDB",
|
|
1034
|
+
key
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/state-validation.js
|
|
1039
|
+
var SchemaValidator = class {
|
|
1040
|
+
constructor(schema, options = {}) {
|
|
1041
|
+
this.schema = schema;
|
|
1042
|
+
this.options = {
|
|
1043
|
+
coerce: false,
|
|
1044
|
+
allowUnknown: true,
|
|
1045
|
+
...options
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Validate value against schema
|
|
1050
|
+
* @param {*} value - Value to validate
|
|
1051
|
+
* @param {Object} schema - Schema to validate against
|
|
1052
|
+
* @param {string} path - Current path in object
|
|
1053
|
+
* @returns {ValidationResult} Validation result
|
|
1054
|
+
*/
|
|
1055
|
+
validate(value, schema = this.schema, path = "") {
|
|
1056
|
+
const errors = [];
|
|
1057
|
+
let coercedValue = value;
|
|
1058
|
+
if (schema.type) {
|
|
1059
|
+
const typeResult = this.validateType(value, schema.type, path);
|
|
1060
|
+
if (!typeResult.valid) {
|
|
1061
|
+
errors.push(...typeResult.errors);
|
|
1062
|
+
if (!this.options.coerce) {
|
|
1063
|
+
return { valid: false, errors, value };
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
coercedValue = typeResult.value;
|
|
1067
|
+
}
|
|
1068
|
+
if (schema.enum) {
|
|
1069
|
+
const enumResult = this.validateEnum(coercedValue, schema.enum, path);
|
|
1070
|
+
if (!enumResult.valid) {
|
|
1071
|
+
errors.push(...enumResult.errors);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (schema.type === "string") {
|
|
1075
|
+
const stringResult = this.validateString(coercedValue, schema, path);
|
|
1076
|
+
if (!stringResult.valid) {
|
|
1077
|
+
errors.push(...stringResult.errors);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
1081
|
+
const numberResult = this.validateNumber(coercedValue, schema, path);
|
|
1082
|
+
if (!numberResult.valid) {
|
|
1083
|
+
errors.push(...numberResult.errors);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (schema.type === "array") {
|
|
1087
|
+
const arrayResult = this.validateArray(coercedValue, schema, path);
|
|
1088
|
+
if (!arrayResult.valid) {
|
|
1089
|
+
errors.push(...arrayResult.errors);
|
|
1090
|
+
}
|
|
1091
|
+
coercedValue = arrayResult.value;
|
|
1092
|
+
}
|
|
1093
|
+
if (schema.type === "object") {
|
|
1094
|
+
const objectResult = this.validateObject(coercedValue, schema, path);
|
|
1095
|
+
if (!objectResult.valid) {
|
|
1096
|
+
errors.push(...objectResult.errors);
|
|
1097
|
+
}
|
|
1098
|
+
coercedValue = objectResult.value;
|
|
1099
|
+
}
|
|
1100
|
+
if (schema.validate && typeof schema.validate === "function") {
|
|
1101
|
+
const customResult = schema.validate(coercedValue);
|
|
1102
|
+
if (customResult !== true) {
|
|
1103
|
+
errors.push({
|
|
1104
|
+
path,
|
|
1105
|
+
message: typeof customResult === "string" ? customResult : "Custom validation failed",
|
|
1106
|
+
type: "custom",
|
|
1107
|
+
value: coercedValue
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
valid: errors.length === 0,
|
|
1113
|
+
errors,
|
|
1114
|
+
value: coercedValue
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
validateType(value, type, path) {
|
|
1118
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
1119
|
+
const errors = [];
|
|
1120
|
+
let coercedValue = value;
|
|
1121
|
+
const types = Array.isArray(type) ? type : [type];
|
|
1122
|
+
const isValid = types.some((t) => {
|
|
1123
|
+
if (t === "array") return Array.isArray(value);
|
|
1124
|
+
if (t === "null") return value === null;
|
|
1125
|
+
if (t === "integer") return typeof value === "number" && Number.isInteger(value);
|
|
1126
|
+
return typeof value === t;
|
|
1127
|
+
});
|
|
1128
|
+
if (!isValid) {
|
|
1129
|
+
if (this.options.coerce) {
|
|
1130
|
+
const primaryType = types[0];
|
|
1131
|
+
try {
|
|
1132
|
+
if (primaryType === "string") {
|
|
1133
|
+
coercedValue = String(value);
|
|
1134
|
+
} else if (primaryType === "number") {
|
|
1135
|
+
coercedValue = Number(value);
|
|
1136
|
+
if (isNaN(coercedValue)) {
|
|
1137
|
+
errors.push({
|
|
1138
|
+
path,
|
|
1139
|
+
message: `Cannot coerce "${value}" to number`,
|
|
1140
|
+
type: "type",
|
|
1141
|
+
value,
|
|
1142
|
+
expected: primaryType
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
} else if (primaryType === "boolean") {
|
|
1146
|
+
coercedValue = Boolean(value);
|
|
1147
|
+
} else if (primaryType === "integer") {
|
|
1148
|
+
coercedValue = parseInt(value, 10);
|
|
1149
|
+
if (isNaN(coercedValue)) {
|
|
1150
|
+
errors.push({
|
|
1151
|
+
path,
|
|
1152
|
+
message: `Cannot coerce "${value}" to integer`,
|
|
1153
|
+
type: "type",
|
|
1154
|
+
value,
|
|
1155
|
+
expected: primaryType
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
} catch {
|
|
1160
|
+
errors.push({
|
|
1161
|
+
path,
|
|
1162
|
+
message: `Cannot coerce value to ${primaryType}`,
|
|
1163
|
+
type: "type",
|
|
1164
|
+
value,
|
|
1165
|
+
expected: primaryType
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
} else {
|
|
1169
|
+
errors.push({
|
|
1170
|
+
path,
|
|
1171
|
+
message: `Expected type ${types.join(" or ")}, got ${actualType}`,
|
|
1172
|
+
type: "type",
|
|
1173
|
+
value,
|
|
1174
|
+
expected: type
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return {
|
|
1179
|
+
valid: errors.length === 0,
|
|
1180
|
+
errors,
|
|
1181
|
+
value: coercedValue
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
validateEnum(value, enumValues, path) {
|
|
1185
|
+
const errors = [];
|
|
1186
|
+
if (!enumValues.includes(value)) {
|
|
1187
|
+
errors.push({
|
|
1188
|
+
path,
|
|
1189
|
+
message: `Value must be one of: ${enumValues.join(", ")}`,
|
|
1190
|
+
type: "enum",
|
|
1191
|
+
value,
|
|
1192
|
+
expected: enumValues
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
return { valid: errors.length === 0, errors };
|
|
1196
|
+
}
|
|
1197
|
+
validateString(value, schema, path) {
|
|
1198
|
+
const errors = [];
|
|
1199
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
1200
|
+
errors.push({
|
|
1201
|
+
path,
|
|
1202
|
+
message: `String length must be >= ${schema.minLength}`,
|
|
1203
|
+
type: "minLength",
|
|
1204
|
+
value
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
1208
|
+
errors.push({
|
|
1209
|
+
path,
|
|
1210
|
+
message: `String length must be <= ${schema.maxLength}`,
|
|
1211
|
+
type: "maxLength",
|
|
1212
|
+
value
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
if (schema.pattern) {
|
|
1216
|
+
const regex = new RegExp(schema.pattern);
|
|
1217
|
+
if (!regex.test(value)) {
|
|
1218
|
+
errors.push({
|
|
1219
|
+
path,
|
|
1220
|
+
message: `String does not match pattern: ${schema.pattern}`,
|
|
1221
|
+
type: "pattern",
|
|
1222
|
+
value
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (schema.format) {
|
|
1227
|
+
const formatResult = this.validateFormat(value, schema.format, path);
|
|
1228
|
+
if (!formatResult.valid) {
|
|
1229
|
+
errors.push(...formatResult.errors);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return { valid: errors.length === 0, errors };
|
|
1233
|
+
}
|
|
1234
|
+
validateFormat(value, format, path) {
|
|
1235
|
+
const errors = [];
|
|
1236
|
+
const formats = {
|
|
1237
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
1238
|
+
url: /^https?:\/\/.+/,
|
|
1239
|
+
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
1240
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
1241
|
+
"date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
|
1242
|
+
};
|
|
1243
|
+
if (formats[format] && !formats[format].test(value)) {
|
|
1244
|
+
errors.push({
|
|
1245
|
+
path,
|
|
1246
|
+
message: `String does not match format: ${format}`,
|
|
1247
|
+
type: "format",
|
|
1248
|
+
value,
|
|
1249
|
+
expected: format
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
return { valid: errors.length === 0, errors };
|
|
1253
|
+
}
|
|
1254
|
+
validateNumber(value, schema, path) {
|
|
1255
|
+
const errors = [];
|
|
1256
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
1257
|
+
errors.push({
|
|
1258
|
+
path,
|
|
1259
|
+
message: `Number must be >= ${schema.minimum}`,
|
|
1260
|
+
type: "minimum",
|
|
1261
|
+
value
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
1265
|
+
errors.push({
|
|
1266
|
+
path,
|
|
1267
|
+
message: `Number must be <= ${schema.maximum}`,
|
|
1268
|
+
type: "maximum",
|
|
1269
|
+
value
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
if (schema.exclusiveMinimum !== void 0 && value <= schema.exclusiveMinimum) {
|
|
1273
|
+
errors.push({
|
|
1274
|
+
path,
|
|
1275
|
+
message: `Number must be > ${schema.exclusiveMinimum}`,
|
|
1276
|
+
type: "exclusiveMinimum",
|
|
1277
|
+
value
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
if (schema.exclusiveMaximum !== void 0 && value >= schema.exclusiveMaximum) {
|
|
1281
|
+
errors.push({
|
|
1282
|
+
path,
|
|
1283
|
+
message: `Number must be < ${schema.exclusiveMaximum}`,
|
|
1284
|
+
type: "exclusiveMaximum",
|
|
1285
|
+
value
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
if (schema.multipleOf !== void 0 && value % schema.multipleOf !== 0) {
|
|
1289
|
+
errors.push({
|
|
1290
|
+
path,
|
|
1291
|
+
message: `Number must be multiple of ${schema.multipleOf}`,
|
|
1292
|
+
type: "multipleOf",
|
|
1293
|
+
value
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
return { valid: errors.length === 0, errors };
|
|
1297
|
+
}
|
|
1298
|
+
validateArray(value, schema, path) {
|
|
1299
|
+
const errors = [];
|
|
1300
|
+
const coercedValue = [...value];
|
|
1301
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
1302
|
+
errors.push({
|
|
1303
|
+
path,
|
|
1304
|
+
message: `Array must have at least ${schema.minItems} items`,
|
|
1305
|
+
type: "minItems",
|
|
1306
|
+
value
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
1310
|
+
errors.push({
|
|
1311
|
+
path,
|
|
1312
|
+
message: `Array must have at most ${schema.maxItems} items`,
|
|
1313
|
+
type: "maxItems",
|
|
1314
|
+
value
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
if (schema.uniqueItems) {
|
|
1318
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1319
|
+
const duplicates = [];
|
|
1320
|
+
value.forEach((item, index) => {
|
|
1321
|
+
const key = JSON.stringify(item);
|
|
1322
|
+
if (seen.has(key)) {
|
|
1323
|
+
duplicates.push(index);
|
|
1324
|
+
}
|
|
1325
|
+
seen.add(key);
|
|
1326
|
+
});
|
|
1327
|
+
if (duplicates.length > 0) {
|
|
1328
|
+
errors.push({
|
|
1329
|
+
path,
|
|
1330
|
+
message: "Array items must be unique",
|
|
1331
|
+
type: "uniqueItems",
|
|
1332
|
+
value
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (schema.items) {
|
|
1337
|
+
value.forEach((item, index) => {
|
|
1338
|
+
const itemPath = `${path}[${index}]`;
|
|
1339
|
+
const itemResult = this.validate(item, schema.items, itemPath);
|
|
1340
|
+
if (!itemResult.valid) {
|
|
1341
|
+
errors.push(...itemResult.errors);
|
|
1342
|
+
}
|
|
1343
|
+
if (this.options.coerce) {
|
|
1344
|
+
coercedValue[index] = itemResult.value;
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
return {
|
|
1349
|
+
valid: errors.length === 0,
|
|
1350
|
+
errors,
|
|
1351
|
+
value: coercedValue
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
validateObject(value, schema, path) {
|
|
1355
|
+
const errors = [];
|
|
1356
|
+
const coercedValue = { ...value };
|
|
1357
|
+
if (schema.required) {
|
|
1358
|
+
schema.required.forEach((prop) => {
|
|
1359
|
+
if (!(prop in value)) {
|
|
1360
|
+
errors.push({
|
|
1361
|
+
path: path ? `${path}.${prop}` : prop,
|
|
1362
|
+
message: `Required property "${prop}" is missing`,
|
|
1363
|
+
type: "required",
|
|
1364
|
+
value: void 0
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
if (schema.properties) {
|
|
1370
|
+
Object.entries(schema.properties).forEach(([prop, propSchema]) => {
|
|
1371
|
+
if (prop in value) {
|
|
1372
|
+
const propPath = path ? `${path}.${prop}` : prop;
|
|
1373
|
+
const propResult = this.validate(value[prop], propSchema, propPath);
|
|
1374
|
+
if (!propResult.valid) {
|
|
1375
|
+
errors.push(...propResult.errors);
|
|
1376
|
+
}
|
|
1377
|
+
if (this.options.coerce) {
|
|
1378
|
+
coercedValue[prop] = propResult.value;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
if (schema.additionalProperties === false && !this.options.allowUnknown) {
|
|
1384
|
+
const allowedProps = new Set(Object.keys(schema.properties || {}));
|
|
1385
|
+
Object.keys(value).forEach((prop) => {
|
|
1386
|
+
if (!allowedProps.has(prop)) {
|
|
1387
|
+
errors.push({
|
|
1388
|
+
path: path ? `${path}.${prop}` : prop,
|
|
1389
|
+
message: `Unknown property "${prop}"`,
|
|
1390
|
+
type: "additionalProperties",
|
|
1391
|
+
value: value[prop]
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
const propCount = Object.keys(value).length;
|
|
1397
|
+
if (schema.minProperties !== void 0 && propCount < schema.minProperties) {
|
|
1398
|
+
errors.push({
|
|
1399
|
+
path,
|
|
1400
|
+
message: `Object must have at least ${schema.minProperties} properties`,
|
|
1401
|
+
type: "minProperties",
|
|
1402
|
+
value
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
if (schema.maxProperties !== void 0 && propCount > schema.maxProperties) {
|
|
1406
|
+
errors.push({
|
|
1407
|
+
path,
|
|
1408
|
+
message: `Object must have at most ${schema.maxProperties} properties`,
|
|
1409
|
+
type: "maxProperties",
|
|
1410
|
+
value
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
return {
|
|
1414
|
+
valid: errors.length === 0,
|
|
1415
|
+
errors,
|
|
1416
|
+
value: coercedValue
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
function createValidatedState(initialState = {}, options = {}) {
|
|
1421
|
+
const opts = {
|
|
1422
|
+
schema: null,
|
|
1423
|
+
validators: {},
|
|
1424
|
+
strict: false,
|
|
1425
|
+
coerce: false,
|
|
1426
|
+
onError: null,
|
|
1427
|
+
validateOnSet: true,
|
|
1428
|
+
validateOnGet: false,
|
|
1429
|
+
required: [],
|
|
1430
|
+
allowUnknown: true,
|
|
1431
|
+
...options
|
|
1432
|
+
};
|
|
1433
|
+
const schemaValidator = opts.schema ? new SchemaValidator(opts.schema, {
|
|
1434
|
+
coerce: opts.coerce,
|
|
1435
|
+
allowUnknown: opts.allowUnknown
|
|
1436
|
+
}) : null;
|
|
1437
|
+
let state = { ...initialState };
|
|
1438
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1439
|
+
const validationErrors = /* @__PURE__ */ new Map();
|
|
1440
|
+
function validateState(value, key = null) {
|
|
1441
|
+
const errors = [];
|
|
1442
|
+
let validatedValue = value;
|
|
1443
|
+
if (schemaValidator) {
|
|
1444
|
+
const schema = key && opts.schema.properties ? opts.schema.properties[key] : opts.schema;
|
|
1445
|
+
const result = schemaValidator.validate(value, schema, key || "");
|
|
1446
|
+
if (!result.valid) {
|
|
1447
|
+
errors.push(...result.errors);
|
|
1448
|
+
}
|
|
1449
|
+
validatedValue = result.value;
|
|
1450
|
+
}
|
|
1451
|
+
if (key && opts.validators[key]) {
|
|
1452
|
+
const validator = opts.validators[key];
|
|
1453
|
+
const result = validator(value);
|
|
1454
|
+
if (result !== true) {
|
|
1455
|
+
errors.push({
|
|
1456
|
+
path: key,
|
|
1457
|
+
message: typeof result === "string" ? result : "Validation failed",
|
|
1458
|
+
type: "custom",
|
|
1459
|
+
value
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
} else if (!key) {
|
|
1463
|
+
Object.entries(opts.validators).forEach(([fieldKey, validator]) => {
|
|
1464
|
+
if (fieldKey in value) {
|
|
1465
|
+
const result = validator(value[fieldKey]);
|
|
1466
|
+
if (result !== true) {
|
|
1467
|
+
errors.push({
|
|
1468
|
+
path: fieldKey,
|
|
1469
|
+
message: typeof result === "string" ? result : "Validation failed",
|
|
1470
|
+
type: "custom",
|
|
1471
|
+
value: value[fieldKey]
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
if (opts.required.length > 0 && !key) {
|
|
1478
|
+
opts.required.forEach((field) => {
|
|
1479
|
+
if (!(field in value)) {
|
|
1480
|
+
errors.push({
|
|
1481
|
+
path: field,
|
|
1482
|
+
message: `Required field "${field}" is missing`,
|
|
1483
|
+
type: "required",
|
|
1484
|
+
value: void 0
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
return {
|
|
1490
|
+
valid: errors.length === 0,
|
|
1491
|
+
errors,
|
|
1492
|
+
value: validatedValue
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
function getState(key) {
|
|
1496
|
+
const value = key ? state[key] : { ...state };
|
|
1497
|
+
if (opts.validateOnGet) {
|
|
1498
|
+
const result = validateState(value, key);
|
|
1499
|
+
if (!result.valid) {
|
|
1500
|
+
validationErrors.set(key || "__root__", result.errors);
|
|
1501
|
+
if (opts.onError) {
|
|
1502
|
+
opts.onError(result.errors);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return value;
|
|
1507
|
+
}
|
|
1508
|
+
function setState(updates) {
|
|
1509
|
+
const oldState = { ...state };
|
|
1510
|
+
if (typeof updates === "function") {
|
|
1511
|
+
updates = updates(oldState);
|
|
1512
|
+
}
|
|
1513
|
+
const newState = { ...state, ...updates };
|
|
1514
|
+
if (opts.validateOnSet) {
|
|
1515
|
+
const result = validateState(newState);
|
|
1516
|
+
if (!result.valid) {
|
|
1517
|
+
validationErrors.set("__root__", result.errors);
|
|
1518
|
+
if (opts.onError) {
|
|
1519
|
+
opts.onError(result.errors);
|
|
1520
|
+
}
|
|
1521
|
+
if (opts.strict) {
|
|
1522
|
+
const error = new Error("Validation failed");
|
|
1523
|
+
error.validationErrors = result.errors;
|
|
1524
|
+
throw error;
|
|
1525
|
+
}
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (opts.coerce) {
|
|
1529
|
+
const updatedKeys = Object.keys(updates);
|
|
1530
|
+
const newUpdates = {};
|
|
1531
|
+
updatedKeys.forEach((key) => {
|
|
1532
|
+
if (result.value[key] !== state[key]) {
|
|
1533
|
+
newUpdates[key] = result.value[key];
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
updates = newUpdates;
|
|
1537
|
+
}
|
|
1538
|
+
validationErrors.clear();
|
|
1539
|
+
}
|
|
1540
|
+
state = { ...state, ...updates };
|
|
1541
|
+
listeners.forEach((listener) => {
|
|
1542
|
+
try {
|
|
1543
|
+
listener(state, oldState);
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
console.error("Listener error:", error);
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
function subscribe(listener) {
|
|
1550
|
+
listeners.add(listener);
|
|
1551
|
+
return () => listeners.delete(listener);
|
|
1552
|
+
}
|
|
1553
|
+
function getErrors(key = "__root__") {
|
|
1554
|
+
return validationErrors.get(key) || [];
|
|
1555
|
+
}
|
|
1556
|
+
function isValid() {
|
|
1557
|
+
const result = validateState(state);
|
|
1558
|
+
if (!result.valid) {
|
|
1559
|
+
validationErrors.set("__root__", result.errors);
|
|
1560
|
+
}
|
|
1561
|
+
return result.valid;
|
|
1562
|
+
}
|
|
1563
|
+
function validateField(key, value) {
|
|
1564
|
+
return validateState(value, key);
|
|
1565
|
+
}
|
|
1566
|
+
return {
|
|
1567
|
+
getState,
|
|
1568
|
+
setState,
|
|
1569
|
+
subscribe,
|
|
1570
|
+
getErrors,
|
|
1571
|
+
isValid,
|
|
1572
|
+
validateField,
|
|
1573
|
+
validate: () => validateState(state)
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
var validators = {
|
|
1577
|
+
/**
|
|
1578
|
+
* Email validator
|
|
1579
|
+
* @param {string} value - Email to validate
|
|
1580
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
1581
|
+
*/
|
|
1582
|
+
email: (value) => {
|
|
1583
|
+
if (typeof value !== "string") return "Email must be a string";
|
|
1584
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return "Invalid email format";
|
|
1585
|
+
return true;
|
|
1586
|
+
},
|
|
1587
|
+
/**
|
|
1588
|
+
* URL validator
|
|
1589
|
+
* @param {string} value - URL to validate
|
|
1590
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
1591
|
+
*/
|
|
1592
|
+
url: (value) => {
|
|
1593
|
+
if (typeof value !== "string") return "URL must be a string";
|
|
1594
|
+
try {
|
|
1595
|
+
new URL(value);
|
|
1596
|
+
return true;
|
|
1597
|
+
} catch {
|
|
1598
|
+
return "Invalid URL format";
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
/**
|
|
1602
|
+
* Range validator
|
|
1603
|
+
* @param {number} min - Minimum value
|
|
1604
|
+
* @param {number} max - Maximum value
|
|
1605
|
+
* @returns {Function} Validator function
|
|
1606
|
+
*/
|
|
1607
|
+
range: (min, max) => (value) => {
|
|
1608
|
+
if (typeof value !== "number") return "Value must be a number";
|
|
1609
|
+
if (value < min || value > max) return `Value must be between ${min} and ${max}`;
|
|
1610
|
+
return true;
|
|
1611
|
+
},
|
|
1612
|
+
/**
|
|
1613
|
+
* Length validator
|
|
1614
|
+
* @param {number} min - Minimum length
|
|
1615
|
+
* @param {number} max - Maximum length
|
|
1616
|
+
* @returns {Function} Validator function
|
|
1617
|
+
*/
|
|
1618
|
+
length: (min, max) => (value) => {
|
|
1619
|
+
if (typeof value !== "string") return "Value must be a string";
|
|
1620
|
+
if (value.length < min || value.length > max) {
|
|
1621
|
+
return `Length must be between ${min} and ${max}`;
|
|
1622
|
+
}
|
|
1623
|
+
return true;
|
|
1624
|
+
},
|
|
1625
|
+
/**
|
|
1626
|
+
* Pattern validator
|
|
1627
|
+
* @param {RegExp|string} pattern - Pattern to match
|
|
1628
|
+
* @returns {Function} Validator function
|
|
1629
|
+
*/
|
|
1630
|
+
pattern: (pattern) => (value) => {
|
|
1631
|
+
if (typeof value !== "string") return "Value must be a string";
|
|
1632
|
+
const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
|
|
1633
|
+
if (!regex.test(value)) return `Value does not match pattern: ${pattern}`;
|
|
1634
|
+
return true;
|
|
1635
|
+
},
|
|
1636
|
+
/**
|
|
1637
|
+
* Required validator
|
|
1638
|
+
* @param {*} value - Value to validate
|
|
1639
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
1640
|
+
*/
|
|
1641
|
+
required: (value) => {
|
|
1642
|
+
if (value === void 0 || value === null || value === "") {
|
|
1643
|
+
return "Value is required";
|
|
1644
|
+
}
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1649
|
+
// src/index.js
|
|
1650
|
+
var index_default = {
|
|
1651
|
+
// Reactive state utilities
|
|
1652
|
+
createReactiveState,
|
|
1653
|
+
observable,
|
|
1654
|
+
computed,
|
|
1655
|
+
// SSR-compatible state management
|
|
1656
|
+
createState,
|
|
1657
|
+
globalStateManager,
|
|
1658
|
+
provideContext,
|
|
1659
|
+
createContextProvider,
|
|
1660
|
+
restoreContext,
|
|
1661
|
+
clearAllContexts,
|
|
1662
|
+
useContext,
|
|
1663
|
+
// Persistence utilities
|
|
1664
|
+
createPersistentState,
|
|
1665
|
+
withLocalStorage,
|
|
1666
|
+
withSessionStorage,
|
|
1667
|
+
withIndexedDB,
|
|
1668
|
+
// Validation utilities
|
|
1669
|
+
createValidatedState,
|
|
1670
|
+
validators,
|
|
1671
|
+
// State utilities
|
|
1672
|
+
stateUtils
|
|
1673
|
+
};
|
|
1674
|
+
export {
|
|
1675
|
+
Observable,
|
|
1676
|
+
ReactiveState,
|
|
1677
|
+
clearAllContexts,
|
|
1678
|
+
computed,
|
|
1679
|
+
createContextProvider,
|
|
1680
|
+
createPersistentState,
|
|
1681
|
+
createReactiveState,
|
|
1682
|
+
createState,
|
|
1683
|
+
createValidatedState,
|
|
1684
|
+
index_default as default,
|
|
1685
|
+
globalStateManager,
|
|
1686
|
+
observable,
|
|
1687
|
+
provideContext,
|
|
1688
|
+
restoreContext,
|
|
1689
|
+
stateUtils,
|
|
1690
|
+
useContext,
|
|
1691
|
+
validators,
|
|
1692
|
+
withIndexedDB,
|
|
1693
|
+
withLocalStorage,
|
|
1694
|
+
withSessionStorage
|
|
1695
|
+
};
|
|
1696
|
+
//# sourceMappingURL=index.js.map
|