From harness-claude
Implements GoF Builder pattern in TypeScript with fluent builders for step-by-step construction of complex objects, build-time validation, and immutable results. Use for telescoping constructors or multiple representations.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Construct complex objects step-by-step using fluent builders and director classes.
Generates Builder pattern for PHP 8.4 classes with fluent interface, validation, optional director, and unit tests. For complex objects with many parameters or optional fields.
Implements Factory Method design pattern in TypeScript with class-based creators, functional switches, and registered generics. Use when object types are runtime-determined, subclasses control instantiation, or to follow Open/Closed Principle.
Guides TypeScript advanced types like generics, conditional types, mapped types, template literals, and utilities for type-safe libraries, APIs, and components. Use for complex type logic and inference.
Share bugs, ideas, or general feedback.
Construct complex objects step-by-step using fluent builders and director classes.
build() time, not during individual settersFluent builder with TypeScript (most common pattern):
interface QueryConfig {
table: string;
conditions: string[];
orderBy?: string;
limit?: number;
offset?: number;
joins: string[];
}
class QueryBuilder {
private config: Partial<QueryConfig> = { conditions: [], joins: [] };
from(table: string): this {
this.config.table = table;
return this;
}
where(condition: string): this {
this.config.conditions!.push(condition);
return this;
}
join(clause: string): this {
this.config.joins!.push(clause);
return this;
}
orderBy(column: string): this {
this.config.orderBy = column;
return this;
}
limit(n: number): this {
this.config.limit = n;
return this;
}
offset(n: number): this {
this.config.offset = n;
return this;
}
build(): string {
if (!this.config.table) throw new Error('Table is required');
let query = `SELECT * FROM ${this.config.table}`;
if (this.config.joins!.length > 0) {
query += ` ${this.config.joins!.join(' ')}`;
}
if (this.config.conditions!.length > 0) {
query += ` WHERE ${this.config.conditions!.join(' AND ')}`;
}
if (this.config.orderBy) query += ` ORDER BY ${this.config.orderBy}`;
if (this.config.limit !== undefined) query += ` LIMIT ${this.config.limit}`;
if (this.config.offset !== undefined) query += ` OFFSET ${this.config.offset}`;
return query;
}
}
// Usage
const query = new QueryBuilder()
.from('users')
.join('LEFT JOIN orders ON orders.user_id = users.id')
.where('users.active = true')
.where('users.age > 18')
.orderBy('users.created_at DESC')
.limit(20)
.offset(40)
.build();
Immutable result via build():
class EmailBuilder {
private to: string[] = [];
private subject = '';
private body = '';
private cc: string[] = [];
private attachments: Buffer[] = [];
addTo(address: string): this {
this.to.push(address);
return this;
}
withSubject(subject: string): this {
this.subject = subject;
return this;
}
withBody(body: string): this {
this.body = body;
return this;
}
addCC(address: string): this {
this.cc.push(address);
return this;
}
addAttachment(data: Buffer): this {
this.attachments.push(data);
return this;
}
build(): Readonly<Email> {
if (this.to.length === 0) throw new Error('At least one recipient required');
if (!this.subject) throw new Error('Subject is required');
return Object.freeze({
to: [...this.to],
subject: this.subject,
body: this.body,
cc: [...this.cc],
attachments: [...this.attachments],
});
}
}
Director class (for reusable construction sequences):
class ReportDirector {
constructor(private builder: ReportBuilder) {}
buildSummaryReport(): void {
this.builder
.setTitle('Summary Report')
.addSection('Overview')
.addSection('KPIs')
.setFooter('Confidential');
}
buildDetailedReport(): void {
this.builder
.setTitle('Detailed Report')
.addSection('Executive Summary')
.addSection('Data Analysis')
.addSection('Charts')
.addSection('Raw Data')
.addSection('Appendix')
.setFooter('Confidential');
}
}
Why not just use an options object? Options objects work well for simple configs, but builders shine when:
build()Anti-patterns:
build() — freeze the resultbuild()TypeScript step builder (enforce required fields at compile time):
// Forces callers to provide required fields before optional ones
interface NeedsTable {
from(table: string): NeedsCondition;
}
interface NeedsCondition {
where(cond: string): NeedsCondition;
build(): string;
limit(n: number): NeedsCondition;
}
class TypeSafeQueryBuilder implements NeedsTable, NeedsCondition {
private table = '';
private conditions: string[] = [];
private limitVal?: number;
from(table: string): NeedsCondition {
this.table = table;
return this;
}
where(cond: string): NeedsCondition {
this.conditions.push(cond);
return this;
}
limit(n: number): NeedsCondition {
this.limitVal = n;
return this;
}
build(): string {
return `SELECT * FROM ${this.table} WHERE ${this.conditions.join(' AND ')}`;
}
static create(): NeedsTable {
return new TypeSafeQueryBuilder();
}
}
// Compile error if you skip from()
const q = TypeSafeQueryBuilder.create().from('users').where('active = true').build();
refactoring.guru/design-patterns/builder