npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@autometa/dto-builder

v0.13.11

Published

Define DTOs and Entities and automatically create a builder class.

Downloads

944

Readme

DTO and Builder Pattern

Full documentation This library allows defining DTO classes with decoratated properties. You an then automatically create a new builder class that incrementally assigns the value of the DTO, and returns the built result.

:::caution This library requires experimental decorators and a reflect polyfill like reflect-metadata :::

Installation

    npm i -D @autometa/dto-builder
    yarn add -D @autometa/dto-builder
    pnpm add -D @autometa/dto-builder

Use

Creating a DTO

To create a DTO, simply create a class which matches the data type your representing. Then, decorate its properties with the @Property decorator.

import { Property } from "@autometa/dto-builder";

export class UserDto {
  @Property
  id: number;
  @Property
  name: string;
  @Property
  age: number;
}

Creating a builder

Now that we have a DTO, we can make a builder for it. Simply pass your DTO class prototype to the Builder function. It will return a new class whos interface matches the DTO, but with functions accepting a value instead of raw properties.

The builder functions are compile-time type safe but do no run time validation.

If the class validator package is installed, the DTO will be validated on build. This can be bypassed by passing false to the build method

import { Builder } from "@autometa/dto-builder";
import { UserDto } from "./user-dto";

class UserBuilder extends Builder(UserDto);

const userBuilder = new UserBuilder();
userBuilder.id(1).name("bob").age(23);
// methods are type safe
// -------------
// error       |
//             V
userBuilder.id("1").name("bob").age(23);

// Bypass
userBuilder
  .id("1" as unknown as number)
  .name("bob")
  .age(23);

You can also pass in an already existing DTO and build it further.

const cachedUser = new UserDto();
const userBuilder = new UserBuilder(cachedUser);

Default Values

You can pass a value into the Property decorator to provide a default value. The default value will be injected by the Builder class.

import { DTO } from "@autometa/dto-builder";

export class UserDto {
  @DTO.value(100)
  id: number;
  @DTO.value("paul")
  name: string;
  @DTO.value(20)
  age: number;
}

const user = new UserBuilder().build();

console.log(user.id === 100); // true
console.log(user.name === "paul"); // true
console.log(user.age === 20); // true

Factories can also be used:

import { Property } from "@autometa/dto-builder";

export class UserDto {
  @DTO.factory(() => Math.random())
  id: number;
  @DTO.factory(() => "paul")
  name: string;
  @DTO.factory(() => 20)
  age: number;
}

Note: factories must by synchronous.

Nesting DTOs

For complex classes with nested classes or objects it is advisable to use a type or interface rather than a Dto type.

// prefer not
class InnerDto {
  value: number;
}

class OuterDto {
  @DTO.dto(InnerDto)
  inner: InnerDto;
}

// prefer
interface Inner {
  value: number;
}

class InnerDto implements Inner {
  value: number;
}

class OuterDto {
  @DTO.dto(InnerDto)
  inner: Inner;
}

To make a DTO available for nesting, pass its prototype to the Property decorator as its default value. Note, this is the prototype, not an instance.


// prefer
interface Inner {
    value: number
}

class InnerDto {
  @DTO.value(1)
  value: number;
}

class OuterDto {
  @DTO.value(InnerDto)
  inner: Inner;
}

const Outer = new OuterBuilder().build()
console.log(outer.inner instanceOf InnerDto); // true
console.log(outer.innerr.value === 1); // true

You can also create a unique dto with default values by calling the static default method on your builder

const Outer = OuterBuilder.default();

For many tests, valid default values may be all you need on your dto. If you wish to make further edits you can pass the instance to a builder later

new OuterBuilder(Outer).inner(new InnerBuilder().value(1).build());

Note that this will mutate the original dto. You do not need to reassign it or even build it.

Dates

The date decorator will create a new date object for that property when the builder is instantiated. If a unix timestamp or parseable string is passed, it will be used to create the date.

import { DTO } from "@autometa/dto-builder";

export class UserDto {
  @DTO.date
  createdAt: Date;
}

// with unix timestamp

export class UserDto {
  @DTO.date(1620000000000)
  createdAt: Date;
}

// with string

export class UserDto {
  @DTO.date("2021-05-02T00:00:00.000Z")
  createdAt: Date;
}

Interfaces - reducing duplication

If you define your types initially as interfaces, or generate interfaces from validation libraries like zod and myzod, you can reduce duplication by extending the DTO function with an interface.

import { DTO } from "@autometa/dto-builder";

interface IUser {
  id: number;
  name: string;
  age: number;
}

export class UserDto extends DTO<IUser> {}

const user = new UserBuilder().id(0).name("bob").build();

DTO From Raw Object

Sometimes it's necessary to convert a raw object into a DTO. This can be achieved by calling fromRaw on the Builder class, passing it the raw object.

const raw = { Outer: { inner: { value: 1 } } };
const dto = OuterBuilder.fromRaw(raw);

expect(dto).toBeInstanceOf(Outer);

Interfaces - Anonymous Object Builders

It might not be desirable to build your object as a class. When not used to extend a class, the DTO function will return an anonymous object builder, with the same interface as the class builder.

import { Builder } from "@autometa/dto-builder";

interface IUser {
  id: number;
  name: string;
  age: number;
}

const UserBuilder = Builder<IUser>();

Deriving a builder and default values

Since anonymous objects cannot be decorated, they cannot accept default values or factories which might change between instantiations.

To work around this, an anonymous builder is derivable. Any values assigned to the builder will stay until the builder is built. However when the derive method is called, a new builder will be created, copying the values from the original. If those values are set agin in the derived builder, they will not affect the original.

const bobBuilder = new UserBuilder().id(1).name("bob").age(23);

const olderBobBuilder = bobBuilder.derive().age(24);