mirror of
https://github.com/zoriya/typebox.git
synced 2025-12-05 22:36:12 +00:00
Revision 0.29.0 (#483)
This commit is contained in:
108
changelog/0.29.0.md
Normal file
108
changelog/0.29.0.md
Normal file
@@ -0,0 +1,108 @@
|
||||
## [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.
|
||||
|
||||
## 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 the type intersection narrowing capabilities of TypeScript with respect to inference.
|
||||
|
||||
### Invert Not
|
||||
|
||||
In revision 0.29.0, it is possible to invert the `Not` type. TypeBox will track each inversion and statically infer appropriately.
|
||||
|
||||
```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
|
||||
```
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.28.20",
|
||||
"version": "0.29.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.28.20",
|
||||
"version": "0.29.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@sinclair/hammer": "^0.17.1",
|
||||
@@ -18,7 +18,7 @@
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^5.1.3"
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
@@ -1410,9 +1410,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
|
||||
"integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
||||
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -2442,9 +2442,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
|
||||
"integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
||||
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
|
||||
"dev": true
|
||||
},
|
||||
"uri-js": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.28.20",
|
||||
"version": "0.29.0",
|
||||
"description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
|
||||
"keywords": [
|
||||
"typescript",
|
||||
@@ -42,6 +42,6 @@
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^5.1.3"
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
77
readme.md
77
readme.md
@@ -84,6 +84,7 @@ License MIT
|
||||
- [Conditional](#types-conditional)
|
||||
- [Template Literal](#types-template-literal)
|
||||
- [Indexed](#types-indexed)
|
||||
- [Not](#types-not)
|
||||
- [Rest](#types-rest)
|
||||
- [Guards](#types-guards)
|
||||
- [Unsafe](#types-unsafe)
|
||||
@@ -353,20 +354,11 @@ The following table lists the Standard TypeBox types. These types are fully comp
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Not( | type T = string │ const T = { │
|
||||
| Type.Union([ │ │ allOf: [{ │
|
||||
│ Type.Literal('x'), │ │ not: { │
|
||||
│ Type.Literal('y'), │ │ anyOf: [ │
|
||||
│ Type.Literal('z') │ │ { const: 'x' }, │
|
||||
│ ]), │ │ { const: 'y' }, │
|
||||
│ Type.String() │ │ { const: 'z' } │
|
||||
│ ) │ │ ] │
|
||||
│ │ │ } │
|
||||
│ │ │ }, { │
|
||||
│ │ │ type: 'string' │
|
||||
│ │ │ }] │
|
||||
│ const T = Type.Not( | type T = unknown │ const T = { │
|
||||
│ Type.String() │ │ not: { │
|
||||
│ ) │ │ type: 'string' │
|
||||
│ │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Extends( │ type T = │ const T = { │
|
||||
│ Type.String(), │ string extends number │ const: false, │
|
||||
@@ -650,7 +642,7 @@ const T = Type.String({ // const T = {
|
||||
}) // format: 'email'
|
||||
// }
|
||||
|
||||
// Mumber must be a multiple of 2
|
||||
// Number must be a multiple of 2
|
||||
const T = Type.Number({ // const T = {
|
||||
multipleOf: 2 // type: 'number',
|
||||
}) // multipleOf: 2
|
||||
@@ -892,6 +884,49 @@ const C = Type.Index(T, Type.KeyOf(T)) // const C = {
|
||||
// }
|
||||
```
|
||||
|
||||
<a name='types-not'></a>
|
||||
|
||||
### Not Types
|
||||
|
||||
Not types are supported with `Type.Not`. This type represents the JSON Schema `not` keyword and will statically infer as `unknown`. Note that negated (or not) types are not supported in TypeScript, but can still be partially expressed by interpreting `not` as the broad type `unknown`. When used with intersect types, the Not type can create refined assertion rules for types by leveraging TypeScript's ability to narrow from `unknown` to an intended type through intersection.
|
||||
|
||||
For example, consider a type which is `number` but not `1 | 2 | 3` and where the static type would still technically be a `number`. The following shows a pseudo TypeScript example using `not` followed by the TypeBox implementation.
|
||||
|
||||
```typescript
|
||||
// Pseudo TypeScript
|
||||
|
||||
type T = number & not (1 | 2 | 3) // allow all numbers except 1, 2, 3
|
||||
|
||||
// TypeBox
|
||||
|
||||
const T = Type.Intersect([ // const T = {
|
||||
Type.Number(), // allOf: [
|
||||
Type.Not(Type.Union([ // { type: "number" },
|
||||
Type.Literal(1), // {
|
||||
Type.Literal(2), // not: {
|
||||
Type.Literal(3) // anyOf: [
|
||||
])) // { const: 1, type: "number" },
|
||||
]) // { const: 2, type: "number" },
|
||||
// { const: 3, type: "number" }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
type T = Static<typeof T> // evaluates as:
|
||||
//
|
||||
// type T = (number & (not (1 | 2 | 3)))
|
||||
// type T = (number & (unknown))
|
||||
// type T = (number)
|
||||
```
|
||||
|
||||
The Not type can be used with constraints to define schematics for types that would otherwise be difficult to express.
|
||||
```typescript
|
||||
const Even = Type.Number({ multipleOf: 2 })
|
||||
|
||||
const Odd = Type.Intersect([Type.Number(), Type.Not(Even)])
|
||||
```
|
||||
<a name='types-rest'></a>
|
||||
|
||||
### Rest Types
|
||||
@@ -1420,29 +1455,25 @@ TypeSystem.AllowNaN = true
|
||||
|
||||
## Workbench
|
||||
|
||||
TypeBox offers a small web based code generation tool that can be used to convert TypeScript types into TypeBox type definitions as well as a variety of other formats.
|
||||
TypeBox offers a web based code generation tool that can be used to convert TypeScript types into TypeBox types as well as a variety of other runtime type representations.
|
||||
|
||||
[Workbench Link Here](https://sinclairzx81.github.io/typebox-workbench/)
|
||||
|
||||
<div align='center'>
|
||||
|
||||
<a href="https://sinclairzx81.github.io/typebox-workbench/"><img src="https://github.com/sinclairzx81/typebox/blob/master/workbench.png?raw=true" /></a>
|
||||
|
||||
</div>
|
||||
|
||||
<a name='ecosystem'></a>
|
||||
|
||||
## Ecosystem
|
||||
|
||||
The following is a list of community packages that provide general tooling and framework support for TypeBox.
|
||||
The following is a list of community packages that provide general tooling and framework integration support for TypeBox.
|
||||
|
||||
| Package | Description |
|
||||
| ------------- | ------------- |
|
||||
| [elysia](https://github.com/elysiajs/elysia) | Fast and friendly Bun web framework |
|
||||
| [fastify-type-provider-typebox](https://github.com/fastify/fastify-type-provider-typebox) | Fastify TypeBox integration with the Fastify Type Provider |
|
||||
| [feathersjs](https://github.com/feathersjs/feathers) | The API and real-time application framework |
|
||||
| [fetch-typebox](https://github.com/erfanium/fetch-typebox) | Drop-in replacement for fetch that brings easy integration with TypeBox |
|
||||
| [schema2typebox](https://github.com/xddq/schema2typebox) | Creating TypeBox code from JSON schemas |
|
||||
| [ts2typebox](https://github.com/xddq/ts2typebox) | Creating TypeBox code from Typescript types |
|
||||
| [ts2typebox](https://github.com/xddq/ts2typebox) | Creating TypeBox code from Typescript types |
|
||||
| [typebox-validators](https://github.com/jtlapp/typebox-validators) | Advanced validators supporting discriminated and heterogeneous unions |
|
||||
|
||||
<a name='benchmark'></a>
|
||||
|
||||
|
||||
@@ -245,9 +245,8 @@ export namespace TypeCompiler {
|
||||
yield `false`
|
||||
}
|
||||
function* Not(schema: Types.TNot, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const left = CreateExpression(schema.allOf[0].not, references, value)
|
||||
const right = CreateExpression(schema.allOf[1], references, value)
|
||||
yield `!${left} && ${right}`
|
||||
const expression = CreateExpression(schema.not, references, value)
|
||||
yield `(!${expression})`
|
||||
}
|
||||
function* Null(schema: Types.TNull, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(${value} === null)`
|
||||
|
||||
@@ -305,10 +305,9 @@ export namespace ValueErrors {
|
||||
yield { type: ValueErrorType.Never, schema, path, value, message: `Value cannot be validated` }
|
||||
}
|
||||
function* Not(schema: Types.TNot, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (Visit(schema.allOf[0].not, references, path, value).next().done === true) {
|
||||
if (Visit(schema.not, references, path, value).next().done === true) {
|
||||
yield { type: ValueErrorType.Not, schema, path, value, message: `Value should not validate` }
|
||||
}
|
||||
yield* Visit(schema.allOf[1], references, path, value)
|
||||
}
|
||||
function* Null(schema: Types.TNull, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value === null)) {
|
||||
|
||||
@@ -313,21 +313,13 @@ export type TIndexRestMany<T extends TSchema, K extends Key[]> =
|
||||
K extends [infer L, ...infer R] ? [TIndexType<T, Assert<L, Key>>, ...TIndexRestMany<T, Assert<R, Key[]>>] :
|
||||
[]
|
||||
// prettier-ignore
|
||||
export type TIndexReduce<T extends TSchema, K extends Key[]> =
|
||||
T extends TRecursive<infer S> ? TIndexReduce<S, K> :
|
||||
export type TIndex<T extends TSchema, K extends Key[]> =
|
||||
T extends TRecursive<infer S> ? TIndex<S, K> :
|
||||
T extends TIntersect ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
||||
T extends TUnion ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
||||
T extends TObject ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
||||
T extends TTuple ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
||||
TNever
|
||||
// prettier-ignore
|
||||
export type TIndex<T extends TSchema, K extends TSchema> =
|
||||
[T, K] extends [TTuple, TNumber] ? UnionType<Assert<T['items'], TSchema[]>> :
|
||||
[T, K] extends [TArray, TNumber] ? AssertType<T['items']> :
|
||||
K extends TTemplateLiteral ? TIndexReduce<T, TTemplateLiteralKeyRest<K>> :
|
||||
K extends TUnion<TLiteral<Key>[]> ? TIndexReduce<T, TUnionLiteralKeyRest<K>> :
|
||||
K extends TLiteral<Key> ? TIndexReduce<T, [K['const']]> :
|
||||
TNever
|
||||
// --------------------------------------------------------------------------
|
||||
// TInteger
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -393,10 +385,10 @@ export interface TNever extends TSchema {
|
||||
// --------------------------------------------------------------------------
|
||||
// TNot
|
||||
// --------------------------------------------------------------------------
|
||||
export interface TNot<Not extends TSchema = TSchema, T extends TSchema = TSchema> extends TSchema {
|
||||
export interface TNot<T extends TSchema = TSchema> extends TSchema {
|
||||
[Kind]: 'Not'
|
||||
static: Static<T>
|
||||
allOf: [{ not: Not }, T]
|
||||
static: T extends TNot<infer U> ? Static<U> : unknown
|
||||
not: T
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// TNull
|
||||
@@ -1063,11 +1055,7 @@ export namespace TypeGuard {
|
||||
return (
|
||||
TKind(schema) &&
|
||||
schema[Kind] === 'Not' &&
|
||||
IsArray(schema.allOf) &&
|
||||
schema.allOf.length === 2 &&
|
||||
IsObject(schema.allOf[0]) &&
|
||||
TSchema(schema.allOf[0].not) &&
|
||||
TSchema(schema.allOf[1])
|
||||
TSchema(schema.not)
|
||||
)
|
||||
}
|
||||
/** Returns true if the given schema is TNull */
|
||||
@@ -1354,8 +1342,7 @@ export namespace ExtendsUndefined {
|
||||
export function Check(schema: TSchema): boolean {
|
||||
if (schema[Kind] === 'Undefined') return true
|
||||
if (schema[Kind] === 'Not') {
|
||||
const not = schema as TNot
|
||||
return Check(not.allOf[1])
|
||||
return !Check(schema.not)
|
||||
}
|
||||
if (schema[Kind] === 'Intersect') {
|
||||
const intersect = schema as TIntersect
|
||||
@@ -1551,6 +1538,18 @@ export namespace TypeExtends {
|
||||
return TypeExtendsResult.True
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Not
|
||||
// --------------------------------------------------------------------------
|
||||
function ResolveNot<T extends TNot>(schema: T): TUnknown | TNot['not'] {
|
||||
let [current, depth]: [TSchema, number] = [schema, 0]
|
||||
while (true) {
|
||||
if (!TypeGuard.TNot(current)) break
|
||||
current = current.not
|
||||
depth += 1
|
||||
}
|
||||
return depth % 2 === 0 ? current : Type.Unknown()
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Null
|
||||
// --------------------------------------------------------------------------
|
||||
function Null(left: TNull, right: TSchema) {
|
||||
@@ -1858,6 +1857,9 @@ export namespace TypeExtends {
|
||||
return TypeGuard.TVoid(right) ? TypeExtendsResult.True : TypeExtendsResult.False
|
||||
}
|
||||
function Visit(left: TSchema, right: TSchema): TypeExtendsResult {
|
||||
// Not Unwrap
|
||||
if (TypeGuard.TNot(left)) return Visit(ResolveNot(left), right)
|
||||
if (TypeGuard.TNot(right)) return Visit(left, ResolveNot(right))
|
||||
// Template Literal Union Unwrap
|
||||
if (TypeGuard.TTemplateLiteral(left)) return Visit(TemplateLiteralResolver.Resolve(left), right)
|
||||
if (TypeGuard.TTemplateLiteral(right)) return Visit(left, TemplateLiteralResolver.Resolve(right))
|
||||
@@ -2441,9 +2443,19 @@ export class StandardTypeBuilder extends TypeBuilder {
|
||||
}
|
||||
}
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index<T extends TSchema, K extends (keyof Static<T>)[]>(schema: T, keys: [...K], options?: SchemaOptions): TIndexReduce<T, Assert<K, Key[]>>
|
||||
public Index<T extends TTuple, K extends TNumber>(schema: T, keys: K, options?: SchemaOptions): UnionType<Assert<T['items'], TSchema[]>>
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index<T extends TSchema, K extends TSchema>(schema: T, key: K, options?: SchemaOptions): TIndex<T, K>
|
||||
public Index<T extends TArray, K extends TNumber>(schema: T, keys: K, options?: SchemaOptions): AssertType<T['items']>
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index<T extends TSchema, K extends TTemplateLiteral>(schema: T, keys: K, options?: SchemaOptions): TIndex<T, TTemplateLiteralKeyRest<K>>
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index<T extends TSchema, K extends TLiteral<Key>>(schema: T, keys: K, options?: SchemaOptions): TIndex<T, [K['const']]>
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index<T extends TSchema, K extends (keyof Static<T>)[]>(schema: T, keys: [...K], options?: SchemaOptions): TIndex<T, Assert<K, Key[]>>
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index<T extends TSchema, K extends TUnion<TLiteral<Key>[]>>(schema: T, keys: K, options?: SchemaOptions): TIndex<T, TUnionLiteralKeyRest<K>>
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index<T extends TSchema, K extends TSchema>(schema: T, key: K, options?: SchemaOptions): TSchema
|
||||
/** `[Standard]` Returns indexed property types for the given keys */
|
||||
public Index(schema: TSchema, unresolved: any, options: SchemaOptions = {}): any {
|
||||
if (TypeGuard.TArray(schema) && TypeGuard.TNumber(unresolved)) {
|
||||
@@ -2508,9 +2520,9 @@ export class StandardTypeBuilder extends TypeBuilder {
|
||||
public Never(options: SchemaOptions = {}): TNever {
|
||||
return this.Create({ ...options, [Kind]: 'Never', not: {} })
|
||||
}
|
||||
/** `[Standard]` Creates a Not type. The first argument is the disallowed type, the second is the allowed. */
|
||||
public Not<N extends TSchema, T extends TSchema>(not: N, schema: T, options?: SchemaOptions): TNot<N, T> {
|
||||
return this.Create({ ...options, [Kind]: 'Not', allOf: [{ not: TypeClone.Clone(not, {}) }, TypeClone.Clone(schema, {})] })
|
||||
/** `[Standard]` Creates a Not type */
|
||||
public Not<T extends TSchema>(not: T, options?: SchemaOptions): TNot<T> {
|
||||
return this.Create({ ...options, [Kind]: 'Not', not })
|
||||
}
|
||||
/** `[Standard]` Creates a Null type */
|
||||
public Null(options: SchemaOptions = {}): TNull {
|
||||
|
||||
@@ -178,7 +178,7 @@ export namespace ValueCast {
|
||||
throw new ValueCastNeverTypeError(schema)
|
||||
}
|
||||
function Not(schema: Types.TNot, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema.allOf[1], references)
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Null(schema: Types.TNull, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
|
||||
@@ -201,7 +201,7 @@ export namespace ValueCheck {
|
||||
return false
|
||||
}
|
||||
function Not(schema: Types.TNot, references: Types.TSchema[], value: any): boolean {
|
||||
return !Visit(schema.allOf[0].not, references, value) && Visit(schema.allOf[1], references, value)
|
||||
return !Visit(schema.not, references, value)
|
||||
}
|
||||
function Null(schema: Types.TNull, references: Types.TSchema[], value: any): boolean {
|
||||
return value === null
|
||||
|
||||
@@ -42,6 +42,11 @@ export class ValueCreateNeverTypeError extends Error {
|
||||
super('ValueCreate: Never types cannot be created')
|
||||
}
|
||||
}
|
||||
export class ValueCreateNotTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Not types must have a default value')
|
||||
}
|
||||
}
|
||||
export class ValueCreateIntersectTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Intersect produced invalid value. Consider using a default value.')
|
||||
@@ -182,7 +187,7 @@ export namespace ValueCreate {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return Visit(schema.allOf[1], references)
|
||||
throw new ValueCreateNotTypeError(schema)
|
||||
}
|
||||
}
|
||||
function Null(schema: Types.TNull, references: Types.TSchema[]): any {
|
||||
|
||||
@@ -2,37 +2,44 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Not', () => {
|
||||
it('Should validate with not number', () => {
|
||||
const T = Type.Not(Type.Number(), Type.String())
|
||||
it('Should validate not number', () => {
|
||||
const T = Type.Not(Type.Number())
|
||||
Fail(T, 1)
|
||||
Ok(T, 'A')
|
||||
Ok(T, '1')
|
||||
})
|
||||
it('Should validate with union left', () => {
|
||||
it('Should validate not not number', () => {
|
||||
const T = Type.Not(Type.Not(Type.Number()))
|
||||
Ok(T, 1)
|
||||
Fail(T, '1')
|
||||
})
|
||||
it('Should validate not union', () => {
|
||||
// prettier-ignore
|
||||
const T = Type.Not(Type.Union([
|
||||
Type.Literal('A'),
|
||||
Type.Literal('B'),
|
||||
Type.Literal('C')
|
||||
]), Type.String())
|
||||
]))
|
||||
Fail(T, 'A')
|
||||
Fail(T, 'B')
|
||||
Fail(T, 'C')
|
||||
Ok(T, 'D')
|
||||
})
|
||||
it('Should validate with union right', () => {
|
||||
// prettier-ignore
|
||||
const T = Type.Not(Type.Number(), Type.Union([
|
||||
Type.String(),
|
||||
Type.Boolean()
|
||||
]))
|
||||
Fail(T, 1)
|
||||
Ok(T, 'A')
|
||||
Ok(T, true)
|
||||
})
|
||||
it('Should not validate with symmetric left right', () => {
|
||||
// prettier-ignore
|
||||
const T = Type.Not(Type.Number(), Type.Number())
|
||||
Fail(T, 1)
|
||||
Fail(T, true) // not a number, but not a number either?
|
||||
it('Should validate not object intersection', () => {
|
||||
const T = Type.Intersect([
|
||||
Type.Object({
|
||||
x: Type.Number(),
|
||||
y: Type.Number(),
|
||||
z: Type.Number(),
|
||||
}),
|
||||
Type.Object({
|
||||
x: Type.Not(Type.Literal(0)),
|
||||
y: Type.Not(Type.Literal(0)),
|
||||
z: Type.Not(Type.Literal(0)),
|
||||
}),
|
||||
])
|
||||
Fail(T, { x: 0, y: 0, z: 0 })
|
||||
Fail(T, { x: 1, y: 0, z: 0 })
|
||||
Fail(T, { x: 1, y: 1, z: 0 })
|
||||
Ok(T, { x: 1, y: 1, z: 1 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ describe('type/compiler/Object', () => {
|
||||
// -----------------------------------------------------
|
||||
it('Should handle extends undefined check 1', () => {
|
||||
const T = Type.Object({
|
||||
A: Type.Not(Type.Number(), Type.Undefined()),
|
||||
A: Type.Not(Type.Number()),
|
||||
B: Type.Union([Type.Number(), Type.Undefined()]),
|
||||
C: Type.Intersect([Type.Undefined(), Type.Undefined()]),
|
||||
})
|
||||
@@ -20,7 +20,7 @@ describe('type/compiler/Object', () => {
|
||||
// https://github.com/sinclairzx81/typebox/issues/437
|
||||
it('Should handle extends undefined check 2', () => {
|
||||
const T = Type.Object({
|
||||
A: Type.Not(Type.Null(), Type.Undefined()),
|
||||
A: Type.Not(Type.Null()),
|
||||
})
|
||||
Ok(T, { A: undefined })
|
||||
Fail(T, { A: null })
|
||||
|
||||
@@ -2,37 +2,44 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/schema/Not', () => {
|
||||
it('Should validate with not number', () => {
|
||||
const T = Type.Not(Type.Number(), Type.String())
|
||||
it('Should validate not number', () => {
|
||||
const T = Type.Not(Type.Number())
|
||||
Fail(T, 1)
|
||||
Ok(T, 'A')
|
||||
Ok(T, '1')
|
||||
})
|
||||
it('Should validate with union left', () => {
|
||||
it('Should validate not not number', () => {
|
||||
const T = Type.Not(Type.Not(Type.Number()))
|
||||
Ok(T, 1)
|
||||
Fail(T, '1')
|
||||
})
|
||||
it('Should validate not union', () => {
|
||||
// prettier-ignore
|
||||
const T = Type.Not(Type.Union([
|
||||
Type.Literal('A'),
|
||||
Type.Literal('B'),
|
||||
Type.Literal('C')
|
||||
]), Type.String())
|
||||
]))
|
||||
Fail(T, 'A')
|
||||
Fail(T, 'B')
|
||||
Fail(T, 'C')
|
||||
Ok(T, 'D')
|
||||
})
|
||||
it('Should validate with union right', () => {
|
||||
// prettier-ignore
|
||||
const T = Type.Not(Type.Number(), Type.Union([
|
||||
Type.String(),
|
||||
Type.Boolean()
|
||||
]))
|
||||
Fail(T, 1)
|
||||
Ok(T, 'A')
|
||||
Ok(T, true)
|
||||
})
|
||||
it('Should not validate with symmetric left right', () => {
|
||||
// prettier-ignore
|
||||
const T = Type.Not(Type.Number(), Type.Number())
|
||||
Fail(T, 1)
|
||||
Fail(T, true) // not a number, but not a number either?
|
||||
it('Should validate not object intersection', () => {
|
||||
const T = Type.Intersect([
|
||||
Type.Object({
|
||||
x: Type.Number(),
|
||||
y: Type.Number(),
|
||||
z: Type.Number(),
|
||||
}),
|
||||
Type.Object({
|
||||
x: Type.Not(Type.Literal(0)),
|
||||
y: Type.Not(Type.Literal(0)),
|
||||
z: Type.Not(Type.Literal(0)),
|
||||
}),
|
||||
])
|
||||
Fail(T, { x: 0, y: 0, z: 0 })
|
||||
Fail(T, { x: 1, y: 0, z: 0 })
|
||||
Fail(T, { x: 1, y: 1, z: 0 })
|
||||
Ok(T, { x: 1, y: 1, z: 1 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import './date'
|
||||
import './function'
|
||||
import './integer'
|
||||
import './literal'
|
||||
import './not'
|
||||
import './null'
|
||||
import './number'
|
||||
import './object'
|
||||
|
||||
130
test/runtime/type/extends/not.ts
Normal file
130
test/runtime/type/extends/not.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Assert } from '../../assert/index'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Note: Not is equivalent to Unknown with the exception of nested negation.
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('type/extends/Not', () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Nested
|
||||
// ---------------------------------------------------------------------------
|
||||
it('Should extend with nested negation', () => {
|
||||
const T1 = Type.String()
|
||||
const T2 = Type.Not(T1)
|
||||
const T3 = Type.Not(T2)
|
||||
const T4 = Type.Not(T3)
|
||||
const T5 = Type.Not(T4)
|
||||
|
||||
const R1 = TypeExtends.Extends(T1, Type.String())
|
||||
const R2 = TypeExtends.Extends(T2, Type.String())
|
||||
const R3 = TypeExtends.Extends(T3, Type.String())
|
||||
const R4 = TypeExtends.Extends(T4, Type.String())
|
||||
const R5 = TypeExtends.Extends(T5, Type.String())
|
||||
|
||||
Assert.isEqual(R1, TypeExtendsResult.True)
|
||||
Assert.isEqual(R2, TypeExtendsResult.False)
|
||||
Assert.isEqual(R3, TypeExtendsResult.True)
|
||||
Assert.isEqual(R4, TypeExtendsResult.False)
|
||||
Assert.isEqual(R5, TypeExtendsResult.True)
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Not as Unknown Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
it('Should extend Any', () => {
|
||||
type T = unknown extends any ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Any())
|
||||
Assert.isEqual(R, TypeExtendsResult.True)
|
||||
})
|
||||
it('Should extend Unknown', () => {
|
||||
type T = unknown extends unknown ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Unknown())
|
||||
Assert.isEqual(R, TypeExtendsResult.True)
|
||||
})
|
||||
it('Should extend String', () => {
|
||||
type T = unknown extends string ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.String())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Boolean', () => {
|
||||
type T = unknown extends boolean ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Boolean())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Number', () => {
|
||||
type T = unknown extends number ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Number())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Integer', () => {
|
||||
type T = unknown extends number ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Integer())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Array 1', () => {
|
||||
type T = unknown extends Array<any> ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Array(Type.Any()))
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Array 2', () => {
|
||||
type T = unknown extends Array<string> ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Array(Type.String()))
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Tuple', () => {
|
||||
type T = unknown extends [number, number] ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Tuple([Type.Number(), Type.Number()]))
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Object 1', () => {
|
||||
type T = unknown extends {} ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Object({}))
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Object 2', () => {
|
||||
type T = unknown extends { a: number } ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Object({ a: Type.Number() }))
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Union 1', () => {
|
||||
type T = unknown extends number | string ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Union([Type.Number(), Type.String()]))
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Union 2', () => {
|
||||
type T = unknown extends any | number ? 1 : 2 // 1
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Union([Type.Any(), Type.String()]))
|
||||
Assert.isEqual(R, TypeExtendsResult.True)
|
||||
})
|
||||
it('Should extend Union 3', () => {
|
||||
type T = unknown extends unknown | number ? 1 : 2 // 1
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Union([Type.Unknown(), Type.String()]))
|
||||
Assert.isEqual(R, TypeExtendsResult.True)
|
||||
})
|
||||
it('Should extend Union 4', () => {
|
||||
type T = unknown extends unknown | any ? 1 : 2 // 1
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Union([Type.Unknown(), Type.String()]))
|
||||
Assert.isEqual(R, TypeExtendsResult.True)
|
||||
})
|
||||
it('Should extend Null', () => {
|
||||
type T = unknown extends null ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Null())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Undefined', () => {
|
||||
type T = unknown extends undefined ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Undefined())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Void', () => {
|
||||
type T = unknown extends void ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Void())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
it('Should extend Date', () => {
|
||||
type T = unknown extends Date ? 1 : 2
|
||||
const R = TypeExtends.Extends(Type.Not(Type.Number()), Type.Date())
|
||||
Assert.isEqual(R, TypeExtendsResult.False)
|
||||
})
|
||||
})
|
||||
@@ -4,20 +4,16 @@ import { Assert } from '../../assert/index'
|
||||
|
||||
describe('type/guard/TNot', () => {
|
||||
it('Should guard for TNot', () => {
|
||||
const R = TypeGuard.TNot(Type.Not(Type.String(), Type.String()))
|
||||
const R = TypeGuard.TNot(Type.Not(Type.String()))
|
||||
Assert.isEqual(R, true)
|
||||
})
|
||||
it('Should not guard for TNot 1', () => {
|
||||
const R = TypeGuard.TNot(Type.Not(null as any, Type.String()))
|
||||
Assert.isEqual(R, false)
|
||||
})
|
||||
it('Should not guard for TNot 2', () => {
|
||||
const R = TypeGuard.TNot(Type.Not(Type.String(), null as any))
|
||||
const R = TypeGuard.TNot(Type.Not(null as any))
|
||||
Assert.isEqual(R, false)
|
||||
})
|
||||
it('Should not guard for TNot with invalid $id', () => {
|
||||
// @ts-ignore
|
||||
const R = TypeGuard.TNot(Type.Not(Type.String(), Type.String()), { $id: 1 })
|
||||
const R = TypeGuard.TNot(Type.Not(Type.String()), { $id: 1 })
|
||||
Assert.isEqual(R, true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,57 +3,50 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Assert } from '../../assert/index'
|
||||
|
||||
describe('value/cast/Not', () => {
|
||||
const T = Type.Not(Type.String(), Type.Number())
|
||||
const E = 0
|
||||
const T = Type.Not(Type.String(), { default: 0 })
|
||||
it('Should upcast from string', () => {
|
||||
const value = 'hello'
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, 0) // default
|
||||
})
|
||||
it('Should upcast from number', () => {
|
||||
const value = 0
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, value)
|
||||
})
|
||||
it('Should upcast from boolean', () => {
|
||||
const value = true
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, value)
|
||||
})
|
||||
it('Should upcast from object', () => {
|
||||
const value = {}
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, value)
|
||||
})
|
||||
it('Should upcast from array', () => {
|
||||
const value = [1]
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, value)
|
||||
})
|
||||
it('Should upcast from undefined', () => {
|
||||
const value = undefined
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, value)
|
||||
})
|
||||
it('Should upcast from null', () => {
|
||||
const value = null
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, value)
|
||||
})
|
||||
it('Should upcast from date', () => {
|
||||
const value = new Date(100)
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, E)
|
||||
Assert.isEqual(result, value)
|
||||
})
|
||||
it('Should preserve', () => {
|
||||
const value = 100
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, 100)
|
||||
})
|
||||
it('Should not preserve when schema is illogical', () => {
|
||||
const T = Type.Not(Type.Number(), Type.Number())
|
||||
const value = 100
|
||||
const result = Value.Cast(T, value)
|
||||
Assert.isEqual(result, 0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,10 +30,4 @@ describe('value/check/Not', () => {
|
||||
Assert.isEqual(Value.Check(T, 'A'), true)
|
||||
Assert.isEqual(Value.Check(T, true), true)
|
||||
})
|
||||
it('Should not validate with symmetric left right', () => {
|
||||
// prettier-ignore
|
||||
const T = Type.Not(Type.Number(), Type.Number())
|
||||
Assert.isEqual(Value.Check(T, 1), false)
|
||||
Assert.isEqual(Value.Check(T, true), false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,18 +3,17 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Assert } from '../../assert/index'
|
||||
|
||||
describe('value/create/Not', () => {
|
||||
it('Should create value', () => {
|
||||
const T = Type.Not(Type.String(), Type.Number())
|
||||
const R = Value.Create(T)
|
||||
Assert.isEqual(R, 0)
|
||||
it('Should throw without default value', () => {
|
||||
const T = Type.Not(Type.String())
|
||||
Assert.throws(() => Value.Create(T))
|
||||
})
|
||||
it('Should create value with default inner', () => {
|
||||
const T = Type.Not(Type.String(), Type.Number({ default: 100 }))
|
||||
const T = Type.Not(Type.String(), { default: 100 })
|
||||
const R = Value.Create(T)
|
||||
Assert.isEqual(R, 100)
|
||||
})
|
||||
it('Should create value with default outer', () => {
|
||||
const T = Type.Not(Type.String(), Type.Number(), { default: 100 })
|
||||
const T = Type.Not(Type.String(), { default: 100 })
|
||||
const R = Value.Create(T)
|
||||
Assert.isEqual(R, 100)
|
||||
})
|
||||
|
||||
@@ -2,6 +2,10 @@ import { Expect } from './assert'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
{
|
||||
const T = Type.Not(Type.Number(), Type.String())
|
||||
Expect(T).ToInfer<string>()
|
||||
const T = Type.Not(Type.Number())
|
||||
Expect(T).ToInfer<unknown>()
|
||||
}
|
||||
{
|
||||
const T = Type.Not(Type.Not(Type.Number()))
|
||||
Expect(T).ToInfer<number>()
|
||||
}
|
||||
|
||||
BIN
typebox.png
BIN
typebox.png
Binary file not shown.
|
Before Width: | Height: | Size: 924 KiB After Width: | Height: | Size: 852 KiB |
BIN
workbench.png
BIN
workbench.png
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
Reference in New Issue
Block a user