@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,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,282 +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('ConnectionPool', () => {
|
|
218
|
-
test('should create connection pool', () => {
|
|
219
|
-
const { ConnectionPool } = require('../../src/database/connection-pool');
|
|
220
|
-
const pool = new ConnectionPool(
|
|
221
|
-
{
|
|
222
|
-
type: 'sqlite',
|
|
223
|
-
config: {
|
|
224
|
-
path: ':memory:',
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
maxConnections: 5,
|
|
229
|
-
},
|
|
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,
|
|
230
96
|
);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
test('should acquire and release connections', async () => {
|
|
239
|
-
const { ConnectionPool } = require('../../src/database/connection-pool');
|
|
240
|
-
const pool = new ConnectionPool({
|
|
241
|
-
type: 'sqlite',
|
|
242
|
-
config: {
|
|
243
|
-
path: ':memory:',
|
|
244
|
-
},
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const connection1 = await pool.acquire();
|
|
248
|
-
expect(connection1).toBeDefined();
|
|
249
|
-
|
|
250
|
-
const stats1 = pool.getPoolStats();
|
|
251
|
-
expect(stats1.total).toBe(1);
|
|
252
|
-
expect(stats1.inUse).toBe(1);
|
|
253
|
-
|
|
254
|
-
pool.release(connection1);
|
|
255
|
-
const stats2 = pool.getPoolStats();
|
|
256
|
-
expect(stats2.inUse).toBe(0);
|
|
257
|
-
|
|
258
|
-
await pool.close();
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('should limit max connections', async () => {
|
|
262
|
-
const { ConnectionPool } = require('../../src/database/connection-pool');
|
|
263
|
-
const pool = new ConnectionPool(
|
|
264
|
-
{
|
|
265
|
-
type: 'sqlite',
|
|
266
|
-
config: {
|
|
267
|
-
path: ':memory:',
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
maxConnections: 2,
|
|
272
|
-
},
|
|
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,
|
|
273
103
|
);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
},
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
const conn1 = await pool.acquire();
|
|
298
|
-
const conn2 = await pool.acquire();
|
|
299
|
-
|
|
300
|
-
await pool.close();
|
|
301
|
-
|
|
302
|
-
const stats = pool.getPoolStats();
|
|
303
|
-
expect(stats.total).toBe(0);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe('DatabaseConnectionManager', () => {
|
|
308
|
-
test('should connect to SQLite database', async () => {
|
|
309
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
310
|
-
const manager = new DatabaseConnectionManager({
|
|
311
|
-
type: 'sqlite',
|
|
312
|
-
config: {
|
|
313
|
-
path: ':memory:',
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
await manager.connect();
|
|
318
|
-
const info = manager.getConnectionInfo();
|
|
319
|
-
|
|
320
|
-
expect(info.status).toBe('connected');
|
|
321
|
-
expect(info.type).toBe('sqlite');
|
|
322
|
-
|
|
323
|
-
await manager.disconnect();
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('should handle connection errors gracefully', async () => {
|
|
327
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
328
|
-
const manager = new DatabaseConnectionManager({
|
|
329
|
-
type: 'sqlite',
|
|
330
|
-
config: {
|
|
331
|
-
path: ':memory:', // 使用内存数据库,应该总是成功
|
|
332
|
-
},
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// SQLite 内存数据库应该总是成功连接
|
|
336
|
-
await manager.connect();
|
|
337
|
-
|
|
338
|
-
const info = manager.getConnectionInfo();
|
|
339
|
-
expect(info.status).toBe('connected');
|
|
340
|
-
|
|
341
|
-
await manager.disconnect();
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
test('should check health status', async () => {
|
|
345
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
346
|
-
const manager = new DatabaseConnectionManager({
|
|
347
|
-
type: 'sqlite',
|
|
348
|
-
config: {
|
|
349
|
-
path: ':memory:',
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
await manager.connect();
|
|
354
|
-
const isHealthy = await manager.healthCheck();
|
|
355
|
-
|
|
356
|
-
expect(isHealthy).toBe(true);
|
|
357
|
-
|
|
358
|
-
await manager.disconnect();
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test('should get pool stats', async () => {
|
|
362
|
-
const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
|
|
363
|
-
const manager = new DatabaseConnectionManager(
|
|
364
|
-
{
|
|
365
|
-
type: 'sqlite',
|
|
366
|
-
config: {
|
|
367
|
-
path: ':memory:',
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
maxConnections: 5,
|
|
372
|
-
},
|
|
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,
|
|
110
|
+
);
|
|
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,
|
|
373
124
|
);
|
|
374
125
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
expect(
|
|
379
|
-
expect(
|
|
380
|
-
expect(stats.total).toBeGreaterThanOrEqual(1);
|
|
381
|
-
|
|
382
|
-
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);
|
|
383
131
|
});
|
|
384
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
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { Semaphore, SqliteAdapter } from '../../src/database/sqlite-adapter';
|
|
4
|
+
|
|
5
|
+
describe('SqliteAdapter', () => {
|
|
6
|
+
test('should execute query and write', async () => {
|
|
7
|
+
const adapter = new SqliteAdapter({
|
|
8
|
+
type: 'sqlite',
|
|
9
|
+
database: ':memory:',
|
|
10
|
+
wal: true,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
await adapter.execute(
|
|
14
|
+
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)',
|
|
15
|
+
);
|
|
16
|
+
await adapter.execute('INSERT INTO users (name) VALUES (?)', ['alice']);
|
|
17
|
+
|
|
18
|
+
const rows = adapter.query<{ id: number; name: string }>(
|
|
19
|
+
'SELECT * FROM users',
|
|
20
|
+
);
|
|
21
|
+
expect(rows.length).toBe(1);
|
|
22
|
+
expect(rows[0]?.name).toBe('alice');
|
|
23
|
+
|
|
24
|
+
adapter.close();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Semaphore', () => {
|
|
29
|
+
test('should acquire and release lock', async () => {
|
|
30
|
+
const semaphore = new Semaphore(1);
|
|
31
|
+
const lock1 = await semaphore.acquire();
|
|
32
|
+
|
|
33
|
+
let acquiredSecond = false;
|
|
34
|
+
const pending = semaphore.acquire().then((lock2) => {
|
|
35
|
+
acquiredSecond = true;
|
|
36
|
+
lock2[Symbol.dispose]();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(acquiredSecond).toBe(false);
|
|
40
|
+
lock1[Symbol.dispose]();
|
|
41
|
+
await pending;
|
|
42
|
+
expect(acquiredSecond).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DbStrategy,
|
|
5
|
+
Session as DbSession,
|
|
6
|
+
getDbStrategy,
|
|
7
|
+
} from '../../src/database/strategy-decorator';
|
|
8
|
+
|
|
9
|
+
describe('DbStrategy decorator', () => {
|
|
10
|
+
test('should read class level strategy', () => {
|
|
11
|
+
@DbStrategy('session')
|
|
12
|
+
class UserController {
|
|
13
|
+
public list(): void {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
expect(getDbStrategy(UserController as any, 'list')).toBe('session');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('should prefer method level strategy', () => {
|
|
20
|
+
@DbStrategy('pool')
|
|
21
|
+
class UserController {
|
|
22
|
+
@DbSession()
|
|
23
|
+
public list(): void {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
expect(getDbStrategy(UserController as any, 'list')).toBe('session');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|