Skip to content

Commit

Permalink
Revision 0.30.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Jul 28, 2023
1 parent da49443 commit 81ab941
Show file tree
Hide file tree
Showing 317 changed files with 7,326 additions and 6,165 deletions.
2 changes: 1 addition & 1 deletion benchmark/compression/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function compression() {
const tests = readdirSync('benchmark/compression/module').map((name) => basename(name, extname(name)))
const results = await Promise.all(tests.map((test) => measure(test)))
const present = results.reduce((acc, c) => {
return { ...acc, [c.test.replace('-', '/')]: { Compiled: c.compiled, Minified: c.minified, Compression: `${c.ratio.toFixed(2)} x` } }
return { ...acc, [c.test.replace(/-/g, '/')]: { Compiled: c.compiled, Minified: c.minified, Compression: `${c.ratio.toFixed(2)} x` } }
}, {})
console.table(present)
}
3 changes: 1 addition & 2 deletions benchmark/compression/module/typebox-compiler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Type } from '@sinclair/typebox'

const T = TypeCompiler.Compile(Type.String())
console.log(TypeCompiler)
4 changes: 2 additions & 2 deletions benchmark/compression/module/typebox-errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { ValueErrorType } from '@sinclair/typebox/errors'
import * as ValueErrors from '@sinclair/typebox/errors'

console.log(ValueErrorType)
console.log(ValueErrors)
2 changes: 1 addition & 1 deletion benchmark/compression/module/typebox-system.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { TypeSystem } from '@sinclair/typebox/system'

TypeSystem.AllowNaN = true
console.log(TypeSystem)
3 changes: 1 addition & 2 deletions benchmark/compression/module/typebox-value.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'

