@hughpeng/mysql 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @hughpeng/mysql
2
+
3
+ 基于 [mysql2](https://github.com/sidorares/node-mysql2) 的 MySQL 连接池与 CRUD 封装,支持预编译参数、批量插入、`ON DUPLICATE KEY UPDATE` 与事务。
4
+
5
+ ## 安装
6
+
7
+ 在 monorepo 内其它包中引用:
8
+
9
+ ```json
10
+ {
11
+ "dependencies": {
12
+ "@hughpeng/mysql": "workspace:*"
13
+ }
14
+ }
15
+ ```
16
+
17
+ 根目录执行 `pnpm install` 后使用。
18
+
19
+ ## 快速开始
20
+
21
+ ```typescript
22
+ import { MySQLClient, type RowDataPacket } from '@hughpeng/mysql'
23
+
24
+ const db = new MySQLClient({
25
+ host: '127.0.0.1',
26
+ port: 3306,
27
+ user: 'root',
28
+ password: 'secret',
29
+ database: 'app',
30
+ })
31
+
32
+ interface UserRow extends RowDataPacket {
33
+ id: number
34
+ name: string
35
+ }
36
+
37
+ const users = await db.query<UserRow[]>('SELECT id, name FROM users WHERE status = ?', [1])
38
+
39
+ await db.insert('users', { name: 'Alice', status: 1 })
40
+
41
+ await db.close()
42
+ ```
43
+
44
+ 配置项与 mysql2 的 [`PoolOptions`](https://github.com/sidorares/node-mysql2#pool-options) 一致;连接池默认 `waitForConnections: true`、`connectionLimit: 10`,可在构造时覆盖。
45
+
46
+ ## API
47
+
48
+ ### `MySQLClient`
49
+
50
+ | 方法 | 说明 |
51
+ |------|------|
52
+ | `query(sql, params?)` | 预编译查询;SELECT 返回行数组,写操作返回 `ResultSetHeader` |
53
+ | `insert(table, row, options?)` | 单行插入 |
54
+ | `insertMany(table, rows, options?)` | 多行插入(各行列名须一致) |
55
+ | `update(table, data, where)` | 等值条件更新;**必须**提供 `where` |
56
+ | `delete(table, where)` | 等值条件删除;**必须**提供 `where` |
57
+ | `transaction(fn)` | 事务:成功 commit,失败 rollback |
58
+ | `close()` | 关闭连接池 |
59
+
60
+ ### `MySQLSession`
61
+
62
+ 事务回调内使用的会话,API 与 `MySQLClient` 上的 CRUD 一致:
63
+
64
+ ```typescript
65
+ await db.transaction(async (tx) => {
66
+ await tx.insert('orders', { user_id: 1, amount: 100 })
67
+ await tx.update('users', { balance: 900 }, { id: 1 })
68
+ })
69
+ ```
70
+
71
+ ### `ON DUPLICATE KEY UPDATE`
72
+
73
+ ```typescript
74
+ await db.insert(
75
+ 'users',
76
+ { id: 1, name: 'Bob', score: 10 },
77
+ { onDuplicateKeyUpdate: ['name', 'score'] },
78
+ )
79
+ // INSERT ... ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `score` = VALUES(`score`)
80
+ ```
81
+
82
+ `onDuplicateKeyUpdate` 中的列必须出现在插入数据中。
83
+
84
+ ## 示例
85
+
86
+ ### 查询
87
+
88
+ ```typescript
89
+ const rows = await db.query<RowDataPacket[]>(
90
+ 'SELECT * FROM products WHERE category_id = ? LIMIT ?',
91
+ [3, 20],
92
+ )
93
+ ```
94
+
95
+ ### 批量插入
96
+
97
+ ```typescript
98
+ await db.insertMany('tags', [
99
+ { name: 'a', sort: 1 },
100
+ { name: 'b', sort: 2 },
101
+ ])
102
+ ```
103
+
104
+ ### 更新 / 删除
105
+
106
+ ```typescript
107
+ await db.update('users', { status: 0 }, { id: 42 })
108
+ await db.delete('sessions', { user_id: 42 })
109
+ ```
110
+
111
+ ## 导出类型
112
+
113
+ | 类型 | 说明 |
114
+ |------|------|
115
+ | `MySQLClient` | 连接池客户端 |
116
+ | `MySQLSession` | 事务内会话 |
117
+ | `MySQLQueryable` | 具备 `execute` 的目标(池或连接) |
118
+ | `MySQLInsertOptions` | 插入选项(含 `onDuplicateKeyUpdate`) |
119
+ | `PoolOptions` | mysql2 连接池配置 |
120
+ | `RowDataPacket` | 查询行类型基类 |
121
+ | `ResultSetHeader` | 写操作结果(`insertId`、`affectedRows` 等) |
122
+ | `ExecuteValues` | 预编译参数类型 |
123
+
124
+ ## 注意事项
125
+
126
+ - `update` / `delete` 在 `where` 为空时会抛错,防止误操作全表。
127
+ - 表名、列名通过反引号转义;**不要**把用户输入直接拼进表名/列名,只通过 `?` 占位符传值。
128
+ - 事务内请始终使用回调参数 `tx`,勿与外层 `db` 混用同一笔业务。
129
+ - 进程退出或测试 teardown 时调用 `close()` 释放连接池。
130
+ - 查询时limit、offset传入数字类型参数时占位解析异常,必须使用字符,例如:`String(2)`
131
+
132
+ ## 开发
133
+
134
+ ```bash
135
+ pnpm build
136
+ pnpm dev
137
+ pnpm lint
138
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var m=require('mysql2/promise');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var m__default=/*#__PURE__*/_interopDefault(m);function n(u){return `\`${String(u).replaceAll("`","``")}\``}function g(u,o){let e=o?.onDuplicateKeyUpdate;if(!e?.length)return "";let t=new Set(u),s=[];for(let r of e){if(!t.has(r))throw new Error(`onDuplicateKeyUpdate: \u5217 "${r}" \u987B\u51FA\u73B0\u5728\u63D2\u5165\u5217\u4E2D\uFF0C\u624D\u80FD\u4F7F\u7528 VALUES(${r})`);s.push(`${n(r)} = VALUES(${n(r)})`);}return ` ON DUPLICATE KEY UPDATE ${s.join(", ")}`}var h=class{constructor(o){this.target=o;}async query(o,e){let[t]=await this.target.execute(o,e);return t}insert(o,e,t){let s=Object.keys(e);if(s.length===0)throw new Error("insert: row \u4E0D\u80FD\u4E3A\u7A7A");let r=s.map(l=>n(l)).join(", "),i=s.map(()=>"?").join(", "),p=g(s,t),y=`INSERT INTO ${n(o)} (${r}) VALUES (${i})${p}`;return this.query(y,s.map(l=>e[l]))}insertMany(o,e,t){if(e.length===0)throw new Error("insertMany: rows \u4E0D\u80FD\u4E3A\u7A7A");let s=Object.keys(e[0]);if(s.length===0)throw new Error("insertMany: \u7B2C\u4E00\u884C\u5217\u4E0D\u80FD\u4E3A\u7A7A");let r=new Set(s);for(let c=1;c<e.length;c++){let d=Object.keys(e[c]);if(d.length!==s.length||!d.every(R=>r.has(R)))throw new Error("insertMany: \u5404\u884C\u5217\u540D\u5FC5\u987B\u4E00\u81F4")}let i=s.map(c=>n(c)).join(", "),p=`(${s.map(()=>"?").join(", ")})`,y=e.map(()=>p).join(", "),l=g(s,t),a=`INSERT INTO ${n(o)} (${i}) VALUES ${y}${l}`,w=e.flatMap(c=>s.map(d=>c[d]));return this.query(a,w)}update(o,e,t){let s=Object.keys(e),r=Object.keys(t);if(s.length===0)throw new Error("update: data \u4E0D\u80FD\u4E3A\u7A7A");if(r.length===0)throw new Error("update: \u5FC5\u987B\u63D0\u4F9B where\uFF0C\u907F\u514D\u5168\u8868\u66F4\u65B0");let i=s.map(a=>`${n(a)} = ?`).join(", "),p=r.map(a=>`${n(a)} = ?`).join(" AND "),y=`UPDATE ${n(o)} SET ${i} WHERE ${p}`,l=[...s.map(a=>e[a]),...r.map(a=>t[a])];return this.query(y,l)}delete(o,e){let t=Object.keys(e);if(t.length===0)throw new Error("delete: \u5FC5\u987B\u63D0\u4F9B where\uFF0C\u907F\u514D\u5168\u8868\u5220\u9664");let s=t.map(i=>`${n(i)} = ?`).join(" AND "),r=`DELETE FROM ${n(o)} WHERE ${s}`;return this.query(r,t.map(i=>e[i]))}},E=class{constructor(o){this.options=o;}pool=null;getPool(){return this.pool?this.pool:(this.pool=m__default.default.createPool({waitForConnections:true,connectionLimit:10,...this.options}),this.pool)}session(){return new h(this.getPool())}query(o,e){return this.session().query(o,e)}insert(o,e,t){return this.session().insert(o,e,t)}insertMany(o,e,t){return this.session().insertMany(o,e,t)}update(o,e,t){return this.session().update(o,e,t)}delete(o,e){return this.session().delete(o,e)}async transaction(o){let t=await this.getPool().getConnection();try{await t.beginTransaction();let s=await o(new h(t));return await t.commit(),s}catch(s){throw await t.rollback(),s}finally{t.release();}}async close(){this.pool&&(await this.pool.end(),this.pool=null);}};exports.MySQLClient=E;exports.MySQLSession=h;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["quoteId","name","onDuplicateKeyUpdateClause","insertColumns","options","cols","allowed","parts","c","MySQLSession","target","sql","params","rows","table","row","keys","k","placeholders","suffix","keySet","i","k2","oneRow","valuesClause","r","data","where","setKeys","whereKeys","setClause","whereClause","values","MySQLClient","mysql","fn","conn","out","error"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ExecuteValues } from 'mysql2'\nimport mysql, {\n type Pool,\n type PoolConnection,\n type PoolOptions,\n type ResultSetHeader,\n type RowDataPacket,\n} from 'mysql2/promise'\n\nfunction quoteId(name: string): string {\n return `\\`${String(name).replaceAll('`', '``')}\\``\n}\n\n/** 具备 `execute` 的目标:连接池或事务内的连接,CRUD API 一致 */\nexport type MySQLQueryable = Pick<Pool, 'execute'>\n\nexport type { ExecuteValues, PoolConnection, PoolOptions, ResultSetHeader, RowDataPacket }\n\n/** 单行 / 多行插入的选项 */\nexport interface MySQLInsertOptions {\n /**\n * 主键或唯一键冲突时,用本次插入的值更新这些列(`ON DUPLICATE KEY UPDATE col = VALUES(col)`)。\n * 列名必须出现在插入数据中,否则无法与 `VALUES()` 对应。\n */\n onDuplicateKeyUpdate?: string[]\n}\n\nfunction onDuplicateKeyUpdateClause(insertColumns: string[], options?: MySQLInsertOptions): string {\n const cols = options?.onDuplicateKeyUpdate\n if (!cols?.length) {\n return ''\n }\n const allowed = new Set(insertColumns)\n const parts: string[] = []\n for (const c of cols) {\n if (!allowed.has(c)) {\n throw new Error(`onDuplicateKeyUpdate: 列 \"${c}\" 须出现在插入列中,才能使用 VALUES(${c})`)\n }\n parts.push(`${quoteId(c)} = VALUES(${quoteId(c)})`)\n }\n return ` ON DUPLICATE KEY UPDATE ${parts.join(', ')}`\n}\n\n/**\n * 在指定 `execute` 目标上执行 query / insert / update / delete。\n * 事务内使用 {@link MySQLClient.transaction} 传入的会话实例,勿与外层 client 混用。\n */\nexport class MySQLSession {\n constructor(private readonly target: MySQLQueryable) {}\n\n /** 执行 SQL(预编译参数),SELECT 返回行数组,写操作返回 {@link ResultSetHeader} */\n async query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(\n sql: string,\n params?: ExecuteValues,\n ): Promise<T> {\n const [rows] = await this.target.execute(sql, params)\n return rows as T\n }\n\n /**\n * 单行插入,键为列名。\n * 多行请用 {@link MySQLSession.insertMany}。\n */\n insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader> {\n const keys = Object.keys(row)\n if (keys.length === 0) {\n throw new Error('insert: row 不能为空')\n }\n const cols = keys.map(k => quoteId(k)).join(', ')\n const placeholders = keys.map(() => '?').join(', ')\n const suffix = onDuplicateKeyUpdateClause(keys, options)\n const sql = `INSERT INTO ${quoteId(table)} (${cols}) VALUES (${placeholders})${suffix}`\n return this.query<ResultSetHeader>(\n sql,\n keys.map(k => row[k]),\n )\n }\n\n /**\n * 多行插入,每行对象键为列名;所有行的键集合必须一致。\n * 可选 {@link MySQLInsertOptions.onDuplicateKeyUpdate} 实现主键/唯一键冲突时按本次插入值更新。\n */\n insertMany(\n table: string,\n rows: Record<string, ExecuteValues>[],\n options?: MySQLInsertOptions,\n ): Promise<ResultSetHeader> {\n if (rows.length === 0) {\n throw new Error('insertMany: rows 不能为空')\n }\n const keys = Object.keys(rows[0])\n if (keys.length === 0) {\n throw new Error('insertMany: 第一行列不能为空')\n }\n const keySet = new Set(keys)\n for (let i = 1; i < rows.length; i++) {\n const k2 = Object.keys(rows[i])\n if (k2.length !== keys.length || !k2.every(k => keySet.has(k))) {\n throw new Error('insertMany: 各行列名必须一致')\n }\n }\n const cols = keys.map(k => quoteId(k)).join(', ')\n const oneRow = `(${keys.map(() => '?').join(', ')})`\n const valuesClause = rows.map(() => oneRow).join(', ')\n const suffix = onDuplicateKeyUpdateClause(keys, options)\n const sql = `INSERT INTO ${quoteId(table)} (${cols}) VALUES ${valuesClause}${suffix}`\n const params = rows.flatMap(r => keys.map(k => r[k]))\n return this.query<ResultSetHeader>(sql, params)\n }\n\n /**\n * 按等值条件更新。where 为空会抛错,避免误更新全表。\n */\n update(\n table: string,\n data: Record<string, ExecuteValues>,\n where: Record<string, ExecuteValues>,\n ): Promise<ResultSetHeader> {\n const setKeys = Object.keys(data)\n const whereKeys = Object.keys(where)\n if (setKeys.length === 0) {\n throw new Error('update: data 不能为空')\n }\n\n if (whereKeys.length === 0) {\n throw new Error('update: 必须提供 where,避免全表更新')\n }\n const setClause = setKeys.map(k => `${quoteId(k)} = ?`).join(', ')\n const whereClause = whereKeys.map(k => `${quoteId(k)} = ?`).join(' AND ')\n const sql = `UPDATE ${quoteId(table)} SET ${setClause} WHERE ${whereClause}`\n const values = [...setKeys.map(k => data[k]), ...whereKeys.map(k => where[k])]\n return this.query<ResultSetHeader>(sql, values)\n }\n\n /**\n * 按等值条件删除。where 为空会抛错,避免误删全表。\n */\n delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader> {\n const whereKeys = Object.keys(where)\n if (whereKeys.length === 0) {\n throw new Error('delete: 必须提供 where,避免全表删除')\n }\n const whereClause = whereKeys.map(k => `${quoteId(k)} = ?`).join(' AND ')\n const sql = `DELETE FROM ${quoteId(table)} WHERE ${whereClause}`\n return this.query<ResultSetHeader>(\n sql,\n whereKeys.map(k => where[k]),\n )\n }\n}\n\n/**\n * 基于 mysql2 连接池:查询、增删改、事务。\n * 配置项见 {@link PoolOptions}(host、user、password、database 等)。\n */\nexport class MySQLClient {\n private pool: Pool | null = null\n\n constructor(private readonly options: PoolOptions) {}\n private getPool(): Pool {\n if (this.pool) {\n return this.pool\n }\n this.pool = mysql.createPool({\n waitForConnections: true,\n connectionLimit: 10,\n ...this.options,\n })\n return this.pool\n }\n\n private session(): MySQLSession {\n return new MySQLSession(this.getPool())\n }\n\n query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(\n sql: string,\n params?: ExecuteValues,\n ): Promise<T> {\n return this.session().query<T>(sql, params)\n }\n\n insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader> {\n return this.session().insert(table, row, options)\n }\n\n insertMany(\n table: string,\n rows: Record<string, ExecuteValues>[],\n options?: MySQLInsertOptions,\n ): Promise<ResultSetHeader> {\n return this.session().insertMany(table, rows, options)\n }\n\n update(\n table: string,\n data: Record<string, ExecuteValues>,\n where: Record<string, ExecuteValues>,\n ): Promise<ResultSetHeader> {\n return this.session().update(table, data, where)\n }\n\n delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader> {\n return this.session().delete(table, where)\n }\n\n /**\n * 在事务中执行回调;成功 commit,失败 rollback。\n * 回调参数为 {@link MySQLSession},与 client 上 `query` / `insert` / `insertMany` / `update` / `delete` 用法一致。\n */\n async transaction<T>(fn: (tx: MySQLSession) => Promise<T>): Promise<T> {\n const pool = this.getPool()\n const conn = await pool.getConnection()\n try {\n await conn.beginTransaction()\n const out = await fn(new MySQLSession(conn))\n await conn.commit()\n return out\n } catch (error) {\n await conn.rollback()\n throw error\n } finally {\n conn.release()\n }\n }\n\n /** 关闭连接池 */\n async close(): Promise<void> {\n if (this.pool) {\n await this.pool.end()\n this.pool = null\n }\n }\n}\n"]}
@@ -0,0 +1,69 @@
1
+ import { ExecuteValues } from 'mysql2';
2
+ export { ExecuteValues } from 'mysql2';
3
+ import { PoolOptions, RowDataPacket, ResultSetHeader, Pool } from 'mysql2/promise';
4
+ export { PoolConnection, PoolOptions, ResultSetHeader, RowDataPacket } from 'mysql2/promise';
5
+
6
+ /** 具备 `execute` 的目标:连接池或事务内的连接,CRUD API 一致 */
7
+ type MySQLQueryable = Pick<Pool, 'execute'>;
8
+
9
+ /** 单行 / 多行插入的选项 */
10
+ interface MySQLInsertOptions {
11
+ /**
12
+ * 主键或唯一键冲突时,用本次插入的值更新这些列(`ON DUPLICATE KEY UPDATE col = VALUES(col)`)。
13
+ * 列名必须出现在插入数据中,否则无法与 `VALUES()` 对应。
14
+ */
15
+ onDuplicateKeyUpdate?: string[];
16
+ }
17
+ /**
18
+ * 在指定 `execute` 目标上执行 query / insert / update / delete。
19
+ * 事务内使用 {@link MySQLClient.transaction} 传入的会话实例,勿与外层 client 混用。
20
+ */
21
+ declare class MySQLSession {
22
+ private readonly target;
23
+ constructor(target: MySQLQueryable);
24
+ /** 执行 SQL(预编译参数),SELECT 返回行数组,写操作返回 {@link ResultSetHeader} */
25
+ query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(sql: string, params?: ExecuteValues): Promise<T>;
26
+ /**
27
+ * 单行插入,键为列名。
28
+ * 多行请用 {@link MySQLSession.insertMany}。
29
+ */
30
+ insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader>;
31
+ /**
32
+ * 多行插入,每行对象键为列名;所有行的键集合必须一致。
33
+ * 可选 {@link MySQLInsertOptions.onDuplicateKeyUpdate} 实现主键/唯一键冲突时按本次插入值更新。
34
+ */
35
+ insertMany(table: string, rows: Record<string, ExecuteValues>[], options?: MySQLInsertOptions): Promise<ResultSetHeader>;
36
+ /**
37
+ * 按等值条件更新。where 为空会抛错,避免误更新全表。
38
+ */
39
+ update(table: string, data: Record<string, ExecuteValues>, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
40
+ /**
41
+ * 按等值条件删除。where 为空会抛错,避免误删全表。
42
+ */
43
+ delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
44
+ }
45
+ /**
46
+ * 基于 mysql2 连接池:查询、增删改、事务。
47
+ * 配置项见 {@link PoolOptions}(host、user、password、database 等)。
48
+ */
49
+ declare class MySQLClient {
50
+ private readonly options;
51
+ private pool;
52
+ constructor(options: PoolOptions);
53
+ private getPool;
54
+ private session;
55
+ query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(sql: string, params?: ExecuteValues): Promise<T>;
56
+ insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader>;
57
+ insertMany(table: string, rows: Record<string, ExecuteValues>[], options?: MySQLInsertOptions): Promise<ResultSetHeader>;
58
+ update(table: string, data: Record<string, ExecuteValues>, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
59
+ delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
60
+ /**
61
+ * 在事务中执行回调;成功 commit,失败 rollback。
62
+ * 回调参数为 {@link MySQLSession},与 client 上 `query` / `insert` / `insertMany` / `update` / `delete` 用法一致。
63
+ */
64
+ transaction<T>(fn: (tx: MySQLSession) => Promise<T>): Promise<T>;
65
+ /** 关闭连接池 */
66
+ close(): Promise<void>;
67
+ }
68
+
69
+ export { MySQLClient, type MySQLInsertOptions, type MySQLQueryable, MySQLSession };
@@ -0,0 +1,69 @@
1
+ import { ExecuteValues } from 'mysql2';
2
+ export { ExecuteValues } from 'mysql2';
3
+ import { PoolOptions, RowDataPacket, ResultSetHeader, Pool } from 'mysql2/promise';
4
+ export { PoolConnection, PoolOptions, ResultSetHeader, RowDataPacket } from 'mysql2/promise';
5
+
6
+ /** 具备 `execute` 的目标:连接池或事务内的连接,CRUD API 一致 */
7
+ type MySQLQueryable = Pick<Pool, 'execute'>;
8
+
9
+ /** 单行 / 多行插入的选项 */
10
+ interface MySQLInsertOptions {
11
+ /**
12
+ * 主键或唯一键冲突时,用本次插入的值更新这些列(`ON DUPLICATE KEY UPDATE col = VALUES(col)`)。
13
+ * 列名必须出现在插入数据中,否则无法与 `VALUES()` 对应。
14
+ */
15
+ onDuplicateKeyUpdate?: string[];
16
+ }
17
+ /**
18
+ * 在指定 `execute` 目标上执行 query / insert / update / delete。
19
+ * 事务内使用 {@link MySQLClient.transaction} 传入的会话实例,勿与外层 client 混用。
20
+ */
21
+ declare class MySQLSession {
22
+ private readonly target;
23
+ constructor(target: MySQLQueryable);
24
+ /** 执行 SQL(预编译参数),SELECT 返回行数组,写操作返回 {@link ResultSetHeader} */
25
+ query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(sql: string, params?: ExecuteValues): Promise<T>;
26
+ /**
27
+ * 单行插入,键为列名。
28
+ * 多行请用 {@link MySQLSession.insertMany}。
29
+ */
30
+ insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader>;
31
+ /**
32
+ * 多行插入,每行对象键为列名;所有行的键集合必须一致。
33
+ * 可选 {@link MySQLInsertOptions.onDuplicateKeyUpdate} 实现主键/唯一键冲突时按本次插入值更新。
34
+ */
35
+ insertMany(table: string, rows: Record<string, ExecuteValues>[], options?: MySQLInsertOptions): Promise<ResultSetHeader>;
36
+ /**
37
+ * 按等值条件更新。where 为空会抛错,避免误更新全表。
38
+ */
39
+ update(table: string, data: Record<string, ExecuteValues>, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
40
+ /**
41
+ * 按等值条件删除。where 为空会抛错,避免误删全表。
42
+ */
43
+ delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
44
+ }
45
+ /**
46
+ * 基于 mysql2 连接池:查询、增删改、事务。
47
+ * 配置项见 {@link PoolOptions}(host、user、password、database 等)。
48
+ */
49
+ declare class MySQLClient {
50
+ private readonly options;
51
+ private pool;
52
+ constructor(options: PoolOptions);
53
+ private getPool;
54
+ private session;
55
+ query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(sql: string, params?: ExecuteValues): Promise<T>;
56
+ insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader>;
57
+ insertMany(table: string, rows: Record<string, ExecuteValues>[], options?: MySQLInsertOptions): Promise<ResultSetHeader>;
58
+ update(table: string, data: Record<string, ExecuteValues>, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
59
+ delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader>;
60
+ /**
61
+ * 在事务中执行回调;成功 commit,失败 rollback。
62
+ * 回调参数为 {@link MySQLSession},与 client 上 `query` / `insert` / `insertMany` / `update` / `delete` 用法一致。
63
+ */
64
+ transaction<T>(fn: (tx: MySQLSession) => Promise<T>): Promise<T>;
65
+ /** 关闭连接池 */
66
+ close(): Promise<void>;
67
+ }
68
+
69
+ export { MySQLClient, type MySQLInsertOptions, type MySQLQueryable, MySQLSession };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import m from'mysql2/promise';function n(u){return `\`${String(u).replaceAll("`","``")}\``}function g(u,o){let e=o?.onDuplicateKeyUpdate;if(!e?.length)return "";let t=new Set(u),s=[];for(let r of e){if(!t.has(r))throw new Error(`onDuplicateKeyUpdate: \u5217 "${r}" \u987B\u51FA\u73B0\u5728\u63D2\u5165\u5217\u4E2D\uFF0C\u624D\u80FD\u4F7F\u7528 VALUES(${r})`);s.push(`${n(r)} = VALUES(${n(r)})`);}return ` ON DUPLICATE KEY UPDATE ${s.join(", ")}`}var h=class{constructor(o){this.target=o;}async query(o,e){let[t]=await this.target.execute(o,e);return t}insert(o,e,t){let s=Object.keys(e);if(s.length===0)throw new Error("insert: row \u4E0D\u80FD\u4E3A\u7A7A");let r=s.map(l=>n(l)).join(", "),i=s.map(()=>"?").join(", "),p=g(s,t),y=`INSERT INTO ${n(o)} (${r}) VALUES (${i})${p}`;return this.query(y,s.map(l=>e[l]))}insertMany(o,e,t){if(e.length===0)throw new Error("insertMany: rows \u4E0D\u80FD\u4E3A\u7A7A");let s=Object.keys(e[0]);if(s.length===0)throw new Error("insertMany: \u7B2C\u4E00\u884C\u5217\u4E0D\u80FD\u4E3A\u7A7A");let r=new Set(s);for(let c=1;c<e.length;c++){let d=Object.keys(e[c]);if(d.length!==s.length||!d.every(R=>r.has(R)))throw new Error("insertMany: \u5404\u884C\u5217\u540D\u5FC5\u987B\u4E00\u81F4")}let i=s.map(c=>n(c)).join(", "),p=`(${s.map(()=>"?").join(", ")})`,y=e.map(()=>p).join(", "),l=g(s,t),a=`INSERT INTO ${n(o)} (${i}) VALUES ${y}${l}`,w=e.flatMap(c=>s.map(d=>c[d]));return this.query(a,w)}update(o,e,t){let s=Object.keys(e),r=Object.keys(t);if(s.length===0)throw new Error("update: data \u4E0D\u80FD\u4E3A\u7A7A");if(r.length===0)throw new Error("update: \u5FC5\u987B\u63D0\u4F9B where\uFF0C\u907F\u514D\u5168\u8868\u66F4\u65B0");let i=s.map(a=>`${n(a)} = ?`).join(", "),p=r.map(a=>`${n(a)} = ?`).join(" AND "),y=`UPDATE ${n(o)} SET ${i} WHERE ${p}`,l=[...s.map(a=>e[a]),...r.map(a=>t[a])];return this.query(y,l)}delete(o,e){let t=Object.keys(e);if(t.length===0)throw new Error("delete: \u5FC5\u987B\u63D0\u4F9B where\uFF0C\u907F\u514D\u5168\u8868\u5220\u9664");let s=t.map(i=>`${n(i)} = ?`).join(" AND "),r=`DELETE FROM ${n(o)} WHERE ${s}`;return this.query(r,t.map(i=>e[i]))}},E=class{constructor(o){this.options=o;}pool=null;getPool(){return this.pool?this.pool:(this.pool=m.createPool({waitForConnections:true,connectionLimit:10,...this.options}),this.pool)}session(){return new h(this.getPool())}query(o,e){return this.session().query(o,e)}insert(o,e,t){return this.session().insert(o,e,t)}insertMany(o,e,t){return this.session().insertMany(o,e,t)}update(o,e,t){return this.session().update(o,e,t)}delete(o,e){return this.session().delete(o,e)}async transaction(o){let t=await this.getPool().getConnection();try{await t.beginTransaction();let s=await o(new h(t));return await t.commit(),s}catch(s){throw await t.rollback(),s}finally{t.release();}}async close(){this.pool&&(await this.pool.end(),this.pool=null);}};export{E as MySQLClient,h as MySQLSession};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["quoteId","name","onDuplicateKeyUpdateClause","insertColumns","options","cols","allowed","parts","c","MySQLSession","target","sql","params","rows","table","row","keys","k","placeholders","suffix","keySet","i","k2","oneRow","valuesClause","r","data","where","setKeys","whereKeys","setClause","whereClause","values","MySQLClient","mysql","fn","conn","out","error"],"mappings":"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","file":"index.js","sourcesContent":["import type { ExecuteValues } from 'mysql2'\nimport mysql, {\n type Pool,\n type PoolConnection,\n type PoolOptions,\n type ResultSetHeader,\n type RowDataPacket,\n} from 'mysql2/promise'\n\nfunction quoteId(name: string): string {\n return `\\`${String(name).replaceAll('`', '``')}\\``\n}\n\n/** 具备 `execute` 的目标:连接池或事务内的连接,CRUD API 一致 */\nexport type MySQLQueryable = Pick<Pool, 'execute'>\n\nexport type { ExecuteValues, PoolConnection, PoolOptions, ResultSetHeader, RowDataPacket }\n\n/** 单行 / 多行插入的选项 */\nexport interface MySQLInsertOptions {\n /**\n * 主键或唯一键冲突时,用本次插入的值更新这些列(`ON DUPLICATE KEY UPDATE col = VALUES(col)`)。\n * 列名必须出现在插入数据中,否则无法与 `VALUES()` 对应。\n */\n onDuplicateKeyUpdate?: string[]\n}\n\nfunction onDuplicateKeyUpdateClause(insertColumns: string[], options?: MySQLInsertOptions): string {\n const cols = options?.onDuplicateKeyUpdate\n if (!cols?.length) {\n return ''\n }\n const allowed = new Set(insertColumns)\n const parts: string[] = []\n for (const c of cols) {\n if (!allowed.has(c)) {\n throw new Error(`onDuplicateKeyUpdate: 列 \"${c}\" 须出现在插入列中,才能使用 VALUES(${c})`)\n }\n parts.push(`${quoteId(c)} = VALUES(${quoteId(c)})`)\n }\n return ` ON DUPLICATE KEY UPDATE ${parts.join(', ')}`\n}\n\n/**\n * 在指定 `execute` 目标上执行 query / insert / update / delete。\n * 事务内使用 {@link MySQLClient.transaction} 传入的会话实例,勿与外层 client 混用。\n */\nexport class MySQLSession {\n constructor(private readonly target: MySQLQueryable) {}\n\n /** 执行 SQL(预编译参数),SELECT 返回行数组,写操作返回 {@link ResultSetHeader} */\n async query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(\n sql: string,\n params?: ExecuteValues,\n ): Promise<T> {\n const [rows] = await this.target.execute(sql, params)\n return rows as T\n }\n\n /**\n * 单行插入,键为列名。\n * 多行请用 {@link MySQLSession.insertMany}。\n */\n insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader> {\n const keys = Object.keys(row)\n if (keys.length === 0) {\n throw new Error('insert: row 不能为空')\n }\n const cols = keys.map(k => quoteId(k)).join(', ')\n const placeholders = keys.map(() => '?').join(', ')\n const suffix = onDuplicateKeyUpdateClause(keys, options)\n const sql = `INSERT INTO ${quoteId(table)} (${cols}) VALUES (${placeholders})${suffix}`\n return this.query<ResultSetHeader>(\n sql,\n keys.map(k => row[k]),\n )\n }\n\n /**\n * 多行插入,每行对象键为列名;所有行的键集合必须一致。\n * 可选 {@link MySQLInsertOptions.onDuplicateKeyUpdate} 实现主键/唯一键冲突时按本次插入值更新。\n */\n insertMany(\n table: string,\n rows: Record<string, ExecuteValues>[],\n options?: MySQLInsertOptions,\n ): Promise<ResultSetHeader> {\n if (rows.length === 0) {\n throw new Error('insertMany: rows 不能为空')\n }\n const keys = Object.keys(rows[0])\n if (keys.length === 0) {\n throw new Error('insertMany: 第一行列不能为空')\n }\n const keySet = new Set(keys)\n for (let i = 1; i < rows.length; i++) {\n const k2 = Object.keys(rows[i])\n if (k2.length !== keys.length || !k2.every(k => keySet.has(k))) {\n throw new Error('insertMany: 各行列名必须一致')\n }\n }\n const cols = keys.map(k => quoteId(k)).join(', ')\n const oneRow = `(${keys.map(() => '?').join(', ')})`\n const valuesClause = rows.map(() => oneRow).join(', ')\n const suffix = onDuplicateKeyUpdateClause(keys, options)\n const sql = `INSERT INTO ${quoteId(table)} (${cols}) VALUES ${valuesClause}${suffix}`\n const params = rows.flatMap(r => keys.map(k => r[k]))\n return this.query<ResultSetHeader>(sql, params)\n }\n\n /**\n * 按等值条件更新。where 为空会抛错,避免误更新全表。\n */\n update(\n table: string,\n data: Record<string, ExecuteValues>,\n where: Record<string, ExecuteValues>,\n ): Promise<ResultSetHeader> {\n const setKeys = Object.keys(data)\n const whereKeys = Object.keys(where)\n if (setKeys.length === 0) {\n throw new Error('update: data 不能为空')\n }\n\n if (whereKeys.length === 0) {\n throw new Error('update: 必须提供 where,避免全表更新')\n }\n const setClause = setKeys.map(k => `${quoteId(k)} = ?`).join(', ')\n const whereClause = whereKeys.map(k => `${quoteId(k)} = ?`).join(' AND ')\n const sql = `UPDATE ${quoteId(table)} SET ${setClause} WHERE ${whereClause}`\n const values = [...setKeys.map(k => data[k]), ...whereKeys.map(k => where[k])]\n return this.query<ResultSetHeader>(sql, values)\n }\n\n /**\n * 按等值条件删除。where 为空会抛错,避免误删全表。\n */\n delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader> {\n const whereKeys = Object.keys(where)\n if (whereKeys.length === 0) {\n throw new Error('delete: 必须提供 where,避免全表删除')\n }\n const whereClause = whereKeys.map(k => `${quoteId(k)} = ?`).join(' AND ')\n const sql = `DELETE FROM ${quoteId(table)} WHERE ${whereClause}`\n return this.query<ResultSetHeader>(\n sql,\n whereKeys.map(k => where[k]),\n )\n }\n}\n\n/**\n * 基于 mysql2 连接池:查询、增删改、事务。\n * 配置项见 {@link PoolOptions}(host、user、password、database 等)。\n */\nexport class MySQLClient {\n private pool: Pool | null = null\n\n constructor(private readonly options: PoolOptions) {}\n private getPool(): Pool {\n if (this.pool) {\n return this.pool\n }\n this.pool = mysql.createPool({\n waitForConnections: true,\n connectionLimit: 10,\n ...this.options,\n })\n return this.pool\n }\n\n private session(): MySQLSession {\n return new MySQLSession(this.getPool())\n }\n\n query<T extends RowDataPacket[] | ResultSetHeader = RowDataPacket[]>(\n sql: string,\n params?: ExecuteValues,\n ): Promise<T> {\n return this.session().query<T>(sql, params)\n }\n\n insert(table: string, row: Record<string, ExecuteValues>, options?: MySQLInsertOptions): Promise<ResultSetHeader> {\n return this.session().insert(table, row, options)\n }\n\n insertMany(\n table: string,\n rows: Record<string, ExecuteValues>[],\n options?: MySQLInsertOptions,\n ): Promise<ResultSetHeader> {\n return this.session().insertMany(table, rows, options)\n }\n\n update(\n table: string,\n data: Record<string, ExecuteValues>,\n where: Record<string, ExecuteValues>,\n ): Promise<ResultSetHeader> {\n return this.session().update(table, data, where)\n }\n\n delete(table: string, where: Record<string, ExecuteValues>): Promise<ResultSetHeader> {\n return this.session().delete(table, where)\n }\n\n /**\n * 在事务中执行回调;成功 commit,失败 rollback。\n * 回调参数为 {@link MySQLSession},与 client 上 `query` / `insert` / `insertMany` / `update` / `delete` 用法一致。\n */\n async transaction<T>(fn: (tx: MySQLSession) => Promise<T>): Promise<T> {\n const pool = this.getPool()\n const conn = await pool.getConnection()\n try {\n await conn.beginTransaction()\n const out = await fn(new MySQLSession(conn))\n await conn.commit()\n return out\n } catch (error) {\n await conn.rollback()\n throw error\n } finally {\n conn.release()\n }\n }\n\n /** 关闭连接池 */\n async close(): Promise<void> {\n if (this.pool) {\n await this.pool.end()\n this.pool = null\n }\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@hughpeng/mysql",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ }
22
+ }
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^25.3.5",
26
+ "tsup": "^8.5.0",
27
+ "typescript": "^5.9.3"
28
+ },
29
+ "dependencies": {
30
+ "mysql2": "^3.22.5"
31
+ },
32
+ "publishConfig": {
33
+ "registry": "https://registry.npmjs.org/",
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "dev": "tsup --watch",
39
+ "lint": "eslint ."
40
+ }
41
+ }