Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript types declaration. #19

Open
majo44 opened this issue Oct 23, 2019 · 3 comments
Open

Typescript types declaration. #19

majo44 opened this issue Oct 23, 2019 · 3 comments

Comments

@majo44
Copy link
Contributor

majo44 commented Oct 23, 2019

As this becomes as an standard, and allows wider adoption, it is worth to add to library the typescript types declaration.

@simonguo
Copy link
Member

@majo44
Copy link
Contributor Author

majo44 commented Oct 24, 2019

Hi, thanks, good to know that there is declaration:

I very like that lib, have simple reasonable api, supports es6, and do not have any dependencies, I checked 20 different similar libs, and I very like to use that one in my project, and I do not using RSuite as I do not using the React there at all, so:

  1. why is not add this declaration to the package, to simplify the consumption
  2. maybe we can create a bit more expressive declaration which can be used together with static generic types, that is example how such declaration can be checked during compilation:
interface A {a: string, b?: number}

// Example of schema with provided type
const typedSchema = new Schema<A>({
    a: NumberType(), // Error Type 'INumberType<Partial<A>>' is not assignable to type 'IType<string, Partial<A>>'... Type 'number' is not assignable to type 'string'.
    // for autocast of data in addRule we can also add type here
    b: NumberType<A>().isRequired('Required').addRule((value, data) => {
        let v: string = value; // Error: Type 'number' is not assignable to type 'string'
        let d: {x: any} = data; // Error: Property 'x' is missing in type 'A' but required in type '{ x: any; }'.
    }),
    c: BooleanType(), // Error: Argument of type '{ a: INumberType<any>; b: INumberType<A>; c: IBooleanType<any>; }' is not assignable to parameter of type 'ISchema<Partial<A>>'.
});

typedSchema.check({ c: 12}); //Error: Argument of type '{ c: number; }' is not assignable to parameter of type 'A'
typedSchema.checkForField('x', '12'); //Error: Argument of type '"x"' is not assignable to parameter of type '"a" | "b" | "c"'.

// Example of schema without provided type
// The validation object type will be inferred
const noTypedSchema = new Schema({
    a: NumberType(),
    b: NumberType().isRequired('Required').addRule((value, data) => {
        let v: string = value; // Error: Type 'number' is not assignable to type 'string'
        let d: {x: any} = data;
    }),
    c: BooleanType(),
});

noTypedSchema.check({ c: 12}); //Error: Type 'number' is not assignable to type 'boolean'.
noTypedSchema.checkForField('x', '12'); //Error: Argument of type '"x"' is not assignable to parameter of type '"a" | "b" | "c"'.


// Example of schema with any type provided
const anySchema = new Schema<any>({
    a: NumberType(),
    b: NumberType().isRequired('Required').addRule((value, data) => {
        let v: string = value; // Error: Type 'number' is not assignable to type 'string'
        let d: {x: any} = data;
    }),
    c: BooleanType(),
});

anySchema.check({ c: 12});
anySchema.checkForField('x', '12');

You can plat with that here

I created prototype of such declaration which allows such static checks:

export type RuleFn<V, D> = (value: V, data: D) =>
    FieldSchemaCheckResult | boolean  | void | undefined | Promise<boolean> | Promise<FieldSchemaCheckResult> | Promise<void>;

export interface IType<P, T> {
  addRule(onValid: RuleFn<P, T>, errorMessage?: string, priority?: boolean): this;
}

export interface IStringType<T = any> extends IType<string, T> {
  isRequired(errorMessage?: string, trim?: boolean): this;
  isEmail(errorMessage?: string): this;
  isURL(errorMessage?: string): this;
  isOneOf(items: Array<string>, errorMessage?: string): this;
  containsLetter(errorMessage?: string): this;
  containsUppercaseLetter(errorMessage?: string): this;
  containsLowercaseLetter(errorMessage?: string): this;
  containsLetterOnly(errorMessage?: string): this;
  containsNumber(errorMessage?: string): this;
  pattern(regExp: RegExp, errorMessage?: string): this;
  rangeLength(minLength: number, maxLength: number, errorMessage?: string): this;
  minLength(minLength: number, errorMessage?: string): this;
  maxLength(maxLength: number, errorMessage?: string): this;
}

