Skip to content

Commit 9057a73

Browse files
committed
* Clean up Compiler and Schema Validator Implementations
* Make EvaluateResult a class instance
1 parent 6dac45a commit 9057a73

23 files changed

Lines changed: 212 additions & 267 deletions

example/index.ts

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,6 @@ import Schema from 'typebox/schema'
66
import Value from 'typebox/value'
77
import Type from 'typebox'
88

9-
// ------------------------------------------------------------------
10-
// Settings
11-
// ------------------------------------------------------------------
9+
const X = Schema.Compile(Type.String())
1210

13-
System.Settings.Set({ enumerableKind: false })
14-
15-
// ------------------------------------------------------------------
16-
// Guard
17-
// ------------------------------------------------------------------
18-
19-
const A = Guard.GraphemeCount('type-📦') // 6
20-
const B = Guard.HasPropertyKey({ x: 1 }, 'x') // true
21-
22-
// ------------------------------------------------------------------
23-
// Type
24-
// ------------------------------------------------------------------
25-
26-
const T = Type.Object({
27-
x: Type.Number(),
28-
y: Type.Number(),
29-
z: Type.Number()
30-
})
31-
32-
// ------------------------------------------------------------------
33-
// Script
34-
// ------------------------------------------------------------------
35-
36-
const S = Type.Script({ T }, `{
37-
[K in keyof T]: T[K] | null
38-
}`)
39-
40-
// ------------------------------------------------------------------
41-
// Infer
42-
// ------------------------------------------------------------------
43-
44-
type T = Type.Static<typeof T>
45-
type S = Type.Static<typeof S>
46-
47-
// ------------------------------------------------------------------
48-
// Parse
49-
// ------------------------------------------------------------------
50-
51-
const R = Value.Parse(T, { x: 1, y: 2, z: 3 })
52-
53-
// ------------------------------------------------------------------
54-
// Compile
55-
// ------------------------------------------------------------------
56-
const C = Compile(S)
57-
58-
const X = C.Parse({ x: 1, y: 2, z: 3 })
59-
60-
// ------------------------------------------------------------------
61-
// Format
62-
// ------------------------------------------------------------------
63-
64-
const E = Format.IsEmail('user@domain.com')
65-
66-
// ------------------------------------------------------------------
67-
// Schema
68-
// ------------------------------------------------------------------
69-
70-
const D = Schema.Parse({ const: 'hello' }, 'hello')
11+
console.log(X)

readme.md

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -279,31 +279,31 @@ The following table shows compile performance for various JSON Schema structures
279279

