Skip to content

Compatible Decorators

Goal

Create one decorator that works with both TypeScript decorator transforms.

Implementation

ts
import 'reflect-metadata';
import { Methods } from '@litert/decorator/compatible';

export const track = Methods.create({
    legacy: (ctx) => {
        Reflect.defineMetadata('tracked', ctx.name, ctx.constructor);
    },
    modern: (_method, ctx) => {
        ctx.metadata!['tracked'] = ctx.name;
    },
});

Note: The legacy callback uses reflect-metadata (Reflect.defineMetadata) to store metadata against the class constructor. The modern callback writes to ctx.metadata because modern member decorators do not receive the owning class constructor or prototype. Do not use getMetadataContainer in legacy callbacks. It is designed for reading modern decorator metadata after the class has been defined.

Dispatch model

Runtime shapeCallback used
Legacy method decorator argumentslegacy(ctx)
Modern method decorator argumentsmodern(method, ctx)

The compatible helper validates the received arguments first. If the decorator is applied to the wrong kind of element, it throws TypeError.

Example code

Compatible decorators have two layers: definitions (the implementations) and consumers (the code that applies them under each TypeScript transform). Every row below links to three files — one definition shared by both consumer builds.

Decorator kindDefinitionLegacy consumerModern consumer
ClassClass.tsClass.tsClass.ts
MethodMethod.tsMethod.tsMethod.ts
PropertyProperty.tsProperty.tsProperty.ts
AccessorAccessor.tsAccessor.tsAccessor.ts
GetterGetter.tsGetter.tsGetter.ts
SetterSetter.tsSetter.tsSetter.ts
Static methodStaticMethod.tsStaticMethod.tsStaticMethod.ts
Static propertyStaticProperty.tsStaticProperty.tsStaticProperty.ts
Static accessorStaticAccessor.tsStaticAccessor.tsStaticAccessor.ts
Static getterStaticGetter.tsStaticGetter.tsStaticGetter.ts
Static setterStaticSetter.tsStaticSetter.tsStaticSetter.ts