ts-patch-mongoose 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/cjs/helpers.js +12 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/hooks/delete-hooks.js +55 -0
- package/dist/cjs/hooks/delete-hooks.js.map +1 -0
- package/dist/cjs/hooks/save-hooks.js +28 -0
- package/dist/cjs/hooks/save-hooks.js.map +1 -0
- package/dist/cjs/hooks/update-hooks.js +89 -0
- package/dist/cjs/hooks/update-hooks.js.map +1 -0
- package/dist/cjs/plugin.js +26 -163
- package/dist/cjs/plugin.js.map +1 -1
- package/dist/cjs/types/helpers.d.ts +4 -0
- package/dist/cjs/types/helpers.d.ts.map +1 -0
- package/dist/cjs/types/hooks/delete-hooks.d.ts +28 -0
- package/dist/cjs/types/hooks/delete-hooks.d.ts.map +1 -0
- package/dist/cjs/types/hooks/save-hooks.d.ts +28 -0
- package/dist/cjs/types/hooks/save-hooks.d.ts.map +1 -0
- package/dist/cjs/types/hooks/update-hooks.d.ts +33 -0
- package/dist/cjs/types/hooks/update-hooks.d.ts.map +1 -0
- package/dist/cjs/types/models/History.d.ts +7 -1
- package/dist/cjs/types/models/History.d.ts.map +1 -1
- package/dist/cjs/types/plugin.d.ts +3 -1
- package/dist/cjs/types/plugin.d.ts.map +1 -1
- package/dist/cjs/types/version.d.ts +1 -0
- package/dist/cjs/types/version.d.ts.map +1 -1
- package/dist/cjs/version.js +2 -1
- package/dist/cjs/version.js.map +1 -1
- package/dist/esm/helpers.js +12 -0
- package/dist/esm/helpers.js.map +1 -0
- package/dist/esm/hooks/delete-hooks.js +55 -0
- package/dist/esm/hooks/delete-hooks.js.map +1 -0
- package/dist/esm/hooks/save-hooks.js +28 -0
- package/dist/esm/hooks/save-hooks.js.map +1 -0
- package/dist/esm/hooks/update-hooks.js +89 -0
- package/dist/esm/hooks/update-hooks.js.map +1 -0
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/plugin.mjs +26 -163
- package/dist/esm/types/helpers.d.ts +4 -0
- package/dist/esm/types/helpers.d.ts.map +1 -0
- package/dist/esm/types/hooks/delete-hooks.d.ts +28 -0
- package/dist/esm/types/hooks/delete-hooks.d.ts.map +1 -0
- package/dist/esm/types/hooks/save-hooks.d.ts +28 -0
- package/dist/esm/types/hooks/save-hooks.d.ts.map +1 -0
- package/dist/esm/types/hooks/update-hooks.d.ts +33 -0
- package/dist/esm/types/hooks/update-hooks.d.ts.map +1 -0
- package/dist/esm/types/models/History.d.ts +7 -1
- package/dist/esm/types/models/History.d.ts.map +1 -1
- package/dist/esm/types/plugin.d.ts +3 -1
- package/dist/esm/types/plugin.d.ts.map +1 -1
- package/dist/esm/types/version.d.ts +1 -0
- package/dist/esm/types/version.d.ts.map +1 -1
- package/dist/esm/version.js +2 -1
- package/dist/esm/version.js.map +1 -1
- package/package.json +12 -12
- package/src/helpers.ts +10 -0
- package/src/hooks/delete-hooks.ts +59 -0
- package/src/hooks/save-hooks.ts +29 -0
- package/src/hooks/update-hooks.ts +100 -0
- package/src/plugin.ts +42 -194
- package/src/version.ts +1 -0
- package/tests/plugin-event-deleted.test.ts +13 -10
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
/// <reference types="mongoose/types/inferschematype" />
|
|
25
25
|
import { Schema } from 'mongoose';
|
|
26
26
|
import type IHistory from '../interfaces/IHistory';
|
|
27
|
-
declare const History: import("mongoose").Model<IHistory, {}, {}, {},
|
|
27
|
+
declare const History: import("mongoose").Model<IHistory, {}, {}, {}, import("mongoose").Document<unknown, {}, IHistory> & IHistory & {
|
|
28
|
+
_id: import("mongoose").Types.ObjectId;
|
|
29
|
+
}, Schema<IHistory, import("mongoose").Model<IHistory, any, any, any, import("mongoose").Document<unknown, any, IHistory> & IHistory & {
|
|
30
|
+
_id: import("mongoose").Types.ObjectId;
|
|
31
|
+
}, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, IHistory, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<IHistory>> & import("mongoose").FlatRecord<IHistory> & {
|
|
32
|
+
_id: import("mongoose").Types.ObjectId;
|
|
33
|
+
}>>;
|
|
28
34
|
export default History;
|
|
29
35
|
//# sourceMappingURL=History.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"History.d.ts","sourceRoot":"","sources":["../../../../src/models/History.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAS,MAAM,UAAU,CAAA;AAExC,OAAO,KAAK,QAAQ,MAAM,wBAAwB,CAAA;AA4ClD,QAAA,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"History.d.ts","sourceRoot":"","sources":["../../../../src/models/History.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAS,MAAM,UAAU,CAAA;AAExC,OAAO,KAAK,QAAQ,MAAM,wBAAwB,CAAA;AA4ClD,QAAA,MAAM,OAAO;;;;;;GAA6C,CAAA;AAE1D,eAAe,OAAO,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
/// <reference types="mongoose/types/aggregate" />
|
|
2
3
|
/// <reference types="mongoose/types/callback" />
|
|
3
4
|
/// <reference types="mongoose/types/collection" />
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
import type { Model, Schema } from 'mongoose';
|
|
26
27
|
import type IPluginOptions from './interfaces/IPluginOptions';
|
|
27
28
|
export declare const patchEventEmitter: {
|
|
29
|
+
[EventEmitter.captureRejectionSymbol]?(error: Error, event: string, ...args: any[]): void;
|
|
28
30
|
addListener(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
29
31
|
on(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
30
32
|
once(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
@@ -41,5 +43,5 @@ export declare const patchEventEmitter: {
|
|
|
41
43
|
prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): any;
|
|
42
44
|
eventNames(): (string | symbol)[];
|
|
43
45
|
};
|
|
44
|
-
export declare const patchHistoryPlugin: <T>(schema: Schema<T, Model<T, any, any, any, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, import("mongoose").ObtainDocumentType<any, T, import("mongoose").DefaultSchemaOptions>>, opts: IPluginOptions<T>) => void;
|
|
46
|
+
export declare const patchHistoryPlugin: <T>(schema: Schema<T, Model<T, any, any, any, import("mongoose").IfAny<T, any, import("mongoose").Document<unknown, any, T> & import("mongoose").Require_id<T>>, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, import("mongoose").ObtainDocumentType<any, T, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>>, import("mongoose").IfAny<import("mongoose").FlatRecord<import("mongoose").ObtainDocumentType<any, T, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>>>, any, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<import("mongoose").ObtainDocumentType<any, T, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>>>> & import("mongoose").Require_id<import("mongoose").FlatRecord<import("mongoose").ObtainDocumentType<any, T, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>>>>>>, opts: IPluginOptions<T>) => void;
|
|
45
47
|
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAWA,OAAO,KAAK,EAAoB,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAC/D,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAA;AAQ7D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;CAAK,CAAA;AAQnC,eAAO,MAAM,kBAAkB,y8BAAoE,IA+ClG,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/version.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,SAAoC,CAAA;AACpE,eAAO,MAAM,WAAW,SAAmC,CAAA"}
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/version.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,SAAoC,CAAA;AACpE,eAAO,MAAM,mBAAmB,SAAoC,CAAA;AACpE,eAAO,MAAM,WAAW,SAAmC,CAAA"}
|
package/dist/esm/version.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isMongoose6 = exports.isMongooseLessThan7 = void 0;
|
|
3
|
+
exports.isMongoose6 = exports.isMongooseLessThan7 = exports.isMongooseLessThan8 = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const semver_1 = require("semver");
|
|
6
6
|
const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
|
|
7
|
+
exports.isMongooseLessThan8 = (0, semver_1.satisfies)(mongoose_1.default.version, '<8');
|
|
7
8
|
exports.isMongooseLessThan7 = (0, semver_1.satisfies)(mongoose_1.default.version, '<7');
|
|
8
9
|
exports.isMongoose6 = (0, semver_1.satisfies)(mongoose_1.default.version, '6');
|
|
9
10
|
if (exports.isMongoose6) {
|
package/dist/esm/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;;AAAA,mCAAkC;AAClC,gEAA+B;AAElB,QAAA,mBAAmB,GAAG,IAAA,kBAAS,EAAC,kBAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACvD,QAAA,WAAW,GAAG,IAAA,kBAAS,EAAC,kBAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;AAE3D,IAAI,mBAAW,EAAE;IACf,kBAAQ,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;CACnC"}
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;;AAAA,mCAAkC;AAClC,gEAA+B;AAElB,QAAA,mBAAmB,GAAG,IAAA,kBAAS,EAAC,kBAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACvD,QAAA,mBAAmB,GAAG,IAAA,kBAAS,EAAC,kBAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACvD,QAAA,WAAW,GAAG,IAAA,kBAAS,EAAC,kBAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;AAE3D,IAAI,mBAAW,EAAE;IACf,kBAAQ,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;CACnC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-patch-mongoose",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Patch history & events for mongoose models",
|
|
5
5
|
"author": "Alex Eagle",
|
|
6
6
|
"license": "MIT",
|
|
@@ -85,22 +85,22 @@
|
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@shelf/jest-mongodb": "4.1.7",
|
|
87
87
|
"@swc/cli": "0.1.62",
|
|
88
|
-
"@swc/core": "1.3.
|
|
89
|
-
"@swc/helpers": "0.5.
|
|
88
|
+
"@swc/core": "1.3.96",
|
|
89
|
+
"@swc/helpers": "0.5.3",
|
|
90
90
|
"@swc/jest": "0.2.29",
|
|
91
91
|
"@swc/register": "0.1.10",
|
|
92
|
-
"@types/jest": "29.5.
|
|
93
|
-
"@types/lodash": "4.14.
|
|
94
|
-
"@types/node": "
|
|
95
|
-
"@typescript-eslint/eslint-plugin": "6.
|
|
96
|
-
"@typescript-eslint/parser": "6.
|
|
97
|
-
"eslint": "8.
|
|
98
|
-
"eslint-plugin-jest": "27.
|
|
92
|
+
"@types/jest": "29.5.7",
|
|
93
|
+
"@types/lodash": "4.14.200",
|
|
94
|
+
"@types/node": "20",
|
|
95
|
+
"@typescript-eslint/eslint-plugin": "6.9.1",
|
|
96
|
+
"@typescript-eslint/parser": "6.9.1",
|
|
97
|
+
"eslint": "8.53.0",
|
|
98
|
+
"eslint-plugin-jest": "27.6.0",
|
|
99
99
|
"eslint-plugin-jest-formatting": "3.1.0",
|
|
100
|
-
"eslint-plugin-sonarjs": "0.
|
|
100
|
+
"eslint-plugin-sonarjs": "0.23.0",
|
|
101
101
|
"jest": "29.7.0",
|
|
102
102
|
"merge": "2.1.1",
|
|
103
|
-
"mongoose": "
|
|
103
|
+
"mongoose": "latest",
|
|
104
104
|
"open-cli": "7.2.0",
|
|
105
105
|
"ts-node": "10.9.1",
|
|
106
106
|
"typescript": "5.2.2"
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { QueryOptions, ToObjectOptions } from 'mongoose'
|
|
2
|
+
|
|
3
|
+
export const isHookIgnored = <T>(options: QueryOptions<T>): boolean => {
|
|
4
|
+
return options.ignoreHook === true || (options.ignoreEvent === true && options.ignorePatchHistory === true)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const toObjectOptions: ToObjectOptions = {
|
|
8
|
+
depopulate: true,
|
|
9
|
+
virtuals: false
|
|
10
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
import { deletePatch } from '../patch'
|
|
4
|
+
import { isHookIgnored } from '../helpers'
|
|
5
|
+
|
|
6
|
+
import type { HydratedDocument, Model, MongooseQueryMiddleware, Schema } from 'mongoose'
|
|
7
|
+
import type IPluginOptions from '../interfaces/IPluginOptions'
|
|
8
|
+
import type IHookContext from '../interfaces/IHookContext'
|
|
9
|
+
|
|
10
|
+
const deleteMethods = [
|
|
11
|
+
'remove',
|
|
12
|
+
'findOneAndDelete',
|
|
13
|
+
'findOneAndRemove',
|
|
14
|
+
'findByIdAndDelete',
|
|
15
|
+
'findByIdAndRemove',
|
|
16
|
+
'deleteOne',
|
|
17
|
+
'deleteMany'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
export const deleteHooksInitialize = <T>(schema: Schema<T>, opts: IPluginOptions<T>): void => {
|
|
21
|
+
schema.pre(deleteMethods as MongooseQueryMiddleware[], { document: false, query: true }, async function (this: IHookContext<T>) {
|
|
22
|
+
const options = this.getOptions()
|
|
23
|
+
if (isHookIgnored(options)) return
|
|
24
|
+
|
|
25
|
+
const model = this.model as Model<T>
|
|
26
|
+
const filter = this.getFilter()
|
|
27
|
+
|
|
28
|
+
this._context = {
|
|
29
|
+
op: this.op,
|
|
30
|
+
modelName: opts.modelName ?? this.model.modelName,
|
|
31
|
+
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
32
|
+
ignoreEvent: options.ignoreEvent as boolean,
|
|
33
|
+
ignorePatchHistory: options.ignorePatchHistory as boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (['remove', 'deleteMany'].includes(this._context.op) && !options.single) {
|
|
37
|
+
const docs = await model.find(filter).lean().exec()
|
|
38
|
+
if (!_.isEmpty(docs)) {
|
|
39
|
+
this._context.deletedDocs = docs as HydratedDocument<T>[]
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
const doc = await model.findOne(filter).lean().exec()
|
|
43
|
+
if (!_.isEmpty(doc)) {
|
|
44
|
+
this._context.deletedDocs = [doc] as HydratedDocument<T>[]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (opts.preDelete && _.isArray(this._context.deletedDocs) && !_.isEmpty(this._context.deletedDocs)) {
|
|
49
|
+
await opts.preDelete(this._context.deletedDocs)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
schema.post(deleteMethods as MongooseQueryMiddleware[], { document: false, query: true }, async function (this: IHookContext<T>) {
|
|
54
|
+
const options = this.getOptions()
|
|
55
|
+
if (isHookIgnored(options)) return
|
|
56
|
+
|
|
57
|
+
await deletePatch(opts, this._context)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createPatch, updatePatch } from '../patch'
|
|
2
|
+
import { toObjectOptions } from '../helpers'
|
|
3
|
+
|
|
4
|
+
import type { HydratedDocument, Model, Schema } from 'mongoose'
|
|
5
|
+
import type IPluginOptions from '../interfaces/IPluginOptions'
|
|
6
|
+
import type IContext from '../interfaces/IContext'
|
|
7
|
+
|
|
8
|
+
export const saveHooksInitialize = <T>(schema: Schema<T>, opts: IPluginOptions<T>): void => {
|
|
9
|
+
schema.pre('save', async function () {
|
|
10
|
+
const current = this.toObject(toObjectOptions) as HydratedDocument<T>
|
|
11
|
+
const model = this.constructor as Model<T>
|
|
12
|
+
|
|
13
|
+
const context: IContext<T> = {
|
|
14
|
+
op: this.isNew ? 'create' : 'update',
|
|
15
|
+
modelName: opts.modelName ?? model.modelName,
|
|
16
|
+
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
17
|
+
createdDocs: [current]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (this.isNew) {
|
|
21
|
+
await createPatch(opts, context)
|
|
22
|
+
} else {
|
|
23
|
+
const original = await model.findById(current._id).lean().exec()
|
|
24
|
+
if (original) {
|
|
25
|
+
await updatePatch(opts, context, current, original as HydratedDocument<T>)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { assign } from 'power-assign'
|
|
3
|
+
|
|
4
|
+
import { createPatch, updatePatch } from '../patch'
|
|
5
|
+
import { isHookIgnored } from '../helpers'
|
|
6
|
+
|
|
7
|
+
import type { HydratedDocument, Model, MongooseQueryMiddleware, Schema, UpdateQuery, UpdateWithAggregationPipeline } from 'mongoose'
|
|
8
|
+
import type IPluginOptions from '../interfaces/IPluginOptions'
|
|
9
|
+
import type IHookContext from '../interfaces/IHookContext'
|
|
10
|
+
|
|
11
|
+
const updateMethods = [
|
|
12
|
+
'update',
|
|
13
|
+
'updateOne',
|
|
14
|
+
'replaceOne',
|
|
15
|
+
'updateMany',
|
|
16
|
+
'findOneAndUpdate',
|
|
17
|
+
'findOneAndReplace',
|
|
18
|
+
'findByIdAndUpdate'
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export const assignUpdate = <T>(document: HydratedDocument<T>, update: UpdateQuery<T>, commands: Record<string, unknown>[]): HydratedDocument<T> => {
|
|
22
|
+
let updated = assign(document, update)
|
|
23
|
+
_.forEach(commands, (command) => {
|
|
24
|
+
try {
|
|
25
|
+
updated = assign(updated, command)
|
|
26
|
+
} catch {
|
|
27
|
+
// we catch assign keys that are not implemented
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return updated
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const splitUpdateAndCommands = <T>(updateQuery: UpdateWithAggregationPipeline | UpdateQuery<T> | null): { update: UpdateQuery<T>, commands: Record<string, unknown>[] } => {
|
|
35
|
+
let update: UpdateQuery<T> = {}
|
|
36
|
+
const commands: Record<string, unknown>[] = []
|
|
37
|
+
|
|
38
|
+
if (!_.isEmpty(updateQuery) && !_.isArray(updateQuery) && _.isObjectLike(updateQuery)) {
|
|
39
|
+
update = _.cloneDeep(updateQuery)
|
|
40
|
+
const keys = _.keys(update).filter((key) => key.startsWith('$'))
|
|
41
|
+
if (!_.isEmpty(keys)) {
|
|
42
|
+
_.forEach(keys, (key) => {
|
|
43
|
+
commands.push({ [key]: update[key] as unknown })
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
45
|
+
delete update[key]
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { update, commands }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const updateHooksInitialize = <T>(schema: Schema<T>, opts: IPluginOptions<T>): void => {
|
|
54
|
+
schema.pre(updateMethods as MongooseQueryMiddleware[], async function (this: IHookContext<T>) {
|
|
55
|
+
const options = this.getOptions()
|
|
56
|
+
if (isHookIgnored(options)) return
|
|
57
|
+
|
|
58
|
+
const model = this.model as Model<T>
|
|
59
|
+
const filter = this.getFilter()
|
|
60
|
+
const count = await this.model.countDocuments(filter).exec()
|
|
61
|
+
|
|
62
|
+
this._context = {
|
|
63
|
+
op: this.op,
|
|
64
|
+
modelName: opts.modelName ?? this.model.modelName,
|
|
65
|
+
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
66
|
+
isNew: options.upsert && count === 0,
|
|
67
|
+
ignoreEvent: options.ignoreEvent as boolean,
|
|
68
|
+
ignorePatchHistory: options.ignorePatchHistory as boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const updateQuery = this.getUpdate()
|
|
72
|
+
const { update, commands } = splitUpdateAndCommands(updateQuery)
|
|
73
|
+
|
|
74
|
+
const cursor = model.find(filter).lean().cursor()
|
|
75
|
+
await cursor.eachAsync(async (doc: HydratedDocument<T>) => {
|
|
76
|
+
await updatePatch(opts, this._context, assignUpdate(doc, update, commands), doc)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
schema.post(updateMethods as MongooseQueryMiddleware[], async function (this: IHookContext<T>) {
|
|
81
|
+
const options = this.getOptions()
|
|
82
|
+
if (isHookIgnored(options)) return
|
|
83
|
+
|
|
84
|
+
if (!this._context.isNew) return
|
|
85
|
+
|
|
86
|
+
const model = this.model as Model<T>
|
|
87
|
+
const updateQuery = this.getUpdate()
|
|
88
|
+
const { update, commands } = splitUpdateAndCommands(updateQuery)
|
|
89
|
+
|
|
90
|
+
const filter = assignUpdate({} as HydratedDocument<T>, update, commands)
|
|
91
|
+
if (!_.isEmpty(filter)) {
|
|
92
|
+
const current = await model.findOne(update).lean().exec()
|
|
93
|
+
if (current) {
|
|
94
|
+
this._context.createdDocs = [current] as HydratedDocument<T>[]
|
|
95
|
+
|
|
96
|
+
await createPatch(opts, this._context)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -1,79 +1,20 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
|
-
import
|
|
2
|
+
import em from './em'
|
|
3
|
+
|
|
4
|
+
import { createPatch, deletePatch } from './patch'
|
|
5
|
+
import { isMongooseLessThan7, isMongooseLessThan8 } from './version'
|
|
6
|
+
import { toObjectOptions } from './helpers'
|
|
3
7
|
|
|
4
|
-
import
|
|
8
|
+
import { saveHooksInitialize } from './hooks/save-hooks'
|
|
9
|
+
import { updateHooksInitialize } from './hooks/update-hooks'
|
|
10
|
+
import { deleteHooksInitialize } from './hooks/delete-hooks'
|
|
5
11
|
|
|
12
|
+
import type { HydratedDocument, Model, Schema } from 'mongoose'
|
|
6
13
|
import type IPluginOptions from './interfaces/IPluginOptions'
|
|
7
14
|
import type IContext from './interfaces/IContext'
|
|
8
|
-
import type IHookContext from './interfaces/IHookContext'
|
|
9
|
-
|
|
10
|
-
import { createPatch, updatePatch, deletePatch } from './patch'
|
|
11
|
-
import { isMongooseLessThan7 } from './version'
|
|
12
|
-
import em from './em'
|
|
13
15
|
|
|
14
16
|
const remove = isMongooseLessThan7 ? 'remove' : 'deleteOne'
|
|
15
17
|
|
|
16
|
-
const toObjectOptions: ToObjectOptions = {
|
|
17
|
-
depopulate: true,
|
|
18
|
-
virtuals: false
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const updateMethods = [
|
|
22
|
-
'update',
|
|
23
|
-
'updateOne',
|
|
24
|
-
'replaceOne',
|
|
25
|
-
'updateMany',
|
|
26
|
-
'findOneAndUpdate',
|
|
27
|
-
'findOneAndReplace',
|
|
28
|
-
'findByIdAndUpdate'
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
const deleteMethods = [
|
|
32
|
-
'remove',
|
|
33
|
-
'findOneAndDelete',
|
|
34
|
-
'findOneAndRemove',
|
|
35
|
-
'findByIdAndDelete',
|
|
36
|
-
'findByIdAndRemove',
|
|
37
|
-
'deleteOne',
|
|
38
|
-
'deleteMany'
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
function isHookIgnored<T> (options: QueryOptions<T>): boolean {
|
|
42
|
-
return options.ignoreHook === true || (options.ignoreEvent === true && options.ignorePatchHistory === true)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function splitUpdateAndCommands<T> (updateQuery: UpdateWithAggregationPipeline | UpdateQuery<T> | null): { update: UpdateQuery<T>, commands: Record<string, unknown>[] } {
|
|
46
|
-
let update: UpdateQuery<T> = {}
|
|
47
|
-
const commands: Record<string, unknown>[] = []
|
|
48
|
-
|
|
49
|
-
if (!_.isEmpty(updateQuery) && !_.isArray(updateQuery) && _.isObjectLike(updateQuery)) {
|
|
50
|
-
update = _.cloneDeep(updateQuery)
|
|
51
|
-
const keys = _.keys(update).filter((key) => key.startsWith('$'))
|
|
52
|
-
if (!_.isEmpty(keys)) {
|
|
53
|
-
_.forEach(keys, (key) => {
|
|
54
|
-
commands.push({ [key]: update[key] as unknown })
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
56
|
-
delete update[key]
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return { update, commands }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function assignUpdate<T> (document: HydratedDocument<T>, update: UpdateQuery<T>, commands: Record<string, unknown>[]): HydratedDocument<T> {
|
|
65
|
-
let updated = assign(document, update)
|
|
66
|
-
_.forEach(commands, (command) => {
|
|
67
|
-
try {
|
|
68
|
-
updated = assign(updated, command)
|
|
69
|
-
} catch {
|
|
70
|
-
// we catch assign keys that are not implemented
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
return updated
|
|
75
|
-
}
|
|
76
|
-
|
|
77
18
|
/**
|
|
78
19
|
* @description Patch patch event emitter
|
|
79
20
|
*/
|
|
@@ -86,27 +27,12 @@ export const patchEventEmitter = em
|
|
|
86
27
|
* @returns {void}
|
|
87
28
|
*/
|
|
88
29
|
export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: IPluginOptions<T>): void {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
modelName: opts.modelName ?? model.modelName,
|
|
96
|
-
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
97
|
-
createdDocs: [current]
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (this.isNew) {
|
|
101
|
-
await createPatch(opts, context)
|
|
102
|
-
} else {
|
|
103
|
-
const original = await model.findById(current._id).lean().exec()
|
|
104
|
-
if (original) {
|
|
105
|
-
await updatePatch(opts, context, current, original as HydratedDocument<T>)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
|
|
30
|
+
// Initialize hooks
|
|
31
|
+
saveHooksInitialize(schema, opts)
|
|
32
|
+
updateHooksInitialize(schema, opts)
|
|
33
|
+
deleteHooksInitialize(schema, opts)
|
|
34
|
+
|
|
35
|
+
// Corner case for insertMany()
|
|
110
36
|
schema.post('insertMany', async function (docs) {
|
|
111
37
|
const context = {
|
|
112
38
|
op: 'create',
|
|
@@ -118,111 +44,33 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
|
|
|
118
44
|
await createPatch(opts, context)
|
|
119
45
|
})
|
|
120
46
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
133
|
-
isNew: options.upsert && count === 0,
|
|
134
|
-
ignoreEvent: options.ignoreEvent as boolean,
|
|
135
|
-
ignorePatchHistory: options.ignorePatchHistory as boolean
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const updateQuery = this.getUpdate()
|
|
139
|
-
const { update, commands } = splitUpdateAndCommands(updateQuery)
|
|
140
|
-
|
|
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)
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
schema.post(updateMethods as MongooseQueryMiddleware[], async function (this: IHookContext<T>) {
|
|
148
|
-
const options = this.getOptions()
|
|
149
|
-
if (isHookIgnored(options)) return
|
|
150
|
-
|
|
151
|
-
if (!this._context.isNew) return
|
|
152
|
-
|
|
153
|
-
const model = this.model as Model<T>
|
|
154
|
-
const updateQuery = this.getUpdate()
|
|
155
|
-
const { update, commands } = splitUpdateAndCommands(updateQuery)
|
|
156
|
-
|
|
157
|
-
const filter = assignUpdate({} as HydratedDocument<T>, update, commands)
|
|
158
|
-
if (!_.isEmpty(filter)) {
|
|
159
|
-
const current = await model.findOne(update).lean().exec()
|
|
160
|
-
if (current) {
|
|
161
|
-
this._context.createdDocs = [current] as HydratedDocument<T>[]
|
|
162
|
-
|
|
163
|
-
await createPatch(opts, this._context)
|
|
47
|
+
// In Mongoose 7, doc.deleteOne() returned a promise that resolved to doc.
|
|
48
|
+
// In Mongoose 8, doc.deleteOne() returns a query for easier chaining, as well as consistency with doc.updateOne().
|
|
49
|
+
if (isMongooseLessThan8) {
|
|
50
|
+
// @ts-expect-error - Mongoose 7 and below
|
|
51
|
+
schema.pre(remove, { document: true, query: false }, async function () {
|
|
52
|
+
// @ts-expect-error - Mongoose 7 and below
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
54
|
+
const original = this.toObject(toObjectOptions) as HydratedDocument<T>
|
|
55
|
+
|
|
56
|
+
if (opts.preDelete && !_.isEmpty(original)) {
|
|
57
|
+
await opts.preDelete([original])
|
|
164
58
|
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const original = this.toObject(toObjectOptions) as HydratedDocument<T>
|
|
178
|
-
const model = this.constructor as Model<T>
|
|
179
|
-
|
|
180
|
-
const context: IContext<T> = {
|
|
181
|
-
op: 'delete',
|
|
182
|
-
modelName: opts.modelName ?? model.modelName,
|
|
183
|
-
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
184
|
-
deletedDocs: [original]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
await deletePatch(opts, context)
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
schema.pre(deleteMethods as MongooseQueryMiddleware[], { document: false, query: true }, async function (this: IHookContext<T>) {
|
|
191
|
-
const options = this.getOptions()
|
|
192
|
-
if (isHookIgnored(options)) return
|
|
193
|
-
|
|
194
|
-
const model = this.model as Model<T>
|
|
195
|
-
const filter = this.getFilter()
|
|
196
|
-
|
|
197
|
-
this._context = {
|
|
198
|
-
op: this.op,
|
|
199
|
-
modelName: opts.modelName ?? this.model.modelName,
|
|
200
|
-
collectionName: opts.collectionName ?? this.model.collection.collectionName,
|
|
201
|
-
ignoreEvent: options.ignoreEvent as boolean,
|
|
202
|
-
ignorePatchHistory: options.ignorePatchHistory as boolean
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (['remove', 'deleteMany'].includes(this._context.op) && !options.single) {
|
|
206
|
-
const docs = await model.find(filter).lean().exec()
|
|
207
|
-
if (!_.isEmpty(docs)) {
|
|
208
|
-
this._context.deletedDocs = docs as HydratedDocument<T>[]
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
const doc = await model.findOne(filter).lean().exec()
|
|
212
|
-
if (!_.isEmpty(doc)) {
|
|
213
|
-
this._context.deletedDocs = [doc] as HydratedDocument<T>[]
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// @ts-expect-error - Mongoose 7 and below
|
|
62
|
+
schema.post(remove, { document: true, query: false }, async function (this: HydratedDocument<T>) {
|
|
63
|
+
const original = this.toObject(toObjectOptions) as HydratedDocument<T>
|
|
64
|
+
const model = this.constructor as Model<T>
|
|
65
|
+
|
|
66
|
+
const context: IContext<T> = {
|
|
67
|
+
op: 'delete',
|
|
68
|
+
modelName: opts.modelName ?? model.modelName,
|
|
69
|
+
collectionName: opts.collectionName ?? model.collection.collectionName,
|
|
70
|
+
deletedDocs: [original]
|
|
214
71
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
schema.post(deleteMethods as MongooseQueryMiddleware[], { document: false, query: true }, async function (this: IHookContext<T>) {
|
|
223
|
-
const options = this.getOptions()
|
|
224
|
-
if (isHookIgnored(options)) return
|
|
225
|
-
|
|
226
|
-
await deletePatch(opts, this._context)
|
|
227
|
-
})
|
|
72
|
+
|
|
73
|
+
await deletePatch(opts, context)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
228
76
|
}
|
package/src/version.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { satisfies } from 'semver'
|
|
2
2
|
import mongoose from 'mongoose'
|
|
3
3
|
|
|
4
|
+
export const isMongooseLessThan8 = satisfies(mongoose.version, '<8')
|
|
4
5
|
export const isMongooseLessThan7 = satisfies(mongoose.version, '<7')
|
|
5
6
|
export const isMongoose6 = satisfies(mongoose.version, '6')
|
|
6
7
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { isMongooseLessThan7 } from '../src/version'
|
|
2
2
|
import mongoose, { model } from 'mongoose'
|
|
3
3
|
|
|
4
|
-
import type { ToObjectOptions } from 'mongoose'
|
|
5
|
-
|
|
6
4
|
import UserSchema from './schemas/UserSchema'
|
|
7
5
|
import { patchHistoryPlugin } from '../src/plugin'
|
|
8
6
|
import History from '../src/models/History'
|
|
9
7
|
|
|
10
8
|
import em from '../src/em'
|
|
11
9
|
import { USER_DELETED } from './constants/events'
|
|
10
|
+
import { toObjectOptions } from '../src/helpers'
|
|
12
11
|
|
|
13
12
|
jest.mock('../src/em', () => {
|
|
14
13
|
return {
|
|
@@ -16,10 +15,6 @@ jest.mock('../src/em', () => {
|
|
|
16
15
|
}
|
|
17
16
|
})
|
|
18
17
|
|
|
19
|
-
const toObjectOptions: ToObjectOptions = {
|
|
20
|
-
depopulate: true,
|
|
21
|
-
virtuals: false
|
|
22
|
-
}
|
|
23
18
|
|
|
24
19
|
describe('plugin - event delete & patch history disabled', () => {
|
|
25
20
|
const uri = `${globalThis.__MONGO_URI__}${globalThis.__MONGO_DB_NAME__}`
|
|
@@ -164,13 +159,17 @@ describe('plugin - event delete & patch history disabled', () => {
|
|
|
164
159
|
it('should findOneAndRemove() and emit one delete event', async () => {
|
|
165
160
|
const users = await User.create([
|
|
166
161
|
{ name: 'John', role: 'user' },
|
|
167
|
-
{ name: 'Alice', role: '
|
|
162
|
+
{ name: 'Alice', role: 'admin' },
|
|
168
163
|
{ name: 'Bob', role: 'admin' }
|
|
169
164
|
])
|
|
170
165
|
|
|
171
166
|
const [john] = users
|
|
172
167
|
|
|
173
|
-
|
|
168
|
+
if (isMongooseLessThan7) {
|
|
169
|
+
await User.findOneAndRemove({ role: 'user' }).exec()
|
|
170
|
+
} else {
|
|
171
|
+
await User.findOneAndDelete({ role: 'user' }).exec()
|
|
172
|
+
}
|
|
174
173
|
|
|
175
174
|
const history = await History.find({})
|
|
176
175
|
expect(history).toHaveLength(0)
|
|
@@ -224,7 +223,11 @@ describe('plugin - event delete & patch history disabled', () => {
|
|
|
224
223
|
|
|
225
224
|
const [john] = users
|
|
226
225
|
|
|
227
|
-
|
|
226
|
+
if (isMongooseLessThan7) {
|
|
227
|
+
await User.findByIdAndRemove(john._id).exec()
|
|
228
|
+
} else {
|
|
229
|
+
await User.findByIdAndDelete(john._id).exec()
|
|
230
|
+
}
|
|
228
231
|
|
|
229
232
|
const history = await History.find({})
|
|
230
233
|
expect(history).toHaveLength(0)
|
|
@@ -245,7 +248,7 @@ describe('plugin - event delete & patch history disabled', () => {
|
|
|
245
248
|
it('should deleteOne() and emit one delete event', async () => {
|
|
246
249
|
const users = await User.create([
|
|
247
250
|
{ name: 'John', role: 'user' },
|
|
248
|
-
{ name: 'Alice', role: '
|
|
251
|
+
{ name: 'Alice', role: 'admin' },
|
|
249
252
|
{ name: 'Bob', role: 'admin' }
|
|
250
253
|
])
|
|
251
254
|
|