@zhin.js/database 1.0.3 → 1.0.5
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/CHANGELOG.md +12 -0
- package/README.md +1360 -34
- package/lib/base/database.d.ts +71 -13
- package/lib/base/database.d.ts.map +1 -1
- package/lib/base/database.js +128 -4
- package/lib/base/database.js.map +1 -1
- package/lib/base/dialect.d.ts +27 -10
- package/lib/base/dialect.d.ts.map +1 -1
- package/lib/base/dialect.js +32 -0
- package/lib/base/dialect.js.map +1 -1
- package/lib/base/index.d.ts +1 -0
- package/lib/base/index.d.ts.map +1 -1
- package/lib/base/index.js +1 -0
- package/lib/base/index.js.map +1 -1
- package/lib/base/model.d.ts +105 -12
- package/lib/base/model.d.ts.map +1 -1
- package/lib/base/model.js +224 -3
- package/lib/base/model.js.map +1 -1
- package/lib/base/query-classes.d.ts +204 -33
- package/lib/base/query-classes.d.ts.map +1 -1
- package/lib/base/query-classes.js +276 -0
- package/lib/base/query-classes.js.map +1 -1
- package/lib/base/thenable.d.ts +7 -7
- package/lib/base/thenable.d.ts.map +1 -1
- package/lib/base/thenable.js +5 -4
- package/lib/base/thenable.js.map +1 -1
- package/lib/base/transaction.d.ts +46 -0
- package/lib/base/transaction.d.ts.map +1 -0
- package/lib/base/transaction.js +186 -0
- package/lib/base/transaction.js.map +1 -0
- package/lib/dialects/memory.d.ts +13 -8
- package/lib/dialects/memory.d.ts.map +1 -1
- package/lib/dialects/memory.js +10 -7
- package/lib/dialects/memory.js.map +1 -1
- package/lib/dialects/mongodb.d.ts +12 -8
- package/lib/dialects/mongodb.d.ts.map +1 -1
- package/lib/dialects/mongodb.js +21 -18
- package/lib/dialects/mongodb.js.map +1 -1
- package/lib/dialects/mysql.d.ts +36 -7
- package/lib/dialects/mysql.d.ts.map +1 -1
- package/lib/dialects/mysql.js +140 -21
- package/lib/dialects/mysql.js.map +1 -1
- package/lib/dialects/pg.d.ts +36 -7
- package/lib/dialects/pg.d.ts.map +1 -1
- package/lib/dialects/pg.js +140 -21
- package/lib/dialects/pg.js.map +1 -1
- package/lib/dialects/redis.d.ts +13 -8
- package/lib/dialects/redis.d.ts.map +1 -1
- package/lib/dialects/redis.js +14 -11
- package/lib/dialects/redis.js.map +1 -1
- package/lib/dialects/sqlite.d.ts +20 -7
- package/lib/dialects/sqlite.d.ts.map +1 -1
- package/lib/dialects/sqlite.js +66 -13
- package/lib/dialects/sqlite.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/migration.d.ts +132 -0
- package/lib/migration.d.ts.map +1 -0
- package/lib/migration.js +475 -0
- package/lib/migration.js.map +1 -0
- package/lib/registry.d.ts +26 -23
- package/lib/registry.d.ts.map +1 -1
- package/lib/registry.js +1 -5
- package/lib/registry.js.map +1 -1
- package/lib/type/document/database.d.ts +12 -12
- package/lib/type/document/database.d.ts.map +1 -1
- package/lib/type/document/database.js +1 -1
- package/lib/type/document/database.js.map +1 -1
- package/lib/type/document/model.d.ts +8 -8
- package/lib/type/document/model.d.ts.map +1 -1
- package/lib/type/document/model.js +1 -1
- package/lib/type/document/model.js.map +1 -1
- package/lib/type/keyvalue/database.d.ts +12 -12
- package/lib/type/keyvalue/database.d.ts.map +1 -1
- package/lib/type/keyvalue/database.js +1 -1
- package/lib/type/keyvalue/database.js.map +1 -1
- package/lib/type/keyvalue/model.d.ts +3 -3
- package/lib/type/keyvalue/model.d.ts.map +1 -1
- package/lib/type/keyvalue/model.js +1 -1
- package/lib/type/keyvalue/model.js.map +1 -1
- package/lib/type/related/database.d.ts +49 -14
- package/lib/type/related/database.d.ts.map +1 -1
- package/lib/type/related/database.js +259 -28
- package/lib/type/related/database.js.map +1 -1
- package/lib/type/related/model.d.ts +252 -16
- package/lib/type/related/model.d.ts.map +1 -1
- package/lib/type/related/model.js +648 -23
- package/lib/type/related/model.js.map +1 -1
- package/lib/types.d.ts +475 -37
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +6 -0
- package/lib/types.js.map +1 -1
- package/package.json +10 -5
- package/src/base/database.ts +168 -24
- package/src/base/dialect.ts +49 -10
- package/src/base/index.ts +2 -1
- package/src/base/model.ts +258 -18
- package/src/base/query-classes.ts +471 -63
- package/src/base/thenable.ts +12 -11
- package/src/base/transaction.ts +213 -0
- package/src/dialects/memory.ts +17 -16
- package/src/dialects/mongodb.ts +44 -42
- package/src/dialects/mysql.ts +155 -26
- package/src/dialects/pg.ts +152 -25
- package/src/dialects/redis.ts +45 -43
- package/src/dialects/sqlite.ts +77 -19
- package/src/index.ts +1 -2
- package/src/migration.ts +544 -0
- package/src/registry.ts +33 -33
- package/src/type/document/database.ts +33 -33
- package/src/type/document/model.ts +15 -15
- package/src/type/keyvalue/database.ts +33 -33
- package/src/type/keyvalue/model.ts +19 -19
- package/src/type/related/database.ts +309 -34
- package/src/type/related/model.ts +801 -34
- package/src/types.ts +559 -44
- package/tests/database.test.ts +1738 -0
package/src/dialects/mysql.ts
CHANGED
|
@@ -1,28 +1,50 @@
|
|
|
1
|
-
import {Dialect} from '../base';
|
|
2
|
-
import {RelatedDatabase} from "../type/related/database";
|
|
3
|
-
import {Registry} from "../registry";
|
|
4
|
-
import type { ConnectionOptions } from 'mysql2/promise';
|
|
5
|
-
import {Database} from "../base";
|
|
6
|
-
import {Column} from "../types";
|
|
1
|
+
import {Dialect} from '../base/index.js';
|
|
2
|
+
import {RelatedDatabase} from "../type/related/database.js";
|
|
3
|
+
import {Registry} from "../registry.js";
|
|
4
|
+
import type { ConnectionOptions, PoolOptions } from 'mysql2/promise';
|
|
5
|
+
import {Database} from "../base/index.js";
|
|
6
|
+
import {Column, Transaction, TransactionOptions, IsolationLevel, PoolConfig} from "../types.js";
|
|
7
7
|
|
|
8
|
-
export interface MySQLDialectConfig extends ConnectionOptions {
|
|
8
|
+
export interface MySQLDialectConfig extends ConnectionOptions {
|
|
9
|
+
/**
|
|
10
|
+
* 连接池配置
|
|
11
|
+
* 如果提供此选项,将使用连接池而不是单连接
|
|
12
|
+
*/
|
|
13
|
+
pool?: PoolConfig;
|
|
14
|
+
}
|
|
9
15
|
|
|
10
|
-
export class MySQLDialect extends Dialect<MySQLDialectConfig, string> {
|
|
16
|
+
export class MySQLDialect<S extends Record<string, object> = Record<string, object>> extends Dialect<MySQLDialectConfig, S, string> {
|
|
11
17
|
private connection: any = null;
|
|
18
|
+
private pool: any = null;
|
|
19
|
+
private usePool: boolean = false;
|
|
12
20
|
|
|
13
21
|
constructor(config: MySQLDialectConfig) {
|
|
14
22
|
super('mysql', config);
|
|
23
|
+
this.usePool = !!config.pool;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
// Connection management
|
|
18
27
|
isConnected(): boolean {
|
|
19
|
-
return this.connection !== null;
|
|
28
|
+
return this.usePool ? this.pool !== null : this.connection !== null;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
async connect(): Promise<void> {
|
|
23
32
|
try {
|
|
33
|
+
if (this.usePool) {
|
|
34
|
+
const { createPool } = await import('mysql2/promise');
|
|
35
|
+
const poolConfig: PoolOptions = {
|
|
36
|
+
...this.config,
|
|
37
|
+
waitForConnections: true,
|
|
38
|
+
connectionLimit: this.config.pool?.max ?? 10,
|
|
39
|
+
queueLimit: 0,
|
|
40
|
+
idleTimeout: this.config.pool?.idleTimeoutMillis ?? 60000,
|
|
41
|
+
};
|
|
42
|
+
this.pool = createPool(poolConfig);
|
|
43
|
+
console.log(`MySQL 连接池已创建 (max: ${poolConfig.connectionLimit})`);
|
|
44
|
+
} else {
|
|
24
45
|
const { createConnection } = await import('mysql2/promise');
|
|
25
46
|
this.connection = await createConnection(this.config);
|
|
47
|
+
}
|
|
26
48
|
} catch (error) {
|
|
27
49
|
console.error('forgot install mysql2 ?');
|
|
28
50
|
throw new Error(`MySQL 连接失败: ${error}`);
|
|
@@ -30,23 +52,50 @@ export class MySQLDialect extends Dialect<MySQLDialectConfig, string> {
|
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
async disconnect(): Promise<void> {
|
|
55
|
+
if (this.usePool && this.pool) {
|
|
56
|
+
await this.pool.end();
|
|
57
|
+
this.pool = null;
|
|
58
|
+
console.log('MySQL 连接池已关闭');
|
|
59
|
+
} else if (this.connection) {
|
|
60
|
+
await this.connection.end();
|
|
33
61
|
this.connection = null;
|
|
62
|
+
}
|
|
34
63
|
}
|
|
35
64
|
|
|
36
65
|
async healthCheck(): Promise<boolean> {
|
|
37
|
-
|
|
66
|
+
if (!this.isConnected()) return false;
|
|
67
|
+
try {
|
|
68
|
+
await this.query('SELECT 1');
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
38
73
|
}
|
|
39
74
|
|
|
40
75
|
async query<U = any>(sql: string, params?: any[]): Promise<U> {
|
|
76
|
+
if (this.usePool) {
|
|
77
|
+
const [rows] = await this.pool.execute(sql, params);
|
|
78
|
+
return rows as U;
|
|
79
|
+
} else {
|
|
41
80
|
const [rows] = await this.connection.execute(sql, params);
|
|
42
81
|
return rows as U;
|
|
82
|
+
}
|
|
43
83
|
}
|
|
44
84
|
|
|
45
85
|
async dispose(): Promise<void> {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
86
|
+
await this.disconnect();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 获取连接池统计信息(仅在使用连接池时有效)
|
|
91
|
+
*/
|
|
92
|
+
getPoolStats(): { total: number; idle: number; waiting: number } | null {
|
|
93
|
+
if (!this.usePool || !this.pool) return null;
|
|
94
|
+
return {
|
|
95
|
+
total: this.pool.pool?._allConnections?.length ?? 0,
|
|
96
|
+
idle: this.pool.pool?._freeConnections?.length ?? 0,
|
|
97
|
+
waiting: this.pool.pool?._connectionQueue?.length ?? 0,
|
|
98
|
+
};
|
|
50
99
|
}
|
|
51
100
|
|
|
52
101
|
// SQL generation methods
|
|
@@ -118,8 +167,8 @@ export class MySQLDialect extends Dialect<MySQLDialectConfig, string> {
|
|
|
118
167
|
return `LIMIT ${offset}, ${limit}`;
|
|
119
168
|
}
|
|
120
169
|
|
|
121
|
-
formatCreateTable(tableName:
|
|
122
|
-
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(tableName)} (${columns.join(', ')}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
|
|
170
|
+
formatCreateTable<T extends keyof S>(tableName: T, columns: string[]): string {
|
|
171
|
+
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(String(tableName))} (${columns.join(', ')}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
|
|
123
172
|
}
|
|
124
173
|
|
|
125
174
|
formatColumnDefinition(field: string, column: Column<any>): string {
|
|
@@ -137,21 +186,101 @@ export class MySQLDialect extends Dialect<MySQLDialectConfig, string> {
|
|
|
137
186
|
return `${name} ${type}${length}${primary}${unique}${autoIncrement}${nullable}${defaultVal}`;
|
|
138
187
|
}
|
|
139
188
|
|
|
140
|
-
formatAlterTable(tableName:
|
|
141
|
-
return `ALTER TABLE ${this.quoteIdentifier(tableName)} ${alterations.join(', ')}`;
|
|
189
|
+
formatAlterTable<T extends keyof S>(tableName: T, alterations: string[]): string {
|
|
190
|
+
return `ALTER TABLE ${this.quoteIdentifier(String(tableName))} ${alterations.join(', ')}`;
|
|
142
191
|
}
|
|
143
192
|
|
|
144
|
-
formatDropTable(tableName:
|
|
193
|
+
formatDropTable<T extends keyof S>(tableName: T, ifExists?: boolean): string {
|
|
145
194
|
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
146
|
-
return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(tableName)}`;
|
|
195
|
+
return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(String(tableName))}`;
|
|
147
196
|
}
|
|
148
197
|
|
|
149
|
-
formatDropIndex(indexName: string, tableName:
|
|
198
|
+
formatDropIndex<T extends keyof S>(indexName: string, tableName: T, ifExists?: boolean): string {
|
|
150
199
|
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
151
|
-
return `DROP INDEX ${ifExistsClause}${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(tableName)}`;
|
|
200
|
+
return `DROP INDEX ${ifExistsClause}${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(String(tableName))}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Transaction Support
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* MySQL 支持事务
|
|
209
|
+
*/
|
|
210
|
+
supportsTransactions(): boolean {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 开始事务
|
|
216
|
+
* 在连接池模式下,会获取一个专用连接用于事务
|
|
217
|
+
*/
|
|
218
|
+
async beginTransaction(options?: TransactionOptions): Promise<Transaction> {
|
|
219
|
+
if (this.usePool) {
|
|
220
|
+
// 从连接池获取一个连接用于事务
|
|
221
|
+
const connection = await this.pool.getConnection();
|
|
222
|
+
|
|
223
|
+
// 设置隔离级别
|
|
224
|
+
if (options?.isolationLevel) {
|
|
225
|
+
await connection.execute(`SET TRANSACTION ISOLATION LEVEL ${this.formatIsolationLevel(options.isolationLevel)}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 开始事务
|
|
229
|
+
await connection.execute('START TRANSACTION');
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
async commit(): Promise<void> {
|
|
233
|
+
try {
|
|
234
|
+
await connection.execute('COMMIT');
|
|
235
|
+
} finally {
|
|
236
|
+
connection.release(); // 归还连接到池
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
async rollback(): Promise<void> {
|
|
241
|
+
try {
|
|
242
|
+
await connection.execute('ROLLBACK');
|
|
243
|
+
} finally {
|
|
244
|
+
connection.release(); // 归还连接到池
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
async query<T = any>(sql: string, params?: any[]): Promise<T> {
|
|
249
|
+
const [rows] = await connection.execute(sql, params);
|
|
250
|
+
return rows as T;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
} else {
|
|
254
|
+
// 单连接模式
|
|
255
|
+
const dialect = this;
|
|
256
|
+
|
|
257
|
+
// 设置隔离级别
|
|
258
|
+
if (options?.isolationLevel) {
|
|
259
|
+
await this.query(`SET TRANSACTION ISOLATION LEVEL ${this.formatIsolationLevel(options.isolationLevel)}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 开始事务
|
|
263
|
+
await this.query('START TRANSACTION');
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
async commit(): Promise<void> {
|
|
267
|
+
await dialect.query('COMMIT');
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
async rollback(): Promise<void> {
|
|
271
|
+
await dialect.query('ROLLBACK');
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
async query<T = any>(sql: string, params?: any[]): Promise<T> {
|
|
275
|
+
return dialect.query<T>(sql, params);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
152
279
|
}
|
|
153
280
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
281
|
+
export class MySQL<S extends Record<string, object> = Record<string, object>> extends RelatedDatabase<MySQLDialectConfig, S> {
|
|
282
|
+
constructor(config: MySQLDialectConfig, definitions?: Database.DefinitionObj<S>) {
|
|
283
|
+
super(new MySQLDialect<S>(config), definitions);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
Registry.register('mysql', MySQL);
|
package/src/dialects/pg.ts
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
|
-
import {Dialect} from '../base';
|
|
2
|
-
import {RelatedDatabase} from "../type/related/database";
|
|
3
|
-
import {Database} from "../base";
|
|
4
|
-
import {Registry} from "../registry";
|
|
5
|
-
import type { ClientConfig } from 'pg';
|
|
6
|
-
import {Column} from "../types";
|
|
1
|
+
import {Dialect} from '../base/index.js';
|
|
2
|
+
import {RelatedDatabase} from "../type/related/database.js";
|
|
3
|
+
import {Database} from "../base/index.js";
|
|
4
|
+
import {Registry} from "../registry.js";
|
|
5
|
+
import type { ClientConfig, PoolConfig as PgPoolConfig } from 'pg';
|
|
6
|
+
import {Column, Transaction, TransactionOptions, PoolConfig} from "../types.js";
|
|
7
7
|
|
|
8
|
-
export interface PostgreSQLDialectConfig extends ClientConfig {
|
|
8
|
+
export interface PostgreSQLDialectConfig extends ClientConfig {
|
|
9
|
+
/**
|
|
10
|
+
* 连接池配置
|
|
11
|
+
* 如果提供此选项,将使用连接池而不是单连接
|
|
12
|
+
*/
|
|
13
|
+
pool?: PoolConfig;
|
|
14
|
+
}
|
|
9
15
|
|
|
10
|
-
export class PostgreSQLDialect extends Dialect<PostgreSQLDialectConfig, string> {
|
|
16
|
+
export class PostgreSQLDialect<S extends Record<string, object> = Record<string, object>> extends Dialect<PostgreSQLDialectConfig, S, string> {
|
|
11
17
|
private connection: any = null;
|
|
18
|
+
private pool: any = null;
|
|
19
|
+
private usePool: boolean = false;
|
|
12
20
|
|
|
13
21
|
constructor(config: PostgreSQLDialectConfig) {
|
|
14
22
|
super('pg', config);
|
|
23
|
+
this.usePool = !!config.pool;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
// Connection management
|
|
18
27
|
isConnected(): boolean {
|
|
19
|
-
return this.connection !== null;
|
|
28
|
+
return this.usePool ? this.pool !== null : this.connection !== null;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
async connect(): Promise<void> {
|
|
23
32
|
try {
|
|
33
|
+
if (this.usePool) {
|
|
34
|
+
const { Pool } = await import('pg');
|
|
35
|
+
const poolConfig: PgPoolConfig = {
|
|
36
|
+
...this.config,
|
|
37
|
+
max: this.config.pool?.max ?? 10,
|
|
38
|
+
min: this.config.pool?.min ?? 2,
|
|
39
|
+
idleTimeoutMillis: this.config.pool?.idleTimeoutMillis ?? 30000,
|
|
40
|
+
connectionTimeoutMillis: this.config.pool?.acquireTimeoutMillis ?? 10000,
|
|
41
|
+
};
|
|
42
|
+
this.pool = new Pool(poolConfig);
|
|
43
|
+
console.log(`PostgreSQL 连接池已创建 (max: ${poolConfig.max})`);
|
|
44
|
+
} else {
|
|
24
45
|
const { Client } = await import('pg');
|
|
25
46
|
this.connection = new Client(this.config);
|
|
26
47
|
await this.connection.connect();
|
|
48
|
+
}
|
|
27
49
|
} catch (error) {
|
|
28
50
|
console.error('forgot install pg ?');
|
|
29
51
|
throw new Error(`PostgreSQL 连接失败: ${error}`);
|
|
@@ -31,23 +53,50 @@ export class PostgreSQLDialect extends Dialect<PostgreSQLDialectConfig, string>
|
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
async disconnect(): Promise<void> {
|
|
56
|
+
if (this.usePool && this.pool) {
|
|
57
|
+
await this.pool.end();
|
|
58
|
+
this.pool = null;
|
|
59
|
+
console.log('PostgreSQL 连接池已关闭');
|
|
60
|
+
} else if (this.connection) {
|
|
61
|
+
await this.connection.end();
|
|
34
62
|
this.connection = null;
|
|
63
|
+
}
|
|
35
64
|
}
|
|
36
65
|
|
|
37
66
|
async healthCheck(): Promise<boolean> {
|
|
38
|
-
|
|
67
|
+
if (!this.isConnected()) return false;
|
|
68
|
+
try {
|
|
69
|
+
await this.query('SELECT 1');
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
39
74
|
}
|
|
40
75
|
|
|
41
76
|
async query<U = any>(sql: string, params?: any[]): Promise<U> {
|
|
77
|
+
if (this.usePool) {
|
|
78
|
+
const result = await this.pool.query(sql, params);
|
|
79
|
+
return result.rows as U;
|
|
80
|
+
} else {
|
|
42
81
|
const result = await this.connection.query(sql, params);
|
|
43
82
|
return result.rows as U;
|
|
83
|
+
}
|
|
44
84
|
}
|
|
45
85
|
|
|
46
86
|
async dispose(): Promise<void> {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
await this.disconnect();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 获取连接池统计信息(仅在使用连接池时有效)
|
|
92
|
+
*/
|
|
93
|
+
getPoolStats(): { total: number; idle: number; waiting: number } | null {
|
|
94
|
+
if (!this.usePool || !this.pool) return null;
|
|
95
|
+
return {
|
|
96
|
+
total: this.pool.totalCount ?? 0,
|
|
97
|
+
idle: this.pool.idleCount ?? 0,
|
|
98
|
+
waiting: this.pool.waitingCount ?? 0,
|
|
99
|
+
};
|
|
51
100
|
}
|
|
52
101
|
|
|
53
102
|
// SQL generation methods
|
|
@@ -119,8 +168,8 @@ export class PostgreSQLDialect extends Dialect<PostgreSQLDialectConfig, string>
|
|
|
119
168
|
return `LIMIT ${limit} OFFSET ${offset}`;
|
|
120
169
|
}
|
|
121
170
|
|
|
122
|
-
formatCreateTable(tableName:
|
|
123
|
-
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(tableName)} (${columns.join(', ')})`;
|
|
171
|
+
formatCreateTable<T extends keyof S>(tableName: T, columns: string[]): string {
|
|
172
|
+
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(String(tableName))} (${columns.join(', ')})`;
|
|
124
173
|
}
|
|
125
174
|
|
|
126
175
|
formatColumnDefinition(field: string, column: Column<any>): string {
|
|
@@ -137,21 +186,99 @@ export class PostgreSQLDialect extends Dialect<PostgreSQLDialectConfig, string>
|
|
|
137
186
|
return `${name} ${type}${length}${primary}${unique}${nullable}${defaultVal}`;
|
|
138
187
|
}
|
|
139
188
|
|
|
140
|
-
formatAlterTable(tableName:
|
|
141
|
-
return `ALTER TABLE ${this.quoteIdentifier(tableName)} ${alterations.join(', ')}`;
|
|
189
|
+
formatAlterTable<T extends keyof S>(tableName: T, alterations: string[]): string {
|
|
190
|
+
return `ALTER TABLE ${this.quoteIdentifier(String(tableName))} ${alterations.join(', ')}`;
|
|
142
191
|
}
|
|
143
192
|
|
|
144
|
-
formatDropTable(tableName:
|
|
193
|
+
formatDropTable<T extends keyof S>(tableName: T, ifExists?: boolean): string {
|
|
145
194
|
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
146
|
-
return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(tableName)}`;
|
|
195
|
+
return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(String(tableName))}`;
|
|
147
196
|
}
|
|
148
197
|
|
|
149
|
-
formatDropIndex(indexName: string, tableName:
|
|
198
|
+
formatDropIndex<T extends keyof S>(indexName: string, tableName: T, ifExists?: boolean): string {
|
|
150
199
|
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
151
200
|
return `DROP INDEX ${ifExistsClause}${this.quoteIdentifier(indexName)}`;
|
|
152
201
|
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Transaction Support
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* PostgreSQL 支持事务
|
|
209
|
+
*/
|
|
210
|
+
supportsTransactions(): boolean {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 开始事务
|
|
216
|
+
* 在连接池模式下,会获取一个专用连接用于事务
|
|
217
|
+
*/
|
|
218
|
+
async beginTransaction(options?: TransactionOptions): Promise<Transaction> {
|
|
219
|
+
if (this.usePool) {
|
|
220
|
+
// 从连接池获取一个连接用于事务
|
|
221
|
+
const client = await this.pool.connect();
|
|
222
|
+
|
|
223
|
+
// 开始事务(可以带隔离级别)
|
|
224
|
+
let beginSql = 'BEGIN';
|
|
225
|
+
if (options?.isolationLevel) {
|
|
226
|
+
beginSql = `BEGIN ISOLATION LEVEL ${this.formatIsolationLevel(options.isolationLevel)}`;
|
|
227
|
+
}
|
|
228
|
+
await client.query(beginSql);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
async commit(): Promise<void> {
|
|
232
|
+
try {
|
|
233
|
+
await client.query('COMMIT');
|
|
234
|
+
} finally {
|
|
235
|
+
client.release(); // 归还连接到池
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
async rollback(): Promise<void> {
|
|
240
|
+
try {
|
|
241
|
+
await client.query('ROLLBACK');
|
|
242
|
+
} finally {
|
|
243
|
+
client.release(); // 归还连接到池
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
async query<T = any>(sql: string, params?: any[]): Promise<T> {
|
|
248
|
+
const result = await client.query(sql, params);
|
|
249
|
+
return result.rows as T;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
} else {
|
|
253
|
+
// 单连接模式
|
|
254
|
+
const dialect = this;
|
|
255
|
+
|
|
256
|
+
// 开始事务(可以带隔离级别)
|
|
257
|
+
let beginSql = 'BEGIN';
|
|
258
|
+
if (options?.isolationLevel) {
|
|
259
|
+
beginSql = `BEGIN ISOLATION LEVEL ${this.formatIsolationLevel(options.isolationLevel)}`;
|
|
260
|
+
}
|
|
261
|
+
await this.query(beginSql);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
async commit(): Promise<void> {
|
|
265
|
+
await dialect.query('COMMIT');
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
async rollback(): Promise<void> {
|
|
269
|
+
await dialect.query('ROLLBACK');
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
async query<T = any>(sql: string, params?: any[]): Promise<T> {
|
|
273
|
+
return dialect.query<T>(sql, params);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
153
278
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
279
|
+
export class PG<S extends Record<string, object> = Record<string, object>> extends RelatedDatabase<PostgreSQLDialectConfig, S> {
|
|
280
|
+
constructor(config: PostgreSQLDialectConfig, definitions?: Database.DefinitionObj<S>) {
|
|
281
|
+
super(new PostgreSQLDialect<S>(config), definitions);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
Registry.register('pg', PG);
|
package/src/dialects/redis.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {Dialect} from "../base";
|
|
2
|
-
import {KeyValueDatabase} from "../type/keyvalue/database";
|
|
3
|
-
import {Registry} from "../registry";
|
|
4
|
-
import {Database} from "../base";
|
|
1
|
+
import {Dialect} from "../base/index.js";
|
|
2
|
+
import {KeyValueDatabase} from "../type/keyvalue/database.js";
|
|
3
|
+
import {Registry} from "../registry.js";
|
|
4
|
+
import {Database} from "../base/index.js";
|
|
5
5
|
import type { RedisClientOptions } from 'redis';
|
|
6
6
|
import {
|
|
7
7
|
BuildQueryResult,
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
AlterQueryParams,
|
|
17
17
|
DropTableQueryParams,
|
|
18
18
|
DropIndexQueryParams
|
|
19
|
-
} from "../types";
|
|
19
|
+
} from "../types.js";
|
|
20
20
|
|
|
21
21
|
export interface RedisDialectConfig extends RedisClientOptions {}
|
|
22
|
-
export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResult> {
|
|
22
|
+
export class RedisDialect<S extends Record<string, object> = Record<string, object>> extends Dialect<RedisDialectConfig, S, KeyValueQueryResult> {
|
|
23
23
|
private client: any = null;
|
|
24
24
|
|
|
25
25
|
constructor(config: RedisDialectConfig) {
|
|
@@ -164,24 +164,24 @@ export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResul
|
|
|
164
164
|
/**
|
|
165
165
|
* 构建查询
|
|
166
166
|
*/
|
|
167
|
-
buildQuery<
|
|
167
|
+
buildQuery<T extends keyof S>(params: QueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
168
168
|
switch (params.type) {
|
|
169
169
|
case 'create':
|
|
170
|
-
return this.buildCreateQuery(params as CreateQueryParams<
|
|
170
|
+
return this.buildCreateQuery(params as CreateQueryParams<S, T>);
|
|
171
171
|
case 'select':
|
|
172
|
-
return this.buildSelectQuery(params as SelectQueryParams<
|
|
172
|
+
return this.buildSelectQuery(params as SelectQueryParams<S, T>);
|
|
173
173
|
case 'insert':
|
|
174
|
-
return this.buildInsertQuery(params as InsertQueryParams<
|
|
174
|
+
return this.buildInsertQuery(params as InsertQueryParams<S, T>);
|
|
175
175
|
case 'update':
|
|
176
|
-
return this.buildUpdateQuery(params as UpdateQueryParams<
|
|
176
|
+
return this.buildUpdateQuery(params as UpdateQueryParams<S, T>);
|
|
177
177
|
case 'delete':
|
|
178
|
-
return this.buildDeleteQuery(params as DeleteQueryParams<
|
|
178
|
+
return this.buildDeleteQuery(params as DeleteQueryParams<S, T>);
|
|
179
179
|
case 'alter':
|
|
180
|
-
return this.buildAlterQuery(params as AlterQueryParams<
|
|
180
|
+
return this.buildAlterQuery(params as AlterQueryParams<S, T>);
|
|
181
181
|
case 'drop_table':
|
|
182
|
-
return this.buildDropTableQuery(params as DropTableQueryParams<
|
|
182
|
+
return this.buildDropTableQuery(params as DropTableQueryParams<S, T>);
|
|
183
183
|
case 'drop_index':
|
|
184
|
-
return this.buildDropIndexQuery(params as DropIndexQueryParams);
|
|
184
|
+
return this.buildDropIndexQuery(params as DropIndexQueryParams<S, T>);
|
|
185
185
|
default:
|
|
186
186
|
throw new Error(`不支持的查询类型: ${(params as any).type}`);
|
|
187
187
|
}
|
|
@@ -236,36 +236,36 @@ export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResul
|
|
|
236
236
|
return `${limit},${offset}`;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
formatCreateTable(tableName:
|
|
240
|
-
return `CREATE TABLE ${tableName} (${columns.join(',')})`;
|
|
239
|
+
formatCreateTable(tableName: keyof S, columns: string[]): string {
|
|
240
|
+
return `CREATE TABLE ${String(tableName)} (${columns.join(',')})`;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
formatAlterTable(tableName:
|
|
244
|
-
return `ALTER TABLE ${tableName} ${alterations.join(',')}`;
|
|
243
|
+
formatAlterTable(tableName: keyof S, alterations: string[]): string {
|
|
244
|
+
return `ALTER TABLE ${String(tableName)} ${alterations.join(',')}`;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
formatDropTable(tableName:
|
|
248
|
-
return `DROP TABLE ${tableName} ${ifExists ? 'IF EXISTS' : ''}`;
|
|
247
|
+
formatDropTable(tableName: keyof S, ifExists?: boolean): string {
|
|
248
|
+
return `DROP TABLE ${String(tableName)} ${ifExists ? 'IF EXISTS' : ''}`;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
formatDropIndex(indexName: string, tableName:
|
|
252
|
-
return `DROP INDEX ${indexName} ON ${tableName} ${ifExists ? 'IF EXISTS' : ''}`;
|
|
251
|
+
formatDropIndex(indexName: string, tableName: keyof S, ifExists?: boolean): string {
|
|
252
|
+
return `DROP INDEX ${indexName} ON ${String(tableName)} ${ifExists ? 'IF EXISTS' : ''}`;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// Redis 特定的查询构建方法
|
|
256
|
-
private buildCreateQuery<T extends
|
|
256
|
+
private buildCreateQuery<T extends keyof S>(params: CreateQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
257
257
|
return {
|
|
258
258
|
query: {
|
|
259
|
-
bucket: params.tableName,
|
|
259
|
+
bucket: String(params.tableName),
|
|
260
260
|
operation: 'keys'
|
|
261
261
|
},
|
|
262
262
|
params: []
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
private buildSelectQuery<T extends
|
|
266
|
+
private buildSelectQuery<T extends keyof S>(params: SelectQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
267
267
|
const query: KeyValueQueryResult = {
|
|
268
|
-
bucket: params.tableName,
|
|
268
|
+
bucket: params.tableName as string,
|
|
269
269
|
operation: 'keys'
|
|
270
270
|
};
|
|
271
271
|
|
|
@@ -284,12 +284,12 @@ export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResul
|
|
|
284
284
|
};
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
private buildInsertQuery<T extends
|
|
287
|
+
private buildInsertQuery<T extends keyof S>(params: InsertQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
288
288
|
const key = this.extractKeyFromData(params.data);
|
|
289
289
|
|
|
290
290
|
return {
|
|
291
291
|
query: {
|
|
292
|
-
bucket: params.tableName,
|
|
292
|
+
bucket: params.tableName as string,
|
|
293
293
|
operation: 'set',
|
|
294
294
|
key: key || 'default',
|
|
295
295
|
value: params.data
|
|
@@ -298,12 +298,12 @@ export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResul
|
|
|
298
298
|
};
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
private buildUpdateQuery<T extends
|
|
301
|
+
private buildUpdateQuery<T extends keyof S>(params: UpdateQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
302
302
|
const key = this.extractKeyFromCondition(params.conditions);
|
|
303
303
|
|
|
304
304
|
return {
|
|
305
305
|
query: {
|
|
306
|
-
bucket: params.tableName,
|
|
306
|
+
bucket: params.tableName as string,
|
|
307
307
|
operation: 'set',
|
|
308
308
|
key: key || 'default',
|
|
309
309
|
value: params.update
|
|
@@ -312,12 +312,12 @@ export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResul
|
|
|
312
312
|
};
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
private buildDeleteQuery<T extends
|
|
315
|
+
private buildDeleteQuery<T extends keyof S>(params: DeleteQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
316
316
|
const key = this.extractKeyFromCondition(params.conditions);
|
|
317
317
|
|
|
318
318
|
return {
|
|
319
319
|
query: {
|
|
320
|
-
bucket: params.tableName,
|
|
320
|
+
bucket: params.tableName as string,
|
|
321
321
|
operation: 'delete',
|
|
322
322
|
key: key || 'default'
|
|
323
323
|
},
|
|
@@ -325,30 +325,30 @@ export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResul
|
|
|
325
325
|
};
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
private buildAlterQuery<T extends
|
|
328
|
+
private buildAlterQuery<T extends keyof S>(params: AlterQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
329
329
|
return {
|
|
330
330
|
query: {
|
|
331
|
-
bucket: params.tableName,
|
|
331
|
+
bucket: params.tableName as string,
|
|
332
332
|
operation: 'keys'
|
|
333
333
|
},
|
|
334
334
|
params: [params.alterations]
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
private buildDropTableQuery<T extends
|
|
338
|
+
private buildDropTableQuery<T extends keyof S>(params: DropTableQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
339
339
|
return {
|
|
340
340
|
query: {
|
|
341
|
-
bucket: params.tableName,
|
|
341
|
+
bucket: params.tableName as string,
|
|
342
342
|
operation: 'clear'
|
|
343
343
|
},
|
|
344
344
|
params: []
|
|
345
345
|
};
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
private buildDropIndexQuery(params: DropIndexQueryParams): BuildQueryResult<KeyValueQueryResult> {
|
|
348
|
+
private buildDropIndexQuery<T extends keyof S>(params: DropIndexQueryParams<S, T>): BuildQueryResult<KeyValueQueryResult> {
|
|
349
349
|
return {
|
|
350
350
|
query: {
|
|
351
|
-
bucket: params.tableName,
|
|
351
|
+
bucket: params.tableName as string,
|
|
352
352
|
operation: 'keys'
|
|
353
353
|
},
|
|
354
354
|
params: [params.indexName]
|
|
@@ -592,7 +592,9 @@ export class RedisDialect extends Dialect<RedisDialectConfig, KeyValueQueryResul
|
|
|
592
592
|
};
|
|
593
593
|
}
|
|
594
594
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
595
|
+
export class Redis<S extends Record<string, object> = Record<string, object>> extends KeyValueDatabase<RedisDialectConfig, S> {
|
|
596
|
+
constructor(config: RedisDialectConfig, definitions?: Database.DefinitionObj<S>) {
|
|
597
|
+
super(new RedisDialect<S>(config), definitions);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
Registry.register('redis', Redis);
|