Real World Example

A Typed Query Builder

ORMs and query builders like Prisma, Drizzle and Kysely use advanced generics so that the result type matches exactly the columns you selected. This simplified version runs on an in-memory table.

Example

type Product = { id: number; name: string; price: number; stock: number; category: "audio" | "kitchen" };

class Query<T, S extends keyof T = keyof T> {
  constructor(private rows: T[], private cols?: S[], private filters: ((r: T) => boolean)[] = [], private order?: [keyof T, "asc" | "desc"]) {}

  select<K extends keyof T>(...cols: K[]): Query<T, K> {
    return new Query<T, K>(this.rows, cols, this.filters, this.order);
  }
  where<K extends keyof T>(col: K, op: "=" | ">" | "<", value: T[K]): Query<T, S> {
    const test = (r: T) => (op === "=" ? r[col] === value : op === ">" ? r[col] > value : r[col] < value);
    return new Query<T, S>(this.rows, this.cols, [...this.filters, test], this.order);
  }
  orderBy(col: keyof T, dir: "asc" | "desc" = "asc"): Query<T, S> {
    return new Query<T, S>(this.rows, this.cols, this.filters, [col, dir]);
  }
  run(): Pick<T, S>[] {
    let out = this.rows.filter((r) => this.filters.every((f) => f(r)));
    if (this.order) {
      const [c, d] = this.order;
      out = [...out].sort((a, b) => (a[c] < b[c] ? -1 : 1) * (d === "asc" ? 1 : -1));
    }
    const cols = this.cols;
    return cols ? out.map((r) => Object.fromEntries(cols.map((c) => [c, r[c]])) as Pick<T, S>) : out;
  }
}

const products: Product[] = [
  { id: 1, name: "Earbuds", price: 1799, stock: 12, category: "audio" },
  { id: 2, name: "Speaker", price: 2999, stock: 0, category: "audio" },
  { id: 3, name: "Headphones", price: 4999, stock: 4, category: "audio" },
  { id: 4, name: "Kettle", price: 1299, stock: 9, category: "kitchen" },
];

const result = new Query(products).select("name", "price").where("category", "=", "audio").where("stock", ">", 0).orderBy("price", "desc").run();
// result: { name: string; price: number }[]
result.forEach((r) => console.log(r.name.padEnd(11), r.price));

Output

Headphones  4999
Earbuds     1799

Note: result[0].stock would be a compile error, because stock was not selected. That is exactly how Prisma's select works.