@dangao/bun-server 2.0.3 → 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/config/config-module.d.ts +3 -0
- package/dist/config/config-module.d.ts.map +1 -1
- 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/context.d.ts +10 -0
- package/dist/core/context.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 +4 -4
- 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 +3544 -2876
- 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/dist/validation/decorators.d.ts.map +1 -1
- 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 +5 -4
- package/src/ai/providers/anthropic-provider.ts +1 -1
- package/src/ai/providers/google-provider.ts +1 -1
- package/src/ai/providers/ollama-provider.ts +1 -1
- package/src/ai/providers/openai-provider.ts +2 -2
- package/src/auth/jwt.ts +1 -1
- package/src/cache/interceptors.ts +3 -3
- package/src/cache/types.ts +10 -10
- package/src/client/runtime.ts +1 -1
- package/src/config/config-module.ts +46 -14
- package/src/config/service.ts +2 -2
- package/src/controller/controller.ts +11 -1
- package/src/controller/param-binder.ts +1 -1
- package/src/conversation/service.ts +1 -1
- package/src/core/application.ts +61 -2
- package/src/core/cluster.ts +4 -4
- package/src/core/context.ts +71 -0
- package/src/core/server.ts +10 -0
- package/src/dashboard/controller.ts +2 -2
- package/src/database/connection-manager.ts +4 -4
- 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 +53 -30
- 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/debug/middleware.ts +2 -2
- package/src/di/module-registry.ts +21 -5
- package/src/error/handler.ts +3 -3
- package/src/events/event-module.ts +4 -4
- package/src/files/static-middleware.ts +2 -2
- package/src/files/storage.ts +1 -1
- package/src/index.ts +27 -1
- package/src/interceptor/builtin/log-interceptor.ts +1 -1
- package/src/mcp/server.ts +1 -1
- package/src/microservice/service-registry/service-registry-module.ts +25 -1
- package/src/middleware/builtin/error-handler.ts +2 -2
- package/src/middleware/builtin/file-upload.ts +1 -1
- package/src/middleware/builtin/rate-limit.ts +1 -1
- package/src/middleware/builtin/static-file.ts +2 -2
- package/src/prompt/stores/file-store.ts +4 -4
- package/src/request/body-parser.ts +3 -3
- 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/src/security/filter.ts +1 -1
- package/src/security/guards/guard-registry.ts +1 -1
- package/src/session/middleware.ts +1 -1
- package/src/session/types.ts +5 -5
- package/src/testing/test-client.ts +1 -1
- package/src/validation/decorators.ts +70 -2
- package/src/validation/rules/common.ts +2 -2
- package/tests/config/config-module-extended.test.ts +24 -0
- package/tests/core/application.test.ts +10 -0
- package/tests/core/context.test.ts +52 -0
- package/tests/database/database-module.test.ts +92 -344
- 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/error/error-handler.test.ts +24 -0
- package/tests/microservice/service-registry.test.ts +15 -0
- package/tests/router/timeout-decorator.test.ts +48 -0
- package/tests/validation/validation.test.ts +18 -0
|
@@ -1,30 +1,207 @@
|
|
|
1
1
|
import { Module, MODULE_METADATA_KEY, type ModuleProvider } from '../di/module';
|
|
2
|
-
import type { ApplicationExtension } from '../extensions/types';
|
|
3
|
-
import {
|
|
4
|
-
InterceptorRegistry,
|
|
5
|
-
INTERCEPTOR_REGISTRY_TOKEN,
|
|
6
|
-
} from '../interceptor';
|
|
7
2
|
import { type AsyncModuleOptions, registerAsyncProviders } from '../di/async-module';
|
|
3
|
+
import type { Constructor } from '@/core/types';
|
|
4
|
+
import type { Middleware } from '../middleware';
|
|
8
5
|
|
|
9
6
|
import { DatabaseExtension } from './database-extension';
|
|
10
7
|
import { DatabaseHealthIndicator } from './health-indicator';
|
|
11
8
|
import { OrmService } from './orm/service';
|
|
12
9
|
import { TransactionManager } from './orm/transaction-manager';
|
|
13
|
-
import { TransactionInterceptor } from './orm/transaction-interceptor';
|
|
14
10
|
import { TRANSACTION_METADATA_KEY } from './orm/transaction-decorator';
|
|
15
11
|
import { DatabaseService } from './service';
|
|
16
12
|
import {
|
|
13
|
+
BUN_SQL_MANAGER_TOKEN,
|
|
14
|
+
DB_TOKEN,
|
|
17
15
|
DATABASE_OPTIONS_TOKEN,
|
|
18
16
|
DATABASE_SERVICE_TOKEN,
|
|
17
|
+
SQLITE_MANAGER_TOKEN,
|
|
18
|
+
type BunSQLConfig,
|
|
19
19
|
type DatabaseModuleOptions,
|
|
20
|
+
type SqliteV2Config,
|
|
20
21
|
} from './types';
|
|
21
22
|
import { ORM_SERVICE_TOKEN } from './orm/types';
|
|
22
23
|
import { TRANSACTION_SERVICE_TOKEN } from './orm/transaction-types';
|
|
24
|
+
import { BunSQLManager } from './sql-manager';
|
|
25
|
+
import { SqliteManager } from './sqlite-adapter';
|
|
26
|
+
import { db, initDbProxy } from './db-proxy';
|
|
27
|
+
import { getDbStrategy } from './strategy-decorator';
|
|
28
|
+
import { runWithSession, type DatabaseSession } from './database-context';
|
|
23
29
|
|
|
24
30
|
@Module({
|
|
25
31
|
providers: [],
|
|
26
32
|
})
|
|
27
33
|
export class DatabaseModule {
|
|
34
|
+
private static isBunSqlType(
|
|
35
|
+
type: DatabaseModuleOptions['type'] | undefined,
|
|
36
|
+
): type is 'postgres' | 'mysql' {
|
|
37
|
+
return type === 'postgres' || type === 'mysql';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public static normalizeConfig(
|
|
41
|
+
options: DatabaseModuleOptions,
|
|
42
|
+
): Array<{ tenantId: string; config: BunSQLConfig | SqliteV2Config }> {
|
|
43
|
+
if (options.tenants && options.tenants.length > 0) {
|
|
44
|
+
return options.tenants.map((tenant) => ({
|
|
45
|
+
tenantId: tenant.id,
|
|
46
|
+
config: tenant.config,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (options.database?.type === 'sqlite') {
|
|
51
|
+
if (options.pool) {
|
|
52
|
+
console.warn('[DatabaseModule] pool options are ignored for SQLite');
|
|
53
|
+
}
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
tenantId: options.defaultTenant ?? 'default',
|
|
57
|
+
config: {
|
|
58
|
+
type: 'sqlite',
|
|
59
|
+
database: options.database.config.path,
|
|
60
|
+
wal: options.wal ?? true,
|
|
61
|
+
maxWriteConcurrency: options.maxWriteConcurrency ?? 1,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (options.database?.type === 'postgres' || options.database?.type === 'mysql') {
|
|
68
|
+
const db = options.database;
|
|
69
|
+
const protocol = db.type === 'mysql' ? 'mysql' : 'postgres';
|
|
70
|
+
const url =
|
|
71
|
+
`${protocol}://${db.config.user}:${db.config.password}@${db.config.host}:${db.config.port}/${db.config.database}`;
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
tenantId: options.defaultTenant ?? 'default',
|
|
75
|
+
config: {
|
|
76
|
+
type: db.type,
|
|
77
|
+
url,
|
|
78
|
+
pool: options.bunSqlPool,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (options.type === 'sqlite') {
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
tenantId: options.defaultTenant ?? 'default',
|
|
88
|
+
config: {
|
|
89
|
+
type: 'sqlite',
|
|
90
|
+
database: options.databasePath ?? ':memory:',
|
|
91
|
+
wal: options.wal ?? true,
|
|
92
|
+
maxWriteConcurrency: options.maxWriteConcurrency ?? 1,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.url && DatabaseModule.isBunSqlType(options.type)) {
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
tenantId: options.defaultTenant ?? 'default',
|
|
102
|
+
config: {
|
|
103
|
+
type: options.type,
|
|
104
|
+
url: options.url,
|
|
105
|
+
pool: options.bunSqlPool,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (options.host && DatabaseModule.isBunSqlType(options.type)) {
|
|
112
|
+
const protocol = options.type === 'mysql' ? 'mysql' : 'postgres';
|
|
113
|
+
const url =
|
|
114
|
+
`${protocol}://${options.username}:${options.password}@${options.host}:${options.port ?? 5432}/${options.databasePath ?? ''}`;
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
tenantId: options.defaultTenant ?? 'default',
|
|
118
|
+
config: {
|
|
119
|
+
type: options.type,
|
|
120
|
+
url,
|
|
121
|
+
pool: options.bunSqlPool,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new Error(
|
|
128
|
+
'[DatabaseModule] invalid configuration: specify tenants or single tenant connection options',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private static createDatabaseMiddleware(
|
|
133
|
+
options: DatabaseModuleOptions,
|
|
134
|
+
normalized: Array<{ tenantId: string; config: BunSQLConfig | SqliteV2Config }>,
|
|
135
|
+
sqlManager: BunSQLManager,
|
|
136
|
+
sqliteManager: SqliteManager,
|
|
137
|
+
): Middleware {
|
|
138
|
+
const defaultStrategy = options.defaultStrategy ?? 'pool';
|
|
139
|
+
const defaultTenant = options.defaultTenant ?? normalized[0]?.tenantId ?? 'default';
|
|
140
|
+
|
|
141
|
+
return async (context, next) => {
|
|
142
|
+
const routeHandler = (context as any).routeHandler as
|
|
143
|
+
| { controller: Constructor<unknown>; method: string }
|
|
144
|
+
| undefined;
|
|
145
|
+
|
|
146
|
+
let strategy: 'pool' | 'session' = defaultStrategy;
|
|
147
|
+
if (routeHandler) {
|
|
148
|
+
const routeStrategy = getDbStrategy(
|
|
149
|
+
routeHandler.controller,
|
|
150
|
+
routeHandler.method,
|
|
151
|
+
);
|
|
152
|
+
const hasTx = Boolean(
|
|
153
|
+
Reflect.getMetadata(
|
|
154
|
+
TRANSACTION_METADATA_KEY,
|
|
155
|
+
routeHandler.controller.prototype,
|
|
156
|
+
routeHandler.method,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
strategy = routeStrategy ?? (hasTx ? 'session' : defaultStrategy);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (strategy !== 'session') {
|
|
163
|
+
return await next();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const selected = normalized.find((item) => item.tenantId === defaultTenant) ?? normalized[0];
|
|
167
|
+
if (!selected) {
|
|
168
|
+
return await next();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (selected.config.type === 'sqlite') {
|
|
172
|
+
const sqlite = sqliteManager.getAdapter(selected.tenantId);
|
|
173
|
+
return await runWithSession(
|
|
174
|
+
{
|
|
175
|
+
tenantId: selected.tenantId,
|
|
176
|
+
sqlite,
|
|
177
|
+
},
|
|
178
|
+
async () => await next(),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const sql = sqlManager.getOrCreate(selected.tenantId, selected.config);
|
|
183
|
+
let reserved: any;
|
|
184
|
+
const session: DatabaseSession = {
|
|
185
|
+
tenantId: selected.tenantId,
|
|
186
|
+
lazyReserve: async () => {
|
|
187
|
+
if (!reserved) {
|
|
188
|
+
reserved = await sql.reserve();
|
|
189
|
+
session.reserved = reserved;
|
|
190
|
+
}
|
|
191
|
+
return reserved;
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
return await runWithSession(session, async () => await next());
|
|
197
|
+
} finally {
|
|
198
|
+
if (reserved) {
|
|
199
|
+
await reserved.release().catch(() => undefined);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
28
205
|
/**
|
|
29
206
|
* 创建数据库模块
|
|
30
207
|
* @param options - 模块配置
|
|
@@ -33,8 +210,47 @@ export class DatabaseModule {
|
|
|
33
210
|
options: DatabaseModuleOptions,
|
|
34
211
|
): typeof DatabaseModule {
|
|
35
212
|
const providers: ModuleProvider[] = [];
|
|
213
|
+
const normalized = DatabaseModule.normalizeConfig(options);
|
|
214
|
+
const sqlManager = new BunSQLManager();
|
|
215
|
+
const sqliteManager = new SqliteManager();
|
|
216
|
+
|
|
217
|
+
for (const item of normalized) {
|
|
218
|
+
if (item.config.type === 'sqlite') {
|
|
219
|
+
sqliteManager.getOrCreate(item.tenantId, item.config);
|
|
220
|
+
} else {
|
|
221
|
+
sqlManager.getOrCreate(item.tenantId, item.config);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
sqlManager.setDefaultTenant(options.defaultTenant ?? normalized[0]?.tenantId ?? 'default');
|
|
225
|
+
sqliteManager.setDefaultTenant(options.defaultTenant ?? normalized[0]?.tenantId ?? 'default');
|
|
226
|
+
|
|
227
|
+
const legacyOptions: DatabaseModuleOptions = options.database
|
|
228
|
+
? options
|
|
229
|
+
: {
|
|
230
|
+
...options,
|
|
231
|
+
database:
|
|
232
|
+
normalized[0]?.config.type === 'sqlite'
|
|
233
|
+
? {
|
|
234
|
+
type: 'sqlite',
|
|
235
|
+
config: {
|
|
236
|
+
path: (normalized[0].config as SqliteV2Config).database,
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
: {
|
|
240
|
+
type: (normalized[0]?.config.type ?? 'postgres') as 'postgres' | 'mysql',
|
|
241
|
+
config: {
|
|
242
|
+
host: options.host ?? 'localhost',
|
|
243
|
+
port: options.port ?? (normalized[0]?.config.type === 'mysql' ? 3306 : 5432),
|
|
244
|
+
database: options.databasePath ?? 'default',
|
|
245
|
+
user: options.username ?? 'root',
|
|
246
|
+
password: options.password ?? '',
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
};
|
|
36
250
|
|
|
37
|
-
const service = new DatabaseService(
|
|
251
|
+
const service = new DatabaseService(legacyOptions);
|
|
252
|
+
const transactionManager = new TransactionManager(sqlManager);
|
|
253
|
+
initDbProxy(sqlManager, transactionManager);
|
|
38
254
|
|
|
39
255
|
providers.push(
|
|
40
256
|
{
|
|
@@ -45,7 +261,22 @@ export class DatabaseModule {
|
|
|
45
261
|
provide: DATABASE_OPTIONS_TOKEN,
|
|
46
262
|
useValue: options,
|
|
47
263
|
},
|
|
48
|
-
|
|
264
|
+
{
|
|
265
|
+
provide: DatabaseService,
|
|
266
|
+
useValue: service,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
provide: BUN_SQL_MANAGER_TOKEN,
|
|
270
|
+
useValue: sqlManager,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
provide: SQLITE_MANAGER_TOKEN,
|
|
274
|
+
useValue: sqliteManager,
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
provide: DB_TOKEN,
|
|
278
|
+
useValue: db,
|
|
279
|
+
},
|
|
49
280
|
);
|
|
50
281
|
|
|
51
282
|
// 如果启用了 ORM,注册 ORM 服务
|
|
@@ -65,13 +296,15 @@ export class DatabaseModule {
|
|
|
65
296
|
}
|
|
66
297
|
|
|
67
298
|
// 注册事务管理器(总是注册,即使 ORM 未启用)
|
|
68
|
-
const transactionManager = new TransactionManager(service);
|
|
69
299
|
providers.push(
|
|
70
300
|
{
|
|
71
301
|
provide: TRANSACTION_SERVICE_TOKEN,
|
|
72
302
|
useValue: transactionManager,
|
|
73
303
|
},
|
|
74
|
-
|
|
304
|
+
{
|
|
305
|
+
provide: TransactionManager,
|
|
306
|
+
useValue: transactionManager,
|
|
307
|
+
},
|
|
75
308
|
);
|
|
76
309
|
|
|
77
310
|
// 数据库健康检查指示器可以通过 DatabaseModule.createHealthIndicator() 方法获取
|
|
@@ -82,7 +315,13 @@ export class DatabaseModule {
|
|
|
82
315
|
Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule) || {};
|
|
83
316
|
|
|
84
317
|
// 创建数据库扩展,用于在应用启动时初始化连接
|
|
85
|
-
const databaseExtension = new DatabaseExtension();
|
|
318
|
+
const databaseExtension = new DatabaseExtension(sqlManager, sqliteManager);
|
|
319
|
+
const middleware = DatabaseModule.createDatabaseMiddleware(
|
|
320
|
+
options,
|
|
321
|
+
normalized,
|
|
322
|
+
sqlManager,
|
|
323
|
+
sqliteManager,
|
|
324
|
+
);
|
|
86
325
|
|
|
87
326
|
const metadata = {
|
|
88
327
|
...existingMetadata,
|
|
@@ -102,6 +341,10 @@ export class DatabaseModule {
|
|
|
102
341
|
...(existingMetadata.extensions || []),
|
|
103
342
|
databaseExtension,
|
|
104
343
|
],
|
|
344
|
+
middlewares: [
|
|
345
|
+
...(existingMetadata.middlewares || []),
|
|
346
|
+
middleware,
|
|
347
|
+
],
|
|
105
348
|
};
|
|
106
349
|
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, DatabaseModule);
|
|
107
350
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { BunSQLManager } from './sql-manager';
|
|
2
|
+
import { getCurrentSession } from './database-context';
|
|
3
|
+
import type { TransactionManager } from './orm/transaction-manager';
|
|
4
|
+
|
|
5
|
+
type DbResult = Promise<unknown>;
|
|
6
|
+
|
|
7
|
+
export interface DbProxy {
|
|
8
|
+
(strings: TemplateStringsArray, ...values: unknown[]): DbResult;
|
|
9
|
+
transaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
10
|
+
tenant(tenantId: string): DbProxy;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let sqlManagerRef: BunSQLManager | undefined;
|
|
14
|
+
let txManagerRef: TransactionManager | undefined;
|
|
15
|
+
|
|
16
|
+
export function initDbProxy(
|
|
17
|
+
sqlManager: BunSQLManager,
|
|
18
|
+
txManager: TransactionManager,
|
|
19
|
+
): void {
|
|
20
|
+
sqlManagerRef = sqlManager;
|
|
21
|
+
txManagerRef = txManager;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureReady(): { sqlManager: BunSQLManager; txManager: TransactionManager } {
|
|
25
|
+
if (!sqlManagerRef || !txManagerRef) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'[db] proxy is not initialized. Ensure DatabaseModule.forRoot() is called before using db.',
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return { sqlManager: sqlManagerRef, txManager: txManagerRef };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const baseDb = async (
|
|
34
|
+
tenantId: string | undefined,
|
|
35
|
+
strings: TemplateStringsArray,
|
|
36
|
+
...values: unknown[]
|
|
37
|
+
): DbResult => {
|
|
38
|
+
const session = getCurrentSession();
|
|
39
|
+
if (session?.reserved) {
|
|
40
|
+
return await session.reserved(strings, ...values);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (session?.lazyReserve) {
|
|
44
|
+
const reserved = await session.lazyReserve();
|
|
45
|
+
return await reserved(strings, ...values);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { sqlManager } = ensureReady();
|
|
49
|
+
if (tenantId) {
|
|
50
|
+
const tenantSql = sqlManager.get(tenantId);
|
|
51
|
+
if (tenantSql) {
|
|
52
|
+
return await tenantSql(strings, ...values);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return await sqlManager.getDefault()(strings, ...values);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function createDb(tenantId?: string): DbProxy {
|
|
59
|
+
const fn = (async (
|
|
60
|
+
strings: TemplateStringsArray,
|
|
61
|
+
...values: unknown[]
|
|
62
|
+
) => baseDb(tenantId, strings, ...values)) as DbProxy;
|
|
63
|
+
|
|
64
|
+
fn.transaction = async <T>(txFn: () => Promise<T>): Promise<T> => {
|
|
65
|
+
const { txManager } = ensureReady();
|
|
66
|
+
return await txManager.runInTransaction(txFn);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
fn.tenant = (id: string): DbProxy => createDb(id);
|
|
70
|
+
|
|
71
|
+
return fn;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const db: DbProxy = createDb();
|
|
75
|
+
|
package/src/database/index.ts
CHANGED
|
@@ -4,9 +4,36 @@ export { DatabaseConnectionManager } from './connection-manager';
|
|
|
4
4
|
export { ConnectionPool } from './connection-pool';
|
|
5
5
|
export { DatabaseHealthIndicator } from './health-indicator';
|
|
6
6
|
export { DatabaseExtension } from './database-extension';
|
|
7
|
+
export { BunSQLManager } from './sql-manager';
|
|
8
|
+
export { SqliteAdapter, SqliteManager, Semaphore } from './sqlite-adapter';
|
|
7
9
|
export {
|
|
10
|
+
db,
|
|
11
|
+
initDbProxy,
|
|
12
|
+
type DbProxy,
|
|
13
|
+
} from './db-proxy';
|
|
14
|
+
export {
|
|
15
|
+
DbStrategy,
|
|
16
|
+
Session as DbSession,
|
|
17
|
+
DB_STRATEGY_KEY,
|
|
18
|
+
getDbStrategy,
|
|
19
|
+
type DbStrategyType,
|
|
20
|
+
} from './strategy-decorator';
|
|
21
|
+
export {
|
|
22
|
+
databaseSessionStore,
|
|
23
|
+
getCurrentSession,
|
|
24
|
+
runWithSession,
|
|
25
|
+
type DatabaseSession,
|
|
26
|
+
type TransactionState,
|
|
27
|
+
type ReservedSqlSession,
|
|
28
|
+
} from './database-context';
|
|
29
|
+
export {
|
|
30
|
+
BUN_SQL_MANAGER_TOKEN,
|
|
31
|
+
DB_TOKEN,
|
|
8
32
|
DATABASE_OPTIONS_TOKEN,
|
|
9
33
|
DATABASE_SERVICE_TOKEN,
|
|
34
|
+
SQLITE_MANAGER_TOKEN,
|
|
35
|
+
type BunSQLConfig,
|
|
36
|
+
type BunSQLPoolOptions,
|
|
10
37
|
type ConnectionInfo,
|
|
11
38
|
type ConnectionPoolOptions,
|
|
12
39
|
type DatabaseConfig,
|
|
@@ -14,7 +41,9 @@ export {
|
|
|
14
41
|
type DatabaseType,
|
|
15
42
|
type MysqlConfig,
|
|
16
43
|
type PostgresConfig,
|
|
44
|
+
type SqliteV2Config,
|
|
17
45
|
type SqliteConfig,
|
|
46
|
+
type TenantConfig,
|
|
18
47
|
} from './types';
|
|
19
48
|
// ORM 导出
|
|
20
49
|
export {
|
|
@@ -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
|
}
|