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.
@@ -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({ depopulate: true });
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
- try {
118
- if (this.isNew) {
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
- catch (error) {
130
- next(error);
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
- try {
155
- const keys = _.keys(update).filter((key) => key.startsWith('$'));
156
- if (update && !_.isEmpty(keys)) {
157
- _.forEach(keys, (key) => {
158
- commands.push({ [key]: update[key] });
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
- schema.post(updateMethods, async function () {
183
- const update = this.getUpdate();
184
- if (update && this._context.isNew) {
185
- const cursor = this.model.findOne(update).cursor();
186
- await cursor.eachAsync((doc) => {
187
- const current = doc.toObject({ depopulate: true });
188
- if (this._context.createdDocs) {
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
- else {
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.pre('remove', async function (next) {
199
- const original = this.toObject({ depopulate: true });
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
- try {
207
- if (opts.eventDeleted) {
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.preDeleteManyCallback && _.isArray(this._context.deletedDocs) && !_.isEmpty(this._context.deletedDocs)) {
241
- await opts.preDeleteManyCallback(this._context.deletedDocs);
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
- preDeleteManyCallback?: (docs: HydratedDocument<T>[]) => Promise<void>;
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,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACtE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,eAAe,cAAc,CAAA"}
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":";;;;;;;;;;;;;;;;;;;;;;;;AAKA,OAAO,KAAK,EAAmC,KAAK,EAA0C,MAAM,EAAS,MAAM,UAAU,CAAA;AAE7H,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AA+H7D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAAK,CAAA;AAQnC,eAAO,MAAM,kBAAkB,oOAAoE,IA2JlG,CAAA"}
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.4",
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.49",
86
+ "@swc/core": "1.3.51",
87
87
  "@swc/helpers": "0.5.0",
88
- "@swc/jest": "0.2.24",
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.192",
91
+ "@types/lodash": "4.14.194",
92
92
  "@types/node": "18.15.11",
93
- "@typescript-eslint/eslint-plugin": "5.58.0",
94
- "@typescript-eslint/parser": "5.58.0",
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.10.5"
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
- preDeleteManyCallback?: (docs: HydratedDocument<T>[]) => Promise<void>
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
+ }