TypeDrop
2026-08-27 Challenge
2026-08-27
Hard
Typed GraphQL-Style Query Builder with Conditional Field Selection & Recursive Inference
You're building the typed query layer for an internal developer portal that fetches data from a REST API mirroring a GraphQL-like selection model. Consumers describe *exactly* which fields they want at compile time — including nested relations — and the return type must reflect only those selected fields, nothing more and nothing less.
Goals
- Define `SelectionSet<T>` so each key maps to `true` for scalars or a nested `SelectionSet` for object/array fields — all keys optional.
- Define `Selected<T, S>` as a recursive mapped/conditional type that includes only the keys present in `S`, narrowing nested objects and array elements accordingly.
- Implement the recursive runtime function `stripToSelection` that mirrors the type-level recursion, pruning keys not in the selection at every nesting level.
- Wire `buildQuerySync` and `buildQuery` to delegate to `stripToSelection`, preserving the full generic return type through both the sync and async paths.
challenge.ts
// Key types and main function signatures
type Address = { street: string; city: string; country: string; postalCode: string };
type Repository = { id: string; name: string; isPrivate: boolean; starCount: number };
type User = {
id: string; username: string; email: string; age: number;
address: Address; repositories: Repository[];
};
// SelectionSet<T> — describe which fields you want (stub shown)
type SelectionSet<T> = { [K in keyof T]?: /* true | SelectionSet<nested> */ unknown };
// Selected<T, S> — the return type contains ONLY the selected fields (stub shown)
type Selected<T, S extends SelectionSet<T>> = { [K in keyof T]: T[K] };
// Synchronous pruning: returns only the selected fields at compile time AND runtime
function buildQuerySync<T extends object, S extends SelectionSet<T>>(
data: T,
selection: S
): Selected<T, S>;
// Async: resolves via fetcher, then prunes identically
function buildQuery<T extends object, S extends SelectionSet<T>>(
fetcher: () => Promise<T>,
selection: S
): Promise<Selected<T, S>>;
Hints (click to reveal)
Hints
- For `SelectionSet<T>`, use a conditional on `NonNullable<T[K]> extends object` to decide whether the value type is `true` or `SelectionSet<UnwrapArray<T[K]>>` — scalars map to `true`, objects/arrays map to a nested selection.
- For `Selected<T, S>`, use key remapping (`as`) with `K extends keyof S ? K : never` to exclude un-selected keys, then branch on whether `S[K]` is `true` or a sub-object to decide the value type — remember to handle arrays separately from plain objects.
- The runtime `stripToSelection` must check `Array.isArray` to branch between array and plain-object handling; for arrays, map each element through a recursive `stripToSelection` call, and for objects iterate `Object.keys(selection)` to copy only the chosen fields.
Useful resources
Or clone locally
git clone -b challenge/2026-08-27 https://github.com/niltonheck/typedrop.git