280280
```python
281281
┌──────────────────────┬─────────────┬─────────────┐
282-
CompileTB1XAJV8
282+
Test TB1XAJV8
283283
├──────────────────────┼─────────────┼─────────────┤
284-
│ Boolean │ 28.4K ops/s │ 7K ops/s │
285-
│ Number │ 21.8K ops/s │ 7.7K ops/s │
286-
│ String │ 47.8K ops/s │ 7.3K ops/s │
287-
│ Null │ 35.6K ops/s │ 7.8K ops/s │
288-
│ Literal_String │ 28.6K ops/s │ 6.3K ops/s │
289-
│ Literal_Number │ 46.6K ops/s │ 6.2K ops/s │
290-
│ Literal_Boolean │ 40.8K ops/s │ 6.6K ops/s │
291-
│ Pattern │ 29.7K ops/s │ 4.9K ops/s │
292-
│ Object_Open │ 6.8K ops/s │ 1.1K ops/s │
293-
│ Object_Close │ 7.4K ops/s │ 833 ops/s │
294-
│ Object_Vector3 │ 19.4K ops/s │ 2.1K ops/s │
295-
│ Object_Basis3 │ 6K ops/s │ 895 ops/s │
296-
│ Intersect_And │ 12K ops/s │ 3.5K ops/s │
297-
│ Intersect_Structural │ 8.4K ops/s │ 1.1K ops/s │
298-
│ Union_Or │ 18.2K ops/s │ 2.5K ops/s │
299-
│ Union_Structural │ 10.9K ops/s │ 1.3K ops/s │
300-
│ Tuple_Values │ 7.3K ops/s │ 1.6K ops/s │
301-
│ Tuple_Objects │ 1.9K ops/s │ 339 ops/s │
302-
│ Array_Numbers_4 │ 29.9K ops/s │ 3.4K ops/s │
303-
│ Array_Numbers_8 │ 20.3K ops/s │ 3.4K ops/s │
304-
│ Array_Numbers_16 │ 29.4K ops/s │ 3.3K ops/s │
305-
│ Array_Objects_Open │ 6.3K ops/s │ 684 ops/s │
306-
│ Array_Objects_Close │ 7.3K ops/s │ 762 ops/s │
284+
│ Boolean │ 29.2K ops/s │ 7.1K ops/s │
285+
│ Number │ 34.5K ops/s │ 7.6K ops/s │
286+
│ String │ 48.9K ops/s │ 8.7K ops/s │
287+
│ Null │ 39.6K ops/s │ 7.6K ops/s │
288+
│ Literal_String │ 46.8K ops/s │ 6.8K ops/s │
289+
│ Literal_Number │ 48.3K ops/s │ 7.4K ops/s │
290+
│ Literal_Boolean │ 48.8K ops/s │ 7.3K ops/s │
291+
│ Pattern │ 32.5K ops/s │ 6.1K ops/s │
292+
│ Object_Open │ 6.6K ops/s │ 1.4K ops/s │
293+
│ Object_Close │ 7.6K ops/s │ 1K ops/s │
294+
│ Object_Vector3 │ 20.8K ops/s │ 2.8K ops/s │
295+
│ Object_Basis3 │ 7.5K ops/s │ 1K ops/s │
296+
│ Intersect_And │ 23K ops/s │ 3.9K ops/s │
297+
│ Intersect_Structural │ 8.7K ops/s │ 1.2K ops/s │
298+
│ Union_Or │ 17.9K ops/s │ 3.4K ops/s │
299+
│ Union_Structural │ 11.3K ops/s │ 2.1K ops/s │
300+
│ Tuple_Values │ 9.6K ops/s │ 2.1K ops/s │
301+
│ Tuple_Objects │ 2.1K ops/s │ 350 ops/s │
302+
│ Array_Numbers_4 │ 33.6K ops/s │ 4.2K ops/s │
303+
│ Array_Numbers_8 │ 39K ops/s │ 3.7K ops/s │
304+
│ Array_Numbers_16 │ 29.6K ops/s │ 3.8K ops/s │
305+
│ Array_Objects_Open │ 7.7K ops/s │ 833 ops/s │
306+
│ Array_Objects_Close │ 7.6K ops/s │ 860 ops/s │
307307
└──────────────────────┴─────────────┴─────────────┘
308308
```
309309

@@ -312,31 +312,31 @@ The following tables shows validation performance for various JSON Schema struct
312312

