@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.
@@ -9,25 +9,87 @@ import {
9
9
  validateIndexKey,
10
10
  } from '@tldraw/utils'
11
11
 
12
- /** @public */
12
+ /**
13
+ * A function that validates and returns a value of type T from unknown input.
14
+ * The function should throw a ValidationError if the value is invalid.
15
+ *
16
+ * @param value - The unknown value to validate
17
+ * @returns The validated value of type T
18
+ * @throws \{ValidationError\} When the value doesn't match the expected type
19
+ * @example
20
+ * ```ts
21
+ * const stringValidator: ValidatorFn<string> = (value) => {
22
+ * if (typeof value !== 'string') {
23
+ * throw new ValidationError('Expected string')
24
+ * }
25
+ * return value
26
+ * }
27
+ * ```
28
+ * @public
29
+ */
13
30
  export type ValidatorFn<T> = (value: unknown) => T
14
- /** @public */
31
+ /**
32
+ * A performance-optimized validation function that can use a previously validated value
33
+ * to avoid revalidating unchanged parts of the data structure.
34
+ *
35
+ * @param knownGoodValue - A previously validated value of type In
36
+ * @param value - The unknown value to validate
37
+ * @returns The validated value of type Out
38
+ * @throws ValidationError When the value doesn't match the expected type
39
+ * @example
40
+ * ```ts
41
+ * const optimizedValidator: ValidatorUsingKnownGoodVersionFn<User> = (
42
+ * knownGood,
43
+ * newValue
44
+ * ) => {
45
+ * if (Object.is(knownGood, newValue)) return knownGood
46
+ * return fullValidation(newValue)
47
+ * }
48
+ * ```
49
+ * @public
50
+ */
15
51
  export type ValidatorUsingKnownGoodVersionFn<In, Out = In> = (
16
52
  knownGoodValue: In,
17
53
  value: unknown
18
54
  ) => Out
19
55
 
20
- /** @public */
56
+ /**
57
+ * Interface for objects that can validate unknown values and return typed results.
58
+ * This is the core interface implemented by all validators in the validation system.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const customValidator: Validatable<number> = {
63
+ * validate(value) {
64
+ * if (typeof value !== 'number') {
65
+ * throw new ValidationError('Expected number')
66
+ * }
67
+ * return value
68
+ * }
69
+ * }
70
+ * ```
71
+ * @public
72
+ */
21
73
  export interface Validatable<T> {
74
+ /**
75
+ * Validates an unknown value and returns it with the correct type.
76
+ *
77
+ * @param value - The unknown value to validate
78
+ * @returns The validated value with type T
79
+ * @throws ValidationError When validation fails
80
+ */
22
81
  validate(value: unknown): T
23
82
  /**
24
- * This is a performance optimizing version of validate that can use a previous
25
- * version of the value to avoid revalidating every part of the new value if
26
- * any part of it has not changed since the last validation.
83
+ * Performance-optimized validation that can use a previously validated value
84
+ * to avoid revalidating unchanged parts of the data structure.
27
85
  *
28
86
  * If the value has not changed but is not referentially equal, the function
29
87
  * should return the previous value.
30
- * @returns
88
+ *
89
+ * @param knownGoodValue - A previously validated value
90
+ * @param newValue - The new value to validate
91
+ * @returns The validated value, potentially reusing the known good value for performance
92
+ * @throws ValidationError When validation fails
31
93
  */
32
94
  validateUsingKnownGoodVersion?(knownGoodValue: T, newValue: unknown): T
33
95
  }
@@ -61,10 +123,33 @@ function formatPath(path: ReadonlyArray<number | string>): string | null {
61
123
  return formattedPath
62
124
  }
63
125
 
