deno.land / x / typebox@0.32.21 / changelog / 0.32.0.md

0.32.0

Overview

Revision 0.32.0 adds support for ESM and carries out the work necessary to fully modularize the TypeBox type system to enable selective type imports. This revision also adds three new types (Mapped, Const, and Deref), along with two new Value functions (Clean and Default) as well as many enhancements to existing types (Index, KeyOf, RegExp, Optional and Readonly). This revision also carries out many internal optimizations to enhance type inference across all types.

This revision is a milestone revision for the TypeBox project. It has several breaking changes and requires a minor revision.

Contents

Type Imports

Revision 0.32.0 adds the ability to import types individually.

import { Type, type Static } from '@sinclair/typebox'                   // classic   - 37.0 kb minified

import { Object, String, Number, type Static } from '@sinclair/typebox' // selective - 6.5 kb minified

Value Function Imports

Revision 0.32.0 adds the ability to import value functions from the /value module path.

import { Value } from '@sinclair/typebox/value'                         // classic   - 61.5 kb minified

import { Check } from '@sinclair/typebox/value'                         // selective - 18.2 kb minified

CommonJS and ESM

Revision 0.32.0 now publishes both CommonJS and ESM builds of TypeBox. Existing CommonJS users should not be impacted by the addition of ESM. For ESM users however, particularily those using bundlers, it's now possible to benefit from deep tree shake optimizations provided by modern bundler tooling.

Types

Revision 0.32.0 adds three new types to the type system and makes enhancements to Readonly and Optional modifiers.

Mapped Type

Revision 0.32.0 adds the Mapped type which replicates TS Mapped Types at runtime. The following shows the syntax comparison between TypeScript and TypeBox.

TypeScript

type T = {
  x: number,
  y: number,
  z: number
}

type M = { [K in keyof T]: T[K] } // a mapped type

TypeBox

const T = Type.Object({
  x: Type.Number(),
  y: Type.Number(),
  z: Type.Number()
})

const M = Type.Mapped(Type.KeyOf(T), K => Type.Index(T, K)) // a mapped type

Mapped types use a functional design to replicate the TypeScript feature. For users interested in this type, it may be helpful to use the TypeBox Workbench which can generate runtime Mapped types from TypeScript syntax.

Const Type

Revision 0.32.0 adds a new Const type that creates readonly types from object, array and primitive literal values. This type analogs the TypeScript as const syntax. The following shows general usage.

const A = Type.Const(1 as const)                               // const A: TLiteral<1>

const B = Type.Const([1, 2, 3] as const)                       // const B: TReadonly<TTuple<[
                                                               //   TLiteral<1>,
                                                               //   TLiteral<2>,
                                                               //   TLiteral<3>
                                                               // ]>>

const C = Type.Const({                                         // const C: TObject<{
  x: 1,                                                        //  x: TReadonly<TLiteral<1>>,
  y: 2,                                                        //  y: TReadonly<TLiteral<2>>,
  z: 3                                                         //  z: TReadonly<TLiteral<3>>,
} as const)                                                    // }>                                            

Revision 0.32.0 continues support for TypeScript 4.0, and because of this, the as const syntax must be appended to each literal value passed to the Const type. When TypeBox ends support for 4.0, updates will be made to this type to make use of Const Type Parameters. This update will enable TypeBox to correctly infer the readonly literal type without the need for as const.

Deref Type

Revision 0.32.0 adds a new Type.Deref type which can be used to dereference type schematics.

const Vector = Type.Object({                                   // const Vector = {
  x: Type.Number(),                                            //   type: 'object',
  y: Type.Number(),                                            //   required: ['x', 'y', 'z'],
}, { $id: 'Vector' })                                          //   properties: {
                                                               //     x: { type: 'number' },
                                                               //     y: { type: 'number' }
                                                               //   },
                                                               //   $id: 'Vector'
                                                               // }

const VectorRef = Type.Ref(Vector)                             // const VectorRef = {
                                                               //   $ref: 'Vector'
                                                               // }
// ... Embedded Reference Type

