@ibodr/validate 0.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/dist/index.mjs ADDED
@@ -0,0 +1,1112 @@
1
+ import { validateIndexKey, registerDrawLibraryVersion, exhaustiveSwitchError, hasOwnProperty, getOwnProperty, STRUCTURED_CLONE_OBJECT_PROTOTYPE } from '@ibodr/utils';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, { get: all[name], enumerable: true });
7
+ };
8
+
9
+ // src/lib/validation.ts
10
+ var validation_exports = {};
11
+ __export(validation_exports, {
12
+ ArrayOfValidator: () => ArrayOfValidator,
13
+ DictValidator: () => DictValidator,
14
+ ObjectValidator: () => ObjectValidator,
15
+ UnionValidator: () => UnionValidator,
16
+ ValidationError: () => ValidationError,
17
+ Validator: () => Validator,
18
+ any: () => any,
19
+ array: () => array,
20
+ arrayOf: () => arrayOf,
21
+ bigint: () => bigint,
22
+ boolean: () => boolean,
23
+ dict: () => dict,
24
+ httpUrl: () => httpUrl,
25
+ indexKey: () => indexKey,
26
+ integer: () => integer,
27
+ jsonDict: () => jsonDict,
28
+ jsonValue: () => jsonValue,
29
+ linkUrl: () => linkUrl,
30
+ literal: () => literal,
31
+ literalEnum: () => literalEnum,
32
+ model: () => model,
33
+ nonZeroFiniteNumber: () => nonZeroFiniteNumber,
34
+ nonZeroInteger: () => nonZeroInteger,
35
+ nonZeroNumber: () => nonZeroNumber,
36
+ nullable: () => nullable,
37
+ number: () => number,
38
+ numberUnion: () => numberUnion,
39
+ object: () => object,
40
+ optional: () => optional,
41
+ or: () => or,
42
+ positiveInteger: () => positiveInteger,
43
+ positiveNumber: () => positiveNumber,
44
+ setEnum: () => setEnum,
45
+ srcUrl: () => srcUrl,
46
+ string: () => string,
47
+ union: () => union,
48
+ unitInterval: () => unitInterval,
49
+ unknown: () => unknown,
50
+ unknownObject: () => unknownObject
51
+ });
52
+ function formatPath(path) {
53
+ if (!path.length) {
54
+ return null;
55
+ }
56
+ let formattedPath = "";
57
+ for (const item of path) {
58
+ if (typeof item === "number") {
59
+ formattedPath += `.${item}`;
60
+ } else if (item.startsWith("(")) {
61
+ if (formattedPath.endsWith(")")) {
62
+ formattedPath = `${formattedPath.slice(0, -1)}, ${item.slice(1)}`;
63
+ } else {
64
+ formattedPath += item;
65
+ }
66
+ } else {
67
+ formattedPath += `.${item}`;
68
+ }
69
+ }
70
+ formattedPath = formattedPath.replace(/id = [^,]+, /, "").replace(/id = [^)]+/, "");
71
+ if (formattedPath.startsWith(".")) {
72
+ return formattedPath.slice(1);
73
+ }
74
+ return formattedPath;
75
+ }
76
+ var ValidationError = class extends Error {
77
+ /**
78
+ * Creates a new ValidationError with contextual information about where the error occurred.
79
+ *
80
+ * rawMessage - The raw error message without path information
81
+ * path - Array indicating the location in the data structure where validation failed
82
+ */
83
+ constructor(rawMessage, path = []) {
84
+ const formattedPath = formatPath(path);
85
+ const indentedMessage = rawMessage.split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
86
+ super(path ? `At ${formattedPath}: ${indentedMessage}` : indentedMessage);
87
+ this.rawMessage = rawMessage;
88
+ this.path = path;
89
+ }
90
+ rawMessage;
91
+ path;
92
+ name = "ValidationError";
93
+ };
94
+ function prefixError(path, fn) {
95
+ try {
96
+ return fn();
97
+ } catch (err) {
98
+ if (err instanceof ValidationError) {
99
+ throw new ValidationError(err.rawMessage, [path, ...err.path]);
100
+ }
101
+ throw new ValidationError(err.toString(), [path]);
102
+ }
103
+ }
104
+ function typeToString(value) {
105
+ if (value === null) return "null";
106
+ if (Array.isArray(value)) return "an array";
107
+ const type = typeof value;
108
+ switch (type) {
109
+ case "bigint":
110
+ case "boolean":
111
+ case "function":
112
+ case "number":
113
+ case "string":
114
+ case "symbol":
115
+ return `a ${type}`;
116
+ case "object":
117
+ return `an ${type}`;
118
+ case "undefined":
119
+ return "undefined";
120
+ default:
121
+ exhaustiveSwitchError(type);
122
+ }
123
+ }
124
+ var Validator = class _Validator {
125
+ /**
126
+ * Creates a new Validator instance.
127
+ *
128
+ * validationFn - Function that validates and returns a value of type T
129
+ * validateUsingKnownGoodVersionFn - Optional performance-optimized validation function
130
+ * skipSameValueCheck - Internal flag to skip dev check for validators that transform values
131
+ */
132
+ constructor(validationFn, validateUsingKnownGoodVersionFn, skipSameValueCheck = false) {
133
+ this.validationFn = validationFn;
134
+ this.validateUsingKnownGoodVersionFn = validateUsingKnownGoodVersionFn;
135
+ this.skipSameValueCheck = skipSameValueCheck;
136
+ }
137
+ validationFn;
138
+ validateUsingKnownGoodVersionFn;
139
+ skipSameValueCheck;
140
+ /**
141
+ * Validates an unknown value and returns it with the correct type. The returned value is
142
+ * guaranteed to be referentially equal to the passed value.
143
+ *
144
+ * @param value - The unknown value to validate
145
+ * @returns The validated value with type T
146
+ * @throws ValidationError When validation fails
147
+ * @example
148
+ * ```ts
149
+ * import { T } from '@ibodr/validate'
150
+ *
151
+ * const name = T.string.validate("Alice") // Returns "Alice" as string
152
+ * const title = T.string.validate("") // Returns "" (empty strings are valid)
153
+ *
154
+ * // These will throw ValidationError:
155
+ * T.string.validate(123) // Expected string, got a number
156
+ * T.string.validate(null) // Expected string, got null
157
+ * T.string.validate(undefined) // Expected string, got undefined
158
+ * ```
159
+ */
160
+ validate(value) {
161
+ const validated = this.validationFn(value);
162
+ if (!this.skipSameValueCheck && !Object.is(value, validated)) {
163
+ throw new ValidationError("Validator functions must return the same value they were passed");
164
+ }
165
+ return validated;
166
+ }
167
+ /**
168
+ * Performance-optimized validation using a previously validated value. If the new value
169
+ * is referentially equal to the known good value, returns the known good value immediately.
170
+ *
171
+ * @param knownGoodValue - A previously validated value
172
+ * @param newValue - The new value to validate
173
+ * @returns The validated value, potentially reusing the known good value
174
+ * @throws ValidationError When validation fails
175
+ * @example
176
+ * ```ts
177
+ * import { T } from '@ibodr/validate'
178
+ *
179
+ * const userValidator = T.object({
180
+ * name: T.string,
181
+ * settings: T.object({ theme: T.literalEnum('light', 'dark') })
182
+ * })
183
+ *
184
+ * const user = userValidator.validate({ name: "Alice", settings: { theme: "light" } })
185
+ *
186
+ * // Later, with partially changed data:
187
+ * const newData = { name: "Alice", settings: { theme: "dark" } }
188
+ * const updated = userValidator.validateUsingKnownGoodVersion(user, newData)
189
+ * // Only validates the changed 'theme' field for better performance
190
+ * ```
191
+ */
192
+ validateUsingKnownGoodVersion(knownGoodValue, newValue) {
193
+ if (Object.is(knownGoodValue, newValue)) {
194
+ return knownGoodValue;
195
+ }
196
+ if (this.validateUsingKnownGoodVersionFn) {
197
+ return this.validateUsingKnownGoodVersionFn(knownGoodValue, newValue);
198
+ }
199
+ return this.validate(newValue);
200
+ }
201
+ /**
202
+ * Type guard that checks if a value is valid without throwing an error.
203
+ *
204
+ * @param value - The value to check
205
+ * @returns True if the value is valid, false otherwise
206
+ * @example
207
+ * ```ts
208
+ * import { T } from '@ibodr/validate'
209
+ *
210
+ * function processUserInput(input: unknown) {
211
+ * if (T.string.isValid(input)) {
212
+ * // input is now typed as string within this block
213
+ * return input.toUpperCase()
214
+ * }
215
+ * if (T.number.isValid(input)) {
216
+ * // input is now typed as number within this block
217
+ * return input.toFixed(2)
218
+ * }
219
+ * throw new Error('Expected string or number')
220
+ * }
221
+ * ```
222
+ */
223
+ isValid(value) {
224
+ try {
225
+ this.validate(value);
226
+ return true;
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
231
+ /**
232
+ * Returns a new validator that also accepts null values.
233
+ *
234
+ * @returns A new validator that accepts T or null
235
+ * @example
236
+ * ```ts
237
+ * import { T } from '@ibodr/validate'
238
+ *
239
+ * const assetValidator = T.object({
240
+ * id: T.string,
241
+ * name: T.string,
242
+ * src: T.srcUrl.nullable(), // Can be null if not loaded yet
243
+ * mimeType: T.string.nullable()
244
+ * })
245
+ *
246
+ * const asset = assetValidator.validate({
247
+ * id: "image-123",
248
+ * name: "photo.jpg",
249
+ * src: null, // Valid - asset not loaded yet
250
+ * mimeType: "image/jpeg"
251
+ * })
252
+ * ```
253
+ */
254
+ nullable() {
255
+ return nullable(this);
256
+ }
257
+ /**
258
+ * Returns a new validator that also accepts undefined values.
259
+ *
260
+ * @returns A new validator that accepts T or undefined
261
+ * @example
262
+ * ```ts
263
+ * import { T } from '@ibodr/validate'
264
+ *
265
+ * const shapeConfigValidator = T.object({
266
+ * type: T.literal('rectangle'),
267
+ * x: T.number,
268
+ * y: T.number,
269
+ * label: T.string.optional(), // Optional property
270
+ * metadata: T.object({ created: T.string }).optional()
271
+ * })
272
+ *
273
+ * // Both of these are valid:
274
+ * const shape1 = shapeConfigValidator.validate({ type: 'rectangle', x: 0, y: 0 })
275
+ * const shape2 = shapeConfigValidator.validate({
276
+ * type: 'rectangle', x: 0, y: 0, label: "My Shape"
277
+ * })
278
+ * ```
279
+ */
280
+ optional() {
281
+ return optional(this);
282
+ }
283
+ /**
284
+ * Creates a new validator by refining this validator with additional logic that can transform
285
+ * the validated value to a new type.
286
+ *
287
+ * @param otherValidationFn - Function that transforms/validates the value to type U
288
+ * @returns A new validator that validates to type U
289
+ * @throws ValidationError When validation or refinement fails
290
+ * @example
291
+ * ```ts
292
+ * import { T, ValidationError } from '@ibodr/validate'
293
+ *
294
+ * // Transform string to ensure it starts with a prefix
295
+ * const prefixedIdValidator = T.string.refine((id) => {
296
+ * return id.startsWith('shape:') ? id : `shape:${id}`
297
+ * })
298
+ *
299
+ * const id1 = prefixedIdValidator.validate("rectangle-123") // Returns "shape:rectangle-123"
300
+ * const id2 = prefixedIdValidator.validate("shape:circle-456") // Returns "shape:circle-456"
301
+ *
302
+ * // Parse and validate JSON strings
303
+ * const jsonValidator = T.string.refine((str) => {
304
+ * try {
305
+ * return JSON.parse(str)
306
+ * } catch {
307
+ * throw new ValidationError('Invalid JSON string')
308
+ * }
309
+ * })
310
+ * ```
311
+ */
312
+ refine(otherValidationFn) {
313
+ return new _Validator(
314
+ (value) => {
315
+ return otherValidationFn(this.validate(value));
316
+ },
317
+ (knownGoodValue, newValue) => {
318
+ const validated = this.validateUsingKnownGoodVersion(knownGoodValue, newValue);
319
+ if (Object.is(knownGoodValue, validated)) {
320
+ return knownGoodValue;
321
+ }
322
+ return otherValidationFn(validated);
323
+ },
324
+ true
325
+ // skipSameValueCheck: refine is designed to transform values
326
+ );
327
+ }
328
+ check(nameOrCheckFn, checkFn) {
329
+ if (typeof nameOrCheckFn === "string") {
330
+ return this.refine((value) => {
331
+ prefixError(`(check ${nameOrCheckFn})`, () => checkFn(value));
332
+ return value;
333
+ });
334
+ } else {
335
+ return this.refine((value) => {
336
+ nameOrCheckFn(value);
337
+ return value;
338
+ });
339
+ }
340
+ }
341
+ };
342
+ var ArrayOfValidator = class extends Validator {
343
+ /**
344
+ * Creates a new ArrayOfValidator.
345
+ *
346
+ * itemValidator - Validator used to validate each array element
347
+ */
348
+ constructor(itemValidator) {
349
+ super(
350
+ (value) => {
351
+ const arr = array.validate(value);
352
+ for (let i = 0; i < arr.length; i++) {
353
+ {
354
+ prefixError(i, () => itemValidator.validate(arr[i]));
355
+ }
356
+ }
357
+ return arr;
358
+ },
359
+ (knownGoodValue, newValue) => {
360
+ if (Object.is(knownGoodValue, newValue)) {
361
+ return knownGoodValue;
362
+ }
363
+ if (!itemValidator.validateUsingKnownGoodVersion) return this.validate(newValue);
364
+ const arr = array.validate(newValue);
365
+ let isDifferent = knownGoodValue.length !== arr.length;
366
+ for (let i = 0; i < arr.length; i++) {
367
+ const item = arr[i];
368
+ if (i >= knownGoodValue.length) {
369
+ isDifferent = true;
370
+ {
371
+ prefixError(i, () => itemValidator.validate(item));
372
+ }
373
+ continue;
374
+ }
375
+ if (Object.is(knownGoodValue[i], item)) {
376
+ continue;
377
+ }
378
+ {
379
+ const checkedItem = prefixError(
380
+ i,
381
+ () => itemValidator.validateUsingKnownGoodVersion(knownGoodValue[i], item)
382
+ );
383
+ if (!Object.is(checkedItem, knownGoodValue[i])) {
384
+ isDifferent = true;
385
+ }
386
+ }
387
+ }
388
+ return isDifferent ? newValue : knownGoodValue;
389
+ }
390
+ );
391
+ this.itemValidator = itemValidator;
392
+ }
393
+ itemValidator;
394
+ /**
395
+ * Returns a new validator that ensures the array is not empty.
396
+ *
397
+ * @returns A new validator that rejects empty arrays
398
+ * @throws ValidationError When the array is empty
399
+ * @example
400
+ * ```ts
401
+ * const nonEmptyStrings = T.arrayOf(T.string).nonEmpty()
402
+ * nonEmptyStrings.validate(["hello"]) // Valid
403
+ * nonEmptyStrings.validate([]) // Throws ValidationError
404
+ * ```
405
+ */
406
+ nonEmpty() {
407
+ return this.check((value) => {
408
+ if (value.length === 0) {
409
+ throw new ValidationError("Expected a non-empty array");
410
+ }
411
+ });
412
+ }
413
+ /**
414
+ * Returns a new validator that ensures the array has more than one element.
415
+ *
416
+ * @returns A new validator that requires at least 2 elements
417
+ * @throws ValidationError When the array has 1 or fewer elements
418
+ * @example
419
+ * ```ts
420
+ * const multipleItems = T.arrayOf(T.string).lengthGreaterThan1()
421
+ * multipleItems.validate(["a", "b"]) // Valid
422
+ * multipleItems.validate(["a"]) // Throws ValidationError
423
+ * ```
424
+ */
425
+ lengthGreaterThan1() {
426
+ return this.check((value) => {
427
+ if (value.length <= 1) {
428
+ throw new ValidationError("Expected an array with length greater than 1");
429
+ }
430
+ });
431
+ }
432
+ };
433
+ var ObjectValidator = class _ObjectValidator extends Validator {
434
+ /**
435
+ * Creates a new ObjectValidator.
436
+ *
437
+ * config - Object mapping property names to their validators
438
+ * shouldAllowUnknownProperties - Whether to allow properties not defined in config
439
+ */
440
+ constructor(config, shouldAllowUnknownProperties = false) {
441
+ super(
442
+ (object2) => {
443
+ if (typeof object2 !== "object" || object2 === null) {
444
+ throw new ValidationError(`Expected object, got ${typeToString(object2)}`);
445
+ }
446
+ for (const key in config) {
447
+ if (!hasOwnProperty(config, key)) continue;
448
+ const validator = config[key];
449
+ {
450
+ prefixError(key, () => {
451
+ ;
452
+ validator.validate(getOwnProperty(object2, key));
453
+ });
454
+ }
455
+ }
456
+ if (!shouldAllowUnknownProperties) {
457
+ for (const key of Object.keys(object2)) {
458
+ if (!hasOwnProperty(config, key)) {
459
+ throw new ValidationError(`Unexpected property`, [key]);
460
+ }
461
+ }
462
+ }
463
+ return object2;
464
+ },
465
+ (knownGoodValue, newValue) => {
466
+ if (Object.is(knownGoodValue, newValue)) {
467
+ return knownGoodValue;
468
+ }
469
+ if (typeof newValue !== "object" || newValue === null) {
470
+ throw new ValidationError(`Expected object, got ${typeToString(newValue)}`);
471
+ }
472
+ let isDifferent = false;
473
+ for (const key in config) {
474
+ if (!hasOwnProperty(config, key)) continue;
475
+ const validator = config[key];
476
+ const prev = getOwnProperty(knownGoodValue, key);
477
+ const next = getOwnProperty(newValue, key);
478
+ if (Object.is(prev, next)) {
479
+ continue;
480
+ }
481
+ {
482
+ const checked = prefixError(key, () => {
483
+ const validatable = validator;
484
+ if (validatable.validateUsingKnownGoodVersion) {
485
+ return validatable.validateUsingKnownGoodVersion(prev, next);
486
+ } else {
487
+ return validatable.validate(next);
488
+ }
489
+ });
490
+ if (!Object.is(checked, prev)) {
491
+ isDifferent = true;
492
+ }
493
+ }
494
+ }
495
+ if (!shouldAllowUnknownProperties) {
496
+ for (const key of Object.keys(newValue)) {
497
+ if (!hasOwnProperty(config, key)) {
498
+ throw new ValidationError(`Unexpected property`, [key]);
499
+ }
500
+ }
501
+ }
502
+ for (const key of Object.keys(knownGoodValue)) {
503
+ if (!hasOwnProperty(newValue, key)) {
504
+ isDifferent = true;
505
+ break;
506
+ }
507
+ }
508
+ return isDifferent ? newValue : knownGoodValue;
509
+ }
510
+ );
511
+ this.config = config;
512
+ this.shouldAllowUnknownProperties = shouldAllowUnknownProperties;
513
+ }
514
+ config;
515
+ shouldAllowUnknownProperties;
516
+ /**
517
+ * Returns a new validator that allows unknown properties in the validated object.
518
+ *
519
+ * @returns A new ObjectValidator that accepts extra properties
520
+ * @example
521
+ * ```ts
522
+ * const flexibleUser = T.object({ name: T.string }).allowUnknownProperties()
523
+ * flexibleUser.validate({ name: "Alice", extra: "allowed" }) // Valid
524
+ * ```
525
+ */
526
+ allowUnknownProperties() {
527
+ return new _ObjectValidator(this.config, true);
528
+ }
529
+ /**
530
+ * Creates a new ObjectValidator by extending this validator with additional properties.
531
+ *
532
+ * @param extension - Object mapping new property names to their validators
533
+ * @returns A new ObjectValidator that validates both original and extended properties
534
+ * @example
535
+ * ```ts
536
+ * const baseUser = T.object({ name: T.string, age: T.number })
537
+ * const adminUser = baseUser.extend({
538
+ * permissions: T.arrayOf(T.string),
539
+ * isAdmin: T.boolean
540
+ * })
541
+ * // adminUser validates: { name: string; age: number; permissions: string[]; isAdmin: boolean }
542
+ * ```
543
+ */
544
+ extend(extension) {
545
+ return new _ObjectValidator({ ...this.config, ...extension });
546
+ }
547
+ };
548
+ var UnionValidator = class _UnionValidator extends Validator {
549
+ /**
550
+ * Creates a new UnionValidator.
551
+ *
552
+ * key - The discriminator property name used to determine the variant
553
+ * config - Object mapping variant names to their validators
554
+ * unknownValueValidation - Function to handle unknown variants
555
+ * useNumberKeys - Whether the discriminator uses number keys instead of strings
556
+ */
557
+ constructor(key, config, unknownValueValidation, useNumberKeys) {
558
+ super(
559
+ (input) => {
560
+ this.expectObject(input);
561
+ const { matchingSchema, variant } = this.getMatchingSchemaAndVariant(input);
562
+ if (matchingSchema === void 0) {
563
+ return this.unknownValueValidation(input, variant);
564
+ }
565
+ return prefixError(`(${key} = ${variant})`, () => matchingSchema.validate(input));
566
+ },
567
+ (prevValue, newValue) => {
568
+ this.expectObject(newValue);
569
+ this.expectObject(prevValue);
570
+ const { matchingSchema, variant } = this.getMatchingSchemaAndVariant(newValue);
571
+ if (matchingSchema === void 0) {
572
+ return this.unknownValueValidation(newValue, variant);
573
+ }
574
+ if (getOwnProperty(prevValue, key) !== getOwnProperty(newValue, key)) {
575
+ return prefixError(`(${key} = ${variant})`, () => matchingSchema.validate(newValue));
576
+ }
577
+ return prefixError(`(${key} = ${variant})`, () => {
578
+ if (matchingSchema.validateUsingKnownGoodVersion) {
579
+ return matchingSchema.validateUsingKnownGoodVersion(prevValue, newValue);
580
+ } else {
581
+ return matchingSchema.validate(newValue);
582
+ }
583
+ });
584
+ }
585
+ );
586
+ this.key = key;
587
+ this.config = config;
588
+ this.unknownValueValidation = unknownValueValidation;
589
+ this.useNumberKeys = useNumberKeys;
590
+ }
591
+ key;
592
+ config;
593
+ unknownValueValidation;
594
+ useNumberKeys;
595
+ expectObject(value) {
596
+ if (typeof value !== "object" || value === null) {
597
+ throw new ValidationError(`Expected an object, got ${typeToString(value)}`, []);
598
+ }
599
+ }
600
+ getMatchingSchemaAndVariant(object2) {
601
+ const variant = getOwnProperty(object2, this.key);
602
+ if (!this.useNumberKeys && typeof variant !== "string") {
603
+ throw new ValidationError(
604
+ `Expected a string for key "${this.key}", got ${typeToString(variant)}`
605
+ );
606
+ } else if (this.useNumberKeys) {
607
+ const numVariant = Number(variant);
608
+ if (numVariant - numVariant !== 0) {
609
+ throw new ValidationError(
610
+ `Expected a number for key "${this.key}", got "${variant}"`
611
+ );
612
+ }
613
+ }
614
+ const matchingSchema = hasOwnProperty(this.config, variant) ? this.config[variant] : void 0;
615
+ return { matchingSchema, variant };
616
+ }
617
+ /**
618
+ * Returns a new UnionValidator that can handle unknown variants using the provided function.
619
+ *
620
+ * @param unknownValueValidation - Function to validate/transform unknown variants
621
+ * @returns A new UnionValidator that accepts unknown variants
622
+ * @example
623
+ * ```ts
624
+ * const shapeValidator = T.union('type', { circle: circleValidator })
625
+ * .validateUnknownVariants((obj, variant) => {
626
+ * console.warn(`Unknown shape type: ${variant}`)
627
+ * return obj as UnknownShape
628
+ * })
629
+ * ```
630
+ */
631
+ validateUnknownVariants(unknownValueValidation) {
632
+ return new _UnionValidator(this.key, this.config, unknownValueValidation, this.useNumberKeys);
633
+ }
634
+ };
635
+ var DictValidator = class extends Validator {
636
+ /**
637
+ * Creates a new DictValidator.
638
+ *
639
+ * keyValidator - Validator for object keys
640
+ * valueValidator - Validator for object values
641
+ */
642
+ constructor(keyValidator, valueValidator) {
643
+ super(
644
+ (object2) => {
645
+ if (typeof object2 !== "object" || object2 === null) {
646
+ throw new ValidationError(`Expected object, got ${typeToString(object2)}`);
647
+ }
648
+ for (const key in object2) {
649
+ if (!hasOwnProperty(object2, key)) continue;
650
+ {
651
+ prefixError(key, () => {
652
+ keyValidator.validate(key);
653
+ valueValidator.validate(object2[key]);
654
+ });
655
+ }
656
+ }
657
+ return object2;
658
+ },
659
+ (knownGoodValue, newValue) => {
660
+ if (typeof newValue !== "object" || newValue === null) {
661
+ throw new ValidationError(`Expected object, got ${typeToString(newValue)}`);
662
+ }
663
+ const newObj = newValue;
664
+ let isDifferent = false;
665
+ let newKeyCount = 0;
666
+ for (const key in newObj) {
667
+ if (!hasOwnProperty(newObj, key)) continue;
668
+ newKeyCount++;
669
+ const next = newObj[key];
670
+ if (!hasOwnProperty(knownGoodValue, key)) {
671
+ isDifferent = true;
672
+ {
673
+ prefixError(key, () => {
674
+ keyValidator.validate(key);
675
+ valueValidator.validate(next);
676
+ });
677
+ }
678
+ continue;
679
+ }
680
+ const prev = knownGoodValue[key];
681
+ if (Object.is(prev, next)) {
682
+ continue;
683
+ }
684
+ {
685
+ const checked = prefixError(key, () => {
686
+ if (valueValidator.validateUsingKnownGoodVersion) {
687
+ return valueValidator.validateUsingKnownGoodVersion(prev, next);
688
+ } else {
689
+ return valueValidator.validate(next);
690
+ }
691
+ });
692
+ if (!Object.is(checked, prev)) {
693
+ isDifferent = true;
694
+ }
695
+ }
696
+ }
697
+ if (!isDifferent) {
698
+ let oldKeyCount = 0;
699
+ for (const key in knownGoodValue) {
700
+ if (hasOwnProperty(knownGoodValue, key)) {
701
+ oldKeyCount++;
702
+ }
703
+ }
704
+ if (oldKeyCount !== newKeyCount) {
705
+ isDifferent = true;
706
+ }
707
+ }
708
+ return isDifferent ? newValue : knownGoodValue;
709
+ }
710
+ );
711
+ this.keyValidator = keyValidator;
712
+ this.valueValidator = valueValidator;
713
+ }
714
+ keyValidator;
715
+ valueValidator;
716
+ };
717
+ function typeofValidator(type) {
718
+ return new Validator((value) => {
719
+ if (typeof value !== type) {
720
+ throw new ValidationError(`Expected ${type}, got ${typeToString(value)}`);
721
+ }
722
+ return value;
723
+ });
724
+ }
725
+ var unknown = new Validator((value) => value);
726
+ var any = new Validator((value) => value);
727
+ var string = typeofValidator("string");
728
+ var number = new Validator((value) => {
729
+ if (Number.isFinite(value)) {
730
+ return value;
731
+ }
732
+ if (typeof value !== "number") {
733
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
734
+ }
735
+ if (value !== value) {
736
+ throw new ValidationError("Expected a number, got NaN");
737
+ }
738
+ throw new ValidationError(`Expected a finite number, got ${value}`);
739
+ });
740
+ var positiveNumber = new Validator((value) => {
741
+ if (Number.isFinite(value) && value >= 0) {
742
+ return value;
743
+ }
744
+ if (typeof value !== "number") {
745
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
746
+ }
747
+ if (value !== value) {
748
+ throw new ValidationError("Expected a number, got NaN");
749
+ }
750
+ if (value < 0) {
751
+ throw new ValidationError(`Expected a positive number, got ${value}`);
752
+ }
753
+ throw new ValidationError(`Expected a finite number, got ${value}`);
754
+ });
755
+ var nonZeroNumber = new Validator((value) => {
756
+ if (Number.isFinite(value) && value > 0) {
757
+ return value;
758
+ }
759
+ if (typeof value !== "number") {
760
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
761
+ }
762
+ if (value !== value) {
763
+ throw new ValidationError("Expected a number, got NaN");
764
+ }
765
+ if (value <= 0) {
766
+ throw new ValidationError(`Expected a non-zero positive number, got ${value}`);
767
+ }
768
+ throw new ValidationError(`Expected a finite number, got ${value}`);
769
+ });
770
+ var nonZeroFiniteNumber = new Validator((value) => {
771
+ if (Number.isFinite(value) && value !== 0) {
772
+ return value;
773
+ }
774
+ if (typeof value !== "number") {
775
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
776
+ }
777
+ if (value !== value) {
778
+ throw new ValidationError("Expected a number, got NaN");
779
+ }
780
+ if (value === 0) {
781
+ throw new ValidationError(`Expected a non-zero number, got 0`);
782
+ }
783
+ throw new ValidationError(`Expected a finite number, got ${value}`);
784
+ });
785
+ var unitInterval = new Validator((value) => {
786
+ if (Number.isFinite(value) && value >= 0 && value <= 1) {
787
+ return value;
788
+ }
789
+ if (typeof value !== "number") {
790
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
791
+ }
792
+ if (value !== value) {
793
+ throw new ValidationError("Expected a number, got NaN");
794
+ }
795
+ throw new ValidationError(`Expected a number between 0 and 1, got ${value}`);
796
+ });
797
+ var integer = new Validator((value) => {
798
+ if (Number.isInteger(value)) {
799
+ return value;
800
+ }
801
+ if (typeof value !== "number") {
802
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
803
+ }
804
+ if (value !== value) {
805
+ throw new ValidationError("Expected a number, got NaN");
806
+ }
807
+ if (value - value !== 0) {
808
+ throw new ValidationError(`Expected a finite number, got ${value}`);
809
+ }
810
+ throw new ValidationError(`Expected an integer, got ${value}`);
811
+ });
812
+ var positiveInteger = new Validator((value) => {
813
+ if (Number.isInteger(value) && value >= 0) {
814
+ return value;
815
+ }
816
+ if (typeof value !== "number") {
817
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
818
+ }
819
+ if (value !== value) {
820
+ throw new ValidationError("Expected a number, got NaN");
821
+ }
822
+ if (value - value !== 0) {
823
+ throw new ValidationError(`Expected a finite number, got ${value}`);
824
+ }
825
+ if (value < 0) {
826
+ throw new ValidationError(`Expected a positive integer, got ${value}`);
827
+ }
828
+ throw new ValidationError(`Expected an integer, got ${value}`);
829
+ });
830
+ var nonZeroInteger = new Validator((value) => {
831
+ if (Number.isInteger(value) && value > 0) {
832
+ return value;
833
+ }
834
+ if (typeof value !== "number") {
835
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
836
+ }
837
+ if (value !== value) {
838
+ throw new ValidationError("Expected a number, got NaN");
839
+ }
840
+ if (value - value !== 0) {
841
+ throw new ValidationError(`Expected a finite number, got ${value}`);
842
+ }
843
+ if (value <= 0) {
844
+ throw new ValidationError(`Expected a non-zero positive integer, got ${value}`);
845
+ }
846
+ throw new ValidationError(`Expected an integer, got ${value}`);
847
+ });
848
+ var boolean = typeofValidator("boolean");
849
+ var bigint = typeofValidator("bigint");
850
+ function literal(expectedValue) {
851
+ return new Validator((actualValue) => {
852
+ if (actualValue !== expectedValue) {
853
+ throw new ValidationError(`Expected ${expectedValue}, got ${JSON.stringify(actualValue)}`);
854
+ }
855
+ return expectedValue;
856
+ });
857
+ }
858
+ var array = new Validator((value) => {
859
+ if (!Array.isArray(value)) {
860
+ throw new ValidationError(`Expected an array, got ${typeToString(value)}`);
861
+ }
862
+ return value;
863
+ });
864
+ function arrayOf(itemValidator) {
865
+ return new ArrayOfValidator(itemValidator);
866
+ }
867
+ var unknownObject = new Validator((value) => {
868
+ if (typeof value !== "object" || value === null) {
869
+ throw new ValidationError(`Expected object, got ${typeToString(value)}`);
870
+ }
871
+ return value;
872
+ });
873
+ function object(config) {
874
+ return new ObjectValidator(config);
875
+ }
876
+ function isPlainObject(value) {
877
+ return typeof value === "object" && value !== null && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null || Object.getPrototypeOf(value) === STRUCTURED_CLONE_OBJECT_PROTOTYPE);
878
+ }
879
+ function isValidJson(value) {
880
+ if (value === null || typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
881
+ return true;
882
+ }
883
+ if (Array.isArray(value)) {
884
+ return value.every(isValidJson);
885
+ }
886
+ if (isPlainObject(value)) {
887
+ return Object.values(value).every(isValidJson);
888
+ }
889
+ return false;
890
+ }
891
+ var jsonValue = new Validator(
892
+ (value) => {
893
+ if (isValidJson(value)) {
894
+ return value;
895
+ }
896
+ throw new ValidationError(`Expected json serializable value, got ${typeof value}`);
897
+ },
898
+ (knownGoodValue, newValue) => {
899
+ if (Array.isArray(knownGoodValue) && Array.isArray(newValue)) {
900
+ let isDifferent = knownGoodValue.length !== newValue.length;
901
+ for (let i = 0; i < newValue.length; i++) {
902
+ if (i >= knownGoodValue.length) {
903
+ isDifferent = true;
904
+ jsonValue.validate(newValue[i]);
905
+ continue;
906
+ }
907
+ const prev = knownGoodValue[i];
908
+ const next = newValue[i];
909
+ if (Object.is(prev, next)) {
910
+ continue;
911
+ }
912
+ const checked = jsonValue.validateUsingKnownGoodVersion(prev, next);
913
+ if (!Object.is(checked, prev)) {
914
+ isDifferent = true;
915
+ }
916
+ }
917
+ return isDifferent ? newValue : knownGoodValue;
918
+ } else if (isPlainObject(knownGoodValue) && isPlainObject(newValue)) {
919
+ let isDifferent = false;
920
+ for (const key of Object.keys(newValue)) {
921
+ if (!hasOwnProperty(knownGoodValue, key)) {
922
+ isDifferent = true;
923
+ jsonValue.validate(newValue[key]);
924
+ continue;
925
+ }
926
+ const prev = knownGoodValue[key];
927
+ const next = newValue[key];
928
+ if (Object.is(prev, next)) {
929
+ continue;
930
+ }
931
+ const checked = jsonValue.validateUsingKnownGoodVersion(prev, next);
932
+ if (!Object.is(checked, prev)) {
933
+ isDifferent = true;
934
+ }
935
+ }
936
+ for (const key of Object.keys(knownGoodValue)) {
937
+ if (!hasOwnProperty(newValue, key)) {
938
+ isDifferent = true;
939
+ break;
940
+ }
941
+ }
942
+ return isDifferent ? newValue : knownGoodValue;
943
+ } else {
944
+ return jsonValue.validate(newValue);
945
+ }
946
+ }
947
+ );
948
+ function jsonDict() {
949
+ return dict(string, jsonValue);
950
+ }
951
+ function dict(keyValidator, valueValidator) {
952
+ return new DictValidator(keyValidator, valueValidator);
953
+ }
954
+ function union(key, config) {
955
+ return new UnionValidator(
956
+ key,
957
+ config,
958
+ (_unknownValue, unknownVariant) => {
959
+ throw new ValidationError(
960
+ `Expected one of ${Object.keys(config).map((key2) => JSON.stringify(key2)).join(" or ")}, got ${JSON.stringify(unknownVariant)}`,
961
+ [key]
962
+ );
963
+ },
964
+ false
965
+ );
966
+ }
967
+ function numberUnion(key, config) {
968
+ return new UnionValidator(
969
+ key,
970
+ config,
971
+ (unknownValue, unknownVariant) => {
972
+ throw new ValidationError(
973
+ `Expected one of ${Object.keys(config).map((key2) => JSON.stringify(key2)).join(" or ")}, got ${JSON.stringify(unknownVariant)}`,
974
+ [key]
975
+ );
976
+ },
977
+ true
978
+ );
979
+ }
980
+ function model(name, validator) {
981
+ return new Validator(
982
+ (value) => {
983
+ return prefixError(name, () => validator.validate(value));
984
+ },
985
+ (prevValue, newValue) => {
986
+ return prefixError(name, () => {
987
+ if (validator.validateUsingKnownGoodVersion) {
988
+ return validator.validateUsingKnownGoodVersion(prevValue, newValue);
989
+ } else {
990
+ return validator.validate(newValue);
991
+ }
992
+ });
993
+ }
994
+ );
995
+ }
996
+ function setEnum(values) {
997
+ return new Validator((value) => {
998
+ if (!values.has(value)) {
999
+ const valuesString = Array.from(values, (value2) => JSON.stringify(value2)).join(" or ");
1000
+ throw new ValidationError(`Expected ${valuesString}, got ${value}`);
1001
+ }
1002
+ return value;
1003
+ });
1004
+ }
1005
+ function optional(validator) {
1006
+ return new Validator(
1007
+ (value) => {
1008
+ if (value === void 0) return void 0;
1009
+ return validator.validate(value);
1010
+ },
1011
+ (knownGoodValue, newValue) => {
1012
+ if (newValue === void 0) return void 0;
1013
+ if (validator.validateUsingKnownGoodVersion && knownGoodValue !== void 0) {
1014
+ return validator.validateUsingKnownGoodVersion(knownGoodValue, newValue);
1015
+ }
1016
+ return validator.validate(newValue);
1017
+ },
1018
+ // Propagate skipSameValueCheck from inner validator to allow refine wrappers
1019
+ validator instanceof Validator && validator.skipSameValueCheck
1020
+ );
1021
+ }
1022
+ function nullable(validator) {
1023
+ return new Validator(
1024
+ (value) => {
1025
+ if (value === null) return null;
1026
+ return validator.validate(value);
1027
+ },
1028
+ (knownGoodValue, newValue) => {
1029
+ if (newValue === null) return null;
1030
+ if (validator.validateUsingKnownGoodVersion && knownGoodValue !== null) {
1031
+ return validator.validateUsingKnownGoodVersion(knownGoodValue, newValue);
1032
+ }
1033
+ return validator.validate(newValue);
1034
+ },
1035
+ // Propagate skipSameValueCheck from inner validator to allow refine wrappers
1036
+ validator instanceof Validator && validator.skipSameValueCheck
1037
+ );
1038
+ }
1039
+ function literalEnum(...values) {
1040
+ return setEnum(new Set(values));
1041
+ }
1042
+ function parseUrl(str) {
1043
+ try {
1044
+ return new URL(str);
1045
+ } catch {
1046
+ if (str.startsWith("/") || str.startsWith("./")) {
1047
+ try {
1048
+ return new URL(str, "http://example.com");
1049
+ } catch {
1050
+ throw new ValidationError(`Expected a valid url, got ${JSON.stringify(str)}`);
1051
+ }
1052
+ }
1053
+ throw new ValidationError(`Expected a valid url, got ${JSON.stringify(str)}`);
1054
+ }
1055
+ }
1056
+ var validLinkProtocols = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
1057
+ var linkUrl = string.check((value) => {
1058
+ if (value === "") return;
1059
+ const url = parseUrl(value);
1060
+ if (!validLinkProtocols.has(url.protocol.toLowerCase())) {
1061
+ throw new ValidationError(
1062
+ `Expected a valid url, got ${JSON.stringify(value)} (invalid protocol)`
1063
+ );
1064
+ }
1065
+ });
1066
+ var validSrcProtocols = /* @__PURE__ */ new Set(["http:", "https:", "data:", "asset:"]);
1067
+ var srcUrl = string.check((value) => {
1068
+ if (value === "") return;
1069
+ const url = parseUrl(value);
1070
+ if (!validSrcProtocols.has(url.protocol.toLowerCase())) {
1071
+ throw new ValidationError(
1072
+ `Expected a valid url, got ${JSON.stringify(value)} (invalid protocol)`
1073
+ );
1074
+ }
1075
+ });
1076
+ var httpUrl = string.check((value) => {
1077
+ if (value === "") return;
1078
+ const url = parseUrl(value);
1079
+ if (!url.protocol.toLowerCase().match(/^https?:$/)) {
1080
+ throw new ValidationError(
1081
+ `Expected a valid url, got ${JSON.stringify(value)} (invalid protocol)`
1082
+ );
1083
+ }
1084
+ });
1085
+ var indexKey = string.refine((key) => {
1086
+ try {
1087
+ validateIndexKey(key);
1088
+ return key;
1089
+ } catch {
1090
+ throw new ValidationError(`Expected an index key, got ${JSON.stringify(key)}`);
1091
+ }
1092
+ });
1093
+ function or(v1, v2) {
1094
+ return new Validator((value) => {
1095
+ try {
1096
+ return v1.validate(value);
1097
+ } catch {
1098
+ return v2.validate(value);
1099
+ }
1100
+ });
1101
+ }
1102
+
1103
+ // src/index.ts
1104
+ registerDrawLibraryVersion(
1105
+ "@ibodr/validate",
1106
+ "0.0.0",
1107
+ "esm"
1108
+ );
1109
+
1110
+ export { ArrayOfValidator, DictValidator, ObjectValidator, validation_exports as T, UnionValidator, Validator };
1111
+ //# sourceMappingURL=index.mjs.map
1112
+ //# sourceMappingURL=index.mjs.map