liwi-mongo 13.4.0 → 14.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -2
- package/dist/definitions/MongoConnection.d.ts +6 -1
- package/dist/definitions/MongoConnection.d.ts.map +1 -1
- package/dist/definitions/MongoCursor.d.ts.map +1 -1
- package/dist/definitions/MongoQuerySingleItem.d.ts +1 -0
- package/dist/definitions/MongoQuerySingleItem.d.ts.map +1 -1
- package/dist/definitions/MongoQuerySingleItem.test.d.ts +2 -0
- package/dist/definitions/MongoQuerySingleItem.test.d.ts.map +1 -0
- package/dist/definitions/MongoStore.d.ts +18 -4
- package/dist/definitions/MongoStore.d.ts.map +1 -1
- package/dist/definitions/SubscribeStore.integration.test.d.ts +2 -0
- package/dist/definitions/SubscribeStore.integration.test.d.ts.map +1 -0
- package/dist/definitions/index.d.ts +1 -1
- package/dist/definitions/index.d.ts.map +1 -1
- package/dist/definitions/memory/applyUpdate.d.ts +23 -0
- package/dist/definitions/memory/applyUpdate.d.ts.map +1 -0
- package/dist/definitions/memory/cloneAsStored.d.ts +10 -0
- package/dist/definitions/memory/cloneAsStored.d.ts.map +1 -0
- package/dist/definitions/memory/cloneAsStored.test.d.ts +2 -0
- package/dist/definitions/memory/cloneAsStored.test.d.ts.map +1 -0
- package/dist/definitions/memory/contract.test.d.ts +2 -0
- package/dist/definitions/memory/contract.test.d.ts.map +1 -0
- package/dist/definitions/memory/createMemoryCollection.d.ts +19 -0
- package/dist/definitions/memory/createMemoryCollection.d.ts.map +1 -0
- package/dist/definitions/memory/createMemoryCursor.d.ts +29 -0
- package/dist/definitions/memory/createMemoryCursor.d.ts.map +1 -0
- package/dist/definitions/memory/createMemoryCursor.test.d.ts +2 -0
- package/dist/definitions/memory/createMemoryCursor.test.d.ts.map +1 -0
- package/dist/definitions/memory/createMemoryDb.d.ts +7 -0
- package/dist/definitions/memory/createMemoryDb.d.ts.map +1 -0
- package/dist/definitions/memory/createMemoryMongoClient.d.ts +13 -0
- package/dist/definitions/memory/createMemoryMongoClient.d.ts.map +1 -0
- package/dist/definitions/memory/createMemoryMongoClient.test.d.ts +2 -0
- package/dist/definitions/memory/createMemoryMongoClient.test.d.ts.map +1 -0
- package/dist/definitions/memory/errors.d.ts +20 -0
- package/dist/definitions/memory/errors.d.ts.map +1 -0
- package/dist/definitions/memory/guardUnsupported.d.ts +3 -0
- package/dist/definitions/memory/guardUnsupported.d.ts.map +1 -0
- package/dist/definitions/memory/indexes/createIndexInfo.d.ts +5 -0
- package/dist/definitions/memory/indexes/createIndexInfo.d.ts.map +1 -0
- package/dist/definitions/memory/indexes/createIndexInfo.test.d.ts +2 -0
- package/dist/definitions/memory/indexes/createIndexInfo.test.d.ts.map +1 -0
- package/dist/definitions/memory/indexes/createMemoryIndexes.d.ts +19 -0
- package/dist/definitions/memory/indexes/createMemoryIndexes.d.ts.map +1 -0
- package/dist/definitions/memory/indexes/createUniqueConstraint.d.ts +16 -0
- package/dist/definitions/memory/indexes/createUniqueConstraint.d.ts.map +1 -0
- package/dist/definitions/memory/normalizeSort.d.ts +4 -0
- package/dist/definitions/memory/normalizeSort.d.ts.map +1 -0
- package/dist/definitions/memory/normalizeSort.test.d.ts +2 -0
- package/dist/definitions/memory/normalizeSort.test.d.ts.map +1 -0
- package/dist/definitions/memory/queryOptions.d.ts +3 -0
- package/dist/definitions/memory/queryOptions.d.ts.map +1 -0
- package/dist/definitions/memory/serializeKey.d.ts +6 -0
- package/dist/definitions/memory/serializeKey.d.ts.map +1 -0
- package/dist/definitions/memory/settle.d.ts +3 -0
- package/dist/definitions/memory/settle.d.ts.map +1 -0
- package/dist/definitions/memory.d.ts +2 -0
- package/dist/definitions/memory.d.ts.map +1 -0
- package/dist/definitions/writes/createReplacePipeline.d.ts +10 -0
- package/dist/definitions/writes/createReplacePipeline.d.ts.map +1 -0
- package/dist/definitions/writes/stampPartialUpdate.d.ts +9 -0
- package/dist/definitions/writes/stampPartialUpdate.d.ts.map +1 -0
- package/dist/definitions/writes/stampPartialUpdate.test.d.ts +2 -0
- package/dist/definitions/writes/stampPartialUpdate.test.d.ts.map +1 -0
- package/dist/index-node.mjs +235 -105
- package/dist/index-node.mjs.map +1 -1
- package/dist/memory-node.mjs +849 -0
- package/dist/memory-node.mjs.map +1 -0
- package/package.json +15 -8
- package/src/MongoConnection.test.ts +24 -2
- package/src/MongoConnection.ts +44 -28
- package/src/MongoCursor.ts +2 -4
- package/src/MongoQuerySingleItem.test.ts +162 -0
- package/src/MongoQuerySingleItem.ts +56 -50
- package/src/MongoStore.test.ts +250 -0
- package/src/MongoStore.ts +122 -26
- package/src/SubscribeStore.integration.test.ts +414 -0
- package/src/index.ts +1 -0
- package/src/indexes/diffIndexes.test.ts +2 -2
- package/src/indexes/normalizeIndex.test.ts +2 -2
- package/src/indexes/normalizeIndex.ts +2 -2
- package/src/memory/applyUpdate.ts +146 -0
- package/src/memory/cloneAsStored.test.ts +55 -0
- package/src/memory/cloneAsStored.ts +34 -0
- package/src/memory/contract.test.ts +967 -0
- package/src/memory/createMemoryCollection.ts +405 -0
- package/src/memory/createMemoryCursor.test.ts +78 -0
- package/src/memory/createMemoryCursor.ts +90 -0
- package/src/memory/createMemoryDb.ts +66 -0
- package/src/memory/createMemoryMongoClient.test.ts +85 -0
- package/src/memory/createMemoryMongoClient.ts +47 -0
- package/src/memory/errors.ts +85 -0
- package/src/memory/guardUnsupported.ts +25 -0
- package/src/memory/indexes/createIndexInfo.test.ts +36 -0
- package/src/memory/indexes/createIndexInfo.ts +82 -0
- package/src/memory/indexes/createMemoryIndexes.ts +178 -0
- package/src/memory/indexes/createUniqueConstraint.ts +151 -0
- package/src/memory/normalizeSort.test.ts +37 -0
- package/src/memory/normalizeSort.ts +56 -0
- package/src/memory/queryOptions.ts +3 -0
- package/src/memory/serializeKey.ts +10 -0
- package/src/memory/settle.ts +5 -0
- package/src/memory.ts +5 -0
- package/src/writes/createReplacePipeline.ts +23 -0
- package/src/writes/stampPartialUpdate.test.ts +93 -0
- package/src/writes/stampPartialUpdate.ts +84 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeSort.d.ts","sourceRoot":"","sources":["../../../src/memory/normalizeSort.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAiB,MAAM,SAAS,CAAC;AAgDnD,qEAAqE;AACrE,eAAO,MAAM,aAAa,SAAU,IAAI,KAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAM7D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeSort.test.d.ts","sourceRoot":"","sources":["../../../src/memory/normalizeSort.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryOptions.d.ts","sourceRoot":"","sources":["../../../src/memory/queryOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,OAAO,CAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializeKey.d.ts","sourceRoot":"","sources":["../../../src/memory/serializeKey.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAGmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settle.d.ts","sourceRoot":"","sources":["../../../src/memory/settle.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,eAAO,MAAM,MAAM,GAAI,MAAM,OAAO,MAAM,MAAM,KAAG,OAAO,CAAC,MAAM,CAG7D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/memory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,GAC9B,MAAM,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Document } from "mongodb";
|
|
2
|
+
import type { MongoBaseModel } from "../MongoBaseModel.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the stored document with `replacement` while keeping the stored `created`
|
|
5
|
+
* (falling back to the replacement's when the stored document has none).
|
|
6
|
+
* `$literal` keeps string values starting with `$` from being read as field paths.
|
|
7
|
+
* Update pipelines require MongoDB >= 4.2.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createReplacePipeline: (replacement: MongoBaseModel<any>) => Document[];
|
|
10
|
+
//# sourceMappingURL=createReplacePipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createReplacePipeline.d.ts","sourceRoot":"","sources":["../../../src/writes/createReplacePipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,gBACnB,cAAc,CAAC,GAAG,CAAC,KAC/B,QAAQ,EAWV,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BaseModel, Update } from "liwi-store";
|
|
2
|
+
/**
|
|
3
|
+
* Throws `ProtectedFieldUpdateError` when the update touches `created`, or touches
|
|
4
|
+
* `updated` with an operator other than `$set`, `$currentDate` or `$max`.
|
|
5
|
+
* Otherwise returns the update with `updated` set to `now`, unless the caller sets
|
|
6
|
+
* `updated` itself or `now` is `undefined`. The caller's update is never mutated.
|
|
7
|
+
*/
|
|
8
|
+
export declare const stampPartialUpdate: <Model extends BaseModel>(update: Update<Model>, now: Date | undefined) => Update<Model>;
|
|
9
|
+
//# sourceMappingURL=stampPartialUpdate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stampPartialUpdate.d.ts","sourceRoot":"","sources":["../../../src/writes/stampPartialUpdate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAoEpD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,SAAS,SAAS,UAChD,MAAM,CAAC,KAAK,CAAC,OAChB,IAAI,GAAG,SAAS,KACpB,MAAM,CAAC,KAAK,CAKd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stampPartialUpdate.test.d.ts","sourceRoot":"","sources":["../../../src/writes/stampPartialUpdate.test.ts"],"names":[],"mappings":""}
|
package/dist/index-node.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { AbstractStoreCursor, ProtectedFieldUpdateError, NotFoundError, AbstractConnection } from 'liwi-store';
|
|
1
2
|
import mongodb from 'mongodb';
|
|
2
|
-
import { AbstractStoreCursor, AbstractConnection } from 'liwi-store';
|
|
3
3
|
import { AbstractSubscribableStoreQuery, SubscribeStore } from 'liwi-subscribe-store';
|
|
4
4
|
import { Query } from 'mingo';
|
|
5
5
|
import { Logger } from 'nightingale-logger';
|
|
@@ -35,11 +35,10 @@ class MongoCursor extends AbstractStoreCursor {
|
|
|
35
35
|
if (!this._result) throw new Error("Cannot call result() before next()");
|
|
36
36
|
return Promise.resolve(this._result);
|
|
37
37
|
}
|
|
38
|
-
close() {
|
|
38
|
+
async close() {
|
|
39
39
|
if (this.cursor) {
|
|
40
|
-
this.cursor.close();
|
|
40
|
+
await this.cursor.close();
|
|
41
41
|
}
|
|
42
|
-
return Promise.resolve();
|
|
43
42
|
}
|
|
44
43
|
toArray() {
|
|
45
44
|
return this.cursor.toArray();
|
|
@@ -215,6 +214,49 @@ class MongoQuerySingleItem extends AbstractSubscribableStoreQuery {
|
|
|
215
214
|
});
|
|
216
215
|
});
|
|
217
216
|
}
|
|
217
|
+
async computeChanges(action, testCriteria) {
|
|
218
|
+
switch (action.type) {
|
|
219
|
+
case "inserted": {
|
|
220
|
+
const filtered = action.next.filter(testCriteria);
|
|
221
|
+
if (filtered.length === 0) return [];
|
|
222
|
+
return [{ type: "updated", result: this.transformer(filtered[0]) }];
|
|
223
|
+
}
|
|
224
|
+
case "deleted": {
|
|
225
|
+
const filtered = action.prev.filter(testCriteria);
|
|
226
|
+
if (filtered.length === 0) return [];
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
type: "deleted",
|
|
230
|
+
keys: filtered.map((object) => object[this.store.keyPath])
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
case "updated": {
|
|
235
|
+
const filtered = action.changes.filter(
|
|
236
|
+
([prev, next2]) => testCriteria(prev) || testCriteria(next2)
|
|
237
|
+
);
|
|
238
|
+
if (filtered.length === 0) return [];
|
|
239
|
+
if (this.options.sort) {
|
|
240
|
+
const { result } = await this.fetch((res) => res);
|
|
241
|
+
return [{ type: "updated", result }];
|
|
242
|
+
}
|
|
243
|
+
if (filtered.length > 1) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"should not match more than 1, use sort if you can have multiple match"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const [, next] = filtered[0];
|
|
249
|
+
return [
|
|
250
|
+
{
|
|
251
|
+
type: "updated",
|
|
252
|
+
result: testCriteria(next) ? this.transformer(next) : null
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
throw new Error("Unsupported type");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
218
260
|
_subscribe(callback, _includeInitial) {
|
|
219
261
|
const store = super.getSubscribeStore();
|
|
220
262
|
const testCriteria = this.createMingoTestCriteria();
|
|
@@ -229,55 +271,12 @@ class MongoQuerySingleItem extends AbstractSubscribableStoreQuery {
|
|
|
229
271
|
]);
|
|
230
272
|
}) : Promise.resolve();
|
|
231
273
|
const unsubscribe = store.subscribe(async (action) => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
type: "updated",
|
|
239
|
-
result: this.transformer(filtered[0])
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
case "deleted": {
|
|
245
|
-
const filtered = action.prev.filter(testCriteria);
|
|
246
|
-
if (filtered.length > 0) {
|
|
247
|
-
changes.push({
|
|
248
|
-
type: "deleted",
|
|
249
|
-
keys: filtered.map((object) => object[this.store.keyPath])
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
case "updated": {
|
|
255
|
-
const filtered = action.changes.filter(
|
|
256
|
-
([prev, next]) => testCriteria(prev)
|
|
257
|
-
);
|
|
258
|
-
if (filtered.length > 0) {
|
|
259
|
-
if (this.options.sort) {
|
|
260
|
-
const { result } = await this.fetch((res) => res);
|
|
261
|
-
changes.push({
|
|
262
|
-
type: "updated",
|
|
263
|
-
result
|
|
264
|
-
});
|
|
265
|
-
} else if (filtered.length !== 1) {
|
|
266
|
-
throw new Error(
|
|
267
|
-
"should not match more than 1, use sort if you can have multiple match"
|
|
268
|
-
);
|
|
269
|
-
} else {
|
|
270
|
-
const [, next] = filtered[0];
|
|
271
|
-
changes.push({
|
|
272
|
-
type: "updated",
|
|
273
|
-
result: testCriteria(next) ? this.transformer(next) : null
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
} else if (filtered.length === 0) ;
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
default:
|
|
280
|
-
throw new Error("Unsupported type");
|
|
274
|
+
let changes;
|
|
275
|
+
try {
|
|
276
|
+
changes = await this.computeChanges(action, testCriteria);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
callback(error, []);
|
|
279
|
+
return;
|
|
281
280
|
}
|
|
282
281
|
if (changes.length === 0) return;
|
|
283
282
|
callback(null, changes);
|
|
@@ -709,6 +708,72 @@ const diffIndexes = ({
|
|
|
709
708
|
};
|
|
710
709
|
const isMongoIndexPlanEmpty = (plan) => plan.toCreate.length === 0 && plan.toRecreate.length === 0 && plan.toCollMod.length === 0 && plan.toDrop.length === 0;
|
|
711
710
|
|
|
711
|
+
const createReplacePipeline = (replacement) => [
|
|
712
|
+
{
|
|
713
|
+
$replaceWith: {
|
|
714
|
+
$mergeObjects: [
|
|
715
|
+
{ $literal: replacement },
|
|
716
|
+
{
|
|
717
|
+
created: { $ifNull: ["$created", { $literal: replacement.created }] }
|
|
718
|
+
}
|
|
719
|
+
]
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
];
|
|
723
|
+
|
|
724
|
+
const protectedFields = ["created", "updated"];
|
|
725
|
+
const updatedOverrideOperators = /* @__PURE__ */ new Set([
|
|
726
|
+
"$set",
|
|
727
|
+
"$currentDate",
|
|
728
|
+
"$max"
|
|
729
|
+
]);
|
|
730
|
+
const findProtectedField = (path) => protectedFields.find(
|
|
731
|
+
(field) => path === field || path.startsWith(`${field}.`)
|
|
732
|
+
);
|
|
733
|
+
const listTouchedPaths = (operator, fields) => {
|
|
734
|
+
const keys = Object.keys(fields);
|
|
735
|
+
if (operator !== "$rename") return keys;
|
|
736
|
+
const renameTargets = Object.values(fields).filter(
|
|
737
|
+
(target) => typeof target === "string"
|
|
738
|
+
);
|
|
739
|
+
return [...keys, ...renameTargets];
|
|
740
|
+
};
|
|
741
|
+
const describeRejection = (field, operator, path) => field === "created" ? `Cannot use ${operator} on "${path}": created is set on insert and never updated` : `Cannot use ${operator} on "${path}": updated can only be set with $set, $currentDate or $max`;
|
|
742
|
+
const validateProtectedFields = (update) => {
|
|
743
|
+
let isUpdatedSetByCaller = false;
|
|
744
|
+
const operations = Object.entries(
|
|
745
|
+
update
|
|
746
|
+
);
|
|
747
|
+
for (const [operator, fields] of operations) {
|
|
748
|
+
if (!fields) continue;
|
|
749
|
+
for (const path of listTouchedPaths(operator, fields)) {
|
|
750
|
+
const field = findProtectedField(path);
|
|
751
|
+
if (!field) continue;
|
|
752
|
+
if (field === "updated" && path === "updated" && updatedOverrideOperators.has(operator)) {
|
|
753
|
+
isUpdatedSetByCaller = true;
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
throw new ProtectedFieldUpdateError(
|
|
757
|
+
describeRejection(field, operator, path)
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return { isUpdatedSetByCaller };
|
|
762
|
+
};
|
|
763
|
+
const stampPartialUpdate = (update, now) => {
|
|
764
|
+
const { isUpdatedSetByCaller } = validateProtectedFields(update);
|
|
765
|
+
if (isUpdatedSetByCaller || !now) return update;
|
|
766
|
+
const $set = { ...update.$set, updated: now };
|
|
767
|
+
return { ...update, $set };
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const resolveWriteTime = (options) => options?.touchUpdated === false ? void 0 : /* @__PURE__ */ new Date();
|
|
771
|
+
const toStoredValues = (values) => Object.fromEntries(
|
|
772
|
+
Object.entries(values).map(([field, value]) => [
|
|
773
|
+
field,
|
|
774
|
+
value === void 0 ? null : value
|
|
775
|
+
])
|
|
776
|
+
);
|
|
712
777
|
class MongoStore {
|
|
713
778
|
keyPath = "_id";
|
|
714
779
|
connection;
|
|
@@ -802,11 +867,37 @@ class MongoStore {
|
|
|
802
867
|
}
|
|
803
868
|
return object;
|
|
804
869
|
}
|
|
870
|
+
/**
|
|
871
|
+
* Always sets `updated` to the write time and keeps the stored `created`: the
|
|
872
|
+
* object's own `created` and `updated` are ignored. The object is not mutated.
|
|
873
|
+
*/
|
|
805
874
|
async replaceOne(object) {
|
|
806
|
-
if (!object.updated) object.updated = /* @__PURE__ */ new Date();
|
|
807
875
|
const collection = await this.collection;
|
|
808
|
-
await collection.
|
|
809
|
-
|
|
876
|
+
const next = await collection.findOneAndUpdate(
|
|
877
|
+
{ _id: object._id },
|
|
878
|
+
createReplacePipeline({ ...object, updated: /* @__PURE__ */ new Date() }),
|
|
879
|
+
{ returnDocument: "after" }
|
|
880
|
+
);
|
|
881
|
+
if (!next) {
|
|
882
|
+
throw new NotFoundError(`Document not found: ${String(object._id)}`);
|
|
883
|
+
}
|
|
884
|
+
return next;
|
|
885
|
+
}
|
|
886
|
+
/** Same rules as `replaceOne`; `next` is derived from the atomic `prev`, no read back. */
|
|
887
|
+
async replaceOneWithInfo(object) {
|
|
888
|
+
const replacement = { ...object, updated: /* @__PURE__ */ new Date() };
|
|
889
|
+
const collection = await this.collection;
|
|
890
|
+
const prev = await collection.findOneAndUpdate(
|
|
891
|
+
{ _id: object._id },
|
|
892
|
+
createReplacePipeline(replacement),
|
|
893
|
+
{ returnDocument: "before" }
|
|
894
|
+
);
|
|
895
|
+
if (!prev) return void 0;
|
|
896
|
+
const storedCreated = prev.created;
|
|
897
|
+
return {
|
|
898
|
+
prev,
|
|
899
|
+
next: { ...replacement, created: storedCreated ?? replacement.created }
|
|
900
|
+
};
|
|
810
901
|
}
|
|
811
902
|
async upsertOne(object, setOnInsertPartialObject) {
|
|
812
903
|
const result = await this.upsertOneWithInfo(
|
|
@@ -816,6 +907,11 @@ class MongoStore {
|
|
|
816
907
|
return result.object;
|
|
817
908
|
}
|
|
818
909
|
async upsertOneWithInfo(object, setOnInsertPartialObject) {
|
|
910
|
+
if (setOnInsertPartialObject && "updated" in setOnInsertPartialObject) {
|
|
911
|
+
throw new ProtectedFieldUpdateError(
|
|
912
|
+
'Cannot use $setOnInsert on "updated": upsert always sets it with $set, pass it in the object instead'
|
|
913
|
+
);
|
|
914
|
+
}
|
|
819
915
|
const $setOnInsert = {
|
|
820
916
|
// @ts-expect-error -- created is Date as set in BaseModel
|
|
821
917
|
created: object.created || /* @__PURE__ */ new Date(),
|
|
@@ -840,7 +936,7 @@ class MongoStore {
|
|
|
840
936
|
object
|
|
841
937
|
};
|
|
842
938
|
}
|
|
843
|
-
const next = { ...prev,
|
|
939
|
+
const next = { ...prev, ...toStoredValues($set) };
|
|
844
940
|
return {
|
|
845
941
|
resolvedAs: "updated",
|
|
846
942
|
inserted: false,
|
|
@@ -851,34 +947,62 @@ class MongoStore {
|
|
|
851
947
|
replaceSeveral(objects) {
|
|
852
948
|
return Promise.all(objects.map((object) => this.replaceOne(object)));
|
|
853
949
|
}
|
|
854
|
-
async partialUpdateByKey(key, partialUpdate, criteria) {
|
|
950
|
+
async partialUpdateByKey(key, partialUpdate, criteria, options) {
|
|
951
|
+
const update = stampPartialUpdate(partialUpdate, resolveWriteTime(options));
|
|
855
952
|
const collection = await this.collection;
|
|
856
|
-
const
|
|
953
|
+
const next = await collection.findOneAndUpdate(
|
|
857
954
|
{ _id: key, ...criteria },
|
|
858
|
-
|
|
955
|
+
update,
|
|
956
|
+
{ returnDocument: "after" }
|
|
957
|
+
);
|
|
958
|
+
if (!next) throw new NotFoundError(`Document not found: ${String(key)}`);
|
|
959
|
+
return next;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* `prev` is read atomically with the write. `next` is read right after it,
|
|
963
|
+
* so it may already include a later concurrent write, which emits its own change.
|
|
964
|
+
* Resolves `undefined` when no document matches.
|
|
965
|
+
*/
|
|
966
|
+
async partialUpdateByKeyWithInfo(key, partialUpdate, criteria, options) {
|
|
967
|
+
const update = stampPartialUpdate(partialUpdate, resolveWriteTime(options));
|
|
968
|
+
const collection = await this.collection;
|
|
969
|
+
const prev = await collection.findOneAndUpdate(
|
|
970
|
+
{ _id: key, ...criteria },
|
|
971
|
+
update,
|
|
972
|
+
{ returnDocument: "before" }
|
|
973
|
+
);
|
|
974
|
+
if (!prev) return void 0;
|
|
975
|
+
return { prev, next: await this.findByKey(key) };
|
|
976
|
+
}
|
|
977
|
+
partialUpdateOne(object, partialUpdate, options) {
|
|
978
|
+
return this.partialUpdateByKey(
|
|
979
|
+
object._id,
|
|
980
|
+
partialUpdate,
|
|
981
|
+
void 0,
|
|
982
|
+
options
|
|
859
983
|
);
|
|
860
|
-
if (!commandResult.acknowledged) {
|
|
861
|
-
console.error(commandResult);
|
|
862
|
-
throw new Error("Update failed");
|
|
863
|
-
}
|
|
864
|
-
const object = await this.findByKey(key);
|
|
865
|
-
return object;
|
|
866
984
|
}
|
|
867
|
-
|
|
868
|
-
|
|
985
|
+
/** One `updateMany` command; every document gets the same `updated`. */
|
|
986
|
+
async partialUpdateMany(criteria, partialUpdate, options) {
|
|
987
|
+
const update = stampPartialUpdate(partialUpdate, resolveWriteTime(options));
|
|
988
|
+
const collection = await this.collection;
|
|
989
|
+
await collection.updateMany(
|
|
990
|
+
criteria,
|
|
991
|
+
update
|
|
992
|
+
);
|
|
869
993
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
874
|
-
collection.updateMany(criteria, partialUpdate)
|
|
875
|
-
)
|
|
876
|
-
).then((res) => void 0);
|
|
994
|
+
async deleteByKey(key, criteria) {
|
|
995
|
+
const collection = await this.collection;
|
|
996
|
+
await collection.deleteOne({ _id: key, ...criteria });
|
|
877
997
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
998
|
+
async deleteByKeyWithInfo(key, criteria) {
|
|
999
|
+
const collection = await this.collection;
|
|
1000
|
+
const prev = await collection.findOneAndDelete({
|
|
1001
|
+
_id: key,
|
|
1002
|
+
...criteria
|
|
1003
|
+
});
|
|
1004
|
+
if (!prev) return void 0;
|
|
1005
|
+
return { prev };
|
|
882
1006
|
}
|
|
883
1007
|
deleteOne(object) {
|
|
884
1008
|
return this.deleteByKey(object._id);
|
|
@@ -933,9 +1057,12 @@ class MongoConnection extends AbstractConnection {
|
|
|
933
1057
|
_connection;
|
|
934
1058
|
_connecting;
|
|
935
1059
|
connectionFailed;
|
|
936
|
-
// TODO interface
|
|
937
1060
|
constructor(config) {
|
|
938
1061
|
super();
|
|
1062
|
+
if ("client" in config) {
|
|
1063
|
+
this.useClient(config.client, "[client]");
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
939
1066
|
if (!config.database) {
|
|
940
1067
|
throw new Error("Missing config database");
|
|
941
1068
|
}
|
|
@@ -953,31 +1080,7 @@ class MongoConnection extends AbstractConnection {
|
|
|
953
1080
|
logger$1.info("connecting", { connectionStringRedacted });
|
|
954
1081
|
const connectPromise = mongodb.MongoClient.connect(connectionString).then((connection) => {
|
|
955
1082
|
logger$1.info("connected", { connectionStringRedacted });
|
|
956
|
-
|
|
957
|
-
logger$1.warn("close", { connectionStringRedacted });
|
|
958
|
-
this.connectionFailed = true;
|
|
959
|
-
this.getConnection = () => {
|
|
960
|
-
throw new Error("MongoDB connection closed");
|
|
961
|
-
};
|
|
962
|
-
});
|
|
963
|
-
connection.on("timeout", () => {
|
|
964
|
-
logger$1.warn("timeout", { connectionStringRedacted });
|
|
965
|
-
this.connectionFailed = true;
|
|
966
|
-
this.getConnection = () => {
|
|
967
|
-
throw new Error("MongoDB connection timeout");
|
|
968
|
-
};
|
|
969
|
-
});
|
|
970
|
-
connection.on("reconnect", () => {
|
|
971
|
-
logger$1.warn("reconnect", { connectionStringRedacted });
|
|
972
|
-
this.connectionFailed = false;
|
|
973
|
-
this.getConnection = () => Promise.resolve(this._connection);
|
|
974
|
-
});
|
|
975
|
-
connection.on("error", (err) => {
|
|
976
|
-
logger$1.warn("error", { connectionStringRedacted, err });
|
|
977
|
-
});
|
|
978
|
-
this._connection = connection;
|
|
979
|
-
this._connecting = void 0;
|
|
980
|
-
this.getConnection = () => Promise.resolve(this._connection);
|
|
1083
|
+
this.useClient(connection, connectionStringRedacted);
|
|
981
1084
|
return connection;
|
|
982
1085
|
}).catch((error) => {
|
|
983
1086
|
logger$1.info("not connected", { connectionStringRedacted });
|
|
@@ -990,6 +1093,33 @@ class MongoConnection extends AbstractConnection {
|
|
|
990
1093
|
this.getConnection = () => Promise.resolve(connectPromise);
|
|
991
1094
|
this._connecting = this.getConnection();
|
|
992
1095
|
}
|
|
1096
|
+
useClient(connection, connectionStringRedacted) {
|
|
1097
|
+
connection.on("close", () => {
|
|
1098
|
+
logger$1.warn("close", { connectionStringRedacted });
|
|
1099
|
+
this.connectionFailed = true;
|
|
1100
|
+
this.getConnection = () => {
|
|
1101
|
+
throw new Error("MongoDB connection closed");
|
|
1102
|
+
};
|
|
1103
|
+
});
|
|
1104
|
+
connection.on("timeout", () => {
|
|
1105
|
+
logger$1.warn("timeout", { connectionStringRedacted });
|
|
1106
|
+
this.connectionFailed = true;
|
|
1107
|
+
this.getConnection = () => {
|
|
1108
|
+
throw new Error("MongoDB connection timeout");
|
|
1109
|
+
};
|
|
1110
|
+
});
|
|
1111
|
+
connection.on("reconnect", () => {
|
|
1112
|
+
logger$1.warn("reconnect", { connectionStringRedacted });
|
|
1113
|
+
this.connectionFailed = false;
|
|
1114
|
+
this.getConnection = () => Promise.resolve(this._connection);
|
|
1115
|
+
});
|
|
1116
|
+
connection.on("error", (err) => {
|
|
1117
|
+
logger$1.warn("error", { connectionStringRedacted, err });
|
|
1118
|
+
});
|
|
1119
|
+
this._connection = connection;
|
|
1120
|
+
this._connecting = void 0;
|
|
1121
|
+
this.getConnection = () => Promise.resolve(this._connection);
|
|
1122
|
+
}
|
|
993
1123
|
getConnection() {
|
|
994
1124
|
throw new Error("call connect()");
|
|
995
1125
|
}
|