-
Notifications
You must be signed in to change notification settings - Fork 115
/
definitions.factory.ts
126 lines (115 loc) · 4.21 KB
/
definitions.factory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { Type } from '@nestjs/common';
import { isUndefined } from '@nestjs/common/utils/shared.utils';
import * as mongoose from 'mongoose';
import { PropOptions } from '../decorators';
import { TypeMetadataStorage } from '../storages/type-metadata.storage';
const BUILT_IN_TYPES: Function[] = [Boolean, Number, String, Map, Date, Buffer];
export class DefinitionsFactory {
static createForClass(target: Type<unknown>): mongoose.SchemaDefinition {
if (!target) {
throw new Error(
`Target class "${target}" passed in to the "DefinitionsFactory#createForClass()" method is "undefined".`,
);
}
let schemaDefinition: mongoose.SchemaDefinition = {};
let parent: Function = target;
while (!isUndefined(parent.prototype)) {
if (parent === Function.prototype) {
break;
}
const schemaMetadata = TypeMetadataStorage.getSchemaMetadataByTarget(
parent as Type<unknown>,
);
if (!schemaMetadata) {
parent = Object.getPrototypeOf(parent);
continue;
}
schemaMetadata.properties?.forEach((item) => {
const options = this.inspectTypeDefinition(item.options as any);
this.inspectRef(item.options as any);
schemaDefinition = {
[item.propertyKey]: options as any,
...schemaDefinition,
};
});
parent = Object.getPrototypeOf(parent);
}
return schemaDefinition;
}
private static inspectTypeDefinition(
optionsOrType: mongoose.SchemaTypeOptions<unknown> | Function,
): PropOptions | [PropOptions] | Function | mongoose.Schema {
if (typeof optionsOrType === 'function') {
if (this.isPrimitive(optionsOrType)) {
return optionsOrType;
} else if (this.isMongooseSchemaType(optionsOrType)) {
return optionsOrType;
}
const isClass = /^class\s/.test(
Function.prototype.toString.call(optionsOrType),
);
optionsOrType = isClass ? optionsOrType : optionsOrType();
const schemaDefinition = this.createForClass(
optionsOrType as Type<unknown>,
);
const schemaMetadata = TypeMetadataStorage.getSchemaMetadataByTarget(
optionsOrType as Type<unknown>,
);
if (schemaMetadata?.options) {
/**
* When options are provided (e.g., `@Schema({ timestamps: true })`)
* create a new nested schema for a subdocument
* @ref https://mongoosejs.com/docs/subdocs.html
**/
return new mongoose.Schema(
schemaDefinition,
schemaMetadata.options,
) as mongoose.Schema;
}
return schemaDefinition;
} else if (typeof optionsOrType.type === 'function') {
optionsOrType.type = this.inspectTypeDefinition(optionsOrType.type);
return optionsOrType;
} else if (Array.isArray(optionsOrType)) {
return optionsOrType.length > 0
? [this.inspectTypeDefinition(optionsOrType[0])]
: (optionsOrType as any);
}
return optionsOrType;
}
private static inspectRef(
optionsOrType: mongoose.SchemaTypeOptions<unknown> | Function,
) {
if (!optionsOrType || typeof optionsOrType !== 'object') {
return;
}
if (typeof optionsOrType?.ref === 'function') {
try {
const result = (optionsOrType.ref as Function)();
optionsOrType.ref = result?.name ?? result;
} catch (err) {
if (err instanceof TypeError) {
const refClassName = (optionsOrType.ref as Function)?.name;
throw new Error(
`Unsupported syntax: Class constructor "${refClassName}" cannot be invoked without 'new'. Make sure to wrap your class reference in an arrow function (for example, "ref: () => ${refClassName}").`,
);
}
throw err;
}
} else if (Array.isArray(optionsOrType.type)) {
if (optionsOrType.type.length > 0) {
this.inspectRef(optionsOrType.type[0]);
}
}
}
private static isPrimitive(type: Function) {
return BUILT_IN_TYPES.includes(type);
}
private static isMongooseSchemaType(type: Function) {
if (!type || !type.prototype) {
return false;
}
const prototype = Object.getPrototypeOf(type.prototype);
return prototype && prototype.constructor === mongoose.SchemaType;
}
}