File: 0.29.0.md

package info (click to toggle)
node-sinclair-typebox 0.34.41-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 6,948 kB
  • sloc: makefile: 4
file content (144 lines) | stat: -rw-r--r-- 4,414 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
## [0.29.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.29.0)

## Overview

Revision 0.29.0 makes a minor interface and schema representation change to the `Type.Not` type. This revision also includes a fix for indexed access types on TypeScript 5.1.6. 

As this revision constitutes a breaking representation change for `Type.Not`, a minor semver revision is required.

## Contents

- Enhancements
  - [Type.Not Representation Change](#Representation-Change)
  - [Not Inversion](#Not-Inversion)
  - [Inference Limitations](#Inference-Limitations)

<a name="Representation-Change"></a>

## Type.Not Representation Change

The `Type.Not` was first introduced in Revision 0.26.0. This type accepted two arguments, the first is the `not` type, the second is the `allowed` type. In 0.26.0, TypeBox would treat the `allowed` type as the inferred type with the schema represented in the following form.

### 0.26.0

```typescript
// allow all numbers except the number 42
//
const T = Type.Not(Type.Literal(42), Type.Number())
//                 ^                 ^
//                 not type          allowed type

// represented as
//
const T = {
  allOf: [
    { not: { const: 42 } },
    { type: 'number' }
  ]
}

// inferred as
//
type T = Static<typeof T>              // type T = number
```
In 0.26.0. the rationale for the second `allowed` argument was provide a correct static type to infer, where one could describe what the type wasn't on the first and what it was on the second (with inference of operating on the second argument). This approach was to echo possible suggestions for negated type syntax in TypeScript.

```typescript
type T = number & not 42 // not actual typescript syntax!
```

### 0.29.0

Revision 0.29.0 changes the `Type.Not` type to take a single `not` argument only. This type statically infers as `unknown`

```typescript
// allow all types except the literal number 42
//
const T = Type.Not(Type.Literal(42)) 
//                 ^
//                 not type 

// represented as
//
const T = { not: { const: 42 } }

// inferred as
//
type T = Static<typeof T>              // type T = unknown

```
### Upgrading to 0.29.0

In revision 0.29.0, you can express the 0.26.0 Not type via `Type.Intersect` which explicitly creates the `allOf` representation. The type inference works in this case as intersected `number & unknown` yields the most narrowed type (which is `number`)

```typescript
// allow all numbers except the number 42
//
const T = Type.Intersect([ Type.Not(Type.Literal(42)), Type.Number() ])
//                         ^                           ^
//                         not type                    allowed type

// represented as
//
const T = {
  allOf: [
    { not: { const: 42 } },
    { type: 'number' }
  ]
}
// inferred as
//
type T = Static<typeof T>              // type T = number
```
The 0.29.0 `Not` type properly represents the JSON Schema `not` keyword in its simplest form, as well as making better use of intersection type narrowing capabilities of TypeScript.

<a name="Not-Inversion"></a>

## Not Inversion

The not type can be inverted through nesting.

```typescript
// not not string
//
const T = Type.Not(Type.Not(Type.String()))

// represented as
//
const T = {
  not: {
    not: {
      type: "string"
    }
  }
}

// inferred as
//
type T = Static<typeof T>              // type T = string
```

<a name="Inference-Limitations"></a>

## Inference Limitations

Not types are synonymous with the concept of [negated types](https://github.com/microsoft/TypeScript/issues/4196) which are not supported in the TypeScript language. Because of this, it is not currently possible to infer negated types in a way one would naturally expect for some cases. Consider the following.

```typescript
const T = Type.Intersect([Type.String(), Type.Not(Type.String())])

type T = Static<typeof T> // type T = string & not string
                          // actual: string
                          // expect: never
```
As such, the use of Not types should be used with some consideration to current limitations, and reserved primarily for narrowing cases such as the following.

```typescript
const T = Type.Intersect([Type.String(), Type.Not(Type.Literal('disallowed string'))])

type T = Static<typeof T> // type T = string & not 'disallowed string'
                          // actual: string
                          // expect: string
```