Skip to content

Commit

Permalink
fix(mongoose): Rewrote createModel and createSchema to conform to mon…
Browse files Browse the repository at this point in the history
…goose definitions for model and schema

Change: Undocumented support for subdocument is replaced by a defined feature.
  • Loading branch information
alex-dev authored and Romakita committed Jan 19, 2019
1 parent a10e592 commit 36edbe1
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 159 deletions.
2 changes: 1 addition & 1 deletion packages/mongoose/src/decorators/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {createModel, createSchema} from "../utils";
import {applySchemaOptions} from "../utils/schemaOptions";

/**
* Define a class a Mongoose Model. The model can be injected to the Service, Controller, Middleware, Converters or Filter with
* Define a class as a Mongoose Model. The model can be injected to the Service, Controller, Middleware, Converters or Filter with
* `@Inject` annotation.
*
* ### Example
Expand Down
6 changes: 6 additions & 0 deletions packages/mongoose/src/interfaces/MongooseSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {SchemaDefinition} from "mongoose";

export interface MongooseSchema {
schema: SchemaDefinition;
virtuals: Map<string, any>;
}
76 changes: 56 additions & 20 deletions packages/mongoose/src/utils/buildMongooseSchema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {JsonSchemesRegistry, PropertyMetadata, PropertyRegistry} from "@tsed/common";
import * as mongoose from "mongoose";
import {Store} from "@tsed/core";
import {JsonSchemesRegistry, PropertyRegistry} from "@tsed/common";
import {MongooseSchema} from "../interfaces/MongooseSchema";
import {MONGOOSE_SCHEMA} from "../constants";

const MONGOOSE_RESERVED_KEYS = ["_id"];
Expand Down Expand Up @@ -37,38 +38,73 @@ export function mapProps(jsonProps: any = {}) {
/**
*
* @param target
* @returns {"mongoose".SchemaDefinition}
* @returns {MongooseSchema}
*/
export function buildMongooseSchema(target: any): mongoose.SchemaDefinition {
export function buildMongooseSchema(target: any): MongooseSchema {
const properties = PropertyRegistry.getProperties(target);
const jsonSchema: any = JsonSchemesRegistry.getSchemaDefinition(target) || {};
const mSchema: mongoose.SchemaDefinition = {};
const schema: MongooseSchema = {schema: {}, virtuals: new Map()};

if (properties) {
properties.forEach((propertyMetadata: PropertyMetadata, propertyKey: string) => {
if (MONGOOSE_RESERVED_KEYS.indexOf(propertyKey) > -1) {
return;
const jsonSchema: any = JsonSchemesRegistry.getSchemaDefinition(target) || {properties: {}};

for (const [key, metadata] of properties.entries()) {
if (MONGOOSE_RESERVED_KEYS.includes(key as string)) {
continue;
}

// Keeping the Mongoose Schema separate so it can overwrite everything once schema has been built.
const mongooseSchema = metadata.store.get(MONGOOSE_SCHEMA) || {};

if (mongooseSchema.ref && mongooseSchema.localField && mongooseSchema.foreignField) {
if (metadata.required) {
throw new Error("Virtual references cannot be required.");
}

mongooseSchema.justOnce = !metadata.isArray;
schema.virtuals.set(key as string, mongooseSchema);
continue;
}

let definition = {
required: propertyMetadata.required
let definition: any = {
required: metadata.required
? function() {
return propertyMetadata.isRequired(this[propertyKey]);
return metadata.isRequired(this[key]);
}
: false
};

if (propertyMetadata.isClass) {
definition = Object.assign(definition, buildMongooseSchema(propertyMetadata.type));
} else {
definition = Object.assign(definition, {type: propertyMetadata.type}, mapProps((jsonSchema.properties || {})[propertyKey]));
if (!metadata.isClass) {
definition = Object.assign(definition, {type: metadata.type}, mapProps(jsonSchema.properties[key]));
} else if (!mongooseSchema.ref) {
// References are handled by the final merge
const propSchema = Store.from(metadata.type).get(MONGOOSE_SCHEMA);

if (!propSchema) {
throw new Error(`${metadata.typeName} is not a registered subdocument. Use decorator '@Subdocument' on the class.`);
}

definition = Object.assign(definition, {type: propSchema});
}

definition = clean(Object.assign(definition, propertyMetadata.store.get(MONGOOSE_SCHEMA) || {}));
definition = clean(Object.assign(definition, mongooseSchema));

if (metadata.isCollection) {
if (metadata.isArray) {
definition = [definition];
} else {
// Can be a Map or a Set;
// Mongoose implements only Map;
if (metadata.collectionType !== Map) {
throw new Error(`Invalid collection type. ${metadata.collectionName} is not supported.`);
}

mSchema[propertyKey] = propertyMetadata.isArray ? [definition] : definition;
});
definition = {type: Map, of: definition};
}
}

schema.schema[key as string] = definition;
}
}

return mSchema;
return schema;
}
14 changes: 1 addition & 13 deletions packages/mongoose/src/utils/createModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@ export function createModel<T>(
skipInit?: boolean
): MongooseModel<T> {
Store.from(target).set(MONGOOSE_MODEL_NAME, name);
target.prototype.serialize = function(options: IConverterOptions, converterService: ConverterService) {
const {checkRequiredValue, ignoreCallback} = options;

return converterService.serializeClass(this, {
type: getClass(target),
checkRequiredValue,
ignoreCallback
});
};

schema.loadClass(target);
const modelInstance: any = mongoose.model(name, schema, collection, skipInit);

return modelInstance;
return mongoose.model(name, schema, collection, skipInit);
}
34 changes: 31 additions & 3 deletions packages/mongoose/src/utils/createSchema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
import {Store, Type} from "@tsed/core";
import {getClass, Store, Type} from "@tsed/core";
import {ConverterService, IConverterOptions} from "@tsed/common";
import * as mongoose from "mongoose";
import {MONGOOSE_SCHEMA} from "../constants";
import {buildMongooseSchema} from "./buildMongooseSchema";
import {MongooseSchema} from "../interfaces/MongooseSchema";

function setUpTarget(target: Type<any>) {
// Using function to avoid this binding.
target.prototype.serialize = function(options: IConverterOptions, converter: ConverterService) {
const {checkRequiredValue, ignoreCallback} = options;

return converter.serializeClass(this, {
type: getClass(target),
checkRequiredValue,
ignoreCallback
});
};
}

function setUpSchema({schema, virtuals}: MongooseSchema, options?: mongoose.SchemaOptions) {
const mongooseSchema = new mongoose.Schema(schema, options);

for (const [key, options] of virtuals.entries()) {
mongooseSchema.virtual(key, options);
}

return mongooseSchema;
}

/**
*
Expand All @@ -13,8 +38,11 @@ export function createSchema(target: Type<any>, options?: mongoose.SchemaOptions
const store = Store.from(target);

if (!store.has(MONGOOSE_SCHEMA)) {
const definition: mongoose.SchemaDefinition = buildMongooseSchema(target);
store.set(MONGOOSE_SCHEMA, new mongoose.Schema(definition, options));
const schema = setUpSchema(buildMongooseSchema(target), options);

setUpTarget(target);
schema.loadClass(target);
store.set(MONGOOSE_SCHEMA, schema);
}

return store.get(MONGOOSE_SCHEMA);
Expand Down
Loading

0 comments on commit 36edbe1

Please sign in to comment.