@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.
Files changed (97) hide show
  1. package/README.md +4 -0
  2. package/dist/controller/controller.d.ts.map +1 -1
  3. package/dist/core/application.d.ts +6 -1
  4. package/dist/core/application.d.ts.map +1 -1
  5. package/dist/core/server.d.ts +5 -0
  6. package/dist/core/server.d.ts.map +1 -1
  7. package/dist/database/database-context.d.ts +25 -0
  8. package/dist/database/database-context.d.ts.map +1 -0
  9. package/dist/database/database-extension.d.ts +8 -9
  10. package/dist/database/database-extension.d.ts.map +1 -1
  11. package/dist/database/database-module.d.ts +7 -1
  12. package/dist/database/database-module.d.ts.map +1 -1
  13. package/dist/database/db-proxy.d.ts +12 -0
  14. package/dist/database/db-proxy.d.ts.map +1 -0
  15. package/dist/database/index.d.ts +6 -1
  16. package/dist/database/index.d.ts.map +1 -1
  17. package/dist/database/orm/transaction-interceptor.d.ts +0 -16
  18. package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
  19. package/dist/database/orm/transaction-manager.d.ts +10 -61
  20. package/dist/database/orm/transaction-manager.d.ts.map +1 -1
  21. package/dist/database/service.d.ts.map +1 -1
  22. package/dist/database/sql-manager.d.ts +14 -0
  23. package/dist/database/sql-manager.d.ts.map +1 -0
  24. package/dist/database/sqlite-adapter.d.ts +32 -0
  25. package/dist/database/sqlite-adapter.d.ts.map +1 -0
  26. package/dist/database/strategy-decorator.d.ts +8 -0
  27. package/dist/database/strategy-decorator.d.ts.map +1 -0
  28. package/dist/database/types.d.ts +122 -1
  29. package/dist/database/types.d.ts.map +1 -1
  30. package/dist/di/container.d.ts +16 -0
  31. package/dist/di/container.d.ts.map +1 -1
  32. package/dist/di/lifecycle.d.ts +48 -0
  33. package/dist/di/lifecycle.d.ts.map +1 -1
  34. package/dist/di/module-registry.d.ts +10 -6
  35. package/dist/di/module-registry.d.ts.map +1 -1
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3267 -2620
  39. package/dist/microservice/service-registry/service-registry-module.d.ts +16 -0
  40. package/dist/microservice/service-registry/service-registry-module.d.ts.map +1 -1
  41. package/dist/router/index.d.ts +1 -0
  42. package/dist/router/index.d.ts.map +1 -1
  43. package/dist/router/registry.d.ts +1 -1
  44. package/dist/router/registry.d.ts.map +1 -1
  45. package/dist/router/route.d.ts +2 -1
  46. package/dist/router/route.d.ts.map +1 -1
  47. package/dist/router/router.d.ts +1 -1
  48. package/dist/router/router.d.ts.map +1 -1
  49. package/dist/router/timeout-decorator.d.ts +6 -0
  50. package/dist/router/timeout-decorator.d.ts.map +1 -0
  51. package/docs/database.md +48 -15
  52. package/docs/idle-timeout.md +42 -0
  53. package/docs/lifecycle.md +80 -4
  54. package/docs/microservice-nacos.md +1 -0
  55. package/docs/microservice-service-registry.md +7 -0
  56. package/docs/zh/database.md +48 -15
  57. package/docs/zh/idle-timeout.md +41 -0
  58. package/docs/zh/lifecycle.md +49 -5
  59. package/docs/zh/microservice-nacos.md +1 -0
  60. package/docs/zh/microservice-service-registry.md +6 -0
  61. package/package.json +1 -1
  62. package/src/controller/controller.ts +11 -1
  63. package/src/core/application.ts +98 -26
  64. package/src/core/server.ts +10 -0
  65. package/src/database/database-context.ts +43 -0
  66. package/src/database/database-extension.ts +12 -45
  67. package/src/database/database-module.ts +254 -11
  68. package/src/database/db-proxy.ts +75 -0
  69. package/src/database/index.ts +29 -0
  70. package/src/database/orm/transaction-interceptor.ts +12 -149
  71. package/src/database/orm/transaction-manager.ts +143 -210
  72. package/src/database/service.ts +28 -2
  73. package/src/database/sql-manager.ts +62 -0
  74. package/src/database/sqlite-adapter.ts +121 -0
  75. package/src/database/strategy-decorator.ts +42 -0
  76. package/src/database/types.ts +133 -1
  77. package/src/di/container.ts +55 -1
  78. package/src/di/lifecycle.ts +114 -0
  79. package/src/di/module-registry.ts +78 -14
  80. package/src/index.ts +31 -1
  81. package/src/microservice/service-registry/service-registry-module.ts +25 -1
  82. package/src/router/index.ts +1 -0
  83. package/src/router/registry.ts +10 -1
  84. package/src/router/route.ts +31 -3
  85. package/src/router/router.ts +10 -1
  86. package/src/router/timeout-decorator.ts +35 -0
  87. package/tests/core/application.test.ts +10 -0
  88. package/tests/database/database-module.test.ts +91 -430
  89. package/tests/database/db-proxy.test.ts +93 -0
  90. package/tests/database/sql-manager.test.ts +43 -0
  91. package/tests/database/sqlite-adapter.test.ts +45 -0
  92. package/tests/database/strategy-decorator.test.ts +29 -0
  93. package/tests/database/transaction.test.ts +84 -222
  94. package/tests/di/lifecycle.test.ts +139 -1
  95. package/tests/di/scoped-lifecycle.test.ts +61 -0
  96. package/tests/microservice/service-registry.test.ts +15 -0
  97. package/tests/router/timeout-decorator.test.ts +48 -0
