A typed builder in TypeScript
The generic tracks which fields have been supplied so far.
interface Query {
table: string;
where?: string;
limit?: number;
}
class QueryBuilder<Have extends keyof Query = never> {
private parts: Partial<Query> = {};
from(table: string): QueryBuilder<Have | "table"> {
this.parts.table = table;
return this as QueryBuilder<Have | "table">;
}
where(clause: string): QueryBuilder<Have | "where"> {
this.parts.where = clause;
return this as QueryBuilder<Have | "where">;
}
build(this: QueryBuilder<"table" | Have>): string {
const { table, where, limit } = this.parts;
return [`SELECT * FROM ${table}`, where && `WHERE ${where}`,
limit && `LIMIT ${limit}`].filter(Boolean).join(" ");
}
}
console.log(new QueryBuilder().from("runs").where("tpm > 90").build());
// new QueryBuilder().where("x").build(); // error: table never set
How it works
- Each setter widens the accumulated key set.
buildonly typechecks once the required keys are present.- The compiler enforces the order you actually need.
Keywords and builtins used here
BooleanPartialQueryBuilderasclassconstextendsfrominterfacenumberprivatereturnstringthis
The run, in numbers
- Lines
- 28
- Characters to type
- 770
- Tokens
- 196
- Three-star pace
- 110 tpm
At the three-star pace of 110 tokens a minute, this run takes about 107 seconds.
Step 1 of 5 in Building & injecting, step 9 of 19 in Classes & patterns.