@ruiapp/rapid-core 0.0.1

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 (134) hide show
  1. package/dist/bootstrapApplicationConfig.d.ts +3 -0
  2. package/dist/core/eventManager.d.ts +7 -0
  3. package/dist/core/http-types.d.ts +3 -0
  4. package/dist/core/httpHandler.d.ts +18 -0
  5. package/dist/core/plugin.d.ts +6 -0
  6. package/dist/core/pluginManager.d.ts +27 -0
  7. package/dist/core/request.d.ts +15 -0
  8. package/dist/core/response.d.ts +17 -0
  9. package/dist/core/routeContext.d.ts +17 -0
  10. package/dist/core/routesBuilder.d.ts +4 -0
  11. package/dist/core/server.d.ts +83 -0
  12. package/dist/dataAccess/dataAccessor.d.ts +20 -0
  13. package/dist/dataAccess/entityManager.d.ts +6 -0
  14. package/dist/dataAccess/entityMapper.d.ts +3 -0
  15. package/dist/dataAccess/filterHelper.d.ts +2 -0
  16. package/dist/dataAccess/propertyMapper.d.ts +3 -0
  17. package/dist/deno-std/assert/assert.d.ts +2 -0
  18. package/dist/deno-std/assert/assertion_error.d.ts +4 -0
  19. package/dist/deno-std/datetime/to_imf.d.ts +17 -0
  20. package/dist/deno-std/http/cookie.d.ts +134 -0
  21. package/dist/helpers/entityHelpers.d.ts +1 -0
  22. package/dist/helpers/inputHelper.d.ts +1 -0
  23. package/dist/helpers/runCollectionEntityHttpHandler.d.ts +5 -0
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.js +3590 -0
  26. package/dist/plugins/authManager/httpHandlers/createSession.d.ts +8 -0
  27. package/dist/plugins/authManager/httpHandlers/deleteSession.d.ts +4 -0
  28. package/dist/plugins/authManager/httpHandlers/getMyProfile.d.ts +4 -0
  29. package/dist/plugins/authManager/httpHandlers/index.d.ts +5 -0
  30. package/dist/plugins/authManager/mod.d.ts +16 -0
  31. package/dist/plugins/authManager/models/AccessToken.d.ts +3 -0
  32. package/dist/plugins/authManager/models/index.d.ts +2 -0
  33. package/dist/plugins/authManager/routes/getMyProfile.d.ts +3 -0
  34. package/dist/plugins/authManager/routes/index.d.ts +2 -0
  35. package/dist/plugins/authManager/routes/signin.d.ts +3 -0
  36. package/dist/plugins/authManager/routes/signout.d.ts +3 -0
  37. package/dist/plugins/dataManager/httpHandlers/addEntityRelations.d.ts +4 -0
  38. package/dist/plugins/dataManager/httpHandlers/countCollectionEntities.d.ts +4 -0
  39. package/dist/plugins/dataManager/httpHandlers/createCollectionEntitiesBatch.d.ts +4 -0
  40. package/dist/plugins/dataManager/httpHandlers/createCollectionEntity.d.ts +4 -0
  41. package/dist/plugins/dataManager/httpHandlers/deleteCollectionEntityById.d.ts +4 -0
  42. package/dist/plugins/dataManager/httpHandlers/findCollectionEntities.d.ts +4 -0
  43. package/dist/plugins/dataManager/httpHandlers/findCollectionEntityById.d.ts +4 -0
  44. package/dist/plugins/dataManager/httpHandlers/queryDatabase.d.ts +4 -0
  45. package/dist/plugins/dataManager/httpHandlers/removeEntityRelations.d.ts +4 -0
  46. package/dist/plugins/dataManager/httpHandlers/updateCollectionEntityById.d.ts +4 -0
  47. package/dist/plugins/dataManager/mod.d.ts +16 -0
  48. package/dist/plugins/metaManager/httpHandlers/getMetaModelDetail.d.ts +4 -0
  49. package/dist/plugins/metaManager/httpHandlers/listMetaModels.d.ts +4 -0
  50. package/dist/plugins/metaManager/mod.d.ts +15 -0
  51. package/dist/plugins/routeManager/httpHandlers/httpProxy.d.ts +4 -0
  52. package/dist/plugins/routeManager/httpHandlers/listMetaRoutes.d.ts +4 -0
  53. package/dist/plugins/routeManager/mod.d.ts +15 -0
  54. package/dist/plugins/webhooks/mod.d.ts +24 -0
  55. package/dist/plugins/webhooks/pluginConfig.d.ts +48 -0
  56. package/dist/polyfill.d.ts +1 -0
  57. package/dist/proxy/mod.d.ts +13 -0
  58. package/dist/proxy/types.d.ts +17 -0
  59. package/dist/queryBuilder/index.d.ts +1 -0
  60. package/dist/queryBuilder/queryBuilder.d.ts +34 -0
  61. package/dist/server.d.ts +31 -0
  62. package/dist/types.d.ts +327 -0
  63. package/dist/utilities/httpUtility.d.ts +1 -0
  64. package/dist/utilities/jwtUtility.d.ts +8 -0
  65. package/dist/utilities/rapidUtility.d.ts +2 -0
  66. package/dist/utilities/typeUtility.d.ts +3 -0
  67. package/package.json +29 -0
  68. package/rollup.config.js +20 -0
  69. package/src/bootstrapApplicationConfig.ts +524 -0
  70. package/src/core/eventManager.ts +21 -0
  71. package/src/core/http-types.ts +4 -0
  72. package/src/core/httpHandler.ts +29 -0
  73. package/src/core/plugin.ts +13 -0
  74. package/src/core/pluginManager.ts +143 -0
  75. package/src/core/request.ts +23 -0
  76. package/src/core/response.ts +77 -0
  77. package/src/core/routeContext.ts +38 -0
  78. package/src/core/routesBuilder.ts +86 -0
  79. package/src/core/server.ts +144 -0
  80. package/src/dataAccess/dataAccessor.ts +110 -0
  81. package/src/dataAccess/entityManager.ts +651 -0
  82. package/src/dataAccess/entityMapper.ts +74 -0
  83. package/src/dataAccess/filterHelper.ts +47 -0
  84. package/src/dataAccess/propertyMapper.ts +27 -0
  85. package/src/deno-std/assert/assert.ts +9 -0
  86. package/src/deno-std/assert/assertion_error.ts +7 -0
  87. package/src/deno-std/datetime/to_imf.ts +47 -0
  88. package/src/deno-std/http/cookie.ts +398 -0
  89. package/src/helpers/entityHelpers.ts +24 -0
  90. package/src/helpers/inputHelper.ts +11 -0
  91. package/src/helpers/runCollectionEntityHttpHandler.ts +34 -0
  92. package/src/index.ts +12 -0
  93. package/src/plugins/authManager/httpHandlers/createSession.ts +57 -0
  94. package/src/plugins/authManager/httpHandlers/deleteSession.ts +22 -0
  95. package/src/plugins/authManager/httpHandlers/getMyProfile.ts +43 -0
  96. package/src/plugins/authManager/httpHandlers/index.ts +10 -0
  97. package/src/plugins/authManager/mod.ts +56 -0
  98. package/src/plugins/authManager/models/AccessToken.ts +56 -0
  99. package/src/plugins/authManager/models/index.ts +5 -0
  100. package/src/plugins/authManager/routes/getMyProfile.ts +15 -0
  101. package/src/plugins/authManager/routes/index.ts +9 -0
  102. package/src/plugins/authManager/routes/signin.ts +15 -0
  103. package/src/plugins/authManager/routes/signout.ts +15 -0
  104. package/src/plugins/dataManager/httpHandlers/addEntityRelations.ts +76 -0
  105. package/src/plugins/dataManager/httpHandlers/countCollectionEntities.ts +22 -0
  106. package/src/plugins/dataManager/httpHandlers/createCollectionEntitiesBatch.ts +57 -0
  107. package/src/plugins/dataManager/httpHandlers/createCollectionEntity.ts +43 -0
  108. package/src/plugins/dataManager/httpHandlers/deleteCollectionEntityById.ts +38 -0
  109. package/src/plugins/dataManager/httpHandlers/findCollectionEntities.ts +35 -0
  110. package/src/plugins/dataManager/httpHandlers/findCollectionEntityById.ts +30 -0
  111. package/src/plugins/dataManager/httpHandlers/queryDatabase.ts +29 -0
  112. package/src/plugins/dataManager/httpHandlers/removeEntityRelations.ts +72 -0
  113. package/src/plugins/dataManager/httpHandlers/updateCollectionEntityById.ts +53 -0
  114. package/src/plugins/dataManager/mod.ts +150 -0
  115. package/src/plugins/metaManager/httpHandlers/getMetaModelDetail.ts +14 -0
  116. package/src/plugins/metaManager/httpHandlers/listMetaModels.ts +13 -0
  117. package/src/plugins/metaManager/mod.ts +419 -0
  118. package/src/plugins/routeManager/httpHandlers/httpProxy.ts +15 -0
  119. package/src/plugins/routeManager/httpHandlers/listMetaRoutes.ts +13 -0
  120. package/src/plugins/routeManager/mod.ts +97 -0
  121. package/src/plugins/webhooks/mod.ts +144 -0
  122. package/src/plugins/webhooks/pluginConfig.ts +74 -0
  123. package/src/polyfill.ts +5 -0
  124. package/src/proxy/mod.ts +47 -0
  125. package/src/proxy/types.ts +21 -0
  126. package/src/queryBuilder/index.ts +1 -0
  127. package/src/queryBuilder/queryBuilder.ts +424 -0
  128. package/src/server.ts +192 -0
  129. package/src/types.ts +438 -0
  130. package/src/utilities/httpUtility.ts +23 -0
  131. package/src/utilities/jwtUtility.ts +16 -0
  132. package/src/utilities/rapidUtility.ts +5 -0
  133. package/src/utilities/typeUtility.ts +11 -0
  134. package/tsconfig.json +19 -0
@@ -0,0 +1,651 @@
1
+ import * as _ from "lodash";
2
+ import {
3
+ CreateEntityOptions,
4
+ EntityFilterOperators,
5
+ EntityFilterOptions,
6
+ FindEntityOptions,
7
+ FindEntityOrderByOptions,
8
+ IRpdDataAccessor,
9
+ RpdDataModel,
10
+ RpdDataModelProperty,
11
+ UpdateEntityByIdOptions,
12
+ } from "~/types";
13
+ import { isNullOrUndefined } from "~/utilities/typeUtility";
14
+ import { mapDbRowToEntity, mapEntityToDbRow } from "./entityMapper";
15
+ import { mapPropertyNameToColumnName } from "./propertyMapper";
16
+ import { isRelationProperty } from "~/utilities/rapidUtility";
17
+ import { IRpdServer } from "~/core/server";
18
+
19
+ function convertToDataAccessOrderBy(model: RpdDataModel, orderByList?: FindEntityOrderByOptions[]) {
20
+ if (!orderByList) {
21
+ return orderByList;
22
+ }
23
+
24
+ return orderByList.map(orderBy => {
25
+ return {
26
+ field: mapPropertyNameToColumnName(model, orderBy.field),
27
+ desc: !!orderBy.desc,
28
+ } as FindEntityOrderByOptions
29
+ })
30
+ }
31
+
32
+ export async function findEntities(
33
+ server: IRpdServer,
34
+ dataAccessor: IRpdDataAccessor,
35
+ options: FindEntityOptions,
36
+ ) {
37
+ const model = dataAccessor.getModel();
38
+ const fieldsToSelect: string[] = [];
39
+ const relationPropertiesToSelect: RpdDataModelProperty[] = [];
40
+ if (options.properties) {
41
+ _.forEach(options.properties, (propertyCode: string) => {
42
+ const property = model.properties.find((e) => e.code === propertyCode);
43
+ if (!property) {
44
+ throw new Error(
45
+ `Collection '${model.namespace}.${model.singularCode}' does not have a property '${propertyCode}'.`,
46
+ );
47
+ }
48
+
49
+ if (isRelationProperty(property)) {
50
+ relationPropertiesToSelect.push(property);
51
+
52
+ if (property.relation === "one" && !property.linkTableName) {
53
+ if (!property.targetIdColumnName) {
54
+ throw new Error(
55
+ `'targetIdColumnName' should be configured for property '${propertyCode}' of model '${model.namespace}.${model.singularCode}'.`,
56
+ );
57
+ }
58
+ fieldsToSelect.push(property.targetIdColumnName);
59
+ }
60
+ } else {
61
+ fieldsToSelect.push(property.columnName || property.code);
62
+ }
63
+ });
64
+ }
65
+
66
+ const processedFilters = await replaceFiltersWithFiltersOperator(
67
+ server,
68
+ model,
69
+ options.filters,
70
+ );
71
+
72
+ const findEntityOptions: FindEntityOptions = {
73
+ filters: processedFilters,
74
+ orderBy: convertToDataAccessOrderBy(model, options.orderBy),
75
+ pagination: options.pagination,
76
+ properties: fieldsToSelect,
77
+ };
78
+ const entities = await dataAccessor.find(findEntityOptions);
79
+ if (!entities.length) {
80
+ return entities;
81
+ }
82
+
83
+ const entityIds = entities.map((e) => e.id);
84
+ if (relationPropertiesToSelect.length) {
85
+ for (const relationProperty of relationPropertiesToSelect) {
86
+ const isManyRelation = relationProperty.relation === "many";
87
+
88
+ if (relationProperty.linkTableName) {
89
+ const targetModel = server.getModel({ singularCode: relationProperty.targetSingularCode! });
90
+ if (!targetModel) {
91
+ continue;
92
+ }
93
+
94
+ if (isManyRelation) {
95
+ const relationLinks = await findManyRelationLinksViaLinkTable(
96
+ server,
97
+ targetModel,
98
+ relationProperty,
99
+ entityIds,
100
+ );
101
+
102
+ _.forEach(entities, (entity: any) => {
103
+ entity[relationProperty.code] = _.filter(relationLinks, (link: any) => {
104
+ return link[relationProperty.selfIdColumnName!] == entity["id"];
105
+ }).map(link => mapDbRowToEntity(targetModel, link.targetEntity));
106
+ });
107
+ }
108
+ } else {
109
+ let relatedEntities: any[];
110
+ if (isManyRelation) {
111
+ relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode(
112
+ server,
113
+ model,
114
+ relationProperty,
115
+ entityIds,
116
+ );
117
+ } else {
118
+ const targetEntityIds = _.uniq(
119
+ _.reject(
120
+ _.map(
121
+ entities,
122
+ (entity: any) => entity[relationProperty.targetIdColumnName!],
123
+ ),
124
+ isNullOrUndefined,
125
+ ),
126
+ );
127
+ relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode(
128
+ server,
129
+ model,
130
+ relationProperty,
131
+ targetEntityIds,
132
+ );
133
+ }
134
+
135
+ const targetModel = server.getModel({
136
+ singularCode: relationProperty.targetSingularCode!,
137
+ });
138
+ entities.forEach((entity) => {
139
+ if (isManyRelation) {
140
+ entity[relationProperty.code] = _.filter(
141
+ relatedEntities,
142
+ (relatedEntity: any) => {
143
+ return relatedEntity[relationProperty.selfIdColumnName!] == entity.id;
144
+ },
145
+ ).map(item => mapDbRowToEntity(targetModel!, item));
146
+ } else {
147
+ entity[relationProperty.code] = mapDbRowToEntity(targetModel!, _.find(
148
+ relatedEntities,
149
+ (relatedEntity: any) => {
150
+ // TODO: id property code should be configurable.
151
+ return relatedEntity["id"] == entity[relationProperty.targetIdColumnName!];
152
+ },
153
+ ));
154
+ }
155
+ });
156
+ }
157
+ }
158
+ }
159
+ return entities.map(item => mapDbRowToEntity(model, item));
160
+ }
161
+
162
+ export async function findEntity(
163
+ server: IRpdServer,
164
+ dataAccessor: IRpdDataAccessor,
165
+ options: FindEntityOptions,
166
+ ) {
167
+ const entities = await findEntities(server, dataAccessor, options);
168
+ return _.first(entities);
169
+ }
170
+
171
+ async function replaceFiltersWithFiltersOperator(
172
+ server: IRpdServer,
173
+ model: RpdDataModel,
174
+ filters: EntityFilterOptions[] | undefined,
175
+ ) {
176
+ if (!filters || !filters.length) {
177
+ return [];
178
+ }
179
+
180
+ const replacedFilters: EntityFilterOptions[] = [];
181
+ for (const filter of filters) {
182
+ const { operator } = filter;
183
+ if (operator === "and" || operator === "or") {
184
+ filter.filters = await replaceFiltersWithFiltersOperator(
185
+ server,
186
+ model,
187
+ filter.filters,
188
+ );
189
+ replacedFilters.push(filter);
190
+ } else if (operator === "exists" || operator === "notExists") {
191
+ const relationProperty: RpdDataModelProperty = _.find(
192
+ model.properties,
193
+ (property: RpdDataModelProperty) => property.code === filter.field,
194
+ );
195
+ if (!relationProperty) {
196
+ throw new Error(
197
+ `Invalid filters. Property '${filter.field}' was not found in model '${model.namespace}.${model.singularCode}'`,
198
+ );
199
+ }
200
+ if (!isRelationProperty(relationProperty)) {
201
+ throw new Error(
202
+ `Invalid filters. Filter with 'existence' operator on property '${filter.field}' is not allowed. You can only use it on an relation property.`,
203
+ );
204
+ }
205
+
206
+ const relatedEntityFilters = filter.filters;
207
+ if (!relatedEntityFilters || !relatedEntityFilters.length) {
208
+ throw new Error(
209
+ `Invalid filters. 'filters' must be provided on filter with 'existence' operator.`,
210
+ );
211
+ }
212
+
213
+
214
+ if (relationProperty.relation === "one") {
215
+ // Optimize when filtering by id of related entity
216
+ if (relatedEntityFilters.length === 1) {
217
+ const relatedEntityIdFilter = relatedEntityFilters[0];
218
+ if (
219
+ (relatedEntityIdFilter.operator === "eq" ||
220
+ relatedEntityIdFilter.operator === "in") &&
221
+ relatedEntityIdFilter.field === "id"
222
+ ) {
223
+ let replacedOperator: EntityFilterOperators;
224
+ if (operator === "exists") {
225
+ replacedOperator = relatedEntityIdFilter.operator;
226
+ } else {
227
+ if (relatedEntityIdFilter.operator === "eq") {
228
+ replacedOperator = "ne";
229
+ } else {
230
+ replacedOperator = "notIn";
231
+ }
232
+ }
233
+ replacedFilters.push({
234
+ field: relationProperty.targetIdColumnName as string,
235
+ operator: replacedOperator,
236
+ value: relatedEntityIdFilter.value,
237
+ });
238
+ continue;
239
+ }
240
+ }
241
+
242
+ const dataAccessor = server.getDataAccessor({
243
+ singularCode: relationProperty.targetSingularCode as string,
244
+ });
245
+ const entities = await dataAccessor.find({
246
+ filters: filter.filters,
247
+ properties: ["id"],
248
+ });
249
+ const entityIds = _.map(entities, (entity: any) => entity["id"]);
250
+ replacedFilters.push({
251
+ field: relationProperty.targetIdColumnName as string,
252
+ operator: operator === "exists" ? "in" : "notIn",
253
+ value: entityIds,
254
+ });
255
+ } else if (!relationProperty.linkTableName) {
256
+ // many relation without link table.
257
+ if (!relationProperty.selfIdColumnName) {
258
+ throw new Error(
259
+ `Invalid filters. 'selfIdColumnName' of property '${relationProperty.code}' was not configured`,
260
+ );
261
+ }
262
+
263
+ const targetEntityDataAccessor = server.getDataAccessor({
264
+ singularCode: relationProperty.targetSingularCode as string,
265
+ });
266
+
267
+ const targetEntities = await targetEntityDataAccessor.find({
268
+ filters: filter.filters,
269
+ properties: [relationProperty.selfIdColumnName],
270
+ });
271
+ const selfEntityIds = _.map(targetEntities, (entity: any) => entity[relationProperty.selfIdColumnName!]);
272
+ replacedFilters.push({
273
+ field: "id",
274
+ operator: operator === "exists" ? "in" : "notIn",
275
+ value: selfEntityIds,
276
+ });
277
+ } else {
278
+ // many relation with link table
279
+ if (!relationProperty.selfIdColumnName) {
280
+ throw new Error(
281
+ `Invalid filters. 'selfIdColumnName' of property '${relationProperty.code}' was not configured`,
282
+ );
283
+ }
284
+
285
+ if (!relationProperty.targetIdColumnName) {
286
+ throw new Error(
287
+ `Invalid filters. 'targetIdColumnName' of property '${relationProperty.code}' was not configured`,
288
+ );
289
+ }
290
+
291
+ // 1. find target entities
292
+ // 2. find links
293
+ // 3. convert to 'in' filter
294
+ const targetEntityDataAccessor = server.getDataAccessor({
295
+ singularCode: relationProperty.targetSingularCode as string,
296
+ });
297
+
298
+ const targetEntities = await targetEntityDataAccessor.find({
299
+ filters: filter.filters,
300
+ properties: ['id'],
301
+ });
302
+ const targetEntityIds = _.map(targetEntities, (entity: any) => entity['id']);
303
+
304
+ const command = `SELECT * FROM ${server.queryBuilder.quoteTable({schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName!})} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName!)} = ANY($1::int[])`;
305
+ const params = [targetEntityIds];
306
+ const links = await server.queryDatabaseObject(command, params);
307
+ const selfEntityIds = links.map(link => link[relationProperty.selfIdColumnName!]);
308
+ replacedFilters.push({
309
+ field: "id",
310
+ operator: operator === "exists" ? "in" : "notIn",
311
+ value: selfEntityIds,
312
+ });
313
+ }
314
+ } else {
315
+ replacedFilters.push(filter);
316
+ }
317
+ }
318
+ return replacedFilters;
319
+ }
320
+
321
+ async function findManyRelationLinksViaLinkTable(
322
+ server: IRpdServer,
323
+ targetModel: RpdDataModel,
324
+ relationProperty: RpdDataModelProperty,
325
+ entityIds: any[],
326
+ ) {
327
+
328
+
329
+ const command = `SELECT * FROM ${server.queryBuilder.quoteTable({schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName!})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = ANY($1::int[])`;
330
+ const params = [entityIds];
331
+ const links = await server.queryDatabaseObject(command, params);
332
+ const targetEntityIds = links.map(link => link[relationProperty.targetIdColumnName!]);
333
+ const findEntityOptions: FindEntityOptions = {
334
+ filters: [
335
+ {
336
+ field: "id",
337
+ operator: "in",
338
+ value: targetEntityIds,
339
+ },
340
+ ],
341
+ };
342
+ const dataAccessor = server.getDataAccessor({
343
+ namespace: targetModel.namespace,
344
+ singularCode: targetModel.singularCode,
345
+ });
346
+ const targetEntities = await dataAccessor.find(findEntityOptions);
347
+
348
+ _.forEach(links, (link: any) => {
349
+ link.targetEntity = _.find(targetEntities, (e: any) => e["id"] == link[relationProperty.targetIdColumnName!]);
350
+ });
351
+
352
+ return links;
353
+ }
354
+
355
+ function findManyRelatedEntitiesViaIdPropertyCode(
356
+ server: IRpdServer,
357
+ model: RpdDataModel,
358
+ relationProperty: RpdDataModelProperty,
359
+ entityIds: any[],
360
+ ) {
361
+ const findEntityOptions: FindEntityOptions = {
362
+ filters: [
363
+ {
364
+ field: relationProperty.selfIdColumnName as string,
365
+ operator: "in",
366
+ value: entityIds,
367
+ },
368
+ ],
369
+ };
370
+ const dataAccessor = server.getDataAccessor({
371
+ singularCode: relationProperty.targetSingularCode as string,
372
+ });
373
+
374
+ return dataAccessor.find(findEntityOptions);
375
+ }
376
+
377
+ function findOneRelatedEntitiesViaIdPropertyCode(
378
+ server: IRpdServer,
379
+ model: RpdDataModel,
380
+ relationProperty: RpdDataModelProperty,
381
+ targetEntityIds: any[],
382
+ ) {
383
+ const findEntityOptions: FindEntityOptions = {
384
+ filters: [
385
+ {
386
+ field: "id",
387
+ operator: "in",
388
+ value: targetEntityIds,
389
+ },
390
+ ],
391
+ };
392
+ const dataAccessor = server.getDataAccessor({
393
+ singularCode: relationProperty.targetSingularCode as string,
394
+ });
395
+
396
+ return dataAccessor.find(findEntityOptions);
397
+ }
398
+
399
+ export async function createEntity(
400
+ server: IRpdServer,
401
+ dataAccessor: IRpdDataAccessor,
402
+ options: CreateEntityOptions,
403
+ ) {
404
+ const model = dataAccessor.getModel();
405
+ const { entity } = options;
406
+
407
+ const oneRelationPropertiesToCreate: RpdDataModelProperty[] = [];
408
+ const manyRelationPropertiesToCreate: RpdDataModelProperty[] = [];
409
+ _.keys(entity).forEach((propertyCode) => {
410
+ const property = model.properties.find((e) => e.code === propertyCode);
411
+ if (!property) {
412
+ // Unknown property
413
+ return;
414
+ }
415
+
416
+ if (isRelationProperty(property)) {
417
+ if (property.relation === "many") {
418
+ manyRelationPropertiesToCreate.push(property);
419
+ } else {
420
+ oneRelationPropertiesToCreate.push(property);
421
+ }
422
+ }
423
+ })
424
+
425
+ const row = mapEntityToDbRow(model, entity);
426
+
427
+ // save one-relation properties
428
+ for (const property of oneRelationPropertiesToCreate) {
429
+ const fieldValue = entity[property.code];
430
+ if (_.isObject(fieldValue)) {
431
+ if (!fieldValue["id"]) {
432
+ const targetDataAccessor = server.getDataAccessor({
433
+ singularCode: property.targetSingularCode!,
434
+ });
435
+ const targetEntity = fieldValue;
436
+ const newTargetEntity = await createEntity(server, targetDataAccessor, {
437
+ entity: targetEntity,
438
+ });
439
+ row[property.targetIdColumnName!] = newTargetEntity["id"];
440
+ } else {
441
+ row[property.targetIdColumnName!] = fieldValue["id"];
442
+ }
443
+ } else {
444
+ // fieldValue is id;
445
+ row[property.targetIdColumnName!] = fieldValue;
446
+ }
447
+ }
448
+
449
+ const newRow = await dataAccessor.create(row);
450
+ const newEntity = mapDbRowToEntity(model, newRow);
451
+
452
+
453
+ // save many-relation properties
454
+ for (const property of manyRelationPropertiesToCreate) {
455
+ newEntity[property.code] = [];
456
+
457
+ const targetDataAccessor = server.getDataAccessor({
458
+ singularCode: property.targetSingularCode!,
459
+ });
460
+
461
+ const relatedEntitiesToBeSaved = entity[property.code];
462
+ if (!_.isArray(relatedEntitiesToBeSaved)) {
463
+ throw new Error(`Value of field '${property.code}' should be an array.`);
464
+ }
465
+
466
+ for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
467
+ let relatedEntityId: any;
468
+ if (_.isObject(relatedEntityToBeSaved)) {
469
+ relatedEntityId = relatedEntityToBeSaved["id"];
470
+ if (!relatedEntityId) {
471
+ // related entity is to be created
472
+ const targetEntity = relatedEntityToBeSaved;
473
+ if (!property.linkTableName) {
474
+ targetEntity[property.selfIdColumnName!] = newEntity.id;
475
+ }
476
+ const newTargetEntity = await createEntity(server, targetDataAccessor, {
477
+ entity: targetEntity,
478
+ });
479
+
480
+ if (property.linkTableName) {
481
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({schema:property.linkSchema, tableName: property.linkTableName})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`
482
+ const params = [newEntity.id, newTargetEntity.id];
483
+ await server.queryDatabaseObject(command, params);
484
+ }
485
+
486
+ newEntity[property.code].push(newTargetEntity);
487
+ } else {
488
+ // related entity is existed
489
+ const targetEntity = await targetDataAccessor.findById(relatedEntityId);
490
+ if (!targetEntity) {
491
+ throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`)
492
+ }
493
+
494
+ if (property.linkTableName) {
495
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({schema:property.linkSchema, tableName: property.linkTableName})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`
496
+ const params = [newEntity.id, relatedEntityId];
497
+ await server.queryDatabaseObject(command, params);
498
+ } else {
499
+ await targetDataAccessor.updateById(targetEntity.id, {[property.selfIdColumnName!]: newEntity.id});
500
+ targetEntity[property.selfIdColumnName!] = newEntity.id;
501
+ }
502
+ newEntity[property.code].push(targetEntity);
503
+ }
504
+ } else {
505
+ // fieldValue is id
506
+ relatedEntityId = relatedEntityToBeSaved
507
+ const targetEntity = await targetDataAccessor.findById(relatedEntityId);
508
+ if (!targetEntity) {
509
+ throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`)
510
+ }
511
+
512
+ if (property.linkTableName) {
513
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({schema:property.linkSchema, tableName: property.linkTableName})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`
514
+ const params = [newEntity.id, relatedEntityId];
515
+ await server.queryDatabaseObject(command, params);
516
+ } else {
517
+ await targetDataAccessor.updateById(targetEntity.id, {[property.selfIdColumnName!]: newEntity.id});
518
+ targetEntity[property.selfIdColumnName!] = newEntity.id;
519
+ }
520
+
521
+ newEntity[property.code].push(targetEntity);
522
+ }
523
+ }
524
+ }
525
+
526
+
527
+ return newEntity;
528
+ }
529
+
530
+
531
+ export async function updateEntityById(
532
+ server: IRpdServer,
533
+ dataAccessor: IRpdDataAccessor,
534
+ options: UpdateEntityByIdOptions,
535
+ ) {
536
+ const model = dataAccessor.getModel();
537
+ const { id, entity, changes } = options;
538
+
539
+ const oneRelationPropertiesToUpdate: RpdDataModelProperty[] = [];
540
+ const manyRelationPropertiesToUpdate: RpdDataModelProperty[] = [];
541
+ _.keys(changes).forEach((propertyCode) => {
542
+ const property = model.properties.find((e) => e.code === propertyCode);
543
+ if (!property) {
544
+ // Unknown property
545
+ return;
546
+ }
547
+
548
+ if (isRelationProperty(property)) {
549
+ if (property.relation === "many") {
550
+ manyRelationPropertiesToUpdate.push(property);
551
+ } else {
552
+ oneRelationPropertiesToUpdate.push(property);
553
+ }
554
+ }
555
+ })
556
+
557
+ const row = mapEntityToDbRow(model, changes);
558
+ oneRelationPropertiesToUpdate.forEach(property => {
559
+ const fieldValue = changes[property.code];
560
+ if (_.isObject(fieldValue)) {
561
+ row[property.targetIdColumnName!] = fieldValue["id"];
562
+ } else {
563
+ row[property.targetIdColumnName!] = fieldValue;
564
+ }
565
+ })
566
+ let updatedRow = row;
567
+ if (Object.keys(row).length) {
568
+ updatedRow = await dataAccessor.updateById(id, row);
569
+ }
570
+ const updatedEntity = Object.assign({}, entity, updatedRow);
571
+
572
+ // save many-relation properties
573
+ for (const property of manyRelationPropertiesToUpdate) {
574
+ const relatedEntities: any[] = [];
575
+ const targetDataAccessor = server.getDataAccessor({
576
+ singularCode: property.targetSingularCode!,
577
+ });
578
+
579
+ const relatedEntitiesToBeSaved = changes[property.code];
580
+ if (!_.isArray(relatedEntitiesToBeSaved)) {
581
+ throw new Error(`Value of field '${property.code}' should be an array.`);
582
+ }
583
+
584
+ if (property.linkTableName) {
585
+ // TODO: should support removing relation
586
+ await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({schema:property.linkSchema, tableName: property.linkTableName})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`, [id]);
587
+ }
588
+
589
+ for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
590
+ let relatedEntityId: any;
591
+ if (_.isObject(relatedEntityToBeSaved)) {
592
+ relatedEntityId = relatedEntityToBeSaved["id"];
593
+ if (!relatedEntityId) {
594
+ // related entity is to be created
595
+ const targetEntity = relatedEntityToBeSaved;
596
+ if (!property.linkTableName) {
597
+ targetEntity[property.selfIdColumnName!] = id;
598
+ }
599
+ const newTargetEntity = await createEntity(server, targetDataAccessor, {
600
+ entity: targetEntity,
601
+ });
602
+
603
+ if (property.linkTableName) {
604
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({schema:property.linkSchema, tableName: property.linkTableName})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`
605
+ const params = [id, newTargetEntity.id];
606
+ await server.queryDatabaseObject(command, params);
607
+ }
608
+
609
+ relatedEntities.push(newTargetEntity);
610
+ } else {
611
+ // related entity is existed
612
+ const targetEntity = await targetDataAccessor.findById(relatedEntityId);
613
+ if (!targetEntity) {
614
+ throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`)
615
+ }
616
+
617
+ if (property.linkTableName) {
618
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({schema:property.linkSchema, tableName: property.linkTableName})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`
619
+ const params = [id, relatedEntityId];
620
+ await server.queryDatabaseObject(command, params);
621
+ } else {
622
+ await targetDataAccessor.updateById(targetEntity.id, {[property.selfIdColumnName!]: id});
623
+ targetEntity[property.selfIdColumnName!] = id;
624
+ }
625
+ relatedEntities.push(targetEntity);
626
+ }
627
+ } else {
628
+ // fieldValue is id
629
+ relatedEntityId = relatedEntityToBeSaved
630
+ const targetEntity = await targetDataAccessor.findById(relatedEntityId);
631
+ if (!targetEntity) {
632
+ throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`)
633
+ }
634
+
635
+ if (property.linkTableName) {
636
+ const command = `INSERT INTO ${server.queryBuilder.quoteTable({schema:property.linkSchema, tableName: property.linkTableName})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`
637
+ const params = [id, relatedEntityId];
638
+ await server.queryDatabaseObject(command, params);
639
+ } else {
640
+ await targetDataAccessor.updateById(targetEntity.id, {[property.selfIdColumnName!]: id});
641
+ targetEntity[property.selfIdColumnName!] = id;
642
+ }
643
+
644
+ relatedEntities.push(targetEntity);
645
+ }
646
+ }
647
+ updatedEntity[property.code] = relatedEntities;
648
+ }
649
+
650
+ return updatedEntity;
651
+ }