deno.land / x / typebox@0.32.21 / changelog / 0.29.0.md
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.
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.
// 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.
type T = number & not 42 // not actual typescript syntax!
Revision 0.29.0 changes the Type.Not
type to take a single not
argument only. This type statically infers as unknown
// 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
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
)
// 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.
The not type can be inverted through nesting.
// 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
Not types are synonymous with the concept of negated types 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.
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.
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
Version Info