Designs complex generic types, refactors `any` types to strict alternatives, creates type guards and utility types, and resolves TypeScript compiler errors. Use when the user asks about TypeScript (TS) types, generics, type inference, type guards, removing `any` types, strict typing, type errors, `infer`, `extends`, conditional types, mapped types, template literal types, branded/opaque types, or utility types like `Partial`, `Record`, `ReturnType`, and `Awaited`.
97
97%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
The builder pattern uses a chain of method calls to incrementally build up a data structure or configuration. With TypeScript, we can make this pattern fully type-safe, tracking accumulated state at the type level.
Each method returns a new or modified builder with updated type information:
new DbSeeder()
.addUser("matt", { name: "Matt" })
.addPost("post1", { title: "Hello" })
.transact();
// Each step updates the type to include what was addedinterface User {
id: string;
name: string;
}
interface Post {
id: string;
title: string;
authorId: string;
}
// Shape that constrains our generic
interface DbShape {
users: Record<string, User>;
posts: Record<string, Post>;
}export class DbSeeder<TDatabase extends DbShape> {
public users: DbShape["users"] = {};
public posts: DbShape["posts"] = {};
// Each method returns DbSeeder with EXTENDED type information
addUser = <Id extends string>(
id: Id,
user: Omit<User, "id">,
): DbSeeder<TDatabase & { users: TDatabase["users"] & Record<Id, User> }> => {
this.users[id] = { ...user, id };
return this;
};
addPost = <Id extends string>(
id: Id,
post: Omit<Post, "id">,
): DbSeeder<TDatabase & { posts: TDatabase["posts"] & Record<Id, Post> }> => {
this.posts[id] = { ...post, id };
return this;
};
// Final method returns the built result with correct types
transact = async () => {
// Actual database operations would go here
return {
users: this.users as TDatabase["users"],
posts: this.posts as TDatabase["posts"],
};
};
}const usage = async () => {
const result = await new DbSeeder()
.addUser("matt", { name: "Matt" })
.addPost("post1", { authorId: "matt", title: "Hello" })
.addPost("post2", { authorId: "matt", title: "World" })
.transact();
// result.users.matt is typed as User
// result.posts.post1 is typed as Post
// result.posts.post2 is typed as Post
console.log(result.users.matt.name); // Type-safe!
console.log(result.posts.post1.title); // Type-safe!
};Each method call extends the type:
new DbSeeder()
// Type: DbSeeder<{ users: {}; posts: {} }>
.addUser("matt", { name: "Matt" })
// Type: DbSeeder<{ users: Record<"matt", User>; posts: {} }>
.addPost("post1", { ... })
// Type: DbSeeder<{ users: Record<"matt", User>; posts: Record<"post1", Post> }>
.addPost("post2", { ... })
// Type: DbSeeder<{ users: Record<"matt", User>; posts: Record<"post1" | "post2", Post> }>Capture literal types by using a generic with string constraint:
addUser = <Id extends string>(
id: Id, // Id is inferred as literal type "matt", not string
user: Omit<User, "id">,
): DbSeeder<TDatabase & { users: TDatabase["users"] & Record<Id, User> }>Use & to add new type information while preserving existing:
TDatabase & { users: TDatabase["users"] & Record<Id, User> }The runtime types don't match compile-time types, so cast in the final method:
transact = async () => {
return {
users: this.users as TDatabase["users"],
posts: this.posts as TDatabase["posts"],
};
};interface QueryState {
table: string | null;
columns: string[];
whereClause: string | null;
}
class QueryBuilder<TState extends QueryState> {
private state: TState;
private constructor(state: TState) {
this.state = state;
}
static create() {
return new QueryBuilder({
table: null,
columns: [],
whereClause: null,
});
}
from<T extends string>(
table: T
): QueryBuilder<TState & { table: T }> {
return new QueryBuilder({ ...this.state, table });
}
select<C extends string[]>(
...columns: C
): QueryBuilder<TState & { columns: C }> {
return new QueryBuilder({ ...this.state, columns });
}
where<W extends string>(
clause: W
): QueryBuilder<TState & { whereClause: W }> {
return new QueryBuilder({ ...this.state, whereClause: clause });
}
// Only allow build if table is set
build(this: QueryBuilder<TState & { table: string }>): string {
const cols = this.state.columns.length
? this.state.columns.join(", ")
: "*";
let sql = `SELECT ${cols} FROM ${this.state.table}`;
if (this.state.whereClause) {
sql += ` WHERE ${this.state.whereClause}`;
}
return sql;
}
}
// Usage
const query = QueryBuilder.create()
.from("users")
.select("id", "name")
.where("active = true")
.build();
// Error: Can't build without from()
QueryBuilder.create().select("id").build(); // Type error!interface ServerConfig {
host: string;
port: number;
ssl?: boolean;
timeout?: number;
}
type RequiredFields = "host" | "port";
type ConfiguredFields<T> = { [K in keyof T]-?: K };
class ConfigBuilder<TConfigured extends Partial<Record<keyof ServerConfig, true>>> {
private config: Partial<ServerConfig> = {};
host(value: string): ConfigBuilder<TConfigured & { host: true }> {
this.config.host = value;
return this as any;
}
port(value: number): ConfigBuilder<TConfigured & { port: true }> {
this.config.port = value;
return this as any;
}
ssl(value: boolean): ConfigBuilder<TConfigured & { ssl: true }> {
this.config.ssl = value;
return this as any;
}
// Only allow build when required fields are set
build(
this: ConfigBuilder<{ host: true; port: true }>
): ServerConfig {
return this.config as ServerConfig;
}
}
// Usage
const config = new ConfigBuilder()
.host("localhost")
.port(3000)
.ssl(true)
.build();
// Error: Missing required fields
new ConfigBuilder().host("localhost").build(); // Type error!export class DbSeeder<
TDatabase extends DbShape = {
users: { defaultUser: User };
posts: {};
}
> {
public users: DbShape["users"] = {
defaultUser: { id: "default", name: "Default User" },
};
// ...
}
// Now every DbSeeder starts with defaultUser
const seeder = new DbSeeder();
// seeder has users.defaultUser by default// BAD - TDatabase could be anything
class DbSeeder<TDatabase> {
// Error: Cannot access TDatabase["users"]
}
// GOOD - constrained to DbShape
class DbSeeder<TDatabase extends DbShape> {
// Can safely access TDatabase["users"] and TDatabase["posts"]
}// BAD - type mismatch
transact = async () => {
return {
users: this.users, // Type: Record<string, User>, not TDatabase["users"]
posts: this.posts,
};
};
// GOOD - cast to match accumulated type
transact = async () => {
return {
users: this.users as TDatabase["users"],
posts: this.posts as TDatabase["posts"],
};
};this Instead of New Type// BAD - returns same type, loses type information
addUser(id: string, user: Omit<User, "id">): this {
return this;
}
// GOOD - returns new generic instantiation
addUser<Id extends string>(
id: Id,
user: Omit<User, "id">,
): DbSeeder<TDatabase & { users: TDatabase["users"] & Record<Id, User> }> {
return this;
}