ts-patch-mongoose 2.9.2 → 2.9.4
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/biome.json +1 -1
- package/dist/index.cjs +38 -32
- package/dist/index.d.cts +6 -20
- package/dist/index.d.mts +6 -20
- package/dist/index.mjs +38 -32
- package/package.json +11 -11
- package/src/hooks/delete-hooks.ts +5 -4
- package/src/hooks/update-hooks.ts +15 -10
- package/src/index.ts +3 -5
- package/src/patch.ts +13 -11
- package/tests/patch.test.ts +1 -1
- package/tests/plugin-event-created.test.ts +1 -1
- package/tests/plugin-global.test.ts +1 -1
- package/vite.config.mts +11 -1
package/biome.json
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var EventEmitter = require('node:events');
|
|
3
|
+
var isEmpty = require('lodash/isEmpty');
|
|
5
4
|
var ms = require('ms');
|
|
6
5
|
var mongoose = require('mongoose');
|
|
6
|
+
var isArray = require('lodash/isArray');
|
|
7
7
|
var jsonpatch = require('fast-json-patch');
|
|
8
|
+
var chunk = require('lodash/chunk');
|
|
9
|
+
var isFunction = require('lodash/isFunction');
|
|
8
10
|
var omit = require('omit-deep');
|
|
11
|
+
var EventEmitter = require('node:events');
|
|
12
|
+
var cloneDeep = require('lodash/cloneDeep');
|
|
13
|
+
var forEach = require('lodash/forEach');
|
|
14
|
+
var isObjectLike = require('lodash/isObjectLike');
|
|
15
|
+
var keys = require('lodash/keys');
|
|
9
16
|
var powerAssign = require('power-assign');
|
|
10
17
|
var semver = require('semver');
|
|
11
18
|
|
|
12
|
-
class PatchEventEmitter extends EventEmitter {
|
|
13
|
-
}
|
|
14
|
-
const em = new PatchEventEmitter();
|
|
15
|
-
|
|
16
19
|
const HistorySchema = new mongoose.Schema(
|
|
17
20
|
{
|
|
18
21
|
op: {
|
|
@@ -92,6 +95,10 @@ const setPatchHistoryTTL = async (ttl) => {
|
|
|
92
95
|
}
|
|
93
96
|
};
|
|
94
97
|
|
|
98
|
+
class PatchEventEmitter extends EventEmitter {
|
|
99
|
+
}
|
|
100
|
+
const em = new PatchEventEmitter();
|
|
101
|
+
|
|
95
102
|
function isPatchHistoryEnabled(opts, context) {
|
|
96
103
|
return !opts.patchHistoryDisabled && !context.ignorePatchHistory;
|
|
97
104
|
}
|
|
@@ -104,24 +111,24 @@ function getJsonOmit(opts, doc) {
|
|
|
104
111
|
}
|
|
105
112
|
function getObjectOmit(opts, doc) {
|
|
106
113
|
if (opts.omit) {
|
|
107
|
-
return omit(
|
|
114
|
+
return omit(isFunction(doc?.toObject) ? doc.toObject() : doc, opts.omit);
|
|
108
115
|
}
|
|
109
116
|
return doc;
|
|
110
117
|
}
|
|
111
118
|
async function getUser(opts, doc) {
|
|
112
|
-
if (
|
|
119
|
+
if (isFunction(opts.getUser)) {
|
|
113
120
|
return await opts.getUser(doc);
|
|
114
121
|
}
|
|
115
122
|
return void 0;
|
|
116
123
|
}
|
|
117
124
|
async function getReason(opts, doc) {
|
|
118
|
-
if (
|
|
125
|
+
if (isFunction(opts.getReason)) {
|
|
119
126
|
return await opts.getReason(doc);
|
|
120
127
|
}
|
|
121
128
|
return void 0;
|
|
122
129
|
}
|
|
123
130
|
async function getMetadata(opts, doc) {
|
|
124
|
-
if (
|
|
131
|
+
if (isFunction(opts.getMetadata)) {
|
|
125
132
|
return await opts.getMetadata(doc);
|
|
126
133
|
}
|
|
127
134
|
return void 0;
|
|
@@ -144,11 +151,11 @@ async function bulkPatch(opts, context, eventKey, docsKey) {
|
|
|
144
151
|
const event = opts[eventKey];
|
|
145
152
|
const docs = context[docsKey];
|
|
146
153
|
const key = eventKey === "eventCreated" ? "doc" : "oldDoc";
|
|
147
|
-
if (
|
|
148
|
-
const chunks =
|
|
149
|
-
for
|
|
154
|
+
if (isEmpty(docs) || !event && !history) return;
|
|
155
|
+
const chunks = chunk(docs, 1e3);
|
|
156
|
+
for (const chunk2 of chunks) {
|
|
150
157
|
const bulk = [];
|
|
151
|
-
for (const doc of
|
|
158
|
+
for (const doc of chunk2) {
|
|
152
159
|
emitEvent(context, event, { [key]: doc });
|
|
153
160
|
if (history) {
|
|
154
161
|
const [user, reason, metadata] = await getData(opts, doc);
|
|
@@ -169,7 +176,7 @@ async function bulkPatch(opts, context, eventKey, docsKey) {
|
|
|
169
176
|
});
|
|
170
177
|
}
|
|
171
178
|
}
|
|
172
|
-
if (history && !
|
|
179
|
+
if (history && !isEmpty(bulk)) {
|
|
173
180
|
await HistoryModel.bulkWrite(bulk, { ordered: false }).catch((error) => {
|
|
174
181
|
console.error(error.message);
|
|
175
182
|
});
|
|
@@ -183,9 +190,9 @@ async function updatePatch(opts, context, current, original) {
|
|
|
183
190
|
const history = isPatchHistoryEnabled(opts, context);
|
|
184
191
|
const currentObject = getJsonOmit(opts, current);
|
|
185
192
|
const originalObject = getJsonOmit(opts, original);
|
|
186
|
-
if (
|
|
193
|
+
if (isEmpty(originalObject) || isEmpty(currentObject)) return;
|
|
187
194
|
const patch = jsonpatch.compare(originalObject, currentObject, true);
|
|
188
|
-
if (
|
|
195
|
+
if (isEmpty(patch)) return;
|
|
189
196
|
emitEvent(context, opts.eventUpdated, { oldDoc: original, doc: current, patch });
|
|
190
197
|
if (history) {
|
|
191
198
|
let version = 0;
|
|
@@ -227,16 +234,16 @@ const deleteHooksInitialize = (schema, opts) => {
|
|
|
227
234
|
};
|
|
228
235
|
if (["remove", "deleteMany"].includes(this._context.op) && !options.single) {
|
|
229
236
|
const docs = await model.find(filter).lean().exec();
|
|
230
|
-
if (!
|
|
237
|
+
if (!isEmpty(docs)) {
|
|
231
238
|
this._context.deletedDocs = docs;
|
|
232
239
|
}
|
|
233
240
|
} else {
|
|
234
241
|
const doc = await model.findOne(filter).lean().exec();
|
|
235
|
-
if (!
|
|
242
|
+
if (!isEmpty(doc)) {
|
|
236
243
|
this._context.deletedDocs = [doc];
|
|
237
244
|
}
|
|
238
245
|
}
|
|
239
|
-
if (opts.preDelete &&
|
|
246
|
+
if (opts.preDelete && isArray(this._context.deletedDocs) && !isEmpty(this._context.deletedDocs)) {
|
|
240
247
|
await opts.preDelete(this._context.deletedDocs);
|
|
241
248
|
}
|
|
242
249
|
});
|
|
@@ -272,7 +279,7 @@ const saveHooksInitialize = (schema, opts) => {
|
|
|
272
279
|
const updateMethods = ["update", "updateOne", "replaceOne", "updateMany", "findOneAndUpdate", "findOneAndReplace", "findByIdAndUpdate"];
|
|
273
280
|
const assignUpdate = (document, update, commands) => {
|
|
274
281
|
let updated = powerAssign.assign(document.toObject(toObjectOptions), update);
|
|
275
|
-
|
|
282
|
+
forEach(commands, (command) => {
|
|
276
283
|
try {
|
|
277
284
|
updated = powerAssign.assign(updated, command);
|
|
278
285
|
} catch {
|
|
@@ -285,11 +292,11 @@ const assignUpdate = (document, update, commands) => {
|
|
|
285
292
|
const splitUpdateAndCommands = (updateQuery) => {
|
|
286
293
|
let update = {};
|
|
287
294
|
const commands = [];
|
|
288
|
-
if (!
|
|
289
|
-
update =
|
|
290
|
-
const keysWithDollarSign =
|
|
291
|
-
if (!
|
|
292
|
-
|
|
295
|
+
if (!isEmpty(updateQuery) && !isArray(updateQuery) && isObjectLike(updateQuery)) {
|
|
296
|
+
update = cloneDeep(updateQuery);
|
|
297
|
+
const keysWithDollarSign = keys(update).filter((key) => key.startsWith("$"));
|
|
298
|
+
if (!isEmpty(keysWithDollarSign)) {
|
|
299
|
+
forEach(keysWithDollarSign, (key) => {
|
|
293
300
|
commands.push({ [key]: update[key] });
|
|
294
301
|
delete update[key];
|
|
295
302
|
});
|
|
@@ -330,13 +337,13 @@ const updateHooksInitialize = (schema, opts) => {
|
|
|
330
337
|
let current = null;
|
|
331
338
|
const filter = this.getFilter();
|
|
332
339
|
const combined = assignUpdate(model.hydrate({}), update, commands);
|
|
333
|
-
if (!
|
|
340
|
+
if (!isEmpty(update) && !current) {
|
|
334
341
|
current = await model.findOne(update).sort("desc").lean().exec();
|
|
335
342
|
}
|
|
336
|
-
if (!
|
|
343
|
+
if (!isEmpty(combined) && !current) {
|
|
337
344
|
current = await model.findOne(combined).sort("desc").lean().exec();
|
|
338
345
|
}
|
|
339
|
-
if (!
|
|
346
|
+
if (!isEmpty(filter) && !current) {
|
|
340
347
|
console.log("filter", filter);
|
|
341
348
|
current = await model.findOne(filter).sort("desc").lean().exec();
|
|
342
349
|
}
|
|
@@ -355,7 +362,6 @@ if (isMongoose6) {
|
|
|
355
362
|
}
|
|
356
363
|
|
|
357
364
|
const remove = isMongooseLessThan7 ? "remove" : "deleteOne";
|
|
358
|
-
const patchEventEmitter = em;
|
|
359
365
|
const patchHistoryPlugin = function plugin(schema, opts) {
|
|
360
366
|
saveHooksInitialize(schema, opts);
|
|
361
367
|
updateHooksInitialize(schema, opts);
|
|
@@ -372,7 +378,7 @@ const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
372
378
|
if (isMongooseLessThan8) {
|
|
373
379
|
schema.pre(remove, { document: true, query: false }, async function() {
|
|
374
380
|
const original = this.toObject(toObjectOptions);
|
|
375
|
-
if (opts.preDelete && !
|
|
381
|
+
if (opts.preDelete && !isEmpty(original)) {
|
|
376
382
|
await opts.preDelete([original]);
|
|
377
383
|
}
|
|
378
384
|
});
|
|
@@ -390,6 +396,6 @@ const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
390
396
|
}
|
|
391
397
|
};
|
|
392
398
|
|
|
393
|
-
exports.patchEventEmitter =
|
|
399
|
+
exports.patchEventEmitter = em;
|
|
394
400
|
exports.patchHistoryPlugin = patchHistoryPlugin;
|
|
395
401
|
exports.setPatchHistoryTTL = setPatchHistoryTTL;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HydratedDocument, Types, Query, Schema } from 'mongoose';
|
|
2
2
|
import { Operation } from 'fast-json-patch';
|
|
3
|
+
import EventEmitter from 'node:events';
|
|
3
4
|
import ms from 'ms';
|
|
4
5
|
|
|
5
6
|
interface History {
|
|
@@ -49,28 +50,13 @@ interface PluginOptions<T> {
|
|
|
49
50
|
preDelete?: (docs: HydratedDocument<T>[]) => Promise<void>;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
declare
|
|
53
|
+
declare class PatchEventEmitter extends EventEmitter {
|
|
54
|
+
}
|
|
55
|
+
declare const em: PatchEventEmitter;
|
|
53
56
|
|
|
54
|
-
declare const
|
|
55
|
-
[EventEmitter.captureRejectionSymbol]?<K>(error: Error, event: string | symbol, ...args: any[]): void;
|
|
56
|
-
addListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
57
|
-
on<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
58
|
-
once<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
59
|
-
removeListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
60
|
-
off<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
61
|
-
removeAllListeners(eventName?: string | symbol | undefined): any;
|
|
62
|
-
setMaxListeners(n: number): any;
|
|
63
|
-
getMaxListeners(): number;
|
|
64
|
-
listeners<K>(eventName: string | symbol): Function[];
|
|
65
|
-
rawListeners<K>(eventName: string | symbol): Function[];
|
|
66
|
-
emit<K>(eventName: string | symbol, ...args: any[]): boolean;
|
|
67
|
-
listenerCount<K>(eventName: string | symbol, listener?: Function | undefined): number;
|
|
68
|
-
prependListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
69
|
-
prependOnceListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
70
|
-
eventNames(): (string | symbol)[];
|
|
71
|
-
};
|
|
57
|
+
declare const setPatchHistoryTTL: (ttl: number | ms.StringValue) => Promise<void>;
|
|
72
58
|
|
|
73
59
|
declare const patchHistoryPlugin: <T>(schema: Schema<T>, opts: PluginOptions<T>) => void;
|
|
74
60
|
|
|
75
|
-
export { patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|
|
61
|
+
export { em as patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|
|
76
62
|
export type { History, HookContext, Metadata, PatchContext, PatchEvent, PluginOptions, User };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HydratedDocument, Types, Query, Schema } from 'mongoose';
|
|
2
2
|
import { Operation } from 'fast-json-patch';
|
|
3
|
+
import EventEmitter from 'node:events';
|
|
3
4
|
import ms from 'ms';
|
|
4
5
|
|
|
5
6
|
interface History {
|
|
@@ -49,28 +50,13 @@ interface PluginOptions<T> {
|
|
|
49
50
|
preDelete?: (docs: HydratedDocument<T>[]) => Promise<void>;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
declare
|
|
53
|
+
declare class PatchEventEmitter extends EventEmitter {
|
|
54
|
+
}
|
|
55
|
+
declare const em: PatchEventEmitter;
|
|
53
56
|
|
|
54
|
-
declare const
|
|
55
|
-
[EventEmitter.captureRejectionSymbol]?<K>(error: Error, event: string | symbol, ...args: any[]): void;
|
|
56
|
-
addListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
57
|
-
on<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
58
|
-
once<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
59
|
-
removeListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
60
|
-
off<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
61
|
-
removeAllListeners(eventName?: string | symbol | undefined): any;
|
|
62
|
-
setMaxListeners(n: number): any;
|
|
63
|
-
getMaxListeners(): number;
|
|
64
|
-
listeners<K>(eventName: string | symbol): Function[];
|
|
65
|
-
rawListeners<K>(eventName: string | symbol): Function[];
|
|
66
|
-
emit<K>(eventName: string | symbol, ...args: any[]): boolean;
|
|
67
|
-
listenerCount<K>(eventName: string | symbol, listener?: Function | undefined): number;
|
|
68
|
-
prependListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
69
|
-
prependOnceListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
70
|
-
eventNames(): (string | symbol)[];
|
|
71
|
-
};
|
|
57
|
+
declare const setPatchHistoryTTL: (ttl: number | ms.StringValue) => Promise<void>;
|
|
72
58
|
|
|
73
59
|
declare const patchHistoryPlugin: <T>(schema: Schema<T>, opts: PluginOptions<T>) => void;
|
|
74
60
|
|
|
75
|
-
export { patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|
|
61
|
+
export { em as patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|
|
76
62
|
export type { History, HookContext, Metadata, PatchContext, PatchEvent, PluginOptions, User };
|
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import EventEmitter from 'node:events';
|
|
1
|
+
import isEmpty from 'lodash/isEmpty';
|
|
3
2
|
import ms from 'ms';
|
|
4
3
|
import mongoose, { Schema, model } from 'mongoose';
|
|
4
|
+
import isArray from 'lodash/isArray';
|
|
5
5
|
import jsonpatch from 'fast-json-patch';
|
|
6
|
+
import chunk from 'lodash/chunk';
|
|
7
|
+
import isFunction from 'lodash/isFunction';
|
|
6
8
|
import omit from 'omit-deep';
|
|
9
|
+
import EventEmitter from 'node:events';
|
|
10
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
11
|
+
import forEach from 'lodash/forEach';
|
|
12
|
+
import isObjectLike from 'lodash/isObjectLike';
|
|
13
|
+
import keys from 'lodash/keys';
|
|
7
14
|
import { assign } from 'power-assign';
|
|
8
15
|
import { satisfies } from 'semver';
|
|
9
16
|
|
|
10
|
-
class PatchEventEmitter extends EventEmitter {
|
|
11
|
-
}
|
|
12
|
-
const em = new PatchEventEmitter();
|
|
13
|
-
|
|
14
17
|
const HistorySchema = new Schema(
|
|
15
18
|
{
|
|
16
19
|
op: {
|
|
@@ -90,6 +93,10 @@ const setPatchHistoryTTL = async (ttl) => {
|
|
|
90
93
|
}
|
|
91
94
|
};
|
|
92
95
|
|
|
96
|
+
class PatchEventEmitter extends EventEmitter {
|
|
97
|
+
}
|
|
98
|
+
const em = new PatchEventEmitter();
|
|
99
|
+
|
|
93
100
|
function isPatchHistoryEnabled(opts, context) {
|
|
94
101
|
return !opts.patchHistoryDisabled && !context.ignorePatchHistory;
|
|
95
102
|
}
|
|
@@ -102,24 +109,24 @@ function getJsonOmit(opts, doc) {
|
|
|
102
109
|
}
|
|
103
110
|
function getObjectOmit(opts, doc) {
|
|
104
111
|
if (opts.omit) {
|
|
105
|
-
return omit(
|
|
112
|
+
return omit(isFunction(doc?.toObject) ? doc.toObject() : doc, opts.omit);
|
|
106
113
|
}
|
|
107
114
|
return doc;
|
|
108
115
|
}
|
|
109
116
|
async function getUser(opts, doc) {
|
|
110
|
-
if (
|
|
117
|
+
if (isFunction(opts.getUser)) {
|
|
111
118
|
return await opts.getUser(doc);
|
|
112
119
|
}
|
|
113
120
|
return void 0;
|
|
114
121
|
}
|
|
115
122
|
async function getReason(opts, doc) {
|
|
116
|
-
if (
|
|
123
|
+
if (isFunction(opts.getReason)) {
|
|
117
124
|
return await opts.getReason(doc);
|
|
118
125
|
}
|
|
119
126
|
return void 0;
|
|
120
127
|
}
|
|
121
128
|
async function getMetadata(opts, doc) {
|
|
122
|
-
if (
|
|
129
|
+
if (isFunction(opts.getMetadata)) {
|
|
123
130
|
return await opts.getMetadata(doc);
|
|
124
131
|
}
|
|
125
132
|
return void 0;
|
|
@@ -142,11 +149,11 @@ async function bulkPatch(opts, context, eventKey, docsKey) {
|
|
|
142
149
|
const event = opts[eventKey];
|
|
143
150
|
const docs = context[docsKey];
|
|
144
151
|
const key = eventKey === "eventCreated" ? "doc" : "oldDoc";
|
|
145
|
-
if (
|
|
146
|
-
const chunks =
|
|
147
|
-
for
|
|
152
|
+
if (isEmpty(docs) || !event && !history) return;
|
|
153
|
+
const chunks = chunk(docs, 1e3);
|
|
154
|
+
for (const chunk2 of chunks) {
|
|
148
155
|
const bulk = [];
|
|
149
|
-
for (const doc of
|
|
156
|
+
for (const doc of chunk2) {
|
|
150
157
|
emitEvent(context, event, { [key]: doc });
|
|
151
158
|
if (history) {
|
|
152
159
|
const [user, reason, metadata] = await getData(opts, doc);
|
|
@@ -167,7 +174,7 @@ async function bulkPatch(opts, context, eventKey, docsKey) {
|
|
|
167
174
|
});
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
|
-
if (history && !
|
|
177
|
+
if (history && !isEmpty(bulk)) {
|
|
171
178
|
await HistoryModel.bulkWrite(bulk, { ordered: false }).catch((error) => {
|
|
172
179
|
console.error(error.message);
|
|
173
180
|
});
|
|
@@ -181,9 +188,9 @@ async function updatePatch(opts, context, current, original) {
|
|
|
181
188
|
const history = isPatchHistoryEnabled(opts, context);
|
|
182
189
|
const currentObject = getJsonOmit(opts, current);
|
|
183
190
|
const originalObject = getJsonOmit(opts, original);
|
|
184
|
-
if (
|
|
191
|
+
if (isEmpty(originalObject) || isEmpty(currentObject)) return;
|
|
185
192
|
const patch = jsonpatch.compare(originalObject, currentObject, true);
|
|
186
|
-
if (
|
|
193
|
+
if (isEmpty(patch)) return;
|
|
187
194
|
emitEvent(context, opts.eventUpdated, { oldDoc: original, doc: current, patch });
|
|
188
195
|
if (history) {
|
|
189
196
|
let version = 0;
|
|
@@ -225,16 +232,16 @@ const deleteHooksInitialize = (schema, opts) => {
|
|
|
225
232
|
};
|
|
226
233
|
if (["remove", "deleteMany"].includes(this._context.op) && !options.single) {
|
|
227
234
|
const docs = await model.find(filter).lean().exec();
|
|
228
|
-
if (!
|
|
235
|
+
if (!isEmpty(docs)) {
|
|
229
236
|
this._context.deletedDocs = docs;
|
|
230
237
|
}
|
|
231
238
|
} else {
|
|
232
239
|
const doc = await model.findOne(filter).lean().exec();
|
|
233
|
-
if (!
|
|
240
|
+
if (!isEmpty(doc)) {
|
|
234
241
|
this._context.deletedDocs = [doc];
|
|
235
242
|
}
|
|
236
243
|
}
|
|
237
|
-
if (opts.preDelete &&
|
|
244
|
+
if (opts.preDelete && isArray(this._context.deletedDocs) && !isEmpty(this._context.deletedDocs)) {
|
|
238
245
|
await opts.preDelete(this._context.deletedDocs);
|
|
239
246
|
}
|
|
240
247
|
});
|
|
@@ -270,7 +277,7 @@ const saveHooksInitialize = (schema, opts) => {
|
|
|
270
277
|
const updateMethods = ["update", "updateOne", "replaceOne", "updateMany", "findOneAndUpdate", "findOneAndReplace", "findByIdAndUpdate"];
|
|
271
278
|
const assignUpdate = (document, update, commands) => {
|
|
272
279
|
let updated = assign(document.toObject(toObjectOptions), update);
|
|
273
|
-
|
|
280
|
+
forEach(commands, (command) => {
|
|
274
281
|
try {
|
|
275
282
|
updated = assign(updated, command);
|
|
276
283
|
} catch {
|
|
@@ -283,11 +290,11 @@ const assignUpdate = (document, update, commands) => {
|
|
|
283
290
|
const splitUpdateAndCommands = (updateQuery) => {
|
|
284
291
|
let update = {};
|
|
285
292
|
const commands = [];
|
|
286
|
-
if (!
|
|
287
|
-
update =
|
|
288
|
-
const keysWithDollarSign =
|
|
289
|
-
if (!
|
|
290
|
-
|
|
293
|
+
if (!isEmpty(updateQuery) && !isArray(updateQuery) && isObjectLike(updateQuery)) {
|
|
294
|
+
update = cloneDeep(updateQuery);
|
|
295
|
+
const keysWithDollarSign = keys(update).filter((key) => key.startsWith("$"));
|
|
296
|
+
if (!isEmpty(keysWithDollarSign)) {
|
|
297
|
+
forEach(keysWithDollarSign, (key) => {
|
|
291
298
|
commands.push({ [key]: update[key] });
|
|
292
299
|
delete update[key];
|
|
293
300
|
});
|
|
@@ -328,13 +335,13 @@ const updateHooksInitialize = (schema, opts) => {
|
|
|
328
335
|
let current = null;
|
|
329
336
|
const filter = this.getFilter();
|
|
330
337
|
const combined = assignUpdate(model.hydrate({}), update, commands);
|
|
331
|
-
if (!
|
|
338
|
+
if (!isEmpty(update) && !current) {
|
|
332
339
|
current = await model.findOne(update).sort("desc").lean().exec();
|
|
333
340
|
}
|
|
334
|
-
if (!
|
|
341
|
+
if (!isEmpty(combined) && !current) {
|
|
335
342
|
current = await model.findOne(combined).sort("desc").lean().exec();
|
|
336
343
|
}
|
|
337
|
-
if (!
|
|
344
|
+
if (!isEmpty(filter) && !current) {
|
|
338
345
|
console.log("filter", filter);
|
|
339
346
|
current = await model.findOne(filter).sort("desc").lean().exec();
|
|
340
347
|
}
|
|
@@ -353,7 +360,6 @@ if (isMongoose6) {
|
|
|
353
360
|
}
|
|
354
361
|
|
|
355
362
|
const remove = isMongooseLessThan7 ? "remove" : "deleteOne";
|
|
356
|
-
const patchEventEmitter = em;
|
|
357
363
|
const patchHistoryPlugin = function plugin(schema, opts) {
|
|
358
364
|
saveHooksInitialize(schema, opts);
|
|
359
365
|
updateHooksInitialize(schema, opts);
|
|
@@ -370,7 +376,7 @@ const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
370
376
|
if (isMongooseLessThan8) {
|
|
371
377
|
schema.pre(remove, { document: true, query: false }, async function() {
|
|
372
378
|
const original = this.toObject(toObjectOptions);
|
|
373
|
-
if (opts.preDelete && !
|
|
379
|
+
if (opts.preDelete && !isEmpty(original)) {
|
|
374
380
|
await opts.preDelete([original]);
|
|
375
381
|
}
|
|
376
382
|
});
|
|
@@ -388,4 +394,4 @@ const patchHistoryPlugin = function plugin(schema, opts) {
|
|
|
388
394
|
}
|
|
389
395
|
};
|
|
390
396
|
|
|
391
|
-
export { patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|
|
397
|
+
export { em as patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-patch-mongoose",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.4",
|
|
4
4
|
"description": "Patch history & events for mongoose models",
|
|
5
5
|
"author": "ilovepixelart",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,25 +73,25 @@
|
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@types/lodash": "4.17.20",
|
|
75
75
|
"@types/ms": "2.1.0",
|
|
76
|
-
"@types/semver": "7.7.
|
|
76
|
+
"@types/semver": "7.7.1",
|
|
77
77
|
"fast-json-patch": "3.1.1",
|
|
78
78
|
"lodash": "4.17.21",
|
|
79
79
|
"ms": "2.1.3",
|
|
80
80
|
"omit-deep": "0.3.0",
|
|
81
81
|
"power-assign": "0.2.10",
|
|
82
|
-
"semver": "7.7.
|
|
82
|
+
"semver": "7.7.3"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@biomejs/biome": "2.
|
|
86
|
-
"@types/node": "24.
|
|
87
|
-
"@vitest/coverage-v8": "
|
|
88
|
-
"mongodb-memory-server": "10.2.
|
|
89
|
-
"mongoose": "8.
|
|
85
|
+
"@biomejs/biome": "2.3.1",
|
|
86
|
+
"@types/node": "24.9.1",
|
|
87
|
+
"@vitest/coverage-v8": "4.0.4",
|
|
88
|
+
"mongodb-memory-server": "10.2.3",
|
|
89
|
+
"mongoose": "8.19.2",
|
|
90
90
|
"open-cli": "8.0.0",
|
|
91
|
-
"pkgroll": "2.
|
|
91
|
+
"pkgroll": "2.20.1",
|
|
92
92
|
"simple-git-hooks": "2.13.1",
|
|
93
|
-
"typescript": "5.9.
|
|
94
|
-
"vitest": "
|
|
93
|
+
"typescript": "5.9.3",
|
|
94
|
+
"vitest": "4.0.4"
|
|
95
95
|
},
|
|
96
96
|
"peerDependencies": {
|
|
97
97
|
"mongoose": ">=6.6.0 < 9"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import isArray from 'lodash/isArray'
|
|
2
|
+
import isEmpty from 'lodash/isEmpty'
|
|
2
3
|
import { isHookIgnored } from '../helpers'
|
|
3
4
|
import { deletePatch } from '../patch'
|
|
4
5
|
|
|
@@ -25,17 +26,17 @@ export const deleteHooksInitialize = <T>(schema: Schema<T>, opts: PluginOptions<
|
|
|
25
26
|
|
|
26
27
|
if (['remove', 'deleteMany'].includes(this._context.op) && !options.single) {
|
|
27
28
|
const docs = await model.find<T>(filter).lean().exec()
|
|
28
|
-
if (!
|
|
29
|
+
if (!isEmpty(docs)) {
|
|
29
30
|
this._context.deletedDocs = docs as HydratedDocument<T>[]
|
|
30
31
|
}
|
|
31
32
|
} else {
|
|
32
33
|
const doc = await model.findOne<T>(filter).lean().exec()
|
|
33
|
-
if (!
|
|
34
|
+
if (!isEmpty(doc)) {
|
|
34
35
|
this._context.deletedDocs = [doc] as HydratedDocument<T>[]
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
if (opts.preDelete &&
|
|
39
|
+
if (opts.preDelete && isArray(this._context.deletedDocs) && !isEmpty(this._context.deletedDocs)) {
|
|
39
40
|
await opts.preDelete(this._context.deletedDocs)
|
|
40
41
|
}
|
|
41
42
|
})
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
2
|
+
import forEach from 'lodash/forEach'
|
|
3
|
+
import isArray from 'lodash/isArray'
|
|
4
|
+
import isEmpty from 'lodash/isEmpty'
|
|
5
|
+
import isObjectLike from 'lodash/isObjectLike'
|
|
6
|
+
import keys from 'lodash/keys'
|
|
2
7
|
import { assign } from 'power-assign'
|
|
3
8
|
import { isHookIgnored, toObjectOptions } from '../helpers'
|
|
4
9
|
import { createPatch, updatePatch } from '../patch'
|
|
@@ -11,7 +16,7 @@ const updateMethods = ['update', 'updateOne', 'replaceOne', 'updateMany', 'findO
|
|
|
11
16
|
export const assignUpdate = <T>(document: HydratedDocument<T>, update: UpdateQuery<T>, commands: Record<string, unknown>[]): HydratedDocument<T> => {
|
|
12
17
|
let updated = assign(document.toObject(toObjectOptions), update)
|
|
13
18
|
// Try catch not working for of loop, keep it as is
|
|
14
|
-
|
|
19
|
+
forEach(commands, (command) => {
|
|
15
20
|
try {
|
|
16
21
|
updated = assign(updated, command)
|
|
17
22
|
} catch {
|
|
@@ -28,11 +33,11 @@ export const splitUpdateAndCommands = <T>(updateQuery: UpdateWithAggregationPipe
|
|
|
28
33
|
let update: UpdateQuery<T> = {}
|
|
29
34
|
const commands: Record<string, unknown>[] = []
|
|
30
35
|
|
|
31
|
-
if (!
|
|
32
|
-
update =
|
|
33
|
-
const keysWithDollarSign =
|
|
34
|
-
if (!
|
|
35
|
-
|
|
36
|
+
if (!isEmpty(updateQuery) && !isArray(updateQuery) && isObjectLike(updateQuery)) {
|
|
37
|
+
update = cloneDeep(updateQuery)
|
|
38
|
+
const keysWithDollarSign = keys(update).filter((key) => key.startsWith('$'))
|
|
39
|
+
if (!isEmpty(keysWithDollarSign)) {
|
|
40
|
+
forEach(keysWithDollarSign, (key) => {
|
|
36
41
|
commands.push({ [key]: update[key] as unknown })
|
|
37
42
|
delete update[key]
|
|
38
43
|
})
|
|
@@ -83,15 +88,15 @@ export const updateHooksInitialize = <T>(schema: Schema<T>, opts: PluginOptions<
|
|
|
83
88
|
let current: HydratedDocument<T> | null = null
|
|
84
89
|
const filter = this.getFilter()
|
|
85
90
|
const combined = assignUpdate(model.hydrate({}), update, commands)
|
|
86
|
-
if (!
|
|
91
|
+
if (!isEmpty(update) && !current) {
|
|
87
92
|
current = (await model.findOne(update).sort('desc').lean().exec()) as HydratedDocument<T>
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
if (!
|
|
95
|
+
if (!isEmpty(combined) && !current) {
|
|
91
96
|
current = (await model.findOne(combined).sort('desc').lean().exec()) as HydratedDocument<T>
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
if (!
|
|
99
|
+
if (!isEmpty(filter) && !current) {
|
|
95
100
|
console.log('filter', filter)
|
|
96
101
|
current = (await model.findOne(filter).sort('desc').lean().exec()) as HydratedDocument<T>
|
|
97
102
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import em from './em'
|
|
1
|
+
import isEmpty from 'lodash/isEmpty'
|
|
3
2
|
import { toObjectOptions } from './helpers'
|
|
4
3
|
import { deleteHooksInitialize } from './hooks/delete-hooks'
|
|
5
4
|
import { saveHooksInitialize } from './hooks/save-hooks'
|
|
@@ -15,8 +14,7 @@ const remove = isMongooseLessThan7 ? 'remove' : 'deleteOne'
|
|
|
15
14
|
/**
|
|
16
15
|
* @description Event emitter for patch operations.
|
|
17
16
|
*/
|
|
18
|
-
export
|
|
19
|
-
|
|
17
|
+
export { default as patchEventEmitter } from './em'
|
|
20
18
|
export { setPatchHistoryTTL } from './helpers'
|
|
21
19
|
export * from './types'
|
|
22
20
|
|
|
@@ -65,7 +63,7 @@ export const patchHistoryPlugin = function plugin<T>(schema: Schema<T>, opts: Pl
|
|
|
65
63
|
// @ts-expect-error - Mongoose 7 and below
|
|
66
64
|
const original = this.toObject(toObjectOptions) as HydratedDocument<T>
|
|
67
65
|
|
|
68
|
-
if (opts.preDelete && !
|
|
66
|
+
if (opts.preDelete && !isEmpty(original)) {
|
|
69
67
|
await opts.preDelete([original])
|
|
70
68
|
}
|
|
71
69
|
})
|
package/src/patch.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import jsonpatch from 'fast-json-patch'
|
|
2
|
-
import
|
|
2
|
+
import chunk from 'lodash/chunk'
|
|
3
|
+
import isEmpty from 'lodash/isEmpty'
|
|
4
|
+
import isFunction from 'lodash/isFunction'
|
|
3
5
|
import omit from 'omit-deep'
|
|
4
6
|
import em from './em'
|
|
5
7
|
import { HistoryModel } from './model'
|
|
@@ -23,28 +25,28 @@ export function getJsonOmit<T>(opts: PluginOptions<T>, doc: HydratedDocument<T>)
|
|
|
23
25
|
|
|
24
26
|
export function getObjectOmit<T>(opts: PluginOptions<T>, doc: HydratedDocument<T>): Partial<T> {
|
|
25
27
|
if (opts.omit) {
|
|
26
|
-
return omit(
|
|
28
|
+
return omit(isFunction(doc?.toObject) ? doc.toObject() : doc, opts.omit)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
return doc
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export async function getUser<T>(opts: PluginOptions<T>, doc: HydratedDocument<T>): Promise<User | undefined> {
|
|
33
|
-
if (
|
|
35
|
+
if (isFunction(opts.getUser)) {
|
|
34
36
|
return await opts.getUser(doc)
|
|
35
37
|
}
|
|
36
38
|
return undefined
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export async function getReason<T>(opts: PluginOptions<T>, doc: HydratedDocument<T>): Promise<string | undefined> {
|
|
40
|
-
if (
|
|
42
|
+
if (isFunction(opts.getReason)) {
|
|
41
43
|
return await opts.getReason(doc)
|
|
42
44
|
}
|
|
43
45
|
return undefined
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export async function getMetadata<T>(opts: PluginOptions<T>, doc: HydratedDocument<T>): Promise<Metadata | undefined> {
|
|
47
|
-
if (
|
|
49
|
+
if (isFunction(opts.getMetadata)) {
|
|
48
50
|
return await opts.getMetadata(doc)
|
|
49
51
|
}
|
|
50
52
|
return undefined
|
|
@@ -72,10 +74,10 @@ export async function bulkPatch<T>(opts: PluginOptions<T>, context: PatchContext
|
|
|
72
74
|
const docs = context[docsKey]
|
|
73
75
|
const key = eventKey === 'eventCreated' ? 'doc' : 'oldDoc'
|
|
74
76
|
|
|
75
|
-
if (
|
|
77
|
+
if (isEmpty(docs) || (!event && !history)) return
|
|
76
78
|
|
|
77
|
-
const chunks =
|
|
78
|
-
for
|
|
79
|
+
const chunks = chunk(docs, 1000)
|
|
80
|
+
for (const chunk of chunks) {
|
|
79
81
|
const bulk = []
|
|
80
82
|
|
|
81
83
|
for (const doc of chunk) {
|
|
@@ -101,7 +103,7 @@ export async function bulkPatch<T>(opts: PluginOptions<T>, context: PatchContext
|
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
if (history && !
|
|
106
|
+
if (history && !isEmpty(bulk)) {
|
|
105
107
|
await HistoryModel.bulkWrite(bulk, { ordered: false }).catch((error: MongooseError) => {
|
|
106
108
|
console.error(error.message)
|
|
107
109
|
})
|
|
@@ -118,10 +120,10 @@ export async function updatePatch<T>(opts: PluginOptions<T>, context: PatchConte
|
|
|
118
120
|
|
|
119
121
|
const currentObject = getJsonOmit(opts, current)
|
|
120
122
|
const originalObject = getJsonOmit(opts, original)
|
|
121
|
-
if (
|
|
123
|
+
if (isEmpty(originalObject) || isEmpty(currentObject)) return
|
|
122
124
|
|
|
123
125
|
const patch = jsonpatch.compare(originalObject, currentObject, true)
|
|
124
|
-
if (
|
|
126
|
+
if (isEmpty(patch)) return
|
|
125
127
|
|
|
126
128
|
emitEvent(context, opts.eventUpdated, { oldDoc: original, doc: current, patch })
|
|
127
129
|
|
package/tests/patch.test.ts
CHANGED
package/vite.config.mts
CHANGED
|
@@ -6,8 +6,18 @@ export default defineConfig({
|
|
|
6
6
|
name: 'node',
|
|
7
7
|
environment: 'node',
|
|
8
8
|
coverage: {
|
|
9
|
-
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'json', 'html', 'lcov'],
|
|
10
11
|
include: ['src/**/*.ts'],
|
|
12
|
+
exclude: [
|
|
13
|
+
'node_modules/**',
|
|
14
|
+
'dist/**',
|
|
15
|
+
'coverage/**',
|
|
16
|
+
'**/*.d.ts',
|
|
17
|
+
'**/*.config.*',
|
|
18
|
+
'**/tests/**',
|
|
19
|
+
'examples/**',
|
|
20
|
+
],
|
|
11
21
|
},
|
|
12
22
|
},
|
|
13
23
|
})
|