@dangao/bun-server 2.1.0 → 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.
@@ -16,6 +16,10 @@ import {
16
16
  callOnModuleDestroy,
17
17
  callOnApplicationBootstrap,
18
18
  callOnApplicationShutdown,
19
+ callComponentBeforeCreate,
20
+ callOnAfterCreate,
21
+ callOnBeforeDestroy,
22
+ callOnAfterDestroy,
19
23
  } from '../../src/di/lifecycle';
20
24
 
21
25
  describe('Lifecycle Hooks', () => {
@@ -68,6 +72,54 @@ describe('Lifecycle Hooks', () => {
68
72
  ]);
69
73
  });
70
74
 
75
+ test('callComponentBeforeCreate and callOnAfterCreate should invoke component creation hooks', () => {
76
+ const calls: string[] = [];
77
+ class TestComponent {
78
+ public static onBeforeCreate(): void {
79
+ calls.push('beforeCreate');
80
+ }
81
+ public onAfterCreate(): void {
82
+ calls.push('afterCreate');
83
+ }
84
+ }
85
+
86
+ callComponentBeforeCreate(TestComponent);
87
+ callOnAfterCreate(new TestComponent());
88
+ expect(calls).toEqual(['beforeCreate', 'afterCreate']);
89
+ });
90
+
91
+ test('callOnBeforeDestroy and callOnAfterDestroy should invoke in reverse order', async () => {
92
+ const calls: string[] = [];
93
+ const instances = [
94
+ {
95
+ onBeforeDestroy: () => {
96
+ calls.push('before:first');
97
+ },
98
+ onAfterDestroy: () => {
99
+ calls.push('after:first');
100
+ },
101
+ },
102
+ {
103
+ onBeforeDestroy: () => {
104
+ calls.push('before:second');
105
+ },
106
+ onAfterDestroy: () => {
107
+ calls.push('after:second');
108
+ },
109
+ },
110
+ ];
111
+
112
+ await callOnBeforeDestroy(instances);
113
+ await callOnAfterDestroy(instances);
114
+
115
+ expect(calls).toEqual([
116
+ 'before:second',
117
+ 'before:first',
118
+ 'after:second',
119
+ 'after:first',
120
+ ]);
121
+ });
122
+
71
123
  test('should skip non-hook instances', async () => {
72
124
  const calls: string[] = [];
73
125
  const instances = [
@@ -90,12 +142,24 @@ describe('Lifecycle Hooks', () => {
90
142
 
91
143
  @Injectable()
92
144
  class LifecycleService implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap, OnApplicationShutdown {
145
+ public static onBeforeCreate(): void {
146
+ calls.push('service:onBeforeCreate');
147
+ }
148
+ public onAfterCreate(): void {
149
+ calls.push('service:onAfterCreate');
150
+ }
151
+ public onBeforeDestroy(): void {
152
+ calls.push('service:onBeforeDestroy');
153
+ }
93
154
  public onModuleInit(): void {
94
155
  calls.push('onModuleInit');
95
156
  }
96
157
  public onModuleDestroy(): void {
97
158
  calls.push('onModuleDestroy');
98
159
  }
160
+ public onAfterDestroy(): void {
161
+ calls.push('service:onAfterDestroy');
162
+ }
99
163
  public onApplicationBootstrap(): void {
100
164
  calls.push('onApplicationBootstrap');
101
165
  }
@@ -105,7 +169,31 @@ describe('Lifecycle Hooks', () => {
105
169
  }
106
170
 
107
171
  @Controller('/test')
108
- class TestController {
172
+ class TestController implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap, OnApplicationShutdown {
173
+ public static onBeforeCreate(): void {
174
+ calls.push('controller:onBeforeCreate');
175
+ }
176
+ public onAfterCreate(): void {
177
+ calls.push('controller:onAfterCreate');
178
+ }
179
+ public onBeforeDestroy(): void {
180
+ calls.push('controller:onBeforeDestroy');
181
+ }
182
+ public onModuleInit(): void {
183
+ calls.push('controller:onModuleInit');
184
+ }
185
+ public onModuleDestroy(): void {
186
+ calls.push('controller:onModuleDestroy');
187
+ }
188
+ public onAfterDestroy(): void {
189
+ calls.push('controller:onAfterDestroy');
190
+ }
191
+ public onApplicationBootstrap(): void {
192
+ calls.push('controller:onApplicationBootstrap');
193
+ }
194
+ public onApplicationShutdown(signal?: string): void {
195
+ calls.push(`controller:onApplicationShutdown:${signal ?? 'none'}`);
196
+ }
109
197
  @GET('/')
110
198
  public get(): string {
111
199
  return 'ok';
@@ -122,8 +210,14 @@ describe('Lifecycle Hooks', () => {
122
210
  app.registerModule(TestModule);
123
211
  await app.listen(0);
124
212
 
213
+ expect(calls).toContain('service:onBeforeCreate');
214
+ expect(calls).toContain('service:onAfterCreate');
215
+ expect(calls).toContain('controller:onBeforeCreate');
216
+ expect(calls).toContain('controller:onAfterCreate');
125
217
  expect(calls).toContain('onModuleInit');
126
218
  expect(calls).toContain('onApplicationBootstrap');
219
+ expect(calls).toContain('controller:onModuleInit');
220
+ expect(calls).toContain('controller:onApplicationBootstrap');
127
221
 
128
222
  const initIdx = calls.indexOf('onModuleInit');
129
223
  const bootIdx = calls.indexOf('onApplicationBootstrap');
@@ -131,10 +225,17 @@ describe('Lifecycle Hooks', () => {
131
225
 
132
226
  await app.stop();
133
227
 
228
+ expect(calls).toContain('service:onBeforeDestroy');
134
229
  expect(calls).toContain('onModuleDestroy');
230
+ expect(calls).toContain('service:onAfterDestroy');
231
+ expect(calls).toContain('controller:onBeforeDestroy');
232
+ expect(calls).toContain('controller:onModuleDestroy');
233
+ expect(calls).toContain('controller:onAfterDestroy');
135
234
  // onApplicationShutdown with no signal when using stop() directly
136
235
  const shutdownEntry = calls.find((c) => c.startsWith('onApplicationShutdown'));
137
236
  expect(shutdownEntry).toBeDefined();
237
+ const controllerShutdownEntry = calls.find((c) => c.startsWith('controller:onApplicationShutdown'));
238
+ expect(controllerShutdownEntry).toBeDefined();
138
239
  });
139
240
 
140
241
  test('should call onModuleInit once for duplicated provider instance', async () => {
@@ -219,5 +219,66 @@ describe('Lifecycle.Scoped', () => {
219
219
  // 工厂函数应该被调用 2 次(每个请求一次)
220
220
  expect(factoryCallCount).toBe(2);
221
221
  });
222
+
223
+ test('should call scoped destroy hooks on context dispose', async () => {
224
+ const calls: string[] = [];
225
+
226
+ @Injectable({ lifecycle: Lifecycle.Scoped })
227
+ class ScopedService {
228
+ public onBeforeDestroy(): void {
229
+ calls.push('before');
230
+ }
231
+
232
+ public onModuleDestroy(): void {
233
+ calls.push('destroy');
234
+ }
235
+
236
+ public onAfterDestroy(): void {
237
+ calls.push('after');
238
+ }
239
+ }
240
+
241
+ container.register(ScopedService);
242
+
243
+ const request = new Request('http://localhost:3000/api/test');
244
+ const context = new Context(request);
245
+
246
+ await contextStore.run(context, async () => {
247
+ const instance = container.resolve(ScopedService);
248
+ expect(instance).toBeInstanceOf(ScopedService);
249
+ });
250
+
251
+ await container.disposeScopedInstances(context);
252
+
253
+ expect(calls).toEqual(['before', 'destroy', 'after']);
254
+ });
255
+
256
+ test('should call scoped destroy hooks once per request context', async () => {
257
+ const calls: string[] = [];
258
+
259
+ @Injectable({ lifecycle: Lifecycle.Scoped })
260
+ class ScopedService {
261
+ public onModuleDestroy(): void {
262
+ calls.push('destroy');
263
+ }
264
+ }
265
+
266
+ container.register(ScopedService);
267
+
268
+ const context1 = new Context(new Request('http://localhost:3000/api/test-1'));
269
+ const context2 = new Context(new Request('http://localhost:3000/api/test-2'));
270
+
271
+ await contextStore.run(context1, async () => {
272
+ container.resolve(ScopedService);
273
+ });
274
+ await container.disposeScopedInstances(context1);
275
+
276
+ await contextStore.run(context2, async () => {
277
+ container.resolve(ScopedService);
278
+ });
279
+ await container.disposeScopedInstances(context2);
280
+
281
+ expect(calls).toEqual(['destroy', 'destroy']);
282
+ });
222
283
  });
223
284