@node-c/core 1.0.0-alpha9 → 1.0.0-beta1

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.
Files changed (160) hide show
  1. package/coverage/src/app.ts.html +349 -0
  2. package/coverage/src/common/configProvider/configProvider.module.ts.html +160 -0
  3. package/coverage/src/common/configProvider/configProvider.service.ts.html +658 -0
  4. package/coverage/src/common/configProvider/index.html +146 -0
  5. package/coverage/src/common/configProvider/index.ts.html +94 -0
  6. package/coverage/src/common/definitions/common.constants.ts.html +160 -0
  7. package/coverage/src/common/definitions/common.errors.ts.html +124 -0
  8. package/coverage/src/common/definitions/index.html +146 -0
  9. package/coverage/src/common/definitions/index.ts.html +94 -0
  10. package/coverage/src/common/utils/index.html +131 -0
  11. package/coverage/src/common/utils/index.ts.html +88 -0
  12. package/coverage/src/common/utils/utils.loadDynamicModules.ts.html +265 -0
  13. package/coverage/src/domain/entityService/domain.entity.service.ts.html +784 -0
  14. package/coverage/src/domain/entityService/index.html +131 -0
  15. package/coverage/src/domain/entityService/index.ts.html +91 -0
  16. package/coverage/src/index.html +116 -0
  17. package/coverage/src/persistance/entityService/index.html +131 -0
  18. package/coverage/src/persistance/entityService/index.ts.html +91 -0
  19. package/coverage/src/persistance/entityService/persistance.entity.service.ts.html +268 -0
  20. package/dist/app.d.ts +10 -9
  21. package/dist/app.js +30 -15
  22. package/dist/app.js.map +1 -1
  23. package/dist/common/configProvider/configProvider.definitions.d.ts +158 -21
  24. package/dist/common/configProvider/configProvider.definitions.js +22 -8
  25. package/dist/common/configProvider/configProvider.definitions.js.map +1 -1
  26. package/dist/common/configProvider/configProvider.module.js +13 -2
  27. package/dist/common/configProvider/configProvider.module.js.map +1 -1
  28. package/dist/common/configProvider/configProvider.service.js +23 -20
  29. package/dist/common/configProvider/configProvider.service.js.map +1 -1
  30. package/dist/common/definitions/common.constants.d.ts +2 -1
  31. package/dist/common/definitions/common.constants.js +1 -0
  32. package/dist/common/definitions/common.constants.js.map +1 -1
  33. package/dist/common/logger/index.d.ts +3 -0
  34. package/dist/common/logger/index.js +20 -0
  35. package/dist/common/logger/index.js.map +1 -0
  36. package/dist/common/logger/logger.definitions.d.ts +4 -0
  37. package/dist/common/logger/logger.definitions.js +3 -0
  38. package/dist/common/logger/logger.definitions.js.map +1 -0
  39. package/dist/common/logger/logger.module.d.ts +5 -0
  40. package/dist/common/logger/logger.module.js +34 -0
  41. package/dist/common/logger/logger.module.js.map +1 -0
  42. package/dist/common/logger/logger.service.d.ts +6 -0
  43. package/dist/common/logger/logger.service.js +56 -0
  44. package/dist/common/logger/logger.service.js.map +1 -0
  45. package/dist/common/utils/base64UrlEncode/base64UrlEncode.method.d.ts +1 -0
  46. package/dist/common/utils/base64UrlEncode/base64UrlEncode.method.js +9 -0
  47. package/dist/common/utils/base64UrlEncode/base64UrlEncode.method.js.map +1 -0
  48. package/dist/common/utils/base64UrlEncode/index.d.ts +1 -0
  49. package/dist/{persistance/entityService → common/utils/base64UrlEncode}/index.js +1 -2
  50. package/dist/common/utils/base64UrlEncode/index.js.map +1 -0
  51. package/dist/common/utils/getNested/getNested.definitions.d.ts +4 -0
  52. package/dist/common/utils/getNested/getNested.definitions.js +3 -0
  53. package/dist/common/utils/getNested/getNested.definitions.js.map +1 -0
  54. package/dist/common/utils/getNested/getNested.method.d.ts +6 -0
  55. package/dist/common/utils/getNested/getNested.method.js +88 -0
  56. package/dist/common/utils/getNested/getNested.method.js.map +1 -0
  57. package/dist/common/utils/getNested/index.d.ts +2 -0
  58. package/dist/common/utils/getNested/index.js +19 -0
  59. package/dist/common/utils/getNested/index.js.map +1 -0
  60. package/dist/common/utils/httpRequest/httpRequest.definitions.d.ts +19 -0
  61. package/dist/common/utils/httpRequest/httpRequest.definitions.js +3 -0
  62. package/dist/common/utils/httpRequest/httpRequest.definitions.js.map +1 -0
  63. package/dist/common/utils/httpRequest/httpRequest.method.d.ts +2 -0
  64. package/dist/common/utils/httpRequest/httpRequest.method.js +56 -0
  65. package/dist/common/utils/httpRequest/httpRequest.method.js.map +1 -0
  66. package/dist/common/utils/httpRequest/index.d.ts +2 -0
  67. package/dist/common/utils/httpRequest/index.js +19 -0
  68. package/dist/common/utils/httpRequest/index.js.map +1 -0
  69. package/dist/common/utils/index.d.ts +5 -1
  70. package/dist/common/utils/index.js +5 -1
  71. package/dist/common/utils/index.js.map +1 -1
  72. package/dist/common/utils/loadDynamicModules/index.d.ts +1 -0
  73. package/dist/common/utils/loadDynamicModules/index.js +18 -0
  74. package/dist/common/utils/loadDynamicModules/index.js.map +1 -0
  75. package/dist/common/utils/{utils.loadDynamicModules.d.ts → loadDynamicModules/utils.loadDynamicModules.d.ts} +1 -1
  76. package/dist/common/utils/loadDynamicModules/utils.loadDynamicModules.js.map +1 -0
  77. package/dist/common/utils/setNested/index.d.ts +2 -0
  78. package/dist/common/utils/setNested/index.js +19 -0
  79. package/dist/common/utils/setNested/index.js.map +1 -0
  80. package/dist/common/utils/setNested/setNested.definitions.d.ts +4 -0
  81. package/dist/common/utils/setNested/setNested.definitions.js +3 -0
  82. package/dist/common/utils/setNested/setNested.definitions.js.map +1 -0
  83. package/dist/common/utils/setNested/setNested.method.d.ts +2 -0
  84. package/dist/common/utils/setNested/setNested.method.js +70 -0
  85. package/dist/common/utils/setNested/setNested.method.js.map +1 -0
  86. package/dist/data/entityService/data.entity.service.d.ts +19 -0
  87. package/dist/data/entityService/data.entity.service.definitions.d.ts +117 -0
  88. package/dist/data/entityService/data.entity.service.definitions.js +28 -0
  89. package/dist/data/entityService/data.entity.service.definitions.js.map +1 -0
  90. package/dist/{persistance/entityService/persistance.entity.service.js → data/entityService/data.entity.service.js} +42 -9
  91. package/dist/data/entityService/data.entity.service.js.map +1 -0
  92. package/dist/data/entityService/index.d.ts +2 -0
  93. package/dist/data/entityService/index.js +19 -0
  94. package/dist/data/entityService/index.js.map +1 -0
  95. package/dist/domain/entityService/domain.entity.service.d.ts +30 -13
  96. package/dist/domain/entityService/domain.entity.service.definitions.d.ts +26 -19
  97. package/dist/domain/entityService/domain.entity.service.definitions.js +6 -6
  98. package/dist/domain/entityService/domain.entity.service.definitions.js.map +1 -1
  99. package/dist/domain/entityService/domain.entity.service.js +78 -69
  100. package/dist/domain/entityService/domain.entity.service.js.map +1 -1
  101. package/dist/index.d.ts +2 -1
  102. package/dist/index.js +2 -1
  103. package/dist/index.js.map +1 -1
  104. package/dist/scripts/generateDatasourceFiles.d.ts +1 -0
  105. package/dist/scripts/generateDatasourceFiles.js +94 -0
  106. package/dist/scripts/generateDatasourceFiles.js.map +1 -0
  107. package/package.json +17 -9
  108. package/src/app.spec.ts +138 -0
  109. package/src/app.ts +120 -0
  110. package/src/common/configProvider/configProvider.definitions.ts +444 -0
  111. package/src/common/configProvider/configProvider.module.spec.ts +90 -0
  112. package/src/common/configProvider/configProvider.module.ts +25 -0
  113. package/src/common/configProvider/configProvider.service.spec.ts +206 -0
  114. package/src/common/configProvider/configProvider.service.ts +204 -0
  115. package/src/common/configProvider/index.ts +3 -0
  116. package/src/common/definitions/common.constants.ts +27 -0
  117. package/src/common/definitions/common.definitions.ts +13 -0
  118. package/src/common/definitions/common.errors.ts +13 -0
  119. package/src/common/definitions/index.ts +3 -0
  120. package/src/common/logger/index.ts +3 -0
  121. package/src/common/logger/logger.definitions.ts +5 -0
  122. package/src/common/logger/logger.module.ts +21 -0
  123. package/src/common/logger/logger.service.ts +40 -0
  124. package/src/common/utils/base64UrlEncode/base64UrlEncode.method.ts +4 -0
  125. package/src/common/utils/base64UrlEncode/index.ts +1 -0
  126. package/src/common/utils/getNested/getNested.definitions.ts +4 -0
  127. package/src/common/utils/getNested/getNested.method.ts +108 -0
  128. package/src/common/utils/getNested/getNested.spec.ts +151 -0
  129. package/src/common/utils/getNested/index.ts +2 -0
  130. package/src/common/utils/httpRequest/httpRequest.definitions.ts +22 -0
  131. package/src/common/utils/httpRequest/httpRequest.method.ts +46 -0
  132. package/src/common/utils/httpRequest/index.ts +2 -0
  133. package/src/common/utils/index.ts +5 -0
  134. package/src/common/utils/loadDynamicModules/index.ts +1 -0
  135. package/src/common/utils/loadDynamicModules/utils.loadDynamicModules.spec.ts +111 -0
  136. package/src/common/utils/loadDynamicModules/utils.loadDynamicModules.ts +69 -0
  137. package/src/common/utils/setNested/index.ts +2 -0
  138. package/src/common/utils/setNested/setNested.definitions.ts +4 -0
  139. package/src/common/utils/setNested/setNested.method.ts +83 -0
  140. package/src/common/utils/setNested/setNested.spec.ts +184 -0
  141. package/src/data/entityService/data.entity.service.definitions.ts +154 -0
  142. package/src/data/entityService/data.entity.service.spec.ts +112 -0
  143. package/src/data/entityService/data.entity.service.ts +147 -0
  144. package/src/data/entityService/index.ts +2 -0
  145. package/src/domain/entityService/domain.entity.service.definitions.ts +142 -0
  146. package/src/domain/entityService/domain.entity.service.spec.ts +126 -0
  147. package/src/domain/entityService/domain.entity.service.ts +424 -0
  148. package/src/domain/entityService/index.ts +2 -0
  149. package/src/index.ts +7 -0
  150. package/src/scripts/generateDatasourceFiles.ts +59 -0
  151. package/src/vitest.config.ts +9 -0
  152. package/dist/common/utils/utils.loadDynamicModules.js.map +0 -1
  153. package/dist/persistance/entityService/index.d.ts +0 -2
  154. package/dist/persistance/entityService/index.js.map +0 -1
  155. package/dist/persistance/entityService/persistance.entity.service.d.ts +0 -11
  156. package/dist/persistance/entityService/persistance.entity.service.definitions.d.ts +0 -76
  157. package/dist/persistance/entityService/persistance.entity.service.definitions.js +0 -23
  158. package/dist/persistance/entityService/persistance.entity.service.definitions.js.map +0 -1
  159. package/dist/persistance/entityService/persistance.entity.service.js.map +0 -1
  160. /package/dist/common/utils/{utils.loadDynamicModules.js → loadDynamicModules/utils.loadDynamicModules.js} +0 -0
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import { AppConfig, AppEnvironment, ConfigProviderModuleOptions } from './configProvider.definitions';
4
+ import { ConfigProviderModule } from './configProvider.module';
5
+ import { ConfigProviderService } from './configProvider.service';
6
+
7
+ import { Constants } from '../definitions';
8
+
9
+ // Helper type predicate: checks if a provider is a factory provider for a given token.
10
+ function isFactoryProvider<T, U>(
11
+ provider: unknown,
12
+ token: T
13
+ ): provider is { provide: T; useFactory: () => Promise<U> } {
14
+ if (typeof provider !== 'object' || provider === null) {
15
+ return false;
16
+ }
17
+ const p = provider as { provide?: T; useFactory?: unknown };
18
+ return p.provide === token && typeof p.useFactory === 'function';
19
+ }
20
+
21
+ describe('ConfigProviderModule', () => {
22
+ // Create a fake AppConfig with full type information.
23
+ const fakeAppConfig: AppConfig = {
24
+ api: {},
25
+ domain: {},
26
+ general: {
27
+ environment: AppEnvironment.Local,
28
+ projectName: 'TestProject',
29
+ projectRootPath: '/fake/path',
30
+ projectVersion: '1.0.0'
31
+ },
32
+ data: {}
33
+ };
34
+ const fakeEnvKeys = {
35
+ API: {
36
+ HTTP: { HOST: 'hostname', PORT: 'port' }
37
+ }
38
+ };
39
+ const fakeEnvKeysParentNames = {
40
+ API: {
41
+ children: { TEST: 'test' },
42
+ name: 'api'
43
+ }
44
+ };
45
+ const fakeModuleOptions: ConfigProviderModuleOptions = {
46
+ appConfigs: { appConfigCommon: fakeAppConfig },
47
+ envKeys: fakeEnvKeys,
48
+ envKeysParentNames: fakeEnvKeysParentNames
49
+ };
50
+
51
+ describe('register()', () => {
52
+ it('should return a dynamic module with proper configuration', async () => {
53
+ // Spy on loadConfig so that the factory returns our fake config.
54
+ const loadConfigSpy = vi.spyOn(ConfigProviderService, 'loadConfig').mockResolvedValue(fakeAppConfig);
55
+ const dynamicModule = ConfigProviderModule.register(fakeModuleOptions);
56
+ // Verify dynamic module properties.
57
+ expect(dynamicModule.global).toBe(true);
58
+ expect(dynamicModule.module).toBe(ConfigProviderModule);
59
+ expect(dynamicModule.providers).toEqual(
60
+ expect.arrayContaining([expect.objectContaining({ provide: Constants.CONFIG }), ConfigProviderService])
61
+ );
62
+ expect(dynamicModule.exports).toEqual([Constants.CONFIG, ConfigProviderService]);
63
+ // Locate the provider with the injection token.
64
+ const configProvider = dynamicModule.providers?.find(p =>
65
+ isFactoryProvider<Constants, AppConfig>(p, Constants.CONFIG)
66
+ ) as unknown as { useFactory: () => Promise<ConfigProviderService> };
67
+ expect(configProvider).toBeDefined();
68
+ // Call the useFactory and verify it returns the fake config.
69
+ const producedConfig = await configProvider.useFactory();
70
+ expect(producedConfig).toEqual(fakeAppConfig);
71
+ expect(loadConfigSpy).toHaveBeenCalledWith(
72
+ { appConfigCommon: fakeAppConfig },
73
+ {
74
+ envKeys: fakeEnvKeys,
75
+ envKeysParentNames: fakeEnvKeysParentNames
76
+ }
77
+ );
78
+ });
79
+ it('should propagate errors from the loadConfig factory', async () => {
80
+ const error = new Error('Load config failed');
81
+ vi.spyOn(ConfigProviderService, 'loadConfig').mockRejectedValue(error);
82
+ const dynamicModule = ConfigProviderModule.register(fakeModuleOptions);
83
+ const configProvider = dynamicModule.providers?.find(p =>
84
+ isFactoryProvider<Constants, AppConfig>(p, Constants.CONFIG)
85
+ ) as unknown as { useFactory: () => Promise<ConfigProviderService> };
86
+ expect(configProvider).toBeDefined();
87
+ await expect(configProvider.useFactory()).rejects.toThrow('Load config failed');
88
+ });
89
+ });
90
+ });
@@ -0,0 +1,25 @@
1
+ import { DynamicModule, Module } from '@nestjs/common';
2
+
3
+ import { ConfigProviderModuleOptions } from './configProvider.definitions';
4
+ import { ConfigProviderService } from './configProvider.service';
5
+
6
+ import { Constants } from '../definitions';
7
+
8
+ @Module({})
9
+ export class ConfigProviderModule {
10
+ static register(options: ConfigProviderModuleOptions): DynamicModule {
11
+ const { appConfigs, ...otherOptions } = options;
12
+ return {
13
+ global: true,
14
+ module: ConfigProviderModule,
15
+ providers: [
16
+ {
17
+ provide: Constants.CONFIG,
18
+ useFactory: async () => await ConfigProviderService.loadConfig(appConfigs, { ...otherOptions })
19
+ },
20
+ ConfigProviderService
21
+ ],
22
+ exports: [Constants.CONFIG, ConfigProviderService]
23
+ };
24
+ }
25
+ }
@@ -0,0 +1,206 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+
4
+ import im from 'immutable';
5
+ import { mergeDeepRight } from 'ramda';
6
+ import { beforeEach, describe, expect, it } from 'vitest';
7
+
8
+ import {
9
+ AppConfig,
10
+ AppConfigCommon,
11
+ AppEnvironment,
12
+ GenerateOrmconfigOptions,
13
+ LoadConfigAppConfigs
14
+ } from './configProvider.definitions';
15
+ import { ConfigProviderService } from './configProvider.service';
16
+
17
+ const BASE_PATH = path.join(__dirname, '../../../test');
18
+
19
+ describe('ConfigProviderService', () => {
20
+ describe('constructor', () => {
21
+ it('should assign the passed config to the service', () => {
22
+ const fakeConfig: AppConfig = {
23
+ api: {},
24
+ domain: {},
25
+ general: {
26
+ environment: AppEnvironment.Local,
27
+ projectName: 'TestProject',
28
+ projectRootPath: '/some/path',
29
+ projectVersion: '1.0.0'
30
+ },
31
+ data: {}
32
+ };
33
+ const service = new ConfigProviderService(fakeConfig);
34
+ expect(service.config).toEqual(fakeConfig);
35
+ });
36
+ });
37
+
38
+ // generateOrmconfig
39
+ describe('generateOrmconfig', () => {
40
+ const defaultFakeModuleName = 'db';
41
+ const defaultFakeOptions: GenerateOrmconfigOptions = {
42
+ entitiesPathInModule: 'entities',
43
+ migrationsPathInModule: 'migrations',
44
+ moduleName: defaultFakeModuleName,
45
+ modulePathInProject: 'src/data/db'
46
+ };
47
+ const defaultFakeConfig: AppConfig = {
48
+ api: {},
49
+ domain: {},
50
+ general: {
51
+ environment: AppEnvironment.Local,
52
+ projectName: 'TestProject',
53
+ projectRootPath: BASE_PATH,
54
+ projectVersion: '1.0.0'
55
+ },
56
+ data: {
57
+ [defaultFakeModuleName]: { type: 'mysql', extra: 'foo' }
58
+ }
59
+ };
60
+ let fakeModuleName: string;
61
+ let fakeOptions: GenerateOrmconfigOptions;
62
+ let fakeConfig: AppConfig;
63
+ let entitiesDirPath: string;
64
+ let migrationsPath: string;
65
+ let ormConfigPath: string;
66
+ // clean up before each test
67
+ beforeEach(async () => {
68
+ fakeModuleName = defaultFakeModuleName;
69
+ fakeOptions = im.fromJS(defaultFakeOptions).toJS() as unknown as GenerateOrmconfigOptions;
70
+ fakeConfig = im.fromJS(defaultFakeConfig).toJS() as unknown as AppConfig;
71
+ entitiesDirPath = path.join(BASE_PATH, fakeOptions.modulePathInProject, fakeOptions.entitiesPathInModule);
72
+ migrationsPath = path.join(BASE_PATH, fakeOptions.modulePathInProject, fakeOptions.migrationsPathInModule);
73
+ ormConfigPath = path.join(BASE_PATH, `ormconfig-${fakeModuleName}.json`);
74
+ try {
75
+ await fs.unlink(ormConfigPath);
76
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
77
+ } catch (_e: unknown) {}
78
+ });
79
+ it('should generate ormconfig file with entities and subscribers arrays correctly', async () => {
80
+ await ConfigProviderService.generateOrmconfig(fakeConfig, fakeOptions);
81
+ const expectedEntities = [
82
+ path.join(entitiesDirPath, 'orders', 'orders.entity.ts'),
83
+ path.join(entitiesDirPath, 'users', 'users.entity.ts')
84
+ ];
85
+ const expectedSubscribers = [
86
+ path.join(entitiesDirPath, 'orders', 'orders.subscriber.ts'),
87
+ path.join(entitiesDirPath, 'users', 'users.subscriber.ts')
88
+ ];
89
+ const expectedMergedConfig = mergeDeepRight(fakeConfig.data[fakeModuleName], {
90
+ entities: expectedEntities,
91
+ subscribers: expectedSubscribers,
92
+ migrations: [`${migrationsPath}/**/*.ts`],
93
+ cli: { migrationsDir: migrationsPath }
94
+ });
95
+ expect((await fs.readFile(ormConfigPath)).toString()).toEqual(JSON.stringify(expectedMergedConfig));
96
+ });
97
+ it('should handle case with empty entities directory (no folders)', async () => {
98
+ fakeOptions.entitiesPathInModule = 'entitiesEmpty';
99
+ await ConfigProviderService.generateOrmconfig(fakeConfig, fakeOptions);
100
+ const expectedMergedConfig = mergeDeepRight(fakeConfig.data[fakeModuleName], {
101
+ entities: [],
102
+ subscribers: [],
103
+ migrations: [`${migrationsPath}/**/*.ts`],
104
+ cli: { migrationsDir: migrationsPath }
105
+ });
106
+ expect((await fs.readFile(ormConfigPath)).toString()).toEqual(JSON.stringify(expectedMergedConfig));
107
+ });
108
+ it('should ignore files that do not match entity or subscriber patterns', async () => {
109
+ await ConfigProviderService.generateOrmconfig(fakeConfig, fakeOptions);
110
+ const writtenConfig = JSON.parse((await fs.readFile(ormConfigPath)).toString());
111
+ expect(writtenConfig.entities).toEqual(
112
+ expect.arrayContaining([
113
+ path.join(entitiesDirPath, 'users', 'users.entity.ts'),
114
+ path.join(entitiesDirPath, 'orders', 'orders.entity.ts')
115
+ ])
116
+ );
117
+ expect(writtenConfig.entities).not.toEqual(
118
+ expect.arrayContaining([
119
+ path.join(entitiesDirPath, 'misc', 'note.txt'),
120
+ path.join(entitiesDirPath, 'misc', 'helper.ts')
121
+ ])
122
+ );
123
+ });
124
+ it('should propagate error if fs.readdir fails', async () => {
125
+ fakeOptions.entitiesPathInModule = 'entitiesNotExistent';
126
+ await expect(ConfigProviderService.generateOrmconfig(fakeConfig, fakeOptions)).rejects.toThrow('ENOENT');
127
+ });
128
+ });
129
+
130
+ // loadConfig
131
+ describe('loadConfig', () => {
132
+ const defaultAppConfigs: LoadConfigAppConfigs = {
133
+ appConfigCommon: {
134
+ general: {
135
+ projectName: 'TestProject',
136
+ projectRootPath: BASE_PATH,
137
+ projectVersion: '1.0.0'
138
+ },
139
+ api: {
140
+ test: {}
141
+ },
142
+ domain: {},
143
+ data: {}
144
+ },
145
+ appConfigProfileLocal: {
146
+ general: { environment: AppEnvironment.Local }
147
+ }
148
+ };
149
+ const defaultEnvKeys = {
150
+ API: {
151
+ HTTP: { HOST: 'hostname', PORT: 'port' }
152
+ }
153
+ };
154
+ const defaultEnvKeysParentNames = {
155
+ API: {
156
+ children: { TEST: 'test' },
157
+ name: 'api'
158
+ }
159
+ };
160
+ let appConfigs: LoadConfigAppConfigs;
161
+
162
+ beforeEach(() => {
163
+ process.env.NODE_ENV = 'local';
164
+ appConfigs = im.fromJS(defaultAppConfigs).toJS() as unknown as LoadConfigAppConfigs;
165
+ });
166
+ it('should load and merge configuration from env file and populate nested values', async () => {
167
+ delete process.env['NODE_ENV'];
168
+ const loadedConfig = await ConfigProviderService.loadConfig(appConfigs, {
169
+ envKeys: defaultEnvKeys,
170
+ envKeysParentNames: defaultEnvKeysParentNames
171
+ });
172
+ expect(loadedConfig.general.environment).toBe('local');
173
+ expect(loadedConfig.api).toBeDefined();
174
+ expect(loadedConfig.api.rest).toBeUndefined();
175
+ expect(loadedConfig.api.test).toBeDefined();
176
+ expect(loadedConfig.api.test.hostname).toBe('127.0.0.1');
177
+ expect(loadedConfig.api.test.port).toBe('3000');
178
+ });
179
+ it('should use default envKeys and envKeysParentNames if options not provided', async () => {
180
+ appConfigs.appConfigCommon.api!.rest = {};
181
+ const loadedConfig = await ConfigProviderService.loadConfig(appConfigs);
182
+ expect(loadedConfig.general.environment).toBe('local');
183
+ expect(loadedConfig.api.rest).toBeDefined();
184
+ expect(loadedConfig.api.rest.hostname).toBe('localhost');
185
+ expect(loadedConfig.api.test.hostname).toBeUndefined();
186
+ });
187
+ it('should throw an error if the .env file is missing', async () => {
188
+ (appConfigs.appConfigCommon as AppConfigCommon).general.projectRootPath = '/fake/path';
189
+ await expect(
190
+ ConfigProviderService.loadConfig(appConfigs, {
191
+ envKeys: defaultEnvKeys,
192
+ envKeysParentNames: defaultEnvKeysParentNames
193
+ })
194
+ ).rejects.toThrow('ENOENT');
195
+ });
196
+ it('should skip env keys that do not match expected pattern', async () => {
197
+ const loadedConfig = await ConfigProviderService.loadConfig(appConfigs, {
198
+ envKeys: defaultEnvKeys,
199
+ envKeysParentNames: defaultEnvKeysParentNames
200
+ });
201
+ expect(loadedConfig.api.test.hostname).toBe('127.0.0.1');
202
+ expect(loadedConfig.api.test.port).toBe('3000');
203
+ expect(loadedConfig.api.something).toBeUndefined();
204
+ });
205
+ });
206
+ });
@@ -0,0 +1,204 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+
4
+ import { Inject, Injectable } from '@nestjs/common';
5
+ import dotenv from 'dotenv';
6
+ import ld from 'lodash';
7
+
8
+ import {
9
+ APP_CONFIG_FROM_ENV_KEYS as APP_CONFIG_FROM_ENV_KEYS_DEFAULT,
10
+ APP_CONFIG_FROM_ENV_KEYS_PARENT_NAMES as APP_CONFIG_FROM_ENV_KEYS_PARENT_NAMES_DEFAULT,
11
+ AppConfigDataRDB,
12
+ AppConfig as AppConfigDefault,
13
+ AppEnvironment,
14
+ GenerateOrmconfigOptions,
15
+ LoadConfigAppConfigs,
16
+ LoadConfigOptions,
17
+ RDBType
18
+ } from './configProvider.definitions';
19
+
20
+ import { Constants } from '../definitions';
21
+ import { setNested } from '../utils';
22
+
23
+ @Injectable()
24
+ export class ConfigProviderService<AppConfig extends AppConfigDefault = AppConfigDefault> {
25
+ constructor(
26
+ @Inject(Constants.CONFIG)
27
+ // eslint-disable-next-line no-unused-vars
28
+ public config: AppConfig
29
+ ) {}
30
+
31
+ // TODO: consider moving this into the data-rdb package
32
+ static async generateOrmconfig<AppConfig extends AppConfigDefault = AppConfigDefault>(
33
+ config: AppConfig,
34
+ options: GenerateOrmconfigOptions
35
+ ): Promise<void> {
36
+ const {
37
+ general: { environment, projectRootPath },
38
+ data
39
+ } = config;
40
+ const { entitiesPathInModule, migrationsPathInModule, moduleName, modulePathInProject, seedsPathInModule } =
41
+ options;
42
+ const entitiesDirPath = path.join(projectRootPath, modulePathInProject, entitiesPathInModule);
43
+ const entitiesDirData = await fs.readdir(entitiesDirPath);
44
+ const entities: string[] = [];
45
+ const migrationsPath = path.join(projectRootPath, modulePathInProject, migrationsPathInModule);
46
+ const moduleConfig = data[moduleName] as AppConfigDataRDB;
47
+ const subscribers: string[] = [];
48
+ for (const i in entitiesDirData) {
49
+ const entityName = entitiesDirData[i];
50
+ if (entityName.match(/^base$/)) {
51
+ continue;
52
+ }
53
+ const entityFolderPath = path.join(entitiesDirPath, entityName);
54
+ const entityFolderChildItemStat = await fs.lstat(entityFolderPath);
55
+ if (!entityFolderChildItemStat.isDirectory()) {
56
+ continue;
57
+ }
58
+ const entityFolderData = await fs.readdir(entityFolderPath);
59
+ for (const j in entityFolderData) {
60
+ const entityFolderFileName = entityFolderData[j];
61
+ if (entityFolderFileName.match(/\.entity\./)) {
62
+ entities.push(path.join(entityFolderPath, entityFolderFileName));
63
+ continue;
64
+ }
65
+ if (entityFolderFileName.match(/\.subscriber\./)) {
66
+ subscribers.push(path.join(entityFolderPath, entityFolderFileName));
67
+ continue;
68
+ }
69
+ }
70
+ }
71
+ // write the ORM Config file
72
+ const ormconfigMigrations: string[] = [`${migrationsPath}/**/*.ts`];
73
+ if (seedsPathInModule) {
74
+ const baseSeedsPath = path.join(projectRootPath, modulePathInProject, seedsPathInModule);
75
+ ormconfigMigrations.push(`${baseSeedsPath}/common/**/*.ts`);
76
+ }
77
+ await fs.writeFile(
78
+ path.join(projectRootPath, `ormconfig-${moduleName}-${environment}.json`),
79
+ JSON.stringify(
80
+ ld.merge(data[moduleName], {
81
+ cli: {
82
+ migrationsDir: migrationsPath
83
+ },
84
+ entities: [...entities],
85
+ migrations: ormconfigMigrations,
86
+ name: moduleConfig.connectionName,
87
+ synchronize: moduleConfig.type === RDBType.Aurora ? true : false,
88
+ subscribers: [...subscribers],
89
+ type: moduleConfig.type === RDBType.Aurora ? RDBType.MySQL : moduleConfig.type,
90
+ ...(moduleConfig.typeormExtraOptions || {})
91
+ })
92
+ )
93
+ );
94
+ // write the Datasource file
95
+ const entitiesPathInProject = path.join(modulePathInProject, entitiesPathInModule);
96
+ await fs.writeFile(
97
+ path.join(projectRootPath, `datasource-${moduleName}-${environment}.ts`),
98
+ "import { loadDynamicModules } from '@node-c/core';\n" +
99
+ '\n' +
100
+ "import { DataSource } from 'typeorm';\n" +
101
+ '\n' +
102
+ `import * as FolderData from './${entitiesPathInProject}';\n` +
103
+ '\n' +
104
+ 'const { entities } = loadDynamicModules(FolderData);\n' +
105
+ '\n' +
106
+ 'export default new DataSource({\n' +
107
+ ` database: '${moduleConfig.database}',\n` +
108
+ ' entities: entities as unknown as string[],\n' +
109
+ ` host: '${moduleConfig.host}',\n` +
110
+ ' logging: false,\n' +
111
+ ` migrations: [${ormconfigMigrations.map(item => `'${item.replace(projectRootPath.replace(/\/$/, ''), '.')}'`).join(', ')}],\n` +
112
+ ` name: '${moduleConfig.connectionName}',\n` +
113
+ ` password: '${moduleConfig.password}',\n` +
114
+ ` port: ${moduleConfig.port},\n` +
115
+ ' subscribers: [],\n' +
116
+ ` synchronize: ${moduleConfig.type === RDBType.Aurora ? true : false},\n` +
117
+ ` type: '${moduleConfig.type === RDBType.Aurora ? RDBType.MySQL : moduleConfig.type}',\n` +
118
+ ` username: '${moduleConfig.user}'\n` +
119
+ '});\n'
120
+ );
121
+ }
122
+
123
+ // TODO: logging about invalid config values
124
+ static async loadConfig<AppConfig extends AppConfigDefault = AppConfigDefault>(
125
+ appConfigs: LoadConfigAppConfigs,
126
+ options?: LoadConfigOptions
127
+ ): Promise<AppConfig> {
128
+ const { useEnvFile, useEnvFileWithPriority, ...optionsData } = options || ({} as LoadConfigOptions);
129
+ const envKeys = optionsData.envKeys || APP_CONFIG_FROM_ENV_KEYS_DEFAULT;
130
+ const envKeysParentNames = optionsData.envKeysParentNames || APP_CONFIG_FROM_ENV_KEYS_PARENT_NAMES_DEFAULT;
131
+ const processEnv = process.env;
132
+ const envName = optionsData.envName || (processEnv['NODE_ENV'] as AppEnvironment) || AppEnvironment.Local;
133
+ const config = ld.merge(
134
+ appConfigs.appConfigCommon,
135
+ appConfigs[
136
+ `appConfigProfile${envName.charAt(0).toUpperCase()}${envName.substring(
137
+ 1,
138
+ envName.length
139
+ )}` as keyof typeof appConfigs
140
+ ]
141
+ ) as AppConfig;
142
+ const moduleNamesByCategoryAndType: {
143
+ [moduleCategory: string]: { [moduleType: string]: string[] };
144
+ } = {};
145
+ const moduleTypesRegex = new RegExp(`^((${Object.keys(envKeys).join(')|(')}))_`);
146
+ config.general.environment = envName;
147
+ let envVars: Record<string, unknown> = processEnv;
148
+ if (useEnvFile) {
149
+ // populate the data from the .env file into the config object
150
+ const envVarsFromFile = dotenv.parse(
151
+ (await fs.readFile(path.join(config.general.projectRootPath, `envFiles/.${envName}.env`))).toString()
152
+ );
153
+ if (useEnvFileWithPriority) {
154
+ envVars = ld.merge(envVars, envVarsFromFile);
155
+ } else {
156
+ envVars = ld.merge(envVarsFromFile, envVars);
157
+ }
158
+ }
159
+ // first pass - create a list of modules by name and map them by module type
160
+ for (const envKey in envVars) {
161
+ const [, moduleCategory] = envKey.match(moduleTypesRegex) || [];
162
+ if (!moduleCategory) {
163
+ continue;
164
+ }
165
+ const [, moduleName] = envKey.match(new RegExp(`^${moduleCategory}_(.+)_MODULE_TYPE$`)) || [];
166
+ if (!moduleName) {
167
+ continue;
168
+ }
169
+ const moduleFields = envKeys[moduleCategory as keyof typeof envKeys];
170
+ const moduleType = envVars[envKey] as keyof typeof moduleFields;
171
+ if (!moduleFields[moduleType]) {
172
+ continue;
173
+ }
174
+ if (!moduleNamesByCategoryAndType[moduleCategory]) {
175
+ moduleNamesByCategoryAndType[moduleCategory] = {};
176
+ }
177
+ if (!moduleNamesByCategoryAndType[moduleCategory][moduleType]) {
178
+ moduleNamesByCategoryAndType[moduleCategory][moduleType] = [];
179
+ }
180
+ moduleNamesByCategoryAndType[moduleCategory][moduleType].push(moduleName);
181
+ }
182
+ // second pass - actually go through the env vars and populate them in the config accordingly
183
+ for (const moduleCategory in moduleNamesByCategoryAndType) {
184
+ const { children: moduleConfigKeysForCategory, name: categoryConfigKey } = envKeysParentNames[moduleCategory];
185
+ const moduleFieldsForCategory = envKeys[moduleCategory as keyof typeof envKeys];
186
+ const moduleNamesByType = moduleNamesByCategoryAndType[moduleCategory];
187
+ for (const moduleType in moduleNamesByType) {
188
+ const moduleFieldsForType = moduleFieldsForCategory[
189
+ moduleType as keyof typeof moduleFieldsForCategory
190
+ ] as Record<string, string>;
191
+ const moduleNames = moduleNamesByType[moduleType];
192
+ moduleNames.forEach(moduleName => {
193
+ const moduleConfigKey = moduleConfigKeysForCategory[moduleName];
194
+ for (const fieldName in moduleFieldsForType) {
195
+ const configKey = `${categoryConfigKey}.${moduleConfigKey}.${moduleFieldsForType[fieldName]}`;
196
+ const envKey = `${moduleCategory}_${moduleName}_${fieldName}`;
197
+ setNested(config, configKey, envVars[envKey]);
198
+ }
199
+ });
200
+ }
201
+ }
202
+ return config;
203
+ }
204
+ }
@@ -0,0 +1,3 @@
1
+ export * from './configProvider.definitions';
2
+ export * from './configProvider.module';
3
+ export * from './configProvider.service';
@@ -0,0 +1,27 @@
1
+ export enum Constants {
2
+ // eslint-disable-next-line no-unused-vars
3
+ API_MODULE_NAME = 'API_MODULE_NAME',
4
+ // eslint-disable-next-line no-unused-vars
5
+ CONFIG = 'CONFIG',
6
+ // eslint-disable-next-line no-unused-vars
7
+ DOMAIN_MODULE_NAME = 'DOMAIN_MODULE_NAME',
8
+ // eslint-disable-next-line no-unused-vars
9
+ DATA_MODULE_NAME = 'DATA_MODULE_NAME'
10
+ }
11
+
12
+ export enum HttpMethod {
13
+ // eslint-disable-next-line no-unused-vars
14
+ DELETE = 'delete',
15
+ // eslint-disable-next-line no-unused-vars
16
+ GET = 'get',
17
+ // eslint-disable-next-line no-unused-vars
18
+ HEAD = 'head',
19
+ // eslint-disable-next-line no-unused-vars
20
+ OPTIONS = 'options',
21
+ // eslint-disable-next-line no-unused-vars
22
+ PATCH = 'patch',
23
+ // eslint-disable-next-line no-unused-vars
24
+ POST = 'post',
25
+ // eslint-disable-next-line no-unused-vars
26
+ PUT = 'put'
27
+ }
@@ -0,0 +1,13 @@
1
+ export interface GenericObject<Values = unknown> {
2
+ [fieldName: string]: Values;
3
+ }
4
+
5
+ export class GenericObjectClass<Values = unknown> implements GenericObject<Values> {
6
+ [fieldName: string]: Values;
7
+ }
8
+
9
+ export type GenericObjectType<Type> =
10
+ | {
11
+ new (): Type;
12
+ }
13
+ | ((..._args: unknown[]) => unknown);
@@ -0,0 +1,13 @@
1
+ import { GenericObject } from './common.definitions';
2
+
3
+ export class ApplicationError implements Error {
4
+ data?: { errorCode?: number } | GenericObject;
5
+ message: string;
6
+ name: string;
7
+
8
+ constructor(message: string, data?: GenericObject) {
9
+ this.message = message;
10
+ this.name = 'ApplicationError';
11
+ this.data = data || {};
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ export * from './common.constants';
2
+ export * from './common.definitions';
3
+ export * from './common.errors';
@@ -0,0 +1,3 @@
1
+ export * from './logger.definitions';
2
+ export * from './logger.module';
3
+ export * from './logger.service';
@@ -0,0 +1,5 @@
1
+ import { Params } from 'nestjs-pino';
2
+
3
+ export interface LoggerModuleOptions {
4
+ pinoParams?: Params;
5
+ }
@@ -0,0 +1,21 @@
1
+ import { DynamicModule, Module } from '@nestjs/common';
2
+
3
+ import ld from 'lodash';
4
+ import { LoggerModule as PinoLoggerModule } from 'nestjs-pino';
5
+
6
+ import { LoggerModuleOptions } from './logger.definitions';
7
+ import { DEFAULT_PINO_PARAMS, LoggerService } from './logger.service';
8
+
9
+ @Module({})
10
+ export class LoggerModule {
11
+ static register(options?: LoggerModuleOptions): DynamicModule {
12
+ const { pinoParams } = options || {};
13
+ return {
14
+ global: true,
15
+ module: LoggerModule,
16
+ imports: [PinoLoggerModule.forRoot(ld.merge(DEFAULT_PINO_PARAMS, pinoParams || {}))],
17
+ providers: [LoggerService],
18
+ exports: [LoggerService]
19
+ };
20
+ }
21
+ }
@@ -0,0 +1,40 @@
1
+ import path from 'path';
2
+
3
+ import { Inject, Injectable } from '@nestjs/common';
4
+
5
+ import { Logger, PARAMS_PROVIDER_TOKEN, Params, PinoLogger } from 'nestjs-pino';
6
+ import { v4 as uuid } from 'uuid';
7
+
8
+ export const DEFAULT_PINO_PARAMS: Params = {
9
+ pinoHttp: {
10
+ // autoLogging: false,
11
+ genReqId: () => uuid(),
12
+ quietReqLogger: true,
13
+ quietResLogger: true,
14
+ transport: {
15
+ targets: [
16
+ {
17
+ options: { destination: path.resolve(process.cwd(), 'logs/app_logs.txt'), sync: false },
18
+ target: 'pino/file'
19
+ },
20
+ {
21
+ // level: 'info',
22
+ options: { destination: 1, sync: false },
23
+ target: 'pino-pretty'
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ };
29
+
30
+ @Injectable()
31
+ export class LoggerService extends Logger {
32
+ constructor(logger: PinoLogger, @Inject(PARAMS_PROVIDER_TOKEN) params: Params) {
33
+ super(logger, params);
34
+ }
35
+
36
+ info(...args: unknown[]): void {
37
+ // eslint-disable-next-line prefer-spread
38
+ this.logger.info.apply(this, args as [unknown, string, ...unknown[]]);
39
+ }
40
+ }
@@ -0,0 +1,4 @@
1
+ export const base64UrlEncode = (buffer: ArrayBuffer | string): string => {
2
+ const actualBuffer = typeof buffer === 'string' ? Buffer.from(buffer, 'utf-8') : Buffer.from(buffer);
3
+ return actualBuffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
4
+ };
@@ -0,0 +1 @@
1
+ export * from './base64UrlEncode.method';