cluster.js 2.24 KB
/**
 * createDbCluster —— 多连接装配 + 降级 + destroy 去重。
 *
 * 一期单连接够用:replica / analytics 未注入时降级到 primary(**同一引用**,可用
 * `===` 断言),调用方读写分离 / 分析分流的写法不变,二期换真副本零改动。
 * 本文件只做装配与降级,不碰任何具体表,不读 env。
 */

import { createDb } from './create-db.js'
import { DbConfigError } from './errors.js'

/**
 * @typedef {Object} DbCluster
 * @property {import('knex').Knex} primary   主库(读写)
 * @property {import('knex').Knex} replica    只读副本(一期 === primary)
 * @property {import('knex').Knex} analytics  分析库(一期 === primary)
 * @property {() => Promise<void>} destroy    关闭所有去重后的底层连接
 */

/**
 * 构造多连接集群。一期 replica/analytics 可省略,省略则降级到 primary(同一引用)。
 * @param {Object} cfg
 * @param {{url:string,pool?:Object,client?:'mysql2'}} cfg.primary    主库(读写)
 * @param {{url:string,pool?:Object,client?:'mysql2'}} [cfg.replica]  只读副本
 * @param {{url:string,pool?:Object,client?:'mysql2'}} [cfg.analytics] 分析库
 * @returns {DbCluster}
 * @throws {DbConfigError} primary 缺失时立即抛
 */
export function createDbCluster(cfg) {
  if (!cfg || typeof cfg !== 'object' || !cfg.primary) {
    throw new DbConfigError('createDbCluster: cfg.primary is required')
  }

  const primary = createDb(cfg.primary)

  // 降级:未注入副本/分析库 → 显式指向 primary 同一引用(不新建第二个连同库的句柄,
  // 避免连接数翻倍)。二期注入真副本/分析库时各自独立。
  const replica = cfg.replica ? createDb(cfg.replica) : primary
  const analytics = cfg.analytics ? createDb(cfg.analytics) : primary

  /**
   * 关闭所有去重后的底层连接。同一引用只 destroy 一次,绝不 double-close。
   * @returns {Promise<void>}
   */
  async function destroy() {
    const seen = new Set()
    const tasks = []
    for (const handle of [primary, replica, analytics]) {
      if (seen.has(handle)) continue
      seen.add(handle)
      tasks.push(handle.destroy())
    }
    await Promise.all(tasks)
  }

  return { primary, replica, analytics, destroy }
}