Skip to content

Commit d784d34

Browse files
committed
feat: add patch to mongo
1 parent 6bb0bb6 commit d784d34

File tree

3 files changed

+89
-6
lines changed

3 files changed

+89
-6
lines changed

packages/mongo/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
},
3030
"dependencies": {
3131
"@js-soft/docdb-access-abstractions": "1.1.0",
32+
"fast-json-patch": "^3.1.1",
3233
"mongodb": "6.17.0"
3334
},
3435
"publishConfig": {

packages/mongo/src/MongoDbCollection.ts

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DatabaseType,
55
IDatabaseCollection
66
} from "@js-soft/docdb-access-abstractions";
7+
import jsonpatch from "fast-json-patch";
78
import { Collection } from "mongodb";
89
import { removeContainsInQuery } from "./queryUtils";
910

@@ -34,16 +35,59 @@ export class MongoDbCollection implements IDatabaseCollection {
3435
}
3536

3637
public async update(oldDoc: any, data: any): Promise<any> {
37-
let doc: any;
38+
if (typeof data.toJSON === "function") data = data.toJSON();
3839

40+
const updateResult = await this.collection.replaceOne(oldDoc, data);
41+
if (updateResult.modifiedCount < 1) throw new Error("Document not found for updating");
42+
43+
return data;
44+
}
45+
46+
public async patch(oldDoc: any, data: any): Promise<any> {
3947
if (typeof data.toJSON === "function") {
40-
doc = data.toJSON();
41-
} else {
42-
doc = data;
48+
data = data.toJSON();
4349
}
4450

45-
await this.collection.replaceOne(oldDoc, doc);
46-
return data;
51+
const patch = jsonpatch.compare(oldDoc, data);
52+
53+
const patchMappedToMongoDbOps = patch
54+
.filter((v) => v.path !== "/_id")
55+
.map((op) => {
56+
const path = op.path.startsWith("/")
57+
? op.path.substring(1).replaceAll("/", ".")
58+
: op.path.replaceAll("/", ".");
59+
60+
switch (op.op) {
61+
case "add":
62+
case "replace":
63+
return { $set: { [path]: op.value } };
64+
case "remove":
65+
return { $unset: { [path]: "" } };
66+
case "move":
67+
// TODO: hard to implement
68+
// return { $set: { [op.path]: oldDoc[op.from] }, $unset: { [op.from]: "" } };
69+
throw new Error("Move operation is not supported in MongoDB");
70+
case "copy":
71+
// TODO: hard to implement
72+
// return { $set: { [op.path]: oldDoc[op.from] } };
73+
throw new Error("Move operation is not supported in MongoDB");
74+
case "test":
75+
// Test operations are not supported in MongoDB, so we ignore them
76+
case "_get":
77+
default:
78+
throw new Error(`Unsupported operation: ${op.op}`);
79+
}
80+
});
81+
82+
const updated = await this.collection.findOneAndUpdate({ id: oldDoc.id }, patchMappedToMongoDbOps, {
83+
returnDocument: "after"
84+
});
85+
86+
if (!updated) {
87+
throw new Error("Document not found for patching");
88+
}
89+
90+
return updated;
4791
}
4892

4993
public async delete(query: any): Promise<boolean> {

packages/mongo/test/DatabaseCollection.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,42 @@ describe("DatabaseCollection", () => {
241241

242242
await expect(db.create({ id: "uniqueValue" })).rejects.toThrow(/[Dd]uplicate key/);
243243
});
244+
245+
describe("patch vs update", () => {
246+
test("update is not working properly", async () => {
247+
const db = await getRandomCollection();
248+
249+
const entry = { id: "test", name: "test" };
250+
const createdEntry = await db.create(entry);
251+
252+
// this will completely replaced
253+
await db.update(createdEntry, { id: "test", name: "updated" });
254+
255+
// this will not replace the above b/c it's not even found
256+
await expect(
257+
db.update(createdEntry, { id: "test", name: "test", anotherField: "newField" })
258+
).rejects.toThrow("Document not found for updating");
259+
260+
const queriedEntry = await db.findOne({ id: "test" });
261+
expect(queriedEntry.name).toBe("updated");
262+
expect(queriedEntry.anotherField).toBeUndefined();
263+
});
264+
265+
test("patch is better", async () => {
266+
const db = await getRandomCollection();
267+
268+
const entry = { id: "test", name: "test" };
269+
const createdEntry = await db.create(entry);
270+
271+
// this changes name
272+
await db.patch(createdEntry, { id: "test", name: "updated" });
273+
274+
// this only adds another field
275+
await db.patch(createdEntry, { id: "test", name: "test", anotherField: "newField" });
276+
277+
const queriedEntry = await db.findOne({ id: "test" });
278+
expect(queriedEntry.name).toBe("updated");
279+
expect(queriedEntry.anotherField).toBe("newField");
280+
});
281+
});
244282
});

0 commit comments

Comments
 (0)