0.2.3 • Published 6 months ago

class-constructor v0.2.3

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

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' }
0.2.3

6 months ago

0.2.1

7 months ago

0.2.2

7 months ago

0.1.0

7 months ago

0.2.0

7 months ago

0.0.1

8 months ago