@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,142 @@
1
+ import { GenericObject } from '../../common/definitions';
2
+
3
+ import {
4
+ DataBulkCreatePrivateOptions,
5
+ DataCreatePrivateOptions,
6
+ DataDeleteOptions,
7
+ DataDeletePrivateOptions,
8
+ DataDeleteResult,
9
+ DataFindOneOptions,
10
+ DataFindOnePrivateOptions,
11
+ DataFindOptions,
12
+ DataFindPrivateOptions,
13
+ DataFindResults,
14
+ DataUpdateOptions,
15
+ DataUpdatePrivateOptions,
16
+ DataUpdateResult
17
+ } from '../../data/entityService';
18
+
19
+ export interface DomainBaseAdditionalServiceOptionsOverrides {
20
+ filterByFirstServiceResultFields?: GenericObject<string>;
21
+ returnData?: boolean;
22
+ runOnNoFirstServiceResultOnly?: boolean | string;
23
+ }
24
+
25
+ export type DomainBaseOptions<Options> = Options & DomainBaseOptionsForAdditionalServices<Options>;
26
+
27
+ export interface DomainBaseOptionsForAdditionalServices<Options> {
28
+ optionsOverridesByService?: GenericObject<Partial<Options> & DomainBaseAdditionalServiceOptionsOverrides>;
29
+ dataServices?: DomainDataServicesKey[];
30
+ }
31
+
32
+ export type DomainBaseOptionsForAdditionalServicesFull<
33
+ Options extends object | undefined = undefined,
34
+ SaveAdditionalResultsOptions extends object | undefined = undefined
35
+ > = DomainBaseOptionsForAdditionalServices<Options> & DomainBaseOptionsWithSearchData<SaveAdditionalResultsOptions>;
36
+
37
+ export interface DomainBaseOptionsWithSearchData<SaveAdditionalResultsOptions extends object | undefined = undefined> {
38
+ saveAdditionalResultsInFirstService?: {
39
+ saveOptions?: SaveAdditionalResultsOptions;
40
+ serviceName: string;
41
+ useResultsForFirstService?: boolean;
42
+ };
43
+ }
44
+
45
+ export interface DomainBaseResult<Result> {
46
+ result: Result;
47
+ resultsByService?: GenericObject<Result>;
48
+ }
49
+
50
+ export type DomainBulkCreatePrivateOptions = DataBulkCreatePrivateOptions;
51
+
52
+ export type DomainBulkCreateData<Entity> = Partial<Entity>[];
53
+
54
+ export type DomainBulkCreateOptions<Options = object> = DomainBaseOptions<Options>;
55
+
56
+ export type DomainBulkCreateResult<Entity> = DomainBaseResult<Entity[]>;
57
+
58
+ export type DomainCreateData<Entity> = Partial<Entity>;
59
+
60
+ export type DomainCreateOptions<Options = object> = DomainBaseOptions<Options>;
61
+
62
+ export type DomainCreatePrivateOptions = DataCreatePrivateOptions;
63
+
64
+ export type DomainCreateResult<Entity> = DomainBaseResult<Entity>;
65
+
66
+ export type DomainDeleteOptions<Options = object> = Options & DomainBaseOptions<DataDeleteOptions>;
67
+
68
+ export type DomainDeletePrivateOptions = DataDeletePrivateOptions;
69
+
70
+ export type DomainDeleteResult<Entity> = DomainBaseResult<DataDeleteResult<Entity>>;
71
+
72
+ export interface DomainEntityServiceDefaultData<Entity> {
73
+ BulkCreate: DomainBulkCreateData<Entity>;
74
+ Create: DomainCreateData<Entity>;
75
+ Update: DomainUpdateData<Entity>;
76
+ }
77
+
78
+ export type DomainFindOneOptions<Options = object> = Options &
79
+ DomainBaseOptions<DataFindOneOptions> &
80
+ DomainBaseOptionsWithSearchData<DomainCreateOptions>;
81
+
82
+ export type DomainFindOnePrivateOptions = DataFindOnePrivateOptions;
83
+
84
+ export type DomainFindOneResult<Entity> = DomainBaseResult<Entity | null>;
85
+
86
+ export type DomainFindOptions<Options = object> = Options &
87
+ DomainBaseOptions<DataFindOptions> &
88
+ DomainBaseOptionsWithSearchData<DomainBulkCreateOptions>;
89
+
90
+ export type DomainFindPrivateOptions = DataFindPrivateOptions;
91
+
92
+ export type DomainFindResult<Entity> = DomainBaseResult<DataFindResults<Entity>>;
93
+
94
+ export enum DomainMethod {
95
+ // eslint-disable-next-line no-unused-vars
96
+ BulkCreate = 'bulkCreate',
97
+ // eslint-disable-next-line no-unused-vars
98
+ Create = 'create',
99
+ // eslint-disable-next-line no-unused-vars
100
+ Delete = 'delete',
101
+ // eslint-disable-next-line no-unused-vars
102
+ Find = 'find',
103
+ // eslint-disable-next-line no-unused-vars
104
+ FindOne = 'findOne',
105
+ // eslint-disable-next-line no-unused-vars
106
+ Update = 'update'
107
+ }
108
+
109
+ export const DOMAIN_ENTITY_SERVICE_DEFAULT_METHODS = [
110
+ DomainMethod.BulkCreate,
111
+ DomainMethod.Create,
112
+ DomainMethod.Delete,
113
+ DomainMethod.Find,
114
+ DomainMethod.FindOne,
115
+ DomainMethod.Update
116
+ ];
117
+
118
+ export enum DomainDataEntityServiceType {
119
+ // eslint-disable-next-line no-unused-vars
120
+ All = 'all',
121
+ // eslint-disable-next-line no-unused-vars
122
+ Main = 'main'
123
+ }
124
+
125
+ export type DomainDataServicesKey = DomainDataEntityServiceType | string;
126
+
127
+ export type DomainRunMethodInAdditionalServicesOptions<Options> = {
128
+ firstServiceResult?: unknown;
129
+ hasFirstServiceResult: boolean;
130
+ methodArgs?: unknown[];
131
+ methodName: string;
132
+ optionsArgIndex?: number;
133
+ optionsOverridesByService?: GenericObject<Partial<Options> & DomainBaseAdditionalServiceOptionsOverrides>;
134
+ };
135
+
136
+ export type DomainUpdateData<Entity> = Partial<Entity>;
137
+
138
+ export type DomainUpdateOptions<Options = object> = Options & DomainBaseOptions<DataUpdateOptions>;
139
+
140
+ export type DomainUpdatePrivateOptions = DataUpdatePrivateOptions;
141
+
142
+ export type DomainUpdateResult<Entity> = DomainBaseResult<DataUpdateResult<Entity>>;
@@ -0,0 +1,126 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { DomainEntityService } from './domain.entity.service';
4
+
5
+ import {
6
+ DataDeleteOptions,
7
+ DataDeleteResult,
8
+ DataEntityService,
9
+ DataFindOneOptions,
10
+ DataFindOptions,
11
+ DataFindResults,
12
+ DataUpdateOptions,
13
+ DataUpdateResult
14
+ } from '../../data/entityService';
15
+
16
+ interface TestEntity {
17
+ id: number;
18
+ name: string;
19
+ }
20
+
21
+ describe('DomainEntityService', () => {
22
+ let mockDataService: DataEntityService<TestEntity>;
23
+ let domainService: DomainEntityService<TestEntity, DataEntityService<TestEntity>>;
24
+
25
+ beforeEach(() => {
26
+ // Create a fully typed mock for the underlying persistence service.
27
+ mockDataService = {
28
+ bulkCreate: vi.fn().mockResolvedValue([{ id: 1, name: 'Test1' }]),
29
+ create: vi.fn().mockResolvedValue({ id: 2, name: 'Test2' }),
30
+ count: vi.fn().mockResolvedValue({ count: 0 }),
31
+ delete: vi.fn().mockResolvedValue({ affected: 1 } as DataDeleteResult),
32
+ find: vi.fn().mockResolvedValue({ items: [{ id: 3, name: 'Test3' }] } as DataFindResults<TestEntity>),
33
+ findOne: vi.fn().mockResolvedValue({ id: 4, name: 'Test4' }),
34
+ getEntityName: vi.fn().mockResolvedValue('mockEntity'),
35
+ update: vi.fn().mockResolvedValue({ updated: { id: 5, name: 'Test5' } } as DataUpdateResult<TestEntity>)
36
+ };
37
+ domainService = new DomainEntityService<TestEntity, DataEntityService<TestEntity>>(mockDataService);
38
+ });
39
+
40
+ describe('bulkCreate', () => {
41
+ it('should call dataEntityService.bulkCreate with the provided data and return its result', async () => {
42
+ const data: TestEntity[] = [{ id: 10, name: 'BulkTest' }];
43
+ const result = await domainService.bulkCreate(data);
44
+ expect(mockDataService.bulkCreate).toHaveBeenCalledWith(data);
45
+ expect(result).toEqual({ result: [{ id: 1, name: 'Test1' }] });
46
+ });
47
+ it('should propagate errors from bulkCreate', async () => {
48
+ const error = new Error('bulk error');
49
+ (mockDataService.bulkCreate as ReturnType<typeof vi.fn>).mockRejectedValueOnce(error);
50
+ await expect(domainService.bulkCreate([{ id: 0, name: 'Error' }])).rejects.toThrow('bulk error');
51
+ });
52
+ });
53
+
54
+ describe('create', () => {
55
+ it('should call dataEntityService.create with the provided data and return its result', async () => {
56
+ const data: TestEntity = { id: 20, name: 'CreateTest' };
57
+ const result = await domainService.create(data);
58
+ expect(mockDataService.create).toHaveBeenCalledWith(data);
59
+ expect(result).toEqual({ result: { id: 2, name: 'Test2' } });
60
+ });
61
+ it('should propagate errors from create', async () => {
62
+ const error = new Error('create error');
63
+ (mockDataService.create as ReturnType<typeof vi.fn>).mockRejectedValueOnce(error);
64
+ await expect(domainService.create({ id: 0, name: 'Error' })).rejects.toThrow('create error');
65
+ });
66
+ });
67
+
68
+ describe('delete', () => {
69
+ it('should call dataEntityService.delete with correct options and return its result', async () => {
70
+ const options: DataDeleteOptions = { filters: { id: 1 } };
71
+ const result = await domainService.delete(options);
72
+ expect(mockDataService.delete).toHaveBeenCalledWith(options);
73
+ expect(result).toEqual({ result: { affected: 1 } });
74
+ });
75
+ it('should propagate errors from delete', async () => {
76
+ const error = new Error('delete error');
77
+ (mockDataService.delete as ReturnType<typeof vi.fn>).mockRejectedValueOnce(error);
78
+ await expect(domainService.delete({ filters: { id: 999 } })).rejects.toThrow('delete error');
79
+ });
80
+ });
81
+
82
+ describe('find', () => {
83
+ it('should call dataEntityService.find with correct options and return its result', async () => {
84
+ const options: DataFindOptions = { filters: { name: 'Test' } };
85
+ const result = await domainService.find(options);
86
+ expect(mockDataService.find).toHaveBeenCalledWith(options);
87
+ expect(result).toEqual({ result: { items: [{ id: 3, name: 'Test3' }] } });
88
+ });
89
+ it('should propagate errors from find', async () => {
90
+ const error = new Error('find error');
91
+ (mockDataService.find as ReturnType<typeof vi.fn>).mockRejectedValueOnce(error);
92
+ await expect(domainService.find({ filters: { name: 'Error' } })).rejects.toThrow('find error');
93
+ });
94
+ });
95
+
96
+ describe('findOne', () => {
97
+ it('should call dataEntityService.findOne with correct options and return its result', async () => {
98
+ const options: DataFindOneOptions = { filters: { id: 4 } };
99
+ const result = await domainService.findOne(options);
100
+ expect(mockDataService.findOne).toHaveBeenCalledWith(options);
101
+ expect(result).toEqual({ result: { id: 4, name: 'Test4' } });
102
+ });
103
+ it('should propagate errors from findOne', async () => {
104
+ const error = new Error('findOne error');
105
+ (mockDataService.findOne as ReturnType<typeof vi.fn>).mockRejectedValueOnce(error);
106
+ await expect(domainService.findOne({ filters: { id: 999 } })).rejects.toThrow('findOne error');
107
+ });
108
+ });
109
+
110
+ describe('update', () => {
111
+ it('should call dataEntityService.update with correct data and options and return its result', async () => {
112
+ const data: TestEntity = { id: 30, name: 'UpdateTest' };
113
+ const options: DataUpdateOptions = { filters: { id: 30 } };
114
+ const result = await domainService.update(data, options);
115
+ expect(mockDataService.update).toHaveBeenCalledWith(data, options);
116
+ expect(result).toEqual({ result: { updated: { id: 5, name: 'Test5' } } });
117
+ });
118
+ it('should propagate errors from update', async () => {
119
+ const error = new Error('update error');
120
+ (mockDataService.update as ReturnType<typeof vi.fn>).mockRejectedValueOnce(error);
121
+ await expect(domainService.update({ id: 0, name: 'Error' }, { filters: { id: 0 } })).rejects.toThrow(
122
+ 'update error'
123
+ );
124
+ });
125
+ });
126
+ });
@@ -0,0 +1,424 @@
1
+ import ld from 'lodash';
2
+
3
+ import {
4
+ DOMAIN_ENTITY_SERVICE_DEFAULT_METHODS,
5
+ DomainBaseAdditionalServiceOptionsOverrides,
6
+ DomainBulkCreateOptions,
7
+ DomainBulkCreatePrivateOptions,
8
+ DomainBulkCreateResult,
9
+ DomainCreateOptions,
10
+ DomainCreatePrivateOptions,
11
+ DomainCreateResult,
12
+ DomainDataEntityServiceType,
13
+ DomainDeleteOptions,
14
+ DomainDeletePrivateOptions,
15
+ DomainDeleteResult,
16
+ DomainEntityServiceDefaultData,
17
+ DomainFindOneOptions,
18
+ DomainFindOnePrivateOptions,
19
+ DomainFindOneResult,
20
+ DomainFindOptions,
21
+ DomainFindPrivateOptions,
22
+ DomainFindResult,
23
+ DomainMethod,
24
+ DomainRunMethodInAdditionalServicesOptions,
25
+ DomainUpdateOptions,
26
+ DomainUpdatePrivateOptions,
27
+ DomainUpdateResult
28
+ } from './domain.entity.service.definitions';
29
+
30
+ import { ApplicationError, GenericObject } from '../../common/definitions';
31
+
32
+ import { LoggerService } from '../../common/logger';
33
+ import { DataDefaultData, DataEntityService, DataFindResults } from '../../data/entityService';
34
+
35
+ // TODO: privateOptionsOverrides by service
36
+ export class DomainEntityService<
37
+ Entity,
38
+ EntityService extends DataEntityService<Entity, DataEntityServiceData>,
39
+ Data extends DomainEntityServiceDefaultData<Entity> = DomainEntityServiceDefaultData<Entity>,
40
+ AdditionalEntityServices extends
41
+ | Record<string, DataEntityService<Partial<Entity>, DataDefaultData<object>>>
42
+ | undefined = undefined,
43
+ DataEntityServiceData extends DataDefaultData<Entity> = DataDefaultData<Entity>
44
+ > {
45
+ constructor(
46
+ // eslint-disable-next-line no-unused-vars
47
+ protected dataEntityService: EntityService,
48
+ // eslint-disable-next-line no-unused-vars
49
+ protected defaultMethods: string[] = DOMAIN_ENTITY_SERVICE_DEFAULT_METHODS,
50
+ // eslint-disable-next-line no-unused-vars
51
+ protected logger: LoggerService,
52
+ // eslint-disable-next-line no-unused-vars
53
+ protected additionalDataEntityServices?: AdditionalEntityServices,
54
+ // eslint-disable-next-line no-unused-vars
55
+ protected defaultAdditionalDataEntityServicesOptions?: {
56
+ [methodName: string]: {
57
+ [serviceName: string]: {
58
+ allowIncoming?: boolean;
59
+ serviceOptions?: DomainBaseAdditionalServiceOptionsOverrides & GenericObject<unknown>;
60
+ };
61
+ };
62
+ }
63
+ ) {}
64
+
65
+ public bulkCreate(
66
+ // eslint-disable-next-line no-unused-vars
67
+ data: Data['BulkCreate'],
68
+ // eslint-disable-next-line no-unused-vars
69
+ options?: DomainBulkCreateOptions,
70
+ // eslint-disable-next-line no-unused-vars
71
+ privateOptions?: DomainBulkCreatePrivateOptions
72
+ ): Promise<DomainBulkCreateResult<Entity>>;
73
+ async bulkCreate(
74
+ data: Data['BulkCreate'],
75
+ options?: DomainBulkCreateOptions,
76
+ privateOptions?: DomainBulkCreatePrivateOptions
77
+ ): Promise<DomainBulkCreateResult<Entity>> {
78
+ if (!this.defaultMethods?.includes(DomainMethod.BulkCreate)) {
79
+ throw new ApplicationError(`Method bulkCreate not implemented for class ${typeof this}.`);
80
+ }
81
+ // const defaultAdditionalDataEntityServicesOptions =
82
+ // this.defaultAdditionalDataEntityServicesOptions?.bulkCreate;
83
+ const { optionsOverridesByService, dataServices = [DomainDataEntityServiceType.Main] } = options || {};
84
+ const [firstServiceName, ...otherServiceNames] = dataServices;
85
+ const result = await this.getDataService(firstServiceName).bulkCreate(data, privateOptions);
86
+ let actualOtherServiceNames: string[] = [];
87
+ let actualOptionsOverridesByService: typeof optionsOverridesByService = {};
88
+ // if (defaultAdditionalDataEntityServicesOptions) {
89
+ // for (const serviceName in defaultAdditionalDataEntityServicesOptions) {
90
+ // const { allowIncoming = true, serviceOptions } = defaultAdditionalDataEntityServicesOptions[serviceName];
91
+ // }
92
+ // } else {
93
+ actualOtherServiceNames = otherServiceNames || [];
94
+ actualOptionsOverridesByService = optionsOverridesByService;
95
+ // }
96
+ return {
97
+ result,
98
+ resultsByService: await this.runMethodInAdditionalServices(actualOtherServiceNames, {
99
+ firstServiceResult: result,
100
+ hasFirstServiceResult: result.length > 0,
101
+ methodArgs: [result, privateOptions],
102
+ methodName: 'bulkCreate',
103
+ optionsArgIndex: 1,
104
+ optionsOverridesByService: actualOptionsOverridesByService
105
+ })
106
+ };
107
+ }
108
+
109
+ public create(
110
+ // eslint-disable-next-line no-unused-vars
111
+ data: Data['Create'],
112
+ // eslint-disable-next-line no-unused-vars
113
+ options?: DomainCreateOptions,
114
+ // eslint-disable-next-line no-unused-vars
115
+ privateOptions?: DomainCreatePrivateOptions
116
+ ): Promise<DomainCreateResult<Entity>>;
117
+ async create<Options extends object | undefined = undefined>(
118
+ data: Data['Create'],
119
+ options?: DomainCreateOptions<Options>,
120
+ privateOptions?: DomainCreatePrivateOptions
121
+ ): Promise<DomainCreateResult<Entity>> {
122
+ if (!this.defaultMethods?.includes(DomainMethod.Create)) {
123
+ throw new ApplicationError(`Method create not implemented for class ${typeof this}.`);
124
+ }
125
+ const { optionsOverridesByService, dataServices = [DomainDataEntityServiceType.Main] } = options || {};
126
+ const [firstServiceName, ...otherServiceNames] = dataServices;
127
+ const result = await this.getDataService(firstServiceName).create(data, privateOptions);
128
+ return {
129
+ result,
130
+ resultsByService: await this.runMethodInAdditionalServices(otherServiceNames || [], {
131
+ firstServiceResult: result,
132
+ hasFirstServiceResult: typeof result !== 'undefined' && result !== null,
133
+ methodArgs: [result, privateOptions],
134
+ methodName: 'create',
135
+ optionsArgIndex: 1,
136
+ optionsOverridesByService
137
+ })
138
+ };
139
+ }
140
+
141
+ public delete(
142
+ // eslint-disable-next-line no-unused-vars
143
+ options: DomainDeleteOptions,
144
+ // eslint-disable-next-line no-unused-vars
145
+ privateOptions?: DomainDeletePrivateOptions
146
+ ): Promise<DomainDeleteResult<Entity>>;
147
+ async delete(
148
+ options: DomainDeleteOptions,
149
+ privateOptions?: DomainDeletePrivateOptions
150
+ ): Promise<DomainDeleteResult<Entity>> {
151
+ if (!this.defaultMethods?.includes(DomainMethod.Delete)) {
152
+ throw new ApplicationError(`Method delete not implemented for class ${typeof this}.`);
153
+ }
154
+ const {
155
+ optionsOverridesByService,
156
+ dataServices = [DomainDataEntityServiceType.Main],
157
+ ...otherOptions
158
+ } = options || {};
159
+ const [firstServiceName, ...otherServiceNames] = dataServices;
160
+ const result = await this.getDataService(firstServiceName).delete(otherOptions, privateOptions);
161
+ return {
162
+ result,
163
+ resultsByService: await this.runMethodInAdditionalServices(otherServiceNames || [], {
164
+ firstServiceResult: { ...result, items: result.originalItems || [] },
165
+ hasFirstServiceResult: !!result.count,
166
+ methodArgs: [otherOptions, privateOptions],
167
+ methodName: 'delete',
168
+ optionsArgIndex: 0,
169
+ optionsOverridesByService
170
+ })
171
+ };
172
+ }
173
+
174
+ public find(
175
+ // eslint-disable-next-line no-unused-vars
176
+ options: DomainFindOptions,
177
+ // eslint-disable-next-line no-unused-vars
178
+ privateOptions?: DomainFindOnePrivateOptions
179
+ ): Promise<DomainFindResult<Entity>>;
180
+ async find(
181
+ options: DomainFindOptions,
182
+ privateOptions?: DomainFindOnePrivateOptions
183
+ ): Promise<DomainFindResult<Entity>> {
184
+ if (!this.defaultMethods?.includes(DomainMethod.Find)) {
185
+ throw new ApplicationError(`Method find not implemented for class ${typeof this}.`);
186
+ }
187
+ const {
188
+ optionsOverridesByService,
189
+ dataServices = [DomainDataEntityServiceType.Main],
190
+ saveAdditionalResultsInFirstService,
191
+ ...otherOptions
192
+ } = options || {};
193
+ const [firstServiceName, ...otherServiceNames] = dataServices;
194
+ let result = await this.getDataService(firstServiceName).find(otherOptions, privateOptions);
195
+ const hasFirstServiceResult = result.items.length > 0;
196
+ const resultsByService = await this.runMethodInAdditionalServices<DataFindResults<Entity>>(
197
+ otherServiceNames || [],
198
+ {
199
+ firstServiceResult: result,
200
+ hasFirstServiceResult,
201
+ methodArgs: [otherOptions, privateOptions],
202
+ methodName: 'find',
203
+ optionsArgIndex: 0,
204
+ optionsOverridesByService
205
+ }
206
+ );
207
+ if (saveAdditionalResultsInFirstService && resultsByService) {
208
+ const { saveOptions, serviceName, useResultsForFirstService } = saveAdditionalResultsInFirstService;
209
+ const dataFromAdditionalService = resultsByService[serviceName];
210
+ if (dataFromAdditionalService?.items?.length) {
211
+ await this.dataEntityService.bulkCreate(dataFromAdditionalService.items, saveOptions);
212
+ if (useResultsForFirstService && !hasFirstServiceResult) {
213
+ result = dataFromAdditionalService;
214
+ }
215
+ }
216
+ }
217
+ return {
218
+ result,
219
+ resultsByService
220
+ };
221
+ }
222
+
223
+ public findOne(
224
+ // eslint-disable-next-line no-unused-vars
225
+ options: DomainFindOneOptions,
226
+ // eslint-disable-next-line no-unused-vars
227
+ privateOptions?: DomainFindPrivateOptions
228
+ ): Promise<DomainFindOneResult<Entity>>;
229
+ async findOne(
230
+ options: DomainFindOneOptions,
231
+ privateOptions?: DomainFindPrivateOptions
232
+ ): Promise<DomainFindOneResult<Entity>> {
233
+ if (!this.defaultMethods?.includes(DomainMethod.FindOne)) {
234
+ throw new ApplicationError(`Method findOne not implemented for class ${typeof this}.`);
235
+ }
236
+ const {
237
+ optionsOverridesByService,
238
+ dataServices = [DomainDataEntityServiceType.Main],
239
+ saveAdditionalResultsInFirstService,
240
+ ...otherOptions
241
+ } = options || {};
242
+ const [firstServiceName, ...otherServiceNames] = dataServices;
243
+ let result: Entity | null = await this.getDataService(firstServiceName).findOne(otherOptions, privateOptions);
244
+ const hasFirstServiceResult = typeof result !== 'undefined' && result !== null;
245
+ const resultsByService = await this.runMethodInAdditionalServices<Entity | null>(otherServiceNames || [], {
246
+ firstServiceResult: result,
247
+ hasFirstServiceResult,
248
+ methodArgs: [otherOptions, privateOptions],
249
+ methodName: 'findOne',
250
+ optionsArgIndex: 0,
251
+ optionsOverridesByService
252
+ });
253
+ if (saveAdditionalResultsInFirstService && resultsByService) {
254
+ const { saveOptions, serviceName, useResultsForFirstService } = saveAdditionalResultsInFirstService;
255
+ const dataFromAdditionalService = resultsByService[serviceName];
256
+ if (dataFromAdditionalService) {
257
+ await this.dataEntityService.create(dataFromAdditionalService, saveOptions);
258
+ if (useResultsForFirstService && !hasFirstServiceResult) {
259
+ result = dataFromAdditionalService;
260
+ }
261
+ }
262
+ }
263
+ return {
264
+ result,
265
+ resultsByService
266
+ };
267
+ }
268
+
269
+ protected getDataService(serviceName: DomainDataEntityServiceType.Main | string): DataEntityService<Entity> {
270
+ if (serviceName === DomainDataEntityServiceType.Main) {
271
+ return this.dataEntityService;
272
+ }
273
+ const service = this.additionalDataEntityServices?.[serviceName];
274
+ if (!service) {
275
+ throw new ApplicationError(
276
+ `DataEntityService ${serviceName} does not exist for DomainEntityService ${this.dataEntityService.getEntityName(true) || '(no entity name)'}.`
277
+ );
278
+ }
279
+ return service as DataEntityService<Entity>;
280
+ }
281
+
282
+ protected async runMethodInAdditionalServices<ServiceReturnData>(
283
+ serviceNames: string[],
284
+ options: DomainRunMethodInAdditionalServicesOptions<unknown>
285
+ ): Promise<GenericObject<ServiceReturnData> | undefined> {
286
+ if (!serviceNames.length) {
287
+ return undefined;
288
+ }
289
+ const {
290
+ firstServiceResult,
291
+ hasFirstServiceResult,
292
+ methodArgs = [],
293
+ methodName,
294
+ optionsArgIndex,
295
+ optionsOverridesByService = {}
296
+ } = options;
297
+ const returnDataByService: GenericObject<ServiceReturnData> = {};
298
+ if (!this.additionalDataEntityServices) {
299
+ throw new ApplicationError(
300
+ `No additional DataEntityServices exist for DomainEntityService ${this.dataEntityService.getEntityName(true) || '(no entity name)'}.`
301
+ );
302
+ }
303
+ if (
304
+ Object.keys(optionsOverridesByService).length &&
305
+ (typeof optionsArgIndex === 'undefined' || optionsArgIndex < 0 || optionsArgIndex > methodArgs.length - 1)
306
+ ) {
307
+ throw new ApplicationError(
308
+ `Invalid optionsArgIndex value ${optionsArgIndex} provided for DomainEntityService ${this.dataEntityService.getEntityName(true) || '(no entity name)'}.}.`
309
+ );
310
+ }
311
+ for (const i in serviceNames) {
312
+ const serviceName = serviceNames[i];
313
+ const service = this.getDataService(serviceName);
314
+ if (!service) {
315
+ throw new ApplicationError(
316
+ `DataEntityService ${serviceName} does not exist for DomainEntityService ${this.dataEntityService.getEntityName(true) || '(no entity name)'}.`
317
+ );
318
+ }
319
+ const serviceMethodOptionsOverrides = optionsOverridesByService[serviceName] || {};
320
+ const {
321
+ filterByFirstServiceResultFields,
322
+ runOnNoFirstServiceResultOnly = true,
323
+ ...actualMethodOptionsOverrides
324
+ } = serviceMethodOptionsOverrides;
325
+ // be extra careful when working with data that has TTL as the main service,
326
+ // since there is no way to check for limited results here.
327
+ if (
328
+ (runOnNoFirstServiceResultOnly === true || runOnNoFirstServiceResultOnly === 'true') &&
329
+ hasFirstServiceResult
330
+ ) {
331
+ continue;
332
+ }
333
+ const serviceMethodArgs = ld.cloneDeep(methodArgs);
334
+ if (typeof serviceMethodArgs[optionsArgIndex!] === 'undefined') {
335
+ if (optionsArgIndex! > serviceMethodArgs.length - 1) {
336
+ serviceMethodArgs.push(actualMethodOptionsOverrides);
337
+ } else {
338
+ serviceMethodArgs[optionsArgIndex!] = actualMethodOptionsOverrides;
339
+ }
340
+ } else {
341
+ serviceMethodArgs[optionsArgIndex!] = {
342
+ ...(serviceMethodArgs[optionsArgIndex!] as object),
343
+ ...actualMethodOptionsOverrides
344
+ };
345
+ }
346
+ if (filterByFirstServiceResultFields && Object.keys(filterByFirstServiceResultFields).length) {
347
+ if (!hasFirstServiceResult) {
348
+ continue;
349
+ }
350
+ const filters: GenericObject = {};
351
+ const resultItems: GenericObject[] = (firstServiceResult as { items?: GenericObject[] }).items || [
352
+ firstServiceResult as GenericObject
353
+ ];
354
+ resultItems.forEach(resultItem => {
355
+ if (!resultItem) {
356
+ return;
357
+ }
358
+ for (const sourceFieldName in filterByFirstServiceResultFields) {
359
+ const fieldValue = resultItem[sourceFieldName];
360
+ const targetFieldName = filterByFirstServiceResultFields[sourceFieldName];
361
+ if (typeof fieldValue === 'undefined') {
362
+ return;
363
+ }
364
+ if (!filters[targetFieldName]) {
365
+ filters[targetFieldName] = [];
366
+ }
367
+ (filters[targetFieldName] as unknown[]).push(fieldValue);
368
+ }
369
+ });
370
+ if (Object.keys(filters).length) {
371
+ const serviceMethodOptions = serviceMethodArgs[optionsArgIndex!] as GenericObject & {
372
+ filters?: GenericObject;
373
+ };
374
+ serviceMethodArgs[optionsArgIndex!] = {
375
+ ...serviceMethodOptions,
376
+ filters: {
377
+ ...ld.omit(serviceMethodOptions.filters || {}, ['page', 'perPage']),
378
+ ...filters
379
+ },
380
+ findAll: true
381
+ };
382
+ }
383
+ }
384
+ returnDataByService[serviceName] = (await (
385
+ service[methodName as keyof DataEntityService<Entity>] as unknown as (
386
+ ..._args: unknown[]
387
+ ) => Promise<ServiceReturnData>
388
+ ).apply(service, serviceMethodArgs)) as ServiceReturnData;
389
+ }
390
+ return returnDataByService;
391
+ }
392
+
393
+ public update(
394
+ // eslint-disable-next-line no-unused-vars
395
+ data: Data['Update'],
396
+ // eslint-disable-next-line no-unused-vars
397
+ options: DomainUpdateOptions,
398
+ // eslint-disable-next-line no-unused-vars
399
+ privateOptions?: DomainUpdatePrivateOptions
400
+ ): Promise<DomainUpdateResult<Entity>>;
401
+ async update(
402
+ data: Data['Update'],
403
+ options: DomainUpdateOptions,
404
+ privateOptions?: DomainUpdatePrivateOptions
405
+ ): Promise<DomainUpdateResult<Entity>> {
406
+ if (!this.defaultMethods?.includes(DomainMethod.Update)) {
407
+ throw new ApplicationError(`Method update not implemented for class ${typeof this}.`);
408
+ }
409
+ const { optionsOverridesByService, dataServices = [DomainDataEntityServiceType.Main], ...otherOptions } = options;
410
+ const [firstServiceName, ...otherServiceNames] = dataServices;
411
+ const result = await this.getDataService(firstServiceName).update(data, otherOptions, privateOptions);
412
+ return {
413
+ result,
414
+ resultsByService: await this.runMethodInAdditionalServices(otherServiceNames || [], {
415
+ firstServiceResult: result,
416
+ hasFirstServiceResult: !!result.count,
417
+ methodArgs: [data, otherOptions, privateOptions],
418
+ methodName: 'update',
419
+ optionsArgIndex: 1,
420
+ optionsOverridesByService
421
+ })
422
+ };
423
+ }
424
+ }
@@ -0,0 +1,2 @@
1
+ export * from './domain.entity.service';
2
+ export * from './domain.entity.service.definitions';