@restorecommerce/resource-base-interface 1.6.4 → 1.7.0

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 (65) hide show
  1. package/eslint.config.mjs +22 -0
  2. package/lib/core/GraphResourcesServiceBase.d.ts +9 -5
  3. package/lib/core/GraphResourcesServiceBase.d.ts.map +1 -0
  4. package/lib/core/GraphResourcesServiceBase.js +79 -110
  5. package/lib/core/GraphResourcesServiceBase.js.map +1 -1
  6. package/lib/core/ResourcesAPI.d.ts +26 -19
  7. package/lib/core/ResourcesAPI.d.ts.map +1 -0
  8. package/lib/core/ResourcesAPI.js +275 -398
  9. package/lib/core/ResourcesAPI.js.map +1 -1
  10. package/lib/core/ServiceBase.d.ts +29 -10
  11. package/lib/core/ServiceBase.d.ts.map +1 -0
  12. package/lib/core/ServiceBase.js +137 -242
  13. package/lib/core/ServiceBase.js.map +1 -1
  14. package/lib/core/index.d.ts +5 -0
  15. package/lib/core/index.d.ts.map +1 -0
  16. package/lib/core/index.js +21 -0
  17. package/lib/core/index.js.map +1 -0
  18. package/lib/core/interfaces.d.ts +6 -72
  19. package/lib/core/interfaces.d.ts.map +1 -0
  20. package/lib/core/interfaces.js +18 -31
  21. package/lib/core/interfaces.js.map +1 -1
  22. package/lib/core/utils.d.ts +3 -1
  23. package/lib/core/utils.d.ts.map +1 -0
  24. package/lib/core/utils.js +28 -32
  25. package/lib/core/utils.js.map +1 -1
  26. package/lib/experimental/AccessControlledServiceBase.d.ts +30 -0
  27. package/lib/experimental/AccessControlledServiceBase.d.ts.map +1 -0
  28. package/lib/experimental/AccessControlledServiceBase.js +232 -0
  29. package/lib/experimental/AccessControlledServiceBase.js.map +1 -0
  30. package/lib/experimental/ClientRegister.d.ts +22 -0
  31. package/lib/experimental/ClientRegister.d.ts.map +1 -0
  32. package/lib/experimental/ClientRegister.js +33 -0
  33. package/lib/experimental/ClientRegister.js.map +1 -0
  34. package/lib/experimental/Pipe.d.ts +6 -0
  35. package/lib/experimental/Pipe.d.ts.map +1 -0
  36. package/lib/experimental/Pipe.js +14 -0
  37. package/lib/experimental/Pipe.js.map +1 -0
  38. package/lib/experimental/ResourceAggregator.d.ts +35 -0
  39. package/lib/experimental/ResourceAggregator.d.ts.map +1 -0
  40. package/lib/experimental/ResourceAggregator.js +94 -0
  41. package/lib/experimental/ResourceAggregator.js.map +1 -0
  42. package/lib/experimental/ResourceAwaitQueue.d.ts +12 -0
  43. package/lib/experimental/ResourceAwaitQueue.d.ts.map +1 -0
  44. package/lib/experimental/ResourceAwaitQueue.js +34 -0
  45. package/lib/experimental/ResourceAwaitQueue.js.map +1 -0
  46. package/lib/experimental/ResourceMap.d.ts +16 -0
  47. package/lib/experimental/ResourceMap.d.ts.map +1 -0
  48. package/lib/experimental/ResourceMap.js +51 -0
  49. package/lib/experimental/ResourceMap.js.map +1 -0
  50. package/lib/{core → experimental}/WorkerBase.d.ts +10 -14
  51. package/lib/experimental/WorkerBase.d.ts.map +1 -0
  52. package/lib/{core → experimental}/WorkerBase.js +44 -30
  53. package/lib/experimental/WorkerBase.js.map +1 -0
  54. package/lib/experimental/index.d.ts +7 -0
  55. package/lib/experimental/index.d.ts.map +1 -0
  56. package/lib/experimental/index.js +23 -0
  57. package/lib/experimental/index.js.map +1 -0
  58. package/lib/index.d.ts +5 -7
  59. package/lib/index.d.ts.map +1 -0
  60. package/lib/index.js +84 -144
  61. package/lib/index.js.map +1 -1
  62. package/lib/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +23 -28
  64. package/tsconfig.json +11 -25
  65. package/lib/core/WorkerBase.js.map +0 -1
@@ -1,103 +1,31 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
3
  exports.ResourcesAPIBase = void 0;
27
- const _ = __importStar(require("lodash"));
28
- const chassis_srv_1 = require("@restorecommerce/chassis-srv");
29
- const uuid = __importStar(require("uuid"));
4
+ const crypto_1 = require("crypto");
30
5
  const utils_1 = require("./utils");
