@dangao/bun-server 2.0.8 → 2.2.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/container.d.ts +16 -0
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/lifecycle.d.ts +48 -0
- package/dist/di/lifecycle.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts +10 -6
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3267 -2620
- 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 +80 -4
- 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 +49 -5
- 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 +98 -26
- 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/container.ts +55 -1
- package/src/di/lifecycle.ts +114 -0
- package/src/di/module-registry.ts +78 -14
- package/src/index.ts +31 -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 +139 -1
- package/tests/di/scoped-lifecycle.test.ts +61 -0
- package/tests/microservice/service-registry.test.ts +15 -0
- package/tests/router/timeout-decorator.test.ts +48 -0
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BUN_SQL_MANAGER_TOKEN,
|
|
5
|
+
DB_TOKEN,
|
|
6
|
+
DATABASE_OPTIONS_TOKEN,
|
|
7
|
+
DATABASE_SERVICE_TOKEN,
|
|
8
|
+
DatabaseModule,
|
|
9
|
+
SQLITE_MANAGER_TOKEN,
|
|
10
|
+
} from '../../src/database';
|
|
5
11
|
import { MODULE_METADATA_KEY } from '../../src/di/module';
|
|
6
12
|
|
|
7
|
-
describe('DatabaseModule', () => {
|
|
8
|
-
let container: Container;
|
|
9
|
-
let moduleRegistry: ModuleRegistry;
|
|
10
|
-
|
|
13
|
+
describe('DatabaseModule V2', () => {
|
|
11
14
|
beforeEach(() => {
|
|
12
|
-
// 清除模块元数据
|
|
13
15
|
Reflect.deleteMetadata(MODULE_METADATA_KEY, DatabaseModule);
|
|
14
|
-
|
|
15
|
-
container = new Container();
|
|
16
|
-
moduleRegistry = ModuleRegistry.getInstance();
|
|
17
|
-
moduleRegistry.clear();
|
|
18
16
|
});
|
|
19
17
|
|
|
20
18
|
afterEach(() => {
|
|
21
|
-
|
|
19
|
+
Reflect.deleteMetadata(MODULE_METADATA_KEY, DatabaseModule);
|
|
22
20
|
});
|
|
23
21
|
|
|
24
|
-
test('should
|
|
25
|
-
DatabaseModule.
|
|
22
|
+
test('should normalize sqlite legacy config', () => {
|
|
23
|
+
const normalized = DatabaseModule.normalizeConfig({
|
|
26
24
|
database: {
|
|
27
25
|
type: 'sqlite',
|
|
28
26
|
config: {
|
|
@@ -30,72 +28,49 @@ describe('DatabaseModule', () => {
|
|
|
30
28
|
},
|
|
31
29
|
},
|
|
32
30
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
expect(metadata.providers).toBeDefined();
|
|
36
|
-
expect(metadata.providers.length).toBeGreaterThan(0);
|
|
37
|
-
|
|
38
|
-
// 检查是否注册了 DATABASE_SERVICE_TOKEN
|
|
39
|
-
const hasServiceToken = metadata.providers.some(
|
|
40
|
-
(p: unknown) =>
|
|
41
|
-
typeof p === 'object' &&
|
|
42
|
-
p !== null &&
|
|
43
|
-
'provide' in p &&
|
|
44
|
-
p.provide === DATABASE_SERVICE_TOKEN,
|
|
45
|
-
);
|
|
46
|
-
expect(hasServiceToken).toBe(true);
|
|
31
|
+
expect(normalized.length).toBe(1);
|
|
32
|
+
expect(normalized[0]?.config.type).toBe('sqlite');
|
|
47
33
|
});
|
|
48
34
|
|
|
49
|
-
test('should
|
|
50
|
-
DatabaseModule.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
35
|
+
test('should normalize postgres url config', () => {
|
|
36
|
+
const normalized = DatabaseModule.normalizeConfig({
|
|
37
|
+
type: 'postgres',
|
|
38
|
+
url: 'postgres://user:pass@localhost:5432/db',
|
|
39
|
+
bunSqlPool: {
|
|
40
|
+
max: 20,
|
|
56
41
|
},
|
|
57
42
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
43
|
+
expect(normalized[0]?.config.type).toBe('postgres');
|
|
44
|
+
if (normalized[0]?.config.type === 'postgres') {
|
|
45
|
+
expect(normalized[0].config.pool?.max).toBe(20);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('should normalize tenants config', () => {
|
|
50
|
+
const normalized = DatabaseModule.normalizeConfig({
|
|
51
|
+
tenants: [
|
|
52
|
+
{
|
|
53
|
+
id: 'tenant-a',
|
|
54
|
+
config: {
|
|
55
|
+
type: 'postgres',
|
|
56
|
+
url: 'postgres://u:p@localhost:5432/a',
|
|
57
|
+
},
|
|
72
58
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
expect(metadata.exports).toContain(DatabaseService);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('should register database extension', () => {
|
|
83
|
-
DatabaseModule.forRoot({
|
|
84
|
-
database: {
|
|
85
|
-
type: 'sqlite',
|
|
86
|
-
config: {
|
|
87
|
-
path: ':memory:',
|
|
59
|
+
{
|
|
60
|
+
id: 'tenant-b',
|
|
61
|
+
config: {
|
|
62
|
+
type: 'sqlite',
|
|
63
|
+
database: ':memory:',
|
|
64
|
+
},
|
|
88
65
|
},
|
|
89
|
-
|
|
66
|
+
],
|
|
90
67
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
expect(
|
|
94
|
-
expect(metadata.extensions.length).toBe(1);
|
|
95
|
-
expect(metadata.extensions[0]).toBeDefined();
|
|
68
|
+
expect(normalized.length).toBe(2);
|
|
69
|
+
expect(normalized[0]?.tenantId).toBe('tenant-a');
|
|
70
|
+
expect(normalized[1]?.tenantId).toBe('tenant-b');
|
|
96
71
|
});
|
|
97
72
|
|
|
98
|
-
test('should
|
|
73
|
+
test('should register v2 providers and middleware', () => {
|
|
99
74
|
DatabaseModule.forRoot({
|
|
100
75
|
database: {
|
|
101
76
|
type: 'sqlite',
|
|
@@ -103,369 +78,55 @@ describe('DatabaseModule', () => {
|
|
|
103
78
|
path: ':memory:',
|
|
104
79
|
},
|
|
105
80
|
},
|
|
106
|
-
|
|
107
|
-
maxConnections: 20,
|
|
108
|
-
connectionTimeout: 60000,
|
|
109
|
-
retryCount: 5,
|
|
110
|
-
retryDelay: 2000,
|
|
111
|
-
},
|
|
81
|
+
defaultStrategy: 'pool',
|
|
112
82
|
});
|
|
113
83
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
expect(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
enableHealthCheck: false,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
moduleRegistry.register(DatabaseModule, container);
|
|
132
|
-
const service = container.resolve<DatabaseService>(DATABASE_SERVICE_TOKEN);
|
|
133
|
-
|
|
134
|
-
expect(service).toBeInstanceOf(DatabaseService);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('DatabaseService', () => {
|
|
139
|
-
let service: DatabaseService;
|
|
140
|
-
|
|
141
|
-
beforeEach(async () => {
|
|
142
|
-
service = new DatabaseService({
|
|
143
|
-
database: {
|
|
144
|
-
type: 'sqlite',
|
|
145
|
-
config: {
|
|
146
|
-
path: ':memory:',
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
await service.initialize();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
afterEach(async () => {
|
|
154
|
-
await service.close();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('should initialize database connection', async () => {
|
|
158
|
-
const info = service.getConnectionInfo();
|
|
159
|
-
expect(info.status).toBe('connected');
|
|
160
|
-
expect(info.type).toBe('sqlite');
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('should get database connection', () => {
|
|
164
|
-
const connection = service.getConnection();
|
|
165
|
-
expect(connection).toBeDefined();
|
|
166
|
-
expect(connection).not.toBeNull();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('should get database type', () => {
|
|
170
|
-
const type = service.getDatabaseType();
|
|
171
|
-
expect(type).toBe('sqlite');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('should check health status', async () => {
|
|
175
|
-
const isHealthy = await service.healthCheck();
|
|
176
|
-
expect(isHealthy).toBe(true);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('should get connection info', () => {
|
|
180
|
-
const info = service.getConnectionInfo();
|
|
181
|
-
expect(info).toBeDefined();
|
|
182
|
-
expect(info.status).toBe('connected');
|
|
183
|
-
expect(info.type).toBe('sqlite');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('should execute SQL query', () => {
|
|
187
|
-
// 创建表
|
|
188
|
-
service.query('CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT)');
|
|
189
|
-
|
|
190
|
-
// 插入数据
|
|
191
|
-
service.query('INSERT INTO test (name) VALUES (?)', ['test']);
|
|
192
|
-
|
|
193
|
-
// 查询数据(SQLite 返回同步结果)
|
|
194
|
-
const result = service.query<{ id: number; name: string }>('SELECT * FROM test');
|
|
195
|
-
|
|
196
|
-
expect(result).toBeDefined();
|
|
197
|
-
expect(Array.isArray(result)).toBe(true);
|
|
198
|
-
expect(result.length).toBe(1);
|
|
199
|
-
expect(result[0].name).toBe('test');
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test('should close database connection', async () => {
|
|
203
|
-
await service.close();
|
|
204
|
-
const info = service.getConnectionInfo();
|
|
205
|
-
expect(info.status).toBe('disconnected');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('should throw error when querying without connection', async () => {
|
|
209
|
-
await service.close();
|
|
210
|
-
|
|
211
|
-
expect(() => {
|
|
212
|
-
service.query('SELECT 1');
|
|
213
|
-
}).toThrow('Database connection is not established');
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
describe('DatabaseService Bun.SQL parameter binding', () => {
|
|
218
|
-
test('should pass parameters as Bun.SQL template values', async () => {
|
|
219
|
-
const service = new DatabaseService({
|
|
220
|
-
database: {
|
|
221
|
-
type: 'postgres',
|
|
222
|
-
config: {
|
|
223
|
-
host: 'localhost',
|
|
224
|
-
port: 5432,
|
|
225
|
-
database: 'test',
|
|
226
|
-
user: 'test',
|
|
227
|
-
password: 'test',
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
let capturedTemplate: TemplateStringsArray | null = null;
|
|
233
|
-
let capturedValues: unknown[] = [];
|
|
234
|
-
const mockConnection = async (
|
|
235
|
-
template: TemplateStringsArray,
|
|
236
|
-
...values: unknown[]
|
|
237
|
-
): Promise<Array<Record<string, unknown>>> => {
|
|
238
|
-
capturedTemplate = template;
|
|
239
|
-
capturedValues = values;
|
|
240
|
-
return [{ ok: true }];
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
(service as unknown as { getConnection: () => unknown }).getConnection = () =>
|
|
244
|
-
mockConnection;
|
|
245
|
-
(
|
|
246
|
-
service as unknown as {
|
|
247
|
-
getDatabaseType: () => 'sqlite' | 'postgres' | 'mysql';
|
|
248
|
-
}
|
|
249
|
-
).getDatabaseType = () => 'postgres';
|
|
250
|
-
|
|
251
|
-
const result = (await service.query<{ ok: boolean }>(
|
|
252
|
-
'SELECT * FROM users WHERE id = ? AND name = ?',
|
|
253
|
-
[1, "O'Reilly"],
|
|
254
|
-
)) as Array<{ ok: boolean }>;
|
|
255
|
-
|
|
256
|
-
expect(Array.isArray(result)).toBe(true);
|
|
257
|
-
expect(result[0]?.ok).toBe(true);
|
|
258
|
-
expect(capturedValues).toEqual([1, "O'Reilly"]);
|
|
259
|
-
expect(capturedTemplate).toBeDefined();
|
|
260
|
-
expect(capturedTemplate?.length).toBe(3);
|
|
261
|
-
expect(capturedTemplate?.[0]).toBe('SELECT * FROM users WHERE id = ');
|
|
262
|
-
expect(capturedTemplate?.[1]).toBe(' AND name = ');
|
|
263
|
-
expect(capturedTemplate?.[2]).toBe('');
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('should throw when placeholders do not match params count', async () => {
|
|
267
|
-
const service = new DatabaseService({
|
|
268
|
-
database: {
|
|
269
|
-
type: 'mysql',
|
|
270
|
-
config: {
|
|
271
|
-
host: 'localhost',
|
|
272
|
-
port: 3306,
|
|
273
|
-
database: 'test',
|
|
274
|
-
user: 'test',
|
|
275
|
-
password: 'test',
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
const mockConnection = async (): Promise<Array<Record<string, unknown>>> => [];
|
|
281
|
-
(service as unknown as { getConnection: () => unknown }).getConnection = () =>
|
|
282
|
-
mockConnection;
|
|
283
|
-
(
|
|
284
|
-
service as unknown as {
|
|
285
|
-
getDatabaseType: () => 'sqlite' | 'postgres' | 'mysql';
|
|
286
|
-
}
|
|
287
|
-
).getDatabaseType = () => 'mysql';
|
|
288
|
-
|
|
289
|
-
await expect(
|
|
290
|
-
service.query('SELECT * FROM users WHERE id = ?', [1, 2]) as Promise<
|
|
291
|
-
unknown[]
|
|
292
|
-
>,
|
|
293
|
-
).rejects.toThrow(
|
|
294
|
-
'Bun.SQL parameterized queries are not fully supported. Consider using template string queries.',
|
|
84
|
+
const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule);
|
|
85
|
+
expect(metadata).toBeDefined();
|
|
86
|
+
expect(metadata.providers).toBeDefined();
|
|
87
|
+
expect(metadata.middlewares).toBeDefined();
|
|
88
|
+
expect(metadata.middlewares.length).toBeGreaterThan(0);
|
|
89
|
+
|
|
90
|
+
const hasDbToken = metadata.providers.some(
|
|
91
|
+
(provider: unknown) =>
|
|
92
|
+
typeof provider === 'object' &&
|
|
93
|
+
provider !== null &&
|
|
94
|
+
'provide' in provider &&
|
|
95
|
+
(provider as { provide: unknown }).provide === DB_TOKEN,
|
|
295
96
|
);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
describe('ConnectionPool', () => {
|
|
305
|
-
test('should create connection pool', () => {
|
|
306
|
-
const { ConnectionPool } = require('../../src/database/connection-pool');
|
|
307
|
-
const pool = new ConnectionPool(
|
|
308
|
-
{
|
|
309
|
-
type: 'sqlite',
|
|
310
|
-
config: {
|
|
311
|
-
path: ':memory:',
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
maxConnections: 5,
|
|
316
|
-
},
|
|
97
|
+
const hasBunSqlManager = metadata.providers.some(
|
|
98
|
+
(provider: unknown) =>
|
|
99
|
+
typeof provider === 'object' &&
|
|
100
|
+
provider !== null &&
|
|
101
|
+
'provide' in provider &&
|
|
102
|
+
(provider as { provide: unknown }).provide === BUN_SQL_MANAGER_TOKEN,
|
|
317
103
|
);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
test('should acquire and release connections', async () => {
|
|
326
|
-
const { ConnectionPool } = require('../../src/database/connection-pool');
|
|
327
|
-
const pool = new ConnectionPool({
|
|
328
|
-
type: 'sqlite',
|
|
329
|
-
config: {
|
|
330
|
-
path: ':memory:',
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
const connection1 = await pool.acquire();
|
|
335
|
-
expect(connection1).toBeDefined();
|
|
336
|
-
|
|
337
|
-
const stats1 = pool.getPoolStats();
|
|
338
|
-
expect(stats1.total).toBe(1);
|
|
339
|
-
expect(stats1.inUse).toBe(1);
|
|
340
|
-
|
|
341
|
-
pool.release(connection1);
|
|
342
|
-
const stats2 = pool.getPoolStats();
|
|
343
|
-
expect(stats2.inUse).toBe(0);
|
|
344
|
-
|
|
345
|
-
await pool.close();
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
test('should limit max connections', async () => {
|
|
349
|
-
const { ConnectionPool } = require('../../src/database/connection-pool');
|
|
350
|
-
const pool = new ConnectionPool(
|
|
351
|
-
{
|
|
352
|
-
type: 'sqlite',
|
|
353
|
-
config: {
|
|
354
|
-
path: ':memory:',
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
maxConnections: 2,
|
|
359
|
-
},
|
|
104
|
+
const hasSqliteManager = metadata.providers.some(
|
|
105
|
+
(provider: unknown) =>
|
|
106
|
+
typeof provider === 'object' &&
|
|
107
|
+
provider !== null &&
|
|
108
|
+
'provide' in provider &&
|
|
109
|
+
(provider as { provide: unknown }).provide === SQLITE_MANAGER_TOKEN,
|
|
360
110
|
);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
test('should close pool and all connections', async () => {
|
|
376
|
-
const { ConnectionPool } = require('../../src/database/connection-pool');
|
|
377
|
-
const pool = new ConnectionPool({
|
|
378
|
-
type: 'sqlite',
|
|
379
|
-
config: {
|
|
380
|
-
path: ':memory:',
|
|
381
|
-
},
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
const conn1 = await pool.acquire();
|
|
385
|
-
const conn2 = await pool.acquire();
|
|
386
|
-
|
|
387
|
-
await pool.close();
|
|
388
|
-
|
|
389
|
-
const stats = pool.getPoolStats();
|
|
390
|
-
expect(stats.total).toBe(0);
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
describe('DatabaseConnectionManager', () => {
|
|
395
|
-
test('should connect to SQLite database', async () => {
|
|
396
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
397
|
-
const manager = new DatabaseConnectionManager({
|
|
398
|
-
type: 'sqlite',
|
|
399
|
-
config: {
|
|
400
|
-
path: ':memory:',
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
await manager.connect();
|
|
405
|
-
const info = manager.getConnectionInfo();
|
|
406
|
-
|
|
407
|
-
expect(info.status).toBe('connected');
|
|
408
|
-
expect(info.type).toBe('sqlite');
|
|
409
|
-
|
|
410
|
-
await manager.disconnect();
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
test('should handle connection errors gracefully', async () => {
|
|
414
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
415
|
-
const manager = new DatabaseConnectionManager({
|
|
416
|
-
type: 'sqlite',
|
|
417
|
-
config: {
|
|
418
|
-
path: ':memory:', // 使用内存数据库,应该总是成功
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// SQLite 内存数据库应该总是成功连接
|
|
423
|
-
await manager.connect();
|
|
424
|
-
|
|
425
|
-
const info = manager.getConnectionInfo();
|
|
426
|
-
expect(info.status).toBe('connected');
|
|
427
|
-
|
|
428
|
-
await manager.disconnect();
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test('should check health status', async () => {
|
|
432
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
433
|
-
const manager = new DatabaseConnectionManager({
|
|
434
|
-
type: 'sqlite',
|
|
435
|
-
config: {
|
|
436
|
-
path: ':memory:',
|
|
437
|
-
},
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
await manager.connect();
|
|
441
|
-
const isHealthy = await manager.healthCheck();
|
|
442
|
-
|
|
443
|
-
expect(isHealthy).toBe(true);
|
|
444
|
-
|
|
445
|
-
await manager.disconnect();
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
test('should get pool stats', async () => {
|
|
449
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
450
|
-
const manager = new DatabaseConnectionManager(
|
|
451
|
-
{
|
|
452
|
-
type: 'sqlite',
|
|
453
|
-
config: {
|
|
454
|
-
path: ':memory:',
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
maxConnections: 5,
|
|
459
|
-
},
|
|
111
|
+
const hasLegacyService = metadata.providers.some(
|
|
112
|
+
(provider: unknown) =>
|
|
113
|
+
typeof provider === 'object' &&
|
|
114
|
+
provider !== null &&
|
|
115
|
+
'provide' in provider &&
|
|
116
|
+
(provider as { provide: unknown }).provide === DATABASE_SERVICE_TOKEN,
|
|
117
|
+
);
|
|
118
|
+
const hasOptionsToken = metadata.providers.some(
|
|
119
|
+
(provider: unknown) =>
|
|
120
|
+
typeof provider === 'object' &&
|
|
121
|
+
provider !== null &&
|
|
122
|
+
'provide' in provider &&
|
|
123
|
+
(provider as { provide: unknown }).provide === DATABASE_OPTIONS_TOKEN,
|
|
460
124
|
);
|
|
461
125
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
expect(
|
|
466
|
-
expect(
|
|
467
|
-
expect(stats.total).toBeGreaterThanOrEqual(1);
|
|
468
|
-
|
|
469
|
-
await manager.disconnect();
|
|
126
|
+
expect(hasDbToken).toBe(true);
|
|
127
|
+
expect(hasBunSqlManager).toBe(true);
|
|
128
|
+
expect(hasSqliteManager).toBe(true);
|
|
129
|
+
expect(hasLegacyService).toBe(true);
|
|
130
|
+
expect(hasOptionsToken).toBe(true);
|
|
470
131
|
});
|
|
471
132
|
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { db, initDbProxy } from '../../src/database/db-proxy';
|
|
4
|
+
import { runWithSession } from '../../src/database/database-context';
|
|
5
|
+
|
|
6
|
+
describe('db proxy', () => {
|
|
7
|
+
test('should fallback to default sql when no session', async () => {
|
|
8
|
+
const calls: unknown[] = [];
|
|
9
|
+
const defaultSql = async (
|
|
10
|
+
strings: TemplateStringsArray,
|
|
11
|
+
...values: unknown[]
|
|
12
|
+
) => {
|
|
13
|
+
calls.push([strings.join('?'), ...values]);
|
|
14
|
+
return [{ ok: true }];
|
|
15
|
+
};
|
|
16
|
+
initDbProxy(
|
|
17
|
+
{
|
|
18
|
+
getDefault: () => defaultSql,
|
|
19
|
+
} as any,
|
|
20
|
+
{
|
|
21
|
+
runInTransaction: async <T>(fn: () => Promise<T>) => await fn(),
|
|
22
|
+
} as any,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const result = await db`SELECT ${1}`;
|
|
26
|
+
expect(result).toEqual([{ ok: true }]);
|
|
27
|
+
expect(calls.length).toBe(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should route to reserved connection in session', async () => {
|
|
31
|
+
const reservedCalls: unknown[] = [];
|
|
32
|
+
const reserved = (async (
|
|
33
|
+
strings: TemplateStringsArray,
|
|
34
|
+
...values: unknown[]
|
|
35
|
+
) => {
|
|
36
|
+
reservedCalls.push([strings.join('?'), ...values]);
|
|
37
|
+
return [{ via: 'reserved' }];
|
|
38
|
+
}) as any;
|
|
39
|
+
|
|
40
|
+
initDbProxy(
|
|
41
|
+
{
|
|
42
|
+
getDefault: () => async () => [{ via: 'default' }],
|
|
43
|
+
} as any,
|
|
44
|
+
{
|
|
45
|
+
runInTransaction: async <T>(fn: () => Promise<T>) => await fn(),
|
|
46
|
+
} as any,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const result = await runWithSession(
|
|
50
|
+
{
|
|
51
|
+
tenantId: 'default',
|
|
52
|
+
reserved,
|
|
53
|
+
},
|
|
54
|
+
async () => await db`SELECT ${2}`,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(result).toEqual([{ via: 'reserved' }]);
|
|
58
|
+
expect(reservedCalls.length).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should use lazyReserve only once', async () => {
|
|
62
|
+
let reservedCount = 0;
|
|
63
|
+
const reserved = (async () => [{ via: 'lazy' }]) as any;
|
|
64
|
+
reserved.begin = async <T>(fn: () => Promise<T>) => await fn();
|
|
65
|
+
reserved.release = async () => undefined;
|
|
66
|
+
|
|
67
|
+
initDbProxy(
|
|
68
|
+
{
|
|
69
|
+
getDefault: () => async () => [{ via: 'default' }],
|
|
70
|
+
} as any,
|
|
71
|
+
{
|
|
72
|
+
runInTransaction: async <T>(fn: () => Promise<T>) => await fn(),
|
|
73
|
+
} as any,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
await runWithSession(
|
|
77
|
+
{
|
|
78
|
+
tenantId: 'default',
|
|
79
|
+
lazyReserve: async () => {
|
|
80
|
+
reservedCount += 1;
|
|
81
|
+
return reserved;
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
async () => {
|
|
85
|
+
await db`SELECT 1`;
|
|
86
|
+
await db`SELECT 2`;
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(reservedCount).toBe(2);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { BunSQLManager } from '../../src/database/sql-manager';
|
|
4
|
+
|
|
5
|
+
describe('BunSQLManager', () => {
|
|
6
|
+
test('should throw when default tenant not initialized', () => {
|
|
7
|
+
const manager = new BunSQLManager();
|
|
8
|
+
expect(() => manager.getDefault()).toThrow();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('should create and reuse instance by tenant', () => {
|
|
12
|
+
const manager = new BunSQLManager();
|
|
13
|
+
const mockSql = { close: async () => undefined };
|
|
14
|
+
let created = 0;
|
|
15
|
+
(manager as any).instances = new Map();
|
|
16
|
+
const originalGetOrCreate = manager.getOrCreate.bind(manager);
|
|
17
|
+
|
|
18
|
+
(manager as any).getOrCreate = (tenantId: string, config: any) => {
|
|
19
|
+
const existing = (manager as any).instances.get(tenantId);
|
|
20
|
+
if (existing) {
|
|
21
|
+
return existing;
|
|
22
|
+
}
|
|
23
|
+
created += 1;
|
|
24
|
+
(manager as any).instances.set(tenantId, mockSql);
|
|
25
|
+
return mockSql;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const a = (manager as any).getOrCreate('t1', {
|
|
29
|
+
type: 'postgres',
|
|
30
|
+
url: 'postgres://u:p@localhost:5432/db',
|
|
31
|
+
});
|
|
32
|
+
const b = (manager as any).getOrCreate('t1', {
|
|
33
|
+
type: 'postgres',
|
|
34
|
+
url: 'postgres://u:p@localhost:5432/db',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(a).toBe(mockSql);
|
|
38
|
+
expect(b).toBe(mockSql);
|
|
39
|
+
expect(created).toBe(1);
|
|
40
|
+
(manager as any).getOrCreate = originalGetOrCreate;
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|