Table of contents

The main validator class that implements the Validatable interface. This is the base class for all validators and provides methods for validation, type checking, and composing validators.

class Validator<T> implements Validatable<T> {}

Example

const numberValidator = new Validator((value) => {
  if (typeof value !== 'number') {
    throw new ValidationError('Expected number')
  }
  return value
})

const result = numberValidator.validate(42) // Returns 42 as number

Constructor

Creates a new Validator instance.

validationFn - Function that validates and returns a value of type T validateUsingKnownGoodVersionFn - Optional performance-optimized validation function

Parameters

NameDescription

validationFn

validateUsingKnownGoodVersionFn

  | undefined
  | ValidatorUsingKnownGoodVersionFn<T>

Properties

validateUsingKnownGoodVersionFn

readonlyoptional
readonly validateUsingKnownGoodVersionFn?:
  | undefined
  | ValidatorUsingKnownGoodVersionFn<T>

validationFn

readonly
readonly validationFn: ValidatorFn<T>

Methods

check( )

Adds an additional validation check without changing the resulting value type. Can be called with just a check function, or with a name for better error messages.

check(name: string, checkFn: (value: T) => void): Validator<T>

Example

import { T, ValidationError } from '@tldraw/validate'

// Basic check without name
const evenNumber = T.number.check((value) => {
  if (value % 2 !== 0) {
    throw new ValidationError('Expected even number')
  }
})

// Named checks for better error messages in complex validators
const shapePositionValidator = T.object({
  x: T.number.check('finite', (value) => {
    if (!Number.isFinite(value)) {
      throw new ValidationError('Position must be finite')
    }
  }),
  y: T.number.check('within-bounds', (value) => {
    if (value < -10000 || value > 10000) {
      throw new ValidationError(
        'Position must be within bounds (-10000 to 10000)'
      )
    }
  }),
})

// Error will be: "At x (check finite): Position must be finite"

Parameters

NameDescription

name

string

Name for the check (used in error messages)

checkFn

(value: T) => void

Function that validates the value (should throw on invalid input)

Returns

Validator<T>

A new validator with the additional check


isValid( )

Type guard that checks if a value is valid without throwing an error.

isValid(value: unknown): value is T

Example

import { T } from '@tldraw/validate'

function processUserInput(input: unknown) {
  if (T.string.isValid(input)) {
    // input is now typed as string within this block
    return input.toUpperCase()
  }
  if (T.number.isValid(input)) {
    // input is now typed as number within this block
    return input.toFixed(2)
  }
  throw new Error('Expected string or number')
}

Parameters

NameDescription

value

unknown

The value to check

Returns

value is T

True if the value is valid, false otherwise


nullable( )

Returns a new validator that also accepts null values.

nullable(): Validator<null | T>

Example

import { T } from '@tldraw/validate'

const assetValidator = T.object({
  id: T.string,
  name: T.string,
  src: T.srcUrl.nullable(), // Can be null if not loaded yet
  mimeType: T.string.nullable(),
})

const asset = assetValidator.validate({
  id: 'image-123',
  name: 'photo.jpg',
  src: null, // Valid - asset not loaded yet
  mimeType: 'image/jpeg',
})

optional( )

Returns a new validator that also accepts undefined values.

optional(): Validator<T | undefined>

Example

import { T } from '@tldraw/validate'

const shapeConfigValidator = T.object({
  type: T.literal('rectangle'),
  x: T.number,
  y: T.number,
  label: T.string.optional(), // Optional property
  metadata: T.object({ created: T.string }).optional(),
})

// Both of these are valid:
const shape1 = shapeConfigValidator.validate({ type: 'rectangle', x: 0, y: 0 })
const shape2 = shapeConfigValidator.validate({
  type: 'rectangle',
  x: 0,
  y: 0,
  label: 'My Shape',
})

refine( )

Creates a new validator by refining this validator with additional logic that can transform the validated value to a new type.

refine<U>(otherValidationFn: (value: T) => U): Validator<U>

Example

import { T, ValidationError } from '@tldraw/validate'

// Transform string to ensure it starts with a prefix
const prefixedIdValidator = T.string.refine((id) => {
  return id.startsWith('shape:') ? id : `shape:${id}`
})

const id1 = prefixedIdValidator.validate('rectangle-123') // Returns "shape:rectangle-123"
const id2 = prefixedIdValidator.validate('shape:circle-456') // Returns "shape:circle-456"

// Parse and validate JSON strings
const jsonValidator = T.string.refine((str) => {
  try {
    return JSON.parse(str)
  } catch {
    throw new ValidationError('Invalid JSON string')
  }
})

Parameters

NameDescription

otherValidationFn

(value: T) => U

Function that transforms/validates the value to type U

Returns

Validator<U>

A new validator that validates to type U


validate( )

Validates an unknown value and returns it with the correct type. The returned value is guaranteed to be referentially equal to the passed value.

validate(value: unknown): T

Example

import { T } from '@tldraw/validate'

const name = T.string.validate('Alice') // Returns "Alice" as string
const title = T.string.validate('') // Returns "" (empty strings are valid)

// These will throw ValidationError:
T.string.validate(123) // Expected string, got a number
T.string.validate(null) // Expected string, got null
T.string.validate(undefined) // Expected string, got undefined

Parameters

NameDescription

value

unknown

The unknown value to validate

Returns

T

The validated value with type T


validateUsingKnownGoodVersion( )

Performance-optimized validation using a previously validated value. If the new value is referentially equal to the known good value, returns the known good value immediately.

validateUsingKnownGoodVersion(knownGoodValue: T, newValue: unknown): T

Example

import { T } from '@tldraw/validate'

const userValidator = T.object({
  name: T.string,
  settings: T.object({ theme: T.literalEnum('light', 'dark') }),
})

const user = userValidator.validate({
  name: 'Alice',
  settings: { theme: 'light' },
})

// Later, with partially changed data:
const newData = { name: 'Alice', settings: { theme: 'dark' } }
const updated = userValidator.validateUsingKnownGoodVersion(user, newData)
// Only validates the changed 'theme' field for better performance

Parameters

NameDescription

knownGoodValue

T

A previously validated value

newValue

unknown

The new value to validate

Returns

T

The validated value, potentially reusing the known good value


Prev
UnionValidator