@tsed/mongoose 8.0.1 → 8.0.3
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/package.json +15 -14
- package/readme.md +5 -5
- package/src/MongooseModule.ts +32 -0
- package/src/constants/constants.ts +20 -0
- package/src/decorators/auto.spec.ts +30 -0
- package/src/decorators/auto.ts +24 -0
- package/src/decorators/discriminatorKey.ts +1 -0
- package/src/decorators/dynamicRef.spec.ts +46 -0
- package/src/decorators/dynamicRef.ts +76 -0
- package/src/decorators/excludeIndexes.spec.ts +30 -0
- package/src/decorators/excludeIndexes.ts +24 -0
- package/src/decorators/expires.spec.ts +18 -0
- package/src/decorators/expires.ts +24 -0
- package/src/decorators/immutable.spec.ts +30 -0
- package/src/decorators/immutable.ts +26 -0
- package/src/decorators/indexed.spec.ts +19 -0
- package/src/decorators/indexed.ts +24 -0
- package/src/decorators/lowercase.spec.ts +30 -0
- package/src/decorators/lowercase.ts +24 -0
- package/src/decorators/model.ts +69 -0
- package/src/decorators/mongooseIndex.spec.ts +26 -0
- package/src/decorators/mongooseIndex.ts +35 -0
- package/src/decorators/mongooseIndexes.spec.ts +35 -0
- package/src/decorators/mongooseIndexes.ts +35 -0
- package/src/decorators/mongoosePlugin.spec.ts +25 -0
- package/src/decorators/mongoosePlugin.ts +20 -0
- package/src/decorators/numberDecimal.spec.ts +266 -0
- package/src/decorators/numberDecimal.ts +84 -0
- package/src/decorators/objectID.spec.ts +24 -0
- package/src/decorators/objectID.ts +39 -0
- package/src/decorators/postHook.spec.ts +44 -0
- package/src/decorators/postHook.ts +72 -0
- package/src/decorators/preHook.spec.ts +75 -0
- package/src/decorators/preHook.ts +70 -0
- package/src/decorators/ref.spec.ts +361 -0
- package/src/decorators/ref.ts +111 -0
- package/src/decorators/schema.ts +79 -0
- package/src/decorators/schemaIgnore.spec.ts +18 -0
- package/src/decorators/schemaIgnore.ts +24 -0
- package/src/decorators/select.spec.ts +18 -0
- package/src/decorators/select.ts +24 -0
- package/src/decorators/sparse.spec.ts +30 -0
- package/src/decorators/sparse.ts +26 -0
- package/src/decorators/text.spec.ts +30 -0
- package/src/decorators/text.ts +25 -0
- package/src/decorators/trim.spec.ts +18 -0
- package/src/decorators/trim.ts +23 -0
- package/src/decorators/unique.spec.ts +18 -0
- package/src/decorators/unique.ts +23 -0
- package/src/decorators/uppercase.spec.ts +30 -0
- package/src/decorators/uppercase.ts +24 -0
- package/src/decorators/versionKey.spec.ts +19 -0
- package/src/decorators/versionKey.ts +9 -0
- package/src/decorators/virtualRef.spec.ts +383 -0
- package/src/decorators/virtualRef.ts +73 -0
- package/src/index.ts +47 -0
- package/src/interfaces/MongooseConnectionOptions.ts +10 -0
- package/src/interfaces/MongooseDocument.ts +3 -0
- package/src/interfaces/MongooseModel.ts +10 -0
- package/src/interfaces/MongooseModelOptions.ts +8 -0
- package/src/interfaces/MongooseSchemaOptions.ts +51 -0
- package/src/interfaces/MongooseSchemaTypes.ts +5 -0
- package/src/interfaces/MongooseVirtualRefOptions.ts +10 -0
- package/src/interfaces/interfaces.ts +9 -0
- package/src/registries/MongooseModels.ts +3 -0
- package/src/services/MongooseConnection.spec.ts +70 -0
- package/src/services/MongooseConnections.ts +58 -0
- package/src/services/MongooseService.spec.ts +73 -0
- package/src/services/MongooseService.ts +77 -0
- package/src/utils/buildMongooseSchema.spec.ts +67 -0
- package/src/utils/createModel.spec.ts +49 -0
- package/src/utils/createModel.ts +54 -0
- package/src/utils/createSchema.spec.ts +771 -0
- package/src/utils/createSchema.ts +198 -0
- package/src/utils/resolveRefType.spec.ts +30 -0
- package/src/utils/resolveRefType.ts +18 -0
- package/src/utils/schemaOptions.spec.ts +68 -0
- package/src/utils/schemaOptions.ts +74 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {decoratorTypeOf, DecoratorTypes, StaticMethodDecorator} from "@tsed/core";
|
|
2
|
+
|
|
3
|
+
import {MongooseHookOptions, MongoosePostHookCB} from "../interfaces/MongooseSchemaOptions.js";
|
|
4
|
+
import {schemaOptions} from "../utils/schemaOptions.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* We can simply attach a `@PostHook` decorator to your model class and
|
|
8
|
+
* define the hook function like you normally would in Mongoose.
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import {Ignore, Required} from "@tsed/platform-http";
|
|
12
|
+
* import {PostHook, Model} from "@tsed/mongoose";
|
|
13
|
+
*
|
|
14
|
+
* @Model()
|
|
15
|
+
* @PostHook("save", (car: CarModel) => {
|
|
16
|
+
* if (car.topSpeedInKmH > 300) {
|
|
17
|
+
* console.log(car.model, 'is fast!');
|
|
18
|
+
* }
|
|
19
|
+
* })
|
|
20
|
+
* export class CarModel {
|
|
21
|
+
* @Ignore()
|
|
22
|
+
* _id: string;
|
|
23
|
+
*
|
|
24
|
+
* @Required()
|
|
25
|
+
* model: string;
|
|
26
|
+
*
|
|
27
|
+
* @Required()
|
|
28
|
+
* isFast: boolean;
|
|
29
|
+
*
|
|
30
|
+
* // or Prehook on static method
|
|
31
|
+
* @PostHook("save")
|
|
32
|
+
* static postSave(car: CarModel) {
|
|
33
|
+
* if (car.topSpeedInKmH > 300) {
|
|
34
|
+
* console.log(car.model, 'is fast!');
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* This will execute the post-save hook each time a `CarModel` document is saved.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} method
|
|
43
|
+
* @param fn
|
|
44
|
+
* @returns {Function}
|
|
45
|
+
* @decorator
|
|
46
|
+
* @mongoose
|
|
47
|
+
* @class
|
|
48
|
+
*/
|
|
49
|
+
export function PostHook<T = any>(method: string, fn: MongoosePostHookCB<T>): ClassDecorator;
|
|
50
|
+
export function PostHook<T = any>(method: string, fn: MongoosePostHookCB<T>, options: MongooseHookOptions): ClassDecorator;
|
|
51
|
+
export function PostHook<T = any>(method: string, options: MongooseHookOptions): StaticMethodDecorator;
|
|
52
|
+
export function PostHook<T = any>(method: string, ...params: any[]): Function {
|
|
53
|
+
return ((...args: any[]) => {
|
|
54
|
+
let options: MongooseHookOptions = params[1];
|
|
55
|
+
let fn: MongoosePostHookCB<T> = params[0];
|
|
56
|
+
|
|
57
|
+
if (decoratorTypeOf(args) === DecoratorTypes.METHOD_STC) {
|
|
58
|
+
options = params[0];
|
|
59
|
+
fn = args[0][args[1]].bind(args[0]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
schemaOptions(args[0], {
|
|
63
|
+
post: [
|
|
64
|
+
{
|
|
65
|
+
method,
|
|
66
|
+
fn,
|
|
67
|
+
options
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
}) as any;
|
|
72
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {MongooseNextCB, PreHook, schemaOptions} from "../../src/index.js";
|
|
2
|
+
|
|
3
|
+
describe("@PreHook()", () => {
|
|
4
|
+
describe("when decorator is used as class decorator", () => {
|
|
5
|
+
it("should call applySchemaOptions", () => {
|
|
6
|
+
// GIVEN
|
|
7
|
+
const fn = vi.fn();
|
|
8
|
+
|
|
9
|
+
// WHEN
|
|
10
|
+
@PreHook("method", fn, {query: true})
|
|
11
|
+
class Test {}
|
|
12
|
+
|
|
13
|
+
// THEN
|
|
14
|
+
const options = schemaOptions(Test);
|
|
15
|
+
|
|
16
|
+
expect(options).toEqual({
|
|
17
|
+
pre: [
|
|
18
|
+
{
|
|
19
|
+
method: "method",
|
|
20
|
+
fn,
|
|
21
|
+
options: {
|
|
22
|
+
query: true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it("should call applySchemaOptions with more options", () => {
|
|
29
|
+
// GIVEN
|
|
30
|
+
const fn: any = vi.fn((instance: any, next: MongooseNextCB, options: any) => {
|
|
31
|
+
return instance;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// WHEN
|
|
35
|
+
@PreHook("method", fn, {query: true})
|
|
36
|
+
class Test {}
|
|
37
|
+
|
|
38
|
+
// THEN
|
|
39
|
+
const options = schemaOptions(Test);
|
|
40
|
+
|
|
41
|
+
expect(options).toEqual({
|
|
42
|
+
pre: [
|
|
43
|
+
{
|
|
44
|
+
method: "method",
|
|
45
|
+
fn,
|
|
46
|
+
options: {
|
|
47
|
+
query: true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("when decorator is used as method decorator", () => {
|
|
56
|
+
it("should call applySchemaOptions", () => {
|
|
57
|
+
class Test {
|
|
58
|
+
@PreHook("save", {
|
|
59
|
+
query: true
|
|
60
|
+
})
|
|
61
|
+
static method() {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const {
|
|
65
|
+
pre: [options]
|
|
66
|
+
} = schemaOptions(Test);
|
|
67
|
+
|
|
68
|
+
expect(options.method).toBe("save");
|
|
69
|
+
expect(options.fn).toBeInstanceOf(Function);
|
|
70
|
+
expect(options.options).toEqual({
|
|
71
|
+
query: true
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {decoratorTypeOf, DecoratorTypes} from "@tsed/core";
|
|
2
|
+
|
|
3
|
+
import {MongooseHookOptions, MongoosePreHookCB} from "../interfaces/MongooseSchemaOptions.js";
|
|
4
|
+
import {schemaOptions} from "../utils/schemaOptions.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* We can simply attach a `@PreHook` decorator to your model class and
|
|
9
|
+
* define the hook function like you normally would in Mongoose.
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import {Ignore, Required} from "@tsed/platform-http";
|
|
13
|
+
* import {PreHook, Model} from "@tsed/mongoose";
|
|
14
|
+
*
|
|
15
|
+
* @Model()
|
|
16
|
+
* @PreHook("save", (car: CarModel, next) => {
|
|
17
|
+
* if (car.model === 'Tesla') {
|
|
18
|
+
* car.isFast = true;
|
|
19
|
+
* }
|
|
20
|
+
* next();
|
|
21
|
+
*})
|
|
22
|
+
* export class CarModel {
|
|
23
|
+
*
|
|
24
|
+
* @Ignore()
|
|
25
|
+
* _id: string;
|
|
26
|
+
*
|
|
27
|
+
* @Required()
|
|
28
|
+
* model: string;
|
|
29
|
+
*
|
|
30
|
+
* @Required()
|
|
31
|
+
* isFast: boolean;
|
|
32
|
+
*
|
|
33
|
+
* // or Prehook on static method
|
|
34
|
+
* @PreHook("save")
|
|
35
|
+
* static preSave(car: CarModel, next) {
|
|
36
|
+
* if (car.model === 'Tesla') {
|
|
37
|
+
* car.isFast = true;
|
|
38
|
+
* }
|
|
39
|
+
* next();
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* This will execute the pre-save hook each time a `CarModel` document is saved.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} method
|
|
47
|
+
* @param fn
|
|
48
|
+
* @param options
|
|
49
|
+
* @returns {Function}
|
|
50
|
+
* @decorator
|
|
51
|
+
* @class
|
|
52
|
+
*/
|
|
53
|
+
export function PreHook<T = any>(method: string, fn?: MongoosePreHookCB<T> | MongooseHookOptions, options?: MongooseHookOptions): Function {
|
|
54
|
+
return (...args: any[]) => {
|
|
55
|
+
if (decoratorTypeOf(args) === DecoratorTypes.METHOD_STC) {
|
|
56
|
+
options = fn as MongooseHookOptions;
|
|
57
|
+
fn = args[0][args[1]].bind(args[0]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
schemaOptions(args[0], {
|
|
61
|
+
pre: [
|
|
62
|
+
{
|
|
63
|
+
method,
|
|
64
|
+
fn: fn as MongoosePreHookCB<T>,
|
|
65
|
+
options
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import {catchError, Store} from "@tsed/core";
|
|
2
|
+
import {getJsonSchema, Property} from "@tsed/schema";
|
|
3
|
+
import {Schema} from "mongoose";
|
|
4
|
+
|
|
5
|
+
import {MONGOOSE_MODEL_NAME, MONGOOSE_SCHEMA} from "../constants/constants.js";
|
|
6
|
+
import {MongooseModels} from "../registries/MongooseModels.js";
|
|
7
|
+
import {Ref} from "./ref.js";
|
|
8
|
+
|
|
9
|
+
describe("@Ref()", () => {
|
|
10
|
+
describe("type is a class", () => {
|
|
11
|
+
it("should set metadata and catch error", () => {
|
|
12
|
+
const error = catchError(() => {
|
|
13
|
+
class Model {
|
|
14
|
+
@Ref(undefined)
|
|
15
|
+
num: number[];
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(error?.message).toEqual(
|
|
20
|
+
"A model is required on `@Ref(model)` decorator. Please give a model or wrap it inside an arrow function if you have a circular reference."
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should set metadata", () => {
|
|
25
|
+
class RefTest {
|
|
26
|
+
@Property()
|
|
27
|
+
id: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Store.from(RefTest).set(MONGOOSE_MODEL_NAME, "RefTest");
|
|
31
|
+
|
|
32
|
+
class Test {
|
|
33
|
+
@Ref(RefTest)
|
|
34
|
+
test: Ref<RefTest>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const store = Store.from(Test, "test");
|
|
38
|
+
const schema = getJsonSchema(Test);
|
|
39
|
+
|
|
40
|
+
expect(schema).toEqual({
|
|
41
|
+
definitions: {
|
|
42
|
+
RefTest: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
id: {
|
|
46
|
+
type: "string"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
properties: {
|
|
52
|
+
test: {
|
|
53
|
+
oneOf: [
|
|
54
|
+
{
|
|
55
|
+
description: "A reference ObjectID",
|
|
56
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
57
|
+
type: "string"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
$ref: "#/definitions/RefTest"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
type: "object"
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(store.get(MONGOOSE_SCHEMA)).toEqual({
|
|
69
|
+
type: Schema.Types.ObjectId,
|
|
70
|
+
ref: RefTest
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("type is a map of class", () => {
|
|
75
|
+
it("should set metadata", () => {
|
|
76
|
+
class RefTest {
|
|
77
|
+
@Property()
|
|
78
|
+
id: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Store.from(RefTest).set(MONGOOSE_MODEL_NAME, "RefTest");
|
|
82
|
+
MongooseModels.set("RefTest", RefTest);
|
|
83
|
+
|
|
84
|
+
class Test {
|
|
85
|
+
@Ref(RefTest)
|
|
86
|
+
test: Map<string, Ref<RefTest>>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const store = Store.from(Test, "test");
|
|
90
|
+
const schema = getJsonSchema(Test);
|
|
91
|
+
|
|
92
|
+
expect(schema).toEqual({
|
|
93
|
+
definitions: {
|
|
94
|
+
RefTest: {
|
|
95
|
+
properties: {
|
|
96
|
+
id: {
|
|
97
|
+
type: "string"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
type: "object"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
properties: {
|
|
104
|
+
test: {
|
|
105
|
+
additionalProperties: {
|
|
106
|
+
oneOf: [
|
|
107
|
+
{
|
|
108
|
+
description: "A reference ObjectID",
|
|
109
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
110
|
+
type: "string"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
$ref: "#/definitions/RefTest"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
type: "object"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
type: "object"
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(store.get(MONGOOSE_SCHEMA)).toEqual({
|
|
124
|
+
type: Schema.Types.ObjectId,
|
|
125
|
+
ref: RefTest
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe("type is a Function", () => {
|
|
130
|
+
it("should set metadata", () => {
|
|
131
|
+
class RefTest {
|
|
132
|
+
@Property()
|
|
133
|
+
id: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Store.from(RefTest).set(MONGOOSE_MODEL_NAME, "RefTest");
|
|
137
|
+
const arrow = () => RefTest;
|
|
138
|
+
|
|
139
|
+
class Test {
|
|
140
|
+
@Ref(arrow)
|
|
141
|
+
test: Ref<RefTest>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const store = Store.from(Test, "test");
|
|
145
|
+
const schema = getJsonSchema(Test);
|
|
146
|
+
|
|
147
|
+
expect(schema).toEqual({
|
|
148
|
+
definitions: {
|
|
149
|
+
RefTest: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
id: {
|
|
153
|
+
type: "string"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
properties: {
|
|
159
|
+
test: {
|
|
160
|
+
oneOf: [
|
|
161
|
+
{
|
|
162
|
+
description: "A reference ObjectID",
|
|
163
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
164
|
+
type: "string"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
$ref: "#/definitions/RefTest"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
type: "object"
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(store.get(MONGOOSE_SCHEMA)).toEqual({
|
|
176
|
+
type: Schema.Types.ObjectId,
|
|
177
|
+
ref: arrow
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe("type is a string (deprecated)", () => {
|
|
182
|
+
it("should set metadata", () => {
|
|
183
|
+
class RefTest {}
|
|
184
|
+
|
|
185
|
+
class Test {
|
|
186
|
+
@Ref("RefTest")
|
|
187
|
+
test: Ref<RefTest>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
MongooseModels.set("RefTest", RefTest);
|
|
191
|
+
const store = Store.from(Test, "test");
|
|
192
|
+
|
|
193
|
+
expect(getJsonSchema(Test)).toEqual({
|
|
194
|
+
definitions: {
|
|
195
|
+
RefTest: {
|
|
196
|
+
properties: {
|
|
197
|
+
id: {
|
|
198
|
+
type: "string"
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
type: "object"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
properties: {
|
|
205
|
+
test: {
|
|
206
|
+
oneOf: [
|
|
207
|
+
{
|
|
208
|
+
description: "A reference ObjectID",
|
|
209
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
210
|
+
type: "string"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
$ref: "#/definitions/RefTest"
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
type: "object"
|
|
219
|
+
});
|
|
220
|
+
expect(store.get(MONGOOSE_SCHEMA)).toEqual({
|
|
221
|
+
type: Schema.Types.ObjectId,
|
|
222
|
+
ref: "RefTest"
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
describe("JsonSchema gets generated based on populated groups", () => {
|
|
227
|
+
class MyChildModel {
|
|
228
|
+
@Property()
|
|
229
|
+
test: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
class MyParentModel {
|
|
233
|
+
@Property()
|
|
234
|
+
id: string;
|
|
235
|
+
|
|
236
|
+
@Ref(MyChildModel, {populatedGroups: ["group1", "group2"]})
|
|
237
|
+
child1: Ref<MyChildModel>;
|
|
238
|
+
|
|
239
|
+
@Ref(MyChildModel, {populatedGroups: ["group2"]})
|
|
240
|
+
child2: Ref<MyChildModel>;
|
|
241
|
+
|
|
242
|
+
@Ref(MyChildModel)
|
|
243
|
+
child3: Ref<MyChildModel>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
it("should reflect the populated groups options in the schema (with given groups)", () => {
|
|
247
|
+
const spec = getJsonSchema(MyParentModel, {
|
|
248
|
+
groups: ["group1", "group3"]
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(spec).toEqual({
|
|
252
|
+
definitions: {
|
|
253
|
+
MyChildModel: {
|
|
254
|
+
properties: {
|
|
255
|
+
test: {
|
|
256
|
+
type: "string"
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
type: "object"
|
|
260
|
+
},
|
|
261
|
+
MyChildModelGroup1Group3: {
|
|
262
|
+
properties: {
|
|
263
|
+
test: {
|
|
264
|
+
type: "string"
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
type: "object"
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
properties: {
|
|
271
|
+
child1: {
|
|
272
|
+
oneOf: [
|
|
273
|
+
{
|
|
274
|
+
$ref: "#/definitions/MyChildModelGroup1Group3"
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
child2: {
|
|
279
|
+
oneOf: [
|
|
280
|
+
{
|
|
281
|
+
description: "A reference ObjectID",
|
|
282
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
283
|
+
type: "string"
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
child3: {
|
|
288
|
+
oneOf: [
|
|
289
|
+
{
|
|
290
|
+
description: "A reference ObjectID",
|
|
291
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
292
|
+
type: "string"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
$ref: "#/definitions/MyChildModel"
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
},
|
|
299
|
+
id: {
|
|
300
|
+
type: "string"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
type: "object"
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should reflect the populated groups options in the schema (without given groups)", () => {
|
|
308
|
+
const spec = getJsonSchema(MyParentModel, {
|
|
309
|
+
groups: []
|
|
310
|
+
});
|
|
311
|
+
expect(spec).toEqual({
|
|
312
|
+
definitions: {
|
|
313
|
+
MyChildModel: {
|
|
314
|
+
properties: {
|
|
315
|
+
test: {
|
|
316
|
+
type: "string"
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
type: "object"
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
properties: {
|
|
323
|
+
child1: {
|
|
324
|
+
oneOf: [
|
|
325
|
+
{
|
|
326
|
+
description: "A reference ObjectID",
|
|
327
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
328
|
+
type: "string"
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
child2: {
|
|
333
|
+
oneOf: [
|
|
334
|
+
{
|
|
335
|
+
description: "A reference ObjectID",
|
|
336
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
337
|
+
type: "string"
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
child3: {
|
|
342
|
+
oneOf: [
|
|
343
|
+
{
|
|
344
|
+
description: "A reference ObjectID",
|
|
345
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
346
|
+
type: "string"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
$ref: "#/definitions/MyChildModel"
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
},
|
|
353
|
+
id: {
|
|
354
|
+
type: "string"
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
type: "object"
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {isArrowFn, isCollection, isObject, isObjectID, isString, StoreMerge, Type, useDecorators} from "@tsed/core";
|
|
2
|
+
import {deserialize, OnDeserialize, OnSerialize, serialize} from "@tsed/json-mapper";
|
|
3
|
+
import {ForwardGroups, JsonEntityFn, lazyRef, matchGroups, OneOf, Property, string} from "@tsed/schema";
|
|
4
|
+
import {Schema as MongooseSchema} from "mongoose";
|
|
5
|
+
|
|
6
|
+
import {MONGOOSE_SCHEMA} from "../constants/constants.js";
|
|
7
|
+
import {MongooseSchemaTypes} from "../interfaces/MongooseSchemaTypes.js";
|
|
8
|
+
import {MongooseModels} from "../registries/MongooseModels.js";
|
|
9
|
+
|
|
10
|
+
interface RefOptions {
|
|
11
|
+
type?: MongooseSchemaTypes;
|
|
12
|
+
populatedGroups?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isRef(value: undefined | string | any) {
|
|
16
|
+
return isObjectID(value) || isString(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function PopulateGroups(populatedGroups: string[]) {
|
|
20
|
+
return useDecorators(
|
|
21
|
+
ForwardGroups(true),
|
|
22
|
+
JsonEntityFn((store) => {
|
|
23
|
+
store.schema.$hooks.on("oneOf", (obj: any[], givenGroups: string[]) => {
|
|
24
|
+
if (matchGroups(populatedGroups, givenGroups)) {
|
|
25
|
+
return obj.filter((x) => x.type === "string"); // keep the object id;
|
|
26
|
+
} else {
|
|
27
|
+
return obj.filter((x) => x.type !== "string"); // keep the ref definition
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Define a property as mongoose reference to other Model (decorated with @Model).
|
|
36
|
+
*
|
|
37
|
+
* ### Example
|
|
38
|
+
*
|
|
39
|
+
* ```typescript
|
|
40
|
+
*
|
|
41
|
+
* @Model()
|
|
42
|
+
* class FooModel {
|
|
43
|
+
*
|
|
44
|
+
* @Ref(Foo2Model)
|
|
45
|
+
* field: Ref<Foo2Model>
|
|
46
|
+
*
|
|
47
|
+
* @Ref(Foo2Model)
|
|
48
|
+
* list: Ref<Foo2Model>[]
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* @Model()
|
|
52
|
+
* class Foo2Model {
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @param model
|
|
57
|
+
* @param options
|
|
58
|
+
* @returns {Function}
|
|
59
|
+
* @decorator
|
|
60
|
+
* @mongoose
|
|
61
|
+
* @property
|
|
62
|
+
*/
|
|
63
|
+
export function Ref(
|
|
64
|
+
model: string | (() => Type) | any,
|
|
65
|
+
options: RefOptions | MongooseSchemaTypes = MongooseSchemaTypes.OBJECT_ID
|
|
66
|
+
): PropertyDecorator {
|
|
67
|
+
if (!model) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"A model is required on `@Ref(model)` decorator. Please give a model or wrap it inside an arrow function if you have a circular reference."
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const getType = () => (isString(model) ? MongooseModels.get(model) : isArrowFn(model) ? model() : model);
|
|
74
|
+
const populatedGroups = (isObject(options) && options.populatedGroups) || [];
|
|
75
|
+
|
|
76
|
+
return useDecorators(
|
|
77
|
+
Property(Object),
|
|
78
|
+
StoreMerge(MONGOOSE_SCHEMA, {
|
|
79
|
+
type: MongooseSchema.Types[isObject(options) ? options.type || MongooseSchemaTypes.OBJECT_ID : options],
|
|
80
|
+
ref: model
|
|
81
|
+
}),
|
|
82
|
+
OnDeserialize((value) => {
|
|
83
|
+
if (isRef(value)) {
|
|
84
|
+
return value.toString();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (isCollection(value) && isRef(value[0])) {
|
|
88
|
+
return value.map(String);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return deserialize(value, {type: getType(), useAlias: false});
|
|
92
|
+
}),
|
|
93
|
+
OnSerialize((value: any, ctx) => {
|
|
94
|
+
if (isRef(value)) {
|
|
95
|
+
return value.toString();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (isCollection(value) && isRef(value[0])) {
|
|
99
|
+
return value.map(String);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const type = getType();
|
|
103
|
+
|
|
104
|
+
return serialize(value, {...ctx, type});
|
|
105
|
+
}),
|
|
106
|
+
OneOf(string().example("5ce7ad3028890bd71749d477").description("A reference ObjectID"), lazyRef(getType)),
|
|
107
|
+
populatedGroups.length && PopulateGroups(populatedGroups)
|
|
108
|
+
) as PropertyDecorator;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type Ref<T> = T | string;
|