@@ -129,11 +129,20 @@ function toTerminals(type: Type): TerminalType[] {
129
129
130
130
type Infer < T extends Type > = T extends Type < infer I > ? I : never ;
131
131
132
- type Func < T > = ( v : unknown ) => Result < T > ;
132
+ const enum FuncMode {
133
+ PASS = 0 ,
134
+ STRICT = 1 ,
135
+ STRIP = 2 ,
136
+ }
137
+ type Func < T > = ( v : unknown , mode : FuncMode ) => Result < T > ;
138
+
139
+ type ParseOptions = {
140
+ mode : "passthrough" | "strict" | "strip" ;
141
+ } ;
133
142
134
143
abstract class Type < Out = unknown > {
135
144
abstract readonly name : string ;
136
- abstract genFunc ( ) : ( v : unknown ) => Result < Out > ;
145
+ abstract genFunc ( ) : Func < Out > ;
137
146
abstract toTerminals ( into : TerminalType [ ] ) : void ;
138
147
139
148
get isOptional ( ) : boolean {
@@ -154,8 +163,15 @@ abstract class Type<Out = unknown> {
154
163
return f ;
155
164
}
156
165
157
- parse ( v : unknown ) : Out {
158
- const r = this . func ( v ) ;
166
+ parse ( v : unknown , options ?: Partial < ParseOptions > ) : Out {
167
+ let mode : FuncMode = FuncMode . PASS ;
168
+ if ( options && options . mode === "strict" ) {
169
+ mode = FuncMode . STRICT ;
170
+ } else if ( options && options . mode === "strip" ) {
171
+ mode = FuncMode . STRIP ;
172
+ }
173
+
174
+ const r = this . func ( v , mode ) ;
159
175
if ( r === true ) {
160
176
return v as Out ;
161
177
} else if ( r . code === "ok" ) {
@@ -178,44 +194,37 @@ type Optionals<T extends Record<string, Type>> = {
178
194
[ K in keyof T ] : undefined extends Infer < T [ K ] > ? K : never ;
179
195
} [ keyof T ] ;
180
196
181
- type UnknownKeys = "passthrough" | "strict" | "strip" | Type ;
182
-
183
197
type ObjectShape = Record < string , Type > ;
184
198
185
199
type ObjectOutput <
186
200
T extends ObjectShape ,
187
- U extends UnknownKeys
201
+ R extends Type | undefined
188
202
> = PrettyIntersection <
189
203
{ [ K in Optionals < T > ] ?: Infer < T [ K ] > } &
190
204
{ [ K in Exclude < keyof T , Optionals < T > > ] : Infer < T [ K ] > } &
191
- ( U extends "passthrough" ? { [ K : string ] : unknown } : unknown ) &
192
- ( U extends Type ? { [ K : string ] : Infer < U > } : unknown )
205
+ ( R extends Type ? { [ K : string ] : Infer < R > } : unknown )
193
206
> ;
194
207
195
208
class ObjectType <
196
209
T extends ObjectShape = ObjectShape ,
197
- U extends UnknownKeys = UnknownKeys
198
- > extends Type < ObjectOutput < T , U > > {
210
+ Rest extends Type | undefined = Type | undefined
211
+ > extends Type < ObjectOutput < T , Rest > > {
199
212
readonly name = "object" ;
200
213
201
- constructor ( readonly shape : T , private readonly unknownKeys : U ) {
214
+ constructor ( readonly shape : T , private readonly restType : Rest ) {
202
215
super ( ) ;
203
216
}
204
217
205
218
toTerminals ( into : TerminalType [ ] ) : void {
206
219
into . push ( this ) ;
207
220
}
208
221
209
- genFunc ( ) : Func < ObjectOutput < T , U > > {
222
+ genFunc ( ) : Func < ObjectOutput < T , Rest > > {
210
223
const shape = this . shape ;
211
- const strip = this . unknownKeys === "strip" ;
212
- const strict = this . unknownKeys === "strict" ;
213
- const passthrough = this . unknownKeys === "passthrough" ;
214
- const catchall =
215
- this . unknownKeys instanceof Type ? this . unknownKeys . func : undefined ;
224
+ const rest = this . restType ? this . restType . func : undefined ;
216
225
217
226
const keys : string [ ] = [ ] ;
218
- const funcs : ( ( v : unknown ) => Result < unknown > ) [ ] = [ ] ;
227
+ const funcs : Func < unknown > [ ] = [ ] ;
219
228
const required : boolean [ ] = [ ] ;
220
229
const knownKeys = Object . create ( null ) ;
221
230
const shapeTemplate = { } as Record < string , unknown > ;
@@ -227,23 +236,27 @@ class ObjectType<
227
236
shapeTemplate [ key ] = undefined ;
228
237
}
229
238
230
- return ( obj ) => {
239
+ return ( obj , mode ) => {
231
240
if ( ! isObject ( obj ) ) {
232
241
return { code : "invalid_type" , expected : [ "object" ] } ;
233
242
}
243
+ const pass = mode === FuncMode . PASS ;
244
+ const strict = mode === FuncMode . STRICT ;
245
+ const strip = mode === FuncMode . STRIP ;
246
+ const template = pass || rest ? obj : shapeTemplate ;
247
+
234
248
let issueTree : IssueTree | undefined = undefined ;
235
249
let output : Record < string , unknown > = obj ;
236
- const template = strict || strip ? shapeTemplate : obj ;
237
- if ( ! passthrough ) {
250
+ if ( strict || strip || rest ) {
238
251
for ( const key in obj ) {
239
252
if ( ! knownKeys [ key ] ) {
240
253
if ( strict ) {
241
254
return { code : "unrecognized_key" , key } ;
242
255
} else if ( strip ) {
243
256
output = { ...template } ;
244
257
break ;
245
- } else if ( catchall ) {
246
- const r = catchall ( obj [ key ] ) ;
258
+ } else if ( rest ) {
259
+ const r = rest ( obj [ key ] , mode ) ;
247
260
if ( r !== true ) {
248
261
if ( r . code === "ok" ) {
249
262
if ( output === obj ) {
@@ -266,7 +279,7 @@ class ObjectType<
266
279
if ( value === undefined && required [ i ] ) {
267
280
return { code : "missing_key" , key } ;
268
281
} else {
269
- const r = funcs [ i ] ( value ) ;
282
+ const r = funcs [ i ] ( value , mode ) ;
270
283
if ( r !== true ) {
271
284
if ( r . code === "ok" ) {
272
285
if ( output === obj ) {
@@ -285,21 +298,12 @@ class ObjectType<
285
298
} else if ( obj === output ) {
286
299
return true ;
287
300
} else {
288
- return { code : "ok" , value : output as ObjectOutput < T , U > } ;
301
+ return { code : "ok" , value : output as ObjectOutput < T , Rest > } ;
289
302
}
290
303
} ;
291
304
}
292
- passthrough ( ) : ObjectType < T , "passthrough" > {
293
- return new ObjectType ( this . shape , "passthrough" ) ;
294
- }
295
- strict ( ) : ObjectType < T , "strict" > {
296
- return new ObjectType ( this . shape , "strict" ) ;
297
- }
298
- strip ( ) : ObjectType < T , "strip" > {
299
- return new ObjectType ( this . shape , "strip" ) ;
300
- }
301
- catchall < C extends Type > ( catchall : C ) : ObjectType < T , C > {
302
- return new ObjectType ( this . shape , catchall ) ;
305
+ rest < R extends Type > ( restType : R ) : ObjectType < T , R > {
306
+ return new ObjectType ( this . shape , restType ) ;
303
307
}
304
308
}
305
309
@@ -316,14 +320,14 @@ class ArrayType<T extends Type = Type> extends Type<Infer<T>[]> {
316
320
317
321
genFunc ( ) : Func < Infer < T > [ ] > {
318
322
const func = this . item . func ;
319
- return ( arr ) => {
323
+ return ( arr , mode ) => {
320
324
if ( ! Array . isArray ( arr ) ) {
321
325
return { code : "invalid_type" , expected : [ "array" ] } ;
322
326
}
323
327
let issueTree : IssueTree | undefined = undefined ;
324
328
let output : Infer < T > [ ] = arr ;
325
329
for ( let i = 0 ; i < arr . length ; i ++ ) {
326
- const r = func ( arr [ i ] ) ;
330
+ const r = func ( arr [ i ] , mode ) ;
327
331
if ( r !== true ) {
328
332
if ( r . code === "ok" ) {
329
333
if ( output === arr ) {
@@ -391,8 +395,12 @@ function createObjectMatchers(
391
395
t : { root : Type ; terminal : TerminalType } [ ]
392
396
) : {
393
397
key : string ;
394
- matcher : ( rootValue : unknown , value : unknown ) => Result < unknown > ;
395
398
isOptional : boolean ;
399
+ matcher : (
400
+ rootValue : unknown ,
401
+ value : unknown ,
402
+ mode : FuncMode
403
+ ) => Result < unknown > ;
396
404
} [ ] {
397
405
const objects : {
398
406
root : Type ;
@@ -460,7 +468,7 @@ function createObjectMatchers(
460
468
461
469
function createUnionMatcher (
462
470
t : { root : Type ; terminal : TerminalType } [ ]
463
- ) : ( rootValue : unknown , value : unknown ) => Result < unknown > {
471
+ ) : ( rootValue : unknown , value : unknown , mode : FuncMode ) => Result < unknown > {
464
472
const literals = new Map < unknown , Type [ ] > ( ) ;
465
473
const types = new Map < BaseType , Type [ ] > ( ) ;
466
474
const allTypes = new Set < BaseType > ( ) ;
@@ -504,7 +512,7 @@ function createUnionMatcher(
504
512
expected : expectedLiterals ,
505
513
} ;
506
514
507
- return ( rootValue : unknown , value : unknown ) => {
515
+ return ( rootValue , value , mode ) => {
508
516
const type = toBaseType ( value ) ;
509
517
if ( ! allTypes . has ( type ) ) {
510
518
return invalidType ;
@@ -514,7 +522,7 @@ function createUnionMatcher(
514
522
if ( options ) {
515
523
let issueTree : IssueTree | undefined ;
516
524
for ( let i = 0 ; i < options . length ; i ++ ) {
517
- const r = options [ i ] . func ( rootValue ) ;
525
+ const r = options [ i ] . func ( rootValue , mode ) ;
518
526
if ( r === true || r . code === "ok" ) {
519
527
return r ;
520
528
}
@@ -560,20 +568,20 @@ class UnionType<T extends Type[] = Type[]> extends Type<Infer<T[number]>> {
560
568
) ;
561
569
const objects = createObjectMatchers ( flattened ) ;
562
570
const base = createUnionMatcher ( flattened ) ;
563
- return ( v ) => {
571
+ return ( v , mode ) => {
564
572
if ( objects . length > 0 && isObject ( v ) ) {
565
573
const item = objects [ 0 ] ;
566
574
const value = v [ item . key ] ;
567
575
if ( value === undefined && ! item . isOptional && ! ( item . key in v ) ) {
568
576
return { code : "missing_key" , key : item . key } ;
569
577
}
570
- const r = item . matcher ( v , value ) ;
578
+ const r = item . matcher ( v , value , mode ) ;
571
579
if ( r === true || r . code === "ok" ) {
572
580
return r as Result < Infer < T [ number ] > > ;
573
581
}
574
582
return prependPath ( item . key , r ) ;
575
583
}
576
- return base ( v , v ) as Result < Infer < T [ number ] > > ;
584
+ return base ( v , v , mode ) as Result < Infer < T [ number ] > > ;
577
585
} ;
578
586
}
579
587
}
@@ -582,7 +590,7 @@ class NumberType extends Type<number> {
582
590
readonly name = "number" ;
583
591
genFunc ( ) : Func < number > {
584
592
const issue : Issue = { code : "invalid_type" , expected : [ "number" ] } ;
585
- return ( v ) => ( typeof v === "number" ? true : issue ) ;
593
+ return ( v , _mode ) => ( typeof v === "number" ? true : issue ) ;
586
594
}
587
595
toTerminals ( into : TerminalType [ ] ) : void {
588
596
into . push ( this ) ;
@@ -592,7 +600,7 @@ class StringType extends Type<number> {
592
600
readonly name = "string" ;
593
601
genFunc ( ) : Func < number > {
594
602
const issue : Issue = { code : "invalid_type" , expected : [ "string" ] } ;
595
- return ( v ) => ( typeof v === "string" ? true : issue ) ;
603
+ return ( v , _mode ) => ( typeof v === "string" ? true : issue ) ;
596
604
}
597
605
toTerminals ( into : TerminalType [ ] ) : void {
598
606
into . push ( this ) ;
@@ -602,7 +610,7 @@ class BigIntType extends Type<number> {
602
610
readonly name = "bigint" ;
603
611
genFunc ( ) : Func < number > {
604
612
const issue : Issue = { code : "invalid_type" , expected : [ "bigint" ] } ;
605
- return ( v ) => ( typeof v === "bigint" ? true : issue ) ;
613
+ return ( v , _mode ) => ( typeof v === "bigint" ? true : issue ) ;
606
614
}
607
615
toTerminals ( into : TerminalType [ ] ) : void {
608
616
into . push ( this ) ;
@@ -612,7 +620,7 @@ class BooleanType extends Type<number> {
612
620
readonly name = "boolean" ;
613
621
genFunc ( ) : Func < number > {
614
622
const issue : Issue = { code : "invalid_type" , expected : [ "boolean" ] } ;
615
- return ( v ) => ( typeof v === "boolean" ? true : issue ) ;
623
+ return ( v , _mode ) => ( typeof v === "boolean" ? true : issue ) ;
616
624
}
617
625
toTerminals ( into : TerminalType [ ] ) : void {
618
626
into . push ( this ) ;
@@ -622,7 +630,7 @@ class UndefinedType extends Type<undefined> {
622
630
readonly name = "undefined" ;
623
631
genFunc ( ) : Func < undefined > {
624
632
const issue : Issue = { code : "invalid_type" , expected : [ "undefined" ] } ;
625
- return ( v ) => ( v === undefined ? true : issue ) ;
633
+ return ( v , _mode ) => ( v === undefined ? true : issue ) ;
626
634
}
627
635
toTerminals ( into : TerminalType [ ] ) : void {
628
636
into . push ( this ) ;
@@ -632,7 +640,7 @@ class NullType extends Type<null> {
632
640
readonly name = "null" ;
633
641
genFunc ( ) : Func < null > {
634
642
const issue : Issue = { code : "invalid_type" , expected : [ "null" ] } ;
635
- return ( v ) => ( v === null ? true : issue ) ;
643
+ return ( v , _mode ) => ( v === null ? true : issue ) ;
636
644
}
637
645
toTerminals ( into : TerminalType [ ] ) : void {
638
646
into . push ( this ) ;
@@ -646,7 +654,7 @@ class LiteralType<Out extends Literal = Literal> extends Type<Out> {
646
654
genFunc ( ) : Func < Out > {
647
655
const value = this . value ;
648
656
const issue : Issue = { code : "invalid_literal" , expected : [ value ] } ;
649
- return ( v ) => ( v === value ? true : issue ) ;
657
+ return ( v , _ ) => ( v === value ? true : issue ) ;
650
658
}
651
659
toTerminals ( into : TerminalType [ ] ) : void {
652
660
into . push ( this ) ;
@@ -666,7 +674,7 @@ class OptionalType<Out, Default> extends Type<Out | Default> {
666
674
this . defaultValue === undefined
667
675
? true
668
676
: ( { code : "ok" , value : this . defaultValue } as const ) ;
669
- return ( v ) => ( v === undefined ? defaultResult : func ( v ) ) ;
677
+ return ( v , mode ) => ( v === undefined ? defaultResult : func ( v , mode ) ) ;
670
678
}
671
679
toTerminals ( into : TerminalType [ ] ) : void {
672
680
into . push ( undefined_ ( ) ) ;
@@ -684,8 +692,8 @@ class TransformType<Out> extends Type<Out> {
684
692
genFunc ( ) : Func < Out > {
685
693
const f = this . transformed . func ;
686
694
const t = this . transformFunc ;
687
- return ( v ) => {
688
- const r = f ( v ) ;
695
+ return ( v , mode ) => {
696
+ const r = f ( v , mode ) ;
689
697
if ( r !== true && r . code !== "ok" ) {
690
698
return r ;
691
699
}
@@ -717,8 +725,8 @@ function null_(): NullType {
717
725
}
718
726
function object < T extends Record < string , Type > > (
719
727
obj : T
720
- ) : ObjectType < T , "strict" > {
721
- return new ObjectType ( obj , "strict" ) ;
728
+ ) : ObjectType < T , undefined > {
729
+ return new ObjectType ( obj , undefined ) ;
722
730
}
723
731
function array < T extends Type > ( item : T ) : ArrayType < T > {
724
732
return new ArrayType ( item ) ;
0 commit comments