31
- let redisClient;
32
- const Strategies = {
33
- INCREMENT: 'increment',
34
- UUID: 'uuid',
35
- RANDOM: 'random',
36
- TIMESTAMP: 'timestamp'
37
- };
38
- const uuidGen = () => {
39
- return uuid.v4().replace(/-/g, '');
40
- };
41
- const isEmptyObject = (obj) => {
42
- return !Object.keys(obj).length;
43
- };
44
- const setDefaults = async (obj, collectionName, subject) => {
45
- const o = obj;
46
- if (_.isEmpty(o.meta)) {
47
- throw new chassis_srv_1.errors.InvalidArgument('Object does not contain ownership information');
48
- }
49
- if (redisClient) {
50
- const values = await redisClient.hGetAll(collectionName);
51
- if (values) {
52
- for (let field in values) {
53
- const strategy = values[field];
54
- switch (strategy) {
55
- case Strategies.INCREMENT:
56
- const key = collectionName + ':' + field;
57
- o[field] = await redisClient.get(key);
58
- await redisClient.incr(key);
59
- break;
60
- case Strategies.UUID:
61
- o[field] = uuidGen();
62
- break;
63
- case Strategies.RANDOM:
64
- o[field] = uuidGen();
65
- break;
66
- case Strategies.TIMESTAMP:
67
- o[field] = await redisClient.time()[0];
68
- break;
69
- }
70
- }
71
- }
72
- }
73
- if (_.isNil(o.meta.created) || o.meta.created === 0) {
74
- o.meta.created = new Date();
75
- }
76
- o.meta.created_by = subject?.id;
77
- o.meta.modified_by = subject?.id;
78
- o.meta.modified = new Date();
79
- if (_.isNil(o.id) || o.id === 0 || isEmptyObject(o.id)) {
80
- o.id = uuidGen();
81
- }
82
- return o;
83
- };
84
- const updateMetadata = (docMeta, newDoc, subject) => {
85
- if (_.isEmpty(newDoc.meta)) {
86
- throw new chassis_srv_1.errors.InvalidArgument(`Update request holds no valid metadata for document ${newDoc.id}`);
87
- }
88
- if (!_.isEmpty(newDoc.meta?.owners)) {
89
- // if ownership is meant to be updated
90
- docMeta.owners = newDoc.meta.owners;
91
- }
92
- docMeta.modified_by = subject?.id;
93
- docMeta.modified = new Date();
94
- newDoc.meta = docMeta;
95
- return newDoc;
96
- };
6
+ // let redisClient: any;
7
+ var Strategies;
8
+ (function (Strategies) {
9
+ Strategies["INCREMENT"] = "increment";
10
+ Strategies["UUID"] = "uuid";
11
+ Strategies["RANDOM"] = "random";
12
+ Strategies["TIMESTAMP"] = "timestamp";
13
+ })(Strategies || (Strategies = {}));
14
+ const uuidGen = () => (0, crypto_1.randomUUID)().replace(/-/g, '');
97
15
  /**
98
16
  * Resource API base provides functions for CRUD operations.
99
17
  */
