TypeDrop

2026-10-02 Challenge

2026-10-02 Hard

Typed Query Builder with Schema Inference

You're building the ORM query layer for a multi-tenant analytics platform. Queries are constructed fluently against a strongly-typed schema, the compiler must enforce that only real column names are selected/filtered, aggregation aliases are tracked in the return type, and the final `execute()` call resolves to a shape derived entirely from the chosen columns and aggregations — zero `any`, zero unsafe casts.

Goals

  • Implement five type utilities (ColumnType, SelectedRow, AggregationDef, AggRow, QueryRow) so that execute()'s return type is fully inferred from the builder's type parameters.
  • Implement the QueryBuilder class so each fluent method returns a new builder instance with updated Cols or Aggs type parameters, preserving immutability.
  • Implement applyFilter with exhaustive narrowing over all nine FilterOperator variants, including isNull / isNotNull against null values.
  • Implement execute() to correctly apply WHERE clauses (AND semantics), compute numeric aggregations (sum/count/avg/min/max) over filtered rows, project selected columns, and honour orderBy and limit.
challenge.ts
// Core schema & builder types at a glance

type ColumnTypeMap = { string: string; number: number; boolean: boolean; date: Date };
type ColumnTag     = keyof ColumnTypeMap;
type ColumnDef<Tag extends ColumnTag = ColumnTag> = { type: Tag; nullable: boolean };
type TableSchema   = Record<string, ColumnDef>;

// Key utility types you must implement:
type ColumnType<Schema extends TableSchema, Col extends keyof Schema> = TODO;
type SelectedRow<Schema extends TableSchema, Cols extends keyof Schema> = TODO;
type AggregationDef = TODO;
type AggRow<Aggs extends readonly AggregationDef[]>                   = TODO;
type QueryRow<Schema extends TableSchema,
              Cols extends keyof Schema,
              Aggs extends readonly AggregationDef[]>                  = TODO;

// Fluent builder — types evolve as you chain:
declare class QueryBuilder<Schema extends TableSchema,
                           Cols extends keyof Schema = never,
                           Aggs extends readonly AggregationDef[] = []> {
  select<NewCols extends keyof Schema>(...cols: NewCols[])
    : QueryBuilder<Schema, NewCols, Aggs>;
  aggregate<NewAggs extends readonly AggregationDef[]>(aggs: NewAggs)
    : QueryBuilder<Schema, Cols, NewAggs>;
  execute(): Promise<QueryRow<Schema, Cols, Aggs>[]>;
}
Hints (click to reveal)

Hints

  • For AggRow, you need to convert a tuple of objects with an `alias` string into an object type — look at how indexed access types and mapped types can iterate over tuple indices to extract each `alias` as a key.
  • QueryRow should be an intersection (or merged mapped type) of SelectedRow and AggRow; when Cols is `never`, SelectedRow produces an empty object `{}` — lean into that.
  • Each QueryBuilder method should spread the existing internal state into the constructor of the new instance, overriding only the field that changed — this is the immutable-builder pattern in TypeScript.

Or clone locally

git clone -b challenge/2026-10-02 https://github.com/niltonheck/typedrop.git