0.1.7 • Published 9 months ago

tsto v0.1.7

Weekly downloads
-
License
MIT
Repository
-
Last release
9 months ago

tsto - Typescript Transfer Objects

Build status

Latest NPM version

Before using the library

Make sure you remember to add the following to your tsconfig.json file in order for decorators to work:

{
  "compilerOptions": {
    // ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
    // ...
  }
}

What is this even?

When you're working with Typescript, sometimes you want to be able to take input in a non-typesafe way, eg. when working with dynamic objects at runtime. The use case I've encountered was api endpoints, where I was tired of using plain types, and not having any control when using the input sent through the endpoint. In this case I would like to be able to fail early if the supplied json will not fit into a class of my choosing.

You could solve this by creating custom mappers, but because we're using Typescript, decorators is the obvious choice. By using decorators we only need to create a class and annotate the properties properly. This will probably make the most sense at the edges of you application, where you have some interface to the outside world.

Example

Consider the following example (I know, there's a lot going on, but bear with me):

// test.dto.ts
import {
  tsto,
  tstoArray,
  tstoFrom,
  tstoNumber,
  tstoObject,
  tstoString,
  TstoSubArrayElementType,
} from 'tsto';

enum TestEnum {
  FirstOption = 1,
  SecondOption = 2,
}

@tsto()
export class GrandChild {
  constructor(
    @tstoEnum(TestEnum) public testEnum: TestEnum,
    @tstoEnum(TestEnum, { useStringsAsInput: true })
    public anotherTestEnum: TestEnum,
  ) {}
}

@tsto()
export class ChildObject {
  constructor(
    @tstoNumber() public anotherTestNumber: number,
    @tstoString() public anotherTestString: string,
    @tstoString() public yetAnotherTestString: string,
    @tstoObject(GrandChild) public grandChild: GrandChild,
  ) {}
}

@tsto()
export class TestDto {
  @tstoString()
  testString!: string;

  @tstoNumber()
  testNumber!: number;

  @tstoObject(ChildObject)
  testObject!: ChildObject;

  @tstoArray(ChildObject)
  testArray!: ChildObject[];

  @tstoArray('string')
  testStringArray!: string[];

  @tstoArray('number')
  testNumberArray!: number[];

  @tstoArray([
    'number',
    'string',
    TstoSubArrayElementType.create(['string', 'number', ChildObject]),
  ])
  testMultiDimensionalArray!: [number, string, [string, number, ChildObject]];

  constructor(@tstoString() public anotherTestString: string) {}
}

// my-controller.ts
import { tstoFrom } from 'tsto';

class MyController {
  @get()
  get(body: TestDto) {
    // ^^^^^^^^^^^^ Here body is not yet actually the TestDto class, it's just syntactic sugar.
    // That's why we parse it through tsto, so that we get a proper instance of TestDto to work with.
    const testDto = tstoFrom(body, TestDto);
    // ^^^^^^^^^^^^ Here we have a proper instance of TestDto.
    // ...
  }
}

Alright! Now that you've studied the example, let's take a look at all the different decorators.

DecoratorShort explanation
@tsto()Simple class decorator that indicates that it's a tsto object.
@tstoEnum(EnumType, options?)Indicates the property is an enum
@tstoNumber(options?)Indicates the property is a number.
@tstoString(options?)Indicates the property is a string.
@tstoObject(MyObjectType, options?)Indicates the property is of an object type.
@tstoArray(arrayTypeDefinition, options?)The most complex type of them all, tries to model arrays types.

Options

Each decorator takes an options object, that at the moment is quite barebones:

{
  nullable?: boolean;
  undefineable?: boolean;
}

Enums

For enums there's an extra option useStringsAsInput. Sometimes you will get strings as input for enums instead of integers, this will handle that.

That means options for @tstoEnum is:

{
  nullable?: boolean;
  undefineable?: boolean;
  useStringsAsInput?: boolean;
}

Arrays

At the moment the library fully supports primitive arrays of string and number types. When using arrays you will be able to make an arrayTypeDefinition, it's basically an array of what to expect. This comes with some rather big caveats when mixing types.

The simple usage is string, number or object based arrays:

@tstoArray('string')
@tstoArray('number')
@tstoArray(MyObjectType)

That's fairly straight forward. But once you start mixing in different types aka. tuples for instance, it gets really complicated. But it can be defined like this:

// Simple tuple
@tstoArray(['string', MyObjectType])

At the moment the library will not expect in specific order, and thus will try to match whatever is in the array to both types. That also means that only a single object type is supported at the moment.

0.1.7

9 months ago

0.1.6

9 months ago

0.1.4

9 months ago

0.1.3

9 months ago

0.1.2

9 months ago

0.1.1

9 months ago

0.1.0

9 months ago

0.0.1

9 months ago