@nest-yalc-2/app 1.3.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/LICENSE +661 -0
- package/README.md +49 -0
- package/package.json +33 -0
- package/src/__tests__/app-bootstrap-base.helper.spec.ts +38 -0
- package/src/__tests__/app-bootstrap-standalone.helper.spec.ts +35 -0
- package/src/__tests__/app-bootstrap.helper.spec.ts +98 -0
- package/src/__tests__/app-config.service.spec.ts +55 -0
- package/src/__tests__/app-context.service.spec.ts +28 -0
- package/src/__tests__/app.events.spec.ts +12 -0
- package/src/__tests__/app.helper.spec.ts +204 -0
- package/src/__tests__/base-app-module.helper.spec.ts +231 -0
- package/src/__tests__/base-app.controller.spec.ts +72 -0
- package/src/__tests__/base-app.service.spec.ts +39 -0
- package/src/__tests__/cls.module.spec.ts +33 -0
- package/src/__tests__/life-cycle-handler.service.spec.ts +48 -0
- package/src/__tests__/tsconfig.json +3 -0
- package/src/__tests__/unwrap-result.interceptor.spec.ts +52 -0
- package/src/app-bootstrap-base.helper.d.ts +41 -0
- package/src/app-bootstrap-base.helper.js +108 -0
- package/src/app-bootstrap-base.helper.js.map +1 -0
- package/src/app-bootstrap-base.helper.ts +199 -0
- package/src/app-bootstrap-standalone.helper.d.ts +15 -0
- package/src/app-bootstrap-standalone.helper.js +45 -0
- package/src/app-bootstrap-standalone.helper.js.map +1 -0
- package/src/app-bootstrap-standalone.helper.ts +60 -0
- package/src/app-bootstrap.helper.d.ts +43 -0
- package/src/app-bootstrap.helper.js +164 -0
- package/src/app-bootstrap.helper.js.map +1 -0
- package/src/app-bootstrap.helper.ts +263 -0
- package/src/app-config.service.d.ts +14 -0
- package/src/app-config.service.js +45 -0
- package/src/app-config.service.js.map +1 -0
- package/src/app-config.service.ts +61 -0
- package/src/app-context.module.d.ts +2 -0
- package/src/app-context.module.js +19 -0
- package/src/app-context.module.js.map +1 -0
- package/src/app-context.module.ts +6 -0
- package/src/app-context.service.d.ts +7 -0
- package/src/app-context.service.js +26 -0
- package/src/app-context.service.js.map +1 -0
- package/src/app-context.service.ts +16 -0
- package/src/app-imports.factory.d.ts +19 -0
- package/src/app-imports.factory.js +223 -0
- package/src/app-imports.factory.js.map +1 -0
- package/src/app-imports.factory.ts +320 -0
- package/src/app.events.d.ts +5 -0
- package/src/app.events.js +10 -0
- package/src/app.events.js.map +1 -0
- package/src/app.events.ts +8 -0
- package/src/app.helper.d.ts +17 -0
- package/src/app.helper.js +26 -0
- package/src/app.helper.js.map +1 -0
- package/src/app.helper.ts +73 -0
- package/src/base-app-module.helper.d.ts +24 -0
- package/src/base-app-module.helper.js +326 -0
- package/src/base-app-module.helper.js.map +1 -0
- package/src/base-app-module.helper.ts +447 -0
- package/src/base-app.controller.d.ts +9 -0
- package/src/base-app.controller.js +56 -0
- package/src/base-app.controller.js.map +1 -0
- package/src/base-app.controller.ts +43 -0
- package/src/base-app.interface.d.ts +33 -0
- package/src/base-app.interface.js +3 -0
- package/src/base-app.interface.js.map +1 -0
- package/src/base-app.interface.ts +55 -0
- package/src/base-app.service.d.ts +7 -0
- package/src/base-app.service.js +78 -0
- package/src/base-app.service.js.map +1 -0
- package/src/base-app.service.ts +29 -0
- package/src/cls.module.d.ts +15 -0
- package/src/cls.module.js +51 -0
- package/src/cls.module.js.map +1 -0
- package/src/cls.module.ts +68 -0
- package/src/conf.type.d.ts +21 -0
- package/src/conf.type.js +3 -0
- package/src/conf.type.js.map +1 -0
- package/src/conf.type.ts +22 -0
- package/src/db-conf.helper.ts-off +93 -0
- package/src/def.const.d.ts +10 -0
- package/src/def.const.js +14 -0
- package/src/def.const.js.map +1 -0
- package/src/def.const.ts +33 -0
- package/src/global.enum.d.ts +6 -0
- package/src/global.enum.js +11 -0
- package/src/global.enum.js.map +1 -0
- package/src/global.enum.ts +6 -0
- package/src/index.d.ts +0 -0
- package/src/index.js +2 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +0 -0
- package/src/life-cycle-handler.service.d.ts +13 -0
- package/src/life-cycle-handler.service.js +47 -0
- package/src/life-cycle-handler.service.js.map +1 -0
- package/src/life-cycle-handler.service.ts +58 -0
- package/src/unwrap-result.interceptor.d.ts +5 -0
- package/src/unwrap-result.interceptor.js +36 -0
- package/src/unwrap-result.interceptor.js.map +1 -0
- package/src/unwrap-result.interceptor.ts +32 -0
- package/src/yalc-controller.interface.d.ts +3 -0
- package/src/yalc-controller.interface.js +3 -0
- package/src/yalc-controller.interface.js.map +1 -0
- package/src/yalc-controller.interface.ts +6 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { Test } from '@nestjs/testing';
|
|
3
|
+
import {
|
|
4
|
+
YalcBaseAppModule,
|
|
5
|
+
registerSingletonDynamicModule,
|
|
6
|
+
envFilePathList,
|
|
7
|
+
yalcBaseAppModuleMetadataFactory,
|
|
8
|
+
YalcDefaultAppModule,
|
|
9
|
+
} from '../base-app-module.helper.js';
|
|
10
|
+
import { LifeCycleHandler } from '../life-cycle-handler.service.js';
|
|
11
|
+
import { DynamicModule, Module } from '@nestjs/common';
|
|
12
|
+
import { IYalcBaseAppOptions } from '../base-app.interface.js';
|
|
13
|
+
import { createMock } from '@golevelup/ts-jest';
|
|
14
|
+
import { AppContextService } from '../app-context.service.js';
|
|
15
|
+
|
|
16
|
+
let appContextService: AppContextService = createMock<AppContextService>({
|
|
17
|
+
initializedApps: new Set(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
class DummyDynamicModule extends YalcBaseAppModule {
|
|
21
|
+
static forRoot(options?: IYalcBaseAppOptions): DynamicModule {
|
|
22
|
+
return this.assignDynamicProperties(
|
|
23
|
+
yalcBaseAppModuleMetadataFactory(this, 'appAlias', {
|
|
24
|
+
...this.assignDynamicProperties({}),
|
|
25
|
+
configFactory: () => ({}),
|
|
26
|
+
logger: true,
|
|
27
|
+
isSingleton: true,
|
|
28
|
+
imports: [YalcDefaultAppModule.forRoot('appAlias', [])],
|
|
29
|
+
...options,
|
|
30
|
+
}),
|
|
31
|
+
options,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Module({
|
|
37
|
+
imports: [
|
|
38
|
+
DummyDynamicModule.forRoot({
|
|
39
|
+
providers: [
|
|
40
|
+
{
|
|
41
|
+
provide: 'test1',
|
|
42
|
+
useValue: 'test',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
})
|
|
48
|
+
class DummyStaticModule1 {}
|
|
49
|
+
|
|
50
|
+
@Module({
|
|
51
|
+
imports: [
|
|
52
|
+
DummyDynamicModule.forRoot({
|
|
53
|
+
providers: [
|
|
54
|
+
{
|
|
55
|
+
provide: 'test2',
|
|
56
|
+
useValue: 'test',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
})
|
|
62
|
+
class DummyStaticModule2 {}
|
|
63
|
+
|
|
64
|
+
describe('base-app', () => {
|
|
65
|
+
describe('actual Nest module instantiation', () => {
|
|
66
|
+
it('should create dynamic module', async () => {
|
|
67
|
+
const module = await Test.createTestingModule({
|
|
68
|
+
imports: [DummyStaticModule2, DummyStaticModule1],
|
|
69
|
+
})
|
|
70
|
+
.overrideProvider(AppContextService)
|
|
71
|
+
.useValue(appContextService);
|
|
72
|
+
|
|
73
|
+
const moduleRef = await module.compile();
|
|
74
|
+
|
|
75
|
+
const lifeCycleHandler = moduleRef.get(LifeCycleHandler);
|
|
76
|
+
|
|
77
|
+
expect(module).toBeDefined();
|
|
78
|
+
expect(lifeCycleHandler).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('YalcBaseAppModule', () => {
|
|
83
|
+
it('should be defined', () => {
|
|
84
|
+
expect(YalcBaseAppModule).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should create dynamic properties', () => {
|
|
88
|
+
const dynamicProperties = YalcBaseAppModule.assignDynamicProperties({});
|
|
89
|
+
expect(dynamicProperties).toEqual({
|
|
90
|
+
module: YalcBaseAppModule,
|
|
91
|
+
global: true,
|
|
92
|
+
isSingleton: false,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should create dynamic properties with options', () => {
|
|
97
|
+
const dynamicProperties = YalcBaseAppModule.assignDynamicProperties(
|
|
98
|
+
{},
|
|
99
|
+
{
|
|
100
|
+
global: false,
|
|
101
|
+
isSingleton: true,
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
expect(dynamicProperties).toEqual({
|
|
105
|
+
module: YalcBaseAppModule,
|
|
106
|
+
global: false,
|
|
107
|
+
isSingleton: true,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should create dynamic module for standalone', () => {
|
|
112
|
+
const dynamicModule = YalcBaseAppModule._forRootStandalone('appAlias');
|
|
113
|
+
expect(dynamicModule).toBeDefined();
|
|
114
|
+
expect(dynamicModule.global).toBe(true);
|
|
115
|
+
expect(dynamicModule.isSingleton).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should create dynamic module', () => {
|
|
119
|
+
const dynamicModule = YalcBaseAppModule._forRoot('appAlias');
|
|
120
|
+
expect(dynamicModule).toBeDefined();
|
|
121
|
+
expect(dynamicModule.global).toBe(true);
|
|
122
|
+
expect(dynamicModule.isSingleton).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('registerSingletonDynamicModule', () => {
|
|
127
|
+
class TestModule extends DummyDynamicModule {
|
|
128
|
+
static forRoot(options?: IYalcBaseAppOptions | undefined): DynamicModule {
|
|
129
|
+
return this._forRoot('test', options);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
it('should register singleton dynamic module', () => {
|
|
134
|
+
const config = TestModule.forRoot();
|
|
135
|
+
const result = registerSingletonDynamicModule(true, TestModule, config);
|
|
136
|
+
expect(result).toBe(config);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should not register the same singleton dynamic module twice', () => {
|
|
140
|
+
const result = registerSingletonDynamicModule(
|
|
141
|
+
true,
|
|
142
|
+
TestModule,
|
|
143
|
+
TestModule.forRoot(),
|
|
144
|
+
);
|
|
145
|
+
expect(result).not.toBe(true);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('envFilePathList', () => {
|
|
150
|
+
it('should create env file path list', () => {
|
|
151
|
+
const paths = envFilePathList('.');
|
|
152
|
+
expect(paths).toContain('./.env');
|
|
153
|
+
if (process.env.NODE_ENV) {
|
|
154
|
+
expect(paths).toContain(`./.env.${process.env.NODE_ENV}`);
|
|
155
|
+
}
|
|
156
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
157
|
+
expect(paths).toContain('./.env.dist');
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should create env file path list for production', () => {
|
|
162
|
+
process.env.NODE_ENV = 'production';
|
|
163
|
+
const paths = envFilePathList('.');
|
|
164
|
+
expect(paths).toContain('./.env');
|
|
165
|
+
expect(paths).toContain(`./.env.production`);
|
|
166
|
+
expect(paths).not.toContain('./.env.dist');
|
|
167
|
+
process.env.NODE_ENV = 'test';
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('baseAppModuleMetadataFactory', () => {
|
|
172
|
+
it('should create base app module metadata', () => {
|
|
173
|
+
const metadata = yalcBaseAppModuleMetadataFactory({}, 'appAlias', {
|
|
174
|
+
logger: true,
|
|
175
|
+
global: true,
|
|
176
|
+
});
|
|
177
|
+
expect(metadata).toBeDefined();
|
|
178
|
+
expect(metadata.controllers).toEqual([]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should create base app module metadata with options', () => {
|
|
182
|
+
const metadata = yalcBaseAppModuleMetadataFactory({}, 'appAlias', {
|
|
183
|
+
envPath: ['./.env.test'],
|
|
184
|
+
extraConfigs: [],
|
|
185
|
+
providers: [],
|
|
186
|
+
imports: [],
|
|
187
|
+
exports: [],
|
|
188
|
+
controllers: [],
|
|
189
|
+
});
|
|
190
|
+
expect(metadata).toBeDefined();
|
|
191
|
+
expect(metadata.controllers).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should create base app module metadata with options and controllers, with envPath as a string', () => {
|
|
195
|
+
class TestController {}
|
|
196
|
+
|
|
197
|
+
const metadata = yalcBaseAppModuleMetadataFactory(
|
|
198
|
+
DummyStaticModule1,
|
|
199
|
+
'appAlias',
|
|
200
|
+
{
|
|
201
|
+
envPath: './.env.test',
|
|
202
|
+
extraConfigs: [],
|
|
203
|
+
providers: [],
|
|
204
|
+
imports: [],
|
|
205
|
+
exports: [],
|
|
206
|
+
controllers: [TestController],
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
expect(metadata).toBeDefined();
|
|
210
|
+
expect(metadata.controllers).toEqual([TestController]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should create base app module metadata without envPath', () => {
|
|
214
|
+
class TestController {}
|
|
215
|
+
|
|
216
|
+
const metadata = yalcBaseAppModuleMetadataFactory(
|
|
217
|
+
DummyStaticModule1,
|
|
218
|
+
'appAlias',
|
|
219
|
+
{
|
|
220
|
+
extraConfigs: [],
|
|
221
|
+
providers: [],
|
|
222
|
+
imports: [],
|
|
223
|
+
exports: [],
|
|
224
|
+
controllers: [TestController],
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
expect(metadata).toBeDefined();
|
|
228
|
+
expect(metadata.controllers).toEqual([TestController]);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it, jest, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
3
|
+
import { BaseAppController } from '../base-app.controller.js';
|
|
4
|
+
import { BaseAppService } from '../base-app.service.js';
|
|
5
|
+
|
|
6
|
+
class TestAppController extends BaseAppController {}
|
|
7
|
+
|
|
8
|
+
describe('BaseAppController', () => {
|
|
9
|
+
const getHello = jest.fn();
|
|
10
|
+
const appService = {
|
|
11
|
+
getHello,
|
|
12
|
+
} as unknown as BaseAppService;
|
|
13
|
+
|
|
14
|
+
const configGet = jest.fn();
|
|
15
|
+
const configService = {
|
|
16
|
+
get: configGet,
|
|
17
|
+
} as unknown as ConfigService;
|
|
18
|
+
|
|
19
|
+
let controller: TestAppController;
|
|
20
|
+
let exitSpy: jest.SpyInstance;
|
|
21
|
+
let logSpy: jest.SpyInstance;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
controller = new TestAppController(appService, configService);
|
|
26
|
+
exitSpy = jest
|
|
27
|
+
.spyOn(process, 'exit')
|
|
28
|
+
.mockImplementation((() => undefined) as any);
|
|
29
|
+
logSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
exitSpy.mockRestore();
|
|
34
|
+
logSpy.mockRestore();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should call BaseAppService with configured app name', () => {
|
|
38
|
+
configGet.mockReturnValue({ appName: 'sample-app' });
|
|
39
|
+
getHello.mockReturnValue('Hello World from sample-app!');
|
|
40
|
+
|
|
41
|
+
const result = controller.getHello();
|
|
42
|
+
|
|
43
|
+
expect(result).toBe('Hello World from sample-app!');
|
|
44
|
+
expect(getHello).toHaveBeenCalledWith('sample-app');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should fallback to default app name when missing', () => {
|
|
48
|
+
configGet.mockReturnValue(undefined);
|
|
49
|
+
getHello.mockReturnValue('Hello World from no-name!');
|
|
50
|
+
|
|
51
|
+
controller.getHello();
|
|
52
|
+
|
|
53
|
+
expect(getHello).toHaveBeenCalledWith('no-name');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should terminate when shutdown is called in dev/test env', () => {
|
|
57
|
+
configGet.mockReturnValue({ isDev: true });
|
|
58
|
+
|
|
59
|
+
controller.shutdown();
|
|
60
|
+
|
|
61
|
+
expect(logSpy).toHaveBeenCalledWith('Bye bye!');
|
|
62
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should ignore shutdown when not in dev/test env', () => {
|
|
66
|
+
configGet.mockReturnValue({ isDev: false, isTest: false });
|
|
67
|
+
|
|
68
|
+
controller.shutdown();
|
|
69
|
+
|
|
70
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
2
|
+
import { ExecutionContext, LoggerService } from '@nestjs/common';
|
|
3
|
+
import { BaseAppService } from '../base-app.service.js';
|
|
4
|
+
|
|
5
|
+
describe('BaseAppService', () => {
|
|
6
|
+
const logger: LoggerService = {
|
|
7
|
+
debug: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
const service = new BaseAppService(logger);
|
|
10
|
+
|
|
11
|
+
it('should format hello message with app name', () => {
|
|
12
|
+
expect(service.getHello('test-app')).toBe('Hello World from test-app!');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should log handler names containing underscore and not starting with "_"', () => {
|
|
16
|
+
const ctx = {
|
|
17
|
+
getHandler: () => ({ name: 'test_handler' }),
|
|
18
|
+
} as unknown as ExecutionContext;
|
|
19
|
+
|
|
20
|
+
service.handleBeforeAllRoutes(ctx);
|
|
21
|
+
|
|
22
|
+
expect(logger.debug).toHaveBeenCalledWith('Running Handler: test_handler');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should ignore handlers without underscore or starting with "_"', () => {
|
|
26
|
+
(logger.debug as jest.Mock).mockClear();
|
|
27
|
+
const ctxWithoutUnderscore = {
|
|
28
|
+
getHandler: () => ({ name: 'handler' }),
|
|
29
|
+
} as unknown as ExecutionContext;
|
|
30
|
+
const ctxInternal = {
|
|
31
|
+
getHandler: () => ({ name: '_internal_handler' }),
|
|
32
|
+
} as unknown as ExecutionContext;
|
|
33
|
+
|
|
34
|
+
service.handleBeforeAllRoutes(ctxWithoutUnderscore);
|
|
35
|
+
service.handleBeforeAllRoutes(ctxInternal);
|
|
36
|
+
|
|
37
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
import {
|
|
4
|
+
YalcAlsService,
|
|
5
|
+
YalcClsModule,
|
|
6
|
+
YalcGlobalClsService,
|
|
7
|
+
} from '../cls.module.js';
|
|
8
|
+
|
|
9
|
+
describe('YalcClsModule', () => {
|
|
10
|
+
it('should expose YalcGlobalClsService in module exports', () => {
|
|
11
|
+
const exportedProviders =
|
|
12
|
+
(Reflect.getMetadata('exports', YalcClsModule) as any[]) ?? [];
|
|
13
|
+
expect(exportedProviders).toContain(YalcGlobalClsService);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should configure a CLS module import', async () => {
|
|
17
|
+
const imports =
|
|
18
|
+
(Reflect.getMetadata('imports', YalcClsModule) as any[]) ?? [];
|
|
19
|
+
expect(imports.length).toBeGreaterThan(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('YalcAlsService should persist store within the async context', () => {
|
|
23
|
+
const als = new YalcAlsService();
|
|
24
|
+
const store = { placeholder: 'value' };
|
|
25
|
+
let seen;
|
|
26
|
+
|
|
27
|
+
als.run(store, () => {
|
|
28
|
+
seen = als.getStore();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(seen).toEqual(store);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
2
|
+
import { LifeCycleHandler } from '../life-cycle-handler.service.js';
|
|
3
|
+
import { AppContextService } from '../app-context.service.js';
|
|
4
|
+
import { createMock } from '@golevelup/ts-jest';
|
|
5
|
+
|
|
6
|
+
describe('LifeCycleHandler', () => {
|
|
7
|
+
let loggerService: any = { debug: jest.fn() };
|
|
8
|
+
let lifeCycleHandler: LifeCycleHandler;
|
|
9
|
+
let appContextService: AppContextService = createMock<AppContextService>({
|
|
10
|
+
initializedApps: new Set(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should initialize app', () => {
|
|
14
|
+
lifeCycleHandler = new LifeCycleHandler(
|
|
15
|
+
loggerService,
|
|
16
|
+
'TestApp',
|
|
17
|
+
appContextService,
|
|
18
|
+
);
|
|
19
|
+
expect(loggerService.debug).toHaveBeenCalledWith(
|
|
20
|
+
'====================== Init TestApp ======================',
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should not initialize the same app twice', () => {
|
|
25
|
+
expect(
|
|
26
|
+
() => new LifeCycleHandler(loggerService, 'TestApp', appContextService),
|
|
27
|
+
).toThrow('Cannot initialize the same app (TestApp) twice');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should initialize the same app twice if skipDuplicateAppCheck is true', () => {
|
|
31
|
+
new LifeCycleHandler(loggerService, 'TestApp', appContextService, {
|
|
32
|
+
skipDuplicateAppCheck: true,
|
|
33
|
+
});
|
|
34
|
+
expect(
|
|
35
|
+
() =>
|
|
36
|
+
new LifeCycleHandler(loggerService, 'TestApp', appContextService, {
|
|
37
|
+
skipDuplicateAppCheck: true,
|
|
38
|
+
}),
|
|
39
|
+
).not.toThrow();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle module destroy', () => {
|
|
43
|
+
lifeCycleHandler.onModuleDestroy();
|
|
44
|
+
expect(loggerService.debug).toHaveBeenCalledWith(
|
|
45
|
+
'====================== Close TestApp ======================',
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { CallHandler, ExecutionContext } from '@nestjs/common';
|
|
3
|
+
import { firstValueFrom, of } from 'rxjs';
|
|
4
|
+
import { UnwrapResultInterceptor } from '../unwrap-result.interceptor.js';
|
|
5
|
+
|
|
6
|
+
const ctx = {
|
|
7
|
+
switchToHttp: () => ({
|
|
8
|
+
getRequest: () => ({}),
|
|
9
|
+
}),
|
|
10
|
+
} as unknown as ExecutionContext;
|
|
11
|
+
|
|
12
|
+
describe('UnwrapResultInterceptor', () => {
|
|
13
|
+
const interceptor = new UnwrapResultInterceptor();
|
|
14
|
+
|
|
15
|
+
it('should unwrap successful Result-like objects', async () => {
|
|
16
|
+
const next: CallHandler = {
|
|
17
|
+
handle: () =>
|
|
18
|
+
of({
|
|
19
|
+
isOk: () => true,
|
|
20
|
+
isErr: () => false,
|
|
21
|
+
value: 'payload',
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const result = await firstValueFrom(interceptor.intercept(ctx, next));
|
|
26
|
+
expect(result).toBe('payload');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should throw when Result-like object contains error', async () => {
|
|
30
|
+
const error = new Error('boom');
|
|
31
|
+
const next: CallHandler = {
|
|
32
|
+
handle: () =>
|
|
33
|
+
of({
|
|
34
|
+
isOk: () => false,
|
|
35
|
+
isErr: () => true,
|
|
36
|
+
error,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
await expect(firstValueFrom(interceptor.intercept(ctx, next))).rejects.toThrow('boom');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should pass through non Result objects', async () => {
|
|
44
|
+
const payload = { ok: true };
|
|
45
|
+
const next: CallHandler = {
|
|
46
|
+
handle: () => of(payload),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const result = await firstValueFrom(interceptor.intercept(ctx, next));
|
|
50
|
+
expect(result).toBe(payload);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DynamicModule, INestApplicationContext, LoggerService, Type } from '@nestjs/common';
|
|
2
|
+
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
|
3
|
+
import type { IServiceConf } from './conf.type.js';
|
|
4
|
+
import { ICreateOptions, INestCreateOptions } from './app-bootstrap.helper.js';
|
|
5
|
+
import { EventModule } from '@nest-yalc-2/event-manager/event.module.js';
|
|
6
|
+
import { LoggerServiceFactory } from '@nest-yalc-2/logger/logger.service.js';
|
|
7
|
+
import { FastifyInstance } from 'fastify';
|
|
8
|
+
export interface IGlobalOptions {
|
|
9
|
+
extraImports?: NonNullable<DynamicModule['imports']>;
|
|
10
|
+
eventModuleClass?: typeof EventModule;
|
|
11
|
+
logger?: typeof LoggerServiceFactory;
|
|
12
|
+
skipMultiServerCheck?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const getBootstrappedApps: () => Set<any>;
|
|
15
|
+
export declare const getMainBootstrappedApp: <TApp extends BaseAppBootstrap<NestFastifyApplication | INestApplicationContext>>() => TApp | null;
|
|
16
|
+
export declare abstract class BaseAppBootstrap<TAppType extends NestFastifyApplication | INestApplicationContext> {
|
|
17
|
+
protected appAlias: string;
|
|
18
|
+
protected readonly appModule: Type<any>;
|
|
19
|
+
protected app?: TAppType;
|
|
20
|
+
protected loggerService: LoggerService;
|
|
21
|
+
protected module: Type<any> | DynamicModule;
|
|
22
|
+
protected isClosed: boolean;
|
|
23
|
+
constructor(appAlias: string, appModule: Type<any>, options?: {
|
|
24
|
+
globalsOptions?: IGlobalOptions;
|
|
25
|
+
});
|
|
26
|
+
initApp(options?: {
|
|
27
|
+
createOptions?: INestCreateOptions;
|
|
28
|
+
fastifyInstance?: FastifyInstance;
|
|
29
|
+
}): Promise<this>;
|
|
30
|
+
setApp(app: TAppType): this;
|
|
31
|
+
closeCleanup(): void;
|
|
32
|
+
isAppClosed(): boolean;
|
|
33
|
+
getAppAlias(): string;
|
|
34
|
+
getConf(): IServiceConf | undefined;
|
|
35
|
+
getApp(): TAppType;
|
|
36
|
+
closeApp(): Promise<void>;
|
|
37
|
+
cleanup(): Promise<void>;
|
|
38
|
+
getAppModule(): Type<any>;
|
|
39
|
+
getModule(): Type<any> | DynamicModule;
|
|
40
|
+
applyBootstrapGlobals(_options?: ICreateOptions): Promise<this>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseAppBootstrap = exports.getMainBootstrappedApp = exports.getBootstrappedApps = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const config_1 = require("@nestjs/config");
|
|
6
|
+
const def_const_js_1 = require("./def.const.js");
|
|
7
|
+
const base_app_module_helper_js_1 = require("./base-app-module.helper.js");
|
|
8
|
+
const logger_helper_js_1 = require("@nest-yalc-2/logger/logger.helper.js");
|
|
9
|
+
const promise_helper_js_1 = require("@nest-yalc-2/utils/promise.helper.js");
|
|
10
|
+
common_1.Logger.overrideLogger((0, logger_helper_js_1.getEnvLoggerLevels)());
|
|
11
|
+
const bootstrappedApps = new Set();
|
|
12
|
+
const getBootstrappedApps = () => {
|
|
13
|
+
return bootstrappedApps;
|
|
14
|
+
};
|
|
15
|
+
exports.getBootstrappedApps = getBootstrappedApps;
|
|
16
|
+
const getMainBootstrappedApp = () => {
|
|
17
|
+
if ((0, exports.getBootstrappedApps)().size === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return (0, exports.getBootstrappedApps)().values().next().value;
|
|
21
|
+
};
|
|
22
|
+
exports.getMainBootstrappedApp = getMainBootstrappedApp;
|
|
23
|
+
class BaseAppBootstrap {
|
|
24
|
+
constructor(appAlias, appModule, options) {
|
|
25
|
+
this.appAlias = appAlias;
|
|
26
|
+
this.appModule = appModule;
|
|
27
|
+
this.isClosed = false;
|
|
28
|
+
this.module = base_app_module_helper_js_1.YalcDefaultAppModule.forRoot(this.appAlias, [appModule, ...(options?.globalsOptions?.extraImports ?? [])], options?.globalsOptions);
|
|
29
|
+
const bootstrappedApp = (0, exports.getMainBootstrappedApp)();
|
|
30
|
+
if (bootstrappedApp &&
|
|
31
|
+
!options?.globalsOptions?.skipMultiServerCheck &&
|
|
32
|
+
process.env.APP_SKIP_MULTISERVER_CHECK !== 'true') {
|
|
33
|
+
throw new Error(`You are trying to bootstrap multiple servers (${bootstrappedApp.appAlias}) in the same process. This is not allowed. Use a different process for each server`);
|
|
34
|
+
}
|
|
35
|
+
(0, exports.getBootstrappedApps)().add(this);
|
|
36
|
+
}
|
|
37
|
+
async initApp(options) {
|
|
38
|
+
options;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
setApp(app) {
|
|
42
|
+
this.app = app;
|
|
43
|
+
const originalCloseFn = this.app.close.bind(this.app);
|
|
44
|
+
this.app.close = async () => {
|
|
45
|
+
const closeRes = await originalCloseFn();
|
|
46
|
+
this.closeCleanup();
|
|
47
|
+
return closeRes;
|
|
48
|
+
};
|
|
49
|
+
const originalInitFn = this.app.init.bind(this.app);
|
|
50
|
+
this.app.init = async () => {
|
|
51
|
+
this.isClosed = false;
|
|
52
|
+
(0, exports.getBootstrappedApps)().add(this);
|
|
53
|
+
let initRes;
|
|
54
|
+
try {
|
|
55
|
+
initRes = await originalInitFn();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.closeCleanup();
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
return initRes;
|
|
62
|
+
};
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
closeCleanup() {
|
|
66
|
+
this.isClosed = true;
|
|
67
|
+
(0, exports.getBootstrappedApps)().delete(this);
|
|
68
|
+
}
|
|
69
|
+
isAppClosed() {
|
|
70
|
+
return this.isClosed;
|
|
71
|
+
}
|
|
72
|
+
getAppAlias() {
|
|
73
|
+
return this.appAlias;
|
|
74
|
+
}
|
|
75
|
+
getConf() {
|
|
76
|
+
const configService = this.getApp().get(config_1.ConfigService);
|
|
77
|
+
return configService.get(this.appAlias);
|
|
78
|
+
}
|
|
79
|
+
getApp() {
|
|
80
|
+
if (!this.app) {
|
|
81
|
+
throw new Error('This app is not initialized yet');
|
|
82
|
+
}
|
|
83
|
+
return this.app;
|
|
84
|
+
}
|
|
85
|
+
async closeApp() {
|
|
86
|
+
await this.cleanup();
|
|
87
|
+
await this.app?.close();
|
|
88
|
+
this.closeCleanup();
|
|
89
|
+
}
|
|
90
|
+
async cleanup() {
|
|
91
|
+
await promise_helper_js_1.globalPromiseTracker.waitForAll();
|
|
92
|
+
}
|
|
93
|
+
getAppModule() {
|
|
94
|
+
return this.appModule;
|
|
95
|
+
}
|
|
96
|
+
getModule() {
|
|
97
|
+
return this.module;
|
|
98
|
+
}
|
|
99
|
+
async applyBootstrapGlobals(_options) {
|
|
100
|
+
this.loggerService = this.getApp().get(def_const_js_1.SYSTEM_LOGGER_SERVICE);
|
|
101
|
+
this.loggerService.debug?.('Setting logger service...');
|
|
102
|
+
this.getApp().useLogger(this.loggerService);
|
|
103
|
+
common_1.Logger.overrideLogger(this.loggerService);
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.BaseAppBootstrap = BaseAppBootstrap;
|
|
108
|
+
//# sourceMappingURL=app-bootstrap-base.helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-bootstrap-base.helper.js","sourceRoot":"","sources":["app-bootstrap-base.helper.ts"],"names":[],"mappings":"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"}
|