TypeScript: type-safe select, where, and orderBy

A relational query filters rows, sorts them, and chooses columns. In TypeScript a table can be an array of objects, and the type of the row describes its columns. The generic constraint K extends keyof T says that a column name must be one of the row type's property names, so a misspelled column is a compile-time error instead of an empty result at run time.

TypeScript
type Row = { id: number; name: string; dept: string; salary: number };

const table: Row[] = [
  { id: 1, name: "Ada", dept: "Eng", salary: 120 },
  { id: 2, name: "Bo", dept: "Ops", salary: 90 },
  { id: 3, name: "Cy", dept: "Eng", salary: 100 },
  { id: 4, name: "Di", dept: "Ops", salary: 80 },
];

function where<T>(rows: T[], predicate: (row: T) => boolean): T[] {
  return rows.filter(predicate);
}

function orderBy<T, K extends keyof T>(rows: T[], key: K): T[] {
  return [...rows].sort((a, b) => (a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0));
}

function select<T, K extends keyof T>(rows: T[], ...keys: K[]): Pick<T, K>[] {
  return rows.map((row) => {
    const picked = {} as Pick<T, K>;
    for (const key of keys) {
      picked[key] = row[key];
    }
    return picked;
  });
}

const wellPaid = where(table, (row) => row.salary >= 100);
const sorted = orderBy(wellPaid, "name");
console.log(JSON.stringify(select(sorted, "name", "salary")));
console.log(JSON.stringify(select(orderBy(table, "salary"), "name")));
Read the full lesson