Skip to content

v1.13.0

Latest

Choose a tag to compare

@G4brym G4brym released this 24 Jan 16:28
· 1 commit to main since this release

What's Changed

  • Add DISTINCT support
  • Add additional JOIN types (RIGHT, FULL, CROSS, NATURAL)
  • Add UNION/INTERSECT/EXCEPT set operations
  • Add CTEs (WITH clause) support
  • Add toSQL() and toDebugSQL() for query inspection
  • Add pagination helper
  • Add EXPLAIN support
  • Add transaction support
  • Add query hooks (beforeQuery/afterQuery)
  • Improve error messages with context and hints

DISTINCT Support

Select unique rows with .distinct(), with PostgreSQL DISTINCT ON support:

// Simple DISTINCT
const uniqueEmails = await qb.select('users').distinct().fields(['email']).all();

Additional JOIN Types

New convenience methods for all JOIN types:

const result = await qb.select('users')
  .innerJoin({table: 'orders', on: 'users.id = orders.user_id'})
  .leftJoin({table: 'profiles', on: 'users.id = profiles.user_id'})
  .rightJoin({table: 'teams', on: 'users.team_id = teams.id'})
  .fullJoin({table: 'projects', on: 'users.id = projects.owner_id'})
  .naturalJoin('departments')
  .all();

Set Operations (UNION/INTERSECT/EXCEPT)

Combine query results with set operations:

const allUsers = await qb.select('active_users').fields(['id', 'name'])
  .union(qb.select('archived_users').fields(['id', 'name']))
  .orderBy({name: 'ASC'})
  .all();

const commonIds = await qb.select('users').fields(['id'])
  .intersect(qb.select('admins').fields(['user_id']))
  .all();

CTEs (WITH Clause)

Define reusable subqueries with Common Table Expressions:

const result = await qb.select('orders')
  .with('active_users', qb.select('users').where('status = ?', 'active'))
  .innerJoin({table: 'active_users', on: 'orders.user_id = active_users.id'})
  .all();

Query Inspection

Debug queries without executing them:

const {sql, params} = qb.select('users').where('id = ?', 1).toSQL();
// sql: "SELECT * FROM users WHERE id = ?"
// params: [1]

const debugSql = qb.select('users').where('id = ?', 1).toDebugSQL();
// "SELECT * FROM users WHERE id = 1"

Pagination Helper

Get results with pagination metadata in one call:

const result = await qb.select('users')
  .where('active = ?', true)
  .paginate({page: 2, perPage: 20});

// result.results: [...users...]
// result.pagination: { page: 2, perPage: 20, total: 150, totalPages: 8, hasNext: true, hasPrev: true }

Transaction Support

Execute multiple queries atomically:

// D1 (async, batch-based)
const results = await qb.transaction(async (tx) => [
  tx.insert({tableName: 'orders', data: {user_id: 1, total: 100}}),
  tx.update({
    tableName: 'users',
    data: {balance: new Raw('balance - 100')},
    where: {conditions: 'id = ?', params: [1]}
  }),
]);

// DOQB (sync, SQLite transactions)
qb.transaction((tx) => {
  tx.insert({tableName: 'orders', data: {user_id: 1, total: 100}}).execute();
  tx.update({
    tableName: 'users',
    data: {balance: new Raw('balance - 100')},
    where: {conditions: 'id = ?', params: [1]}
  }).execute();
});

Query Hooks

Add middleware for logging, metrics, or tenant filtering:

qb.beforeQuery(async (query, type) => {
  console.log(`Executing ${type}: ${query.query}`);
  return query;
});

qb.afterQuery(async (result, query, duration) => {
  metrics.record(query.query, duration);
  return result;
});

Full Changelog: v1.12.0...v1.13.0