Skip to content

Commit

Permalink
feat(mongoose): Schema decorator can be used to decorate a class
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-dev authored and Romakita committed Jan 19, 2019
1 parent b4ff467 commit c7ff02d
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 147 deletions.
6 changes: 1 addition & 5 deletions packages/mongoose/src/decorators/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {MongooseModelOptions} from "../interfaces/MongooseModelOptions";
import {registerModel} from "../registries/MongooseModelRegistry";
import {createModel, createSchema} from "../utils";
import {applySchemaOptions} from "../utils/schemaOptions";

/**
* Define a class as a Mongoose Model. The model can be injected to the Service, Controller, Middleware, Converters or Filter with
Expand Down Expand Up @@ -41,10 +40,7 @@ import {applySchemaOptions} from "../utils/schemaOptions";
*/
export function Model(options: MongooseModelOptions = {}) {
return (target: any) => {
const schema = createSchema(target, options.schemaOptions);

applySchemaOptions(target, options);

const schema = createSchema(target, options);
registerModel(target, createModel(target, schema, options.name, options.collection, options.skipInit));
};
}
3 changes: 1 addition & 2 deletions packages/mongoose/src/decorators/postHook.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {getDecoratorType} from "@tsed/core";
import {MongoosePostErrorHookCB, MongoosePostHookCB} from "../interfaces/MongooseModelOptions";
import {MongoosePostErrorHookCB, MongoosePostHookCB} from "../interfaces";
import {applySchemaOptions} from "../utils/schemaOptions";

