Skip to content

Commit 1e22d27

Browse files
committed
Version 1.1.38
* Clean up Compiler and Schema Validator Implementations * Make EvaluateResult a class * Deprecate Clone function on Compiler Validator
1 parent 6dac45a commit 1e22d27

23 files changed

Lines changed: 180 additions & 176 deletions

example/standard/standard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ THE SOFTWARE.
2727
---------------------------------------------------------------------------*/
2828

2929
import { type Static, type TSchema } from 'typebox'
30-
import { TLocalizedValidationError } from 'typebox/error'
30+
import { type TLocalizedValidationError } from 'typebox/error'
3131
import { Validator } from 'typebox/schema'
3232
import { Guard } from 'typebox/guard'
3333

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
}

src/schema/build.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ import * as Schema from './types/index.ts'
4444
function CreateCode(build: BuildResult): string {
4545
const functions = build.Functions().join(';\n')
4646
const statements = build.UseUnevaluated()
47-
? ['const context = new CheckContext({}, {})', `return ${build.Call()}`]
48-
: [`return ${build.Call()}`]
47+
? ['const context = new CheckContext({}, {})', `return ${build.Entry()}`]
48+
: [`return ${build.Entry()}`]
4949
return `${functions}; return (value) => { ${statements.join('; ')} }`
5050
}
5151
// ------------------------------------------------------------------
@@ -79,10 +79,21 @@ export type CheckFunction = (value: unknown) => boolean
7979
// ------------------------------------------------------------------
8080
// EvaluateResult
8181
// ------------------------------------------------------------------
82-
export interface EvaluateResult {
83-
IsAccelerated: boolean
84-
Code: string
85-
Check: CheckFunction
82+
export class EvaluateResult {
83+
constructor(
84+
private readonly isAccelerated: boolean,
85+
private readonly code: string,
86+
private readonly check: CheckFunction
87+
) {}
88+
public IsAccelerated() {
89+
return this.isAccelerated
90+
}
91+
public Code(): string {
92+
return this.code
93+
}
94+
public Check(value: unknown): boolean {
95+
return this.check(value)
96+
}
8697
}
8798
// ------------------------------------------------------------------
8899
// BuildResult
@@ -93,7 +104,7 @@ export class BuildResult {
93104
private readonly schema: Schema.XSchema,
94105
private readonly external: Engine.TExternal,
95106
private readonly functions: string[],
96-
private readonly call: string,
107+
private readonly entry: string,
97108
private readonly useUnevaluated: boolean
98109
) { }
99110
/** Returns the Context used for this build */
@@ -117,14 +128,14 @@ export class BuildResult {
117128
return this.functions
118129
}
119130
/** Return entry function call. */
120-
public Call(): string {
121-
return this.call
131+
public Entry(): string {
132+
return this.entry
122133
}
123134
/** Evaluates the build into a validation function */
124135
public Evaluate(): EvaluateResult {
125-
const Code = CreateCode(this)
126-
const Check = CreateCheck(this, Code)
127-
return { IsAccelerated: Environment.CanEvaluate(), Code, Check }
136+
const code = CreateCode(this)
137+
const check = CreateCheck(this, code)
138+
return new EvaluateResult(Environment.CanEvaluate(), code, check)
128139
}
129140
}
130141
// ------------------------------------------------------------------

0 commit comments

Comments
 (0)