Files
typebox/changelog/0.34.0.md
sinclair 13d553220c Publish
2025-12-24 15:44:34 +09:00

13 KiB

0.34.0


Revision Updates

0.34.0

Overview

Revision 0.34.0 represents a significant milestone for the TypeBox project. This update changes how TypeBox manages type references (Ref) and introduces a new Module type to support mutual recursion and self-referencing types. Additionally, it includes a new submodule for parsing TypeScript syntax directly into TypeBox types.

Please note that this release includes breaking changes to Ref and some deprecations. These updates require a minor semver revision.

Contents

Enhancements

Below are the enhancements introduced in Version 0.34.0.

Module Types

Revision 0.34.0 introduces a new type, called Module. Modules are represented as JSON Schema $def schematics, specifically designed to support both mutual and self-recursive types. This addition resolves a longstanding issue in TypeBox, where complex recursive structures often encountered "definition order" problems, making certain recursive structures difficult to represent cleanly. With Modules, you can now define schematics within a Module context, allowing them to be referenced within the type system through a separate inference operation.

// The following creates a circular recursive type.

const Module = Type.Module({
  A: Type.Object({ 
    b: Type.Ref('B') // Ref B:
  }),
  B: Type.Object({
    c: Type.Ref('C') // Ref C:
  }),
  C: Type.Object({
    a: Type.Ref('A') // Ref A:
  }),
})

// Module types must be imported before use.

const A = Module.Import('A')                     // const A: TImport<{...}, 'A'>

type A = Static<typeof A>                        // type A = { 
                                                 //   b: {
                                                 //     c: {
                                                 //       a: {
                                                 //         b: ...
                                                 //       }
                                                 //     }
                                                 //   }
                                                 // }

Syntax Types

Revision 0.34.0 introduces a new submodule for parsing TypeScript syntax directly into TypeBox types, implemented both at runtime and within the type system. This feature was made possible through the development of a separate project, ParseBox (MIT-licensed), which provides a symmetric runtime and type-level parsing infrastructure.

As of 0.34.0, Syntax Types are available as an opt-in feature, with the parsing infrastructure adding approximately 10kb (minified) to the existing type builder. With further optimizations, this feature may be elevated to a top-level import in future updates to minimize bundling size.

To use Syntax Types, import them from the @sinclair/typebox/syntax path.

import { Parse } from '@sinclair/typebox/syntax'

// All primitive types are supported

const A = Parse('string')    // const A: TString
const B = Parse('number')    // const B: TNumber
const C = Parse('boolean')   // const C: TBoolean

// ... Multiline parsing is supported (but comments are not)

const T = Parse(`{
  x: number
  y: number
  z: number
}`)


// ... Parametertized parsing is supported
const O = Parse({ T }, `T & { w: number }`) // const O: TIntersect<[
                                            //   TObject<{
                                            //     x: TNumber,
                                            //     y: TNumber,
                                            //     z: TNumber,
                                            //   }>,
                                            //   TObject<{
                                            //     w: TNumber
                                            //   }>
                                            // ]>

// ... Module parsing is also supported.

const Math = Parse(`module Math {
  export interface X {
    x: number
  }
  export interface Y {
    y: number
  }
  export interface Z {
    z: number
  }
  export interface Vector extends X, Y, Z {
    type: 'Vector'
  }
}`)

const Vector = Math.Import('Vector')     

Runtime parsing performance should be quite good; however, static parsing performance could be improved. TypeScript will invoke the parser for each property accessed at design time. Ongoing efforts within the ParseBox project aim to optimize string parsing in TypeScript, with additional research underway into type-level caching strategies within the TypeScript compiler. Additional optimizations will be explored over the course of 0.34.x.

Breaking Changes

The following are the breaking changes in Revision 0.34.0.

Ref

Revision 0.34.0 introduces a breaking change to Ref, modifying its signature to accept only constant string values. Previously, Ref could accept an existing TypeBox type, provided it had an $id assigned.


// Revision 0.33.0

const T = Type.String({ $id: 'T' })

const R = Type.Ref(T)

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

// Revision 0.34.0

const T = Type.String({ $id: 'T' })

const R = Type.Ref('T')

type R = Static<typeof R>      // type R = unknown

In Revision 0.34.0, the inferred type for Ref is now unknown. Implementations using the previous version of Ref can switch to Unsafe to type the reference to the target value.

// Revision 0.34.0

const T = Type.String({ $id: 'T' })

const R = Type.Unsafe<Static<typeof T>>(Type.Ref('T'))

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

Deref

Revision 0.34.0 removes the Deref type, which was previously used to dereference schematics. Since the Ref signature has changed from TSchema to string, there is no longer a way to resolve reference types accurately. TypeBox may provide a prototype example of this type upon request.

Strict

Revision 0.34.0 removes the Strict type from the Type Builder, which was deprecated in version 0.33.8. This type was introduced several years ago in response to a change in Ajv that prohibited unknown keywords. At that time, TypeBox used string property keys for kind and modifier, which required either Ajv configuration or the use of Strict. These properties have since been updated to Symbol properties, resolving the issues with Ajv. However, the Strict type remained due to some use in ecosystem projects, which has since reduced.

For those who still need Strict, the recommended approach is to use the JSON stringify/parse method outlined in the deprecation notice.

/**
 * @deprecated `[Json]` Omits compositing symbols from this schema. It is recommended
 * to use the JSON parse/stringify to remove compositing symbols if needed. This
 * is how Strict works internally.
 *
 * ```typescript
 * JSON.parse(JSON.stringify(Type.String()))
 * ```
 */
export function Strict<T extends TSchema>(schema: T): TStrict<T> {
  return JSON.parse(JSON.stringify(schema))
}