@coherent.js/state 1.0.0-beta.5 → 1.0.0-beta.7
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/dist/index.js.map +7 -0
- package/dist/reactive-state.js +512 -0
- package/dist/reactive-state.js.map +7 -0
- package/dist/state-manager.js +102 -0
- package/dist/state-manager.js.map +7 -0
- package/dist/state-persistence.js +472 -0
- package/dist/state-persistence.js.map +7 -0
- package/dist/state-validation.js +621 -0
- package/dist/state-validation.js.map +7 -0
- package/package.json +13 -3
- package/types/index.d.ts +364 -39
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
// src/state-validation.js
|
|
2
|
+
var SchemaValidator = class {
|
|
3
|
+
constructor(schema, options = {}) {
|
|
4
|
+
this.schema = schema;
|
|
5
|
+
this.options = {
|
|
6
|
+
coerce: false,
|
|
7
|
+
allowUnknown: true,
|
|
8
|
+
...options
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validate value against schema
|
|
13
|
+
* @param {*} value - Value to validate
|
|
14
|
+
* @param {Object} schema - Schema to validate against
|
|
15
|
+
* @param {string} path - Current path in object
|
|
16
|
+
* @returns {ValidationResult} Validation result
|
|
17
|
+
*/
|
|
18
|
+
validate(value, schema = this.schema, path = "") {
|
|
19
|
+
const errors = [];
|
|
20
|
+
let coercedValue = value;
|
|
21
|
+
if (schema.type) {
|
|
22
|
+
const typeResult = this.validateType(value, schema.type, path);
|
|
23
|
+
if (!typeResult.valid) {
|
|
24
|
+
errors.push(...typeResult.errors);
|
|
25
|
+
if (!this.options.coerce) {
|
|
26
|
+
return { valid: false, errors, value };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
coercedValue = typeResult.value;
|
|
30
|
+
}
|
|
31
|
+
if (schema.enum) {
|
|
32
|
+
const enumResult = this.validateEnum(coercedValue, schema.enum, path);
|
|
33
|
+
if (!enumResult.valid) {
|
|
34
|
+
errors.push(...enumResult.errors);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (schema.type === "string") {
|
|
38
|
+
const stringResult = this.validateString(coercedValue, schema, path);
|
|
39
|
+
if (!stringResult.valid) {
|
|
40
|
+
errors.push(...stringResult.errors);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
44
|
+
const numberResult = this.validateNumber(coercedValue, schema, path);
|
|
45
|
+
if (!numberResult.valid) {
|
|
46
|
+
errors.push(...numberResult.errors);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (schema.type === "array") {
|
|
50
|
+
const arrayResult = this.validateArray(coercedValue, schema, path);
|
|
51
|
+
if (!arrayResult.valid) {
|
|
52
|
+
errors.push(...arrayResult.errors);
|
|
53
|
+
}
|
|
54
|
+
coercedValue = arrayResult.value;
|
|
55
|
+
}
|
|
56
|
+
if (schema.type === "object") {
|
|
57
|
+
const objectResult = this.validateObject(coercedValue, schema, path);
|
|
58
|
+
if (!objectResult.valid) {
|
|
59
|
+
errors.push(...objectResult.errors);
|
|
60
|
+
}
|
|
61
|
+
coercedValue = objectResult.value;
|
|
62
|
+
}
|
|
63
|
+
if (schema.validate && typeof schema.validate === "function") {
|
|
64
|
+
const customResult = schema.validate(coercedValue);
|
|
65
|
+
if (customResult !== true) {
|
|
66
|
+
errors.push({
|
|
67
|
+
path,
|
|
68
|
+
message: typeof customResult === "string" ? customResult : "Custom validation failed",
|
|
69
|
+
type: "custom",
|
|
70
|
+
value: coercedValue
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
valid: errors.length === 0,
|
|
76
|
+
errors,
|
|
77
|
+
value: coercedValue
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
validateType(value, type, path) {
|
|
81
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
82
|
+
const errors = [];
|
|
83
|
+
let coercedValue = value;
|
|
84
|
+
const types = Array.isArray(type) ? type : [type];
|
|
85
|
+
const isValid = types.some((t) => {
|
|
86
|
+
if (t === "array") return Array.isArray(value);
|
|
87
|
+
if (t === "null") return value === null;
|
|
88
|
+
if (t === "integer") return typeof value === "number" && Number.isInteger(value);
|
|
89
|
+
return typeof value === t;
|
|
90
|
+
});
|
|
91
|
+
if (!isValid) {
|
|
92
|
+
if (this.options.coerce) {
|
|
93
|
+
const primaryType = types[0];
|
|
94
|
+
try {
|
|
95
|
+
if (primaryType === "string") {
|
|
96
|
+
coercedValue = String(value);
|
|
97
|
+
} else if (primaryType === "number") {
|
|
98
|
+
coercedValue = Number(value);
|
|
99
|
+
if (isNaN(coercedValue)) {
|
|
100
|
+
errors.push({
|
|
101
|
+
path,
|
|
102
|
+
message: `Cannot coerce "${value}" to number`,
|
|
103
|
+
type: "type",
|
|
104
|
+
value,
|
|
105
|
+
expected: primaryType
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} else if (primaryType === "boolean") {
|
|
109
|
+
coercedValue = Boolean(value);
|
|
110
|
+
} else if (primaryType === "integer") {
|
|
111
|
+
coercedValue = parseInt(value, 10);
|
|
112
|
+
if (isNaN(coercedValue)) {
|
|
113
|
+
errors.push({
|
|
114
|
+
path,
|
|
115
|
+
message: `Cannot coerce "${value}" to integer`,
|
|
116
|
+
type: "type",
|
|
117
|
+
value,
|
|
118
|
+
expected: primaryType
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
errors.push({
|
|
124
|
+
path,
|
|
125
|
+
message: `Cannot coerce value to ${primaryType}`,
|
|
126
|
+
type: "type",
|
|
127
|
+
value,
|
|
128
|
+
expected: primaryType
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
errors.push({
|
|
133
|
+
path,
|
|
134
|
+
message: `Expected type ${types.join(" or ")}, got ${actualType}`,
|
|
135
|
+
type: "type",
|
|
136
|
+
value,
|
|
137
|
+
expected: type
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
valid: errors.length === 0,
|
|
143
|
+
errors,
|
|
144
|
+
value: coercedValue
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
validateEnum(value, enumValues, path) {
|
|
148
|
+
const errors = [];
|
|
149
|
+
if (!enumValues.includes(value)) {
|
|
150
|
+
errors.push({
|
|
151
|
+
path,
|
|
152
|
+
message: `Value must be one of: ${enumValues.join(", ")}`,
|
|
153
|
+
type: "enum",
|
|
154
|
+
value,
|
|
155
|
+
expected: enumValues
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return { valid: errors.length === 0, errors };
|
|
159
|
+
}
|
|
160
|
+
validateString(value, schema, path) {
|
|
161
|
+
const errors = [];
|
|
162
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
163
|
+
errors.push({
|
|
164
|
+
path,
|
|
165
|
+
message: `String length must be >= ${schema.minLength}`,
|
|
166
|
+
type: "minLength",
|
|
167
|
+
value
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
171
|
+
errors.push({
|
|
172
|
+
path,
|
|
173
|
+
message: `String length must be <= ${schema.maxLength}`,
|
|
174
|
+
type: "maxLength",
|
|
175
|
+
value
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (schema.pattern) {
|
|
179
|
+
const regex = new RegExp(schema.pattern);
|
|
180
|
+
if (!regex.test(value)) {
|
|
181
|
+
errors.push({
|
|
182
|
+
path,
|
|
183
|
+
message: `String does not match pattern: ${schema.pattern}`,
|
|
184
|
+
type: "pattern",
|
|
185
|
+
value
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (schema.format) {
|
|
190
|
+
const formatResult = this.validateFormat(value, schema.format, path);
|
|
191
|
+
if (!formatResult.valid) {
|
|
192
|
+
errors.push(...formatResult.errors);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { valid: errors.length === 0, errors };
|
|
196
|
+
}
|
|
197
|
+
validateFormat(value, format, path) {
|
|
198
|
+
const errors = [];
|
|
199
|
+
const formats = {
|
|
200
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
201
|
+
url: /^https?:\/\/.+/,
|
|
202
|
+
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
203
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
204
|
+
"date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
|
205
|
+
};
|
|
206
|
+
if (formats[format] && !formats[format].test(value)) {
|
|
207
|
+
errors.push({
|
|
208
|
+
path,
|
|
209
|
+
message: `String does not match format: ${format}`,
|
|
210
|
+
type: "format",
|
|
211
|
+
value,
|
|
212
|
+
expected: format
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return { valid: errors.length === 0, errors };
|
|
216
|
+
}
|
|
217
|
+
validateNumber(value, schema, path) {
|
|
218
|
+
const errors = [];
|
|
219
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
220
|
+
errors.push({
|
|
221
|
+
path,
|
|
222
|
+
message: `Number must be >= ${schema.minimum}`,
|
|
223
|
+
type: "minimum",
|
|
224
|
+
value
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
228
|
+
errors.push({
|
|
229
|
+
path,
|
|
230
|
+
message: `Number must be <= ${schema.maximum}`,
|
|
231
|
+
type: "maximum",
|
|
232
|
+
value
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (schema.exclusiveMinimum !== void 0 && value <= schema.exclusiveMinimum) {
|
|
236
|
+
errors.push({
|
|
237
|
+
path,
|
|
238
|
+
message: `Number must be > ${schema.exclusiveMinimum}`,
|
|
239
|
+
type: "exclusiveMinimum",
|
|
240
|
+
value
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (schema.exclusiveMaximum !== void 0 && value >= schema.exclusiveMaximum) {
|
|
244
|
+
errors.push({
|
|
245
|
+
path,
|
|
246
|
+
message: `Number must be < ${schema.exclusiveMaximum}`,
|
|
247
|
+
type: "exclusiveMaximum",
|
|
248
|
+
value
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (schema.multipleOf !== void 0 && value % schema.multipleOf !== 0) {
|
|
252
|
+
errors.push({
|
|
253
|
+
path,
|
|
254
|
+
message: `Number must be multiple of ${schema.multipleOf}`,
|
|
255
|
+
type: "multipleOf",
|
|
256
|
+
value
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return { valid: errors.length === 0, errors };
|
|
260
|
+
}
|
|
261
|
+
validateArray(value, schema, path) {
|
|
262
|
+
const errors = [];
|
|
263
|
+
const coercedValue = [...value];
|
|
264
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
265
|
+
errors.push({
|
|
266
|
+
path,
|
|
267
|
+
message: `Array must have at least ${schema.minItems} items`,
|
|
268
|
+
type: "minItems",
|
|
269
|
+
value
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
273
|
+
errors.push({
|
|
274
|
+
path,
|
|
275
|
+
message: `Array must have at most ${schema.maxItems} items`,
|
|
276
|
+
type: "maxItems",
|
|
277
|
+
value
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (schema.uniqueItems) {
|
|
281
|
+
const seen = /* @__PURE__ */ new Set();
|
|
282
|
+
const duplicates = [];
|
|
283
|
+
value.forEach((item, index) => {
|
|
284
|
+
const key = JSON.stringify(item);
|
|
285
|
+
if (seen.has(key)) {
|
|
286
|
+
duplicates.push(index);
|
|
287
|
+
}
|
|
288
|
+
seen.add(key);
|
|
289
|
+
});
|
|
290
|
+
if (duplicates.length > 0) {
|
|
291
|
+
errors.push({
|
|
292
|
+
path,
|
|
293
|
+
message: "Array items must be unique",
|
|
294
|
+
type: "uniqueItems",
|
|
295
|
+
value
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (schema.items) {
|
|
300
|
+
value.forEach((item, index) => {
|
|
301
|
+
const itemPath = `${path}[${index}]`;
|
|
302
|
+
const itemResult = this.validate(item, schema.items, itemPath);
|
|
303
|
+
if (!itemResult.valid) {
|
|
304
|
+
errors.push(...itemResult.errors);
|
|
305
|
+
}
|
|
306
|
+
if (this.options.coerce) {
|
|
307
|
+
coercedValue[index] = itemResult.value;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
valid: errors.length === 0,
|
|
313
|
+
errors,
|
|
314
|
+
value: coercedValue
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
validateObject(value, schema, path) {
|
|
318
|
+
const errors = [];
|
|
319
|
+
const coercedValue = { ...value };
|
|
320
|
+
if (schema.required) {
|
|
321
|
+
schema.required.forEach((prop) => {
|
|
322
|
+
if (!(prop in value)) {
|
|
323
|
+
errors.push({
|
|
324
|
+
path: path ? `${path}.${prop}` : prop,
|
|
325
|
+
message: `Required property "${prop}" is missing`,
|
|
326
|
+
type: "required",
|
|
327
|
+
value: void 0
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (schema.properties) {
|
|
333
|
+
Object.entries(schema.properties).forEach(([prop, propSchema]) => {
|
|
334
|
+
if (prop in value) {
|
|
335
|
+
const propPath = path ? `${path}.${prop}` : prop;
|
|
336
|
+
const propResult = this.validate(value[prop], propSchema, propPath);
|
|
337
|
+
if (!propResult.valid) {
|
|
338
|
+
errors.push(...propResult.errors);
|
|
339
|
+
}
|
|
340
|
+
if (this.options.coerce) {
|
|
341
|
+
coercedValue[prop] = propResult.value;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (schema.additionalProperties === false && !this.options.allowUnknown) {
|
|
347
|
+
const allowedProps = new Set(Object.keys(schema.properties || {}));
|
|
348
|
+
Object.keys(value).forEach((prop) => {
|
|
349
|
+
if (!allowedProps.has(prop)) {
|
|
350
|
+
errors.push({
|
|
351
|
+
path: path ? `${path}.${prop}` : prop,
|
|
352
|
+
message: `Unknown property "${prop}"`,
|
|
353
|
+
type: "additionalProperties",
|
|
354
|
+
value: value[prop]
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const propCount = Object.keys(value).length;
|
|
360
|
+
if (schema.minProperties !== void 0 && propCount < schema.minProperties) {
|
|
361
|
+
errors.push({
|
|
362
|
+
path,
|
|
363
|
+
message: `Object must have at least ${schema.minProperties} properties`,
|
|
364
|
+
type: "minProperties",
|
|
365
|
+
value
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (schema.maxProperties !== void 0 && propCount > schema.maxProperties) {
|
|
369
|
+
errors.push({
|
|
370
|
+
path,
|
|
371
|
+
message: `Object must have at most ${schema.maxProperties} properties`,
|
|
372
|
+
type: "maxProperties",
|
|
373
|
+
value
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
valid: errors.length === 0,
|
|
378
|
+
errors,
|
|
379
|
+
value: coercedValue
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
function createValidatedState(initialState = {}, options = {}) {
|
|
384
|
+
const opts = {
|
|
385
|
+
schema: null,
|
|
386
|
+
validators: {},
|
|
387
|
+
strict: false,
|
|
388
|
+
coerce: false,
|
|
389
|
+
onError: null,
|
|
390
|
+
validateOnSet: true,
|
|
391
|
+
validateOnGet: false,
|
|
392
|
+
required: [],
|
|
393
|
+
allowUnknown: true,
|
|
394
|
+
...options
|
|
395
|
+
};
|
|
396
|
+
const schemaValidator = opts.schema ? new SchemaValidator(opts.schema, {
|
|
397
|
+
coerce: opts.coerce,
|
|
398
|
+
allowUnknown: opts.allowUnknown
|
|
399
|
+
}) : null;
|
|
400
|
+
let state = { ...initialState };
|
|
401
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
402
|
+
const validationErrors = /* @__PURE__ */ new Map();
|
|
403
|
+
function validateState(value, key = null) {
|
|
404
|
+
const errors = [];
|
|
405
|
+
let validatedValue = value;
|
|
406
|
+
if (schemaValidator) {
|
|
407
|
+
const schema = key && opts.schema.properties ? opts.schema.properties[key] : opts.schema;
|
|
408
|
+
const result = schemaValidator.validate(value, schema, key || "");
|
|
409
|
+
if (!result.valid) {
|
|
410
|
+
errors.push(...result.errors);
|
|
411
|
+
}
|
|
412
|
+
validatedValue = result.value;
|
|
413
|
+
}
|
|
414
|
+
if (key && opts.validators[key]) {
|
|
415
|
+
const validator = opts.validators[key];
|
|
416
|
+
const result = validator(value);
|
|
417
|
+
if (result !== true) {
|
|
418
|
+
errors.push({
|
|
419
|
+
path: key,
|
|
420
|
+
message: typeof result === "string" ? result : "Validation failed",
|
|
421
|
+
type: "custom",
|
|
422
|
+
value
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
} else if (!key) {
|
|
426
|
+
Object.entries(opts.validators).forEach(([fieldKey, validator]) => {
|
|
427
|
+
if (fieldKey in value) {
|
|
428
|
+
const result = validator(value[fieldKey]);
|
|
429
|
+
if (result !== true) {
|
|
430
|
+
errors.push({
|
|
431
|
+
path: fieldKey,
|
|
432
|
+
message: typeof result === "string" ? result : "Validation failed",
|
|
433
|
+
type: "custom",
|
|
434
|
+
value: value[fieldKey]
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if (opts.required.length > 0 && !key) {
|
|
441
|
+
opts.required.forEach((field) => {
|
|
442
|
+
if (!(field in value)) {
|
|
443
|
+
errors.push({
|
|
444
|
+
path: field,
|
|
445
|
+
message: `Required field "${field}" is missing`,
|
|
446
|
+
type: "required",
|
|
447
|
+
value: void 0
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
valid: errors.length === 0,
|
|
454
|
+
errors,
|
|
455
|
+
value: validatedValue
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function getState(key) {
|
|
459
|
+
const value = key ? state[key] : { ...state };
|
|
460
|
+
if (opts.validateOnGet) {
|
|
461
|
+
const result = validateState(value, key);
|
|
462
|
+
if (!result.valid) {
|
|
463
|
+
validationErrors.set(key || "__root__", result.errors);
|
|
464
|
+
if (opts.onError) {
|
|
465
|
+
opts.onError(result.errors);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
function setState(updates) {
|
|
472
|
+
const oldState = { ...state };
|
|
473
|
+
if (typeof updates === "function") {
|
|
474
|
+
updates = updates(oldState);
|
|
475
|
+
}
|
|
476
|
+
const newState = { ...state, ...updates };
|
|
477
|
+
if (opts.validateOnSet) {
|
|
478
|
+
const result = validateState(newState);
|
|
479
|
+
if (!result.valid) {
|
|
480
|
+
validationErrors.set("__root__", result.errors);
|
|
481
|
+
if (opts.onError) {
|
|
482
|
+
opts.onError(result.errors);
|
|
483
|
+
}
|
|
484
|
+
if (opts.strict) {
|
|
485
|
+
const error = new Error("Validation failed");
|
|
486
|
+
error.validationErrors = result.errors;
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (opts.coerce) {
|
|
492
|
+
const updatedKeys = Object.keys(updates);
|
|
493
|
+
const newUpdates = {};
|
|
494
|
+
updatedKeys.forEach((key) => {
|
|
495
|
+
if (result.value[key] !== state[key]) {
|
|
496
|
+
newUpdates[key] = result.value[key];
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
updates = newUpdates;
|
|
500
|
+
}
|
|
501
|
+
validationErrors.clear();
|
|
502
|
+
}
|
|
503
|
+
state = { ...state, ...updates };
|
|
504
|
+
listeners.forEach((listener) => {
|
|
505
|
+
try {
|
|
506
|
+
listener(state, oldState);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.error("Listener error:", error);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function subscribe(listener) {
|
|
513
|
+
listeners.add(listener);
|
|
514
|
+
return () => listeners.delete(listener);
|
|
515
|
+
}
|
|
516
|
+
function getErrors(key = "__root__") {
|
|
517
|
+
return validationErrors.get(key) || [];
|
|
518
|
+
}
|
|
519
|
+
function isValid() {
|
|
520
|
+
const result = validateState(state);
|
|
521
|
+
if (!result.valid) {
|
|
522
|
+
validationErrors.set("__root__", result.errors);
|
|
523
|
+
}
|
|
524
|
+
return result.valid;
|
|
525
|
+
}
|
|
526
|
+
function validateField(key, value) {
|
|
527
|
+
return validateState(value, key);
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
getState,
|
|
531
|
+
setState,
|
|
532
|
+
subscribe,
|
|
533
|
+
getErrors,
|
|
534
|
+
isValid,
|
|
535
|
+
validateField,
|
|
536
|
+
validate: () => validateState(state)
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
var validators = {
|
|
540
|
+
/**
|
|
541
|
+
* Email validator
|
|
542
|
+
* @param {string} value - Email to validate
|
|
543
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
544
|
+
*/
|
|
545
|
+
email: (value) => {
|
|
546
|
+
if (typeof value !== "string") return "Email must be a string";
|
|
547
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return "Invalid email format";
|
|
548
|
+
return true;
|
|
549
|
+
},
|
|
550
|
+
/**
|
|
551
|
+
* URL validator
|
|
552
|
+
* @param {string} value - URL to validate
|
|
553
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
554
|
+
*/
|
|
555
|
+
url: (value) => {
|
|
556
|
+
if (typeof value !== "string") return "URL must be a string";
|
|
557
|
+
try {
|
|
558
|
+
new URL(value);
|
|
559
|
+
return true;
|
|
560
|
+
} catch {
|
|
561
|
+
return "Invalid URL format";
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
/**
|
|
565
|
+
* Range validator
|
|
566
|
+
* @param {number} min - Minimum value
|
|
567
|
+
* @param {number} max - Maximum value
|
|
568
|
+
* @returns {Function} Validator function
|
|
569
|
+
*/
|
|
570
|
+
range: (min, max) => (value) => {
|
|
571
|
+
if (typeof value !== "number") return "Value must be a number";
|
|
572
|
+
if (value < min || value > max) return `Value must be between ${min} and ${max}`;
|
|
573
|
+
return true;
|
|
574
|
+
},
|
|
575
|
+
/**
|
|
576
|
+
* Length validator
|
|
577
|
+
* @param {number} min - Minimum length
|
|
578
|
+
* @param {number} max - Maximum length
|
|
579
|
+
* @returns {Function} Validator function
|
|
580
|
+
*/
|
|
581
|
+
length: (min, max) => (value) => {
|
|
582
|
+
if (typeof value !== "string") return "Value must be a string";
|
|
583
|
+
if (value.length < min || value.length > max) {
|
|
584
|
+
return `Length must be between ${min} and ${max}`;
|
|
585
|
+
}
|
|
586
|
+
return true;
|
|
587
|
+
},
|
|
588
|
+
/**
|
|
589
|
+
* Pattern validator
|
|
590
|
+
* @param {RegExp|string} pattern - Pattern to match
|
|
591
|
+
* @returns {Function} Validator function
|
|
592
|
+
*/
|
|
593
|
+
pattern: (pattern) => (value) => {
|
|
594
|
+
if (typeof value !== "string") return "Value must be a string";
|
|
595
|
+
const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
|
|
596
|
+
if (!regex.test(value)) return `Value does not match pattern: ${pattern}`;
|
|
597
|
+
return true;
|
|
598
|
+
},
|
|
599
|
+
/**
|
|
600
|
+
* Required validator
|
|
601
|
+
* @param {*} value - Value to validate
|
|
602
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
603
|
+
*/
|
|
604
|
+
required: (value) => {
|
|
605
|
+
if (value === void 0 || value === null || value === "") {
|
|
606
|
+
return "Value is required";
|
|
607
|
+
}
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
var state_validation_default = {
|
|
612
|
+
createValidatedState,
|
|
613
|
+
validators,
|
|
614
|
+
SchemaValidator
|
|
615
|
+
};
|
|
616
|
+
export {
|
|
617
|
+
createValidatedState,
|
|
618
|
+
state_validation_default as default,
|
|
619
|
+
validators
|
|
620
|
+
};
|
|
621
|
+
//# sourceMappingURL=state-validation.js.map
|