All questions

Drizzle Query Builder

Premium

Drizzle Query Builder

Build a small in-memory query builder over an array of plain-object rows, inspired by how Drizzle ORM reads: query(rows).where(...).orderBy(...).limit(...).all(). The chain reads top-to-bottom like a sentence, and nothing actually runs until the terminal .all() call. Along the way you'll write small operator helperseq, gt, and, or — that build the predicates .where() filters on. A predicate here is just a function that takes one row and returns true or false.

Signature

// query(rows) returns a chainable, lazy builder. Build steps accumulate;
// .all() executes filter → sort → offset → limit and returns a new array.
query(rows: object[]): Builder

interface Builder {
  where(cond: (row) => boolean): Builder;        // chain multiple → AND
  orderBy(key: string, dir?: 'asc' | 'desc'): Builder; // dir defaults to 'asc'
  limit(n: number): Builder;
  offset(n: number): Builder;
  all(): object[];                                // terminal — runs the plan
}

// Operator helpers each return a predicate (row) => boolean:
eq(key, value)   ne(key, value)
gt(key, value)   gte(key, value)
lt(key, value)   lte(key, value)
and(...conds)    or(...conds)   // combine predicates into one predicate

query is also exported under the alias drizzleQueryBuilder.

Examples

const users = [
  { id: 1, name: 'Ada', age: 36, role: 'admin' },
  { id: 2, name: 'Bo',  age: 22, role: 'user'  },
  { id: 3, name: 'Cy',  age: 41, role: 'user'  },
  { id: 4, name: 'Di',  age: 22, role: 'admin' },
  { id: 5, name: 'Eve', age: 30, role: 'user'  },
];

// Filter to users, sort oldest-first, take the second page of size 1.
query(users)
  .where(eq('role', 'user'))
  .orderBy('age', 'desc')
  .offset(1)
  .limit(1)
  .all();
// → [{ id: 5, name: 'Eve', age: 30, role: 'user' }]
// role=user → ids [2,3,5]; by age desc → [3,5,2]; offset 1 → [5,2]; limit 1 → [5].
// A compound predicate: adults under 35, in one where().
query(users).where(and(gt('age', 18), lt('age', 35))).all();
// → rows for Bo (22), Di (22), Eve (30)

// or() keeps a row if any branch matches.
query(users).where(or(eq('age', 41), eq('age', 36))).all();
// → rows for Ada (36), Cy (41)

Notes

  • Execution order is fixed: filter, then sort, then offset, then limit. Offset is applied before limit, so .offset(4).limit(2) means "skip 4, then take 2."
  • Chaining .where() AND-combines. Two .where() calls keep only rows passing both. To express OR within a single filter, use the or(...) helper.
  • orderBy direction defaults to 'asc'. Chaining multiple orderBy calls makes the first key primary and later keys tie-breakers.
  • The source array is never mutated. .all() returns a new array; the rows you passed in keep their original order and contents. Sorting must happen on a copy.
  • Out-of-bounds paging is harmless. limit larger than the row count returns whatever is available; offset past the end returns [].
  • The builder is lazy and reusable. Building the chain only records a plan; .all() runs it. Calling .all() twice on the same builder gives the same result.
  • Don't worry about projections (selecting a subset of columns), joins across tables, aggregations (count, sum), or compiling to real SQL — those are listed under Going further in the solution.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium