@tldraw/validate 4.1.0-next.b6dfe9bccde9 → 4.1.0-next.b73a0d46b63f
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-cjs/index.d.ts +771 -77
- package/dist-cjs/index.js +2 -2
- package/dist-cjs/lib/validation.js +226 -15
- package/dist-cjs/lib/validation.js.map +2 -2
- package/dist-esm/index.d.mts +771 -77
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/validation.mjs +226 -15
- package/dist-esm/lib/validation.mjs.map +2 -2
- package/package.json +2 -2
- package/src/lib/validation.ts +778 -78
- package/src/test/validation.test.ts +54 -3
package/dist-esm/index.mjs
CHANGED
|
@@ -30,6 +30,12 @@ function formatPath(path) {
|
|
|
30
30
|
return formattedPath;
|
|
31
31
|
}
|
|
32
32
|
class ValidationError extends Error {
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new ValidationError with contextual information about where the error occurred.
|
|
35
|
+
*
|
|
36
|
+
* rawMessage - The raw error message without path information
|
|
37
|
+
* path - Array indicating the location in the data structure where validation failed
|
|
38
|
+
*/
|
|
33
39
|
constructor(rawMessage, path = []) {
|
|
34
40
|
const formattedPath = formatPath(path);
|
|
35
41
|
const indentedMessage = rawMessage.split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
|
|
@@ -70,13 +76,35 @@ function typeToString(value) {
|
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
class Validator {
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new Validator instance.
|
|
81
|
+
*
|
|
82
|
+
* validationFn - Function that validates and returns a value of type T
|
|
83
|
+
* validateUsingKnownGoodVersionFn - Optional performance-optimized validation function
|
|
84
|
+
*/
|
|
73
85
|
constructor(validationFn, validateUsingKnownGoodVersionFn) {
|
|
74
86
|
this.validationFn = validationFn;
|
|
75
87
|
this.validateUsingKnownGoodVersionFn = validateUsingKnownGoodVersionFn;
|
|
76
88
|
}
|
|
77
89
|
/**
|
|
78
|
-
*
|
|
90
|
+
* Validates an unknown value and returns it with the correct type. The returned value is
|
|
79
91
|
* guaranteed to be referentially equal to the passed value.
|
|
92
|
+
*
|
|
93
|
+
* @param value - The unknown value to validate
|
|
94
|
+
* @returns The validated value with type T
|
|
95
|
+
* @throws ValidationError When validation fails
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* import { T } from '@tldraw/validate'
|
|
99
|
+
*
|
|
100
|
+
* const name = T.string.validate("Alice") // Returns "Alice" as string
|
|
101
|
+
* const title = T.string.validate("") // Returns "" (empty strings are valid)
|
|
102
|
+
*
|
|
103
|
+
* // These will throw ValidationError:
|
|
104
|
+
* T.string.validate(123) // Expected string, got a number
|
|
105
|
+
* T.string.validate(null) // Expected string, got null
|
|
106
|
+
* T.string.validate(undefined) // Expected string, got undefined
|
|
107
|
+
* ```
|
|
80
108
|
*/
|
|
81
109
|
validate(value) {
|
|
82
110
|
const validated = this.validationFn(value);
|
|
@@ -85,6 +113,31 @@ class Validator {
|
|
|
85
113
|
}
|
|
86
114
|
return validated;
|
|
87
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Performance-optimized validation using a previously validated value. If the new value
|
|
118
|
+
* is referentially equal to the known good value, returns the known good value immediately.
|
|
119
|
+
*
|
|
120
|
+
* @param knownGoodValue - A previously validated value
|
|
121
|
+
* @param newValue - The new value to validate
|
|
122
|
+
* @returns The validated value, potentially reusing the known good value
|
|
123
|
+
* @throws ValidationError When validation fails
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { T } from '@tldraw/validate'
|
|
127
|
+
*
|
|
128
|
+
* const userValidator = T.object({
|
|
129
|
+
* name: T.string,
|
|
130
|
+
* settings: T.object({ theme: T.literalEnum('light', 'dark') })
|
|
131
|
+
* })
|
|
132
|
+
*
|
|
133
|
+
* const user = userValidator.validate({ name: "Alice", settings: { theme: "light" } })
|
|
134
|
+
*
|
|
135
|
+
* // Later, with partially changed data:
|
|
136
|
+
* const newData = { name: "Alice", settings: { theme: "dark" } }
|
|
137
|
+
* const updated = userValidator.validateUsingKnownGoodVersion(user, newData)
|
|
138
|
+
* // Only validates the changed 'theme' field for better performance
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
88
141
|
validateUsingKnownGoodVersion(knownGoodValue, newValue) {
|
|
89
142
|
if (Object.is(knownGoodValue, newValue)) {
|
|
90
143
|
return knownGoodValue;
|
|
@@ -94,7 +147,28 @@ class Validator {
|
|
|
94
147
|
}
|
|
95
148
|
return this.validate(newValue);
|
|
96
149
|
}
|
|
97
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Type guard that checks if a value is valid without throwing an error.
|
|
152
|
+
*
|
|
153
|
+
* @param value - The value to check
|
|
154
|
+
* @returns True if the value is valid, false otherwise
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* import { T } from '@tldraw/validate'
|
|
158
|
+
*
|
|
159
|
+
* function processUserInput(input: unknown) {
|
|
160
|
+
* if (T.string.isValid(input)) {
|
|
161
|
+
* // input is now typed as string within this block
|
|
162
|
+
* return input.toUpperCase()
|
|
163
|
+
* }
|
|
164
|
+
* if (T.number.isValid(input)) {
|
|
165
|
+
* // input is now typed as number within this block
|
|
166
|
+
* return input.toFixed(2)
|
|
167
|
+
* }
|
|
168
|
+
* throw new Error('Expected string or number')
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
98
172
|
isValid(value) {
|
|
99
173
|
try {
|
|
100
174
|
this.validate(value);
|
|
@@ -104,22 +178,85 @@ class Validator {
|
|
|
104
178
|
}
|
|
105
179
|
}
|
|
106
180
|
/**
|
|
107
|
-
* Returns a new validator that also accepts null
|
|
108
|
-
*
|
|
181
|
+
* Returns a new validator that also accepts null values.
|
|
182
|
+
*
|
|
183
|
+
* @returns A new validator that accepts T or null
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* import { T } from '@tldraw/validate'
|
|
187
|
+
*
|
|
188
|
+
* const assetValidator = T.object({
|
|
189
|
+
* id: T.string,
|
|
190
|
+
* name: T.string,
|
|
191
|
+
* src: T.srcUrl.nullable(), // Can be null if not loaded yet
|
|
192
|
+
* mimeType: T.string.nullable()
|
|
193
|
+
* })
|
|
194
|
+
*
|
|
195
|
+
* const asset = assetValidator.validate({
|
|
196
|
+
* id: "image-123",
|
|
197
|
+
* name: "photo.jpg",
|
|
198
|
+
* src: null, // Valid - asset not loaded yet
|
|
199
|
+
* mimeType: "image/jpeg"
|
|
200
|
+
* })
|
|
201
|
+
* ```
|
|
109
202
|
*/
|
|
110
203
|
nullable() {
|
|
111
204
|
return nullable(this);
|
|
112
205
|
}
|
|
113
206
|
/**
|
|
114
|
-
* Returns a new validator that also accepts
|
|
115
|
-
*
|
|
207
|
+
* Returns a new validator that also accepts undefined values.
|
|
208
|
+
*
|
|
209
|
+
* @returns A new validator that accepts T or undefined
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* import { T } from '@tldraw/validate'
|
|
213
|
+
*
|
|
214
|
+
* const shapeConfigValidator = T.object({
|
|
215
|
+
* type: T.literal('rectangle'),
|
|
216
|
+
* x: T.number,
|
|
217
|
+
* y: T.number,
|
|
218
|
+
* label: T.string.optional(), // Optional property
|
|
219
|
+
* metadata: T.object({ created: T.string }).optional()
|
|
220
|
+
* })
|
|
221
|
+
*
|
|
222
|
+
* // Both of these are valid:
|
|
223
|
+
* const shape1 = shapeConfigValidator.validate({ type: 'rectangle', x: 0, y: 0 })
|
|
224
|
+
* const shape2 = shapeConfigValidator.validate({
|
|
225
|
+
* type: 'rectangle', x: 0, y: 0, label: "My Shape"
|
|
226
|
+
* })
|
|
227
|
+
* ```
|
|
116
228
|
*/
|
|
117
229
|
optional() {
|
|
118
230
|
return optional(this);
|
|
119
231
|
}
|
|
120
232
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
233
|
+
* Creates a new validator by refining this validator with additional logic that can transform
|
|
234
|
+
* the validated value to a new type.
|
|
235
|
+
*
|
|
236
|
+
* @param otherValidationFn - Function that transforms/validates the value to type U
|
|
237
|
+
* @returns A new validator that validates to type U
|
|
238
|
+
* @throws ValidationError When validation or refinement fails
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* import { T, ValidationError } from '@tldraw/validate'
|
|
242
|
+
*
|
|
243
|
+
* // Transform string to ensure it starts with a prefix
|
|
244
|
+
* const prefixedIdValidator = T.string.refine((id) => {
|
|
245
|
+
* return id.startsWith('shape:') ? id : `shape:${id}`
|
|
246
|
+
* })
|
|
247
|
+
*
|
|
248
|
+
* const id1 = prefixedIdValidator.validate("rectangle-123") // Returns "shape:rectangle-123"
|
|
249
|
+
* const id2 = prefixedIdValidator.validate("shape:circle-456") // Returns "shape:circle-456"
|
|
250
|
+
*
|
|
251
|
+
* // Parse and validate JSON strings
|
|
252
|
+
* const jsonValidator = T.string.refine((str) => {
|
|
253
|
+
* try {
|
|
254
|
+
* return JSON.parse(str)
|
|
255
|
+
* } catch {
|
|
256
|
+
* throw new ValidationError('Invalid JSON string')
|
|
257
|
+
* }
|
|
258
|
+
* })
|
|
259
|
+
* ```
|
|
123
260
|
*/
|
|
124
261
|
refine(otherValidationFn) {
|
|
125
262
|
return new Validator(
|
|
@@ -150,6 +287,11 @@ class Validator {
|
|
|
150
287
|
}
|
|
151
288
|
}
|
|
152
289
|
class ArrayOfValidator extends Validator {
|
|
290
|
+
/**
|
|
291
|
+
* Creates a new ArrayOfValidator.
|
|
292
|
+
*
|
|
293
|
+
* itemValidator - Validator used to validate each array element
|
|
294
|
+
*/
|
|
153
295
|
constructor(itemValidator) {
|
|
154
296
|
super(
|
|
155
297
|
(value) => {
|
|
@@ -186,6 +328,18 @@ class ArrayOfValidator extends Validator {
|
|
|
186
328
|
);
|
|
187
329
|
this.itemValidator = itemValidator;
|
|
188
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Returns a new validator that ensures the array is not empty.
|
|
333
|
+
*
|
|
334
|
+
* @returns A new validator that rejects empty arrays
|
|
335
|
+
* @throws ValidationError When the array is empty
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* const nonEmptyStrings = T.arrayOf(T.string).nonEmpty()
|
|
339
|
+
* nonEmptyStrings.validate(["hello"]) // Valid
|
|
340
|
+
* nonEmptyStrings.validate([]) // Throws ValidationError
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
189
343
|
nonEmpty() {
|
|
190
344
|
return this.check((value) => {
|
|
191
345
|
if (value.length === 0) {
|
|
@@ -193,6 +347,18 @@ class ArrayOfValidator extends Validator {
|
|
|
193
347
|
}
|
|
194
348
|
});
|
|
195
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Returns a new validator that ensures the array has more than one element.
|
|
352
|
+
*
|
|
353
|
+
* @returns A new validator that requires at least 2 elements
|
|
354
|
+
* @throws ValidationError When the array has 1 or fewer elements
|
|
355
|
+
* @example
|
|
356
|
+
* ```ts
|
|
357
|
+
* const multipleItems = T.arrayOf(T.string).lengthGreaterThan1()
|
|
358
|
+
* multipleItems.validate(["a", "b"]) // Valid
|
|
359
|
+
* multipleItems.validate(["a"]) // Throws ValidationError
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
196
362
|
lengthGreaterThan1() {
|
|
197
363
|
return this.check((value) => {
|
|
198
364
|
if (value.length <= 1) {
|
|
@@ -202,6 +368,12 @@ class ArrayOfValidator extends Validator {
|
|
|
202
368
|
}
|
|
203
369
|
}
|
|
204
370
|
class ObjectValidator extends Validator {
|
|
371
|
+
/**
|
|
372
|
+
* Creates a new ObjectValidator.
|
|
373
|
+
*
|
|
374
|
+
* config - Object mapping property names to their validators
|
|
375
|
+
* shouldAllowUnknownProperties - Whether to allow properties not defined in config
|
|
376
|
+
*/
|
|
205
377
|
constructor(config, shouldAllowUnknownProperties = false) {
|
|
206
378
|
super(
|
|
207
379
|
(object2) => {
|
|
@@ -265,21 +437,32 @@ class ObjectValidator extends Validator {
|
|
|
265
437
|
this.config = config;
|
|
266
438
|
this.shouldAllowUnknownProperties = shouldAllowUnknownProperties;
|
|
267
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Returns a new validator that allows unknown properties in the validated object.
|
|
442
|
+
*
|
|
443
|
+
* @returns A new ObjectValidator that accepts extra properties
|
|
444
|
+
* @example
|
|
445
|
+
* ```ts
|
|
446
|
+
* const flexibleUser = T.object({ name: T.string }).allowUnknownProperties()
|
|
447
|
+
* flexibleUser.validate({ name: "Alice", extra: "allowed" }) // Valid
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
268
450
|
allowUnknownProperties() {
|
|
269
451
|
return new ObjectValidator(this.config, true);
|
|
270
452
|
}
|
|
271
453
|
/**
|
|
272
|
-
*
|
|
454
|
+
* Creates a new ObjectValidator by extending this validator with additional properties.
|
|
273
455
|
*
|
|
456
|
+
* @param extension - Object mapping new property names to their validators
|
|
457
|
+
* @returns A new ObjectValidator that validates both original and extended properties
|
|
274
458
|
* @example
|
|
275
|
-
*
|
|
276
459
|
* ```ts
|
|
277
|
-
* const
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* meowVolume: T.number,
|
|
460
|
+
* const baseUser = T.object({ name: T.string, age: T.number })
|
|
461
|
+
* const adminUser = baseUser.extend({
|
|
462
|
+
* permissions: T.arrayOf(T.string),
|
|
463
|
+
* isAdmin: T.boolean
|
|
282
464
|
* })
|
|
465
|
+
* // adminUser validates: { name: string; age: number; permissions: string[]; isAdmin: boolean }
|
|
283
466
|
* ```
|
|
284
467
|
*/
|
|
285
468
|
extend(extension) {
|
|
@@ -287,6 +470,14 @@ class ObjectValidator extends Validator {
|
|
|
287
470
|
}
|
|
288
471
|
}
|
|
289
472
|
class UnionValidator extends Validator {
|
|
473
|
+
/**
|
|
474
|
+
* Creates a new UnionValidator.
|
|
475
|
+
*
|
|
476
|
+
* key - The discriminator property name used to determine the variant
|
|
477
|
+
* config - Object mapping variant names to their validators
|
|
478
|
+
* unknownValueValidation - Function to handle unknown variants
|
|
479
|
+
* useNumberKeys - Whether the discriminator uses number keys instead of strings
|
|
480
|
+
*/
|
|
290
481
|
constructor(key, config, unknownValueValidation, useNumberKeys) {
|
|
291
482
|
super(
|
|
292
483
|
(input) => {
|
|
@@ -338,11 +529,31 @@ class UnionValidator extends Validator {
|
|
|
338
529
|
const matchingSchema = hasOwnProperty(this.config, variant) ? this.config[variant] : void 0;
|
|
339
530
|
return { matchingSchema, variant };
|
|
340
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Returns a new UnionValidator that can handle unknown variants using the provided function.
|
|
534
|
+
*
|
|
535
|
+
* @param unknownValueValidation - Function to validate/transform unknown variants
|
|
536
|
+
* @returns A new UnionValidator that accepts unknown variants
|
|
537
|
+
* @example
|
|
538
|
+
* ```ts
|
|
539
|
+
* const shapeValidator = T.union('type', { circle: circleValidator })
|
|
540
|
+
* .validateUnknownVariants((obj, variant) => {
|
|
541
|
+
* console.warn(`Unknown shape type: ${variant}`)
|
|
542
|
+
* return obj as UnknownShape
|
|
543
|
+
* })
|
|
544
|
+
* ```
|
|
545
|
+
*/
|
|
341
546
|
validateUnknownVariants(unknownValueValidation) {
|
|
342
547
|
return new UnionValidator(this.key, this.config, unknownValueValidation, this.useNumberKeys);
|
|
343
548
|
}
|
|
344
549
|
}
|
|
345
550
|
class DictValidator extends Validator {
|
|
551
|
+
/**
|
|
552
|
+
* Creates a new DictValidator.
|
|
553
|
+
*
|
|
554
|
+
* keyValidator - Validator for object keys
|
|
555
|
+
* valueValidator - Validator for object values
|
|
556
|
+
*/
|
|
346
557
|
constructor(keyValidator, valueValidator) {
|
|
347
558
|
super(
|
|
348
559
|
(object2) => {
|