ts-patch-mongoose 1.1.4 → 1.1.6
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/.eslintrc +8 -3
- package/dist/cjs/patch.js +88 -0
- package/dist/cjs/patch.js.map +1 -0
- package/dist/cjs/plugin.js +50 -146
- package/dist/cjs/plugin.js.map +1 -1
- package/dist/cjs/types/interfaces/IPluginOptions.d.ts +1 -1
- package/dist/cjs/types/interfaces/IPluginOptions.d.ts.map +1 -1
- package/dist/cjs/types/patch.d.ts +12 -0
- package/dist/cjs/types/patch.d.ts.map +1 -0
- package/dist/cjs/types/plugin.d.ts.map +1 -1
- package/dist/esm/patch.js +79 -0
- package/dist/esm/patch.js.map +1 -0
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/plugin.mjs +47 -143
- package/dist/esm/types/interfaces/IPluginOptions.d.ts +1 -1
- package/dist/esm/types/interfaces/IPluginOptions.d.ts.map +1 -1
- package/dist/esm/types/patch.d.ts +12 -0
- package/dist/esm/types/patch.d.ts.map +1 -0
- package/dist/esm/types/plugin.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/interfaces/IPluginOptions.ts +1 -1
- package/src/patch.ts +101 -0
- package/src/plugin.ts +55 -155
- package/tests/em.test.ts +5 -5
- package/tests/patch.test.ts +141 -0
- package/tests/plugin-callback.test.ts +50 -0
- package/tests/plugin-event-deleted.test.ts +20 -13
package/dist/esm/plugin.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import omit from 'omit-deep';
|
|
3
|
-
import jsonpatch from 'fast-json-patch';
|
|
4
2
|
import { assign } from 'power-assign';
|
|
3
|
+
import { createPatch, updatePatch, deletePatch } from './patch';
|
|
5
4
|
import em from './em';
|
|
6
|
-
import History from './models/History';
|
|
7
5
|
const options = {
|
|
8
6
|
document: false,
|
|
9
7
|
query: true
|
|
10
8
|
};
|
|
9
|
+
const toObjectOptions = {
|
|
10
|
+
depopulate: true,
|
|
11
|
+
virtuals: false
|
|
12
|
+
};
|
|
11
13
|
const updateMethods = [
|
|
12
14
|
'update',
|
|
13
15
|
'updateOne',
|
|
@@ -26,87 +28,10 @@ const deleteMethods = [
|
|
|
26
28
|
'deleteOne',
|
|
27
29
|
'deleteMany'
|
|
28
30
|
];
|
|
29
|
-
function getObjects(opts, current, original) {
|
|
30
|
-
let currentObject = JSON.parse(JSON.stringify(current));
|
|
31
|
-
let originalObject = JSON.parse(JSON.stringify(original));
|
|
32
|
-
if (opts.omit) {
|
|
33
|
-
currentObject = omit(currentObject, opts.omit);
|
|
34
|
-
originalObject = omit(originalObject, opts.omit);
|
|
35
|
-
}
|
|
36
|
-
return { currentObject, originalObject };
|
|
37
|
-
}
|
|
38
|
-
async function updatePatch(opts, context, current, original) {
|
|
39
|
-
const { currentObject, originalObject } = getObjects(opts, current, original);
|
|
40
|
-
if (_.isEmpty(originalObject) || _.isEmpty(currentObject))
|
|
41
|
-
return;
|
|
42
|
-
const patch = jsonpatch.compare(originalObject, currentObject, true);
|
|
43
|
-
if (_.isEmpty(patch))
|
|
44
|
-
return;
|
|
45
|
-
if (opts.eventUpdated) {
|
|
46
|
-
em.emit(opts.eventUpdated, { oldDoc: original, doc: current, patch });
|
|
47
|
-
}
|
|
48
|
-
if (opts.patchHistoryDisabled)
|
|
49
|
-
return;
|
|
50
|
-
let version = 0;
|
|
51
|
-
const lastHistory = await History.findOne({ collectionId: original._id }).sort('-version').exec();
|
|
52
|
-
if (lastHistory && lastHistory.version >= 0) {
|
|
53
|
-
version = lastHistory.version + 1;
|
|
54
|
-
}
|
|
55
|
-
await History.create({
|
|
56
|
-
op: context.op,
|
|
57
|
-
modelName: context.modelName,
|
|
58
|
-
collectionName: context.collectionName,
|
|
59
|
-
collectionId: original._id,
|
|
60
|
-
patch,
|
|
61
|
-
version
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
async function bulkPatch(opts, context, eventKey, docsKey) {
|
|
65
|
-
const event = opts[eventKey];
|
|
66
|
-
const docs = context[docsKey];
|
|
67
|
-
const key = eventKey === 'eventCreated' ? 'doc' : 'oldDoc';
|
|
68
|
-
if (_.isEmpty(docs) || (!event && opts.patchHistoryDisabled))
|
|
69
|
-
return;
|
|
70
|
-
const chunks = _.chunk(docs, 1000);
|
|
71
|
-
for await (const chunk of chunks) {
|
|
72
|
-
const bulk = [];
|
|
73
|
-
for (const doc of chunk) {
|
|
74
|
-
if (event)
|
|
75
|
-
em.emit(event, { [key]: doc });
|
|
76
|
-
if (!opts.patchHistoryDisabled) {
|
|
77
|
-
bulk.push({
|
|
78
|
-
insertOne: {
|
|
79
|
-
document: {
|
|
80
|
-
op: context.op,
|
|
81
|
-
modelName: context.modelName,
|
|
82
|
-
collectionName: context.collectionName,
|
|
83
|
-
collectionId: doc._id,
|
|
84
|
-
doc,
|
|
85
|
-
version: 0
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (!opts.patchHistoryDisabled) {
|
|
92
|
-
await History
|
|
93
|
-
.bulkWrite(bulk, { ordered: false })
|
|
94
|
-
.catch((err) => {
|
|
95
|
-
console.error(err);
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async function createPatch(opts, context) {
|
|
101
|
-
await bulkPatch(opts, context, 'eventCreated', 'createdDocs');
|
|
102
|
-
}
|
|
103
|
-
async function deletePatch(opts, context) {
|
|
104
|
-
await bulkPatch(opts, context, 'eventDeleted', 'deletedDocs');
|
|
105
|
-
}
|
|
106
31
|
export const patchEventEmitter = em;
|
|
107
32
|
export const patchHistoryPlugin = function plugin(schema, opts) {
|
|
108
33
|
schema.pre('save', async function (next) {
|
|
109
|
-
const current = this.toObject(
|
|
34
|
+
const current = this.toObject(toObjectOptions);
|
|
110
35
|
const model = this.constructor;
|
|
111
36
|
const context = {
|
|
112
37
|
op: this.isNew ? 'create' : 'update',
|
|
@@ -114,21 +39,16 @@ export const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
114
39
|
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
115
40
|
createdDocs: [current]
|
|
116
41
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
await createPatch(opts, context);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
const original = await model.findById(current._id).exec();
|
|
123
|
-
if (original) {
|
|
124
|
-
await updatePatch(opts, context, current, original);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
next();
|
|
42
|
+
if (this.isNew) {
|
|
43
|
+
await createPatch(opts, context);
|
|
128
44
|
}
|
|
129
|
-
|
|
130
|
-
|
|
45
|
+
else {
|
|
46
|
+
const original = await model.findById(current._id).exec();
|
|
47
|
+
if (original) {
|
|
48
|
+
await updatePatch(opts, context, current, original);
|
|
49
|
+
}
|
|
131
50
|
}
|
|
51
|
+
next();
|
|
132
52
|
});
|
|
133
53
|
schema.post('insertMany', async function (docs) {
|
|
134
54
|
const context = {
|
|
@@ -151,68 +71,52 @@ export const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
151
71
|
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
152
72
|
isNew: options.upsert && count === 0
|
|
153
73
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
delete update[key];
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
const cursor = this.model.find(filter).cursor();
|
|
163
|
-
await cursor.eachAsync(async (doc) => {
|
|
164
|
-
let current = doc.toObject({ depopulate: true });
|
|
165
|
-
const original = doc.toObject({ depopulate: true });
|
|
166
|
-
current = assign(current, update);
|
|
167
|
-
_.forEach(commands, (command) => {
|
|
168
|
-
try {
|
|
169
|
-
current = assign(current, command);
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
await updatePatch(opts, this._context, current, original);
|
|
74
|
+
const keys = _.keys(update).filter((key) => key.startsWith('$'));
|
|
75
|
+
if (update && !_.isEmpty(keys)) {
|
|
76
|
+
_.forEach(keys, (key) => {
|
|
77
|
+
commands.push({ [key]: update[key] });
|
|
78
|
+
delete update[key];
|
|
175
79
|
});
|
|
176
|
-
next();
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
next(error);
|
|
180
80
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this._context.createdDocs.push(current);
|
|
81
|
+
const cursor = this.model.find(filter).cursor();
|
|
82
|
+
await cursor.eachAsync(async (doc) => {
|
|
83
|
+
let current = doc.toObject(toObjectOptions);
|
|
84
|
+
const original = doc.toObject(toObjectOptions);
|
|
85
|
+
current = assign(current, update);
|
|
86
|
+
_.forEach(commands, (command) => {
|
|
87
|
+
try {
|
|
88
|
+
current = assign(current, command);
|
|
190
89
|
}
|
|
191
|
-
|
|
192
|
-
this._context.createdDocs = [current];
|
|
90
|
+
catch (error) {
|
|
193
91
|
}
|
|
194
92
|
});
|
|
93
|
+
await updatePatch(opts, this._context, current, original);
|
|
94
|
+
});
|
|
95
|
+
next();
|
|
96
|
+
});
|
|
97
|
+
schema.post(updateMethods, async function () {
|
|
98
|
+
const update = this.getUpdate();
|
|
99
|
+
if (!update || !this._context.isNew)
|
|
100
|
+
return;
|
|
101
|
+
const found = await this.model.findOne(update).exec();
|
|
102
|
+
if (found) {
|
|
103
|
+
const current = found.toObject(toObjectOptions);
|
|
104
|
+
this._context.createdDocs = [current];
|
|
195
105
|
await createPatch(opts, this._context);
|
|
196
106
|
}
|
|
197
107
|
});
|
|
198
|
-
schema.
|
|
199
|
-
const original = this.toObject(
|
|
108
|
+
schema.post('remove', async function () {
|
|
109
|
+
const original = this.toObject(toObjectOptions);
|
|
200
110
|
const model = this.constructor;
|
|
201
111
|
const context = {
|
|
202
112
|
op: 'delete',
|
|
203
113
|
modelName: opts.modelName ?? model.modelName,
|
|
204
114
|
collectionName: opts.collectionName ?? model.collection.collectionName
|
|
205
115
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
em.emit(opts.eventDeleted, { oldDoc: original });
|
|
209
|
-
}
|
|
210
|
-
await deletePatch(opts, context);
|
|
211
|
-
next();
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
next(error);
|
|
116
|
+
if (opts.eventDeleted) {
|
|
117
|
+
em.emit(opts.eventDeleted, { oldDoc: original });
|
|
215
118
|
}
|
|
119
|
+
await deletePatch(opts, context);
|
|
216
120
|
});
|
|
217
121
|
schema.pre(deleteMethods, options, async function (next) {
|
|
218
122
|
const filter = this.getFilter();
|
|
@@ -237,8 +141,8 @@ export const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
237
141
|
this._context.deletedDocs = [doc];
|
|
238
142
|
}
|
|
239
143
|
}
|
|
240
|
-
if (opts.
|
|
241
|
-
await opts.
|
|
144
|
+
if (opts.preDeleteCallback && _.isArray(this._context.deletedDocs) && !_.isEmpty(this._context.deletedDocs)) {
|
|
145
|
+
await opts.preDeleteCallback(this._context.deletedDocs);
|
|
242
146
|
}
|
|
243
147
|
next();
|
|
244
148
|
});
|
|
@@ -6,7 +6,7 @@ interface IPluginOptions<T> {
|
|
|
6
6
|
eventCreated?: string;
|
|
7
7
|
eventDeleted?: string;
|
|
8
8
|
patchHistoryDisabled?: boolean;
|
|
9
|
-
|
|
9
|
+
preDeleteCallback?: (docs: HydratedDocument<T>[]) => Promise<void>;
|
|
10
10
|
omit?: string[];
|
|
11
11
|
}
|
|
12
12
|
export default IPluginOptions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IPluginOptions.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/IPluginOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAEhD,UAAU,cAAc,CAAC,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,
|
|
1
|
+
{"version":3,"file":"IPluginOptions.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/IPluginOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAEhD,UAAU,cAAc,CAAC,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,eAAe,cAAc,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HydratedDocument } from 'mongoose';
|
|
2
|
+
import type IPluginOptions from './interfaces/IPluginOptions';
|
|
3
|
+
import type IContext from './interfaces/IContext';
|
|
4
|
+
export declare function getObjects<T>(opts: IPluginOptions<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): {
|
|
5
|
+
currentObject: Partial<T>;
|
|
6
|
+
originalObject: Partial<T>;
|
|
7
|
+
};
|
|
8
|
+
export declare function bulkPatch<T>(opts: IPluginOptions<T>, context: IContext<T>, eventKey: 'eventCreated' | 'eventDeleted', docsKey: 'createdDocs' | 'deletedDocs'): Promise<void>;
|
|
9
|
+
export declare function createPatch<T>(opts: IPluginOptions<T>, context: IContext<T>): Promise<void>;
|
|
10
|
+
export declare function updatePatch<T>(opts: IPluginOptions<T>, context: IContext<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): Promise<void>;
|
|
11
|
+
export declare function deletePatch<T>(opts: IPluginOptions<T>, context: IContext<T>): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=patch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../../../src/patch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,UAAU,CAAA;AAEvD,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AAC7D,OAAO,KAAK,QAAQ,MAAM,uBAAuB,CAAA;AAKjD,wBAAgB,UAAU,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,CAU9K;AAED,wBAAsB,SAAS,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,GAAG,cAAc,EAAE,OAAO,EAAE,aAAa,GAAG,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCnL;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAElG;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B/J;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAElG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAGA,OAAO,KAAK,EAAoB,KAAK,EAA2B,MAAM,EAAmB,MAAM,UAAU,CAAA;AAEzG,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AAwC7D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAAK,CAAA;AAQnC,eAAO,MAAM,kBAAkB,oOAAoE,IAgJlG,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-patch-mongoose",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Patch history & events for mongoose models",
|
|
5
5
|
"author": "Alex Eagle",
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,15 +83,15 @@
|
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@shelf/jest-mongodb": "4.1.7",
|
|
85
85
|
"@swc/cli": "0.1.62",
|
|
86
|
-
"@swc/core": "1.3.
|
|
86
|
+
"@swc/core": "1.3.51",
|
|
87
87
|
"@swc/helpers": "0.5.0",
|
|
88
|
-
"@swc/jest": "0.2.
|
|
88
|
+
"@swc/jest": "0.2.26",
|
|
89
89
|
"@swc/register": "0.1.10",
|
|
90
90
|
"@types/jest": "29.5.0",
|
|
91
|
-
"@types/lodash": "4.14.
|
|
91
|
+
"@types/lodash": "4.14.194",
|
|
92
92
|
"@types/node": "18.15.11",
|
|
93
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
94
|
-
"@typescript-eslint/parser": "5.
|
|
93
|
+
"@typescript-eslint/eslint-plugin": "5.59.0",
|
|
94
|
+
"@typescript-eslint/parser": "5.59.0",
|
|
95
95
|
"eslint": "8.38.0",
|
|
96
96
|
"eslint-config-standard": "17.0.0",
|
|
97
97
|
"eslint-plugin-import": "2.27.5",
|
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"eslint-plugin-n": "15.7.0",
|
|
101
101
|
"eslint-plugin-node": "11.1.0",
|
|
102
102
|
"eslint-plugin-promise": "6.1.1",
|
|
103
|
+
"eslint-plugin-sonarjs": "0.19.0",
|
|
103
104
|
"jest": "29.5.0",
|
|
104
105
|
"merge": "2.1.1",
|
|
105
106
|
"mongoose": "6.10.5",
|
|
@@ -107,6 +108,6 @@
|
|
|
107
108
|
"typescript": "5.0.4"
|
|
108
109
|
},
|
|
109
110
|
"peerDependencies": {
|
|
110
|
-
"mongoose": ">=6.
|
|
111
|
+
"mongoose": ">=6.6.0 < 7"
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -7,7 +7,7 @@ interface IPluginOptions<T> {
|
|
|
7
7
|
eventCreated?: string
|
|
8
8
|
eventDeleted?: string
|
|
9
9
|
patchHistoryDisabled?: boolean
|
|
10
|
-
|
|
10
|
+
preDeleteCallback?: (docs: HydratedDocument<T>[]) => Promise<void>
|
|
11
11
|
omit?: string[]
|
|
12
12
|
}
|
|
13
13
|
|
package/src/patch.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import omit from 'omit-deep'
|
|
3
|
+
import jsonpatch from 'fast-json-patch'
|
|
4
|
+
|
|
5
|
+
import type { HydratedDocument, Types } from 'mongoose'
|
|
6
|
+
|
|
7
|
+
import type IPluginOptions from './interfaces/IPluginOptions'
|
|
8
|
+
import type IContext from './interfaces/IContext'
|
|
9
|
+
|
|
10
|
+
import History from './models/History'
|
|
11
|
+
import em from './em'
|
|
12
|
+
|
|
13
|
+
export function getObjects<T> (opts: IPluginOptions<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): { currentObject: Partial<T>, originalObject: Partial<T> } {
|
|
14
|
+
let currentObject = JSON.parse(JSON.stringify(current)) as Partial<T>
|
|
15
|
+
let originalObject = JSON.parse(JSON.stringify(original)) as Partial<T>
|
|
16
|
+
|
|
17
|
+
if (opts.omit) {
|
|
18
|
+
currentObject = omit(currentObject, opts.omit)
|
|
19
|
+
originalObject = omit(originalObject, opts.omit)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { currentObject, originalObject }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function bulkPatch<T> (opts: IPluginOptions<T>, context: IContext<T>, eventKey: 'eventCreated' | 'eventDeleted', docsKey: 'createdDocs' | 'deletedDocs'): Promise<void> {
|
|
26
|
+
const event = opts[eventKey]
|
|
27
|
+
const docs = context[docsKey]
|
|
28
|
+
const key = eventKey === 'eventCreated' ? 'doc' : 'oldDoc'
|
|
29
|
+
|
|
30
|
+
if (_.isEmpty(docs) || (!event && opts.patchHistoryDisabled)) return
|
|
31
|
+
|
|
32
|
+
const chunks = _.chunk(docs, 1000)
|
|
33
|
+
|
|
34
|
+
for await (const chunk of chunks) {
|
|
35
|
+
const bulk = []
|
|
36
|
+
|
|
37
|
+
for (const doc of chunk) {
|
|
38
|
+
if (event) em.emit(event, { [key]: doc })
|
|
39
|
+
|
|
40
|
+
if (!opts.patchHistoryDisabled) {
|
|
41
|
+
bulk.push({
|
|
42
|
+
insertOne: {
|
|
43
|
+
document: {
|
|
44
|
+
op: context.op,
|
|
45
|
+
modelName: context.modelName,
|
|
46
|
+
collectionName: context.collectionName,
|
|
47
|
+
collectionId: doc._id as Types.ObjectId,
|
|
48
|
+
doc,
|
|
49
|
+
version: 0
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!opts.patchHistoryDisabled) {
|
|
57
|
+
await History.bulkWrite(bulk, { ordered: false })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function createPatch<T> (opts: IPluginOptions<T>, context: IContext<T>): Promise<void> {
|
|
63
|
+
await bulkPatch(opts, context, 'eventCreated', 'createdDocs')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function updatePatch<T> (opts: IPluginOptions<T>, context: IContext<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): Promise<void> {
|
|
67
|
+
const { currentObject, originalObject } = getObjects(opts, current, original)
|
|
68
|
+
|
|
69
|
+
if (_.isEmpty(originalObject) || _.isEmpty(currentObject)) return
|
|
70
|
+
|
|
71
|
+
const patch = jsonpatch.compare(originalObject, currentObject, true)
|
|
72
|
+
|
|
73
|
+
if (_.isEmpty(patch)) return
|
|
74
|
+
|
|
75
|
+
if (opts.eventUpdated) {
|
|
76
|
+
em.emit(opts.eventUpdated, { oldDoc: original, doc: current, patch })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (opts.patchHistoryDisabled) return
|
|
80
|
+
|
|
81
|
+
let version = 0
|
|
82
|
+
|
|
83
|
+
const lastHistory = await History.findOne({ collectionId: original._id as Types.ObjectId }).sort('-version').exec()
|
|
84
|
+
|
|
85
|
+
if (lastHistory && lastHistory.version >= 0) {
|
|
86
|
+
version = lastHistory.version + 1
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await History.create({
|
|
90
|
+
op: context.op,
|
|
91
|
+
modelName: context.modelName,
|
|
92
|
+
collectionName: context.collectionName,
|
|
93
|
+
collectionId: original._id as Types.ObjectId,
|
|
94
|
+
patch,
|
|
95
|
+
version
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function deletePatch<T> (opts: IPluginOptions<T>, context: IContext<T>): Promise<void> {
|
|
100
|
+
await bulkPatch(opts, context, 'eventDeleted', 'deletedDocs')
|
|
101
|
+
}
|