Comparing version 1.0.0-alpha.5 to 1.0.0-alpha.6
@@ -1,27 +0,11 @@ | ||
import { ValidationError, Type, StringType, NumberType, LiteralType, ObjectType, ArrayType, UnionType, PartialType, TupleType, DateType, LazyType, UndefinedType, NullType, EnumType, BooleanType, UnknownType, Literal, ObjectShape, ObjectOptions, AnyType, UnionOptions, PartialOpts, IntersectionResult, DeepPartialShape, PartialShape, Eval, ToUnion, keySignature, StringTypes, OptionalType, BigIntOptions, BigIntType } from './types'; | ||
import { ValidationError, Type, StringType, NumberType, LiteralType, ObjectType, ArrayType, UnionType, PartialType, TupleType, DateType, LazyType, UndefinedType, NullType, EnumType, BooleanType, UnknownType, NumberOptions, Literal, ObjectShape, ObjectOptions, AnyType, ArrayOptions, UnionOptions, PartialOpts, IntersectionResult, DeepPartialShape, PartialShape, Eval, ToUnion, keySignature, StringTypes, OptionalType, BigIntOptions, BigIntType, StringOptions } from './types'; | ||
export { ValidationError, Type, Infer, keySignature } from './types'; | ||
export declare const string: (opts?: Partial<{ | ||
pattern: RegExp; | ||
min: number; | ||
max: number; | ||
predicate: (value: string) => boolean; | ||
predicateErrMsg: string; | ||
valid: string[]; | ||
}> | undefined) => StringType; | ||
export declare const string: (opts?: StringOptions | undefined) => StringType; | ||
export declare const boolean: () => BooleanType; | ||
export declare const number: (opts?: Partial<{ | ||
min: number; | ||
max: number; | ||
coerce: boolean; | ||
}> | undefined) => NumberType; | ||
export declare const number: (opts?: NumberOptions | undefined) => NumberType; | ||
export declare const bigint: (opts?: BigIntOptions | undefined) => BigIntType; | ||
export declare const unknown: () => UnknownType; | ||
export declare const literal: <T extends Literal>(literal: T) => LiteralType<T>; | ||
export declare const object: <T extends ObjectShape>(shape: T, opts?: ObjectOptions | undefined) => ObjectType<T>; | ||
export declare const array: <T extends AnyType>(schema: T, opts?: Partial<{ | ||
length: number; | ||
min: number; | ||
max: number; | ||
unique: boolean; | ||
}> | undefined) => ArrayType<T>; | ||
export declare const object: <T extends ObjectShape>(shape: T, opts?: ObjectOptions<T> | undefined) => ObjectType<T>; | ||
export declare const array: <T extends AnyType>(schema: T, opts?: ArrayOptions<T> | undefined) => ArrayType<T>; | ||
export declare const union: <T extends AnyType[]>(schemas: T, opts?: UnionOptions | undefined) => UnionType<T>; | ||
@@ -63,16 +47,5 @@ export declare const intersection: <T extends AnyType, K extends AnyType>(l: T, r: K) => IntersectionResult<T, K>; | ||
Type: typeof Type; | ||
string: (opts?: Partial<{ | ||
pattern: RegExp; | ||
min: number; | ||
max: number; | ||
predicate: (value: string) => boolean; | ||
predicateErrMsg: string; | ||
valid: string[]; | ||
}> | undefined) => StringType; | ||
string: (opts?: StringOptions | undefined) => StringType; | ||
boolean: () => BooleanType; | ||
number: (opts?: Partial<{ | ||
min: number; | ||
max: number; | ||
coerce: boolean; | ||
}> | undefined) => NumberType; | ||
number: (opts?: NumberOptions | undefined) => NumberType; | ||
bigint: (opts?: BigIntOptions | undefined) => BigIntType; | ||
@@ -83,9 +56,4 @@ unknown: () => UnknownType; | ||
date: () => DateType; | ||
object: <T_2 extends ObjectShape>(shape: T_2, opts?: ObjectOptions | undefined) => ObjectType<T_2>; | ||
array: <T_3 extends AnyType>(schema: T_3, opts?: Partial<{ | ||
length: number; | ||
min: number; | ||
max: number; | ||
unique: boolean; | ||
}> | undefined) => ArrayType<T_3>; | ||
object: <T_2 extends ObjectShape>(shape: T_2, opts?: ObjectOptions<T_2> | undefined) => ObjectType<T_2>; | ||
array: <T_3 extends AnyType>(schema: T_3, opts?: ArrayOptions<T_3> | undefined) => ArrayType<T_3>; | ||
union: <T_4 extends AnyType[]>(schemas: T_4, opts?: UnionOptions | undefined) => UnionType<T_4>; | ||
@@ -92,0 +60,0 @@ intersection: <T_5 extends AnyType, K extends AnyType>(l: T_5, r: K) => IntersectionResult<T_5, K>; |
@@ -6,3 +6,5 @@ export declare abstract class Type<T> { | ||
or<K extends AnyType>(schema: K): UnionType<[this, K]>; | ||
optional(this: OptionalType<any>): this; | ||
optional(): OptionalType<this>; | ||
nullable(this: NullableType<any>): this; | ||
nullable(): NullableType<this>; | ||
@@ -22,20 +24,24 @@ try(value: unknown): T | ValidationError; | ||
export declare type IntersectionResult<T extends AnyType, K extends AnyType> = T extends ObjectType<any> ? K extends ObjectType<any> ? T extends ObjectType<infer Shape1> ? K extends ObjectType<infer Shape2> ? ObjectType<Eval<MergeShapes<Shape1, Shape2>>> : never : never : IntersectionType<T, K> : T extends ArrayType<any> ? K extends ArrayType<any> ? T extends ArrayType<infer S1> ? K extends ArrayType<infer S2> ? ArrayType<IntersectionResult<S1, S2>> : never : never : IntersectionType<T, K> : T extends TupleType<any> ? K extends TupleType<any> ? T extends TupleType<infer S1> ? K extends TupleType<infer S2> ? TupleType<Join<S1, S2>> : never : never : IntersectionType<T, K> : IntersectionType<T, K>; | ||
export declare type StringOptions = Partial<{ | ||
pattern: RegExp; | ||
min: number; | ||
max: number; | ||
predicate: (value: string) => boolean; | ||
predicateErrMsg: string; | ||
valid: string[]; | ||
}>; | ||
declare type ErrMsg<T> = string | ((value: T) => string); | ||
declare type Predicate<T> = { | ||
func: (value: T) => boolean; | ||
errMsg?: ErrMsg<T>; | ||
}; | ||
export declare type StringOptions = { | ||
min?: number; | ||
max?: number; | ||
pattern?: RegExp; | ||
valid?: string[]; | ||
predicate?: Predicate<string>['func'] | Predicate<string> | Predicate<string>[]; | ||
}; | ||
export declare class StringType extends Type<string> { | ||
private opts; | ||
private readonly predicates; | ||
constructor(opts?: StringOptions); | ||
parse(value: unknown): string; | ||
and<K extends AnyType>(schema: K): IntersectionType<this, K>; | ||
pattern(regexp: RegExp): StringType; | ||
min(x: number): StringType; | ||
max(x: number): StringType; | ||
valid(list: string[]): StringType; | ||
predicate(fn: StringOptions['predicate'], errMsg?: string): StringType; | ||
pattern(regexp: RegExp, errMsg?: ErrMsg<string>): StringType; | ||
min(x: number, errMsg?: ErrMsg<string>): StringType; | ||
max(x: number, errMsg?: ErrMsg<string>): StringType; | ||
valid(list: string[], errMsg?: ErrMsg<string>): StringType; | ||
withPredicate(fn: Predicate<string>['func'], errMsg?: ErrMsg<string>): StringType; | ||
} | ||
@@ -46,15 +52,18 @@ export declare class BooleanType extends Type<boolean> { | ||
} | ||
export declare type NumberOptions = Partial<{ | ||
min: number; | ||
max: number; | ||
coerce: boolean; | ||
}>; | ||
export declare type NumberOptions = { | ||
min?: number; | ||
max?: number; | ||
coerce?: boolean; | ||
predicate?: Predicate<number>['func'] | Predicate<number> | Predicate<number>[]; | ||
}; | ||
export declare class NumberType extends Type<number> { | ||
private opts; | ||
private readonly predicates; | ||
constructor(opts?: NumberOptions); | ||
parse(value: unknown): number; | ||
and<K extends AnyType>(schema: K): IntersectionType<this, K>; | ||
min(x: number): NumberType; | ||
max(x: number): NumberType; | ||
min(x: number, errMsg?: ErrMsg<number>): NumberType; | ||
max(x: number, errMsg?: ErrMsg<number>): NumberType; | ||
coerce(value?: boolean): NumberType; | ||
withPredicate(fn: Predicate<number>['func'], errMsg?: ErrMsg<number>): NumberType; | ||
} | ||
@@ -64,10 +73,12 @@ export declare type BigIntOptions = { | ||
max?: number | bigint; | ||
predicate?: Predicate<bigint>['func'] | Predicate<bigint> | Predicate<bigint>[]; | ||
}; | ||
export declare class BigIntType extends Type<bigint> { | ||
private opts; | ||
private readonly predicates; | ||
constructor(opts?: BigIntOptions); | ||
parse(value: unknown): bigint; | ||
and<K extends AnyType>(schema: K): IntersectionType<this, K>; | ||
min(x: number | bigint): BigIntType; | ||
max(x: number | bigint): BigIntType; | ||
min(x: number | bigint, errMsg?: ErrMsg<bigint>): BigIntType; | ||
max(x: number | bigint, errMsg?: ErrMsg<bigint>): BigIntType; | ||
withPredicate(fn: Predicate<bigint>['func'], errMsg?: ErrMsg<bigint>): BigIntType; | ||
} | ||
@@ -105,6 +116,11 @@ export declare class UndefinedType extends Type<undefined> { | ||
} | ||
export declare type DateOptions = { | ||
predicate?: Predicate<Date>['func'] | Predicate<Date> | Predicate<Date>[]; | ||
}; | ||
export declare class DateType extends Type<Date> { | ||
constructor(); | ||
private readonly predicates; | ||
constructor(opts?: DateOptions); | ||
parse(value: unknown): Date; | ||
and<K extends AnyType>(schema: K): IntersectionType<this, K>; | ||
withPredicate(fn: Predicate<Date>['func'], errMsg?: ErrMsg<Date>): DateType; | ||
} | ||
@@ -127,7 +143,7 @@ export declare const keySignature: unique symbol; | ||
} : {} : {} : {}; | ||
declare type InferObjectShape<T extends ObjectShape> = InferKeySignature<T> & { | ||
declare type InferObjectShape<T extends ObjectShape> = Eval<InferKeySignature<T> & { | ||
[key in OptionalKeys<T>]?: T[key] extends Type<infer K> ? K : any; | ||
} & { | ||
[key in RequiredKeys<T>]: T[key] extends Type<infer K> ? K : any; | ||
}; | ||
}>; | ||
export declare type ToUnion<T extends any[]> = T[number]; | ||
@@ -147,14 +163,16 @@ export declare type PartialShape<T extends ObjectShape> = { | ||
}; | ||
export declare type ObjectOptions = { | ||
export declare type ObjectOptions<T extends ObjectShape> = { | ||
allowUnknown?: boolean; | ||
predicate?: Predicate<InferObjectShape<T>>['func'] | Predicate<InferObjectShape<T>> | Predicate<InferObjectShape<T>>[]; | ||
}; | ||
export declare class ObjectType<T extends ObjectShape> extends Type<Eval<InferObjectShape<T>>> { | ||
export declare class ObjectType<T extends ObjectShape> extends Type<InferObjectShape<T>> { | ||
private readonly objectShape; | ||
private readonly opts?; | ||
constructor(objectShape: T, opts?: ObjectOptions | undefined); | ||
parse(value: unknown, parseOpts?: ObjectOptions & PathOptions): Eval<InferObjectShape<T>>; | ||
private readonly predicates; | ||
constructor(objectShape: T, opts?: ObjectOptions<T> | undefined); | ||
parse(value: unknown, parseOpts?: ObjectOptions<any> & PathOptions): InferObjectShape<T>; | ||
and<K extends AnyType>(schema: K): IntersectionResult<this, K>; | ||
pick<K extends T extends { | ||
[keySignature]: AnyType; | ||
} ? string : StringTypes<keyof T>>(keys: K[], opts?: ObjectOptions): ObjectType<Eval<Pick<T, Extract<StringTypes<keyof T>, ToUnion<typeof keys>>> & (T extends { | ||
} ? string : StringTypes<keyof T>>(keys: K[], opts?: ObjectOptions<Eval<Pick<T, Extract<StringTypes<keyof T>, ToUnion<typeof keys>>> & (T extends { | ||
[keySignature]: AnyType; | ||
@@ -165,25 +183,36 @@ } ? T extends { | ||
[key in Exclude<ToUnion<typeof keys>, keyof T>]: KeySig; | ||
} : {} : {})>>): ObjectType<Eval<Pick<T, Extract<StringTypes<keyof T>, ToUnion<typeof keys>>> & (T extends { | ||
[keySignature]: AnyType; | ||
} ? T extends { | ||
[keySignature]: infer KeySig; | ||
} ? { | ||
[key in Exclude<ToUnion<typeof keys>, keyof T>]: KeySig; | ||
} : {} : {})>>; | ||
omit<K extends StringTypes<keyof T>>(keys: K[], opts?: ObjectOptions): ObjectType<Eval<Omit<T, ToUnion<typeof keys>>>>; | ||
partial<K extends ObjectOptions & PartialOpts>(opts?: K): ObjectType<Eval<K extends { | ||
omit<K extends StringTypes<keyof T>>(keys: K[], opts?: ObjectOptions<Eval<Omit<T, ToUnion<typeof keys>>>>): ObjectType<Eval<Omit<T, ToUnion<typeof keys>>>>; | ||
partial<K extends ObjectOptions<Eval<DeepPartialShape<T>>> & { | ||
deep: true; | ||
} ? DeepPartialShape<T> : PartialShape<T>>>; | ||
}>(opts?: K): ObjectType<Eval<DeepPartialShape<T>>>; | ||
partial<K extends ObjectOptions<Eval<PartialShape<T>>> & PartialOpts>(opts?: K): ObjectType<Eval<PartialShape<T>>>; | ||
withPredicate(fn: Predicate<InferObjectShape<T>>['func'], errMsg?: ErrMsg<InferObjectShape<T>>): ObjectType<T>; | ||
} | ||
export declare type ArrayOptions = Partial<{ | ||
length: number; | ||
min: number; | ||
max: number; | ||
unique: boolean; | ||
}>; | ||
export declare type ArrayOptions<T extends AnyType> = { | ||
length?: number; | ||
min?: number; | ||
max?: number; | ||
unique?: boolean; | ||
predicate?: Predicate<Infer<T>[]>['func'] | Predicate<Infer<T>[]> | Predicate<Infer<T>[]>[]; | ||
}; | ||
export declare class ArrayType<T extends AnyType> extends Type<Infer<T>[]> { | ||
private readonly schema; | ||
private readonly opts; | ||
private readonly predicates; | ||
private readonly _parse; | ||
constructor(schema: T, opts?: ArrayOptions); | ||
parse(value: unknown, parseOptions?: PathOptions & ObjectOptions): Infer<T>[]; | ||
length(value: number): ArrayType<T>; | ||
min(value: number): ArrayType<T>; | ||
max(value: number): ArrayType<T>; | ||
unique(value?: boolean): ArrayType<T>; | ||
constructor(schema: T, opts?: ArrayOptions<T>); | ||
parse(value: unknown, parseOptions?: PathOptions & ObjectOptions<any>): Infer<T>[]; | ||
length(value: number, errMsg?: ErrMsg<Infer<T>[]>): ArrayType<T>; | ||
min(value: number, errMsg?: ErrMsg<Infer<T>[]>): ArrayType<T>; | ||
max(value: number, errMsg?: ErrMsg<Infer<T>[]>): ArrayType<T>; | ||
unique(): ArrayType<T>; | ||
and<K extends AnyType>(schema: K): IntersectionResult<this, K>; | ||
withPredicate(fn: Predicate<Infer<T>[]>['func'], errMsg?: ErrMsg<Infer<T>[]>): ArrayType<T>; | ||
} | ||
@@ -203,5 +232,7 @@ declare type IntersecWrapper<A extends any, B extends any> = A extends AnyType ? B extends AnyType ? IntersectionResult<A, B> : never : never; | ||
private readonly schemas; | ||
constructor(schemas: T); | ||
private readonly predicates; | ||
constructor(schemas: T, predicate?: Predicate<InferTuple<T>>['func'] | Predicate<InferTuple<T>> | Predicate<InferTuple<T>>[]); | ||
parse(value: unknown): InferTuple<T>; | ||
and<K extends AnyType>(schema: K): K extends TupleType<any> ? K extends TupleType<infer Arr> ? TupleType<Join<T, Arr>> : never : IntersectionType<this, K>; | ||
withPredicate(fn: Predicate<InferTuple<T>>['func'], errMsg?: ErrMsg<InferTuple<T>>): TupleType<T>; | ||
} | ||
@@ -224,3 +255,3 @@ declare type InferTupleUnion<T extends any[]> = Infer<T[number]>; | ||
constructor(left: T, right: K); | ||
parse(value: unknown, opts?: PathOptions & ObjectOptions): Eval<Infer<T> & Infer<K>>; | ||
parse(value: unknown, opts?: PathOptions & ObjectOptions<any>): Eval<Infer<T> & Infer<K>>; | ||
and<K extends AnyType>(schema: K): IntersectionType<this, K>; | ||
@@ -227,0 +258,0 @@ } |
@@ -9,5 +9,11 @@ "use strict"; | ||
optional() { | ||
if (this instanceof OptionalType) { | ||
return this; | ||
} | ||
return new OptionalType(this); | ||
} | ||
nullable() { | ||
if (this instanceof NullableType) { | ||
return this; | ||
} | ||
return new NullableType(this); | ||
@@ -59,6 +65,57 @@ } | ||
const coercionTypeSymbol = Symbol.for('coercion'); | ||
const normalizePredicates = (predicate) => { | ||
if (!predicate) { | ||
return null; | ||
} | ||
if (typeof predicate === 'function') { | ||
return [{ func: predicate }]; | ||
} | ||
if (Array.isArray(predicate)) { | ||
return predicate; | ||
} | ||
return [predicate]; | ||
}; | ||
const applyPredicates = (predicates, value) => { | ||
try { | ||
for (const predicate of predicates) { | ||
if (!predicate.func(value)) { | ||
throw new ValidationError(predicate.errMsg | ||
? typeof predicate.errMsg === 'function' | ||
? predicate.errMsg(value) | ||
: predicate.errMsg | ||
: 'failed anonymous predicate function'); | ||
} | ||
} | ||
} | ||
catch (err) { | ||
if (err instanceof ValidationError) { | ||
throw err; | ||
} | ||
throw new ValidationError(err.message); | ||
} | ||
}; | ||
const appendPredicate = (predicates, pred) => { | ||
if (!predicates) { | ||
return [pred]; | ||
} | ||
return [...predicates, pred]; | ||
}; | ||
class StringType extends Type { | ||
constructor(opts = {}) { | ||
constructor(opts) { | ||
super(); | ||
this.opts = opts; | ||
this.predicates = normalizePredicates(opts === null || opts === void 0 ? void 0 : opts.predicate); | ||
let self = this; | ||
if (typeof (opts === null || opts === void 0 ? void 0 : opts.min) !== 'undefined') { | ||
self = self.min(opts.min); | ||
} | ||
if (typeof (opts === null || opts === void 0 ? void 0 : opts.max) !== 'undefined') { | ||
self = self.max(opts.max); | ||
} | ||
if (typeof (opts === null || opts === void 0 ? void 0 : opts.pattern) !== 'undefined') { | ||
self = self.pattern(opts.pattern); | ||
} | ||
if (opts === null || opts === void 0 ? void 0 : opts.valid) { | ||
self = self.valid(opts.valid); | ||
} | ||
return self; | ||
} | ||
@@ -69,27 +126,5 @@ parse(value) { | ||
} | ||
if (typeof this.opts.min === 'number' && value.length < this.opts.min) { | ||
throw new ValidationError(`expected string to have length greater than or equal to ${this.opts.min} but had length ${value.length}`); | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, value); | ||
} | ||
if (typeof this.opts.max === 'number' && value.length > this.opts.max) { | ||
throw new ValidationError(`expected string to have length less than or equal to ${this.opts.max} but had length ${value.length}`); | ||
} | ||
if (this.opts.pattern instanceof RegExp && !this.opts.pattern.test(value)) { | ||
throw new ValidationError(`expected string to match pattern ${this.opts.pattern} but did not`); | ||
} | ||
if (this.opts.valid && !this.opts.valid.includes(value)) { | ||
throw new ValidationError(`expected string to be one of: ${JSON.stringify(this.opts.valid)}`); | ||
} | ||
if (this.opts.predicate) { | ||
try { | ||
if (this.opts.predicate(value) === false) { | ||
throw new ValidationError(this.opts.predicateErrMsg || 'expected string to pass predicate function'); | ||
} | ||
} | ||
catch (err) { | ||
if (err instanceof ValidationError) { | ||
throw err; | ||
} | ||
throw new ValidationError(err.message); | ||
} | ||
} | ||
return value; | ||
@@ -100,16 +135,18 @@ } | ||
} | ||
pattern(regexp) { | ||
return new StringType(Object.assign(Object.assign({}, this.opts), { pattern: regexp })); | ||
pattern(regexp, errMsg) { | ||
return this.withPredicate(value => regexp.test(value), errMsg || `expected string to match pattern ${regexp} but did not`); | ||
} | ||
min(x) { | ||
return new StringType(Object.assign(Object.assign({}, this.opts), { min: x })); | ||
min(x, errMsg) { | ||
return this.withPredicate((value) => value.length >= x, errMsg || | ||
((value) => `expected string to have length greater than or equal to ${x} but had length ${value.length}`)); | ||
} | ||
max(x) { | ||
return new StringType(Object.assign(Object.assign({}, this.opts), { max: x })); | ||
max(x, errMsg) { | ||
return this.withPredicate((value) => value.length <= x, errMsg || | ||
((value) => `expected string to have length less than or equal to ${x} but had length ${value.length}`)); | ||
} | ||
valid(list) { | ||
return new StringType(Object.assign(Object.assign({}, this.opts), { valid: list })); | ||
valid(list, errMsg) { | ||
return this.withPredicate((value) => list.includes(value), errMsg || `expected string to be one of: ${JSON.stringify(list)}`); | ||
} | ||
predicate(fn, errMsg) { | ||
return new StringType(Object.assign(Object.assign({}, this.opts), { predicate: fn, predicateErrMsg: errMsg || this.opts.predicateErrMsg })); | ||
withPredicate(fn, errMsg) { | ||
return new StringType({ predicate: appendPredicate(this.predicates, { func: fn, errMsg }) }); | ||
} | ||
@@ -135,2 +172,11 @@ } | ||
this[coercionTypeSymbol] = !!opts.coerce; | ||
this.predicates = normalizePredicates(opts.predicate); | ||
let self = this; | ||
if (typeof opts.max !== 'undefined') { | ||
self = self.max(opts.max); | ||
} | ||
if (typeof opts.min !== 'undefined') { | ||
self = self.min(opts.min); | ||
} | ||
return self; | ||
} | ||
@@ -148,8 +194,5 @@ parse(value) { | ||
} | ||
if (typeof this.opts.min === 'number' && value < this.opts.min) { | ||
throw new ValidationError(`expected number to be greater than or equal to ${this.opts.min} but got ${value}`); | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, value); | ||
} | ||
if (typeof this.opts.max === 'number' && value > this.opts.max) { | ||
throw new ValidationError(`expected number to be less than or equal to ${this.opts.max} but got ${value}`); | ||
} | ||
return value; | ||
@@ -160,11 +203,20 @@ } | ||
} | ||
min(x) { | ||
return new NumberType(Object.assign(Object.assign({}, this.opts), { min: x })); | ||
min(x, errMsg) { | ||
return this.withPredicate(value => value >= x, errMsg || (value => `expected number to be greater than or equal to ${x} but got ${value}`)); | ||
} | ||
max(x) { | ||
return new NumberType(Object.assign(Object.assign({}, this.opts), { max: x })); | ||
max(x, errMsg) { | ||
return this.withPredicate(value => value <= x, errMsg || (value => `expected number to be less than or equal to ${x} but got ${value}`)); | ||
} | ||
coerce(value) { | ||
return new NumberType(Object.assign(Object.assign({}, this.opts), { coerce: value !== undefined ? value : true })); | ||
return new NumberType({ | ||
predicate: this.predicates || undefined, | ||
coerce: value !== undefined ? value : true, | ||
}); | ||
} | ||
withPredicate(fn, errMsg) { | ||
return new NumberType({ | ||
coerce: this.opts.coerce, | ||
predicate: appendPredicate(this.predicates, { func: fn, errMsg }), | ||
}); | ||
} | ||
} | ||
@@ -175,4 +227,4 @@ exports.NumberType = NumberType; | ||
super(); | ||
this.opts = opts; | ||
this[coercionTypeSymbol] = true; | ||
this.predicates = normalizePredicates(opts.predicate); | ||
} | ||
@@ -182,8 +234,5 @@ parse(value) { | ||
const int = BigInt(value); | ||
if (this.opts.min !== undefined && int < this.opts.min) { | ||
throw new ValidationError(`expected bigint to be greater than or equal to ${this.opts.min} but got ${int}`); | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, int); | ||
} | ||
if (this.opts.max !== undefined && int > this.opts.max) { | ||
throw new ValidationError(`expected bigint to be less than or equal to ${this.opts.max} but got ${int}`); | ||
} | ||
return int; | ||
@@ -201,8 +250,11 @@ } | ||
} | ||
min(x) { | ||
return new BigIntType(Object.assign(Object.assign({}, this.opts), { min: x })); | ||
min(x, errMsg) { | ||
return this.withPredicate(value => value >= x, errMsg || (value => `expected bigint to be greater than or equal to ${x} but got ${value}`)); | ||
} | ||
max(x) { | ||
return new BigIntType(Object.assign(Object.assign({}, this.opts), { max: x })); | ||
max(x, errMsg) { | ||
return this.withPredicate(value => value <= x, errMsg || (value => `expected bigint to be less than or equal to ${x} but got ${value}`)); | ||
} | ||
withPredicate(fn, errMsg) { | ||
return new BigIntType({ predicate: appendPredicate(this.predicates, { func: fn, errMsg }) }); | ||
} | ||
} | ||
@@ -299,20 +351,27 @@ exports.BigIntType = BigIntType; | ||
exports.NullableType = NullableType; | ||
// Non Primitive types | ||
const stringToDate = (str) => { | ||
const date = new Date(str); | ||
if (isNaN(date.getTime())) { | ||
throw new ValidationError(`expected date string to be valid date`); | ||
} | ||
return date; | ||
}; | ||
const assertDate = (date) => { | ||
if (!(date instanceof Date)) { | ||
throw new ValidationError('expected type Date but got ' + typeOf(date)); | ||
} | ||
return date; | ||
}; | ||
class DateType extends Type { | ||
constructor() { | ||
constructor(opts) { | ||
super(); | ||
this[coercionTypeSymbol] = true; | ||
this.predicates = normalizePredicates(opts === null || opts === void 0 ? void 0 : opts.predicate); | ||
} | ||
parse(value) { | ||
if (typeof value === 'string') { | ||
const date = new Date(value); | ||
if (isNaN(date.getTime())) { | ||
throw new ValidationError(`expected date string to be valid date`); | ||
} | ||
return date; | ||
const date = typeof value === 'string' ? stringToDate(value) : assertDate(value); | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, date); | ||
} | ||
if (!(value instanceof Date)) { | ||
throw new ValidationError('expected type Date but got ' + typeOf(value)); | ||
} | ||
return value; | ||
return date; | ||
} | ||
@@ -322,2 +381,5 @@ and(schema) { | ||
} | ||
withPredicate(fn, errMsg) { | ||
return new DateType({ predicate: appendPredicate(this.predicates, { func: fn, errMsg }) }); | ||
} | ||
} | ||
@@ -331,2 +393,3 @@ exports.DateType = DateType; | ||
this.opts = opts; | ||
this.predicates = normalizePredicates(opts === null || opts === void 0 ? void 0 : opts.predicate); | ||
const keys = Object.keys(this.objectShape); | ||
@@ -384,2 +447,5 @@ this[allowUnknownSymbol] = !!(opts === null || opts === void 0 ? void 0 : opts.allowUnknown); | ||
} | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, convVal || value); | ||
} | ||
return convVal || value; | ||
@@ -408,2 +474,5 @@ } | ||
} | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, convVal || value); | ||
} | ||
return convVal || value; | ||
@@ -433,2 +502,5 @@ } | ||
} | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, convVal || value); | ||
} | ||
return convVal || value; | ||
@@ -483,2 +555,5 @@ } | ||
} | ||
withPredicate(fn, errMsg) { | ||
return new ObjectType(this.objectShape, Object.assign(Object.assign({}, this.opts), { predicate: appendPredicate(this.predicates, { func: fn, errMsg }) })); | ||
} | ||
} | ||
@@ -491,2 +566,3 @@ exports.ObjectType = ObjectType; | ||
this.opts = opts; | ||
this.predicates = normalizePredicates(this.opts.predicate); | ||
this[coercionTypeSymbol] = this.schema[coercionTypeSymbol]; | ||
@@ -497,2 +573,16 @@ this._parse = | ||
: (elem) => this.schema.parse(elem); | ||
let self = this; | ||
if (typeof opts.length !== 'undefined') { | ||
self = this.length(opts.length); | ||
} | ||
if (typeof opts.min !== 'undefined') { | ||
self = this.min(opts.min); | ||
} | ||
if (typeof opts.max !== 'undefined') { | ||
self = this.max(opts.max); | ||
} | ||
if (opts.unique === true) { | ||
self = this.unique(); | ||
} | ||
return self; | ||
} | ||
@@ -503,23 +593,2 @@ parse(value, parseOptions) { | ||
} | ||
if (typeof this.opts.length === 'number' && this.opts.length >= 0 && value.length !== this.opts.length) { | ||
throw new ValidationError(`expected array to have length ${this.opts.length} but got ${value.length}`); | ||
} | ||
if (typeof this.opts.min === 'number' && value.length < this.opts.min) { | ||
throw new ValidationError(`expected array to have length greater than or equal to ${this.opts.min} but got ${value.length}`); | ||
} | ||
if (typeof this.opts.max === 'number' && value.length > this.opts.max) { | ||
throw new ValidationError(`expected array to have length less than or equal to ${this.opts.max} but got ${value.length}`); | ||
} | ||
if (this.opts.unique === true && new Set(value).size !== value.length) { | ||
const seenMap = new Map(); | ||
value.forEach((elem, idx) => { | ||
const seenAt = seenMap.get(elem); | ||
if (!seenAt) { | ||
seenMap.set(elem, [idx]); | ||
} | ||
else { | ||
throw new ValidationError(`expected array to be unique but found same element at indexes ${seenAt[0]} and ${idx}`); | ||
} | ||
}); | ||
} | ||
const convValue = this[coercionTypeSymbol] ? [] : undefined; | ||
@@ -542,15 +611,28 @@ for (let i = 0; i < value.length; i++) { | ||
} | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, convValue || value); | ||
} | ||
return convValue || value; | ||
} | ||
length(value) { | ||
return new ArrayType(this.schema, Object.assign(Object.assign({}, this.opts), { length: value })); | ||
length(value, errMsg) { | ||
return this.withPredicate(arr => arr.length === value, errMsg || (arr => `expected array to have length ${value} but got ${arr.length}`)); | ||
} | ||
min(value) { | ||
return new ArrayType(this.schema, Object.assign(Object.assign({}, this.opts), { min: value })); | ||
min(value, errMsg) { | ||
return this.withPredicate(arr => arr.length >= value, errMsg || (arr => `expected array to have length greater than or equal to ${value} but got ${arr.length}`)); | ||
} | ||
max(value) { | ||
return new ArrayType(this.schema, Object.assign(Object.assign({}, this.opts), { max: value })); | ||
max(value, errMsg) { | ||
return this.withPredicate(arr => arr.length <= value, errMsg || (arr => `expected array to have length less than or equal to ${value} but got ${arr.length}`)); | ||
} | ||
unique(value = true) { | ||
return new ArrayType(this.schema, Object.assign(Object.assign({}, this.opts), { unique: value })); | ||
unique() { | ||
return this.withPredicate(arr => { | ||
const seenMap = new Map(); | ||
arr.forEach((elem, idx) => { | ||
const seenAt = seenMap.get(elem); | ||
if (seenAt) { | ||
throw new ValidationError(`expected array to be unique but found same element at indexes ${seenAt[0]} and ${idx}`); | ||
} | ||
seenMap.set(elem, [idx]); | ||
}); | ||
return true; | ||
}); | ||
} | ||
@@ -563,8 +645,12 @@ and(schema) { | ||
} | ||
withPredicate(fn, errMsg) { | ||
return new ArrayType(this.schema, { predicate: appendPredicate(this.predicates, { func: fn, errMsg }) }); | ||
} | ||
} | ||
exports.ArrayType = ArrayType; | ||
class TupleType extends Type { | ||
constructor(schemas) { | ||
constructor(schemas, predicate) { | ||
super(); | ||
this.schemas = schemas; | ||
this.predicates = normalizePredicates(predicate); | ||
this[coercionTypeSymbol] = schemas.some(schema => schema[coercionTypeSymbol]); | ||
@@ -593,2 +679,5 @@ } | ||
} | ||
if (this.predicates) { | ||
applyPredicates(this.predicates, convValue || value); | ||
} | ||
return convValue || value; | ||
@@ -617,2 +706,5 @@ } | ||
} | ||
withPredicate(fn, errMsg) { | ||
return new TupleType(this.schemas, appendPredicate(this.predicates, { func: fn, errMsg })); | ||
} | ||
} | ||
@@ -619,0 +711,0 @@ exports.TupleType = TupleType; |
{ | ||
"name": "myzod", | ||
"version": "1.0.0-alpha.5", | ||
"version": "1.0.0-alpha.6", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "./libs/index.js", |
104
readme.md
@@ -176,9 +176,21 @@ # myzod | ||
- min: `number` - sets the minimum length for the string | ||
- max: `number` - sets the maximum length for the string | ||
- pattern: `RegExp` - expression string must match | ||
- valid: `string[]` - array of valid strings | ||
- predicate: `(val: string) => boolean` - predicate function to extend string validation. | ||
- predicateErrMsg: `string` - error message to throw in ValidationError should predicate fail | ||
- min `number` - min length of string | ||
- max `number` - max length of string | ||
- pattern `RegExp` - regular expression string must match | ||
- valid `string[]` - list of valid stings | ||
- predicate `Predicate<string>` - custom predicates to apply to string value | ||
methods: | ||
- `min(value: number, errMsg?: string) => StringType` | ||
returns a new string schema where minimum string lenth is min | ||
- `max(value: number, errMsg?: string) => StringType` | ||
returns a new string schema where maximum string length is max | ||
- `pattern(value: RegExp, errMsg?: string) => StringType` | ||
returns a new string schema where string must match pattern | ||
- `valid(list: string[], errMsg?: string) => StringType` | ||
returns a new string schema where string must be included in valid string array | ||
- `withPredicate(fn: (val: string) => boolean), errMsg?: string }` | ||
returns a new schema where string must pass predicate function(s). | ||
options can be passed as an option object or chained from schema. | ||
@@ -188,3 +200,3 @@ | ||
myzod.string({ min: 3, max: 10, pattern: /^hey/ }); | ||
// Same as: | ||
// same as | ||
myzod.string().min(3).max(10).pattern(/^hey/); | ||
@@ -204,9 +216,9 @@ ``` | ||
const helloworld = myzod.literals('hello', 'world'); | ||
typeof HelloWorld = myzod.Infer<typeof helloworld>; // => 'hello' | 'world' | ||
type HelloWorld = myzod.Infer<typeof helloworld>; // => 'hello' | 'world' | ||
``` | ||
Myzod is not interested in reimplementing all possible string validations, ie isUUID, isEmail, isAlphaNumeric, etc. The myzod string validation can be easily extended using the predicate and predicateErrMsg options | ||
Myzod is not interested in reimplementing all possible string validations, ie isUUID, isEmail, isAlphaNumeric, etc. The myzod string validation can be easily extended via the withPredicate API. | ||
```typescript | ||
const uuidSchema = myzod.string().predicate(validator.isUUID, 'expected string to be uuid'); | ||
const uuidSchema = myzod.string().withPredicate(validator.isUUID, 'expected string to be uuid'); | ||
@@ -219,2 +231,10 @@ type UUID = Infer<typeof uuidSchema>; // => string | ||
Note that you can register multiple predicates, and that each invocation will create a new schema: | ||
```typescript | ||
const greeting = myzod.string().withPredicate(value => value.startsWith('hello'), 'string must start with hello'); | ||
const evenGreeting = greeting.withPredicate(value => value.length % 2 === 0, 'string must have even length'); | ||
const oddGreeting = greeting.withPredicate(value => value.length % 2 === 1, 'string must have odd length'); | ||
``` | ||
#### Number | ||
@@ -228,2 +248,13 @@ | ||
methods: | ||
- `min(value: number, errMsg?: string) => NumberType` | ||
returns a new number schema where number must be greater than or equal to min value | ||
- `max(value: number, errMsg?: string) => NumberType` | ||
returns a new number schema where number must be less than or equal to max value | ||
- `withPredicate(fn: (value: number) => boolean, errMsg?: string) => NumberType` | ||
returns a new number schema where number must satisfy predicate function | ||
- `coerce(flag?: boolean) => NumberType` | ||
returns a new number schema which depending on the flag will coerce strings to numbers | ||
options can be passed as an option object or chained from schema. | ||
@@ -255,2 +286,11 @@ | ||
methods: | ||
- `min(value: number | bigint) => BigIntType` | ||
returns a new bigint schema where value must be at least min | ||
- `max(value: number | bigint) => BigIntType` | ||
returns a new bigint schema where value must be lesser or equal to max | ||
- `withPredicate(fn: (value: bigint) => boolean, errMsg?: string) => BigIntType` | ||
returns a new bigint schema where value must pass predicate function | ||
options can be passed as an option object or chained from schema. | ||
@@ -368,5 +408,19 @@ | ||
#### object.withPredicate | ||
You can add predicate functions to object schemas. Note that these predicate functions will not be kept around for schemas produces from object.pick/omit/partial as they predicate function signatures need to change for those signatures. | ||
```typescript | ||
const registrationSchema = myzod | ||
.object({ | ||
email: z.string().withPredicate(validator.isEmail, 'expected email'), | ||
password: z.string().min(8), | ||
confirmedPassword: z.string(), | ||
}) | ||
.withPredicate(value => value.password === value.confirmedPassword, 'password and confirmed do not match'); | ||
``` | ||
#### object.pick/omit/partial | ||
The Object type has utility methods pick, omit, and partial for creating new ObjectType schemas based on the current instance. | ||
The Object type has utility methods pick, omit, and partial for creating new ObjectType schemas based on the current instance. Note once more that predicates do not carry over from base schema. | ||
@@ -490,2 +544,15 @@ ```typescript | ||
methods: | ||
- `length(value: number, errMsg?: string) => ArrayType<T>` | ||
returns a new array schema of the same type where the length of the array must be value | ||
- `min(value: number, errMsg?: string) => ArrayType<T>` | ||
returns a new array schema of the same type where the minimum length is value | ||
- `max(value: number, errMsg?: string) => ArrayType<T>` | ||
returns a new array schema of the same type where the maximum length is value | ||
- `unique() => ArrayType<T>` | ||
returns a new array schema of the same type where every element must be unique | ||
- `withPredicate(fn: (value: T[]) => boolean, errMsg?: string) => ArrayType<T>` | ||
returns a new array schema that must respect predicate function | ||
Signature: | ||
@@ -509,2 +576,7 @@ | ||
methods: | ||
- `withPredicate(fn: (value: Infer<TupleType<T>>) => boolean, errMsg?: string) => TupleType<T>` | ||
returns a new tuple type that must respect predicate function | ||
Tuples are similar to arrays but allow for mixed types of static length. | ||
@@ -552,2 +624,7 @@ Note that myzod does not support intersections of tuple types at this time. | ||
methods: | ||
- `withPredicate(fn: (value: Date) => boolean, errMsg?: string) => DateType` | ||
returns a new date schema where value must pass predicate function(s) | ||
the myzod.date function creates a date schema. Values that will be successfully parsed by this schema are | ||
@@ -563,2 +640,7 @@ Javascript Date instances and valid string representations of dates. The returned parse Date will be an instance of Date. | ||
schema.parse(date.toISOString()); // returns a date instance equal to date | ||
// WithPredicate example | ||
const weekDay = myzod | ||
.date() | ||
.withPredicate(date => date.getUTCDate() !== 6 && date.getUTCDate() !== 0, 'expected weekday'); | ||
``` | ||
@@ -565,0 +647,0 @@ |
77650
1305
752