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

tsto

v0.1.7

Published

Typescript Transfer Objects - library for seamlessly transform loosly typed input into strongly typed output.

Downloads

9

Readme

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.

| Decorator | Short 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.