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