This library bridges the gap between TC39 Proposal Decorators (Stage 3) and TypeScript Experimental Decorators, allowing you to write decorators that work in both environments.
universal-decorator 
This library bridges the gap between TC39 Proposal Decorators (Stage 3) and TypeScript Experimental Decorators, allowing you to write decorators that work in both environments.
It provides a compatibility layer for:
- Unified Decorator Definition: Define a single decorator that handles both standard and experimental behaviors.
- Metadata Reflection: A
reflect-metadatacompatible API (getMetadata,hasMetadata,getMetadataKeys) that works with the new metadata proposal. - Metadata Inheritance: seamless inheritance of metadata values, complying with the proposal spec.
Usage
defineDecorator
defineDecorator allows you to define the implementation for both ECMAScript (Standard) decorators and TypeScript
Experimental decorators in one place.
import { defineDecorator } from "@denostack/universal-decorator"; const myDecorator = defineDecorator({ // Implementation for TC39 Stage 3 Decorators ecma(target, ctx) { console.log(`[ECMA] Decorating ${ctx.kind}: ${String(ctx.name)}`); // Logic for standard decorators... }, // Implementation for TypeScript Experimental Decorators tsExperimental(target, property, descriptor) { console.log(`[TS] Decorating ${property ? String(property) : "class"}`); // Logic for experimental decorators... }, }); // Usage @myDecorator class MyClass { @myDecorator method() {} }
Metadata Reflection
This library provides a metadata API inspired by reflect-metadata, but adapted for the modern decorator context
structure.
The functions getMetadata, hasMetadata, getMetadataKeys, getOwnMetadata, hasOwnMetadata, and
getOwnMetadataKeys follow the usage pattern of reflect-metadata. However, when specifying a property, they expect an
object structure { name: string | symbol, static: boolean } which aligns with the context object in the TC39
decorator proposal.
import { defineMetadataDecorator, getMetadata } from "@denostack/universal-decorator"; // Define metadata @defineMetadataDecorator("role", "admin") class User { @defineMetadataDecorator("permissions", ["read", "write"]) static defaultPermissions: string[]; @defineMetadataDecorator("visible", true) getName() {} } // Retrieve metadata // Class metadata const classRole = getMetadata("role", User); // "admin" // Static property metadata // Note: Property identifier is an object { name, static } const staticPerms = getMetadata("permissions", User, { name: "defaultPermissions", static: true }); // ["read", "write"] // Instance property metadata const methodVisible = getMetadata("visible", User, { name: "getName", static: false }); // true
defineMetadataDecorator and Inheritance
defineMetadataDecorator creates a decorator specifically for attaching metadata. It supports metadata inheritance,
meaning metadata defined on a parent class is accessible to child classes unless overridden.
You can also inherit and extend existing metadata values using a callback function: (previousValue) => newValue.
import { defineMetadataDecorator, getMetadata, getOwnMetadata, hasOwnMetadata } from "@denostack/universal-decorator"; // 1. Simple Definition @defineMetadataDecorator("custom:tag", "v1") class Parent {} // 2. Inheritance & Extension // The callback receives the metadata value from the parent class (if any) @defineMetadataDecorator("custom:tag", (parentValue) => `${parentValue} -> v2`) class Child extends Parent {} console.log(getMetadata("custom:tag", Parent)); // "v1" console.log(getMetadata("custom:tag", Child)); // "v1 -> v2" // You can use `hasOwnMetadata` and `getOwnMetadata` to check for metadata directly defined // on a class, rather than inherited metadata. @defineMetadataDecorator("custom:attr", "only-child") class GrandChild extends Child {} console.log(getOwnMetadata("custom:tag", Parent)); // "v1" console.log(getOwnMetadata("custom:tag", Child)); // "v1 -> v2" console.log(getOwnMetadata("custom:tag", GrandChild)); // undefined (not directly defined on GrandChild) console.log(hasOwnMetadata("custom:attr", GrandChild)); // true console.log(getOwnMetadata("custom:attr", GrandChild)); // "only-child"
This inheritance mechanism follows the behavior specified in the Decorator Metadata proposal, ensuring that metadata flows down the prototype chain correctly.
Comparison with reflect-metadata
While reflect-metadata uses (metadataKey, target, propertyKey):
- reflect-metadata:
Reflect.getMetadata("key", Target.prototype, "methodName") - universal-decorator:
getMetadata("key", Target, { name: "methodName", static: false })
This library consciously adopts the { name, static } structure to be forwards-compatible with the context object
provided by standard decorators. Metadata resolution follows the prototype chain (inheritance) by default, as per the
proposal, but you can also access own metadata using getOwnMetadata.
References
Add Package
deno add jsr:@denostack/universal-decorator
Import symbol
import * as universal_decorator from "@denostack/universal-decorator";
Import directly with a jsr specifier
import * as universal_decorator from "jsr:@denostack/universal-decorator";
Add Package
pnpm i jsr:@denostack/universal-decorator
pnpm dlx jsr add @denostack/universal-decorator
Import symbol
import * as universal_decorator from "@denostack/universal-decorator";
Add Package
yarn add jsr:@denostack/universal-decorator
yarn dlx jsr add @denostack/universal-decorator
Import symbol
import * as universal_decorator from "@denostack/universal-decorator";
Add Package
vlt install jsr:@denostack/universal-decorator
Import symbol
import * as universal_decorator from "@denostack/universal-decorator";
Add Package
npx jsr add @denostack/universal-decorator
Import symbol
import * as universal_decorator from "@denostack/universal-decorator";
Add Package
bunx jsr add @denostack/universal-decorator
Import symbol
import * as universal_decorator from "@denostack/universal-decorator";