wok-server 0.7.2 → 0.8.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 +101 -14
- package/dist/http-client/index.js +1 -2
- package/dist/lock/index.js +5 -2
- package/dist/mvc/config.js +4 -2
- package/dist/mvc/exchange.js +32 -7
- package/dist/mvc/index.js +1 -1
- package/dist/mvc/server.js +12 -4
- package/dist/mysql/manager/base.js +5 -8
- package/dist/mysql/manager/ops/delete.js +2 -1
- package/dist/mysql/manager/ops/find.js +8 -4
- package/dist/mysql/manager/ops/insert.js +15 -40
- package/dist/mysql/manager/ops/update.js +2 -1
- package/dist/mysql/manager/ops/upsert.js +11 -31
- package/dist/mysql/migration.js +10 -3
- package/documentation/en/mysql.md +26 -33
- package/documentation/zh-cn/mysql.md +27 -34
- package/documentation/zh-cn/philosophy.md +433 -0
- package/package.json +1 -1
- package/skills/wok-server-api-rules/SKILL.md +113 -0
- package/skills/wok-server-code-navigation/SKILL.md +169 -95
- package/skills/wok-server-mysql/SKILL.md +16 -10
- package/skills/wok-server-mysql/references/version-control.md +7 -5
- package/src/http-client/index.ts +0 -1
- package/src/lock/index.ts +5 -2
- package/src/mvc/config.ts +9 -2
- package/src/mvc/exchange.ts +31 -6
- package/src/mvc/index.ts +1 -1
- package/src/mvc/server.ts +11 -5
- package/src/mysql/manager/base.ts +17 -17
- package/src/mysql/manager/ops/delete.ts +2 -1
- package/src/mysql/manager/ops/find.ts +8 -4
- package/src/mysql/manager/ops/insert.ts +23 -61
- package/src/mysql/manager/ops/update.ts +2 -1
- package/src/mysql/manager/ops/upsert.ts +31 -51
- package/src/mysql/migration.ts +14 -3
- package/types/lock/index.d.ts +3 -0
- package/types/mvc/config.d.ts +5 -0
- package/types/mvc/exchange.d.ts +13 -3
- package/types/mysql/manager/base.d.ts +12 -12
- package/types/mysql/manager/ops/insert.d.ts +2 -16
- package/types/mysql/manager/ops/upsert.d.ts +3 -4
package/README.md
CHANGED
|
@@ -15,15 +15,6 @@
|
|
|
15
15
|
- **数据库** — MySQL、MongoDB
|
|
16
16
|
- **其他** — 周期任务
|
|
17
17
|
|
|
18
|
-
## 为什么选择 Wok Server
|
|
19
|
-
|
|
20
|
-
- 函数式为主,少量面向对象,学习成本低
|
|
21
|
-
- 保持克制,不引入代理、装饰器等增强技术
|
|
22
|
-
- 轻量封装,兼容第三方 HTTP 生态
|
|
23
|
-
- 完整的类型定义,配合 IDE 智能补全,开发效率高
|
|
24
|
-
- 注释即文档,方法及参数均有详细说明
|
|
25
|
-
- 内置国际化支持,可扩展多语言
|
|
26
|
-
|
|
27
18
|
## 快速开始
|
|
28
19
|
|
|
29
20
|
```bash
|
|
@@ -61,12 +52,108 @@ npx skills add peaktai/wok-server --all
|
|
|
61
52
|
npx skills add https://gitee.com/tai/wok-server.git --all
|
|
62
53
|
```
|
|
63
54
|
|
|
64
|
-
##
|
|
55
|
+
## 为什么选择 Wok Server
|
|
56
|
+
|
|
57
|
+
核心原则只有一个:**实用主义**。每一个设计决策,都是在"简单透明"和"功能强大"之间选择了前者——不是因为做不到,而是因为**选择不做**。
|
|
58
|
+
|
|
59
|
+
### 纯对象配置,没有魔法
|
|
60
|
+
|
|
61
|
+
实体和数据库的映射就是一个普通对象,不需要学习装饰器、反射或任何框架特有的语法:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
export interface User {
|
|
65
|
+
id: string
|
|
66
|
+
name: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const tableUser: Table<User> = {
|
|
70
|
+
tableName: 'user',
|
|
71
|
+
id: 'id',
|
|
72
|
+
columns: ['name']
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
所有信息一目了然,出问题直接看这一个文件。不需要理解隐式的元数据系统,AI 和人类都容易理解。
|
|
77
|
+
|
|
78
|
+
### 显式优于隐式
|
|
79
|
+
|
|
80
|
+
读写分离、数据源选择都由代码显式决定,不会出现"写后读到旧数据"的意外:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const masterMgr = getMysqlManager('master')
|
|
84
|
+
const slaveMgr = getMysqlManager('slave')
|
|
85
|
+
|
|
86
|
+
// 写操作显式走主库
|
|
87
|
+
await masterMgr.insert(tableUser, { id: '001', nickname: 'jack' })
|
|
88
|
+
|
|
89
|
+
// 读操作显式走从库
|
|
90
|
+
const user = await slaveMgr.findById(tableUser, '001')
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
代码多一点,但完全可控。调试时清晰知道 SQL 发到了哪里。
|
|
94
|
+
|
|
95
|
+
### 纯 SQL 迁移,零学习成本
|
|
96
|
+
|
|
97
|
+
迁移就是按顺序执行的 SQL 文件,框架只负责"按顺序执行 + 版本记录":
|
|
98
|
+
|
|
99
|
+
```sql
|
|
100
|
+
-- 001_init.sql
|
|
101
|
+
CREATE TABLE `user` (
|
|
102
|
+
`id` varchar(32) NOT NULL PRIMARY KEY,
|
|
103
|
+
`nickname` varchar(100) DEFAULT NULL
|
|
104
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
105
|
+
|
|
106
|
+
-- 002_add_age.sql
|
|
107
|
+
ALTER TABLE `user` ADD COLUMN `age` int DEFAULT NULL AFTER `nickname`;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
会 SQL 就会写迁移,完全透明,DBA 可直接审查,任何数据库特性都能用。
|
|
111
|
+
|
|
112
|
+
### 统一且直觉的 API
|
|
113
|
+
|
|
114
|
+
查询 API 遵循统一模式,学会一个,其他都是同一模式:
|
|
115
|
+
|
|
116
|
+
| 操作 | 单条 | 列表 | 分页 | 部分字段 |
|
|
117
|
+
|------|------|------|------|---------|
|
|
118
|
+
| 查询 | `findFirst` | `find` | `paginate` | `findSelect` / `paginateSelect` |
|
|
119
|
+
|
|
120
|
+
条件查询用链式方法,不需要记忆操作符类名:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
await mgr.findFirst(tableUser, c =>
|
|
124
|
+
c.like('name', 'Bob%').gt('age', 18).between('score', 60, 100)
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
方法名就是自然语言,不需要 import 任何东西。
|
|
129
|
+
|
|
130
|
+
### 简单直接的类型系统
|
|
131
|
+
|
|
132
|
+
类型是约束工具,不是智力游戏:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// findById 返回 User | undefined
|
|
136
|
+
const user = await mgr.findById(tableUser, '001')
|
|
137
|
+
|
|
138
|
+
// insert 参数自动处理可选字段和元组表达式
|
|
139
|
+
await mgr.insert(tableUser, { id: '001', name: 'Bob' })
|
|
140
|
+
|
|
141
|
+
// findSelect 返回 Pick<User, K>
|
|
142
|
+
const list = await mgr.findSelect(tableUser, ['id', 'name'])
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
没有复杂的条件类型、映射类型体操或 `DeepPartial` 等抽象概念。用简单的泛型参数实现类型安全,配合 IDE 智能补全,开发效率高。
|
|
146
|
+
|
|
147
|
+
### 函数式为主,学习成本低
|
|
65
148
|
|
|
66
|
-
|
|
149
|
+
以函数式风格为主,少量面向对象,保持克制,不引入代理、装饰器等增强技术。轻量封装,兼容第三方 HTTP 生态。注释即文档,方法及参数均有详细说明。内置国际化支持,可扩展多语言。
|
|
67
150
|
|
|
68
|
-
|
|
151
|
+
### 对 AI 编程友好
|
|
69
152
|
|
|
70
|
-
|
|
153
|
+
wok-server 的简单透明设计,在 AI 辅助编程时代有天然优势:
|
|
71
154
|
|
|
72
|
-
|
|
155
|
+
- 纯对象配置 = AI 容易理解上下文,不需要理解框架特有的元数据系统
|
|
156
|
+
- 显式调用 = AI 从代码本身就能理解意图,不需要追踪隐式的配置
|
|
157
|
+
- 纯 SQL 迁移 = AI 直接生成可执行代码,没有生成步骤、没有交互、没有黑盒转换
|
|
158
|
+
- 无关联 ORM 自动加载 = AI 写 join 时必须显式写 SQL,性能问题更容易从 SQL 本身识别
|
|
159
|
+
- 统一的元组表达式 = AI 学习一次模式,到处复用,不容易混淆
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getJson = exports.postJson = exports.doRequest = void 0;
|
|
4
4
|
const http_1 = require("http");
|
|
5
5
|
const https_1 = require("https");
|
|
6
|
-
const url_1 = require("url");
|
|
7
6
|
/**
|
|
8
7
|
* 发送 http 请求.
|
|
9
8
|
* @param opts
|
|
@@ -11,7 +10,7 @@ const url_1 = require("url");
|
|
|
11
10
|
*/
|
|
12
11
|
function doRequest(opts) {
|
|
13
12
|
return new Promise((resolve, reject) => {
|
|
14
|
-
const url = new
|
|
13
|
+
const url = new URL(opts.url);
|
|
15
14
|
// query
|
|
16
15
|
if (opts.query) {
|
|
17
16
|
Object.entries(opts.query).forEach(entry => {
|
package/dist/lock/index.js
CHANGED
|
@@ -5,6 +5,9 @@ const crypto_1 = require("crypto");
|
|
|
5
5
|
/**
|
|
6
6
|
* 锁管理器,主要用于将不确定的顺序且有冲突的异步操作顺序执行,
|
|
7
7
|
* 防止异步流程庞大穿插执行造成的数据混乱和错误,常见于请求的处理。
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ 注意:这是一个本地内存锁,仅在当前进程内生效,不支持分布式场景。
|
|
10
|
+
* 如果需要在多进程、多服务器环境下使用,请使用 Redis 等分布式锁方案。
|
|
8
11
|
*/
|
|
9
12
|
class ServerLockManager {
|
|
10
13
|
/**
|
|
@@ -13,7 +16,7 @@ class ServerLockManager {
|
|
|
13
16
|
lockMap = new Map();
|
|
14
17
|
constructor() {
|
|
15
18
|
// 定期清理,将过期的信息移除,防止内存泄漏
|
|
16
|
-
|
|
19
|
+
setInterval(() => {
|
|
17
20
|
const keysToBeDeleted = [];
|
|
18
21
|
const now = Date.now();
|
|
19
22
|
for (const entry of this.lockMap.entries()) {
|
|
@@ -95,7 +98,7 @@ class ServerLockManager {
|
|
|
95
98
|
*/
|
|
96
99
|
sleep() {
|
|
97
100
|
return new Promise((resolve, reject) => {
|
|
98
|
-
setTimeout(resolve,
|
|
101
|
+
setTimeout(resolve, 50);
|
|
99
102
|
});
|
|
100
103
|
}
|
|
101
104
|
}
|
package/dist/mvc/config.js
CHANGED
|
@@ -13,7 +13,8 @@ function getConfig() {
|
|
|
13
13
|
corsAllowOrigin: '*',
|
|
14
14
|
tlsEnable: false,
|
|
15
15
|
tlsKey: '',
|
|
16
|
-
tlsCert: ''
|
|
16
|
+
tlsCert: '',
|
|
17
|
+
maxBodySize: 10 * 1024 * 1024
|
|
17
18
|
}, 'SERVER', {
|
|
18
19
|
port: [(0, validation_1.notNull)(), (0, validation_1.min)(80), (0, validation_1.max)(65535)],
|
|
19
20
|
timeout: [(0, validation_1.notNull)(), (0, validation_1.min)(1000), (0, validation_1.max)(600000)],
|
|
@@ -21,7 +22,8 @@ function getConfig() {
|
|
|
21
22
|
corsAllowOrigin: [(0, validation_1.notBlank)()],
|
|
22
23
|
corsAllowHeaders: [(0, validation_1.notBlank)()],
|
|
23
24
|
corsAllowMethods: [(0, validation_1.notBlank)()],
|
|
24
|
-
tlsEnable: [(0, validation_1.notNull)()]
|
|
25
|
+
tlsEnable: [(0, validation_1.notNull)()],
|
|
26
|
+
maxBodySize: [(0, validation_1.notNull)(), (0, validation_1.min)(0)]
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
exports.getConfig = getConfig;
|
package/dist/mvc/exchange.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ServerExchange = void 0;
|
|
3
|
+
exports.ServerExchange = exports.PayloadTooLargeError = void 0;
|
|
4
|
+
const config_1 = require("./config");
|
|
4
5
|
const query_1 = require("./query");
|
|
5
6
|
const render_1 = require("./render");
|
|
6
7
|
const text_1 = require("./render/text");
|
|
8
|
+
/**
|
|
9
|
+
* 请求体超出大小限制时抛出的错误
|
|
10
|
+
*/
|
|
11
|
+
class PayloadTooLargeError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'PayloadTooLargeError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.PayloadTooLargeError = PayloadTooLargeError;
|
|
7
18
|
/**
|
|
8
19
|
* 服务的数据交换对象.
|
|
9
20
|
*/
|
|
@@ -15,7 +26,11 @@ class ServerExchange {
|
|
|
15
26
|
this.request = request;
|
|
16
27
|
this.response = response;
|
|
17
28
|
}
|
|
18
|
-
|
|
29
|
+
/**
|
|
30
|
+
* 读取请求体为 Buffer
|
|
31
|
+
* @param maxSize 最大字节数,不传则使用全局配置 SERVER_MAX_BODY_SIZE,设为 0 不限制
|
|
32
|
+
*/
|
|
33
|
+
bodyBuffer(maxSize) {
|
|
19
34
|
if (this.#bufferPromise) {
|
|
20
35
|
return this.#bufferPromise;
|
|
21
36
|
}
|
|
@@ -23,21 +38,31 @@ class ServerExchange {
|
|
|
23
38
|
if (this.request.readableEnded) {
|
|
24
39
|
throw new Error('Request has ended!');
|
|
25
40
|
}
|
|
41
|
+
const limit = maxSize ?? (0, config_1.getConfig)().maxBodySize;
|
|
26
42
|
let body = [];
|
|
43
|
+
let received = 0;
|
|
27
44
|
this.request
|
|
28
45
|
.resume()
|
|
29
46
|
.on('error', reject)
|
|
30
|
-
.on('data', chunk =>
|
|
47
|
+
.on('data', (chunk) => {
|
|
48
|
+
received += chunk.length;
|
|
49
|
+
if (limit > 0 && received > limit) {
|
|
50
|
+
this.request.pause();
|
|
51
|
+
reject(new PayloadTooLargeError(`Request body exceeds ${limit} bytes`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
body.push(chunk);
|
|
55
|
+
})
|
|
31
56
|
.on('end', () => resolve(Buffer.concat(body)));
|
|
32
57
|
});
|
|
33
58
|
return this.#bufferPromise;
|
|
34
59
|
}
|
|
35
|
-
async bodyText() {
|
|
36
|
-
const buffer = await this.bodyBuffer();
|
|
60
|
+
async bodyText(maxSize) {
|
|
61
|
+
const buffer = await this.bodyBuffer(maxSize);
|
|
37
62
|
return buffer.toString('utf-8');
|
|
38
63
|
}
|
|
39
|
-
async bodyJson() {
|
|
40
|
-
const buffer = await this.bodyBuffer();
|
|
64
|
+
async bodyJson(maxSize) {
|
|
65
|
+
const buffer = await this.bodyBuffer(maxSize);
|
|
41
66
|
const bodyText = buffer.toString('utf-8');
|
|
42
67
|
if (!bodyText || !bodyText.trim()) {
|
|
43
68
|
return {};
|
package/dist/mvc/index.js
CHANGED
package/dist/mvc/server.js
CHANGED
|
@@ -71,8 +71,12 @@ class WokServer {
|
|
|
71
71
|
this.handleRequest(req, res).catch(error => {
|
|
72
72
|
(0, log_1.getLogger)().error(`Handle request failed:${req.url}`, error);
|
|
73
73
|
if (!res.writableEnded) {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if (error instanceof exchange_1.PayloadTooLargeError) {
|
|
75
|
+
(0, render_1.renderError)(res, error.message, 413);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
(0, render_1.renderError)(res, error.message ? error.message : 'Internal Server Error', 500);
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
});
|
|
78
82
|
});
|
|
@@ -90,8 +94,12 @@ class WokServer {
|
|
|
90
94
|
this.handleRequest(req, res).catch(error => {
|
|
91
95
|
(0, log_1.getLogger)().error(`Handle request failed:${req.url}`, error);
|
|
92
96
|
if (!res.writableEnded) {
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
if (error instanceof exchange_1.PayloadTooLargeError) {
|
|
98
|
+
(0, render_1.renderError)(res, error.message, 413);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
(0, render_1.renderError)(res, error.message ? error.message : 'Internal Server Error', 500);
|
|
102
|
+
}
|
|
95
103
|
}
|
|
96
104
|
});
|
|
97
105
|
});
|
|
@@ -102,9 +102,6 @@ class BaseMysqlManager {
|
|
|
102
102
|
* @returns
|
|
103
103
|
*/
|
|
104
104
|
async deleteOne(table, criteria) {
|
|
105
|
-
if (!Object.keys(criteria).length) {
|
|
106
|
-
throw new exception_1.MysqlException('criteria cannot be empty !');
|
|
107
|
-
}
|
|
108
105
|
const res = await this.queryWithConnection(conn => (0, ops_1.deleteMany)(this.opts.config, conn, {
|
|
109
106
|
table,
|
|
110
107
|
criteria,
|
|
@@ -133,7 +130,7 @@ class BaseMysqlManager {
|
|
|
133
130
|
/**
|
|
134
131
|
* 插入数据. 不支持自增加长id,id必须提前生成,请使用 uuid.
|
|
135
132
|
* @param table 表信息
|
|
136
|
-
* @param data
|
|
133
|
+
* @param data 完整实体数据,必填字段必须存在
|
|
137
134
|
* @returns 插入后的数据
|
|
138
135
|
*/
|
|
139
136
|
insert(table, data) {
|
|
@@ -142,7 +139,7 @@ class BaseMysqlManager {
|
|
|
142
139
|
/**
|
|
143
140
|
* 批量插入
|
|
144
141
|
* @param table 表
|
|
145
|
-
* @param list
|
|
142
|
+
* @param list 要插入的完整实体数据列表
|
|
146
143
|
*/
|
|
147
144
|
insertMany(table, list) {
|
|
148
145
|
return this.queryWithConnection(conn => (0, ops_1.insertMany)(this.opts.config, conn, table, list));
|
|
@@ -151,7 +148,7 @@ class BaseMysqlManager {
|
|
|
151
148
|
* Upsert 单条数据
|
|
152
149
|
* 如果主键冲突则更新,否则插入
|
|
153
150
|
* @param table 表信息
|
|
154
|
-
* @param data
|
|
151
|
+
* @param data 完整实体数据
|
|
155
152
|
* @returns
|
|
156
153
|
*/
|
|
157
154
|
upsert(table, data) {
|
|
@@ -161,7 +158,7 @@ class BaseMysqlManager {
|
|
|
161
158
|
* Upsert 多条数据
|
|
162
159
|
* 如果主键冲突则更新,否则插入
|
|
163
160
|
* @param table 表
|
|
164
|
-
* @param list
|
|
161
|
+
* @param list 要插入的完整实体数据列表
|
|
165
162
|
* @returns 影响的行数
|
|
166
163
|
*/
|
|
167
164
|
upsertMany(table, list) {
|
|
@@ -171,7 +168,7 @@ class BaseMysqlManager {
|
|
|
171
168
|
* Upsert 单条数据(支持自定义更新器)
|
|
172
169
|
* 如果主键冲突则按自定义逻辑更新,否则插入
|
|
173
170
|
* @param table 表信息
|
|
174
|
-
* @param data
|
|
171
|
+
* @param data 完整实体数据
|
|
175
172
|
* @param updater 冲突时的更新器
|
|
176
173
|
* @returns
|
|
177
174
|
*/
|
|
@@ -51,7 +51,8 @@ async function deleteMany(config, connection, opts) {
|
|
|
51
51
|
}
|
|
52
52
|
// 数量限制
|
|
53
53
|
if (opts.limit) {
|
|
54
|
-
sql += ` limit
|
|
54
|
+
sql += ` limit ? `;
|
|
55
|
+
values.push(opts.limit);
|
|
55
56
|
}
|
|
56
57
|
const res = await (0, utils_1.promiseQuery)(config, connection, sql, values);
|
|
57
58
|
return res.affectedRows;
|
|
@@ -58,9 +58,11 @@ async function find(config, conn, opts) {
|
|
|
58
58
|
}
|
|
59
59
|
// 数量限制
|
|
60
60
|
if (opts.limit) {
|
|
61
|
-
sql += ` limit
|
|
61
|
+
sql += ` limit ? `;
|
|
62
|
+
values.push(opts.limit);
|
|
62
63
|
if (opts.offset) {
|
|
63
|
-
sql += ` offset
|
|
64
|
+
sql += ` offset ?`;
|
|
65
|
+
values.push(opts.offset);
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
const res = await (0, utils_1.promiseQuery)(config, conn, sql, values);
|
|
@@ -90,9 +92,11 @@ async function findSelect(config, conn, opts) {
|
|
|
90
92
|
}
|
|
91
93
|
// 数量限制
|
|
92
94
|
if (opts.limit) {
|
|
93
|
-
sql += ` limit
|
|
95
|
+
sql += ` limit ? `;
|
|
96
|
+
values.push(opts.limit);
|
|
94
97
|
if (opts.offset) {
|
|
95
|
-
sql += ` offset
|
|
98
|
+
sql += ` offset ?`;
|
|
99
|
+
values.push(opts.offset);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
const res = await (0, utils_1.promiseQuery)(config, conn, sql, values);
|
|
@@ -1,28 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.insertMany = exports.insert =
|
|
3
|
+
exports.insertMany = exports.insert = void 0;
|
|
4
4
|
const exception_1 = require("../../exception");
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
6
|
const utils_2 = require("./utils");
|
|
7
|
-
/**
|
|
8
|
-
* 处理 insert value,支持表达式
|
|
9
|
-
* @returns { frag: SQL 片段, values: 参数值数组 }
|
|
10
|
-
*/
|
|
11
|
-
function processInsertValue(value) {
|
|
12
|
-
if (Array.isArray(value)) {
|
|
13
|
-
if (value[0] === 'now') {
|
|
14
|
-
return { frag: 'NOW()', values: [] };
|
|
15
|
-
}
|
|
16
|
-
if (value[0] === 'set') {
|
|
17
|
-
return { frag: '?', values: [(0, utils_2.processColumnValue)(value[1])] };
|
|
18
|
-
}
|
|
19
|
-
if (value[0] === 'expr') {
|
|
20
|
-
return { frag: value[1], values: value[2] || [] };
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return { frag: '?', values: [(0, utils_2.processColumnValue)(value)] };
|
|
24
|
-
}
|
|
25
|
-
exports.processInsertValue = processInsertValue;
|
|
26
7
|
/**
|
|
27
8
|
* 为表插入数据
|
|
28
9
|
* @param connection
|
|
@@ -49,23 +30,22 @@ async function insert(config, connection, table, data) {
|
|
|
49
30
|
columnsSet.add(table.updatedDate.column);
|
|
50
31
|
}
|
|
51
32
|
const columns = Array.from(columnsSet);
|
|
52
|
-
// 构建 sql
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const sql = `insert into ??(${columns.map(() => '??').join(',')}) values(${fragList.join(',')})`;
|
|
61
|
-
const values = [table.tableName, ...columns, ...insertValues];
|
|
33
|
+
// 构建 sql
|
|
34
|
+
const sql = `insert into ??(${columns.map(() => '??').join(',')}) values(${columns.map(() => '?').join(',')})`;
|
|
35
|
+
const values = [
|
|
36
|
+
table.tableName,
|
|
37
|
+
...columns,
|
|
38
|
+
...columns.map(col => (0, utils_2.processColumnValue)(data[col]))
|
|
39
|
+
];
|
|
62
40
|
const res = await (0, utils_1.promiseQuery)(config, connection, sql, values);
|
|
63
41
|
const packet = res;
|
|
64
42
|
if (packet.affectedRows !== 1) {
|
|
65
43
|
throw new exception_1.MysqlException(`Insert failed,table:${table.tableName},primary key: ${data[table.id]}`);
|
|
66
44
|
}
|
|
67
45
|
// 自动生成的id处理
|
|
68
|
-
if (packet.insertId &&
|
|
46
|
+
if (packet.insertId &&
|
|
47
|
+
(data[table.id] === undefined || data[table.id] === null)) {
|
|
48
|
+
;
|
|
69
49
|
data[table.id] = packet.insertId;
|
|
70
50
|
}
|
|
71
51
|
return data;
|
|
@@ -106,21 +86,16 @@ async function insertMany(config, connection, table, list) {
|
|
|
106
86
|
if (idx > 0) {
|
|
107
87
|
sql += ',';
|
|
108
88
|
}
|
|
109
|
-
const fragList = [];
|
|
110
|
-
const rowValues = [];
|
|
111
89
|
if (table.createdDate) {
|
|
90
|
+
;
|
|
112
91
|
data[table.createdDate.column] = createdData;
|
|
113
92
|
}
|
|
114
93
|
if (table.updatedDate) {
|
|
94
|
+
;
|
|
115
95
|
data[table.updatedDate.column] = updatedDate;
|
|
116
96
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fragList.push(frag);
|
|
120
|
-
rowValues.push(...vs);
|
|
121
|
-
}
|
|
122
|
-
sql += `(${fragList.join(',')})`;
|
|
123
|
-
values.push(...rowValues);
|
|
97
|
+
sql += `(${columns.map(() => '?').join(',')})`;
|
|
98
|
+
values.push(...columns.map(col => (0, utils_2.processColumnValue)(data[col])));
|
|
124
99
|
});
|
|
125
100
|
const res = await (0, utils_1.promiseQuery)(config, connection, sql, values);
|
|
126
101
|
const rsh = res;
|
|
@@ -213,7 +213,8 @@ async function updateMany(config, connection, opts) {
|
|
|
213
213
|
}
|
|
214
214
|
// 数量限制
|
|
215
215
|
if (opts.limit) {
|
|
216
|
-
sql += ` limit
|
|
216
|
+
sql += ` limit ? `;
|
|
217
|
+
values.push(opts.limit);
|
|
217
218
|
}
|
|
218
219
|
const res = await (0, utils_1.promiseQuery)(config, connection, sql, values);
|
|
219
220
|
const packet = res;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.upsertWithUpdater = exports.upsertMany = exports.upsert = void 0;
|
|
4
4
|
const utils_1 = require("../utils");
|
|
5
|
-
const
|
|
5
|
+
const utils_2 = require("./utils");
|
|
6
6
|
const update_1 = require("./update");
|
|
7
7
|
/**
|
|
8
8
|
* Upsert 单条数据
|
|
@@ -33,26 +33,19 @@ async function upsert(config, connection, table, data) {
|
|
|
33
33
|
}
|
|
34
34
|
const columns = Array.from(columnsSet);
|
|
35
35
|
// 构建 insert values
|
|
36
|
-
const
|
|
37
|
-
const insertValues = [];
|
|
38
|
-
for (const col of columns) {
|
|
39
|
-
const { frag, values: vs } = (0, insert_1.processInsertValue)(data[col]);
|
|
40
|
-
fragList.push(frag);
|
|
41
|
-
insertValues.push(...vs);
|
|
42
|
-
}
|
|
43
|
-
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${fragList.join(',')})`;
|
|
36
|
+
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${columns.map(() => '?').join(',')})`;
|
|
37
|
+
const insertValues = [table.tableName, ...columns, ...columns.map(col => (0, utils_2.processColumnValue)(data[col]))];
|
|
44
38
|
// 构建 on duplicate key update(排除 id)
|
|
45
39
|
const updateColumns = columns.filter(col => col !== table.id);
|
|
46
40
|
const updateFragments = [];
|
|
47
41
|
const updateValues = [];
|
|
48
42
|
for (const col of updateColumns) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
updateValues.push(col, ...vs);
|
|
43
|
+
updateFragments.push(`?? = ?`);
|
|
44
|
+
updateValues.push(col, (0, utils_2.processColumnValue)(data[col]));
|
|
52
45
|
}
|
|
53
46
|
const updateSql = ` on duplicate key update ${updateFragments.join(',')}`;
|
|
54
47
|
const sql = insertSql + updateSql;
|
|
55
|
-
const values = [
|
|
48
|
+
const values = [...insertValues, ...updateValues];
|
|
56
49
|
const res = await (0, utils_1.promiseQuery)(config, connection, sql, values);
|
|
57
50
|
const packet = res;
|
|
58
51
|
if (packet.insertId && (data[table.id] === undefined || data[table.id] === null)) {
|
|
@@ -98,21 +91,14 @@ async function upsertMany(config, connection, table, list) {
|
|
|
98
91
|
if (idx > 0) {
|
|
99
92
|
sql += ',';
|
|
100
93
|
}
|
|
101
|
-
const fragList = [];
|
|
102
|
-
const rowValues = [];
|
|
103
94
|
if (table.createdDate) {
|
|
104
95
|
data[table.createdDate.column] = createdData;
|
|
105
96
|
}
|
|
106
97
|
if (table.updatedDate) {
|
|
107
98
|
data[table.updatedDate.column] = updatedDate;
|
|
108
99
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
fragList.push(frag);
|
|
112
|
-
rowValues.push(...vs);
|
|
113
|
-
}
|
|
114
|
-
sql += `(${fragList.join(',')})`;
|
|
115
|
-
values.push(...rowValues);
|
|
100
|
+
sql += `(${columns.map(() => '?').join(',')})`;
|
|
101
|
+
values.push(...columns.map(col => (0, utils_2.processColumnValue)(data[col])));
|
|
116
102
|
});
|
|
117
103
|
const updateColumns = columns.filter(col => col !== table.id);
|
|
118
104
|
sql += ` on duplicate key update ${updateColumns.map(() => '?? = values(??)').join(',')}`;
|
|
@@ -156,18 +142,12 @@ async function upsertWithUpdater(config, connection, table, data, updater) {
|
|
|
156
142
|
}
|
|
157
143
|
const columns = Array.from(columnsSet);
|
|
158
144
|
// 构建 insert values
|
|
159
|
-
const
|
|
160
|
-
const insertValues = [];
|
|
161
|
-
for (const col of columns) {
|
|
162
|
-
const { frag, values: vs } = (0, insert_1.processInsertValue)(data[col]);
|
|
163
|
-
fragList.push(frag);
|
|
164
|
-
insertValues.push(...vs);
|
|
165
|
-
}
|
|
166
|
-
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${fragList.join(',')})`;
|
|
145
|
+
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${columns.map(() => '?').join(',')})`;
|
|
146
|
+
const insertValues = [table.tableName, ...columns, ...columns.map(col => (0, utils_2.processColumnValue)(data[col]))];
|
|
167
147
|
const convertRes = (0, update_1.updatorToSql)(table, updater);
|
|
168
148
|
const updateSql = ` on duplicate key update ${convertRes.sql}`;
|
|
169
149
|
const sql = insertSql + updateSql;
|
|
170
|
-
const values = [
|
|
150
|
+
const values = [...insertValues, ...convertRes.values];
|
|
171
151
|
const res = await (0, utils_1.promiseQuery)(config, connection, sql, values);
|
|
172
152
|
const packet = res;
|
|
173
153
|
if (packet.insertId && (data[table.id] === undefined || data[table.id] === null)) {
|
package/dist/mysql/migration.js
CHANGED
|
@@ -30,14 +30,21 @@ async function migrate(config, conn) {
|
|
|
30
30
|
if (!file.endsWith('.sql')) {
|
|
31
31
|
throw new Error(`版本文件名没有以 .sql 为后缀:${file}`);
|
|
32
32
|
}
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
throw new Error(`Version file
|
|
33
|
+
const match = file.match(/^(\d+)/);
|
|
34
|
+
if (!match) {
|
|
35
|
+
throw new Error(`Version file name must start with a number:${file}`);
|
|
36
36
|
}
|
|
37
|
+
const version = parseInt(match[1]);
|
|
37
38
|
versions.push({ version, filePath });
|
|
38
39
|
}
|
|
39
40
|
// 排序,判定顺序
|
|
40
41
|
versions.sort((o1, o2) => o1.version - o2.version);
|
|
42
|
+
// 去重校验
|
|
43
|
+
for (let i = 1; i < versions.length; i++) {
|
|
44
|
+
if (versions[i].version === versions[i - 1].version) {
|
|
45
|
+
throw new Error(`Duplicate version number ${versions[i].version} in files: ${versions[i - 1].filePath} and ${versions[i].filePath}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
41
48
|
for (let i = 0; i < versions.length; i++) {
|
|
42
49
|
const version = versions[i];
|
|
43
50
|
if (version.version !== i + 1) {
|