@tsed/mongoose 8.0.1 → 8.0.2
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/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,35 @@
|
|
|
1
|
+
import {schemaOptions} from "../utils/schemaOptions.js";
|
|
2
|
+
import {MongooseIndexes} from "./mongooseIndexes.js";
|
|
3
|
+
|
|
4
|
+
describe("@MongooseIndexes()", () => {
|
|
5
|
+
class Test {}
|
|
6
|
+
|
|
7
|
+
it("should store options", () => {
|
|
8
|
+
// WHEN
|
|
9
|
+
@MongooseIndexes([
|
|
10
|
+
{fields: {field: "1"}, options: {}},
|
|
11
|
+
{fields: {field2: "1"}, options: {}}
|
|
12
|
+
])
|
|
13
|
+
class Test {}
|
|
14
|
+
|
|
15
|
+
// THEN
|
|
16
|
+
const options = schemaOptions(Test);
|
|
17
|
+
|
|
18
|
+
expect(options).toEqual({
|
|
19
|
+
indexes: [
|
|
20
|
+
{
|
|
21
|
+
fields: {
|
|
22
|
+
field: "1"
|
|
23
|
+
},
|
|
24
|
+
options: {}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
fields: {
|
|
28
|
+
field2: "1"
|
|
29
|
+
},
|
|
30
|
+
options: {}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {schemaOptions} from "../utils/schemaOptions.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calls schema.index() to define multiple indexes (most likely compound) for the schema.
|
|
5
|
+
*
|
|
6
|
+
* ### Example
|
|
7
|
+
*
|
|
8
|
+
* ```typescript
|
|
9
|
+
* @Model()
|
|
10
|
+
* @MongooseIndexes([{fields: {first: 1, second: 1}, options:{unique: 1}}, {fields: {first: 1, third: 1}, options:{unique: 1}}])
|
|
11
|
+
* export class EventModel {
|
|
12
|
+
*
|
|
13
|
+
* @Property()
|
|
14
|
+
* first: string;
|
|
15
|
+
*
|
|
16
|
+
* @Property()
|
|
17
|
+
* second: string;
|
|
18
|
+
*
|
|
19
|
+
* @Property()
|
|
20
|
+
* third: string;
|
|
21
|
+
*
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @param indexes - define multiple mongoose indexes
|
|
26
|
+
* @returns {Function}
|
|
27
|
+
* @decorator
|
|
28
|
+
* @mongoose
|
|
29
|
+
* @class
|
|
30
|
+
*/
|
|
31
|
+
export function MongooseIndexes(indexes: Array<{fields: object; options?: any}>): Function {
|
|
32
|
+
return (target: any) => {
|
|
33
|
+
schemaOptions(target, {indexes});
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {MongoosePlugin} from "../../src/decorators/mongoosePlugin.js";
|
|
2
|
+
import {schemaOptions} from "../../src/utils/schemaOptions.js";
|
|
3
|
+
|
|
4
|
+
describe("@MongoosePlugin()", () => {
|
|
5
|
+
it("should store options", () => {
|
|
6
|
+
// GIVEN
|
|
7
|
+
const fn = vi.fn();
|
|
8
|
+
|
|
9
|
+
// WHEN
|
|
10
|
+
@MongoosePlugin(fn, {})
|
|
11
|
+
class Test {}
|
|
12
|
+
|
|
13
|
+
// THEN
|
|
14
|
+
const options = schemaOptions(Test);
|
|
15
|
+
|
|
16
|
+
expect(options).toEqual({
|
|
17
|
+
plugins: [
|
|
18
|
+
{
|
|
19
|
+
plugin: fn,
|
|
20
|
+
options: {}
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
import {schemaOptions} from "../utils/schemaOptions.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {(schema: "mongoose".Schema, options?: any) => void} plugin
|
|
8
|
+
* @param options
|
|
9
|
+
* @returns {Function}
|
|
10
|
+
* @decorator
|
|
11
|
+
* @mongoose
|
|
12
|
+
* @class
|
|
13
|
+
*/
|
|
14
|
+
export function MongoosePlugin(plugin: (schema: mongoose.Schema, options?: any) => void, options?: any): Function {
|
|
15
|
+
return (target: any) => {
|
|
16
|
+
schemaOptions(target, {
|
|
17
|
+
plugins: [{plugin, options}]
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import {Store} from "@tsed/core";
|
|
2
|
+
import {deserialize, serialize} from "@tsed/json-mapper";
|
|
3
|
+
import {getJsonSchema} from "@tsed/schema";
|
|
4
|
+
import {Schema, Types} from "mongoose";
|
|
5
|
+
|
|
6
|
+
import {MONGOOSE_SCHEMA} from "../constants/constants.js";
|
|
7
|
+
import {Decimal128, DecimalFormat, NumberDecimal} from "./numberDecimal.js";
|
|
8
|
+
|
|
9
|
+
describe("@NumberDecimal()", () => {
|
|
10
|
+
it("should declare a Decimal128 field", () => {
|
|
11
|
+
// WHEN
|
|
12
|
+
class Test {
|
|
13
|
+
@NumberDecimal()
|
|
14
|
+
price: Decimal128;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// THEN
|
|
18
|
+
const store = Store.from(Test, "price");
|
|
19
|
+
const schema = getJsonSchema(Test);
|
|
20
|
+
|
|
21
|
+
expect(schema).toEqual({
|
|
22
|
+
properties: {
|
|
23
|
+
price: {
|
|
24
|
+
examples: [12.34],
|
|
25
|
+
type: "number",
|
|
26
|
+
format: "decimal"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
type: "object"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(store.get(MONGOOSE_SCHEMA)).toEqual({
|
|
33
|
+
type: Schema.Types.Decimal128
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it("should declare a decimal number format", () => {
|
|
37
|
+
// WHEN
|
|
38
|
+
const format = new DecimalFormat();
|
|
39
|
+
const validate: (value: any) => boolean = format.validate;
|
|
40
|
+
|
|
41
|
+
// THEN
|
|
42
|
+
const store = Store.from(DecimalFormat);
|
|
43
|
+
|
|
44
|
+
expect(store.get("ajv:formats")).toEqual({
|
|
45
|
+
name: "decimal",
|
|
46
|
+
options: {
|
|
47
|
+
type: "number"
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(validate(undefined)).toBe(false);
|
|
52
|
+
expect(validate(null)).toBe(false);
|
|
53
|
+
expect(validate([])).toBe(false);
|
|
54
|
+
expect(validate({})).toBe(false);
|
|
55
|
+
expect(validate("0.0")).toBe(true);
|
|
56
|
+
expect(validate(NaN)).toBe(true);
|
|
57
|
+
expect(validate(0)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
it("should deserialize a number value", () => {
|
|
60
|
+
// WHEN
|
|
61
|
+
class Model {
|
|
62
|
+
@NumberDecimal()
|
|
63
|
+
price: Decimal128;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// THEN
|
|
67
|
+
const result = deserialize(
|
|
68
|
+
{
|
|
69
|
+
price: 1234.56
|
|
70
|
+
},
|
|
71
|
+
{type: Model, additionalProperties: false}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(result).toBeInstanceOf(Model);
|
|
75
|
+
expect(result).toEqual({
|
|
76
|
+
price: Types.Decimal128.fromString("1234.56")
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it("should deserialize a string value", () => {
|
|
80
|
+
// WHEN
|
|
81
|
+
class Model {
|
|
82
|
+
@NumberDecimal()
|
|
83
|
+
price: Decimal128;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// THEN
|
|
87
|
+
const result = deserialize(
|
|
88
|
+
{
|
|
89
|
+
price: "1234.56"
|
|
90
|
+
},
|
|
91
|
+
{type: Model, additionalProperties: false}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result).toBeInstanceOf(Model);
|
|
95
|
+
expect(result).toEqual({
|
|
96
|
+
price: Types.Decimal128.fromString("1234.56")
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
it("should serialize a number value", () => {
|
|
100
|
+
// WHEN
|
|
101
|
+
class Model {
|
|
102
|
+
@NumberDecimal()
|
|
103
|
+
price: Decimal128;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const mdl = new Model();
|
|
107
|
+
mdl.price = Types.Decimal128.fromString("1234.56");
|
|
108
|
+
|
|
109
|
+
// THEN
|
|
110
|
+
const result = serialize(mdl);
|
|
111
|
+
|
|
112
|
+
expect(result).toEqual({
|
|
113
|
+
price: 1234.56
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it("should serialize an undefined value", () => {
|
|
117
|
+
// WHEN
|
|
118
|
+
class Model {
|
|
119
|
+
@NumberDecimal()
|
|
120
|
+
price?: Decimal128;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const mdl = new Model();
|
|
124
|
+
|
|
125
|
+
// THEN
|
|
126
|
+
const result = serialize(mdl);
|
|
127
|
+
|
|
128
|
+
expect(result).not.toHaveProperty("price");
|
|
129
|
+
});
|
|
130
|
+
it("should deserialize an undefined value", () => {
|
|
131
|
+
// WHEN
|
|
132
|
+
class Model {
|
|
133
|
+
@NumberDecimal()
|
|
134
|
+
price?: Decimal128;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// THEN
|
|
138
|
+
const result = deserialize({}, {type: Model, additionalProperties: false});
|
|
139
|
+
|
|
140
|
+
expect(result).toBeInstanceOf(Model);
|
|
141
|
+
expect(result.price).toEqual(undefined);
|
|
142
|
+
});
|
|
143
|
+
it("should deserialize a number value using custom decimal", () => {
|
|
144
|
+
// GIVEN
|
|
145
|
+
class Decimal {
|
|
146
|
+
constructor(private value: any) {}
|
|
147
|
+
toString() {
|
|
148
|
+
return `${this.value}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const fakePrice = new Decimal("1234.56");
|
|
153
|
+
|
|
154
|
+
const obj = {Decimal};
|
|
155
|
+
vi.spyOn(obj, "Decimal").mockReturnValue(fakePrice);
|
|
156
|
+
|
|
157
|
+
// WHEN
|
|
158
|
+
class Model {
|
|
159
|
+
@NumberDecimal(obj.Decimal)
|
|
160
|
+
price: Decimal;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// THEN
|
|
164
|
+
const result = deserialize(
|
|
165
|
+
{
|
|
166
|
+
price: "1234.56"
|
|
167
|
+
},
|
|
168
|
+
{type: Model, additionalProperties: false}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(obj.Decimal).toHaveBeenCalledWith("1234.56");
|
|
172
|
+
|
|
173
|
+
expect(result).toBeInstanceOf(Model);
|
|
174
|
+
expect(result).toEqual({
|
|
175
|
+
price: fakePrice
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
it("should serialize a number value using custom decimal", () => {
|
|
179
|
+
// GIVEN
|
|
180
|
+
class Decimal {
|
|
181
|
+
constructor(private value: any) {}
|
|
182
|
+
toString() {
|
|
183
|
+
return `${this.value}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// WHEN
|
|
188
|
+
class Model {
|
|
189
|
+
@NumberDecimal(Decimal)
|
|
190
|
+
price: Decimal;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const mdl = new Model();
|
|
194
|
+
mdl.price = new Decimal("1234.56");
|
|
195
|
+
|
|
196
|
+
// THEN
|
|
197
|
+
const result = serialize(mdl);
|
|
198
|
+
|
|
199
|
+
expect(result).toEqual({
|
|
200
|
+
price: 1234.56
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
it("should convert Decimal128 to custom implementation", () => {
|
|
204
|
+
// GIVEN
|
|
205
|
+
class Decimal {
|
|
206
|
+
constructor(private value: any) {}
|
|
207
|
+
toString() {
|
|
208
|
+
return `${this.value}`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const testDecimal = Types.Decimal128.fromString("1234.56");
|
|
213
|
+
const fakePrice = new Decimal("1234.56");
|
|
214
|
+
|
|
215
|
+
const obj = {Decimal};
|
|
216
|
+
vi.spyOn(obj, "Decimal").mockReturnValue(fakePrice);
|
|
217
|
+
|
|
218
|
+
// WHEN
|
|
219
|
+
class Model {
|
|
220
|
+
@NumberDecimal(obj.Decimal)
|
|
221
|
+
price: Decimal;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const mdl = new Model();
|
|
225
|
+
mdl.price = new Decimal("1234.56");
|
|
226
|
+
|
|
227
|
+
// THEN
|
|
228
|
+
const store = Store.from(Model, "price");
|
|
229
|
+
const schema = store.get(MONGOOSE_SCHEMA);
|
|
230
|
+
|
|
231
|
+
expect(schema.get).toBeInstanceOf(Function);
|
|
232
|
+
expect(schema.get(testDecimal)).toBe(fakePrice);
|
|
233
|
+
expect(obj.Decimal).toHaveBeenCalledWith(testDecimal);
|
|
234
|
+
});
|
|
235
|
+
it("should not convert custom decimal again", () => {
|
|
236
|
+
// GIVEN
|
|
237
|
+
class Decimal {
|
|
238
|
+
constructor(private value: any) {}
|
|
239
|
+
toString() {
|
|
240
|
+
return `${this.value}`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const fakePrice = new Decimal("1234.56");
|
|
245
|
+
|
|
246
|
+
const obj = {Decimal};
|
|
247
|
+
vi.spyOn(obj, "Decimal").mockReturnValue(fakePrice);
|
|
248
|
+
|
|
249
|
+
// WHEN
|
|
250
|
+
class Model {
|
|
251
|
+
@NumberDecimal(obj.Decimal)
|
|
252
|
+
price: Decimal;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const mdl = new Model();
|
|
256
|
+
mdl.price = new Decimal("1234.56");
|
|
257
|
+
|
|
258
|
+
// THEN
|
|
259
|
+
const store = Store.from(Model, "price");
|
|
260
|
+
const schema = store.get(MONGOOSE_SCHEMA);
|
|
261
|
+
|
|
262
|
+
expect(schema.get).toBeInstanceOf(Function);
|
|
263
|
+
expect(schema.get(fakePrice)).toBe(fakePrice);
|
|
264
|
+
expect(obj.Decimal).not.toHaveBeenCalled();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {Formats, FormatsMethods} from "@tsed/ajv";
|
|
2
|
+
import {isNumber, isString, StoreMerge, useDecorators} from "@tsed/core";
|
|
3
|
+
import {OnDeserialize, OnSerialize} from "@tsed/json-mapper";
|
|
4
|
+
import {Example, Format, Property} from "@tsed/schema";
|
|
5
|
+
import {Schema as MongooseSchema, SchemaTypeOptions, Types} from "mongoose";
|
|
6
|
+
|
|
7
|
+
import {MONGOOSE_SCHEMA} from "../constants/constants.js";
|
|
8
|
+
|
|
9
|
+
@Formats("decimal", {type: "number"})
|
|
10
|
+
export class DecimalFormat implements FormatsMethods<string | number> {
|
|
11
|
+
validate(num: string | number): boolean {
|
|
12
|
+
return isString(num) || isNumber(num);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isDecimal(value: undefined | number | any) {
|
|
17
|
+
return value && value._bsontype === "Decimal128";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tell Mongoose whether to define an Decimal128 property.
|
|
22
|
+
* Will be serialized as `number` with format as `decimal`.
|
|
23
|
+
* ### Example
|
|
24
|
+
*
|
|
25
|
+
* ```typescript
|
|
26
|
+
* @Model()
|
|
27
|
+
* export class PriceModel {
|
|
28
|
+
* @NumberDecimal()
|
|
29
|
+
* price: Decimal128;
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
* Optionally using custom decimal type, such as `Big` from big.js
|
|
33
|
+
* ```typescript
|
|
34
|
+
* @Model()
|
|
35
|
+
* export class PriceModel {
|
|
36
|
+
* @NumberDecimal(Big)
|
|
37
|
+
* price: Big;
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
* @param type Optional decimal type constructor
|
|
41
|
+
* @decorator
|
|
42
|
+
* @mongoose
|
|
43
|
+
* @schema
|
|
44
|
+
*/
|
|
45
|
+
export function NumberDecimal(type?: any) {
|
|
46
|
+
const schema: SchemaTypeOptions<Decimal128> = {
|
|
47
|
+
type: MongooseSchema.Types.Decimal128
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (type) {
|
|
51
|
+
// Define property getter to convert Decimal128 to custom type
|
|
52
|
+
schema.get = (value) => {
|
|
53
|
+
return isDecimal(value) ? new type(value) : value;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return useDecorators(
|
|
58
|
+
Property(Number),
|
|
59
|
+
Format("decimal"),
|
|
60
|
+
Example(12.34),
|
|
61
|
+
StoreMerge(MONGOOSE_SCHEMA, schema),
|
|
62
|
+
|
|
63
|
+
// Deserialize number value from JSON to Decimal128
|
|
64
|
+
OnDeserialize((value) => {
|
|
65
|
+
if (value === undefined) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
if (type) {
|
|
69
|
+
return new type(value);
|
|
70
|
+
}
|
|
71
|
+
if (isString(value)) {
|
|
72
|
+
return Types.Decimal128.fromString(value);
|
|
73
|
+
}
|
|
74
|
+
return Types.Decimal128.fromString(`${value}`);
|
|
75
|
+
}),
|
|
76
|
+
|
|
77
|
+
// Serialize decimal value to floating point number
|
|
78
|
+
OnSerialize((value: any, ctx) => {
|
|
79
|
+
return value && Number(value);
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type Decimal128 = Types.Decimal128;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {getJsonSchema} from "@tsed/schema";
|
|
2
|
+
|
|
3
|
+
import {ObjectID} from "./objectID.js";
|
|
4
|
+
|
|
5
|
+
describe("ObjectID", () => {
|
|
6
|
+
it("should declare an ObjectID field", () => {
|
|
7
|
+
class MyModelTest {
|
|
8
|
+
@ObjectID("id")
|
|
9
|
+
_id: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
expect(getJsonSchema(MyModelTest)).toEqual({
|
|
13
|
+
properties: {
|
|
14
|
+
id: {
|
|
15
|
+
description: "An ObjectID",
|
|
16
|
+
examples: ["5ce7ad3028890bd71749d477"],
|
|
17
|
+
pattern: "^[0-9a-fA-F]{24}$",
|
|
18
|
+
type: "string"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
type: "object"
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {useDecorators} from "@tsed/core";
|
|
2
|
+
import {OnDeserialize} from "@tsed/json-mapper";
|
|
3
|
+
import {Description, Example, Name, Pattern} from "@tsed/schema";
|
|
4
|
+
import {Types} from "mongoose";
|
|
5
|
+
|
|
6
|
+
import {Auto} from "./auto.js";
|
|
7
|
+
import {Schema} from "./schema.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tell Mongoose whether to define an ObjectId property.
|
|
11
|
+
* ### Example
|
|
12
|
+
*
|
|
13
|
+
* ```typescript
|
|
14
|
+
* @Model()
|
|
15
|
+
* export class EventModel {
|
|
16
|
+
* @ObjectId('id')
|
|
17
|
+
* _id: string;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
* @param name
|
|
21
|
+
* @decorator
|
|
22
|
+
* @mongoose
|
|
23
|
+
* @schema
|
|
24
|
+
*/
|
|
25
|
+
export function ObjectID(name?: string) {
|
|
26
|
+
return useDecorators(
|
|
27
|
+
name && Name(name),
|
|
28
|
+
Pattern(/^[0-9a-fA-F]{24}$/),
|
|
29
|
+
Description("An ObjectID"),
|
|
30
|
+
Example("5ce7ad3028890bd71749d477"),
|
|
31
|
+
Auto(),
|
|
32
|
+
Schema({
|
|
33
|
+
type: Types.ObjectId,
|
|
34
|
+
match: undefined
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ObjectID = Types.ObjectId;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {schemaOptions} from "../utils/schemaOptions.js";
|
|
2
|
+
import {PostHook} from "./postHook.js";
|
|
3
|
+
|
|
4
|
+
describe("@PostHook()", () => {
|
|
5
|
+
describe("when decorator is used as class decorator", () => {
|
|
6
|
+
it("should call applySchemaOptions", () => {
|
|
7
|
+
// GIVEN
|
|
8
|
+
const fn = vi.fn();
|
|
9
|
+
|
|
10
|
+
// WHEN
|
|
11
|
+
@PostHook("method", fn)
|
|
12
|
+
class Test {}
|
|
13
|
+
|
|
14
|
+
// THEN
|
|
15
|
+
const options = schemaOptions(Test);
|
|
16
|
+
|
|
17
|
+
expect(options).toEqual({
|
|
18
|
+
post: [
|
|
19
|
+
{
|
|
20
|
+
method: "method",
|
|
21
|
+
fn,
|
|
22
|
+
options: undefined
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("when decorator is used as method decorator", () => {
|
|
30
|
+
it("should call applySchemaOptions", () => {
|
|
31
|
+
class Test {
|
|
32
|
+
@PostHook("save", {})
|
|
33
|
+
static method() {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
post: [options]
|
|
38
|
+
} = schemaOptions(Test);
|
|
39
|
+
|
|
40
|
+
expect(options.method).toEqual("save");
|
|
41
|
+
expect(options.fn).toBeInstanceOf(Function);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -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
|
+
}
|