ts-roids

ts-roids

100+ types and decorators to bullet proof TypeScript even more.

CI @latest npm downloads Socket Badge


Installation

npm

npm i ts-roids

pnpm

pnpm i ts-roids

If you're only using types, you can install it as a devDependency. And if you're using decorators, set this.

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

Requires TypesScript v5.0+

Documentation

Checkout the full API reference for all usage examples with details.

Types

Decorators

Basic Usage

Finalize and freeze objects

import type { Optional, NewType, MaybeUndefined } from 'ts-roids';
import { Final, Frozen, Singleton } from 'ts-roids';

type Bar = NewType<'Bar', string>;
type Baz = NewType<'Baz', string>;
type Secret = NewType<'Secret', string>;

abstract class BaseFoo<T> {
public abstract requestFoo(secret: Secret, baz: Baz): Promise<Optional<T>>;
}

@Final
@Frozen
@Singleton
class Foo<T> extends BaseFoo<T> {
private static readonly rnd = Math.random();
private readonly foo: T;
public bar: Optional<Bar>; // `Bar` then becomes readonly with the decorator

public constructor(foo: T, bar?: MaybeUndefined<Bar>) {
super();
this.foo = foo;
this.bar = bar ?? null;
}

public override async requestFoo(
secret: Secret,
baz: Baz
): Promise<Optional<T>> {
if (
Foo.rnd > 0.5 &&
secret.concat().toLowerCase() === '123' &&
baz.concat().toLowerCase() === 'baz' &&
this.bar !== null
) {
return await Promise.resolve(this.foo);
}

return null;
}
}

class SubFoo extends Foo<string> {
constructor(foo: string) {
super(foo);
}
}

// No problem with instantiation
const foo = new Foo('foo');

// The Singleton ensures the same instance is returned
const foo2 = new Foo('bar');
console.log(foo2 === foo); // True

// Since the object is final:
// The line below will cause a TypeError: Cannot inherit from the final class Foo
new SubFoo('subFoo');

// Since the object is frozen:
// The line below will cause a TypeError: Cannot add property 'requestFoo', object is not extensible
foo.requestFoo = async () => {
return await Promise.resolve('not foo');
};

// The line below will cause a TypeError: Cannot assign to read only property 'bar'
foo.bar = 'not bar' as Bar;

The TypeScript team has not yet introduced a built-in final modifier yet, check this, this and many other requests. Although they introduced override in v4.3 .

Decorators like @Final provide a limited way to emulate final behavior, these are merely band-aids for now, until TS officially supports a true final modifier.

You can also seal an object btw.

@Sealed
class Person {
constructor(name: string, age?: number) {}
}

const john = new Person('John', 30);

// Existing properties can still be modified
john.age = 31; // No Errors

// Existing properties cannot be re-configured nor deleted

(john as any).email = 'john@doe.com'; // TypeError: Cannot add property email,
// object is not extensible

delete john.age; // TypeError: Cannot delete property 'age'

Changelog

See releases.

License

GPL-3