@zhin.js/database 1.0.45 → 1.0.48

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.
@@ -1,286 +0,0 @@
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
-
8
- export interface MySQLDialectConfig extends ConnectionOptions {
9
- /**
10
- * 连接池配置
11
- * 如果提供此选项,将使用连接池而不是单连接
12
- */
13
- pool?: PoolConfig;
14
- }
15
-
16
- export class MySQLDialect<S extends Record<string, object> = Record<string, object>> extends Dialect<MySQLDialectConfig, S, string> {
17
- private connection: any = null;
18
- private pool: any = null;
19
- private usePool: boolean = false;
20
-
21
- constructor(config: MySQLDialectConfig) {
22
- super('mysql', config);
23
- this.usePool = !!config.pool;
24
- }
25
-
26
- // Connection management
27
- isConnected(): boolean {
28
- return this.usePool ? this.pool !== null : this.connection !== null;
29
- }
30
-
31
- async connect(): Promise<void> {
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 {
45
- const { createConnection } = await import('mysql2/promise');
46
- this.connection = await createConnection(this.config);
47
- }
48
- } catch (error) {
49
- console.error('forgot install mysql2 ?');
50
- throw new Error(`MySQL 连接失败: ${error}`);
51
- }
52
- }
53
-
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();
61
- this.connection = null;
62
- }
63
- }
64
-
65
- async healthCheck(): Promise<boolean> {
66
- if (!this.isConnected()) return false;
67
- try {
68
- await this.query('SELECT 1');
69
- return true;
70
- } catch {
71
- return false;
72
- }
73
- }
74
-
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 {
80
- const [rows] = await this.connection.execute(sql, params);
81
- return rows as U;
82
- }
83
- }
84
-
85
- async dispose(): Promise<void> {
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
- };
99
- }
100
-
101
- // SQL generation methods
102
- mapColumnType(type: string): string {
103
- const typeMap: Record<string, string> = {
104
- 'text': 'TEXT',
105
- 'integer': 'INT',
106
- 'float': 'FLOAT',
107
- 'boolean': 'BOOLEAN',
108
- 'date': 'DATETIME',
109
- 'json': 'JSON'
110
- };
111
- return typeMap[type.toLowerCase()] || 'TEXT';
112
- }
113
-
114
- quoteIdentifier(identifier: string): string {
115
- return `\`${identifier}\``;
116
- }
117
-
118
- getParameterPlaceholder(index: number): string {
119
- return '?';
120
- }
121
-
122
- getStatementTerminator(): string {
123
- return ';';
124
- }
125
-
126
- formatBoolean(value: boolean): string {
127
- return value ? 'TRUE' : 'FALSE';
128
- }
129
-
130
- formatDate(value: Date): string {
131
- return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
132
- }
133
-
134
- formatJson(value: any): string {
135
- return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
136
- }
137
-
138
- escapeString(value: string): string {
139
- return value.replace(/'/g, "''");
140
- }
141
-
142
- formatDefaultValue(value: any): string {
143
- if (typeof value === 'string') {
144
- return `'${this.escapeString(value)}'`;
145
- } else if (typeof value === 'number' || typeof value === 'boolean') {
146
- return value.toString();
147
- } else if (value instanceof Date) {
148
- return this.formatDate(value);
149
- } else if (value === null) {
150
- return 'NULL';
151
- } else if (typeof value === 'object') {
152
- return this.formatJson(value);
153
- } else {
154
- throw new Error(`Unsupported default value type: ${typeof value}`);
155
- }
156
- }
157
-
158
- formatLimit(limit: number): string {
159
- return `LIMIT ${limit}`;
160
- }
161
-
162
- formatOffset(offset: number): string {
163
- return `OFFSET ${offset}`;
164
- }
165
-
166
- formatLimitOffset(limit: number, offset: number): string {
167
- return `LIMIT ${offset}, ${limit}`;
168
- }
169
-
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`;
172
- }
173
-
174
- formatColumnDefinition(field: string, column: Column<any>): string {
175
- const name = this.quoteIdentifier(String(field));
176
- const type = this.mapColumnType(column.type);
177
- const length = column.length ? `(${column.length})` : '';
178
- const nullable = column.nullable === false ? ' NOT NULL' : '';
179
- const primary = column.primary ? ' PRIMARY KEY' : '';
180
- const unique = column.unique ? ' UNIQUE' : '';
181
- const autoIncrement = column.autoIncrement ? ' AUTO_INCREMENT' : '';
182
- const defaultVal = column.default !== undefined
183
- ? ` DEFAULT ${this.formatDefaultValue(column.default)}`
184
- : '';
185
-
186
- return `${name} ${type}${length}${primary}${unique}${autoIncrement}${nullable}${defaultVal}`;
187
- }
188
-
189
- formatAlterTable<T extends keyof S>(tableName: T, alterations: string[]): string {
190
- return `ALTER TABLE ${this.quoteIdentifier(String(tableName))} ${alterations.join(', ')}`;
191
- }
192
-
193
- formatDropTable<T extends keyof S>(tableName: T, ifExists?: boolean): string {
194
- const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
195
- return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(String(tableName))}`;
196
- }
197
-
198
- formatDropIndex<T extends keyof S>(indexName: string, tableName: T, ifExists?: boolean): string {
199
- const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
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
- }
279
- }
280
- }
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);
@@ -1,284 +0,0 @@
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
-
8
- export interface PostgreSQLDialectConfig extends ClientConfig {
9
- /**
10
- * 连接池配置
11
- * 如果提供此选项,将使用连接池而不是单连接
12
- */
13
- pool?: PoolConfig;
14
- }
15
-
16
- export class PostgreSQLDialect<S extends Record<string, object> = Record<string, object>> extends Dialect<PostgreSQLDialectConfig, S, string> {
17
- private connection: any = null;
18
- private pool: any = null;
19
- private usePool: boolean = false;
20
-
21
- constructor(config: PostgreSQLDialectConfig) {
22
- super('pg', config);
23
- this.usePool = !!config.pool;
24
- }
25
-
26
- // Connection management
27
- isConnected(): boolean {
28
- return this.usePool ? this.pool !== null : this.connection !== null;
29
- }
30
-
31
- async connect(): Promise<void> {
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 {
45
- const { Client } = await import('pg');
46
- this.connection = new Client(this.config);
47
- await this.connection.connect();
48
- }
49
- } catch (error) {
50
- console.error('forgot install pg ?');
51
- throw new Error(`PostgreSQL 连接失败: ${error}`);
52
- }
53
- }
54
-
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();
62
- this.connection = null;
63
- }
64
- }
65
-
66
- async healthCheck(): Promise<boolean> {
67
- if (!this.isConnected()) return false;
68
- try {
69
- await this.query('SELECT 1');
70
- return true;
71
- } catch {
72
- return false;
73
- }
74
- }
75
-
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 {
81
- const result = await this.connection.query(sql, params);
82
- return result.rows as U;
83
- }
84
- }
85
-
86
- async dispose(): Promise<void> {
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
- };
100
- }
101
-
102
- // SQL generation methods
103
- mapColumnType(type: string): string {
104
- const typeMap: Record<string, string> = {
105
- 'text': 'TEXT',
106
- 'integer': 'INTEGER',
107
- 'float': 'REAL',
108
- 'boolean': 'BOOLEAN',
109
- 'date': 'TIMESTAMP',
110
- 'json': 'JSONB'
111
- };
112
- return typeMap[type.toLowerCase()] || 'TEXT';
113
- }
114
-
115
- quoteIdentifier(identifier: string): string {
116
- return `"${identifier}"`;
117
- }
118
-
119
- getParameterPlaceholder(index: number): string {
120
- return `$${index + 1}`;
121
- }
122
-
123
- getStatementTerminator(): string {
124
- return ';';
125
- }
126
-
127
- formatBoolean(value: boolean): string {
128
- return value ? 'TRUE' : 'FALSE';
129
- }
130
-
131
- formatDate(value: Date): string {
132
- return `'${value.toISOString()}'::TIMESTAMP`;
133
- }
134
-
135
- formatJson(value: any): string {
136
- return `'${JSON.stringify(value).replace(/'/g, "''")}'::JSONB`;
137
- }
138
-
139
- escapeString(value: string): string {
140
- return value.replace(/'/g, "''");
141
- }
142
-
143
- formatDefaultValue(value: any): string {
144
- if (typeof value === 'string') {
145
- return `'${this.escapeString(value)}'`;
146
- } else if (typeof value === 'number' || typeof value === 'boolean') {
147
- return value.toString();
148
- } else if (value instanceof Date) {
149
- return this.formatDate(value);
150
- } else if (value === null) {
151
- return 'NULL';
152
- } else if (typeof value === 'object') {
153
- return this.formatJson(value);
154
- } else {
155
- throw new Error(`Unsupported default value type: ${typeof value}`);
156
- }
157
- }
158
-
159
- formatLimit(limit: number): string {
160
- return `LIMIT ${limit}`;
161
- }
162
-
163
- formatOffset(offset: number): string {
164
- return `OFFSET ${offset}`;
165
- }
166
-
167
- formatLimitOffset(limit: number, offset: number): string {
168
- return `LIMIT ${limit} OFFSET ${offset}`;
169
- }
170
-
171
- formatCreateTable<T extends keyof S>(tableName: T, columns: string[]): string {
172
- return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(String(tableName))} (${columns.join(', ')})`;
173
- }
174
-
175
- formatColumnDefinition(field: string, column: Column<any>): string {
176
- const name = this.quoteIdentifier(String(field));
177
- const type = this.mapColumnType(column.type);
178
- const length = column.length ? `(${column.length})` : '';
179
- const nullable = column.nullable === false ? ' NOT NULL' : '';
180
- const primary = column.primary ? ' PRIMARY KEY' : '';
181
- const unique = column.unique ? ' UNIQUE' : '';
182
- const defaultVal = column.default !== undefined
183
- ? ` DEFAULT ${this.formatDefaultValue(column.default)}`
184
- : '';
185
-
186
- return `${name} ${type}${length}${primary}${unique}${nullable}${defaultVal}`;
187
- }
188
-
189
- formatAlterTable<T extends keyof S>(tableName: T, alterations: string[]): string {
190
- return `ALTER TABLE ${this.quoteIdentifier(String(tableName))} ${alterations.join(', ')}`;
191
- }
192
-
193
- formatDropTable<T extends keyof S>(tableName: T, ifExists?: boolean): string {
194
- const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
195
- return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(String(tableName))}`;
196
- }
197
-
198
- formatDropIndex<T extends keyof S>(indexName: string, tableName: T, ifExists?: boolean): string {
199
- const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
200
- return `DROP INDEX ${ifExistsClause}${this.quoteIdentifier(indexName)}`;
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
- }
278
- }
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);