npm.io
3.18.1 • Published 1 week ago

@deepseek-ai/schemastery

Licence
MIT
Version
3.18.1
Deps
2
Size
105 kB
Vulns
0
Weekly
0
Stars
184.4K

Schemastery

Codecov downloads npm GitHub

Type Driven Schema Validator.

Features

  • Lightweight. Much smaller than other validation libraries.
  • Easy to use. You can use any schema as a function or constructor directly.
  • Powerful. Schemastery supports some advanced types such as union, intersect and transform.
  • Extensible. You can create your own schema types via Schema.extend().
  • Serializable. Schema objects can be serialized into JSON and then be hydrated in another environment.

Basic Examples

use as validator (JavaScript)
const Schema = require('schemastery')

const validate = Schema.number().default(10)

validate(0)     // 0
validate(null)  // 10
validate('')    // TypeError
use as constructor (TypeScript)
import Schema from 'schemastery'

interface Config {
  foo: Record<string, string>
  bar: string[]
}

const Config = Schema.object({
  foo: Schema.dict(Schema.string()).default({}),
  bar: Schema.array(Schema.string()).default([]),
})

// config is an instance of Config
// in this case, that is { foo: {}, bar: [] }
const config = new Config()

General Types

Schema.any()

Assert that the value is of any type.

const validate = Schema.any()

validate()            // undefined
validate(0)           // 0
validate({})          // {}
Schema.never()

Assert that the value is nullable.

const validate = Schema.never()

validate()            // undefined
validate(0)           // TypeError
validate({})          // TypeError
Schema.const(value)

Assert that the value is equal to the given constant.

const validate = Schema.const(10)

validate(10)          // 10
validate(0)           // TypeError
Schema.number()

Assert that the value is a number.

const validate = Schema.number()

validate()            // undefined
validate(1)           // 1
validate('')          // TypeError
Schema.string()

Assert that the value is a string.

const validate = Schema.string()

validate()            // undefined
validate(0)           // TypeError
validate('foo')       // 'foo'
Schema.boolean()

Assert that the value is a boolean.

const validate = Schema.boolean()

validate()            // undefined
validate(0)           // TypeError
validate(true)        // true
Schema.is(constructor)

Assert that the value is an instance of the given constructor.

const validate = Schema.is(RegExp)

validate()            // undefined
validate(/foo/)       // /foo/
validate('foo')       // TypeError
Schema.array(inner)

Assert that the value is an array of inner. The default value will be [] if not specified.

const validate = Schema.array(Schema.number())

validate()                  // []
validate(0)                 // TypeError
validate([0, 1])            // [0, 1]
validate([0, '1'])          // TypeError
Schema.dict(inner)

Assert that the value is a dictionary of inner. The default value will be {} if not specified.

const validate = Schema.dict(Schema.number())

validate()                  // {}
validate(0)                 // TypeError
validate({ a: 0, b: 1 })    // { a: 0, b: 1 }
validate({ a: 0, b: '1' })  // TypeError
Schema.tuple(list)

Assert that the value is a tuple whose each element is of corresponding subtype. The default value will be [] if not specified.

const validate = Schema.tuple([
  Schema.number(),
  Schema.string(),
])

validate()                  // []
validate([0])               // { a: 0 }
validate([0, 1])            // TypeError
validate([0, '1'])          // [0, '1']
Schema.object(dict)

Assert that the value is an object whose each property is of corresponding subtype. The default value will be {} if not specified.

const validate = Schema.object({
  a: Schema.number(),
  b: Schema.string(),
})

validate()                  // {}
validate({ a: 0 })          // { a: 0 }
validate({ a: 0, b: 1 })    // TypeError
validate({ a: 0, b: '1' })  // { a: 0, b: '1' }
Schema.union(list)

Assert that the value is one of the specified types.

const validate = Schema.union([
  Schema.number(),
  Schema.string(),
])

validate()                  // undefined
validate(0)                 // 0
validate('1')               // '1'
validate(true)              // TypeError
Schema.intersect(list)

