Last active
February 26, 2021 23:56
-
-
Save ChristianIvicevic/6f4b919b2edfe88c0eca515e5a4ad11b to your computer and use it in GitHub Desktop.
This file contains 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
type Class<T = unknown, Arguments extends unknown[] = unknown[]> = new ( | |
...args: Arguments | |
) => T; | |
const nameof = <T>(name: keyof T) => name; | |
type Builder<T> = { | |
[K in keyof Omit< | |
T, | |
{ | |
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never; | |
}[keyof T] | |
>]-?: (value: T[K]) => Builder<T>; | |
} & { | |
build(): T; | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// FUNCTIONAL BUILDER | |
/////////////////////////////////////////////////////////////////////////////// | |
export const createBuilder = <T>(Constructor?: Class<T>) => { | |
const draft: Record<string, unknown> = {}; | |
const builder = new Proxy( | |
{} as Builder<T>, | |
{ | |
get(_target, property) { | |
if (property === nameof<Builder<unknown>>('build')) { | |
if (Constructor === undefined) { | |
return () => ({ ...draft }); | |
} | |
return () => Object.assign(new Constructor(), { ...draft }); | |
} | |
return (value: unknown): unknown => { | |
draft[property as string] = value; | |
return builder; | |
}; | |
}, | |
}, | |
); | |
return builder; | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// SAMPLE TYPE AND CLASS | |
/////////////////////////////////////////////////////////////////////////////// | |
type SampleType = { | |
value1: number; | |
value2: string; | |
value3: boolean; | |
}; | |
class SampleClass { | |
public publicNumber?: number; | |
protected protectedString?: string; | |
private privateBoolean?: boolean; | |
public stillWorks() { | |
console.log( | |
`${this.publicNumber} ${this.protectedString} ${this.privateBoolean}`, | |
); | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// FUNCTIONAL BUILDER SAMPLE | |
/////////////////////////////////////////////////////////////////////////////// | |
const objectFromType = createBuilder<SampleType>() | |
.value1(1) | |
.value2('2') | |
.value3(false) | |
.build(); | |
console.log(JSON.stringify(objectFromType)); | |
const objectFromClass = createBuilder(SampleClass).publicNumber(1).build(); | |
objectFromClass.stillWorks(); | |
const objectFromClass2 = createBuilder(SampleClass) | |
.publicNumber(1) | |
// Property 'protectedString' does not exist on type 'Builder<SampleClass>'. | |
.protectedString('2') | |
// Property 'privateBoolean' does not exist on type 'Builder<SampleClass>'. | |
.privateBoolean(false) | |
.build(); | |
/////////////////////////////////////////////////////////////////////////////// | |
// BUILDER MIXIN | |
/////////////////////////////////////////////////////////////////////////////// | |
export const WithBuilder = < | |
T extends new (...args: any[]) => any, | |
R = T extends new (...args: unknown[]) => infer R ? R : never | |
>( | |
Constructor: T, | |
) => | |
class extends Constructor { | |
public static builder() { | |
return createBuilder<R>(Constructor); | |
} | |
}; | |
const ClassWithBuilderMixin = WithBuilder( | |
class { | |
public value1?: number; | |
public value2?: string; | |
public value3?: boolean; | |
}, | |
); | |
const newObject = ClassWithBuilderMixin.builder() | |
.value1(1) | |
.value2('2') | |
.value3(false) | |
.build(); | |
console.log(JSON.stringify(newObject)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment