@ruiapp/rapid-core 0.11.5 → 0.11.7

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 (204) hide show
  1. package/CHANGELOG.md +11 -11
  2. package/dist/core/pluginManager.d.ts +2 -0
  3. package/dist/core/server.d.ts +3 -0
  4. package/dist/dataAccess/entityManager.d.ts +3 -3
  5. package/dist/index.js +118 -41
  6. package/dist/server.d.ts +1 -0
  7. package/dist/types.d.ts +2 -0
  8. package/package.json +1 -1
  9. package/rollup.config.js +16 -16
  10. package/src/bootstrapApplicationConfig.ts +904 -904
  11. package/src/core/ExpressionInterpreter.ts +45 -45
  12. package/src/core/actionHandler.ts +24 -24
  13. package/src/core/actionHandlers/ifActionHandler.ts +27 -27
  14. package/src/core/eventManager.ts +20 -20
  15. package/src/core/facility.ts +7 -7
  16. package/src/core/http/formDataParser.ts +87 -87
  17. package/src/core/http-types.ts +4 -4
  18. package/src/core/pluginManager.ts +202 -193
  19. package/src/core/providers/runtimeProvider.ts +5 -5
  20. package/src/core/request.ts +96 -96
  21. package/src/core/response.ts +84 -84
  22. package/src/core/routeContext.ts +127 -127
  23. package/src/core/routes/healthz.ts +20 -20
  24. package/src/core/routes/index.ts +3 -3
  25. package/src/core/routesBuilder.ts +129 -122
  26. package/src/core/server.ts +161 -158
  27. package/src/dataAccess/columnTypeMapper.ts +22 -22
  28. package/src/dataAccess/dataAccessTypes.ts +165 -165
  29. package/src/dataAccess/dataAccessor.ts +135 -135
  30. package/src/dataAccess/entityManager.ts +2031 -1986
  31. package/src/dataAccess/entityMapper.ts +111 -111
  32. package/src/dataAccess/entityValidator.ts +33 -33
  33. package/src/dataAccess/propertyMapper.ts +28 -28
  34. package/src/deno-std/assert/assert.ts +9 -9
  35. package/src/deno-std/assert/assertion_error.ts +7 -7
  36. package/src/deno-std/datetime/to_imf.ts +32 -32
  37. package/src/deno-std/encoding/base64.ts +141 -141
  38. package/src/deno-std/http/cookie.ts +372 -372
  39. package/src/facilities/cache/CacheFacilityTypes.ts +29 -29
  40. package/src/facilities/cache/CacheFactory.ts +31 -31
  41. package/src/facilities/cache/MemoryCache.ts +58 -58
  42. package/src/facilities/cache/MemoryCacheProvider.ts +15 -15
  43. package/src/facilities/log/LogFacility.ts +35 -35
  44. package/src/helpers/dbTransactionHelper.ts +42 -42
  45. package/src/helpers/entityHelper.ts +89 -89
  46. package/src/helpers/filterHelper.ts +148 -148
  47. package/src/helpers/inputHelper.ts +11 -11
  48. package/src/helpers/licenseHelper.ts +29 -29
  49. package/src/helpers/metaHelper.ts +111 -111
  50. package/src/helpers/runCollectionEntityActionHandler.ts +58 -58
  51. package/src/index.ts +85 -85
  52. package/src/plugins/auth/AuthPlugin.ts +107 -107
  53. package/src/plugins/auth/AuthPluginTypes.ts +11 -11
  54. package/src/plugins/auth/actionHandlers/changePassword.ts +61 -61
  55. package/src/plugins/auth/actionHandlers/createSession.ts +68 -68
  56. package/src/plugins/auth/actionHandlers/deleteSession.ts +18 -18
  57. package/src/plugins/auth/actionHandlers/getMyProfile.ts +28 -28
  58. package/src/plugins/auth/actionHandlers/index.ts +8 -8
  59. package/src/plugins/auth/actionHandlers/resetPassword.ts +45 -45
  60. package/src/plugins/auth/models/AccessToken.ts +56 -56
  61. package/src/plugins/auth/models/index.ts +3 -3
  62. package/src/plugins/auth/routes/changePassword.ts +15 -15
  63. package/src/plugins/auth/routes/getMyProfile.ts +15 -15
  64. package/src/plugins/auth/routes/index.ts +7 -7
  65. package/src/plugins/auth/routes/resetPassword.ts +15 -15
  66. package/src/plugins/auth/routes/signin.ts +15 -15
  67. package/src/plugins/auth/routes/signout.ts +15 -15
  68. package/src/plugins/auth/services/AuthService.ts +66 -66
  69. package/src/plugins/cronJob/CronJobPlugin.ts +104 -104
  70. package/src/plugins/cronJob/CronJobPluginTypes.ts +44 -44
  71. package/src/plugins/cronJob/actionHandlers/index.ts +4 -4
  72. package/src/plugins/cronJob/actionHandlers/runCronJob.ts +32 -32
  73. package/src/plugins/cronJob/entityWatchers/cronJobEntityWatchers.ts +24 -24
  74. package/src/plugins/cronJob/entityWatchers/index.ts +4 -4
  75. package/src/plugins/cronJob/models/CronJob.ts +129 -129
  76. package/src/plugins/cronJob/models/index.ts +3 -3
  77. package/src/plugins/cronJob/routes/index.ts +3 -3
  78. package/src/plugins/cronJob/routes/runCronJob.ts +15 -15
  79. package/src/plugins/cronJob/services/CronJobService.ts +255 -255
  80. package/src/plugins/dataManage/DataManagePlugin.ts +165 -165
  81. package/src/plugins/dataManage/actionHandlers/addEntityRelations.ts +15 -15
  82. package/src/plugins/dataManage/actionHandlers/countCollectionEntities.ts +17 -17
  83. package/src/plugins/dataManage/actionHandlers/createCollectionEntitiesBatch.ts +81 -81
  84. package/src/plugins/dataManage/actionHandlers/createCollectionEntity.ts +20 -20
  85. package/src/plugins/dataManage/actionHandlers/deleteCollectionEntities.ts +47 -45
  86. package/src/plugins/dataManage/actionHandlers/deleteCollectionEntityById.ts +20 -20
  87. package/src/plugins/dataManage/actionHandlers/findCollectionEntities.ts +27 -27
  88. package/src/plugins/dataManage/actionHandlers/findCollectionEntityById.ts +30 -30
  89. package/src/plugins/dataManage/actionHandlers/queryDatabase.ts +22 -22
  90. package/src/plugins/dataManage/actionHandlers/removeEntityRelations.ts +15 -15
  91. package/src/plugins/dataManage/actionHandlers/saveEntity.ts +46 -46
  92. package/src/plugins/dataManage/actionHandlers/updateCollectionEntityById.ts +38 -38
  93. package/src/plugins/entityAccessControl/EntityAccessControlPlugin.ts +146 -146
  94. package/src/plugins/fileManage/FileManagePlugin.ts +52 -52
  95. package/src/plugins/fileManage/actionHandlers/downloadDocument.ts +65 -65
  96. package/src/plugins/fileManage/actionHandlers/downloadFile.ts +44 -44
  97. package/src/plugins/fileManage/actionHandlers/uploadFile.ts +33 -33
  98. package/src/plugins/fileManage/routes/downloadDocument.ts +15 -15
  99. package/src/plugins/fileManage/routes/downloadFile.ts +15 -15
  100. package/src/plugins/fileManage/routes/index.ts +5 -5
  101. package/src/plugins/fileManage/routes/uploadFile.ts +15 -15
  102. package/src/plugins/license/LicensePlugin.ts +79 -79
  103. package/src/plugins/license/LicensePluginTypes.ts +95 -95
  104. package/src/plugins/license/LicenseService.ts +141 -141
  105. package/src/plugins/license/actionHandlers/getLicense.ts +18 -18
  106. package/src/plugins/license/actionHandlers/index.ts +5 -5
  107. package/src/plugins/license/actionHandlers/updateLicense.ts +24 -24
  108. package/src/plugins/license/helpers/certHelper.ts +21 -21
  109. package/src/plugins/license/helpers/cryptoHelper.ts +47 -47
  110. package/src/plugins/license/models/index.ts +1 -1
  111. package/src/plugins/license/routes/getLicense.ts +15 -15
  112. package/src/plugins/license/routes/index.ts +4 -4
  113. package/src/plugins/license/routes/updateLicense.ts +15 -15
  114. package/src/plugins/mail/MailPlugin.ts +74 -74
  115. package/src/plugins/mail/MailPluginTypes.ts +27 -27
  116. package/src/plugins/mail/MailService.ts +38 -38
  117. package/src/plugins/mail/actionHandlers/index.ts +3 -3
  118. package/src/plugins/mail/models/index.ts +1 -1
  119. package/src/plugins/mail/routes/index.ts +1 -1
  120. package/src/plugins/metaManage/MetaManagePlugin.ts +198 -198
  121. package/src/plugins/metaManage/actionHandlers/getMetaModelDetail.ts +10 -10
  122. package/src/plugins/metaManage/actionHandlers/listMetaModels.ts +10 -10
  123. package/src/plugins/metaManage/actionHandlers/listMetaRoutes.ts +10 -10
  124. package/src/plugins/metaManage/services/MetaService.ts +376 -376
  125. package/src/plugins/notification/NotificationPlugin.ts +68 -68
  126. package/src/plugins/notification/NotificationPluginTypes.ts +13 -13
  127. package/src/plugins/notification/NotificationService.ts +25 -25
  128. package/src/plugins/notification/actionHandlers/index.ts +3 -3
  129. package/src/plugins/notification/models/Notification.ts +60 -60
  130. package/src/plugins/notification/models/index.ts +3 -3
  131. package/src/plugins/notification/routes/index.ts +1 -1
  132. package/src/plugins/routeManage/RouteManagePlugin.ts +64 -64
  133. package/src/plugins/routeManage/actionHandlers/httpProxy.ts +13 -13
  134. package/src/plugins/routeManage/actionHandlers/mock.ts +28 -28
  135. package/src/plugins/sequence/SequencePlugin.ts +146 -146
  136. package/src/plugins/sequence/SequencePluginTypes.ts +69 -69
  137. package/src/plugins/sequence/SequenceService.ts +92 -92
  138. package/src/plugins/sequence/actionHandlers/generateSn.ts +32 -32
  139. package/src/plugins/sequence/actionHandlers/index.ts +4 -4
  140. package/src/plugins/sequence/models/SequenceAutoIncrementRecord.ts +49 -49
  141. package/src/plugins/sequence/models/SequenceRule.ts +42 -42
  142. package/src/plugins/sequence/models/index.ts +4 -4
  143. package/src/plugins/sequence/routes/generateSn.ts +15 -15
  144. package/src/plugins/sequence/routes/index.ts +3 -3
  145. package/src/plugins/sequence/segment-utility.ts +11 -11
  146. package/src/plugins/sequence/segments/autoIncrement.ts +90 -90
  147. package/src/plugins/sequence/segments/dayOfMonth.ts +19 -19
  148. package/src/plugins/sequence/segments/index.ts +9 -9
  149. package/src/plugins/sequence/segments/literal.ts +16 -16
  150. package/src/plugins/sequence/segments/month.ts +19 -19
  151. package/src/plugins/sequence/segments/parameter.ts +20 -20
  152. package/src/plugins/sequence/segments/year.ts +19 -19
  153. package/src/plugins/serverOperation/ServerOperationPlugin.ts +93 -93
  154. package/src/plugins/serverOperation/ServerOperationPluginTypes.ts +25 -25
  155. package/src/plugins/serverOperation/actionHandlers/index.ts +4 -4
  156. package/src/plugins/serverOperation/actionHandlers/runServerOperation.ts +20 -20
  157. package/src/plugins/setting/SettingPlugin.ts +68 -68
  158. package/src/plugins/setting/SettingPluginTypes.ts +37 -37
  159. package/src/plugins/setting/SettingService.ts +222 -222
  160. package/src/plugins/setting/actionHandlers/getSystemSettingValues.ts +30 -30
  161. package/src/plugins/setting/actionHandlers/getUserSettingValues.ts +38 -38
  162. package/src/plugins/setting/actionHandlers/index.ts +6 -6
  163. package/src/plugins/setting/actionHandlers/setSystemSettingValues.ts +30 -30
  164. package/src/plugins/setting/models/SystemSettingGroupSetting.ts +57 -57
  165. package/src/plugins/setting/models/SystemSettingItem.ts +48 -48
  166. package/src/plugins/setting/models/SystemSettingItemSetting.ts +73 -73
  167. package/src/plugins/setting/models/UserSettingGroupSetting.ts +57 -57
  168. package/src/plugins/setting/models/UserSettingItem.ts +55 -55
  169. package/src/plugins/setting/models/UserSettingItemSetting.ts +73 -73
  170. package/src/plugins/setting/models/index.ts +8 -8
  171. package/src/plugins/setting/routes/getSystemSettingValues.ts +15 -15
  172. package/src/plugins/setting/routes/getUserSettingValues.ts +15 -15
  173. package/src/plugins/setting/routes/index.ts +5 -5
  174. package/src/plugins/setting/routes/setSystemSettingValues.ts +15 -15
  175. package/src/plugins/stateMachine/StateMachinePlugin.ts +196 -196
  176. package/src/plugins/stateMachine/StateMachinePluginTypes.ts +48 -48
  177. package/src/plugins/stateMachine/actionHandlers/index.ts +4 -4
  178. package/src/plugins/stateMachine/actionHandlers/sendStateMachineEvent.ts +54 -54
  179. package/src/plugins/stateMachine/models/StateMachine.ts +42 -42
  180. package/src/plugins/stateMachine/models/index.ts +3 -3
  181. package/src/plugins/stateMachine/routes/index.ts +3 -3
  182. package/src/plugins/stateMachine/routes/sendStateMachineEvent.ts +15 -15
  183. package/src/plugins/stateMachine/stateMachineHelper.ts +36 -36
  184. package/src/plugins/webhooks/WebhooksPlugin.ts +148 -148
  185. package/src/plugins/webhooks/pluginConfig.ts +75 -75
  186. package/src/polyfill.ts +5 -5
  187. package/src/proxy/mod.ts +38 -38
  188. package/src/proxy/types.ts +21 -21
  189. package/src/queryBuilder/index.ts +1 -1
  190. package/src/queryBuilder/queryBuilder.ts +755 -755
  191. package/src/server.ts +567 -556
  192. package/src/types/cron-job-types.ts +71 -71
  193. package/src/types.ts +884 -882
  194. package/src/utilities/accessControlUtility.ts +33 -33
  195. package/src/utilities/entityUtility.ts +18 -18
  196. package/src/utilities/errorUtility.ts +15 -15
  197. package/src/utilities/fsUtility.ts +137 -137
  198. package/src/utilities/httpUtility.ts +19 -19
  199. package/src/utilities/jwtUtility.ts +26 -26
  200. package/src/utilities/passwordUtility.ts +26 -26
  201. package/src/utilities/pathUtility.ts +14 -14
  202. package/src/utilities/timeUtility.ts +17 -17
  203. package/src/utilities/typeUtility.ts +15 -15
  204. package/tsconfig.json +19 -19
