Mongo-TS Struct
About :
Mongoose Documents, by default, are not covered by there schema type.
The common solution For type-cover Document object, is an interface for each schema, which lead to redundancy (define the schema once as a schema definition object and once as an interface) and may take more code maintenance.
By defining a class that represent your Mongoose schema, and using Typescript decorators around the class's members, your schema definitions are being stored behind the scene and a type-cover 'native' Mongoose schema is created.
Using mongo-ts your schema need to be written once as a class - and a typed cover schema will be created for you.
References :
Features :
- Generate class definition to a native mongoose schema.
- Support OOP writing style by enabling schema class extending.
- Support class method & static implementation and invoking them through a document and Model.
- Reduce redundancies by inferring the property type (using reflection).
- Cover created and fetched documents with the schema class type definition.
Table Of Content :
Installation
npm i mongo-ts-struct -S
Quick Setup and Usage
-
Assuming you got an existing typescript node app, install the module with
npm i mongo-ts-struct -S
.Make sure the setting on your
tsconfig.json
file allowing decorators :"compilerOptions":"experimentalDecorators": true"emitDecoratorMetadata": true...... -
Moving on to the code,
The common mongoose schema setup with typescript on the background is looking something like :// on file - blog.interface.ts// on file - blog.schema.ts;;;// on file - blog.model.ts;;blogSchema.methods.recentComments =;
The down falls on this approach :
- You got three files for a single schema, you can place it all in one file, but that might be consider a bad practice.
- The redundancy and duplicate definition of your schema are very match noticeable.
Maintaining two definitions located in separate files in sync can cause some hide-of-sight bugs. - A method defined with
schema.methods
will not be type covered, meaning, the calling of the method will not be supported by typescript compiler.
This approach can be reduce now to the following, while avoiding all the mentioned fall becks.
The interface definition, mongoose-schema and schema-functions are all located under the same roof of a single class, just like the familiar OOP coding style.
// on file - blog.ts / a single file will suffice;; - You got three files for a single schema, you can place it all in one file, but that might be consider a bad practice.
-
The data queries are all remain the same, the returned documents are cover with the schema-class type, and the schema-class method can be invoked by the returned documents as well;
Api Reference
Schema Class Decorator
Overview:
Every schema class must be decorate with @TypedSchema
.
With that, the schema definitions can be collected (using field decorators) from the class, and the schema-class extending can be supported and work properly.
Class decorated with @TypedSchema
supports multiple function-hooks (stages) in the schema creation, using those hooks you can make your custom changes the schema creation process as you please.
@TypedSchema(config?: TypedSchemaConfig)
Description:
Example:
;
/* Will be mapped to : */ Admin =
interface TypedSchemaConfig
Description:
::TODO::
Definition:
::TODO::
toModel<M, T extends Ctor<M>>(TypedSchemeClass: T, modelName: string,preModelCreation: PreModelCreationFunc<T>)
Description:
::TODO::
Definition:
::TODO::
Schema Creation Hooks
Overview:
The process of generating a 'native' schema from schema-class, is divided to stages :
-
first there is a checkup, if a 'native' schema has been generated from that class, if so, its cached and the hook
onSchemaCreated
is called, else, -
the schema definition is being constructed from the metadata of the class and the hook
onConstructDefinitions
is called. -
after the schema definitions are determent, the 'native' schema is created and the hook
onSchemaCreated
is called, -
then the 'native' schema object is bound to any static / class method, that was decorated in the schema-class, and the hook
onSchemaBound
is called.
OnConstructDefinitions
Description:
An interface the schema-class can implement and apply the hook onConstructDefinitions
.
onConstructDefinitions
function executed after the schema definition has been constructed from the schema-class metadata, the schema definition object and decorated static / class methods object is provided as an argument.
Definition:
OnSchemaCreated
Description:
An interface the schema-class can implement and apply the hook onSchemaCreated
.
onSchemaCreated
function executed after the 'native' schema as created, the new schema object is provided as an argument.
Definition:
OnSchemaBound
Description:
An interface the schema-class can implement and apply the hook onSchemaBound
.
onSchemaBound
function executed after static / class method, had been bound to the 'native' schema, the bound schema object is provided as an argument.
Definition:
OnSchemaCached
Description:
An interface the schema-class can implement and apply the hook onSchemaCached
.
onSchemaCached
function executed only if a 'native' schema has been created from the schema-class (and can be cached), in case the schema can be cached non of the other hooks will ran.
The cached schema object is provided as an argument.
Definition:
Class Members Decorators
Overview:
Mongo-TS uses field decorators to collect data regard the decorated class members, with that data the member's schema definition is created and mapped to relevant property on the generated schema.
@Prop(definition?: Partial<PropertyDefinition>)
Description:
Decorator that infer the type's constructor of the decorated property using reflection, mapped it as the type
value of the property schema definition.
Example:
/* Will be mapped to : */ User =
@Ref(modelName: string, definition?: Partial<PropertyDefinition>)
Description:
Decorator that define a ref type
property by a provided modal name.
Example:
/* Will be mapped to : */ User =
@ArrayRef(modelName: string, definition?: Partial<PropertyDefinition>)
Description:
Decorator that define an array ref type
property by a provided modal name.
Example:
/* Will be mapped to : */ User =
@ArrayOf(type: SupportedTypes | Function, definition: Partial<PropertyDefinition>)
Description:
Decorator that get SupportedTypes
(one of the string values string
| number
| boolean
| any
) as a type indicator, or a constructor type function of a schema-class (decorated with @TypedSchema
), and define an array of that type.
An array type field can be inferred using reflection but currently the type of that array can't be detect.
Example:
/* Will be mapped to : */ User =
@Enum(enumKeys: Array<string>, definition?: Partial<PropertyDefinition>)
Description:
Decorator that define an Enum type property by a provided enum keys array (an array of string).
The property type can be the enum type or an array of that enum, the @Enum
will infer and map the property type accordingly.
Example:
// helper, take enum type and return his keys as an array.; /* Will be mapped to : */User = /* Will be mapped to : */Profile =
@Property(type: any, definition?: Partial<PropertyDefinition>)
Description:
Decorator that allows a free / custom definition of of the decorated property.
Useful in any case that not supported by an out-of-the-box decorator.
Note:
In must cases the @Property
decorator will be used, a duplication of the field type definition will be made, there for a more elegant approach will be to create a separate schema-class of for that field and decorate it with @Prop
.
Example:
/* Will be mapped to : */User = ...}
Compositions
Decorators by nature can be compose on top of each other, with decorator that set a specific definition attribute it can make more sense to utilize that behaviors.
An option for setting a class member's definition can be by composing specific-attribute decorators.
@Default(value: boolean = true)
Description:
Decorator that set the default
definition attribute to the provided value.
Example:
@Required(value: (boolean | string) = true)
Description:
Decorator that set the required
definition attribute to the provided value.
Example:
@Unique(value: boolean = true)
Description:
Decorator that set the unique
definition attribute to the provided value.
Example:
@Match(value: RegExp | string)
Description:
Decorator that set the match
definition attribute to the provided value.
Example:
Static & Class Method Decorators
Overview
Mongo-TS uses method decorators to reference and apply the decorated method (class method or static method) to the document and Model.
@Method()
Description:
Decorator that define a class method as a schema method for any document to use.
Example:
; UserModel.findByIdid.then;
@Static()
Description:
Decorator that define a static method as a schema method for any model to use.
Example:
; // calling this method in a static like syntax - will be supported in compile time as well as run time!UserModel.searchByName'bob'.then;
Important !
Document object that been 'leaned' will not be able to invoke any of his bounded class methods, altho, in compile time, the method under the document's type can be access.
; ; UserModel.findByIdid.lean.then;
Note:
If .lean()
was not chained before the .then()
than the method user.getEmailAccountProvider()
would have been called as expected.
Custom Default Schema Definition
Overview
::TODO::
Fallbacks
This module works best with flat schemas (zero redundancies).
The solution for multilayered schema, is to cover each complex (let say, more then three members) layer with a class and use it in the parent layer.
E.g, you could write your class like :
You can see that there is a repeating definition of the member profile
- that is the redundancy we aim to loos.
So by separating this class in to two layers, you can elegantly write it like :