install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/ts-decorator-patterns" ~/.claude/skills/intense-visions-harness-engineering-ts-decorator-patterns && rm -rf "$T"
manifest:
agents/skills/claude-code/ts-decorator-patterns/SKILL.mdsource content
TypeScript Decorator Patterns
Implement class, method, and property decorators with reflect-metadata in TypeScript
When to Use
- Adding cross-cutting concerns (logging, validation, caching) to classes and methods
- Working with frameworks that use decorators (NestJS, TypeORM, Angular)
- Implementing dependency injection with metadata reflection
- Creating reusable, declarative annotations for class members
Instructions
- Enable decorators in
:tsconfig.json
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
- Class decorator — wraps or replaces the class constructor:
function Sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @Sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } }
- Method decorator — intercepts method calls:
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyKey} with`, args); const result = original.apply(this, args); console.log(`${propertyKey} returned`, result); return result; }; } class Calculator { @Log add(a: number, b: number): number { return a + b; } }
- Property decorator:
function MinLength(min: number) { return function (target: any, propertyKey: string) { let value: string; Object.defineProperty(target, propertyKey, { get: () => value, set: (newValue: string) => { if (newValue.length < min) throw new Error(`${propertyKey} must be at least ${min} chars`); value = newValue; }, }); }; } class User { @MinLength(3) name: string = ''; }
- Parameter decorator — marks parameters for injection or validation:
function Inject(token: string) { return function (target: any, propertyKey: string | undefined, parameterIndex: number) { const existingInjections = Reflect.getOwnMetadata('injections', target) || []; existingInjections.push({ index: parameterIndex, token }); Reflect.defineMetadata('injections', existingInjections, target); }; }
- Decorator factory — return a decorator from a function to accept configuration:
function Retry(attempts: number, delay: number) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = async function (...args: any[]) { for (let i = 0; i < attempts; i++) { try { return await original.apply(this, args); } catch (e) { if (i === attempts - 1) throw e; await new Promise((r) => setTimeout(r, delay)); } } }; }; } class ApiClient { @Retry(3, 1000) async fetchData(): Promise<Data> { /* ... */ } }
- Decorator composition — decorators apply bottom-up:
class Service { @Log // Applied second (outer) @Retry(3, 100) // Applied first (inner) async getData(): Promise<Data> { /* ... */ } }
Details
TypeScript decorators are an implementation of the TC39 Stage 3 decorators proposal (legacy version). They execute at class definition time, not at runtime method invocation.
Decorator execution order:
- Parameter decorators (left to right per parameter)
- Method and property decorators (top to bottom per class member)
- Class decorators (bottom to top if multiple)
uses emitDecoratorMetadata
reflect-metadata to emit type information that decorators can read at runtime. This powers dependency injection in NestJS and TypeORM. Install reflect-metadata and import it once at the entry point.
TC39 Stage 3 decorators (2023+): TypeScript 5.0+ supports the new standard decorators syntax WITHOUT
experimentalDecorators. The new syntax is different from the legacy syntax — they use a different function signature and do not support parameter decorators or metadata emission by default.
Legacy vs Stage 3:
- Legacy:
— widely used in NestJS, Angular, TypeORMfunction Dec(target, key, descriptor) - Stage 3:
— new standard, limited framework adoption so farfunction Dec(value, context) - If you use NestJS or Angular, continue with
experimentalDecorators: true
Trade-offs:
- Decorators add powerful metaprogramming capabilities — but hide behavior behind annotations
increases bundle size with reflection metadataemitDecoratorMetadata- Decorator type safety is limited — the decorator function signature uses
for targetsany - Debuggability suffers when multiple decorators transform a method — stack traces point to the decorator wrapper, not the original method
Source
https://typescriptlang.org/docs/handbook/decorators.html
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.