Revision 0.33.4 (#953)

* Add Assert and Parse Functions

* Documentation

* Version

* Benchmarks
This commit is contained in:
sinclairzx81
2024-08-10 18:16:09 +09:00
committed by GitHub
parent a5b03c0b83
commit 92851ea30a
29 changed files with 592 additions and 236 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@sinclair/typebox",
"version": "0.33.3",
"version": "0.33.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@sinclair/typebox",
"version": "0.33.3",
"version": "0.33.4",
"license": "MIT",
"devDependencies": {
"@arethetypeswrong/cli": "^0.13.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.33.3",
"version": "0.33.4",
"description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
+114 -130
View File
@@ -74,13 +74,12 @@ License MIT
- [Indexed](#types-indexed)
- [Mapped](#types-mapped)
- [Conditional](#types-conditional)
- [Intrinsic](#types-intrinsic)
- [Transform](#types-transform)
- [Rest](#types-rest)
- [Guard](#types-guard)
- [Unsafe](#types-unsafe)
- [Strict](#types-strict)
- [Values](#values)
- [Assert](#values-assert)
- [Create](#values-create)
- [Clone](#values-clone)
- [Check](#values-check)
@@ -90,6 +89,7 @@ License MIT
- [Cast](#values-cast)
- [Decode](#values-decode)
- [Encode](#values-decode)
- [Parse](#values-parse)
- [Equal](#values-equal)
- [Hash](#values-hash)
- [Diff](#values-diff)
@@ -176,19 +176,17 @@ type T = Static<typeof T> // type T = {
//--------------------------------------------------------------------------------------------
//
// ... then use the type both as Json Schema and as a TypeScript type.
// ... or use the type to parse JavaScript values.
//
//--------------------------------------------------------------------------------------------
import { Value } from '@sinclair/typebox/value'
function receive(value: T) { // ... as a Static Type
if(Value.Check(T, value)) { // ... as a Json Schema
// ok...
}
}
const R = Value.Parse(T, value) // const R: {
// id: string,
// name: string,
// timestamp: number
// }
```
<a name='types'></a>
@@ -1000,38 +998,6 @@ const C = Type.Exclude( // type C = Exclude<1 | 2 |
) // ]>
```
<a name='types-intrinsic'></a>
### Intrinsic Types
TypeBox supports the TypeScript intrinsic string manipulation types Uppercase, Lowercase, Capitalize and Uncapitalize. These types can be used to remap Literal, Template Literal and Union of Literal types.
```typescript
// TypeScript
type A = Capitalize<'hello'> // type A = 'Hello'
type B = Capitalize<'hello' | 'world'> // type C = 'Hello' | 'World'
type C = Capitalize<`hello${1|2|3}`> // type B = 'Hello1' | 'Hello2' | 'Hello3'
// TypeBox
const A = Type.Capitalize(Type.Literal('hello')) // const A: TLiteral<'Hello'>
const B = Type.Capitalize(Type.Union([ // const B: TUnion<[
Type.Literal('hello'), // TLiteral<'Hello'>,
Type.Literal('world') // TLiteral<'World'>
])) // ]>
const C = Type.Capitalize( // const C: TTemplateLiteral<[
Type.TemplateLiteral('hello${1|2|3}') // TLiteral<'Hello'>,
) // TUnion<[
// TLiteral<'1'>,
// TLiteral<'2'>,
// TLiteral<'3'>
// ]>
// ]>
```
<a name='types-transform'></a>
### Transform Types
@@ -1061,26 +1027,6 @@ type E = StaticEncode<typeof T> // type E = Array<number>
type T = Static<typeof T> // type T = Array<number>
```
<a name='types-rest'></a>
### Rest Types
TypeBox provides the Rest type to uniformly extract variadic tuples from Intersect, Union and Tuple types. This type can be useful to remap variadic types into different forms. The following uses Rest to remap a Tuple into a Union.
```typescript
const T = Type.Tuple([ // const T: TTuple<[
Type.String(), // TString,
Type.Number() // TNumber
]) // ]>
const R = Type.Rest(T) // const R: [TString, TNumber]
const U = Type.Union(R) // const T: TUnion<[
// TString,
// TNumber
// ]>
```
<a name='types-unsafe'></a>
### Unsafe Types
@@ -1171,6 +1117,18 @@ TypeBox provides an optional Value submodule that can be used to perform structu
import { Value } from '@sinclair/typebox/value'
```
<a name='values-assert'></a>
### Assert
Use the Assert function to assert a value is valid.
```typescript
let value: unknown = 1
Value.Assert(Type.Number(), value) // throws AssertError if invalid
```
<a name='values-create'></a>
### Create
@@ -1296,6 +1254,32 @@ const A = Value.Encode(Type.String(), 'hello') // const A = 'hello'
const B = Value.Encode(Type.String(), 42) // throw
```
<a name='values-parse'></a>
### Parse
Use the Parse function to parse a value or throw if invalid. This function internally uses Default, Clean, Convert and Decode to make a best effort attempt to parse the value into the expected type. This function should not be used in performance critical code paths.
```typescript
const T = Type.Object({ x: Type.Number({ default: 0 }), y: Type.Number({ default: 0 }) })
// Default
const A = Value.Parse(T, { }) // const A = { x: 0, y: 0 }
// Convert
const B = Value.Parse(T, { x: '1', y: '2' }) // const B = { x: 1, y: 2 }
// Clean
const C = Value.Parse(T, { x: 1, y: 2, z: 3 }) // const C = { x: 1, y: 2 }
// Assert
const D = Value.Parse(T, undefined) // throws AssertError
```
<a name='values-equal'></a>
### Equal
@@ -1716,37 +1700,37 @@ This benchmark measures compilation performance for varying types.
```typescript
(index) Iterations Ajv TypeCompiler Performance
(index) Iterations Ajv TypeCompiler Performance
Literal_String 1000 ' 242 ms' ' 10 ms' ' 24.20 x'
Literal_Number 1000 ' 200 ms' ' 8 ms' ' 25.00 x'
Literal_Boolean 1000 ' 168 ms' ' 6 ms' ' 28.00 x'
Primitive_Number 1000 ' 165 ms' ' 8 ms' ' 20.63 x'
Primitive_String 1000 ' 154 ms' ' 6 ms' ' 25.67 x'
Primitive_String_Pattern 1000 ' 208 ms' ' 14 ms' ' 14.86 x'
Primitive_Boolean 1000 ' 142 ms' ' 6 ms' ' 23.67 x'
Primitive_Null 1000 ' 143 ms' ' 6 ms' ' 23.83 x'
Object_Unconstrained 1000 ' 1217 ms' ' 31 ms' ' 39.26 x'
Object_Constrained 1000 ' 1275 ms' ' 26 ms' ' 49.04 x'
Object_Vector3 1000 ' 405 ms' ' 12 ms' ' 33.75 x'
Object_Box3D 1000 ' 1833 ms' ' 27 ms' ' 67.89 x'
Tuple_Primitive 1000 ' 475 ms' ' 13 ms' ' 36.54 x'
Tuple_Object 1000 ' 1267 ms' ' 30 ms' ' 42.23 x'
Composite_Intersect 1000 ' 604 ms' ' 18 ms' ' 33.56 x'
Composite_Union 1000 ' 545 ms' ' 20 ms' ' 27.25 x'
Math_Vector4 1000 ' 829 ms' ' 12 ms' ' 69.08 x'
Math_Matrix4 1000 ' 405 ms' ' 10 ms' ' 40.50 x'
Array_Primitive_Number 1000 ' 372 ms' ' 12 ms' ' 31.00 x'
Array_Primitive_String 1000 ' 327 ms' ' 5 ms' ' 65.40 x'
Array_Primitive_Boolean 1000 ' 300 ms' ' 4 ms' ' 75.00 x'
Array_Object_Unconstrained 1000 ' 1755 ms' ' 21 ms' ' 83.57 x'
Array_Object_Constrained 1000 ' 1516 ms' ' 20 ms' ' 75.80 x'
Array_Tuple_Primitive 1000 ' 825 ms' ' 14 ms' ' 58.93 x'
Array_Tuple_Object 1000 ' 1616 ms' ' 16 ms' ' 101.00 x'
Array_Composite_Intersect 1000 ' 776 ms' ' 16 ms' ' 48.50 x'
Array_Composite_Union 1000 ' 820 ms' ' 14 ms' ' 58.57 x'
Array_Math_Vector4 1000 ' 1166 ms' ' 15 ms' ' 77.73 x'
Array_Math_Matrix4 1000 ' 695 ms' ' 8 ms' ' 86.88 x'
Literal_String 1000 ' 211 ms' ' 8 ms' ' 26.38 x'
Literal_Number 1000 ' 185 ms' ' 5 ms' ' 37.00 x'
Literal_Boolean 1000 ' 195 ms' ' 4 ms' ' 48.75 x'
Primitive_Number 1000 ' 149 ms' ' 7 ms' ' 21.29 x'
Primitive_String 1000 ' 135 ms' ' 5 ms' ' 27.00 x'
Primitive_String_Pattern 1000 ' 193 ms' ' 10 ms' ' 19.30 x'
Primitive_Boolean 1000 ' 152 ms' ' 4 ms' ' 38.00 x'
Primitive_Null 1000 ' 147 ms' ' 4 ms' ' 36.75 x'
Object_Unconstrained 1000 ' 1065 ms' ' 26 ms' ' 40.96 x'
Object_Constrained 1000 ' 1183 ms' ' 26 ms' ' 45.50 x'
Object_Vector3 1000 ' 407 ms' ' 9 ms' ' 45.22 x'
Object_Box3D 1000 ' 1777 ms' ' 24 ms' ' 74.04 x'
Tuple_Primitive 1000 ' 485 ms' ' 11 ms' ' 44.09 x'
Tuple_Object 1000 ' 1344 ms' ' 17 ms' ' 79.06 x'
Composite_Intersect 1000 ' 606 ms' ' 14 ms' ' 43.29 x'
Composite_Union 1000 ' 522 ms' ' 17 ms' ' 30.71 x'
Math_Vector4 1000 ' 851 ms' ' 9 ms' ' 94.56 x'
Math_Matrix4 1000 ' 406 ms' ' 10 ms' ' 40.60 x'
Array_Primitive_Number 1000 ' 367 ms' ' 6 ms' ' 61.17 x'
Array_Primitive_String 1000 ' 339 ms' ' 7 ms' ' 48.43 x'
Array_Primitive_Boolean 1000 ' 325 ms' ' 5 ms' ' 65.00 x'
Array_Object_Unconstrained 1000 ' 1863 ms' ' 21 ms' ' 88.71 x'
Array_Object_Constrained 1000 ' 1535 ms' ' 18 ms' ' 85.28 x'
Array_Tuple_Primitive 1000 ' 829 ms' ' 14 ms' ' 59.21 x'
Array_Tuple_Object 1000 ' 1674 ms' ' 14 ms' ' 119.57 x'
Array_Composite_Intersect 1000 ' 789 ms' ' 13 ms' ' 60.69 x'
Array_Composite_Union 1000 ' 822 ms' ' 15 ms' ' 54.80 x'
Array_Math_Vector4 1000 ' 1129 ms' ' 14 ms' ' 80.64 x'
Array_Math_Matrix4 1000 ' 673 ms' ' 9 ms' ' 74.78 x'
```
@@ -1758,39 +1742,39 @@ This benchmark measures validation performance for varying types.
```typescript
(index) Iterations ValueCheck Ajv TypeCompiler Performance
(index) Iterations ValueCheck Ajv TypeCompiler Performance
Literal_String 1000000 ' 18 ms' ' 5 ms' ' 4 ms' ' 1.25 x'
Literal_Number 1000000 ' 16 ms' ' 18 ms' ' 10 ms' ' 1.80 x'
Literal_Boolean 1000000 ' 15 ms' ' 19 ms' ' 10 ms' ' 1.90 x'
Primitive_Number 1000000 ' 21 ms' ' 19 ms' ' 10 ms' ' 1.90 x'
Primitive_String 1000000 ' 22 ms' ' 18 ms' ' 9 ms' ' 2.00 x'
Primitive_String_Pattern 1000000 ' 155 ms' ' 41 ms' ' 34 ms' ' 1.21 x'
Primitive_Boolean 1000000 ' 18 ms' ' 17 ms' ' 9 ms' ' 1.89 x'
Primitive_Null 1000000 ' 19 ms' ' 17 ms' ' 9 ms' ' 1.89 x'
Object_Unconstrained 1000000 ' 1003 ms' ' 32 ms' ' 24 ms' ' 1.33 x'
Object_Constrained 1000000 ' 1265 ms' ' 49 ms' ' 38 ms' ' 1.29 x'
Object_Vector3 1000000 ' 418 ms' ' 22 ms' ' 13 ms' ' 1.69 x'
Object_Box3D 1000000 ' 2035 ms' ' 56 ms' ' 49 ms' ' 1.14 x'
Object_Recursive 1000000 ' 5243 ms' ' 326 ms' ' 157 ms' ' 2.08 x'
Tuple_Primitive 1000000 ' 153 ms' ' 20 ms' ' 12 ms' ' 1.67 x'
Tuple_Object 1000000 ' 781 ms' ' 28 ms' ' 18 ms' ' 1.56 x'
Composite_Intersect 1000000 ' 742 ms' ' 25 ms' ' 14 ms' ' 1.79 x'
Composite_Union 1000000 ' 558 ms' ' 24 ms' ' 13 ms' ' 1.85 x'
Math_Vector4 1000000 ' 246 ms' ' 22 ms' ' 11 ms' ' 2.00 x'
Math_Matrix4 1000000 ' 1052 ms' ' 43 ms' ' 28 ms' ' 1.54 x'
Array_Primitive_Number 1000000 ' 272 ms' ' 22 ms' ' 12 ms' ' 1.83 x'
Array_Primitive_String 1000000 ' 235 ms' ' 24 ms' ' 14 ms' ' 1.71 x'
Array_Primitive_Boolean 1000000 ' 134 ms' ' 23 ms' ' 14 ms' ' 1.64 x'
Array_Object_Unconstrained 1000000 ' 6280 ms' ' 65 ms' ' 59 ms' ' 1.10 x'
Array_Object_Constrained 1000000 ' 6076 ms' ' 130 ms' ' 119 ms' ' 1.09 x'
Array_Object_Recursive 1000000 ' 22738 ms' ' 1730 ms' ' 635 ms' ' 2.72 x'
Array_Tuple_Primitive 1000000 ' 689 ms' ' 35 ms' ' 30 ms' ' 1.17 x'
Array_Tuple_Object 1000000 ' 3266 ms' ' 63 ms' ' 52 ms' ' 1.21 x'
Array_Composite_Intersect 1000000 ' 3310 ms' ' 44 ms' ' 36 ms' ' 1.22 x'
Array_Composite_Union 1000000 ' 2432 ms' ' 69 ms' ' 33 ms' ' 2.09 x'
Array_Math_Vector4 1000000 ' 1158 ms' ' 37 ms' ' 24 ms' ' 1.54 x'
Array_Math_Matrix4 1000000 ' 5435 ms' ' 132 ms' ' 92 ms' ' 1.43 x'
Literal_String 1000000 ' 17 ms' ' 5 ms' ' 5 ms' ' 1.00 x'
Literal_Number 1000000 ' 14 ms' ' 18 ms' ' 9 ms' ' 2.00 x'
Literal_Boolean 1000000 ' 14 ms' ' 20 ms' ' 9 ms' ' 2.22 x'
Primitive_Number 1000000 ' 17 ms' ' 19 ms' ' 9 ms' ' 2.11 x'
Primitive_String 1000000 ' 17 ms' ' 18 ms' ' 10 ms' ' 1.80 x'
Primitive_String_Pattern 1000000 ' 172 ms' ' 46 ms' ' 41 ms' ' 1.12 x'
Primitive_Boolean 1000000 ' 14 ms' ' 19 ms' ' 10 ms' ' 1.90 x'
Primitive_Null 1000000 ' 16 ms' ' 19 ms' ' 9 ms' ' 2.11 x'
Object_Unconstrained 1000000 ' 437 ms' ' 28 ms' ' 14 ms' ' 2.00 x'
Object_Constrained 1000000 ' 653 ms' ' 46 ms' ' 37 ms' ' 1.24 x'
Object_Vector3 1000000 ' 201 ms' ' 22 ms' ' 12 ms' ' 1.83 x'
Object_Box3D 1000000 ' 961 ms' ' 37 ms' ' 19 ms' ' 1.95 x'
Object_Recursive 1000000 ' 3715 ms' ' 363 ms' ' 174 ms' ' 2.09 x'
Tuple_Primitive 1000000 ' 107 ms' ' 23 ms' ' 11 ms' ' 2.09 x'
Tuple_Object 1000000 ' 375 ms' ' 28 ms' ' 15 ms' ' 1.87 x'
Composite_Intersect 1000000 ' 377 ms' ' 22 ms' ' 12 ms' ' 1.83 x'
Composite_Union 1000000 ' 337 ms' ' 30 ms' ' 17 ms' ' 1.76 x'
Math_Vector4 1000000 ' 137 ms' ' 23 ms' ' 11 ms' ' 2.09 x'
Math_Matrix4 1000000 ' 576 ms' ' 37 ms' ' 28 ms' ' 1.32 x'
Array_Primitive_Number 1000000 ' 145 ms' ' 23 ms' ' 12 ms' ' 1.92 x'
Array_Primitive_String 1000000 ' 152 ms' ' 22 ms' ' 13 ms' ' 1.69 x'
Array_Primitive_Boolean 1000000 ' 131 ms' ' 20 ms' ' 13 ms' ' 1.54 x'
Array_Object_Unconstrained 1000000 ' 2821 ms' ' 62 ms' ' 45 ms' ' 1.38 x'
Array_Object_Constrained 1000000 ' 2958 ms' ' 119 ms' ' 134 ms' ' 0.89 x'
Array_Object_Recursive 1000000 ' 14695 ms' ' 1621 ms' ' 635 ms' ' 2.55 x'
Array_Tuple_Primitive 1000000 ' 478 ms' ' 35 ms' ' 28 ms' ' 1.25 x'
Array_Tuple_Object 1000000 ' 1623 ms' ' 63 ms' ' 48 ms' ' 1.31 x'
Array_Composite_Intersect 1000000 ' 1582 ms' ' 43 ms' ' 30 ms' ' 1.43 x'
Array_Composite_Union 1000000 ' 1331 ms' ' 76 ms' ' 40 ms' ' 1.90 x'
Array_Math_Vector4 1000000 ' 564 ms' ' 38 ms' ' 24 ms' ' 1.58 x'
Array_Math_Matrix4 1000000 ' 2382 ms' ' 111 ms' ' 83 ms' ' 1.34 x'
```
@@ -1802,13 +1786,13 @@ The following table lists esbuild compiled and minified sizes for each TypeBox m
```typescript
(index) Compiled Minified Compression
(index) Compiled Minified Compression
typebox/compiler '126.9 kb' ' 55.7 kb' '2.28 x'
typebox/errors ' 46.1 kb' ' 20.8 kb' '2.22 x'
typebox/system ' 4.7 kb' ' 2.0 kb' '2.33 x'
typebox/value '152.2 kb' ' 64.5 kb' '2.36 x'
typebox ' 95.7 kb' ' 39.8 kb' '2.40 x'
typebox/compiler '119.6 kb' ' 52.6 kb' '2.27 x'
typebox/errors ' 48.6 kb' ' 21.9 kb' '2.22 x'
typebox/system ' 7.4 kb' ' 3.2 kb' '2.33 x'
typebox/value '157.8 kb' ' 66.6 kb' '2.37 x'
typebox ' 98.3 kb' ' 40.9 kb' '2.40 x'
```
+5 -4
View File
@@ -71,6 +71,7 @@ import type { TSymbol } from '../type/symbol/index'
import type { TUndefined } from '../type/undefined/index'
import type { TUint8Array } from '../type/uint8array/index'
import type { TVoid } from '../type/void/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
@@ -104,15 +105,15 @@ export class TypeCheck<T extends TSchema> {
return this.checkFunc(value)
}
/** Decodes a value or throws if error */
public Decode(value: unknown): StaticDecode<T> {
public Decode<R = StaticDecode<T>>(value: unknown): R {
if (!this.checkFunc(value)) throw new TransformDecodeCheckError(this.schema, value, this.Errors(value).First()!)
return this.hasTransform ? TransformDecode(this.schema, this.references, value) : value
return (this.hasTransform ? TransformDecode(this.schema, this.references, value) : value) as never
}
/** Encodes a value or throws if error */
public Encode(value: unknown): StaticEncode<T> {
public Encode<R = StaticEncode<T>>(value: unknown): R {
const encoded = this.hasTransform ? TransformEncode(this.schema, this.references, value) : value
if (!this.checkFunc(encoded)) throw new TransformEncodeCheckError(this.schema, value, this.Errors(value).First()!)
return encoded
return encoded as never
}
}
// ------------------------------------------------------------------
+73
View File
@@ -0,0 +1,73 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2024 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.
---------------------------------------------------------------------------*/
import { Errors, ValueErrorIterator, ValueError } from '../../errors/index'
import { TypeBoxError } from '../../type/error/error'
import { TSchema } from '../../type/schema/index'
import { Static } from '../../type/static/index'
import { Check } from '../check/check'
// ------------------------------------------------------------------
// AssertError
// ------------------------------------------------------------------
export class AssertError extends TypeBoxError {
readonly #iterator: ValueErrorIterator
error: ValueError | undefined
constructor(iterator: ValueErrorIterator) {
const error = iterator.First()
super(error === undefined ? 'Invalid Value' : error.message)
this.#iterator = iterator
this.error = error
}
/** Returns an iterator for each error in this value. */
public Errors(): ValueErrorIterator {
return new ValueErrorIterator(this.#Iterator())
}
*#Iterator(): IterableIterator<ValueError> {
if (this.error) yield this.error
yield* this.#iterator
}
}
// ------------------------------------------------------------------
// AssertValue
// ------------------------------------------------------------------
function AssertValue(schema: TSchema, references: TSchema[], value: unknown): unknown {
if (Check(schema, references, value)) return
throw new AssertError(Errors(schema, references, value))
}
// ------------------------------------------------------------------
// Assert
// ------------------------------------------------------------------
/** Asserts a value matches the given type or throws an `AssertError` if invalid */
export function Assert<T extends TSchema>(schema: T, references: TSchema[], value: unknown): asserts value is Static<T>
/** Asserts a value matches the given type or throws an `AssertError` if invalid */
export function Assert<T extends TSchema>(schema: T, value: unknown): asserts value is Static<T>
/** Asserts a value matches the given type or throws an `AssertError` if invalid */
export function Assert(...args: any[]): unknown {
return args.length === 3 ? AssertValue(args[0], args[1], args[2]) : AssertValue(args[0], [], args[1])
}
+29
View File
@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2024 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 './assert'
+2 -2
View File
@@ -26,7 +26,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsStandardObject, IsArray, IsString, IsNumber, IsNull } from '../guard/index'
import { IsObject, IsArray, IsString, IsNumber, IsNull } from '../guard/index'
import { TypeBoxError } from '../../type/error/index'
import { Kind } from '../../type/symbols/index'
import { Create } from '../create/index'
@@ -135,7 +135,7 @@ function FromConstructor(schema: TConstructor, references: TSchema[], value: any
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: any): any {
const created = Create(schema, references)
const mapped = IsStandardObject(created) && IsStandardObject(value) ? { ...(created as any), ...value } : value
const mapped = IsObject(created) && IsObject(value) ? { ...(created as any), ...value } : value
return Check(schema, references, mapped) ? mapped : Create(schema, references)
}
function FromNever(schema: TNever, references: TSchema[], value: any): any {
+8 -7
View File
@@ -47,6 +47,7 @@ import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// prettier-ignore
import {
HasPropertyKey,
IsString,
IsObject,
IsArray,
@@ -57,13 +58,13 @@ import {
// ------------------------------------------------------------------
// prettier-ignore
import {
IsSchema
} from '../../type/guard/type'
IsKind
} from '../../type/guard/kind'
// ------------------------------------------------------------------
// IsCheckable
// ------------------------------------------------------------------
function IsCheckable(schema: unknown): boolean {
return IsSchema(schema) && schema[Kind] !== 'Unsafe'
return IsKind(schema) && schema[Kind] !== 'Unsafe'
}
// ------------------------------------------------------------------
// Types
@@ -76,7 +77,7 @@ function FromIntersect(schema: TIntersect, references: TSchema[], value: unknown
const unevaluatedProperties = schema.unevaluatedProperties as TSchema
const intersections = schema.allOf.map((schema) => Visit(schema, references, Clone(value)))
const composite = intersections.reduce((acc: any, value: any) => (IsObject(value) ? { ...acc, ...value } : value), {})
if (!IsObject(value) || !IsObject(composite) || !IsSchema(unevaluatedProperties)) return composite
if (!IsObject(value) || !IsObject(composite) || !IsKind(unevaluatedProperties)) return composite
const knownkeys = KeyOfPropertyKeys(schema) as string[]
for (const key of Object.getOwnPropertyNames(value)) {
if (knownkeys.includes(key)) continue
@@ -90,11 +91,11 @@ function FromObject(schema: TObject, references: TSchema[], value: unknown): any
if (!IsObject(value) || IsArray(value)) return value // Check IsArray for AllowArrayObject configuration
const additionalProperties = schema.additionalProperties as TSchema
for (const key of Object.getOwnPropertyNames(value)) {
if (key in schema.properties) {
if (HasPropertyKey(schema.properties, key)) {
value[key] = Visit(schema.properties[key], references, value[key])
continue
}
if (IsSchema(additionalProperties) && Check(additionalProperties, references, value[key])) {
if (IsKind(additionalProperties) && Check(additionalProperties, references, value[key])) {
value[key] = Visit(additionalProperties, references, value[key])
continue
}
@@ -113,7 +114,7 @@ function FromRecord(schema: TRecord, references: TSchema[], value: unknown): any
value[key] = Visit(propertySchema, references, value[key])
continue
}
if (IsSchema(additionalProperties) && Check(additionalProperties, references, value[key])) {
if (IsKind(additionalProperties) && Check(additionalProperties, references, value[key])) {
value[key] = Visit(additionalProperties, references, value[key])
continue
}
+20 -12
View File
@@ -26,16 +26,16 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import type { ObjectType, ArrayType, TypedArrayType, ValueType } from '../guard/index'
import type { ObjectType as FromObject, ArrayType as FromArray, TypedArrayType, ValueType } from '../guard/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsArray, IsDate, IsStandardObject, IsTypedArray, IsValueType } from '../guard/index'
import { IsArray, IsDate, IsMap, IsSet, IsObject, IsTypedArray, IsValueType } from '../guard/index'
// ------------------------------------------------------------------
// Clonable
// ------------------------------------------------------------------
function ObjectType(value: ObjectType): any {
function FromObject(value: FromObject): any {
const Acc = {} as Record<PropertyKey, unknown>
for (const key of Object.getOwnPropertyNames(value)) {
Acc[key] = Clone(value[key])
@@ -45,16 +45,22 @@ function ObjectType(value: ObjectType): any {
}
return Acc
}
function ArrayType(value: ArrayType): any {
function FromArray(value: FromArray): any {
return value.map((element: any) => Clone(element))
}
function TypedArrayType(value: TypedArrayType): any {
function FromTypedArray(value: TypedArrayType): any {
return value.slice()
}
function DateType(value: Date): any {
function FromMap(value: Map<unknown, unknown>): any {
return new Map(Clone([...value.entries()]))
}
function FromSet(value: Set<unknown>): any {
return new Set(Clone([...value.entries()]))
}
function FromDate(value: Date): any {
return new Date(value.toISOString())
}
function ValueType(value: ValueType): any {
function FromValue(value: ValueType): any {
return value
}
// ------------------------------------------------------------------
@@ -62,10 +68,12 @@ function ValueType(value: ValueType): any {
// ------------------------------------------------------------------
/** Returns a clone of the given value */
export function Clone<T extends unknown>(value: T): T {
if (IsArray(value)) return ArrayType(value)
if (IsDate(value)) return DateType(value)
if (IsStandardObject(value)) return ObjectType(value)
if (IsTypedArray(value)) return TypedArrayType(value)
if (IsValueType(value)) return ValueType(value)
if (IsArray(value)) return FromArray(value)
if (IsDate(value)) return FromDate(value)
if (IsTypedArray(value)) return FromTypedArray(value)
if (IsMap(value)) return FromMap(value)
if (IsSet(value)) return FromSet(value)
if (IsObject(value)) return FromObject(value)
if (IsValueType(value)) return FromValue(value)
throw new Error('ValueClone: Unable to clone value')
}
+19 -22
View File
@@ -106,7 +106,7 @@ function TryConvertLiteral(schema: TLiteral, value: unknown) {
IsString(schema.const) ? TryConvertLiteralString(value, schema.const) :
IsNumber(schema.const) ? TryConvertLiteralNumber(value, schema.const) :
IsBoolean(schema.const) ? TryConvertLiteralBoolean(value, schema.const) :
Clone(value)
value
)
}
function TryConvertBoolean(value: unknown) {
@@ -156,7 +156,7 @@ function TryConvertDate(value: unknown) {
// ------------------------------------------------------------------
// Default
// ------------------------------------------------------------------
function Default(value: any) {
function Default(value: unknown): unknown {
return value
}
// ------------------------------------------------------------------
@@ -192,26 +192,21 @@ function FromNumber(schema: TNumber, references: TSchema[], value: any): unknown
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[], value: any): unknown {
const isConvertable = IsObject(value)
if(!isConvertable) return value
const result: Record<PropertyKey, unknown> = {}
for(const key of Object.keys(value)) {
result[key] = HasPropertyKey(schema.properties, key)
? Visit(schema.properties[key], references, value[key])
: value[key]
if(!IsObject(value)) return value
for(const key of Object.getOwnPropertyNames(schema.properties)) {
value[key] = Visit(schema.properties[key], references, value[key])
}
return result
return value
}
function FromRecord(schema: TRecord, references: TSchema[], value: any): unknown {
const isConvertable = IsObject(value)
if (!isConvertable) return value
const propertyKey = Object.getOwnPropertyNames(schema.patternProperties)[0]
const property = schema.patternProperties[propertyKey]
const result = {} as Record<string, unknown>
for (const [propKey, propValue] of Object.entries(value)) {
result[propKey] = Visit(property, references, propValue)
value[propKey] = Visit(property, references, propValue)
}
return result
return value
}
function FromRef(schema: TRef, references: TSchema[], value: any): unknown {
return Visit(Deref(schema, references), references, value)
@@ -233,21 +228,25 @@ function FromTuple(schema: TTuple, references: TSchema[], value: any): unknown {
return (index < schema.items!.length)
? Visit(schema.items![index], references, value)
: value
})
})
}
function FromUndefined(schema: TUndefined, references: TSchema[], value: any): unknown {
return TryConvertUndefined(value)
}
function FromUnion(schema: TUnion, references: TSchema[], value: any): unknown {
for (const subschema of schema.anyOf) {
const converted = Visit(subschema, references, value)
const converted = Visit(subschema, references, Clone(value))
if (!Check(subschema, references, converted)) continue
return converted
}
return value
}
function AddReference(references: TSchema[], schema: TSchema): TSchema[] {
references.push(schema)
return references
}
function Visit(schema: TSchema, references: TSchema[], value: any): unknown {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const references_ = IsString(schema.$id) ? AddReference(references, schema) : references
const schema_ = schema as any
switch (schema[Kind]) {
case 'Array':
@@ -293,14 +292,12 @@ function Visit(schema: TSchema, references: TSchema[], value: any): unknown {
// ------------------------------------------------------------------
// Convert
// ------------------------------------------------------------------
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(schema: TSchema, references: TSchema[], value: unknown): unknown
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(schema: TSchema, value: unknown): unknown
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
// prettier-ignore
export function Convert(...args: any[]) {
return args.length === 3
? Visit(args[0], args[1], args[2])
: Visit(args[0], [], args[1])
return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1])
}
+5 -1
View File
@@ -389,8 +389,12 @@ function FromKind(schema: TSchema, references: TSchema[]): any {
throw new Error('User defined types must specify a default value')
}
}
function AddReference(references: TSchema[], schema: TSchema): TSchema[] {
references.push(schema)
return references
}
function Visit(schema: TSchema, references: TSchema[]): unknown {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const references_ = IsString(schema.$id) ? AddReference(references, schema) : references
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Any':
+14 -16
View File
@@ -48,7 +48,7 @@ import { IsString, IsObject, IsArray, IsUndefined } from '../guard/index'
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
import { IsSchema } from '../../type/guard/type'
import { IsKind } from '../../type/guard/kind'
// ------------------------------------------------------------------
// ValueOrDefault
// ------------------------------------------------------------------
@@ -56,16 +56,10 @@ function ValueOrDefault(schema: TSchema, value: unknown) {
return value === undefined && 'default' in schema ? Clone(schema.default) : value
}
// ------------------------------------------------------------------
// IsCheckable
// HasDefaultProperty
// ------------------------------------------------------------------
function IsCheckable(schema: unknown): boolean {
return IsSchema(schema) && schema[Kind] !== 'Unsafe'
}
// ------------------------------------------------------------------
// IsDefaultSchema
// ------------------------------------------------------------------
function IsDefaultSchema(value: unknown): value is TSchema {
return IsSchema(value) && 'default' in value
function HasDefaultProperty(schema: unknown): schema is TSchema {
return IsKind(schema) && 'default' in schema
}
// ------------------------------------------------------------------
// Types
@@ -92,11 +86,11 @@ function FromObject(schema: TObject, references: TSchema[], value: unknown): any
const knownPropertyKeys = Object.getOwnPropertyNames(schema.properties)
// properties
for (const key of knownPropertyKeys) {
if (!IsDefaultSchema(schema.properties[key])) continue
if (!HasDefaultProperty(schema.properties[key])) continue
defaulted[key] = Visit(schema.properties[key], references, defaulted[key])
}
// return if not additional properties
if (!IsDefaultSchema(additionalPropertiesSchema)) return defaulted
if (!HasDefaultProperty(additionalPropertiesSchema)) return defaulted
// additional properties
for (const key of Object.getOwnPropertyNames(defaulted)) {
if (knownPropertyKeys.includes(key)) continue
@@ -112,11 +106,11 @@ function FromRecord(schema: TRecord, references: TSchema[], value: unknown): any
const knownPropertyKey = new RegExp(propertyKeyPattern)
// properties
for (const key of Object.getOwnPropertyNames(defaulted)) {
if (!(knownPropertyKey.test(key) && IsDefaultSchema(propertySchema))) continue
if (!(knownPropertyKey.test(key) && HasDefaultProperty(propertySchema))) continue
defaulted[key] = Visit(propertySchema, references, defaulted[key])
}
// return if not additional properties
if (!IsDefaultSchema(additionalPropertiesSchema)) return defaulted
if (!HasDefaultProperty(additionalPropertiesSchema)) return defaulted
// additional properties
for (const key of Object.getOwnPropertyNames(defaulted)) {
if (knownPropertyKey.test(key)) continue
@@ -143,14 +137,18 @@ function FromUnion(schema: TUnion, references: TSchema[], value: unknown): any {
const defaulted = ValueOrDefault(schema, value)
for (const inner of schema.anyOf) {
const result = Visit(inner, references, defaulted)
if (IsCheckable(inner) && Check(inner, result)) {
if (Check(inner, result)) {
return result
}
}
return defaulted
}
function AddReference(references: TSchema[], schema: TSchema): TSchema[] {
references.push(schema)
return references
}
function Visit(schema: TSchema, references: TSchema[], value: unknown): any {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const references_ = IsString(schema.$id) ? AddReference(references, schema) : references
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Array':
+3 -3
View File
@@ -26,14 +26,14 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsStandardObject, IsDate, IsArray, IsTypedArray, IsValueType } from '../guard/index'
import { IsObject, IsDate, IsArray, IsTypedArray, IsValueType } from '../guard/index'
import type { ObjectType, ArrayType, TypedArrayType, ValueType } from '../guard/index'
// ------------------------------------------------------------------
// Equality Checks
// ------------------------------------------------------------------
function ObjectType(left: ObjectType, right: unknown): boolean {
if (!IsStandardObject(right)) return false
if (!IsObject(right)) return false
const leftKeys = [...Object.keys(left), ...Object.getOwnPropertySymbols(left)]
const rightKeys = [...Object.keys(right), ...Object.getOwnPropertySymbols(right)]
if (leftKeys.length !== rightKeys.length) return false
@@ -58,10 +58,10 @@ function ValueType(left: ValueType, right: unknown): any {
// ------------------------------------------------------------------
/** Returns true if the left value deep-equals the right */
export function Equal<T>(left: T, right: unknown): right is T {
if (IsStandardObject(left)) return ObjectType(left, right)
if (IsDate(left)) return DateType(left, right)
if (IsTypedArray(left)) return TypedArrayType(left, right)
if (IsArray(left)) return ArrayType(left, right)
if (IsObject(left)) return ObjectType(left, right)
if (IsValueType(left)) return ValueType(left, right)
throw new Error('ValueEquals: Unable to compare value')
}
+2 -2
View File
@@ -26,7 +26,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsArray, IsBoolean, IsBigInt, IsDate, IsNull, IsNumber, IsStandardObject, IsString, IsSymbol, IsUint8Array, IsUndefined } from '../guard/index'
import { IsArray, IsBoolean, IsBigInt, IsDate, IsNull, IsNumber, IsObject, IsString, IsSymbol, IsUint8Array, IsUndefined } from '../guard/index'
import { TypeBoxError } from '../../type/error/index'
// ------------------------------------------------------------------
@@ -140,7 +140,7 @@ function Visit(value: any) {
if (IsDate(value)) return DateType(value)
if (IsNull(value)) return NullType(value)
if (IsNumber(value)) return NumberType(value)
if (IsStandardObject(value)) return ObjectType(value)
if (IsObject(value)) return ObjectType(value)
if (IsString(value)) return StringType(value)
if (IsSymbol(value)) return SymbolType(value)
if (IsUint8Array(value)) return Uint8ArrayType(value)
+2
View File
@@ -37,6 +37,7 @@ export * from './guard/index'
// ------------------------------------------------------------------
// Operators
// ------------------------------------------------------------------
export * from './assert/index'
export * from './cast/index'
export * from './check/index'
export * from './clean/index'
@@ -48,6 +49,7 @@ export * from './delta/index'
export * from './equal/index'
export * from './hash/index'
export * from './mutate/index'
export * from './parse/index'
export * from './pointer/index'
export * from './transform/index'
// ------------------------------------------------------------------
+5 -5
View File
@@ -26,7 +26,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsStandardObject, IsArray, IsTypedArray, IsValueType, type TypedArrayType } from '../guard/index'
import { IsObject, IsArray, IsTypedArray, IsValueType, type TypedArrayType } from '../guard/index'
import { ValuePointer } from '../pointer/index'
import { Clone } from '../clone/index'
import { TypeBoxError } from '../../type/error/index'
@@ -44,7 +44,7 @@ export class ValueMutateError extends TypeBoxError {
// ------------------------------------------------------------------
export type Mutable = { [key: string]: unknown } | unknown[]
function ObjectType(root: Mutable, path: string, current: unknown, next: Record<string, unknown>) {
if (!IsStandardObject(current)) {
if (!IsObject(current)) {
ValuePointer.Set(root, path, Clone(next))
} else {
const currentKeys = Object.getOwnPropertyNames(current)
@@ -90,7 +90,7 @@ function ValueType(root: Mutable, path: string, current: unknown, next: unknown)
function Visit(root: Mutable, path: string, current: unknown, next: unknown) {
if (IsArray(next)) return ArrayType(root, path, current, next)
if (IsTypedArray(next)) return TypedArrayType(root, path, current, next)
if (IsStandardObject(next)) return ObjectType(root, path, current, next)
if (IsObject(next)) return ObjectType(root, path, current, next)
if (IsValueType(next)) return ValueType(root, path, current, next)
}
// ------------------------------------------------------------------
@@ -102,8 +102,8 @@ function IsNonMutableValue(value: unknown): value is Mutable {
function IsMismatchedValue(current: unknown, next: unknown) {
// prettier-ignore
return (
(IsStandardObject(current) && IsArray(next)) ||
(IsArray(current) && IsStandardObject(next))
(IsObject(current) && IsArray(next)) ||
(IsArray(current) && IsObject(next))
)
}
// ------------------------------------------------------------------
+29
View File
@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2024 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 './parse'
+68
View File
@@ -0,0 +1,68 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2024 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.
---------------------------------------------------------------------------*/
import { TransformDecode, HasTransform } from '../transform/index'
import { TSchema } from '../../type/schema/index'
import { StaticDecode } from '../../type/static/index'
import { Assert } from '../assert/assert'
import { Default } from '../default/default'
import { Convert } from '../convert/convert'
import { Clean } from '../clean/clean'
import { Clone } from '../clone/index'
// ------------------------------------------------------------------
// ParseReducer
// ------------------------------------------------------------------
type ReducerFunction = (schema: TSchema, references: TSchema[], value: unknown) => unknown
// prettier-ignore
const ParseReducer: ReducerFunction[] = [
(_schema, _references, value) => Clone(value),
(schema, references, value) => Default(schema, references, value),
(schema, references, value) => Clean(schema, references, value),
(schema, references, value) => Convert(schema, references, value),
(schema, references, value) => { Assert(schema, references, value); return value },
(schema, references, value) => (HasTransform(schema, references) ? TransformDecode(schema, references, value) : value),
]
// ------------------------------------------------------------------
// ParseValue
// ------------------------------------------------------------------
function ParseValue<T extends TSchema, R = StaticDecode<T>>(schema: T, references: TSchema[], value: unknown): R {
return ParseReducer.reduce((value, reducer) => reducer(schema, references, value), value) as R
}
// ------------------------------------------------------------------
// Parse
// ------------------------------------------------------------------
/** Parses a value or throws an `AssertError` if invalid. */
export function Parse<T extends TSchema, R = StaticDecode<T>>(schema: T, references: TSchema[], value: unknown): R
/** Parses a value or throws an `AssertError` if invalid. */
export function Parse<T extends TSchema, R = StaticDecode<T>>(schema: T, value: unknown): R
/** Parses a value or throws an `AssertError` if invalid. */
export function Parse(...args: any[]): unknown {
return args.length === 3 ? ParseValue(args[0], args[1], args[2]) : ParseValue(args[0], [], args[1])
}
+9 -5
View File
@@ -48,7 +48,7 @@ import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsStandardObject, IsArray, IsValueType } from '../guard/index'
import { IsObject, IsArray, IsValueType } from '../guard/index'
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
@@ -97,7 +97,7 @@ function FromArray(schema: TArray, references: TSchema[], path: string, value: a
}
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any) {
if (!IsStandardObject(value) || IsValueType(value)) return Default(schema, path, value)
if (!IsObject(value) || IsValueType(value)) return Default(schema, path, value)
const knownEntries = KeyOfPropertyEntries(schema)
const knownKeys = knownEntries.map(entry => entry[0])
const knownProperties = { ...value } as Record<PropertyKey, unknown>
@@ -120,7 +120,7 @@ function FromNot(schema: TNot, references: TSchema[], path: string, value: any)
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[], path: string, value: any) {
if (!IsStandardObject(value)) return Default(schema, path, value)
if (!IsObject(value)) return Default(schema, path, value)
const knownKeys = KeyOfPropertyKeys(schema)
const knownProperties = { ...value } as Record<PropertyKey, unknown>
for(const key of knownKeys) if(key in knownProperties) {
@@ -139,7 +139,7 @@ function FromObject(schema: TObject, references: TSchema[], path: string, value:
}
// prettier-ignore
function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any) {
if (!IsStandardObject(value)) return Default(schema, path, value)
if (!IsObject(value)) return Default(schema, path, value)
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
const knownKeys = new RegExp(pattern)
const knownProperties = { ...value } as Record<PropertyKey, unknown>
@@ -183,9 +183,13 @@ function FromUnion(schema: TUnion, references: TSchema[], path: string, value: a
}
return Default(schema, path, value)
}
function AddReference(references: TSchema[], schema: TSchema): TSchema[] {
references.push(schema)
return references
}
// prettier-ignore
function Visit(schema: TSchema, references: TSchema[], path: string, value: any): any {
const references_ = typeof schema.$id === 'string' ? [...references, schema] : references
const references_ = typeof schema.$id === 'string' ? AddReference(references, schema) : references
const schema_ = schema as any
switch (schema[Kind]) {
case 'Array':
+10 -6
View File
@@ -48,7 +48,7 @@ import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsStandardObject, IsArray, IsValueType } from '../guard/index'
import { IsObject, IsArray, IsValueType } from '../guard/index'
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
@@ -98,7 +98,7 @@ function FromArray(schema: TArray, references: TSchema[], path: string, value: a
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any) {
const defaulted = Default(schema, path, value)
if (!IsStandardObject(value) || IsValueType(value)) return defaulted
if (!IsObject(value) || IsValueType(value)) return defaulted
const knownEntries = KeyOfPropertyEntries(schema)
const knownKeys = knownEntries.map(entry => entry[0])
const knownProperties = { ...defaulted } as Record<PropertyKey, unknown>
@@ -123,7 +123,7 @@ function FromNot(schema: TNot, references: TSchema[], path: string, value: any)
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[], path: string, value: any) {
const defaulted = Default(schema, path, value)
if (!IsStandardObject(defaulted)) return defaulted
if (!IsObject(defaulted)) return defaulted
const knownKeys = KeyOfPropertyKeys(schema) as string[]
const knownProperties = { ...defaulted } as Record<PropertyKey, unknown>
for(const key of knownKeys) if(key in knownProperties) {
@@ -143,7 +143,7 @@ function FromObject(schema: TObject, references: TSchema[], path: string, value:
// prettier-ignore
function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any) {
const defaulted = Default(schema, path, value) as Record<PropertyKey, unknown>
if (!IsStandardObject(value)) return defaulted
if (!IsObject(value)) return defaulted
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
const knownKeys = new RegExp(pattern)
const knownProperties = {...defaulted } as Record<PropertyKey, unknown>
@@ -151,7 +151,7 @@ function FromRecord(schema: TRecord, references: TSchema[], path: string, value:
knownProperties[key] = Visit(schema.patternProperties[pattern], references, `${path}/${key}`, knownProperties[key])
}
if (!IsSchema(schema.additionalProperties)) {
return Default(schema, path, knownProperties)
return knownProperties
}
const unknownKeys = Object.getOwnPropertyNames(knownProperties)
const additionalProperties = schema.additionalProperties as TSchema
@@ -194,9 +194,13 @@ function FromUnion(schema: TUnion, references: TSchema[], path: string, value: a
}
return Default(schema, path, value)
}
function AddReference(references: TSchema[], schema: TSchema): TSchema[] {
references.push(schema)
return references
}
// prettier-ignore
function Visit(schema: TSchema, references: TSchema[], path: string, value: any): any {
const references_ = typeof schema.$id === 'string' ? [...references, schema] : references
const references_ = typeof schema.$id === 'string' ? AddReference(references, schema) : references
const schema_ = schema as any
switch (schema[Kind]) {
case 'Array':
+5 -1
View File
@@ -120,9 +120,13 @@ function FromTuple(schema: TTuple, references: TSchema[]) {
function FromUnion(schema: TUnion, references: TSchema[]) {
return IsTransform(schema) || schema.anyOf.some((schema) => Visit(schema, references))
}
function AddReference(references: TSchema[], schema: TSchema): TSchema[] {
references.push(schema)
return references
}
// prettier-ignore
function Visit(schema: TSchema, references: TSchema[]): boolean {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const references_ = IsString(schema.$id) ? AddReference(references, schema) : references
const schema_ = schema as any
if (schema.$id && visited.has(schema.$id)) return false
if (schema.$id) visited.add(schema.$id)
+30 -12
View File
@@ -27,6 +27,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import { HasTransform, TransformDecode, TransformEncode, TransformDecodeCheckError, TransformEncodeCheckError } from '../transform/index'
import { Assert as AssertValue } from '../assert/index'
import { Mutate as MutateValue, type Mutable } from '../mutate/index'
import { Hash as HashValue } from '../hash/index'
import { Equal as EqualValue } from '../equal/index'
@@ -36,6 +37,7 @@ import { Convert as ConvertValue } from '../convert/index'
import { Create as CreateValue } from '../create/index'
import { Clean as CleanValue } from '../clean/index'
import { Check as CheckValue } from '../check/index'
import { Parse as ParseValue } from '../parse/index'
import { Default as DefaultValue } from '../default/index'
import { Diff as DiffValue, Patch as PatchValue, Edit } from '../delta/index'
import { Errors as ValueErrors, ValueErrorIterator } from '../../errors/index'
@@ -43,12 +45,20 @@ import { Errors as ValueErrors, ValueErrorIterator } from '../../errors/index'
import type { TSchema } from '../../type/schema/index'
import type { Static, StaticDecode, StaticEncode } from '../../type/static/index'
/** Asserts a value matches the given type or throws an `AssertError` if invalid. */
export function Assert<T extends TSchema, R = Static<T>>(schema: T, references: TSchema[], value: unknown): asserts value is R
/** Asserts a value matches the given type or throws an `AssertError` if invalid. */
export function Assert<T extends TSchema, R = Static<T>>(schema: T, value: unknown): asserts value is R
/** Asserts a value matches the given type or throws an `AssertError` if invalid. */
export function Assert(...args: any[]): any {
return AssertValue.apply(AssertValue, args as any)
}
/** Casts a value into a given type. The return value will retain as much information of the original value as possible. */
export function Cast<T extends TSchema>(schema: T, references: TSchema[], value: unknown): Static<T>
/** Casts a value into a given type. The return value will retain as much information of the original value as possible. */
export function Cast<T extends TSchema>(schema: T, value: unknown): Static<T>
/** Casts a value into a given type. The return value will retain as much information of the original value as possible. */
export function Cast(...args: any[]) {
export function Cast(...args: any[]): any {
return CastValue.apply(CastValue, args as any)
}
/** Creates a value from the given type and references */
@@ -56,7 +66,7 @@ export function Create<T extends TSchema>(schema: T, references: TSchema[]): Sta
/** Creates a value from the given type */
export function Create<T extends TSchema>(schema: T): Static<T>
/** Creates a value from the given type */
export function Create(...args: any[]) {
export function Create(...args: any[]): any {
return CreateValue.apply(CreateValue, args as any)
}
/** Returns true if the value matches the given type and references */
@@ -64,7 +74,7 @@ export function Check<T extends TSchema>(schema: T, references: TSchema[], value
/** Returns true if the value matches the given type */
export function Check<T extends TSchema>(schema: T, value: unknown): value is Static<T>
/** Returns true if the value matches the given type */
export function Check(...args: any[]) {
export function Check(...args: any[]): any {
return CheckValue.apply(CheckValue, args as any)
}
/** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */
@@ -72,15 +82,15 @@ export function Clean(schema: TSchema, references: TSchema[], value: unknown): u
/** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */
export function Clean(schema: TSchema, value: unknown): unknown
/** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */
export function Clean(...args: any[]) {
export function Clean(...args: any[]): any {
return CleanValue.apply(CleanValue, args as any)
}
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(schema: TSchema, references: TSchema[], value: unknown): unknown
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(schema: TSchema, value: unknown): unknown
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(...args: any[]) {
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(...args: any[]): any {
return ConvertValue.apply(ConvertValue, args as any)
}
/** Returns a structural clone of the given value */
@@ -92,7 +102,7 @@ export function Decode<T extends TSchema, R = StaticDecode<T>>(schema: T, refere
/** Decodes a value or throws if error */
export function Decode<T extends TSchema, R = StaticDecode<T>>(schema: T, value: unknown): R
/** Decodes a value or throws if error */
export function Decode(...args: any[]) {
export function Decode(...args: any[]): any {
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
if (!Check(schema, references, value)) throw new TransformDecodeCheckError(schema, value, Errors(schema, references, value).First()!)
return HasTransform(schema, references) ? TransformDecode(schema, references, value) : value
@@ -102,7 +112,7 @@ export function Default(schema: TSchema, references: TSchema[], value: unknown):
/** `[Mutable]` Generates missing properties on a value using default schema annotations if available. This function does not check the value and returns an unknown type. You should Check the result before use. Default is a mutable operation. To avoid mutation, Clone the value first. */
export function Default(schema: TSchema, value: unknown): unknown
/** `[Mutable]` Generates missing properties on a value using default schema annotations if available. This function does not check the value and returns an unknown type. You should Check the result before use. Default is a mutable operation. To avoid mutation, Clone the value first. */
export function Default(...args: any[]) {
export function Default(...args: any[]): any {
return DefaultValue.apply(DefaultValue, args as any)
}
/** Encodes a value or throws if error */
@@ -110,18 +120,26 @@ export function Encode<T extends TSchema, R = StaticEncode<T>>(schema: T, refere
/** Encodes a value or throws if error */
export function Encode<T extends TSchema, R = StaticEncode<T>>(schema: T, value: unknown): R
/** Encodes a value or throws if error */
export function Encode(...args: any[]) {
export function Encode(...args: any[]): any {
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
const encoded = HasTransform(schema, references) ? TransformEncode(schema, references, value) : value
if (!Check(schema, references, encoded)) throw new TransformEncodeCheckError(schema, encoded, Errors(schema, references, encoded).First()!)
return encoded
}
/** Parses a value or throws an `AssertError` if invalid. */
export function Parse<T extends TSchema, R = StaticDecode<T>>(schema: T, references: TSchema[], value: unknown): R
/** Parses a value or throws an `AssertError` if invalid. */
export function Parse<T extends TSchema, R = StaticDecode<T>>(schema: T, value: unknown): R
/** Parses a value or throws an `AssertError` if invalid. */
export function Parse(...args: any[]): unknown {
return ParseValue.apply(ParseValue, args as any)
}
/** Returns an iterator for each error in this value. */
export function Errors<T extends TSchema>(schema: T, references: TSchema[], value: unknown): ValueErrorIterator
/** Returns an iterator for each error in this value. */
export function Errors<T extends TSchema>(schema: T, value: unknown): ValueErrorIterator
/** Returns an iterator for each error in this value. */
export function Errors(...args: any[]) {
export function Errors(...args: any[]): any {
return ValueErrors.apply(ValueErrors, args as any)
}
/** Returns true if left and right values are structurally equal */
+35
View File
@@ -0,0 +1,35 @@
import { Value, AssertError } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
describe('value/Assert', () => {
it('Should Assert', () => {
Assert.Throws(() => Value.Assert(Type.String(), 1))
})
it('Should throw AssertError', () => {
try {
Value.Assert(Type.String(), 1)
} catch (error) {
if (error instanceof AssertError) {
return
}
throw error
}
})
it('Should throw AssertError and produce Iterator', () => {
try {
Value.Assert(Type.String(), 1)
} catch (error) {
if (error instanceof AssertError) {
const first = error.Errors().First()
Assert.HasProperty(first, 'type')
Assert.HasProperty(first, 'schema')
Assert.HasProperty(first, 'path')
Assert.HasProperty(first, 'value')
Assert.HasProperty(first, 'message')
return
}
throw error
}
})
})
+1
View File
@@ -0,0 +1 @@
import './assert'
+8 -4
View File
@@ -1,5 +1,5 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Type, Kind, TypeRegistry } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
describe('value/default/Union', () => {
@@ -86,25 +86,29 @@ describe('value/default/Union', () => {
// Interior Unsafe
// ----------------------------------------------------------------
it('Should default interior unsafe 1', () => {
TypeRegistry.Set('DefaultUnsafe', (schema, value) => typeof value === 'string')
const T = Type.Union([
Type.Object({
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
}),
Type.Unsafe({ default: 'hello' }),
Type.Unsafe({ [Kind]: 'DefaultUnsafe', default: 'hello' }),
])
const R = Value.Default(T, undefined)
Assert.IsEqual(R, undefined)
Assert.IsEqual(R, 'hello')
TypeRegistry.Delete('DefaultUnsafe')
})
it('Should default interior unsafe 2', () => {
TypeRegistry.Set('DefaultUnsafe', (schema, value) => typeof value === 'string')
const T = Type.Union([
Type.Object({
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
}),
Type.Unsafe({ default: 'hello' }),
Type.Unsafe({ [Kind]: 'DefaultUnsafe', default: 'hello' }),
])
const R = Value.Default(T, 'world')
Assert.IsEqual(R, 'world')
TypeRegistry.Delete('DefaultUnsafe')
})
})
+2
View File
@@ -1,3 +1,4 @@
import './assert'
import './cast'
import './check'
import './clean'
@@ -10,5 +11,6 @@ import './equal'
import './guard'
import './hash'
import './mutate'
import './parse'
import './pointer'
import './transform'
+1
View File
@@ -0,0 +1 @@
import './parse'
+90
View File
@@ -0,0 +1,90 @@
import { Value, AssertError } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
// prettier-ignore
describe('value/Parse', () => {
it('Should Parse', () => {
const A = Value.Parse(Type.Literal('hello'), 'hello')
Assert.IsEqual(A, 'hello')
})
it('Should not Parse', () => {
Assert.Throws(() => Value.Parse(Type.Literal('hello'), 'world'))
})
it('Should throw AssertError', () => {
try {
Value.Parse(Type.Literal('hello'), 'world')
} catch(error) {
if(error instanceof AssertError) {
return
}
throw error
}
})
it('Should throw AssertError and produce Iterator', () => {
try {
Value.Parse(Type.Literal('hello'), 'world')
} catch(error) {
if(error instanceof AssertError) {
const first = error.Errors().First()
Assert.HasProperty(first, 'type')
Assert.HasProperty(first, 'schema')
Assert.HasProperty(first, 'path')
Assert.HasProperty(first, 'value')
Assert.HasProperty(first, 'message')
return
}
throw error
}
})
// ----------------------------------------------------------------
// Default
// ----------------------------------------------------------------
it('Should use Default values', () => {
const X = Value.Parse(Type.Object({
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 })
}), { })
Assert.IsEqual(X, { x: 1, y: 2 })
})
it('Should throw on invalid Default values', () => {
Assert.Throws(() => Value.Parse(Type.Object({
x: Type.Number({ default: null }),
y: Type.Number({ default: null })
}), { }))
})
// ----------------------------------------------------------------
// Convert
// ----------------------------------------------------------------
it('Should Convert value 1', () => {
const X = Value.Parse(Type.Object({
x: Type.Number(),
y: Type.Number()
}), { x: '1', y: '2' })
Assert.IsEqual(X, { x: 1, y: 2 })
})
it('Should Convert value 2', () => {
const X = Value.Parse(Type.Array(Type.String()), [1, 2, 3, 4])
Assert.IsEqual(X, ['1', '2', '3', '4'])
})
// ----------------------------------------------------------------
// Clean
// ----------------------------------------------------------------
it('Should Clean value', () => {
const X = Value.Parse(Type.Object({
x: Type.Number(),
y: Type.Number()
}), { x: 1, y: 2, z: 3 })
Assert.IsEqual(X, { x: 1, y: 2 })
})
// ----------------------------------------------------------------
// Decode
// ----------------------------------------------------------------
it('Should Decode value', () => {
const T = Type.Transform(Type.String())
.Decode(value => 'hello')
.Encode(value => value)
const X = Value.Parse(T, 'world')
Assert.IsEqual(X, 'hello')
})
})
-1
View File
@@ -1,6 +1,5 @@
import * as Encoder from './_encoder'
import { Assert } from '../../assert'
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
describe('value/transform/Record', () => {