@maiyunnet/kebab 4.1.0 → 5.0.1

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/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "4.1.0";
8
+ export declare const VER = "5.0.1";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  export declare const LIB_PATH: string;
@@ -37,7 +37,12 @@ export interface IConfig {
37
37
  [key: string]: Json;
38
38
  };
39
39
  'const': IConfigConst;
40
- 'db': IConfigDb;
40
+ 'db': Record<string, {
41
+ 'default': IConfigDb;
42
+ 'read': IConfigDb;
43
+ }> & {
44
+ 'default': 'MYSQL' | 'PGSQL';
45
+ };
41
46
  'jwt': IConfigJwt;
42
47
  'kv': IConfigKv;
43
48
  'route': Record<string, string>;
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '4.1.0';
9
+ export const VER = '5.0.1';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
@@ -0,0 +1,91 @@
1
+ import * as mysql2 from 'mysql2/promise';
2
+ import * as pg from 'pg';
3
+ import * as lDb from '#kebab/lib/db.js';
4
+ import * as kebab from '#kebab/index.js';
5
+ /** --- 数据库连接对象 --- */
6
+ export declare class Connection {
7
+ /** --- 本连接最后一次使用时间 --- */
8
+ private _last;
9
+ /** --- 最后两次执行的 sql 完整串 --- */
10
+ private readonly _lastSql;
11
+ /** --- 数据库连接对象 --- */
12
+ private readonly _link;
13
+ /** --- 当前连接是否正在被独占使用 --- */
14
+ private _using;
15
+ /** --- 当发生断开,则需从连接池移除连接 --- */
16
+ private _lost;
17
+ /** --- 当前正在处理事务 --- */
18
+ private _transaction;
19
+ /** --- 当前的连接配置信息 --- */
20
+ private readonly _etc;
21
+ constructor(etc: kebab.IConfigDb, link: mysql2.Connection | pg.Client);
22
+ /**
23
+ * --- 获取连接 etc 信息 ---
24
+ */
25
+ getEtc(): kebab.IConfigDb;
26
+ /** --- 获取数据库服务类型 --- */
27
+ getService(): lDb.ESERVICE;
28
+ /**
29
+ * --- 获取最后一次获取连接的时间 ---
30
+ */
31
+ getLast(): number;
32
+ /**
33
+ * --- 获取最后两次执行的 sql 字符串 ---
34
+ */
35
+ getLastSql(): Array<{
36
+ 'sql': string;
37
+ 'values'?: kebab.DbValue[];
38
+ }>;
39
+ /**
40
+ * --- 将本条连接设置为不可用 ---
41
+ */
42
+ setLost(): void;
43
+ /**
44
+ * --- 是否已经丢失 ---
45
+ */
46
+ isLost(): boolean;
47
+ /**
48
+ * --- 是否是开启事务状态 ---
49
+ */
50
+ isTransaction(): boolean;
51
+ /**
52
+ * --- 获取当前状态是否正在被使用中 ---
53
+ */
54
+ isUsing(): boolean;
55
+ /**
56
+ * --- 判断是否可用(丢失的也算不可用),返回 true 代表获取成功并自动刷新最后时间 ---
57
+ */
58
+ using(): boolean;
59
+ /**
60
+ * --- 取消占用 ---
61
+ */
62
+ used(): void;
63
+ /**
64
+ * --- 设定最后使用时间 ---
65
+ */
66
+ refreshLast(): void;
67
+ /**
68
+ * --- 通过执行一条语句判断当前连接是否可用 ---
69
+ * @param last 是否刷新最后使用时间(默认刷新)
70
+ */
71
+ isAvailable(last?: boolean): Promise<boolean>;
72
+ /**
73
+ * --- 执行一条 SQL 并获得返回数据 ---
74
+ * @param sql 执行的 SQL 字符串
75
+ * @param values 要替换的 data 数据
76
+ */
77
+ query(sql: string, values?: kebab.DbValue[]): Promise<lDb.IData>;
78
+ /**
79
+ * --- 执行一条 SQL 并获得影响行数对象 packet ---
80
+ * @param sql 执行的 SQL 字符串
81
+ * @param values 要替换的 data 数据
82
+ */
83
+ execute(sql: string, values?: kebab.DbValue[]): Promise<lDb.IPacket>;
84
+ /**
85
+ * --- 关闭连接,一般情况下不使用 ---
86
+ */
87
+ end(): Promise<boolean>;
88
+ beginTransaction(): Promise<boolean>;
89
+ commit(): Promise<boolean>;
90
+ rollback(): Promise<boolean>;
91
+ }
package/lib/db/conn.js ADDED
@@ -0,0 +1,328 @@
1
+ import * as pg from 'pg';
2
+ import * as lCore from '#kebab/lib/core.js';
3
+ import * as lTime from '#kebab/lib/time.js';
4
+ import * as lDb from '#kebab/lib/db.js';
5
+ // --- 注册解析器 ---
6
+ pg.types.setTypeParser(pg.types.builtins.POLYGON, val => {
7
+ if (val === null) {
8
+ return null;
9
+ }
10
+ // --- val 如 ((1,1),(2,2),(3,3),(1,1)) ---
11
+ // --- 去除两端括号,然后按分隔解析坐标 ---
12
+ const matches = val
13
+ .replace(/^\(\(/, '')
14
+ .replace(/\)\)$/, '')
15
+ .split('),(');
16
+ const points = matches.map(pair => {
17
+ const [xStr, yStr] = pair.split(',');
18
+ return { 'x': parseFloat(xStr), 'y': parseFloat(yStr) };
19
+ });
20
+ // --- 返回 [{x: 1, y: 1}, {x :2, y: 2}, ... ] ---
21
+ return points;
22
+ });
23
+ /** --- 数据库连接对象 --- */
24
+ export class Connection {
25
+ constructor(etc, link) {
26
+ /** --- 本连接最后一次使用时间 --- */
27
+ this._last = 0;
28
+ /** --- 最后两次执行的 sql 完整串 --- */
29
+ this._lastSql = [];
30
+ /** --- 当前连接是否正在被独占使用 --- */
31
+ this._using = false;
32
+ /** --- 当发生断开,则需从连接池移除连接 --- */
33
+ this._lost = false;
34
+ /** --- 当前正在处理事务 --- */
35
+ this._transaction = false;
36
+ this._etc = etc;
37
+ this._link = link;
38
+ this.refreshLast();
39
+ }
40
+ /**
41
+ * --- 获取连接 etc 信息 ---
42
+ */
43
+ getEtc() {
44
+ return this._etc;
45
+ }
46
+ /** --- 获取数据库服务类型 --- */
47
+ getService() {
48
+ return this._link instanceof pg.Client ? lDb.ESERVICE.PGSQL : lDb.ESERVICE.MYSQL;
49
+ }
50
+ /**
51
+ * --- 获取最后一次获取连接的时间 ---
52
+ */
53
+ getLast() {
54
+ return this._last;
55
+ }
56
+ /**
57
+ * --- 获取最后两次执行的 sql 字符串 ---
58
+ */
59
+ getLastSql() {
60
+ return this._lastSql;
61
+ }
62
+ /**
63
+ * --- 将本条连接设置为不可用 ---
64
+ */
65
+ setLost() {
66
+ this._lost = true;
67
+ }
68
+ /**
69
+ * --- 是否已经丢失 ---
70
+ */
71
+ isLost() {
72
+ return this._lost;
73
+ }
74
+ /**
75
+ * --- 是否是开启事务状态 ---
76
+ */
77
+ isTransaction() {
78
+ return this._transaction;
79
+ }
80
+ /**
81
+ * --- 获取当前状态是否正在被使用中 ---
82
+ */
83
+ isUsing() {
84
+ return this._using;
85
+ }
86
+ /**
87
+ * --- 判断是否可用(丢失的也算不可用),返回 true 代表获取成功并自动刷新最后时间 ---
88
+ */
89
+ using() {
90
+ if (this._lost || this._using) {
91
+ return false;
92
+ }
93
+ else {
94
+ this.refreshLast();
95
+ this._using = true;
96
+ return true;
97
+ }
98
+ }
99
+ /**
100
+ * --- 取消占用 ---
101
+ */
102
+ used() {
103
+ this._using = false;
104
+ }
105
+ /**
106
+ * --- 设定最后使用时间 ---
107
+ */
108
+ refreshLast() {
109
+ this._last = lTime.stamp();
110
+ }
111
+ /**
112
+ * --- 通过执行一条语句判断当前连接是否可用 ---
113
+ * @param last 是否刷新最后使用时间(默认刷新)
114
+ */
115
+ async isAvailable(last = true) {
116
+ if (last) {
117
+ this.refreshLast();
118
+ }
119
+ try {
120
+ if (this._link instanceof pg.Client) {
121
+ await this._link.query('SELECT 1');
122
+ }
123
+ else {
124
+ await this._link.query('SELECT 1');
125
+ }
126
+ return true;
127
+ }
128
+ catch {
129
+ return false;
130
+ }
131
+ }
132
+ /**
133
+ * --- 执行一条 SQL 并获得返回数据 ---
134
+ * @param sql 执行的 SQL 字符串
135
+ * @param values 要替换的 data 数据
136
+ */
137
+ async query(sql, values) {
138
+ const rtn = {
139
+ 'rows': null,
140
+ 'fields': [],
141
+ 'error': null,
142
+ 'result': 1,
143
+ };
144
+ try {
145
+ this.refreshLast();
146
+ if (this._lastSql.length === 2) {
147
+ this._lastSql.splice(0, 1);
148
+ }
149
+ this._lastSql.push({
150
+ 'sql': sql,
151
+ 'values': values
152
+ });
153
+ const time = Date.now();
154
+ if (this._link instanceof pg.Client) {
155
+ const res = await this._link.query(sql, values);
156
+ rtn.rows = res.rows;
157
+ rtn.fields = res.fields.map(item => ({
158
+ 'name': item.name,
159
+ 'length': item.dataTypeSize,
160
+ }));
161
+ }
162
+ else {
163
+ const res = await this._link.execute(sql, values);
164
+ rtn.rows = res[0];
165
+ rtn.fields = res[1].map(item => ({
166
+ 'name': item.orgName || item.name,
167
+ 'length': item.length ?? -1,
168
+ }));
169
+ }
170
+ if (Date.now() - time > 200) {
171
+ lCore.log({}, '[WARNING][DB][Connection][query] slow sql 200ms: ' + sql, '-warning');
172
+ }
173
+ }
174
+ catch (e) {
175
+ rtn.error = {
176
+ 'message': e.message,
177
+ 'errno': e.errno ?? Number(e.code),
178
+ };
179
+ rtn.result = -500;
180
+ }
181
+ if (!this._transaction) {
182
+ this._using = false;
183
+ }
184
+ // --- 返回数据 ---
185
+ return rtn;
186
+ }
187
+ /**
188
+ * --- 执行一条 SQL 并获得影响行数对象 packet ---
189
+ * @param sql 执行的 SQL 字符串
190
+ * @param values 要替换的 data 数据
191
+ */
192
+ async execute(sql, values) {
193
+ const rtn = {
194
+ 'packet': null,
195
+ 'fields': [],
196
+ 'error': null,
197
+ 'result': 1,
198
+ };
199
+ try {
200
+ this.refreshLast();
201
+ if (this._lastSql.length === 2) {
202
+ this._lastSql.splice(0, 1);
203
+ }
204
+ this._lastSql.push({
205
+ 'sql': sql,
206
+ 'values': values,
207
+ });
208
+ const time = Date.now();
209
+ if (this._link instanceof pg.Client) {
210
+ if (sql.startsWith('INSERT ') && !sql.includes('RETURNING ')) {
211
+ if (sql.endsWith(';')) {
212
+ sql = sql.slice(0, -1);
213
+ }
214
+ sql += ' RETURNING "id"';
215
+ }
216
+ const res = await this._link.query(sql, values);
217
+ rtn.packet = {
218
+ 'affected': res.rowCount ?? 0,
219
+ 'insert': res.rows[0]?.id ?? 0,
220
+ };
221
+ rtn.fields = res.fields.map(item => ({
222
+ 'name': item.name,
223
+ 'length': item.dataTypeSize,
224
+ }));
225
+ }
226
+ else {
227
+ const res = await this._link.execute(sql, values);
228
+ rtn.packet = {
229
+ 'affected': res[0].affectedRows,
230
+ 'insert': res[0].insertId,
231
+ };
232
+ if (res[1]) {
233
+ rtn.fields = res[1].map(item => ({
234
+ 'name': item.orgName,
235
+ 'length': item.length ?? -1,
236
+ }));
237
+ }
238
+ }
239
+ if (Date.now() - time > 200) {
240
+ lCore.log({}, '[WARNING][DB][Connection][execute] slow sql 200ms: ' + sql, '-warning');
241
+ }
242
+ }
243
+ catch (e) {
244
+ let errno = e.errno ?? Number(e.code.replace(/[a-z][A-Z]/g, ''));
245
+ if (errno === 23505) {
246
+ errno = 1062;
247
+ }
248
+ // --- e.errno 可能为 1062 ---
249
+ rtn.error = {
250
+ 'message': e.message,
251
+ 'errno': errno,
252
+ };
253
+ rtn.result = -500;
254
+ }
255
+ if (!this._transaction) {
256
+ this._using = false;
257
+ }
258
+ // --- 返回数据 ---
259
+ return rtn;
260
+ }
261
+ /**
262
+ * --- 关闭连接,一般情况下不使用 ---
263
+ */
264
+ async end() {
265
+ try {
266
+ await this._link.end();
267
+ return true;
268
+ }
269
+ catch {
270
+ return false;
271
+ }
272
+ }
273
+ // --- 事务,只能在独占连接中使用,pool 创建事务返回独占连接,commit 或 rollback 释放连接回池 ---
274
+ async beginTransaction() {
275
+ if (this._using) {
276
+ try {
277
+ this._transaction = true;
278
+ if (this._link instanceof pg.Client) {
279
+ await this._link.query('BEGIN');
280
+ }
281
+ else {
282
+ await this._link.beginTransaction();
283
+ }
284
+ return true;
285
+ }
286
+ catch {
287
+ return false;
288
+ }
289
+ }
290
+ else {
291
+ return false;
292
+ }
293
+ }
294
+ async commit() {
295
+ try {
296
+ if (this._link instanceof pg.Client) {
297
+ await this._link.query('COMMIT');
298
+ }
299
+ else {
300
+ await this._link.commit();
301
+ }
302
+ this.refreshLast();
303
+ this._transaction = false;
304
+ this._using = false;
305
+ return true;
306
+ }
307
+ catch {
308
+ return false;
309
+ }
310
+ }
311
+ async rollback() {
312
+ try {
313
+ if (this._link instanceof pg.Client) {
314
+ await this._link.query('ROLLBACK');
315
+ }
316
+ else {
317
+ await this._link.rollback();
318
+ }
319
+ this.refreshLast();
320
+ this._transaction = false;
321
+ this._using = false;
322
+ return true;
323
+ }
324
+ catch {
325
+ return false;
326
+ }
327
+ }
328
+ }
@@ -0,0 +1,61 @@
1
+ import * as kebab from '#kebab/index.js';
2
+ import * as lDb from '#kebab/lib/db.js';
3
+ import * as sCtr from '#kebab/sys/ctr.js';
4
+ import { Transaction } from './tran.js';
5
+ /** --- 连接信息 --- */
6
+ export interface IConnectionInfo {
7
+ 'id': number;
8
+ 'service': lDb.ESERVICE;
9
+ 'last': number;
10
+ 'host': string;
11
+ 'port': number;
12
+ 'name': string;
13
+ 'user': string;
14
+ 'lost': boolean;
15
+ 'using': boolean;
16
+ 'transaction': boolean;
17
+ }
18
+ /**
19
+ * --- 获取当前连接池中所有连接的信息 ---
20
+ */
21
+ export declare function getConnectionList(): IConnectionInfo[];
22
+ /** --- 数据库连接池对象 --- */
23
+ export declare class Pool {
24
+ /** --- SQL 执行次数 --- */
25
+ private _queries;
26
+ /** --- 当前服务商 --- */
27
+ private readonly _service;
28
+ /** --- 当前 Pool 对象的数据库连接信息 --- */
29
+ private readonly _etc;
30
+ constructor(etc: kebab.IConfigDb, opt: {
31
+ /** --- 服务商 --- */
32
+ 'service': lDb.ESERVICE;
33
+ });
34
+ /** --- 获取当前连接的服务商 --- */
35
+ getService(): lDb.ESERVICE;
36
+ /**
37
+ * --- 执行一条 SQL,无视顺序和相同连接,随用随取 ---
38
+ * @param sql 执行的 SQL 字符串
39
+ * @param values 要替换的 data 数据
40
+ * @returns error.errno = -500 表示系统错误
41
+ */
42
+ query(sql: string, values?: kebab.DbValue[]): Promise<lDb.IData>;
43
+ /**
44
+ * --- 执行一条 SQL 并获得影响行数对象 packet,连接失败抛出错误 ---
45
+ * @param sql 执行的 SQL 字符串
46
+ * @param values 要替换的 data 数据
47
+ */
48
+ execute(sql: string, values?: kebab.DbValue[]): Promise<lDb.IPacket>;
49
+ /**
50
+ * --- 开启事务,返回事务对象并锁定连接,别人任何人不可用,有 ctr 的话必传 this,独立执行时可传 null ---
51
+ */
52
+ beginTransaction(ctr: sCtr.Ctr | null): Promise<Transaction | null>;
53
+ /**
54
+ * --- 获取一个连接,自动变为 using 状态,;连接失败会返回 null ---
55
+ */
56
+ private _getConnection;
57
+ /**
58
+ * --- 获取 SQL 执行次数 ---
59
+ */
60
+ getQueries(): number;
61
+ }