100
18
  class ResourcesAPIBase {
19
+ db;
20
+ collectionName;
21
+ edgeCfg;
22
+ graphName;
23
+ logger;
24
+ resourceName;
25
+ bufferFields;
26
+ requiredFields;
27
+ timeStampFields;
28
+ redisClient;
101
29
  /**
102
30
  * @constructor
103
31
  * @param {object} db Chassis arangodb provider.
@@ -115,29 +43,20 @@ class ResourcesAPIBase {
115
43
  if (!fieldHandlerConf) {
116
44
  return;
117
45
  }
118
- const strategyCfg = fieldHandlerConf.strategies;
119
- if (!redisClient) {
120
- redisClient = fieldHandlerConf.redisClient;
121
- }
122
- if (fieldHandlerConf.bufferFields) {
123
- this.bufferFields = fieldHandlerConf.bufferFields;
124
- }
125
- // config fix to be removed after ts-proto is used
126
- if (fieldHandlerConf.timeStampFields) {
127
- this.timeStampFields = fieldHandlerConf.timeStampFields;
128
- }
129
- if (fieldHandlerConf.requiredFields) {
130
- this.requiredFields = fieldHandlerConf.requiredFields;
131
- }
46
+ const strategyCfg = fieldHandlerConf?.strategies ?? [];
47
+ this.redisClient = fieldHandlerConf?.redisClient;
48
+ this.bufferFields = fieldHandlerConf?.bufferFields;
49
+ this.timeStampFields = fieldHandlerConf?.timeStampFields;
50
+ this.requiredFields = fieldHandlerConf?.requiredFields?.[this.resourceName] ?? fieldHandlerConf?.requiredFields;
132
51
  // values for Redis hash set
133
- for (let field in strategyCfg) {
52
+ for (const field in strategyCfg) {
134
53
  const strategy = strategyCfg[field].strategy;
135
- redisClient.hSet(collectionName, field, strategy);
54
+ this.redisClient.hSet(collectionName, field, strategy);
136
55
  switch (strategy) {
137
- case Strategies.INCREMENT:
56
+ case Strategies.INCREMENT: {
138
57
  // check if value already exists in redis
139
58
  let startingValue;
140
- startingValue = redisClient.get(`${collectionName}:${field}`).then((val) => val);
59
+ startingValue = this.redisClient.get(`${collectionName}:${field}`).then((val) => val);
141
60
  if (!startingValue) {
142
61
  if (strategyCfg[field].startingValue) {
143
62
  startingValue = Number.isNaN(strategyCfg[field].startingValue) ?
@@ -146,42 +65,91 @@ class ResourcesAPIBase {
146
65
  else {
147
66
  startingValue = '0';
148
67
  }
149
- redisClient.set(`${collectionName}:${field}`, startingValue).then((val) => val);
68
+ this.redisClient.set(`${collectionName}:${field}`, startingValue).then((val) => val);
150
69
  }
151
70
  break;
71
+ }
152
72
  default:
153
73
  break;
154
74
  }
155
75
  }
156
76
  }
77
+ catchOperationError(msg, err) {
78
+ this.logger?.error(msg, err);
79
+ return {
80
+ code: Number.isInteger(err.code) ? err.code : 500,
81
+ message: err.message ?? 'Unknown Error!',
82
+ };
83
+ }
84
+ catchStatusError(msg, err) {
85
+ this.logger?.error(msg, err);
86
+ return {
87
+ code: Number.isInteger(err.code) ? err.code : 500,
88
+ message: err.message ?? 'Unknown Error!',
89
+ };
90
+ }
91
+ setMeta(o, subject, create = false) {
92
+ o.meta ??= {};
93
+ if (create) {
94
+ o.meta.created ??= new Date();
95
+ o.meta.created_by ??= subject?.id;
96
+ }
97
+ o.meta.modified_by ??= subject?.id;
98
+ o.meta.modified ??= new Date();
99
+ if (!o.id?.length || o.id?.toString() === '0') {
100
+ o.id = uuidGen();
101
+ }
102
+ return o;
103
+ }
104
+ async setDefaults(o, collectionName, subject, create = false) {
105
+ if (create && this.redisClient) {
106
+ const values = await this.redisClient.hGetAll(collectionName);
107
+ if (values) {
108
+ for (const field in values) {
109
+ const strategy = values[field];
110
+ switch (strategy) {
111
+ case Strategies.INCREMENT: {
112
+ const key = `${collectionName}:${field}`;
113
+ o[field] = await this.redisClient.get(key);
114
+ await this.redisClient.incr(key);
115
+ break;
116
+ }
117
+ case Strategies.TIMESTAMP:
118
+ o[field] = (await this.redisClient.time()).getTime();
119
+ break;
120
+ default:
121
+ case Strategies.UUID:
122
+ case Strategies.RANDOM:
123
+ o[field] = uuidGen();
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return this.setMeta(o, subject, create);
130
+ }
157
131
  /**
158
132
  * Finds documents based on provided filters and options
159
133
  * @param {object} filter key value filter using mongodb/nedb filter format.
160
134
  * @param {number} limit
161
135
  * @param {number} offset
162
136
  * @param {object} sort key value, key=field value: 1=ASCENDING, -1=DESCENDING, 0=UNSORTED
163
- * @param {object} field key value, key=field value: 0=exclude, 1=include
137
+ * @param {object} fields key value, key=field value: 0=exclude, 1=include
164
138
  * @returns {an Object that contains an items field}
165
139
  */
166
- async read(filter = {}, limit = 1000, offset = 0, sort = {}, field = {}, customQueries = [], customArgs = {}, search) {
140
+ async read(filter = {}, limit = 1000, offset = 0, sort = {}, fields = {}, customQueries = [], customArgs = {}, search) {
167
141
  const options = {
168
142
  limit: Math.min(limit, 1000),
169
143
  offset,
170
144
  sort,
171
- fields: field,
145
+ fields,
172
146
  customQueries,
173
- customArguments: customArgs.value ? JSON.parse(customArgs.value.toString()) : {},
147
+ customArguments: customArgs?.value ? JSON.parse(customArgs.value.toString()) : {},
174
148
  search
175
149
  };
176
150
  let entities = await this.db.find(this.collectionName, filter, options);
177
- if (this.bufferFields && entities?.length > 0) {
178
- // encode the msg obj back to buffer obj and send it back
179
- entities = this.encodeOrDecode(entities, this.bufferFields, 'encode');
180
- }
181
- if (this.timeStampFields && entities?.length > 0) {
182
- // convert number to Date Object
183
- entities = this.encodeOrDecode(entities, this.timeStampFields, 'convertMilisecToDateObj');
184
- }
151
+ entities = this.encodeOrDecode(entities, this.bufferFields, 'encode');
152
+ entities = this.encodeOrDecode(entities, this.timeStampFields, 'convertMilisecToDateObj');
185
153
  return entities;
186
154
  }
187
155
  /**
@@ -189,96 +157,68 @@ class ResourcesAPIBase {
189
157
  *
190
158
  * @param {array.object} documents
191
159
  */
192
- async create(documents, subject) {
160
+ async create(documents, subject, events) {
193
161
  const collection = this.collectionName;
194
- let result = [];
195
- try {
196
- let result = [];
197
- // check if all the required fields are present
198
- if (this.requiredFields && this.requiredFields[this.resourceName]) {
199
- const requiredFieldsResult = this.checkRequiredFields(this.requiredFields[this.resourceName], documents, result);
200
- documents = requiredFieldsResult.documents;
201
- result = requiredFieldsResult.result;
202
- }
203
- documents = await Promise.all(documents.map(async (doc) => {
204
- return await setDefaults(doc, collection, subject);
205
- }));
206
- if (this.bufferFields && documents?.length > 0) {
207
- documents = this.encodeOrDecode(documents, this.bufferFields, 'decode');
208
- }
209
- if (this.timeStampFields && documents?.length > 0) {
210
- // convert Date Object to Number
211
- documents = this.encodeOrDecode(documents, this.timeStampFields, 'convertDateObjToMilisec');
212
- }
213
- if (this.isGraphDB(this.db)) {
214
- await this.db.createGraphDB(this.graphName);
215
- await this.db.addVertexCollection(collection);
216
- let createVertexResp = await this.db.createVertex(collection, documents);
217
- for (let document of documents) {
218
- if (this.edgeCfg && _.isArray(this.edgeCfg) && this.edgeCfg.length > 0) {
219
- for (let eachEdgeCfg of this.edgeCfg) {
220
- const fromIDkey = eachEdgeCfg.from;
221
- const from_id = document[fromIDkey];
222
- const toIDkey = eachEdgeCfg.to;
223
- const to_id = document[toIDkey];
224
- // edges are created outbound, if it is inbound - check for direction
225
- const direction = eachEdgeCfg.direction;
226
- let fromVerticeName = collection;
227
- let toVerticeName = eachEdgeCfg.toVerticeName;
228
- if (direction === 'inbound') {
229
- fromVerticeName = eachEdgeCfg.fromVerticeName;
230
- toVerticeName = collection;
231
- }
232
- if (from_id && to_id) {
233
- if (_.isArray(to_id)) {
234
- for (let toID of to_id) {
235
- await this.db.createEdge(eachEdgeCfg.edgeName, null, `${fromVerticeName}/${from_id}`, `${toVerticeName}/${toID}`);
236
- }
237
- }
238
- else {
239
- await this.db.createEdge(eachEdgeCfg.edgeName, null, `${fromVerticeName}/${from_id}`, `${toVerticeName}/${to_id}`);
240
- }
162
+ const result = new Array();
163
+ // check if all the required fields are present
164
+ if (this.requiredFields) {
165
+ documents = this.checkRequiredFields(this.requiredFields, documents, result);
166
+ }
167
+ documents = await Promise.all(documents.map(async (doc) => await this.setDefaults(doc, collection, subject, true)));
168
+ documents = this.encodeOrDecode(documents, this.bufferFields, 'decode');
169
+ documents = this.encodeOrDecode(documents, this.timeStampFields, 'convertDateObjToMilisec');
170
+ if (this.isGraphDB(this.db)) {
171
+ const db = this.db;
172
+ await db.addVertexCollection(collection);
173
+ const createVertexResp = await this.db.createVertex(collection, documents);
174
+ await Promise.all(documents.map(async (document) => {
175
+ try {
176
+ for (const eachEdgeCfg of this.edgeCfg) {
177
+ const fromIDkey = eachEdgeCfg.from;
178
+ const from_id = document[fromIDkey];
179
+ const toIDkey = eachEdgeCfg.to;
180
+ const to_id = document[toIDkey];
181
+ // edges are created outbound, if it is inbound - check for direction
182
+ const inbound = eachEdgeCfg.direction === 'inbound';
183
+ const fromVerticeName = inbound ? eachEdgeCfg.fromVerticeName : collection;
184
+ const toVerticeName = inbound ? collection : eachEdgeCfg.toVerticeName;
185
+ const ids = Array.isArray(to_id) ? to_id : [to_id];
186
+ if (from_id && to_id) {
187
+ for (const id of ids) {
188
+ await db.createEdge(eachEdgeCfg.edgeName, null, `${fromVerticeName}/${from_id}`, `${toVerticeName}/${id}`);
241
189
  }
242
190
  }
243
191
  }
244
192
  }
245
- if (_.isArray(createVertexResp)) {
246
- createVertexResp.forEach((eachVertexResp) => result.push(eachVertexResp));
247
- }
248
- else {
249
- result.push(createVertexResp);
250
- }
251
- if (this.timeStampFields && result?.length > 0) {
252
- // convert number to Date Object
253
- result = this.encodeOrDecode(result, this.timeStampFields, 'convertMilisecToDateObj');
193
+ catch (error) {
194
+ result.push({
195
+ error: true,
196
+ errorNum: error?.code,
197
+ errorMessage: error?.details ?? error?.message
198
+ });
254
199
  }
255
- return result;
200
+ }));
201
+ if (Array.isArray(createVertexResp)) {
202
+ result.push(...createVertexResp);
256
203
  }
257
204
  else {
258
- let checkReqFieldResult = [];
259
- if (!_.isEmpty(result)) {
260
- checkReqFieldResult = result;
261
- }
262
- result = await this.db.insert(collection, documents);
263
- if (!_.isEmpty(checkReqFieldResult)) {
264
- checkReqFieldResult.forEach((reqFieldResult) => result.push(reqFieldResult));
265
- }
266
- if (this.timeStampFields && result?.length > 0) {
267
- // convert number to Date Object
268
- result = this.encodeOrDecode(result, this.timeStampFields, 'convertMilisecToDateObj');
269
- }
270
- return result;
205
+ result.push(createVertexResp);
271
206
  }
207
+ this.encodeOrDecode(result, this.timeStampFields, 'convertMilisecToDateObj');
272
208
  }
273
- catch (error) {
274
- this.logger?.error('Error creating documents', { code: error?.code, message: error?.message, stack: error?.stack });
275
- result.push({
276
- error: true,
277
- errorNum: error?.code,
278
- errorMessage: error?.details ? error?.details : error?.message
279
- });
280
- return result;
209
+ else {
210
+ const inserts = await this.db.insert(collection, documents);
211
+ result.push(...inserts);
212
+ this.encodeOrDecode(result, this.timeStampFields, 'convertMilisecToDateObj');
213
+ }
214
+ if (events) {
215
+ await Promise.all(result?.map(async (item) => {
216
+ if (!item?.error) {
217
+ await events.emit(`${this.resourceName}Created`, item);
218
+ }
219
+ }));
281
220
  }
221
+ return result;
282
222
  }
283
223
  isGraphDB(db) {
284
224
  return !!this.edgeCfg;
@@ -288,85 +228,67 @@ class ResourcesAPIBase {
288
228
  * @param requiredFields
289
229
  * @param documents
290
230
  */
291
- checkRequiredFields(requiredFields, documents, result) {
292
- documents.forEach((document) => {
293
- requiredFields.forEach((eachField) => {
294
- const isArray = _.isArray(eachField);
295
- if (!document[eachField]) {
296
- result.push({
231
+ checkRequiredFields(requiredFields, documents, errors) {
232
+ const valid = documents.filter((document) => {
233
+ return requiredFields.every((field) => {
234
+ if (document[field] === undefined || (Array.isArray(document[field]) && document[field].length === 0)) {
235
+ errors.push({
236
+ id: document.id,
297
237
  error: true,
298
238
  errorNum: 400,
299
- errorMessage: `Field ${eachField} is necessary for ${this.resourceName} for documentID ${document.id}`
239
+ errorMessage: `Field ${field} is necessary for ${this.resourceName} in document ${document.id}`
300
240
  });
301
- documents = documents.filter(doc => doc.id != document.id);
302
- }
303
- if ((isArray && document[eachField].length == 0)) {
304
- result.push({
305
- error: true,
306
- errorNum: 400,
307
- errorMessage: `Field ${eachField} is necessary for ${this.resourceName} for documentID ${document.id}`
308
- });
309
- documents = documents.filter(doc => doc.id != document.id);
241
+ return false;
310
242
  }
243
+ return true;
311
244
  });
312
245
  });
313
- return { documents, result };
246
+ return valid;
314
247
  }
315
248
  /**
316
249
  * Removes documents found by id.
317
250
  *
318
251
  * @param [array.string] ids List of document IDs.
319
252
  */
320
- async delete(ids) {
321
- let deleteResponse = [];
322
- try {
323
- if (!_.isArray(ids)) {
324
- ids = [ids];
325
- }
326
- if (this.isGraphDB(this.db)) {
327
- // Modify the Ids to include documentHandle
328
- if (ids.length > 0) {
329
- ids = _.map(ids, (id) => {
330
- return `${this.collectionName}/${id}`;
331
- });
332
- deleteResponse = await this.db.removeVertex(this.collectionName, ids);
333
- return deleteResponse;
334
- }
253
+ async delete(ids, events) {
254
+ let response;
255
+ if (!Array.isArray(ids)) {
256
+ ids = [ids];
257
+ }
258
+ if (this.isGraphDB(this.db)) {
259
+ // Modify the Ids to include documentHandle
260
+ if (ids.length > 0) {
261
+ ids = ids?.map((id) => `${this.collectionName}/${id}`);
262
+ response = await this.db.removeVertex(this.collectionName, ids);
335
263
  }
336
- deleteResponse = await this.db.delete(this.collectionName, ids);
337
- return deleteResponse;
338
264
  }
339
- catch (error) {
340
- this.logger?.error('Error deleting documents', { code: error?.code, message: error?.message, stack: error?.stack });
341
- deleteResponse.push({
342
- error: true,
343
- errorNum: error?.code,
344
- errorMessage: error?.details ? error?.details : error?.message
345
- });
346
- return deleteResponse;
265
+ else {
266
+ response = await this.db.delete(this.collectionName, ids);
347
267
  }
268
+ if (events) {
269
+ await Promise.all(response?.map(async (id) => {
270
+ if (typeof id === 'string') {
271
+ id = { id };
272
+ }
273
+ if (!id.error) {
274
+ await events?.emit(`${this.resourceName}Deleted`, id);
275
+ }
276
+ }));
277
+ }
278
+ return response;
348
279
  }
349
280
  /**
350
281
  * Delete all documents in the collection.
351
282
  */
352
- async deleteCollection() {
283
+ async deleteCollection(events) {
284
+ await this.db.truncate(this.collectionName);
353
285
  if (this.isGraphDB(this.db)) {
354
- // graph edges are only deleted automatically when a specific vertex is deleted
355
- // (`truncate` does not work in this case)
356
- const ids = await this.db.find(this.collectionName, {}, {
357
- fields: {
358
- id: 1
359
- }
360
- });
361
- await this.delete(_.map(ids, (doc) => {
362
- return doc.id;
363
- }));
364
- return ids;
286
+ const db = this.db;
287
+ const edges = await db.getGraphDB().get().then((info) => info.edgeDefinitions);
288
+ await Promise.all(edges?.filter(edge => Object.values(edge).flatMap(edge => edge).includes(this.collectionName)).map(edge => db.truncate(edge.collection)) ?? []);
365
289
  }
366
- else {
367
- const entities = await this.db.find(this.collectionName, {}, { fields: { id: 1 } });
368
- await this.db.truncate(this.collectionName);
369
- return entities;
290
+ if (events) {
291
+ await events?.emit(`${this.resourceName}DeletedAll`, { collection: this.collectionName });
370
292
  }
371
293
  }
372
294
  /**
@@ -374,73 +296,39 @@ class ResourcesAPIBase {
374
296
  *
375
297
  * @param [array.object] documents
376
298
  */
377
- async upsert(documents, events, resourceName, subject) {
378
- let result = [];
379
- let createDocsResult = [];
380
- let updateDocsResult = [];
381
- try {
382
- let createDocuments = [];
383
- let updateDocuments = [];
384
- if (this.bufferFields && documents) {
385
- documents = this.encodeOrDecode(documents, this.bufferFields, 'decode');
299
+ async upsert(documents, subject, events) {
300
+ const createDocuments = new Array();
301
+ const updateDocuments = new Array();
302
+ documents = this.encodeOrDecode(documents, this.bufferFields, 'decode');
303
+ const orgs = new Set(await this.db.find(this.collectionName, {
304
+ _key: {
305
+ $in: [...new Set(documents?.map(doc => doc.id).filter(id => id))],
306
+ },
307
+ }, {
308
+ fields: {
309
+ id: 1
386
310
  }
387
- const dispatch = await Promise.all(documents.map(async (doc) => {
388
- let foundDocs;
389
- if (doc && doc.id) {
390
- foundDocs = await this.db.find(this.collectionName, { id: doc.id }, {
391
- fields: {
392
- meta: 1
393
- }
394
- });
395
- }
396
- let eventName;
397
- if (_.isEmpty(foundDocs)) {
398
- // insert
399
- setDefaults(doc, this.collectionName, subject);
400
- createDocuments.push(doc);
401
- eventName = 'Created';
402
- }
403
- else {
404
- // convert dateTimeStamp fields
405
- if (this.timeStampFields) {
406
- foundDocs = this.encodeOrDecode(foundDocs, this.timeStampFields, 'convertMilisecToDateObj');
407
- }
408
- // update
409
- const dbDoc = foundDocs[0];
410
- updateMetadata(dbDoc.meta, doc, subject);
411
- updateDocuments.push(doc);
412
- eventName = 'Modified';
413
- }
414
- if (events) {
415
- return events.emit(`${resourceName}${eventName}`, doc);
416
- }
417
- }));
418
- if (createDocuments?.length > 0) {
419
- createDocsResult = await this.create(createDocuments, subject);
420
- }
421
- if (updateDocuments?.length > 0) {
422
- updateDocsResult = await this.update(updateDocuments, subject);
311
+ }).then(resp => resp.map(doc => doc.id)));
312
+ documents?.forEach((doc) => {
313
+ if (orgs.has(doc?.id)) {
314
+ // update
315
+ updateDocuments.push(doc);
423
316
  }
424
- result = _.union(createDocuments, updateDocuments);
425
- if (this.timeStampFields && result?.length > 0) {
426
- // convert number to Date Object
427
- result = this.encodeOrDecode(result, this.timeStampFields, 'convertMilisecToDateObj');
428
- }
429
- await Promise.all(dispatch);
430
- if (this.bufferFields && result?.length > 0) {
431
- result = this.encodeOrDecode(result, this.bufferFields, 'encode');
317
+ else {
318
+ // insert
319
+ createDocuments.push(doc);
432
320
  }
433
- return result;
321
+ });
322
+ if (updateDocuments?.length > 0) {
323
+ await this.update(updateDocuments, subject, events);
434
324
  }
435
- catch (error) {
436
- this.logger?.error('Error upserting documents', { code: error?.code, message: error?.message, stack: error?.stack });
437
- result.push({
438
- error: true,
439
- errorNum: error?.code,
440
- errorMessage: error?.details ? error?.details : error?.message
441
- });
442
- return result;
325
+ if (createDocuments?.length > 0) {
326
+ await this.create(createDocuments, subject, events);
443
327
  }
328
+ const result = [...updateDocuments, ...createDocuments];
329
+ this.encodeOrDecode(result, this.timeStampFields, 'convertMilisecToDateObj');
330
+ this.encodeOrDecode(result, this.bufferFields, 'encode');
331
+ return result;
444
332
  }
445
333
  /**
446
334
  * Finds documents by id and updates them.
@@ -448,96 +336,85 @@ class ResourcesAPIBase {
448
336
  * @param [array.object] documents
449
337
  * A list of documents or partial documents. Each document must contain an id field.
450
338
  */
451
- async update(documents, subject) {
452
- let updateResponse = [];
453
- try {
454
- const collectionName = this.collectionName;
455
- if (this.bufferFields && documents) {
456
- documents = this.encodeOrDecode(documents, this.bufferFields, 'decode');
457
- }
458
- let docsWithUpMetadata = await Promise.all(documents.map(async (doc) => {
459
- let foundDocs = await this.db.find(collectionName, { id: doc.id }, { limit: 1 });
460
- let dbDoc;
461
- if (foundDocs && foundDocs.length === 1) {
462
- dbDoc = foundDocs[0];
463
- doc = updateMetadata(dbDoc.meta, doc, subject);
464
- }
465
- else {
466
- dbDoc = doc; // doc not existing assigning to generate error message in response
467
- }
339
+ async update(documents, subject, events) {
340
+ documents = this.encodeOrDecode(documents, this.bufferFields, 'decode');
341
+ documents = documents.map((doc) => this.setMeta(doc, subject));
342
+ documents = await Promise.all(documents.map(async (doc) => {
343
+ try {
468
344
  if (this.isGraphDB(this.db)) {
469
345
  const db = this.db;
470
- await Promise.all(this.edgeCfg.map(async (eachEdgeCfg) => {
471
- const toIDkey = eachEdgeCfg.to;
472
- let modified_to_idValues = doc[toIDkey];
473
- let db_to_idValues = dbDoc[toIDkey];
474
- if (_.isArray(modified_to_idValues)) {
475
- modified_to_idValues = _.sortBy(modified_to_idValues);
476
- }
477
- if (_.isArray(db_to_idValues)) {
478
- db_to_idValues = _.sortBy(db_to_idValues);
479
- }
346
+ await Promise.all(this.edgeCfg.map(async (edgeCfg) => {
347
+ const to_id = doc[edgeCfg.to];
348
+ const from_id = doc[edgeCfg.from];
349
+ const edgeCollectionName = edgeCfg.edgeName;
480
350
  // delete and recreate only if there is a difference in references
481
- if (!_.isEqual(modified_to_idValues, db_to_idValues)) {
482
- // delete and recreate the edge (since there is no way to update the edge as we dont add id to the edge as for doc)
483
- const fromIDkey = eachEdgeCfg.from;
484
- const from_id = doc[fromIDkey];
485
- let fromVerticeName = collectionName;
486
- let toVerticeName = eachEdgeCfg.toVerticeName;
487
- const direction = eachEdgeCfg.direction;
488
- if (direction === 'inbound') {
489
- fromVerticeName = eachEdgeCfg.fromVerticeName;
490
- toVerticeName = collectionName;
491
- }
492
- const edgeCollectionName = eachEdgeCfg.edgeName;
493
- let outgoingEdges = await db.getOutEdges(edgeCollectionName, `${collectionName}/${dbDoc.id}`);
494
- if (_.isArray(outgoingEdges.edges)) {
495
- await Promise.all(outgoingEdges.edges.map((outgoingEdge) => db.removeEdge(edgeCollectionName, outgoingEdge._id)));
496
- }
497
- let incomingEdges = await db.getInEdges(edgeCollectionName, `${collectionName}/${dbDoc.id}`);
498
- if (_.isArray(incomingEdges.edges)) {
499
- await Promise.all(incomingEdges.edges.map((incomingEdge) => db.removeEdge(edgeCollectionName, incomingEdge._id)));
351
+ if (edgeCfg.direction === 'inbound' && from_id) {
352
+ const from_ids = Array.isArray(from_id) ? from_id : [from_id];
353
+ // if (!from_ids?.length) return;
354
+ if (typeof to_id !== 'string')
355
+ throw Error('Inbound value `to` has to be a single string!');
356
+ const fromVerticeName = edgeCfg.fromVerticeName;
357
+ const toVerticeName = edgeCfg.toVerticeName ?? this.collectionName;
358
+ const incoming = await db.getInEdges(edgeCollectionName, `${fromVerticeName}/${to_id}`);
359
+ // Remove edges that are no longer defined
360
+ if (Array.isArray(incoming.edges)) {
361
+ await Promise.all(incoming.edges?.filter((edge) => !from_ids.includes(edge._from)).map((edge) => db.removeEdge(edgeCollectionName, edge._id)));
500
362
  }
501
363
  // Create new edges
502
- if (from_id && modified_to_idValues) {
503
- if (_.isArray(modified_to_idValues)) {
504
- await Promise.all(modified_to_idValues.map((toID) => db.createEdge(eachEdgeCfg.edgeName, null, `${fromVerticeName}/${from_id}`, `${toVerticeName}/${toID}`)));
505
- }
506
- else {
507
- await db.createEdge(edgeCollectionName, null, `${fromVerticeName}/${from_id}`, `${toVerticeName}/${modified_to_idValues}`);
508
- }
364
+ await Promise.all(from_ids.filter(id => !incoming.edges?.includes(id)).map(id => db.createEdge(edgeCfg.edgeName, null, `${fromVerticeName}/${from_id}`, `${toVerticeName}/${id}`)));
365
+ }
366
+ else if (to_id) {
367
+ const to_ids = Array.isArray(to_id) ? to_id : [to_id];
368
+ // if (!to_ids?.length) return;
369
+ if (typeof from_id !== 'string')
370
+ throw Error('Outbound value `from` has to be a single string!');
371
+ const fromVerticeName = edgeCfg.fromVerticeName ?? this.collectionName;
372
+ const toVerticeName = edgeCfg.toVerticeName;
373
+ const outgoing = await db.getOutEdges(edgeCollectionName, `${fromVerticeName}/${from_id}`);
374
+ // Remove edges that are no longer defined
375
+ if (Array.isArray(outgoing.edges)) {
376
+ await Promise.all(outgoing.edges?.filter((edge) => !to_ids.includes(edge._to)).map((edge) => db.removeEdge(edgeCollectionName, edge._id)));
509
377
  }
378
+ // Create new edges
379
+ await Promise.all(to_ids.filter(id => !outgoing.edges?.includes(id)).map(id => db.createEdge(edgeCfg.edgeName, null, `${fromVerticeName}/${from_id}`, `${toVerticeName}/${id}`)));
510
380
  }
511
381
  }));
512
382
  }
513
383
  return doc;
514
- }));
515
- if (this.timeStampFields && docsWithUpMetadata?.length > 0) {
516
- docsWithUpMetadata = this.encodeOrDecode(docsWithUpMetadata, this.timeStampFields, 'convertDateObjToMilisec');
517
384
  }
518
- updateResponse = await this.db.update(collectionName, docsWithUpMetadata);
519
- if (this.timeStampFields && updateResponse?.length > 0) {
520
- updateResponse = this.encodeOrDecode(updateResponse, this.timeStampFields, 'convertMilisecToDateObj');
385
+ catch (error) {
386
+ this.logger?.error(`Error updating document ${doc.id}`, error);
387
+ return {
388
+ ...doc,
389
+ error: true,
390
+ errorNum: error?.code,
391
+ errorMessage: `On graph update: ${error?.details ?? error?.message}`
392
+ };
521
393
  }
522
- if (this.bufferFields && updateResponse?.length > 0) {
523
- updateResponse = this.encodeOrDecode(updateResponse, this.bufferFields, 'encode');
524
- }
525
- return updateResponse;
526
- }
527
- catch (error) {
528
- this.logger?.error('Error updating documents', { code: error?.code, message: error?.message, stack: error?.stack });
529
- updateResponse.push({
530
- error: true,
531
- errorNum: error?.code,
532
- errorMessage: error?.message
533
- });
534
- return updateResponse;
394
+ }));
395
+ const errors = documents.filter(doc => doc.error);
396
+ const updates = documents.filter(doc => !doc.error);
397
+ this.encodeOrDecode(updates, this.timeStampFields, 'convertDateObjToMilisec');
398
+ const results = await this.db.update(this.collectionName, updates);
399
+ results.push(...errors);
400
+ this.encodeOrDecode(results, this.timeStampFields, 'convertMilisecToDateObj');
401
+ if (events) {
402
+ await Promise.all(results?.map(async (item) => {
403
+ if (!item.error) {
404
+ await events.emit(`${this.resourceName}Modified`, item);
405
+ }
406
+ }));
535
407
  }
408
+ this.encodeOrDecode(results, this.bufferFields, 'encode');
409
+ return results;
536
410
  }
537
- encodeOrDecode(documents, fieldPaths, fieldHanlder) {
538
- for (let doc of documents) {
539
- for (let fieldPath of fieldPaths) {
540
- doc = (0, utils_1.fieldHandler)(doc, fieldPath, fieldHanlder);
411
+ encodeOrDecode(documents, fieldPaths, mode) {
412
+ const arr = Array.isArray(documents) ? documents : [documents];
413
+ if (fieldPaths?.length && arr?.length) {
414
+ for (const doc of arr) {
415
+ for (const fieldPath of fieldPaths) {
416
+ (0, utils_1.fieldHandler)(doc, fieldPath, mode);
417
+ }
541
418
  }
542
419
  }
543
420
  return documents;