wok-server 0.7.1 → 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 +5 -4
- 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 +8 -7
- 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/http-client/index.d.ts +4 -4
- 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/src/mvc/exchange.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'http'
|
|
2
|
+
import { getConfig } from './config'
|
|
2
3
|
import { QueryString } from './query'
|
|
3
4
|
import { HtmlStuct, renderError, renderFile, renderHtml, renderJson } from './render'
|
|
4
5
|
import { renderText } from './render/text'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* 请求体超出大小限制时抛出的错误
|
|
9
|
+
*/
|
|
10
|
+
export class PayloadTooLargeError extends Error {
|
|
11
|
+
constructor(message: string) {
|
|
12
|
+
super(message)
|
|
13
|
+
this.name = 'PayloadTooLargeError'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
/**
|
|
7
18
|
* 服务的数据交换对象.
|
|
8
19
|
*/
|
|
@@ -11,7 +22,11 @@ export class ServerExchange {
|
|
|
11
22
|
|
|
12
23
|
constructor(readonly request: IncomingMessage, readonly response: ServerResponse) {}
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
/**
|
|
26
|
+
* 读取请求体为 Buffer
|
|
27
|
+
* @param maxSize 最大字节数,不传则使用全局配置 SERVER_MAX_BODY_SIZE,设为 0 不限制
|
|
28
|
+
*/
|
|
29
|
+
bodyBuffer(maxSize?: number): Promise<Buffer> {
|
|
15
30
|
if (this.#bufferPromise) {
|
|
16
31
|
return this.#bufferPromise
|
|
17
32
|
}
|
|
@@ -19,23 +34,33 @@ export class ServerExchange {
|
|
|
19
34
|
if (this.request.readableEnded) {
|
|
20
35
|
throw new Error('Request has ended!')
|
|
21
36
|
}
|
|
37
|
+
const limit = maxSize ?? getConfig().maxBodySize
|
|
22
38
|
let body: Buffer[] = []
|
|
39
|
+
let received = 0
|
|
23
40
|
this.request
|
|
24
41
|
.resume()
|
|
25
42
|
.on('error', reject)
|
|
26
|
-
.on('data', chunk =>
|
|
43
|
+
.on('data', (chunk: Buffer) => {
|
|
44
|
+
received += chunk.length
|
|
45
|
+
if (limit > 0 && received > limit) {
|
|
46
|
+
this.request.pause()
|
|
47
|
+
reject(new PayloadTooLargeError(`Request body exceeds ${limit} bytes`))
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
body.push(chunk)
|
|
51
|
+
})
|
|
27
52
|
.on('end', () => resolve(Buffer.concat(body)))
|
|
28
53
|
})
|
|
29
54
|
return this.#bufferPromise
|
|
30
55
|
}
|
|
31
56
|
|
|
32
|
-
async bodyText(): Promise<string> {
|
|
33
|
-
const buffer = await this.bodyBuffer()
|
|
57
|
+
async bodyText(maxSize?: number): Promise<string> {
|
|
58
|
+
const buffer = await this.bodyBuffer(maxSize)
|
|
34
59
|
return buffer.toString('utf-8')
|
|
35
60
|
}
|
|
36
61
|
|
|
37
|
-
async bodyJson<T>(): Promise<T> {
|
|
38
|
-
const buffer = await this.bodyBuffer()
|
|
62
|
+
async bodyJson<T>(maxSize?: number): Promise<T> {
|
|
63
|
+
const buffer = await this.bodyBuffer(maxSize)
|
|
39
64
|
const bodyText = buffer.toString('utf-8')
|
|
40
65
|
if (!bodyText || !bodyText.trim()) {
|
|
41
66
|
return {} as any
|
package/src/mvc/index.ts
CHANGED
package/src/mvc/server.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { isAbsolute, resolve } from 'path'
|
|
|
7
7
|
import { getLogger } from '../log'
|
|
8
8
|
import { accessLogInterceptor } from './access-log'
|
|
9
9
|
import { WebConfig, getConfig } from './config'
|
|
10
|
-
import { ServerExchange } from './exchange'
|
|
10
|
+
import { PayloadTooLargeError, ServerExchange } from './exchange'
|
|
11
11
|
import { Interceptor } from './interceptor'
|
|
12
12
|
import { renderError } from './render'
|
|
13
13
|
import { RouterHandler, Routers } from './router'
|
|
@@ -103,8 +103,11 @@ export class WokServer {
|
|
|
103
103
|
this.handleRequest(req, res).catch(error => {
|
|
104
104
|
getLogger().error(`Handle request failed:${req.url}`, error)
|
|
105
105
|
if (!res.writableEnded) {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
if (error instanceof PayloadTooLargeError) {
|
|
107
|
+
renderError(res, error.message, 413)
|
|
108
|
+
} else {
|
|
109
|
+
renderError(res, error.message ? error.message : 'Internal Server Error', 500)
|
|
110
|
+
}
|
|
108
111
|
}
|
|
109
112
|
})
|
|
110
113
|
})
|
|
@@ -123,8 +126,11 @@ export class WokServer {
|
|
|
123
126
|
this.handleRequest(req, res).catch(error => {
|
|
124
127
|
getLogger().error(`Handle request failed:${req.url}`, error)
|
|
125
128
|
if (!res.writableEnded) {
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
if (error instanceof PayloadTooLargeError) {
|
|
130
|
+
renderError(res, error.message, 413)
|
|
131
|
+
} else {
|
|
132
|
+
renderError(res, error.message ? error.message : 'Internal Server Error', 500)
|
|
133
|
+
}
|
|
128
134
|
}
|
|
129
135
|
})
|
|
130
136
|
}
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
OrderBy,
|
|
13
13
|
UpdateOpts,
|
|
14
14
|
Updater,
|
|
15
|
-
InsertValue,
|
|
16
15
|
count,
|
|
17
16
|
deleteMany,
|
|
18
17
|
deleteById,
|
|
@@ -138,10 +137,7 @@ export abstract class BaseMysqlManager {
|
|
|
138
137
|
* @param criteria
|
|
139
138
|
* @returns
|
|
140
139
|
*/
|
|
141
|
-
async deleteOne<T>(table: Table<T>, criteria:
|
|
142
|
-
if (!Object.keys(criteria).length) {
|
|
143
|
-
throw new MysqlException('criteria cannot be empty !')
|
|
144
|
-
}
|
|
140
|
+
async deleteOne<T>(table: Table<T>, criteria: MixCriteria<T>) {
|
|
145
141
|
const res = await this.queryWithConnection(conn =>
|
|
146
142
|
deleteMany(this.opts.config, conn, {
|
|
147
143
|
table,
|
|
@@ -179,50 +175,52 @@ export abstract class BaseMysqlManager {
|
|
|
179
175
|
/**
|
|
180
176
|
* 插入数据. 不支持自增加长id,id必须提前生成,请使用 uuid.
|
|
181
177
|
* @param table 表信息
|
|
182
|
-
* @param data
|
|
178
|
+
* @param data 完整实体数据,必填字段必须存在
|
|
183
179
|
* @returns 插入后的数据
|
|
184
180
|
*/
|
|
185
|
-
insert<T>(table: Table<T>, data:
|
|
181
|
+
insert<T>(table: Table<T>, data: T): Promise<T> {
|
|
186
182
|
return this.queryWithConnection(conn => insert(this.opts.config, conn, table, data))
|
|
187
183
|
}
|
|
188
184
|
/**
|
|
189
185
|
* 批量插入
|
|
190
186
|
* @param table 表
|
|
191
|
-
* @param list
|
|
187
|
+
* @param list 要插入的完整实体数据列表
|
|
192
188
|
*/
|
|
193
|
-
insertMany<T>(table: Table<T>, list:
|
|
189
|
+
insertMany<T>(table: Table<T>, list: T[]): Promise<void> {
|
|
194
190
|
return this.queryWithConnection(conn => insertMany(this.opts.config, conn, table, list))
|
|
195
191
|
}
|
|
196
192
|
/**
|
|
197
193
|
* Upsert 单条数据
|
|
198
194
|
* 如果主键冲突则更新,否则插入
|
|
199
195
|
* @param table 表信息
|
|
200
|
-
* @param data
|
|
196
|
+
* @param data 完整实体数据
|
|
201
197
|
* @returns
|
|
202
198
|
*/
|
|
203
|
-
upsert<T>(table: Table<T>, data:
|
|
199
|
+
upsert<T>(table: Table<T>, data: T): Promise<T> {
|
|
204
200
|
return this.queryWithConnection(conn => upsert(this.opts.config, conn, table, data))
|
|
205
201
|
}
|
|
206
202
|
/**
|
|
207
203
|
* Upsert 多条数据
|
|
208
204
|
* 如果主键冲突则更新,否则插入
|
|
209
205
|
* @param table 表
|
|
210
|
-
* @param list
|
|
206
|
+
* @param list 要插入的完整实体数据列表
|
|
211
207
|
* @returns 影响的行数
|
|
212
208
|
*/
|
|
213
|
-
upsertMany<T>(table: Table<T>, list:
|
|
209
|
+
upsertMany<T>(table: Table<T>, list: T[]): Promise<number> {
|
|
214
210
|
return this.queryWithConnection(conn => upsertMany(this.opts.config, conn, table, list))
|
|
215
211
|
}
|
|
216
212
|
/**
|
|
217
213
|
* Upsert 单条数据(支持自定义更新器)
|
|
218
214
|
* 如果主键冲突则按自定义逻辑更新,否则插入
|
|
219
215
|
* @param table 表信息
|
|
220
|
-
* @param data
|
|
216
|
+
* @param data 完整实体数据
|
|
221
217
|
* @param updater 冲突时的更新器
|
|
222
218
|
* @returns
|
|
223
219
|
*/
|
|
224
|
-
upsertWithUpdater<T>(table: Table<T>, data:
|
|
225
|
-
return this.queryWithConnection(conn =>
|
|
220
|
+
upsertWithUpdater<T>(table: Table<T>, data: T, updater: Updater<T>): Promise<T> {
|
|
221
|
+
return this.queryWithConnection(conn =>
|
|
222
|
+
upsertWithUpdater(this.opts.config, conn, table, data, updater)
|
|
223
|
+
)
|
|
226
224
|
}
|
|
227
225
|
/**
|
|
228
226
|
* 更新
|
|
@@ -307,7 +305,9 @@ export abstract class BaseMysqlManager {
|
|
|
307
305
|
* @param opts
|
|
308
306
|
* @returns
|
|
309
307
|
*/
|
|
310
|
-
paginateSelect<T, K extends keyof T>(
|
|
308
|
+
paginateSelect<T, K extends keyof T>(
|
|
309
|
+
opts: MysqlPaginateSelectOpts<T, K>
|
|
310
|
+
): Promise<MysqlPage<Pick<T, K>>> {
|
|
311
311
|
return this.queryWithConnection(conn => paginateSelect(this.opts.config, conn, opts))
|
|
312
312
|
}
|
|
313
313
|
|
|
@@ -84,7 +84,8 @@ export async function deleteMany<T>(
|
|
|
84
84
|
}
|
|
85
85
|
// 数量限制
|
|
86
86
|
if (opts.limit) {
|
|
87
|
-
sql += ` limit
|
|
87
|
+
sql += ` limit ? `
|
|
88
|
+
values.push(opts.limit)
|
|
88
89
|
}
|
|
89
90
|
const res = await promiseQuery(config, connection, sql, values)
|
|
90
91
|
return (res as ResultSetHeader).affectedRows
|
|
@@ -96,9 +96,11 @@ export async function find<T>(
|
|
|
96
96
|
}
|
|
97
97
|
// 数量限制
|
|
98
98
|
if (opts.limit) {
|
|
99
|
-
sql += ` limit
|
|
99
|
+
sql += ` limit ? `
|
|
100
|
+
values.push(opts.limit)
|
|
100
101
|
if (opts.offset) {
|
|
101
|
-
sql += ` offset
|
|
102
|
+
sql += ` offset ?`
|
|
103
|
+
values.push(opts.offset)
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
const res = await promiseQuery(config, conn, sql, values)
|
|
@@ -142,9 +144,11 @@ export async function findSelect<T, K extends keyof T>(
|
|
|
142
144
|
}
|
|
143
145
|
// 数量限制
|
|
144
146
|
if (opts.limit) {
|
|
145
|
-
sql += ` limit
|
|
147
|
+
sql += ` limit ? `
|
|
148
|
+
values.push(opts.limit)
|
|
146
149
|
if (opts.offset) {
|
|
147
|
-
sql += ` offset
|
|
150
|
+
sql += ` offset ?`
|
|
151
|
+
values.push(opts.offset)
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
const res = await promiseQuery(config, conn, sql, values)
|
|
@@ -5,37 +5,6 @@ import { promiseQuery } from '../utils'
|
|
|
5
5
|
import { MysqlConfig } from '../../config'
|
|
6
6
|
import { processColumnValue } from './utils'
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* 插入值类型,支持在 INSERT VALUES 中使用表达式
|
|
10
|
-
*/
|
|
11
|
-
export type InsertValue<T> = {
|
|
12
|
-
[K in keyof T]?:
|
|
13
|
-
| T[K]
|
|
14
|
-
| ['now']
|
|
15
|
-
| ['set', T[K]]
|
|
16
|
-
| ['expr', string]
|
|
17
|
-
| ['expr', string, any[]]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 处理 insert value,支持表达式
|
|
22
|
-
* @returns { frag: SQL 片段, values: 参数值数组 }
|
|
23
|
-
*/
|
|
24
|
-
export function processInsertValue(value: any): { frag: string; values: any[] } {
|
|
25
|
-
if (Array.isArray(value)) {
|
|
26
|
-
if (value[0] === 'now') {
|
|
27
|
-
return { frag: 'NOW()', values: [] }
|
|
28
|
-
}
|
|
29
|
-
if (value[0] === 'set') {
|
|
30
|
-
return { frag: '?', values: [processColumnValue(value[1])] }
|
|
31
|
-
}
|
|
32
|
-
if (value[0] === 'expr') {
|
|
33
|
-
return { frag: value[1], values: value[2] || [] }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return { frag: '?', values: [processColumnValue(value)] }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
8
|
/**
|
|
40
9
|
* 为表插入数据
|
|
41
10
|
* @param connection
|
|
@@ -47,7 +16,7 @@ export async function insert<T>(
|
|
|
47
16
|
config: MysqlConfig,
|
|
48
17
|
connection: PoolConnection,
|
|
49
18
|
table: Table<T>,
|
|
50
|
-
data:
|
|
19
|
+
data: T
|
|
51
20
|
): Promise<T> {
|
|
52
21
|
// 列信息,使用 set 防止 columns 中重复配置 id 和更新创建时间列
|
|
53
22
|
let columnsSet: Set<keyof T> = new Set()
|
|
@@ -58,37 +27,37 @@ export async function insert<T>(
|
|
|
58
27
|
table.columns.forEach(col => columnsSet.add(col))
|
|
59
28
|
if (table.createdDate) {
|
|
60
29
|
const createdData = table.createdDate.type === 'date' ? new Date() : new Date().getTime()
|
|
61
|
-
data[table.createdDate.column] = createdData
|
|
30
|
+
;(data as any)[table.createdDate.column] = createdData
|
|
62
31
|
columnsSet.add(table.createdDate.column)
|
|
63
32
|
}
|
|
64
33
|
if (table.updatedDate) {
|
|
65
34
|
const updatedDate = table.updatedDate.type === 'date' ? new Date() : new Date().getTime()
|
|
66
|
-
data[table.updatedDate.column] = updatedDate
|
|
35
|
+
;(data as any)[table.updatedDate.column] = updatedDate
|
|
67
36
|
columnsSet.add(table.updatedDate.column)
|
|
68
37
|
}
|
|
69
38
|
const columns = Array.from(columnsSet)
|
|
70
|
-
// 构建 sql
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
const sql = `insert into ??(${columns.map(() => '??').join(',')}) values(${fragList.join(',')})`
|
|
79
|
-
const values: any[] = [table.tableName, ...columns, ...insertValues]
|
|
39
|
+
// 构建 sql
|
|
40
|
+
const sql = `insert into ??(${columns.map(() => '??').join(',')}) values(${columns.map(() => '?').join(',')})`
|
|
41
|
+
const values: any[] = [
|
|
42
|
+
table.tableName,
|
|
43
|
+
...columns,
|
|
44
|
+
...columns.map(col => processColumnValue((data as any)[col]))
|
|
45
|
+
]
|
|
80
46
|
const res = await promiseQuery(config, connection, sql, values)
|
|
81
47
|
const packet = res as ResultSetHeader
|
|
82
48
|
if (packet.affectedRows !== 1) {
|
|
83
49
|
throw new MysqlException(
|
|
84
|
-
`Insert failed,table:${table.tableName},primary key: ${data[table.id]}`
|
|
50
|
+
`Insert failed,table:${table.tableName},primary key: ${(data as any)[table.id]}`
|
|
85
51
|
)
|
|
86
52
|
}
|
|
87
53
|
// 自动生成的id处理
|
|
88
|
-
if (
|
|
89
|
-
|
|
54
|
+
if (
|
|
55
|
+
packet.insertId &&
|
|
56
|
+
((data as any)[table.id] === undefined || (data as any)[table.id] === null)
|
|
57
|
+
) {
|
|
58
|
+
;(data as any)[table.id] = packet.insertId
|
|
90
59
|
}
|
|
91
|
-
return data
|
|
60
|
+
return data
|
|
92
61
|
}
|
|
93
62
|
/**
|
|
94
63
|
* 一次插入多条记录
|
|
@@ -100,7 +69,7 @@ export async function insertMany<T>(
|
|
|
100
69
|
config: MysqlConfig,
|
|
101
70
|
connection: PoolConnection,
|
|
102
71
|
table: Table<T>,
|
|
103
|
-
list:
|
|
72
|
+
list: T[]
|
|
104
73
|
): Promise<void> {
|
|
105
74
|
if (!list.length) {
|
|
106
75
|
return
|
|
@@ -108,7 +77,7 @@ export async function insertMany<T>(
|
|
|
108
77
|
// 列信息,使用 set 防止 columns 中重复配置 id 和更新创建时间列
|
|
109
78
|
let columnsSet: Set<keyof T> = new Set()
|
|
110
79
|
// 批量插入必须统一使用 id 或不使用 id,以第一条记录为准
|
|
111
|
-
if (list[0][table.id]) {
|
|
80
|
+
if ((list[0] as any)[table.id]) {
|
|
112
81
|
columnsSet.add(table.id)
|
|
113
82
|
}
|
|
114
83
|
table.columns.forEach(col => columnsSet.add(col))
|
|
@@ -130,21 +99,14 @@ export async function insertMany<T>(
|
|
|
130
99
|
if (idx > 0) {
|
|
131
100
|
sql += ','
|
|
132
101
|
}
|
|
133
|
-
const fragList: string[] = []
|
|
134
|
-
const rowValues: any[] = []
|
|
135
102
|
if (table.createdDate) {
|
|
136
|
-
data[table.createdDate.column] = createdData
|
|
103
|
+
;(data as any)[table.createdDate.column] = createdData
|
|
137
104
|
}
|
|
138
105
|
if (table.updatedDate) {
|
|
139
|
-
data[table.updatedDate.column] = updatedDate
|
|
140
|
-
}
|
|
141
|
-
for (const col of columns) {
|
|
142
|
-
const { frag, values: vs } = processInsertValue(data[col])
|
|
143
|
-
fragList.push(frag)
|
|
144
|
-
rowValues.push(...vs)
|
|
106
|
+
;(data as any)[table.updatedDate.column] = updatedDate
|
|
145
107
|
}
|
|
146
|
-
sql += `(${
|
|
147
|
-
values.push(...
|
|
108
|
+
sql += `(${columns.map(() => '?').join(',')})`
|
|
109
|
+
values.push(...columns.map(col => processColumnValue((data as any)[col])))
|
|
148
110
|
})
|
|
149
111
|
|
|
150
112
|
const res = await promiseQuery(config, connection, sql, values)
|
|
@@ -310,7 +310,8 @@ export async function updateMany<T>(
|
|
|
310
310
|
}
|
|
311
311
|
// 数量限制
|
|
312
312
|
if (opts.limit) {
|
|
313
|
-
sql += ` limit
|
|
313
|
+
sql += ` limit ? `
|
|
314
|
+
values.push(opts.limit)
|
|
314
315
|
}
|
|
315
316
|
const res = await promiseQuery(config, connection, sql, values)
|
|
316
317
|
const packet = res as ResultSetHeader
|
|
@@ -2,7 +2,7 @@ import { PoolConnection, ResultSetHeader } from 'mysql2'
|
|
|
2
2
|
import { MysqlConfig } from '../../config'
|
|
3
3
|
import { Table } from '../../table-info'
|
|
4
4
|
import { promiseQuery } from '../utils'
|
|
5
|
-
import {
|
|
5
|
+
import { processColumnValue } from './utils'
|
|
6
6
|
import { Updater, updatorToSql } from './update'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -18,10 +18,10 @@ export async function upsert<T>(
|
|
|
18
18
|
config: MysqlConfig,
|
|
19
19
|
connection: PoolConnection,
|
|
20
20
|
table: Table<T>,
|
|
21
|
-
data:
|
|
21
|
+
data: T
|
|
22
22
|
): Promise<T> {
|
|
23
23
|
let columnsSet: Set<keyof T> = new Set()
|
|
24
|
-
if (data[table.id]) {
|
|
24
|
+
if ((data as any)[table.id]) {
|
|
25
25
|
columnsSet.add(table.id)
|
|
26
26
|
}
|
|
27
27
|
table.columns.forEach(col => columnsSet.add(col))
|
|
@@ -31,50 +31,43 @@ export async function upsert<T>(
|
|
|
31
31
|
|
|
32
32
|
if (table.createdDate) {
|
|
33
33
|
const createdData = table.createdDate.type === 'date' ? now : nowTimestamp
|
|
34
|
-
data[table.createdDate.column] = createdData
|
|
34
|
+
;(data as any)[table.createdDate.column] = createdData
|
|
35
35
|
columnsSet.add(table.createdDate.column)
|
|
36
36
|
}
|
|
37
37
|
if (table.updatedDate) {
|
|
38
38
|
const updatedDate = table.updatedDate.type === 'date' ? now : nowTimestamp
|
|
39
|
-
data[table.updatedDate.column] = updatedDate
|
|
39
|
+
;(data as any)[table.updatedDate.column] = updatedDate
|
|
40
40
|
columnsSet.add(table.updatedDate.column)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const columns = Array.from(columnsSet)
|
|
44
44
|
|
|
45
45
|
// 构建 insert values
|
|
46
|
-
const
|
|
47
|
-
const insertValues: any[] = []
|
|
48
|
-
for (const col of columns) {
|
|
49
|
-
const { frag, values: vs } = processInsertValue(data[col])
|
|
50
|
-
fragList.push(frag)
|
|
51
|
-
insertValues.push(...vs)
|
|
52
|
-
}
|
|
53
|
-
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${fragList.join(',')})`
|
|
46
|
+
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${columns.map(() => '?').join(',')})`
|
|
47
|
+
const insertValues: any[] = [table.tableName, ...columns, ...columns.map(col => processColumnValue((data as any)[col]))]
|
|
54
48
|
|
|
55
49
|
// 构建 on duplicate key update(排除 id)
|
|
56
50
|
const updateColumns = columns.filter(col => col !== table.id)
|
|
57
51
|
const updateFragments: string[] = []
|
|
58
52
|
const updateValues: any[] = []
|
|
59
53
|
for (const col of updateColumns) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
updateValues.push(col, ...vs)
|
|
54
|
+
updateFragments.push(`?? = ?`)
|
|
55
|
+
updateValues.push(col, processColumnValue((data as any)[col]))
|
|
63
56
|
}
|
|
64
57
|
const updateSql = ` on duplicate key update ${updateFragments.join(',')}`
|
|
65
58
|
|
|
66
59
|
const sql = insertSql + updateSql
|
|
67
60
|
|
|
68
|
-
const values = [
|
|
61
|
+
const values = [...insertValues, ...updateValues]
|
|
69
62
|
|
|
70
63
|
const res = await promiseQuery(config, connection, sql, values)
|
|
71
64
|
const packet = res as ResultSetHeader
|
|
72
65
|
|
|
73
|
-
if (packet.insertId && (data[table.id] === undefined || data[table.id] === null)) {
|
|
74
|
-
data[table.id] = packet.insertId
|
|
66
|
+
if (packet.insertId && ((data as any)[table.id] === undefined || (data as any)[table.id] === null)) {
|
|
67
|
+
(data as any)[table.id] = packet.insertId
|
|
75
68
|
}
|
|
76
69
|
|
|
77
|
-
return data
|
|
70
|
+
return data
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
/**
|
|
@@ -90,14 +83,14 @@ export async function upsertMany<T>(
|
|
|
90
83
|
config: MysqlConfig,
|
|
91
84
|
connection: PoolConnection,
|
|
92
85
|
table: Table<T>,
|
|
93
|
-
list:
|
|
86
|
+
list: T[]
|
|
94
87
|
): Promise<number> {
|
|
95
88
|
if (!list.length) {
|
|
96
89
|
return 0
|
|
97
90
|
}
|
|
98
91
|
|
|
99
92
|
let columnsSet: Set<keyof T> = new Set()
|
|
100
|
-
if (list[0][table.id]) {
|
|
93
|
+
if ((list[0] as any)[table.id]) {
|
|
101
94
|
columnsSet.add(table.id)
|
|
102
95
|
}
|
|
103
96
|
table.columns.forEach(col => columnsSet.add(col))
|
|
@@ -125,21 +118,14 @@ export async function upsertMany<T>(
|
|
|
125
118
|
if (idx > 0) {
|
|
126
119
|
sql += ','
|
|
127
120
|
}
|
|
128
|
-
const fragList: string[] = []
|
|
129
|
-
const rowValues: any[] = []
|
|
130
121
|
if (table.createdDate) {
|
|
131
|
-
data[table.createdDate.column] = createdData
|
|
122
|
+
(data as any)[table.createdDate.column] = createdData
|
|
132
123
|
}
|
|
133
124
|
if (table.updatedDate) {
|
|
134
|
-
data[table.updatedDate.column] = updatedDate
|
|
135
|
-
}
|
|
136
|
-
for (const col of columns) {
|
|
137
|
-
const { frag, values: vs } = processInsertValue(data[col])
|
|
138
|
-
fragList.push(frag)
|
|
139
|
-
rowValues.push(...vs)
|
|
125
|
+
(data as any)[table.updatedDate.column] = updatedDate
|
|
140
126
|
}
|
|
141
|
-
sql += `(${
|
|
142
|
-
values.push(...
|
|
127
|
+
sql += `(${columns.map(() => '?').join(',')})`
|
|
128
|
+
values.push(...columns.map(col => processColumnValue((data as any)[col])))
|
|
143
129
|
})
|
|
144
130
|
|
|
145
131
|
const updateColumns = columns.filter(col => col !== table.id)
|
|
@@ -166,15 +152,15 @@ export async function upsertWithUpdater<T>(
|
|
|
166
152
|
config: MysqlConfig,
|
|
167
153
|
connection: PoolConnection,
|
|
168
154
|
table: Table<T>,
|
|
169
|
-
data:
|
|
155
|
+
data: T,
|
|
170
156
|
updater: Updater<T>
|
|
171
157
|
): Promise<T> {
|
|
172
158
|
let columnsSet: Set<keyof T> = new Set()
|
|
173
|
-
if (data[table.id]) {
|
|
159
|
+
if ((data as any)[table.id]) {
|
|
174
160
|
columnsSet.add(table.id)
|
|
175
161
|
}
|
|
176
162
|
table.columns.forEach(col => {
|
|
177
|
-
if (data[col] !== undefined) {
|
|
163
|
+
if ((data as any)[col] !== undefined) {
|
|
178
164
|
columnsSet.add(col)
|
|
179
165
|
}
|
|
180
166
|
})
|
|
@@ -184,26 +170,20 @@ export async function upsertWithUpdater<T>(
|
|
|
184
170
|
|
|
185
171
|
if (table.createdDate) {
|
|
186
172
|
const createdData = table.createdDate.type === 'date' ? now : nowTimestamp
|
|
187
|
-
data[table.createdDate.column] = createdData
|
|
173
|
+
;(data as any)[table.createdDate.column] = createdData
|
|
188
174
|
columnsSet.add(table.createdDate.column)
|
|
189
175
|
}
|
|
190
176
|
if (table.updatedDate) {
|
|
191
177
|
const updatedDate = table.updatedDate.type === 'date' ? now : nowTimestamp
|
|
192
|
-
data[table.updatedDate.column] = updatedDate
|
|
178
|
+
;(data as any)[table.updatedDate.column] = updatedDate
|
|
193
179
|
columnsSet.add(table.updatedDate.column)
|
|
194
180
|
}
|
|
195
181
|
|
|
196
182
|
const columns = Array.from(columnsSet)
|
|
197
183
|
|
|
198
184
|
// 构建 insert values
|
|
199
|
-
const
|
|
200
|
-
const insertValues: any[] = []
|
|
201
|
-
for (const col of columns) {
|
|
202
|
-
const { frag, values: vs } = processInsertValue(data[col])
|
|
203
|
-
fragList.push(frag)
|
|
204
|
-
insertValues.push(...vs)
|
|
205
|
-
}
|
|
206
|
-
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${fragList.join(',')})`
|
|
185
|
+
const insertSql = `insert into ??(${columns.map(() => '??').join(',')}) values(${columns.map(() => '?').join(',')})`
|
|
186
|
+
const insertValues: any[] = [table.tableName, ...columns, ...columns.map(col => processColumnValue((data as any)[col]))]
|
|
207
187
|
|
|
208
188
|
const convertRes = updatorToSql(table, updater)
|
|
209
189
|
|
|
@@ -211,14 +191,14 @@ export async function upsertWithUpdater<T>(
|
|
|
211
191
|
|
|
212
192
|
const sql = insertSql + updateSql
|
|
213
193
|
|
|
214
|
-
const values = [
|
|
194
|
+
const values = [...insertValues, ...convertRes.values]
|
|
215
195
|
|
|
216
196
|
const res = await promiseQuery(config, connection, sql, values)
|
|
217
197
|
const packet = res as ResultSetHeader
|
|
218
198
|
|
|
219
|
-
if (packet.insertId && (data[table.id] === undefined || data[table.id] === null)) {
|
|
220
|
-
data[table.id] = packet.insertId
|
|
199
|
+
if (packet.insertId && ((data as any)[table.id] === undefined || (data as any)[table.id] === null)) {
|
|
200
|
+
(data as any)[table.id] = packet.insertId
|
|
221
201
|
}
|
|
222
202
|
|
|
223
|
-
return data
|
|
224
|
-
}
|
|
203
|
+
return data
|
|
204
|
+
}
|
package/src/mysql/migration.ts
CHANGED
|
@@ -38,14 +38,25 @@ export async function migrate(config: MysqlConfig, conn: Connection) {
|
|
|
38
38
|
if (!file.endsWith('.sql')) {
|
|
39
39
|
throw new Error(`版本文件名没有以 .sql 为后缀:${file}`)
|
|
40
40
|
}
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
throw new Error(`Version file
|
|
41
|
+
const match = file.match(/^(\d+)/)
|
|
42
|
+
if (!match) {
|
|
43
|
+
throw new Error(`Version file name must start with a number:${file}`)
|
|
44
44
|
}
|
|
45
|
+
const version = parseInt(match[1])
|
|
45
46
|
versions.push({ version, filePath })
|
|
46
47
|
}
|
|
47
48
|
// 排序,判定顺序
|
|
48
49
|
versions.sort((o1, o2) => o1.version - o2.version)
|
|
50
|
+
// 去重校验
|
|
51
|
+
for (let i = 1; i < versions.length; i++) {
|
|
52
|
+
if (versions[i].version === versions[i - 1].version) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Duplicate version number ${versions[i].version} in files: ${
|
|
55
|
+
versions[i - 1].filePath
|
|
56
|
+
} and ${versions[i].filePath}`
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
49
60
|
for (let i = 0; i < versions.length; i++) {
|
|
50
61
|
const version = versions[i]
|
|
51
62
|
if (version.version !== i + 1) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
/// <reference types="node" />
|
|
4
|
-
import {
|
|
4
|
+
import { Agent as HttpAgent, IncomingHttpHeaders } from 'http';
|
|
5
5
|
import { Agent as HttpsAgent } from 'https';
|
|
6
6
|
/**
|
|
7
7
|
* 请求选项.
|
|
@@ -67,11 +67,11 @@ export declare function doRequest(opts: HttpRequestOpts): Promise<HttpResponseIn
|
|
|
67
67
|
* 发送 json 格式的 post 请求,body 是未序列化的普通对象,表示要发送的请求数据.
|
|
68
68
|
* @param opts
|
|
69
69
|
*/
|
|
70
|
-
export declare function postJson<T>(opts: Pick<HttpRequestOpts, 'url' | 'query' | 'headers' | 'timeout'> & {
|
|
71
|
-
body:
|
|
70
|
+
export declare function postJson<T>(opts: Pick<HttpRequestOpts, 'url' | 'query' | 'headers' | 'timeout' | 'agent'> & {
|
|
71
|
+
body: unknown;
|
|
72
72
|
}): Promise<T>;
|
|
73
73
|
/**
|
|
74
74
|
* get 请求获取 json 格式数据.
|
|
75
75
|
* @param opts
|
|
76
76
|
*/
|
|
77
|
-
export declare function getJson<T>(opts: Pick<HttpRequestOpts, 'url' | 'query' | 'headers' | 'timeout'>): Promise<T>;
|
|
77
|
+
export declare function getJson<T>(opts: Pick<HttpRequestOpts, 'url' | 'query' | 'headers' | 'timeout' | 'agent'>): Promise<T>;
|
package/types/lock/index.d.ts
CHANGED