Skip to content

xseman/decorator-toolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

decorator-toolkit

Modern TC39 decorators for reducing repetitive code in TypeScript.

FeaturesInstallationDocumentationUsageAvailable Decorators

Features

  • Built for modern TC39 decorators in TypeScript 5+
  • Covers sync and async method workflows
  • Includes caching, retry, timeout, debounce, throttling, delegation, rate limiting, lazy evaluation, and resource disposal

Installation

npm

$ npm install decorator-toolkit

# or using Bun
$ bun add decorator-toolkit

Usage

This package targets the standard TC39 decorator model. It is intended for TypeScript 5+ projects using standard decorators rather than legacy experimentalDecorators semantics.

Legacy TypeScript decorators are also available for projects that still use the older transform. Import them from decorator-toolkit/legacy or decorator-toolkit/<name>/legacy.

Compiler Setup

At minimum, use a modern TypeScript configuration that emits native class features and supports standard decorators:

{
	"compilerOptions": {
		"target": "ES2022",
		"module": "Node16",
		"moduleResolution": "Node16"
	}
}

Note

Method decorators in this package apply to methods only, bindAll applies to classes, and accessor decorators apply to accessor members only. Private members are not supported.

Tip

Decorators that use default behavior can be written as @decorator or @decorator(). This applies to bind, bindAll, cancelPrevious, cache, cacheAsync, delegate, dispose, execTime, lazy, readonly, and throttleAsync.

Basic Example

import {
	after,
	before,
	retry,
	timeout,
} from "decorator-toolkit";

class PaymentService {
	private readonly events: string[] = [];

	beforeSave(): void {
		this.events.push("before");
	}

	afterSave(params: { args: [string]; response: string; }): void {
		this.events.push(`after:${params.args[0]}:${params.response}`);
	}

	@before<PaymentService>({ func: "beforeSave" })
	@after<PaymentService, string, [string]>({ func: "afterSave", wait: true })
	@retry(3)
	@timeout(1_000)
	async save(id: string): Promise<string> {
		return `saved:${id}`;
	}
}

Caching And Rate Limiting

import {
	cache,
	rateLimit,
} from "decorator-toolkit";

class DirectoryService {
	@cache({ ttlMs: 5_000 })
	lookupUser(id: string): { id: string; name: string; } {
		return { id, name: `user:${id}` };
	}

	@rateLimit<DirectoryService, [string]>({
		allowedCalls: 10,
		timeSpanMs: 60_000,
		keyResolver: (userId) => userId,
	})
	openProfile(userId: string): string {
		return `/users/${userId}`;
	}
}

Accessor Decorators

readonly and refreshable are accessor decorators, so they must decorate accessor members. lazy decorates get accessors directly.

import {
	lazy,
	readonly,
	refreshable,
} from "decorator-toolkit";

class SessionStore {
	@readonly
	accessor id = crypto.randomUUID();

	@lazy
	get config(): object {
		return buildExpensiveConfig(); // computed once per instance
	}

	@refreshable<SessionStore, number>({
		dataProvider: "loadCounter",
		intervalMs: 5_000,
	})
	accessor counter: number | null = 0;

	async loadCounter(): Promise<number> {
		return Date.now();
	}
}

const store = new SessionStore();
store.counter = null;

Assigning null to a refreshable accessor stops future refresh cycles for that accessor.

Root And Subpath Imports

You can import from the root package:

import {
	delegate,
	timeout,
} from "decorator-toolkit";

Or import specific modules via subpaths:

import { cache } from "decorator-toolkit/cache";
import {
	timeout,
	TimeoutError,
} from "decorator-toolkit/timeout";

Legacy TypeScript decorators are available from the existing suffix subpaths, and decorator-toolkit/legacy re-exports the full legacy surface:

import { cache as legacyCache } from "decorator-toolkit/cache/legacy";
import {
	cache,
	timeout,
} from "decorator-toolkit/legacy";

Use the suffix path when you want one decorator only. Use the legacy barrel when you want several legacy decorators from a single import.

Documentation

Start with docs/README.md for grouped references and usage patterns. The decorator list below links to dedicated pages with current TC39 examples adapted from the legacy site.

Available Decorators

Decorator Purpose
after Runs a hook after a method call, optionally waiting for async resolution
before Runs a hook before a method call, optionally waiting for async hooks
bind Binds a method to its instance or class during initialization
bindAll Binds all public instance methods declared on a class
cancelPrevious Rejects the previous pending async invocation with CanceledPromise
debounce Coalesces rapid method calls into a later single execution
delegate Shares one in-flight async invocation across callers with the same key
delay Schedules method execution after a fixed delay
dispose Wires a method to Symbol.dispose or Symbol.asyncDispose
execTime Reports method execution duration
cache Caches synchronous method results
cacheAsync Caches async results and deduplicates pending async calls
lazy Computes a getter once per instance and caches the result
multiDispatch Starts multiple async attempts and resolves on the first success
onError Forwards thrown or rejected errors to a handler
rateLimit Limits how many calls may happen within a configured time window
readonly Makes an accessor write-protected
refreshable Refreshes an accessor from an async data provider on an interval
retry Retries async methods using a fixed or custom delay strategy
throttle Limits how often a method can run
throttleAsync Queues async calls and executes them with bounded concurrency
timeout Rejects slow async methods with TimeoutError

About

Modern TC39 decorators for common TypeScript patterns

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors