From harness-claude
Implements GoF Command pattern in TypeScript: encapsulate operations as objects for undo/redo, queuing, transaction logs, and parameterizing methods. Use for task queues or action histories.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Encapsulate operations as command objects to support undo, redo, and command queuing.
Encapsulates JavaScript operations as objects for undo/redo, queuing, logging, and decoupling invokers from executors. Use in editors or apps needing reversible actions or operation history.
Implements CQRS (Command Query Responsibility Segregation) for scalable architectures. Use when separating read/write models, optimizing query performance, or building event-sourced systems.
Implements CQRS patterns to separate read/write models, scale reads independently, and build event-sourced systems for query optimization.
Share bugs, ideas, or general feedback.
Encapsulate operations as command objects to support undo, redo, and command queuing.
Command interface with undo:
interface Command {
execute(): Promise<void>;
undo(): Promise<void>;
readonly description: string;
}
// Concrete command
class CreateUserCommand implements Command {
readonly description: string;
private createdUserId: string | null = null;
constructor(
private readonly userRepo: UserRepository,
private readonly data: { email: string; name: string }
) {
this.description = `Create user: ${data.email}`;
}
async execute(): Promise<void> {
const user = await this.userRepo.create(this.data);
this.createdUserId = user.id;
console.log(`Created user ${user.id}`);
}
async undo(): Promise<void> {
if (!this.createdUserId) throw new Error('Cannot undo: command was not executed');
await this.userRepo.delete(this.createdUserId);
console.log(`Deleted user ${this.createdUserId}`);
this.createdUserId = null;
}
}
class UpdateEmailCommand implements Command {
readonly description: string;
private previousEmail: string | null = null;
constructor(
private readonly userRepo: UserRepository,
private readonly userId: string,
private readonly newEmail: string
) {
this.description = `Update email for user ${userId}`;
}
async execute(): Promise<void> {
const user = await this.userRepo.findById(this.userId);
if (!user) throw new Error(`User ${this.userId} not found`);
this.previousEmail = user.email;
await this.userRepo.update(this.userId, { email: this.newEmail });
}
async undo(): Promise<void> {
if (!this.previousEmail) throw new Error('Cannot undo: command was not executed');
await this.userRepo.update(this.userId, { email: this.previousEmail });
}
}
Command history (invoker with undo/redo stack):
class CommandHistory {
private undoStack: Command[] = [];
private redoStack: Command[] = [];
async execute(command: Command): Promise<void> {
await command.execute();
this.undoStack.push(command);
this.redoStack = []; // clear redo history after new command
console.log(`Executed: ${command.description}`);
}
async undo(): Promise<void> {
const command = this.undoStack.pop();
if (!command) throw new Error('Nothing to undo');
await command.undo();
this.redoStack.push(command);
console.log(`Undone: ${command.description}`);
}
async redo(): Promise<void> {
const command = this.redoStack.pop();
if (!command) throw new Error('Nothing to redo');
await command.execute();
this.undoStack.push(command);
console.log(`Redone: ${command.description}`);
}
getHistory(): string[] {
return this.undoStack.map((c) => c.description);
}
}
// Usage
const history = new CommandHistory();
await history.execute(new CreateUserCommand(repo, { email: 'alice@example.com', name: 'Alice' }));
await history.execute(new UpdateEmailCommand(repo, 'user-1', 'alice2@example.com'));
await history.undo(); // reverts email update
await history.redo(); // re-applies email update
Command queue for background jobs:
class CommandQueue {
private queue: Command[] = [];
private running = false;
enqueue(command: Command): void {
this.queue.push(command);
if (!this.running) this.processNext();
}
private async processNext(): Promise<void> {
this.running = true;
while (this.queue.length > 0) {
const command = this.queue.shift()!;
try {
await command.execute();
} catch (err) {
console.error(`Command failed: ${command.description}`, err);
}
}
this.running = false;
}
}
Macro command (batch):
class MacroCommand implements Command {
constructor(
private readonly commands: Command[],
public readonly description: string
) {}
async execute(): Promise<void> {
for (const cmd of this.commands) await cmd.execute();
}
async undo(): Promise<void> {
for (const cmd of [...this.commands].reverse()) await cmd.undo();
}
}
When to store state for undo: The command must capture enough state before execute() to reverse it. Common patterns: capture the previous value, capture an ID for delete operations, or save a full snapshot.
Anti-patterns:
Command vs. Strategy: Strategy encapsulates an interchangeable algorithm selected at construction time. Command encapsulates a specific operation with data, executed and potentially reversed. Commands are objects that DO something once; strategies are policies applied repeatedly.
Event sourcing connection: Command pattern at the infrastructure level becomes event sourcing. Each executed command emits an event stored in an append-only log. Undo becomes a compensating event.
refactoring.guru/design-patterns/command