class-constructor v0.2.3
Introduction
This library is designed to help you build classes with optional properties using the builder pattern. It discovers class fields and creates optimized builder that works faster than implicit builder implementation.
According to benchmarks, the generated builder is up to 25% faster than traditional builder implementations.
Navigation
Installation
npm install class-constructor
or
yarn add class-constructor
or
pnpm add class-constructor
Important!
Make following changes in your tsconfig.json
:
{
"compilerOptions": {
// set useDefineForClassFields to true for correct class fields initialization
// this option will set undefined to uninitialized fields that will allow builder to track them
"useDefineForClassFields": true,
// or target is ES2022 or higher
"target": "ES2022",
// note: that is highly recommended to set target to ES2022 or higher
}
}
That will enable usage of upcoming standard version of class fields, that is necessary for builder to index fields.
Compatibility
Latest version of the package gets rid of ES6 Proxy.
It not only significantly improved performance, but also made the library compatible with older environments (just make sure you have useDefineForClassFields
set to true
in your tsconfig.json
). For target lower than ES2022, there may be issues with performance, but that was not proved yet.
Builder pattern
Builder pattern is a design pattern that allows you to build complex objects step by step. Example:
// for example, we have a Button class
// it has a required property 'text' and optional properties 'color' and 'size' and 10 more ...
// if all those properties would be accepted through constructor, it would be a mess
// we should have remember an order of arguments, and it would be hard to read and maintain
new Button('Click me', undefined, undefined, 'red');
// instead, we can use builder pattern
// it allows us to build an object step by step
Button.builder('Click me').withColor('red').build();
But, the builder pattern is not easy to implement, and it requires a lot of boilerplate code.
Examples
class-constructor
library allows you to create builders with just few lines of code.
Basic usage
import { toBuilderMethod } from 'class-constructor';
class Button {
public text: string;
public color?: string;
public size?: string;
constructor(text: string) {
this.text = text;
}
public static builder = toBuilderMethod(Button).classAsOptionals();
}
// and here we go
const buttonBuilder = Button.builder('Click me');
buttonBuilder.color('red');
console.log(buttonBuilder.color()); // 'red'
buttonBuilder.build();
// ^ class Button { text: 'Click me', color: 'red', size: undefined }
Default values
You can set default values for optional properties:
class Button {
public text: string;
public color?: string = 'red';
public size?: string = 'small';
constructor(text: string) {
this.text = text;
}
public static builder = toBuilderMethod(Button).classAsOptionals();
}
Button.builder('Click me').color('blue').build();
// ^ class Button { text: 'Click me', color: 'blue', size: 'small' }
Private properties
If you have private properties in your class, you still can build class with them. But to do so, you will need to create an interface to let TypeScript know about them.
interface ButtonOptionals {
color?: string; // note that we don't have _ prefix here
size?: string;
}
class Button {
private _text: string;
// we prefix private properties with "_" even if builder option interface doesn't have them
// class builder will find them and be able to set them
private _color?: string = 'red';
private _size?: string = 'small';
constructor(text: string) {
this._text = text;
}
public static builder = toBuilderMethod(Button).withOptionals<ButtonOptionals>();
// ... getters and setters ...
}
Button.builder('Click me').color('blue').build();
// ^ class Button { _text: 'Click me', _color: 'blue', _size: 'small' }
Property accessors
Accessor priority
interface WithPrivateAndPublicPropertyOptionals {
name: string;
}
class WithPrivateAndPublicProperty {
private _name?: string;
// property with full match will be preferred over private property
public name?: string;
static builder = toBuilderMethod(WithPrivateAndPublicProperty).withOptionals<WithPrivateAndPublicPropertyOptionals>();
}
WithPrivateAndPublicProperty.builder().name('Jake').build();
// ^ class WithPrivateAndPublicProperty { _name: undefined, name: 'Jake' }
Getters and setters
Getters and setters will be ignored by default.
class WithGetterAndSetter {
private _name?: string;
public get name() {
console.log('getter');
return this._name;
}
public set name(value: string) {
console.log('setter', value);
this._name = value;
}
public static builder = toBuilderMethod(WithGetterAndSetter).classAsOptionals();
}
const builder = WithGetterAndSetter.builder();
builder.name('Jake');
// no console.log called
console.log(builder.name()); // 'Jake'
// no console.log called
builder.build();
// ^ class WithGetterAndSetter { _name: 'Jake' }
To enable getters and setters, you can use BuilderAccessors
decorator.
class WithGetterAndSetter {
@BuilderAccessors((target) => target.name, (target, value) => (target._name = value))
private _name?: string;
public get name() {
return this._name;
}
public set name(value: string) {
console.log('setter', value);
this._name = value;
}
public static builder = toBuilderMethod(WithGetterAndSetter).classAsOptionals();
}
const builder = WithGetterAndSetter.builder();
builder.name('Jake');
// console.log called: > setter
console.log(builder.name()); // 'Jake'
// console.log called: > getter
builder.build();
// ^ class WithGetterAndSetter { _name: 'Jake' }