const Vertex = Type.Object({                                   // const Vertex = {
  position: VectorRef,                                         //   type: 'object',
  texcoord: VectorRef,                                         //   required: ['position', 'texcoord'],
})                                                             //   properties: {
                                                               //     position: { $ref: 'Vector' },
                                                               //     texcoord: { $ref: 'Vector' }
                                                               //   }
                                                               // }

// ... Dereferenced Embedded Reference Type

const VertexDeref = Type.Deref(Vertex, [Vector])               // const VertexDeref = {
                                                               //   type: 'object',
                                                               //   required: ['position', 'texcoord'],
                                                               //   properties: {
                                                               //     position: {
                                                               //       type: 'object',
                                                               //       required: ['x', 'y', 'z'],
                                                               //       properties: {
                                                               //         x: { type: 'number' },
                                                               //         y: { type: 'number' }
                                                               //       }
                                                               //     },
                                                               //     texcoord: {
                                                               //       type: 'object',
                                                               //       required: ['x', 'y', 'z'],
                                                               //       properties: {
                                                               //         x: { type: 'number' },
                                                               //         y: { type: 'number' }
                                                               //       }
                                                               //     }
                                                               //   }
                                                               // }

The addition of Deref was prompted by issues composing reference types with mapping types (such as Partial, Required, Pick and Omit) which is generally not supported. Prior to Revision 0.32.0, there was some expectation for users to maintain and dereference types manually. In 0.32.0, users will still need to maintain references, but Deref will offer a more convenient mechanism to normalize reference types prior to composition.

RegExp Type

Revision 0.32.0 updates RegExp to support the full ECMA 262 regular expression syntax. In previous revisions, this type had been expressed as an alias for TString with a pattern to try ensure compliance with the regular expression subset supported by Json Schema. In Revision 0.32.0, RegExp is given a new type representation unto itself (named TRegExp) which houses both source and flags properties used to reconstruct a JavaScript regular expression object, making it properly distinct from TString and fully supportive of UTF-16.

// Case Insensitive

const T = Type.RegExp(/abc/i)                                  // const T = {
                                                               //   type: 'RegExp',
                                                               //   source: 'abc',
                                                               //   flags: 'i'
                                                               // }

type T = Static<typeof T>                                      // type T = string

Value.Check(T, 'abc')                                          // ok
Value.Check(T, 'ABC')                                          // ok

// Extended Syntax

const E = Type.RegExp(/<a?:.+?:\d{18}>|\p{Extended_Pictographic}/gu)

Value.Check(E, '♥️♦️♠️♣️')                                         // ok - emoji supported

The RegExp type can be thought of as a more capable TemplateLiteral that can only reasonably infer as string. Additionally, the RegExp inference type of string is unique to the other [JavaScript] types in that it does not infer as it's named type. The updates to RegExp were prompted by the limitations with Json Schema expressions, and to provide better options for users requiring general Unicode validation support. For Json Schema compliance, the recommendation moving forward will be to use either String with pattern or TemplateLiteral.

const T = Type.String({ pattern: '^(a|b|c)$' })                // Json Schema compliant

const T = Type.TemplateLiteral('${a|b|c}')                     // Json Schema compliant

const T = Type.RegExp(/$(a|b|c)$/)                             // Non Json Schema compliant

Subtract Modifier

Revision 0.32.0 adds new overloads for Readonly and Optional modifiers that enable them to subtract (or remove) that modifier from a type. Both Readonly and Optional now accept an optional secondary boolean argument that if false, will remove the modifier.

TypeScript

type T = {
  x?: number,
  y?: number
}
type M = { [K in keyof T]-?: T[K] } // -? - subtract optional modifier

TypeBox

const T = Type.Object({
  x: Type.Optional(Type.Number()),
  y: Type.Optional(Type.Number())
})
const M = Type.Mapped(Type.KeyOf(T), K => {
  return Type.Optional(Type.Index(T, K), false) // false - subtract optional modifier
})

Subtractive modifiers are provided in support of the new Mapped type feature.

Values

Revision 0.32.0 adds two new functions to the Value module.

Clean Function

Revision 0.32.0 adds a new Clean function that can be used to omit any values unknown to the type. This function will work irrespective of if additionalProperties is specified on the type. The Clean function is intended to replicate the functionality of Ajv's removeAdditional configuration.

const T = Type.Object({ 
  x: Type.Number(), 
  y: Type.Number() 
})

const X = Value.Clean(T, null)                                  // const 'X = null

const Y = Value.Clean(T, { x: 1 })                              // const 'Y = { x: 1 }

const Z = Value.Clean(T, { x: 1, y: 2, z: 3 })                  // const 'Z = { x: 1, y: 2 }

Note: the Clean function does not check the validity of the value being cleaned, and does not provide assurances that the result will be valid. Its return value is unknown and should be checked before use.

Default Function

Revision 0.32.0 adds a new Default function that can be used to add missing values if the type specifies a default annotation. This function is intended to replicate Ajv's useDefaults functionality.

const T = Type.Object({ 
  x: Type.Number({ default: 0 }), 
  y: Type.Number({ default: 0 })
})

const X = Value.Default(T, null)                               // const 'X = null - non-enumerable

const Y = Value.Default(T, { })                                // const 'Y = { x: 0, y: 0 }

const Z = Value.Default(T, { x: 1 })                           // const 'Z = { x: 1, y: 0 }

The Default function does not check the validity of the value being defaulted, and does not provide assurances that the result will be valid. Its return value is unknown and should be checked before use.

Optimizations

Following the work to modularize TypeBox's type system, additional optimizations were carried out across each submodule to only import dependent type infrastructure. This has led to some fairly significant reductions in output sizes across each submodule. The main TypeBox import has increased in size due in part to the new Mapped types feature and other associative types, however selective imports supported on this revision should offer options for users concerned about output bundle size. There will be contined work to optimize the new type system throughout 0.32.0 and subsequent revisions.

The following shows the comparisons between 0.31.0 and 0.32.0.

// Revision 0.31.0

┌──────────────────────┬────────────┬────────────┬─────────────┐
       (index)        │  Compiled  │  Minified  │ Compression │
├──────────────────────┼────────────┼────────────┼─────────────┤
│ typebox/compiler     │ '163.6 kb'' 71.6 kb''2.28 x'   │
│ typebox/errors       │ '113.3 kb'' 50.1 kb''2.26 x'   │
│ typebox/system       │ ' 83.9 kb'' 37.5 kb''2.24 x'   │
│ typebox/value        │ '191.1 kb'' 82.3 kb''2.32 x'   │
│ typebox              │ ' 73.8 kb'' 32.3 kb''2.29 x'   │
└──────────────────────┴────────────┴────────────┴─────────────┘

//  Revision 0.32.0

┌──────────────────────┬────────────┬────────────┬─────────────┐
       (index)        │  Compiled  │  Minified  │ Compression │
├──────────────────────┼────────────┼────────────┼─────────────┤
│ typebox/compiler     │ '120.6 kb'' 52.9 kb''2.28 x'   │
│ typebox/errors       │ ' 55.7 kb'' 25.5 kb''2.19 x'   │
│ typebox/system       │ '  4.7 kb''  2.0 kb''2.33 x'   │
│ typebox/value        │ '146.2 kb'' 62.0 kb''2.36 x'   │
│ typebox              │ ' 91.4 kb'' 37.8 kb''2.42 x'   │
└──────────────────────┴────────────┴────────────┴─────────────┘

Errors

Revision 0.32.0 makes some enhancements to errors.

Error Parameter

Revision 0.32.0 updates TypeBox's ErrorFunction to accept an ErrorParameter that contains additional information regarding the cause of a validation error. In Revision 0.31.0, only errorType and schema were passed through to this function. In 0.32.0 the additional properties value and path are also passed through. This update was prompted by some users needing to be able to generate specific error messages derived from specific values or other associated information.

The following shows the changes from 0.31.0 to 0.32.0.

// Revision 0.31.0

import { TypeSystemErrorFunction } from '@sinclair/typebox/system'

