cry-db 2.5.1 → 2.5.2
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/dist/mongo.d.mts +3 -3
- package/dist/mongo.d.mts.map +1 -1
- package/dist/mongo.mjs +693 -452
- package/dist/mongo.mjs.map +1 -1
- package/package.json +1 -1
package/dist/mongo.mjs
CHANGED
|
@@ -7,13 +7,13 @@ var _a;
|
|
|
7
7
|
// AI modified: 2026-05-09 (terminal bracket-id `arr[<id>]` z array vrednostjo → idempotent insert preko aggregation pipeline ($concatArrays + $filter); quoted bracket id `['p0']`/`["p0"]` zdaj enak unquoted `[p0]`)
|
|
8
8
|
// AI modified: 2026-04-25 (GetNewerSpec.specId — disambiguate duplicate-collection specs in findNewerMany / findNewerManyStream)
|
|
9
9
|
// AI modified: 2026-04-21
|
|
10
|
-
import * as bcrypt from
|
|
11
|
-
import { Formatter, FracturedJsonOptions } from
|
|
10
|
+
import * as bcrypt from "bcrypt";
|
|
11
|
+
import { Formatter, FracturedJsonOptions } from "fracturedjsonjs";
|
|
12
12
|
import cloneDeep from "lodash.clonedeep";
|
|
13
|
-
import { ObjectId, ReadConcern, ReadPreference, Timestamp, WriteConcern } from
|
|
13
|
+
import { ObjectId, ReadConcern, ReadPreference, Timestamp, WriteConcern, } from "mongodb";
|
|
14
14
|
import { TypedEmitter } from "tiny-typed-emitter";
|
|
15
|
-
import { Db, log } from
|
|
16
|
-
import { FIELD_SEQUENCES_COLLECTION, SEQUENCES_COLLECTION } from
|
|
15
|
+
import { Db, log } from "./db.mjs";
|
|
16
|
+
import { FIELD_SEQUENCES_COLLECTION, SEQUENCES_COLLECTION, } from "./types.mjs";
|
|
17
17
|
import { Base } from "./base.mjs";
|
|
18
18
|
const MONGO_DEBUG_UPDATE_COLLECTIONS = (_a = process.env.MONGO_DEBUG_UPDATE_COLLECTIONS) === null || _a === void 0 ? void 0 : _a.match(/yes|true|da|1/i);
|
|
19
19
|
const assert = (cond, msg, calledFrom, payload) => {
|
|
@@ -29,20 +29,20 @@ const assert = (cond, msg, calledFrom, payload) => {
|
|
|
29
29
|
parts.push(String(payload));
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
const fullMsg = parts.join(
|
|
32
|
+
const fullMsg = parts.join(" | ");
|
|
33
33
|
const err = new Error(fullMsg);
|
|
34
34
|
console.error("assert failed:", fullMsg, "\n" + err.stack);
|
|
35
35
|
throw err;
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
const saltRounds = 10;
|
|
39
|
-
const HASHED_PREFIX =
|
|
40
|
-
const parseFieldList = (fields) => (typeof fields ===
|
|
41
|
-
.map(f => f.trim())
|
|
42
|
-
.filter(f => f.length > 0);
|
|
39
|
+
const HASHED_PREFIX = "__hashed__";
|
|
40
|
+
const parseFieldList = (fields) => (typeof fields === "string" ? fields.split(",") : fields)
|
|
41
|
+
.map((f) => f.trim())
|
|
42
|
+
.filter((f) => f.length > 0);
|
|
43
43
|
// String helper functions (faster than RegExp)
|
|
44
|
-
const startsWithDollar = (s) => s.length > 0 && s[0] ===
|
|
45
|
-
const startsWithDoubleUnderscore = (s) => s.length > 1 && s[0] ===
|
|
44
|
+
const startsWithDollar = (s) => s.length > 0 && s[0] === "$";
|
|
45
|
+
const startsWithDoubleUnderscore = (s) => s.length > 1 && s[0] === "_" && s[1] === "_";
|
|
46
46
|
const isServerReserved = (s) => s === "_rev" || s === "_ts";
|
|
47
47
|
const startsWithHashedPrefix = (s) => s.length > 10 && s.startsWith(HASHED_PREFIX);
|
|
48
48
|
const isHex24 = (s) => {
|
|
@@ -60,8 +60,8 @@ const TRANSACTION_OPTIONS = {
|
|
|
60
60
|
defaultTransactionOptions: {
|
|
61
61
|
readPreference: new ReadPreference("primary"),
|
|
62
62
|
readConcern: new ReadConcern("local"),
|
|
63
|
-
writeConcern: new WriteConcern("majority")
|
|
64
|
-
}
|
|
63
|
+
writeConcern: new WriteConcern("majority"),
|
|
64
|
+
},
|
|
65
65
|
};
|
|
66
66
|
// FracturedJson formatter for readable object logging
|
|
67
67
|
const fjOptions = new FracturedJsonOptions();
|
|
@@ -76,7 +76,9 @@ export const fj = (value) => {
|
|
|
76
76
|
if (Array.isArray(value)) {
|
|
77
77
|
return value.map(fj);
|
|
78
78
|
}
|
|
79
|
-
if (value &&
|
|
79
|
+
if (value &&
|
|
80
|
+
typeof value === "object" &&
|
|
81
|
+
Object.getPrototypeOf(value) === Object.prototype) {
|
|
80
82
|
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, fj(v)]));
|
|
81
83
|
}
|
|
82
84
|
return value;
|
|
@@ -86,26 +88,28 @@ export const fj = (value) => {
|
|
|
86
88
|
// `key: undefined` entries (which cry-db routes to `$unset` / `$pull`) are
|
|
87
89
|
// invisible in the log, making bug diagnosis impossible. Walk the structure
|
|
88
90
|
// and replace `undefined` with a readable sentinel.
|
|
89
|
-
const UNDEFINED_MARKER =
|
|
91
|
+
const UNDEFINED_MARKER = "<undefined>";
|
|
90
92
|
const markUndefined = (value) => {
|
|
91
93
|
if (value === undefined)
|
|
92
94
|
return UNDEFINED_MARKER;
|
|
93
95
|
if (Array.isArray(value))
|
|
94
96
|
return value.map(markUndefined);
|
|
95
|
-
if (value &&
|
|
97
|
+
if (value &&
|
|
98
|
+
typeof value === "object" &&
|
|
99
|
+
Object.getPrototypeOf(value) === Object.prototype) {
|
|
96
100
|
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, markUndefined(v)]));
|
|
97
101
|
}
|
|
98
102
|
return value;
|
|
99
103
|
};
|
|
100
104
|
// Wrap log methods to auto-format objects with fracturedjson
|
|
101
|
-
const fjArgs = (args) => args.map(a => {
|
|
105
|
+
const fjArgs = (args) => args.map((a) => {
|
|
102
106
|
var _a;
|
|
103
107
|
if (a === undefined)
|
|
104
108
|
return UNDEFINED_MARKER;
|
|
105
|
-
if (typeof a !==
|
|
109
|
+
if (typeof a !== "object" || a === null)
|
|
106
110
|
return a;
|
|
107
111
|
const replaced = markUndefined(fj(a));
|
|
108
|
-
if (typeof replaced !==
|
|
112
|
+
if (typeof replaced !== "object" || replaced === null)
|
|
109
113
|
return replaced;
|
|
110
114
|
try {
|
|
111
115
|
return (_a = fjFormatter.Serialize(replaced)) !== null && _a !== void 0 ? _a : JSON.stringify(replaced);
|
|
@@ -115,11 +119,21 @@ const fjArgs = (args) => args.map(a => {
|
|
|
115
119
|
}
|
|
116
120
|
});
|
|
117
121
|
const fjLog = {
|
|
118
|
-
debug: (...args) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
debug: (...args) => {
|
|
123
|
+
log.debug(...fjArgs(args));
|
|
124
|
+
},
|
|
125
|
+
trace: (...args) => {
|
|
126
|
+
log.trace(...fjArgs(args));
|
|
127
|
+
},
|
|
128
|
+
info: (...args) => {
|
|
129
|
+
log.info(...fjArgs(args));
|
|
130
|
+
},
|
|
131
|
+
warn: (...args) => {
|
|
132
|
+
log.warn(...fjArgs(args));
|
|
133
|
+
},
|
|
134
|
+
error: (...args) => {
|
|
135
|
+
log.error(...fjArgs(args));
|
|
136
|
+
},
|
|
123
137
|
};
|
|
124
138
|
// export const DummyExportToFixTsCompilation = true;
|
|
125
139
|
export class Mongo extends Db {
|
|
@@ -136,13 +150,13 @@ export class Mongo extends Db {
|
|
|
136
150
|
this.auditCollectionName = "dblog";
|
|
137
151
|
this.auditedCollectionsLower = [];
|
|
138
152
|
this.auditedCollections = this.auditCollections(process.env.AUDIT_COLLECTIONS || []);
|
|
139
|
-
this._findNewerRemoveFields = new Set(parseFieldList(process.env.REMOVE_FIELDS_FOR_FINDNEWER ||
|
|
140
|
-
this._removeFieldsAlways = new Set(parseFieldList(process.env.REMOVE_FIELDS_ALWAYS ||
|
|
153
|
+
this._findNewerRemoveFields = new Set(parseFieldList(process.env.REMOVE_FIELDS_FOR_FINDNEWER || ""));
|
|
154
|
+
this._removeFieldsAlways = new Set(parseFieldList(process.env.REMOVE_FIELDS_ALWAYS || ""));
|
|
141
155
|
this.emitter = new TypedEmitter();
|
|
142
156
|
this.user = undefined;
|
|
143
157
|
this.audit = undefined;
|
|
144
158
|
this._sequencesIndexEnsured = false;
|
|
145
|
-
fjLog.debug(
|
|
159
|
+
fjLog.debug("new Mongo:", this.url, this.db);
|
|
146
160
|
}
|
|
147
161
|
on(evt, listener) {
|
|
148
162
|
fjLog.debug("on", evt, listener);
|
|
@@ -157,19 +171,19 @@ export class Mongo extends Db {
|
|
|
157
171
|
this.emitter.once(evt, listener);
|
|
158
172
|
}
|
|
159
173
|
setUser(username) {
|
|
160
|
-
return this.user = username;
|
|
174
|
+
return (this.user = username);
|
|
161
175
|
}
|
|
162
176
|
setAudit(obj) {
|
|
163
|
-
return this.audit = obj;
|
|
177
|
+
return (this.audit = obj);
|
|
164
178
|
}
|
|
165
179
|
useRevisions(enabled) {
|
|
166
|
-
return this.revisions = !!enabled;
|
|
180
|
+
return (this.revisions = !!enabled);
|
|
167
181
|
}
|
|
168
182
|
usesRevisions() {
|
|
169
183
|
return this.revisions;
|
|
170
184
|
}
|
|
171
185
|
useSoftDelete(enabled) {
|
|
172
|
-
return this.softdelete = !!enabled;
|
|
186
|
+
return (this.softdelete = !!enabled);
|
|
173
187
|
}
|
|
174
188
|
usesSoftDelete() {
|
|
175
189
|
return this.softdelete;
|
|
@@ -180,26 +194,26 @@ export class Mongo extends Db {
|
|
|
180
194
|
* unless `returnArchived: true` is passed in opts.
|
|
181
195
|
*/
|
|
182
196
|
useArchive(enabled) {
|
|
183
|
-
return this.softarchive = !!enabled;
|
|
197
|
+
return (this.softarchive = !!enabled);
|
|
184
198
|
}
|
|
185
199
|
usesArchive() {
|
|
186
200
|
return this.softarchive;
|
|
187
201
|
}
|
|
188
202
|
useAuditing(enabled) {
|
|
189
|
-
return this.auditing = enabled;
|
|
203
|
+
return (this.auditing = enabled);
|
|
190
204
|
}
|
|
191
205
|
usesAuditing() {
|
|
192
206
|
return this.auditing;
|
|
193
207
|
}
|
|
194
208
|
useSync(enabled) {
|
|
195
|
-
return this.syncSupport = enabled;
|
|
209
|
+
return (this.syncSupport = enabled);
|
|
196
210
|
}
|
|
197
211
|
usesSync() {
|
|
198
212
|
return this.syncSupport;
|
|
199
213
|
}
|
|
200
214
|
auditToCollection(c) {
|
|
201
215
|
if (c)
|
|
202
|
-
return this.auditCollectionName = c;
|
|
216
|
+
return (this.auditCollectionName = c);
|
|
203
217
|
return this.auditCollectionName;
|
|
204
218
|
}
|
|
205
219
|
auditCollection(coll, enable = true) {
|
|
@@ -239,17 +253,23 @@ export class Mongo extends Db {
|
|
|
239
253
|
if (arr === undefined)
|
|
240
254
|
return this.auditedCollections;
|
|
241
255
|
if (arr && typeof arr === "string")
|
|
242
|
-
this.auditedCollections = arr
|
|
256
|
+
this.auditedCollections = arr
|
|
257
|
+
.toString()
|
|
258
|
+
.split(",")
|
|
259
|
+
.map((a) => a.trim().toLowerCase())
|
|
260
|
+
.filter((a) => !!a);
|
|
243
261
|
if (arr instanceof Array)
|
|
244
|
-
this.auditedCollections = arr
|
|
245
|
-
|
|
262
|
+
this.auditedCollections = arr
|
|
263
|
+
.map((a) => a.trim().toLowerCase())
|
|
264
|
+
.filter((a) => !!a);
|
|
265
|
+
this.auditedCollectionsLower = this.auditedCollections.map((a) => a.toLowerCase());
|
|
246
266
|
return this.auditedCollections;
|
|
247
267
|
}
|
|
248
268
|
auditingCollection(coll, db = this.db) {
|
|
249
269
|
return this._shouldAuditCollection(db, coll);
|
|
250
270
|
}
|
|
251
271
|
emitPublishEvents(enabled) {
|
|
252
|
-
return this.emittingPublishEvents = enabled;
|
|
272
|
+
return (this.emittingPublishEvents = enabled);
|
|
253
273
|
}
|
|
254
274
|
emitsPublishEvents() {
|
|
255
275
|
return this.emittingPublishEvents;
|
|
@@ -261,7 +281,7 @@ export class Mongo extends Db {
|
|
|
261
281
|
}
|
|
262
282
|
if (enabled)
|
|
263
283
|
fjLog.debug("publishing rev events");
|
|
264
|
-
return this.emittingPublishRevEvents = enabled;
|
|
284
|
+
return (this.emittingPublishRevEvents = enabled);
|
|
265
285
|
}
|
|
266
286
|
emitsPublishRevEvents() {
|
|
267
287
|
return this.emittingPublishRevEvents;
|
|
@@ -273,13 +293,13 @@ export class Mongo extends Db {
|
|
|
273
293
|
async distinct(collection, field, opts = {}) {
|
|
274
294
|
assert(collection, "collection is required", "distinct");
|
|
275
295
|
assert(field, "field is required", "distinct", { collection });
|
|
276
|
-
fjLog.debug(
|
|
296
|
+
fjLog.debug("distinct called", collection, field);
|
|
277
297
|
const dbName = this.db;
|
|
278
298
|
let client = await this.connect();
|
|
279
299
|
let conn = client.db(dbName).collection(collection);
|
|
280
300
|
let ret = await conn.distinct(field, {}, opts);
|
|
281
301
|
ret.sort();
|
|
282
|
-
fjLog.debug(
|
|
302
|
+
fjLog.debug("distinct returns", ret);
|
|
283
303
|
return ret;
|
|
284
304
|
}
|
|
285
305
|
async count(collection, query = {}, opts = {}) {
|
|
@@ -292,11 +312,11 @@ export class Mongo extends Db {
|
|
|
292
312
|
if (!query._archived)
|
|
293
313
|
query._archived = { $exists: false };
|
|
294
314
|
}
|
|
295
|
-
fjLog.debug(
|
|
315
|
+
fjLog.debug("count called", collection, query, opts);
|
|
296
316
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
297
317
|
return await conn.countDocuments(query, opts);
|
|
298
318
|
}, false, { operation: "count", collection, query, opts });
|
|
299
|
-
fjLog.debug(
|
|
319
|
+
fjLog.debug("count returns", ret);
|
|
300
320
|
return ret;
|
|
301
321
|
}
|
|
302
322
|
async find(collection, query = {}, opts = {}) {
|
|
@@ -310,12 +330,12 @@ export class Mongo extends Db {
|
|
|
310
330
|
if (!query._archived)
|
|
311
331
|
query._archived = { $exists: false };
|
|
312
332
|
}
|
|
313
|
-
fjLog.debug(
|
|
333
|
+
fjLog.debug("find called", collection, query, opts);
|
|
314
334
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
315
335
|
let r = this._applyQueryOpts(conn.find(query, this._buildFindOptions(opts)), opts);
|
|
316
336
|
return this._processReturnedObject(await r.toArray());
|
|
317
337
|
}, false, { operation: "find", collection, query, opts });
|
|
318
|
-
fjLog.debug(
|
|
338
|
+
fjLog.debug("find returns", ret);
|
|
319
339
|
return ret;
|
|
320
340
|
}
|
|
321
341
|
async findAll(collection, query = {}, opts = {}) {
|
|
@@ -325,7 +345,7 @@ export class Mongo extends Db {
|
|
|
325
345
|
let r = this._applyQueryOpts(conn.find(query, this._buildFindOptions(opts)), opts);
|
|
326
346
|
return this._processReturnedObject(await r.toArray());
|
|
327
347
|
}, false, { operation: "findAll", collection, query, opts });
|
|
328
|
-
fjLog.debug(
|
|
348
|
+
fjLog.debug("findAll returns", ret);
|
|
329
349
|
return ret;
|
|
330
350
|
}
|
|
331
351
|
async findNewer(collection, timestamp, query = {}, opts = {}) {
|
|
@@ -338,12 +358,12 @@ export class Mongo extends Db {
|
|
|
338
358
|
if (!query._archived)
|
|
339
359
|
query._archived = { $exists: false };
|
|
340
360
|
}
|
|
341
|
-
fjLog.debug(
|
|
361
|
+
fjLog.debug("findNewer called", collection, timestamp, query, opts);
|
|
342
362
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
343
363
|
let r = this._applyQueryOpts(conn.find(query, this._buildFindOptions(opts)).sort({ _ts: 1 }), opts);
|
|
344
364
|
return this._processReturnedObject(await r.toArray(), this._findNewerRemoveFields);
|
|
345
365
|
}, false, { operation: "findNewer", collection, timestamp, query, opts });
|
|
346
|
-
fjLog.debug(
|
|
366
|
+
fjLog.debug("findNewer returns", ret);
|
|
347
367
|
return ret;
|
|
348
368
|
}
|
|
349
369
|
/**
|
|
@@ -357,7 +377,7 @@ export class Mongo extends Db {
|
|
|
357
377
|
*/
|
|
358
378
|
async findNewerMany(spec = [], defaultOpts = {}) {
|
|
359
379
|
var _a;
|
|
360
|
-
fjLog.debug(
|
|
380
|
+
fjLog.debug("findNewerMany called", spec);
|
|
361
381
|
const dbName = this.db; // Capture db at operation start
|
|
362
382
|
let conn = await this.connect();
|
|
363
383
|
const getOneColl = async (coll) => {
|
|
@@ -375,7 +395,11 @@ export class Mongo extends Db {
|
|
|
375
395
|
if (process.env.MONGO_DEBUG_FINDNEWERMANY) {
|
|
376
396
|
fjLog.debug("findNewerMany <-", coll.collection, coll.specId, coll.timestamp, coll.query, " -> ", JSON.stringify(query));
|
|
377
397
|
}
|
|
378
|
-
let r = this._applyQueryOpts(conn
|
|
398
|
+
let r = this._applyQueryOpts(conn
|
|
399
|
+
.db(dbName)
|
|
400
|
+
.collection(coll.collection)
|
|
401
|
+
.find(query, {})
|
|
402
|
+
.sort({ _ts: 1 }), opts);
|
|
379
403
|
let data = await r.toArray();
|
|
380
404
|
if (process.env.MONGO_DEBUG_FINDNEWERMANY) {
|
|
381
405
|
fjLog.debug("findNewerMany ->", coll.collection, JSON.stringify(data, null, 2));
|
|
@@ -397,7 +421,9 @@ export class Mongo extends Db {
|
|
|
397
421
|
for (let coll of spec)
|
|
398
422
|
promises.push(getOneColl(coll));
|
|
399
423
|
let out = {};
|
|
400
|
-
(await Promise.all(promises))
|
|
424
|
+
(await Promise.all(promises))
|
|
425
|
+
.filter((r) => r.data.length)
|
|
426
|
+
.forEach((r) => (out[r.key] = r.data));
|
|
401
427
|
return out;
|
|
402
428
|
}
|
|
403
429
|
}
|
|
@@ -417,7 +443,7 @@ export class Mongo extends Db {
|
|
|
417
443
|
* @param defaultOpts - Default query options applied to all collections
|
|
418
444
|
*/
|
|
419
445
|
async findNewerManyStream(spec = [], onChunk, chunkSize = 200, defaultOpts = {}) {
|
|
420
|
-
fjLog.debug(
|
|
446
|
+
fjLog.debug("findNewerManyStream called", spec);
|
|
421
447
|
const dbName = this.db;
|
|
422
448
|
let conn = await this.connect();
|
|
423
449
|
for (const coll of spec) {
|
|
@@ -431,7 +457,11 @@ export class Mongo extends Db {
|
|
|
431
457
|
if (!query._archived)
|
|
432
458
|
query._archived = { $exists: false };
|
|
433
459
|
}
|
|
434
|
-
let cursor = this._applyQueryOpts(conn
|
|
460
|
+
let cursor = this._applyQueryOpts(conn
|
|
461
|
+
.db(dbName)
|
|
462
|
+
.collection(coll.collection)
|
|
463
|
+
.find(query, {})
|
|
464
|
+
.sort({ _ts: 1 }), opts);
|
|
435
465
|
let chunk = [];
|
|
436
466
|
for await (const doc of cursor) {
|
|
437
467
|
this._processReturnedObject(doc, this._findNewerRemoveFields);
|
|
@@ -449,7 +479,7 @@ export class Mongo extends Db {
|
|
|
449
479
|
async latestTimestamps(collections) {
|
|
450
480
|
assert(collections, "collections is required", "latestTimestamps");
|
|
451
481
|
assert(collections instanceof Array, "collections must be an Array", "latestTimestamps");
|
|
452
|
-
fjLog.debug(
|
|
482
|
+
fjLog.debug("latestTimestamps called", collections);
|
|
453
483
|
const dbName = this.db; // Capture db at operation start
|
|
454
484
|
let conn = await this.connect();
|
|
455
485
|
const getOne = async (collection) => {
|
|
@@ -469,15 +499,17 @@ export class Mongo extends Db {
|
|
|
469
499
|
for (let c of collections)
|
|
470
500
|
promises.push(getOne(c));
|
|
471
501
|
let out = {};
|
|
472
|
-
(await Promise.all(promises)).forEach(r => {
|
|
473
|
-
|
|
474
|
-
|
|
502
|
+
(await Promise.all(promises)).forEach((r) => {
|
|
503
|
+
if (r.ts)
|
|
504
|
+
out[r.collection] = r.ts;
|
|
505
|
+
});
|
|
506
|
+
fjLog.debug("latestTimestamps returns", out);
|
|
475
507
|
return out;
|
|
476
508
|
}
|
|
477
509
|
async latestTimestamp(collection) {
|
|
478
510
|
var _a;
|
|
479
511
|
assert(collection, "collection is required", "latestTimestamp");
|
|
480
|
-
fjLog.debug(
|
|
512
|
+
fjLog.debug("latestTimestamp called", collection);
|
|
481
513
|
const dbName = this.db; // Capture db at operation start
|
|
482
514
|
let conn = await this.connect();
|
|
483
515
|
let cursor = conn
|
|
@@ -489,11 +521,11 @@ export class Mongo extends Db {
|
|
|
489
521
|
.limit(1);
|
|
490
522
|
let res = await cursor.toArray();
|
|
491
523
|
let ts = (_a = res === null || res === void 0 ? void 0 : res[0]) === null || _a === void 0 ? void 0 : _a._ts;
|
|
492
|
-
fjLog.debug(
|
|
524
|
+
fjLog.debug("latestTimestamp returns", ts);
|
|
493
525
|
return ts;
|
|
494
526
|
}
|
|
495
527
|
_createQueryForNewer(timestamp, query) {
|
|
496
|
-
let ts =
|
|
528
|
+
let ts = timestamp === 1 || timestamp === "1" || timestamp === "0" || !timestamp
|
|
497
529
|
? {}
|
|
498
530
|
: { _ts: { $gt: Base.timestamp(timestamp) } };
|
|
499
531
|
if (query) {
|
|
@@ -572,9 +604,12 @@ export class Mongo extends Db {
|
|
|
572
604
|
query._archived = { $exists: false };
|
|
573
605
|
}
|
|
574
606
|
// if (!query._blocked) query._blocked = { $exists: false }; // intentionally - blocked records are returned
|
|
575
|
-
fjLog.debug(
|
|
576
|
-
let ret = await this.executeTransactionally(collection, async (conn) => await conn.findOne(query, {
|
|
577
|
-
|
|
607
|
+
fjLog.debug("findOne called", collection, query, projection);
|
|
608
|
+
let ret = await this.executeTransactionally(collection, async (conn) => await conn.findOne(query, {
|
|
609
|
+
...(projection ? { projection } : {}),
|
|
610
|
+
...this._sessionOpt(),
|
|
611
|
+
}), false, { operation: "findOne", collection, query, projection });
|
|
612
|
+
fjLog.debug("findOne returns", ret);
|
|
578
613
|
return this._processReturnedObject(ret);
|
|
579
614
|
}
|
|
580
615
|
async findById(collection, id, projection, opts = {}) {
|
|
@@ -592,27 +627,30 @@ export class Mongo extends Db {
|
|
|
592
627
|
if (this.softarchive && !opts.returnArchived) {
|
|
593
628
|
query._archived = { $exists: false };
|
|
594
629
|
}
|
|
595
|
-
fjLog.debug(
|
|
596
|
-
fjLog.trace(
|
|
630
|
+
fjLog.debug("findById called", dbName, collection, id, projection);
|
|
631
|
+
fjLog.trace("findById executing with query", collection, query, projection);
|
|
597
632
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
598
|
-
let r = await conn.findOne(query, {
|
|
633
|
+
let r = await conn.findOne(query, {
|
|
634
|
+
...(projection ? { projection } : {}),
|
|
635
|
+
...this._sessionOpt(),
|
|
636
|
+
});
|
|
599
637
|
return r;
|
|
600
638
|
}, false, { operation: "findById", collection, id, projection });
|
|
601
|
-
fjLog.debug(
|
|
639
|
+
fjLog.debug("findById returns", ret);
|
|
602
640
|
return this._processReturnedObject(ret);
|
|
603
641
|
}
|
|
604
642
|
async findByIdsInManyCollections(request, opts = {}) {
|
|
605
643
|
if (!request || request.length === 0)
|
|
606
644
|
return {};
|
|
607
645
|
const dbName = this.db;
|
|
608
|
-
fjLog.debug(
|
|
646
|
+
fjLog.debug("findByIdsInManyCollections called", dbName, request);
|
|
609
647
|
// Process all collections in parallel
|
|
610
648
|
const fetchOne = async (req) => {
|
|
611
649
|
const { collection, ids, projection } = req;
|
|
612
650
|
if (!collection || !ids || ids.length === 0) {
|
|
613
651
|
return { collection, entities: [] };
|
|
614
652
|
}
|
|
615
|
-
const objectIds = ids.map(id => Mongo._toId(id));
|
|
653
|
+
const objectIds = ids.map((id) => Mongo._toId(id));
|
|
616
654
|
const entities = await this.executeTransactionally(collection, async (conn) => {
|
|
617
655
|
let query = { _id: { $in: objectIds } };
|
|
618
656
|
if (this.softdelete && !opts.returnDeleted) {
|
|
@@ -621,17 +659,28 @@ export class Mongo extends Db {
|
|
|
621
659
|
if (this.softarchive && !opts.returnArchived) {
|
|
622
660
|
query._archived = { $exists: false };
|
|
623
661
|
}
|
|
624
|
-
let r = conn.find(query, {
|
|
662
|
+
let r = conn.find(query, {
|
|
663
|
+
...(projection ? { projection } : {}),
|
|
664
|
+
...this._sessionOpt(),
|
|
665
|
+
});
|
|
625
666
|
return await r.toArray();
|
|
626
|
-
}, false, {
|
|
627
|
-
|
|
667
|
+
}, false, {
|
|
668
|
+
operation: "findByIdsInManyCollections",
|
|
669
|
+
collection,
|
|
670
|
+
ids,
|
|
671
|
+
projection,
|
|
672
|
+
});
|
|
673
|
+
return {
|
|
674
|
+
collection,
|
|
675
|
+
entities: this._processReturnedObject(entities),
|
|
676
|
+
};
|
|
628
677
|
};
|
|
629
678
|
const results = await Promise.all(request.map(fetchOne));
|
|
630
679
|
const result = {};
|
|
631
680
|
for (const { collection, entities } of results) {
|
|
632
681
|
result[collection] = entities;
|
|
633
682
|
}
|
|
634
|
-
fjLog.debug(
|
|
683
|
+
fjLog.debug("findByIdsInManyCollections returns", result);
|
|
635
684
|
return result;
|
|
636
685
|
}
|
|
637
686
|
async findByIds(collection, ids, projection, opts = {}) {
|
|
@@ -640,8 +689,8 @@ export class Mongo extends Db {
|
|
|
640
689
|
if (!ids || ids.length === 0)
|
|
641
690
|
return [];
|
|
642
691
|
const dbName = this.db;
|
|
643
|
-
const objectIds = ids.map(id => Mongo._toId(id));
|
|
644
|
-
fjLog.debug(
|
|
692
|
+
const objectIds = ids.map((id) => Mongo._toId(id));
|
|
693
|
+
fjLog.debug("findByIds called", dbName, collection, ids, projection);
|
|
645
694
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
646
695
|
let query = { _id: { $in: objectIds } };
|
|
647
696
|
if (this.softdelete && !opts.returnDeleted) {
|
|
@@ -650,10 +699,13 @@ export class Mongo extends Db {
|
|
|
650
699
|
if (this.softarchive && !opts.returnArchived) {
|
|
651
700
|
query._archived = { $exists: false };
|
|
652
701
|
}
|
|
653
|
-
let r = conn.find(query, {
|
|
702
|
+
let r = conn.find(query, {
|
|
703
|
+
...(projection ? { projection } : {}),
|
|
704
|
+
...this._sessionOpt(),
|
|
705
|
+
});
|
|
654
706
|
return await r.toArray();
|
|
655
707
|
}, false, { operation: "findByIds", collection, ids, projection });
|
|
656
|
-
fjLog.debug(
|
|
708
|
+
fjLog.debug("findByIds returns", ret);
|
|
657
709
|
return this._processReturnedObject(ret);
|
|
658
710
|
}
|
|
659
711
|
async updateOne(collection, query, update, options = { returnFullObject: false }) {
|
|
@@ -666,7 +718,7 @@ export class Mongo extends Db {
|
|
|
666
718
|
let opts = {
|
|
667
719
|
upsert: false,
|
|
668
720
|
returnDocument: "after",
|
|
669
|
-
...this._sessionOpt()
|
|
721
|
+
...this._sessionOpt(),
|
|
670
722
|
};
|
|
671
723
|
if (this._hasHashedKeys(update))
|
|
672
724
|
await this._processHashedKeys(update);
|
|
@@ -682,24 +734,33 @@ export class Mongo extends Db {
|
|
|
682
734
|
const isPipeline = Array.isArray(processed.update);
|
|
683
735
|
// For pipeline form, sequence-keys auto-injection cannot recurse into array of stages.
|
|
684
736
|
// Skip the `update.$set` normalization and seqKeys path entirely when pipeline.
|
|
685
|
-
let seqKeys = isPipeline
|
|
686
|
-
|
|
737
|
+
let seqKeys = isPipeline
|
|
738
|
+
? undefined
|
|
739
|
+
: this._findSequenceKeys(update.$set);
|
|
740
|
+
fjLog.debug("updateOne called", collection, query, update);
|
|
687
741
|
let obj = await this.executeTransactionally(collection, async (conn, client) => {
|
|
688
742
|
if (!isPipeline) {
|
|
689
743
|
update.$set = update.$set || {};
|
|
690
744
|
if (seqKeys)
|
|
691
745
|
await this._processSequenceField(client, dbName, collection, update.$set, seqKeys);
|
|
692
|
-
if (update.$set === undefined ||
|
|
746
|
+
if (update.$set === undefined ||
|
|
747
|
+
Object.keys(update.$set).length === 0)
|
|
693
748
|
delete update.$set;
|
|
694
749
|
}
|
|
695
750
|
let res = await conn.findOneAndUpdate(query, processed.update, opts);
|
|
696
751
|
if (!res)
|
|
697
752
|
return null;
|
|
698
753
|
let resObj = this._buildPublishDelta(res, inputUpdate, !!(options === null || options === void 0 ? void 0 : options.returnFullObject));
|
|
699
|
-
await this._publishAndAudit(
|
|
754
|
+
await this._publishAndAudit("update", dbName, collection, resObj, false, inputUpdate);
|
|
700
755
|
return resObj;
|
|
701
|
-
}, !!seqKeys, {
|
|
702
|
-
|
|
756
|
+
}, !!seqKeys, {
|
|
757
|
+
operation: "updateOne",
|
|
758
|
+
collection,
|
|
759
|
+
query,
|
|
760
|
+
update: processed.update,
|
|
761
|
+
options,
|
|
762
|
+
});
|
|
763
|
+
fjLog.debug("updateOne returns", obj);
|
|
703
764
|
return this._processReturnedObject(await obj);
|
|
704
765
|
}
|
|
705
766
|
async save(collection, update, id = undefined, options = { returnFullObject: false }) {
|
|
@@ -710,7 +771,7 @@ export class Mongo extends Db {
|
|
|
710
771
|
let opts = {
|
|
711
772
|
upsert: true,
|
|
712
773
|
returnDocument: "after",
|
|
713
|
-
...this._sessionOpt()
|
|
774
|
+
...this._sessionOpt(),
|
|
714
775
|
};
|
|
715
776
|
let _id = Mongo.toId(id || update._id) || Mongo.newid();
|
|
716
777
|
if (this._hasHashedKeys(update))
|
|
@@ -721,24 +782,25 @@ export class Mongo extends Db {
|
|
|
721
782
|
if (processed.arrayFilters)
|
|
722
783
|
opts.arrayFilters = processed.arrayFilters;
|
|
723
784
|
const isPipeline = Array.isArray(processed.update);
|
|
724
|
-
fjLog.debug(
|
|
785
|
+
fjLog.debug("save called", collection, id, update);
|
|
725
786
|
let seqKeys = isPipeline ? undefined : this._findSequenceKeys(update.$set);
|
|
726
787
|
let obj = await this.executeTransactionally(collection, async (conn, client) => {
|
|
727
788
|
if (!isPipeline) {
|
|
728
789
|
update.$set = update.$set || {};
|
|
729
790
|
if (seqKeys)
|
|
730
791
|
await this._processSequenceField(client, dbName, collection, update.$set, seqKeys);
|
|
731
|
-
if (update.$set === undefined ||
|
|
792
|
+
if (update.$set === undefined ||
|
|
793
|
+
Object.keys(update.$set).length === 0)
|
|
732
794
|
delete update.$set;
|
|
733
795
|
}
|
|
734
796
|
let res = await conn.findOneAndUpdate({ _id }, processed.update, opts);
|
|
735
797
|
if (!res)
|
|
736
798
|
return null;
|
|
737
799
|
let resObj = this._buildPublishDelta(res, inputUpdate, !!(options === null || options === void 0 ? void 0 : options.returnFullObject));
|
|
738
|
-
await this._publishAndAudit(
|
|
800
|
+
await this._publishAndAudit("update", dbName, collection, resObj, false, inputUpdate);
|
|
739
801
|
return resObj;
|
|
740
802
|
}, !!seqKeys, { operation: "save", collection, _id, update: processed.update, options });
|
|
741
|
-
fjLog.debug(
|
|
803
|
+
fjLog.debug("save returns", obj);
|
|
742
804
|
return this._processReturnedObject(await obj);
|
|
743
805
|
}
|
|
744
806
|
async update(collection, query, update) {
|
|
@@ -754,7 +816,7 @@ export class Mongo extends Db {
|
|
|
754
816
|
let opts = {
|
|
755
817
|
upsert: false,
|
|
756
818
|
returnDocument: "after",
|
|
757
|
-
...this._sessionOpt()
|
|
819
|
+
...this._sessionOpt(),
|
|
758
820
|
};
|
|
759
821
|
if (this._hasHashedKeys(update))
|
|
760
822
|
await this._processHashedKeys(update);
|
|
@@ -763,36 +825,39 @@ export class Mongo extends Db {
|
|
|
763
825
|
if (processed.arrayFilters)
|
|
764
826
|
opts.arrayFilters = processed.arrayFilters;
|
|
765
827
|
const isPipeline = Array.isArray(processed.update);
|
|
766
|
-
fjLog.debug(
|
|
828
|
+
fjLog.debug("update called", collection, query, update);
|
|
767
829
|
let seqKeys = isPipeline ? undefined : this._findSequenceKeys(update.$set);
|
|
768
830
|
let obj = await this.executeTransactionally(collection, async (conn, client) => {
|
|
769
831
|
if (!isPipeline) {
|
|
770
832
|
update.$set = update.$set || {};
|
|
771
833
|
if (seqKeys)
|
|
772
834
|
await this._processSequenceField(client, dbName, collection, update.$set, seqKeys);
|
|
773
|
-
if (update.$set === undefined ||
|
|
835
|
+
if (update.$set === undefined ||
|
|
836
|
+
Object.keys(update.$set).length === 0)
|
|
774
837
|
delete update.$set;
|
|
775
838
|
}
|
|
776
|
-
fjLog.debug(
|
|
839
|
+
fjLog.debug("update called", collection, query, update);
|
|
777
840
|
let res = await conn.updateMany(query, processed.update, opts);
|
|
778
841
|
let resObj = {
|
|
779
842
|
n: res.modifiedCount,
|
|
780
|
-
ok: !!res.acknowledged
|
|
843
|
+
ok: !!res.acknowledged,
|
|
781
844
|
};
|
|
782
845
|
// No `rawUpdate` for `updateMany` — mongo doesn't return per-doc _id-s,
|
|
783
846
|
// so the spec is not actionable on the consumer side. Subscribers see
|
|
784
847
|
// `enquireLastTs: true` on the rev event and refetch via `findNewer*`.
|
|
785
|
-
await this._publishAndAudit(
|
|
848
|
+
await this._publishAndAudit("updateMany", dbName, collection, resObj);
|
|
786
849
|
return resObj;
|
|
787
850
|
}, !!seqKeys, { operation: "update", collection, query, update: processed.update });
|
|
788
|
-
fjLog.debug(
|
|
851
|
+
fjLog.debug("update returns", obj);
|
|
789
852
|
return await obj;
|
|
790
853
|
}
|
|
791
854
|
async upsert(collection, query, update, options = { returnFullObject: false }) {
|
|
792
855
|
assert(collection, "collection is required", "upsert");
|
|
793
856
|
assert(query, "query is required", "upsert", { collection });
|
|
794
857
|
assert(update, "update is required", "upsert", { collection });
|
|
795
|
-
assert(typeof update ===
|
|
858
|
+
assert(typeof update === "object", "update must be an object", "upsert", {
|
|
859
|
+
collection,
|
|
860
|
+
});
|
|
796
861
|
// if (!Object.keys(update).length) return null;
|
|
797
862
|
const dbName = this.db; // Capture db at operation start
|
|
798
863
|
query = this.replaceIds(query);
|
|
@@ -801,9 +866,9 @@ export class Mongo extends Db {
|
|
|
801
866
|
...options,
|
|
802
867
|
upsert: true,
|
|
803
868
|
returnDocument: "after",
|
|
804
|
-
...this._sessionOpt()
|
|
869
|
+
...this._sessionOpt(),
|
|
805
870
|
};
|
|
806
|
-
fjLog.debug(
|
|
871
|
+
fjLog.debug("upsert called", collection, query, update);
|
|
807
872
|
if (this._hasHashedKeys(update))
|
|
808
873
|
await this._processHashedKeys(update);
|
|
809
874
|
const inputUpdate = cloneDeep(update);
|
|
@@ -813,7 +878,7 @@ export class Mongo extends Db {
|
|
|
813
878
|
opts.arrayFilters = processed.arrayFilters;
|
|
814
879
|
const isPipeline = Array.isArray(processed.update);
|
|
815
880
|
let seqKeys = isPipeline ? undefined : this._findSequenceKeys(update.$set);
|
|
816
|
-
fjLog.debug(
|
|
881
|
+
fjLog.debug("upsert processed", collection, query, update);
|
|
817
882
|
if (Object.keys(query).length === 0)
|
|
818
883
|
query._id = Mongo.newid();
|
|
819
884
|
let ret = await this.executeTransactionally(collection, async (conn, client) => {
|
|
@@ -821,7 +886,8 @@ export class Mongo extends Db {
|
|
|
821
886
|
update.$set = update.$set || {};
|
|
822
887
|
if (seqKeys)
|
|
823
888
|
await this._processSequenceField(client, dbName, collection, update.$set, seqKeys);
|
|
824
|
-
if (update.$set === undefined ||
|
|
889
|
+
if (update.$set === undefined ||
|
|
890
|
+
Object.keys(update.$set).length === 0)
|
|
825
891
|
delete update.$set;
|
|
826
892
|
}
|
|
827
893
|
let ret = await conn.findOneAndUpdate(query, processed.update, opts);
|
|
@@ -832,21 +898,24 @@ export class Mongo extends Db {
|
|
|
832
898
|
// For inserts, surface the full record (query fields like {a:4} need to land);
|
|
833
899
|
// for updates, build the de-bracketed delta. rawUpdate is sent on update only —
|
|
834
900
|
// an insert isn't a delta against a prior state, so the spec adds no signal there.
|
|
835
|
-
let retObj = isInsert
|
|
901
|
+
let retObj = isInsert
|
|
902
|
+
? ret
|
|
903
|
+
: this._buildPublishDelta(ret, inputUpdate, !!(options === null || options === void 0 ? void 0 : options.returnFullObject));
|
|
836
904
|
await this._publishAndAudit(oper, dbName, collection, retObj, false, isInsert ? undefined : inputUpdate);
|
|
837
905
|
return this._buildPublishDelta(ret, inputUpdate, !!(options === null || options === void 0 ? void 0 : options.returnFullObject));
|
|
838
906
|
}
|
|
839
|
-
;
|
|
840
907
|
return ret;
|
|
841
908
|
}, !!seqKeys, { operation: "upsert", query, update, options });
|
|
842
|
-
fjLog.debug(
|
|
909
|
+
fjLog.debug("upsert returns", ret);
|
|
843
910
|
return this._processReturnedObject(await ret);
|
|
844
911
|
}
|
|
845
912
|
async insert(collection, insert) {
|
|
846
913
|
assert(collection, "collection is required", "insert");
|
|
847
914
|
assert(insert, "insert is required", "insert", { collection });
|
|
848
|
-
assert(typeof insert === "object", "insert must be an object", "insert", {
|
|
849
|
-
|
|
915
|
+
assert(typeof insert === "object", "insert must be an object", "insert", {
|
|
916
|
+
collection,
|
|
917
|
+
});
|
|
918
|
+
fjLog.debug("insert called", collection, insert);
|
|
850
919
|
const dbName = this.db; // Capture db at operation start
|
|
851
920
|
insert = this.replaceIds(insert);
|
|
852
921
|
this._stripDoubleUnderscoreKeys(insert);
|
|
@@ -861,10 +930,10 @@ export class Mongo extends Db {
|
|
|
861
930
|
insert = await this._processSequenceField(client, dbName, collection, insert, seqKeys);
|
|
862
931
|
let obj = await conn.insertOne(insert, this._sessionOpt());
|
|
863
932
|
let fullObj = { _id: obj.insertedId, ...insert };
|
|
864
|
-
await this._publishAndAudit(
|
|
933
|
+
await this._publishAndAudit("insert", dbName, collection, fullObj);
|
|
865
934
|
return fullObj;
|
|
866
935
|
}, !!seqKeys, { operation: "insert", collection, insert });
|
|
867
|
-
fjLog.debug(
|
|
936
|
+
fjLog.debug("insert returns", ret);
|
|
868
937
|
return this._processReturnedObject(await ret);
|
|
869
938
|
}
|
|
870
939
|
/**
|
|
@@ -880,7 +949,7 @@ export class Mongo extends Db {
|
|
|
880
949
|
assert(collectionsBatches, "collectionsBatches is required", "updateCollections");
|
|
881
950
|
assert(collectionsBatches instanceof Array, "collectionsBatches must be an Array", "updateCollections");
|
|
882
951
|
const debugMethod = MONGO_DEBUG_UPDATE_COLLECTIONS ? "info" : "debug";
|
|
883
|
-
(_a = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _a === void 0 ? void 0 : _a.call(fjLog,
|
|
952
|
+
(_a = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _a === void 0 ? void 0 : _a.call(fjLog, "updateCollections called", collectionsBatches.length, "collections");
|
|
884
953
|
(_b = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _b === void 0 ? void 0 : _b.call(fjLog, collectionsBatches);
|
|
885
954
|
const dbName = this.db;
|
|
886
955
|
const conn = await this.connect();
|
|
@@ -898,7 +967,7 @@ export class Mongo extends Db {
|
|
|
898
967
|
updated: [],
|
|
899
968
|
deleted: [],
|
|
900
969
|
mustRefresh: [],
|
|
901
|
-
}
|
|
970
|
+
},
|
|
902
971
|
};
|
|
903
972
|
const batchData = [];
|
|
904
973
|
const errors = [];
|
|
@@ -907,12 +976,12 @@ export class Mongo extends Db {
|
|
|
907
976
|
// server-side (SEQ_*/__hashed__)? Captured BEFORE the bulk sequence
|
|
908
977
|
// resolution and per-update hashing mutate the updates in place, so
|
|
909
978
|
// the placeholders are still observable here.
|
|
910
|
-
const updateResolvedFlags = (updates !== null && updates !== void 0 ? updates : []).map(u => this._hasSequenceFields(u.update) || this._hasHashedKeys(u.update));
|
|
979
|
+
const updateResolvedFlags = (updates !== null && updates !== void 0 ? updates : []).map((u) => this._hasSequenceFields(u.update) || this._hasHashedKeys(u.update));
|
|
911
980
|
// Handle sequences in updates before processing
|
|
912
981
|
if (updates === null || updates === void 0 ? void 0 : updates.length) {
|
|
913
|
-
const updatesWithSeq = updates.filter(u => this._hasSequenceFields(u.update));
|
|
982
|
+
const updatesWithSeq = updates.filter((u) => this._hasSequenceFields(u.update));
|
|
914
983
|
if (updatesWithSeq.length > 0) {
|
|
915
|
-
await this._processSequenceFieldForMany(conn, dbName, collection, updatesWithSeq.map(u => u.update));
|
|
984
|
+
await this._processSequenceFieldForMany(conn, dbName, collection, updatesWithSeq.map((u) => u.update));
|
|
916
985
|
}
|
|
917
986
|
}
|
|
918
987
|
// Process updates (with upsert) in parallel
|
|
@@ -929,7 +998,7 @@ export class Mongo extends Db {
|
|
|
929
998
|
const processed = this._applyBracketProcessing(processedBase);
|
|
930
999
|
if (processed.warnings) {
|
|
931
1000
|
for (const w of processed.warnings) {
|
|
932
|
-
const msg = w.kind ===
|
|
1001
|
+
const msg = w.kind === "overlap"
|
|
933
1002
|
? `same-id overlap at "${w.path}" — terminal-bracket value ${JSON.stringify(w.value)} sets the element base; sub-field overlay applies on top`
|
|
934
1003
|
: `dropped nested-bracket ${w.op} path "${w.path}" = ${JSON.stringify(w.value)}`;
|
|
935
1004
|
warnings.push({ _id: String(_id), error: msg });
|
|
@@ -939,8 +1008,10 @@ export class Mongo extends Db {
|
|
|
939
1008
|
upsert: true,
|
|
940
1009
|
returnDocument: "after",
|
|
941
1010
|
includeResultMetadata: true,
|
|
942
|
-
...(processed.arrayFilters
|
|
943
|
-
|
|
1011
|
+
...(processed.arrayFilters
|
|
1012
|
+
? { arrayFilters: processed.arrayFilters }
|
|
1013
|
+
: {}),
|
|
1014
|
+
...this._sessionOpt(),
|
|
944
1015
|
};
|
|
945
1016
|
(_a = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _a === void 0 ? void 0 : _a.call(fjLog, ` updateCollections findOneAndUpdate ${dbName}.${collection}`, { _id: objectId }, "UPDATE", processed.update, "opts", opts);
|
|
946
1017
|
const res = await conn
|
|
@@ -950,7 +1021,9 @@ export class Mongo extends Db {
|
|
|
950
1021
|
if (res === null || res === void 0 ? void 0 : res.value) {
|
|
951
1022
|
// Determine if this was an insert or update based on lastErrorObject
|
|
952
1023
|
const wasInsert = !((_b = res.lastErrorObject) === null || _b === void 0 ? void 0 : _b.updatedExisting);
|
|
953
|
-
const operation = wasInsert
|
|
1024
|
+
const operation = wasInsert
|
|
1025
|
+
? "insert"
|
|
1026
|
+
: "update";
|
|
954
1027
|
// mustRefresh: the client must replace its local copy when cry-db
|
|
955
1028
|
// resolved a field (SEQ_*/__hashed__) OR a concurrent external write
|
|
956
1029
|
// advanced the stored `_rev` beyond this write (gap > the +1 this write
|
|
@@ -958,10 +1031,10 @@ export class Mongo extends Db {
|
|
|
958
1031
|
// old-client fallback. Built from a sanitized clone (hash never leaks).
|
|
959
1032
|
const writtenRev = res.value._rev;
|
|
960
1033
|
const inc = this.revisions ? 1 : 0;
|
|
961
|
-
const externallyModified = !wasInsert
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const mustRefresh =
|
|
1034
|
+
const externallyModified = !wasInsert &&
|
|
1035
|
+
typeof writtenRev === "number" &&
|
|
1036
|
+
writtenRev - inc > (clientRev !== null && clientRev !== void 0 ? clientRev : 0);
|
|
1037
|
+
const mustRefresh = updateResolvedFlags[idx] || externallyModified
|
|
965
1038
|
? this._buildMustRefreshRecord(res.value)
|
|
966
1039
|
: undefined;
|
|
967
1040
|
// Inserts surface the full doc (query fields must reach the client).
|
|
@@ -972,12 +1045,24 @@ export class Mongo extends Db {
|
|
|
972
1045
|
? res.value
|
|
973
1046
|
: this._buildPublishDelta(res.value, inputUpdate, false);
|
|
974
1047
|
this._processReturnedObject(retObj);
|
|
975
|
-
return {
|
|
1048
|
+
return {
|
|
1049
|
+
success: true,
|
|
1050
|
+
_id,
|
|
1051
|
+
data: retObj,
|
|
1052
|
+
operation,
|
|
1053
|
+
wasInsert,
|
|
1054
|
+
rawUpdate: wasInsert ? undefined : inputUpdate,
|
|
1055
|
+
mustRefresh,
|
|
1056
|
+
};
|
|
976
1057
|
}
|
|
977
|
-
return { success: false, _id, error:
|
|
1058
|
+
return { success: false, _id, error: "Operation failed" };
|
|
978
1059
|
}
|
|
979
1060
|
catch (err) {
|
|
980
|
-
return {
|
|
1061
|
+
return {
|
|
1062
|
+
success: false,
|
|
1063
|
+
_id,
|
|
1064
|
+
error: err.message || String(err),
|
|
1065
|
+
};
|
|
981
1066
|
}
|
|
982
1067
|
});
|
|
983
1068
|
const updateResults = await Promise.all(updatePromises);
|
|
@@ -1021,26 +1106,32 @@ export class Mongo extends Db {
|
|
|
1021
1106
|
// Hard delete applies no `_rev` increment: the removed doc's `_rev`
|
|
1022
1107
|
// exceeding the client's base means an external write landed first.
|
|
1023
1108
|
// Omitted `_rev` (clientRev → 0) forces a refresh — old-client fallback.
|
|
1024
|
-
const externallyModified = typeof res._rev ===
|
|
1025
|
-
|
|
1109
|
+
const externallyModified = typeof res._rev === "number" &&
|
|
1110
|
+
res._rev > (clientRev !== null && clientRev !== void 0 ? clientRev : 0);
|
|
1026
1111
|
const mustRefresh = externallyModified
|
|
1027
1112
|
? this._buildMustRefreshRecord(res)
|
|
1028
1113
|
: undefined;
|
|
1029
1114
|
this._processReturnedObject(res);
|
|
1030
|
-
return {
|
|
1115
|
+
return {
|
|
1116
|
+
success: true,
|
|
1117
|
+
_id,
|
|
1118
|
+
data: res,
|
|
1119
|
+
operation: "delete",
|
|
1120
|
+
mustRefresh,
|
|
1121
|
+
};
|
|
1031
1122
|
}
|
|
1032
|
-
return { success: false, _id, error:
|
|
1123
|
+
return { success: false, _id, error: "Document not found" };
|
|
1033
1124
|
}
|
|
1034
1125
|
else {
|
|
1035
1126
|
const del = {
|
|
1036
1127
|
$set: { _deleted: new Date() },
|
|
1037
1128
|
$inc: { _rev: 1 },
|
|
1038
|
-
$currentDate: { _ts: { $type:
|
|
1129
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1039
1130
|
};
|
|
1040
1131
|
const opts = {
|
|
1041
1132
|
upsert: false,
|
|
1042
1133
|
returnDocument: "after",
|
|
1043
|
-
...this._sessionOpt()
|
|
1134
|
+
...this._sessionOpt(),
|
|
1044
1135
|
};
|
|
1045
1136
|
const res = await conn
|
|
1046
1137
|
.db(dbName)
|
|
@@ -1050,19 +1141,29 @@ export class Mongo extends Db {
|
|
|
1050
1141
|
// Soft delete applied a +1 `_rev` increment: a gap beyond that +1
|
|
1051
1142
|
// means an external write modified the record before this delete.
|
|
1052
1143
|
// Omitted `_rev` (clientRev → 0) forces a refresh — old-client fallback.
|
|
1053
|
-
const externallyModified = typeof res._rev ===
|
|
1054
|
-
|
|
1144
|
+
const externallyModified = typeof res._rev === "number" &&
|
|
1145
|
+
res._rev - 1 > (clientRev !== null && clientRev !== void 0 ? clientRev : 0);
|
|
1055
1146
|
const mustRefresh = externallyModified
|
|
1056
1147
|
? this._buildMustRefreshRecord(res)
|
|
1057
1148
|
: undefined;
|
|
1058
1149
|
this._processReturnedObject(res);
|
|
1059
|
-
return {
|
|
1150
|
+
return {
|
|
1151
|
+
success: true,
|
|
1152
|
+
_id,
|
|
1153
|
+
data: res,
|
|
1154
|
+
operation: "delete",
|
|
1155
|
+
mustRefresh,
|
|
1156
|
+
};
|
|
1060
1157
|
}
|
|
1061
|
-
return { success: false, _id, error:
|
|
1158
|
+
return { success: false, _id, error: "Document not found" };
|
|
1062
1159
|
}
|
|
1063
1160
|
}
|
|
1064
1161
|
catch (err) {
|
|
1065
|
-
return {
|
|
1162
|
+
return {
|
|
1163
|
+
success: false,
|
|
1164
|
+
_id,
|
|
1165
|
+
error: err.message || String(err),
|
|
1166
|
+
};
|
|
1066
1167
|
}
|
|
1067
1168
|
});
|
|
1068
1169
|
const deleteResults = await Promise.all(deletePromises);
|
|
@@ -1099,8 +1200,8 @@ export class Mongo extends Db {
|
|
|
1099
1200
|
operation: "batch",
|
|
1100
1201
|
db: dbName,
|
|
1101
1202
|
collection,
|
|
1102
|
-
data: batchData
|
|
1103
|
-
}
|
|
1203
|
+
data: batchData,
|
|
1204
|
+
},
|
|
1104
1205
|
});
|
|
1105
1206
|
}
|
|
1106
1207
|
if (this.emittingPublishRevEvents) {
|
|
@@ -1108,11 +1209,11 @@ export class Mongo extends Db {
|
|
|
1108
1209
|
operation: "batch",
|
|
1109
1210
|
db: dbName,
|
|
1110
1211
|
collection,
|
|
1111
|
-
data: batchData.map(item => ({
|
|
1212
|
+
data: batchData.map((item) => ({
|
|
1112
1213
|
operation: item.operation,
|
|
1113
1214
|
_id: item.data._id,
|
|
1114
1215
|
_ts: item.data._ts,
|
|
1115
|
-
}))
|
|
1216
|
+
})),
|
|
1116
1217
|
};
|
|
1117
1218
|
this.emit("publishRev", {
|
|
1118
1219
|
channel: `dbrev/${dbName}/${collection}`,
|
|
@@ -1128,22 +1229,24 @@ export class Mongo extends Db {
|
|
|
1128
1229
|
results.push(result);
|
|
1129
1230
|
}
|
|
1130
1231
|
}
|
|
1131
|
-
(_c = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _c === void 0 ? void 0 : _c.call(fjLog,
|
|
1232
|
+
(_c = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _c === void 0 ? void 0 : _c.call(fjLog, "updateCollections returns", results);
|
|
1132
1233
|
return results;
|
|
1133
1234
|
}
|
|
1134
1235
|
async upsertBatch(collection, batch) {
|
|
1135
1236
|
assert(collection, "collection is required", "upsertBatch");
|
|
1136
1237
|
assert(batch, "batch is required", "upsertBatch", { collection });
|
|
1137
|
-
assert(batch instanceof Array, "batch must be an Array", "upsertBatch", {
|
|
1138
|
-
|
|
1238
|
+
assert(batch instanceof Array, "batch must be an Array", "upsertBatch", {
|
|
1239
|
+
collection,
|
|
1240
|
+
});
|
|
1241
|
+
fjLog.debug("upsertBatch called", collection, batch);
|
|
1139
1242
|
const dbName = this.db; // Capture db at operation start
|
|
1140
1243
|
batch = this.replaceIds(batch);
|
|
1141
|
-
await Promise.all(batch.map(item => this._processHashedKeys(item === null || item === void 0 ? void 0 : item.update)));
|
|
1244
|
+
await Promise.all(batch.map((item) => this._processHashedKeys(item === null || item === void 0 ? void 0 : item.update)));
|
|
1142
1245
|
let ret = await this.executeTransactionally(collection, async (conn, client) => {
|
|
1143
1246
|
// Only process sequences if any batch item has SEQ_NEXT or SEQ_LAST
|
|
1144
|
-
const updatesWithSeq = batch.filter(b => this._hasSequenceFields(b.update));
|
|
1247
|
+
const updatesWithSeq = batch.filter((b) => this._hasSequenceFields(b.update));
|
|
1145
1248
|
if (updatesWithSeq.length > 0) {
|
|
1146
|
-
await this._processSequenceFieldForMany(client, dbName, collection, updatesWithSeq.map(b => b.update));
|
|
1249
|
+
await this._processSequenceFieldForMany(client, dbName, collection, updatesWithSeq.map((b) => b.update));
|
|
1147
1250
|
}
|
|
1148
1251
|
// Process all updates in parallel
|
|
1149
1252
|
const upsertPromises = batch.map(async (part) => {
|
|
@@ -1154,7 +1257,7 @@ export class Mongo extends Db {
|
|
|
1154
1257
|
upsert: true,
|
|
1155
1258
|
returnDocument: "after",
|
|
1156
1259
|
includeResultMetadata: true,
|
|
1157
|
-
...this._sessionOpt()
|
|
1260
|
+
...this._sessionOpt(),
|
|
1158
1261
|
};
|
|
1159
1262
|
if (this._hasHashedKeys(update))
|
|
1160
1263
|
await this._processHashedKeys(update);
|
|
@@ -1182,7 +1285,11 @@ export class Mongo extends Db {
|
|
|
1182
1285
|
? ret
|
|
1183
1286
|
: this._buildPublishDelta(ret, inputUpdate, !!(opts === null || opts === void 0 ? void 0 : opts.returnFullObject));
|
|
1184
1287
|
this._processReturnedObject(retObj);
|
|
1185
|
-
return {
|
|
1288
|
+
return {
|
|
1289
|
+
operation: oper,
|
|
1290
|
+
data: retObj,
|
|
1291
|
+
rawUpdate: oper === "update" ? inputUpdate : undefined,
|
|
1292
|
+
};
|
|
1186
1293
|
});
|
|
1187
1294
|
const results = await Promise.all(upsertPromises);
|
|
1188
1295
|
// Filter out nulls and separate batchData and changes
|
|
@@ -1205,8 +1312,8 @@ export class Mongo extends Db {
|
|
|
1205
1312
|
operation: "batch",
|
|
1206
1313
|
db: dbName,
|
|
1207
1314
|
collection,
|
|
1208
|
-
data: batchData
|
|
1209
|
-
}
|
|
1315
|
+
data: batchData,
|
|
1316
|
+
},
|
|
1210
1317
|
});
|
|
1211
1318
|
}
|
|
1212
1319
|
if (this.emittingPublishRevEvents) {
|
|
@@ -1214,11 +1321,11 @@ export class Mongo extends Db {
|
|
|
1214
1321
|
operation: "batch",
|
|
1215
1322
|
db: dbName,
|
|
1216
1323
|
collection,
|
|
1217
|
-
data: batchData.map(item => ({
|
|
1324
|
+
data: batchData.map((item) => ({
|
|
1218
1325
|
operation: item.operation,
|
|
1219
1326
|
_id: item.data._id,
|
|
1220
1327
|
_ts: item.data._ts,
|
|
1221
|
-
}))
|
|
1328
|
+
})),
|
|
1222
1329
|
};
|
|
1223
1330
|
this.emit("publishRev", {
|
|
1224
1331
|
channel: `dbrev/${dbName}/${collection}`,
|
|
@@ -1227,23 +1334,28 @@ export class Mongo extends Db {
|
|
|
1227
1334
|
}
|
|
1228
1335
|
return changes;
|
|
1229
1336
|
}, false, { operation: "upsertBatch", collection, batch });
|
|
1230
|
-
fjLog.debug(
|
|
1337
|
+
fjLog.debug("upsertBatch returns", ret);
|
|
1231
1338
|
return ret;
|
|
1232
1339
|
}
|
|
1233
1340
|
async insertMany(collection, insert) {
|
|
1234
1341
|
assert(collection, "collection is required", "insertMany");
|
|
1235
1342
|
assert(insert, "insert is required", "insertMany", { collection });
|
|
1236
|
-
assert(insert instanceof Array, "insert must be an Array", "insertMany", {
|
|
1237
|
-
|
|
1343
|
+
assert(insert instanceof Array, "insert must be an Array", "insertMany", {
|
|
1344
|
+
collection,
|
|
1345
|
+
});
|
|
1346
|
+
fjLog.debug("insertMany called", collection, insert);
|
|
1238
1347
|
const dbName = this.db; // Capture db at operation start
|
|
1239
1348
|
insert = this.replaceIds(insert);
|
|
1240
|
-
insert.forEach(item => this._stripDoubleUnderscoreKeys(item));
|
|
1241
|
-
await Promise.all(insert.map(item => this._processHashedKeys(item)));
|
|
1349
|
+
insert.forEach((item) => this._stripDoubleUnderscoreKeys(item));
|
|
1350
|
+
await Promise.all(insert.map((item) => this._processHashedKeys(item)));
|
|
1242
1351
|
if (this.revisions)
|
|
1243
|
-
insert.forEach(ins => {
|
|
1352
|
+
insert.forEach((ins) => {
|
|
1353
|
+
ins._rev = 1;
|
|
1354
|
+
ins._ts = Base.timestamp();
|
|
1355
|
+
});
|
|
1244
1356
|
let ret = await this.executeTransactionally(collection, async (conn, client) => {
|
|
1245
1357
|
// Only process sequences if any insert has SEQ_NEXT or SEQ_LAST
|
|
1246
|
-
const insertsWithSeq = insert.filter(item => this._hasSequenceFields(item));
|
|
1358
|
+
const insertsWithSeq = insert.filter((item) => this._hasSequenceFields(item));
|
|
1247
1359
|
if (insertsWithSeq.length > 0) {
|
|
1248
1360
|
await this._processSequenceFieldForMany(client, dbName, collection, insert);
|
|
1249
1361
|
}
|
|
@@ -1253,16 +1365,20 @@ export class Mongo extends Db {
|
|
|
1253
1365
|
let n = Number(ns);
|
|
1254
1366
|
let rec = {
|
|
1255
1367
|
_id: obj.insertedIds[n],
|
|
1256
|
-
...insert[n]
|
|
1368
|
+
...insert[n],
|
|
1257
1369
|
};
|
|
1258
1370
|
ret.push(rec);
|
|
1259
1371
|
}
|
|
1260
|
-
if (this.emittingPublishEvents ||
|
|
1261
|
-
|
|
1372
|
+
if (this.emittingPublishEvents ||
|
|
1373
|
+
this.emittingPublishRevEvents ||
|
|
1374
|
+
this.auditing) {
|
|
1375
|
+
await Promise.all(ret
|
|
1376
|
+
.filter((rec) => rec)
|
|
1377
|
+
.map((rec) => this._publishAndAudit("insert", dbName, collection, rec)));
|
|
1262
1378
|
}
|
|
1263
1379
|
return ret;
|
|
1264
1380
|
}, false, { operation: "insertMany", collection, insert });
|
|
1265
|
-
fjLog.debug(
|
|
1381
|
+
fjLog.debug("insertMany returns", ret);
|
|
1266
1382
|
return ret;
|
|
1267
1383
|
}
|
|
1268
1384
|
async deleteOne(collection, query) {
|
|
@@ -1272,36 +1388,46 @@ export class Mongo extends Db {
|
|
|
1272
1388
|
query = this.replaceIds(query);
|
|
1273
1389
|
if (!this.softdelete) {
|
|
1274
1390
|
const opts = this._sessionOpt();
|
|
1275
|
-
fjLog.debug(
|
|
1391
|
+
fjLog.debug("deleteOne called", this.softdelete, collection, query);
|
|
1276
1392
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1277
1393
|
let obj = await conn.findOneAndDelete(query, opts);
|
|
1278
1394
|
if (obj)
|
|
1279
|
-
await this._publishAndAudit(
|
|
1395
|
+
await this._publishAndAudit("delete", dbName, collection, obj);
|
|
1280
1396
|
return obj;
|
|
1281
|
-
}, false, {
|
|
1282
|
-
|
|
1397
|
+
}, false, {
|
|
1398
|
+
operation: "deleteOne",
|
|
1399
|
+
collection,
|
|
1400
|
+
query,
|
|
1401
|
+
softdelete: this.softdelete,
|
|
1402
|
+
});
|
|
1403
|
+
fjLog.debug("deleteOne returns", ret);
|
|
1283
1404
|
return ret;
|
|
1284
1405
|
}
|
|
1285
1406
|
else {
|
|
1286
1407
|
let opts = {
|
|
1287
1408
|
upsert: false,
|
|
1288
1409
|
returnDocument: "after",
|
|
1289
|
-
...this._sessionOpt()
|
|
1410
|
+
...this._sessionOpt(),
|
|
1290
1411
|
};
|
|
1291
|
-
fjLog.debug(
|
|
1412
|
+
fjLog.debug("deleteOne called", collection, query);
|
|
1292
1413
|
let ret = await this.executeTransactionally(collection, async (conn /*, client*/) => {
|
|
1293
1414
|
let del = {
|
|
1294
1415
|
$set: { _deleted: new Date() },
|
|
1295
|
-
$inc: { _rev: 1
|
|
1296
|
-
$currentDate: { _ts: { $type:
|
|
1416
|
+
$inc: { _rev: 1 },
|
|
1417
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1297
1418
|
};
|
|
1298
1419
|
// if (this.syncSupport) del.$set._csq = await this._getNextCollectionUpdateSeqNo(collection, client)
|
|
1299
1420
|
let obj = await conn.findOneAndUpdate(query, del, opts);
|
|
1300
1421
|
if (obj)
|
|
1301
|
-
await this._publishAndAudit(
|
|
1422
|
+
await this._publishAndAudit("delete", dbName, collection, obj);
|
|
1302
1423
|
return obj;
|
|
1303
|
-
}, false, {
|
|
1304
|
-
|
|
1424
|
+
}, false, {
|
|
1425
|
+
operation: "deleteOne",
|
|
1426
|
+
collection,
|
|
1427
|
+
query,
|
|
1428
|
+
softdelete: this.softdelete,
|
|
1429
|
+
});
|
|
1430
|
+
fjLog.debug("deleteOne returns", ret);
|
|
1305
1431
|
return ret;
|
|
1306
1432
|
}
|
|
1307
1433
|
}
|
|
@@ -1309,56 +1435,56 @@ export class Mongo extends Db {
|
|
|
1309
1435
|
let opts = {
|
|
1310
1436
|
upsert: false,
|
|
1311
1437
|
returnDocument: "after",
|
|
1312
|
-
...this._sessionOpt()
|
|
1438
|
+
...this._sessionOpt(),
|
|
1313
1439
|
};
|
|
1314
1440
|
const dbName = this.db; // Capture db at operation start
|
|
1315
1441
|
query = this.replaceIds(query);
|
|
1316
|
-
fjLog.debug(
|
|
1442
|
+
fjLog.debug("blockOne called", collection, query);
|
|
1317
1443
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1318
1444
|
query._blocked = { $exists: 0 };
|
|
1319
1445
|
let update = {
|
|
1320
|
-
$set: { _blocked: new Date()
|
|
1321
|
-
$inc: { _rev: 1
|
|
1322
|
-
$currentDate: { _ts: { $type:
|
|
1446
|
+
$set: { _blocked: new Date() },
|
|
1447
|
+
$inc: { _rev: 1 },
|
|
1448
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1323
1449
|
};
|
|
1324
1450
|
// if (this.syncSupport) update.$set._csq = await this._getNextCollectionUpdateSeqNo(collection, client)
|
|
1325
1451
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1326
1452
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1327
1453
|
return {
|
|
1328
|
-
ok: false
|
|
1454
|
+
ok: false,
|
|
1329
1455
|
};
|
|
1330
|
-
await this._publishAndAudit(
|
|
1456
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1331
1457
|
return obj;
|
|
1332
1458
|
}, false, { operation: "blockOne", collection, query });
|
|
1333
|
-
fjLog.debug(
|
|
1459
|
+
fjLog.debug("blockOne returns", ret);
|
|
1334
1460
|
return ret;
|
|
1335
1461
|
}
|
|
1336
1462
|
async unblockOne(collection, query) {
|
|
1337
1463
|
let opts = {
|
|
1338
1464
|
upsert: false,
|
|
1339
1465
|
returnDocument: "after",
|
|
1340
|
-
...this._sessionOpt()
|
|
1466
|
+
...this._sessionOpt(),
|
|
1341
1467
|
};
|
|
1342
1468
|
const dbName = this.db; // Capture db at operation start
|
|
1343
1469
|
query = this.replaceIds(query);
|
|
1344
|
-
fjLog.debug(
|
|
1470
|
+
fjLog.debug("unblockOne called", collection, query);
|
|
1345
1471
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1346
1472
|
query._blocked = { $exists: 1 };
|
|
1347
1473
|
let update = {
|
|
1348
1474
|
$unset: { _blocked: 1 },
|
|
1349
|
-
$inc: { _rev: 1
|
|
1350
|
-
$currentDate: { _ts: { $type:
|
|
1475
|
+
$inc: { _rev: 1 },
|
|
1476
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1351
1477
|
};
|
|
1352
1478
|
// if (this.syncSupport) update.$set = { _csq: await this._getNextCollectionUpdateSeqNo(collection, client) };
|
|
1353
1479
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1354
1480
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1355
1481
|
return {
|
|
1356
|
-
ok: false
|
|
1482
|
+
ok: false,
|
|
1357
1483
|
};
|
|
1358
|
-
await this._publishAndAudit(
|
|
1484
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1359
1485
|
return obj;
|
|
1360
1486
|
}, false, { operation: "unblockOne", collection, query });
|
|
1361
|
-
fjLog.debug(
|
|
1487
|
+
fjLog.debug("unblockOne returns", ret);
|
|
1362
1488
|
return ret;
|
|
1363
1489
|
}
|
|
1364
1490
|
/**
|
|
@@ -1369,27 +1495,27 @@ export class Mongo extends Db {
|
|
|
1369
1495
|
let opts = {
|
|
1370
1496
|
upsert: false,
|
|
1371
1497
|
returnDocument: "after",
|
|
1372
|
-
...this._sessionOpt()
|
|
1498
|
+
...this._sessionOpt(),
|
|
1373
1499
|
};
|
|
1374
1500
|
const dbName = this.db;
|
|
1375
1501
|
query = this.replaceIds(query);
|
|
1376
|
-
fjLog.debug(
|
|
1502
|
+
fjLog.debug("archiveOne called", collection, query);
|
|
1377
1503
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1378
1504
|
query._archived = { $exists: false };
|
|
1379
1505
|
let update = {
|
|
1380
|
-
$set: { _archived: new Date()
|
|
1381
|
-
$inc: { _rev: 1
|
|
1382
|
-
$currentDate: { _ts: { $type:
|
|
1506
|
+
$set: { _archived: new Date() },
|
|
1507
|
+
$inc: { _rev: 1 },
|
|
1508
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1383
1509
|
};
|
|
1384
1510
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1385
1511
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1386
1512
|
return {
|
|
1387
|
-
ok: false
|
|
1513
|
+
ok: false,
|
|
1388
1514
|
};
|
|
1389
|
-
await this._publishAndAudit(
|
|
1515
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1390
1516
|
return obj;
|
|
1391
1517
|
}, false, { operation: "archiveOne", collection, query });
|
|
1392
|
-
fjLog.debug(
|
|
1518
|
+
fjLog.debug("archiveOne returns", ret);
|
|
1393
1519
|
return ret;
|
|
1394
1520
|
}
|
|
1395
1521
|
/**
|
|
@@ -1400,27 +1526,27 @@ export class Mongo extends Db {
|
|
|
1400
1526
|
let opts = {
|
|
1401
1527
|
upsert: false,
|
|
1402
1528
|
returnDocument: "after",
|
|
1403
|
-
...this._sessionOpt()
|
|
1529
|
+
...this._sessionOpt(),
|
|
1404
1530
|
};
|
|
1405
1531
|
const dbName = this.db;
|
|
1406
1532
|
query = this.replaceIds(query);
|
|
1407
|
-
fjLog.debug(
|
|
1533
|
+
fjLog.debug("unarchiveOne called", collection, query);
|
|
1408
1534
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1409
1535
|
query._archived = { $exists: true };
|
|
1410
1536
|
let update = {
|
|
1411
1537
|
$unset: { _archived: 1 },
|
|
1412
|
-
$inc: { _rev: 1
|
|
1413
|
-
$currentDate: { _ts: { $type:
|
|
1538
|
+
$inc: { _rev: 1 },
|
|
1539
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1414
1540
|
};
|
|
1415
1541
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1416
1542
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1417
1543
|
return {
|
|
1418
|
-
ok: false
|
|
1544
|
+
ok: false,
|
|
1419
1545
|
};
|
|
1420
|
-
await this._publishAndAudit(
|
|
1546
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1421
1547
|
return obj;
|
|
1422
1548
|
}, false, { operation: "unarchiveOne", collection, query });
|
|
1423
|
-
fjLog.debug(
|
|
1549
|
+
fjLog.debug("unarchiveOne returns", ret);
|
|
1424
1550
|
return ret;
|
|
1425
1551
|
}
|
|
1426
1552
|
/**
|
|
@@ -1429,7 +1555,9 @@ export class Mongo extends Db {
|
|
|
1429
1555
|
async archiveOneById(collection, id) {
|
|
1430
1556
|
assert(collection, "collection is required", "archiveOneById");
|
|
1431
1557
|
assert(id, "id is required", "archiveOneById", { collection });
|
|
1432
|
-
return await this.archiveOne(collection, {
|
|
1558
|
+
return await this.archiveOne(collection, {
|
|
1559
|
+
_id: Mongo._toId(id),
|
|
1560
|
+
});
|
|
1433
1561
|
}
|
|
1434
1562
|
/**
|
|
1435
1563
|
* Archives multiple documents found by their `_id`s.
|
|
@@ -1443,29 +1571,32 @@ export class Mongo extends Db {
|
|
|
1443
1571
|
const opts = {
|
|
1444
1572
|
upsert: false,
|
|
1445
1573
|
returnDocument: "after",
|
|
1446
|
-
...this._sessionOpt()
|
|
1574
|
+
...this._sessionOpt(),
|
|
1447
1575
|
};
|
|
1448
1576
|
const dbName = this.db;
|
|
1449
|
-
fjLog.debug(
|
|
1577
|
+
fjLog.debug("archiveManyByIds called", collection, ids);
|
|
1450
1578
|
let ret = [];
|
|
1451
1579
|
for (const id of ids) {
|
|
1452
1580
|
let r = await this.executeTransactionally(collection, async (conn) => {
|
|
1453
|
-
let query = {
|
|
1581
|
+
let query = {
|
|
1582
|
+
_id: Mongo._toId(id),
|
|
1583
|
+
_archived: { $exists: false },
|
|
1584
|
+
};
|
|
1454
1585
|
let update = {
|
|
1455
1586
|
$set: { _archived: new Date() },
|
|
1456
1587
|
$inc: { _rev: 1 },
|
|
1457
|
-
$currentDate: { _ts: { $type:
|
|
1588
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1458
1589
|
};
|
|
1459
1590
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1460
1591
|
if (obj === null || obj === void 0 ? void 0 : obj._id) {
|
|
1461
|
-
await this._publishAndAudit(
|
|
1592
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1462
1593
|
}
|
|
1463
1594
|
return obj;
|
|
1464
1595
|
}, false, { operation: "archiveManyByIds", collection, id });
|
|
1465
1596
|
if (r === null || r === void 0 ? void 0 : r._id)
|
|
1466
1597
|
ret.push(r);
|
|
1467
1598
|
}
|
|
1468
|
-
fjLog.debug(
|
|
1599
|
+
fjLog.debug("archiveManyByIds returns", ret);
|
|
1469
1600
|
return ret;
|
|
1470
1601
|
}
|
|
1471
1602
|
/**
|
|
@@ -1474,7 +1605,9 @@ export class Mongo extends Db {
|
|
|
1474
1605
|
async unarchiveOneById(collection, id) {
|
|
1475
1606
|
assert(collection, "collection is required", "unarchiveOneById");
|
|
1476
1607
|
assert(id, "id is required", "unarchiveOneById", { collection });
|
|
1477
|
-
return await this.unarchiveOne(collection, {
|
|
1608
|
+
return await this.unarchiveOne(collection, {
|
|
1609
|
+
_id: Mongo._toId(id),
|
|
1610
|
+
});
|
|
1478
1611
|
}
|
|
1479
1612
|
/**
|
|
1480
1613
|
* Unarchives multiple documents found by their `_id`s.
|
|
@@ -1488,52 +1621,57 @@ export class Mongo extends Db {
|
|
|
1488
1621
|
const opts = {
|
|
1489
1622
|
upsert: false,
|
|
1490
1623
|
returnDocument: "after",
|
|
1491
|
-
...this._sessionOpt()
|
|
1624
|
+
...this._sessionOpt(),
|
|
1492
1625
|
};
|
|
1493
1626
|
const dbName = this.db;
|
|
1494
|
-
fjLog.debug(
|
|
1627
|
+
fjLog.debug("unarchiveManyByIds called", collection, ids);
|
|
1495
1628
|
let ret = [];
|
|
1496
1629
|
for (const id of ids) {
|
|
1497
1630
|
let r = await this.executeTransactionally(collection, async (conn) => {
|
|
1498
|
-
let query = {
|
|
1631
|
+
let query = {
|
|
1632
|
+
_id: Mongo._toId(id),
|
|
1633
|
+
_archived: { $exists: true },
|
|
1634
|
+
};
|
|
1499
1635
|
let update = {
|
|
1500
1636
|
$unset: { _archived: 1 },
|
|
1501
1637
|
$inc: { _rev: 1 },
|
|
1502
|
-
$currentDate: { _ts: { $type:
|
|
1638
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1503
1639
|
};
|
|
1504
1640
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1505
1641
|
if (obj === null || obj === void 0 ? void 0 : obj._id) {
|
|
1506
|
-
await this._publishAndAudit(
|
|
1642
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1507
1643
|
}
|
|
1508
1644
|
return obj;
|
|
1509
1645
|
}, false, { operation: "unarchiveManyByIds", collection, id });
|
|
1510
1646
|
if (r === null || r === void 0 ? void 0 : r._id)
|
|
1511
1647
|
ret.push(r);
|
|
1512
1648
|
}
|
|
1513
|
-
fjLog.debug(
|
|
1649
|
+
fjLog.debug("unarchiveManyByIds returns", ret);
|
|
1514
1650
|
return ret;
|
|
1515
1651
|
}
|
|
1516
1652
|
async hardDeleteOne(collection, query) {
|
|
1517
1653
|
assert(collection, "collection is required", "hardDeleteOne");
|
|
1518
1654
|
assert(query, "query is required", "hardDeleteOne", { collection });
|
|
1519
1655
|
const dbName = this.db; // Capture db at operation start
|
|
1520
|
-
if (typeof query === "string" ||
|
|
1656
|
+
if (typeof query === "string" ||
|
|
1657
|
+
typeof query === "number" ||
|
|
1658
|
+
query instanceof ObjectId) {
|
|
1521
1659
|
query = { _id: query };
|
|
1522
1660
|
}
|
|
1523
1661
|
query = this.replaceIds(query);
|
|
1524
1662
|
let opts = {
|
|
1525
1663
|
returnDocument: "after",
|
|
1526
|
-
...this._sessionOpt()
|
|
1664
|
+
...this._sessionOpt(),
|
|
1527
1665
|
};
|
|
1528
|
-
fjLog.debug(
|
|
1666
|
+
fjLog.debug("hardDeleteOne called", collection, query);
|
|
1529
1667
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1530
1668
|
let obj = await conn.findOneAndDelete(query, opts);
|
|
1531
1669
|
if (obj) {
|
|
1532
|
-
await this._publishAndAudit(
|
|
1670
|
+
await this._publishAndAudit("delete", dbName, collection, obj);
|
|
1533
1671
|
}
|
|
1534
1672
|
return obj;
|
|
1535
1673
|
}, false, { operation: "hardDeleteOne", collection, query });
|
|
1536
|
-
fjLog.debug(
|
|
1674
|
+
fjLog.debug("hardDeleteOne returns", ret);
|
|
1537
1675
|
return ret;
|
|
1538
1676
|
}
|
|
1539
1677
|
async delete(collection, query) {
|
|
@@ -1544,42 +1682,42 @@ export class Mongo extends Db {
|
|
|
1544
1682
|
query = this.replaceIds(query);
|
|
1545
1683
|
if (!this.softdelete) {
|
|
1546
1684
|
const opts = this._sessionOpt();
|
|
1547
|
-
fjLog.debug(
|
|
1685
|
+
fjLog.debug("delete called", collection, query);
|
|
1548
1686
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1549
1687
|
let obj = await conn.deleteMany(query, opts);
|
|
1550
1688
|
let resObj = {
|
|
1551
1689
|
n: obj.deletedCount,
|
|
1552
|
-
ok: !!obj.acknowledged
|
|
1690
|
+
ok: !!obj.acknowledged,
|
|
1553
1691
|
};
|
|
1554
|
-
await this._publishAndAudit(
|
|
1692
|
+
await this._publishAndAudit("deleteMany", dbName, collection, resObj);
|
|
1555
1693
|
return resObj;
|
|
1556
1694
|
}, false, { operation: "delete", collection, query, softdelete: this.softdelete });
|
|
1557
|
-
fjLog.debug(
|
|
1695
|
+
fjLog.debug("delete returns", ret);
|
|
1558
1696
|
return ret;
|
|
1559
1697
|
}
|
|
1560
1698
|
else {
|
|
1561
1699
|
let opts = {
|
|
1562
1700
|
upsert: false,
|
|
1563
1701
|
returnDocument: "after",
|
|
1564
|
-
...this._sessionOpt()
|
|
1702
|
+
...this._sessionOpt(),
|
|
1565
1703
|
};
|
|
1566
1704
|
let date = new Date();
|
|
1567
|
-
fjLog.debug(
|
|
1705
|
+
fjLog.debug("delete called", collection, query);
|
|
1568
1706
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1569
1707
|
let upd = {
|
|
1570
1708
|
$set: { _deleted: date },
|
|
1571
1709
|
$inc: { _rev: 1 },
|
|
1572
|
-
$currentDate: { _ts: { $type:
|
|
1710
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1573
1711
|
};
|
|
1574
1712
|
let obj = await conn.updateMany(query, upd, opts);
|
|
1575
1713
|
let resObj = {
|
|
1576
1714
|
n: obj.modifiedCount,
|
|
1577
|
-
ok: !!obj.acknowledged
|
|
1715
|
+
ok: !!obj.acknowledged,
|
|
1578
1716
|
};
|
|
1579
|
-
await this._publishAndAudit(
|
|
1717
|
+
await this._publishAndAudit("deleteMany", dbName, collection, resObj);
|
|
1580
1718
|
return resObj;
|
|
1581
1719
|
}, false, { operation: "delete", collection, query, softdelete: this.softdelete });
|
|
1582
|
-
fjLog.debug(
|
|
1720
|
+
fjLog.debug("delete returns", ret);
|
|
1583
1721
|
return ret;
|
|
1584
1722
|
}
|
|
1585
1723
|
}
|
|
@@ -1590,22 +1728,27 @@ export class Mongo extends Db {
|
|
|
1590
1728
|
const dbName = this.db; // Capture db at operation start
|
|
1591
1729
|
query = this.replaceIds(query);
|
|
1592
1730
|
const opts = this._sessionOpt();
|
|
1593
|
-
fjLog.debug(
|
|
1731
|
+
fjLog.debug("hardDelete called", collection, query);
|
|
1594
1732
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1595
1733
|
let obj = await conn.deleteMany(query, opts);
|
|
1596
1734
|
let resObj = {
|
|
1597
1735
|
n: obj.deletedCount,
|
|
1598
|
-
ok: !!obj.acknowledged
|
|
1736
|
+
ok: !!obj.acknowledged,
|
|
1599
1737
|
};
|
|
1600
|
-
await this._publishAndAudit(
|
|
1738
|
+
await this._publishAndAudit("deleteMany", dbName, collection, resObj);
|
|
1601
1739
|
return resObj;
|
|
1602
|
-
}, false, {
|
|
1603
|
-
|
|
1740
|
+
}, false, {
|
|
1741
|
+
operation: "hardDelete",
|
|
1742
|
+
collection,
|
|
1743
|
+
query,
|
|
1744
|
+
softdelete: this.softdelete,
|
|
1745
|
+
});
|
|
1746
|
+
fjLog.debug("hardDelete returns", ret);
|
|
1604
1747
|
return ret;
|
|
1605
1748
|
}
|
|
1606
1749
|
async testHash(collection, query, field, unhashedValue) {
|
|
1607
1750
|
let _field;
|
|
1608
|
-
fjLog.debug(
|
|
1751
|
+
fjLog.debug("testHash called", collection, query, field, unhashedValue);
|
|
1609
1752
|
if (typeof field === "object") {
|
|
1610
1753
|
if (Object.keys(field).length === 1)
|
|
1611
1754
|
[_field, unhashedValue] = Object.entries(field)[0];
|
|
@@ -1618,13 +1761,16 @@ export class Mongo extends Db {
|
|
|
1618
1761
|
_field = HASHED_PREFIX + _field;
|
|
1619
1762
|
const dbName = this.db; // Capture db at operation start
|
|
1620
1763
|
let conn = await this.connect();
|
|
1621
|
-
let obj = await conn
|
|
1764
|
+
let obj = await conn
|
|
1765
|
+
.db(dbName)
|
|
1766
|
+
.collection(collection)
|
|
1767
|
+
.findOne(query, { projection: { [_field]: 1 }, ...this._sessionOpt() });
|
|
1622
1768
|
if (!obj || !obj[_field]) {
|
|
1623
|
-
fjLog.debug(
|
|
1769
|
+
fjLog.debug("testHash returns false", obj);
|
|
1624
1770
|
return false;
|
|
1625
1771
|
}
|
|
1626
1772
|
let res = await bcrypt.compare(unhashedValue, obj[_field].hash);
|
|
1627
|
-
fjLog.debug(
|
|
1773
|
+
fjLog.debug("testHash returns", res);
|
|
1628
1774
|
return res;
|
|
1629
1775
|
}
|
|
1630
1776
|
async aggregate(collection, pipeline, opts = {
|
|
@@ -1632,7 +1778,7 @@ export class Mongo extends Db {
|
|
|
1632
1778
|
}) {
|
|
1633
1779
|
assert(collection, "collection is required", "aggregate");
|
|
1634
1780
|
assert(pipeline instanceof Array, "pipeline must be an Array", "aggregate", { collection });
|
|
1635
|
-
fjLog.debug(
|
|
1781
|
+
fjLog.debug("aggregate called", collection, pipeline);
|
|
1636
1782
|
pipeline = this.replaceIds(pipeline);
|
|
1637
1783
|
if (this.session)
|
|
1638
1784
|
opts.session = this.session;
|
|
@@ -1640,7 +1786,7 @@ export class Mongo extends Db {
|
|
|
1640
1786
|
let res = await conn.aggregate(pipeline, opts).toArray();
|
|
1641
1787
|
return res;
|
|
1642
1788
|
}, false, { operation: "aggregate", collection, pipeline, opts });
|
|
1643
|
-
fjLog.debug(
|
|
1789
|
+
fjLog.debug("aggregare returns", ret);
|
|
1644
1790
|
return ret;
|
|
1645
1791
|
}
|
|
1646
1792
|
async isUnique(collection, field, value, id) {
|
|
@@ -1648,7 +1794,7 @@ export class Mongo extends Db {
|
|
|
1648
1794
|
assert(field, "field is required", "isUnique", { collection });
|
|
1649
1795
|
if (!value)
|
|
1650
1796
|
return false;
|
|
1651
|
-
fjLog.debug(
|
|
1797
|
+
fjLog.debug("isUnique called", collection, field, value, id);
|
|
1652
1798
|
let _id = id === null || id === void 0 ? void 0 : id.toString();
|
|
1653
1799
|
let query = typeof value === "object" ? value : { [field]: value };
|
|
1654
1800
|
query = this.replaceIds(query);
|
|
@@ -1666,12 +1812,12 @@ export class Mongo extends Db {
|
|
|
1666
1812
|
return false;
|
|
1667
1813
|
}
|
|
1668
1814
|
}
|
|
1669
|
-
fjLog.debug(
|
|
1815
|
+
fjLog.debug("isUnique returns", ret);
|
|
1670
1816
|
return ret;
|
|
1671
1817
|
}
|
|
1672
1818
|
async dropCollection(collection) {
|
|
1673
1819
|
assert(collection, "collection is required", "dropCollection");
|
|
1674
|
-
fjLog.debug(
|
|
1820
|
+
fjLog.debug("dropCollection called", this.auditCollections);
|
|
1675
1821
|
const dbName = this.db; // Capture db at operation start
|
|
1676
1822
|
let client = await this.connect();
|
|
1677
1823
|
let existing = await client.db(dbName).collections();
|
|
@@ -1679,14 +1825,15 @@ export class Mongo extends Db {
|
|
|
1679
1825
|
if (existingNames.has(collection)) {
|
|
1680
1826
|
await client.db(dbName).dropCollection(collection);
|
|
1681
1827
|
}
|
|
1682
|
-
fjLog.debug(
|
|
1828
|
+
fjLog.debug("dropCollection returns");
|
|
1683
1829
|
}
|
|
1684
1830
|
async resetCollectionSync(collection) {
|
|
1685
1831
|
assert(collection, "collection is required", "resetCollectionSync");
|
|
1686
|
-
fjLog.debug(
|
|
1832
|
+
fjLog.debug("resetCollectionSync called for", collection);
|
|
1687
1833
|
const dbName = this.db; // Capture db at operation start
|
|
1688
1834
|
let client = await this.connect();
|
|
1689
|
-
await client
|
|
1835
|
+
await client
|
|
1836
|
+
.db(dbName)
|
|
1690
1837
|
.collection(SEQUENCES_COLLECTION)
|
|
1691
1838
|
.findOneAndDelete({ collection });
|
|
1692
1839
|
fjLog.debug(`resetCollectionSync for ${collection} returns`);
|
|
@@ -1694,7 +1841,7 @@ export class Mongo extends Db {
|
|
|
1694
1841
|
async dropCollections(collections) {
|
|
1695
1842
|
assert(collections, "collections is required", "dropCollections");
|
|
1696
1843
|
assert(collections instanceof Array, "collections must be an Array", "dropCollections");
|
|
1697
|
-
fjLog.debug(
|
|
1844
|
+
fjLog.debug("dropCollections called", this.auditCollections);
|
|
1698
1845
|
const dbName = this.db; // Capture db at operation start
|
|
1699
1846
|
let client = await this.connect();
|
|
1700
1847
|
let existing = await client.db(dbName).collections();
|
|
@@ -1704,12 +1851,12 @@ export class Mongo extends Db {
|
|
|
1704
1851
|
await client.db(dbName).dropCollection(collection);
|
|
1705
1852
|
}
|
|
1706
1853
|
}
|
|
1707
|
-
fjLog.debug(
|
|
1854
|
+
fjLog.debug("dropCollections returns");
|
|
1708
1855
|
}
|
|
1709
1856
|
async createCollections(collections) {
|
|
1710
1857
|
assert(collections, "collections is required", "createCollections");
|
|
1711
1858
|
assert(collections instanceof Array, "collections must be an Array", "createCollections");
|
|
1712
|
-
fjLog.debug(
|
|
1859
|
+
fjLog.debug("createCollections called", this.auditCollections);
|
|
1713
1860
|
const dbName = this.db; // Capture db at operation start
|
|
1714
1861
|
let client = await this.connect();
|
|
1715
1862
|
let existing = await this.getCollections();
|
|
@@ -1719,25 +1866,25 @@ export class Mongo extends Db {
|
|
|
1719
1866
|
await client.db(dbName).createCollection(collection);
|
|
1720
1867
|
}
|
|
1721
1868
|
}
|
|
1722
|
-
fjLog.debug(
|
|
1869
|
+
fjLog.debug("createCollections returns");
|
|
1723
1870
|
}
|
|
1724
1871
|
async createCollection(collection) {
|
|
1725
1872
|
assert(collection, "collection is required", "createCollection");
|
|
1726
|
-
fjLog.debug(
|
|
1873
|
+
fjLog.debug("createCollection called", collection);
|
|
1727
1874
|
const dbName = this.db; // Capture db at operation start
|
|
1728
1875
|
let client = await this.connect();
|
|
1729
1876
|
let existing = await this.getCollections();
|
|
1730
1877
|
if (!existing.includes(collection)) {
|
|
1731
1878
|
await client.db(dbName).createCollection(collection);
|
|
1732
1879
|
}
|
|
1733
|
-
fjLog.debug(
|
|
1880
|
+
fjLog.debug("createCollection returns");
|
|
1734
1881
|
}
|
|
1735
1882
|
async dbLogPurge(collection, _id) {
|
|
1736
1883
|
assert(collection, "collection is required", "dbLogPurge");
|
|
1737
|
-
fjLog.debug(
|
|
1884
|
+
fjLog.debug("dblogPurge called", collection, _id);
|
|
1738
1885
|
const dbName = this.db; // Capture db at operation start
|
|
1739
1886
|
let ret = await this.executeTransactionally(collection, async () => {
|
|
1740
|
-
let cond = { db: dbName, collection
|
|
1887
|
+
let cond = { db: dbName, collection };
|
|
1741
1888
|
if (_id !== undefined)
|
|
1742
1889
|
cond._id = Mongo._toId(_id);
|
|
1743
1890
|
let client = await this.connect();
|
|
@@ -1747,15 +1894,15 @@ export class Mongo extends Db {
|
|
|
1747
1894
|
.deleteMany(cond, this._sessionOpt());
|
|
1748
1895
|
return {
|
|
1749
1896
|
ok: !!ret.acknowledged,
|
|
1750
|
-
n: ret.deletedCount
|
|
1897
|
+
n: ret.deletedCount,
|
|
1751
1898
|
};
|
|
1752
1899
|
}, false, { operation: "dbLogPurge", collection, _id });
|
|
1753
|
-
fjLog.debug(
|
|
1900
|
+
fjLog.debug("dblogPurge returns", ret);
|
|
1754
1901
|
return ret;
|
|
1755
1902
|
}
|
|
1756
1903
|
async dbLogGet(collection, _id) {
|
|
1757
1904
|
assert(collection, "collection is required", "dbLogGet");
|
|
1758
|
-
fjLog.debug(
|
|
1905
|
+
fjLog.debug("dblogGet called", collection, _id);
|
|
1759
1906
|
const dbName = this.db; // Capture db at operation start
|
|
1760
1907
|
let ret = await this.executeTransactionally(collection, async () => {
|
|
1761
1908
|
let cond = { db: dbName, collection };
|
|
@@ -1770,7 +1917,7 @@ export class Mongo extends Db {
|
|
|
1770
1917
|
.toArray();
|
|
1771
1918
|
return ret;
|
|
1772
1919
|
}, false, { operation: "dbLogGet", collection, _id });
|
|
1773
|
-
fjLog.debug(
|
|
1920
|
+
fjLog.debug("dblogGet returns", ret);
|
|
1774
1921
|
return ret;
|
|
1775
1922
|
}
|
|
1776
1923
|
// HELPER FUNCTIONS
|
|
@@ -1805,13 +1952,13 @@ export class Mongo extends Db {
|
|
|
1805
1952
|
if (typeof data === "string")
|
|
1806
1953
|
return data;
|
|
1807
1954
|
if (data instanceof Array) {
|
|
1808
|
-
return data.map(d => this.replaceIds(d));
|
|
1955
|
+
return data.map((d) => this.replaceIds(d));
|
|
1809
1956
|
}
|
|
1810
|
-
if (typeof data ==
|
|
1957
|
+
if (typeof data == "object" && (data === null || data === void 0 ? void 0 : data.t) && (data === null || data === void 0 ? void 0 : data.i) !== undefined)
|
|
1811
1958
|
return Base.timestamp(data);
|
|
1812
|
-
if (typeof data ==
|
|
1959
|
+
if (typeof data == "object" && (data === null || data === void 0 ? void 0 : data.high) && (data === null || data === void 0 ? void 0 : data.low) !== undefined)
|
|
1813
1960
|
return Base.timestamp(data);
|
|
1814
|
-
if (typeof data ==
|
|
1961
|
+
if (typeof data == "object") {
|
|
1815
1962
|
for (let key in data) {
|
|
1816
1963
|
data[key] = this.replaceIds(data[key]);
|
|
1817
1964
|
}
|
|
@@ -1820,7 +1967,7 @@ export class Mongo extends Db {
|
|
|
1820
1967
|
throw new Error(`ObjectId test failed for ${data}`);
|
|
1821
1968
|
}
|
|
1822
1969
|
catch (err) {
|
|
1823
|
-
console.error(
|
|
1970
|
+
console.error("error in replaceIds", err, data);
|
|
1824
1971
|
throw err;
|
|
1825
1972
|
}
|
|
1826
1973
|
}
|
|
@@ -1868,14 +2015,14 @@ export class Mongo extends Db {
|
|
|
1868
2015
|
this._sequencesIndexEnsured = false;
|
|
1869
2016
|
}
|
|
1870
2017
|
async inTransaction() {
|
|
1871
|
-
if (!await this.isOnReplicaSet())
|
|
2018
|
+
if (!(await this.isOnReplicaSet()))
|
|
1872
2019
|
return false;
|
|
1873
2020
|
if (!this.session)
|
|
1874
2021
|
return false;
|
|
1875
2022
|
return this.session.inTransaction();
|
|
1876
2023
|
}
|
|
1877
2024
|
async withTransaction(funct) {
|
|
1878
|
-
if (!await this.isOnReplicaSet())
|
|
2025
|
+
if (!(await this.isOnReplicaSet()))
|
|
1879
2026
|
return await funct(await this.connect(), null);
|
|
1880
2027
|
const hadSession = !!this.session;
|
|
1881
2028
|
try {
|
|
@@ -1899,14 +2046,16 @@ export class Mongo extends Db {
|
|
|
1899
2046
|
await this.session.endSession();
|
|
1900
2047
|
fjLog.info("session ended after error");
|
|
1901
2048
|
}
|
|
1902
|
-
catch {
|
|
2049
|
+
catch {
|
|
2050
|
+
/* ignore cleanup errors */
|
|
2051
|
+
}
|
|
1903
2052
|
this.session = undefined;
|
|
1904
2053
|
}
|
|
1905
2054
|
throw err;
|
|
1906
2055
|
}
|
|
1907
2056
|
}
|
|
1908
2057
|
async startTransaction() {
|
|
1909
|
-
if (!await this.isOnReplicaSet())
|
|
2058
|
+
if (!(await this.isOnReplicaSet()))
|
|
1910
2059
|
return;
|
|
1911
2060
|
let client = await this.connect();
|
|
1912
2061
|
try {
|
|
@@ -1914,13 +2063,13 @@ export class Mongo extends Db {
|
|
|
1914
2063
|
this.session = client.startSession(TRANSACTION_OPTIONS);
|
|
1915
2064
|
fjLog.info("session started");
|
|
1916
2065
|
}
|
|
1917
|
-
if (!await this.inTransaction()) {
|
|
2066
|
+
if (!(await this.inTransaction())) {
|
|
1918
2067
|
await this.session.startTransaction();
|
|
1919
2068
|
fjLog.info("transaction started");
|
|
1920
2069
|
}
|
|
1921
2070
|
}
|
|
1922
2071
|
catch (err) {
|
|
1923
|
-
fjLog.error(
|
|
2072
|
+
fjLog.error("startTransaction error", err);
|
|
1924
2073
|
try {
|
|
1925
2074
|
if (this.session) {
|
|
1926
2075
|
await this.session.endSession();
|
|
@@ -1935,10 +2084,10 @@ export class Mongo extends Db {
|
|
|
1935
2084
|
}
|
|
1936
2085
|
}
|
|
1937
2086
|
async commitTransaction() {
|
|
1938
|
-
if (!await this.isOnReplicaSet())
|
|
2087
|
+
if (!(await this.isOnReplicaSet()))
|
|
1939
2088
|
return;
|
|
1940
2089
|
try {
|
|
1941
|
-
if (!await this.inTransaction())
|
|
2090
|
+
if (!(await this.inTransaction()))
|
|
1942
2091
|
return;
|
|
1943
2092
|
let session = this.session;
|
|
1944
2093
|
await session.commitTransaction();
|
|
@@ -1952,10 +2101,10 @@ export class Mongo extends Db {
|
|
|
1952
2101
|
}
|
|
1953
2102
|
}
|
|
1954
2103
|
async abortTransaction() {
|
|
1955
|
-
if (!await this.isOnReplicaSet())
|
|
2104
|
+
if (!(await this.isOnReplicaSet()))
|
|
1956
2105
|
return;
|
|
1957
2106
|
try {
|
|
1958
|
-
if (!await this.inTransaction())
|
|
2107
|
+
if (!(await this.inTransaction()))
|
|
1959
2108
|
return;
|
|
1960
2109
|
let session = this.session;
|
|
1961
2110
|
await session.abortTransaction();
|
|
@@ -2009,22 +2158,24 @@ export class Mongo extends Db {
|
|
|
2009
2158
|
// E11000 duplicate key — log concise one-liner (avoid dumping full batch)
|
|
2010
2159
|
if (x.match(/E11000/i)) {
|
|
2011
2160
|
const dupMatch = x.match(/dup key:\s*(\{[^}]+\})/);
|
|
2012
|
-
const count = Array.isArray(debugObject.insert)
|
|
2161
|
+
const count = Array.isArray(debugObject.insert)
|
|
2162
|
+
? debugObject.insert.length
|
|
2163
|
+
: 1;
|
|
2013
2164
|
const rollback = this.session ? "ROLLBACK - " : "";
|
|
2014
2165
|
fjLog.error(`${rollback}Mongo ${debugObject.operation} E11000 on ${dbName}.${collection} (batch=${count}${dupMatch ? `, ${dupMatch[1].trim()}` : ""}) — ignoring`);
|
|
2015
2166
|
if (debugObject.operation === "insert")
|
|
2016
2167
|
return debugObject.insert;
|
|
2017
2168
|
throw err;
|
|
2018
2169
|
}
|
|
2019
|
-
fjLog.error(`Mongo command has failed for ${dbName}.${collection} - ${
|
|
2170
|
+
fjLog.error(`Mongo command has failed for ${dbName}.${collection} - ${this.session ? "ROLLBACK - " : ""} ${err.message || err}`);
|
|
2020
2171
|
fjLog.error(debugObject);
|
|
2021
2172
|
fjLog.debug(err);
|
|
2022
|
-
let isRepeatable = x.match(/Topology is closed, please connect/i)
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2173
|
+
let isRepeatable = x.match(/Topology is closed, please connect/i) ||
|
|
2174
|
+
x.match(/connection timed out/i) ||
|
|
2175
|
+
x.match(/not master/i) ||
|
|
2176
|
+
x.match(/Topology closed/i) ||
|
|
2177
|
+
x.match(/Topology is closed/i) ||
|
|
2178
|
+
x.match(/Connection pool closed/i);
|
|
2028
2179
|
if (isRepeatable) {
|
|
2029
2180
|
try {
|
|
2030
2181
|
fjLog.error("Trying to reopen connection and repeat as");
|
|
@@ -2049,11 +2200,11 @@ export class Mongo extends Db {
|
|
|
2049
2200
|
}
|
|
2050
2201
|
async _findLastSequenceForKey(connection, key) {
|
|
2051
2202
|
var _a;
|
|
2052
|
-
let maxfld = await
|
|
2203
|
+
let maxfld = await connection
|
|
2053
2204
|
.find({}, this._sessionOpt())
|
|
2054
2205
|
.sort({ [key]: -1 })
|
|
2055
2206
|
.limit(1)
|
|
2056
|
-
.toArray()
|
|
2207
|
+
.toArray();
|
|
2057
2208
|
if (maxfld.length === 0)
|
|
2058
2209
|
return undefined;
|
|
2059
2210
|
return parseInt((_a = maxfld === null || maxfld === void 0 ? void 0 : maxfld[0]) === null || _a === void 0 ? void 0 : _a[key]) || 0;
|
|
@@ -2095,19 +2246,19 @@ export class Mongo extends Db {
|
|
|
2095
2246
|
if (!existingSeq) {
|
|
2096
2247
|
// First time for this collection/field - ensure index exists
|
|
2097
2248
|
await this._ensureSequencesIndex(dbConnection);
|
|
2098
|
-
const seedValue =
|
|
2249
|
+
const seedValue = existingMax !== undefined && existingMax >= 1 ? existingMax : 0;
|
|
2099
2250
|
// Use $max to handle concurrent seeding - only sets if greater than current value
|
|
2100
2251
|
// This ensures the highest seed value wins in case of concurrent operations
|
|
2101
2252
|
await dbConnection
|
|
2102
2253
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2103
2254
|
.findOneAndUpdate({ collection, field }, {
|
|
2104
2255
|
$max: { lastSeqNum: seedValue },
|
|
2105
|
-
$setOnInsert: { collection, field }
|
|
2106
|
-
}, { upsert: true, returnDocument:
|
|
2256
|
+
$setOnInsert: { collection, field },
|
|
2257
|
+
}, { upsert: true, returnDocument: "after", ...this._sessionOpt() });
|
|
2107
2258
|
// Now increment atomically to get next value
|
|
2108
2259
|
const updated = await dbConnection
|
|
2109
2260
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2110
|
-
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument:
|
|
2261
|
+
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument: "after", ...this._sessionOpt() });
|
|
2111
2262
|
return updated.lastSeqNum;
|
|
2112
2263
|
}
|
|
2113
2264
|
// Sequence exists - check if collection was cleared (reset scenario)
|
|
@@ -2120,7 +2271,7 @@ export class Mongo extends Db {
|
|
|
2120
2271
|
// Increment and return
|
|
2121
2272
|
const result = await dbConnection
|
|
2122
2273
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2123
|
-
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument:
|
|
2274
|
+
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument: "after", ...this._sessionOpt() });
|
|
2124
2275
|
return result.lastSeqNum;
|
|
2125
2276
|
}
|
|
2126
2277
|
/**
|
|
@@ -2169,14 +2320,14 @@ export class Mongo extends Db {
|
|
|
2169
2320
|
if (!existingSeq) {
|
|
2170
2321
|
// First time for this collection/field - ensure index exists
|
|
2171
2322
|
await this._ensureSequencesIndex(dbConnection);
|
|
2172
|
-
const seedValue =
|
|
2323
|
+
const seedValue = existingMax !== undefined && existingMax >= 1 ? existingMax : 0;
|
|
2173
2324
|
// Use $max to handle concurrent seeding
|
|
2174
2325
|
await dbConnection
|
|
2175
2326
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2176
2327
|
.findOneAndUpdate({ collection, field }, {
|
|
2177
2328
|
$max: { lastSeqNum: seedValue },
|
|
2178
|
-
$setOnInsert: { collection, field }
|
|
2179
|
-
}, { upsert: true, returnDocument:
|
|
2329
|
+
$setOnInsert: { collection, field },
|
|
2330
|
+
}, { upsert: true, returnDocument: "after", ...this._sessionOpt() });
|
|
2180
2331
|
}
|
|
2181
2332
|
else if (existingMax === undefined && existingSeq.lastSeqNum > 0) {
|
|
2182
2333
|
// Collection is empty but we have a non-zero sequence - reset to 0
|
|
@@ -2187,7 +2338,7 @@ export class Mongo extends Db {
|
|
|
2187
2338
|
// Now atomically reserve the range
|
|
2188
2339
|
const result = await dbConnection
|
|
2189
2340
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2190
|
-
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: count } }, { returnDocument:
|
|
2341
|
+
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: count } }, { returnDocument: "after", ...this._sessionOpt() });
|
|
2191
2342
|
const end = result.lastSeqNum;
|
|
2192
2343
|
return { start: end - count + 1, end };
|
|
2193
2344
|
}
|
|
@@ -2212,17 +2363,20 @@ export class Mongo extends Db {
|
|
|
2212
2363
|
return;
|
|
2213
2364
|
if (!this._hasSequenceFields(object) && !this.syncSupport)
|
|
2214
2365
|
return;
|
|
2215
|
-
const seqKeys = Object.keys(object).filter(key => object[key] ===
|
|
2366
|
+
const seqKeys = Object.keys(object).filter((key) => object[key] === "SEQ_NEXT" ||
|
|
2367
|
+
object[key] === "SEQ_LAST");
|
|
2216
2368
|
return { seqKeys };
|
|
2217
2369
|
}
|
|
2218
2370
|
async _processSequenceField(client, dbName, collection, insert, seqKeys) {
|
|
2219
|
-
assert(this.client, "client is required", "_processSequenceField", {
|
|
2371
|
+
assert(this.client, "client is required", "_processSequenceField", {
|
|
2372
|
+
collection,
|
|
2373
|
+
});
|
|
2220
2374
|
// if (this.syncSupport) {
|
|
2221
2375
|
// insert._csq = (await this._getNextCollectionUpdateSeqNo(collection, client));
|
|
2222
2376
|
// }
|
|
2223
2377
|
const db = client.db(dbName);
|
|
2224
2378
|
for (const seqKey of (seqKeys === null || seqKeys === void 0 ? void 0 : seqKeys.seqKeys) || []) {
|
|
2225
|
-
if (insert[seqKey] ===
|
|
2379
|
+
if (insert[seqKey] === "SEQ_LAST") {
|
|
2226
2380
|
insert[seqKey] = await this._getLastSequenceValue(db, collection, seqKey);
|
|
2227
2381
|
}
|
|
2228
2382
|
else {
|
|
@@ -2232,7 +2386,9 @@ export class Mongo extends Db {
|
|
|
2232
2386
|
return insert;
|
|
2233
2387
|
}
|
|
2234
2388
|
async _processSequenceFieldForMany(connection, dbName, collection, inserts) {
|
|
2235
|
-
assert(this.client, "client is required", "_processSequenceFieldForMany", {
|
|
2389
|
+
assert(this.client, "client is required", "_processSequenceFieldForMany", {
|
|
2390
|
+
collection,
|
|
2391
|
+
});
|
|
2236
2392
|
assert(connection, "connection is required", "_processSequenceFieldForMany", { collection });
|
|
2237
2393
|
if (!(inserts === null || inserts === void 0 ? void 0 : inserts.length))
|
|
2238
2394
|
return;
|
|
@@ -2240,7 +2396,7 @@ export class Mongo extends Db {
|
|
|
2240
2396
|
let seqKeysSet = new Set();
|
|
2241
2397
|
for (let insert of inserts) {
|
|
2242
2398
|
let spec = this._findSequenceKeys(insert);
|
|
2243
|
-
spec === null || spec === void 0 ? void 0 : spec.seqKeys.forEach(key => seqKeysSet.add(key));
|
|
2399
|
+
spec === null || spec === void 0 ? void 0 : spec.seqKeys.forEach((key) => seqKeysSet.add(key));
|
|
2244
2400
|
}
|
|
2245
2401
|
let seqKeys = Array.from(seqKeysSet);
|
|
2246
2402
|
if (!seqKeys.length)
|
|
@@ -2251,7 +2407,7 @@ export class Mongo extends Db {
|
|
|
2251
2407
|
// Count SEQ_NEXT occurrences to reserve the range
|
|
2252
2408
|
let seqNextCount = 0;
|
|
2253
2409
|
for (const insert of inserts) {
|
|
2254
|
-
if (insert[seqKey] ===
|
|
2410
|
+
if (insert[seqKey] === "SEQ_NEXT")
|
|
2255
2411
|
seqNextCount++;
|
|
2256
2412
|
}
|
|
2257
2413
|
// Get current value and reserve range for SEQ_NEXT if needed
|
|
@@ -2268,10 +2424,10 @@ export class Mongo extends Db {
|
|
|
2268
2424
|
// Process inserts in order - SEQ_LAST gets current, SEQ_NEXT increments and gets new value
|
|
2269
2425
|
for (const insert of inserts) {
|
|
2270
2426
|
const val = insert[seqKey];
|
|
2271
|
-
if (val ===
|
|
2427
|
+
if (val === "SEQ_LAST") {
|
|
2272
2428
|
insert[seqKey] = currentValue;
|
|
2273
2429
|
}
|
|
2274
|
-
else if (val ===
|
|
2430
|
+
else if (val === "SEQ_NEXT") {
|
|
2275
2431
|
currentValue++;
|
|
2276
2432
|
insert[seqKey] = currentValue;
|
|
2277
2433
|
}
|
|
@@ -2282,9 +2438,13 @@ export class Mongo extends Db {
|
|
|
2282
2438
|
_getFieldsRecursively(obj, into = []) {
|
|
2283
2439
|
for (let key in obj) {
|
|
2284
2440
|
const isDollarKey = startsWithDollar(key);
|
|
2285
|
-
if (isDollarKey &&
|
|
2441
|
+
if (isDollarKey &&
|
|
2442
|
+
typeof obj[key] === "object" &&
|
|
2443
|
+
!Array.isArray(obj[key]))
|
|
2286
2444
|
this._getFieldsRecursively(obj[key], into);
|
|
2287
|
-
else if (!isDollarKey &&
|
|
2445
|
+
else if (!isDollarKey &&
|
|
2446
|
+
typeof obj[key] === "object" &&
|
|
2447
|
+
!Array.isArray(obj[key]))
|
|
2288
2448
|
into.push(key);
|
|
2289
2449
|
else if (!isDollarKey)
|
|
2290
2450
|
into.push(key);
|
|
@@ -2314,7 +2474,7 @@ export class Mongo extends Db {
|
|
|
2314
2474
|
const out = {};
|
|
2315
2475
|
const keys = this._getFieldsRecursively(inputUpdate);
|
|
2316
2476
|
for (const k of keys) {
|
|
2317
|
-
const bracketIdx = k.indexOf(
|
|
2477
|
+
const bracketIdx = k.indexOf("[");
|
|
2318
2478
|
if (bracketIdx < 0) {
|
|
2319
2479
|
out[k] = wholerecord[k];
|
|
2320
2480
|
continue;
|
|
@@ -2322,7 +2482,7 @@ export class Mongo extends Db {
|
|
|
2322
2482
|
// Bracket path: publish the top-level parent field as a whole,
|
|
2323
2483
|
// sliced from the post-update document. The "top-level parent" is
|
|
2324
2484
|
// everything before the FIRST `.` or `[` separator.
|
|
2325
|
-
const dotIdx = k.indexOf(
|
|
2485
|
+
const dotIdx = k.indexOf(".");
|
|
2326
2486
|
const cutAt = dotIdx >= 0 && dotIdx < bracketIdx ? dotIdx : bracketIdx;
|
|
2327
2487
|
const topField = k.substring(0, cutAt);
|
|
2328
2488
|
if (topField && !(topField in out))
|
|
@@ -2351,7 +2511,7 @@ export class Mongo extends Db {
|
|
|
2351
2511
|
if (!this.auditing)
|
|
2352
2512
|
return false;
|
|
2353
2513
|
const fullName = ((db ? db + "." : "") + (col || "")).toLowerCase();
|
|
2354
|
-
return this.auditedCollectionsLower.some(m => fullName.includes(m));
|
|
2514
|
+
return this.auditedCollectionsLower.some((m) => fullName.includes(m));
|
|
2355
2515
|
}
|
|
2356
2516
|
async _publishAndAudit(operation, db, collection, dataToPublish, noEmit, rawUpdate) {
|
|
2357
2517
|
if (!dataToPublish._id && !["deleteMany", "updateMany"].includes(operation))
|
|
@@ -2383,7 +2543,7 @@ export class Mongo extends Db {
|
|
|
2383
2543
|
}
|
|
2384
2544
|
}
|
|
2385
2545
|
if (this._shouldAuditCollection(db, collection)) {
|
|
2386
|
-
if ([
|
|
2546
|
+
if (["insert", "update", "delete"].includes(operation))
|
|
2387
2547
|
await this._writeAuditRecord(db, collection, operation, data);
|
|
2388
2548
|
}
|
|
2389
2549
|
return toPublishAll;
|
|
@@ -2411,11 +2571,11 @@ export class Mongo extends Db {
|
|
|
2411
2571
|
const user = (_a = data === null || data === void 0 ? void 0 : data._auth) === null || _a === void 0 ? void 0 : _a.username;
|
|
2412
2572
|
let toPublish;
|
|
2413
2573
|
switch (operation) {
|
|
2414
|
-
case
|
|
2574
|
+
case "insert":
|
|
2415
2575
|
toPublish = {
|
|
2416
2576
|
channel,
|
|
2417
2577
|
payload: {
|
|
2418
|
-
operation:
|
|
2578
|
+
operation: "insert",
|
|
2419
2579
|
db,
|
|
2420
2580
|
collection,
|
|
2421
2581
|
_id: data._id,
|
|
@@ -2425,11 +2585,11 @@ export class Mongo extends Db {
|
|
|
2425
2585
|
},
|
|
2426
2586
|
};
|
|
2427
2587
|
break;
|
|
2428
|
-
case
|
|
2588
|
+
case "update":
|
|
2429
2589
|
toPublish = {
|
|
2430
2590
|
channel,
|
|
2431
2591
|
payload: {
|
|
2432
|
-
operation:
|
|
2592
|
+
operation: "update",
|
|
2433
2593
|
db,
|
|
2434
2594
|
collection,
|
|
2435
2595
|
_id: data._id,
|
|
@@ -2439,11 +2599,11 @@ export class Mongo extends Db {
|
|
|
2439
2599
|
},
|
|
2440
2600
|
};
|
|
2441
2601
|
break;
|
|
2442
|
-
case
|
|
2602
|
+
case "delete":
|
|
2443
2603
|
toPublish = {
|
|
2444
2604
|
channel,
|
|
2445
2605
|
payload: {
|
|
2446
|
-
operation:
|
|
2606
|
+
operation: "delete",
|
|
2447
2607
|
db,
|
|
2448
2608
|
collection,
|
|
2449
2609
|
_id: data._id,
|
|
@@ -2453,30 +2613,30 @@ export class Mongo extends Db {
|
|
|
2453
2613
|
},
|
|
2454
2614
|
};
|
|
2455
2615
|
break;
|
|
2456
|
-
case
|
|
2616
|
+
case "updateMany":
|
|
2457
2617
|
toPublish = {
|
|
2458
2618
|
channel,
|
|
2459
2619
|
payload: {
|
|
2460
|
-
operation:
|
|
2620
|
+
operation: "updateMany",
|
|
2461
2621
|
db,
|
|
2462
2622
|
collection,
|
|
2463
2623
|
enquireLastTs: true,
|
|
2464
2624
|
},
|
|
2465
2625
|
};
|
|
2466
2626
|
break;
|
|
2467
|
-
case
|
|
2627
|
+
case "deleteMany":
|
|
2468
2628
|
toPublish = {
|
|
2469
2629
|
channel,
|
|
2470
2630
|
payload: {
|
|
2471
|
-
operation:
|
|
2631
|
+
operation: "deleteMany",
|
|
2472
2632
|
db,
|
|
2473
2633
|
collection,
|
|
2474
2634
|
enquireLastTs: true,
|
|
2475
2635
|
},
|
|
2476
2636
|
};
|
|
2477
2637
|
break;
|
|
2478
|
-
case
|
|
2479
|
-
throw new Error(
|
|
2638
|
+
case "batch":
|
|
2639
|
+
throw new Error("batch operation should use direct emit, not _makeRevPublication");
|
|
2480
2640
|
}
|
|
2481
2641
|
if (user)
|
|
2482
2642
|
toPublish.user = user;
|
|
@@ -2493,12 +2653,17 @@ export class Mongo extends Db {
|
|
|
2493
2653
|
if (!this.auditing)
|
|
2494
2654
|
return;
|
|
2495
2655
|
let client = await this.connect();
|
|
2496
|
-
let previousAuditRecords = await (await client
|
|
2656
|
+
let previousAuditRecords = await (await client
|
|
2657
|
+
.db(db)
|
|
2658
|
+
.collection(this.auditCollectionName)
|
|
2659
|
+
.aggregate([
|
|
2497
2660
|
{ $match: { entityid: Mongo._toId(data._id) } },
|
|
2498
2661
|
{ $sort: { rev: -1 } },
|
|
2499
|
-
{ $limit: 1 }
|
|
2662
|
+
{ $limit: 1 },
|
|
2500
2663
|
], this.session ? { session: this.session } : {})).toArray();
|
|
2501
|
-
let previousAuditRecord = previousAuditRecords.length
|
|
2664
|
+
let previousAuditRecord = previousAuditRecords.length
|
|
2665
|
+
? previousAuditRecords[0]
|
|
2666
|
+
: { rev: 0, changes: {} };
|
|
2502
2667
|
if (previousAuditRecords.length === 0)
|
|
2503
2668
|
await this.createCollection(this.auditCollectionName);
|
|
2504
2669
|
let dataNoId = { ...data };
|
|
@@ -2517,11 +2682,12 @@ export class Mongo extends Db {
|
|
|
2517
2682
|
auditRecord.user = user;
|
|
2518
2683
|
if (audit)
|
|
2519
2684
|
auditRecord.audit = audit;
|
|
2520
|
-
fjLog.trace(
|
|
2521
|
-
let ret = await client
|
|
2685
|
+
fjLog.trace("AUDITING", auditRecord);
|
|
2686
|
+
let ret = await client
|
|
2687
|
+
.db(db)
|
|
2522
2688
|
.collection(this.auditCollectionName)
|
|
2523
2689
|
.insertOne(auditRecord, this._sessionOpt());
|
|
2524
|
-
fjLog.debug(
|
|
2690
|
+
fjLog.debug("AUDITED", auditRecord, ret.insertedId);
|
|
2525
2691
|
}
|
|
2526
2692
|
_sessionOpt() {
|
|
2527
2693
|
return this.session ? { session: this.session } : {};
|
|
@@ -2552,7 +2718,7 @@ export class Mongo extends Db {
|
|
|
2552
2718
|
return Object.keys(obj).some(startsWithHashedPrefix);
|
|
2553
2719
|
}
|
|
2554
2720
|
_hasSequenceFields(obj) {
|
|
2555
|
-
return Object.values(obj).some(v => v ===
|
|
2721
|
+
return Object.values(obj).some((v) => v === "SEQ_NEXT" || v === "SEQ_LAST");
|
|
2556
2722
|
}
|
|
2557
2723
|
_processUpdateObject(update) {
|
|
2558
2724
|
for (let k in update) {
|
|
@@ -2560,7 +2726,8 @@ export class Mongo extends Db {
|
|
|
2560
2726
|
const keyStr = String(key);
|
|
2561
2727
|
if (keyStr[0] === "$")
|
|
2562
2728
|
continue;
|
|
2563
|
-
if (startsWithDoubleUnderscore(keyStr) &&
|
|
2729
|
+
if (startsWithDoubleUnderscore(keyStr) &&
|
|
2730
|
+
!keyStr.startsWith(HASHED_PREFIX)) {
|
|
2564
2731
|
delete update[key];
|
|
2565
2732
|
continue;
|
|
2566
2733
|
}
|
|
@@ -2568,7 +2735,7 @@ export class Mongo extends Db {
|
|
|
2568
2735
|
delete update[key];
|
|
2569
2736
|
continue;
|
|
2570
2737
|
}
|
|
2571
|
-
if (key ===
|
|
2738
|
+
if (key === "" || key === null || key === undefined) {
|
|
2572
2739
|
delete update[key];
|
|
2573
2740
|
continue;
|
|
2574
2741
|
}
|
|
@@ -2586,12 +2753,14 @@ export class Mongo extends Db {
|
|
|
2586
2753
|
if (this.revisions) {
|
|
2587
2754
|
update.$inc = update.$inc || {};
|
|
2588
2755
|
update.$inc._rev = 1;
|
|
2589
|
-
const currentDate = update.$currentDate &&
|
|
2756
|
+
const currentDate = update.$currentDate &&
|
|
2757
|
+
typeof update.$currentDate === "object" &&
|
|
2758
|
+
!Array.isArray(update.$currentDate)
|
|
2590
2759
|
? update.$currentDate
|
|
2591
2760
|
: undefined;
|
|
2592
2761
|
update.$currentDate = {
|
|
2593
2762
|
...(currentDate || {}),
|
|
2594
|
-
_ts: { $type:
|
|
2763
|
+
_ts: { $type: "timestamp" },
|
|
2595
2764
|
};
|
|
2596
2765
|
}
|
|
2597
2766
|
if (update.$set) {
|
|
@@ -2631,35 +2800,41 @@ export class Mongo extends Db {
|
|
|
2631
2800
|
const translatePath = (path) => {
|
|
2632
2801
|
const tokens = Mongo._tokenizePath(path);
|
|
2633
2802
|
const out = [];
|
|
2634
|
-
let parentKey =
|
|
2803
|
+
let parentKey = "";
|
|
2635
2804
|
for (const t of tokens) {
|
|
2636
|
-
if (t.length >= 2 &&
|
|
2805
|
+
if (t.length >= 2 &&
|
|
2806
|
+
t.charCodeAt(0) === 91 /* [ */ &&
|
|
2807
|
+
t.charCodeAt(t.length - 1) === 93 /* ] */) {
|
|
2637
2808
|
const idStr = Mongo._unquoteBracketId(t, path);
|
|
2638
2809
|
const memoKey = `${parentKey}|${idStr}`;
|
|
2639
2810
|
let filterName = memo.get(memoKey);
|
|
2640
2811
|
if (filterName === undefined) {
|
|
2641
2812
|
filterName = `f${counter++}`;
|
|
2642
2813
|
memo.set(memoKey, filterName);
|
|
2643
|
-
filters.push({
|
|
2814
|
+
filters.push({
|
|
2815
|
+
[`${filterName}._id`]: Mongo._normalizeBracketId(idStr),
|
|
2816
|
+
});
|
|
2644
2817
|
}
|
|
2645
2818
|
out.push(`$[${filterName}]`);
|
|
2646
|
-
parentKey = parentKey
|
|
2819
|
+
parentKey = parentKey
|
|
2820
|
+
? `${parentKey}.$[${filterName}]`
|
|
2821
|
+
: `$[${filterName}]`;
|
|
2647
2822
|
}
|
|
2648
2823
|
else {
|
|
2649
2824
|
out.push(t);
|
|
2650
2825
|
parentKey = parentKey ? `${parentKey}.${t}` : t;
|
|
2651
2826
|
}
|
|
2652
2827
|
}
|
|
2653
|
-
return out.join(
|
|
2828
|
+
return out.join(".");
|
|
2654
2829
|
};
|
|
2655
2830
|
const translateOp = (opName) => {
|
|
2656
2831
|
const op = update[opName];
|
|
2657
|
-
if (!op || typeof op !==
|
|
2832
|
+
if (!op || typeof op !== "object")
|
|
2658
2833
|
return;
|
|
2659
2834
|
// Fast path: skip rebuild if no bracket present.
|
|
2660
2835
|
let hasBracket = false;
|
|
2661
2836
|
for (const k of Object.keys(op)) {
|
|
2662
|
-
if (k.indexOf(
|
|
2837
|
+
if (k.indexOf("[") >= 0) {
|
|
2663
2838
|
hasBracket = true;
|
|
2664
2839
|
break;
|
|
2665
2840
|
}
|
|
@@ -2672,8 +2847,8 @@ export class Mongo extends Db {
|
|
|
2672
2847
|
}
|
|
2673
2848
|
update[opName] = translated;
|
|
2674
2849
|
};
|
|
2675
|
-
translateOp(
|
|
2676
|
-
translateOp(
|
|
2850
|
+
translateOp("$set");
|
|
2851
|
+
translateOp("$unset");
|
|
2677
2852
|
return filters.length > 0 ? filters : undefined;
|
|
2678
2853
|
}
|
|
2679
2854
|
/**
|
|
@@ -2723,38 +2898,38 @@ export class Mongo extends Db {
|
|
|
2723
2898
|
var _a;
|
|
2724
2899
|
if (value === null || value === undefined)
|
|
2725
2900
|
return false;
|
|
2726
|
-
if (typeof value !==
|
|
2901
|
+
if (typeof value !== "object")
|
|
2727
2902
|
return false;
|
|
2728
2903
|
if (value instanceof Date)
|
|
2729
2904
|
return false;
|
|
2730
2905
|
const ctorName = (_a = value.constructor) === null || _a === void 0 ? void 0 : _a.name;
|
|
2731
|
-
if (ctorName ===
|
|
2732
|
-
ctorName ===
|
|
2733
|
-
ctorName ===
|
|
2734
|
-
ctorName ===
|
|
2735
|
-
ctorName ===
|
|
2736
|
-
ctorName ===
|
|
2737
|
-
ctorName ===
|
|
2906
|
+
if (ctorName === "ObjectId" ||
|
|
2907
|
+
ctorName === "Timestamp" ||
|
|
2908
|
+
ctorName === "Decimal128" ||
|
|
2909
|
+
ctorName === "Binary" ||
|
|
2910
|
+
ctorName === "Long" ||
|
|
2911
|
+
ctorName === "Double" ||
|
|
2912
|
+
ctorName === "Int32")
|
|
2738
2913
|
return false;
|
|
2739
2914
|
return true;
|
|
2740
2915
|
}
|
|
2741
2916
|
static _tokenizePath(path) {
|
|
2742
2917
|
const out = [];
|
|
2743
|
-
let buf =
|
|
2918
|
+
let buf = "";
|
|
2744
2919
|
for (let i = 0; i < path.length; i++) {
|
|
2745
2920
|
const ch = path[i];
|
|
2746
|
-
if (ch ===
|
|
2921
|
+
if (ch === ".") {
|
|
2747
2922
|
if (buf) {
|
|
2748
2923
|
out.push(buf);
|
|
2749
|
-
buf =
|
|
2924
|
+
buf = "";
|
|
2750
2925
|
}
|
|
2751
2926
|
}
|
|
2752
|
-
else if (ch ===
|
|
2927
|
+
else if (ch === "[") {
|
|
2753
2928
|
if (buf) {
|
|
2754
2929
|
out.push(buf);
|
|
2755
|
-
buf =
|
|
2930
|
+
buf = "";
|
|
2756
2931
|
}
|
|
2757
|
-
const close = path.indexOf(
|
|
2932
|
+
const close = path.indexOf("]", i);
|
|
2758
2933
|
if (close < 0) {
|
|
2759
2934
|
buf += ch;
|
|
2760
2935
|
continue;
|
|
@@ -2786,7 +2961,8 @@ export class Mongo extends Db {
|
|
|
2786
2961
|
if (len >= 2) {
|
|
2787
2962
|
const first = id.charCodeAt(0);
|
|
2788
2963
|
const last = id.charCodeAt(len - 1);
|
|
2789
|
-
if ((first === 39 /* ' */ && last === 39) ||
|
|
2964
|
+
if ((first === 39 /* ' */ && last === 39) ||
|
|
2965
|
+
(first === 34 /* " */ && last === 34)) {
|
|
2790
2966
|
id = id.slice(1, -1);
|
|
2791
2967
|
}
|
|
2792
2968
|
}
|
|
@@ -2818,11 +2994,11 @@ export class Mongo extends Db {
|
|
|
2818
2994
|
*/
|
|
2819
2995
|
_validateAndAutoFillTerminalBracketValues(update) {
|
|
2820
2996
|
const $set = update.$set;
|
|
2821
|
-
if (!$set || typeof $set !==
|
|
2997
|
+
if (!$set || typeof $set !== "object")
|
|
2822
2998
|
return;
|
|
2823
2999
|
let hasBracket = false;
|
|
2824
3000
|
for (const k of Object.keys($set)) {
|
|
2825
|
-
if (k.indexOf(
|
|
3001
|
+
if (k.indexOf("[") >= 0) {
|
|
2826
3002
|
hasBracket = true;
|
|
2827
3003
|
break;
|
|
2828
3004
|
}
|
|
@@ -2832,17 +3008,17 @@ export class Mongo extends Db {
|
|
|
2832
3008
|
for (const path of Object.keys($set)) {
|
|
2833
3009
|
const tokens = Mongo._tokenizePath(path);
|
|
2834
3010
|
const lastToken = tokens[tokens.length - 1];
|
|
2835
|
-
const isTerminalBracket = !!(lastToken
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3011
|
+
const isTerminalBracket = !!((lastToken &&
|
|
3012
|
+
lastToken.length >= 2 &&
|
|
3013
|
+
lastToken.charCodeAt(0) === 91 /* [ */ &&
|
|
3014
|
+
lastToken.charCodeAt(lastToken.length - 1) === 93) /* ] */);
|
|
2839
3015
|
if (!isTerminalBracket)
|
|
2840
3016
|
continue;
|
|
2841
3017
|
const bracketId = Mongo._unquoteBracketId(lastToken, path); // throws on empty
|
|
2842
3018
|
const value = $set[path];
|
|
2843
3019
|
const validateElement = (el, locator) => {
|
|
2844
|
-
if (el == null || typeof el !==
|
|
2845
|
-
throw new Error(`cry-db: bracket-id terminal at "${path}"${locator} — value must be an object with _id matching bracket id "${bracketId}" (got ${el === null ?
|
|
3020
|
+
if (el == null || typeof el !== "object") {
|
|
3021
|
+
throw new Error(`cry-db: bracket-id terminal at "${path}"${locator} — value must be an object with _id matching bracket id "${bracketId}" (got ${el === null ? "null" : typeof el}).`);
|
|
2846
3022
|
}
|
|
2847
3023
|
if (el._id == null) {
|
|
2848
3024
|
throw new Error(`cry-db: bracket-id terminal at "${path}"${locator} — element is missing _id. Set it explicitly to "${bracketId}" (auto-fill is no longer supported).`);
|
|
@@ -2855,8 +3031,10 @@ export class Mongo extends Db {
|
|
|
2855
3031
|
for (let i = 0; i < value.length; i++)
|
|
2856
3032
|
validateElement(value[i], ` [${i}]`);
|
|
2857
3033
|
}
|
|
2858
|
-
else if (value !== null &&
|
|
2859
|
-
|
|
3034
|
+
else if (value !== null &&
|
|
3035
|
+
value !== undefined &&
|
|
3036
|
+
typeof value === "object") {
|
|
3037
|
+
validateElement(value, "");
|
|
2860
3038
|
}
|
|
2861
3039
|
// Primitives (string/number/boolean) — pass through to existing arrayFilters
|
|
2862
3040
|
// path. Mongo would set the array element to the primitive, which is unusual
|
|
@@ -2887,12 +3065,12 @@ export class Mongo extends Db {
|
|
|
2887
3065
|
*/
|
|
2888
3066
|
_extractArrayInserts(update) {
|
|
2889
3067
|
const $set = update.$set;
|
|
2890
|
-
if (!$set || typeof $set !==
|
|
3068
|
+
if (!$set || typeof $set !== "object")
|
|
2891
3069
|
return undefined;
|
|
2892
3070
|
// Fast path: no bracket present in any key.
|
|
2893
3071
|
let hasBracket = false;
|
|
2894
3072
|
for (const k of Object.keys($set)) {
|
|
2895
|
-
if (k.indexOf(
|
|
3073
|
+
if (k.indexOf("[") >= 0) {
|
|
2896
3074
|
hasBracket = true;
|
|
2897
3075
|
break;
|
|
2898
3076
|
}
|
|
@@ -2905,19 +3083,19 @@ export class Mongo extends Db {
|
|
|
2905
3083
|
const value = $set[path];
|
|
2906
3084
|
const tokens = Mongo._tokenizePath(path);
|
|
2907
3085
|
const lastToken = tokens[tokens.length - 1];
|
|
2908
|
-
const isTerminalBracket = !!(lastToken
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3086
|
+
const isTerminalBracket = !!((lastToken &&
|
|
3087
|
+
lastToken.length >= 2 &&
|
|
3088
|
+
lastToken.charCodeAt(0) === 91 /* [ */ &&
|
|
3089
|
+
lastToken.charCodeAt(lastToken.length - 1) === 93) /* ] */);
|
|
2912
3090
|
if (isTerminalBracket && Array.isArray(value)) {
|
|
2913
3091
|
const parentTokens = tokens.slice(0, -1);
|
|
2914
3092
|
// Reject nested-bracket parent paths — combining $push semantics with
|
|
2915
3093
|
// arrayFilters-targeted parent is not cleanly expressible in mongo.
|
|
2916
3094
|
const parentHasBracket = parentTokens.some((t) => t.length > 0 && t.charCodeAt(0) === 91);
|
|
2917
3095
|
// Every element must have `_id` (required so we can dedupe before push).
|
|
2918
|
-
const allElementsHaveId = value.every((e) => e && typeof e ===
|
|
3096
|
+
const allElementsHaveId = value.every((e) => e && typeof e === "object" && e._id != null);
|
|
2919
3097
|
if (!parentHasBracket && allElementsHaveId && parentTokens.length > 0) {
|
|
2920
|
-
const fieldPath = parentTokens.join(
|
|
3098
|
+
const fieldPath = parentTokens.join(".");
|
|
2921
3099
|
const ids = value.map((e) => String(e._id));
|
|
2922
3100
|
inserts.push({ field: fieldPath, ids, elements: value });
|
|
2923
3101
|
continue;
|
|
@@ -2954,11 +3132,11 @@ export class Mongo extends Db {
|
|
|
2954
3132
|
*/
|
|
2955
3133
|
_extractArrayRemoves(update) {
|
|
2956
3134
|
const $unset = update.$unset;
|
|
2957
|
-
if (!$unset || typeof $unset !==
|
|
3135
|
+
if (!$unset || typeof $unset !== "object")
|
|
2958
3136
|
return undefined;
|
|
2959
3137
|
let hasBracket = false;
|
|
2960
3138
|
for (const k of Object.keys($unset)) {
|
|
2961
|
-
if (k.indexOf(
|
|
3139
|
+
if (k.indexOf("[") >= 0) {
|
|
2962
3140
|
hasBracket = true;
|
|
2963
3141
|
break;
|
|
2964
3142
|
}
|
|
@@ -2970,15 +3148,15 @@ export class Mongo extends Db {
|
|
|
2970
3148
|
for (const path of Object.keys($unset)) {
|
|
2971
3149
|
const tokens = Mongo._tokenizePath(path);
|
|
2972
3150
|
const lastToken = tokens[tokens.length - 1];
|
|
2973
|
-
const isTerminalBracket = !!(lastToken
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3151
|
+
const isTerminalBracket = !!((lastToken &&
|
|
3152
|
+
lastToken.length >= 2 &&
|
|
3153
|
+
lastToken.charCodeAt(0) === 91 /* [ */ &&
|
|
3154
|
+
lastToken.charCodeAt(lastToken.length - 1) === 93) /* ] */);
|
|
2977
3155
|
if (isTerminalBracket) {
|
|
2978
3156
|
const parentTokens = tokens.slice(0, -1);
|
|
2979
3157
|
const parentHasBracket = parentTokens.some((t) => t.length > 0 && t.charCodeAt(0) === 91);
|
|
2980
3158
|
if (!parentHasBracket && parentTokens.length > 0) {
|
|
2981
|
-
const fieldPath = parentTokens.join(
|
|
3159
|
+
const fieldPath = parentTokens.join(".");
|
|
2982
3160
|
const id = Mongo._unquoteBracketId(lastToken, path);
|
|
2983
3161
|
if (!removesByField.has(fieldPath))
|
|
2984
3162
|
removesByField.set(fieldPath, []);
|
|
@@ -2993,7 +3171,10 @@ export class Mongo extends Db {
|
|
|
2993
3171
|
update.$unset = remaining;
|
|
2994
3172
|
if (Object.keys(remaining).length === 0)
|
|
2995
3173
|
delete update.$unset;
|
|
2996
|
-
return Array.from(removesByField.entries()).map(([field, ids]) => ({
|
|
3174
|
+
return Array.from(removesByField.entries()).map(([field, ids]) => ({
|
|
3175
|
+
field,
|
|
3176
|
+
ids,
|
|
3177
|
+
}));
|
|
2997
3178
|
}
|
|
2998
3179
|
/**
|
|
2999
3180
|
* Extracts SINGLE-bracket bracket-by-_id paths from `$set`/`$unset`, grouped
|
|
@@ -3029,11 +3210,11 @@ export class Mongo extends Db {
|
|
|
3029
3210
|
};
|
|
3030
3211
|
const processOp = (opName) => {
|
|
3031
3212
|
const op = update[opName];
|
|
3032
|
-
if (!op || typeof op !==
|
|
3213
|
+
if (!op || typeof op !== "object")
|
|
3033
3214
|
return;
|
|
3034
3215
|
let hasBracket = false;
|
|
3035
3216
|
for (const k of Object.keys(op)) {
|
|
3036
|
-
if (k.indexOf(
|
|
3217
|
+
if (k.indexOf("[") >= 0) {
|
|
3037
3218
|
hasBracket = true;
|
|
3038
3219
|
break;
|
|
3039
3220
|
}
|
|
@@ -3048,18 +3229,20 @@ export class Mongo extends Db {
|
|
|
3048
3229
|
let bracketIdx = -1;
|
|
3049
3230
|
for (let i = 0; i < tokens.length; i++) {
|
|
3050
3231
|
const t = tokens[i];
|
|
3051
|
-
if (t.length >= 2 &&
|
|
3232
|
+
if (t.length >= 2 &&
|
|
3233
|
+
t.charCodeAt(0) === 91 /* [ */ &&
|
|
3234
|
+
t.charCodeAt(t.length - 1) === 93 /* ] */) {
|
|
3052
3235
|
bracketCount++;
|
|
3053
3236
|
bracketIdx = i;
|
|
3054
3237
|
}
|
|
3055
3238
|
}
|
|
3056
3239
|
if (bracketCount === 1 && bracketIdx > 0) {
|
|
3057
|
-
const parentField = tokens.slice(0, bracketIdx).join(
|
|
3240
|
+
const parentField = tokens.slice(0, bracketIdx).join(".");
|
|
3058
3241
|
const id = Mongo._unquoteBracketId(tokens[bracketIdx], path);
|
|
3059
|
-
const restPath = tokens.slice(bracketIdx + 1).join(
|
|
3242
|
+
const restPath = tokens.slice(bracketIdx + 1).join(".");
|
|
3060
3243
|
const elOps = ensureEntry(parentField, id);
|
|
3061
|
-
if (opName ===
|
|
3062
|
-
if (restPath ===
|
|
3244
|
+
if (opName === "$set") {
|
|
3245
|
+
if (restPath === "") {
|
|
3063
3246
|
// `arr[id]: <obj>` — whole-element replace.
|
|
3064
3247
|
elOps.replace = op[path];
|
|
3065
3248
|
}
|
|
@@ -3070,7 +3253,7 @@ export class Mongo extends Db {
|
|
|
3070
3253
|
else {
|
|
3071
3254
|
// `$unset arr[id].field` — sub-field unset. Terminal `$unset arr[id]`
|
|
3072
3255
|
// is already extracted by `_extractArrayRemoves`.
|
|
3073
|
-
if (restPath ===
|
|
3256
|
+
if (restPath === "") {
|
|
3074
3257
|
remaining[path] = op[path];
|
|
3075
3258
|
continue;
|
|
3076
3259
|
}
|
|
@@ -3088,8 +3271,8 @@ export class Mongo extends Db {
|
|
|
3088
3271
|
delete update[opName];
|
|
3089
3272
|
}
|
|
3090
3273
|
};
|
|
3091
|
-
processOp(
|
|
3092
|
-
processOp(
|
|
3274
|
+
processOp("$set");
|
|
3275
|
+
processOp("$unset");
|
|
3093
3276
|
return extractedAny ? result : undefined;
|
|
3094
3277
|
}
|
|
3095
3278
|
/**
|
|
@@ -3108,8 +3291,8 @@ export class Mongo extends Db {
|
|
|
3108
3291
|
const filteredInput = {
|
|
3109
3292
|
$filter: {
|
|
3110
3293
|
input: { $ifNull: [baseInputPath, []] },
|
|
3111
|
-
as:
|
|
3112
|
-
cond: { $not: { $in: [
|
|
3294
|
+
as: "el",
|
|
3295
|
+
cond: { $not: { $in: ["$$el._id", dedupedRemoveIds] } },
|
|
3113
3296
|
},
|
|
3114
3297
|
};
|
|
3115
3298
|
const concatExpr = {
|
|
@@ -3120,15 +3303,15 @@ export class Mongo extends Db {
|
|
|
3120
3303
|
const branches = [];
|
|
3121
3304
|
for (const [id, elOps] of ops.elementOps.entries()) {
|
|
3122
3305
|
branches.push({
|
|
3123
|
-
case: { $eq: [
|
|
3124
|
-
then: Mongo._buildElementMergeExpr(
|
|
3306
|
+
case: { $eq: ["$$el._id", Mongo._normalizeBracketId(id)] },
|
|
3307
|
+
then: Mongo._buildElementMergeExpr("$$el", elOps),
|
|
3125
3308
|
});
|
|
3126
3309
|
}
|
|
3127
3310
|
return {
|
|
3128
3311
|
$map: {
|
|
3129
3312
|
input: concatExpr,
|
|
3130
|
-
as:
|
|
3131
|
-
in: { $switch: { branches, default:
|
|
3313
|
+
as: "el",
|
|
3314
|
+
in: { $switch: { branches, default: "$$el" } },
|
|
3132
3315
|
},
|
|
3133
3316
|
};
|
|
3134
3317
|
}
|
|
@@ -3211,13 +3394,13 @@ export class Mongo extends Db {
|
|
|
3211
3394
|
return {
|
|
3212
3395
|
$let: {
|
|
3213
3396
|
vars: { replaced: replaceExpr },
|
|
3214
|
-
in: wrapWithNestedArrays(
|
|
3397
|
+
in: wrapWithNestedArrays("$$replaced", "$$replaced"),
|
|
3215
3398
|
},
|
|
3216
3399
|
};
|
|
3217
3400
|
}
|
|
3218
3401
|
const root = { directs: {}, nested: {}, unsets: [] };
|
|
3219
3402
|
const walkInsert = (path, isUnset, value) => {
|
|
3220
|
-
const parts = path.split(
|
|
3403
|
+
const parts = path.split(".");
|
|
3221
3404
|
let node = root;
|
|
3222
3405
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
3223
3406
|
const key = parts[i];
|
|
@@ -3255,15 +3438,17 @@ export class Mongo extends Db {
|
|
|
3255
3438
|
overlay[k] = buildOverlay(sub, subPath);
|
|
3256
3439
|
}
|
|
3257
3440
|
// Merge sets/nested onto the existing object (or {} if absent).
|
|
3258
|
-
let merged = {
|
|
3441
|
+
let merged = {
|
|
3442
|
+
$mergeObjects: [{ $ifNull: [basePath, {}] }, overlay],
|
|
3443
|
+
};
|
|
3259
3444
|
// Drop unset top-level keys at this level via kv-filter.
|
|
3260
3445
|
if (node.unsets.length > 0) {
|
|
3261
3446
|
merged = {
|
|
3262
3447
|
$arrayToObject: {
|
|
3263
3448
|
$filter: {
|
|
3264
3449
|
input: { $objectToArray: merged },
|
|
3265
|
-
as:
|
|
3266
|
-
cond: { $not: { $in: [
|
|
3450
|
+
as: "kv",
|
|
3451
|
+
cond: { $not: { $in: ["$$kv.k", node.unsets] } },
|
|
3267
3452
|
},
|
|
3268
3453
|
},
|
|
3269
3454
|
};
|
|
@@ -3273,11 +3458,11 @@ export class Mongo extends Db {
|
|
|
3273
3458
|
// If a replace is also requested, treat replacement as the new base.
|
|
3274
3459
|
// Wrap with `$let` since a literal object isn't addressable by string field path.
|
|
3275
3460
|
if (hasReplace) {
|
|
3276
|
-
const overlayExpr = buildOverlay(root,
|
|
3461
|
+
const overlayExpr = buildOverlay(root, "$$replaced");
|
|
3277
3462
|
return {
|
|
3278
3463
|
$let: {
|
|
3279
3464
|
vars: { replaced: { $literal: ops.replace } },
|
|
3280
|
-
in: wrapWithNestedArrays(overlayExpr,
|
|
3465
|
+
in: wrapWithNestedArrays(overlayExpr, "$$replaced"),
|
|
3281
3466
|
},
|
|
3282
3467
|
};
|
|
3283
3468
|
}
|
|
@@ -3298,7 +3483,7 @@ export class Mongo extends Db {
|
|
|
3298
3483
|
static _drainIncAndCurrentDateToPipelineStages(update) {
|
|
3299
3484
|
const stages = [];
|
|
3300
3485
|
const inc = update.$inc;
|
|
3301
|
-
if (inc && typeof inc ===
|
|
3486
|
+
if (inc && typeof inc === "object") {
|
|
3302
3487
|
const setObj = {};
|
|
3303
3488
|
for (const [k, v] of Object.entries(inc)) {
|
|
3304
3489
|
setObj[k] = { $add: [{ $ifNull: [`$${k}`, 0] }, v] };
|
|
@@ -3308,13 +3493,15 @@ export class Mongo extends Db {
|
|
|
3308
3493
|
delete update.$inc;
|
|
3309
3494
|
}
|
|
3310
3495
|
const cd = update.$currentDate;
|
|
3311
|
-
if (cd && typeof cd ===
|
|
3496
|
+
if (cd && typeof cd === "object") {
|
|
3312
3497
|
const setObj = {};
|
|
3313
3498
|
for (const [k, spec] of Object.entries(cd)) {
|
|
3314
3499
|
let isTimestamp = false;
|
|
3315
|
-
if (spec &&
|
|
3500
|
+
if (spec &&
|
|
3501
|
+
typeof spec === "object" &&
|
|
3502
|
+
spec.$type === "timestamp")
|
|
3316
3503
|
isTimestamp = true;
|
|
3317
|
-
setObj[k] = isTimestamp ?
|
|
3504
|
+
setObj[k] = isTimestamp ? "$$CLUSTER_TIME" : "$$NOW";
|
|
3318
3505
|
}
|
|
3319
3506
|
if (Object.keys(setObj).length > 0)
|
|
3320
3507
|
stages.push({ $set: setObj });
|
|
@@ -3370,14 +3557,18 @@ export class Mongo extends Db {
|
|
|
3370
3557
|
continue;
|
|
3371
3558
|
let bracketCount = 0;
|
|
3372
3559
|
for (const t of tokens) {
|
|
3373
|
-
if (t.length >= 2 &&
|
|
3560
|
+
if (t.length >= 2 &&
|
|
3561
|
+
t.charCodeAt(0) === 91 &&
|
|
3562
|
+
t.charCodeAt(t.length - 1) === 93) {
|
|
3374
3563
|
bracketCount++;
|
|
3375
3564
|
}
|
|
3376
3565
|
}
|
|
3377
3566
|
if (bracketCount < 2)
|
|
3378
3567
|
continue;
|
|
3379
3568
|
const last = tokens[tokens.length - 1];
|
|
3380
|
-
if (last.length >= 2 &&
|
|
3569
|
+
if (last.length >= 2 &&
|
|
3570
|
+
last.charCodeAt(0) === 91 &&
|
|
3571
|
+
last.charCodeAt(last.length - 1) === 93) {
|
|
3381
3572
|
return true;
|
|
3382
3573
|
}
|
|
3383
3574
|
}
|
|
@@ -3385,13 +3576,51 @@ export class Mongo extends Db {
|
|
|
3385
3576
|
};
|
|
3386
3577
|
const $setEarlyForNested = update.$set;
|
|
3387
3578
|
const $unsetEarlyForNested = update.$unset;
|
|
3388
|
-
const hasNestedTerminal = hasNestedTerminalBracket($setEarlyForNested) ||
|
|
3579
|
+
const hasNestedTerminal = hasNestedTerminalBracket($setEarlyForNested) ||
|
|
3580
|
+
hasNestedTerminalBracket($unsetEarlyForNested);
|
|
3389
3581
|
// Pure sub-field updates only (no inserts, no removes, no nested-terminal) —
|
|
3390
3582
|
// keep on legacy arrayFilters path (faster, smaller payload, doesn't fight
|
|
3391
3583
|
// $inc/$currentDate). Nested sub-field paths like `arr[A].sub[B].field`
|
|
3392
3584
|
// ARE supported via nested arrayFilters here.
|
|
3393
3585
|
if (!inserts && !removes && !hasNestedTerminal) {
|
|
3394
3586
|
const arrayFilters = this._extractArrayFilters(update);
|
|
3587
|
+
// Detect conflict: bare-field `$set` with array value alongside a
|
|
3588
|
+
// bracket-transformed sub-path on the same field (`postavke: [...]` +
|
|
3589
|
+
// `postavke.$[f0].nbc`). MongoDB silently drops the sub-field update
|
|
3590
|
+
// in this case — throw so the caller gets an error instead of data loss.
|
|
3591
|
+
if (arrayFilters) {
|
|
3592
|
+
// Collect bare-field array keys and sub-paths from BOTH
|
|
3593
|
+
// $set AND $unset — $set.postavke conflicts with both
|
|
3594
|
+
// $set.postavke.$[f0].nbc AND $unset.postavke.$[f0].nbc.
|
|
3595
|
+
const $setCheck = update.$set;
|
|
3596
|
+
const $unsetCheck = update.$unset;
|
|
3597
|
+
const bareFields = [];
|
|
3598
|
+
const subPaths = [];
|
|
3599
|
+
if ($setCheck) {
|
|
3600
|
+
for (const k of Object.keys($setCheck)) {
|
|
3601
|
+
(k.includes(".") || k.includes("[") ? subPaths : bareFields).push(k);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
if ($unsetCheck) {
|
|
3605
|
+
for (const k of Object.keys($unsetCheck)) {
|
|
3606
|
+
(k.includes(".") || k.includes("[") ? subPaths : bareFields).push(k);
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
for (const bare of bareFields) {
|
|
3610
|
+
// Only $set bare fields with array values cause conflicts.
|
|
3611
|
+
if ($setCheck && Array.isArray($setCheck[bare])) {
|
|
3612
|
+
for (const sub of subPaths) {
|
|
3613
|
+
if (sub.startsWith(bare + ".") || sub.startsWith(bare + "[")) {
|
|
3614
|
+
throw new Error(`cry-db: bare-field array replacement "${bare}" conflicts ` +
|
|
3615
|
+
`with bracket-by-_id sub-field path "${sub}" on the same ` +
|
|
3616
|
+
`array. Replace the bare-field assignment with a ` +
|
|
3617
|
+
`terminal-bracket form (e.g. \`${bare}[<id>]: [...]\`) ` +
|
|
3618
|
+
`when combining array updates with bracket-by-_id paths.`);
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3395
3624
|
return arrayFilters ? { update, arrayFilters } : { update };
|
|
3396
3625
|
}
|
|
3397
3626
|
// Removes-only fast path: `$pull` on update doc. Coexists fine with `$set` on
|
|
@@ -3412,15 +3641,14 @@ export class Mongo extends Db {
|
|
|
3412
3641
|
for (const k of Object.keys($setEarly)) {
|
|
3413
3642
|
if (k === field)
|
|
3414
3643
|
return true;
|
|
3415
|
-
if (k.startsWith(field +
|
|
3644
|
+
if (k.startsWith(field + "."))
|
|
3416
3645
|
return true;
|
|
3417
|
-
if (k.startsWith(field +
|
|
3646
|
+
if (k.startsWith(field + "["))
|
|
3418
3647
|
return true;
|
|
3419
3648
|
}
|
|
3420
3649
|
return false;
|
|
3421
3650
|
};
|
|
3422
|
-
const removesOnlyNoSetConflict = removes && !inserts
|
|
3423
|
-
&& !removes.some(rm => setTargetsField(rm.field));
|
|
3651
|
+
const removesOnlyNoSetConflict = removes && !inserts && !removes.some((rm) => setTargetsField(rm.field));
|
|
3424
3652
|
if (removesOnlyNoSetConflict) {
|
|
3425
3653
|
const pullOp = (update.$pull || {});
|
|
3426
3654
|
for (const rm of removes) {
|
|
@@ -3465,12 +3693,20 @@ export class Mongo extends Db {
|
|
|
3465
3693
|
const fieldOps = new Map();
|
|
3466
3694
|
const ensureField = (container, field) => {
|
|
3467
3695
|
if (!container.has(field))
|
|
3468
|
-
container.set(field, {
|
|
3696
|
+
container.set(field, {
|
|
3697
|
+
removeIds: [],
|
|
3698
|
+
insertElements: [],
|
|
3699
|
+
elementOps: new Map(),
|
|
3700
|
+
});
|
|
3469
3701
|
return container.get(field);
|
|
3470
3702
|
};
|
|
3471
3703
|
const ensureElement = (fops, id) => {
|
|
3472
3704
|
if (!fops.elementOps.has(id))
|
|
3473
|
-
fops.elementOps.set(id, {
|
|
3705
|
+
fops.elementOps.set(id, {
|
|
3706
|
+
sets: {},
|
|
3707
|
+
unsets: [],
|
|
3708
|
+
nestedArrayOps: new Map(),
|
|
3709
|
+
});
|
|
3474
3710
|
const elOps = fops.elementOps.get(id);
|
|
3475
3711
|
if (!elOps.nestedArrayOps)
|
|
3476
3712
|
elOps.nestedArrayOps = new Map();
|
|
@@ -3514,7 +3750,7 @@ export class Mongo extends Db {
|
|
|
3514
3750
|
return;
|
|
3515
3751
|
const remaining = {};
|
|
3516
3752
|
for (const path of Object.keys(op)) {
|
|
3517
|
-
if (path.indexOf(
|
|
3753
|
+
if (path.indexOf("[") < 0) {
|
|
3518
3754
|
remaining[path] = op[path];
|
|
3519
3755
|
continue;
|
|
3520
3756
|
}
|
|
@@ -3522,14 +3758,16 @@ export class Mongo extends Db {
|
|
|
3522
3758
|
const brackets = [];
|
|
3523
3759
|
for (let i = 0; i < tokens.length; i++) {
|
|
3524
3760
|
const t = tokens[i];
|
|
3525
|
-
if (t.length >= 2 &&
|
|
3761
|
+
if (t.length >= 2 &&
|
|
3762
|
+
t.charCodeAt(0) === 91 &&
|
|
3763
|
+
t.charCodeAt(t.length - 1) === 93) {
|
|
3526
3764
|
brackets.push({ idx: i, id: Mongo._unquoteBracketId(t, path) });
|
|
3527
3765
|
}
|
|
3528
3766
|
}
|
|
3529
3767
|
if (brackets.length < 2) {
|
|
3530
3768
|
// Single-bracket: extractors above should have handled it; if it
|
|
3531
3769
|
// remained, it's malformed (e.g. empty parent). Drop with warning.
|
|
3532
|
-
warnings.push({ path, value: op[path], op: opName, kind:
|
|
3770
|
+
warnings.push({ path, value: op[path], op: opName, kind: "dropped" });
|
|
3533
3771
|
continue;
|
|
3534
3772
|
}
|
|
3535
3773
|
// Walk all but the innermost bracket — descend through nestedArrayOps.
|
|
@@ -3538,7 +3776,7 @@ export class Mongo extends Db {
|
|
|
3538
3776
|
for (let bi = 0; bi < brackets.length - 1; bi++) {
|
|
3539
3777
|
const bracket = brackets[bi];
|
|
3540
3778
|
const fieldStart = bi === 0 ? 0 : brackets[bi - 1].idx + 1;
|
|
3541
|
-
const fieldName = tokens.slice(fieldStart, bracket.idx).join(
|
|
3779
|
+
const fieldName = tokens.slice(fieldStart, bracket.idx).join(".");
|
|
3542
3780
|
if (!fieldName) {
|
|
3543
3781
|
ok = false;
|
|
3544
3782
|
break;
|
|
@@ -3548,21 +3786,21 @@ export class Mongo extends Db {
|
|
|
3548
3786
|
currentMap = elOps.nestedArrayOps;
|
|
3549
3787
|
}
|
|
3550
3788
|
if (!ok) {
|
|
3551
|
-
warnings.push({ path, value: op[path], op: opName, kind:
|
|
3789
|
+
warnings.push({ path, value: op[path], op: opName, kind: "dropped" });
|
|
3552
3790
|
continue;
|
|
3553
3791
|
}
|
|
3554
3792
|
const lastB = brackets[brackets.length - 1];
|
|
3555
3793
|
const fieldStart = brackets[brackets.length - 2].idx + 1;
|
|
3556
|
-
const innerFieldName = tokens.slice(fieldStart, lastB.idx).join(
|
|
3557
|
-
const restPath = tokens.slice(lastB.idx + 1).join(
|
|
3794
|
+
const innerFieldName = tokens.slice(fieldStart, lastB.idx).join(".");
|
|
3795
|
+
const restPath = tokens.slice(lastB.idx + 1).join(".");
|
|
3558
3796
|
if (!innerFieldName) {
|
|
3559
|
-
warnings.push({ path, value: op[path], op: opName, kind:
|
|
3797
|
+
warnings.push({ path, value: op[path], op: opName, kind: "dropped" });
|
|
3560
3798
|
continue;
|
|
3561
3799
|
}
|
|
3562
3800
|
const innerFOps = ensureField(currentMap, innerFieldName);
|
|
3563
3801
|
const value = op[path];
|
|
3564
|
-
if (opName ===
|
|
3565
|
-
if (restPath ===
|
|
3802
|
+
if (opName === "$set") {
|
|
3803
|
+
if (restPath === "") {
|
|
3566
3804
|
if (Array.isArray(value)) {
|
|
3567
3805
|
innerFOps.removeIds.push(lastB.id);
|
|
3568
3806
|
innerFOps.insertElements.push(...value);
|
|
@@ -3580,7 +3818,7 @@ export class Mongo extends Db {
|
|
|
3580
3818
|
}
|
|
3581
3819
|
else {
|
|
3582
3820
|
// $unset
|
|
3583
|
-
if (restPath ===
|
|
3821
|
+
if (restPath === "") {
|
|
3584
3822
|
innerFOps.removeIds.push(lastB.id);
|
|
3585
3823
|
}
|
|
3586
3824
|
else {
|
|
@@ -3593,8 +3831,8 @@ export class Mongo extends Db {
|
|
|
3593
3831
|
if (Object.keys(remaining).length === 0)
|
|
3594
3832
|
delete update[opName];
|
|
3595
3833
|
};
|
|
3596
|
-
processNestedPaths(update.$set,
|
|
3597
|
-
processNestedPaths(update.$unset,
|
|
3834
|
+
processNestedPaths(update.$set, "$set");
|
|
3835
|
+
processNestedPaths(update.$unset, "$unset");
|
|
3598
3836
|
// Detect same-id overlap at NESTED levels: a "new base" (terminal-array
|
|
3599
3837
|
// insert OR whole-element replace) targeting the same `_id` as a
|
|
3600
3838
|
// sub-field set/unset on the same element. Both apply — new base goes
|
|
@@ -3614,7 +3852,7 @@ export class Mongo extends Db {
|
|
|
3614
3852
|
const detectOverlaps = (container, parentPathRepr) => {
|
|
3615
3853
|
for (const [field, fops] of container.entries()) {
|
|
3616
3854
|
const fullField = parentPathRepr ? `${parentPathRepr}.${field}` : field;
|
|
3617
|
-
const isNested = parentPathRepr !==
|
|
3855
|
+
const isNested = parentPathRepr !== "";
|
|
3618
3856
|
// Flavor 1: terminal-array insert + sub-field on same id.
|
|
3619
3857
|
const removeIdSet = new Set(fops.removeIds);
|
|
3620
3858
|
if (isNested) {
|
|
@@ -3622,16 +3860,16 @@ export class Mongo extends Db {
|
|
|
3622
3860
|
const elOps = fops.elementOps.get(id);
|
|
3623
3861
|
if (!elOps)
|
|
3624
3862
|
continue;
|
|
3625
|
-
const hasOps = Object.keys(elOps.sets).length > 0
|
|
3626
|
-
|
|
3627
|
-
|
|
3863
|
+
const hasOps = Object.keys(elOps.sets).length > 0 ||
|
|
3864
|
+
elOps.unsets.length > 0 ||
|
|
3865
|
+
elOps.replace !== undefined;
|
|
3628
3866
|
if (hasOps) {
|
|
3629
3867
|
const insertedEl = fops.insertElements.find((e) => String(e._id) === id);
|
|
3630
3868
|
warnings.push({
|
|
3631
3869
|
path: `${fullField}[${id}]`,
|
|
3632
3870
|
value: insertedEl,
|
|
3633
|
-
op:
|
|
3634
|
-
kind:
|
|
3871
|
+
op: "$set",
|
|
3872
|
+
kind: "overlap",
|
|
3635
3873
|
});
|
|
3636
3874
|
}
|
|
3637
3875
|
}
|
|
@@ -3641,14 +3879,13 @@ export class Mongo extends Db {
|
|
|
3641
3879
|
for (const [id, elOps] of fops.elementOps.entries()) {
|
|
3642
3880
|
if (isNested && !removeIdSet.has(id)) {
|
|
3643
3881
|
const hasReplace = elOps.replace !== undefined;
|
|
3644
|
-
const hasSetsOrUnsets = Object.keys(elOps.sets).length > 0
|
|
3645
|
-
|| elOps.unsets.length > 0;
|
|
3882
|
+
const hasSetsOrUnsets = Object.keys(elOps.sets).length > 0 || elOps.unsets.length > 0;
|
|
3646
3883
|
if (hasReplace && hasSetsOrUnsets) {
|
|
3647
3884
|
warnings.push({
|
|
3648
3885
|
path: `${fullField}[${id}]`,
|
|
3649
3886
|
value: elOps.replace,
|
|
3650
|
-
op:
|
|
3651
|
-
kind:
|
|
3887
|
+
op: "$set",
|
|
3888
|
+
kind: "overlap",
|
|
3652
3889
|
});
|
|
3653
3890
|
}
|
|
3654
3891
|
}
|
|
@@ -3658,19 +3895,21 @@ export class Mongo extends Db {
|
|
|
3658
3895
|
}
|
|
3659
3896
|
}
|
|
3660
3897
|
};
|
|
3661
|
-
detectOverlaps(fieldOps,
|
|
3898
|
+
detectOverlaps(fieldOps, "");
|
|
3662
3899
|
if (warnings.length > 0) {
|
|
3663
|
-
fjLog.warn(
|
|
3900
|
+
fjLog.warn("cry-db: bracket-by-_id warnings:", warnings);
|
|
3664
3901
|
}
|
|
3665
3902
|
if (update.$set && Object.keys(update.$set).length === 0)
|
|
3666
3903
|
delete update.$set;
|
|
3667
|
-
if (update.$unset &&
|
|
3904
|
+
if (update.$unset &&
|
|
3905
|
+
Object.keys(update.$unset).length === 0)
|
|
3668
3906
|
delete update.$unset;
|
|
3669
3907
|
const pipeline = [];
|
|
3670
3908
|
if (update.$set && Object.keys(update.$set).length > 0) {
|
|
3671
3909
|
pipeline.push({ $set: update.$set });
|
|
3672
3910
|
}
|
|
3673
|
-
if (update.$unset &&
|
|
3911
|
+
if (update.$unset &&
|
|
3912
|
+
Object.keys(update.$unset).length > 0) {
|
|
3674
3913
|
pipeline.push({ $unset: Object.keys(update.$unset) });
|
|
3675
3914
|
}
|
|
3676
3915
|
// Translate $inc / $currentDate so revisions (_rev/_ts) still update in pipeline form.
|
|
@@ -3686,7 +3925,9 @@ export class Mongo extends Db {
|
|
|
3686
3925
|
},
|
|
3687
3926
|
});
|
|
3688
3927
|
}
|
|
3689
|
-
return warnings.length > 0
|
|
3928
|
+
return warnings.length > 0
|
|
3929
|
+
? { update: pipeline, warnings }
|
|
3930
|
+
: { update: pipeline };
|
|
3690
3931
|
}
|
|
3691
3932
|
async _processHashedKeys(obj) {
|
|
3692
3933
|
const hashedKeys = Object.keys(obj).filter(startsWithHashedPrefix);
|
|
@@ -3742,10 +3983,10 @@ export class Mongo extends Db {
|
|
|
3742
3983
|
}
|
|
3743
3984
|
else {
|
|
3744
3985
|
for (let key in ret) {
|
|
3745
|
-
if (startsWithHashedPrefix(key)
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3986
|
+
if (startsWithHashedPrefix(key) ||
|
|
3987
|
+
startsWithDoubleUnderscore(key) ||
|
|
3988
|
+
removeFields.has(key) ||
|
|
3989
|
+
this._removeFieldsAlways.has(key))
|
|
3749
3990
|
delete ret[key];
|
|
3750
3991
|
}
|
|
3751
3992
|
}
|