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.
Files changed (35) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/interfaces/IEvent.js +3 -0
  3. package/dist/cjs/interfaces/IEvent.js.map +1 -0
  4. package/dist/cjs/patch.js +35 -27
  5. package/dist/cjs/patch.js.map +1 -1
  6. package/dist/cjs/plugin.js +31 -20
  7. package/dist/cjs/plugin.js.map +1 -1
  8. package/dist/cjs/types/interfaces/IContext.d.ts +2 -0
  9. package/dist/cjs/types/interfaces/IContext.d.ts.map +1 -1
  10. package/dist/cjs/types/interfaces/IEvent.d.ts +9 -0
  11. package/dist/cjs/types/interfaces/IEvent.d.ts.map +1 -0
  12. package/dist/cjs/types/patch.d.ts +2 -0
  13. package/dist/cjs/types/patch.d.ts.map +1 -1
  14. package/dist/cjs/types/plugin.d.ts.map +1 -1
  15. package/dist/esm/interfaces/IEvent.js +2 -0
  16. package/dist/esm/interfaces/IEvent.js.map +1 -0
  17. package/dist/esm/patch.js +33 -26
  18. package/dist/esm/patch.js.map +1 -1
  19. package/dist/esm/plugin.js.map +1 -1
  20. package/dist/esm/plugin.mjs +31 -20
  21. package/dist/esm/types/interfaces/IContext.d.ts +2 -0
  22. package/dist/esm/types/interfaces/IContext.d.ts.map +1 -1
  23. package/dist/esm/types/interfaces/IEvent.d.ts +9 -0
  24. package/dist/esm/types/interfaces/IEvent.d.ts.map +1 -0
  25. package/dist/esm/types/patch.d.ts +2 -0
  26. package/dist/esm/types/patch.d.ts.map +1 -1
  27. package/dist/esm/types/plugin.d.ts.map +1 -1
  28. package/package.json +6 -6
  29. package/src/interfaces/IContext.ts +2 -0
  30. package/src/interfaces/IEvent.ts +10 -0
  31. package/src/patch.ts +38 -29
  32. package/src/plugin.ts +41 -29
  33. package/tests/em.test.ts +34 -0
  34. package/tests/patch.test.ts +1 -1
  35. package/tests/plugin-pre-delete.test.ts +116 -9
@@ -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.ignoreHook)
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 = this.model.find(filter).cursor();
105
+ const cursor = model.find(filter).lean().cursor();
100
106
  await cursor.eachAsync(async (doc) => {
101
- const current = assignUpdate(doc.toObject(toObjectOptions), update, commands);
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.ignoreHook)
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 found = await this.model.findOne(update).exec();
117
- if (found) {
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.ignoreHook)
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 this.model.find(filter).exec();
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 this.model.findOne(filter).exec();
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.ignoreHook)
176
+ if (isHookIgnored(options))
166
177
  return;
167
178
  await deletePatch(opts, this._context);
168
179
  });
@@ -6,6 +6,8 @@ interface IContext<T> {
6
6
  isNew?: boolean;
7
7
  createdDocs?: HydratedDocument<T>[];
8
8
  deletedDocs?: HydratedDocument<T>[];
9
+ ignoreEvent?: boolean;
10
+ ignorePatchHistory?: boolean;
9
11
  }
10
12
  export default IContext;
11
13
  //# sourceMappingURL=IContext.d.ts.map
@@ -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;CACpC;AAED,eAAe,QAAQ,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;AAKzE,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,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,CAoC/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
+ {"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,EAA2B,MAAM,EAA+D,MAAM,UAAU,CAAA;AAErJ,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AAsE7D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAAK,CAAA;AAQnC,eAAO,MAAM,kBAAkB,oOAAoE,IAoIlG,CAAA"}
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.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.53",
88
- "@swc/helpers": "0.5.0",
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.0",
94
- "@typescript-eslint/eslint-plugin": "5.59.0",
95
- "@typescript-eslint/parser": "5.59.0",
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",
@@ -7,6 +7,8 @@ interface IContext<T> {
7
7
  isNew?: boolean
8
8
  createdDocs?: HydratedDocument<T>[]
9
9
  deletedDocs?: HydratedDocument<T>[]
10
+ ignoreEvent?: boolean
11
+ ignorePatchHistory?: boolean
10
12
  }
11
13
 
12
14
  export default IContext
@@ -0,0 +1,10 @@
1
+ import type { Operation } from 'fast-json-patch'
2
+ import type { HydratedDocument } from 'mongoose'
3
+
4
+ interface IEvent<T> {
5
+ oldDoc?: HydratedDocument<T>
6
+ doc?: HydratedDocument<T>
7
+ patch?: Operation[]
8
+ }
9
+
10
+ export default IEvent
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 && opts.patchHistoryDisabled)) return
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
- if (event) em.emit(event, { [key]: doc })
89
+ emitEvent(context, event, { [key]: doc })
79
90
 
80
- if (!opts.patchHistoryDisabled) {
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 (!opts.patchHistoryDisabled) {
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 { currentObject, originalObject } = getObjects(opts, current, original)
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
- if (opts.eventUpdated) {
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 (opts.patchHistoryDisabled) return
131
+ if (history) {
132
+ let version = 0
123
133
 
124
- let version = 0
134
+ const lastHistory = await History.findOne({ collectionId: original._id as Types.ObjectId }).sort('-version').exec()
125
135
 
126
- const lastHistory = await History.findOne({ collectionId: original._id as Types.ObjectId }).sort('-version').exec()
136
+ if (lastHistory && lastHistory.version >= 0) {
137
+ version = lastHistory.version + 1
138
+ }
127
139
 
128
- if (lastHistory && lastHistory.version >= 0) {
129
- version = lastHistory.version + 1
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.ignoreHook) return
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 = this.model.find<HydratedDocument<T>>(filter).cursor()
135
- await cursor.eachAsync(async (doc) => {
136
- const current = assignUpdate(doc.toObject(toObjectOptions) as HydratedDocument<T>, update, commands)
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.ignoreHook) return
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 found = await this.model.findOne<HydratedDocument<T>>(update).exec()
155
- if (found) {
156
- const current = found.toObject(toObjectOptions) as HydratedDocument<T>
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.ignoreHook) return
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 this.model.find<HydratedDocument<T>>(filter).exec()
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 this.model.findOne<HydratedDocument<T>>(filter).exec()
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.ignoreHook) return
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
  })
@@ -40,7 +40,7 @@ describe('patch tests', () => {
40
40
  })
41
41
 
42
42
  beforeEach(async () => {
43
- await mongoose.connection.collection('tests').deleteMany({})
43
+ await mongoose.connection.collection('users').deleteMany({})
44
44
  await mongoose.connection.collection('patches').deleteMany({})
45
45
  })
46
46