package/src/server.ts CHANGED
@@ -1,556 +1,567 @@
1
- import DataAccessor from "./dataAccess/dataAccessor";
2
- import {
3
- GetDataAccessorOptions,
4
- GetModelOptions,
5
- IDatabaseAccessor,
6
- IDatabaseConfig,
7
- IQueryBuilder,
8
- IRpdDataAccessor,
9
- RpdApplicationConfig,
10
- RpdDataModel,
11
- RpdServerEventTypes,
12
- RapidServerConfig,
13
- RpdDataModelProperty,
14
- CreateEntityOptions,
15
- UpdateEntityByIdOptions,
16
- EntityWatchHandlerContext,
17
- EntityWatcherType,
18
- RpdEntityCreateEventPayload,
19
- EmitServerEventOptions,
20
- IDatabaseClient,
21
- RpdRouteActionConfig,
22
- RunActionHandlersOptions,
23
- } from "./types";
24
-
25
- import QueryBuilder from "./queryBuilder/queryBuilder";
26
- import PluginManager from "./core/pluginManager";
27
- import EventManager from "./core/eventManager";
28
- import { ActionHandler, ActionHandlerContext, IPluginActionHandler } from "./core/actionHandler";
29
- import { IRpdServer, RapidPlugin } from "./core/server";
30
- import { buildRoutes } from "./core/routesBuilder";
31
- import { Next, RouteContext } from "./core/routeContext";
32
- import { RapidRequest } from "./core/request";
33
- import bootstrapApplicationConfig from "./bootstrapApplicationConfig";
34
- import EntityManager from "./dataAccess/entityManager";
35
- import { bind, cloneDeep, find, forEach, isString, merge, omit } from "lodash";
36
- import { Logger } from "./facilities/log/LogFacility";
37
- import { FacilityFactory } from "./core/facility";
38
- import { CronJobConfiguration } from "./types/cron-job-types";
39
- import coreRoutes from "./core/routes";
40
- import { interpreteConfigExpressions } from "./core/ExpressionInterpreter";
41
- import ifActionHandler from "./core/actionHandlers/ifActionHandler";
42
-
43
- export interface InitServerOptions {
44
- logger: Logger;
45
- databaseAccessor: IDatabaseAccessor;
46
- databaseConfig: IDatabaseConfig;
47
- serverConfig: RapidServerConfig;
48
- applicationConfig?: RpdApplicationConfig;
49
- facilityFactories?: FacilityFactory[];
50
- plugins?: RapidPlugin[];
51
- entityWatchers?: EntityWatcherType[];
52
-
53
- /**
54
- * Application level cron jobs.
55
- */
56
- cronJobs?: CronJobConfiguration[];
57
-
58
- /**
59
- * All cron jobs of the server will be disabled if set `true`.
60
- */
61
- disableCronJobs?: boolean;
62
- }
63
-
64
- export class RapidServer implements IRpdServer {
65
- #logger: Logger;
66
- #facilityFactories: Map<string, FacilityFactory>;
67
- #pluginManager: PluginManager;
68
- #plugins: RapidPlugin[];
69
- #eventManager: EventManager<RpdServerEventTypes>;
70
- #middlewares: any[];
71
- #bootstrapApplicationConfig: RpdApplicationConfig;
72
- #applicationConfig: RpdApplicationConfig;
73
- #actionHandlersMapByCode: Map<string, ActionHandler>;
74
- #databaseAccessor: IDatabaseAccessor;
75
- #cachedDataAccessors: Map<string, DataAccessor>;
76
-
77
- #entityBeforeCreateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
78
- #entityCreateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
79
- #entityBeforeUpdateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
80
- #entityUpdateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
81
- #entityBeforeDeleteEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
82
- #entityDeleteEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
83
- #entityAddRelationsEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
84
- #entityRemoveRelationsEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
85
- #entityBeforeResponseEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
86
- #entityWatchers: EntityWatcherType[];
87
- #appEntityWatchers: EntityWatcherType[];
88
-
89
- #cronJobs: CronJobConfiguration[];
90
- #appCronJobs: CronJobConfiguration[];
91
- #disableCronJobs: boolean;
92
-
93
- #cachedEntityManager: Map<string, EntityManager>;
94
- #services: Map<string, any>;
95
- queryBuilder: IQueryBuilder;
96
- config: RapidServerConfig;
97
- databaseConfig: IDatabaseConfig;
98
- #buildedRoutes: (ctx: any, next: any) => any;
99
-
100
- constructor(options: InitServerOptions) {
101
- this.#logger = options.logger;
102
-
103
- this.#facilityFactories = new Map();
104
- if (options.facilityFactories) {
105
- forEach(options.facilityFactories, (factory) => {
106
- this.registerFacilityFactory(factory);
107
- });
108
- }
109
-
110
- this.#pluginManager = new PluginManager(this);
111
- this.#eventManager = new EventManager();
112
- this.#middlewares = [];
113
- this.#bootstrapApplicationConfig = options.applicationConfig || bootstrapApplicationConfig;
114
-
115
- this.#applicationConfig = {} as RpdApplicationConfig;
116
- this.#actionHandlersMapByCode = new Map();
117
- this.#databaseAccessor = options.databaseAccessor;
118
- this.#cachedDataAccessors = new Map();
119
- this.#cachedEntityManager = new Map();
120
-
121
- this.#entityBeforeCreateEventEmitters = new EventManager();
122
- this.#entityCreateEventEmitters = new EventManager();
123
- this.#entityBeforeUpdateEventEmitters = new EventManager();
124
- this.#entityUpdateEventEmitters = new EventManager();
125
- this.#entityBeforeDeleteEventEmitters = new EventManager();
126
- this.#entityDeleteEventEmitters = new EventManager();
127
- this.#entityAddRelationsEventEmitters = new EventManager();
128
- this.#entityRemoveRelationsEventEmitters = new EventManager();
129
- this.#entityBeforeResponseEventEmitters = new EventManager();
130
-
131
- this.registerEventHandler("entity.beforeCreate", this.#handleEntityEvent.bind(this, "entity.beforeCreate"));
132
- this.registerEventHandler("entity.create", this.#handleEntityEvent.bind(this, "entity.create"));
133
- this.registerEventHandler("entity.beforeUpdate", this.#handleEntityEvent.bind(this, "entity.beforeUpdate"));
134
- this.registerEventHandler("entity.update", this.#handleEntityEvent.bind(this, "entity.update"));
135
- this.registerEventHandler("entity.beforeDelete", this.#handleEntityEvent.bind(this, "entity.beforeDelete"));
136
- this.registerEventHandler("entity.delete", this.#handleEntityEvent.bind(this, "entity.delete"));
137
- this.registerEventHandler("entity.addRelations", this.#handleEntityEvent.bind(this, "entity.addRelations"));
138
- this.registerEventHandler("entity.removeRelations", this.#handleEntityEvent.bind(this, "entity.removeRelations"));
139
- this.registerEventHandler("entity.beforeResponse", this.#handleEntityEvent.bind(this, "entity.beforeResponse"));
140
-
141
- this.#entityWatchers = [];
142
- this.#appEntityWatchers = options.entityWatchers || [];
143
-
144
- this.#cronJobs = [];
145
- this.#appCronJobs = options.cronJobs || [];
146
- this.#disableCronJobs = !!options.disableCronJobs;
147
-
148
- this.#services = new Map();
149
-
150
- this.queryBuilder = new QueryBuilder({
151
- dbDefaultSchema: options.databaseConfig.dbDefaultSchema,
152
- });
153
- this.databaseConfig = options.databaseConfig;
154
- this.config = options.serverConfig;
155
-
156
- this.#plugins = options.plugins || [];
157
- }
158
-
159
- getLogger(): Logger {
160
- return this.#logger;
161
- }
162
-
163
- getApplicationConfig() {
164
- return this.#applicationConfig;
165
- }
166
-
167
- getDatabaseAccessor(): IDatabaseAccessor {
168
- return this.#databaseAccessor;
169
- }
170
-
171
- appendApplicationConfig(config: Partial<RpdApplicationConfig>) {
172
- const { models, dataDictionaries, routes } = config;
173
- if (models) {
174
- for (const model of models) {
175
- const originalModel = find(this.#applicationConfig.models, (item) => item.singularCode == model.singularCode);
176
- if (originalModel) {
177
- merge(
178
- originalModel,
179
- omit(model, ["id", "maintainedBy", "namespace", "singularCode", "pluralCode", "schema", "tableName", "properties", "extensions"]),
180
- );
181
- originalModel.name = model.name;
182
- const originalProperties = originalModel.properties;
183
- for (const property of model.properties) {
184
- const originalProperty = find(originalProperties, (item) => item.code == property.code);
185
- if (originalProperty) {
186
- originalProperty.name = property.name;
187
- } else {
188
- originalProperties.push(property);
189
- }
190
- }
191
- } else {
192
- this.#applicationConfig.models.push(model);
193
- }
194
- }
195
- }
196
-
197
- if (dataDictionaries) {
198
- for (const dataDictionary of dataDictionaries) {
199
- const originalDataDictionary = find(this.#applicationConfig.dataDictionaries, (item) => item.code == dataDictionary.code);
200
- if (originalDataDictionary) {
201
- originalDataDictionary.name = dataDictionary.name;
202
- originalDataDictionary.description = dataDictionary.description;
203
- originalDataDictionary.entries = dataDictionary.entries;
204
- } else {
205
- this.#applicationConfig.dataDictionaries.push(dataDictionary);
206
- }
207
- }
208
- }
209
- if (routes) {
210
- for (const route of routes) {
211
- const originalRoute = find(this.#applicationConfig.routes, (item) => item.code == route.code);
212
- if (originalRoute) {
213
- originalRoute.name = route.name;
214
- originalRoute.actions = route.actions;
215
- } else {
216
- this.#applicationConfig.routes.push(route);
217
- }
218
- }
219
- }
220
- }
221
-
222
- appendModelProperties(modelSingularCode: string, properties: RpdDataModelProperty[]) {
223
- const originalModel = find(this.#applicationConfig.models, (item) => item.singularCode == modelSingularCode);
224
- if (!originalModel) {
225
- throw new Error(`Cannot append model properties. Model '${modelSingularCode}' was not found.`);
226
- }
227
-
228
- const originalProperties = originalModel.properties;
229
- for (const property of properties) {
230
- const originalProperty = find(originalProperties, (item) => item.code == property.code);
231
- if (originalProperty) {
232
- originalProperty.name = property.name;
233
- } else {
234
- originalProperties.push(property);
235
- }
236
- }
237
- }
238
-
239
- registerActionHandler(plugin: RapidPlugin, options: IPluginActionHandler) {
240
- const handler = bind(options.handler, null, plugin);
241
- this.#actionHandlersMapByCode.set(options.code, handler);
242
- }
243
-
244
- getActionHandlerByCode(code: string) {
245
- return this.#actionHandlersMapByCode.get(code);
246
- }
247
-
248
- registerMiddleware(middleware: any) {
249
- this.#middlewares.push(middleware);
250
- }
251
-
252
- getDataAccessor<T = any>(options: GetDataAccessorOptions): IRpdDataAccessor<T> {
253
- const { namespace, singularCode } = options;
254
-
255
- let dataAccessor = this.#cachedDataAccessors.get(singularCode);
256
- if (dataAccessor) {
257
- return dataAccessor;
258
- }
259
-
260
- const model = this.getModel(options);
261
- if (!model) {
262
- throw new Error(`Data model ${namespace}.${singularCode} not found.`);
263
- }
264
-
265
- dataAccessor = new DataAccessor<T>(this, this.#databaseAccessor, {
266
- model,
267
- queryBuilder: this.queryBuilder as QueryBuilder,
268
- });
269
- this.#cachedDataAccessors.set(singularCode, dataAccessor);
270
- return dataAccessor;
271
- }
272
-
273
- getModel(options: GetModelOptions): RpdDataModel | undefined {
274
- if (options.namespace) {
275
- return this.#applicationConfig?.models.find((e) => e.namespace === options.namespace && e.singularCode === options.singularCode);
276
- }
277
-
278
- return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
279
- }
280
-
281
- getEntityManager<TEntity = any>(singularCode: string): EntityManager<TEntity> {
282
- let entityManager = this.#cachedEntityManager.get(singularCode);
283
- if (entityManager) {
284
- return entityManager;
285
- }
286
-
287
- const dataAccessor = this.getDataAccessor({ singularCode });
288
- entityManager = new EntityManager(this, dataAccessor);
289
- this.#cachedEntityManager.set(singularCode, entityManager);
290
- return entityManager;
291
- }
292
-
293
- registerEventHandler<K extends keyof RpdServerEventTypes>(eventName: K, listener: (...args: RpdServerEventTypes[K]) => void) {
294
- this.#eventManager.on(eventName, listener);
295
- }
296
-
297
- registerEntityWatcher(entityWatcher: EntityWatcherType) {
298
- this.#entityWatchers.push(entityWatcher);
299
- }
300
-
301
- async emitEvent<TEventName extends keyof RpdServerEventTypes>(event: EmitServerEventOptions<TEventName>) {
302
- const { eventName, payload, sender, routeContext: routerContext } = event;
303
- this.#logger.debug(`Emitting '${eventName}' event.`, { eventName });
304
- this.#logger.verbose(`Event payload: `, { payload });
305
- await this.#eventManager.emit<TEventName>(eventName, sender, payload as any, routerContext);
306
-
307
- // TODO: should move this logic into metaManager
308
- // if (
309
- // (eventName === "entity.create" || eventName === "entity.update" ||
310
- // eventName === "entity.delete") &&
311
- // payload.namespace === "meta"
312
- // ) {
313
- // await this.configureApplication();
314
- // }
315
- }
316
-
317
- registerService(name: string, service: any) {
318
- this.#services.set(name, service);
319
- }
320
-
321
- getService<TService>(name: string): TService {
322
- return this.#services.get(name);
323
- }
324
-
325
- registerCronJob(job: CronJobConfiguration): void {
326
- const jobDuplicate = find(this.#cronJobs, (item: CronJobConfiguration) => item.code === job.code);
327
- if (jobDuplicate) {
328
- this.#logger.warn(`Duplicated cron job with code "${job.code}"`);
329
- }
330
-
331
- this.#cronJobs.push(job);
332
- }
333
-
334
- listCronJobs() {
335
- return [...this.#cronJobs, ...this.#appCronJobs];
336
- }
337
-
338
- async start() {
339
- this.#logger.info("Starting rapid server...");
340
-
341
- // register core action handlers
342
- this.registerActionHandler(null, ifActionHandler);
343
-
344
- const pluginManager = this.#pluginManager;
345
- await pluginManager.loadPlugins(this.#plugins);
346
-
347
- await pluginManager.initPlugins();
348
-
349
- await pluginManager.registerMiddlewares();
350
- await pluginManager.registerActionHandlers();
351
- await pluginManager.registerEventHandlers();
352
- await pluginManager.registerMessageHandlers();
353
- await pluginManager.registerTaskProcessors();
354
-
355
- this.#entityWatchers = this.#entityWatchers.concat(this.#appEntityWatchers);
356
- for (const entityWatcher of this.#entityWatchers) {
357
- if (entityWatcher.eventName === "entity.beforeCreate") {
358
- this.#entityBeforeCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
359
- } else if (entityWatcher.eventName === "entity.create") {
360
- this.#entityCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
361
- } else if (entityWatcher.eventName === "entity.beforeUpdate") {
362
- this.#entityBeforeUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
363
- } else if (entityWatcher.eventName === "entity.update") {
364
- this.#entityUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
365
- } else if (entityWatcher.eventName === "entity.beforeDelete") {
366
- this.#entityBeforeDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
367
- } else if (entityWatcher.eventName === "entity.delete") {
368
- this.#entityDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
369
- } else if (entityWatcher.eventName === "entity.addRelations") {
370
- this.#entityAddRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
371
- } else if (entityWatcher.eventName === "entity.removeRelations") {
372
- this.#entityRemoveRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
373
- } else if (entityWatcher.eventName === "entity.beforeResponse") {
374
- this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
375
- }
376
- }
377
-
378
- this.#applicationConfig = cloneDeep(this.#bootstrapApplicationConfig) as RpdApplicationConfig;
379
- this.appendApplicationConfig({
380
- routes: coreRoutes,
381
- });
382
- await this.configureApplication();
383
- this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
384
-
385
- if (!this.#disableCronJobs) {
386
- await pluginManager.registerCronJobs();
387
- }
388
-
389
- this.#logger.info(`Rapid server ready.`);
390
- await pluginManager.onApplicationReady(this.#applicationConfig);
391
- }
392
-
393
- async configureApplication() {
394
- const pluginManager = this.#pluginManager;
395
- await pluginManager.onLoadingApplication(this.#applicationConfig);
396
- await pluginManager.configureModels(this.#applicationConfig);
397
- await pluginManager.configureModelProperties(this.#applicationConfig);
398
- await pluginManager.configureServices(this.#applicationConfig);
399
- await pluginManager.configureRoutes(this.#applicationConfig);
400
-
401
- // TODO: check application configuration.
402
-
403
- await pluginManager.onApplicationLoaded(this.#applicationConfig);
404
- }
405
-
406
- registerFacilityFactory(factory: FacilityFactory) {
407
- this.#facilityFactories.set(factory.name, factory);
408
- }
409
-
410
- async getFacility<TFacility = any, TOptions = any>(name: string, options?: TOptions, nullIfUnknownFacility?: boolean): Promise<TFacility> {
411
- const factory = this.#facilityFactories.get(name);
412
- if (!factory) {
413
- if (nullIfUnknownFacility) {
414
- return null;
415
- } else {
416
- throw new Error(`Failed to get facility. Unknown facility name: ${name}`);
417
- }
418
- }
419
-
420
- return await factory.createFacility(this, options);
421
- }
422
-
423
- async queryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient, dropErrorLog?: boolean): Promise<any[]> {
424
- try {
425
- return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
426
- } catch (err) {
427
- if (!dropErrorLog) {
428
- this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
429
- }
430
- throw err;
431
- }
432
- }
433
-
434
- async tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient, dropErrorLog?: boolean): Promise<any[]> {
435
- try {
436
- return await this.queryDatabaseObject(sql, params, client);
437
- } catch (err) {
438
- if (!dropErrorLog) {
439
- this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
440
- }
441
- }
442
-
443
- return [];
444
- }
445
-
446
- get middlewares() {
447
- return this.#middlewares;
448
- }
449
-
450
- async handleRequest(rapidRequest: RapidRequest, next: Next) {
451
- await rapidRequest.parseBody();
452
- const routeContext: RouteContext = new RouteContext(this, rapidRequest);
453
- const { response } = routeContext;
454
-
455
- try {
456
- await this.#pluginManager.onPrepareRouteContext(routeContext);
457
- await this.#buildedRoutes(routeContext, next);
458
- } catch (ex) {
459
- let error: any;
460
- if (isString(ex)) {
461
- error = {
462
- message: ex,
463
- };
464
- } else {
465
- error = { name: ex.name, message: ex.message, stack: ex.stack };
466
- }
467
- this.#logger.error("handle request error.", { error });
468
- response.json({ error }, 500);
469
- }
470
-
471
- if (!response.status && !response.body) {
472
- response.json(
473
- {
474
- error: {
475
- message: `No route handler was found to handle this request. ${rapidRequest.method} ${rapidRequest.url}`,
476
- },
477
- },
478
- 404,
479
- );
480
- }
481
- return response.getResponse();
482
- }
483
-
484
- async runActionHandlers(handlerContext: ActionHandlerContext, actions: RpdRouteActionConfig[]) {
485
- if (!actions || !actions.length) {
486
- return;
487
- }
488
-
489
- for (const action of actions) {
490
- const actionCode = action.code;
491
- interpreteConfigExpressions(action.config, handlerContext.vars);
492
-
493
- const handler = this.getActionHandlerByCode(actionCode);
494
- if (!handler) {
495
- throw new Error("Unknown handler: " + actionCode);
496
- }
497
-
498
- await this.beforeRunActionHandler(handlerContext, action);
499
- await handler(handlerContext, action.config);
500
- }
501
- }
502
-
503
- async beforeRunRouteActions(handlerContext: ActionHandlerContext) {
504
- await this.#pluginManager.beforeRunRouteActions(handlerContext);
505
- }
506
-
507
- async beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig) {
508
- await this.#pluginManager.beforeRunActionHandler(handlerContext, actionConfig);
509
- }
510
-
511
- async beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions) {
512
- await this.#pluginManager.beforeCreateEntity(model, options);
513
- }
514
-
515
- async beforeUpdateEntity(model: RpdDataModel, options: UpdateEntityByIdOptions, currentEntity: any) {
516
- await this.#pluginManager.beforeUpdateEntity(model, options, currentEntity);
517
- }
518
-
519
- async #handleEntityEvent(eventName: keyof RpdServerEventTypes, sender: RapidPlugin, payload: RpdEntityCreateEventPayload, routerContext?: RouteContext) {
520
- const { modelSingularCode, baseModelSingularCode } = payload;
521
- if (!routerContext) {
522
- routerContext = new RouteContext(this);
523
- }
524
- const entityWatchHandlerContext: EntityWatchHandlerContext<typeof eventName> = {
525
- server: this,
526
- payload,
527
- routerContext,
528
- };
529
-
530
- let emitter: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
531
- if (eventName === "entity.beforeCreate") {
532
- emitter = this.#entityBeforeCreateEventEmitters;
533
- } else if (eventName === "entity.create") {
534
- emitter = this.#entityCreateEventEmitters;
535
- } else if (eventName === "entity.beforeUpdate") {
536
- emitter = this.#entityBeforeUpdateEventEmitters;
537
- } else if (eventName === "entity.update") {
538
- emitter = this.#entityUpdateEventEmitters;
539
- } else if (eventName === "entity.beforeDelete") {
540
- emitter = this.#entityBeforeDeleteEventEmitters;
541
- } else if (eventName === "entity.delete") {
542
- emitter = this.#entityDeleteEventEmitters;
543
- } else if (eventName === "entity.addRelations") {
544
- emitter = this.#entityAddRelationsEventEmitters;
545
- } else if (eventName === "entity.removeRelations") {
546
- emitter = this.#entityRemoveRelationsEventEmitters;
547
- } else if (eventName === "entity.beforeResponse") {
548
- emitter = this.#entityBeforeResponseEventEmitters;
549
- }
550
-
551
- await emitter.emit(modelSingularCode, entityWatchHandlerContext);
552
- if (baseModelSingularCode) {
553
- await emitter.emit(baseModelSingularCode, entityWatchHandlerContext);
554
- }
555
- }
556
- }
1
+ import DataAccessor from "./dataAccess/dataAccessor";
2
+ import {
3
+ GetDataAccessorOptions,
4
+ GetModelOptions,
5
+ IDatabaseAccessor,
6
+ IDatabaseConfig,
7
+ IQueryBuilder,
8
+ IRpdDataAccessor,
9
+ RpdApplicationConfig,
10
+ RpdDataModel,
11
+ RpdServerEventTypes,
12
+ RapidServerConfig,
13
+ RpdDataModelProperty,
14
+ CreateEntityOptions,
15
+ UpdateEntityByIdOptions,
16
+ EntityWatchHandlerContext,
17
+ EntityWatcherType,
18
+ RpdEntityCreateEventPayload,
19
+ EmitServerEventOptions,
20
+ IDatabaseClient,
21
+ RpdRouteActionConfig,
22
+ RunActionHandlersOptions,
23
+ } from "./types";
24
+
25
+ import QueryBuilder from "./queryBuilder/queryBuilder";
26
+ import PluginManager from "./core/pluginManager";
27
+ import EventManager from "./core/eventManager";
28
+ import { ActionHandler, ActionHandlerContext, IPluginActionHandler } from "./core/actionHandler";
29
+ import { IRpdServer, RapidPlugin } from "./core/server";
30
+ import { buildRoutes } from "./core/routesBuilder";
31
+ import { Next, RouteContext } from "./core/routeContext";
32
+ import { RapidRequest } from "./core/request";
33
+ import bootstrapApplicationConfig from "./bootstrapApplicationConfig";
34
+ import EntityManager from "./dataAccess/entityManager";
35
+ import { bind, cloneDeep, find, forEach, isString, merge, omit } from "lodash";
36
+ import { Logger } from "./facilities/log/LogFacility";
37
+ import { FacilityFactory } from "./core/facility";
38
+ import { CronJobConfiguration } from "./types/cron-job-types";
39
+ import coreRoutes from "./core/routes";
40
+ import { interpreteConfigExpressions } from "./core/ExpressionInterpreter";
41
+ import ifActionHandler from "./core/actionHandlers/ifActionHandler";
42
+
43
+ export interface InitServerOptions {
44
+ logger: Logger;
45
+ databaseAccessor: IDatabaseAccessor;
46
+ databaseConfig: IDatabaseConfig;
47
+ serverConfig: RapidServerConfig;
48
+ applicationConfig?: RpdApplicationConfig;
49
+ facilityFactories?: FacilityFactory[];
50
+ plugins?: RapidPlugin[];
51
+ entityWatchers?: EntityWatcherType[];
52
+
53
+ /**
54
+ * Application level cron jobs.
55
+ */
56
+ cronJobs?: CronJobConfiguration[];
57
+
58
+ /**
59
+ * All cron jobs of the server will be disabled if set `true`.
60
+ */
61
+ disableCronJobs?: boolean;
62
+ }
63
+
64
+ export class RapidServer implements IRpdServer {
65
+ #logger: Logger;
66
+ #facilityFactories: Map<string, FacilityFactory>;
67
+ #pluginManager: PluginManager;
68
+ #plugins: RapidPlugin[];
69
+ #eventManager: EventManager<RpdServerEventTypes>;
70
+ #middlewares: any[];
71
+ #bootstrapApplicationConfig: RpdApplicationConfig;
72
+ #applicationConfig: RpdApplicationConfig;
73
+ #actionHandlersMapByCode: Map<string, ActionHandler>;
74
+ #databaseAccessor: IDatabaseAccessor;
75
+ #cachedDataAccessors: Map<string, DataAccessor>;
76
+
77
+ #entityBeforeCreateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
78
+ #entityCreateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
79
+ #entityBeforeUpdateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
80
+ #entityUpdateEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
81
+ #entityBeforeDeleteEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
82
+ #entityDeleteEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
83
+ #entityAddRelationsEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
84
+ #entityRemoveRelationsEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
85
+ #entityBeforeResponseEventEmitters: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
86
+ #entityWatchers: EntityWatcherType[];
87
+ #appEntityWatchers: EntityWatcherType[];
88
+
89
+ #cronJobs: CronJobConfiguration[];
90
+ #appCronJobs: CronJobConfiguration[];
91
+ #disableCronJobs: boolean;
92
+
93
+ #cachedEntityManager: Map<string, EntityManager>;
94
+ #services: Map<string, any>;
95
+ queryBuilder: IQueryBuilder;
96
+ config: RapidServerConfig;
97
+ databaseConfig: IDatabaseConfig;
98
+ #buildedRoutes: (ctx: any, next: any) => any;
99
+
100
+ constructor(options: InitServerOptions) {
101
+ this.#logger = options.logger;
102
+
103
+ this.#facilityFactories = new Map();
104
+ if (options.facilityFactories) {
105
+ forEach(options.facilityFactories, (factory) => {
106
+ this.registerFacilityFactory(factory);
107
+ });
108
+ }
109
+
110
+ this.#pluginManager = new PluginManager(this);
111
+ this.#eventManager = new EventManager();
112
+ this.#middlewares = [];
113
+ this.#bootstrapApplicationConfig = options.applicationConfig || bootstrapApplicationConfig;
114
+
115
+ this.#applicationConfig = {} as RpdApplicationConfig;
116
+ this.#actionHandlersMapByCode = new Map();
117
+ this.#databaseAccessor = options.databaseAccessor;
118
+ this.#cachedDataAccessors = new Map();
119
+ this.#cachedEntityManager = new Map();
120
+
121
+ this.#entityBeforeCreateEventEmitters = new EventManager();
122
+ this.#entityCreateEventEmitters = new EventManager();
123
+ this.#entityBeforeUpdateEventEmitters = new EventManager();
124
+ this.#entityUpdateEventEmitters = new EventManager();
125
+ this.#entityBeforeDeleteEventEmitters = new EventManager();
126
+ this.#entityDeleteEventEmitters = new EventManager();
127
+ this.#entityAddRelationsEventEmitters = new EventManager();
128
+ this.#entityRemoveRelationsEventEmitters = new EventManager();
129
+ this.#entityBeforeResponseEventEmitters = new EventManager();
130
+
131
+ this.registerEventHandler("entity.beforeCreate", this.#handleEntityEvent.bind(this, "entity.beforeCreate"));
132
+ this.registerEventHandler("entity.create", this.#handleEntityEvent.bind(this, "entity.create"));
133
+ this.registerEventHandler("entity.beforeUpdate", this.#handleEntityEvent.bind(this, "entity.beforeUpdate"));
134
+ this.registerEventHandler("entity.update", this.#handleEntityEvent.bind(this, "entity.update"));
135
+ this.registerEventHandler("entity.beforeDelete", this.#handleEntityEvent.bind(this, "entity.beforeDelete"));
136
+ this.registerEventHandler("entity.delete", this.#handleEntityEvent.bind(this, "entity.delete"));
137
+ this.registerEventHandler("entity.addRelations", this.#handleEntityEvent.bind(this, "entity.addRelations"));
138
+ this.registerEventHandler("entity.removeRelations", this.#handleEntityEvent.bind(this, "entity.removeRelations"));
139
+ this.registerEventHandler("entity.beforeResponse", this.#handleEntityEvent.bind(this, "entity.beforeResponse"));
140
+
141
+ this.#entityWatchers = [];
142
+ this.#appEntityWatchers = options.entityWatchers || [];
143
+
144
+ this.#cronJobs = [];
145
+ this.#appCronJobs = options.cronJobs || [];
146
+ this.#disableCronJobs = !!options.disableCronJobs;
147
+
148
+ this.#services = new Map();
149
+
150
+ this.queryBuilder = new QueryBuilder({
151
+ dbDefaultSchema: options.databaseConfig.dbDefaultSchema,
152
+ });
153
+ this.databaseConfig = options.databaseConfig;
154
+ this.config = options.serverConfig;
155
+
156
+ this.#plugins = options.plugins || [];
157
+ }
158
+
159
+ getLogger(): Logger {
160
+ return this.#logger;
161
+ }
162
+
163
+ getApplicationConfig() {
164
+ return this.#applicationConfig;
165
+ }
166
+
167
+ getDatabaseAccessor(): IDatabaseAccessor {
168
+ return this.#databaseAccessor;
169
+ }
170
+
171
+ appendApplicationConfig(config: Partial<RpdApplicationConfig>) {
172
+ const { models, dataDictionaries, routes } = config;
173
+ if (models) {
174
+ for (const model of models) {
175
+ const originalModel = find(this.#applicationConfig.models, (item) => item.singularCode == model.singularCode);
176
+ if (originalModel) {
177
+ merge(
178
+ originalModel,
179
+ omit(model, ["id", "maintainedBy", "namespace", "singularCode", "pluralCode", "schema", "tableName", "properties", "extensions"]),
180
+ );
181
+ originalModel.name = model.name;
182
+ const originalProperties = originalModel.properties;
183
+ for (const property of model.properties) {
184
+ const originalProperty = find(originalProperties, (item) => item.code == property.code);
185
+ if (originalProperty) {
186
+ originalProperty.name = property.name;
187
+ } else {
188
+ originalProperties.push(property);
189
+ }
190
+ }
191
+ } else {
192
+ this.#applicationConfig.models.push(model);
193
+ }
194
+ }
195
+ }
196
+
197
+ if (dataDictionaries) {
198
+ for (const dataDictionary of dataDictionaries) {
199
+ const originalDataDictionary = find(this.#applicationConfig.dataDictionaries, (item) => item.code == dataDictionary.code);
200
+ if (originalDataDictionary) {
201
+ originalDataDictionary.name = dataDictionary.name;
202
+ originalDataDictionary.description = dataDictionary.description;
203
+ originalDataDictionary.entries = dataDictionary.entries;
204
+ } else {
205
+ this.#applicationConfig.dataDictionaries.push(dataDictionary);
206
+ }
207
+ }
208
+ }
209
+ if (routes) {
210
+ for (const route of routes) {
211
+ const originalRoute = find(this.#applicationConfig.routes, (item) => item.code == route.code);
212
+ if (originalRoute) {
213
+ originalRoute.name = route.name;
214
+ originalRoute.actions = route.actions;
215
+ } else {
216
+ this.#applicationConfig.routes.push(route);
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ appendModelProperties(modelSingularCode: string, properties: RpdDataModelProperty[]) {
223
+ const originalModel = find(this.#applicationConfig.models, (item) => item.singularCode == modelSingularCode);
224
+ if (!originalModel) {
225
+ throw new Error(`Cannot append model properties. Model '${modelSingularCode}' was not found.`);
226
+ }
227
+
228
+ const originalProperties = originalModel.properties;
229
+ for (const property of properties) {
230
+ const originalProperty = find(originalProperties, (item) => item.code == property.code);
231
+ if (originalProperty) {
232
+ originalProperty.name = property.name;
233
+ } else {
234
+ originalProperties.push(property);
235
+ }
236
+ }
237
+ }
238
+
239
+ registerActionHandler(plugin: RapidPlugin, options: IPluginActionHandler) {
240
+ const handler = bind(options.handler, null, plugin);
241
+ this.#actionHandlersMapByCode.set(options.code, handler);
242
+ }
243
+
244
+ getActionHandlerByCode(code: string) {
245
+ return this.#actionHandlersMapByCode.get(code);
246
+ }
247
+
248
+ registerMiddleware(middleware: any) {
249
+ this.#middlewares.push(middleware);
250
+ }
251
+
252
+ getDataAccessor<T = any>(options: GetDataAccessorOptions): IRpdDataAccessor<T> {
253
+ const { namespace, singularCode } = options;
254
+
255
+ let dataAccessor = this.#cachedDataAccessors.get(singularCode);
256
+ if (dataAccessor) {
257
+ return dataAccessor;
258
+ }
259
+
260
+ const model = this.getModel(options);
261
+ if (!model) {
262
+ throw new Error(`Data model ${namespace}.${singularCode} not found.`);
263
+ }
264
+
265
+ dataAccessor = new DataAccessor<T>(this, this.#databaseAccessor, {
266
+ model,
267
+ queryBuilder: this.queryBuilder as QueryBuilder,
268
+ });
269
+ this.#cachedDataAccessors.set(singularCode, dataAccessor);
270
+ return dataAccessor;
271
+ }
272
+
273
+ getModel(options: GetModelOptions): RpdDataModel | undefined {
274
+ if (options.namespace) {
275
+ return this.#applicationConfig?.models.find((e) => e.namespace === options.namespace && e.singularCode === options.singularCode);
276
+ }
277
+
278
+ return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
279
+ }
280
+
281
+ getEntityManager<TEntity = any>(singularCode: string): EntityManager<TEntity> {
282
+ let entityManager = this.#cachedEntityManager.get(singularCode);
283
+ if (entityManager) {
284
+ return entityManager;
285
+ }
286
+
287
+ const dataAccessor = this.getDataAccessor({ singularCode });
288
+ entityManager = new EntityManager(this, dataAccessor);
289
+ this.#cachedEntityManager.set(singularCode, entityManager);
290
+ return entityManager;
291
+ }
292
+
293
+ registerEventHandler<K extends keyof RpdServerEventTypes>(eventName: K, listener: (...args: RpdServerEventTypes[K]) => void) {
294
+ this.#eventManager.on(eventName, listener);
295
+ }
296
+
297
+ registerEntityWatcher(entityWatcher: EntityWatcherType) {
298
+ this.#entityWatchers.push(entityWatcher);
299
+ }
300
+
301
+ async emitEvent<TEventName extends keyof RpdServerEventTypes>(event: EmitServerEventOptions<TEventName>) {
302
+ const { eventName, payload, sender, routeContext: routerContext } = event;
303
+ this.#logger.debug(`Emitting '${eventName}' event.`, { eventName });
304
+ this.#logger.verbose(`Event payload: `, { payload });
305
+ await this.#eventManager.emit<TEventName>(eventName, sender, payload as any, routerContext);
306
+
307
+ // TODO: should move this logic into metaManager
308
+ // if (
309
+ // (eventName === "entity.create" || eventName === "entity.update" ||
310
+ // eventName === "entity.delete") &&
311
+ // payload.namespace === "meta"
312
+ // ) {
313
+ // await this.configureApplication();
314
+ // }
315
+ }
316
+
317
+ registerService(name: string, service: any) {
318
+ this.#services.set(name, service);
319
+ }
320
+
321
+ getService<TService>(name: string): TService {
322
+ return this.#services.get(name);
323
+ }
324
+
325
+ registerCronJob(job: CronJobConfiguration): void {
326
+ const jobDuplicate = find(this.#cronJobs, (item: CronJobConfiguration) => item.code === job.code);
327
+ if (jobDuplicate) {
328
+ this.#logger.warn(`Duplicated cron job with code "${job.code}"`);
329
+ }
330
+
331
+ this.#cronJobs.push(job);
332
+ }
333
+
334
+ listCronJobs() {
335
+ return [...this.#cronJobs, ...this.#appCronJobs];
336
+ }
337
+
338
+ async start() {
339
+ this.#logger.info("Starting rapid server...");
340
+
341
+ // register core action handlers
342
+ this.registerActionHandler(null, ifActionHandler);
343
+
344
+ const pluginManager = this.#pluginManager;
345
+ await pluginManager.loadPlugins(this.#plugins);
346
+
347
+ await pluginManager.initPlugins();
348
+
349
+ await pluginManager.registerMiddlewares();
350
+ await pluginManager.registerActionHandlers();
351
+ await pluginManager.registerEventHandlers();
352
+ await pluginManager.registerMessageHandlers();
353
+ await pluginManager.registerTaskProcessors();
354
+
355
+ this.#entityWatchers = this.#entityWatchers.concat(this.#appEntityWatchers);
356
+ for (const entityWatcher of this.#entityWatchers) {
357
+ if (entityWatcher.eventName === "entity.beforeCreate") {
358
+ this.#entityBeforeCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
359
+ } else if (entityWatcher.eventName === "entity.create") {
360
+ this.#entityCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
361
+ } else if (entityWatcher.eventName === "entity.beforeUpdate") {
362
+ this.#entityBeforeUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
363
+ } else if (entityWatcher.eventName === "entity.update") {
364
+ this.#entityUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
365
+ } else if (entityWatcher.eventName === "entity.beforeDelete") {
366
+ this.#entityBeforeDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
367
+ } else if (entityWatcher.eventName === "entity.delete") {
368
+ this.#entityDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
369
+ } else if (entityWatcher.eventName === "entity.addRelations") {
370
+ this.#entityAddRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
371
+ } else if (entityWatcher.eventName === "entity.removeRelations") {
372
+ this.#entityRemoveRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
373
+ } else if (entityWatcher.eventName === "entity.beforeResponse") {
374
+ this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
375
+ }
376
+ }
377
+
378
+ this.#applicationConfig = cloneDeep(this.#bootstrapApplicationConfig) as RpdApplicationConfig;
379
+ this.appendApplicationConfig({
380
+ routes: coreRoutes,
381
+ });
382
+ await this.configureApplication();
383
+ this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
384
+
385
+ if (!this.#disableCronJobs) {
386
+ await pluginManager.registerCronJobs();
387
+ }
388
+
389
+ this.#logger.info(`Rapid server ready.`);
390
+ await pluginManager.onApplicationReady(this.#applicationConfig);
391
+ }
392
+
393
+ async configureApplication() {
394
+ const pluginManager = this.#pluginManager;
395
+ await pluginManager.onLoadingApplication(this.#applicationConfig);
396
+ await pluginManager.configureModels(this.#applicationConfig);
397
+ await pluginManager.configureModelProperties(this.#applicationConfig);
398
+ await pluginManager.configureServices(this.#applicationConfig);
399
+ await pluginManager.configureRoutes(this.#applicationConfig);
400
+
401
+ // TODO: check application configuration.
402
+
403
+ await pluginManager.onApplicationLoaded(this.#applicationConfig);
404
+ }
405
+
406
+ registerFacilityFactory(factory: FacilityFactory) {
407
+ this.#facilityFactories.set(factory.name, factory);
408
+ }
409
+
410
+ async getFacility<TFacility = any, TOptions = any>(name: string, options?: TOptions, nullIfUnknownFacility?: boolean): Promise<TFacility> {
411
+ const factory = this.#facilityFactories.get(name);
412
+ if (!factory) {
413
+ if (nullIfUnknownFacility) {
414
+ return null;
415
+ } else {
416
+ throw new Error(`Failed to get facility. Unknown facility name: ${name}`);
417
+ }
418
+ }
419
+
420
+ return await factory.createFacility(this, options);
421
+ }
422
+
423
+ async queryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient, dropErrorLog?: boolean): Promise<any[]> {
424
+ try {
425
+ return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
426
+ } catch (err) {
427
+ if (!dropErrorLog) {
428
+ this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
429
+ }
430
+ throw err;
431
+ }
432
+ }
433
+
434
+ async tryQueryDatabaseObject(sql: string, params?: unknown[] | Record<string, unknown>, client?: IDatabaseClient, dropErrorLog?: boolean): Promise<any[]> {
435
+ try {
436
+ return await this.queryDatabaseObject(sql, params, client);
437
+ } catch (err) {
438
+ if (!dropErrorLog) {
439
+ this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
440
+ }
441
+ }
442
+
443
+ return [];
444
+ }
445
+
446
+ get middlewares() {
447
+ return this.#middlewares;
448
+ }
449
+
450
+ async handleRequest(rapidRequest: RapidRequest, next: Next) {
451
+ await rapidRequest.parseBody();
452
+ const routeContext: RouteContext = new RouteContext(this, rapidRequest);
453
+ const { response } = routeContext;
454
+
455
+ try {
456
+ await this.#pluginManager.onPrepareRouteContext(routeContext);
457
+ await this.#buildedRoutes(routeContext, next);
458
+ } catch (ex) {
459
+ let error: any;
460
+ if (isString(ex)) {
461
+ error = {
462
+ message: ex,
463
+ };
464
+ } else {
465
+ error = { name: ex.name, message: ex.message, stack: ex.stack };
466
+ }
467
+ this.#logger.error("handle request error.", { error });
468
+ response.json({ error }, 500);
469
+ }
470
+
471
+ if (!response.status && !response.body) {
472
+ response.json(
473
+ {
474
+ error: {
475
+ message: `No route handler was found to handle this request. ${rapidRequest.method} ${rapidRequest.url}`,
476
+ },
477
+ },
478
+ 404,
479
+ );
480
+ }
481
+ return response.getResponse();
482
+ }
483
+
484
+ async runActionHandlers(handlerContext: ActionHandlerContext, actions: RpdRouteActionConfig[]) {
485
+ if (!actions || !actions.length) {
486
+ return;
487
+ }
488
+
489
+ for (const action of actions) {
490
+ const actionCode = action.code;
491
+ interpreteConfigExpressions(action.config, handlerContext.vars);
492
+
493
+ const handler = this.getActionHandlerByCode(actionCode);
494
+ if (!handler) {
495
+ throw new Error("Unknown handler: " + actionCode);
496
+ }
497
+ let err: any;
498
+ await this.beforeRunActionHandler(handlerContext, action);
499
+ try {
500
+ await handler(handlerContext, action.config);
501
+ } catch (error) {
502
+ err = error;
503
+ throw error;
504
+ } finally {
505
+ await this.afterRunActionHandler(handlerContext, action);
506
+ }
507
+ }
508
+ }
509
+
510
+ async beforeRunRouteActions(handlerContext: ActionHandlerContext) {
511
+ await this.#pluginManager.beforeRunRouteActions(handlerContext);
512
+ }
513
+
514
+ async beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig) {
515
+ await this.#pluginManager.beforeRunActionHandler(handlerContext, actionConfig);
516
+ }
517
+
518
+ async afterRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig, error?: Error) {
519
+ await this.#pluginManager.afterRunActionHandler(handlerContext, actionConfig, error);
520
+ }
521
+
522
+ async beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions) {
523
+ await this.#pluginManager.beforeCreateEntity(model, options);
524
+ }
525
+
526
+ async beforeUpdateEntity(model: RpdDataModel, options: UpdateEntityByIdOptions, currentEntity: any) {
527
+ await this.#pluginManager.beforeUpdateEntity(model, options, currentEntity);
528
+ }
529
+
530
+ async #handleEntityEvent(eventName: keyof RpdServerEventTypes, sender: RapidPlugin, payload: RpdEntityCreateEventPayload, routerContext?: RouteContext) {
531
+ const { modelSingularCode, baseModelSingularCode } = payload;
532
+ if (!routerContext) {
533
+ routerContext = new RouteContext(this);
534
+ }
535
+ const entityWatchHandlerContext: EntityWatchHandlerContext<typeof eventName> = {
536
+ server: this,
537
+ payload,
538
+ routerContext,
539
+ };
540
+
541
+ let emitter: EventManager<Record<string, [EntityWatchHandlerContext<any>]>>;
542
+ if (eventName === "entity.beforeCreate") {
543
+ emitter = this.#entityBeforeCreateEventEmitters;
544
+ } else if (eventName === "entity.create") {
545
+ emitter = this.#entityCreateEventEmitters;
546
+ } else if (eventName === "entity.beforeUpdate") {
547
+ emitter = this.#entityBeforeUpdateEventEmitters;
548
+ } else if (eventName === "entity.update") {
549
+ emitter = this.#entityUpdateEventEmitters;
550
+ } else if (eventName === "entity.beforeDelete") {
551
+ emitter = this.#entityBeforeDeleteEventEmitters;
552
+ } else if (eventName === "entity.delete") {
553
+ emitter = this.#entityDeleteEventEmitters;
554
+ } else if (eventName === "entity.addRelations") {
555
+ emitter = this.#entityAddRelationsEventEmitters;
556
+ } else if (eventName === "entity.removeRelations") {
557
+ emitter = this.#entityRemoveRelationsEventEmitters;
558
+ } else if (eventName === "entity.beforeResponse") {
559
+ emitter = this.#entityBeforeResponseEventEmitters;
560
+ }
561
+
562
+ await emitter.emit(modelSingularCode, entityWatchHandlerContext);
563
+ if (baseModelSingularCode) {
564
+ await emitter.emit(baseModelSingularCode, entityWatchHandlerContext);
565
+ }
566
+ }
567
+ }