Assert that the value should match each specified type.

const validate = Schema.intersect([
  Schema.object({ a: Schema.string().required() }),
  Schema.object({ b: Schema.number().default(0) }),
])

validate()                  // TypeError
validate({ a: '' })         // { a: '', b: 0 }
validate({ a: '', b: 1 })   // { a: '', b: 1 }
validate({ a: '', b: '2' }) // TypeError
Schema.transform(inner, callback)

Assert that the value is of the specified subtype and then transformed by callback.

const validate = Schema.transform(Schema.number().default(0), n => n + 1)

validate()                  // 1
validate('0')               // TypeError
validate(10)                // 11

Instance Methods

Note: default and required are mutually exclusive.

schema.required()

Assert that the value is not nullable.

schema.default(value)

Set the fallback value when nullable.

schema.description(text)

Set the description of the schema.

schema.simplify(value)

Normalize a value by removing parts that are equal to schema defaults. This is useful when storing user configuration and keeping persisted files compact.

const Config = Schema.object({
  foo: Schema.string().default(''),
  bar: Schema.number().default(0),
})

Config.simplify({ foo: '', bar: 1 }) // { bar: 1 }

Validation Options

All schemas are callable. The second argument accepts validation options:

const Config = Schema.object({
  foo: Schema.number(),
})

Config({ foo: '1' }, { autofix: true }) // {}
  • autofix: remove invalid object properties where possible.
  • ignore: skip validation for selected values and schema nodes.
  • path: provide an initial path for nested validation errors.

Shorthand Syntax

Some shorthand syntax is available for inner types.

  • undefined -> Schema.any()
  • String -> Schema.string()
  • Number -> Schema.number()
  • Boolean -> Schema.boolean()
  • 1 -> Schema.const(1) (only for primitive types)
  • Date -> Schema.is(Date)
Schema.array(String)        // Schema.array(Schema.string())
Schema.dict(RegExp)         // Schema.dict(Schema.is(RegExp))
Schema.union([1, 2])        // Schema.union([Schema.const(1), Schema.const(2)])

You can also use Schema.from() to get the inferred schema from a shorthand value.

Schema.from()               // Schema.any()
Schema.from(Date)           // Schema.is(Date)
Schema.from('foo')          // Schema.const('foo')

Advanced Examples

Here are some examples which demonstrate how to define advanced types.

Enumeration
const Enum = Schema.union(['red', 'blue'])

Enum('red')                 // 'red'
Enum('blue')                // 'blue'
Enum('green')               // TypeError
ToString
const ToString = Schema.transform(Schema.any(), v => String(v))

ToString('')                // ''
ToString(0)                 // '0'
ToString({})                // '{}'
Listable
const Listable = Schema.union([
  Schema.array(Number),
  Schema.transform(Number, n => [n]),
]).default([])

Listable()                  // []
Listable(0)                 // [0]
Listable([1, 2])            // [1, 2]
Alias
const Config = Schema.dict(Number, Schema.union([
  'foo',
  Schema.transform('bar', () => 'foo'),
]))

Config({ foo: 1 })          // { foo: 1 }
Config({ bar: 2 })          // { foo: 2 }
Config({ bar: '3' })        // TypeError

Extensibility

Custom schema types are registered with Schema.extend(type, resolve). A resolver receives the input value, schema node, validation options, and a strict flag. Return [value] for accepted input, or [value, adapted] when the caller should write an adapted value back to the source object.

Schema.extend('trimmed', (data, schema, options) => {
  if (typeof data !== 'string') {
    throw new Schema.ValidationError(`expected string but got ${data}`, options)
  }
  return [data.trim()]
})

Serializability

const schema1 = Schema.object({
  foo: Schema.string(),
  bar: Schema.number(),
})

// should have the same effect as schema1
const schema2 = new Schema(JSON.parse(JSON.stringify(schema1)))

Schemastery also exposes the Standard Schema ~standard property, so compatible tools can validate values without depending on Schemastery-specific APIs.