@@ -1,28 +1,26 @@
1
- import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
- import { DatabaseModule, DatabaseService, DATABASE_SERVICE_TOKEN } from '../../src/database';
3
- import { Container } from '../../src/di/container';
4
- import { ModuleRegistry } from '../../src/di/module-registry';
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
- moduleRegistry.clear();
19
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, DatabaseModule);
22
20
  });
23
21
 
24
- test('should register database service provider', () => {
25
- DatabaseModule.forRoot({
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
- const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule);
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 register database service instance', () => {
50
- DatabaseModule.forRoot({
51
- database: {
52
- type: 'sqlite',
53
- config: {
54
- path: ':memory:',
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
- moduleRegistry.register(DatabaseModule, container);
60
- const service = container.resolve<DatabaseService>(DATABASE_SERVICE_TOKEN);
61
-
62
- expect(service).toBeInstanceOf(DatabaseService);
63
- expect(service.getDatabaseType()).toBe('sqlite');
64
- });
65
-
66
- test('should export database service', () => {
67
- DatabaseModule.forRoot({
68
- database: {
69
- type: 'sqlite',
70
- config: {
71
- path: ':memory:',
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
- const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule);
77
- expect(metadata.exports).toBeDefined();
78
- expect(metadata.exports).toContain(DATABASE_SERVICE_TOKEN);
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
- const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule);
93
- expect(metadata.extensions).toBeDefined();
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 support connection pool options', () => {
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
- pool: {
107
- maxConnections: 20,
108
- connectionTimeout: 60000,
109
- retryCount: 5,
110
- retryDelay: 2000,
111
- },
81
+ defaultStrategy: 'pool',
112
82
  });
113
83
 
114
- moduleRegistry.register(DatabaseModule, container);
115
- const service = container.resolve<DatabaseService>(DATABASE_SERVICE_TOKEN);
116
-
117
- expect(service).toBeInstanceOf(DatabaseService);
118
- });
119
-
120
- test('should support disabling health check', () => {
121
- DatabaseModule.forRoot({
122
- database: {
123
- type: 'sqlite',
124
- config: {
125
- path: ':memory:',
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
- await expect(
297
- service.query('SELECT * FROM users WHERE id = ?', [1, 2]) as Promise<
298
- unknown[]
299
- >,
300
- ).rejects.toThrow('Original error: SQL placeholders count does not match parameters count');
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
- expect(pool).toBeDefined();
320
- const stats = pool.getPoolStats();
321
- expect(stats.maxConnections).toBe(5);
322
- expect(stats.total).toBe(0);
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
- const conn1 = await pool.acquire();
363
- const conn2 = await pool.acquire();
364
-
365
- const stats = pool.getPoolStats();
366
- expect(stats.total).toBe(2);
367
- expect(stats.inUse).toBe(2);
368
-
369
- pool.release(conn1);
370
- pool.release(conn2);
371
-
372
- await pool.close();
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
- await manager.connect();
463
- const stats = manager.getPoolStats();
464
-
465
- expect(stats).toBeDefined();
466
- expect(stats.maxConnections).toBe(5);
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
+