/**
* We can simply attach a `@PostHook` decorator to your model class and
* define the hook function like you normally would in Mongoose.
*
* ```typescript
* import {IgnoreProperty, Required} from "@tsed/common";
* import {PostHook, Model} from "@tsed/mongoose";
Expand Down
2 changes: 1 addition & 1 deletion packages/mongoose/src/decorators/preHook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {getDecoratorType} from "@tsed/core";
import {HookErrorCallback} from "mongoose";
import {MongoosePreHookAsyncCB, MongoosePreHookSyncCB} from "../interfaces/MongooseModelOptions";
import {MongoosePreHookAsyncCB, MongoosePreHookSyncCB} from "../interfaces";
import {applySchemaOptions} from "../utils/schemaOptions";

export interface PreHookOptions {
Expand Down
46 changes: 41 additions & 5 deletions packages/mongoose/src/decorators/schema.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,53 @@
import {getDecoratorType} from "@tsed/core";
import {PropertyMetadata, PropertyRegistry} from "@tsed/common";
import {SchemaTypeOpts} from "mongoose";
import {MONGOOSE_SCHEMA} from "../constants";
import {MongooseSchemaOptions} from "../interfaces";
import {createSchema} from "../utils";

/**
* Define a class as a Mongoose Schema ready to be used to compose other schemes and models.
*
* ### Example
*
* ```typescript
* @Schema()
* export class EventSchema {
* @Property()
* field: string;
* }
* ```
*
* ### Options
*
* - `schemaOptions` (mongoose.SchemaOptions): Option to configure the schema behavior.
*
* @param {MongooseSchemaOptions | undefined} options
* @returns {(target: any) => void}
* @decorator
* @mongoose
*/
export function Schema(options?: MongooseSchemaOptions): (target: any) => void;
/**
* Attach a schema on property class.
*
* @param definition A mongoose schema
* @param {SchemaTypeOpts<any>} definition
* @returns {Function}
* @decorator
* @mongoose
*/
export function Schema(definition: SchemaTypeOpts<any>) {
return PropertyRegistry.decorate((propertyMetadata: PropertyMetadata) => {
propertyMetadata.store.merge(MONGOOSE_SCHEMA, definition);
});
export function Schema(definition: SchemaTypeOpts<any>): Function;
export function Schema(options: MongooseSchemaOptions | SchemaTypeOpts<any> = {}) {
return (...parameters: any[]) => {
switch (getDecoratorType(parameters)) {
case "property":
return PropertyRegistry.decorate((propertyMetadata: PropertyMetadata) => {
propertyMetadata.store.merge(MONGOOSE_SCHEMA, options as SchemaTypeOpts<any>);
})(...parameters);
case "class":
const [target] = parameters;
createSchema(target, options as MongooseSchemaOptions);
break;
}
};
}
72 changes: 2 additions & 70 deletions packages/mongoose/src/interfaces/MongooseModelOptions.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,7 @@
import {HookDoneFunction, HookErrorCallback, HookNextFunction, NativeError, Schema, SchemaOptions} from "mongoose";
import {MongooseDocument} from "./MongooseDocument";
import {MongooseSchemaOptions} from "./MongooseSchemaOptions";

/**
*
*/
export interface MongoosePreHookAsyncCB<T> {
(doc: MongooseDocument<T>, next: HookNextFunction, done: HookDoneFunction): any;
}

export interface MongoosePreHookSyncCB<T> {
(doc: MongooseDocument<T>, next: HookNextFunction): any;
}

/**
*
*/
export interface MongoosePostErrorHookCB<T> {
(error: any, doc: MongooseDocument<T>, next: (err?: NativeError) => void): void;
}

/**
*
*/
export interface MongoosePostHookCB<T> {
(doc: MongooseDocument<T>, next: (err?: NativeError) => void): void;
}

/**
*
*/
export interface MongoosePreHook {
method: string;
fn: MongoosePreHookSyncCB<any> | MongoosePreHookAsyncCB<any>;
parallel?: boolean;
errorCb?: HookErrorCallback;
}

/**
*
*/
export interface MongoosePostHook {
method: string;
fn: MongoosePostHookCB<any> | MongoosePostErrorHookCB<any>;
}

/**
*
*/
export interface MongoosePluginOptions {
plugin: (schema: Schema, options?: any) => void;
options?: any;
}

/**
*
*/
export interface MongooseIndexOptions {
fields: object;
options?: any;
}

/**
*
*/
export interface MongooseModelOptions {
schemaOptions?: SchemaOptions;
export interface MongooseModelOptions extends MongooseSchemaOptions {
name?: string;
collection?: string;
skipInit?: boolean;
plugins?: MongoosePluginOptions[];
indexes?: MongooseIndexOptions[];
pre?: MongoosePreHook[];
post?: MongoosePostHook[];
}
48 changes: 48 additions & 0 deletions packages/mongoose/src/interfaces/MongooseSchemaOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {HookDoneFunction, HookErrorCallback, HookNextFunction, NativeError, Schema, SchemaOptions} from "mongoose";
import {MongooseDocument} from "./MongooseDocument";

export interface MongoosePreHookAsyncCB<T> {
(doc: MongooseDocument<T>, next: HookNextFunction, done: HookDoneFunction): any;
}

export interface MongoosePreHookSyncCB<T> {
(doc: MongooseDocument<T>, next: HookNextFunction): any;
}

export interface MongoosePostErrorHookCB<T> {
(error: any, doc: MongooseDocument<T>, next: (err?: NativeError) => void): void;
}

export interface MongoosePostHookCB<T> {
(doc: MongooseDocument<T>, next: (err?: NativeError) => void): void;
}

export interface MongoosePreHook {
method: string;
fn: MongoosePreHookSyncCB<any> | MongoosePreHookAsyncCB<any>;
parallel?: boolean;
errorCb?: HookErrorCallback;
}

export interface MongoosePostHook {
method: string;
fn: MongoosePostHookCB<any> | MongoosePostErrorHookCB<any>;
}

export interface MongoosePluginOptions {
plugin: (schema: Schema, options?: any) => void;
options?: any;
}

export interface MongooseIndexOptions {
fields: object;
options?: any;
}

export interface MongooseSchemaOptions {
schemaOptions?: SchemaOptions;
plugins?: MongoosePluginOptions[];
indexes?: MongooseIndexOptions[];
pre?: MongoosePreHook[];
post?: MongoosePostHook[];
}
2 changes: 2 additions & 0 deletions packages/mongoose/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export * from "./MDBConnection";
export * from "./MongooseDocument";
export * from "./MongooseModel";
export * from "./MongooseModelOptions";
export * from "./MongooseSchema";
export * from "./MongooseSchemaOptions";
14 changes: 2 additions & 12 deletions packages/mongoose/src/utils/buildMongooseSchema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Store} from "@tsed/core";
import {JsonSchemesRegistry, PropertyRegistry} from "@tsed/common";
import {MongooseSchema} from "../interfaces/MongooseSchema";
import {MONGOOSE_SCHEMA} from "../constants";
import {MongooseSchema} from "../interfaces";

const MONGOOSE_RESERVED_KEYS = ["_id"];

Expand Down Expand Up @@ -56,10 +56,6 @@ export function buildMongooseSchema(target: any): MongooseSchema {
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;
Expand All @@ -77,13 +73,7 @@ export function buildMongooseSchema(target: any): MongooseSchema {
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 = Object.assign(definition, {type: Store.from(metadata.type).get(MONGOOSE_SCHEMA)});
}

definition = clean(Object.assign(definition, mongooseSchema));
Expand Down
12 changes: 6 additions & 6 deletions packages/mongoose/src/utils/createSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {getClass, Store, Type} from "@tsed/core";
import {ConverterService, IConverterOptions} from "@tsed/common";
import * as mongoose from "mongoose";
import {MONGOOSE_SCHEMA} from "../constants";
import {MongooseSchema, MongooseSchemaOptions} from "../interfaces";
import {buildMongooseSchema} from "./buildMongooseSchema";
import {MongooseSchema} from "../interfaces/MongooseSchema";
import {applySchemaOptions} from "./schemaOptions";

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

Expand Down Expand Up @@ -34,15 +34,15 @@ function setUpSchema({schema, virtuals}: MongooseSchema, options?: mongoose.Sche
* @param {"mongoose".SchemaOptions} options
* @returns {"mongoose".Schema}
*/
export function createSchema(target: Type<any>, options?: mongoose.SchemaOptions): mongoose.Schema {
export function createSchema(target: Type<any>, options: MongooseSchemaOptions = {}): mongoose.Schema {
const store = Store.from(target);

if (!store.has(MONGOOSE_SCHEMA)) {
const schema = setUpSchema(buildMongooseSchema(target), options);

const schema = setUpSchema(buildMongooseSchema(target), options.schemaOptions);
store.set(MONGOOSE_SCHEMA, schema);
applySchemaOptions(target, options);
setUpTarget(target);
schema.loadClass(target);
store.set(MONGOOSE_SCHEMA, schema);
}

return store.get(MONGOOSE_SCHEMA);
Expand Down
37 changes: 16 additions & 21 deletions packages/mongoose/src/utils/schemaOptions.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import {deepExtends, Store} from "@tsed/core";
import {HookDoneFunction, HookNextFunction, Schema} from "mongoose";
import {MONGOOSE_SCHEMA, MONGOOSE_SCHEMA_OPTIONS} from "../constants";
import {MongooseModelOptions} from "../interfaces/MongooseModelOptions";
import {MongooseSchemaOptions} from "../interfaces";

/**
*
* @param target
* @param {MongooseModelOptions} options
* @param {MongooseSchemaOptions} options
*/
export function schemaOptions(target: any, options?: MongooseModelOptions) {
export function schemaOptions(target: any, options?: MongooseSchemaOptions) {
const store = Store.from(target);
if (options) {
options = deepExtends(store.get(MONGOOSE_SCHEMA_OPTIONS) || {}, options);
store.set(MONGOOSE_SCHEMA_OPTIONS, options);
}

options = deepExtends(store.get(MONGOOSE_SCHEMA_OPTIONS) || {}, options);
store.set(MONGOOSE_SCHEMA_OPTIONS, options);

return store.get(MONGOOSE_SCHEMA_OPTIONS);
}
Expand All @@ -24,23 +23,21 @@ export function schemaOptions(target: any, options?: MongooseModelOptions) {
* @returns {any}
*/
export function buildPreHook(fn: Function) {
if (fn.length === 2) {
return function(next: HookNextFunction) {
return fn(this, next);
};
}

return function(next: HookNextFunction, done: HookDoneFunction) {
return fn(this, next, done);
};
return fn.length === 2
? function(next: HookNextFunction) {
return fn(this, next);
}
: function(next: HookNextFunction, done: HookDoneFunction) {
return fn(this, next, done);
};
}

/**
*
* @param target
* @param {MongooseModelOptions} options
* @param {MongooseSchemaOptions} options
*/
export function applySchemaOptions(target: any, options: MongooseModelOptions) {
export function applySchemaOptions(target: any, options: MongooseSchemaOptions) {
const store = Store.from(target);

options = schemaOptions(target, options);
Expand All @@ -49,9 +46,7 @@ export function applySchemaOptions(target: any, options: MongooseModelOptions) {
const schema: Schema = store.get(MONGOOSE_SCHEMA);

if (options.plugins) {
if (options.plugins) {
options.plugins.forEach(item => schema.plugin(item.plugin, item.options));
}
options.plugins.forEach(item => schema.plugin(item.plugin, item.options));
}

if (options.indexes) {
Expand Down
Loading

0 comments on commit c7ff02d

Please sign in to comment.