@dangao/bun-server 2.0.8 → 2.1.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 +4 -0
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts +6 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/server.d.ts +5 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/database/database-context.d.ts +25 -0
- package/dist/database/database-context.d.ts.map +1 -0
- package/dist/database/database-extension.d.ts +8 -9
- package/dist/database/database-extension.d.ts.map +1 -1
- package/dist/database/database-module.d.ts +7 -1
- package/dist/database/database-module.d.ts.map +1 -1
- package/dist/database/db-proxy.d.ts +12 -0
- package/dist/database/db-proxy.d.ts.map +1 -0
- package/dist/database/index.d.ts +6 -1
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/orm/transaction-interceptor.d.ts +0 -16
- package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
- package/dist/database/orm/transaction-manager.d.ts +10 -61
- package/dist/database/orm/transaction-manager.d.ts.map +1 -1
- package/dist/database/service.d.ts.map +1 -1
- package/dist/database/sql-manager.d.ts +14 -0
- package/dist/database/sql-manager.d.ts.map +1 -0
- package/dist/database/sqlite-adapter.d.ts +32 -0
- package/dist/database/sqlite-adapter.d.ts.map +1 -0
- package/dist/database/strategy-decorator.d.ts +8 -0
- package/dist/database/strategy-decorator.d.ts.map +1 -0
- package/dist/database/types.d.ts +122 -1
- package/dist/database/types.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3115 -2586
- package/dist/microservice/service-registry/service-registry-module.d.ts +16 -0
- package/dist/microservice/service-registry/service-registry-module.d.ts.map +1 -1
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/registry.d.ts +1 -1
- package/dist/router/registry.d.ts.map +1 -1
- package/dist/router/route.d.ts +2 -1
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/router.d.ts +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/timeout-decorator.d.ts +6 -0
- package/dist/router/timeout-decorator.d.ts.map +1 -0
- package/docs/database.md +48 -15
- package/docs/idle-timeout.md +42 -0
- package/docs/lifecycle.md +6 -0
- package/docs/microservice-nacos.md +1 -0
- package/docs/microservice-service-registry.md +7 -0
- package/docs/zh/database.md +48 -15
- package/docs/zh/idle-timeout.md +41 -0
- package/docs/zh/lifecycle.md +5 -0
- package/docs/zh/microservice-nacos.md +1 -0
- package/docs/zh/microservice-service-registry.md +6 -0
- package/package.json +1 -1
- package/src/controller/controller.ts +11 -1
- package/src/core/application.ts +60 -1
- package/src/core/server.ts +10 -0
- package/src/database/database-context.ts +43 -0
- package/src/database/database-extension.ts +12 -45
- package/src/database/database-module.ts +254 -11
- package/src/database/db-proxy.ts +75 -0
- package/src/database/index.ts +29 -0
- package/src/database/orm/transaction-interceptor.ts +12 -149
- package/src/database/orm/transaction-manager.ts +143 -210
- package/src/database/service.ts +28 -2
- package/src/database/sql-manager.ts +62 -0
- package/src/database/sqlite-adapter.ts +121 -0
- package/src/database/strategy-decorator.ts +42 -0
- package/src/database/types.ts +133 -1
- package/src/di/module-registry.ts +20 -4
- package/src/index.ts +27 -1
- package/src/microservice/service-registry/service-registry-module.ts +25 -1
- package/src/router/index.ts +1 -0
- package/src/router/registry.ts +10 -1
- package/src/router/route.ts +31 -3
- package/src/router/router.ts +10 -1
- package/src/router/timeout-decorator.ts +35 -0
- package/tests/core/application.test.ts +10 -0
- package/tests/database/database-module.test.ts +91 -430
- package/tests/database/db-proxy.test.ts +93 -0
- package/tests/database/sql-manager.test.ts +43 -0
- package/tests/database/sqlite-adapter.test.ts +45 -0
- package/tests/database/strategy-decorator.test.ts +29 -0
- package/tests/database/transaction.test.ts +84 -222
- package/tests/di/lifecycle.test.ts +37 -0
- package/tests/microservice/service-registry.test.ts +15 -0
- package/tests/router/timeout-decorator.test.ts +48 -0
|
@@ -3,8 +3,8 @@ import type { Context } from '../../core/context';
|
|
|
3
3
|
import type { Interceptor } from '../../interceptor';
|
|
4
4
|
import { TRANSACTION_SERVICE_TOKEN } from './transaction-types';
|
|
5
5
|
import { TransactionManager } from './transaction-manager';
|
|
6
|
-
import { getTransactionMetadata
|
|
7
|
-
import { Propagation
|
|
6
|
+
import { getTransactionMetadata } from './transaction-decorator';
|
|
7
|
+
import { Propagation } from './transaction-types';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* 事务拦截器
|
|
@@ -51,44 +51,26 @@ export class TransactionInterceptor implements Interceptor {
|
|
|
51
51
|
case Propagation.REQUIRED:
|
|
52
52
|
if (currentTransaction) {
|
|
53
53
|
// 加入现有事务
|
|
54
|
-
return await
|
|
55
|
-
originalMethod,
|
|
56
|
-
target,
|
|
57
|
-
args,
|
|
58
|
-
currentTransaction.id,
|
|
59
|
-
transactionManager,
|
|
60
|
-
);
|
|
54
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
61
55
|
} else {
|
|
62
56
|
// 创建新事务
|
|
63
|
-
return await
|
|
64
|
-
originalMethod,
|
|
65
|
-
target,
|
|
66
|
-
args,
|
|
57
|
+
return await transactionManager.runInTransaction(
|
|
58
|
+
async () => await Promise.resolve(originalMethod.apply(target, args)),
|
|
67
59
|
transactionMetadata,
|
|
68
|
-
transactionManager,
|
|
69
60
|
);
|
|
70
61
|
}
|
|
71
62
|
|
|
72
63
|
case Propagation.REQUIRES_NEW:
|
|
73
64
|
// 总是创建新事务
|
|
74
|
-
return await
|
|
75
|
-
originalMethod,
|
|
76
|
-
target,
|
|
77
|
-
args,
|
|
65
|
+
return await transactionManager.runInNewTransaction(
|
|
66
|
+
async () => await Promise.resolve(originalMethod.apply(target, args)),
|
|
78
67
|
transactionMetadata,
|
|
79
|
-
transactionManager,
|
|
80
68
|
);
|
|
81
69
|
|
|
82
70
|
case Propagation.SUPPORTS:
|
|
83
71
|
if (currentTransaction) {
|
|
84
72
|
// 加入现有事务
|
|
85
|
-
return await
|
|
86
|
-
originalMethod,
|
|
87
|
-
target,
|
|
88
|
-
args,
|
|
89
|
-
currentTransaction.id,
|
|
90
|
-
transactionManager,
|
|
91
|
-
);
|
|
73
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
92
74
|
} else {
|
|
93
75
|
// 非事务执行
|
|
94
76
|
return await Promise.resolve(originalMethod.apply(target, args));
|
|
@@ -109,22 +91,15 @@ export class TransactionInterceptor implements Interceptor {
|
|
|
109
91
|
case Propagation.NESTED:
|
|
110
92
|
if (currentTransaction) {
|
|
111
93
|
// 创建嵌套事务(保存点)
|
|
112
|
-
return await
|
|
113
|
-
originalMethod,
|
|
114
|
-
target,
|
|
115
|
-
args,
|
|
116
|
-
currentTransaction.id,
|
|
94
|
+
return await transactionManager.runInNestedTransaction(
|
|
95
|
+
async () => await Promise.resolve(originalMethod.apply(target, args)),
|
|
117
96
|
transactionMetadata,
|
|
118
|
-
transactionManager,
|
|
119
97
|
);
|
|
120
98
|
} else {
|
|
121
99
|
// 创建新事务
|
|
122
|
-
return await
|
|
123
|
-
originalMethod,
|
|
124
|
-
target,
|
|
125
|
-
args,
|
|
100
|
+
return await transactionManager.runInTransaction(
|
|
101
|
+
async () => await Promise.resolve(originalMethod.apply(target, args)),
|
|
126
102
|
transactionMetadata,
|
|
127
|
-
transactionManager,
|
|
128
103
|
);
|
|
129
104
|
}
|
|
130
105
|
|
|
@@ -148,116 +123,4 @@ export class TransactionInterceptor implements Interceptor {
|
|
|
148
123
|
return await interceptor.execute(target, propertyKey, originalMethod, args, container);
|
|
149
124
|
}
|
|
150
125
|
|
|
151
|
-
/**
|
|
152
|
-
* 在新事务中执行方法
|
|
153
|
-
*/
|
|
154
|
-
private static async executeInNewTransaction<T>(
|
|
155
|
-
method: (...args: unknown[]) => T | Promise<T>,
|
|
156
|
-
target: unknown,
|
|
157
|
-
args: unknown[],
|
|
158
|
-
options: {
|
|
159
|
-
timeout?: number;
|
|
160
|
-
rollbackFor?: Array<new () => Error>;
|
|
161
|
-
noRollbackFor?: Array<new () => Error>;
|
|
162
|
-
},
|
|
163
|
-
transactionManager: TransactionManager,
|
|
164
|
-
): Promise<T> {
|
|
165
|
-
const context = await transactionManager.beginTransaction({
|
|
166
|
-
timeout: options.timeout,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const result = await Promise.resolve(method.apply(target, args));
|
|
171
|
-
await transactionManager.commitTransaction(context.id);
|
|
172
|
-
return result;
|
|
173
|
-
} catch (error) {
|
|
174
|
-
// 检查是否需要回滚
|
|
175
|
-
if (this.shouldRollback(error, options.rollbackFor, options.noRollbackFor)) {
|
|
176
|
-
await transactionManager.rollbackTransaction(context.id);
|
|
177
|
-
} else {
|
|
178
|
-
await transactionManager.commitTransaction(context.id);
|
|
179
|
-
}
|
|
180
|
-
throw error;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* 在现有事务中执行方法
|
|
186
|
-
*/
|
|
187
|
-
private static async executeInExistingTransaction<T>(
|
|
188
|
-
method: (...args: unknown[]) => T | Promise<T>,
|
|
189
|
-
target: unknown,
|
|
190
|
-
args: unknown[],
|
|
191
|
-
transactionId: string,
|
|
192
|
-
transactionManager: TransactionManager,
|
|
193
|
-
): Promise<T> {
|
|
194
|
-
// 直接执行,不提交或回滚(由外层事务管理)
|
|
195
|
-
return await Promise.resolve(method.apply(target, args));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* 在嵌套事务中执行方法
|
|
200
|
-
*/
|
|
201
|
-
private static async executeInNestedTransaction<T>(
|
|
202
|
-
method: (...args: unknown[]) => T | Promise<T>,
|
|
203
|
-
target: unknown,
|
|
204
|
-
args: unknown[],
|
|
205
|
-
parentTransactionId: string,
|
|
206
|
-
options: {
|
|
207
|
-
timeout?: number;
|
|
208
|
-
rollbackFor?: Array<new () => Error>;
|
|
209
|
-
noRollbackFor?: Array<new () => Error>;
|
|
210
|
-
},
|
|
211
|
-
transactionManager: TransactionManager,
|
|
212
|
-
): Promise<T> {
|
|
213
|
-
const savepointName = await transactionManager.createSavepoint(parentTransactionId);
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const result = await Promise.resolve(method.apply(target, args));
|
|
217
|
-
// 嵌套事务成功,不执行任何操作(保存点保留)
|
|
218
|
-
return result;
|
|
219
|
-
} catch (error) {
|
|
220
|
-
// 检查是否需要回滚到保存点
|
|
221
|
-
if (this.shouldRollback(error, options.rollbackFor, options.noRollbackFor)) {
|
|
222
|
-
await transactionManager.rollbackToSavepoint(parentTransactionId, savepointName);
|
|
223
|
-
}
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* 判断是否应该回滚
|
|
230
|
-
*/
|
|
231
|
-
private static shouldRollback(
|
|
232
|
-
error: unknown,
|
|
233
|
-
rollbackFor?: Array<new () => Error>,
|
|
234
|
-
noRollbackFor?: Array<new () => Error>,
|
|
235
|
-
): boolean {
|
|
236
|
-
if (!error) {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// 如果指定了 noRollbackFor,且错误匹配,则不回滚
|
|
241
|
-
if (noRollbackFor && noRollbackFor.length > 0) {
|
|
242
|
-
for (const ErrorClass of noRollbackFor) {
|
|
243
|
-
if (error instanceof ErrorClass) {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// 如果指定了 rollbackFor,且错误匹配,则回滚
|
|
250
|
-
if (rollbackFor && rollbackFor.length > 0) {
|
|
251
|
-
for (const ErrorClass of rollbackFor) {
|
|
252
|
-
if (error instanceof ErrorClass) {
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// 如果指定了 rollbackFor 但错误不匹配,则不回滚
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// 默认情况下,所有错误都回滚
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
126
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { Inject, Injectable } from '../../di/decorators';
|
|
2
|
+
import { getCurrentSession, runWithSession } from '../database-context';
|
|
3
|
+
import { BUN_SQL_MANAGER_TOKEN } from '../types';
|
|
4
|
+
import type { BunSQLManager } from '../sql-manager';
|
|
4
5
|
import {
|
|
5
|
-
Propagation,
|
|
6
6
|
IsolationLevel,
|
|
7
7
|
TransactionStatus,
|
|
8
8
|
type TransactionOptions,
|
|
@@ -15,262 +15,195 @@ import {
|
|
|
15
15
|
*/
|
|
16
16
|
@Injectable()
|
|
17
17
|
export class TransactionManager {
|
|
18
|
-
private readonly transactions = new Map<string, TransactionContext>();
|
|
19
|
-
private readonly connectionTransactions = new Map<unknown, string>(); // connection -> transactionId
|
|
20
|
-
|
|
21
18
|
public constructor(
|
|
22
|
-
@Inject(
|
|
23
|
-
private readonly
|
|
19
|
+
@Inject(BUN_SQL_MANAGER_TOKEN)
|
|
20
|
+
private readonly sqlManager: BunSQLManager,
|
|
24
21
|
) {}
|
|
25
22
|
|
|
26
23
|
/**
|
|
27
|
-
*
|
|
24
|
+
* 在当前 session 中执行事务
|
|
28
25
|
*/
|
|
29
|
-
public async
|
|
26
|
+
public async runInTransaction<T>(
|
|
27
|
+
fn: () => Promise<T>,
|
|
30
28
|
options: TransactionOptions = {},
|
|
31
|
-
): Promise<
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
level: 0,
|
|
38
|
-
savepoints: [],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// 获取数据库连接
|
|
42
|
-
const connection = await this.databaseService.getConnection();
|
|
43
|
-
this.connectionTransactions.set(connection, transactionId);
|
|
44
|
-
this.transactions.set(transactionId, context);
|
|
45
|
-
|
|
46
|
-
// 开始事务(根据数据库类型)
|
|
47
|
-
await this.executeBegin(connection, options);
|
|
48
|
-
|
|
49
|
-
return context;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 提交事务
|
|
54
|
-
*/
|
|
55
|
-
public async commitTransaction(transactionId: string): Promise<void> {
|
|
56
|
-
const context = this.transactions.get(transactionId);
|
|
57
|
-
if (!context) {
|
|
58
|
-
throw new Error(`Transaction ${transactionId} not found`);
|
|
29
|
+
): Promise<T> {
|
|
30
|
+
const session = getCurrentSession();
|
|
31
|
+
if (!session) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'[TransactionManager] No database session in current request context',
|
|
34
|
+
);
|
|
59
35
|
}
|
|
60
36
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
37
|
+
if (session.sqlite) {
|
|
38
|
+
return await this.runInSqliteTransaction(fn);
|
|
63
39
|
}
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
41
|
+
let reserved = session.reserved;
|
|
42
|
+
if (!reserved && session.lazyReserve) {
|
|
43
|
+
reserved = await session.lazyReserve();
|
|
44
|
+
}
|
|
45
|
+
if (!reserved) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'[TransactionManager] No reserved session in current context. Add @Session() or @DbStrategy("session").',
|
|
48
|
+
);
|
|
69
49
|
}
|
|
70
50
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
51
|
+
const transactionId = this.generateTransactionId();
|
|
52
|
+
session.transaction = {
|
|
53
|
+
id: transactionId,
|
|
54
|
+
status: TransactionStatus.ACTIVE,
|
|
55
|
+
level: 0,
|
|
56
|
+
savepoints: [],
|
|
57
|
+
};
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!context) {
|
|
84
|
-
throw new Error(`Transaction ${transactionId} not found`);
|
|
59
|
+
if (options.isolationLevel) {
|
|
60
|
+
const isolationLevel = this.getIsolationLevelSQL(options.isolationLevel);
|
|
61
|
+
if (isolationLevel) {
|
|
62
|
+
await reserved`SET TRANSACTION ISOLATION LEVEL ${isolationLevel}`;
|
|
63
|
+
}
|
|
85
64
|
}
|
|
86
65
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
66
|
+
try {
|
|
67
|
+
const result = await reserved.begin(fn);
|
|
68
|
+
if (session.transaction) {
|
|
69
|
+
session.transaction.status = TransactionStatus.COMMITTED;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (session.transaction) {
|
|
74
|
+
session.transaction.status = TransactionStatus.ROLLED_BACK;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
} finally {
|
|
78
|
+
session.transaction = undefined;
|
|
91
79
|
}
|
|
92
|
-
|
|
93
|
-
// 回滚事务
|
|
94
|
-
await this.executeRollback(connection);
|
|
95
|
-
|
|
96
|
-
context.status = TransactionStatus.ROLLED_BACK;
|
|
97
|
-
this.cleanupTransaction(transactionId);
|
|
98
80
|
}
|
|
99
81
|
|
|
100
82
|
/**
|
|
101
|
-
*
|
|
83
|
+
* REQUIRES_NEW:使用独立连接开启新事务
|
|
102
84
|
*/
|
|
103
|
-
public async
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
85
|
+
public async runInNewTransaction<T>(
|
|
86
|
+
fn: () => Promise<T>,
|
|
87
|
+
options: TransactionOptions = {},
|
|
88
|
+
): Promise<T> {
|
|
89
|
+
const reserved = await this.sqlManager.getDefault().reserve();
|
|
90
|
+
try {
|
|
91
|
+
const tenantId = getCurrentSession()?.tenantId ?? 'default';
|
|
92
|
+
return await runWithSession(
|
|
93
|
+
{
|
|
94
|
+
tenantId,
|
|
95
|
+
reserved: reserved as any,
|
|
96
|
+
},
|
|
97
|
+
() => this.runInTransaction(fn, options),
|
|
98
|
+
);
|
|
99
|
+
} finally {
|
|
100
|
+
await reserved.release();
|
|
117
101
|
}
|
|
118
|
-
|
|
119
|
-
return savepointName;
|
|
120
102
|
}
|
|
121
103
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
throw new Error(`Transaction ${transactionId} not found`);
|
|
104
|
+
public async runInNestedTransaction<T>(
|
|
105
|
+
fn: () => Promise<T>,
|
|
106
|
+
options: TransactionOptions = {},
|
|
107
|
+
): Promise<T> {
|
|
108
|
+
const session = getCurrentSession();
|
|
109
|
+
if (!session?.transaction || !session.reserved) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'[TransactionManager] NESTED propagation requires an active transaction',
|
|
112
|
+
);
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
115
|
+
const savepointName = `sp_${session.transaction.level}_${Date.now()}`;
|
|
116
|
+
session.transaction.level += 1;
|
|
117
|
+
session.transaction.savepoints.push(savepointName);
|
|
138
118
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
119
|
+
await session.reserved`SAVEPOINT ${savepointName}`;
|
|
120
|
+
try {
|
|
121
|
+
return await fn();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (this.shouldRollback(error, options)) {
|
|
124
|
+
await session.reserved`ROLLBACK TO SAVEPOINT ${savepointName}`;
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
127
|
+
} finally {
|
|
128
|
+
session.transaction.level = Math.max(0, session.transaction.level - 1);
|
|
129
|
+
session.transaction.savepoints = session.transaction.savepoints.filter(
|
|
130
|
+
(item) => item !== savepointName,
|
|
131
|
+
);
|
|
144
132
|
}
|
|
145
133
|
}
|
|
146
134
|
|
|
147
|
-
/**
|
|
148
|
-
* 获取当前事务上下文
|
|
149
|
-
*/
|
|
150
135
|
public getCurrentTransaction(): TransactionContext | null {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (context.status === TransactionStatus.ACTIVE) {
|
|
155
|
-
return context;
|
|
156
|
-
}
|
|
136
|
+
const tx = getCurrentSession()?.transaction;
|
|
137
|
+
if (!tx) {
|
|
138
|
+
return null;
|
|
157
139
|
}
|
|
158
|
-
return
|
|
140
|
+
return {
|
|
141
|
+
id: tx.id,
|
|
142
|
+
status: tx.status,
|
|
143
|
+
startTime: 0,
|
|
144
|
+
level: tx.level,
|
|
145
|
+
savepoints: tx.savepoints,
|
|
146
|
+
};
|
|
159
147
|
}
|
|
160
148
|
|
|
161
|
-
/**
|
|
162
|
-
* 检查是否有活动事务
|
|
163
|
-
*/
|
|
164
149
|
public hasActiveTransaction(): boolean {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 生成事务 ID
|
|
170
|
-
*/
|
|
171
|
-
private generateTransactionId(): string {
|
|
172
|
-
return `tx_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 查找连接对应的事务 ID
|
|
177
|
-
*/
|
|
178
|
-
private findConnectionByTransactionId(transactionId: string): unknown {
|
|
179
|
-
for (const [connection, txId] of this.connectionTransactions.entries()) {
|
|
180
|
-
if (txId === transactionId) {
|
|
181
|
-
return connection;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return null;
|
|
150
|
+
const tx = this.getCurrentTransaction();
|
|
151
|
+
return tx?.status === TransactionStatus.ACTIVE;
|
|
185
152
|
}
|
|
186
153
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
for (const [connection, txId] of this.connectionTransactions.entries()) {
|
|
194
|
-
if (txId === transactionId) {
|
|
195
|
-
this.connectionTransactions.delete(connection);
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
154
|
+
public async runInSqliteTransaction<T>(fn: () => Promise<T>): Promise<T> {
|
|
155
|
+
const session = getCurrentSession();
|
|
156
|
+
if (!session?.sqlite) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
'[TransactionManager] No sqlite adapter found in current request context',
|
|
159
|
+
);
|
|
198
160
|
}
|
|
199
|
-
}
|
|
200
161
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
} else if (dbType === 'postgres' || dbType === 'mysql') {
|
|
211
|
-
// PostgreSQL 和 MySQL 使用 Bun.SQL,需要设置隔离级别
|
|
212
|
-
let sql = 'START TRANSACTION';
|
|
213
|
-
if (options.isolationLevel) {
|
|
214
|
-
const isolation = this.getIsolationLevelSQL(options.isolationLevel);
|
|
215
|
-
sql += ` ${isolation}`;
|
|
216
|
-
}
|
|
217
|
-
if (options.readOnly) {
|
|
218
|
-
sql += ' READ ONLY';
|
|
219
|
-
}
|
|
220
|
-
await this.databaseService.query(sql);
|
|
162
|
+
using _lock = await session.sqlite.semaphore.acquire();
|
|
163
|
+
await session.sqlite.execute('BEGIN TRANSACTION');
|
|
164
|
+
try {
|
|
165
|
+
const result = await fn();
|
|
166
|
+
await session.sqlite.execute('COMMIT');
|
|
167
|
+
return result;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
await session.sqlite.execute('ROLLBACK');
|
|
170
|
+
throw error;
|
|
221
171
|
}
|
|
222
172
|
}
|
|
223
173
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
*/
|
|
227
|
-
private async executeCommit(connection: unknown): Promise<void> {
|
|
228
|
-
await this.databaseService.query('COMMIT');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* 执行 ROLLBACK 语句
|
|
233
|
-
*/
|
|
234
|
-
private async executeRollback(connection: unknown): Promise<void> {
|
|
235
|
-
await this.databaseService.query('ROLLBACK');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* 执行 SAVEPOINT 语句
|
|
240
|
-
*/
|
|
241
|
-
private async executeSavepoint(connection: unknown, savepointName: string): Promise<void> {
|
|
242
|
-
await this.databaseService.query(`SAVEPOINT ${savepointName}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* 执行 ROLLBACK TO SAVEPOINT 语句
|
|
247
|
-
*/
|
|
248
|
-
private async executeRollbackToSavepoint(
|
|
249
|
-
connection: unknown,
|
|
250
|
-
savepointName: string,
|
|
251
|
-
): Promise<void> {
|
|
252
|
-
await this.databaseService.query(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
|
174
|
+
private generateTransactionId(): string {
|
|
175
|
+
return `tx_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
253
176
|
}
|
|
254
177
|
|
|
255
|
-
/**
|
|
256
|
-
* 获取隔离级别的 SQL
|
|
257
|
-
*/
|
|
258
178
|
private getIsolationLevelSQL(level: IsolationLevel): string {
|
|
259
|
-
const dbType = this.databaseService['config'].database.type;
|
|
260
179
|
const levelMap: Record<IsolationLevel, string> = {
|
|
261
180
|
[IsolationLevel.READ_UNCOMMITTED]: 'READ UNCOMMITTED',
|
|
262
181
|
[IsolationLevel.READ_COMMITTED]: 'READ COMMITTED',
|
|
263
182
|
[IsolationLevel.REPEATABLE_READ]: 'REPEATABLE READ',
|
|
264
183
|
[IsolationLevel.SERIALIZABLE]: 'SERIALIZABLE',
|
|
265
184
|
};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
185
|
+
return levelMap[level];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private shouldRollback(
|
|
189
|
+
error: unknown,
|
|
190
|
+
options: Pick<TransactionOptions, 'rollbackFor' | 'noRollbackFor'>,
|
|
191
|
+
): boolean {
|
|
192
|
+
if (options.noRollbackFor && options.noRollbackFor.length > 0) {
|
|
193
|
+
for (const ErrorClass of options.noRollbackFor) {
|
|
194
|
+
if (error instanceof ErrorClass) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
271
198
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
199
|
+
if (options.rollbackFor && options.rollbackFor.length > 0) {
|
|
200
|
+
for (const ErrorClass of options.rollbackFor) {
|
|
201
|
+
if (error instanceof ErrorClass) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
275
208
|
}
|
|
276
209
|
}
|
package/src/database/service.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Injectable } from '../di/decorators';
|
|
2
2
|
|
|
3
3
|
import { DatabaseConnectionManager } from './connection-manager';
|
|
4
|
+
import { getCurrentSession } from './database-context';
|
|
4
5
|
import type {
|
|
5
6
|
ConnectionInfo,
|
|
6
7
|
DatabaseConfig,
|
|
@@ -18,8 +19,27 @@ export class DatabaseService {
|
|
|
18
19
|
|
|
19
20
|
public constructor(options: DatabaseModuleOptions) {
|
|
20
21
|
this.options = options;
|
|
22
|
+
const databaseConfig: DatabaseConfig =
|
|
23
|
+
options.database ??
|
|
24
|
+
(options.type === 'sqlite'
|
|
25
|
+
? {
|
|
26
|
+
type: 'sqlite',
|
|
27
|
+
config: {
|
|
28
|
+
path: options.databasePath ?? ':memory:',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
: {
|
|
32
|
+
type: (options.type ?? 'postgres') as 'postgres' | 'mysql',
|
|
33
|
+
config: {
|
|
34
|
+
host: options.host ?? 'localhost',
|
|
35
|
+
port: options.port ?? 5432,
|
|
36
|
+
database: options.databasePath ?? 'default',
|
|
37
|
+
user: options.username ?? 'root',
|
|
38
|
+
password: options.password ?? '',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
21
41
|
this.connectionManager = new DatabaseConnectionManager(
|
|
22
|
-
|
|
42
|
+
databaseConfig,
|
|
23
43
|
options.pool,
|
|
24
44
|
);
|
|
25
45
|
}
|
|
@@ -96,7 +116,13 @@ export class DatabaseService {
|
|
|
96
116
|
* SQLite 返回同步结果,PostgreSQL/MySQL 返回异步结果
|
|
97
117
|
*/
|
|
98
118
|
public query<T = unknown>(sql: string, params?: unknown[]): T[] | Promise<T[]> {
|
|
99
|
-
const
|
|
119
|
+
const session = getCurrentSession();
|
|
120
|
+
if (session?.sqlite) {
|
|
121
|
+
return session.sqlite.query<T>(sql, (params ?? []) as any);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const perRequestConnection = session?.reserved;
|
|
125
|
+
const connection = perRequestConnection ?? this.getConnection();
|
|
100
126
|
if (!connection) {
|
|
101
127
|
throw new Error('Database connection is not established');
|
|
102
128
|
}
|