cry-db 2.5.1 → 2.5.3
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 +697 -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,18 +976,19 @@ 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
|
|
919
988
|
if (updates === null || updates === void 0 ? void 0 : updates.length) {
|
|
920
989
|
const updatePromises = updates.map(async ({ _id, _rev: clientRev, update }, idx) => {
|
|
921
990
|
var _a, _b;
|
|
991
|
+
let processedUpdateForDiagnostics;
|
|
922
992
|
try {
|
|
923
993
|
const objectId = Mongo._toId(_id);
|
|
924
994
|
update = this.replaceIds(update);
|
|
@@ -927,9 +997,10 @@ export class Mongo extends Db {
|
|
|
927
997
|
const inputUpdate = cloneDeep(update);
|
|
928
998
|
const processedBase = this._processUpdateObject({ ...update });
|
|
929
999
|
const processed = this._applyBracketProcessing(processedBase);
|
|
1000
|
+
processedUpdateForDiagnostics = processed.update;
|
|
930
1001
|
if (processed.warnings) {
|
|
931
1002
|
for (const w of processed.warnings) {
|
|
932
|
-
const msg = w.kind ===
|
|
1003
|
+
const msg = w.kind === "overlap"
|
|
933
1004
|
? `same-id overlap at "${w.path}" — terminal-bracket value ${JSON.stringify(w.value)} sets the element base; sub-field overlay applies on top`
|
|
934
1005
|
: `dropped nested-bracket ${w.op} path "${w.path}" = ${JSON.stringify(w.value)}`;
|
|
935
1006
|
warnings.push({ _id: String(_id), error: msg });
|
|
@@ -939,10 +1010,13 @@ export class Mongo extends Db {
|
|
|
939
1010
|
upsert: true,
|
|
940
1011
|
returnDocument: "after",
|
|
941
1012
|
includeResultMetadata: true,
|
|
942
|
-
...(processed.arrayFilters
|
|
943
|
-
|
|
1013
|
+
...(processed.arrayFilters
|
|
1014
|
+
? { arrayFilters: processed.arrayFilters }
|
|
1015
|
+
: {}),
|
|
1016
|
+
...this._sessionOpt(),
|
|
944
1017
|
};
|
|
945
1018
|
(_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);
|
|
1019
|
+
console.error(`[Mongo] updateCollections findOneAndUpdate payload collection=${collection} _id=${String(_id)} clientRev=${clientRev}`, { collection, _id: String(_id), clientRev, filter: { _id: objectId }, update: processed.update });
|
|
946
1020
|
const res = await conn
|
|
947
1021
|
.db(dbName)
|
|
948
1022
|
.collection(collection)
|
|
@@ -950,7 +1024,9 @@ export class Mongo extends Db {
|
|
|
950
1024
|
if (res === null || res === void 0 ? void 0 : res.value) {
|
|
951
1025
|
// Determine if this was an insert or update based on lastErrorObject
|
|
952
1026
|
const wasInsert = !((_b = res.lastErrorObject) === null || _b === void 0 ? void 0 : _b.updatedExisting);
|
|
953
|
-
const operation = wasInsert
|
|
1027
|
+
const operation = wasInsert
|
|
1028
|
+
? "insert"
|
|
1029
|
+
: "update";
|
|
954
1030
|
// mustRefresh: the client must replace its local copy when cry-db
|
|
955
1031
|
// resolved a field (SEQ_*/__hashed__) OR a concurrent external write
|
|
956
1032
|
// advanced the stored `_rev` beyond this write (gap > the +1 this write
|
|
@@ -958,10 +1034,10 @@ export class Mongo extends Db {
|
|
|
958
1034
|
// old-client fallback. Built from a sanitized clone (hash never leaks).
|
|
959
1035
|
const writtenRev = res.value._rev;
|
|
960
1036
|
const inc = this.revisions ? 1 : 0;
|
|
961
|
-
const externallyModified = !wasInsert
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const mustRefresh =
|
|
1037
|
+
const externallyModified = !wasInsert &&
|
|
1038
|
+
typeof writtenRev === "number" &&
|
|
1039
|
+
writtenRev - inc > (clientRev !== null && clientRev !== void 0 ? clientRev : 0);
|
|
1040
|
+
const mustRefresh = updateResolvedFlags[idx] || externallyModified
|
|
965
1041
|
? this._buildMustRefreshRecord(res.value)
|
|
966
1042
|
: undefined;
|
|
967
1043
|
// Inserts surface the full doc (query fields must reach the client).
|
|
@@ -972,12 +1048,25 @@ export class Mongo extends Db {
|
|
|
972
1048
|
? res.value
|
|
973
1049
|
: this._buildPublishDelta(res.value, inputUpdate, false);
|
|
974
1050
|
this._processReturnedObject(retObj);
|
|
975
|
-
return {
|
|
1051
|
+
return {
|
|
1052
|
+
success: true,
|
|
1053
|
+
_id,
|
|
1054
|
+
data: retObj,
|
|
1055
|
+
operation,
|
|
1056
|
+
wasInsert,
|
|
1057
|
+
rawUpdate: wasInsert ? undefined : inputUpdate,
|
|
1058
|
+
mustRefresh,
|
|
1059
|
+
};
|
|
976
1060
|
}
|
|
977
|
-
return { success: false, _id, error:
|
|
1061
|
+
return { success: false, _id, error: "Operation failed" };
|
|
978
1062
|
}
|
|
979
1063
|
catch (err) {
|
|
980
|
-
|
|
1064
|
+
console.error(`[Mongo] updateCollections findOneAndUpdate error collection=${collection} _id=${String(_id)} reason=${err.message || String(err)}`, { collection, _id: String(_id), clientRev, err, update: processedUpdateForDiagnostics });
|
|
1065
|
+
return {
|
|
1066
|
+
success: false,
|
|
1067
|
+
_id,
|
|
1068
|
+
error: err.message || String(err),
|
|
1069
|
+
};
|
|
981
1070
|
}
|
|
982
1071
|
});
|
|
983
1072
|
const updateResults = await Promise.all(updatePromises);
|
|
@@ -1021,26 +1110,32 @@ export class Mongo extends Db {
|
|
|
1021
1110
|
// Hard delete applies no `_rev` increment: the removed doc's `_rev`
|
|
1022
1111
|
// exceeding the client's base means an external write landed first.
|
|
1023
1112
|
// Omitted `_rev` (clientRev → 0) forces a refresh — old-client fallback.
|
|
1024
|
-
const externallyModified = typeof res._rev ===
|
|
1025
|
-
|
|
1113
|
+
const externallyModified = typeof res._rev === "number" &&
|
|
1114
|
+
res._rev > (clientRev !== null && clientRev !== void 0 ? clientRev : 0);
|
|
1026
1115
|
const mustRefresh = externallyModified
|
|
1027
1116
|
? this._buildMustRefreshRecord(res)
|
|
1028
1117
|
: undefined;
|
|
1029
1118
|
this._processReturnedObject(res);
|
|
1030
|
-
return {
|
|
1119
|
+
return {
|
|
1120
|
+
success: true,
|
|
1121
|
+
_id,
|
|
1122
|
+
data: res,
|
|
1123
|
+
operation: "delete",
|
|
1124
|
+
mustRefresh,
|
|
1125
|
+
};
|
|
1031
1126
|
}
|
|
1032
|
-
return { success: false, _id, error:
|
|
1127
|
+
return { success: false, _id, error: "Document not found" };
|
|
1033
1128
|
}
|
|
1034
1129
|
else {
|
|
1035
1130
|
const del = {
|
|
1036
1131
|
$set: { _deleted: new Date() },
|
|
1037
1132
|
$inc: { _rev: 1 },
|
|
1038
|
-
$currentDate: { _ts: { $type:
|
|
1133
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1039
1134
|
};
|
|
1040
1135
|
const opts = {
|
|
1041
1136
|
upsert: false,
|
|
1042
1137
|
returnDocument: "after",
|
|
1043
|
-
...this._sessionOpt()
|
|
1138
|
+
...this._sessionOpt(),
|
|
1044
1139
|
};
|
|
1045
1140
|
const res = await conn
|
|
1046
1141
|
.db(dbName)
|
|
@@ -1050,19 +1145,29 @@ export class Mongo extends Db {
|
|
|
1050
1145
|
// Soft delete applied a +1 `_rev` increment: a gap beyond that +1
|
|
1051
1146
|
// means an external write modified the record before this delete.
|
|
1052
1147
|
// Omitted `_rev` (clientRev → 0) forces a refresh — old-client fallback.
|
|
1053
|
-
const externallyModified = typeof res._rev ===
|
|
1054
|
-
|
|
1148
|
+
const externallyModified = typeof res._rev === "number" &&
|
|
1149
|
+
res._rev - 1 > (clientRev !== null && clientRev !== void 0 ? clientRev : 0);
|
|
1055
1150
|
const mustRefresh = externallyModified
|
|
1056
1151
|
? this._buildMustRefreshRecord(res)
|
|
1057
1152
|
: undefined;
|
|
1058
1153
|
this._processReturnedObject(res);
|
|
1059
|
-
return {
|
|
1154
|
+
return {
|
|
1155
|
+
success: true,
|
|
1156
|
+
_id,
|
|
1157
|
+
data: res,
|
|
1158
|
+
operation: "delete",
|
|
1159
|
+
mustRefresh,
|
|
1160
|
+
};
|
|
1060
1161
|
}
|
|
1061
|
-
return { success: false, _id, error:
|
|
1162
|
+
return { success: false, _id, error: "Document not found" };
|
|
1062
1163
|
}
|
|
1063
1164
|
}
|
|
1064
1165
|
catch (err) {
|
|
1065
|
-
return {
|
|
1166
|
+
return {
|
|
1167
|
+
success: false,
|
|
1168
|
+
_id,
|
|
1169
|
+
error: err.message || String(err),
|
|
1170
|
+
};
|
|
1066
1171
|
}
|
|
1067
1172
|
});
|
|
1068
1173
|
const deleteResults = await Promise.all(deletePromises);
|
|
@@ -1099,8 +1204,8 @@ export class Mongo extends Db {
|
|
|
1099
1204
|
operation: "batch",
|
|
1100
1205
|
db: dbName,
|
|
1101
1206
|
collection,
|
|
1102
|
-
data: batchData
|
|
1103
|
-
}
|
|
1207
|
+
data: batchData,
|
|
1208
|
+
},
|
|
1104
1209
|
});
|
|
1105
1210
|
}
|
|
1106
1211
|
if (this.emittingPublishRevEvents) {
|
|
@@ -1108,11 +1213,11 @@ export class Mongo extends Db {
|
|
|
1108
1213
|
operation: "batch",
|
|
1109
1214
|
db: dbName,
|
|
1110
1215
|
collection,
|
|
1111
|
-
data: batchData.map(item => ({
|
|
1216
|
+
data: batchData.map((item) => ({
|
|
1112
1217
|
operation: item.operation,
|
|
1113
1218
|
_id: item.data._id,
|
|
1114
1219
|
_ts: item.data._ts,
|
|
1115
|
-
}))
|
|
1220
|
+
})),
|
|
1116
1221
|
};
|
|
1117
1222
|
this.emit("publishRev", {
|
|
1118
1223
|
channel: `dbrev/${dbName}/${collection}`,
|
|
@@ -1128,22 +1233,24 @@ export class Mongo extends Db {
|
|
|
1128
1233
|
results.push(result);
|
|
1129
1234
|
}
|
|
1130
1235
|
}
|
|
1131
|
-
(_c = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _c === void 0 ? void 0 : _c.call(fjLog,
|
|
1236
|
+
(_c = fjLog === null || fjLog === void 0 ? void 0 : fjLog[debugMethod]) === null || _c === void 0 ? void 0 : _c.call(fjLog, "updateCollections returns", results);
|
|
1132
1237
|
return results;
|
|
1133
1238
|
}
|
|
1134
1239
|
async upsertBatch(collection, batch) {
|
|
1135
1240
|
assert(collection, "collection is required", "upsertBatch");
|
|
1136
1241
|
assert(batch, "batch is required", "upsertBatch", { collection });
|
|
1137
|
-
assert(batch instanceof Array, "batch must be an Array", "upsertBatch", {
|
|
1138
|
-
|
|
1242
|
+
assert(batch instanceof Array, "batch must be an Array", "upsertBatch", {
|
|
1243
|
+
collection,
|
|
1244
|
+
});
|
|
1245
|
+
fjLog.debug("upsertBatch called", collection, batch);
|
|
1139
1246
|
const dbName = this.db; // Capture db at operation start
|
|
1140
1247
|
batch = this.replaceIds(batch);
|
|
1141
|
-
await Promise.all(batch.map(item => this._processHashedKeys(item === null || item === void 0 ? void 0 : item.update)));
|
|
1248
|
+
await Promise.all(batch.map((item) => this._processHashedKeys(item === null || item === void 0 ? void 0 : item.update)));
|
|
1142
1249
|
let ret = await this.executeTransactionally(collection, async (conn, client) => {
|
|
1143
1250
|
// Only process sequences if any batch item has SEQ_NEXT or SEQ_LAST
|
|
1144
|
-
const updatesWithSeq = batch.filter(b => this._hasSequenceFields(b.update));
|
|
1251
|
+
const updatesWithSeq = batch.filter((b) => this._hasSequenceFields(b.update));
|
|
1145
1252
|
if (updatesWithSeq.length > 0) {
|
|
1146
|
-
await this._processSequenceFieldForMany(client, dbName, collection, updatesWithSeq.map(b => b.update));
|
|
1253
|
+
await this._processSequenceFieldForMany(client, dbName, collection, updatesWithSeq.map((b) => b.update));
|
|
1147
1254
|
}
|
|
1148
1255
|
// Process all updates in parallel
|
|
1149
1256
|
const upsertPromises = batch.map(async (part) => {
|
|
@@ -1154,7 +1261,7 @@ export class Mongo extends Db {
|
|
|
1154
1261
|
upsert: true,
|
|
1155
1262
|
returnDocument: "after",
|
|
1156
1263
|
includeResultMetadata: true,
|
|
1157
|
-
...this._sessionOpt()
|
|
1264
|
+
...this._sessionOpt(),
|
|
1158
1265
|
};
|
|
1159
1266
|
if (this._hasHashedKeys(update))
|
|
1160
1267
|
await this._processHashedKeys(update);
|
|
@@ -1182,7 +1289,11 @@ export class Mongo extends Db {
|
|
|
1182
1289
|
? ret
|
|
1183
1290
|
: this._buildPublishDelta(ret, inputUpdate, !!(opts === null || opts === void 0 ? void 0 : opts.returnFullObject));
|
|
1184
1291
|
this._processReturnedObject(retObj);
|
|
1185
|
-
return {
|
|
1292
|
+
return {
|
|
1293
|
+
operation: oper,
|
|
1294
|
+
data: retObj,
|
|
1295
|
+
rawUpdate: oper === "update" ? inputUpdate : undefined,
|
|
1296
|
+
};
|
|
1186
1297
|
});
|
|
1187
1298
|
const results = await Promise.all(upsertPromises);
|
|
1188
1299
|
// Filter out nulls and separate batchData and changes
|
|
@@ -1205,8 +1316,8 @@ export class Mongo extends Db {
|
|
|
1205
1316
|
operation: "batch",
|
|
1206
1317
|
db: dbName,
|
|
1207
1318
|
collection,
|
|
1208
|
-
data: batchData
|
|
1209
|
-
}
|
|
1319
|
+
data: batchData,
|
|
1320
|
+
},
|
|
1210
1321
|
});
|
|
1211
1322
|
}
|
|
1212
1323
|
if (this.emittingPublishRevEvents) {
|
|
@@ -1214,11 +1325,11 @@ export class Mongo extends Db {
|
|
|
1214
1325
|
operation: "batch",
|
|
1215
1326
|
db: dbName,
|
|
1216
1327
|
collection,
|
|
1217
|
-
data: batchData.map(item => ({
|
|
1328
|
+
data: batchData.map((item) => ({
|
|
1218
1329
|
operation: item.operation,
|
|
1219
1330
|
_id: item.data._id,
|
|
1220
1331
|
_ts: item.data._ts,
|
|
1221
|
-
}))
|
|
1332
|
+
})),
|
|
1222
1333
|
};
|
|
1223
1334
|
this.emit("publishRev", {
|
|
1224
1335
|
channel: `dbrev/${dbName}/${collection}`,
|
|
@@ -1227,23 +1338,28 @@ export class Mongo extends Db {
|
|
|
1227
1338
|
}
|
|
1228
1339
|
return changes;
|
|
1229
1340
|
}, false, { operation: "upsertBatch", collection, batch });
|
|
1230
|
-
fjLog.debug(
|
|
1341
|
+
fjLog.debug("upsertBatch returns", ret);
|
|
1231
1342
|
return ret;
|
|
1232
1343
|
}
|
|
1233
1344
|
async insertMany(collection, insert) {
|
|
1234
1345
|
assert(collection, "collection is required", "insertMany");
|
|
1235
1346
|
assert(insert, "insert is required", "insertMany", { collection });
|
|
1236
|
-
assert(insert instanceof Array, "insert must be an Array", "insertMany", {
|
|
1237
|
-
|
|
1347
|
+
assert(insert instanceof Array, "insert must be an Array", "insertMany", {
|
|
1348
|
+
collection,
|
|
1349
|
+
});
|
|
1350
|
+
fjLog.debug("insertMany called", collection, insert);
|
|
1238
1351
|
const dbName = this.db; // Capture db at operation start
|
|
1239
1352
|
insert = this.replaceIds(insert);
|
|
1240
|
-
insert.forEach(item => this._stripDoubleUnderscoreKeys(item));
|
|
1241
|
-
await Promise.all(insert.map(item => this._processHashedKeys(item)));
|
|
1353
|
+
insert.forEach((item) => this._stripDoubleUnderscoreKeys(item));
|
|
1354
|
+
await Promise.all(insert.map((item) => this._processHashedKeys(item)));
|
|
1242
1355
|
if (this.revisions)
|
|
1243
|
-
insert.forEach(ins => {
|
|
1356
|
+
insert.forEach((ins) => {
|
|
1357
|
+
ins._rev = 1;
|
|
1358
|
+
ins._ts = Base.timestamp();
|
|
1359
|
+
});
|
|
1244
1360
|
let ret = await this.executeTransactionally(collection, async (conn, client) => {
|
|
1245
1361
|
// Only process sequences if any insert has SEQ_NEXT or SEQ_LAST
|
|
1246
|
-
const insertsWithSeq = insert.filter(item => this._hasSequenceFields(item));
|
|
1362
|
+
const insertsWithSeq = insert.filter((item) => this._hasSequenceFields(item));
|
|
1247
1363
|
if (insertsWithSeq.length > 0) {
|
|
1248
1364
|
await this._processSequenceFieldForMany(client, dbName, collection, insert);
|
|
1249
1365
|
}
|
|
@@ -1253,16 +1369,20 @@ export class Mongo extends Db {
|
|
|
1253
1369
|
let n = Number(ns);
|
|
1254
1370
|
let rec = {
|
|
1255
1371
|
_id: obj.insertedIds[n],
|
|
1256
|
-
...insert[n]
|
|
1372
|
+
...insert[n],
|
|
1257
1373
|
};
|
|
1258
1374
|
ret.push(rec);
|
|
1259
1375
|
}
|
|
1260
|
-
if (this.emittingPublishEvents ||
|
|
1261
|
-
|
|
1376
|
+
if (this.emittingPublishEvents ||
|
|
1377
|
+
this.emittingPublishRevEvents ||
|
|
1378
|
+
this.auditing) {
|
|
1379
|
+
await Promise.all(ret
|
|
1380
|
+
.filter((rec) => rec)
|
|
1381
|
+
.map((rec) => this._publishAndAudit("insert", dbName, collection, rec)));
|
|
1262
1382
|
}
|
|
1263
1383
|
return ret;
|
|
1264
1384
|
}, false, { operation: "insertMany", collection, insert });
|
|
1265
|
-
fjLog.debug(
|
|
1385
|
+
fjLog.debug("insertMany returns", ret);
|
|
1266
1386
|
return ret;
|
|
1267
1387
|
}
|
|
1268
1388
|
async deleteOne(collection, query) {
|
|
@@ -1272,36 +1392,46 @@ export class Mongo extends Db {
|
|
|
1272
1392
|
query = this.replaceIds(query);
|
|
1273
1393
|
if (!this.softdelete) {
|
|
1274
1394
|
const opts = this._sessionOpt();
|
|
1275
|
-
fjLog.debug(
|
|
1395
|
+
fjLog.debug("deleteOne called", this.softdelete, collection, query);
|
|
1276
1396
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1277
1397
|
let obj = await conn.findOneAndDelete(query, opts);
|
|
1278
1398
|
if (obj)
|
|
1279
|
-
await this._publishAndAudit(
|
|
1399
|
+
await this._publishAndAudit("delete", dbName, collection, obj);
|
|
1280
1400
|
return obj;
|
|
1281
|
-
}, false, {
|
|
1282
|
-
|
|
1401
|
+
}, false, {
|
|
1402
|
+
operation: "deleteOne",
|
|
1403
|
+
collection,
|
|
1404
|
+
query,
|
|
1405
|
+
softdelete: this.softdelete,
|
|
1406
|
+
});
|
|
1407
|
+
fjLog.debug("deleteOne returns", ret);
|
|
1283
1408
|
return ret;
|
|
1284
1409
|
}
|
|
1285
1410
|
else {
|
|
1286
1411
|
let opts = {
|
|
1287
1412
|
upsert: false,
|
|
1288
1413
|
returnDocument: "after",
|
|
1289
|
-
...this._sessionOpt()
|
|
1414
|
+
...this._sessionOpt(),
|
|
1290
1415
|
};
|
|
1291
|
-
fjLog.debug(
|
|
1416
|
+
fjLog.debug("deleteOne called", collection, query);
|
|
1292
1417
|
let ret = await this.executeTransactionally(collection, async (conn /*, client*/) => {
|
|
1293
1418
|
let del = {
|
|
1294
1419
|
$set: { _deleted: new Date() },
|
|
1295
|
-
$inc: { _rev: 1
|
|
1296
|
-
$currentDate: { _ts: { $type:
|
|
1420
|
+
$inc: { _rev: 1 },
|
|
1421
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1297
1422
|
};
|
|
1298
1423
|
// if (this.syncSupport) del.$set._csq = await this._getNextCollectionUpdateSeqNo(collection, client)
|
|
1299
1424
|
let obj = await conn.findOneAndUpdate(query, del, opts);
|
|
1300
1425
|
if (obj)
|
|
1301
|
-
await this._publishAndAudit(
|
|
1426
|
+
await this._publishAndAudit("delete", dbName, collection, obj);
|
|
1302
1427
|
return obj;
|
|
1303
|
-
}, false, {
|
|
1304
|
-
|
|
1428
|
+
}, false, {
|
|
1429
|
+
operation: "deleteOne",
|
|
1430
|
+
collection,
|
|
1431
|
+
query,
|
|
1432
|
+
softdelete: this.softdelete,
|
|
1433
|
+
});
|
|
1434
|
+
fjLog.debug("deleteOne returns", ret);
|
|
1305
1435
|
return ret;
|
|
1306
1436
|
}
|
|
1307
1437
|
}
|
|
@@ -1309,56 +1439,56 @@ export class Mongo extends Db {
|
|
|
1309
1439
|
let opts = {
|
|
1310
1440
|
upsert: false,
|
|
1311
1441
|
returnDocument: "after",
|
|
1312
|
-
...this._sessionOpt()
|
|
1442
|
+
...this._sessionOpt(),
|
|
1313
1443
|
};
|
|
1314
1444
|
const dbName = this.db; // Capture db at operation start
|
|
1315
1445
|
query = this.replaceIds(query);
|
|
1316
|
-
fjLog.debug(
|
|
1446
|
+
fjLog.debug("blockOne called", collection, query);
|
|
1317
1447
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1318
1448
|
query._blocked = { $exists: 0 };
|
|
1319
1449
|
let update = {
|
|
1320
|
-
$set: { _blocked: new Date()
|
|
1321
|
-
$inc: { _rev: 1
|
|
1322
|
-
$currentDate: { _ts: { $type:
|
|
1450
|
+
$set: { _blocked: new Date() },
|
|
1451
|
+
$inc: { _rev: 1 },
|
|
1452
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1323
1453
|
};
|
|
1324
1454
|
// if (this.syncSupport) update.$set._csq = await this._getNextCollectionUpdateSeqNo(collection, client)
|
|
1325
1455
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1326
1456
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1327
1457
|
return {
|
|
1328
|
-
ok: false
|
|
1458
|
+
ok: false,
|
|
1329
1459
|
};
|
|
1330
|
-
await this._publishAndAudit(
|
|
1460
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1331
1461
|
return obj;
|
|
1332
1462
|
}, false, { operation: "blockOne", collection, query });
|
|
1333
|
-
fjLog.debug(
|
|
1463
|
+
fjLog.debug("blockOne returns", ret);
|
|
1334
1464
|
return ret;
|
|
1335
1465
|
}
|
|
1336
1466
|
async unblockOne(collection, query) {
|
|
1337
1467
|
let opts = {
|
|
1338
1468
|
upsert: false,
|
|
1339
1469
|
returnDocument: "after",
|
|
1340
|
-
...this._sessionOpt()
|
|
1470
|
+
...this._sessionOpt(),
|
|
1341
1471
|
};
|
|
1342
1472
|
const dbName = this.db; // Capture db at operation start
|
|
1343
1473
|
query = this.replaceIds(query);
|
|
1344
|
-
fjLog.debug(
|
|
1474
|
+
fjLog.debug("unblockOne called", collection, query);
|
|
1345
1475
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1346
1476
|
query._blocked = { $exists: 1 };
|
|
1347
1477
|
let update = {
|
|
1348
1478
|
$unset: { _blocked: 1 },
|
|
1349
|
-
$inc: { _rev: 1
|
|
1350
|
-
$currentDate: { _ts: { $type:
|
|
1479
|
+
$inc: { _rev: 1 },
|
|
1480
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1351
1481
|
};
|
|
1352
1482
|
// if (this.syncSupport) update.$set = { _csq: await this._getNextCollectionUpdateSeqNo(collection, client) };
|
|
1353
1483
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1354
1484
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1355
1485
|
return {
|
|
1356
|
-
ok: false
|
|
1486
|
+
ok: false,
|
|
1357
1487
|
};
|
|
1358
|
-
await this._publishAndAudit(
|
|
1488
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1359
1489
|
return obj;
|
|
1360
1490
|
}, false, { operation: "unblockOne", collection, query });
|
|
1361
|
-
fjLog.debug(
|
|
1491
|
+
fjLog.debug("unblockOne returns", ret);
|
|
1362
1492
|
return ret;
|
|
1363
1493
|
}
|
|
1364
1494
|
/**
|
|
@@ -1369,27 +1499,27 @@ export class Mongo extends Db {
|
|
|
1369
1499
|
let opts = {
|
|
1370
1500
|
upsert: false,
|
|
1371
1501
|
returnDocument: "after",
|
|
1372
|
-
...this._sessionOpt()
|
|
1502
|
+
...this._sessionOpt(),
|
|
1373
1503
|
};
|
|
1374
1504
|
const dbName = this.db;
|
|
1375
1505
|
query = this.replaceIds(query);
|
|
1376
|
-
fjLog.debug(
|
|
1506
|
+
fjLog.debug("archiveOne called", collection, query);
|
|
1377
1507
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1378
1508
|
query._archived = { $exists: false };
|
|
1379
1509
|
let update = {
|
|
1380
|
-
$set: { _archived: new Date()
|
|
1381
|
-
$inc: { _rev: 1
|
|
1382
|
-
$currentDate: { _ts: { $type:
|
|
1510
|
+
$set: { _archived: new Date() },
|
|
1511
|
+
$inc: { _rev: 1 },
|
|
1512
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1383
1513
|
};
|
|
1384
1514
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1385
1515
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1386
1516
|
return {
|
|
1387
|
-
ok: false
|
|
1517
|
+
ok: false,
|
|
1388
1518
|
};
|
|
1389
|
-
await this._publishAndAudit(
|
|
1519
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1390
1520
|
return obj;
|
|
1391
1521
|
}, false, { operation: "archiveOne", collection, query });
|
|
1392
|
-
fjLog.debug(
|
|
1522
|
+
fjLog.debug("archiveOne returns", ret);
|
|
1393
1523
|
return ret;
|
|
1394
1524
|
}
|
|
1395
1525
|
/**
|
|
@@ -1400,27 +1530,27 @@ export class Mongo extends Db {
|
|
|
1400
1530
|
let opts = {
|
|
1401
1531
|
upsert: false,
|
|
1402
1532
|
returnDocument: "after",
|
|
1403
|
-
...this._sessionOpt()
|
|
1533
|
+
...this._sessionOpt(),
|
|
1404
1534
|
};
|
|
1405
1535
|
const dbName = this.db;
|
|
1406
1536
|
query = this.replaceIds(query);
|
|
1407
|
-
fjLog.debug(
|
|
1537
|
+
fjLog.debug("unarchiveOne called", collection, query);
|
|
1408
1538
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1409
1539
|
query._archived = { $exists: true };
|
|
1410
1540
|
let update = {
|
|
1411
1541
|
$unset: { _archived: 1 },
|
|
1412
|
-
$inc: { _rev: 1
|
|
1413
|
-
$currentDate: { _ts: { $type:
|
|
1542
|
+
$inc: { _rev: 1 },
|
|
1543
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1414
1544
|
};
|
|
1415
1545
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1416
1546
|
if (!obj || !(obj === null || obj === void 0 ? void 0 : obj._id))
|
|
1417
1547
|
return {
|
|
1418
|
-
ok: false
|
|
1548
|
+
ok: false,
|
|
1419
1549
|
};
|
|
1420
|
-
await this._publishAndAudit(
|
|
1550
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1421
1551
|
return obj;
|
|
1422
1552
|
}, false, { operation: "unarchiveOne", collection, query });
|
|
1423
|
-
fjLog.debug(
|
|
1553
|
+
fjLog.debug("unarchiveOne returns", ret);
|
|
1424
1554
|
return ret;
|
|
1425
1555
|
}
|
|
1426
1556
|
/**
|
|
@@ -1429,7 +1559,9 @@ export class Mongo extends Db {
|
|
|
1429
1559
|
async archiveOneById(collection, id) {
|
|
1430
1560
|
assert(collection, "collection is required", "archiveOneById");
|
|
1431
1561
|
assert(id, "id is required", "archiveOneById", { collection });
|
|
1432
|
-
return await this.archiveOne(collection, {
|
|
1562
|
+
return await this.archiveOne(collection, {
|
|
1563
|
+
_id: Mongo._toId(id),
|
|
1564
|
+
});
|
|
1433
1565
|
}
|
|
1434
1566
|
/**
|
|
1435
1567
|
* Archives multiple documents found by their `_id`s.
|
|
@@ -1443,29 +1575,32 @@ export class Mongo extends Db {
|
|
|
1443
1575
|
const opts = {
|
|
1444
1576
|
upsert: false,
|
|
1445
1577
|
returnDocument: "after",
|
|
1446
|
-
...this._sessionOpt()
|
|
1578
|
+
...this._sessionOpt(),
|
|
1447
1579
|
};
|
|
1448
1580
|
const dbName = this.db;
|
|
1449
|
-
fjLog.debug(
|
|
1581
|
+
fjLog.debug("archiveManyByIds called", collection, ids);
|
|
1450
1582
|
let ret = [];
|
|
1451
1583
|
for (const id of ids) {
|
|
1452
1584
|
let r = await this.executeTransactionally(collection, async (conn) => {
|
|
1453
|
-
let query = {
|
|
1585
|
+
let query = {
|
|
1586
|
+
_id: Mongo._toId(id),
|
|
1587
|
+
_archived: { $exists: false },
|
|
1588
|
+
};
|
|
1454
1589
|
let update = {
|
|
1455
1590
|
$set: { _archived: new Date() },
|
|
1456
1591
|
$inc: { _rev: 1 },
|
|
1457
|
-
$currentDate: { _ts: { $type:
|
|
1592
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1458
1593
|
};
|
|
1459
1594
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1460
1595
|
if (obj === null || obj === void 0 ? void 0 : obj._id) {
|
|
1461
|
-
await this._publishAndAudit(
|
|
1596
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1462
1597
|
}
|
|
1463
1598
|
return obj;
|
|
1464
1599
|
}, false, { operation: "archiveManyByIds", collection, id });
|
|
1465
1600
|
if (r === null || r === void 0 ? void 0 : r._id)
|
|
1466
1601
|
ret.push(r);
|
|
1467
1602
|
}
|
|
1468
|
-
fjLog.debug(
|
|
1603
|
+
fjLog.debug("archiveManyByIds returns", ret);
|
|
1469
1604
|
return ret;
|
|
1470
1605
|
}
|
|
1471
1606
|
/**
|
|
@@ -1474,7 +1609,9 @@ export class Mongo extends Db {
|
|
|
1474
1609
|
async unarchiveOneById(collection, id) {
|
|
1475
1610
|
assert(collection, "collection is required", "unarchiveOneById");
|
|
1476
1611
|
assert(id, "id is required", "unarchiveOneById", { collection });
|
|
1477
|
-
return await this.unarchiveOne(collection, {
|
|
1612
|
+
return await this.unarchiveOne(collection, {
|
|
1613
|
+
_id: Mongo._toId(id),
|
|
1614
|
+
});
|
|
1478
1615
|
}
|
|
1479
1616
|
/**
|
|
1480
1617
|
* Unarchives multiple documents found by their `_id`s.
|
|
@@ -1488,52 +1625,57 @@ export class Mongo extends Db {
|
|
|
1488
1625
|
const opts = {
|
|
1489
1626
|
upsert: false,
|
|
1490
1627
|
returnDocument: "after",
|
|
1491
|
-
...this._sessionOpt()
|
|
1628
|
+
...this._sessionOpt(),
|
|
1492
1629
|
};
|
|
1493
1630
|
const dbName = this.db;
|
|
1494
|
-
fjLog.debug(
|
|
1631
|
+
fjLog.debug("unarchiveManyByIds called", collection, ids);
|
|
1495
1632
|
let ret = [];
|
|
1496
1633
|
for (const id of ids) {
|
|
1497
1634
|
let r = await this.executeTransactionally(collection, async (conn) => {
|
|
1498
|
-
let query = {
|
|
1635
|
+
let query = {
|
|
1636
|
+
_id: Mongo._toId(id),
|
|
1637
|
+
_archived: { $exists: true },
|
|
1638
|
+
};
|
|
1499
1639
|
let update = {
|
|
1500
1640
|
$unset: { _archived: 1 },
|
|
1501
1641
|
$inc: { _rev: 1 },
|
|
1502
|
-
$currentDate: { _ts: { $type:
|
|
1642
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1503
1643
|
};
|
|
1504
1644
|
let obj = await conn.findOneAndUpdate(query, update, opts);
|
|
1505
1645
|
if (obj === null || obj === void 0 ? void 0 : obj._id) {
|
|
1506
|
-
await this._publishAndAudit(
|
|
1646
|
+
await this._publishAndAudit("update", dbName, collection, obj);
|
|
1507
1647
|
}
|
|
1508
1648
|
return obj;
|
|
1509
1649
|
}, false, { operation: "unarchiveManyByIds", collection, id });
|
|
1510
1650
|
if (r === null || r === void 0 ? void 0 : r._id)
|
|
1511
1651
|
ret.push(r);
|
|
1512
1652
|
}
|
|
1513
|
-
fjLog.debug(
|
|
1653
|
+
fjLog.debug("unarchiveManyByIds returns", ret);
|
|
1514
1654
|
return ret;
|
|
1515
1655
|
}
|
|
1516
1656
|
async hardDeleteOne(collection, query) {
|
|
1517
1657
|
assert(collection, "collection is required", "hardDeleteOne");
|
|
1518
1658
|
assert(query, "query is required", "hardDeleteOne", { collection });
|
|
1519
1659
|
const dbName = this.db; // Capture db at operation start
|
|
1520
|
-
if (typeof query === "string" ||
|
|
1660
|
+
if (typeof query === "string" ||
|
|
1661
|
+
typeof query === "number" ||
|
|
1662
|
+
query instanceof ObjectId) {
|
|
1521
1663
|
query = { _id: query };
|
|
1522
1664
|
}
|
|
1523
1665
|
query = this.replaceIds(query);
|
|
1524
1666
|
let opts = {
|
|
1525
1667
|
returnDocument: "after",
|
|
1526
|
-
...this._sessionOpt()
|
|
1668
|
+
...this._sessionOpt(),
|
|
1527
1669
|
};
|
|
1528
|
-
fjLog.debug(
|
|
1670
|
+
fjLog.debug("hardDeleteOne called", collection, query);
|
|
1529
1671
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1530
1672
|
let obj = await conn.findOneAndDelete(query, opts);
|
|
1531
1673
|
if (obj) {
|
|
1532
|
-
await this._publishAndAudit(
|
|
1674
|
+
await this._publishAndAudit("delete", dbName, collection, obj);
|
|
1533
1675
|
}
|
|
1534
1676
|
return obj;
|
|
1535
1677
|
}, false, { operation: "hardDeleteOne", collection, query });
|
|
1536
|
-
fjLog.debug(
|
|
1678
|
+
fjLog.debug("hardDeleteOne returns", ret);
|
|
1537
1679
|
return ret;
|
|
1538
1680
|
}
|
|
1539
1681
|
async delete(collection, query) {
|
|
@@ -1544,42 +1686,42 @@ export class Mongo extends Db {
|
|
|
1544
1686
|
query = this.replaceIds(query);
|
|
1545
1687
|
if (!this.softdelete) {
|
|
1546
1688
|
const opts = this._sessionOpt();
|
|
1547
|
-
fjLog.debug(
|
|
1689
|
+
fjLog.debug("delete called", collection, query);
|
|
1548
1690
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1549
1691
|
let obj = await conn.deleteMany(query, opts);
|
|
1550
1692
|
let resObj = {
|
|
1551
1693
|
n: obj.deletedCount,
|
|
1552
|
-
ok: !!obj.acknowledged
|
|
1694
|
+
ok: !!obj.acknowledged,
|
|
1553
1695
|
};
|
|
1554
|
-
await this._publishAndAudit(
|
|
1696
|
+
await this._publishAndAudit("deleteMany", dbName, collection, resObj);
|
|
1555
1697
|
return resObj;
|
|
1556
1698
|
}, false, { operation: "delete", collection, query, softdelete: this.softdelete });
|
|
1557
|
-
fjLog.debug(
|
|
1699
|
+
fjLog.debug("delete returns", ret);
|
|
1558
1700
|
return ret;
|
|
1559
1701
|
}
|
|
1560
1702
|
else {
|
|
1561
1703
|
let opts = {
|
|
1562
1704
|
upsert: false,
|
|
1563
1705
|
returnDocument: "after",
|
|
1564
|
-
...this._sessionOpt()
|
|
1706
|
+
...this._sessionOpt(),
|
|
1565
1707
|
};
|
|
1566
1708
|
let date = new Date();
|
|
1567
|
-
fjLog.debug(
|
|
1709
|
+
fjLog.debug("delete called", collection, query);
|
|
1568
1710
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1569
1711
|
let upd = {
|
|
1570
1712
|
$set: { _deleted: date },
|
|
1571
1713
|
$inc: { _rev: 1 },
|
|
1572
|
-
$currentDate: { _ts: { $type:
|
|
1714
|
+
$currentDate: { _ts: { $type: "timestamp" } },
|
|
1573
1715
|
};
|
|
1574
1716
|
let obj = await conn.updateMany(query, upd, opts);
|
|
1575
1717
|
let resObj = {
|
|
1576
1718
|
n: obj.modifiedCount,
|
|
1577
|
-
ok: !!obj.acknowledged
|
|
1719
|
+
ok: !!obj.acknowledged,
|
|
1578
1720
|
};
|
|
1579
|
-
await this._publishAndAudit(
|
|
1721
|
+
await this._publishAndAudit("deleteMany", dbName, collection, resObj);
|
|
1580
1722
|
return resObj;
|
|
1581
1723
|
}, false, { operation: "delete", collection, query, softdelete: this.softdelete });
|
|
1582
|
-
fjLog.debug(
|
|
1724
|
+
fjLog.debug("delete returns", ret);
|
|
1583
1725
|
return ret;
|
|
1584
1726
|
}
|
|
1585
1727
|
}
|
|
@@ -1590,22 +1732,27 @@ export class Mongo extends Db {
|
|
|
1590
1732
|
const dbName = this.db; // Capture db at operation start
|
|
1591
1733
|
query = this.replaceIds(query);
|
|
1592
1734
|
const opts = this._sessionOpt();
|
|
1593
|
-
fjLog.debug(
|
|
1735
|
+
fjLog.debug("hardDelete called", collection, query);
|
|
1594
1736
|
let ret = await this.executeTransactionally(collection, async (conn) => {
|
|
1595
1737
|
let obj = await conn.deleteMany(query, opts);
|
|
1596
1738
|
let resObj = {
|
|
1597
1739
|
n: obj.deletedCount,
|
|
1598
|
-
ok: !!obj.acknowledged
|
|
1740
|
+
ok: !!obj.acknowledged,
|
|
1599
1741
|
};
|
|
1600
|
-
await this._publishAndAudit(
|
|
1742
|
+
await this._publishAndAudit("deleteMany", dbName, collection, resObj);
|
|
1601
1743
|
return resObj;
|
|
1602
|
-
}, false, {
|
|
1603
|
-
|
|
1744
|
+
}, false, {
|
|
1745
|
+
operation: "hardDelete",
|
|
1746
|
+
collection,
|
|
1747
|
+
query,
|
|
1748
|
+
softdelete: this.softdelete,
|
|
1749
|
+
});
|
|
1750
|
+
fjLog.debug("hardDelete returns", ret);
|
|
1604
1751
|
return ret;
|
|
1605
1752
|
}
|
|
1606
1753
|
async testHash(collection, query, field, unhashedValue) {
|
|
1607
1754
|
let _field;
|
|
1608
|
-
fjLog.debug(
|
|
1755
|
+
fjLog.debug("testHash called", collection, query, field, unhashedValue);
|
|
1609
1756
|
if (typeof field === "object") {
|
|
1610
1757
|
if (Object.keys(field).length === 1)
|
|
1611
1758
|
[_field, unhashedValue] = Object.entries(field)[0];
|
|
@@ -1618,13 +1765,16 @@ export class Mongo extends Db {
|
|
|
1618
1765
|
_field = HASHED_PREFIX + _field;
|
|
1619
1766
|
const dbName = this.db; // Capture db at operation start
|
|
1620
1767
|
let conn = await this.connect();
|
|
1621
|
-
let obj = await conn
|
|
1768
|
+
let obj = await conn
|
|
1769
|
+
.db(dbName)
|
|
1770
|
+
.collection(collection)
|
|
1771
|
+
.findOne(query, { projection: { [_field]: 1 }, ...this._sessionOpt() });
|
|
1622
1772
|
if (!obj || !obj[_field]) {
|
|
1623
|
-
fjLog.debug(
|
|
1773
|
+
fjLog.debug("testHash returns false", obj);
|
|
1624
1774
|
return false;
|
|
1625
1775
|
}
|
|
1626
1776
|
let res = await bcrypt.compare(unhashedValue, obj[_field].hash);
|
|
1627
|
-
fjLog.debug(
|
|
1777
|
+
fjLog.debug("testHash returns", res);
|
|
1628
1778
|
return res;
|
|
1629
1779
|
}
|
|
1630
1780
|
async aggregate(collection, pipeline, opts = {
|
|
@@ -1632,7 +1782,7 @@ export class Mongo extends Db {
|
|
|
1632
1782
|
}) {
|
|
1633
1783
|
assert(collection, "collection is required", "aggregate");
|
|
1634
1784
|
assert(pipeline instanceof Array, "pipeline must be an Array", "aggregate", { collection });
|
|
1635
|
-
fjLog.debug(
|
|
1785
|
+
fjLog.debug("aggregate called", collection, pipeline);
|
|
1636
1786
|
pipeline = this.replaceIds(pipeline);
|
|
1637
1787
|
if (this.session)
|
|
1638
1788
|
opts.session = this.session;
|
|
@@ -1640,7 +1790,7 @@ export class Mongo extends Db {
|
|
|
1640
1790
|
let res = await conn.aggregate(pipeline, opts).toArray();
|
|
1641
1791
|
return res;
|
|
1642
1792
|
}, false, { operation: "aggregate", collection, pipeline, opts });
|
|
1643
|
-
fjLog.debug(
|
|
1793
|
+
fjLog.debug("aggregare returns", ret);
|
|
1644
1794
|
return ret;
|
|
1645
1795
|
}
|
|
1646
1796
|
async isUnique(collection, field, value, id) {
|
|
@@ -1648,7 +1798,7 @@ export class Mongo extends Db {
|
|
|
1648
1798
|
assert(field, "field is required", "isUnique", { collection });
|
|
1649
1799
|
if (!value)
|
|
1650
1800
|
return false;
|
|
1651
|
-
fjLog.debug(
|
|
1801
|
+
fjLog.debug("isUnique called", collection, field, value, id);
|
|
1652
1802
|
let _id = id === null || id === void 0 ? void 0 : id.toString();
|
|
1653
1803
|
let query = typeof value === "object" ? value : { [field]: value };
|
|
1654
1804
|
query = this.replaceIds(query);
|
|
@@ -1666,12 +1816,12 @@ export class Mongo extends Db {
|
|
|
1666
1816
|
return false;
|
|
1667
1817
|
}
|
|
1668
1818
|
}
|
|
1669
|
-
fjLog.debug(
|
|
1819
|
+
fjLog.debug("isUnique returns", ret);
|
|
1670
1820
|
return ret;
|
|
1671
1821
|
}
|
|
1672
1822
|
async dropCollection(collection) {
|
|
1673
1823
|
assert(collection, "collection is required", "dropCollection");
|
|
1674
|
-
fjLog.debug(
|
|
1824
|
+
fjLog.debug("dropCollection called", this.auditCollections);
|
|
1675
1825
|
const dbName = this.db; // Capture db at operation start
|
|
1676
1826
|
let client = await this.connect();
|
|
1677
1827
|
let existing = await client.db(dbName).collections();
|
|
@@ -1679,14 +1829,15 @@ export class Mongo extends Db {
|
|
|
1679
1829
|
if (existingNames.has(collection)) {
|
|
1680
1830
|
await client.db(dbName).dropCollection(collection);
|
|
1681
1831
|
}
|
|
1682
|
-
fjLog.debug(
|
|
1832
|
+
fjLog.debug("dropCollection returns");
|
|
1683
1833
|
}
|
|
1684
1834
|
async resetCollectionSync(collection) {
|
|
1685
1835
|
assert(collection, "collection is required", "resetCollectionSync");
|
|
1686
|
-
fjLog.debug(
|
|
1836
|
+
fjLog.debug("resetCollectionSync called for", collection);
|
|
1687
1837
|
const dbName = this.db; // Capture db at operation start
|
|
1688
1838
|
let client = await this.connect();
|
|
1689
|
-
await client
|
|
1839
|
+
await client
|
|
1840
|
+
.db(dbName)
|
|
1690
1841
|
.collection(SEQUENCES_COLLECTION)
|
|
1691
1842
|
.findOneAndDelete({ collection });
|
|
1692
1843
|
fjLog.debug(`resetCollectionSync for ${collection} returns`);
|
|
@@ -1694,7 +1845,7 @@ export class Mongo extends Db {
|
|
|
1694
1845
|
async dropCollections(collections) {
|
|
1695
1846
|
assert(collections, "collections is required", "dropCollections");
|
|
1696
1847
|
assert(collections instanceof Array, "collections must be an Array", "dropCollections");
|
|
1697
|
-
fjLog.debug(
|
|
1848
|
+
fjLog.debug("dropCollections called", this.auditCollections);
|
|
1698
1849
|
const dbName = this.db; // Capture db at operation start
|
|
1699
1850
|
let client = await this.connect();
|
|
1700
1851
|
let existing = await client.db(dbName).collections();
|
|
@@ -1704,12 +1855,12 @@ export class Mongo extends Db {
|
|
|
1704
1855
|
await client.db(dbName).dropCollection(collection);
|
|
1705
1856
|
}
|
|
1706
1857
|
}
|
|
1707
|
-
fjLog.debug(
|
|
1858
|
+
fjLog.debug("dropCollections returns");
|
|
1708
1859
|
}
|
|
1709
1860
|
async createCollections(collections) {
|
|
1710
1861
|
assert(collections, "collections is required", "createCollections");
|
|
1711
1862
|
assert(collections instanceof Array, "collections must be an Array", "createCollections");
|
|
1712
|
-
fjLog.debug(
|
|
1863
|
+
fjLog.debug("createCollections called", this.auditCollections);
|
|
1713
1864
|
const dbName = this.db; // Capture db at operation start
|
|
1714
1865
|
let client = await this.connect();
|
|
1715
1866
|
let existing = await this.getCollections();
|
|
@@ -1719,25 +1870,25 @@ export class Mongo extends Db {
|
|
|
1719
1870
|
await client.db(dbName).createCollection(collection);
|
|
1720
1871
|
}
|
|
1721
1872
|
}
|
|
1722
|
-
fjLog.debug(
|
|
1873
|
+
fjLog.debug("createCollections returns");
|
|
1723
1874
|
}
|
|
1724
1875
|
async createCollection(collection) {
|
|
1725
1876
|
assert(collection, "collection is required", "createCollection");
|
|
1726
|
-
fjLog.debug(
|
|
1877
|
+
fjLog.debug("createCollection called", collection);
|
|
1727
1878
|
const dbName = this.db; // Capture db at operation start
|
|
1728
1879
|
let client = await this.connect();
|
|
1729
1880
|
let existing = await this.getCollections();
|
|
1730
1881
|
if (!existing.includes(collection)) {
|
|
1731
1882
|
await client.db(dbName).createCollection(collection);
|
|
1732
1883
|
}
|
|
1733
|
-
fjLog.debug(
|
|
1884
|
+
fjLog.debug("createCollection returns");
|
|
1734
1885
|
}
|
|
1735
1886
|
async dbLogPurge(collection, _id) {
|
|
1736
1887
|
assert(collection, "collection is required", "dbLogPurge");
|
|
1737
|
-
fjLog.debug(
|
|
1888
|
+
fjLog.debug("dblogPurge called", collection, _id);
|
|
1738
1889
|
const dbName = this.db; // Capture db at operation start
|
|
1739
1890
|
let ret = await this.executeTransactionally(collection, async () => {
|
|
1740
|
-
let cond = { db: dbName, collection
|
|
1891
|
+
let cond = { db: dbName, collection };
|
|
1741
1892
|
if (_id !== undefined)
|
|
1742
1893
|
cond._id = Mongo._toId(_id);
|
|
1743
1894
|
let client = await this.connect();
|
|
@@ -1747,15 +1898,15 @@ export class Mongo extends Db {
|
|
|
1747
1898
|
.deleteMany(cond, this._sessionOpt());
|
|
1748
1899
|
return {
|
|
1749
1900
|
ok: !!ret.acknowledged,
|
|
1750
|
-
n: ret.deletedCount
|
|
1901
|
+
n: ret.deletedCount,
|
|
1751
1902
|
};
|
|
1752
1903
|
}, false, { operation: "dbLogPurge", collection, _id });
|
|
1753
|
-
fjLog.debug(
|
|
1904
|
+
fjLog.debug("dblogPurge returns", ret);
|
|
1754
1905
|
return ret;
|
|
1755
1906
|
}
|
|
1756
1907
|
async dbLogGet(collection, _id) {
|
|
1757
1908
|
assert(collection, "collection is required", "dbLogGet");
|
|
1758
|
-
fjLog.debug(
|
|
1909
|
+
fjLog.debug("dblogGet called", collection, _id);
|
|
1759
1910
|
const dbName = this.db; // Capture db at operation start
|
|
1760
1911
|
let ret = await this.executeTransactionally(collection, async () => {
|
|
1761
1912
|
let cond = { db: dbName, collection };
|
|
@@ -1770,7 +1921,7 @@ export class Mongo extends Db {
|
|
|
1770
1921
|
.toArray();
|
|
1771
1922
|
return ret;
|
|
1772
1923
|
}, false, { operation: "dbLogGet", collection, _id });
|
|
1773
|
-
fjLog.debug(
|
|
1924
|
+
fjLog.debug("dblogGet returns", ret);
|
|
1774
1925
|
return ret;
|
|
1775
1926
|
}
|
|
1776
1927
|
// HELPER FUNCTIONS
|
|
@@ -1805,13 +1956,13 @@ export class Mongo extends Db {
|
|
|
1805
1956
|
if (typeof data === "string")
|
|
1806
1957
|
return data;
|
|
1807
1958
|
if (data instanceof Array) {
|
|
1808
|
-
return data.map(d => this.replaceIds(d));
|
|
1959
|
+
return data.map((d) => this.replaceIds(d));
|
|
1809
1960
|
}
|
|
1810
|
-
if (typeof data ==
|
|
1961
|
+
if (typeof data == "object" && (data === null || data === void 0 ? void 0 : data.t) && (data === null || data === void 0 ? void 0 : data.i) !== undefined)
|
|
1811
1962
|
return Base.timestamp(data);
|
|
1812
|
-
if (typeof data ==
|
|
1963
|
+
if (typeof data == "object" && (data === null || data === void 0 ? void 0 : data.high) && (data === null || data === void 0 ? void 0 : data.low) !== undefined)
|
|
1813
1964
|
return Base.timestamp(data);
|
|
1814
|
-
if (typeof data ==
|
|
1965
|
+
if (typeof data == "object") {
|
|
1815
1966
|
for (let key in data) {
|
|
1816
1967
|
data[key] = this.replaceIds(data[key]);
|
|
1817
1968
|
}
|
|
@@ -1820,7 +1971,7 @@ export class Mongo extends Db {
|
|
|
1820
1971
|
throw new Error(`ObjectId test failed for ${data}`);
|
|
1821
1972
|
}
|
|
1822
1973
|
catch (err) {
|
|
1823
|
-
console.error(
|
|
1974
|
+
console.error("error in replaceIds", err, data);
|
|
1824
1975
|
throw err;
|
|
1825
1976
|
}
|
|
1826
1977
|
}
|
|
@@ -1868,14 +2019,14 @@ export class Mongo extends Db {
|
|
|
1868
2019
|
this._sequencesIndexEnsured = false;
|
|
1869
2020
|
}
|
|
1870
2021
|
async inTransaction() {
|
|
1871
|
-
if (!await this.isOnReplicaSet())
|
|
2022
|
+
if (!(await this.isOnReplicaSet()))
|
|
1872
2023
|
return false;
|
|
1873
2024
|
if (!this.session)
|
|
1874
2025
|
return false;
|
|
1875
2026
|
return this.session.inTransaction();
|
|
1876
2027
|
}
|
|
1877
2028
|
async withTransaction(funct) {
|
|
1878
|
-
if (!await this.isOnReplicaSet())
|
|
2029
|
+
if (!(await this.isOnReplicaSet()))
|
|
1879
2030
|
return await funct(await this.connect(), null);
|
|
1880
2031
|
const hadSession = !!this.session;
|
|
1881
2032
|
try {
|
|
@@ -1899,14 +2050,16 @@ export class Mongo extends Db {
|
|
|
1899
2050
|
await this.session.endSession();
|
|
1900
2051
|
fjLog.info("session ended after error");
|
|
1901
2052
|
}
|
|
1902
|
-
catch {
|
|
2053
|
+
catch {
|
|
2054
|
+
/* ignore cleanup errors */
|
|
2055
|
+
}
|
|
1903
2056
|
this.session = undefined;
|
|
1904
2057
|
}
|
|
1905
2058
|
throw err;
|
|
1906
2059
|
}
|
|
1907
2060
|
}
|
|
1908
2061
|
async startTransaction() {
|
|
1909
|
-
if (!await this.isOnReplicaSet())
|
|
2062
|
+
if (!(await this.isOnReplicaSet()))
|
|
1910
2063
|
return;
|
|
1911
2064
|
let client = await this.connect();
|
|
1912
2065
|
try {
|
|
@@ -1914,13 +2067,13 @@ export class Mongo extends Db {
|
|
|
1914
2067
|
this.session = client.startSession(TRANSACTION_OPTIONS);
|
|
1915
2068
|
fjLog.info("session started");
|
|
1916
2069
|
}
|
|
1917
|
-
if (!await this.inTransaction()) {
|
|
2070
|
+
if (!(await this.inTransaction())) {
|
|
1918
2071
|
await this.session.startTransaction();
|
|
1919
2072
|
fjLog.info("transaction started");
|
|
1920
2073
|
}
|
|
1921
2074
|
}
|
|
1922
2075
|
catch (err) {
|
|
1923
|
-
fjLog.error(
|
|
2076
|
+
fjLog.error("startTransaction error", err);
|
|
1924
2077
|
try {
|
|
1925
2078
|
if (this.session) {
|
|
1926
2079
|
await this.session.endSession();
|
|
@@ -1935,10 +2088,10 @@ export class Mongo extends Db {
|
|
|
1935
2088
|
}
|
|
1936
2089
|
}
|
|
1937
2090
|
async commitTransaction() {
|
|
1938
|
-
if (!await this.isOnReplicaSet())
|
|
2091
|
+
if (!(await this.isOnReplicaSet()))
|
|
1939
2092
|
return;
|
|
1940
2093
|
try {
|
|
1941
|
-
if (!await this.inTransaction())
|
|
2094
|
+
if (!(await this.inTransaction()))
|
|
1942
2095
|
return;
|
|
1943
2096
|
let session = this.session;
|
|
1944
2097
|
await session.commitTransaction();
|
|
@@ -1952,10 +2105,10 @@ export class Mongo extends Db {
|
|
|
1952
2105
|
}
|
|
1953
2106
|
}
|
|
1954
2107
|
async abortTransaction() {
|
|
1955
|
-
if (!await this.isOnReplicaSet())
|
|
2108
|
+
if (!(await this.isOnReplicaSet()))
|
|
1956
2109
|
return;
|
|
1957
2110
|
try {
|
|
1958
|
-
if (!await this.inTransaction())
|
|
2111
|
+
if (!(await this.inTransaction()))
|
|
1959
2112
|
return;
|
|
1960
2113
|
let session = this.session;
|
|
1961
2114
|
await session.abortTransaction();
|
|
@@ -2009,22 +2162,24 @@ export class Mongo extends Db {
|
|
|
2009
2162
|
// E11000 duplicate key — log concise one-liner (avoid dumping full batch)
|
|
2010
2163
|
if (x.match(/E11000/i)) {
|
|
2011
2164
|
const dupMatch = x.match(/dup key:\s*(\{[^}]+\})/);
|
|
2012
|
-
const count = Array.isArray(debugObject.insert)
|
|
2165
|
+
const count = Array.isArray(debugObject.insert)
|
|
2166
|
+
? debugObject.insert.length
|
|
2167
|
+
: 1;
|
|
2013
2168
|
const rollback = this.session ? "ROLLBACK - " : "";
|
|
2014
2169
|
fjLog.error(`${rollback}Mongo ${debugObject.operation} E11000 on ${dbName}.${collection} (batch=${count}${dupMatch ? `, ${dupMatch[1].trim()}` : ""}) — ignoring`);
|
|
2015
2170
|
if (debugObject.operation === "insert")
|
|
2016
2171
|
return debugObject.insert;
|
|
2017
2172
|
throw err;
|
|
2018
2173
|
}
|
|
2019
|
-
fjLog.error(`Mongo command has failed for ${dbName}.${collection} - ${
|
|
2174
|
+
fjLog.error(`Mongo command has failed for ${dbName}.${collection} - ${this.session ? "ROLLBACK - " : ""} ${err.message || err}`);
|
|
2020
2175
|
fjLog.error(debugObject);
|
|
2021
2176
|
fjLog.debug(err);
|
|
2022
|
-
let isRepeatable = x.match(/Topology is closed, please connect/i)
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2177
|
+
let isRepeatable = x.match(/Topology is closed, please connect/i) ||
|
|
2178
|
+
x.match(/connection timed out/i) ||
|
|
2179
|
+
x.match(/not master/i) ||
|
|
2180
|
+
x.match(/Topology closed/i) ||
|
|
2181
|
+
x.match(/Topology is closed/i) ||
|
|
2182
|
+
x.match(/Connection pool closed/i);
|
|
2028
2183
|
if (isRepeatable) {
|
|
2029
2184
|
try {
|
|
2030
2185
|
fjLog.error("Trying to reopen connection and repeat as");
|
|
@@ -2049,11 +2204,11 @@ export class Mongo extends Db {
|
|
|
2049
2204
|
}
|
|
2050
2205
|
async _findLastSequenceForKey(connection, key) {
|
|
2051
2206
|
var _a;
|
|
2052
|
-
let maxfld = await
|
|
2207
|
+
let maxfld = await connection
|
|
2053
2208
|
.find({}, this._sessionOpt())
|
|
2054
2209
|
.sort({ [key]: -1 })
|
|
2055
2210
|
.limit(1)
|
|
2056
|
-
.toArray()
|
|
2211
|
+
.toArray();
|
|
2057
2212
|
if (maxfld.length === 0)
|
|
2058
2213
|
return undefined;
|
|
2059
2214
|
return parseInt((_a = maxfld === null || maxfld === void 0 ? void 0 : maxfld[0]) === null || _a === void 0 ? void 0 : _a[key]) || 0;
|
|
@@ -2095,19 +2250,19 @@ export class Mongo extends Db {
|
|
|
2095
2250
|
if (!existingSeq) {
|
|
2096
2251
|
// First time for this collection/field - ensure index exists
|
|
2097
2252
|
await this._ensureSequencesIndex(dbConnection);
|
|
2098
|
-
const seedValue =
|
|
2253
|
+
const seedValue = existingMax !== undefined && existingMax >= 1 ? existingMax : 0;
|
|
2099
2254
|
// Use $max to handle concurrent seeding - only sets if greater than current value
|
|
2100
2255
|
// This ensures the highest seed value wins in case of concurrent operations
|
|
2101
2256
|
await dbConnection
|
|
2102
2257
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2103
2258
|
.findOneAndUpdate({ collection, field }, {
|
|
2104
2259
|
$max: { lastSeqNum: seedValue },
|
|
2105
|
-
$setOnInsert: { collection, field }
|
|
2106
|
-
}, { upsert: true, returnDocument:
|
|
2260
|
+
$setOnInsert: { collection, field },
|
|
2261
|
+
}, { upsert: true, returnDocument: "after", ...this._sessionOpt() });
|
|
2107
2262
|
// Now increment atomically to get next value
|
|
2108
2263
|
const updated = await dbConnection
|
|
2109
2264
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2110
|
-
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument:
|
|
2265
|
+
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument: "after", ...this._sessionOpt() });
|
|
2111
2266
|
return updated.lastSeqNum;
|
|
2112
2267
|
}
|
|
2113
2268
|
// Sequence exists - check if collection was cleared (reset scenario)
|
|
@@ -2120,7 +2275,7 @@ export class Mongo extends Db {
|
|
|
2120
2275
|
// Increment and return
|
|
2121
2276
|
const result = await dbConnection
|
|
2122
2277
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2123
|
-
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument:
|
|
2278
|
+
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: 1 } }, { returnDocument: "after", ...this._sessionOpt() });
|
|
2124
2279
|
return result.lastSeqNum;
|
|
2125
2280
|
}
|
|
2126
2281
|
/**
|
|
@@ -2169,14 +2324,14 @@ export class Mongo extends Db {
|
|
|
2169
2324
|
if (!existingSeq) {
|
|
2170
2325
|
// First time for this collection/field - ensure index exists
|
|
2171
2326
|
await this._ensureSequencesIndex(dbConnection);
|
|
2172
|
-
const seedValue =
|
|
2327
|
+
const seedValue = existingMax !== undefined && existingMax >= 1 ? existingMax : 0;
|
|
2173
2328
|
// Use $max to handle concurrent seeding
|
|
2174
2329
|
await dbConnection
|
|
2175
2330
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2176
2331
|
.findOneAndUpdate({ collection, field }, {
|
|
2177
2332
|
$max: { lastSeqNum: seedValue },
|
|
2178
|
-
$setOnInsert: { collection, field }
|
|
2179
|
-
}, { upsert: true, returnDocument:
|
|
2333
|
+
$setOnInsert: { collection, field },
|
|
2334
|
+
}, { upsert: true, returnDocument: "after", ...this._sessionOpt() });
|
|
2180
2335
|
}
|
|
2181
2336
|
else if (existingMax === undefined && existingSeq.lastSeqNum > 0) {
|
|
2182
2337
|
// Collection is empty but we have a non-zero sequence - reset to 0
|
|
@@ -2187,7 +2342,7 @@ export class Mongo extends Db {
|
|
|
2187
2342
|
// Now atomically reserve the range
|
|
2188
2343
|
const result = await dbConnection
|
|
2189
2344
|
.collection(FIELD_SEQUENCES_COLLECTION)
|
|
2190
|
-
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: count } }, { returnDocument:
|
|
2345
|
+
.findOneAndUpdate({ collection, field }, { $inc: { lastSeqNum: count } }, { returnDocument: "after", ...this._sessionOpt() });
|
|
2191
2346
|
const end = result.lastSeqNum;
|
|
2192
2347
|
return { start: end - count + 1, end };
|
|
2193
2348
|
}
|
|
@@ -2212,17 +2367,20 @@ export class Mongo extends Db {
|
|
|
2212
2367
|
return;
|
|
2213
2368
|
if (!this._hasSequenceFields(object) && !this.syncSupport)
|
|
2214
2369
|
return;
|
|
2215
|
-
const seqKeys = Object.keys(object).filter(key => object[key] ===
|
|
2370
|
+
const seqKeys = Object.keys(object).filter((key) => object[key] === "SEQ_NEXT" ||
|
|
2371
|
+
object[key] === "SEQ_LAST");
|
|
2216
2372
|
return { seqKeys };
|
|
2217
2373
|
}
|
|
2218
2374
|
async _processSequenceField(client, dbName, collection, insert, seqKeys) {
|
|
2219
|
-
assert(this.client, "client is required", "_processSequenceField", {
|
|
2375
|
+
assert(this.client, "client is required", "_processSequenceField", {
|
|
2376
|
+
collection,
|
|
2377
|
+
});
|
|
2220
2378
|
// if (this.syncSupport) {
|
|
2221
2379
|
// insert._csq = (await this._getNextCollectionUpdateSeqNo(collection, client));
|
|
2222
2380
|
// }
|
|
2223
2381
|
const db = client.db(dbName);
|
|
2224
2382
|
for (const seqKey of (seqKeys === null || seqKeys === void 0 ? void 0 : seqKeys.seqKeys) || []) {
|
|
2225
|
-
if (insert[seqKey] ===
|
|
2383
|
+
if (insert[seqKey] === "SEQ_LAST") {
|
|
2226
2384
|
insert[seqKey] = await this._getLastSequenceValue(db, collection, seqKey);
|
|
2227
2385
|
}
|
|
2228
2386
|
else {
|
|
@@ -2232,7 +2390,9 @@ export class Mongo extends Db {
|
|
|
2232
2390
|
return insert;
|
|
2233
2391
|
}
|
|
2234
2392
|
async _processSequenceFieldForMany(connection, dbName, collection, inserts) {
|
|
2235
|
-
assert(this.client, "client is required", "_processSequenceFieldForMany", {
|
|
2393
|
+
assert(this.client, "client is required", "_processSequenceFieldForMany", {
|
|
2394
|
+
collection,
|
|
2395
|
+
});
|
|
2236
2396
|
assert(connection, "connection is required", "_processSequenceFieldForMany", { collection });
|
|
2237
2397
|
if (!(inserts === null || inserts === void 0 ? void 0 : inserts.length))
|
|
2238
2398
|
return;
|
|
@@ -2240,7 +2400,7 @@ export class Mongo extends Db {
|
|
|
2240
2400
|
let seqKeysSet = new Set();
|
|
2241
2401
|
for (let insert of inserts) {
|
|
2242
2402
|
let spec = this._findSequenceKeys(insert);
|
|
2243
|
-
spec === null || spec === void 0 ? void 0 : spec.seqKeys.forEach(key => seqKeysSet.add(key));
|
|
2403
|
+
spec === null || spec === void 0 ? void 0 : spec.seqKeys.forEach((key) => seqKeysSet.add(key));
|
|
2244
2404
|
}
|
|
2245
2405
|
let seqKeys = Array.from(seqKeysSet);
|
|
2246
2406
|
if (!seqKeys.length)
|
|
@@ -2251,7 +2411,7 @@ export class Mongo extends Db {
|
|
|
2251
2411
|
// Count SEQ_NEXT occurrences to reserve the range
|
|
2252
2412
|
let seqNextCount = 0;
|
|
2253
2413
|
for (const insert of inserts) {
|
|
2254
|
-
if (insert[seqKey] ===
|
|
2414
|
+
if (insert[seqKey] === "SEQ_NEXT")
|
|
2255
2415
|
seqNextCount++;
|
|
2256
2416
|
}
|
|
2257
2417
|
// Get current value and reserve range for SEQ_NEXT if needed
|
|
@@ -2268,10 +2428,10 @@ export class Mongo extends Db {
|
|
|
2268
2428
|
// Process inserts in order - SEQ_LAST gets current, SEQ_NEXT increments and gets new value
|
|
2269
2429
|
for (const insert of inserts) {
|
|
2270
2430
|
const val = insert[seqKey];
|
|
2271
|
-
if (val ===
|
|
2431
|
+
if (val === "SEQ_LAST") {
|
|
2272
2432
|
insert[seqKey] = currentValue;
|
|
2273
2433
|
}
|
|
2274
|
-
else if (val ===
|
|
2434
|
+
else if (val === "SEQ_NEXT") {
|
|
2275
2435
|
currentValue++;
|
|
2276
2436
|
insert[seqKey] = currentValue;
|
|
2277
2437
|
}
|
|
@@ -2282,9 +2442,13 @@ export class Mongo extends Db {
|
|
|
2282
2442
|
_getFieldsRecursively(obj, into = []) {
|
|
2283
2443
|
for (let key in obj) {
|
|
2284
2444
|
const isDollarKey = startsWithDollar(key);
|
|
2285
|
-
if (isDollarKey &&
|
|
2445
|
+
if (isDollarKey &&
|
|
2446
|
+
typeof obj[key] === "object" &&
|
|
2447
|
+
!Array.isArray(obj[key]))
|
|
2286
2448
|
this._getFieldsRecursively(obj[key], into);
|
|
2287
|
-
else if (!isDollarKey &&
|
|
2449
|
+
else if (!isDollarKey &&
|
|
2450
|
+
typeof obj[key] === "object" &&
|
|
2451
|
+
!Array.isArray(obj[key]))
|
|
2288
2452
|
into.push(key);
|
|
2289
2453
|
else if (!isDollarKey)
|
|
2290
2454
|
into.push(key);
|
|
@@ -2314,7 +2478,7 @@ export class Mongo extends Db {
|
|
|
2314
2478
|
const out = {};
|
|
2315
2479
|
const keys = this._getFieldsRecursively(inputUpdate);
|
|
2316
2480
|
for (const k of keys) {
|
|
2317
|
-
const bracketIdx = k.indexOf(
|
|
2481
|
+
const bracketIdx = k.indexOf("[");
|
|
2318
2482
|
if (bracketIdx < 0) {
|
|
2319
2483
|
out[k] = wholerecord[k];
|
|
2320
2484
|
continue;
|
|
@@ -2322,7 +2486,7 @@ export class Mongo extends Db {
|
|
|
2322
2486
|
// Bracket path: publish the top-level parent field as a whole,
|
|
2323
2487
|
// sliced from the post-update document. The "top-level parent" is
|
|
2324
2488
|
// everything before the FIRST `.` or `[` separator.
|
|
2325
|
-
const dotIdx = k.indexOf(
|
|
2489
|
+
const dotIdx = k.indexOf(".");
|
|
2326
2490
|
const cutAt = dotIdx >= 0 && dotIdx < bracketIdx ? dotIdx : bracketIdx;
|
|
2327
2491
|
const topField = k.substring(0, cutAt);
|
|
2328
2492
|
if (topField && !(topField in out))
|
|
@@ -2351,7 +2515,7 @@ export class Mongo extends Db {
|
|
|
2351
2515
|
if (!this.auditing)
|
|
2352
2516
|
return false;
|
|
2353
2517
|
const fullName = ((db ? db + "." : "") + (col || "")).toLowerCase();
|
|
2354
|
-
return this.auditedCollectionsLower.some(m => fullName.includes(m));
|
|
2518
|
+
return this.auditedCollectionsLower.some((m) => fullName.includes(m));
|
|
2355
2519
|
}
|
|
2356
2520
|
async _publishAndAudit(operation, db, collection, dataToPublish, noEmit, rawUpdate) {
|
|
2357
2521
|
if (!dataToPublish._id && !["deleteMany", "updateMany"].includes(operation))
|
|
@@ -2383,7 +2547,7 @@ export class Mongo extends Db {
|
|
|
2383
2547
|
}
|
|
2384
2548
|
}
|
|
2385
2549
|
if (this._shouldAuditCollection(db, collection)) {
|
|
2386
|
-
if ([
|
|
2550
|
+
if (["insert", "update", "delete"].includes(operation))
|
|
2387
2551
|
await this._writeAuditRecord(db, collection, operation, data);
|
|
2388
2552
|
}
|
|
2389
2553
|
return toPublishAll;
|
|
@@ -2411,11 +2575,11 @@ export class Mongo extends Db {
|
|
|
2411
2575
|
const user = (_a = data === null || data === void 0 ? void 0 : data._auth) === null || _a === void 0 ? void 0 : _a.username;
|
|
2412
2576
|
let toPublish;
|
|
2413
2577
|
switch (operation) {
|
|
2414
|
-
case
|
|
2578
|
+
case "insert":
|
|
2415
2579
|
toPublish = {
|
|
2416
2580
|
channel,
|
|
2417
2581
|
payload: {
|
|
2418
|
-
operation:
|
|
2582
|
+
operation: "insert",
|
|
2419
2583
|
db,
|
|
2420
2584
|
collection,
|
|
2421
2585
|
_id: data._id,
|
|
@@ -2425,11 +2589,11 @@ export class Mongo extends Db {
|
|
|
2425
2589
|
},
|
|
2426
2590
|
};
|
|
2427
2591
|
break;
|
|
2428
|
-
case
|
|
2592
|
+
case "update":
|
|
2429
2593
|
toPublish = {
|
|
2430
2594
|
channel,
|
|
2431
2595
|
payload: {
|
|
2432
|
-
operation:
|
|
2596
|
+
operation: "update",
|
|
2433
2597
|
db,
|
|
2434
2598
|
collection,
|
|
2435
2599
|
_id: data._id,
|
|
@@ -2439,11 +2603,11 @@ export class Mongo extends Db {
|
|
|
2439
2603
|
},
|
|
2440
2604
|
};
|
|
2441
2605
|
break;
|
|
2442
|
-
case
|
|
2606
|
+
case "delete":
|
|
2443
2607
|
toPublish = {
|
|
2444
2608
|
channel,
|
|
2445
2609
|
payload: {
|
|
2446
|
-
operation:
|
|
2610
|
+
operation: "delete",
|
|
2447
2611
|
db,
|
|
2448
2612
|
collection,
|
|
2449
2613
|
_id: data._id,
|
|
@@ -2453,30 +2617,30 @@ export class Mongo extends Db {
|
|
|
2453
2617
|
},
|
|
2454
2618
|
};
|
|
2455
2619
|
break;
|
|
2456
|
-
case
|
|
2620
|
+
case "updateMany":
|
|
2457
2621
|
toPublish = {
|
|
2458
2622
|
channel,
|
|
2459
2623
|
payload: {
|
|
2460
|
-
operation:
|
|
2624
|
+
operation: "updateMany",
|
|
2461
2625
|
db,
|
|
2462
2626
|
collection,
|
|
2463
2627
|
enquireLastTs: true,
|
|
2464
2628
|
},
|
|
2465
2629
|
};
|
|
2466
2630
|
break;
|
|
2467
|
-
case
|
|
2631
|
+
case "deleteMany":
|
|
2468
2632
|
toPublish = {
|
|
2469
2633
|
channel,
|
|
2470
2634
|
payload: {
|
|
2471
|
-
operation:
|
|
2635
|
+
operation: "deleteMany",
|
|
2472
2636
|
db,
|
|
2473
2637
|
collection,
|
|
2474
2638
|
enquireLastTs: true,
|
|
2475
2639
|
},
|
|
2476
2640
|
};
|
|
2477
2641
|
break;
|
|
2478
|
-
case
|
|
2479
|
-
throw new Error(
|
|
2642
|
+
case "batch":
|
|
2643
|
+
throw new Error("batch operation should use direct emit, not _makeRevPublication");
|
|
2480
2644
|
}
|
|
2481
2645
|
if (user)
|
|
2482
2646
|
toPublish.user = user;
|
|
@@ -2493,12 +2657,17 @@ export class Mongo extends Db {
|
|
|
2493
2657
|
if (!this.auditing)
|
|
2494
2658
|
return;
|
|
2495
2659
|
let client = await this.connect();
|
|
2496
|
-
let previousAuditRecords = await (await client
|
|
2660
|
+
let previousAuditRecords = await (await client
|
|
2661
|
+
.db(db)
|
|
2662
|
+
.collection(this.auditCollectionName)
|
|
2663
|
+
.aggregate([
|
|
2497
2664
|
{ $match: { entityid: Mongo._toId(data._id) } },
|
|
2498
2665
|
{ $sort: { rev: -1 } },
|
|
2499
|
-
{ $limit: 1 }
|
|
2666
|
+
{ $limit: 1 },
|
|
2500
2667
|
], this.session ? { session: this.session } : {})).toArray();
|
|
2501
|
-
let previousAuditRecord = previousAuditRecords.length
|
|
2668
|
+
let previousAuditRecord = previousAuditRecords.length
|
|
2669
|
+
? previousAuditRecords[0]
|
|
2670
|
+
: { rev: 0, changes: {} };
|
|
2502
2671
|
if (previousAuditRecords.length === 0)
|
|
2503
2672
|
await this.createCollection(this.auditCollectionName);
|
|
2504
2673
|
let dataNoId = { ...data };
|
|
@@ -2517,11 +2686,12 @@ export class Mongo extends Db {
|
|
|
2517
2686
|
auditRecord.user = user;
|
|
2518
2687
|
if (audit)
|
|
2519
2688
|
auditRecord.audit = audit;
|
|
2520
|
-
fjLog.trace(
|
|
2521
|
-
let ret = await client
|
|
2689
|
+
fjLog.trace("AUDITING", auditRecord);
|
|
2690
|
+
let ret = await client
|
|
2691
|
+
.db(db)
|
|
2522
2692
|
.collection(this.auditCollectionName)
|
|
2523
2693
|
.insertOne(auditRecord, this._sessionOpt());
|
|
2524
|
-
fjLog.debug(
|
|
2694
|
+
fjLog.debug("AUDITED", auditRecord, ret.insertedId);
|
|
2525
2695
|
}
|
|
2526
2696
|
_sessionOpt() {
|
|
2527
2697
|
return this.session ? { session: this.session } : {};
|
|
@@ -2552,7 +2722,7 @@ export class Mongo extends Db {
|
|
|
2552
2722
|
return Object.keys(obj).some(startsWithHashedPrefix);
|
|
2553
2723
|
}
|
|
2554
2724
|
_hasSequenceFields(obj) {
|
|
2555
|
-
return Object.values(obj).some(v => v ===
|
|
2725
|
+
return Object.values(obj).some((v) => v === "SEQ_NEXT" || v === "SEQ_LAST");
|
|
2556
2726
|
}
|
|
2557
2727
|
_processUpdateObject(update) {
|
|
2558
2728
|
for (let k in update) {
|
|
@@ -2560,7 +2730,8 @@ export class Mongo extends Db {
|
|
|
2560
2730
|
const keyStr = String(key);
|
|
2561
2731
|
if (keyStr[0] === "$")
|
|
2562
2732
|
continue;
|
|
2563
|
-
if (startsWithDoubleUnderscore(keyStr) &&
|
|
2733
|
+
if (startsWithDoubleUnderscore(keyStr) &&
|
|
2734
|
+
!keyStr.startsWith(HASHED_PREFIX)) {
|
|
2564
2735
|
delete update[key];
|
|
2565
2736
|
continue;
|
|
2566
2737
|
}
|
|
@@ -2568,7 +2739,7 @@ export class Mongo extends Db {
|
|
|
2568
2739
|
delete update[key];
|
|
2569
2740
|
continue;
|
|
2570
2741
|
}
|
|
2571
|
-
if (key ===
|
|
2742
|
+
if (key === "" || key === null || key === undefined) {
|
|
2572
2743
|
delete update[key];
|
|
2573
2744
|
continue;
|
|
2574
2745
|
}
|
|
@@ -2586,12 +2757,14 @@ export class Mongo extends Db {
|
|
|
2586
2757
|
if (this.revisions) {
|
|
2587
2758
|
update.$inc = update.$inc || {};
|
|
2588
2759
|
update.$inc._rev = 1;
|
|
2589
|
-
const currentDate = update.$currentDate &&
|
|
2760
|
+
const currentDate = update.$currentDate &&
|
|
2761
|
+
typeof update.$currentDate === "object" &&
|
|
2762
|
+
!Array.isArray(update.$currentDate)
|
|
2590
2763
|
? update.$currentDate
|
|
2591
2764
|
: undefined;
|
|
2592
2765
|
update.$currentDate = {
|
|
2593
2766
|
...(currentDate || {}),
|
|
2594
|
-
_ts: { $type:
|
|
2767
|
+
_ts: { $type: "timestamp" },
|
|
2595
2768
|
};
|
|
2596
2769
|
}
|
|
2597
2770
|
if (update.$set) {
|
|
@@ -2631,35 +2804,41 @@ export class Mongo extends Db {
|
|
|
2631
2804
|
const translatePath = (path) => {
|
|
2632
2805
|
const tokens = Mongo._tokenizePath(path);
|
|
2633
2806
|
const out = [];
|
|
2634
|
-
let parentKey =
|
|
2807
|
+
let parentKey = "";
|
|
2635
2808
|
for (const t of tokens) {
|
|
2636
|
-
if (t.length >= 2 &&
|
|
2809
|
+
if (t.length >= 2 &&
|
|
2810
|
+
t.charCodeAt(0) === 91 /* [ */ &&
|
|
2811
|
+
t.charCodeAt(t.length - 1) === 93 /* ] */) {
|
|
2637
2812
|
const idStr = Mongo._unquoteBracketId(t, path);
|
|
2638
2813
|
const memoKey = `${parentKey}|${idStr}`;
|
|
2639
2814
|
let filterName = memo.get(memoKey);
|
|
2640
2815
|
if (filterName === undefined) {
|
|
2641
2816
|
filterName = `f${counter++}`;
|
|
2642
2817
|
memo.set(memoKey, filterName);
|
|
2643
|
-
filters.push({
|
|
2818
|
+
filters.push({
|
|
2819
|
+
[`${filterName}._id`]: Mongo._normalizeBracketId(idStr),
|
|
2820
|
+
});
|
|
2644
2821
|
}
|
|
2645
2822
|
out.push(`$[${filterName}]`);
|
|
2646
|
-
parentKey = parentKey
|
|
2823
|
+
parentKey = parentKey
|
|
2824
|
+
? `${parentKey}.$[${filterName}]`
|
|
2825
|
+
: `$[${filterName}]`;
|
|
2647
2826
|
}
|
|
2648
2827
|
else {
|
|
2649
2828
|
out.push(t);
|
|
2650
2829
|
parentKey = parentKey ? `${parentKey}.${t}` : t;
|
|
2651
2830
|
}
|
|
2652
2831
|
}
|
|
2653
|
-
return out.join(
|
|
2832
|
+
return out.join(".");
|
|
2654
2833
|
};
|
|
2655
2834
|
const translateOp = (opName) => {
|
|
2656
2835
|
const op = update[opName];
|
|
2657
|
-
if (!op || typeof op !==
|
|
2836
|
+
if (!op || typeof op !== "object")
|
|
2658
2837
|
return;
|
|
2659
2838
|
// Fast path: skip rebuild if no bracket present.
|
|
2660
2839
|
let hasBracket = false;
|
|
2661
2840
|
for (const k of Object.keys(op)) {
|
|
2662
|
-
if (k.indexOf(
|
|
2841
|
+
if (k.indexOf("[") >= 0) {
|
|
2663
2842
|
hasBracket = true;
|
|
2664
2843
|
break;
|
|
2665
2844
|
}
|
|
@@ -2672,8 +2851,8 @@ export class Mongo extends Db {
|
|
|
2672
2851
|
}
|
|
2673
2852
|
update[opName] = translated;
|
|
2674
2853
|
};
|
|
2675
|
-
translateOp(
|
|
2676
|
-
translateOp(
|
|
2854
|
+
translateOp("$set");
|
|
2855
|
+
translateOp("$unset");
|
|
2677
2856
|
return filters.length > 0 ? filters : undefined;
|
|
2678
2857
|
}
|
|
2679
2858
|
/**
|
|
@@ -2723,38 +2902,38 @@ export class Mongo extends Db {
|
|
|
2723
2902
|
var _a;
|
|
2724
2903
|
if (value === null || value === undefined)
|
|
2725
2904
|
return false;
|
|
2726
|
-
if (typeof value !==
|
|
2905
|
+
if (typeof value !== "object")
|
|
2727
2906
|
return false;
|
|
2728
2907
|
if (value instanceof Date)
|
|
2729
2908
|
return false;
|
|
2730
2909
|
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 ===
|
|
2910
|
+
if (ctorName === "ObjectId" ||
|
|
2911
|
+
ctorName === "Timestamp" ||
|
|
2912
|
+
ctorName === "Decimal128" ||
|
|
2913
|
+
ctorName === "Binary" ||
|
|
2914
|
+
ctorName === "Long" ||
|
|
2915
|
+
ctorName === "Double" ||
|
|
2916
|
+
ctorName === "Int32")
|
|
2738
2917
|
return false;
|
|
2739
2918
|
return true;
|
|
2740
2919
|
}
|
|
2741
2920
|
static _tokenizePath(path) {
|
|
2742
2921
|
const out = [];
|
|
2743
|
-
let buf =
|
|
2922
|
+
let buf = "";
|
|
2744
2923
|
for (let i = 0; i < path.length; i++) {
|
|
2745
2924
|
const ch = path[i];
|
|
2746
|
-
if (ch ===
|
|
2925
|
+
if (ch === ".") {
|
|
2747
2926
|
if (buf) {
|
|
2748
2927
|
out.push(buf);
|
|
2749
|
-
buf =
|
|
2928
|
+
buf = "";
|
|
2750
2929
|
}
|
|
2751
2930
|
}
|
|
2752
|
-
else if (ch ===
|
|
2931
|
+
else if (ch === "[") {
|
|
2753
2932
|
if (buf) {
|
|
2754
2933
|
out.push(buf);
|
|
2755
|
-
buf =
|
|
2934
|
+
buf = "";
|
|
2756
2935
|
}
|
|
2757
|
-
const close = path.indexOf(
|
|
2936
|
+
const close = path.indexOf("]", i);
|
|
2758
2937
|
if (close < 0) {
|
|
2759
2938
|
buf += ch;
|
|
2760
2939
|
continue;
|
|
@@ -2786,7 +2965,8 @@ export class Mongo extends Db {
|
|
|
2786
2965
|
if (len >= 2) {
|
|
2787
2966
|
const first = id.charCodeAt(0);
|
|
2788
2967
|
const last = id.charCodeAt(len - 1);
|
|
2789
|
-
if ((first === 39 /* ' */ && last === 39) ||
|
|
2968
|
+
if ((first === 39 /* ' */ && last === 39) ||
|
|
2969
|
+
(first === 34 /* " */ && last === 34)) {
|
|
2790
2970
|
id = id.slice(1, -1);
|
|
2791
2971
|
}
|
|
2792
2972
|
}
|
|
@@ -2818,11 +2998,11 @@ export class Mongo extends Db {
|
|
|
2818
2998
|
*/
|
|
2819
2999
|
_validateAndAutoFillTerminalBracketValues(update) {
|
|
2820
3000
|
const $set = update.$set;
|
|
2821
|
-
if (!$set || typeof $set !==
|
|
3001
|
+
if (!$set || typeof $set !== "object")
|
|
2822
3002
|
return;
|
|
2823
3003
|
let hasBracket = false;
|
|
2824
3004
|
for (const k of Object.keys($set)) {
|
|
2825
|
-
if (k.indexOf(
|
|
3005
|
+
if (k.indexOf("[") >= 0) {
|
|
2826
3006
|
hasBracket = true;
|
|
2827
3007
|
break;
|
|
2828
3008
|
}
|
|
@@ -2832,17 +3012,17 @@ export class Mongo extends Db {
|
|
|
2832
3012
|
for (const path of Object.keys($set)) {
|
|
2833
3013
|
const tokens = Mongo._tokenizePath(path);
|
|
2834
3014
|
const lastToken = tokens[tokens.length - 1];
|
|
2835
|
-
const isTerminalBracket = !!(lastToken
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3015
|
+
const isTerminalBracket = !!((lastToken &&
|
|
3016
|
+
lastToken.length >= 2 &&
|
|
3017
|
+
lastToken.charCodeAt(0) === 91 /* [ */ &&
|
|
3018
|
+
lastToken.charCodeAt(lastToken.length - 1) === 93) /* ] */);
|
|
2839
3019
|
if (!isTerminalBracket)
|
|
2840
3020
|
continue;
|
|
2841
3021
|
const bracketId = Mongo._unquoteBracketId(lastToken, path); // throws on empty
|
|
2842
3022
|
const value = $set[path];
|
|
2843
3023
|
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 ?
|
|
3024
|
+
if (el == null || typeof el !== "object") {
|
|
3025
|
+
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
3026
|
}
|
|
2847
3027
|
if (el._id == null) {
|
|
2848
3028
|
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 +3035,10 @@ export class Mongo extends Db {
|
|
|
2855
3035
|
for (let i = 0; i < value.length; i++)
|
|
2856
3036
|
validateElement(value[i], ` [${i}]`);
|
|
2857
3037
|
}
|
|
2858
|
-
else if (value !== null &&
|
|
2859
|
-
|
|
3038
|
+
else if (value !== null &&
|
|
3039
|
+
value !== undefined &&
|
|
3040
|
+
typeof value === "object") {
|
|
3041
|
+
validateElement(value, "");
|
|
2860
3042
|
}
|
|
2861
3043
|
// Primitives (string/number/boolean) — pass through to existing arrayFilters
|
|
2862
3044
|
// path. Mongo would set the array element to the primitive, which is unusual
|
|
@@ -2887,12 +3069,12 @@ export class Mongo extends Db {
|
|
|
2887
3069
|
*/
|
|
2888
3070
|
_extractArrayInserts(update) {
|
|
2889
3071
|
const $set = update.$set;
|
|
2890
|
-
if (!$set || typeof $set !==
|
|
3072
|
+
if (!$set || typeof $set !== "object")
|
|
2891
3073
|
return undefined;
|
|
2892
3074
|
// Fast path: no bracket present in any key.
|
|
2893
3075
|
let hasBracket = false;
|
|
2894
3076
|
for (const k of Object.keys($set)) {
|
|
2895
|
-
if (k.indexOf(
|
|
3077
|
+
if (k.indexOf("[") >= 0) {
|
|
2896
3078
|
hasBracket = true;
|
|
2897
3079
|
break;
|
|
2898
3080
|
}
|
|
@@ -2905,19 +3087,19 @@ export class Mongo extends Db {
|
|
|
2905
3087
|
const value = $set[path];
|
|
2906
3088
|
const tokens = Mongo._tokenizePath(path);
|
|
2907
3089
|
const lastToken = tokens[tokens.length - 1];
|
|
2908
|
-
const isTerminalBracket = !!(lastToken
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3090
|
+
const isTerminalBracket = !!((lastToken &&
|
|
3091
|
+
lastToken.length >= 2 &&
|
|
3092
|
+
lastToken.charCodeAt(0) === 91 /* [ */ &&
|
|
3093
|
+
lastToken.charCodeAt(lastToken.length - 1) === 93) /* ] */);
|
|
2912
3094
|
if (isTerminalBracket && Array.isArray(value)) {
|
|
2913
3095
|
const parentTokens = tokens.slice(0, -1);
|
|
2914
3096
|
// Reject nested-bracket parent paths — combining $push semantics with
|
|
2915
3097
|
// arrayFilters-targeted parent is not cleanly expressible in mongo.
|
|
2916
3098
|
const parentHasBracket = parentTokens.some((t) => t.length > 0 && t.charCodeAt(0) === 91);
|
|
2917
3099
|
// Every element must have `_id` (required so we can dedupe before push).
|
|
2918
|
-
const allElementsHaveId = value.every((e) => e && typeof e ===
|
|
3100
|
+
const allElementsHaveId = value.every((e) => e && typeof e === "object" && e._id != null);
|
|
2919
3101
|
if (!parentHasBracket && allElementsHaveId && parentTokens.length > 0) {
|
|
2920
|
-
const fieldPath = parentTokens.join(
|
|
3102
|
+
const fieldPath = parentTokens.join(".");
|
|
2921
3103
|
const ids = value.map((e) => String(e._id));
|
|
2922
3104
|
inserts.push({ field: fieldPath, ids, elements: value });
|
|
2923
3105
|
continue;
|
|
@@ -2954,11 +3136,11 @@ export class Mongo extends Db {
|
|
|
2954
3136
|
*/
|
|
2955
3137
|
_extractArrayRemoves(update) {
|
|
2956
3138
|
const $unset = update.$unset;
|
|
2957
|
-
if (!$unset || typeof $unset !==
|
|
3139
|
+
if (!$unset || typeof $unset !== "object")
|
|
2958
3140
|
return undefined;
|
|
2959
3141
|
let hasBracket = false;
|
|
2960
3142
|
for (const k of Object.keys($unset)) {
|
|
2961
|
-
if (k.indexOf(
|
|
3143
|
+
if (k.indexOf("[") >= 0) {
|
|
2962
3144
|
hasBracket = true;
|
|
2963
3145
|
break;
|
|
2964
3146
|
}
|
|
@@ -2970,15 +3152,15 @@ export class Mongo extends Db {
|
|
|
2970
3152
|
for (const path of Object.keys($unset)) {
|
|
2971
3153
|
const tokens = Mongo._tokenizePath(path);
|
|
2972
3154
|
const lastToken = tokens[tokens.length - 1];
|
|
2973
|
-
const isTerminalBracket = !!(lastToken
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3155
|
+
const isTerminalBracket = !!((lastToken &&
|
|
3156
|
+
lastToken.length >= 2 &&
|
|
3157
|
+
lastToken.charCodeAt(0) === 91 /* [ */ &&
|
|
3158
|
+
lastToken.charCodeAt(lastToken.length - 1) === 93) /* ] */);
|
|
2977
3159
|
if (isTerminalBracket) {
|
|
2978
3160
|
const parentTokens = tokens.slice(0, -1);
|
|
2979
3161
|
const parentHasBracket = parentTokens.some((t) => t.length > 0 && t.charCodeAt(0) === 91);
|
|
2980
3162
|
if (!parentHasBracket && parentTokens.length > 0) {
|
|
2981
|
-
const fieldPath = parentTokens.join(
|
|
3163
|
+
const fieldPath = parentTokens.join(".");
|
|
2982
3164
|
const id = Mongo._unquoteBracketId(lastToken, path);
|
|
2983
3165
|
if (!removesByField.has(fieldPath))
|
|
2984
3166
|
removesByField.set(fieldPath, []);
|
|
@@ -2993,7 +3175,10 @@ export class Mongo extends Db {
|
|
|
2993
3175
|
update.$unset = remaining;
|
|
2994
3176
|
if (Object.keys(remaining).length === 0)
|
|
2995
3177
|
delete update.$unset;
|
|
2996
|
-
return Array.from(removesByField.entries()).map(([field, ids]) => ({
|
|
3178
|
+
return Array.from(removesByField.entries()).map(([field, ids]) => ({
|
|
3179
|
+
field,
|
|
3180
|
+
ids,
|
|
3181
|
+
}));
|
|
2997
3182
|
}
|
|
2998
3183
|
/**
|
|
2999
3184
|
* Extracts SINGLE-bracket bracket-by-_id paths from `$set`/`$unset`, grouped
|
|
@@ -3029,11 +3214,11 @@ export class Mongo extends Db {
|
|
|
3029
3214
|
};
|
|
3030
3215
|
const processOp = (opName) => {
|
|
3031
3216
|
const op = update[opName];
|
|
3032
|
-
if (!op || typeof op !==
|
|
3217
|
+
if (!op || typeof op !== "object")
|
|
3033
3218
|
return;
|
|
3034
3219
|
let hasBracket = false;
|
|
3035
3220
|
for (const k of Object.keys(op)) {
|
|
3036
|
-
if (k.indexOf(
|
|
3221
|
+
if (k.indexOf("[") >= 0) {
|
|
3037
3222
|
hasBracket = true;
|
|
3038
3223
|
break;
|
|
3039
3224
|
}
|
|
@@ -3048,18 +3233,20 @@ export class Mongo extends Db {
|
|
|
3048
3233
|
let bracketIdx = -1;
|
|
3049
3234
|
for (let i = 0; i < tokens.length; i++) {
|
|
3050
3235
|
const t = tokens[i];
|
|
3051
|
-
if (t.length >= 2 &&
|
|
3236
|
+
if (t.length >= 2 &&
|
|
3237
|
+
t.charCodeAt(0) === 91 /* [ */ &&
|
|
3238
|
+
t.charCodeAt(t.length - 1) === 93 /* ] */) {
|
|
3052
3239
|
bracketCount++;
|
|
3053
3240
|
bracketIdx = i;
|
|
3054
3241
|
}
|
|
3055
3242
|
}
|
|
3056
3243
|
if (bracketCount === 1 && bracketIdx > 0) {
|
|
3057
|
-
const parentField = tokens.slice(0, bracketIdx).join(
|
|
3244
|
+
const parentField = tokens.slice(0, bracketIdx).join(".");
|
|
3058
3245
|
const id = Mongo._unquoteBracketId(tokens[bracketIdx], path);
|
|
3059
|
-
const restPath = tokens.slice(bracketIdx + 1).join(
|
|
3246
|
+
const restPath = tokens.slice(bracketIdx + 1).join(".");
|
|
3060
3247
|
const elOps = ensureEntry(parentField, id);
|
|
3061
|
-
if (opName ===
|
|
3062
|
-
if (restPath ===
|
|
3248
|
+
if (opName === "$set") {
|
|
3249
|
+
if (restPath === "") {
|
|
3063
3250
|
// `arr[id]: <obj>` — whole-element replace.
|
|
3064
3251
|
elOps.replace = op[path];
|
|
3065
3252
|
}
|
|
@@ -3070,7 +3257,7 @@ export class Mongo extends Db {
|
|
|
3070
3257
|
else {
|
|
3071
3258
|
// `$unset arr[id].field` — sub-field unset. Terminal `$unset arr[id]`
|
|
3072
3259
|
// is already extracted by `_extractArrayRemoves`.
|
|
3073
|
-
if (restPath ===
|
|
3260
|
+
if (restPath === "") {
|
|
3074
3261
|
remaining[path] = op[path];
|
|
3075
3262
|
continue;
|
|
3076
3263
|
}
|
|
@@ -3088,8 +3275,8 @@ export class Mongo extends Db {
|
|
|
3088
3275
|
delete update[opName];
|
|
3089
3276
|
}
|
|
3090
3277
|
};
|
|
3091
|
-
processOp(
|
|
3092
|
-
processOp(
|
|
3278
|
+
processOp("$set");
|
|
3279
|
+
processOp("$unset");
|
|
3093
3280
|
return extractedAny ? result : undefined;
|
|
3094
3281
|
}
|
|
3095
3282
|
/**
|
|
@@ -3108,8 +3295,8 @@ export class Mongo extends Db {
|
|
|
3108
3295
|
const filteredInput = {
|
|
3109
3296
|
$filter: {
|
|
3110
3297
|
input: { $ifNull: [baseInputPath, []] },
|
|
3111
|
-
as:
|
|
3112
|
-
cond: { $not: { $in: [
|
|
3298
|
+
as: "el",
|
|
3299
|
+
cond: { $not: { $in: ["$$el._id", dedupedRemoveIds] } },
|
|
3113
3300
|
},
|
|
3114
3301
|
};
|
|
3115
3302
|
const concatExpr = {
|
|
@@ -3120,15 +3307,15 @@ export class Mongo extends Db {
|
|
|
3120
3307
|
const branches = [];
|
|
3121
3308
|
for (const [id, elOps] of ops.elementOps.entries()) {
|
|
3122
3309
|
branches.push({
|
|
3123
|
-
case: { $eq: [
|
|
3124
|
-
then: Mongo._buildElementMergeExpr(
|
|
3310
|
+
case: { $eq: ["$$el._id", Mongo._normalizeBracketId(id)] },
|
|
3311
|
+
then: Mongo._buildElementMergeExpr("$$el", elOps),
|
|
3125
3312
|
});
|
|
3126
3313
|
}
|
|
3127
3314
|
return {
|
|
3128
3315
|
$map: {
|
|
3129
3316
|
input: concatExpr,
|
|
3130
|
-
as:
|
|
3131
|
-
in: { $switch: { branches, default:
|
|
3317
|
+
as: "el",
|
|
3318
|
+
in: { $switch: { branches, default: "$$el" } },
|
|
3132
3319
|
},
|
|
3133
3320
|
};
|
|
3134
3321
|
}
|
|
@@ -3211,13 +3398,13 @@ export class Mongo extends Db {
|
|
|
3211
3398
|
return {
|
|
3212
3399
|
$let: {
|
|
3213
3400
|
vars: { replaced: replaceExpr },
|
|
3214
|
-
in: wrapWithNestedArrays(
|
|
3401
|
+
in: wrapWithNestedArrays("$$replaced", "$$replaced"),
|
|
3215
3402
|
},
|
|
3216
3403
|
};
|
|
3217
3404
|
}
|
|
3218
3405
|
const root = { directs: {}, nested: {}, unsets: [] };
|
|
3219
3406
|
const walkInsert = (path, isUnset, value) => {
|
|
3220
|
-
const parts = path.split(
|
|
3407
|
+
const parts = path.split(".");
|
|
3221
3408
|
let node = root;
|
|
3222
3409
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
3223
3410
|
const key = parts[i];
|
|
@@ -3255,15 +3442,17 @@ export class Mongo extends Db {
|
|
|
3255
3442
|
overlay[k] = buildOverlay(sub, subPath);
|
|
3256
3443
|
}
|
|
3257
3444
|
// Merge sets/nested onto the existing object (or {} if absent).
|
|
3258
|
-
let merged = {
|
|
3445
|
+
let merged = {
|
|
3446
|
+
$mergeObjects: [{ $ifNull: [basePath, {}] }, overlay],
|
|
3447
|
+
};
|
|
3259
3448
|
// Drop unset top-level keys at this level via kv-filter.
|
|
3260
3449
|
if (node.unsets.length > 0) {
|
|
3261
3450
|
merged = {
|
|
3262
3451
|
$arrayToObject: {
|
|
3263
3452
|
$filter: {
|
|
3264
3453
|
input: { $objectToArray: merged },
|
|
3265
|
-
as:
|
|
3266
|
-
cond: { $not: { $in: [
|
|
3454
|
+
as: "kv",
|
|
3455
|
+
cond: { $not: { $in: ["$$kv.k", node.unsets] } },
|
|
3267
3456
|
},
|
|
3268
3457
|
},
|
|
3269
3458
|
};
|
|
@@ -3273,11 +3462,11 @@ export class Mongo extends Db {
|
|
|
3273
3462
|
// If a replace is also requested, treat replacement as the new base.
|
|
3274
3463
|
// Wrap with `$let` since a literal object isn't addressable by string field path.
|
|
3275
3464
|
if (hasReplace) {
|
|
3276
|
-
const overlayExpr = buildOverlay(root,
|
|
3465
|
+
const overlayExpr = buildOverlay(root, "$$replaced");
|
|
3277
3466
|
return {
|
|
3278
3467
|
$let: {
|
|
3279
3468
|
vars: { replaced: { $literal: ops.replace } },
|
|
3280
|
-
in: wrapWithNestedArrays(overlayExpr,
|
|
3469
|
+
in: wrapWithNestedArrays(overlayExpr, "$$replaced"),
|
|
3281
3470
|
},
|
|
3282
3471
|
};
|
|
3283
3472
|
}
|
|
@@ -3298,7 +3487,7 @@ export class Mongo extends Db {
|
|
|
3298
3487
|
static _drainIncAndCurrentDateToPipelineStages(update) {
|
|
3299
3488
|
const stages = [];
|
|
3300
3489
|
const inc = update.$inc;
|
|
3301
|
-
if (inc && typeof inc ===
|
|
3490
|
+
if (inc && typeof inc === "object") {
|
|
3302
3491
|
const setObj = {};
|
|
3303
3492
|
for (const [k, v] of Object.entries(inc)) {
|
|
3304
3493
|
setObj[k] = { $add: [{ $ifNull: [`$${k}`, 0] }, v] };
|
|
@@ -3308,13 +3497,15 @@ export class Mongo extends Db {
|
|
|
3308
3497
|
delete update.$inc;
|
|
3309
3498
|
}
|
|
3310
3499
|
const cd = update.$currentDate;
|
|
3311
|
-
if (cd && typeof cd ===
|
|
3500
|
+
if (cd && typeof cd === "object") {
|
|
3312
3501
|
const setObj = {};
|
|
3313
3502
|
for (const [k, spec] of Object.entries(cd)) {
|
|
3314
3503
|
let isTimestamp = false;
|
|
3315
|
-
if (spec &&
|
|
3504
|
+
if (spec &&
|
|
3505
|
+
typeof spec === "object" &&
|
|
3506
|
+
spec.$type === "timestamp")
|
|
3316
3507
|
isTimestamp = true;
|
|
3317
|
-
setObj[k] = isTimestamp ?
|
|
3508
|
+
setObj[k] = isTimestamp ? "$$CLUSTER_TIME" : "$$NOW";
|
|
3318
3509
|
}
|
|
3319
3510
|
if (Object.keys(setObj).length > 0)
|
|
3320
3511
|
stages.push({ $set: setObj });
|
|
@@ -3370,14 +3561,18 @@ export class Mongo extends Db {
|
|
|
3370
3561
|
continue;
|
|
3371
3562
|
let bracketCount = 0;
|
|
3372
3563
|
for (const t of tokens) {
|
|
3373
|
-
if (t.length >= 2 &&
|
|
3564
|
+
if (t.length >= 2 &&
|
|
3565
|
+
t.charCodeAt(0) === 91 &&
|
|
3566
|
+
t.charCodeAt(t.length - 1) === 93) {
|
|
3374
3567
|
bracketCount++;
|
|
3375
3568
|
}
|
|
3376
3569
|
}
|
|
3377
3570
|
if (bracketCount < 2)
|
|
3378
3571
|
continue;
|
|
3379
3572
|
const last = tokens[tokens.length - 1];
|
|
3380
|
-
if (last.length >= 2 &&
|
|
3573
|
+
if (last.length >= 2 &&
|
|
3574
|
+
last.charCodeAt(0) === 91 &&
|
|
3575
|
+
last.charCodeAt(last.length - 1) === 93) {
|
|
3381
3576
|
return true;
|
|
3382
3577
|
}
|
|
3383
3578
|
}
|
|
@@ -3385,13 +3580,51 @@ export class Mongo extends Db {
|
|
|
3385
3580
|
};
|
|
3386
3581
|
const $setEarlyForNested = update.$set;
|
|
3387
3582
|
const $unsetEarlyForNested = update.$unset;
|
|
3388
|
-
const hasNestedTerminal = hasNestedTerminalBracket($setEarlyForNested) ||
|
|
3583
|
+
const hasNestedTerminal = hasNestedTerminalBracket($setEarlyForNested) ||
|
|
3584
|
+
hasNestedTerminalBracket($unsetEarlyForNested);
|
|
3389
3585
|
// Pure sub-field updates only (no inserts, no removes, no nested-terminal) —
|
|
3390
3586
|
// keep on legacy arrayFilters path (faster, smaller payload, doesn't fight
|
|
3391
3587
|
// $inc/$currentDate). Nested sub-field paths like `arr[A].sub[B].field`
|
|
3392
3588
|
// ARE supported via nested arrayFilters here.
|
|
3393
3589
|
if (!inserts && !removes && !hasNestedTerminal) {
|
|
3394
3590
|
const arrayFilters = this._extractArrayFilters(update);
|
|
3591
|
+
// Detect conflict: bare-field `$set` with array value alongside a
|
|
3592
|
+
// bracket-transformed sub-path on the same field (`postavke: [...]` +
|
|
3593
|
+
// `postavke.$[f0].nbc`). MongoDB silently drops the sub-field update
|
|
3594
|
+
// in this case — throw so the caller gets an error instead of data loss.
|
|
3595
|
+
if (arrayFilters) {
|
|
3596
|
+
// Collect bare-field array keys and sub-paths from BOTH
|
|
3597
|
+
// $set AND $unset — $set.postavke conflicts with both
|
|
3598
|
+
// $set.postavke.$[f0].nbc AND $unset.postavke.$[f0].nbc.
|
|
3599
|
+
const $setCheck = update.$set;
|
|
3600
|
+
const $unsetCheck = update.$unset;
|
|
3601
|
+
const bareFields = [];
|
|
3602
|
+
const subPaths = [];
|
|
3603
|
+
if ($setCheck) {
|
|
3604
|
+
for (const k of Object.keys($setCheck)) {
|
|
3605
|
+
(k.includes(".") || k.includes("[") ? subPaths : bareFields).push(k);
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
if ($unsetCheck) {
|
|
3609
|
+
for (const k of Object.keys($unsetCheck)) {
|
|
3610
|
+
(k.includes(".") || k.includes("[") ? subPaths : bareFields).push(k);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
for (const bare of bareFields) {
|
|
3614
|
+
// Only $set bare fields with array values cause conflicts.
|
|
3615
|
+
if ($setCheck && Array.isArray($setCheck[bare])) {
|
|
3616
|
+
for (const sub of subPaths) {
|
|
3617
|
+
if (sub.startsWith(bare + ".") || sub.startsWith(bare + "[")) {
|
|
3618
|
+
throw new Error(`cry-db: bare-field array replacement "${bare}" conflicts ` +
|
|
3619
|
+
`with bracket-by-_id sub-field path "${sub}" on the same ` +
|
|
3620
|
+
`array. Replace the bare-field assignment with a ` +
|
|
3621
|
+
`terminal-bracket form (e.g. \`${bare}[<id>]: [...]\`) ` +
|
|
3622
|
+
`when combining array updates with bracket-by-_id paths.`);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3395
3628
|
return arrayFilters ? { update, arrayFilters } : { update };
|
|
3396
3629
|
}
|
|
3397
3630
|
// Removes-only fast path: `$pull` on update doc. Coexists fine with `$set` on
|
|
@@ -3412,15 +3645,14 @@ export class Mongo extends Db {
|
|
|
3412
3645
|
for (const k of Object.keys($setEarly)) {
|
|
3413
3646
|
if (k === field)
|
|
3414
3647
|
return true;
|
|
3415
|
-
if (k.startsWith(field +
|
|
3648
|
+
if (k.startsWith(field + "."))
|
|
3416
3649
|
return true;
|
|
3417
|
-
if (k.startsWith(field +
|
|
3650
|
+
if (k.startsWith(field + "["))
|
|
3418
3651
|
return true;
|
|
3419
3652
|
}
|
|
3420
3653
|
return false;
|
|
3421
3654
|
};
|
|
3422
|
-
const removesOnlyNoSetConflict = removes && !inserts
|
|
3423
|
-
&& !removes.some(rm => setTargetsField(rm.field));
|
|
3655
|
+
const removesOnlyNoSetConflict = removes && !inserts && !removes.some((rm) => setTargetsField(rm.field));
|
|
3424
3656
|
if (removesOnlyNoSetConflict) {
|
|
3425
3657
|
const pullOp = (update.$pull || {});
|
|
3426
3658
|
for (const rm of removes) {
|
|
@@ -3465,12 +3697,20 @@ export class Mongo extends Db {
|
|
|
3465
3697
|
const fieldOps = new Map();
|
|
3466
3698
|
const ensureField = (container, field) => {
|
|
3467
3699
|
if (!container.has(field))
|
|
3468
|
-
container.set(field, {
|
|
3700
|
+
container.set(field, {
|
|
3701
|
+
removeIds: [],
|
|
3702
|
+
insertElements: [],
|
|
3703
|
+
elementOps: new Map(),
|
|
3704
|
+
});
|
|
3469
3705
|
return container.get(field);
|
|
3470
3706
|
};
|
|
3471
3707
|
const ensureElement = (fops, id) => {
|
|
3472
3708
|
if (!fops.elementOps.has(id))
|
|
3473
|
-
fops.elementOps.set(id, {
|
|
3709
|
+
fops.elementOps.set(id, {
|
|
3710
|
+
sets: {},
|
|
3711
|
+
unsets: [],
|
|
3712
|
+
nestedArrayOps: new Map(),
|
|
3713
|
+
});
|
|
3474
3714
|
const elOps = fops.elementOps.get(id);
|
|
3475
3715
|
if (!elOps.nestedArrayOps)
|
|
3476
3716
|
elOps.nestedArrayOps = new Map();
|
|
@@ -3514,7 +3754,7 @@ export class Mongo extends Db {
|
|
|
3514
3754
|
return;
|
|
3515
3755
|
const remaining = {};
|
|
3516
3756
|
for (const path of Object.keys(op)) {
|
|
3517
|
-
if (path.indexOf(
|
|
3757
|
+
if (path.indexOf("[") < 0) {
|
|
3518
3758
|
remaining[path] = op[path];
|
|
3519
3759
|
continue;
|
|
3520
3760
|
}
|
|
@@ -3522,14 +3762,16 @@ export class Mongo extends Db {
|
|
|
3522
3762
|
const brackets = [];
|
|
3523
3763
|
for (let i = 0; i < tokens.length; i++) {
|
|
3524
3764
|
const t = tokens[i];
|
|
3525
|
-
if (t.length >= 2 &&
|
|
3765
|
+
if (t.length >= 2 &&
|
|
3766
|
+
t.charCodeAt(0) === 91 &&
|
|
3767
|
+
t.charCodeAt(t.length - 1) === 93) {
|
|
3526
3768
|
brackets.push({ idx: i, id: Mongo._unquoteBracketId(t, path) });
|
|
3527
3769
|
}
|
|
3528
3770
|
}
|
|
3529
3771
|
if (brackets.length < 2) {
|
|
3530
3772
|
// Single-bracket: extractors above should have handled it; if it
|
|
3531
3773
|
// remained, it's malformed (e.g. empty parent). Drop with warning.
|
|
3532
|
-
warnings.push({ path, value: op[path], op: opName, kind:
|
|
3774
|
+
warnings.push({ path, value: op[path], op: opName, kind: "dropped" });
|
|
3533
3775
|
continue;
|
|
3534
3776
|
}
|
|
3535
3777
|
// Walk all but the innermost bracket — descend through nestedArrayOps.
|
|
@@ -3538,7 +3780,7 @@ export class Mongo extends Db {
|
|
|
3538
3780
|
for (let bi = 0; bi < brackets.length - 1; bi++) {
|
|
3539
3781
|
const bracket = brackets[bi];
|
|
3540
3782
|
const fieldStart = bi === 0 ? 0 : brackets[bi - 1].idx + 1;
|
|
3541
|
-
const fieldName = tokens.slice(fieldStart, bracket.idx).join(
|
|
3783
|
+
const fieldName = tokens.slice(fieldStart, bracket.idx).join(".");
|
|
3542
3784
|
if (!fieldName) {
|
|
3543
3785
|
ok = false;
|
|
3544
3786
|
break;
|
|
@@ -3548,21 +3790,21 @@ export class Mongo extends Db {
|
|
|
3548
3790
|
currentMap = elOps.nestedArrayOps;
|
|
3549
3791
|
}
|
|
3550
3792
|
if (!ok) {
|
|
3551
|
-
warnings.push({ path, value: op[path], op: opName, kind:
|
|
3793
|
+
warnings.push({ path, value: op[path], op: opName, kind: "dropped" });
|
|
3552
3794
|
continue;
|
|
3553
3795
|
}
|
|
3554
3796
|
const lastB = brackets[brackets.length - 1];
|
|
3555
3797
|
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(
|
|
3798
|
+
const innerFieldName = tokens.slice(fieldStart, lastB.idx).join(".");
|
|
3799
|
+
const restPath = tokens.slice(lastB.idx + 1).join(".");
|
|
3558
3800
|
if (!innerFieldName) {
|
|
3559
|
-
warnings.push({ path, value: op[path], op: opName, kind:
|
|
3801
|
+
warnings.push({ path, value: op[path], op: opName, kind: "dropped" });
|
|
3560
3802
|
continue;
|
|
3561
3803
|
}
|
|
3562
3804
|
const innerFOps = ensureField(currentMap, innerFieldName);
|
|
3563
3805
|
const value = op[path];
|
|
3564
|
-
if (opName ===
|
|
3565
|
-
if (restPath ===
|
|
3806
|
+
if (opName === "$set") {
|
|
3807
|
+
if (restPath === "") {
|
|
3566
3808
|
if (Array.isArray(value)) {
|
|
3567
3809
|
innerFOps.removeIds.push(lastB.id);
|
|
3568
3810
|
innerFOps.insertElements.push(...value);
|
|
@@ -3580,7 +3822,7 @@ export class Mongo extends Db {
|
|
|
3580
3822
|
}
|
|
3581
3823
|
else {
|
|
3582
3824
|
// $unset
|
|
3583
|
-
if (restPath ===
|
|
3825
|
+
if (restPath === "") {
|
|
3584
3826
|
innerFOps.removeIds.push(lastB.id);
|
|
3585
3827
|
}
|
|
3586
3828
|
else {
|
|
@@ -3593,8 +3835,8 @@ export class Mongo extends Db {
|
|
|
3593
3835
|
if (Object.keys(remaining).length === 0)
|
|
3594
3836
|
delete update[opName];
|
|
3595
3837
|
};
|
|
3596
|
-
processNestedPaths(update.$set,
|
|
3597
|
-
processNestedPaths(update.$unset,
|
|
3838
|
+
processNestedPaths(update.$set, "$set");
|
|
3839
|
+
processNestedPaths(update.$unset, "$unset");
|
|
3598
3840
|
// Detect same-id overlap at NESTED levels: a "new base" (terminal-array
|
|
3599
3841
|
// insert OR whole-element replace) targeting the same `_id` as a
|
|
3600
3842
|
// sub-field set/unset on the same element. Both apply — new base goes
|
|
@@ -3614,7 +3856,7 @@ export class Mongo extends Db {
|
|
|
3614
3856
|
const detectOverlaps = (container, parentPathRepr) => {
|
|
3615
3857
|
for (const [field, fops] of container.entries()) {
|
|
3616
3858
|
const fullField = parentPathRepr ? `${parentPathRepr}.${field}` : field;
|
|
3617
|
-
const isNested = parentPathRepr !==
|
|
3859
|
+
const isNested = parentPathRepr !== "";
|
|
3618
3860
|
// Flavor 1: terminal-array insert + sub-field on same id.
|
|
3619
3861
|
const removeIdSet = new Set(fops.removeIds);
|
|
3620
3862
|
if (isNested) {
|
|
@@ -3622,16 +3864,16 @@ export class Mongo extends Db {
|
|
|
3622
3864
|
const elOps = fops.elementOps.get(id);
|
|
3623
3865
|
if (!elOps)
|
|
3624
3866
|
continue;
|
|
3625
|
-
const hasOps = Object.keys(elOps.sets).length > 0
|
|
3626
|
-
|
|
3627
|
-
|
|
3867
|
+
const hasOps = Object.keys(elOps.sets).length > 0 ||
|
|
3868
|
+
elOps.unsets.length > 0 ||
|
|
3869
|
+
elOps.replace !== undefined;
|
|
3628
3870
|
if (hasOps) {
|
|
3629
3871
|
const insertedEl = fops.insertElements.find((e) => String(e._id) === id);
|
|
3630
3872
|
warnings.push({
|
|
3631
3873
|
path: `${fullField}[${id}]`,
|
|
3632
3874
|
value: insertedEl,
|
|
3633
|
-
op:
|
|
3634
|
-
kind:
|
|
3875
|
+
op: "$set",
|
|
3876
|
+
kind: "overlap",
|
|
3635
3877
|
});
|
|
3636
3878
|
}
|
|
3637
3879
|
}
|
|
@@ -3641,14 +3883,13 @@ export class Mongo extends Db {
|
|
|
3641
3883
|
for (const [id, elOps] of fops.elementOps.entries()) {
|
|
3642
3884
|
if (isNested && !removeIdSet.has(id)) {
|
|
3643
3885
|
const hasReplace = elOps.replace !== undefined;
|
|
3644
|
-
const hasSetsOrUnsets = Object.keys(elOps.sets).length > 0
|
|
3645
|
-
|| elOps.unsets.length > 0;
|
|
3886
|
+
const hasSetsOrUnsets = Object.keys(elOps.sets).length > 0 || elOps.unsets.length > 0;
|
|
3646
3887
|
if (hasReplace && hasSetsOrUnsets) {
|
|
3647
3888
|
warnings.push({
|
|
3648
3889
|
path: `${fullField}[${id}]`,
|
|
3649
3890
|
value: elOps.replace,
|
|
3650
|
-
op:
|
|
3651
|
-
kind:
|
|
3891
|
+
op: "$set",
|
|
3892
|
+
kind: "overlap",
|
|
3652
3893
|
});
|
|
3653
3894
|
}
|
|
3654
3895
|
}
|
|
@@ -3658,19 +3899,21 @@ export class Mongo extends Db {
|
|
|
3658
3899
|
}
|
|
3659
3900
|
}
|
|
3660
3901
|
};
|
|
3661
|
-
detectOverlaps(fieldOps,
|
|
3902
|
+
detectOverlaps(fieldOps, "");
|
|
3662
3903
|
if (warnings.length > 0) {
|
|
3663
|
-
fjLog.warn(
|
|
3904
|
+
fjLog.warn("cry-db: bracket-by-_id warnings:", warnings);
|
|
3664
3905
|
}
|
|
3665
3906
|
if (update.$set && Object.keys(update.$set).length === 0)
|
|
3666
3907
|
delete update.$set;
|
|
3667
|
-
if (update.$unset &&
|
|
3908
|
+
if (update.$unset &&
|
|
3909
|
+
Object.keys(update.$unset).length === 0)
|
|
3668
3910
|
delete update.$unset;
|
|
3669
3911
|
const pipeline = [];
|
|
3670
3912
|
if (update.$set && Object.keys(update.$set).length > 0) {
|
|
3671
3913
|
pipeline.push({ $set: update.$set });
|
|
3672
3914
|
}
|
|
3673
|
-
if (update.$unset &&
|
|
3915
|
+
if (update.$unset &&
|
|
3916
|
+
Object.keys(update.$unset).length > 0) {
|
|
3674
3917
|
pipeline.push({ $unset: Object.keys(update.$unset) });
|
|
3675
3918
|
}
|
|
3676
3919
|
// Translate $inc / $currentDate so revisions (_rev/_ts) still update in pipeline form.
|
|
@@ -3686,7 +3929,9 @@ export class Mongo extends Db {
|
|
|
3686
3929
|
},
|
|
3687
3930
|
});
|
|
3688
3931
|
}
|
|
3689
|
-
return warnings.length > 0
|
|
3932
|
+
return warnings.length > 0
|
|
3933
|
+
? { update: pipeline, warnings }
|
|
3934
|
+
: { update: pipeline };
|
|
3690
3935
|
}
|
|
3691
3936
|
async _processHashedKeys(obj) {
|
|
3692
3937
|
const hashedKeys = Object.keys(obj).filter(startsWithHashedPrefix);
|
|
@@ -3742,10 +3987,10 @@ export class Mongo extends Db {
|
|
|
3742
3987
|
}
|
|
3743
3988
|
else {
|
|
3744
3989
|
for (let key in ret) {
|
|
3745
|
-
if (startsWithHashedPrefix(key)
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3990
|
+
if (startsWithHashedPrefix(key) ||
|
|
3991
|
+
startsWithDoubleUnderscore(key) ||
|
|
3992
|
+
removeFields.has(key) ||
|
|
3993
|
+
this._removeFieldsAlways.has(key))
|
|
3749
3994
|
delete ret[key];
|
|
3750
3995
|
}
|
|
3751
3996
|
}
|