export interface INumberType<T = any> extends IType<number, T>  {
  isRequired(errorMessage?: string): this;
  isInteger(errorMessage?: string): this;
  isOneOf(items: Array<number>, errorMessage?: string): this;
  pattern(regExp: RegExp, errorMessage?: string): this;
  range(minLength: number, maxLength: number, errorMessage?: string): this;
  min(min: number, errorMessage?: string): this;
  max(max: number, errorMessage?: string): this;
}

export interface IArrayType<I = any, T = any> extends IType<Array<I>, T>  {
  isRequired(errorMessage?: string): this;
  rangeLength(minLength: number, maxLength: number, errorMessage?: string): this;
  minLength(minLength: number, errorMessage?: string): this;
  maxLength(maxLength: number, errorMessage?: string): this;
  unrepeatable(errorMessage?: string): this;
  of(type: IType<I, T>, errorMessage?: string): this;
}

export interface IDateType<T = any> extends IType<Date, T> {
  isRequired(errorMessage?: string): this;
  range(min: Date, max: Date, errorMessage?: string): this;
  min(min: Date, errorMessage?: string): this;
  max(max: Date, errorMessage?: string): this;
}

export interface IObjectType<O = any, T = any> extends IType<O, T> {
  isRequired(errorMessage?: string): this;
  shape(shape: ISchema<O>): this;
}

export interface IBooleanType<T = any> extends IType<boolean, T>  {
  isRequired(errorMessage?: string): this;
}

type IType<X, T> =
  T extends any ? any :
    X extends string ? IStringType<T>:
        X extends number ? INumberType<T>:
            X extends boolean ? IBooleanType<T>:
                X extends Date ? IDateType<T>:
                    X extends Array<infer I> ? IArrayType<I, T>:
                        X extends any ?
                            IStringType<T> | INumberType<T> | IBooleanType<T> | IArrayType<any, T> | IDateType<T> | IObjectType<X, T> :
                            IObjectType<X, T>;

export type ISchema<T> = {
  [P in keyof T]: IType<T[P], T>;
}

export type FieldSchemaCheckResult = {
  hasError: boolean,
  errorMessage: string
}

export type SchemaCheckResult<T> = {
  [P in keyof T]: FieldSchemaCheckResult
};

export interface ISchemaModel<T = any> {
  schema: ISchema<T>;
  check(data: T): SchemaCheckResult<T>;
  checkAsync(data: T): Promise<SchemaCheckResult<T>>;
  checkForField<K extends keyof T>(fieldName: K, fieldValue: T[K], data?: T): FieldSchemaCheckResult;
  checkForFieldAsync<K extends keyof T>(fieldName: K, fieldValue: T[K], data?: T): Promise<FieldSchemaCheckResult>;
  getFieldType<K extends keyof T>(fieldName: K): IType<T[K], T>;
  getKeys(): Array<PropertyKey>;
}

export interface ISchemaModelFactory {
  <T>(schema: ISchema<Partial<T>>): ISchemaModel<T>;
  combine<T>(...schemas: Array<ISchemaModel<Partial<T>>>): ISchemaModel<T>;
}

export declare const SchemaModel: ISchemaModelFactory;
export declare function StringType<T = any>(errorMessage?: string): IStringType<T>;
export declare function NumberType<T = any>(errorMessage?: string): INumberType<T>;
export declare function BooleanType<T = any>(errorMessage?: string): IBooleanType<T>;
export declare function ArrayType<I = any, T = any>(errorMessage?: string): IArrayType<I, T>;
export declare function DateType<T = any>(errorMessage?: string): IDateType<T>;
export declare function ObjectType<O = any, T = any>(errorMessage?: string): IObjectType<O, T>;
export declare class Schema<T = any> {
  constructor(schema: ISchema<Partial<T>>);
  check(data: T): SchemaCheckResult<T>;
  checkAsync(data: T): Promise<SchemaCheckResult<T>>;
  checkForField<K extends keyof T>(fieldName: K, fieldValue: T[K], data?: T): FieldSchemaCheckResult;
  checkForFieldAsync<K extends keyof T>(fieldName: K, fieldValue: T[K], data?: T): Promise<FieldSchemaCheckResult>;
  schema: ISchema<T>;
  getFieldType<K extends keyof T>(fieldName: K): IType<T[K], T>;
  getKeys(): Array<PropertyKey>;
}

@simonguo
Copy link
Member

schema-typed should indeed have a separate typescript type definition.
If you like, I hope you can submit a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants