@onebun/core 0.1.1 → 0.1.3
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 +233 -0
- package/package.json +1 -1
- package/src/{application.test.ts → application/application.test.ts} +125 -5
- package/src/{application.ts → application/application.ts} +239 -13
- package/src/application/index.ts +9 -0
- package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
- package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
- package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
- package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
- package/src/{decorators.ts → decorators/decorators.ts} +3 -2
- package/src/decorators/index.ts +15 -0
- package/src/docs-examples.test.ts +753 -0
- package/src/index.ts +50 -41
- package/src/module/index.ts +12 -0
- package/src/{module.test.ts → module/module.test.ts} +3 -2
- package/src/{module.ts → module/module.ts} +15 -8
- package/src/queue/adapters/index.ts +8 -0
- package/src/queue/adapters/memory.adapter.test.ts +405 -0
- package/src/queue/adapters/memory.adapter.ts +509 -0
- package/src/queue/adapters/redis.adapter.ts +673 -0
- package/src/queue/cron-expression.test.ts +145 -0
- package/src/queue/cron-expression.ts +115 -0
- package/src/queue/cron-parser.test.ts +185 -0
- package/src/queue/cron-parser.ts +287 -0
- package/src/queue/decorators.test.ts +292 -0
- package/src/queue/decorators.ts +493 -0
- package/src/queue/docs-examples.test.ts +449 -0
- package/src/queue/guards.test.ts +309 -0
- package/src/queue/guards.ts +307 -0
- package/src/queue/index.ts +118 -0
- package/src/queue/pattern-matcher.test.ts +191 -0
- package/src/queue/pattern-matcher.ts +252 -0
- package/src/queue/queue.service.ts +421 -0
- package/src/queue/scheduler.test.ts +235 -0
- package/src/queue/scheduler.ts +379 -0
- package/src/queue/types.ts +502 -0
- package/src/redis/index.ts +8 -0
- package/src/redis/redis-client.ts +502 -0
- package/src/redis/shared-redis.ts +231 -0
- package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
- package/src/service-client/index.ts +10 -0
- package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
- package/src/{service-client.ts → service-client/service-client.ts} +1 -1
- package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
- package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
- package/src/testing/index.ts +7 -0
- package/src/types.ts +84 -5
- package/src/websocket/index.ts +50 -0
- package/src/websocket/ws-base-gateway.test.ts +479 -0
- package/src/websocket/ws-base-gateway.ts +514 -0
- package/src/websocket/ws-client.test.ts +511 -0
- package/src/websocket/ws-client.ts +628 -0
- package/src/websocket/ws-client.types.ts +129 -0
- package/src/websocket/ws-decorators.test.ts +331 -0
- package/src/websocket/ws-decorators.ts +418 -0
- package/src/websocket/ws-guards.test.ts +334 -0
- package/src/websocket/ws-guards.ts +298 -0
- package/src/websocket/ws-handler.ts +658 -0
- package/src/websocket/ws-integration.test.ts +518 -0
- package/src/websocket/ws-pattern-matcher.test.ts +152 -0
- package/src/websocket/ws-pattern-matcher.ts +240 -0
- package/src/websocket/ws-service-definition.ts +224 -0
- package/src/websocket/ws-socketio-protocol.test.ts +344 -0
- package/src/websocket/ws-socketio-protocol.ts +567 -0
- package/src/websocket/ws-storage-memory.test.ts +246 -0
- package/src/websocket/ws-storage-memory.ts +222 -0
- package/src/websocket/ws-storage-redis.ts +302 -0
- package/src/websocket/ws-storage.ts +210 -0
- package/src/websocket/ws.types.ts +342 -0
- /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
- /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
- /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
- /package/src/{config.service.ts → module/config.service.ts} +0 -0
- /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
- /package/src/{controller.ts → module/controller.ts} +0 -0
- /package/src/{service.test.ts → module/service.test.ts} +0 -0
- /package/src/{service.ts → module/service.ts} +0 -0
- /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
- /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
- /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
- /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
package/README.md
CHANGED
|
@@ -291,6 +291,170 @@ return this.error('User not found', 404);
|
|
|
291
291
|
|
|
292
292
|
Errors thrown in controller methods are automatically caught and converted to standardized error responses.
|
|
293
293
|
|
|
294
|
+
## WebSocket Gateway
|
|
295
|
+
|
|
296
|
+
OneBun provides WebSocket support with a Gateway pattern similar to NestJS, with full Socket.IO protocol compatibility.
|
|
297
|
+
|
|
298
|
+
### Basic Gateway
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
import {
|
|
302
|
+
WebSocketGateway,
|
|
303
|
+
BaseWebSocketGateway,
|
|
304
|
+
OnConnect,
|
|
305
|
+
OnDisconnect,
|
|
306
|
+
OnMessage,
|
|
307
|
+
Client,
|
|
308
|
+
MessageData,
|
|
309
|
+
} from '@onebun/core';
|
|
310
|
+
import type { WsClientData } from '@onebun/core';
|
|
311
|
+
|
|
312
|
+
@WebSocketGateway({ path: '/ws' })
|
|
313
|
+
export class ChatGateway extends BaseWebSocketGateway {
|
|
314
|
+
@OnConnect()
|
|
315
|
+
handleConnect(@Client() client: WsClientData) {
|
|
316
|
+
console.log(`Client ${client.id} connected`);
|
|
317
|
+
return { event: 'welcome', data: { clientId: client.id } };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
@OnDisconnect()
|
|
321
|
+
handleDisconnect(@Client() client: WsClientData) {
|
|
322
|
+
console.log(`Client ${client.id} disconnected`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@OnMessage('chat:message')
|
|
326
|
+
handleMessage(
|
|
327
|
+
@Client() client: WsClientData,
|
|
328
|
+
@MessageData() data: { text: string }
|
|
329
|
+
) {
|
|
330
|
+
// Broadcast to all clients
|
|
331
|
+
this.broadcast('chat:message', {
|
|
332
|
+
userId: client.id,
|
|
333
|
+
text: data.text,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Pattern Matching
|
|
340
|
+
|
|
341
|
+
Event patterns support wildcards and named parameters:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// Wildcard: matches chat:general, chat:private, etc.
|
|
345
|
+
@OnMessage('chat:*')
|
|
346
|
+
handleAnyChat(@MessageData() data: unknown) {}
|
|
347
|
+
|
|
348
|
+
// Named parameter: extracts roomId
|
|
349
|
+
@OnMessage('chat:{roomId}:message')
|
|
350
|
+
handleRoomMessage(@PatternParams() params: { roomId: string }) {}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Room Management
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
@WebSocketGateway({ path: '/ws' })
|
|
357
|
+
export class RoomGateway extends BaseWebSocketGateway {
|
|
358
|
+
@OnJoinRoom('room:{roomId}')
|
|
359
|
+
async handleJoin(
|
|
360
|
+
@Client() client: WsClientData,
|
|
361
|
+
@RoomName() room: string,
|
|
362
|
+
@PatternParams() params: { roomId: string }
|
|
363
|
+
) {
|
|
364
|
+
await this.joinRoom(client.id, room);
|
|
365
|
+
this.emitToRoom(room, 'user:joined', { userId: client.id });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@OnLeaveRoom('room:*')
|
|
369
|
+
async handleLeave(@Client() client: WsClientData, @RoomName() room: string) {
|
|
370
|
+
await this.leaveRoom(client.id, room);
|
|
371
|
+
this.emitToRoom(room, 'user:left', { userId: client.id });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Guards
|
|
377
|
+
|
|
378
|
+
Protect WebSocket handlers with guards:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { UseWsGuards, WsAuthGuard, WsPermissionGuard } from '@onebun/core';
|
|
382
|
+
|
|
383
|
+
@UseWsGuards(WsAuthGuard)
|
|
384
|
+
@OnMessage('protected:*')
|
|
385
|
+
handleProtected(@Client() client: WsClientData) {
|
|
386
|
+
// Only authenticated clients can access
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@UseWsGuards(new WsPermissionGuard('admin'))
|
|
390
|
+
@OnMessage('admin:*')
|
|
391
|
+
handleAdmin(@Client() client: WsClientData) {
|
|
392
|
+
// Only clients with 'admin' permission
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Typed Client
|
|
397
|
+
|
|
398
|
+
Generate a type-safe WebSocket client:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { createWsServiceDefinition, createWsClient } from '@onebun/core';
|
|
402
|
+
import { AppModule } from './app.module';
|
|
403
|
+
|
|
404
|
+
const definition = createWsServiceDefinition(AppModule);
|
|
405
|
+
const client = createWsClient(definition, {
|
|
406
|
+
url: 'ws://localhost:3000/ws',
|
|
407
|
+
auth: { token: 'your-token' },
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
await client.connect();
|
|
411
|
+
|
|
412
|
+
// Type-safe event emission
|
|
413
|
+
await client.ChatGateway.emit('chat:message', { text: 'Hello!' });
|
|
414
|
+
|
|
415
|
+
// Subscribe to events
|
|
416
|
+
client.ChatGateway.on('chat:message', (data) => {
|
|
417
|
+
console.log('Received:', data);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
client.disconnect();
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Storage Options
|
|
424
|
+
|
|
425
|
+
Configure client/room storage:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
const app = new OneBunApplication(AppModule, {
|
|
429
|
+
websocket: {
|
|
430
|
+
storage: {
|
|
431
|
+
type: 'memory', // or 'redis' for multi-instance
|
|
432
|
+
redis: {
|
|
433
|
+
url: 'redis://localhost:6379',
|
|
434
|
+
prefix: 'ws:',
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Socket.IO Compatibility
|
|
442
|
+
|
|
443
|
+
OneBun WebSocket Gateway is fully compatible with `socket.io-client`:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import { io } from 'socket.io-client';
|
|
447
|
+
|
|
448
|
+
const socket = io('ws://localhost:3000/ws', {
|
|
449
|
+
auth: { token: 'your-token' },
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
socket.on('chat:message', (data) => console.log(data));
|
|
453
|
+
socket.emit('chat:message', { text: 'Hello!' });
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
For complete API documentation, see [docs/api/websocket.md](../../docs/api/websocket.md).
|
|
457
|
+
|
|
294
458
|
## Application
|
|
295
459
|
|
|
296
460
|
Create and start an OneBun application:
|
|
@@ -313,6 +477,75 @@ app.start()
|
|
|
313
477
|
|
|
314
478
|
The application automatically creates a logger based on NODE_ENV (development or production) and handles all Effect.js calls internally.
|
|
315
479
|
|
|
480
|
+
## Graceful Shutdown
|
|
481
|
+
|
|
482
|
+
OneBun supports graceful shutdown to cleanly close connections and release resources when the application stops. **Graceful shutdown is enabled by default.**
|
|
483
|
+
|
|
484
|
+
### Default Behavior
|
|
485
|
+
|
|
486
|
+
By default, the application automatically handles SIGTERM and SIGINT signals:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
const app = new OneBunApplication(AppModule, {
|
|
490
|
+
port: 3000,
|
|
491
|
+
// gracefulShutdown: true is the default
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
await app.start();
|
|
495
|
+
// Application will automatically handle shutdown signals
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Disabling Graceful Shutdown
|
|
499
|
+
|
|
500
|
+
If you need to manage shutdown manually, you can disable automatic handling:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
const app = new OneBunApplication(AppModule, {
|
|
504
|
+
gracefulShutdown: false, // Disable automatic SIGTERM/SIGINT handling
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
await app.start();
|
|
508
|
+
|
|
509
|
+
// Now you must handle shutdown manually:
|
|
510
|
+
// Option 1: Enable signal handlers later
|
|
511
|
+
app.enableGracefulShutdown();
|
|
512
|
+
|
|
513
|
+
// Option 2: Stop programmatically
|
|
514
|
+
await app.stop();
|
|
515
|
+
|
|
516
|
+
// Option 3: Stop but keep shared Redis connection open (for other consumers)
|
|
517
|
+
await app.stop({ closeSharedRedis: false });
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### What Gets Cleaned Up
|
|
521
|
+
|
|
522
|
+
When the application stops, the following resources are cleaned up:
|
|
523
|
+
|
|
524
|
+
1. **HTTP Server** - Bun server is stopped
|
|
525
|
+
2. **WebSocket Handler** - All WebSocket connections are closed
|
|
526
|
+
3. **Shared Redis** - If using SharedRedisProvider, the connection is closed (unless `closeSharedRedis: false`)
|
|
527
|
+
|
|
528
|
+
### Shared Redis Connection
|
|
529
|
+
|
|
530
|
+
When using `SharedRedisProvider` for cache, WebSocket storage, or other features, the connection is automatically closed on shutdown:
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
import { SharedRedisProvider, OneBunApplication } from '@onebun/core';
|
|
534
|
+
|
|
535
|
+
// Configure shared Redis at startup
|
|
536
|
+
SharedRedisProvider.configure({
|
|
537
|
+
url: 'redis://localhost:6379',
|
|
538
|
+
keyPrefix: 'myapp:',
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const app = new OneBunApplication(AppModule, {
|
|
542
|
+
gracefulShutdown: true,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
await app.start();
|
|
546
|
+
// Shared Redis will be closed when app receives SIGTERM/SIGINT
|
|
547
|
+
```
|
|
548
|
+
|
|
316
549
|
## License
|
|
317
550
|
|
|
318
551
|
[LGPL-3.0](../../LICENSE)
|
package/package.json
CHANGED
|
@@ -9,10 +9,8 @@ import {
|
|
|
9
9
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
10
10
|
import { register } from 'prom-client';
|
|
11
11
|
|
|
12
|
-
import type { ApplicationOptions } from '
|
|
12
|
+
import type { ApplicationOptions } from '../types';
|
|
13
13
|
|
|
14
|
-
import { OneBunApplication } from './application';
|
|
15
|
-
import { Controller as BaseController } from './controller';
|
|
16
14
|
import {
|
|
17
15
|
Module,
|
|
18
16
|
Controller,
|
|
@@ -21,8 +19,11 @@ import {
|
|
|
21
19
|
Param,
|
|
22
20
|
Query,
|
|
23
21
|
Body,
|
|
24
|
-
} from '
|
|
25
|
-
import {
|
|
22
|
+
} from '../decorators/decorators';
|
|
23
|
+
import { Controller as BaseController } from '../module/controller';
|
|
24
|
+
import { makeMockLoggerLayer } from '../testing/test-utils';
|
|
25
|
+
|
|
26
|
+
import { OneBunApplication } from './application';
|
|
26
27
|
|
|
27
28
|
// Helper function to create app with mock logger to suppress logs in tests
|
|
28
29
|
function createTestApp(
|
|
@@ -1102,4 +1103,123 @@ describe('OneBunApplication', () => {
|
|
|
1102
1103
|
expect(mockTraceService.extractFromHeaders).not.toHaveBeenCalled();
|
|
1103
1104
|
});
|
|
1104
1105
|
});
|
|
1106
|
+
|
|
1107
|
+
describe('Graceful shutdown', () => {
|
|
1108
|
+
let originalServe: typeof Bun.serve;
|
|
1109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1110
|
+
let mockServer: any;
|
|
1111
|
+
|
|
1112
|
+
beforeEach(() => {
|
|
1113
|
+
register.clear();
|
|
1114
|
+
|
|
1115
|
+
mockServer = {
|
|
1116
|
+
stop: mock(),
|
|
1117
|
+
hostname: 'localhost',
|
|
1118
|
+
port: 3000,
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
originalServe = Bun.serve;
|
|
1122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1123
|
+
(Bun as any).serve = mock(() => mockServer);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
afterEach(() => {
|
|
1127
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1128
|
+
(Bun as any).serve = originalServe;
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
test('should stop server and cleanup resources on stop()', async () => {
|
|
1132
|
+
@Module({})
|
|
1133
|
+
class TestModule {}
|
|
1134
|
+
|
|
1135
|
+
const app = createTestApp(TestModule);
|
|
1136
|
+
await app.start();
|
|
1137
|
+
|
|
1138
|
+
await app.stop();
|
|
1139
|
+
|
|
1140
|
+
expect(mockServer.stop).toHaveBeenCalled();
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
test('should accept closeSharedRedis option in stop()', async () => {
|
|
1144
|
+
@Module({})
|
|
1145
|
+
class TestModule {}
|
|
1146
|
+
|
|
1147
|
+
const app = createTestApp(TestModule);
|
|
1148
|
+
await app.start();
|
|
1149
|
+
|
|
1150
|
+
// Stop without closing Redis
|
|
1151
|
+
await app.stop({ closeSharedRedis: false });
|
|
1152
|
+
|
|
1153
|
+
expect(mockServer.stop).toHaveBeenCalled();
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
test('should enable graceful shutdown by default', async () => {
|
|
1157
|
+
@Module({})
|
|
1158
|
+
class TestModule {}
|
|
1159
|
+
|
|
1160
|
+
// Track process.on calls
|
|
1161
|
+
const processOnMock = mock();
|
|
1162
|
+
const originalProcessOn = process.on.bind(process);
|
|
1163
|
+
process.on = processOnMock as typeof process.on;
|
|
1164
|
+
|
|
1165
|
+
// No gracefulShutdown option - should be enabled by default
|
|
1166
|
+
const app = createTestApp(TestModule);
|
|
1167
|
+
await app.start();
|
|
1168
|
+
|
|
1169
|
+
// Verify handlers were registered
|
|
1170
|
+
expect(processOnMock).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
|
|
1171
|
+
expect(processOnMock).toHaveBeenCalledWith('SIGINT', expect.any(Function));
|
|
1172
|
+
|
|
1173
|
+
// Cleanup
|
|
1174
|
+
process.on = originalProcessOn;
|
|
1175
|
+
await app.stop();
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
test('should disable graceful shutdown when option is false', async () => {
|
|
1179
|
+
@Module({})
|
|
1180
|
+
class TestModule {}
|
|
1181
|
+
|
|
1182
|
+
// Track process.on calls
|
|
1183
|
+
const processOnMock = mock();
|
|
1184
|
+
const originalProcessOn = process.on.bind(process);
|
|
1185
|
+
process.on = processOnMock as typeof process.on;
|
|
1186
|
+
|
|
1187
|
+
// Explicitly disable graceful shutdown
|
|
1188
|
+
const app = createTestApp(TestModule, { gracefulShutdown: false });
|
|
1189
|
+
await app.start();
|
|
1190
|
+
|
|
1191
|
+
// Verify handlers were NOT registered
|
|
1192
|
+
expect(processOnMock).not.toHaveBeenCalledWith('SIGTERM', expect.any(Function));
|
|
1193
|
+
expect(processOnMock).not.toHaveBeenCalledWith('SIGINT', expect.any(Function));
|
|
1194
|
+
|
|
1195
|
+
// Cleanup
|
|
1196
|
+
process.on = originalProcessOn;
|
|
1197
|
+
await app.stop();
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
test('should provide enableGracefulShutdown method for manual setup', async () => {
|
|
1201
|
+
@Module({})
|
|
1202
|
+
class TestModule {}
|
|
1203
|
+
|
|
1204
|
+
// Disable automatic graceful shutdown
|
|
1205
|
+
const app = createTestApp(TestModule, { gracefulShutdown: false });
|
|
1206
|
+
await app.start();
|
|
1207
|
+
|
|
1208
|
+
// Track process.on calls
|
|
1209
|
+
const processOnMock = mock();
|
|
1210
|
+
const originalProcessOn = process.on.bind(process);
|
|
1211
|
+
process.on = processOnMock as typeof process.on;
|
|
1212
|
+
|
|
1213
|
+
// Manually enable graceful shutdown
|
|
1214
|
+
app.enableGracefulShutdown();
|
|
1215
|
+
|
|
1216
|
+
// Verify handlers were registered
|
|
1217
|
+
expect(processOnMock).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
|
|
1218
|
+
expect(processOnMock).toHaveBeenCalledWith('SIGINT', expect.any(Function));
|
|
1219
|
+
|
|
1220
|
+
// Cleanup
|
|
1221
|
+
process.on = originalProcessOn;
|
|
1222
|
+
await app.stop();
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1105
1225
|
});
|