ts-patch-mongoose 2.8.0 → 2.8.1
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/LICENSE +1 -1
- package/README.md +8 -8
- package/dist/index.cjs +395 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.mts +75 -0
- package/dist/index.mjs +391 -0
- package/package.json +36 -29
- package/src/helpers.ts +1 -1
- package/src/hooks/delete-hooks.ts +4 -4
- package/src/hooks/update-hooks.ts +10 -10
- package/src/{plugin.ts → index.ts} +6 -2
- package/src/{models/History.ts → model.ts} +1 -1
- package/src/patch.ts +11 -11
- package/src/version.ts +2 -0
- package/tests/em.test.ts +1 -1
- package/tests/helpers.test.ts +2 -2
- package/tests/patch.test.ts +1 -1
- package/tests/plugin-event-created.test.ts +2 -2
- package/tests/plugin-event-deleted.test.ts +2 -2
- package/tests/plugin-event-updated.test.ts +2 -2
- package/tests/plugin-global.test.ts +2 -2
- package/tests/plugin-omit-all.test.ts +2 -2
- package/tests/plugin-patch-history-disabled.test.ts +2 -2
- package/tests/plugin-pre-delete.test.ts +1 -1
- package/tests/plugin-pre-save.test.ts +1 -1
- package/tests/plugin.test.ts +2 -2
- package/tsconfig.json +5 -5
- package/vite.config.mts +1 -0
- package/dist/cjs/em.js +0 -9
- package/dist/cjs/em.js.map +0 -1
- package/dist/cjs/helpers.js +0 -43
- package/dist/cjs/helpers.js.map +0 -1
- package/dist/cjs/hooks/delete-hooks.js +0 -46
- package/dist/cjs/hooks/delete-hooks.js.map +0 -1
- package/dist/cjs/hooks/save-hooks.js +0 -30
- package/dist/cjs/hooks/save-hooks.js.map +0 -1
- package/dist/cjs/hooks/update-hooks.js +0 -93
- package/dist/cjs/hooks/update-hooks.js.map +0 -1
- package/dist/cjs/models/History.js +0 -46
- package/dist/cjs/models/History.js.map +0 -1
- package/dist/cjs/patch.js +0 -144
- package/dist/cjs/patch.js.map +0 -1
- package/dist/cjs/plugin.js +0 -51
- package/dist/cjs/plugin.js.map +0 -1
- package/dist/cjs/types/em.d.ts +0 -6
- package/dist/cjs/types/em.d.ts.map +0 -1
- package/dist/cjs/types/helpers.d.ts +0 -6
- package/dist/cjs/types/helpers.d.ts.map +0 -1
- package/dist/cjs/types/hooks/delete-hooks.d.ts +0 -4
- package/dist/cjs/types/hooks/delete-hooks.d.ts.map +0 -1
- package/dist/cjs/types/hooks/save-hooks.d.ts +0 -4
- package/dist/cjs/types/hooks/save-hooks.d.ts.map +0 -1
- package/dist/cjs/types/hooks/update-hooks.d.ts +0 -9
- package/dist/cjs/types/hooks/update-hooks.d.ts.map +0 -1
- package/dist/cjs/types/models/History.d.ts +0 -17
- package/dist/cjs/types/models/History.d.ts.map +0 -1
- package/dist/cjs/types/patch.d.ts +0 -15
- package/dist/cjs/types/patch.d.ts.map +0 -1
- package/dist/cjs/types/plugin.d.ts +0 -23
- package/dist/cjs/types/plugin.d.ts.map +0 -1
- package/dist/cjs/types/types.d.ts +0 -49
- package/dist/cjs/types/types.d.ts.map +0 -1
- package/dist/cjs/types/version.d.ts +0 -4
- package/dist/cjs/types/version.d.ts.map +0 -1
- package/dist/cjs/types.js +0 -3
- package/dist/cjs/types.js.map +0 -1
- package/dist/cjs/version.js +0 -13
- package/dist/cjs/version.js.map +0 -1
- package/dist/esm/em.js +0 -6
- package/dist/esm/em.js.map +0 -1
- package/dist/esm/helpers.js +0 -37
- package/dist/esm/helpers.js.map +0 -1
- package/dist/esm/hooks/delete-hooks.js +0 -42
- package/dist/esm/hooks/delete-hooks.js.map +0 -1
- package/dist/esm/hooks/save-hooks.js +0 -26
- package/dist/esm/hooks/save-hooks.js.map +0 -1
- package/dist/esm/hooks/update-hooks.js +0 -87
- package/dist/esm/hooks/update-hooks.js.map +0 -1
- package/dist/esm/models/History.js +0 -43
- package/dist/esm/models/History.js.map +0 -1
- package/dist/esm/package.json +0 -1
- package/dist/esm/patch.js +0 -129
- package/dist/esm/patch.js.map +0 -1
- package/dist/esm/plugin.js +0 -45
- package/dist/esm/plugin.js.map +0 -1
- package/dist/esm/types/em.d.ts +0 -6
- package/dist/esm/types/em.d.ts.map +0 -1
- package/dist/esm/types/helpers.d.ts +0 -6
- package/dist/esm/types/helpers.d.ts.map +0 -1
- package/dist/esm/types/hooks/delete-hooks.d.ts +0 -4
- package/dist/esm/types/hooks/delete-hooks.d.ts.map +0 -1
- package/dist/esm/types/hooks/save-hooks.d.ts +0 -4
- package/dist/esm/types/hooks/save-hooks.d.ts.map +0 -1
- package/dist/esm/types/hooks/update-hooks.d.ts +0 -9
- package/dist/esm/types/hooks/update-hooks.d.ts.map +0 -1
- package/dist/esm/types/models/History.d.ts +0 -17
- package/dist/esm/types/models/History.d.ts.map +0 -1
- package/dist/esm/types/patch.d.ts +0 -15
- package/dist/esm/types/patch.d.ts.map +0 -1
- package/dist/esm/types/plugin.d.ts +0 -23
- package/dist/esm/types/plugin.d.ts.map +0 -1
- package/dist/esm/types/types.d.ts +0 -49
- package/dist/esm/types/types.d.ts.map +0 -1
- package/dist/esm/types/version.d.ts +0 -4
- package/dist/esm/types/version.d.ts.map +0 -1
- package/dist/esm/types.js +0 -2
- package/dist/esm/types.js.map +0 -1
- package/dist/esm/version.js +0 -9
- package/dist/esm/version.js.map +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -30,13 +30,13 @@ I need to track changes of mongoose models and save them as patch history (audit
|
|
|
30
30
|
|
|
31
31
|
## Features
|
|
32
32
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
33
|
+
- Track changes in mongoose models
|
|
34
|
+
- Save changes in a separate collection as a patch history
|
|
35
|
+
- Emit events when a model is created, updated or deleted
|
|
36
|
+
- Omit fields that you don't want to track in patch history
|
|
37
|
+
- Subscribe to one/many types of event
|
|
38
|
+
- Use events or patch history or both
|
|
39
|
+
- Supports ESM and CommonJS
|
|
40
40
|
|
|
41
41
|
## Installation
|
|
42
42
|
|
|
@@ -71,7 +71,7 @@ bun add mongoose@8
|
|
|
71
71
|
|
|
72
72
|
## Example
|
|
73
73
|
|
|
74
|
-
How to use it with express [ts-express-
|
|
74
|
+
How to use it with express [ts-express-tsx](https://github.com/ilovepixelart/ts-express-tsx)
|
|
75
75
|
|
|
76
76
|
Create your event constants `events.ts`
|
|
77
77
|
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var _ = require('lodash');
|
|
4
|
+
var EventEmitter = require('node:events');
|
|
5
|
+
var ms = require('ms');
|
|
6
|
+
var mongoose = require('mongoose');
|
|
7
|
+
var jsonpatch = require('fast-json-patch');
|
|
8
|
+
var omit = require('omit-deep');
|
|
9
|
+
var semver = require('semver');
|
|
10
|
+
var powerAssign = require('power-assign');
|
|
11
|
+
|
|
12
|
+
class PatchEventEmitter extends EventEmitter {
|
|
13
|
+
}
|
|
14
|
+
const em = new PatchEventEmitter();
|
|
15
|
+
|
|
16
|
+
const HistorySchema = new mongoose.Schema(
|
|
17
|
+
{
|
|
18
|
+
op: {
|
|
19
|
+
type: String,
|
|
20
|
+
required: true
|
|
21
|
+
},
|
|
22
|
+
modelName: {
|
|
23
|
+
type: String,
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
collectionName: {
|
|
27
|
+
type: String,
|
|
28
|
+
required: true
|
|
29
|
+
},
|
|
30
|
+
collectionId: {
|
|
31
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
32
|
+
required: true
|
|
33
|
+
},
|
|
34
|
+
doc: {
|
|
35
|
+
type: Object
|
|
36
|
+
},
|
|
37
|
+
patch: {
|
|
38
|
+
type: Array
|
|
39
|
+
},
|
|
40
|
+
user: {
|
|
41
|
+
type: Object
|
|
42
|
+
},
|
|
43
|
+
reason: {
|
|
44
|
+
type: String
|
|
45
|
+
},
|
|
46
|
+
metadata: {
|
|
47
|
+
type: Object
|
|
48
|
+
},
|
|
49
|
+
version: {
|
|
50
|
+
type: Number,
|
|
51
|
+
min: 0,
|
|
52
|
+
default: 0
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{ timestamps: true }
|
|
56
|
+
);
|
|
57
|
+
HistorySchema.index({ collectionId: 1, version: -1 });
|
|
58
|
+
HistorySchema.index({ op: 1, modelName: 1, collectionName: 1, collectionId: 1, reason: 1, version: 1 });
|
|
59
|
+
const HistoryModel = mongoose.model("History", HistorySchema, "history");
|
|
60
|
+
|
|
61
|
+
const isHookIgnored = (options) => {
|
|
62
|
+
return options.ignoreHook === true || options.ignoreEvent === true && options.ignorePatchHistory === true;
|
|
63
|
+
};
|
|
64
|
+
const toObjectOptions = {
|
|
65
|
+
depopulate: true,
|
|
66
|
+
virtuals: false
|
|
67
|
+
};
|
|
68
|
+
const setPatchHistoryTTL = async (ttl) => {
|
|
69
|
+
const name = "createdAt_1_TTL";
|
|
70
|
+
try {
|
|
71
|
+
const indexes = await HistoryModel.collection.indexes();
|
|
72
|
+
const existingIndex = indexes?.find((index) => index.name === name);
|
|
73
|
+
if (!ttl && existingIndex) {
|
|
74
|
+
await HistoryModel.collection.dropIndex(name);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const milliseconds = typeof ttl === "string" ? ms(ttl) : ttl;
|
|
78
|
+
if (milliseconds < 1e3 && existingIndex) {
|
|
79
|
+
await HistoryModel.collection.dropIndex(name);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const expireAfterSeconds = milliseconds / 1e3;
|
|
83
|
+
if (existingIndex && existingIndex.expireAfterSeconds === expireAfterSeconds) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (existingIndex) {
|
|
87
|
+
await HistoryModel.collection.dropIndex(name);
|
|
88
|
+
}
|
|
89
|
+
await HistoryModel.collection.createIndex({ createdAt: 1 }, { expireAfterSeconds, name });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error("Couldn't create or update index for history collection", err);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function isPatchHistoryEnabled(opts, context) {
|
|
96
|
+
return !opts.patchHistoryDisabled && !context.ignorePatchHistory;
|
|
97
|
+
}
|
|
98
|
+
function getJsonOmit(opts, doc) {
|
|
99
|
+
const object = JSON.parse(JSON.stringify(doc));
|
|
100
|
+
if (opts.omit) {
|
|
101
|
+
return omit(object, opts.omit);
|
|
102
|
+
}
|
|
103
|
+
return object;
|
|
104
|
+
}
|
|
105
|
+
function getObjectOmit(opts, doc) {
|
|
106
|
+
if (opts.omit) {
|
|
107
|
+
return omit(_.isFunction(doc?.toObject) ? doc.toObject() : doc, opts.omit);
|
|
108
|
+
}
|
|
109
|
+
return doc;
|
|
110
|
+
}
|
|
111
|
+
async function getUser(opts) {
|
|
112
|
+
if (_.isFunction(opts.getUser)) {
|
|
113
|
+
return await opts.getUser();
|
|
114
|
+
}
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
async function getReason(opts) {
|
|
118
|
+
if (_.isFunction(opts.getReason)) {
|
|
119
|
+
return await opts.getReason();
|
|
120
|
+
}
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
async function getMetadata(opts) {
|
|
124
|
+
if (_.isFunction(opts.getMetadata)) {
|
|
125
|
+
return await opts.getMetadata();
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
function getValue(item) {
|
|
130
|
+
return item.status === "fulfilled" ? item.value : void 0;
|
|
131
|
+
}
|
|
132
|
+
async function getData(opts) {
|
|
133
|
+
return Promise.allSettled([getUser(opts), getReason(opts), getMetadata(opts)]).then(([user, reason, metadata]) => {
|
|
134
|
+
return [getValue(user), getValue(reason), getValue(metadata)];
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function emitEvent(context, event, data) {
|
|
138
|
+
if (event && !context.ignoreEvent) {
|
|
139
|
+
em.emit(event, data);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function bulkPatch(opts, context, eventKey, docsKey) {
|
|
143
|
+
const history = isPatchHistoryEnabled(opts, context);
|
|
144
|
+
const event = opts[eventKey];
|
|
145
|
+
const docs = context[docsKey];
|
|
146
|
+
const key = eventKey === "eventCreated" ? "doc" : "oldDoc";
|
|
147
|
+
if (_.isEmpty(docs) || !event && !history) return;
|
|
148
|
+
const [user, reason, metadata] = await getData(opts);
|
|
149
|
+
const chunks = _.chunk(docs, 1e3);
|
|
150
|
+
for await (const chunk of chunks) {
|
|
151
|
+
const bulk = [];
|
|
152
|
+
for (const doc of chunk) {
|
|
153
|
+
emitEvent(context, event, { [key]: doc });
|
|
154
|
+
if (history) {
|
|
155
|
+
bulk.push({
|
|
156
|
+
insertOne: {
|
|
157
|
+
document: {
|
|
158
|
+
op: context.op,
|
|
159
|
+
modelName: context.modelName,
|
|
160
|
+
collectionName: context.collectionName,
|
|
161
|
+
collectionId: doc._id,
|
|
162
|
+
doc: getObjectOmit(opts, doc),
|
|
163
|
+
user,
|
|
164
|
+
reason,
|
|
165
|
+
metadata,
|
|
166
|
+
version: 0
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (history && !_.isEmpty(bulk)) {
|
|
173
|
+
await HistoryModel.bulkWrite(bulk, { ordered: false }).catch((error) => {
|
|
174
|
+
console.error(error.message);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function createPatch(opts, context) {
|
|
180
|
+
await bulkPatch(opts, context, "eventCreated", "createdDocs");
|
|
181
|
+
}
|
|
182
|
+
async function updatePatch(opts, context, current, original) {
|
|
183
|
+
const history = isPatchHistoryEnabled(opts, context);
|
|
184
|
+
const currentObject = getJsonOmit(opts, current);
|
|
185
|
+
const originalObject = getJsonOmit(opts, original);
|
|
186
|
+
if (_.isEmpty(originalObject) || _.isEmpty(currentObject)) return;
|
|
187
|
+
const patch = jsonpatch.compare(originalObject, currentObject, true);
|
|
188
|
+
if (_.isEmpty(patch)) return;
|
|
189
|
+
emitEvent(context, opts.eventUpdated, { oldDoc: original, doc: current, patch });
|
|
190
|
+
if (history) {
|
|
191
|
+
let version = 0;
|
|
192
|
+
const lastHistory = await HistoryModel.findOne({ collectionId: original._id }).sort("-version").exec();
|
|
193
|
+
if (lastHistory && lastHistory.version >= 0) {
|
|
194
|
+
version = lastHistory.version + 1;
|
|
195
|
+
}
|
|
196
|
+
const [user, reason, metadata] = await getData(opts);
|
|
197
|
+
await HistoryModel.create({
|
|
198
|
+
op: context.op,
|
|
199
|
+
modelName: context.modelName,
|
|
200
|
+
collectionName: context.collectionName,
|
|
201
|
+
collectionId: original._id,
|
|
202
|
+
patch,
|
|
203
|
+
user,
|
|
204
|
+
reason,
|
|
205
|
+
metadata,
|
|
206
|
+
version
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function deletePatch(opts, context) {
|
|
211
|
+
await bulkPatch(opts, context, "eventDeleted", "deletedDocs");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const isMongooseLessThan8 = semver.satisfies(mongoose.version, "<8");
|
|
215
|
+
const isMongooseLessThan7 = semver.satisfies(mongoose.version, "<7");
|
|
216
|
+
const isMongoose6 = semver.satisfies(mongoose.version, "6");
|
|
217
|
+
if (isMongoose6) {
|
|
218
|
+
mongoose.set("strictQuery", false);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const deleteMethods = ["remove", "findOneAndDelete", "findOneAndRemove", "findByIdAndDelete", "findByIdAndRemove", "deleteOne", "deleteMany"];
|
|
222
|
+
const deleteHooksInitialize = (schema, opts) => {
|
|
223
|
+
schema.pre(deleteMethods, { document: false, query: true }, async function() {
|
|
224
|
+
const options = this.getOptions();
|
|
225
|
+
if (isHookIgnored(options)) return;
|
|
226
|
+
const model = this.model;
|
|
227
|
+
const filter = this.getFilter();
|
|
228
|
+
this._context = {
|
|
229
|
+
op: this.op,
|
|
230
|
+
modelName: opts.modelName ?? this.model.modelName,
|
|
231
|
+
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
232
|
+
ignoreEvent: options.ignoreEvent,
|
|
233
|
+
ignorePatchHistory: options.ignorePatchHistory
|
|
234
|
+
};
|
|
235
|
+
if (["remove", "deleteMany"].includes(this._context.op) && !options.single) {
|
|
236
|
+
const docs = await model.find(filter).lean().exec();
|
|
237
|
+
if (!_.isEmpty(docs)) {
|
|
238
|
+
this._context.deletedDocs = docs;
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
const doc = await model.findOne(filter).lean().exec();
|
|
242
|
+
if (!_.isEmpty(doc)) {
|
|
243
|
+
this._context.deletedDocs = [doc];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (opts.preDelete && _.isArray(this._context.deletedDocs) && !_.isEmpty(this._context.deletedDocs)) {
|
|
247
|
+
await opts.preDelete(this._context.deletedDocs);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
schema.post(deleteMethods, { document: false, query: true }, async function() {
|
|
251
|
+
const options = this.getOptions();
|
|
252
|
+
if (isHookIgnored(options)) return;
|
|
253
|
+
await deletePatch(opts, this._context);
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const saveHooksInitialize = (schema, opts) => {
|
|
258
|
+
schema.pre("save", async function() {
|
|
259
|
+
if (this.constructor.name !== "model") return;
|
|
260
|
+
const current = this.toObject(toObjectOptions);
|
|
261
|
+
const model = this.constructor;
|
|
262
|
+
const context = {
|
|
263
|
+
op: this.isNew ? "create" : "update",
|
|
264
|
+
modelName: opts.modelName ?? model.modelName,
|
|
265
|
+
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
266
|
+
createdDocs: [current]
|
|
267
|
+
};
|
|
268
|
+
if (this.isNew) {
|
|
269
|
+
await createPatch(opts, context);
|
|
270
|
+
} else {
|
|
271
|
+
const original = await model.findById(current._id).lean().exec();
|
|
272
|
+
if (original) {
|
|
273
|
+
await updatePatch(opts, context, current, original);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const updateMethods = ["update", "updateOne", "replaceOne", "updateMany", "findOneAndUpdate", "findOneAndReplace", "findByIdAndUpdate"];
|
|
280
|
+
const assignUpdate = (document, update, commands) => {
|
|
281
|
+
let updated = powerAssign.assign(document.toObject(toObjectOptions), update);
|
|
282
|
+
_.forEach(commands, (command) => {
|
|
283
|
+
try {
|
|
284
|
+
updated = powerAssign.assign(updated, command);
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
const doc = document.set(updated).toObject(toObjectOptions);
|
|
289
|
+
if (update.createdAt) doc.createdAt = update.createdAt;
|
|
290
|
+
return doc;
|
|
291
|
+
};
|
|
292
|
+
const splitUpdateAndCommands = (updateQuery) => {
|
|
293
|
+
let update = {};
|
|
294
|
+
const commands = [];
|
|
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) => {
|
|
300
|
+
commands.push({ [key]: update[key] });
|
|
301
|
+
delete update[key];
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { update, commands };
|
|
306
|
+
};
|
|
307
|
+
const updateHooksInitialize = (schema, opts) => {
|
|
308
|
+
schema.pre(updateMethods, async function() {
|
|
309
|
+
const options = this.getOptions();
|
|
310
|
+
if (isHookIgnored(options)) return;
|
|
311
|
+
const model = this.model;
|
|
312
|
+
const filter = this.getFilter();
|
|
313
|
+
const count = await this.model.countDocuments(filter).exec();
|
|
314
|
+
this._context = {
|
|
315
|
+
op: this.op,
|
|
316
|
+
modelName: opts.modelName ?? this.model.modelName,
|
|
317
|
+
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
318
|
+
isNew: Boolean(options.upsert) && count === 0,
|
|
319
|
+
ignoreEvent: options.ignoreEvent,
|
|
320
|
+
ignorePatchHistory: options.ignorePatchHistory
|
|
321
|
+
};
|
|
322
|
+
const updateQuery = this.getUpdate();
|
|
323
|
+
const { update, commands } = splitUpdateAndCommands(updateQuery);
|
|
324
|
+
const cursor = model.find(filter).cursor();
|
|
325
|
+
await cursor.eachAsync(async (doc) => {
|
|
326
|
+
const origDoc = doc.toObject(toObjectOptions);
|
|
327
|
+
await updatePatch(opts, this._context, assignUpdate(doc, update, commands), origDoc);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
schema.post(updateMethods, async function() {
|
|
331
|
+
const options = this.getOptions();
|
|
332
|
+
if (isHookIgnored(options)) return;
|
|
333
|
+
if (!this._context.isNew) return;
|
|
334
|
+
const model = this.model;
|
|
335
|
+
const updateQuery = this.getUpdate();
|
|
336
|
+
const { update, commands } = splitUpdateAndCommands(updateQuery);
|
|
337
|
+
let current = null;
|
|
338
|
+
const filter = this.getFilter();
|
|
339
|
+
const combined = assignUpdate(model.hydrate({}), update, commands);
|
|
340
|
+
if (!_.isEmpty(update) && !current) {
|
|
341
|
+
current = await model.findOne(update).sort("desc").lean().exec();
|
|
342
|
+
}
|
|
343
|
+
if (!_.isEmpty(combined) && !current) {
|
|
344
|
+
current = await model.findOne(combined).sort("desc").lean().exec();
|
|
345
|
+
}
|
|
346
|
+
if (!_.isEmpty(filter) && !current) {
|
|
347
|
+
console.log("filter", filter);
|
|
348
|
+
current = await model.findOne(filter).sort("desc").lean().exec();
|
|
349
|
+
}
|
|
350
|
+
if (current) {
|
|
351
|
+
this._context.createdDocs = [current];
|
|
352
|
+
await createPatch(opts, this._context);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const remove = isMongooseLessThan7 ? "remove" : "deleteOne";
|
|
358
|
+
const patchEventEmitter = em;
|
|
359
|
+
const patchHistoryPlugin = function plugin(schema, opts) {
|
|
360
|
+
saveHooksInitialize(schema, opts);
|
|
361
|
+
updateHooksInitialize(schema, opts);
|
|
362
|
+
deleteHooksInitialize(schema, opts);
|
|
363
|
+
schema.post("insertMany", async function(docs) {
|
|
364
|
+
const context = {
|
|
365
|
+
op: "create",
|
|
366
|
+
modelName: opts.modelName ?? this.modelName,
|
|
367
|
+
collectionName: opts.collectionName ?? this.collection.collectionName,
|
|
368
|
+
createdDocs: docs
|
|
369
|
+
};
|
|
370
|
+
await createPatch(opts, context);
|
|
371
|
+
});
|
|
372
|
+
if (isMongooseLessThan8) {
|
|
373
|
+
schema.pre(remove, { document: true, query: false }, async function() {
|
|
374
|
+
const original = this.toObject(toObjectOptions);
|
|
375
|
+
if (opts.preDelete && !_.isEmpty(original)) {
|
|
376
|
+
await opts.preDelete([original]);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
schema.post(remove, { document: true, query: false }, async function() {
|
|
380
|
+
const original = this.toObject(toObjectOptions);
|
|
381
|
+
const model = this.constructor;
|
|
382
|
+
const context = {
|
|
383
|
+
op: "delete",
|
|
384
|
+
modelName: opts.modelName ?? model.modelName,
|
|
385
|
+
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
386
|
+
deletedDocs: [original]
|
|
387
|
+
};
|
|
388
|
+
await deletePatch(opts, context);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
exports.patchEventEmitter = patchEventEmitter;
|
|
394
|
+
exports.patchHistoryPlugin = patchHistoryPlugin;
|
|
395
|
+
exports.setPatchHistoryTTL = setPatchHistoryTTL;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { HydratedDocument, Types, Query, Schema } from 'mongoose';
|
|
2
|
+
import { Operation } from 'fast-json-patch';
|
|
3
|
+
import ms from 'ms';
|
|
4
|
+
|
|
5
|
+
interface History {
|
|
6
|
+
op: string;
|
|
7
|
+
modelName: string;
|
|
8
|
+
collectionName: string;
|
|
9
|
+
collectionId: Types.ObjectId;
|
|
10
|
+
version: number;
|
|
11
|
+
doc?: object;
|
|
12
|
+
user?: object;
|
|
13
|
+
reason?: string;
|
|
14
|
+
metadata?: object;
|
|
15
|
+
patch?: Operation[];
|
|
16
|
+
}
|
|
17
|
+
interface PatchEvent<T> {
|
|
18
|
+
oldDoc?: HydratedDocument<T>;
|
|
19
|
+
doc?: HydratedDocument<T>;
|
|
20
|
+
patch?: Operation[];
|
|
21
|
+
}
|
|
22
|
+
interface PatchContext<T> {
|
|
23
|
+
op: string;
|
|
24
|
+
modelName: string;
|
|
25
|
+
collectionName: string;
|
|
26
|
+
isNew?: boolean;
|
|
27
|
+
createdDocs?: HydratedDocument<T>[];
|
|
28
|
+
deletedDocs?: HydratedDocument<T>[];
|
|
29
|
+
ignoreEvent?: boolean;
|
|
30
|
+
ignorePatchHistory?: boolean;
|
|
31
|
+
}
|
|
32
|
+
type HookContext<T> = Query<T, T> & {
|
|
33
|
+
op: string;
|
|
34
|
+
_context: PatchContext<T>;
|
|
35
|
+
};
|
|
36
|
+
type User = Record<string, unknown>;
|
|
37
|
+
type Metadata = Record<string, unknown>;
|
|
38
|
+
interface PluginOptions<T> {
|
|
39
|
+
modelName?: string;
|
|
40
|
+
collectionName?: string;
|
|
41
|
+
eventUpdated?: string;
|
|
42
|
+
eventCreated?: string;
|
|
43
|
+
eventDeleted?: string;
|
|
44
|
+
getUser?: () => Promise<User> | User;
|
|
45
|
+
getReason?: () => Promise<string> | string;
|
|
46
|
+
getMetadata?: () => Promise<Metadata> | Metadata;
|
|
47
|
+
omit?: string[];
|
|
48
|
+
patchHistoryDisabled?: boolean;
|
|
49
|
+
preDelete?: (docs: HydratedDocument<T>[]) => Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare const setPatchHistoryTTL: (ttl: number | ms.StringValue) => Promise<void>;
|
|
53
|
+
|
|
54
|
+
declare const patchEventEmitter: {
|
|
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
|
+
};
|
|
72
|
+
|
|
73
|
+
declare const patchHistoryPlugin: <T>(schema: Schema<T>, opts: PluginOptions<T>) => void;
|
|
74
|
+
|
|
75
|
+
export { type History, type HookContext, type Metadata, type PatchContext, type PatchEvent, type PluginOptions, type User, patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { HydratedDocument, Types, Query, Schema } from 'mongoose';
|
|
2
|
+
import { Operation } from 'fast-json-patch';
|
|
3
|
+
import ms from 'ms';
|
|
4
|
+
|
|
5
|
+
interface History {
|
|
6
|
+
op: string;
|
|
7
|
+
modelName: string;
|
|
8
|
+
collectionName: string;
|
|
9
|
+
collectionId: Types.ObjectId;
|
|
10
|
+
version: number;
|
|
11
|
+
doc?: object;
|
|
12
|
+
user?: object;
|
|
13
|
+
reason?: string;
|
|
14
|
+
metadata?: object;
|
|
15
|
+
patch?: Operation[];
|
|
16
|
+
}
|
|
17
|
+
interface PatchEvent<T> {
|
|
18
|
+
oldDoc?: HydratedDocument<T>;
|
|
19
|
+
doc?: HydratedDocument<T>;
|
|
20
|
+
patch?: Operation[];
|
|
21
|
+
}
|
|
22
|
+
interface PatchContext<T> {
|
|
23
|
+
op: string;
|
|
24
|
+
modelName: string;
|
|
25
|
+
collectionName: string;
|
|
26
|
+
isNew?: boolean;
|
|
27
|
+
createdDocs?: HydratedDocument<T>[];
|
|
28
|
+
deletedDocs?: HydratedDocument<T>[];
|
|
29
|
+
ignoreEvent?: boolean;
|
|
30
|
+
ignorePatchHistory?: boolean;
|
|
31
|
+
}
|
|
32
|
+
type HookContext<T> = Query<T, T> & {
|
|
33
|
+
op: string;
|
|
34
|
+
_context: PatchContext<T>;
|
|
35
|
+
};
|
|
36
|
+
type User = Record<string, unknown>;
|
|
37
|
+
type Metadata = Record<string, unknown>;
|
|
38
|
+
interface PluginOptions<T> {
|
|
39
|
+
modelName?: string;
|
|
40
|
+
collectionName?: string;
|
|
41
|
+
eventUpdated?: string;
|
|
42
|
+
eventCreated?: string;
|
|
43
|
+
eventDeleted?: string;
|
|
44
|
+
getUser?: () => Promise<User> | User;
|
|
45
|
+
getReason?: () => Promise<string> | string;
|
|
46
|
+
getMetadata?: () => Promise<Metadata> | Metadata;
|
|
47
|
+
omit?: string[];
|
|
48
|
+
patchHistoryDisabled?: boolean;
|
|
49
|
+
preDelete?: (docs: HydratedDocument<T>[]) => Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare const setPatchHistoryTTL: (ttl: number | ms.StringValue) => Promise<void>;
|
|
53
|
+
|
|
54
|
+
declare const patchEventEmitter: {
|
|
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
|
+
};
|
|
72
|
+
|
|
73
|
+
declare const patchHistoryPlugin: <T>(schema: Schema<T>, opts: PluginOptions<T>) => void;
|
|
74
|
+
|
|
75
|
+
export { type History, type HookContext, type Metadata, type PatchContext, type PatchEvent, type PluginOptions, type User, patchEventEmitter, patchHistoryPlugin, setPatchHistoryTTL };
|