/** * 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, }