Last active
November 16, 2020 09:49
-
-
Save Nikamura/81b84673677bacf70f80b3b6a093de5c to your computer and use it in GitHub Desktop.
Fluent builder pattern in TypeScript with type support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Types Except & SetRequired from https://github.com/sindresorhus/type-fest | |
type Except<ObjectType, KeysType extends keyof ObjectType> = Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>; | |
type SetRequired<BaseType, Keys extends keyof BaseType = keyof BaseType> = Except<BaseType, Keys> & | |
Required<Pick<BaseType, Keys>> extends infer InferredType | |
? { [KeyType in keyof InferredType]: InferredType[KeyType] } | |
: never; | |
class ChainableBuilder<T, X> { | |
constructor(public args: T, public x: new (...args: any[]) => X) {} | |
public set<K extends keyof T>( | |
key: K, | |
value: string, | |
): SetRequired<T, K> extends Required<T> | |
? ChainableBuilder<SetRequired<T, K>, X> | |
: Omit<ChainableBuilder<SetRequired<T, K>, X>, "build"> { | |
return new this.constructor({ ...this.args, [key]: value }, this.x); | |
} | |
public build(): X { | |
return new this.x(this.args); | |
} | |
} | |
class User { | |
public email: string; | |
private _password: string; | |
constructor(args: Required<IUserBuilderOpts>) { | |
this.email = args.email; | |
this._password = args.password; | |
} | |
} | |
interface IUserBuilderOpts { | |
email?: string; | |
password?: string; | |
} | |
const builder = new ChainableBuilder<IUserBuilderOpts, User>({}, User); | |
builder.set("email", "value").build(); // Property 'build' does not exist on type 'Pick<ChainableBuilder<{ password?: string | undefined; email: string; }, User>, "args" | "x" | "set">'.ts(2339) | |
builder.set("email", "value").set("email", "value").build(); // Property 'build' does not exist on type 'Pick<ChainableBuilder<{ password?: string | undefined; email: string; }, User>, "args" | "x" | "set">'.ts(2339) | |
builder.set("email", "value").set("password", "value").build(); // works | |
builder.build(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment