15 minlesson

Builder Variations in TypeScript

Builder Variations in TypeScript

Generic Builder

typescript
1class Builder<T> {
2 private data: Partial<T> = {};
3
4 set<K extends keyof T>(key: K, value: T[K]): this {
5 this.data[key] = value;
6 return this;
7 }
8
9 build(): T {
10 return this.data as T;
11 }
12}
13
14const shipment = new Builder<Shipment>()
15 .set('sender', senderAddress)
16 .set('recipient', recipientAddress)
17 .build();

Immutable Builder

typescript
1class ImmutableBuilder<T> {
2 constructor(private readonly data: Partial<T> = {}) {}
3
4 set<K extends keyof T>(key: K, value: T[K]): ImmutableBuilder<T> {
5 return new ImmutableBuilder({ ...this.data, [key]: value });
6 }
7
8 build(): T {
9 return Object.freeze(this.data) as T;
10 }
11}

Builder with Validation

typescript
1class ValidatingBuilder {
2 private errors: string[] = [];
3
4 from(address: Address): this {
5 if (!address.postalCode) {
6 this.errors.push('Sender postal code required');
7 }
8 this.data.sender = address;
9 return this;
10 }
11
12 build(): Shipment {
13 if (this.errors.length > 0) {
14 throw new ValidationError(this.errors);
15 }
16 return new Shipment(this.data);
17 }
18}

Async Builder

typescript
1class AsyncShipmentBuilder {
2 async from(address: Address): Promise<this> {
3 await this.validateAddress(address);
4 this.data.sender = address;
5 return this;
6 }
7
8 async build(): Promise<Shipment> {
9 const rates = await this.fetchRates();
10 return new Shipment({ ...this.data, rates });
11 }
12}
13
14// Usage
15const shipment = await new AsyncShipmentBuilder()
16 .then(b => b.from(sender))
17 .then(b => b.to(recipient))
18 .then(b => b.build());

Summary

VariantBenefit
GenericReusable for any type
ImmutableThread-safe, functional
ValidatingFail fast with clear errors
AsyncHandle async operations