@cocreate/mongodb 1.24.1 → 1.25.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.
package/src/object.js ADDED
@@ -0,0 +1,509 @@
1
+ /********************************************************************************
2
+ * Copyright (C) 2023 CoCreate and Contributors.
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU Affero General Public License as published
6
+ * by the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ ********************************************************************************/
17
+
18
+ import { ObjectId } from "mongodb";
19
+ import {
20
+ dotNotationToObject,
21
+ searchData,
22
+ isValidDate
23
+ } from "@cocreate/utils";
24
+
25
+ import {
26
+ dbClient,
27
+ createFilter,
28
+ createData,
29
+ errorHandler
30
+ } from "./utilities.js";
31
+
32
+ function object(method, data) {
33
+ return new Promise(
34
+ async (resolve, reject) => {
35
+ try {
36
+ const client = await dbClient(data);
37
+
38
+ if (!client || client.status === false) return resolve(data);
39
+
40
+ let type = "object";
41
+ let documents = [];
42
+
43
+ if (!data["timeStamp"])
44
+ data["timeStamp"] = new Date().toISOString();
45
+
46
+ let databases = data.database;
47
+ if (!Array.isArray(databases)) databases = [databases];
48
+
49
+ for (let database of databases) {
50
+ let arrays = data.array;
51
+ if (!Array.isArray(arrays)) arrays = [arrays];
52
+
53
+ for (let array of arrays) {
54
+ const db = client.db(database);
55
+ const arrayObj = db.collection(array);
56
+ const reference = {
57
+ $storage: data.storageName,
58
+ $database: database,
59
+ $array: array
60
+ };
61
+
62
+ if (!data[type]) data[type] = [];
63
+ else if (typeof data[type] === "string")
64
+ data[type] = [{ _id: data[type] }];
65
+ else if (!Array.isArray(data[type]))
66
+ data[type] = [data[type]];
67
+
68
+ let isFilter;
69
+ if (data.$filter) isFilter = true;
70
+ if ((isFilter && !data[type].length) || data.isFilter)
71
+ data[type].splice(0, 0, {
72
+ isFilter: "isEmptyObjectFilter"
73
+ });
74
+
75
+ let filter = await createFilter(data, arrayObj);
76
+
77
+ let projections = {},
78
+ projection = {},
79
+ globalUpdate = {},
80
+ options = {};
81
+
82
+ if (data.$filter && data.$filter.key)
83
+ projection = data.$filter.key;
84
+
85
+ if (method === "update")
86
+ createUpdate(globalUpdate, options, data, true);
87
+
88
+ for (let i = 0; i < data[type].length; i++) {
89
+ let $storage = data[type][i].$storage || [];
90
+ let $database = data[type][i].$database || [];
91
+ let $array = data[type][i].$array || [];
92
+ let update = { ...globalUpdate };
93
+
94
+ if (!Array.isArray($storage))
95
+ $storage = [data[type][i].$storage];
96
+ if (!Array.isArray($database))
97
+ $database = [data[type][i].$database];
98
+ if (!Array.isArray($array))
99
+ $array = [data[type][i].$array];
100
+
101
+ if (!$storage.includes(data.storageName))
102
+ $storage.push(data.storageName);
103
+ if (!$database.includes(database))
104
+ $database.push(database);
105
+ if (!$array.includes(array)) $array.push(array);
106
+
107
+ delete data[type][i].$storage;
108
+ delete data[type][i].$database;
109
+ delete data[type][i].$array;
110
+
111
+ if (method !== "create" && data[type][i].$filter) {
112
+ isFilter = true;
113
+ reference["$filter"] = data[type][i].$filter;
114
+ filter = await createFilter(
115
+ { $filter: data[type][i].$filter },
116
+ arrayObj
117
+ );
118
+ }
119
+
120
+ let { query, sort, index, limit } = filter;
121
+
122
+ if (method === "create") {
123
+ if (typeof replaceArray === 'function') {
124
+ data[type][i] = replaceArray(data[type][i]);
125
+ }
126
+ data[type][i] = dotNotationToObject(
127
+ data[type][i]
128
+ );
129
+ data[type][i]["organization_id"] =
130
+ data["organization_id"];
131
+ data[type][i]["created"] = {
132
+ on: new Date(data.timeStamp),
133
+ by: data.user_id || data.clientId
134
+ };
135
+ } else if (method === "update") {
136
+ if (!data[type][i].modified)
137
+ data[type][i].modified = {
138
+ on: new Date(data.timeStamp),
139
+ by: data.user_id || data.clientId
140
+ };
141
+ else
142
+ data[type][i].modified.on = new Date(
143
+ data[type][i].modified.on
144
+ );
145
+
146
+ data[type][i].organization_id =
147
+ data.organization_id;
148
+ createUpdate(update, options, data[type][i]);
149
+ }
150
+
151
+ if (data[type][i]._id || method === "create" || isFilter) {
152
+ if (method !== "create") {
153
+ try {
154
+ if (data[type][i]._id) {
155
+ query._id = new ObjectId(data[type][i]._id);
156
+ }
157
+ } catch (error) {
158
+ if (
159
+ method === "update" &&
160
+ options.upsert
161
+ ) {
162
+ data[type][i]._id = new ObjectId();
163
+ query._id = data[type][i]._id;
164
+ } else {
165
+ errorHandler(
166
+ data,
167
+ error,
168
+ database,
169
+ array
170
+ );
171
+ continue;
172
+ }
173
+ }
174
+ }
175
+
176
+ try {
177
+ process.emit("usage", {
178
+ type: "egress",
179
+ data: { query, update, projection, options },
180
+ organization_id: data.organization_id
181
+ });
182
+
183
+ let result;
184
+ if (method === "create") {
185
+ if (data[type][i]._id) {
186
+ try {
187
+ data[type][i]._id = new ObjectId(
188
+ data[type][i]._id
189
+ );
190
+ } catch (error) {
191
+ delete data[type][i]._id;
192
+ }
193
+ }
194
+ result = await arrayObj.insertOne(
195
+ data[type][i]
196
+ );
197
+ data[type][i]._id =
198
+ result.insertedId.toString();
199
+ documents.push({ ...data[type][i], ...reference });
200
+ } else {
201
+ let cursor = arrayObj.find(query, { projection });
202
+ if (sort && Object.keys(sort).length > 0) cursor = cursor.sort(sort);
203
+ if (limit) cursor = cursor.limit(limit);
204
+
205
+ let hasDocuments = false;
206
+
207
+ for await (const document of cursor) {
208
+ hasDocuments = true;
209
+
210
+ process.emit("usage", {
211
+ type: "ingress",
212
+ data: document,
213
+ organization_id: data.organization_id
214
+ });
215
+
216
+ if (data.$filter && data.$filter.search) {
217
+ let isMatch = searchData(
218
+ document,
219
+ data.$filter.search
220
+ );
221
+ if (!isMatch) continue;
222
+ }
223
+
224
+ if (method === "read") {
225
+ document._id = document._id.toString();
226
+ let object = data[type].find(
227
+ (obj) =>
228
+ obj._id &&
229
+ obj._id.toString() === document._id.toString()
230
+ );
231
+
232
+ if (object && object.$storage && object.modified && object.modified.on) {
233
+ if (document.modified && new Date(object.modified.on) > new Date(document.modified.on)) {
234
+ object = { ...document, ...object };
235
+ let docUpdate = {};
236
+ let docOptions = {};
237
+ createUpdate(docUpdate, docOptions, object);
238
+ let updateResult = await arrayObj.updateOne(
239
+ { _id: new ObjectId(document._id) },
240
+ docUpdate,
241
+ docOptions
242
+ );
243
+
244
+ process.emit("usage", {
245
+ type: "ingress",
246
+ data: updateResult,
247
+ organization_id: data.organization_id
248
+ });
249
+ }
250
+ }
251
+ documents.push({ ...document, ...reference });
252
+ } else {
253
+ process.emit("usage", {
254
+ type: "egress",
255
+ data: { _id: document._id, update, options },
256
+ organization_id: data.organization_id
257
+ });
258
+
259
+ if (method === "update") {
260
+ if (update["$pull"] && update["$unset"]) {
261
+ result = await arrayObj.updateOne(
262
+ { _id: document._id },
263
+ { $unset: update["$unset"] },
264
+ options
265
+ );
266
+ delete update["$unset"];
267
+ }
268
+
269
+ if (options.returnNewDocument) {
270
+ let updatedObj = await arrayObj.findOneAndUpdate(
271
+ { _id: document._id },
272
+ update,
273
+ { ...options, returnDocument: 'after' }
274
+ );
275
+
276
+ if (updatedObj && updatedObj.value) {
277
+ for (let key of Object.keys(updatedObj.value)) {
278
+ if (key === "_id" || !options.newArray) continue;
279
+ let newArrayKey = options.newArray[key];
280
+ if (newArrayKey) {
281
+ let idx = updatedObj.value[key].length - 1;
282
+ if (idx >= 0) {
283
+ data[type][i][newArrayKey.replace("[]", `[${idx}]`)] = data[type][i][newArrayKey];
284
+ }
285
+ }
286
+ }
287
+ }
288
+ } else {
289
+ result = await arrayObj.updateOne(
290
+ { _id: document._id },
291
+ update,
292
+ options
293
+ );
294
+ }
295
+ } else if (method === "delete") {
296
+ result = await arrayObj.deleteOne({
297
+ _id: document._id
298
+ });
299
+ }
300
+
301
+ process.emit("usage", {
302
+ type: "ingress",
303
+ data: result,
304
+ organization_id: data.organization_id
305
+ });
306
+
307
+ documents.push({
308
+ ...data[type][i],
309
+ ...reference,
310
+ _id: document._id.toString()
311
+ });
312
+ }
313
+ }
314
+
315
+ if (!hasDocuments && method === "update" && (data.upsert || options.upsert)) {
316
+ let upsertId = data[type][i]._id ? new ObjectId(data[type][i]._id) : new ObjectId();
317
+ let result = await arrayObj.updateOne(
318
+ { _id: upsertId },
319
+ update,
320
+ { ...options, upsert: true }
321
+ );
322
+
323
+ process.emit("usage", {
324
+ type: "ingress",
325
+ data: result,
326
+ organization_id: data.organization_id
327
+ });
328
+
329
+ documents.push({
330
+ ...data[type][i],
331
+ ...reference,
332
+ _id: upsertId.toString()
333
+ });
334
+ }
335
+ }
336
+ } catch (error) {
337
+ errorHandler(data, error, database, array);
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ data = createData(
345
+ data,
346
+ documents,
347
+ type
348
+ );
349
+ resolve(data);
350
+ } catch (error) {
351
+ errorHandler(data, error);
352
+ console.log(method, "error", error);
353
+ resolve(data);
354
+ }
355
+ }
356
+ );
357
+ }
358
+
359
+ function createUpdate(update, options, data, isGlobal) {
360
+ if (data.upsert) options.upsert = data.upsert;
361
+ if (data.$upsert) options.upsert = data.$upsert;
362
+ if (data.$update) delete data.$update;
363
+
364
+ Object.keys(data).forEach((key) => {
365
+ if ((isGlobal && !key.startsWith("$")) || key === "_id") return;
366
+
367
+ if (isValidDate(data[key])) data[key] = new Date(data[key]);
368
+
369
+ let operator;
370
+ if (key.endsWith("]")) {
371
+ const regex = /^(.*(?:\[\d+\].*?)?)\[(.*?)\](?:\[\])?$/;
372
+ var match = key.match(regex);
373
+ var index = parseInt(match[2], 10);
374
+ if (index === NaN) operator = match[2];
375
+ var arrayKey = match[1].replace(/\[(\d+)\]/g, ".$1");
376
+ }
377
+
378
+ if (key.startsWith("$")) operator = key.split(".")[0];
379
+ else if (
380
+ !operator &&
381
+ typeof data[key] === "string" &&
382
+ data[key].startsWith("$")
383
+ )
384
+ operator = data[key];
385
+
386
+ if (!update["$set"]) update["$set"] = {};
387
+
388
+ let originalKey = key;
389
+ key = key.replace(/\[(\d+)\]/g, ".$1");
390
+
391
+ if (originalKey.endsWith("[]")) {
392
+ if (!options.projection) {
393
+ options.projection = {};
394
+ options.arrayKey = {};
395
+ options.returnNewDocument = true;
396
+ } else {
397
+ options.projection[key.replace(operator + ".", "")] = 1;
398
+ options.arrayKey[key.replace(operator + ".", "")] = originalKey;
399
+ }
400
+ if (!key.startsWith("$")) operator = "$push";
401
+ if (key.endsWith("[]")) key = key.replace("[]", "");
402
+ }
403
+
404
+ let operators = [
405
+ "$rename",
406
+ "$inc",
407
+ "$push",
408
+ "$each",
409
+ "$splice",
410
+ "$unset",
411
+ "$delete",
412
+ "$slice",
413
+ "$pop",
414
+ "$shift",
415
+ "$addToSet",
416
+ "$pull",
417
+ "$currentDate"
418
+ ];
419
+ if (!operators.includes(operator) && typeof index !== "number") {
420
+ if (!isGlobal) update["$set"][key] = data[originalKey];
421
+ return;
422
+ }
423
+
424
+ let updates = {};
425
+ if (operator === "$rename") {
426
+ if (key === "$rename")
427
+ for (let oldkey of Object.keys(data[originalKey])) {
428
+ key = "$rename." + oldkey;
429
+ updates[key] = data[originalKey][oldkey].replace(
430
+ /\[(\d+)\]/g,
431
+ ".$1"
432
+ );
433
+ }
434
+ else updates[key] = data[originalKey].replace(/\[(\d+)\]/g, ".$1");
435
+ } else if (
436
+ operator === "$delete" ||
437
+ operator === "$unset" ||
438
+ operator === "$slice"
439
+ ) {
440
+ operator = "$unset";
441
+ updates[key] = 1;
442
+ const pullkey = key.split(".");
443
+ pullkey.shift();
444
+ pullkey.pop();
445
+ if (!update["$pull"]) update["$pull"] = {};
446
+ update["$pull"][pullkey.join(".")] = null;
447
+ } else if (operator === "$pop") {
448
+ key = arrayKey;
449
+ updates[key] = index || 1;
450
+ } else if (operator === "$addToSet" || operator === "$pull") {
451
+ updates[key] = data[originalKey];
452
+ } else if (
453
+ operator === "$push" ||
454
+ operator === "$each" ||
455
+ typeof index === "number"
456
+ ) {
457
+ updates[key] = data[originalKey];
458
+ if (typeof index === "number" && index >= 0) {
459
+ if (!operator) operator = "$set";
460
+ else {
461
+ if (operator === "$push")
462
+ updates[key] = [data[originalKey]];
463
+
464
+ let insert = { $each: updates[key] };
465
+ insert.$postion = index;
466
+ if (index >= 0) updates[arrayKey] = insert;
467
+ else updates[key] = insert;
468
+ }
469
+ }
470
+ } else if (operator === "$inc") {
471
+ updates[key] = data[originalKey];
472
+ } else if (operator === "$currentDate") {
473
+ updates[key] = data[originalKey];
474
+ }
475
+
476
+ if (!update[operator]) update[operator] = {};
477
+
478
+ if (key === operator)
479
+ update[operator] = {
480
+ ...update[operator],
481
+ ...replaceArray(updates[key])
482
+ };
483
+ else update[operator][key.replace(operator + ".", "")] = updates[key];
484
+ });
485
+ }
486
+
487
+ function createProjection(projection, data) {
488
+ Object.keys(data).forEach((key) => {
489
+ if (
490
+ !["_id", "organization_id", "isFIlter"].includes(key) &&
491
+ !key.startsWith("$")
492
+ )
493
+ projection[key.replace(/\[(\d+)\]/g, ".$1")] = 1;
494
+ });
495
+
496
+ return projection;
497
+ }
498
+
499
+ function replaceArray(data = {}) {
500
+ let object = {};
501
+
502
+ Object.keys(data).forEach((key) => {
503
+ object[key.replace(/\[(\d+)\]/g, ".$1")] = data[key];
504
+ });
505
+
506
+ return object;
507
+ }
508
+
509
+ export default object;