313313
```python
314314
┌──────────────────────┬──────────────┬──────────────┐
315-
ValidateTB1XAJV8
315+
Test TB1XAJV8
316316
├──────────────────────┼──────────────┼──────────────┤
317-
│ Boolean │ 164.1M ops/s │ 181.5M ops/s │
318-
│ Number │ 107M ops/s │ 50.2M ops/s │
319-
│ String │ 102.2M ops/s │ 61.9M ops/s │
320-
│ Null │ 112.1M ops/s │ 48.2M ops/s │
321-
│ Literal_String │ 102.8M ops/s │ 61.5M ops/s │
322-
│ Literal_Number │ 109.1M ops/s │ 46.4M ops/s │
323-
│ Literal_Boolean │ 109.6M ops/s │ 63.3M ops/s │
324-
│ Pattern │ 24.7M ops/s │ 20.3M ops/s │
325-
│ Object_Open │ 75.4M ops/s │ 37.3M ops/s │
326-
│ Object_Close │ 35.9M ops/s │ 21.9M ops/s │
327-
│ Object_Vector3 │ 77.6M ops/s │ 47.4M ops/s │
328-
│ Object_Basis3 │ 37M ops/s │ 24.3M ops/s │
329-
│ Intersect_And │ 93.3M ops/s │ 61.1M ops/s │
330-
│ Intersect_Structural │ 83M ops/s │ 36.4M ops/s │
331-
│ Union_Or │ 99.7M ops/s │ 8.6M ops/s │
332-
│ Union_Structural │ 81.3M ops/s │ 43.5M ops/s │
333-
│ Tuple_Values │ 72.4M ops/s │ 41.7M ops/s │
334-
│ Tuple_Objects │ 32.6M ops/s │ 22.4M ops/s │
335-
│ Array_Numbers_4 │ 94.1M ops/s │ 42.8M ops/s │
336-
│ Array_Numbers_8 │ 90.6M ops/s │ 42.3M ops/s │
337-
│ Array_Numbers_16 │ 77.5M ops/s │ 40.2M ops/s │
338-
│ Array_Objects_Open │ 26.3M ops/s │ 19.6M ops/s │
339-
│ Array_Objects_Close │ 9.1M ops/s │ 10M ops/s │
317+
│ Boolean │ 192.2M ops/s │ 189.5M ops/s │
318+
│ Number │ 112.4M ops/s │ 61M ops/s │
319+
│ String │ 113.7M ops/s │ 64.1M ops/s │
320+
│ Null │ 112.8M ops/s │ 64.9M ops/s │
321+
│ Literal_String │ 108M ops/s │ 62.9M ops/s │
322+
│ Literal_Number │ 113.5M ops/s │ 63.2M ops/s │
323+
│ Literal_Boolean │ 109.2M ops/s │ 64.1M ops/s │
324+
│ Pattern │ 26.5M ops/s │ 22.4M ops/s │
325+
│ Object_Open │ 78M ops/s │ 47.2M ops/s │
326+
│ Object_Close │ 38.6M ops/s │ 27.6M ops/s │
327+
│ Object_Vector3 │ 91M ops/s │ 51.3M ops/s │
328+
│ Object_Basis3 │ 41.1M ops/s │ 27.4M ops/s │
329+
│ Intersect_And │ 107.6M ops/s │ 59.9M ops/s │
330+
│ Intersect_Structural │ 83.6M ops/s │ 46.3M ops/s │
331+
│ Union_Or │ 95M ops/s │ 7.9M ops/s │
332+
│ Union_Structural │ 84.5M ops/s │ 52.3M ops/s │
333+
│ Tuple_Values │ 74.7M ops/s │ 53M ops/s │
334+
│ Tuple_Objects │ 32.9M ops/s │ 22.3M ops/s │
335+
│ Array_Numbers_4 │ 93.3M ops/s │ 55.1M ops/s │
336+
│ Array_Numbers_8 │ 90.3M ops/s │ 50.8M ops/s │
337+
│ Array_Numbers_16 │ 76.8M ops/s │ 39.6M ops/s │
338+
│ Array_Objects_Open │ 28.7M ops/s │ 20.4M ops/s │
339+
│ Array_Objects_Close │ 10.3M ops/s │ 10.8M ops/s │
340340
└──────────────────────┴──────────────┴──────────────┘
341341
```
342342

