@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.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.b6dfe9bccde9",
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
- * Asserts that the passed value is of the correct type and returns it. The returned value is
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
- /** Checks that the passed value is of the correct type. */
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 or undefined. The resulting value will always be
161
- * null.
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 null or undefined. The resulting value will always be
168
- * null.
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
- * Refine this validation to a new type. The passed-in validation function should throw an error
175
- * if the value can't be converted to the new type, or return the new type otherwise.
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
- * Extend an object validator by adding additional properties.
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 animalValidator = T.object({
331
- * name: T.string,
332
- * })
333
- * const catValidator = animalValidator.extend({
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) => {