{"version":3,"sources":["../src/db/configure.ts","../src/db/helpers.ts","../src/db/transaction.ts","../src/db/where-builder.ts","../src/db/sql-builder.ts"],"names":[],"mappings":"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","file":"db.mjs","sourcesContent":["/**\n * configureDb — DB 풀 주입\n *\n * 앱 시작 시 한 번만 호출한다. mysql2/promise Pool과 호환되는 인터페이스를 받는다.\n * global 에 저장하므로 Next.js 개발 모드 hot reload 에도 풀이 유지된다.\n *\n * @example\n * import pool from \"./config/db\";\n * import { configureDb } from \"@sunkim4638/admin-modules/db\";\n * configureDb(pool);\n */\n\nexport interface DbConnection {\n    query(sql: string, params?: any[]): Promise<[any, any]>;\n    beginTransaction(): Promise<void>;\n    commit(): Promise<void>;\n    rollback(): Promise<void>;\n    release(): void;\n}\n\nexport interface DbPool {\n    query(sql: string, params?: any[]): Promise<[any, any]>;\n    getConnection(): Promise<DbConnection>;\n}\n\ndeclare global {\n    // eslint-disable-next-line no-var\n    var __adminModulesDbPool__: DbPool | undefined;\n}\n\nexport function configureDb(pool: DbPool): void {\n    global.__adminModulesDbPool__ = pool;\n}\n\nexport function getPool(): DbPool {\n    if (!global.__adminModulesDbPool__) {\n        throw new Error(\"[db] configureDb()를 먼저 호출해야 합니다.\");\n    }\n    return global.__adminModulesDbPool__;\n}\n","/**\n * DB 헬퍼 — queryOne / queryAll / insert / update / remove\n *\n * 모든 함수는 마지막 인자로 conn(커넥션)을 받을 수 있다.\n * conn을 넘기면 해당 커넥션을 사용하고, 생략하면 풀에서 자동으로 커넥션을 꺼낸다.\n * 트랜잭션 내에서 사용할 때는 반드시 conn을 넘겨야 한다.\n *\n * @example\n * // 단순 조회\n * const user = await queryOne<User>(\"SELECT * FROM users WHERE id = ?\", [1]);\n *\n * // 트랜잭션 내 사용\n * await withTransaction(async (conn) => {\n *   const id = await insert(\"orders\", { userId: 1, amount: 5000 }, conn);\n *   await update(\"users\", { point: 0 }, { id: 1 }, conn);\n * });\n */\n\nimport { getPool, type DbConnection } from \"./configure\";\n\n/**\n * WHERE 절 빌더\n * - null          → `col IS NULL`\n * - 배열          → `col IN (?, ?, ...)`\n * - 그 외(일반값) → `col = ?`\n */\nfunction buildWhere(where: Record<string, any>): { sql: string; vals: any[] } {\n    const clauses: string[] = [];\n    const vals: any[] = [];\n\n    for (const [k, v] of Object.entries(where)) {\n        if (v === null) {\n            clauses.push(`${k} IS NULL`);\n        } else if (Array.isArray(v)) {\n            if (v.length === 0) {\n                throw new Error(`[db] where.${k}: 빈 배열은 사용할 수 없습니다.`);\n            }\n            clauses.push(`${k} IN (${v.map(() => \"?\").join(\", \")})`);\n            vals.push(...v);\n        } else {\n            clauses.push(`${k} = ?`);\n            vals.push(v);\n        }\n    }\n\n    return { sql: clauses.join(\" AND \"), vals };\n}\n\n/** 단일 row 조회. 결과가 없으면 null 반환. */\nexport const queryOne = async <T>(\n    sql: string,\n    params: any[] = [],\n    conn?: DbConnection\n): Promise<T | null> => {\n    const [rows] = conn\n        ? await conn.query(sql, params)\n        : await getPool().query(sql, params);\n    const data = rows as T[];\n    return data.length > 0 ? data[0] : null;\n};\n\n/** 리스트 조회. 결과가 없으면 빈 배열 반환. */\nexport const queryAll = async <T>(\n    sql: string,\n    params: any[] = [],\n    conn?: DbConnection\n): Promise<T[]> => {\n    const [rows] = conn\n        ? await conn.query(sql, params)\n        : await getPool().query(sql, params);\n    return rows as T[];\n};\n\n/**\n * INSERT\n * @returns 삽입된 row의 insertId\n */\nexport const insert = async (\n    table: string,\n    row: Record<string, any>,\n    conn?: DbConnection\n): Promise<number> => {\n    if (!table || !row || Object.keys(row).length === 0) {\n        throw new Error(\"[db] insert: table과 row가 필요합니다.\");\n    }\n    const keys = Object.keys(row);\n    const vals = keys.map((k) => row[k]);\n    const sql = `INSERT INTO ${table} (${keys.join(\", \")}) VALUES (${keys.map(() => \"?\").join(\", \")})`;\n    const [result] = conn\n        ? await conn.query(sql, vals)\n        : await getPool().query(sql, vals);\n    if (!result || (result as any).insertId === 0) {\n        throw new Error(\"[db] insert: 데이터 삽입에 실패했습니다.\");\n    }\n    return (result as any).insertId;\n};\n\n/**\n * UPDATE\n * where 조건: null(IS NULL), 배열(IN), 일반값(=) 모두 지원.\n * @returns affectedRows\n */\nexport const update = async (\n    table: string,\n    row: Record<string, any>,\n    where: Record<string, any>,\n    conn?: DbConnection\n): Promise<number> => {\n    if (\n        !table ||\n        !row || Object.keys(row).length === 0 ||\n        !where || Object.keys(where).length === 0\n    ) {\n        throw new Error(\"[db] update: table, row, where가 필요합니다.\");\n    }\n    const keys = Object.keys(row);\n    const vals = keys.map((k) => row[k]);\n    const setSql = keys.map((k) => `${k} = ?`).join(\", \");\n    const { sql: whereSql, vals: whereVals } = buildWhere(where);\n    const sql = `UPDATE ${table} SET ${setSql} WHERE ${whereSql}`;\n    const [result] = conn\n        ? await conn.query(sql, [...vals, ...whereVals])\n        : await getPool().query(sql, [...vals, ...whereVals]);\n    if (!result || (result as any).affectedRows === 0) {\n        throw new Error(\"[db] update: 데이터 수정에 실패했습니다.\");\n    }\n    return (result as any).affectedRows;\n};\n\n/**\n * DELETE\n * where 조건: null(IS NULL), 배열(IN), 일반값(=) 모두 지원.\n * @returns affectedRows\n */\nexport const remove = async (\n    table: string,\n    where: Record<string, any>,\n    conn?: DbConnection\n): Promise<number> => {\n    if (!table || !where || Object.keys(where).length === 0) {\n        throw new Error(\"[db] remove: table과 where가 필요합니다.\");\n    }\n    const { sql: whereSql, vals: whereVals } = buildWhere(where);\n    const sql = `DELETE FROM ${table} WHERE ${whereSql}`;\n    const [result] = conn\n        ? await conn.query(sql, whereVals)\n        : await getPool().query(sql, whereVals);\n    if (!result || (result as any).affectedRows === 0) {\n        throw new Error(\"[db] remove: 데이터 삭제에 실패했습니다.\");\n    }\n    return (result as any).affectedRows;\n};\n","/**\n * 트랜잭션 / 커넥션 관리 유틸\n *\n * @example\n * // 트랜잭션 (에러 시 자동 롤백)\n * await withTransaction(async (conn) => {\n *   const id = await insert(\"orders\", { userId: 1 }, conn);\n *   await update(\"users\", { balance: 0 }, { id: 1 }, conn);\n * });\n *\n * @example\n * // 단일 커넥션 (트랜잭션 없이 커넥션 재사용)\n * await withConnection(async (conn) => {\n *   const user = await queryOne<User>(\"SELECT ...\", [], conn);\n *   const logs = await queryAll(\"SELECT ...\", [], conn);\n * });\n */\n\nimport { getPool, type DbConnection } from \"./configure\";\n\n/** 트랜잭션 — 에러 시 자동 롤백 후 예외를 다시 던진다. */\nexport const withTransaction = async (\n    callback: (conn: DbConnection) => Promise<void>\n): Promise<void> => {\n    const conn = await getPool().getConnection();\n    try {\n        await conn.beginTransaction();\n        await callback(conn);\n        await conn.commit();\n    } catch (err) {\n        await conn.rollback();\n        throw err;\n    } finally {\n        conn.release();\n    }\n};\n\n/** 단일 커넥션으로 여러 쿼리 처리 (트랜잭션 없음, 커넥션 자동 반환). */\nexport const withConnection = async (\n    callback: (conn: DbConnection) => Promise<void>\n): Promise<void> => {\n    const conn = await getPool().getConnection();\n    try {\n        await callback(conn);\n    } finally {\n        conn.release();\n    }\n};\n","/**\n * createWhere — SQL WHERE 절 빌더\n *\n * 메서드 체이닝으로 조건을 추가하고, build()로 SQL과 바인딩 값을 반환한다.\n * 값이 undefined / null / 빈 문자열이면 자동으로 스킵한다.\n *\n * @example\n * const w = createWhere();\n *\n * w.equal(\"U.status\", data.status);          // undefined면 스킵\n * w.like(\"U.name\", data.keyword);            // → U.name LIKE '%keyword%'\n * w.likeAny([\"U.name\", \"U.email\"], data.keyword); // 검색 유형이 \"전체\"일 때 → (U.name LIKE ? OR U.email LIKE ?)\n * w.between(\"U.createdAt\", data.startDate, data.endDate);\n * w.in(\"U.role\", [\"admin\", \"manager\"]);\n * w.raw(\"U.deletedAt IS NULL\");\n *\n * const { whereSql, whereValues } = w.build();\n * // whereSql  → \" WHERE U.name LIKE ? AND U.createdAt BETWEEN ? AND ? AND U.deletedAt IS NULL\"\n * // whereValues → [\"%keyword%\", startDate, endDate]\n */\n\ninterface WhereClause {\n    sql: string;\n    vals: unknown[];\n}\n\nexport function createWhere() {\n    const clauses: WhereClause[] = [];\n\n    const isEmpty = (v: unknown) =>\n        v === undefined || v === null || v === \"\";\n\n    return {\n        /**\n         * col = ?\n         * val이 undefined / null / \"\"이면 스킵\n         */\n        equal(col: string, val: unknown) {\n            if (!isEmpty(val)) {\n                clauses.push({ sql: `${col} = ?`, vals: [val] });\n            }\n            return this;\n        },\n\n        /**\n         * col LIKE '%val%'\n         * val이 falsy이면 스킵\n         */\n        like(col: string, val: unknown) {\n            if (val && typeof val === \"string\" && val.trim() !== \"\") {\n                clauses.push({ sql: `${col} LIKE ?`, vals: [`%${val.trim()}%`] });\n            }\n            return this;\n        },\n\n        /**\n         * (col1 LIKE ? OR col2 LIKE ? OR ...)\n         * 키워드 검색 유형이 \"전체\"일 때처럼, 여러 컬럼에 동시에 LIKE 검색할 때 사용.\n         * val이 falsy이거나 cols가 빈 배열이면 스킵.\n         * @example w.likeAny([\"U.name\", \"U.email\"], data.keyword)\n         */\n        likeAny(cols: string[], val: unknown) {\n            if (cols.length > 0 && val && typeof val === \"string\" && val.trim() !== \"\") {\n                const kw = `%${val.trim()}%`;\n                clauses.push({\n                    sql: `(${cols.map((c) => `${c} LIKE ?`).join(\" OR \")})`,\n                    vals: cols.map(() => kw),\n                });\n            }\n            return this;\n        },\n\n        /**\n         * col BETWEEN ? AND ?\n         * start 또는 end가 falsy이면 스킵\n         */\n        between(col: string, start: unknown, end: unknown) {\n            if (start && end) {\n                clauses.push({ sql: `${col} BETWEEN ? AND ?`, vals: [start, end] });\n            }\n            return this;\n        },\n\n        /**\n         * col IN (?, ?, ...)\n         * vals가 빈 배열이면 스킵\n         */\n        in(col: string, vals: unknown[]) {\n            if (vals.length > 0) {\n                const placeholders = vals.map(() => \"?\").join(\", \");\n                clauses.push({ sql: `${col} IN (${placeholders})`, vals });\n            }\n            return this;\n        },\n\n        /**\n         * col IS NULL\n         */\n        isNull(col: string) {\n            clauses.push({ sql: `${col} IS NULL`, vals: [] });\n            return this;\n        },\n\n        /**\n         * col IS NOT NULL\n         */\n        isNotNull(col: string) {\n            clauses.push({ sql: `${col} IS NOT NULL`, vals: [] });\n            return this;\n        },\n\n        /**\n         * 직접 SQL 조각과 바인딩 값을 추가\n         * @example w.raw(\"(U.role = ? OR U.role = ?)\", [\"admin\", \"manager\"])\n         */\n        raw(sql: string, vals: unknown[] = []) {\n            clauses.push({ sql, vals });\n            return this;\n        },\n\n        /** WHERE 절 SQL과 바인딩 값 배열을 반환 */\n        build(): { whereSql: string; whereValues: unknown[] } {\n            if (clauses.length === 0) {\n                return { whereSql: \"\", whereValues: [] };\n            }\n            const whereSql = \" WHERE \" + clauses.map((c) => c.sql).join(\" AND \");\n            const whereValues = clauses.flatMap((c) => c.vals);\n            return { whereSql, whereValues };\n        },\n    };\n}\n","/**\n * SQL 빌더 유틸 — ORDER BY / LIMIT OFFSET\n *\n * @example\n * const orderSql = buildOrderBy(\n *   { name: \"U.name\", createdAt: \"U.createAt\" },\n *   data.sortKey,\n *   data.sortType,\n *   \"U.id DESC\"   // 기본 정렬 (sortKey 없을 때)\n * );\n * // → \" ORDER BY U.name ASC\"\n *\n * @example\n * const { sql: pageSql, values: pageValues } = buildPagination(data.page, data.pageSize);\n * // → { sql: \" LIMIT ?, ?\", values: [20, 20] }\n *\n * @example\n * const list = await queryAll(\n *   `SELECT * FROM users ${orderSql} ${pageSql}`,\n *   [...whereValues, ...pageValues]\n * );\n */\n\n/** 정렬 컬럼 맵 — { [sortKey]: \"실제컬럼명\" } */\nexport type SortColumns = Record<string, string>;\n\n/**\n * ORDER BY 절 생성\n * @param columns   sortKey → 실제 컬럼명 맵\n * @param sortKey   요청에서 받은 정렬 키\n * @param sortType  \"ASC\" | \"DESC\" (대소문자 무관)\n * @param defaultSql  sortKey가 없거나 맵에 없을 때 사용할 기본 정렬 SQL (컬럼명만, ORDER BY 제외)\n */\nexport function buildOrderBy(\n    columns: SortColumns,\n    sortKey: string | undefined,\n    sortType: string | undefined,\n    defaultSql = \"\"\n): string {\n    const col = sortKey ? columns[sortKey] : undefined;\n    const direction =\n        sortType?.toUpperCase() === \"ASC\" || sortType?.toUpperCase() === \"DESC\"\n            ? sortType.toUpperCase()\n            : undefined;\n\n    if (col && direction) {\n        return ` ORDER BY ${col} ${direction}`;\n    }\n    if (defaultSql) {\n        return ` ORDER BY ${defaultSql}`;\n    }\n    return \"\";\n}\n\n/**\n * LIMIT / OFFSET 절 생성\n * page 또는 pageSize가 없으면 빈 문자열과 빈 배열을 반환한다.\n * @returns { sql, values } — values를 query params에 스프레드해서 사용\n */\nexport function buildPagination(\n    page: number | undefined,\n    pageSize: number | undefined\n): { sql: string; values: number[] } {\n    if (!page || !pageSize) {\n        return { sql: \"\", values: [] };\n    }\n    const offset = (page - 1) * pageSize;\n    return { sql: \" LIMIT ?, ?\", values: [offset, pageSize] };\n}\n"]}