src/compile/code.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ function FunctionSection(build: BuildResult): string[] {
7676
// ------------------------------------------------------------------
7777
function ExportSection(build: BuildResult): string[] {
7878
const body = build.UseUnevaluated()
79-
? `const context = new CheckContext({}, {}); return ${build.Call()}`
80-
: `return ${build.Call()}`
79+
? `const context = new CheckContext({}, {}); return ${build.Entry()}`
80+
: `return ${build.Entry()}`
8181
return [
8282
Separator(),
8383
TsIgnore(),

src/compile/validator.ts

Lines changed: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@ THE SOFTWARE.
3030

3131
import { Settings } from '../system/settings/index.ts'
3232
import { Arguments } from '../system/arguments/index.ts'
33-
import { Environment } from '../system/environment/index.ts'
3433
import { type TLocalizedValidationError } from '../error/index.ts'
3534
import { type StaticDecode, type StaticEncode, type TProperties, type TSchema, Base } from '../type/index.ts'
3635
import { Errors, Clean, Convert, Create, Default, Decode, Encode, HasCodec, Parser, ParseError } from '../value/index.ts'
37-
import { Build } from '../schema/index.ts'
36+
import { Build, BuildResult, EvaluateResult } from '../schema/index.ts'
3837

3938
// ------------------------------------------------------------------
4039
// Validator<...>
@@ -43,121 +42,117 @@ export class Validator<Context extends TProperties = TProperties, Type extends T
4342
Encode extends unknown = StaticEncode<Type, Context>,
4443
Decode extends unknown = StaticDecode<Type, Context>
4544
> extends Base<Encode> {
46-
private readonly context: Context
47-
private readonly type: Type
48-
private readonly isAccelerated: boolean
4945
private readonly hasCodec: boolean
50-
private readonly code: string
51-
private readonly check: (value: unknown) => boolean
46+
private readonly buildResult: BuildResult
47+
private readonly evaluateResult: EvaluateResult
5248
/** Constructs a Validator with the given Context and Type. */
5349
constructor(context: Context, type: Type)
5450
/** Constructs a Validator with the given arguments. */
55-
constructor(context: Context, type: Type, isEvaluated: boolean, hasCodec: boolean, code: string, check: (value: unknown) => boolean)
51+
constructor(hasCodec: boolean, buildResult: BuildResult, evaluateResult: EvaluateResult)
5652
/** Constructs a Validator. */
5753
constructor(...args: unknown[]) {
5854
super()
59-
const matched: [Context, Type, boolean, boolean, string, (value: unknown) => boolean] | [Context, Type] = Arguments.Match(args, {
60-
6: (context, type, isEvalulated, hasCodec, code, check) => [context, type, isEvalulated, hasCodec, code, check],
55+
const matched: [boolean, BuildResult, EvaluateResult] | [Context, Type] = Arguments.Match(args, {
56+
3: (hasCodec, buildResult, evaluateResult) => [hasCodec, buildResult, evaluateResult],
6157
2: (context, type) => [context, type]
6258
})
63-
if(matched.length === 6) {
64-
const [context, type, isEvaluated, hasCodec, code, check] = matched
65-
this.context = context
66-
this.type = type
67-
this.isAccelerated = isEvaluated
59+
// Note: The Base type requires this Validator to be Clone, but where we cannot safely clone
60+
// the BuildResult or the EvaluateResult. For now we need pass the Validator constructor a
61+
// cloned instance of BuildResult and EvaluateResult such that the Validator clone shares
62+
// the same pre-compiled fields. We should remove this overload when Base is removed.
63+
if(matched.length === 3 && matched[1] instanceof BuildResult && matched[2] instanceof EvaluateResult) {
64+
const [hasCodec, buildResult, evaluateResult] = matched
6865
this.hasCodec = hasCodec
69-
this.code = code
70-
this.check = check
66+
this.buildResult = buildResult
67+
this.evaluateResult = evaluateResult
7168
} else {
7269
const [context, type] = matched as [Context, Type]
73-
const result = Build(context, type).Evaluate()
7470
this.hasCodec = HasCodec(context, type)
75-
this.context = context
76-
this.type = type
77-
this.isAccelerated = result.IsAccelerated
78-
this.code = result.Code
79-
this.check = result.Check as never
71+
this.buildResult = Build(context, type)
72+
this.evaluateResult = this.buildResult.Evaluate()
8073
}
8174
}
8275
// ----------------------------------------------------------------
8376
// IsAccelerated
8477
// ----------------------------------------------------------------
8578
/** Returns true if this Validator is using JIT acceleration. */
8679
public IsAccelerated(): boolean {
87-
return this.isAccelerated
80+
return this.evaluateResult.IsAccelerated()
8881
}
8982
// ----------------------------------------------------------------
90-
// Context | Type
83+
// Context & Type
9184
// ----------------------------------------------------------------
9285
/** Returns the Context for this validator. */
9386
public Context(): Context {
94-
return this.context
87+
return this.buildResult.Context() as never
9588
}
9689
/** Returns the underlying Type used to construct this Validator. */
9790
public Type(): Type {
98-
return this.type
91+
return this.buildResult.Schema() as never
9992
}
10093
// ----------------------------------------------------------------
10194
// Code
10295
// ----------------------------------------------------------------
10396
/** Returns the generated code for this validator. */
10497
public Code(): string {
105-
return this.code
98+
return this.evaluateResult.Code()
10699
}
107100
// ----------------------------------------------------------------
108-
// Base<...>
101+
// Standard Validator
109102
// ----------------------------------------------------------------
110103
/** Performs a type-guard check on the provided value. */
111104
public override Check(value: unknown): value is Encode {
112-
return this.check(value)
105+
return this.evaluateResult.Check(value)
106+
}
107+
/** Validates a value and returns it. Will throw if invalid. */
108+
public Parse(value: unknown): Encode {
109+
const checked = this.Check(value)
110+
if(checked) return value as never
111+
if(Settings.Get().correctiveParse) return Parser(this.Context(), this.Type(), value) as never
112+
throw new ParseError(value, this.Errors(value))
113113
}
114114
/** Inspects a value and returns a detailed list of validation errors. */
115115
public override Errors(value: unknown): TLocalizedValidationError[] {
116-
if (Environment.CanEvaluate() && this.check(value)) return []
117-
return Errors(this.context, this.type, value)
116+
if (this.IsAccelerated() && this.Check(value)) return []
117+
return Errors(this.Context(), this.Type(), value)
118118
}
119+
// ----------------------------------------------------------------
120+
// Value.* Operations
121+
// ----------------------------------------------------------------
119122
/** Cleans a value using the Validator type. */
120123
public override Clean(value: unknown): unknown {
121-
return Clean(this.context, this.type, value)
124+
return Clean(this.Context(), this.Type(), value)
122125
}
123126
/** Converts a value using the Validator type. */
124127
public override Convert(value: unknown): unknown {
125-
return Convert(this.context, this.type, value)
128+
return Convert(this.Context(), this.Type(), value)
126129
}
127130
/** Creates a value using the Validator type. */
128131
public override Create(): Encode {
129-
return Create(this.context, this.type)
132+
return Create(this.Context(), this.Type())
130133
}
131134
/** Creates defaults using the Validator type. */
132135
public override Default(value: unknown): unknown {
133-
return Default(this.context, this.type, value)
134-
}
135-
/** Clones this validator. */
136-
public override Clone(): Validator<Context, Type> {
137-
return new Validator<Context, Type>(
138-
this.context,
139-
this.type,
140-
this.isAccelerated,
141-
this.hasCodec,
142-
this.code,
143-
this.check
144-
)
145-
}
146-
/** Validates a value and returns it. Will throw if invalid. */
147-
public Parse(value: unknown): Encode {
148-
const checked = this.Check(value)
149-
if(checked) return value as never
150-
if(Settings.Get().correctiveParse) return Parser(this.context, this.type, value) as never
151-
throw new ParseError(value, this.Errors(value))
136+
return Default(this.Context(), this.Type(), value)
152137
}
153138
/** Decodes a value */
154139
public Decode(value: unknown): Decode {
155-
const result = this.hasCodec ? Decode(this.context, this.type, value) : this.Parse(value)
140+
const result = this.hasCodec ? Decode(this.Context(), this.Type(), value) : this.Parse(value)
156141
return result as never
157142
}
158143
/** Encodes a value */
159144
public Encode(value: unknown): Encode {
160-
const result = this.hasCodec ? Encode(this.context, this.type, value) : this.Parse(value)
145+
const result = this.hasCodec ? Encode(this.Context(), this.Type(), value) : this.Parse(value)
161146
return result as never
162147
}
148+
// ----------------------------------------------------------------
149+
// Deprecations
150+
// ----------------------------------------------------------------
151+
/**
152+
* @deprecated Validator instances should not support Clone because they are owners of JIT evaluated functions. This function will be
153+
* removed in the next version of TypeBox (relates to Type.Base deprecation)
154+
*/
155+
public override Clone(): Validator<Context, Type> {
156+
return new Validator<Context, Type>(this.hasCodec, this.buildResult, this.evaluateResult)
157+
}
163158
}

0 commit comments

Comments
 (0)