@dangao/bun-server 3.1.0 → 3.3.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/dist/ai/types.d.ts +4 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/auth/types.d.ts +4 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/cache/interceptors.d.ts +3 -3
- package/dist/cache/interceptors.d.ts.map +1 -1
- package/dist/cache/service.d.ts +6 -6
- package/dist/cache/service.d.ts.map +1 -1
- package/dist/cache/types.d.ts +12 -12
- package/dist/cache/types.d.ts.map +1 -1
- package/dist/config/service.d.ts +6 -4
- package/dist/config/service.d.ts.map +1 -1
- package/dist/core/application.d.ts +4 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/context.d.ts +4 -2
- package/dist/core/context.d.ts.map +1 -1
- package/dist/database/connection-manager.d.ts +2 -2
- package/dist/database/connection-manager.d.ts.map +1 -1
- package/dist/database/connection-pool.d.ts +4 -4
- package/dist/database/connection-pool.d.ts.map +1 -1
- package/dist/database/database-module.d.ts.map +1 -1
- package/dist/database/db-proxy.d.ts.map +1 -1
- package/dist/database/driver.d.ts +83 -0
- package/dist/database/driver.d.ts.map +1 -0
- package/dist/database/index.d.ts +2 -1
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/service.d.ts +0 -10
- package/dist/database/service.d.ts.map +1 -1
- package/dist/database/sql-manager.d.ts.map +1 -1
- package/dist/database/sqlite-adapter.d.ts +4 -2
- package/dist/database/sqlite-adapter.d.ts.map +1 -1
- package/dist/database/types.d.ts +26 -0
- package/dist/database/types.d.ts.map +1 -1
- package/dist/di/container.d.ts +2 -0
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/di/module.d.ts +11 -1
- package/dist/di/module.d.ts.map +1 -1
- package/dist/di/types.d.ts +1 -1
- package/dist/di/types.d.ts.map +1 -1
- package/dist/error/handler.d.ts.map +1 -1
- package/dist/error/http-exception.d.ts +8 -8
- package/dist/error/http-exception.d.ts.map +1 -1
- package/dist/error/index.d.ts +1 -0
- package/dist/error/index.d.ts.map +1 -1
- package/dist/events/service.d.ts +2 -2
- package/dist/events/service.d.ts.map +1 -1
- package/dist/events/types.d.ts +12 -3
- package/dist/events/types.d.ts.map +1 -1
- package/dist/index.js +5951 -5820
- package/dist/index.node.mjs +310 -139
- package/dist/interceptor/base-interceptor.d.ts +3 -3
- package/dist/interceptor/base-interceptor.d.ts.map +1 -1
- package/dist/interceptor/builtin/log-interceptor.d.ts +1 -1
- package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -1
- package/dist/interceptor/builtin/permission-interceptor.d.ts +1 -1
- package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -1
- package/dist/interceptor/interceptor-chain.d.ts +1 -1
- package/dist/interceptor/interceptor-chain.d.ts.map +1 -1
- package/dist/interceptor/interceptor-registry.d.ts +3 -1
- package/dist/interceptor/interceptor-registry.d.ts.map +1 -1
- package/dist/interceptor/types.d.ts +6 -1
- package/dist/interceptor/types.d.ts.map +1 -1
- package/dist/microservice/service-client/types.d.ts +1 -0
- package/dist/microservice/service-client/types.d.ts.map +1 -1
- package/dist/microservice/tracing/tracer.d.ts +1 -0
- package/dist/microservice/tracing/tracer.d.ts.map +1 -1
- package/dist/middleware/builtin/file-upload.d.ts +2 -0
- package/dist/middleware/builtin/file-upload.d.ts.map +1 -1
- package/dist/middleware/builtin/rate-limit.d.ts +9 -1
- package/dist/middleware/builtin/rate-limit.d.ts.map +1 -1
- package/dist/queue/service.d.ts +2 -2
- package/dist/queue/service.d.ts.map +1 -1
- package/dist/queue/types.d.ts +25 -1
- package/dist/queue/types.d.ts.map +1 -1
- package/dist/router/decorators.d.ts +1 -2
- package/dist/router/decorators.d.ts.map +1 -1
- package/dist/security/guards/types.d.ts +1 -0
- package/dist/security/guards/types.d.ts.map +1 -1
- package/dist/security/types.d.ts +1 -1
- package/dist/security/types.d.ts.map +1 -1
- package/dist/session/types.d.ts +8 -0
- package/dist/session/types.d.ts.map +1 -1
- package/dist/swagger/decorators.d.ts +1 -1
- package/dist/swagger/decorators.d.ts.map +1 -1
- package/dist/swagger/types.d.ts +1 -1
- package/dist/swagger/types.d.ts.map +1 -1
- package/dist/testing/harness.d.ts +1 -1
- package/dist/testing/harness.d.ts.map +1 -1
- package/dist/testing/test-client.d.ts +1 -1
- package/dist/testing/test-client.d.ts.map +1 -1
- package/dist/testing/testing-module.d.ts +2 -2
- package/dist/testing/testing-module.d.ts.map +1 -1
- package/dist/validation/errors.d.ts +5 -1
- package/dist/validation/errors.d.ts.map +1 -1
- package/docs/database.md +44 -0
- package/docs/zh/database.md +44 -0
- package/package.json +3 -3
- package/src/ai/types.ts +5 -0
- package/src/auth/types.ts +4 -1
- package/src/cache/interceptors.ts +6 -6
- package/src/cache/service-proxy.ts +2 -2
- package/src/cache/service.ts +17 -8
- package/src/cache/types.ts +12 -12
- package/src/config/service.ts +8 -6
- package/src/core/application.ts +7 -1
- package/src/core/context.ts +6 -3
- package/src/database/connection-manager.ts +5 -46
- package/src/database/connection-pool.ts +26 -49
- package/src/database/database-module.ts +6 -0
- package/src/database/db-proxy.ts +3 -2
- package/src/database/driver.ts +368 -0
- package/src/database/index.ts +8 -0
- package/src/database/service.ts +3 -74
- package/src/database/sql-manager.ts +38 -24
- package/src/database/sqlite-adapter.ts +4 -3
- package/src/database/types.ts +27 -2
- package/src/di/container.ts +13 -0
- package/src/di/module-registry.ts +2 -3
- package/src/di/module.ts +21 -2
- package/src/di/types.ts +1 -2
- package/src/error/handler.ts +3 -4
- package/src/error/http-exception.ts +11 -11
- package/src/error/index.ts +1 -1
- package/src/events/service.ts +4 -2
- package/src/events/types.ts +14 -3
- package/src/interceptor/base-interceptor.ts +5 -6
- package/src/interceptor/builtin/log-interceptor.ts +2 -3
- package/src/interceptor/builtin/permission-interceptor.ts +2 -3
- package/src/interceptor/interceptor-chain.ts +6 -7
- package/src/interceptor/interceptor-registry.ts +5 -3
- package/src/interceptor/types.ts +9 -4
- package/src/microservice/service-client/types.ts +1 -1
- package/src/microservice/tracing/tracer.ts +15 -3
- package/src/middleware/builtin/file-upload.ts +3 -2
- package/src/middleware/builtin/rate-limit.ts +22 -5
- package/src/queue/service.ts +1 -1
- package/src/queue/types.ts +40 -1
- package/src/router/decorators.ts +2 -3
- package/src/security/guards/types.ts +2 -1
- package/src/security/types.ts +1 -2
- package/src/session/service.ts +1 -1
- package/src/session/types.ts +10 -0
- package/src/swagger/decorators.ts +15 -4
- package/src/swagger/generator.ts +2 -3
- package/src/swagger/types.ts +1 -2
- package/src/testing/harness.ts +1 -2
- package/src/testing/test-client.ts +2 -2
- package/src/testing/testing-module.ts +5 -5
- package/src/validation/errors.ts +6 -2
- package/tests/bun-test-shim.d.ts +11 -0
- package/tests/controller/context-decorator.test.ts +1 -2
- package/tests/database/database-module.test.ts +4 -0
- package/tests/database/driver-mysql2.test.ts +95 -0
- package/tests/database/driver.test.ts +234 -0
- package/tests/database/orm.test.ts +4 -0
- package/tests/di/module.test.ts +199 -1
- package/tests/events/event-emitter.test.ts +2 -2
- package/tests/global.d.ts +30 -0
- package/tests/queue/queue-service.test.ts +14 -0
- package/tests/testing/testing-module.test.ts +20 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
closeViaDriver,
|
|
5
|
+
getConnectionDriver,
|
|
6
|
+
healthCheckViaDriver,
|
|
7
|
+
queryViaDriver,
|
|
8
|
+
resolveDriver,
|
|
9
|
+
tagConnection,
|
|
10
|
+
templateQueryViaDriver,
|
|
11
|
+
} from '../../src/database/driver';
|
|
12
|
+
|
|
13
|
+
describe('resolveDriver', () => {
|
|
14
|
+
test("'auto' on bun resolves to bun-sql for mysql and postgres", () => {
|
|
15
|
+
expect(resolveDriver('mysql', 'auto', 'bun')).toBe('bun-sql');
|
|
16
|
+
expect(resolveDriver('postgres', 'auto', 'bun')).toBe('bun-sql');
|
|
17
|
+
expect(resolveDriver('mysql', undefined, 'bun')).toBe('bun-sql');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("'auto' on node resolves to js drivers", () => {
|
|
21
|
+
expect(resolveDriver('mysql', 'auto', 'node')).toBe('mysql2');
|
|
22
|
+
expect(resolveDriver('postgres', 'auto', 'node')).toBe('postgres');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("'mysql2' forces mysql2 regardless of engine", () => {
|
|
26
|
+
expect(resolveDriver('mysql', 'mysql2', 'bun')).toBe('mysql2');
|
|
27
|
+
expect(resolveDriver('mysql', 'mysql2', 'node')).toBe('mysql2');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("'postgres' forces postgres regardless of engine", () => {
|
|
31
|
+
expect(resolveDriver('postgres', 'postgres', 'bun')).toBe('postgres');
|
|
32
|
+
expect(resolveDriver('postgres', 'postgres', 'node')).toBe('postgres');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("'bun-sql' is valid on bun, throws on node", () => {
|
|
36
|
+
expect(resolveDriver('mysql', 'bun-sql', 'bun')).toBe('bun-sql');
|
|
37
|
+
expect(() => resolveDriver('mysql', 'bun-sql', 'node')).toThrow();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("'mysql2' on postgres type throws", () => {
|
|
41
|
+
expect(() => resolveDriver('postgres', 'mysql2', 'bun')).toThrow();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("'postgres' on mysql type throws", () => {
|
|
45
|
+
expect(() => resolveDriver('mysql', 'postgres', 'bun')).toThrow();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('connection tagging', () => {
|
|
50
|
+
test('tag and read back driver on object connection', () => {
|
|
51
|
+
const conn = { query: () => undefined };
|
|
52
|
+
tagConnection(conn, 'mysql2');
|
|
53
|
+
expect(getConnectionDriver(conn)).toBe('mysql2');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('tag preserves identity', () => {
|
|
57
|
+
const conn = {};
|
|
58
|
+
expect(tagConnection(conn, 'mysql2')).toBe(conn);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('tag is non-enumerable', () => {
|
|
62
|
+
const conn = { query: () => undefined };
|
|
63
|
+
tagConnection(conn, 'mysql2');
|
|
64
|
+
expect(Object.keys(conn)).toEqual(['query']);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('callable connection without tag falls back to bun-sql', () => {
|
|
68
|
+
const conn = (() => undefined) as unknown;
|
|
69
|
+
expect(getConnectionDriver(conn)).toBe('bun-sql');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('untagged plain object returns undefined', () => {
|
|
73
|
+
expect(getConnectionDriver({ query: () => undefined })).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('queryViaDriver', () => {
|
|
78
|
+
test('mysql2 connection uses .query and returns rows (not [rows, fields])', async () => {
|
|
79
|
+
const calls: Array<{ sql: string; params: unknown[] }> = [];
|
|
80
|
+
const conn = tagConnection(
|
|
81
|
+
{
|
|
82
|
+
query: async (sql: string, params: unknown[]) => {
|
|
83
|
+
calls.push({ sql, params });
|
|
84
|
+
return [[{ id: 1 }], [{ name: 'id' }]];
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
'mysql2',
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const rows = await queryViaDriver(conn, 'SELECT * FROM t WHERE id = ?', [1]);
|
|
91
|
+
expect(rows).toEqual([{ id: 1 }]);
|
|
92
|
+
expect(calls).toEqual([{ sql: 'SELECT * FROM t WHERE id = ?', params: [1] }]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('postgres connection uses .unsafe and returns rows', async () => {
|
|
96
|
+
const calls: Array<{ sql: string; params: unknown[] }> = [];
|
|
97
|
+
const conn = tagConnection(
|
|
98
|
+
{
|
|
99
|
+
unsafe: async (sql: string, params: unknown[]) => {
|
|
100
|
+
calls.push({ sql, params });
|
|
101
|
+
return [{ ok: true }];
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
'postgres',
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const rows = await queryViaDriver(conn, 'SELECT 1 WHERE x = ?', ['y']);
|
|
108
|
+
expect(rows).toEqual([{ ok: true }]);
|
|
109
|
+
expect(calls).toEqual([{ sql: 'SELECT 1 WHERE x = ?', params: ['y'] }]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('bun-sql connection uses template string path', async () => {
|
|
113
|
+
let received: { strings: string[]; values: unknown[] } | null = null;
|
|
114
|
+
const conn = tagConnection(
|
|
115
|
+
Object.assign(
|
|
116
|
+
async (strings: TemplateStringsArray, ...values: unknown[]) => {
|
|
117
|
+
received = { strings: Array.from(strings), values };
|
|
118
|
+
return [{ via: 'bun-sql' }];
|
|
119
|
+
},
|
|
120
|
+
),
|
|
121
|
+
'bun-sql',
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const rows = await queryViaDriver(conn, 'SELECT * FROM t WHERE id = ?', [42]);
|
|
125
|
+
expect(rows).toEqual([{ via: 'bun-sql' }]);
|
|
126
|
+
expect(received!.strings).toEqual(['SELECT * FROM t WHERE id = ', '']);
|
|
127
|
+
expect(received!.values).toEqual([42]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('templateQueryViaDriver', () => {
|
|
132
|
+
test('mysql2 transforms template into ? placeholders', async () => {
|
|
133
|
+
const calls: Array<{ sql: string; params: unknown[] }> = [];
|
|
134
|
+
const conn = tagConnection(
|
|
135
|
+
{
|
|
136
|
+
query: async (sql: string, params: unknown[]) => {
|
|
137
|
+
calls.push({ sql, params });
|
|
138
|
+
return [[{ id: 7 }], []];
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
'mysql2',
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const strings = Object.assign(['SELECT * FROM t WHERE id = ', ''], {
|
|
145
|
+
raw: ['SELECT * FROM t WHERE id = ', ''],
|
|
146
|
+
}) as unknown as TemplateStringsArray;
|
|
147
|
+
|
|
148
|
+
const rows = await templateQueryViaDriver(conn, strings, [7]);
|
|
149
|
+
expect(rows).toEqual([{ id: 7 }]);
|
|
150
|
+
expect(calls).toEqual([
|
|
151
|
+
{ sql: 'SELECT * FROM t WHERE id = ?', params: [7] },
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('callable connection invoked directly as tagged template', async () => {
|
|
156
|
+
let received: unknown[] = [];
|
|
157
|
+
const conn = tagConnection(
|
|
158
|
+
Object.assign(
|
|
159
|
+
async (_strings: TemplateStringsArray, ...values: unknown[]) => {
|
|
160
|
+
received = values;
|
|
161
|
+
return [{ ok: 1 }];
|
|
162
|
+
},
|
|
163
|
+
),
|
|
164
|
+
'bun-sql',
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const strings = Object.assign(['SELECT ', ''], {
|
|
168
|
+
raw: ['SELECT ', ''],
|
|
169
|
+
}) as unknown as TemplateStringsArray;
|
|
170
|
+
|
|
171
|
+
const rows = await templateQueryViaDriver(conn, strings, [99]);
|
|
172
|
+
expect(rows).toEqual([{ ok: 1 }]);
|
|
173
|
+
expect(received).toEqual([99]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('healthCheckViaDriver', () => {
|
|
178
|
+
test('mysql2 health check runs SELECT 1', async () => {
|
|
179
|
+
const conn = tagConnection(
|
|
180
|
+
{
|
|
181
|
+
query: async () => [[{ '1': 1 }], []],
|
|
182
|
+
},
|
|
183
|
+
'mysql2',
|
|
184
|
+
);
|
|
185
|
+
expect(await healthCheckViaDriver(conn)).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('returns false when query throws', async () => {
|
|
189
|
+
const conn = tagConnection(
|
|
190
|
+
{
|
|
191
|
+
query: async () => {
|
|
192
|
+
throw new Error('down');
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
'mysql2',
|
|
196
|
+
);
|
|
197
|
+
expect(await healthCheckViaDriver(conn)).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('closeViaDriver', () => {
|
|
202
|
+
test('mysql2 connection closed via .end()', async () => {
|
|
203
|
+
let ended = false;
|
|
204
|
+
let closed = false;
|
|
205
|
+
const conn = tagConnection(
|
|
206
|
+
{
|
|
207
|
+
end: async () => {
|
|
208
|
+
ended = true;
|
|
209
|
+
},
|
|
210
|
+
close: async () => {
|
|
211
|
+
closed = true;
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
'mysql2',
|
|
215
|
+
);
|
|
216
|
+
await closeViaDriver(conn);
|
|
217
|
+
expect(ended).toBe(true);
|
|
218
|
+
expect(closed).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('bun-sql connection closed via .close()', async () => {
|
|
222
|
+
let closed = false;
|
|
223
|
+
const conn = tagConnection(
|
|
224
|
+
{
|
|
225
|
+
close: async () => {
|
|
226
|
+
closed = true;
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
'bun-sql',
|
|
230
|
+
);
|
|
231
|
+
await closeViaDriver(conn);
|
|
232
|
+
expect(closed).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../src/database/orm';
|
|
12
12
|
import { DatabaseService, DATABASE_SERVICE_TOKEN } from '../../src/database';
|
|
13
13
|
import { Container } from '../../src/di/container';
|
|
14
|
+
import { initRuntime } from '../../src/platform/runtime';
|
|
14
15
|
|
|
15
16
|
// 测试实体
|
|
16
17
|
@Entity('test_users')
|
|
@@ -71,6 +72,9 @@ describe('BaseRepository', () => {
|
|
|
71
72
|
let databaseService: DatabaseService;
|
|
72
73
|
|
|
73
74
|
beforeEach(async () => {
|
|
75
|
+
// DatabaseService.initialize() 会读取 getRuntime(),确保运行时已初始化
|
|
76
|
+
// (避免依赖其他测试文件的执行顺序)
|
|
77
|
+
initRuntime('bun');
|
|
74
78
|
container = new Container();
|
|
75
79
|
databaseService = new DatabaseService({
|
|
76
80
|
database: {
|
package/tests/di/module.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Controller, ControllerRegistry } from '../../src/controller/controller'
|
|
|
5
5
|
import { GET } from '../../src/router/decorators';
|
|
6
6
|
import { Module } from '../../src/di/module';
|
|
7
7
|
import { ModuleRegistry } from '../../src/di/module-registry';
|
|
8
|
+
import { Container } from '../../src/di/container';
|
|
8
9
|
import { RouteRegistry } from '../../src/router/registry';
|
|
9
10
|
import { Injectable, Inject } from '../../src/di/decorators';
|
|
10
11
|
import { Context } from '../../src/core/context';
|
|
@@ -177,6 +178,204 @@ describe('ModuleRegistry', () => {
|
|
|
177
178
|
expect(() => ref.container.resolve(ALIAS)).toThrow(/Provider not found/);
|
|
178
179
|
});
|
|
179
180
|
|
|
181
|
+
test('FactoryProvider inject passes resolved dependencies to useFactory', () => {
|
|
182
|
+
const TOKEN = Symbol.for('test.factory.inject.basic');
|
|
183
|
+
|
|
184
|
+
@Injectable()
|
|
185
|
+
class Dep {
|
|
186
|
+
public greet(): string {
|
|
187
|
+
return 'hello';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Module({
|
|
192
|
+
providers: [
|
|
193
|
+
Dep,
|
|
194
|
+
{
|
|
195
|
+
provide: TOKEN,
|
|
196
|
+
useFactory: (dep: Dep) => ({
|
|
197
|
+
dep,
|
|
198
|
+
message: dep.greet(),
|
|
199
|
+
}),
|
|
200
|
+
inject: [Dep],
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
})
|
|
204
|
+
class TestModule {}
|
|
205
|
+
|
|
206
|
+
const app = new Application();
|
|
207
|
+
app.registerModule(TestModule);
|
|
208
|
+
|
|
209
|
+
const ref = ModuleRegistry.getInstance().getModuleRef(TestModule)!;
|
|
210
|
+
const dep = ref.container.resolve(Dep);
|
|
211
|
+
const result = ref.container.resolve<{ dep: Dep; message: string }>(TOKEN);
|
|
212
|
+
|
|
213
|
+
expect(result.dep).toBe(dep);
|
|
214
|
+
expect(result.message).toBe('hello');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('FactoryProvider without inject or with empty inject still passes Container to useFactory', () => {
|
|
218
|
+
const TOKEN = Symbol.for('test.factory.inject.compat');
|
|
219
|
+
const EMPTY_INJECT_TOKEN = Symbol.for('test.factory.inject.compat.empty');
|
|
220
|
+
|
|
221
|
+
@Injectable()
|
|
222
|
+
class Dep {
|
|
223
|
+
public readonly value = 'manual';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@Module({
|
|
227
|
+
providers: [
|
|
228
|
+
Dep,
|
|
229
|
+
{
|
|
230
|
+
provide: TOKEN,
|
|
231
|
+
useFactory: (container: Container) => ({
|
|
232
|
+
isContainer: container instanceof Container,
|
|
233
|
+
dep: container.resolve(Dep),
|
|
234
|
+
}),
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
provide: EMPTY_INJECT_TOKEN,
|
|
238
|
+
useFactory: (container: Container) => ({
|
|
239
|
+
isContainer: container instanceof Container,
|
|
240
|
+
dep: container.resolve(Dep),
|
|
241
|
+
}),
|
|
242
|
+
inject: [],
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
})
|
|
246
|
+
class TestModule {}
|
|
247
|
+
|
|
248
|
+
const app = new Application();
|
|
249
|
+
app.registerModule(TestModule);
|
|
250
|
+
|
|
251
|
+
const ref = ModuleRegistry.getInstance().getModuleRef(TestModule)!;
|
|
252
|
+
const dep = ref.container.resolve(Dep);
|
|
253
|
+
const result = ref.container.resolve<{ isContainer: boolean; dep: Dep }>(TOKEN);
|
|
254
|
+
const emptyInjectResult = ref.container.resolve<{ isContainer: boolean; dep: Dep }>(
|
|
255
|
+
EMPTY_INJECT_TOKEN,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
expect(result.isContainer).toBe(true);
|
|
259
|
+
expect(result.dep).toBe(dep);
|
|
260
|
+
expect(emptyInjectResult.isContainer).toBe(true);
|
|
261
|
+
expect(emptyInjectResult.dep).toBe(dep);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('FactoryProvider inject resolves tokens exported from imported modules', () => {
|
|
265
|
+
const DEP_TOKEN = Symbol.for('test.factory.inject.exported.dep');
|
|
266
|
+
const TOKEN = Symbol.for('test.factory.inject.exported.result');
|
|
267
|
+
|
|
268
|
+
interface DepContract {
|
|
269
|
+
label(): string;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const exportedDep: DepContract = {
|
|
273
|
+
label: () => 'from-export',
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
@Module({
|
|
277
|
+
providers: [{ provide: DEP_TOKEN, useValue: exportedDep }],
|
|
278
|
+
exports: [DEP_TOKEN],
|
|
279
|
+
})
|
|
280
|
+
class SharedModule {}
|
|
281
|
+
|
|
282
|
+
@Module({
|
|
283
|
+
imports: [SharedModule],
|
|
284
|
+
providers: [
|
|
285
|
+
{
|
|
286
|
+
provide: TOKEN,
|
|
287
|
+
useFactory: (dep: DepContract) => ({
|
|
288
|
+
dep,
|
|
289
|
+
label: dep.label(),
|
|
290
|
+
}),
|
|
291
|
+
inject: [DEP_TOKEN],
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
})
|
|
295
|
+
class AppModule {}
|
|
296
|
+
|
|
297
|
+
const app = new Application();
|
|
298
|
+
app.registerModule(AppModule);
|
|
299
|
+
|
|
300
|
+
const ref = ModuleRegistry.getInstance().getModuleRef(AppModule)!;
|
|
301
|
+
const result = ref.container.resolve<{ dep: DepContract; label: string }>(TOKEN);
|
|
302
|
+
|
|
303
|
+
expect(result.dep).toBe(exportedDep);
|
|
304
|
+
expect(result.label).toBe('from-export');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('FactoryProvider inject preserves token order for factory arguments', () => {
|
|
308
|
+
const FIRST = Symbol.for('test.factory.inject.order.first');
|
|
309
|
+
const SECOND = Symbol.for('test.factory.inject.order.second');
|
|
310
|
+
const TOKEN = Symbol.for('test.factory.inject.order.result');
|
|
311
|
+
|
|
312
|
+
@Module({
|
|
313
|
+
providers: [
|
|
314
|
+
{ provide: FIRST, useValue: 'first' },
|
|
315
|
+
{ provide: SECOND, useValue: 'second' },
|
|
316
|
+
{
|
|
317
|
+
provide: TOKEN,
|
|
318
|
+
useFactory: (second: string, first: string) => [second, first],
|
|
319
|
+
inject: [SECOND, FIRST],
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
})
|
|
323
|
+
class TestModule {}
|
|
324
|
+
|
|
325
|
+
const app = new Application();
|
|
326
|
+
app.registerModule(TestModule);
|
|
327
|
+
|
|
328
|
+
const ref = ModuleRegistry.getInstance().getModuleRef(TestModule)!;
|
|
329
|
+
const result = ref.container.resolve<string[]>(TOKEN);
|
|
330
|
+
|
|
331
|
+
expect(result).toEqual(['second', 'first']);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('FactoryProvider inject works when mixed with useExisting useValue and useClass providers', () => {
|
|
335
|
+
const CLASS_TOKEN = Symbol.for('test.factory.inject.mix.class');
|
|
336
|
+
const VALUE_TOKEN = Symbol.for('test.factory.inject.mix.value');
|
|
337
|
+
const ALIAS_TOKEN = Symbol.for('test.factory.inject.mix.alias');
|
|
338
|
+
const TOKEN = Symbol.for('test.factory.inject.mix.result');
|
|
339
|
+
|
|
340
|
+
class Impl {
|
|
341
|
+
public readonly kind = 'impl';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const value = { kind: 'value' };
|
|
345
|
+
|
|
346
|
+
@Module({
|
|
347
|
+
providers: [
|
|
348
|
+
{ provide: CLASS_TOKEN, useClass: Impl },
|
|
349
|
+
{ provide: VALUE_TOKEN, useValue: value },
|
|
350
|
+
{ provide: ALIAS_TOKEN, useExisting: CLASS_TOKEN },
|
|
351
|
+
{
|
|
352
|
+
provide: TOKEN,
|
|
353
|
+
useFactory: (aliased: Impl, injectedValue: typeof value) => ({
|
|
354
|
+
aliased,
|
|
355
|
+
injectedValue,
|
|
356
|
+
}),
|
|
357
|
+
inject: [ALIAS_TOKEN, VALUE_TOKEN],
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
})
|
|
361
|
+
class TestModule {}
|
|
362
|
+
|
|
363
|
+
const app = new Application();
|
|
364
|
+
app.registerModule(TestModule);
|
|
365
|
+
|
|
366
|
+
const ref = ModuleRegistry.getInstance().getModuleRef(TestModule)!;
|
|
367
|
+
const classInstance = ref.container.resolve<Impl>(CLASS_TOKEN);
|
|
368
|
+
const aliasInstance = ref.container.resolve<Impl>(ALIAS_TOKEN);
|
|
369
|
+
const valueInstance = ref.container.resolve<typeof value>(VALUE_TOKEN);
|
|
370
|
+
const result = ref.container.resolve<{ aliased: Impl; injectedValue: typeof value }>(TOKEN);
|
|
371
|
+
|
|
372
|
+
expect(classInstance).toBeInstanceOf(Impl);
|
|
373
|
+
expect(aliasInstance).toBe(classInstance);
|
|
374
|
+
expect(valueInstance).toBe(value);
|
|
375
|
+
expect(result.aliased).toBe(classInstance);
|
|
376
|
+
expect(result.injectedValue).toBe(value);
|
|
377
|
+
});
|
|
378
|
+
|
|
180
379
|
test('should throw error for circular module dependencies', () => {
|
|
181
380
|
@Module({
|
|
182
381
|
imports: [],
|
|
@@ -199,4 +398,3 @@ describe('ModuleRegistry', () => {
|
|
|
199
398
|
);
|
|
200
399
|
});
|
|
201
400
|
});
|
|
202
|
-
|
|
@@ -257,7 +257,7 @@ describe('EventEmitterService', () => {
|
|
|
257
257
|
});
|
|
258
258
|
|
|
259
259
|
test('should use custom error handler when provided', () => {
|
|
260
|
-
const errorHandler = mock(() => {});
|
|
260
|
+
const errorHandler = mock((_error: Error) => {});
|
|
261
261
|
const emitterWithErrorHandler = new EventEmitterService({
|
|
262
262
|
onError: errorHandler,
|
|
263
263
|
});
|
|
@@ -274,7 +274,7 @@ describe('EventEmitterService', () => {
|
|
|
274
274
|
|
|
275
275
|
describe('maxListeners warning', () => {
|
|
276
276
|
test('should warn when exceeding maxListeners', () => {
|
|
277
|
-
const consoleSpy = mock(() => {});
|
|
277
|
+
const consoleSpy = mock((_message: string) => {});
|
|
278
278
|
const originalWarn = console.warn;
|
|
279
279
|
console.warn = consoleSpy;
|
|
280
280
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Body {
|
|
3
|
+
json(): Promise<any>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface Response {
|
|
7
|
+
json(): Promise<any>;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module 'bun:test' {
|
|
12
|
+
export function expect(actual?: any): any;
|
|
13
|
+
export function mock(fn?: (...args: any[]) => any): any;
|
|
14
|
+
|
|
15
|
+
interface MatchersBuiltin<T = unknown> {
|
|
16
|
+
toBe(expected: any): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Matchers<T = unknown> {
|
|
20
|
+
toBe(expected: any): void;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare module '@dangao/logsmith' {
|
|
25
|
+
interface LogEntry {
|
|
26
|
+
data?: unknown;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {};
|
|
@@ -47,6 +47,16 @@ function createMockStore(): QueueStore {
|
|
|
47
47
|
return first as Job<T> | undefined;
|
|
48
48
|
},
|
|
49
49
|
|
|
50
|
+
async updateStatus(queueName: string, jobId: string, status: Job['status']): Promise<boolean> {
|
|
51
|
+
const job = queues.get(queueName)?.get(jobId);
|
|
52
|
+
if (!job) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
job.status = status;
|
|
56
|
+
job.updatedAt = Date.now();
|
|
57
|
+
return true;
|
|
58
|
+
},
|
|
59
|
+
|
|
50
60
|
async complete(queueName: string, jobId: string, result?: any): Promise<boolean> {
|
|
51
61
|
const job = queues.get(queueName)?.get(jobId);
|
|
52
62
|
if (job) {
|
|
@@ -84,6 +94,10 @@ function createMockStore(): QueueStore {
|
|
|
84
94
|
}
|
|
85
95
|
return { waiting, active, completed, failed };
|
|
86
96
|
},
|
|
97
|
+
|
|
98
|
+
async count(queueName: string): Promise<number> {
|
|
99
|
+
return queues.get(queueName)?.size ?? 0;
|
|
100
|
+
},
|
|
87
101
|
};
|
|
88
102
|
}
|
|
89
103
|
|
|
@@ -126,4 +126,24 @@ describe('TestingModule', () => {
|
|
|
126
126
|
const greeter = module.get<Greeter>(GREETER_TOKEN);
|
|
127
127
|
expect(greeter.greet('X')).toBe('Factory: X');
|
|
128
128
|
});
|
|
129
|
+
|
|
130
|
+
test('should inject dependencies into provider factories', async () => {
|
|
131
|
+
const FACTORY_TOKEN = Symbol('FactoryGreeter');
|
|
132
|
+
|
|
133
|
+
const module = await Test.createTestingModule({
|
|
134
|
+
providers: [
|
|
135
|
+
{ provide: GREETER_TOKEN, useClass: RealGreeter },
|
|
136
|
+
{
|
|
137
|
+
provide: FACTORY_TOKEN,
|
|
138
|
+
useFactory: (greeter: Greeter) => ({
|
|
139
|
+
greet: (name: string) => greeter.greet(name).toUpperCase(),
|
|
140
|
+
}),
|
|
141
|
+
inject: [GREETER_TOKEN],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
}).compile();
|
|
145
|
+
|
|
146
|
+
const greeter = module.get<Greeter>(FACTORY_TOKEN);
|
|
147
|
+
expect(greeter.greet('Test')).toBe('HELLO, TEST!');
|
|
148
|
+
});
|
|
129
149
|
});
|