@traversable/typebox
v0.0.31
Published
<br> <h1 align="center">แฏ๐๐ฟ๐ฎ๐๐ฒ๐ฟ๐๐ฎ๐ฏ๐น๐ฒ/๐๐๐ฝ๐ฒ๐ฏ๐ผ๐ </h1> <br>
Downloads
29
Readme
Requirements
@traversable/typebox has a peer dependency on TypeBox (v0.34).
Getting started
$ pnpm add @traversable/typebox @sinclair/typeboxHere's an example of importing the library:
import * as T from '@sinclair/typebox'
import { box } from '@traversable/typebox'
// or, if you prefer, you can use named imports:
import { deepClone, deepEqual } from '@traversable/typebox'
// see below for specific examplesTable of contents
Fuzz-tested, production ready
Advanced
Features
box.deepClone
box.deepClone lets users derive a specialized "deep copy" function that works with values that have been already validated.
Because the values have already been validated, clone times are significantly faster than alternatives like window.structuredClone and Lodash.cloneDeep.
Performance comparison
Here's a Bolt sandbox if you'd like to run the benchmarks yourself.
โโโโโโโโโโโโโโโโโโโ
โ (avg) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค
โ Lodash.cloneDeep โ 31.32x faster โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค
โ window.structuredClone โ 54.36x faster โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโThis article goes into more detail about what makes box.deepClone so fast.
Example
import * as T from '@sinclair/typebox'
import { box } from '@traversable/typebox'
const Address = T.Object({
street1: T.String(),
street2: T.Optional(T.String()),
city: T.String(),
})
const deepClone = box.deepClone(Address)
const deepEqual = box.deepEqual(Address)
const sherlock = { street1: '221 Baker St', street2: '#B', city: 'London' }
const harry = { street1: '4 Privet Dr', city: 'Little Whinging' }
const sherlockCloned = deepClone(sherlock)
const harryCloned = deepClone(harry)
deepEqual(sherlockCloned, sherlock) // => true
deepEqual(harryCloned, harry) // => true
sherlock === sherlockCloned // => false
harry === harryCloned // => falseSee also
box.deepClone.writeable
box.deepClone.writeable lets users derive a specialized "deep clone" function that works with values that have been already validated.
Compared to box.deepClone, box.deepClone.writeable returns
the clone function in stringified ("writeable") form.
Example
import { box } from '@traversable/typebox'
const deepClone = box.deepClone.writeable({
type: 'object',
required: ['street1', 'city'],
properties: {
street1: { type: 'string' },
street2: { type: 'string' },
city: { type: 'string' },
}
}, { typeName: 'Address' })
console.log(deepClone)
// =>
// type Address = { street1: string; street2?: string; city: string; }
// function deepClone(prev: Address): Address {
// return {
// street1: prev.street1,
// ...prev.street2 !== undefined && { street2: prev.street2 },
// city: prev.city
// }
// }See also
box.deepEqual
box.deepEqual lets users derive a specialized "deep equal" function that works with values that have been already validated.
Because the values have already been validated, comparison times are significantly faster than alternatives like NodeJS.isDeepStrictEqual and Lodash.isEqual.
Performance comparison
Here's a Bolt sandbox if you'd like to run the benchmarks yourself.
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ Array (avg) โ Object (avg) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโค
โ NodeJS.isDeepStrictEqual โ 40.3x faster โ 56.5x faster โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโค
โ Lodash.isEqual โ 53.7x faster โ 60.1x faster โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโThis article goes into more detail about what makes box.deepEqual so fast.
Notes
- Best performance
- Works in any environment that supports defining functions using the
Functionconstructor, including (as of May 2025) Cloudflare workers ๐
Example
import { box } from '@traversable/typebox'
const deepEqual = box.deepEqual({
type: 'object',
required: ['street1', 'city'],
properties: {
street1: { type: 'string' },
street2: { type: 'string' },
city: { type: 'string' },
}
})
deepEqual(
{ street1: '221B Baker St', city: 'London' },
{ street1: '221B Baker St', city: 'London' }
) // => true
deepEqual(
{ street1: '221B Baker St', city: 'London' },
{ street1: '4 Privet Dr', city: 'Little Whinging' }
) // => falseSee also
box.deepEqual.writeable
box.deepEqual.writeable lets users derive a specialized "deep equal" function that works with values that have been already validated.
Compared to box.deepEqual, box.deepEqual.writeable returns
the deep equal function in stringified ("writeable") form.
Notes
- Useful when you're consuming a set of TypeBox schemas and writing all them to disc somewhere
- Also useful for testing purposes or for troubleshooting, since it gives you a way to "see" exactly what the deepEqual functions are doing
Example
import { box } from '@traversable/typebox'
const deepEqual = box.deepEqual.writeable({
type: 'object',
required: ['street1', 'city'],
properties: {
street1: { type: 'string' },
street2: { type: 'string' },
city: { type: 'string' },
}
}, { typeName: 'Address' })
console.log(deepEqual)
// =>
// type Address = { street1: string; street2?: string; city: string; }
// function deepEqual(x: Address, y: Address) {
// if (x === y) return true;
// if (x.street1 !== y.street1) return false;
// if (x.street2 !== y.street2) return false;
// if (x.city !== y.city) return false;
// return true;
// }See also
box.fold
[!NOTE]
box.foldis an advanced API.
Use box.fold to define a recursive traversal of a TypeBox schema. Useful when building a schema rewriter.
What does it do?
Writing an arbitrary traversal with box.fold is:
- non-recursive
- 100% type-safe
The way it works is pretty simple: if you imagine all the places in the TypeBox schema that are recursive, those "holes" will be the type that you provide via type parameter.
Example
Let's write a function that takes an arbitrary TypeBox schema, and generates mock data that satisfies the schema (a.k.a. a "faker").
[!NOTE] You can play with this example on StackBlitz
import * as T from '@sinclair/typebox'
import { faker } from '@faker-js/faker'
import { F, tagged } from '@traversable/typebox'
type Fake = () => unknown
const fake = F.fold<Fake>((x) => {
// ๐__๐ this type parameter fills in the "holes" below
switch (true) {
case tagged('array')(x): return () => faker.helpers.multiple(
() => x.items()
// ^? method items: Fake
// ๐__๐
)
case tagged('never')(x): return () => void 0
case tagged('unknown')(x): return () => void 0
case tagged('any')(x): return () => void 0
case tagged('void')(x): return () => void 0
case tagged('null')(x): return () => null
case tagged('undefined')(x): return () => undefined
case tagged('symbol')(x): return () => Symbol()
case tagged('boolean')(x): return () => faker.datatype.boolean()
case tagged('integer')(x): return () => faker.number.int()
case tagged('bigInt')(x): return () => faker.number.bigInt()
case tagged('number')(x): return () => faker.number.float()
case tagged('string')(x): return () => faker.lorem.words()
case tagged('date')(x): return () => faker.date.recent()
case tagged('literal')(x): return () => x.const
case tagged('allOf')(x): return () => Object.assign({}, ...x.allOf)
case tagged('anyOf')(x): return () => faker.helpers.arrayElement(x.anyOf.map((option) => option()))
case tagged('optional')(x): return () => faker.helpers.arrayElement([x.schema, undefined])
case tagged('tuple')(x): return () => x.items.map((item) => item())
case tagged('record')(x): return () => Object.fromEntries(Object.entries(x.patternProperties).map(([k, v]) => [k, v()]))
case tagged('object')(x): return () => Object.fromEntries(Object.entries(x.properties).map(([k, v]) => [k, v()]))
default: { x satisfies never; throw Error('Unsupported schema') }
// ๐_______________๐
// exhaustiveness check works
}
})
// Let's test it out:
const mock = fake(
T.Object({
abc: T.Array(T.String()),
def: T.Optional(
T.Tuple([
T.Number(),
T.Boolean()
])
)
})
)
console.log(mock())
// => {
// abc: [
// 'annus iure consequatur',
// 'aer suus autem',
// 'delectus patrocinor deporto',
// 'benevolentia tonsor odit',
// 'stabilis dolor tres',
// 'mollitia quibusdam vociferor'
// ],
// def: [-882, false]
// }Theory
box.fold is similar to, but more powerful than, the visitor pattern.
If you're curious about the theory behind it, its implementation was based on a 1991 paper called Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.
See also
box.Functor
[!NOTE]
box.Functoris an advanced API.
box.Functor is the primary abstraction that powers @traversable/typebox.
box.Functor is a powertool. Most of @traversable/typebox uses box.Functor under the hood.
Compared to the rest of the library, it's fairly "low-level", so unless you're doing something pretty advanced you probably won't need to use it directly.
