@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-cjs/index.js
CHANGED
|
@@ -37,11 +37,11 @@ __export(index_exports, {
|
|
|
37
37
|
});
|
|
38
38
|
module.exports = __toCommonJS(index_exports);
|
|
39
39
|
var import_utils = require("@tldraw/utils");
|
|
40
|
-
var T = __toESM(require("./lib/validation"));
|
|
40
|
+
var T = __toESM(require("./lib/validation"), 1);
|
|
41
41
|
var import_validation = require("./lib/validation");
|
|
42
42
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
43
43
|
"@tldraw/validate",
|
|
44
|
-
"4.1.0-next.
|
|
44
|
+
"4.1.0-next.b73a0d46b63f",
|
|
45
45
|
"cjs"
|
|
46
46
|
);
|
|
47
47
|
//# sourceMappingURL=index.js.map
|
|
@@ -83,6 +83,12 @@ function formatPath(path) {
|
|
|
83
83
|
return formattedPath;
|
|
84
84
|
}
|
|
85
85
|
class ValidationError extends Error {
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new ValidationError with contextual information about where the error occurred.
|
|
88
|
+
*
|
|
89
|
+
* rawMessage - The raw error message without path information
|
|
90
|
+
* path - Array indicating the location in the data structure where validation failed
|
|
91
|
+
*/
|
|
86
92
|
constructor(rawMessage, path = []) {
|
|
87
93
|
const formattedPath = formatPath(path);
|
|
88
94
|
const indentedMessage = rawMessage.split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
|
|
@@ -123,13 +129,35 @@ function typeToString(value) {
|
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
class Validator {
|
|
132
|
+
/**
|
|
133
|
+
* Creates a new Validator instance.
|
|
134
|
+
*
|
|
135
|
+
* validationFn - Function that validates and returns a value of type T
|
|
136
|
+
* validateUsingKnownGoodVersionFn - Optional performance-optimized validation function
|
|
137
|
+
*/
|
|
126
138
|
constructor(validationFn, validateUsingKnownGoodVersionFn) {
|
|
127
139
|
this.validationFn = validationFn;
|
|
128
140
|
this.validateUsingKnownGoodVersionFn = validateUsingKnownGoodVersionFn;
|
|
129
141
|
}
|
|
130
142
|
/**
|
|
131
|
-
*
|
|
143
|
+
* Validates an unknown value and returns it with the correct type. The returned value is
|
|
132
144
|
* guaranteed to be referentially equal to the passed value.
|
|
145
|
+
*
|
|
146
|
+
* @param value - The unknown value to validate
|
|
147
|
+
* @returns The validated value with type T
|
|
148
|
+
* @throws ValidationError When validation fails
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { T } from '@tldraw/validate'
|
|
152
|
+
*
|
|
153
|
+
* const name = T.string.validate("Alice") // Returns "Alice" as string
|
|
154
|
+
* const title = T.string.validate("") // Returns "" (empty strings are valid)
|
|
155
|
+
*
|
|
156
|
+
* // These will throw ValidationError:
|
|
157
|
+
* T.string.validate(123) // Expected string, got a number
|
|
158
|
+
* T.string.validate(null) // Expected string, got null
|
|
159
|
+
* T.string.validate(undefined) // Expected string, got undefined
|
|
160
|
+
* ```
|
|
133
161
|
*/
|
|
134
162
|
validate(value) {
|
|
135
163
|
const validated = this.validationFn(value);
|
|
@@ -138,6 +166,31 @@ class Validator {
|
|
|
138
166
|
}
|
|
139
167
|
return validated;
|
|
140
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Performance-optimized validation using a previously validated value. If the new value
|
|
171
|
+
* is referentially equal to the known good value, returns the known good value immediately.
|
|
172
|
+
*
|
|
173
|
+
* @param knownGoodValue - A previously validated value
|
|
174
|
+
* @param newValue - The new value to validate
|
|
175
|
+
* @returns The validated value, potentially reusing the known good value
|
|
176
|
+
* @throws ValidationError When validation fails
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* import { T } from '@tldraw/validate'
|
|
180
|
+
*
|
|
181
|
+
* const userValidator = T.object({
|
|
182
|
+
* name: T.string,
|
|
183
|
+
* settings: T.object({ theme: T.literalEnum('light', 'dark') })
|
|
184
|
+
* })
|
|
185
|
+
*
|
|
186
|
+
* const user = userValidator.validate({ name: "Alice", settings: { theme: "light" } })
|
|
187
|
+
*
|
|
188
|
+
* // Later, with partially changed data:
|
|
189
|
+
* const newData = { name: "Alice", settings: { theme: "dark" } }
|
|
190
|
+
* const updated = userValidator.validateUsingKnownGoodVersion(user, newData)
|
|
191
|
+
* // Only validates the changed 'theme' field for better performance
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
141
194
|
validateUsingKnownGoodVersion(knownGoodValue, newValue) {
|
|
142
195
|
if (Object.is(knownGoodValue, newValue)) {
|
|
143
196
|
return knownGoodValue;
|
|
@@ -147,7 +200,28 @@ class Validator {
|
|
|
147
200
|
}
|
|
148
201
|
return this.validate(newValue);
|
|
149
202
|
}
|
|
150
|
-
/**
|
|
203
|
+
/**
|
|
204
|
+
* Type guard that checks if a value is valid without throwing an error.
|
|
205
|
+
*
|
|
206
|
+
* @param value - The value to check
|
|
207
|
+
* @returns True if the value is valid, false otherwise
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* import { T } from '@tldraw/validate'
|
|
211
|
+
*
|
|
212
|
+
* function processUserInput(input: unknown) {
|
|
213
|
+
* if (T.string.isValid(input)) {
|
|
214
|
+
* // input is now typed as string within this block
|
|
215
|
+
* return input.toUpperCase()
|
|
216
|
+
* }
|
|
217
|
+
* if (T.number.isValid(input)) {
|
|
218
|
+
* // input is now typed as number within this block
|
|
219
|
+
* return input.toFixed(2)
|
|
220
|
+
* }
|
|
221
|
+
* throw new Error('Expected string or number')
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
151
225
|
isValid(value) {
|
|
152
226
|
try {
|
|
153
227
|
this.validate(value);
|
|
@@ -157,22 +231,85 @@ class Validator {
|
|
|
157
231
|
}
|
|
158
232
|
}
|
|
159
233
|
/**
|
|
160
|
-
* Returns a new validator that also accepts null
|
|
161
|
-
*
|
|
234
|
+
* Returns a new validator that also accepts null values.
|
|
235
|
+
*
|
|
236
|
+
* @returns A new validator that accepts T or null
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* import { T } from '@tldraw/validate'
|
|
240
|
+
*
|
|
241
|
+
* const assetValidator = T.object({
|
|
242
|
+
* id: T.string,
|
|
243
|
+
* name: T.string,
|
|
244
|
+
* src: T.srcUrl.nullable(), // Can be null if not loaded yet
|
|
245
|
+
* mimeType: T.string.nullable()
|
|
246
|
+
* })
|
|
247
|
+
*
|
|
248
|
+
* const asset = assetValidator.validate({
|
|
249
|
+
* id: "image-123",
|
|
250
|
+
* name: "photo.jpg",
|
|
251
|
+
* src: null, // Valid - asset not loaded yet
|
|
252
|
+
* mimeType: "image/jpeg"
|
|
253
|
+
* })
|
|
254
|
+
* ```
|
|
162
255
|
*/
|
|
163
256
|
nullable() {
|
|
164
257
|
return nullable(this);
|
|
165
258
|
}
|
|
166
259
|
/**
|
|
167
|
-
* Returns a new validator that also accepts
|
|
168
|
-
*
|
|
260
|
+
* Returns a new validator that also accepts undefined values.
|
|
261
|
+
*
|
|
262
|
+
* @returns A new validator that accepts T or undefined
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* import { T } from '@tldraw/validate'
|
|
266
|
+
*
|
|
267
|
+
* const shapeConfigValidator = T.object({
|
|
268
|
+
* type: T.literal('rectangle'),
|
|
269
|
+
* x: T.number,
|
|
270
|
+
* y: T.number,
|
|
271
|
+
* label: T.string.optional(), // Optional property
|
|
272
|
+
* metadata: T.object({ created: T.string }).optional()
|
|
273
|
+
* })
|
|
274
|
+
*
|
|
275
|
+
* // Both of these are valid:
|
|
276
|
+
* const shape1 = shapeConfigValidator.validate({ type: 'rectangle', x: 0, y: 0 })
|
|
277
|
+
* const shape2 = shapeConfigValidator.validate({
|
|
278
|
+
* type: 'rectangle', x: 0, y: 0, label: "My Shape"
|
|
279
|
+
* })
|
|
280
|
+
* ```
|
|
169
281
|
*/
|
|
170
282
|
optional() {
|
|
171
283
|
return optional(this);
|
|
172
284
|
}
|
|
173
285
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
286
|
+
* Creates a new validator by refining this validator with additional logic that can transform
|
|
287
|
+
* the validated value to a new type.
|
|
288
|
+
*
|
|
289
|
+
* @param otherValidationFn - Function that transforms/validates the value to type U
|
|
290
|
+
* @returns A new validator that validates to type U
|
|
291
|
+
* @throws ValidationError When validation or refinement fails
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* import { T, ValidationError } from '@tldraw/validate'
|
|
295
|
+
*
|
|
296
|
+
* // Transform string to ensure it starts with a prefix
|
|
297
|
+
* const prefixedIdValidator = T.string.refine((id) => {
|
|
298
|
+
* return id.startsWith('shape:') ? id : `shape:${id}`
|
|
299
|
+
* })
|
|
300
|
+
*
|
|
301
|
+
* const id1 = prefixedIdValidator.validate("rectangle-123") // Returns "shape:rectangle-123"
|
|
302
|
+
* const id2 = prefixedIdValidator.validate("shape:circle-456") // Returns "shape:circle-456"
|
|
303
|
+
*
|
|
304
|
+
* // Parse and validate JSON strings
|
|
305
|
+
* const jsonValidator = T.string.refine((str) => {
|
|
306
|
+
* try {
|
|
307
|
+
* return JSON.parse(str)
|
|
308
|
+
* } catch {
|
|
309
|
+
* throw new ValidationError('Invalid JSON string')
|
|
310
|
+
* }
|
|
311
|
+
* })
|
|
312
|
+
* ```
|
|
176
313
|
*/
|
|
177
314
|
refine(otherValidationFn) {
|
|
178
315
|
return new Validator(
|
|
@@ -203,6 +340,11 @@ class Validator {
|
|
|
203
340
|
}
|
|
204
341
|
}
|
|
205
342
|
class ArrayOfValidator extends Validator {
|
|
343
|
+
/**
|
|
344
|
+
* Creates a new ArrayOfValidator.
|
|
345
|
+
*
|
|
346
|
+
* itemValidator - Validator used to validate each array element
|
|
347
|
+
*/
|
|
206
348
|
constructor(itemValidator) {
|
|
207
349
|
super(
|
|
208
350
|
(value) => {
|
|
@@ -239,6 +381,18 @@ class ArrayOfValidator extends Validator {
|
|
|
239
381
|
);
|
|
240
382
|
this.itemValidator = itemValidator;
|
|
241
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Returns a new validator that ensures the array is not empty.
|
|
386
|
+
*
|
|
387
|
+
* @returns A new validator that rejects empty arrays
|
|
388
|
+
* @throws ValidationError When the array is empty
|
|
389
|
+
* @example
|
|
390
|
+
* ```ts
|
|
391
|
+
* const nonEmptyStrings = T.arrayOf(T.string).nonEmpty()
|
|
392
|
+
* nonEmptyStrings.validate(["hello"]) // Valid
|
|
393
|
+
* nonEmptyStrings.validate([]) // Throws ValidationError
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
242
396
|
nonEmpty() {
|
|
243
397
|
return this.check((value) => {
|
|
244
398
|
if (value.length === 0) {
|
|
@@ -246,6 +400,18 @@ class ArrayOfValidator extends Validator {
|
|
|
246
400
|
}
|
|
247
401
|
});
|
|
248
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Returns a new validator that ensures the array has more than one element.
|
|
405
|
+
*
|
|
406
|
+
* @returns A new validator that requires at least 2 elements
|
|
407
|
+
* @throws ValidationError When the array has 1 or fewer elements
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* const multipleItems = T.arrayOf(T.string).lengthGreaterThan1()
|
|
411
|
+
* multipleItems.validate(["a", "b"]) // Valid
|
|
412
|
+
* multipleItems.validate(["a"]) // Throws ValidationError
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
249
415
|
lengthGreaterThan1() {
|
|
250
416
|
return this.check((value) => {
|
|
251
417
|
if (value.length <= 1) {
|
|
@@ -255,6 +421,12 @@ class ArrayOfValidator extends Validator {
|
|
|
255
421
|
}
|
|
256
422
|
}
|
|
257
423
|
class ObjectValidator extends Validator {
|
|
424
|
+
/**
|
|
425
|
+
* Creates a new ObjectValidator.
|
|
426
|
+
*
|
|
427
|
+
* config - Object mapping property names to their validators
|
|
428
|
+
* shouldAllowUnknownProperties - Whether to allow properties not defined in config
|
|
429
|
+
*/
|
|
258
430
|
constructor(config, shouldAllowUnknownProperties = false) {
|
|
259
431
|
super(
|
|
260
432
|
(object2) => {
|
|
@@ -318,21 +490,32 @@ class ObjectValidator extends Validator {
|
|
|
318
490
|
this.config = config;
|
|
319
491
|
this.shouldAllowUnknownProperties = shouldAllowUnknownProperties;
|
|
320
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Returns a new validator that allows unknown properties in the validated object.
|
|
495
|
+
*
|
|
496
|
+
* @returns A new ObjectValidator that accepts extra properties
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* const flexibleUser = T.object({ name: T.string }).allowUnknownProperties()
|
|
500
|
+
* flexibleUser.validate({ name: "Alice", extra: "allowed" }) // Valid
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
321
503
|
allowUnknownProperties() {
|
|
322
504
|
return new ObjectValidator(this.config, true);
|
|
323
505
|
}
|
|
324
506
|
/**
|
|
325
|
-
*
|
|
507
|
+
* Creates a new ObjectValidator by extending this validator with additional properties.
|
|
326
508
|
*
|
|
509
|
+
* @param extension - Object mapping new property names to their validators
|
|
510
|
+
* @returns A new ObjectValidator that validates both original and extended properties
|
|
327
511
|
* @example
|
|
328
|
-
*
|
|
329
512
|
* ```ts
|
|
330
|
-
* const
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* meowVolume: T.number,
|
|
513
|
+
* const baseUser = T.object({ name: T.string, age: T.number })
|
|
514
|
+
* const adminUser = baseUser.extend({
|
|
515
|
+
* permissions: T.arrayOf(T.string),
|
|
516
|
+
* isAdmin: T.boolean
|
|
335
517
|
* })
|
|
518
|
+
* // adminUser validates: { name: string; age: number; permissions: string[]; isAdmin: boolean }
|
|
336
519
|
* ```
|
|
337
520
|
*/
|
|
338
521
|
extend(extension) {
|
|
@@ -340,6 +523,14 @@ class ObjectValidator extends Validator {
|
|
|
340
523
|
}
|
|
341
524
|
}
|
|
342
525
|
class UnionValidator extends Validator {
|
|
526
|
+
/**
|
|
527
|
+
* Creates a new UnionValidator.
|
|
528
|
+
*
|
|
529
|
+
* key - The discriminator property name used to determine the variant
|
|
530
|
+
* config - Object mapping variant names to their validators
|
|
531
|
+
* unknownValueValidation - Function to handle unknown variants
|
|
532
|
+
* useNumberKeys - Whether the discriminator uses number keys instead of strings
|
|
533
|
+
*/
|
|
343
534
|
constructor(key, config, unknownValueValidation, useNumberKeys) {
|
|
344
535
|
super(
|
|
345
536
|
(input) => {
|
|
@@ -391,11 +582,31 @@ class UnionValidator extends Validator {
|
|
|
391
582
|
const matchingSchema = (0, import_utils.hasOwnProperty)(this.config, variant) ? this.config[variant] : void 0;
|
|
392
583
|
return { matchingSchema, variant };
|
|
393
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Returns a new UnionValidator that can handle unknown variants using the provided function.
|
|
587
|
+
*
|
|
588
|
+
* @param unknownValueValidation - Function to validate/transform unknown variants
|
|
589
|
+
* @returns A new UnionValidator that accepts unknown variants
|
|
590
|
+
* @example
|
|
591
|
+
* ```ts
|
|
592
|
+
* const shapeValidator = T.union('type', { circle: circleValidator })
|
|
593
|
+
* .validateUnknownVariants((obj, variant) => {
|
|
594
|
+
* console.warn(`Unknown shape type: ${variant}`)
|
|
595
|
+
* return obj as UnknownShape
|
|
596
|
+
* })
|
|
597
|
+
* ```
|
|
598
|
+
*/
|
|
394
599
|
validateUnknownVariants(unknownValueValidation) {
|
|
395
600
|
return new UnionValidator(this.key, this.config, unknownValueValidation, this.useNumberKeys);
|
|
396
601
|
}
|
|
397
602
|
}
|
|
398
603
|
class DictValidator extends Validator {
|
|
604
|
+
/**
|
|
605
|
+
* Creates a new DictValidator.
|
|
606
|
+
*
|
|
607
|
+
* keyValidator - Validator for object keys
|
|
608
|
+
* valueValidator - Validator for object values
|
|
609
|
+
*/
|
|
399
610
|
constructor(keyValidator, valueValidator) {
|
|
400
611
|
super(
|
|
401
612
|
(object2) => {
|