const T = Value.Create(Type.String())
console.log(Value)
2 changes: 1 addition & 1 deletion benchmark/measurement/module/cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export namespace Cases {

export const Primitive_String = Type.String()

export const Primitive_String_Pattern = Type.RegEx(/foo/, { default: 'foo' })
export const Primitive_String_Pattern = Type.RegExp(/foo/, { default: 'foo' })

export const Primitive_Boolean = Type.Boolean()

Expand Down
256 changes: 256 additions & 0 deletions changelog/0.30.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
## [0.30.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.30.0)

## Overview

Revision 0.30.0 updates the schema representations Extended types and includes two new iterator types. This revision also changes how TypeBox handles readonly and optional modifiers. Additional updates include adding additional constraints supported on newer versions of the specification, as well initial attempts to reduce package size.

This revision includes breaking representation changes to Extended Types and Modifiers and requires a minor semver.

## Contents

- Changes
- [Extended Type Representations](#Extended-Type-Representations)
- [Optional and Readonly](#Optional-and-Readonly)
- [Iterator and AsyncIterator](#Iterator-and-AsyncIterator)
- [Order Independent References](#Order-Independent-References)
- [Value Submodules](#Value-Submodules)
- [Additional Array Constaints](#Additional-Array-Constaints)
- [Reducing Package Size](#Reducing-Package-Size)
- Breaking
- [RegEx Renamed To RegExp](#RegEx-Renamed-To-RegExp)
- [Modifier Symbol Deprecated](#Modifier-Deprecated)


<a name="Extended-Type-Representations"></a>

## Extended Type Representations

Revision 0.30.0 updates representations for all extended types. This change is made due to TypeBox's observed role as a general purpose JavaScript validation library as well as to deprecate support for extended type validation in Ajv (which was partially functional at best)

Attempts were made on Revision 0.25.0 to restructure extended types to provide Ajv hooks for custom type configuration. These hooks used the `type` property where `{ type: 'object', instanceOf: 'TypeName' }` was used to configure schematics for JavaScript objects, and `{ type: 'null', typeOf: 'PrimitiveName' }` was used for JavaScript primitives. Despite these hooks, Ajv would still struggle with validation of primitive types (such as `undefined`), and for the types `Function`, `Constructor` and `Promise`; these were meaningless to Ajv and it did not make sense to try provide hooks for a validator that could not use them anyway.

This change mostly represents a move towards a formal specification to express pure JavaScript constructs. The change will implicate the use of `Uint8Array` and `Date` objects when configuring for Ajv. A supplimentary fallback will be provided in the `/examples` directory using `Type.Unsafe`

```typescript
// Revision 0.29.0
//
const T = Type.Date() // const T: TDate = { type: 'object', instanceOf: 'Date' }

const U = Type.Undefined() // const U: TUndefined = { type: 'null', typeOf: 'Undefined' }

// Revision 0.30.0
//
const T = Type.Date() // const T: TDate = { type: 'Date' }

const U = Type.Undefined() // const U: TUndefined = { type: 'undefined' }
```

<a name="Optional-and-Readonly"></a>

## Optional and Readonly

Revision 0.30.0 deprecates the `[Modifier]` symbol and introduces two new symbols, `[Readonly]` and `[Optional]`. This change is carried out to simplify internal type inference as well as to simplify runtime mapping logic. This change should not implicate users leveraging the `Type.*` for type construction, however implementors building runtime mapping utilities should update to use the new symbols.

```typescript
// Revision 0.29.0
//
const A = Type.ReadonlyOptional(Type.Number()) // const A: TReadonlyOptional<TNumber> = {
// type: 'number',
// [TypeBox.Modifier]: 'ReadonlyOptional'
// }

const B = Type.Readonly(Type.Number()) // const B: TReadonly<TNumber> = {
// type: 'number',
// [TypeBox.Modifier]: 'Readonly'
// }

const C = Type.Optional(Type.Number()) // const C: TOptional<TNumber> = {
// type: 'number',
// [TypeBox.Modifier]: 'Optional'
// }

// Revision 0.30.0
//
const A = Type.ReadonlyOptional(Type.Number()) // const A: TReadonly<TOptional<TNumber>> = {
// type: 'number',
// [TypeBox.Readonly]: 'Readonly',
// [TypeBox.Optional]: 'Optional'
// }

const B = Type.Readonly(Type.Number()) // const B: TReadonly<TNumber> = {
// type: 'number',
// [TypeBox.Readonly]: 'Readonly'
// }

const C = Type.Optional(Type.Number()) // const C: TOptional<TNumber> = {
// type: 'number',
// [TypeBox.Optional]: 'Optional'
// }
```

<a name="Iterator-and-AsyncIterator"></a>

## Iterator and AsyncIterator

Revision 0.30.0 adds additional extended types `Type.Iterator` and `Type.AsyncIterator`. These types compliment the existing non-validatable extended types and can be used in wider infrastructure to describe interfaces that return iterator sequences.

```typescript
// Revision 0.30.0
//
const EnumerableRange = <T extends TSchema>(T: T) => Type.Function([
Type.Number({ description: 'Start index' }),
Type.Number({ description: 'End index' })
], Type.Iterator(T))


type EnumerableNumberRange = Static<typeof EnumerableNumberRange>
const EnumerableNumberRange = EnumerableRange(Type.Number())

const Range: EnumerableNumberRange = function * (start: number, end: number) {
for(let i = start; i < end; i++) yield i
}

const R = [...Range(10, 20)] // const R = [10, 11, 12, ..., 19]
```

<a name="Order-Independent-References"></a>

## Order Independent References

Revision 0.30.0 adds a overload for `Type.Ref` to enable non order dependent type referencing. Prior to this revision, reference targets needed to be defined before referencing. Revision 0.30.0 lifts this restriction and allows reference yet to be defined targets through the use of `typeof` operator. This overload makes use of TypeScript's ability to reason about all types irrespective of topological ordering.

This overload is principally implemented for TypeScript to TypeBox code generation where TypeScript types are not guaranteed to be sorted using runtime dependent ordering.

```typescript
// Revision 0.29.0
//
const R = Type.Ref(T) // Error: T isn't defined yet

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

// Revision 0.30.0
//
const R = Type.Ref<typeof T>('T') // Ok: infer from typeof T

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

<a name="Value-Submodules"></a>

## Value Submodules

Revision 0.30.0 refactors the `Value.*` module to allow each submodule to be imported individually. This change is part of larger refactoring to reduce the size of the Value module, but also to address issues with bundlers unable to tree-shake against the internal namespaces.

The `Value.*` namespace is retained on Revision 0.30.0

```typescript
// Revision 0.29.0
//
import { Value } from '@sinclair/typebox/value' // Value.* namespace
const A = Value.Create(Type.String())

// Revision 0.30.0
//
import { Create } from '@sinclair/typebox/value/create' // Create function only
const B = Create(Type.String())
```

<a name="Additional Array Constaints"></a>

## Additional Array Constaints

Revision 0.30.0 provides support for additional constraints on `Type.Array`. The `contains` keyword is now supported as well as draft 2019-09 `minContains` and `maxContains` keywords. These keywords are documented as types and validator support is also provided. Documentation on these constraints can be found https://json-schema.org/understanding-json-schema/reference/array.html#contains

The introduction of these constraints continues ongoing work to support newer versions of the JSON Schema specification where `minContains` and `maxContains` join `unevaluatedProperties` as representations of the 2019-09 specification.

```typescript
// Revision 0.30.0
//
const T = Type.Array(Type.Number(), {
contains: Type.Literal(1),
minContains: 3,
maxContains: 5
})

Value.Check(T, [1, 1, 1]) // true - between 3 and 5 instances of 1
Value.Check(T, [1, 1, 1, 1, 1]) // true - between 3 and 5 instances of 1
Value.Check(T, [0, 1, 1, 1, 1, 1]) // true - between 3 and 5 instances of 1
Value.Check(T, [1, 1]) // false - less than 3 instances of 1
Value.Check(T, [1, 1, 1, 1, 1, 1]) // false - more than 5 instances of 1
Value.Check(T, [0]) // false - no instances of 1
```

<a name="RegEx-Renamed-To-RegExp"></a>

## RegEx Renamed To RegExp

Revision 0.30.0 flags `Type.RegEx` as deprecated but provides `Type.RegExp` as an alternative (matching the JavaScript `RegExp` type name). Additionally this type has also been moved from the `Standard` to `Extended` type set. The `RegExp` type will no longer considered part of the Standard type set due to JavaScript Regular Expressions supporting a wider range of symbols and control characeters than is supported by the ECMA262 subset used by the JSON Schema specification. Information on the ECMA262 subset supported by JSON Schema can be found at the following Url https://json-schema.org/understanding-json-schema/reference/regular_expressions.html

As `Type.RegEx()` is widely used. TypeBox will retain this type under the `@deprecated` annotation for the 0.30.0 - 0.31.0 revisions.

```typescript
// Revision 0.29.0

const T = Type.RegEx(/abc/)

// Revision 0.30.0

const A = Type.RegEx(/abc/) // deprecation warning!

const B = Type.RegExp(/abc/) // Extended Type

const T = Type.String({ pattern: /abc/.source }) // Standard Type
```
For Unicode (UTF-16) support on 0.30.0, the recommendation is to continue using custom formats.

```typescript
import { Type, FormatRegistry } from '@sinclair/typebox'

FormatRegistry.Set('emoji', value => /<a?:.+?:\d{18}>|\p{Extended_Pictographic}/gu.test(value))

const T = Type.String({ format: 'emoji' })

Value.Check(T, '♥️♦️♠️♣️') // Ok
```
For information on configuring custom formats on Ajv, refer to https://ajv.js.org/guide/formats.html#user-defined-formats


<a name="Reducing-Package-Size"></a>

## Reducing Package Size

```typescript
// Revision 0.29.0
//
┌──────────────────────┬────────────┬────────────┬─────────────┐
│ (index) │ CompiledMinifiedCompression
├──────────────────────┼────────────┼────────────┼─────────────┤
typebox/compiler'130.3 kb'' 58.2 kb''2.24 x'
typebox/errors'113.3 kb'' 49.8 kb''2.27 x'
typebox/system' 78.8 kb'' 32.2 kb''2.45 x'
typebox/value'180.0 kb'' 77.7 kb''2.32 x'
typebox' 77.7 kb'' 31.7 kb''2.45 x'
└──────────────────────┴────────────┴────────────┴─────────────┘

// Revision 0.30.0
//
┌──────────────────────┬────────────┬────────────┬─────────────┐
│ (index) │ CompiledMinifiedCompression
├──────────────────────┼────────────┼────────────┼─────────────┤
typebox/compiler'128.9 kb'' 58.5 kb''2.20 x'
typebox/errors'111.1 kb'' 49.8 kb''2.23 x'
typebox/system' 75.9 kb'' 31.4 kb''2.42 x'
typebox/value'180.4 kb'' 79.0 kb''2.28 x'
typebox' 74.8 kb'' 31.0 kb''2.42 x'
└──────────────────────┴────────────┴────────────┴─────────────┘
```
38 changes: 6 additions & 32 deletions example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,12 @@ import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Value, ValuePointer } from '@sinclair/typebox/value'
import { Type, TypeGuard, Kind, Static, TSchema } from '@sinclair/typebox'

// -----------------------------------------------------------
// Create: Type
// -----------------------------------------------------------
const T = Type.Intersect([Type.String(), Type.Number()])

const T = Type.Object({
const R = Type.RegExp('hello world')

const M = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
y: Type.String(),
z: Type.Boolean(),
})

type T = Static<typeof T>

console.log(T)

// -----------------------------------------------------------
// Create: Value
// -----------------------------------------------------------

const V = Value.Create(T)

console.log(V)

// -----------------------------------------------------------
// Compile: Type
// -----------------------------------------------------------

const C = TypeCompiler.Compile(T)

console.log(C.Code())

// -----------------------------------------------------------
// Check: Value
// -----------------------------------------------------------

console.log(C.Check(V))
29 changes: 29 additions & 0 deletions example/transform/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/transform
The MIT License (MIT)
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/

export * from './transform'
23 changes: 23 additions & 0 deletions example/transform/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Transform

Use Transform to apply codec functions to a type. Transform is designed to be used with the Decode and Encode functions to handle automatic encode and decode of JSON values. The following shows transforming Date objects to and from number types.

```typescript
import { Transform, Encode, Decode } from './transform'

// Applies codec functions to a type
const Timestamp = Transform(Type.Number(), {
decode: (value) => new Date(value),
encode: (value) => value.getTime(),
})
// Transform type can be nested within objects
const N = Type.Object({
timestamp: Timestamp
})

// Decodes as { timestamp: Date }
const D = Decode(N, { timestamp: Date.now() })

// Encodes as { timestamp: number }
const E = Encode(N, D)
```
Loading

0 comments on commit 81ab941

Please sign in to comment.