ts-patch-mongoose 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cjs/interfaces/IEvent.js +3 -0
- package/dist/cjs/interfaces/IEvent.js.map +1 -0
- package/dist/cjs/patch.js +35 -27
- package/dist/cjs/patch.js.map +1 -1
- package/dist/cjs/plugin.js +31 -20
- package/dist/cjs/plugin.js.map +1 -1
- package/dist/cjs/types/interfaces/IContext.d.ts +2 -0
- package/dist/cjs/types/interfaces/IContext.d.ts.map +1 -1
- package/dist/cjs/types/interfaces/IEvent.d.ts +9 -0
- package/dist/cjs/types/interfaces/IEvent.d.ts.map +1 -0
- package/dist/cjs/types/patch.d.ts +2 -0
- package/dist/cjs/types/patch.d.ts.map +1 -1
- package/dist/cjs/types/plugin.d.ts.map +1 -1
- package/dist/esm/interfaces/IEvent.js +2 -0
- package/dist/esm/interfaces/IEvent.js.map +1 -0
- package/dist/esm/patch.js +33 -26
- package/dist/esm/patch.js.map +1 -1
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/plugin.mjs +31 -20
- package/dist/esm/types/interfaces/IContext.d.ts +2 -0
- package/dist/esm/types/interfaces/IContext.d.ts.map +1 -1
- package/dist/esm/types/interfaces/IEvent.d.ts +9 -0
- package/dist/esm/types/interfaces/IEvent.d.ts.map +1 -0
- package/dist/esm/types/patch.d.ts +2 -0
- package/dist/esm/types/patch.d.ts.map +1 -1
- package/dist/esm/types/plugin.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/interfaces/IContext.ts +2 -0
- package/src/interfaces/IEvent.ts +10 -0
- package/src/patch.ts +38 -29
- package/src/plugin.ts +41 -29
- package/tests/em.test.ts +34 -0
- package/tests/patch.test.ts +1 -1
- package/tests/plugin-pre-delete.test.ts +116 -9
package/dist/esm/plugin.mjs
CHANGED
|
@@ -26,6 +26,9 @@ const deleteMethods = [
|
|
|
26
26
|
'deleteOne',
|
|
27
27
|
'deleteMany'
|
|
28
28
|
];
|
|
29
|
+
function isHookIgnored(options) {
|
|
30
|
+
return options.ignoreHook === true || (options.ignoreEvent === true && options.ignorePatchHistory === true);
|
|
31
|
+
}
|
|
29
32
|
function splitUpdateAndCommands(updateQuery) {
|
|
30
33
|
let update = {};
|
|
31
34
|
const commands = [];
|
|
@@ -67,7 +70,7 @@ export const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
67
70
|
await createPatch(opts, context);
|
|
68
71
|
}
|
|
69
72
|
else {
|
|
70
|
-
const original = await model.findById(current._id).exec();
|
|
73
|
+
const original = await model.findById(current._id).lean().exec();
|
|
71
74
|
if (original) {
|
|
72
75
|
await updatePatch(opts, context, current, original);
|
|
73
76
|
}
|
|
@@ -84,74 +87,82 @@ export const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
84
87
|
});
|
|
85
88
|
schema.pre(updateMethods, async function () {
|
|
86
89
|
const options = this.getOptions();
|
|
87
|
-
if (options
|
|
90
|
+
if (isHookIgnored(options))
|
|
88
91
|
return;
|
|
92
|
+
const model = this.model;
|
|
89
93
|
const filter = this.getFilter();
|
|
90
94
|
const count = await this.model.count(filter).exec();
|
|
91
95
|
this._context = {
|
|
92
96
|
op: this.op,
|
|
93
97
|
modelName: opts.modelName ?? this.model.modelName,
|
|
94
98
|
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
95
|
-
isNew: options.upsert && count === 0
|
|
99
|
+
isNew: options.upsert && count === 0,
|
|
100
|
+
ignoreEvent: options.ignoreEvent,
|
|
101
|
+
ignorePatchHistory: options.ignorePatchHistory
|
|
96
102
|
};
|
|
97
103
|
const updateQuery = this.getUpdate();
|
|
98
104
|
const { update, commands } = splitUpdateAndCommands(updateQuery);
|
|
99
|
-
const cursor =
|
|
105
|
+
const cursor = model.find(filter).lean().cursor();
|
|
100
106
|
await cursor.eachAsync(async (doc) => {
|
|
101
|
-
|
|
102
|
-
const original = doc.toObject(toObjectOptions);
|
|
103
|
-
await updatePatch(opts, this._context, current, original);
|
|
107
|
+
await updatePatch(opts, this._context, assignUpdate(doc, update, commands), doc);
|
|
104
108
|
});
|
|
105
109
|
});
|
|
106
110
|
schema.post(updateMethods, async function () {
|
|
107
111
|
const options = this.getOptions();
|
|
108
|
-
if (options
|
|
112
|
+
if (isHookIgnored(options))
|
|
109
113
|
return;
|
|
110
114
|
if (!this._context.isNew)
|
|
111
115
|
return;
|
|
116
|
+
const model = this.model;
|
|
112
117
|
const updateQuery = this.getUpdate();
|
|
113
118
|
const { update, commands } = splitUpdateAndCommands(updateQuery);
|
|
114
119
|
const filter = assignUpdate({}, update, commands);
|
|
115
120
|
if (!_.isEmpty(filter)) {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
const current = found.toObject(toObjectOptions);
|
|
121
|
+
const current = await model.findOne(update).lean().exec();
|
|
122
|
+
if (current) {
|
|
119
123
|
this._context.createdDocs = [current];
|
|
120
124
|
await createPatch(opts, this._context);
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
127
|
});
|
|
128
|
+
schema.pre(remove, { document: true, query: false }, async function () {
|
|
129
|
+
const original = this.toObject(toObjectOptions);
|
|
130
|
+
if (opts.preDelete && !_.isEmpty(original)) {
|
|
131
|
+
await opts.preDelete([original]);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
124
134
|
schema.post(remove, { document: true, query: false }, async function () {
|
|
125
135
|
const original = this.toObject(toObjectOptions);
|
|
126
136
|
const model = this.constructor;
|
|
127
137
|
const context = {
|
|
128
138
|
op: 'delete',
|
|
129
139
|
modelName: opts.modelName ?? model.modelName,
|
|
130
|
-
collectionName: opts.collectionName ?? model.collection.collectionName
|
|
140
|
+
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
141
|
+
deletedDocs: [original]
|
|
131
142
|
};
|
|
132
|
-
if (opts.eventDeleted) {
|
|
133
|
-
em.emit(opts.eventDeleted, { oldDoc: original });
|
|
134
|
-
}
|
|
135
143
|
await deletePatch(opts, context);
|
|
136
144
|
});
|
|
137
145
|
schema.pre(deleteMethods, { document: false, query: true }, async function () {
|
|
138
146
|
const options = this.getOptions();
|
|
139
|
-
if (options
|
|
147
|
+
if (isHookIgnored(options))
|
|
140
148
|
return;
|
|
149
|
+
const model = this.model;
|
|
141
150
|
const filter = this.getFilter();
|
|
142
151
|
this._context = {
|
|
143
152
|
op: this.op,
|
|
144
153
|
modelName: opts.modelName ?? this.model.modelName,
|
|
145
|
-
collectionName: opts.collectionName ?? this.model.collection.collectionName
|
|
154
|
+
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
155
|
+
ignoreEvent: options.ignoreEvent,
|
|
156
|
+
ignorePatchHistory: options.ignorePatchHistory
|
|
146
157
|
};
|
|
147
158
|
if (['remove', 'deleteMany'].includes(this._context.op) && !options.single) {
|
|
148
|
-
const docs = await
|
|
159
|
+
const docs = await model.find(filter).lean().exec();
|
|
149
160
|
if (!_.isEmpty(docs)) {
|
|
150
161
|
this._context.deletedDocs = docs;
|
|
151
162
|
}
|
|
152
163
|
}
|
|
153
164
|
else {
|
|
154
|
-
const doc = await
|
|
165
|
+
const doc = await model.findOne(filter).lean().exec();
|
|
155
166
|
if (!_.isEmpty(doc)) {
|
|
156
167
|
this._context.deletedDocs = [doc];
|
|
157
168
|
}
|
|
@@ -162,7 +173,7 @@ export const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
162
173
|
});
|
|
163
174
|
schema.post(deleteMethods, { document: false, query: true }, async function () {
|
|
164
175
|
const options = this.getOptions();
|
|
165
|
-
if (options
|
|
176
|
+
if (isHookIgnored(options))
|
|
166
177
|
return;
|
|
167
178
|
await deletePatch(opts, this._context);
|
|
168
179
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IContext.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/IContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAEhD,UAAU,QAAQ,CAAC,CAAC;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAA;IACnC,WAAW,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"IContext.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/IContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAEhD,UAAU,QAAQ,CAAC,CAAC;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAA;IACnC,WAAW,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAA;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,eAAe,QAAQ,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Operation } from 'fast-json-patch';
|
|
2
|
+
import type { HydratedDocument } from 'mongoose';
|
|
3
|
+
interface IEvent<T> {
|
|
4
|
+
oldDoc?: HydratedDocument<T>;
|
|
5
|
+
doc?: HydratedDocument<T>;
|
|
6
|
+
patch?: Operation[];
|
|
7
|
+
}
|
|
8
|
+
export default IEvent;
|
|
9
|
+
//# sourceMappingURL=IEvent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IEvent.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/IEvent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAEhD,UAAU,MAAM,CAAC,CAAC;IAChB,MAAM,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;IAC5B,GAAG,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;IACzB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAA;CACpB;AAED,eAAe,MAAM,CAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HydratedDocument } from 'mongoose';
|
|
2
|
+
import type IEvent from './interfaces/IEvent';
|
|
2
3
|
import type IContext from './interfaces/IContext';
|
|
3
4
|
import type IPluginOptions from './interfaces/IPluginOptions';
|
|
4
5
|
import type { User, Reason, Metadata } from './interfaces/IPluginOptions';
|
|
@@ -11,6 +12,7 @@ export declare function getReason<T>(opts: IPluginOptions<T>): Promise<Reason |
|
|
|
11
12
|
export declare function getMetadata<T>(opts: IPluginOptions<T>): Promise<Metadata | undefined>;
|
|
12
13
|
export declare function getValue<T>(item: PromiseSettledResult<T>): T | undefined;
|
|
13
14
|
export declare function getData<T>(opts: IPluginOptions<T>): Promise<[User | undefined, Reason | undefined, Metadata | undefined]>;
|
|
15
|
+
export declare function emitEvent<T>(context: IContext<T>, event: string | undefined, data: IEvent<T>): void;
|
|
14
16
|
export declare function bulkPatch<T>(opts: IPluginOptions<T>, context: IContext<T>, eventKey: 'eventCreated' | 'eventDeleted', docsKey: 'createdDocs' | 'deletedDocs'): Promise<void>;
|
|
15
17
|
export declare function createPatch<T>(opts: IPluginOptions<T>, context: IContext<T>): Promise<void>;
|
|
16
18
|
export declare function updatePatch<T>(opts: IPluginOptions<T>, context: IContext<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): Promise<void>;
|
|
@@ -1 +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,QAAQ,MAAM,uBAAuB,CAAA;AACjD,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;
|
|
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,MAAM,MAAM,qBAAqB,CAAA;AAC7C,OAAO,KAAK,QAAQ,MAAM,uBAAuB,CAAA;AACjD,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AASzE,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,OAAO,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAKpF;AAED,wBAAsB,SAAS,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAKxF;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAK5F;AAED,wBAAgB,QAAQ,CAAE,CAAC,EAAG,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAE1E;AAED,wBAAsB,OAAO,CAAC,CAAC,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,GAAG,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAC,CAUhI;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAG,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAIpG;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,CAwCnL;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,CAkC/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":";;;;;;;;;;;;;;;;;;;;;;;;AAGA,OAAO,KAAK,EAAoB,KAAK,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAGA,OAAO,KAAK,EAAoB,KAAK,EAAyC,MAAM,EAA+D,MAAM,UAAU,CAAA;AAEnK,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AA0E7D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAAK,CAAA;AAQnC,eAAO,MAAM,kBAAkB,oOAAoE,IA4IlG,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-patch-mongoose",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Patch history & events for mongoose models",
|
|
5
5
|
"author": "Alex Eagle",
|
|
6
6
|
"license": "MIT",
|
|
@@ -84,15 +84,15 @@
|
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@shelf/jest-mongodb": "4.1.7",
|
|
86
86
|
"@swc/cli": "0.1.62",
|
|
87
|
-
"@swc/core": "1.3.
|
|
88
|
-
"@swc/helpers": "0.5.
|
|
87
|
+
"@swc/core": "1.3.55",
|
|
88
|
+
"@swc/helpers": "0.5.1",
|
|
89
89
|
"@swc/jest": "0.2.26",
|
|
90
90
|
"@swc/register": "0.1.10",
|
|
91
91
|
"@types/jest": "29.5.1",
|
|
92
92
|
"@types/lodash": "4.14.194",
|
|
93
|
-
"@types/node": "18.16.
|
|
94
|
-
"@typescript-eslint/eslint-plugin": "5.59.
|
|
95
|
-
"@typescript-eslint/parser": "5.59.
|
|
93
|
+
"@types/node": "18.16.1",
|
|
94
|
+
"@typescript-eslint/eslint-plugin": "5.59.1",
|
|
95
|
+
"@typescript-eslint/parser": "5.59.1",
|
|
96
96
|
"eslint": "8.39.0",
|
|
97
97
|
"eslint-config-standard": "17.0.0",
|
|
98
98
|
"eslint-plugin-import": "2.27.5",
|
package/src/patch.ts
CHANGED
|
@@ -4,6 +4,7 @@ import jsonpatch from 'fast-json-patch'
|
|
|
4
4
|
|
|
5
5
|
import type { HydratedDocument, Types } from 'mongoose'
|
|
6
6
|
|
|
7
|
+
import type IEvent from './interfaces/IEvent'
|
|
7
8
|
import type IContext from './interfaces/IContext'
|
|
8
9
|
import type IPluginOptions from './interfaces/IPluginOptions'
|
|
9
10
|
import type { User, Reason, Metadata } from './interfaces/IPluginOptions'
|
|
@@ -11,6 +12,10 @@ import type { User, Reason, Metadata } from './interfaces/IPluginOptions'
|
|
|
11
12
|
import History from './models/History'
|
|
12
13
|
import em from './em'
|
|
13
14
|
|
|
15
|
+
function isPatchHistoryEnabled<T> (opts: IPluginOptions<T>, context: IContext<T>): boolean {
|
|
16
|
+
return !opts.patchHistoryDisabled && !context.ignorePatchHistory
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export function getObjects<T> (opts: IPluginOptions<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): { currentObject: Partial<T>, originalObject: Partial<T> } {
|
|
15
20
|
let currentObject = JSON.parse(JSON.stringify(current)) as Partial<T>
|
|
16
21
|
let originalObject = JSON.parse(JSON.stringify(original)) as Partial<T>
|
|
@@ -60,24 +65,30 @@ export async function getData<T> (opts: IPluginOptions<T>): Promise<[User | unde
|
|
|
60
65
|
})
|
|
61
66
|
}
|
|
62
67
|
|
|
68
|
+
export function emitEvent<T> (context: IContext<T>, event: string | undefined, data: IEvent<T>): void {
|
|
69
|
+
if (event && !context.ignoreEvent) {
|
|
70
|
+
em.emit(event, data)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
export async function bulkPatch<T> (opts: IPluginOptions<T>, context: IContext<T>, eventKey: 'eventCreated' | 'eventDeleted', docsKey: 'createdDocs' | 'deletedDocs'): Promise<void> {
|
|
75
|
+
const history = isPatchHistoryEnabled(opts, context)
|
|
64
76
|
const event = opts[eventKey]
|
|
65
77
|
const docs = context[docsKey]
|
|
66
78
|
const key = eventKey === 'eventCreated' ? 'doc' : 'oldDoc'
|
|
67
79
|
|
|
68
|
-
if (_.isEmpty(docs) || (!event &&
|
|
80
|
+
if (_.isEmpty(docs) || (!event && !history)) return
|
|
69
81
|
|
|
70
82
|
const [user, reason, metadata] = await getData(opts)
|
|
71
83
|
|
|
72
84
|
const chunks = _.chunk(docs, 1000)
|
|
73
|
-
|
|
74
85
|
for await (const chunk of chunks) {
|
|
75
86
|
const bulk = []
|
|
76
87
|
|
|
77
88
|
for (const doc of chunk) {
|
|
78
|
-
|
|
89
|
+
emitEvent(context, event, { [key]: doc })
|
|
79
90
|
|
|
80
|
-
if (
|
|
91
|
+
if (history) {
|
|
81
92
|
bulk.push({
|
|
82
93
|
insertOne: {
|
|
83
94
|
document: {
|
|
@@ -96,7 +107,7 @@ export async function bulkPatch<T> (opts: IPluginOptions<T>, context: IContext<T
|
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
|
|
99
|
-
if (
|
|
110
|
+
if (history) {
|
|
100
111
|
await History.bulkWrite(bulk, { ordered: false })
|
|
101
112
|
}
|
|
102
113
|
}
|
|
@@ -107,41 +118,39 @@ export async function createPatch<T> (opts: IPluginOptions<T>, context: IContext
|
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
export async function updatePatch<T> (opts: IPluginOptions<T>, context: IContext<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): Promise<void> {
|
|
110
|
-
const
|
|
121
|
+
const history = isPatchHistoryEnabled(opts, context)
|
|
111
122
|
|
|
123
|
+
const { currentObject, originalObject } = getObjects(opts, current, original)
|
|
112
124
|
if (_.isEmpty(originalObject) || _.isEmpty(currentObject)) return
|
|
113
125
|
|
|
114
126
|
const patch = jsonpatch.compare(originalObject, currentObject, true)
|
|
115
|
-
|
|
116
127
|
if (_.isEmpty(patch)) return
|
|
117
128
|
|
|
118
|
-
|
|
119
|
-
em.emit(opts.eventUpdated, { oldDoc: original, doc: current, patch })
|
|
120
|
-
}
|
|
129
|
+
emitEvent(context, opts.eventUpdated, { oldDoc: original, doc: current, patch })
|
|
121
130
|
|
|
122
|
-
if (
|
|
131
|
+
if (history) {
|
|
132
|
+
let version = 0
|
|
123
133
|
|
|
124
|
-
|
|
134
|
+
const lastHistory = await History.findOne({ collectionId: original._id as Types.ObjectId }).sort('-version').exec()
|
|
125
135
|
|
|
126
|
-
|
|
136
|
+
if (lastHistory && lastHistory.version >= 0) {
|
|
137
|
+
version = lastHistory.version + 1
|
|
138
|
+
}
|
|
127
139
|
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
const [user, reason, metadata] = await getData(opts)
|
|
141
|
+
|
|
142
|
+
await History.create({
|
|
143
|
+
op: context.op,
|
|
144
|
+
modelName: context.modelName,
|
|
145
|
+
collectionName: context.collectionName,
|
|
146
|
+
collectionId: original._id as Types.ObjectId,
|
|
147
|
+
patch,
|
|
148
|
+
user,
|
|
149
|
+
reason,
|
|
150
|
+
metadata,
|
|
151
|
+
version
|
|
152
|
+
})
|
|
130
153
|
}
|
|
131
|
-
|
|
132
|
-
const [user, reason, metadata] = await getData(opts)
|
|
133
|
-
|
|
134
|
-
await History.create({
|
|
135
|
-
op: context.op,
|
|
136
|
-
modelName: context.modelName,
|
|
137
|
-
collectionName: context.collectionName,
|
|
138
|
-
collectionId: original._id as Types.ObjectId,
|
|
139
|
-
patch,
|
|
140
|
-
user,
|
|
141
|
-
reason,
|
|
142
|
-
metadata,
|
|
143
|
-
version
|
|
144
|
-
})
|
|
145
154
|
}
|
|
146
155
|
|
|
147
156
|
export async function deletePatch<T> (opts: IPluginOptions<T>, context: IContext<T>): Promise<void> {
|
package/src/plugin.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { assign } from 'power-assign'
|
|
3
3
|
|
|
4
|
-
import type { HydratedDocument, Model, MongooseQueryMiddleware, Schema, ToObjectOptions, UpdateQuery, UpdateWithAggregationPipeline } from 'mongoose'
|
|
4
|
+
import type { HydratedDocument, Model, MongooseQueryMiddleware, QueryOptions, Schema, ToObjectOptions, UpdateQuery, UpdateWithAggregationPipeline } from 'mongoose'
|
|
5
5
|
|
|
6
6
|
import type IPluginOptions from './interfaces/IPluginOptions'
|
|
7
7
|
import type IContext from './interfaces/IContext'
|
|
@@ -38,6 +38,10 @@ const deleteMethods = [
|
|
|
38
38
|
'deleteMany'
|
|
39
39
|
]
|
|
40
40
|
|
|
41
|
+
function isHookIgnored<T> (options: QueryOptions<T>): boolean {
|
|
42
|
+
return options.ignoreHook === true || (options.ignoreEvent === true && options.ignorePatchHistory === true)
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
function splitUpdateAndCommands<T> (updateQuery: UpdateWithAggregationPipeline | UpdateQuery<T> | null): { update: UpdateQuery<T>, commands: Record<string, unknown>[] } {
|
|
42
46
|
let update: UpdateQuery<T> = {}
|
|
43
47
|
const commands: Record<string, unknown>[] = []
|
|
@@ -96,9 +100,9 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
|
|
|
96
100
|
if (this.isNew) {
|
|
97
101
|
await createPatch(opts, context)
|
|
98
102
|
} else {
|
|
99
|
-
const original = await model.findById(current._id).exec()
|
|
103
|
+
const original = await model.findById(current._id).lean().exec()
|
|
100
104
|
if (original) {
|
|
101
|
-
await updatePatch(opts, context, current, original)
|
|
105
|
+
await updatePatch(opts, context, current, original as HydratedDocument<T>)
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
})
|
|
@@ -116,8 +120,9 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
|
|
|
116
120
|
|
|
117
121
|
schema.pre(updateMethods as MongooseQueryMiddleware[], async function (this: IHookContext<T>) {
|
|
118
122
|
const options = this.getOptions()
|
|
119
|
-
if (options
|
|
123
|
+
if (isHookIgnored(options)) return
|
|
120
124
|
|
|
125
|
+
const model = this.model as Model<T>
|
|
121
126
|
const filter = this.getFilter()
|
|
122
127
|
const count = await this.model.count(filter).exec()
|
|
123
128
|
|
|
@@ -125,54 +130,58 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
|
|
|
125
130
|
op: this.op,
|
|
126
131
|
modelName: opts.modelName ?? this.model.modelName,
|
|
127
132
|
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
128
|
-
isNew: options.upsert && count === 0
|
|
133
|
+
isNew: options.upsert && count === 0,
|
|
134
|
+
ignoreEvent: options.ignoreEvent as boolean,
|
|
135
|
+
ignorePatchHistory: options.ignorePatchHistory as boolean
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
const updateQuery = this.getUpdate()
|
|
132
139
|
const { update, commands } = splitUpdateAndCommands(updateQuery)
|
|
133
140
|
|
|
134
|
-
const cursor =
|
|
135
|
-
await cursor.eachAsync(async (doc) => {
|
|
136
|
-
|
|
137
|
-
const original = doc.toObject(toObjectOptions) as HydratedDocument<T>
|
|
138
|
-
|
|
139
|
-
await updatePatch(opts, this._context, current, original)
|
|
141
|
+
const cursor = model.find(filter).lean().cursor()
|
|
142
|
+
await cursor.eachAsync(async (doc: HydratedDocument<T>) => {
|
|
143
|
+
await updatePatch(opts, this._context, assignUpdate(doc, update, commands), doc)
|
|
140
144
|
})
|
|
141
145
|
})
|
|
142
146
|
|
|
143
147
|
schema.post(updateMethods as MongooseQueryMiddleware[], async function (this: IHookContext<T>) {
|
|
144
148
|
const options = this.getOptions()
|
|
145
|
-
if (options
|
|
149
|
+
if (isHookIgnored(options)) return
|
|
146
150
|
|
|
147
151
|
if (!this._context.isNew) return
|
|
148
152
|
|
|
153
|
+
const model = this.model as Model<T>
|
|
149
154
|
const updateQuery = this.getUpdate()
|
|
150
155
|
const { update, commands } = splitUpdateAndCommands(updateQuery)
|
|
151
156
|
|
|
152
157
|
const filter = assignUpdate({} as HydratedDocument<T>, update, commands)
|
|
153
158
|
if (!_.isEmpty(filter)) {
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
this._context.createdDocs = [current]
|
|
159
|
+
const current = await model.findOne(update).lean().exec()
|
|
160
|
+
if (current) {
|
|
161
|
+
this._context.createdDocs = [current] as HydratedDocument<T>[]
|
|
158
162
|
|
|
159
163
|
await createPatch(opts, this._context)
|
|
160
164
|
}
|
|
161
165
|
}
|
|
162
166
|
})
|
|
163
167
|
|
|
168
|
+
schema.pre(remove, { document: true, query: false }, async function () {
|
|
169
|
+
const original = this.toObject(toObjectOptions) as HydratedDocument<T>
|
|
170
|
+
|
|
171
|
+
if (opts.preDelete && !_.isEmpty(original)) {
|
|
172
|
+
await opts.preDelete([original])
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
164
176
|
schema.post(remove, { document: true, query: false }, async function (this: HydratedDocument<T>) {
|
|
165
|
-
const original = this.toObject(toObjectOptions)
|
|
177
|
+
const original = this.toObject(toObjectOptions) as HydratedDocument<T>
|
|
166
178
|
const model = this.constructor as Model<T>
|
|
167
179
|
|
|
168
180
|
const context: IContext<T> = {
|
|
169
181
|
op: 'delete',
|
|
170
182
|
modelName: opts.modelName ?? model.modelName,
|
|
171
|
-
collectionName: opts.collectionName ?? model.collection.collectionName
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (opts.eventDeleted) {
|
|
175
|
-
em.emit(opts.eventDeleted, { oldDoc: original })
|
|
183
|
+
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
184
|
+
deletedDocs: [original]
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
await deletePatch(opts, context)
|
|
@@ -180,25 +189,28 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
|
|
|
180
189
|
|
|
181
190
|
schema.pre(deleteMethods as MongooseQueryMiddleware[], { document: false, query: true }, async function (this: IHookContext<T>) {
|
|
182
191
|
const options = this.getOptions()
|
|
183
|
-
if (options
|
|
192
|
+
if (isHookIgnored(options)) return
|
|
184
193
|
|
|
194
|
+
const model = this.model as Model<T>
|
|
185
195
|
const filter = this.getFilter()
|
|
186
196
|
|
|
187
197
|
this._context = {
|
|
188
198
|
op: this.op,
|
|
189
199
|
modelName: opts.modelName ?? this.model.modelName,
|
|
190
|
-
collectionName: opts.collectionName ?? this.model.collection.collectionName
|
|
200
|
+
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
201
|
+
ignoreEvent: options.ignoreEvent as boolean,
|
|
202
|
+
ignorePatchHistory: options.ignorePatchHistory as boolean
|
|
191
203
|
}
|
|
192
204
|
|
|
193
205
|
if (['remove', 'deleteMany'].includes(this._context.op) && !options.single) {
|
|
194
|
-
const docs = await
|
|
206
|
+
const docs = await model.find(filter).lean().exec()
|
|
195
207
|
if (!_.isEmpty(docs)) {
|
|
196
|
-
this._context.deletedDocs = docs
|
|
208
|
+
this._context.deletedDocs = docs as HydratedDocument<T>[]
|
|
197
209
|
}
|
|
198
210
|
} else {
|
|
199
|
-
const doc = await
|
|
211
|
+
const doc = await model.findOne(filter).lean().exec()
|
|
200
212
|
if (!_.isEmpty(doc)) {
|
|
201
|
-
this._context.deletedDocs = [doc]
|
|
213
|
+
this._context.deletedDocs = [doc] as HydratedDocument<T>[]
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
216
|
|
|
@@ -209,7 +221,7 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
|
|
|
209
221
|
|
|
210
222
|
schema.post(deleteMethods as MongooseQueryMiddleware[], { document: false, query: true }, async function (this: IHookContext<T>) {
|
|
211
223
|
const options = this.getOptions()
|
|
212
|
-
if (options
|
|
224
|
+
if (isHookIgnored(options)) return
|
|
213
225
|
|
|
214
226
|
await deletePatch(opts, this._context)
|
|
215
227
|
})
|
package/tests/em.test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { emitEvent } from '../src/patch'
|
|
1
2
|
import { patchEventEmitter } from '../src/plugin'
|
|
2
3
|
|
|
3
4
|
describe('em', () => {
|
|
@@ -13,4 +14,37 @@ describe('em', () => {
|
|
|
13
14
|
patchEventEmitter.emit('test')
|
|
14
15
|
expect(count).toBe(1)
|
|
15
16
|
})
|
|
17
|
+
|
|
18
|
+
it('emitEvent', async () => {
|
|
19
|
+
const fn = jest.fn()
|
|
20
|
+
patchEventEmitter.on('test', fn)
|
|
21
|
+
|
|
22
|
+
const context = {
|
|
23
|
+
op: 'test',
|
|
24
|
+
modelName: 'Test',
|
|
25
|
+
collectionName: 'tests'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
emitEvent(context, 'test', { doc: { name: 'test' } })
|
|
29
|
+
expect(fn).toHaveBeenCalledTimes(1)
|
|
30
|
+
|
|
31
|
+
patchEventEmitter.off('test', fn)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('emitEvent ignore', async () => {
|
|
35
|
+
const fn = jest.fn()
|
|
36
|
+
patchEventEmitter.on('test', fn)
|
|
37
|
+
|
|
38
|
+
const context = {
|
|
39
|
+
ignoreEvent: true,
|
|
40
|
+
op: 'test',
|
|
41
|
+
modelName: 'Test',
|
|
42
|
+
collectionName: 'tests'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
emitEvent(context, 'test', { doc: { name: 'test' } })
|
|
46
|
+
expect(fn).toHaveBeenCalledTimes(0)
|
|
47
|
+
|
|
48
|
+
patchEventEmitter.off('test', fn)
|
|
49
|
+
})
|
|
16
50
|
})
|
package/tests/patch.test.ts
CHANGED
|
@@ -40,7 +40,7 @@ describe('patch tests', () => {
|
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
beforeEach(async () => {
|
|
43
|
-
await mongoose.connection.collection('
|
|
43
|
+
await mongoose.connection.collection('users').deleteMany({})
|
|
44
44
|
await mongoose.connection.collection('patches').deleteMany({})
|
|
45
45
|
})
|
|
46
46
|
|