64
- /** @public */
126
+ /**
127
+ * Error thrown when validation fails. Provides detailed information about what went wrong
128
+ * and where in the data structure the error occurred.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * try {
133
+ * validator.validate(invalidData)
134
+ * } catch (error) {
135
+ * if (error instanceof ValidationError) {
136
+ * console.log(error.message) // "At users.0.email: Expected valid URL"
137
+ * console.log(error.path) // ['users', 0, 'email']
138
+ * console.log(error.rawMessage) // "Expected valid URL"
139
+ * }
140
+ * }
141
+ * ```
142
+ * @public
143
+ */
65
144
  export class ValidationError extends Error {
66
145
  override name = 'ValidationError'
67
146
 
147
+ /**
148
+ * Creates a new ValidationError with contextual information about where the error occurred.
149
+ *
150
+ * rawMessage - The raw error message without path information
151
+ * path - Array indicating the location in the data structure where validation failed
152
+ */
68
153
  constructor(
69
154
  public readonly rawMessage: string,
70
155
  public readonly path: ReadonlyArray<number | string> = []
@@ -110,19 +195,67 @@ function typeToString(value: unknown): string {
110
195
  }
111
196
  }
112
197
 
113
- /** @public */
198
+ /**
199
+ * Utility type that extracts the validated type from a Validatable object.
200
+ * Useful for deriving TypeScript types from validator definitions.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const userValidator = T.object({ name: T.string, age: T.number })
205
+ * type User = TypeOf<typeof userValidator> // { name: string; age: number }
206
+ * ```
207
+ * @public
208
+ */
114
209
  export type TypeOf<V extends Validatable<any>> = V extends Validatable<infer T> ? T : never
115
210
 
116
- /** @public */
211
+ /**
212
+ * The main validator class that implements the Validatable interface. This is the base class
213
+ * for all validators and provides methods for validation, type checking, and composing validators.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * const numberValidator = new Validator((value) => {
218
+ * if (typeof value !== 'number') {
219
+ * throw new ValidationError('Expected number')
220
+ * }
221
+ * return value
222
+ * })
223
+ *
224
+ * const result = numberValidator.validate(42) // Returns 42 as number
225
+ * ```
226
+ * @public
227
+ */
117
228
  export class Validator<T> implements Validatable<T> {
229
+ /**
230
+ * Creates a new Validator instance.
231
+ *
232
+ * validationFn - Function that validates and returns a value of type T
233
+ * validateUsingKnownGoodVersionFn - Optional performance-optimized validation function
234
+ */
118
235
  constructor(
119
236
  readonly validationFn: ValidatorFn<T>,
120
237
  readonly validateUsingKnownGoodVersionFn?: ValidatorUsingKnownGoodVersionFn<T>
121
238
  ) {}
122
239
 
123
240
  /**
124
- * Asserts that the passed value is of the correct type and returns it. The returned value is
241
+ * Validates an unknown value and returns it with the correct type. The returned value is
125
242
  * guaranteed to be referentially equal to the passed value.
243
+ *
244
+ * @param value - The unknown value to validate
245
+ * @returns The validated value with type T
246
+ * @throws ValidationError When validation fails
247
+ * @example
248
+ * ```ts
249
+ * import { T } from '@tldraw/validate'
250
+ *
251
+ * const name = T.string.validate("Alice") // Returns "Alice" as string
252
+ * const title = T.string.validate("") // Returns "" (empty strings are valid)
253
+ *
254
+ * // These will throw ValidationError:
255
+ * T.string.validate(123) // Expected string, got a number
256
+ * T.string.validate(null) // Expected string, got null
257
+ * T.string.validate(undefined) // Expected string, got undefined
258
+ * ```
126
259
  */
127
260
  validate(value: unknown): T {
128
261
  const validated = this.validationFn(value)
@@ -132,6 +265,31 @@ export class Validator<T> implements Validatable<T> {
132
265
  return validated
133
266
  }
134
267
 
268
+ /**
269
+ * Performance-optimized validation using a previously validated value. If the new value
270
+ * is referentially equal to the known good value, returns the known good value immediately.
271
+ *
272
+ * @param knownGoodValue - A previously validated value
273
+ * @param newValue - The new value to validate
274
+ * @returns The validated value, potentially reusing the known good value
275
+ * @throws ValidationError When validation fails
276
+ * @example
277
+ * ```ts
278
+ * import { T } from '@tldraw/validate'
279
+ *
280
+ * const userValidator = T.object({
281
+ * name: T.string,
282
+ * settings: T.object({ theme: T.literalEnum('light', 'dark') })
283
+ * })
284
+ *
285
+ * const user = userValidator.validate({ name: "Alice", settings: { theme: "light" } })
286
+ *
287
+ * // Later, with partially changed data:
288
+ * const newData = { name: "Alice", settings: { theme: "dark" } }
289
+ * const updated = userValidator.validateUsingKnownGoodVersion(user, newData)
290
+ * // Only validates the changed 'theme' field for better performance
291
+ * ```
292
+ */
135
293
  validateUsingKnownGoodVersion(knownGoodValue: T, newValue: unknown): T {
136
294
  if (Object.is(knownGoodValue, newValue)) {
137
295
  return knownGoodValue as T
@@ -144,7 +302,28 @@ export class Validator<T> implements Validatable<T> {
144
302
  return this.validate(newValue)
145
303
  }
146
304
 
147
- /** Checks that the passed value is of the correct type. */
305
+ /**
306
+ * Type guard that checks if a value is valid without throwing an error.
307
+ *
308
+ * @param value - The value to check
309
+ * @returns True if the value is valid, false otherwise
310
+ * @example
311
+ * ```ts
312
+ * import { T } from '@tldraw/validate'
313
+ *
314
+ * function processUserInput(input: unknown) {
315
+ * if (T.string.isValid(input)) {
316
+ * // input is now typed as string within this block
317
+ * return input.toUpperCase()
318
+ * }
319
+ * if (T.number.isValid(input)) {
320
+ * // input is now typed as number within this block
321
+ * return input.toFixed(2)
322
+ * }
323
+ * throw new Error('Expected string or number')
324
+ * }
325
+ * ```
326
+ */
148
327
  isValid(value: unknown): value is T {
149
328
  try {
150
329
  this.validate(value)
@@ -155,24 +334,87 @@ export class Validator<T> implements Validatable<T> {
155
334
  }
156
335
 
157
336
  /**
158
- * Returns a new validator that also accepts null or undefined. The resulting value will always be
159
- * null.
337
+ * Returns a new validator that also accepts null values.
338
+ *
339
+ * @returns A new validator that accepts T or null
340
+ * @example
341
+ * ```ts
342
+ * import { T } from '@tldraw/validate'
343
+ *
344
+ * const assetValidator = T.object({
345
+ * id: T.string,
346
+ * name: T.string,
347
+ * src: T.srcUrl.nullable(), // Can be null if not loaded yet
348
+ * mimeType: T.string.nullable()
349
+ * })
350
+ *
351
+ * const asset = assetValidator.validate({
352
+ * id: "image-123",
353
+ * name: "photo.jpg",
354
+ * src: null, // Valid - asset not loaded yet
355
+ * mimeType: "image/jpeg"
356
+ * })
357
+ * ```
160
358
  */
161
359
  nullable(): Validator<T | null> {
162
360
  return nullable(this)
163
361
  }
164
362
 
165
363
  /**
166
- * Returns a new validator that also accepts null or undefined. The resulting value will always be
167
- * null.
364
+ * Returns a new validator that also accepts undefined values.
365
+ *
366
+ * @returns A new validator that accepts T or undefined
367
+ * @example
368
+ * ```ts
369
+ * import { T } from '@tldraw/validate'
370
+ *
371
+ * const shapeConfigValidator = T.object({
372
+ * type: T.literal('rectangle'),
373
+ * x: T.number,
374
+ * y: T.number,
375
+ * label: T.string.optional(), // Optional property
376
+ * metadata: T.object({ created: T.string }).optional()
377
+ * })
378
+ *
379
+ * // Both of these are valid:
380
+ * const shape1 = shapeConfigValidator.validate({ type: 'rectangle', x: 0, y: 0 })
381
+ * const shape2 = shapeConfigValidator.validate({
382
+ * type: 'rectangle', x: 0, y: 0, label: "My Shape"
383
+ * })
384
+ * ```
168
385
  */
169
386
  optional(): Validator<T | undefined> {
170
387
  return optional(this)
171
388
  }
172
389
 
173
390
  /**
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.
391
+ * Creates a new validator by refining this validator with additional logic that can transform
392
+ * the validated value to a new type.
393
+ *
394
+ * @param otherValidationFn - Function that transforms/validates the value to type U
395
+ * @returns A new validator that validates to type U
396
+ * @throws ValidationError When validation or refinement fails
397
+ * @example
398
+ * ```ts
399
+ * import { T, ValidationError } from '@tldraw/validate'
400
+ *
401
+ * // Transform string to ensure it starts with a prefix
402
+ * const prefixedIdValidator = T.string.refine((id) => {
403
+ * return id.startsWith('shape:') ? id : `shape:${id}`
404
+ * })
405
+ *
406
+ * const id1 = prefixedIdValidator.validate("rectangle-123") // Returns "shape:rectangle-123"
407
+ * const id2 = prefixedIdValidator.validate("shape:circle-456") // Returns "shape:circle-456"
408
+ *
409
+ * // Parse and validate JSON strings
410
+ * const jsonValidator = T.string.refine((str) => {
411
+ * try {
412
+ * return JSON.parse(str)
413
+ * } catch {
414
+ * throw new ValidationError('Invalid JSON string')
415
+ * }
416
+ * })
417
+ * ```
176
418
  */
177
419
  refine<U>(otherValidationFn: (value: T) => U): Validator<U> {
178
420
  return new Validator(
@@ -191,19 +433,49 @@ export class Validator<T> implements Validatable<T> {
191
433
  }
192
434
 
193
435
  /**
194
- * Refine this validation with an additional check that doesn't change the resulting value.
436
+ * Adds an additional validation check without changing the resulting value type.
437
+ * Can be called with just a check function, or with a name for better error messages.
195
438
  *
439
+ * @param name - Name for the check (used in error messages)
440
+ * @param checkFn - Function that validates the value (should throw on invalid input)
441
+ * @returns A new validator with the additional check
442
+ * @throws ValidationError When the check fails
196
443
  * @example
197
- *
198
444
  * ```ts
199
- * const numberLessThan10Validator = T.number.check((value) => {
200
- * if (value >= 10) {
201
- * throw new ValidationError(`Expected number less than 10, got ${value}`)
202
- * }
445
+ * import { T, ValidationError } from '@tldraw/validate'
446
+ *
447
+ * // Basic check without name
448
+ * const evenNumber = T.number.check((value) => {
449
+ * if (value % 2 !== 0) {
450
+ * throw new ValidationError('Expected even number')
451
+ * }
452
+ * })
453
+ *
454
+ * // Named checks for better error messages in complex validators
455
+ * const shapePositionValidator = T.object({
456
+ * x: T.number.check('finite', (value) => {
457
+ * if (!Number.isFinite(value)) {
458
+ * throw new ValidationError('Position must be finite')
459
+ * }
460
+ * }),
461
+ * y: T.number.check('within-bounds', (value) => {
462
+ * if (value < -10000 || value > 10000) {
463
+ * throw new ValidationError('Position must be within bounds (-10000 to 10000)')
464
+ * }
465
+ * })
203
466
  * })
467
+ *
468
+ * // Error will be: "At x (check finite): Position must be finite"
204
469
  * ```
205
470
  */
206
471
  check(name: string, checkFn: (value: T) => void): Validator<T>
472
+ /**
473
+ * Adds an additional validation check without changing the resulting value type.
474
+ *
475
+ * @param checkFn - Function that validates the value (should throw on invalid input)
476
+ * @returns A new validator with the additional check
477
+ * @throws ValidationError When the check fails
478
+ */
207
479
  check(checkFn: (value: T) => void): Validator<T>
208
480
  check(nameOrCheckFn: string | ((value: T) => void), checkFn?: (value: T) => void): Validator<T> {
209
481
  if (typeof nameOrCheckFn === 'string') {
@@ -220,8 +492,25 @@ export class Validator<T> implements Validatable<T> {
220
492
  }
221
493
  }
222
494
 
223
- /** @public */
495
+ /**
496
+ * Validator for arrays where each element is validated using the provided item validator.
497
+ * Extends the base Validator class with array-specific validation methods.
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * const stringArray = new ArrayOfValidator(T.string)
502
+ * const numbers = stringArray.validate(["a", "b", "c"]) // Returns string[]
503
+ *
504
+ * const userArray = T.arrayOf(T.object({ name: T.string, age: T.number }))
505
+ * ```
506
+ * @public
507
+ */
224
508
  export class ArrayOfValidator<T> extends Validator<T[]> {
509
+ /**
510
+ * Creates a new ArrayOfValidator.
511
+ *
512
+ * itemValidator - Validator used to validate each array element
513
+ */
225
514
  constructor(readonly itemValidator: Validatable<T>) {
226
515
  super(
227
516
  (value) => {
@@ -259,6 +548,18 @@ export class ArrayOfValidator<T> extends Validator<T[]> {
259
548
  )
260
549
  }
261
550
 
551
+ /**
552
+ * Returns a new validator that ensures the array is not empty.
553
+ *
554
+ * @returns A new validator that rejects empty arrays
555
+ * @throws ValidationError When the array is empty
556
+ * @example
557
+ * ```ts
558
+ * const nonEmptyStrings = T.arrayOf(T.string).nonEmpty()
559
+ * nonEmptyStrings.validate(["hello"]) // Valid
560
+ * nonEmptyStrings.validate([]) // Throws ValidationError
561
+ * ```
562
+ */
262
563
  nonEmpty() {
263
564
  return this.check((value) => {
264
565
  if (value.length === 0) {
@@ -267,6 +568,18 @@ export class ArrayOfValidator<T> extends Validator<T[]> {
267
568
  })
268
569
  }
269
570
 
571
+ /**
572
+ * Returns a new validator that ensures the array has more than one element.
573
+ *
574
+ * @returns A new validator that requires at least 2 elements
575
+ * @throws ValidationError When the array has 1 or fewer elements
576
+ * @example
577
+ * ```ts
578
+ * const multipleItems = T.arrayOf(T.string).lengthGreaterThan1()
579
+ * multipleItems.validate(["a", "b"]) // Valid
580
+ * multipleItems.validate(["a"]) // Throws ValidationError
581
+ * ```
582
+ */
270
583
  lengthGreaterThan1() {
271
584
  return this.check((value) => {
272
585
  if (value.length <= 1) {
@@ -276,8 +589,33 @@ export class ArrayOfValidator<T> extends Validator<T[]> {
276
589
  }
277
590
  }
278
591
 
279
- /** @public */
592
+ /**
593
+ * Validator for objects with a defined shape. Each property is validated using its corresponding
594
+ * validator from the config object. Can be configured to allow or reject unknown properties.
595
+ *
596
+ * @example
597
+ * ```ts
598
+ * const userValidator = new ObjectValidator({
599
+ * name: T.string,
600
+ * age: T.number,
601
+ * email: T.string.optional()
602
+ * })
603
+ *
604
+ * const user = userValidator.validate({
605
+ * name: "Alice",
606
+ * age: 25,
607
+ * email: "alice@example.com"
608
+ * })
609
+ * ```
610
+ * @public
611
+ */
280
612
  export class ObjectValidator<Shape extends object> extends Validator<Shape> {
613
+ /**
614
+ * Creates a new ObjectValidator.
615
+ *
616
+ * config - Object mapping property names to their validators
617
+ * shouldAllowUnknownProperties - Whether to allow properties not defined in config
618
+ */
281
619
  constructor(
282
620
  public readonly config: {
283
621
  readonly [K in keyof Shape]: Validatable<Shape[K]>
@@ -353,22 +691,33 @@ export class ObjectValidator<Shape extends object> extends Validator<Shape> {
353
691
  )
354
692
  }
355
693
 
694
+ /**
695
+ * Returns a new validator that allows unknown properties in the validated object.
696
+ *
697
+ * @returns A new ObjectValidator that accepts extra properties
698
+ * @example
699
+ * ```ts
700
+ * const flexibleUser = T.object({ name: T.string }).allowUnknownProperties()
701
+ * flexibleUser.validate({ name: "Alice", extra: "allowed" }) // Valid
702
+ * ```
703
+ */
356
704
  allowUnknownProperties() {
357
705
  return new ObjectValidator(this.config, true)
358
706
  }
359
707
 
360
708
  /**
361
- * Extend an object validator by adding additional properties.
709
+ * Creates a new ObjectValidator by extending this validator with additional properties.
362
710
  *
711
+ * @param extension - Object mapping new property names to their validators
712
+ * @returns A new ObjectValidator that validates both original and extended properties
363
713
  * @example
364
- *
365
714
  * ```ts
366
- * const animalValidator = T.object({
367
- * name: T.string,
368
- * })
369
- * const catValidator = animalValidator.extend({
370
- * meowVolume: T.number,
715
+ * const baseUser = T.object({ name: T.string, age: T.number })
716
+ * const adminUser = baseUser.extend({
717
+ * permissions: T.arrayOf(T.string),
718
+ * isAdmin: T.boolean
371
719
  * })
720
+ * // adminUser validates: { name: string; age: number; permissions: string[]; isAdmin: boolean }
372
721
  * ```
373
722
  */
374
723
  extend<Extension extends Record<string, unknown>>(extension: {
@@ -380,19 +729,53 @@ export class ObjectValidator<Shape extends object> extends Validator<Shape> {
380
729
  }
381
730
  }
382
731
 
383
- // pass this into itself e.g. Config extends UnionObjectSchemaConfig<Key, Config>
384
- /** @public */
732
+ /**
733
+ * Configuration type for union validators. Each variant must be a validator that produces
734
+ * an object with the discriminator key set to the variant name.
735
+ *
736
+ * @example
737
+ * ```ts
738
+ * type ShapeConfig = UnionValidatorConfig<'type', {
739
+ * circle: Validatable<{ type: 'circle'; radius: number }>
740
+ * square: Validatable<{ type: 'square'; size: number }>
741
+ * }>
742
+ * ```
743
+ * @public
744
+ */
385
745
  export type UnionValidatorConfig<Key extends string, Config> = {
386
746
  readonly [Variant in keyof Config]: Validatable<any> & {
387
747
  validate(input: any): { readonly [K in Key]: Variant }
388
748
  }
389
749
  }
390
- /** @public */
750
+ /**
751
+ * Validator for discriminated union types. Validates objects that can be one of several variants,
752
+ * distinguished by a discriminator property (key) that indicates which variant the object represents.
753
+ *
754
+ * @example
755
+ * ```ts
756
+ * const shapeValidator = new UnionValidator('type', {
757
+ * circle: T.object({ type: T.literal('circle'), radius: T.number }),
758
+ * square: T.object({ type: T.literal('square'), size: T.number })
759
+ * }, () => { throw new Error('Unknown shape') }, false)
760
+ *
761
+ * const circle = shapeValidator.validate({ type: 'circle', radius: 5 })
762
+ * // circle is typed as { type: 'circle'; radius: number }
763
+ * ```
764
+ * @public
765
+ */
391
766
  export class UnionValidator<
392
767
  Key extends string,
393
768
  Config extends UnionValidatorConfig<Key, Config>,
394
769
  UnknownValue = never,
395
770
  > extends Validator<TypeOf<Config[keyof Config]> | UnknownValue> {
771
+ /**
772
+ * Creates a new UnionValidator.
773
+ *
774
+ * key - The discriminator property name used to determine the variant
775
+ * config - Object mapping variant names to their validators
776
+ * unknownValueValidation - Function to handle unknown variants
777
+ * useNumberKeys - Whether the discriminator uses number keys instead of strings
778
+ */
396
779
  constructor(
397
780
  private readonly key: Key,
398
781
  private readonly config: Config,
@@ -458,6 +841,20 @@ export class UnionValidator<
458
841
  return { matchingSchema, variant }
459
842
  }
460
843
 
844
+ /**
845
+ * Returns a new UnionValidator that can handle unknown variants using the provided function.
846
+ *
847
+ * @param unknownValueValidation - Function to validate/transform unknown variants
848
+ * @returns A new UnionValidator that accepts unknown variants
849
+ * @example
850
+ * ```ts
851
+ * const shapeValidator = T.union('type', { circle: circleValidator })
852
+ * .validateUnknownVariants((obj, variant) => {
853
+ * console.warn(`Unknown shape type: ${variant}`)
854
+ * return obj as UnknownShape
855
+ * })
856
+ * ```
857
+ */
461
858
  validateUnknownVariants<Unknown>(
462
859
  unknownValueValidation: (value: object, variant: string) => Unknown
463
860
  ): UnionValidator<Key, Config, Unknown> {
@@ -465,8 +862,29 @@ export class UnionValidator<
465
862
  }
466
863
  }
467
864
 
468
- /** @public */
865
+ /**
866
+ * Validator for dictionary/map objects where both keys and values are validated.
867
+ * Useful for validating objects used as key-value stores.
868
+ *
869
+ * @example
870
+ * ```ts
871
+ * const scoreDict = new DictValidator(T.string, T.number)
872
+ * const scores = scoreDict.validate({
873
+ * "alice": 100,
874
+ * "bob": 85,
875
+ * "charlie": 92
876
+ * })
877
+ * // scores is typed as Record<string, number>
878
+ * ```
879
+ * @public
880
+ */
469
881
  export class DictValidator<Key extends string, Value> extends Validator<Record<Key, Value>> {
882
+ /**
883
+ * Creates a new DictValidator.
884
+ *
885
+ * keyValidator - Validator for object keys
886
+ * valueValidator - Validator for object values
887
+ */
470
888
  constructor(
471
889
  public readonly keyValidator: Validatable<Key>,
472
890
  public readonly valueValidator: Validatable<Value>
@@ -543,30 +961,51 @@ function typeofValidator<T>(type: string): Validator<T> {
543
961
  }
544
962
 
545
963
  /**
546
- * Validation that accepts any value. Useful as a starting point for building your own custom
547
- * validations.
964
+ * Validator that accepts any value without type checking. Useful as a starting point for
965
+ * building custom validations or when you need to accept truly unknown data.
548
966
  *
967
+ * @example
968
+ * ```ts
969
+ * const result = T.unknown.validate(anything) // Returns the value as-is
970
+ * // result is typed as unknown
971
+ * ```
549
972
  * @public
550
973
  */
551
974
  export const unknown = new Validator((value) => value)
552
975
  /**
553
- * Validation that accepts any value. Generally this should be avoided, but you can use it as an
554
- * escape hatch if you want to work without validations for e.g. a prototype.
976
+ * Validator that accepts any value and types it as 'any'. This should generally be avoided
977
+ * as it bypasses type safety, but can be used as an escape hatch for prototyping.
555
978
  *
979
+ * @example
980
+ * ```ts
981
+ * const result = T.any.validate(anything) // Returns the value as any
982
+ * // result is typed as any - use with caution!
983
+ * ```
556
984
  * @public
557
985
  */
558
986
  export const any = new Validator((value): any => value)
559
987
 
560
988
  /**
561
- * Validates that a value is a string.
989
+ * Validator that ensures a value is a string.
562
990
  *
991
+ * @example
992
+ * ```ts
993
+ * const name = T.string.validate("hello") // Returns "hello" as string
994
+ * T.string.validate(123) // Throws ValidationError: "Expected string, got a number"
995
+ * ```
563
996
  * @public
564
997
  */
565
998
  export const string = typeofValidator<string>('string')
566
999
 
567
1000
  /**
568
- * Validates that a value is a finite non-NaN number.
1001
+ * Validator that ensures a value is a finite, non-NaN number. Rejects Infinity, -Infinity, and NaN.
569
1002
  *
1003
+ * @example
1004
+ * ```ts
1005
+ * const count = T.number.validate(42) // Returns 42 as number
1006
+ * T.number.validate(NaN) // Throws ValidationError: "Expected a number, got NaN"
1007
+ * T.number.validate(Infinity) // Throws ValidationError: "Expected a finite number, got Infinity"
1008
+ * ```
570
1009
  * @public
571
1010
  */
572
1011
  export const number = typeofValidator<number>('number').check((number) => {
@@ -578,40 +1017,73 @@ export const number = typeofValidator<number>('number').check((number) => {
578
1017
  }
579
1018
  })
580
1019
  /**
581
- * Fails if value \< 0
1020
+ * Validator that ensures a value is a non-negative number (\>= 0).
1021
+ * Despite the name "positive", this validator accepts zero.
582
1022
  *
1023
+ * @example
1024
+ * ```ts
1025
+ * const price = T.positiveNumber.validate(29.99) // Returns 29.99
1026
+ * const free = T.positiveNumber.validate(0) // Returns 0 (valid)
1027
+ * T.positiveNumber.validate(-1) // Throws ValidationError: "Expected a positive number, got -1"
1028
+ * ```
583
1029
  * @public
584
1030
  */
585
1031
  export const positiveNumber = number.check((value) => {
586
1032
  if (value < 0) throw new ValidationError(`Expected a positive number, got ${value}`)
587
1033
  })
588
1034
  /**
589
- * Fails if value \<= 0
1035
+ * Validator that ensures a value is a positive number (\> 0). Rejects zero and negative numbers.
590
1036
  *
1037
+ * @example
1038
+ * ```ts
1039
+ * const quantity = T.nonZeroNumber.validate(0.01) // Returns 0.01
1040
+ * T.nonZeroNumber.validate(0) // Throws ValidationError: "Expected a non-zero positive number, got 0"
1041
+ * T.nonZeroNumber.validate(-5) // Throws ValidationError: "Expected a non-zero positive number, got -5"
1042
+ * ```
591
1043
  * @public
592
1044
  */
593
1045
  export const nonZeroNumber = number.check((value) => {
594
1046
  if (value <= 0) throw new ValidationError(`Expected a non-zero positive number, got ${value}`)
595
1047
  })
596
1048
  /**
597
- * Fails if number is not an integer
1049
+ * Validator that ensures a value is an integer (whole number).
598
1050
  *
1051
+ * @example
1052
+ * ```ts
1053
+ * const count = T.integer.validate(42) // Returns 42
1054
+ * T.integer.validate(3.14) // Throws ValidationError: "Expected an integer, got 3.14"
1055
+ * T.integer.validate(-5) // Returns -5 (negative integers are valid)
1056
+ * ```
599
1057
  * @public
600
1058
  */
601
1059
  export const integer = number.check((value) => {
602
1060
  if (!Number.isInteger(value)) throw new ValidationError(`Expected an integer, got ${value}`)
603
1061
  })
604
1062
  /**
605
- * Fails if value \< 0 and is not an integer
1063
+ * Validator that ensures a value is a non-negative integer (\>= 0).
1064
+ * Despite the name "positive", this validator accepts zero.
606
1065
  *
1066
+ * @example
1067
+ * ```ts
1068
+ * const index = T.positiveInteger.validate(5) // Returns 5
1069
+ * const start = T.positiveInteger.validate(0) // Returns 0 (valid)
1070
+ * T.positiveInteger.validate(-1) // Throws ValidationError: "Expected a positive integer, got -1"
1071
+ * T.positiveInteger.validate(3.14) // Throws ValidationError: "Expected an integer, got 3.14"
1072
+ * ```
607
1073
  * @public
608
1074
  */
609
1075
  export const positiveInteger = integer.check((value) => {
610
1076
  if (value < 0) throw new ValidationError(`Expected a positive integer, got ${value}`)
611
1077
  })
612
1078
  /**
613
- * Fails if value \<= 0 and is not an integer
1079
+ * Validator that ensures a value is a positive integer (\> 0). Rejects zero and negative integers.
614
1080
  *
1081
+ * @example
1082
+ * ```ts
1083
+ * const itemCount = T.nonZeroInteger.validate(1) // Returns 1
1084
+ * T.nonZeroInteger.validate(0) // Throws ValidationError: "Expected a non-zero positive integer, got 0"
1085
+ * T.nonZeroInteger.validate(-5) // Throws ValidationError: "Expected a non-zero positive integer, got -5"
1086
+ * ```
615
1087
  * @public
616
1088
  */
617
1089
  export const nonZeroInteger = integer.check((value) => {
@@ -619,26 +1091,44 @@ export const nonZeroInteger = integer.check((value) => {
619
1091
  })
620
1092
 
621
1093
  /**
622
- * Validates that a value is boolean.
1094
+ * Validator that ensures a value is a boolean.
623
1095
  *
1096
+ * @example
1097
+ * ```ts
1098
+ * const isActive = T.boolean.validate(true) // Returns true
1099
+ * const isEnabled = T.boolean.validate(false) // Returns false
1100
+ * T.boolean.validate("true") // Throws ValidationError: "Expected boolean, got a string"
1101
+ * ```
624
1102
  * @public
625
1103
  */
626
1104
  export const boolean = typeofValidator<boolean>('boolean')
627
1105
  /**
628
- * Validates that a value is a bigint.
1106
+ * Validator that ensures a value is a bigint.
629
1107
  *
1108
+ * @example
1109
+ * ```ts
1110
+ * const largeNumber = T.bigint.validate(123n) // Returns 123n
1111
+ * T.bigint.validate(123) // Throws ValidationError: "Expected bigint, got a number"
1112
+ * ```
630
1113
  * @public
631
1114
  */
632
1115
  export const bigint = typeofValidator<bigint>('bigint')
633
1116
  /**
634
- * Validates that a value matches another that was passed in.
1117
+ * Creates a validator that only accepts a specific literal value.
635
1118
  *
1119
+ * @param expectedValue - The exact value that must be matched
1120
+ * @returns A validator that only accepts the specified literal value
1121
+ * @throws ValidationError When the value doesn't match the expected literal
636
1122
  * @example
637
- *
638
1123
  * ```ts
639
1124
  * const trueValidator = T.literal(true)
640
- * ```
1125
+ * trueValidator.validate(true) // Returns true
1126
+ * trueValidator.validate(false) // Throws ValidationError
641
1127
  *
1128
+ * const statusValidator = T.literal("active")
1129
+ * statusValidator.validate("active") // Returns "active"
1130
+ * statusValidator.validate("inactive") // Throws ValidationError
1131
+ * ```
642
1132
  * @public
643
1133
  */
644
1134
  export function literal<T extends string | number | boolean>(expectedValue: T): Validator<T> {
@@ -651,8 +1141,17 @@ export function literal<T extends string | number | boolean>(expectedValue: T):
651
1141
  }
652
1142
 
653
1143
  /**
654
- * Validates that a value is an array. To check the contents of the array, use T.arrayOf.
1144
+ * Validator that ensures a value is an array. Does not validate the contents of the array.
1145
+ * Use T.arrayOf() to validate both the array structure and its contents.
655
1146
  *
1147
+ * @example
1148
+ * ```ts
1149
+ * const items = T.array.validate([1, "hello", true]) // Returns unknown[]
1150
+ * T.array.validate("not array") // Throws ValidationError: "Expected an array, got a string"
1151
+ *
1152
+ * // For typed arrays, use T.arrayOf:
1153
+ * const numbers = T.arrayOf(T.number).validate([1, 2, 3]) // Returns number[]
1154
+ * ```
656
1155
  * @public
657
1156
  */
658
1157
  export const array = new Validator<unknown[]>((value) => {
@@ -663,15 +1162,37 @@ export const array = new Validator<unknown[]>((value) => {
663
1162
  })
664
1163
 
665
1164
  /**
666
- * Validates that a value is an array whose contents matches the passed-in validator.
1165
+ * Creates a validator for arrays where each element is validated using the provided validator.
667
1166
  *
1167
+ * @param itemValidator - Validator to use for each array element
1168
+ * @returns An ArrayOfValidator that validates both array structure and element types
1169
+ * @throws ValidationError When the value is not an array or when any element is invalid
1170
+ * @example
1171
+ * ```ts
1172
+ * const numberArray = T.arrayOf(T.number)
1173
+ * numberArray.validate([1, 2, 3]) // Returns number[]
1174
+ * numberArray.validate([1, "2", 3]) // Throws ValidationError at index 1
1175
+ *
1176
+ * const userArray = T.arrayOf(T.object({ name: T.string, age: T.number }))
1177
+ * ```
668
1178
  * @public
669
1179
  */
670
1180
  export function arrayOf<T>(itemValidator: Validatable<T>): ArrayOfValidator<T> {
671
1181
  return new ArrayOfValidator(itemValidator)
672
1182
  }
673
1183
 
674
- /** @public */
1184
+ /**
1185
+ * Validator that ensures a value is an object (non-null, non-array). Does not validate
1186
+ * the properties of the object.
1187
+ *
1188
+ * @example
1189
+ * ```ts
1190
+ * const obj = T.unknownObject.validate({ any: "properties" }) // Returns Record<string, unknown>
1191
+ * T.unknownObject.validate(null) // Throws ValidationError: "Expected object, got null"
1192
+ * T.unknownObject.validate([1, 2, 3]) // Throws ValidationError: "Expected object, got an array"
1193
+ * ```
1194
+ * @public
1195
+ */
675
1196
  export const unknownObject = new Validator<Record<string, unknown>>((value) => {
676
1197
  if (typeof value !== 'object' || value === null) {
677
1198
  throw new ValidationError(`Expected object, got ${typeToString(value)}`)
@@ -680,8 +1201,29 @@ export const unknownObject = new Validator<Record<string, unknown>>((value) => {
680
1201
  })
681
1202
 
682
1203
  /**
683
- * Validate an object has a particular shape.
1204
+ * Creates a validator for objects with a defined shape. Each property is validated using
1205
+ * its corresponding validator from the config object.
684
1206
  *
1207
+ * @param config - Object mapping property names to their validators
1208
+ * @returns An ObjectValidator that validates the object structure and all properties
1209
+ * @throws ValidationError When the value is not an object or when any property is invalid
1210
+ * @example
1211
+ * ```ts
1212
+ * const userValidator = T.object({
1213
+ * name: T.string,
1214
+ * age: T.number,
1215
+ * email: T.string.optional(),
1216
+ * isActive: T.boolean
1217
+ * })
1218
+ *
1219
+ * const user = userValidator.validate({
1220
+ * name: "Alice",
1221
+ * age: 25,
1222
+ * email: "alice@example.com",
1223
+ * isActive: true
1224
+ * })
1225
+ * // user is typed with full type safety
1226
+ * ```
685
1227
  * @public
686
1228
  */
687
1229
  export function object<Shape extends object>(config: {
@@ -722,8 +1264,15 @@ function isValidJson(value: any): value is JsonValue {
722
1264
  }
723
1265
 
724
1266
  /**
725
- * Validate that a value is valid JSON.
1267
+ * Validator that ensures a value is valid JSON (string, number, boolean, null, array, or plain object).
1268
+ * Rejects functions, undefined, symbols, and other non-JSON values.
726
1269
  *
1270
+ * @example
1271
+ * ```ts
1272
+ * const data = T.jsonValue.validate({ name: "Alice", scores: [1, 2, 3], active: true })
1273
+ * T.jsonValue.validate(undefined) // Throws ValidationError
1274
+ * T.jsonValue.validate(() => {}) // Throws ValidationError
1275
+ * ```
727
1276
  * @public
728
1277
  */
729
1278
  export const jsonValue: Validator<JsonValue> = new Validator<JsonValue>(
@@ -786,8 +1335,19 @@ export const jsonValue: Validator<JsonValue> = new Validator<JsonValue>(
786
1335
  )
787
1336
 
788
1337
  /**
789
- * Validate an object has a particular shape.
1338
+ * Creates a validator for JSON dictionaries (objects with string keys and JSON-serializable values).
790
1339
  *
1340
+ * @returns A DictValidator that validates string keys and JSON values
1341
+ * @throws ValidationError When keys are not strings or values are not JSON-serializable
1342
+ * @example
1343
+ * ```ts
1344
+ * const config = T.jsonDict().validate({
1345
+ * "setting1": "value",
1346
+ * "setting2": 42,
1347
+ * "setting3": ["a", "b", "c"],
1348
+ * "setting4": { nested: true }
1349
+ * })
1350
+ * ```
791
1351
  * @public
792
1352
  */
793
1353
  export function jsonDict(): DictValidator<string, JsonValue> {
@@ -795,8 +1355,23 @@ export function jsonDict(): DictValidator<string, JsonValue> {
795
1355
  }
796
1356
 
797
1357
  /**
798
- * Validation that an option is a dict with particular keys and values.
1358
+ * Creates a validator for dictionary objects where both keys and values are validated.
1359
+ * Useful for validating objects used as key-value maps.
799
1360
  *
1361
+ * @param keyValidator - Validator for object keys
1362
+ * @param valueValidator - Validator for object values
1363
+ * @returns A DictValidator that validates all keys and values
1364
+ * @throws ValidationError When any key or value is invalid
1365
+ * @example
1366
+ * ```ts
1367
+ * const scores = T.dict(T.string, T.number)
1368
+ * scores.validate({ "alice": 100, "bob": 85 }) // Valid
1369
+ *
1370
+ * const userPrefs = T.dict(T.string, T.object({
1371
+ * theme: T.literalEnum('light', 'dark'),
1372
+ * notifications: T.boolean
1373
+ * }))
1374
+ * ```
800
1375
  * @public
801
1376
  */
802
1377
  export function dict<Key extends string, Value>(
@@ -807,17 +1382,24 @@ export function dict<Key extends string, Value>(
807
1382
  }
808
1383
 
809
1384
  /**
810
- * Validate a union of several object types. Each object must have a property matching `key` which
811
- * should be a unique string.
1385
+ * Creates a validator for discriminated union types. Validates objects that can be one of
1386
+ * several variants, distinguished by a discriminator property.
812
1387
  *
1388
+ * @param key - The discriminator property name used to determine the variant
1389
+ * @param config - Object mapping variant names to their validators
1390
+ * @returns A UnionValidator that validates based on the discriminator value
1391
+ * @throws ValidationError When the discriminator is invalid or the variant validation fails
813
1392
  * @example
814
- *
815
1393
  * ```ts
816
- * const catValidator = T.object({ kind: T.literal('cat'), meow: T.boolean })
817
- * const dogValidator = T.object({ kind: T.literal('dog'), bark: T.boolean })
818
- * const animalValidator = T.union('kind', { cat: catValidator, dog: dogValidator })
819
- * ```
1394
+ * const shapeValidator = T.union('type', {
1395
+ * circle: T.object({ type: T.literal('circle'), radius: T.number }),
1396
+ * square: T.object({ type: T.literal('square'), size: T.number }),
1397
+ * triangle: T.object({ type: T.literal('triangle'), base: T.number, height: T.number })
1398
+ * })
820
1399
  *
1400
+ * const circle = shapeValidator.validate({ type: 'circle', radius: 5 })
1401
+ * // circle is typed as { type: 'circle'; radius: number }
1402
+ * ```
821
1403
  * @public
822
1404
  */
823
1405
  export function union<Key extends string, Config extends UnionValidatorConfig<Key, Config>>(
@@ -840,6 +1422,13 @@ export function union<Key extends string, Config extends UnionValidatorConfig<Ke
840
1422
  }
841
1423
 
842
1424
  /**
1425
+ * Creates a validator for discriminated union types using number discriminators instead of strings.
1426
+ * This is an internal function used for specific cases where numeric discriminators are needed.
1427
+ *
1428
+ * @param key - The discriminator property name used to determine the variant
1429
+ * @param config - Object mapping variant names to their validators
1430
+ * @returns A UnionValidator that validates based on numeric discriminator values
1431
+ * @throws ValidationError When the discriminator is invalid or the variant validation fails
843
1432
  * @internal
844
1433
  */
845
1434
  export function numberUnion<Key extends string, Config extends UnionValidatorConfig<Key, Config>>(
@@ -862,9 +1451,23 @@ export function numberUnion<Key extends string, Config extends UnionValidatorCon
862
1451
  }
863
1452
 
864
1453
  /**
865
- * A named object with an ID. Errors will be reported as being part of the object with the given
866
- * name.
1454
+ * Creates a validator for named model objects with enhanced error reporting. The model name
1455
+ * will be included in error messages to provide better debugging context.
867
1456
  *
1457
+ * @param name - The name of the model (used in error messages)
1458
+ * @param validator - The validator for the model structure
1459
+ * @returns A Validator with enhanced error reporting that includes the model name
1460
+ * @throws ValidationError With model name context when validation fails
1461
+ * @example
1462
+ * ```ts
1463
+ * const userModel = T.model('User', T.object({
1464
+ * id: T.string,
1465
+ * name: T.string,
1466
+ * email: T.linkUrl
1467
+ * }))
1468
+ *
1469
+ * // Error message will be: "At User.email: Expected a valid url, got 'invalid-email'"
1470
+ * ```
868
1471
  * @public
869
1472
  */
870
1473
  export function model<T extends { readonly id: string }>(
@@ -887,7 +1490,21 @@ export function model<T extends { readonly id: string }>(
887
1490
  )
888
1491
  }
889
1492
 
890
- /** @public */
1493
+ /**
1494
+ * Creates a validator that only accepts values from a given Set of allowed values.
1495
+ *
1496
+ * @param values - Set containing the allowed values
1497
+ * @returns A validator that only accepts values from the provided set
1498
+ * @throws ValidationError When the value is not in the allowed set
1499
+ * @example
1500
+ * ```ts
1501
+ * const allowedColors = new Set(['red', 'green', 'blue'] as const)
1502
+ * const colorValidator = T.setEnum(allowedColors)
1503
+ * colorValidator.validate('red') // Returns 'red'
1504
+ * colorValidator.validate('yellow') // Throws ValidationError
1505
+ * ```
1506
+ * @public
1507
+ */
891
1508
  export function setEnum<T>(values: ReadonlySet<T>): Validator<T> {
892
1509
  return new Validator((value) => {
893
1510
  if (!values.has(value as T)) {
@@ -898,7 +1515,20 @@ export function setEnum<T>(values: ReadonlySet<T>): Validator<T> {
898
1515
  })
899
1516
  }
900
1517
 
901
- /** @public */
1518
+ /**
1519
+ * Creates a validator that accepts either the validated type or undefined.
1520
+ *
1521
+ * @param validator - The base validator to make optional
1522
+ * @returns A validator that accepts T or undefined
1523
+ * @example
1524
+ * ```ts
1525
+ * const optionalString = T.optional(T.string)
1526
+ * optionalString.validate("hello") // Returns "hello"
1527
+ * optionalString.validate(undefined) // Returns undefined
1528
+ * optionalString.validate(null) // Throws ValidationError
1529
+ * ```
1530
+ * @public
1531
+ */
902
1532
  export function optional<T>(validator: Validatable<T>): Validator<T | undefined> {
903
1533
  return new Validator(
904
1534
  (value) => {
@@ -916,7 +1546,20 @@ export function optional<T>(validator: Validatable<T>): Validator<T | undefined>
916
1546
  )
917
1547
  }
918
1548
 
919
- /** @public */
1549
+ /**
1550
+ * Creates a validator that accepts either the validated type or null.
1551
+ *
1552
+ * @param validator - The base validator to make nullable
1553
+ * @returns A validator that accepts T or null
1554
+ * @example
1555
+ * ```ts
1556
+ * const nullableString = T.nullable(T.string)
1557
+ * nullableString.validate("hello") // Returns "hello"
1558
+ * nullableString.validate(null) // Returns null
1559
+ * nullableString.validate(undefined) // Throws ValidationError
1560
+ * ```
1561
+ * @public
1562
+ */
920
1563
  export function nullable<T>(validator: Validatable<T>): Validator<T | null> {
921
1564
  return new Validator(
922
1565
  (value) => {
@@ -933,7 +1576,21 @@ export function nullable<T>(validator: Validatable<T>): Validator<T | null> {
933
1576
  )
934
1577
  }
935
1578
 
936
- /** @public */
1579
+ /**
1580
+ * Creates a validator that only accepts one of the provided literal values.
1581
+ * This is a convenience function that creates a setEnum from the provided values.
1582
+ *
1583
+ * @param values - The allowed literal values
1584
+ * @returns A validator that only accepts the provided literal values
1585
+ * @throws ValidationError When the value is not one of the allowed literals
1586
+ * @example
1587
+ * ```ts
1588
+ * const themeValidator = T.literalEnum('light', 'dark', 'auto')
1589
+ * themeValidator.validate('light') // Returns 'light'
1590
+ * themeValidator.validate('blue') // Throws ValidationError: Expected "light" or "dark" or "auto", got blue
1591
+ * ```
1592
+ * @public
1593
+ */
937
1594
  export function literalEnum<const Values extends readonly unknown[]>(
938
1595
  ...values: Values
939
1596
  ): Validator<Values[number]> {
@@ -958,8 +1615,16 @@ function parseUrl(str: string) {
958
1615
  const validLinkProtocols = new Set(['http:', 'https:', 'mailto:'])
959
1616
 
960
1617
  /**
961
- * Validates that a value is a url safe to use as a link.
1618
+ * Validator for URLs that are safe to use as user-facing links. Accepts http, https, and mailto protocols.
1619
+ * This validator provides security by rejecting potentially dangerous protocols like javascript:.
962
1620
  *
1621
+ * @example
1622
+ * ```ts
1623
+ * const link = T.linkUrl.validate("https://example.com") // Valid
1624
+ * const email = T.linkUrl.validate("mailto:user@example.com") // Valid
1625
+ * T.linkUrl.validate("") // Valid (empty string allowed)
1626
+ * T.linkUrl.validate("javascript:alert(1)") // Throws ValidationError (unsafe protocol)
1627
+ * ```
963
1628
  * @public
964
1629
  */
965
1630
  export const linkUrl = string.check((value) => {
@@ -977,8 +1642,16 @@ export const linkUrl = string.check((value) => {
977
1642
  const validSrcProtocols = new Set(['http:', 'https:', 'data:', 'asset:'])
978
1643
 
979
1644
  /**
980
- * Validates that a valid is a url safe to load as an asset.
1645
+ * Validator for URLs that are safe to use as asset sources. Accepts http, https, data, and asset protocols.
1646
+ * The asset: protocol refers to tldraw's local IndexedDB object store.
981
1647
  *
1648
+ * @example
1649
+ * ```ts
1650
+ * const imageUrl = T.srcUrl.validate("https://example.com/image.png") // Valid
1651
+ * const dataUrl = T.srcUrl.validate("data:image/png;base64,iVBORw0...") // Valid
1652
+ * const assetUrl = T.srcUrl.validate("asset:abc123") // Valid (local asset reference)
1653
+ * T.srcUrl.validate("") // Valid (empty string allowed)
1654
+ * ```
982
1655
  * @public
983
1656
  */
984
1657
  export const srcUrl = string.check((value) => {
@@ -993,8 +1666,15 @@ export const srcUrl = string.check((value) => {
993
1666
  })
994
1667
 
995
1668
  /**
996
- * Validates an http(s) url
1669
+ * Validator for HTTP and HTTPS URLs only. Rejects all other protocols.
997
1670
  *
1671
+ * @example
1672
+ * ```ts
1673
+ * const apiUrl = T.httpUrl.validate("https://api.example.com") // Valid
1674
+ * const httpUrl = T.httpUrl.validate("http://localhost:3000") // Valid
1675
+ * T.httpUrl.validate("") // Valid (empty string allowed)
1676
+ * T.httpUrl.validate("ftp://files.example.com") // Throws ValidationError (not http/https)
1677
+ * ```
998
1678
  * @public
999
1679
  */
1000
1680
  export const httpUrl = string.check((value) => {
@@ -1009,7 +1689,15 @@ export const httpUrl = string.check((value) => {
1009
1689
  })
1010
1690
 
1011
1691
  /**
1012
- * Validates that a value is an IndexKey.
1692
+ * Validator for IndexKey values used in tldraw's indexing system. An IndexKey is a string
1693
+ * that meets specific format requirements for use as a database index.
1694
+ *
1695
+ * @throws ValidationError When the string is not a valid IndexKey format
1696
+ * @example
1697
+ * ```ts
1698
+ * const key = T.indexKey.validate("valid_index_key") // Returns IndexKey
1699
+ * T.indexKey.validate("invalid key!") // Throws ValidationError (invalid format)
1700
+ * ```
1013
1701
  * @public
1014
1702
  */
1015
1703
  export const indexKey = string.refine<IndexKey>((key) => {
@@ -1022,8 +1710,20 @@ export const indexKey = string.refine<IndexKey>((key) => {
1022
1710
  })
1023
1711
 
1024
1712
  /**
1025
- * Validate a value against one of two types.
1713
+ * Creates a validator that accepts values matching either of two validators.
1714
+ * Tries the first validator, and if it fails, tries the second validator.
1026
1715
  *
1716
+ * @param v1 - The first validator to try
1717
+ * @param v2 - The second validator to try if the first fails
1718
+ * @returns A validator that accepts values matching either validator
1719
+ * @throws ValidationError When the value matches neither validator (throws error from v2)
1720
+ * @example
1721
+ * ```ts
1722
+ * const stringOrNumber = T.or(T.string, T.number)
1723
+ * stringOrNumber.validate("hello") // Returns "hello" as string
1724
+ * stringOrNumber.validate(42) // Returns 42 as number
1725
+ * stringOrNumber.validate(true) // Throws ValidationError from number validator
1726
+ * ```
1027
1727
  * @public
1028
1728
  */
1029
1729
  export function or<T1, T2>(v1: Validatable<T1>, v2: Validatable<T2>): Validator<T1 | T2> {