TypeSystemErrorFunction.Set((schema, errorType) => {

   return 'oh no, an error!'
})

// Revision 0.32.0

import { SetErrorFunction } from '@sinclair/typebox/errors'

SetErrorFunction(({ schema, errorType, path, value }) => { // as destructured object

  return 'oh no, an error!'
})

Note that Revision 0.32.0 does make a breaking interface change by moving the ErrorFunction from /system to /errors. See breaking changes for more information.

Breaking

The following list the breaking changes in Revision 0.32.0.

Renamed Symbols

Revision 0.32.0 renames the Optional, Required and Transform symbols to OptionalKind, RequiredKind and TransformKind. This change was necessary to avoid conflicts with exported type functions.

// Revision 0.31.0
import { Kind, Hint, Optional, Required, Transform } from '@sinclair/typebox' // these are symbols

// Revision 0.32.0
import { 
  Kind, Hint, OptionalKind, RequiredKind, TransformKind,                      // these are symbols
  Optional, Required, Transform                                               // these are type imports
} from '@sinclair/typebox' 

TypeGuard Interface Change

Revision 0.32.0 has a breaking interface change on the TypeGuard utility where the T prefixed guard functions have been updated to use the Is prefix. This naming change is perhaps somewhat more sensible than the previous naming, however the update was largely prompted by TypeScript compiler issues where interface types (i.e. TString) where conflicting with the TString functions leading to breakage in CommonJS.

// Revision 0.31.0

import { TypeGuard, Kind } from '@sinclair/typebox'

const R = TypeGuard.TString({ ... })

// Revision 0.32.0

import { TypeGuard } from '@sinclair/typebox'

const R = TypeGuard.IsString({ ... })

Value Submodule Imports

The value submodule function import paths are unfortunately no longer supported. Instead, these can be imported directly on the /value path. The need to break the submodule paths was mostly due to complexities configuring dual ESM and CommonJS publishing for the package, as well as retaining support for pre and post node16 module resolution (of which many complexities reside, both for Node as well as for TypeScript type module resolution)

// Revision 0.31.0

import { Check } from '@sinclair/typebox/value/check'

// Revision 0.32.0

import { Check } from '@sinclair/typebox/value'

Error Function

The TypeSystemErrorFunction has been replaced with SetErrorFunction which can be imported on the /errors submodule. This change is generally a tidy up, and to reserve the /system submodule for type system policy configuration, as well as future Json Schema generation options (draft 2020-12)

// Revision 0.31.0

import { TypeSystemErrorFunction, ValueErrorType, DefaultErrorFunction } from '@sinclair/typebox/system'

TypeSystemErrorFunction.Set((schema, errorType) => { // i18n override
  switch(errorType) {
    /* en-US */ case ValueErrorType.String: return 'Expected string'
    /* fr-FR */ case ValueErrorType.Number: return 'Nombre attendu'  
    /* ko-KR */ case ValueErrorType.Boolean: return '예상 부울'      
    /* en-US */ default: return DefaultErrorFunction(schema, errorType)          
  }
})

// Revision 0.32.0

import { SetErrorFunction, ValueErrorType, DefaultErrorFunction } from '@sinclair/typebox/errors'

SetErrorFunction((error) => { // i18n override
  switch(error.errorType) {
    /* en-US */ case ValueErrorType.String: return 'Expected string'
    /* fr-FR */ case ValueErrorType.Number: return 'Nombre attendu'  
    /* ko-KR */ case ValueErrorType.Boolean: return '예상 부울'      
    /* en-US */ default: return DefaultErrorFunction(error)          
  }
})

RegEx

This RegEx function was flagged for deprecation on 0.30.0. It has been removed on Revision 0.32.0. Use the Type.RegExp type, or Type.String with a pattern to remain compatible with the Json Schema specification.

// Revision 0.31.0

const T = Type.RegEx(/abc/)                                    // deprecation warning

// Revision 0.32.0

const A = Type.RegExp(/abc/)                                   // JavaScript Type

const B = Type.String({ pattern: /abc/.source })               // Json Type
typebox

Version Info

Tagged at
4 weeks ago