From harness-claude
Implements GOF Decorator pattern in TypeScript: class-based for objects (encryption, compression) and function-based for async functions (retry). Use to add stackable runtime behaviors without subclassing.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Attach additional behavior to objects at runtime by wrapping them in decorator objects.
Implements JavaScript Decorator pattern to dynamically extend functions and objects with behaviors like logging, caching, auth without source modification. Use for composing cross-cutting concerns at runtime.
Generates Decorator pattern for PHP 8.4: interface, abstract decorator, concrete decorators (logging, caching, metrics, transactional), optional factory, and unit tests. For dynamic behavior without inheritance.
Provides TypeScript patterns for Promises, async/await, async iterators, error handling, and type-safe wrappers when writing asynchronous code.
Share bugs, ideas, or general feedback.
Attach additional behavior to objects at runtime by wrapping them in decorator objects.
Classic structural decorator:
// Component interface
interface DataSource {
write(data: string): Promise<void>;
read(): Promise<string>;
}
// Concrete component
class FileDataSource implements DataSource {
constructor(private readonly path: string) {}
async write(data: string): Promise<void> {
// Write to file
console.log(`Writing to ${this.path}: ${data}`);
}
async read(): Promise<string> {
// Read from file
return `data from ${this.path}`;
}
}
// Base decorator — implements the interface and wraps a component
abstract class DataSourceDecorator implements DataSource {
constructor(protected readonly wrapped: DataSource) {}
async write(data: string): Promise<void> {
return this.wrapped.write(data);
}
async read(): Promise<string> {
return this.wrapped.read();
}
}
// Concrete decorator: encryption
class EncryptionDecorator extends DataSourceDecorator {
async write(data: string): Promise<void> {
const encrypted = Buffer.from(data).toString('base64'); // simplified
await this.wrapped.write(encrypted);
}
async read(): Promise<string> {
const data = await this.wrapped.read();
return Buffer.from(data, 'base64').toString('utf8'); // simplified
}
}
// Concrete decorator: compression
class CompressionDecorator extends DataSourceDecorator {
async write(data: string): Promise<void> {
const compressed = `[compressed:${data}]`; // simplified
await this.wrapped.write(compressed);
}
async read(): Promise<string> {
const data = await this.wrapped.read();
return data.replace(/^\[compressed:/, '').replace(/\]$/, '');
}
}
// Stack decorators in any order
const source: DataSource = new CompressionDecorator(
new EncryptionDecorator(new FileDataSource('/data/users.dat'))
);
await source.write('hello world');
Function-based decorator (idiomatic TypeScript for async functions):
type AsyncFn<T extends unknown[], R> = (...args: T) => Promise<R>;
// Retry decorator
function withRetry<T extends unknown[], R>(
fn: AsyncFn<T, R>,
maxAttempts = 3,
delayMs = 500
): AsyncFn<T, R> {
return async (...args: T): Promise<R> => {
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn(...args);
} catch (err) {
lastError = err as Error;
if (attempt < maxAttempts) {
await new Promise((r) => setTimeout(r, delayMs * attempt));
}
}
}
throw lastError!;
};
}
// Cache decorator
function withCache<T extends unknown[], R>(fn: AsyncFn<T, R>, ttlMs = 60_000): AsyncFn<T, R> {
const cache = new Map<string, { value: R; expiresAt: number }>();
return async (...args: T): Promise<R> => {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && Date.now() < cached.expiresAt) return cached.value;
const value = await fn(...args);
cache.set(key, { value, expiresAt: Date.now() + ttlMs });
return value;
};
}
// Compose decorators
const fetchUser = async (id: string): Promise<User> => {
return db.users.findOneOrFail(id);
};
const robustFetchUser = withCache(withRetry(fetchUser, 3), 30_000);
Decorator vs. Proxy: Both wrap an object. The Decorator adds behavior; the Proxy controls access. In practice the implementation is similar — the distinction is intent. Use Decorator for feature stacking (logging, caching), use Proxy for access control (authorization, lazy loading).
TypeScript class decorators vs. GOF Decorator pattern: TypeScript's @Decorator syntax is a different mechanism (metaprogramming on class metadata). The GOF Decorator is a runtime object-wrapping pattern. The GOF pattern works without experimentalDecorators and is preferred for production code.
Anti-patterns:
Middleware pipeline (alternative for many cross-cutting concerns):
type Middleware<T> = (value: T, next: () => Promise<T>) => Promise<T>;
async function pipeline<T>(value: T, middlewares: Middleware<T>[]): Promise<T> {
const run = async (index: number): Promise<T> => {
if (index >= middlewares.length) return value;
return middlewares[index](value, () => run(index + 1));
};
return run(0);
}
refactoring.guru/design-patterns/decorator