helpers.js 2.14 KB
/**
 * helpers —— 共享查询助手:纯函数,不持有连接,作用于传入的 query builder 或行对象。
 *
 * 零 IO、最易测。`paginate` / `notDeleted` 在 builder 上施加约束并返回 builder(链式)。
 * 无任何领域语义。全链路统一 snake_case(plan/db-root-solution.md 决定 2),不存在任何大小写转换助手。
 */

/**
 * 游标分页(keyset):在 builder 上施加 keyset 分页,返回 builder。
 *
 * 语义:按 `orderBy.column` 排序,取 `limit` 行;若给了 `cursor`(上一页最后一行的
 * 排序列值),则只取该游标之后的行(升序 `>`,降序 `<`)。多取一行由调用方判断 hasMore
 * 不在此处兜——本函数职责单一,只施加 where + orderBy + limit。
 *
 * @param {import('knex').Knex.QueryBuilder} qb     Knex query builder
 * @param {Object} [opts]
 * @param {string|number|null} [opts.cursor]       上一页最后一行的排序列值;首页传 null/undefined
 * @param {number} [opts.limit]                    每页条数(默认 20)
 * @param {{column?:string,direction?:('asc'|'desc')}} [opts.orderBy]  排序列与方向
 * @returns {import('knex').Knex.QueryBuilder} 同一个 builder(链式)
 */
export function paginate(qb, opts = {}) {
  const limit =
    typeof opts.limit === 'number' && Number.isInteger(opts.limit) && opts.limit > 0
      ? opts.limit
      : 20
  const column = (opts.orderBy && opts.orderBy.column) || 'id'
  const direction = (opts.orderBy && opts.orderBy.direction) === 'desc' ? 'desc' : 'asc'

  if (opts.cursor !== undefined && opts.cursor !== null) {
    const operator = direction === 'desc' ? '<' : '>'
    qb.where(column, operator, opts.cursor)
  }

  return qb.orderBy(column, direction).limit(limit)
}

/**
 * 软删除过滤:where deleted_at is null。
 * @param {import('knex').Knex.QueryBuilder} qb
 * @param {string} [column]   软删除列名(默认 deleted_at)
 * @returns {import('knex').Knex.QueryBuilder}
 */
export function notDeleted(qb, column = 'deleted_at') {
  return qb.whereNull(column)
}

/** 共享查询助手集合。 */
export const helpers = {
  paginate,
  notDeleted,
}