Installation
Install with npm:
npm i -S lemon-js
Usage
Define models
import { TypedModel, model, property } from 'lemon-js'; @modelexport class Permission extends TypedModel { @property public name: string;} @modelexport class User extends TypedModel { @property public age: number; @property({ index: true }) public email: string; // Use either ref or refer. @property([{ refer: Permission }]) public permissions: [Permission]; @property({ default: false }) public isActive: boolean; @property({ unique: true }) public name: string; public get displayName() { return `${ this.name } <${ this.email }>`; } public static findByEmail(email: string): Query<User> { return this.findOne({ email }); }} @modelexport class Post extends TypedModel { @property public title: string; @property public body: string; @property({ default: 0 }) public readCount: number; @property public creator: User; public static findByTitle(title: string): Query<Post> { return this.findOne({ title }); }} @modelexport class TypedUser extends User { @property({ default: 'user' }) public type: string;}
Insert objects
const write = new Permission({ name: 'write'});await write.save(); const read = new Permission({ name: 'read'});await read.save(); const user = new User({ age: 20, email: 'user1@example.com', name: 'User 1', permissions: [read._id, write._id]});await user.save(); const post = new Post({ body: 'Post body', creator: user._id, title: 'Post 1',});await post.save();
Retrieve objects
const post = await Post.findByTitle('Post 1').populate('creator').exec();expect(post.title).to.be.equal('Post 1');expect(post.creator.displayName).to.be.equal('User 1 <user1@example.com>'); const user = await User.findByEmail('user1@example.com').populate('permissions').exec();expect(user.name).to.be.equal('User 1');expect(user.permissions.length).to.be.equal(2);expect(user.permissions[0].name).to.be.equal('read');expect(user.permissions[1].name).to.be.equal('write');
Sub-document
Model
export interface IColor { r: number; g: number; b: number;} export interface ICar { make: string; model: string; color: IColor; users?: User[];} export interface IRoom { color: IColor; name: string; owner: User; windows: IWindow[]; computer: IComputer;} export interface IWindow { color: IColor; installer: User;} export interface IComputer { color: IColor; users: User[];} export interface IHouse { name: string; car: ICar; rooms: IRoom[];} @modelexport class House extends TypedModel implements IHouse { @property name: string; @property({ subdoc: true, make: String, model: String, color: { r: Number, g: Number, b: Number }, users: [{ ref: User }] }) car: ICar; @property([{ subdoc: true, name: String, color: { r: Number, g: Number, b: Number }, owner: { refer: User }, windows: [{ // Nested subdoc subdoc: true, installer: { ref: User } }], computer: { // Nested subdoc subdoc: true, users: [{ ref: User }] } }]) rooms: IRoom[];}
Insert a sub-document
const wife = new User({ age: 20, email: 'user1@example.com', name: 'Wife', permissions: []});await wife.save(); const husband = new User({ age: 25, email: 'user2@example.com', name: 'Husband', permissions: []});await husband.save(); const house = new House({ name: 'home', car: { make: 'BMW', model: '550i', color: { r: 255, g: 20, b: 20 }, users: [wife._id, husband._id] }, rooms: [{ name: 'bedroom 1', color: { r: 20, g: 255, b: 20 }, owner: wife._id }, { name: 'bedroom 2', color: { r: 20, g: 20, b: 255 }, owner: husband._id }]});await house.save();
Populate a subdocument
const house: House = await House.findOne({ name: 'home' }).populate('rooms.owner').exec() as House;const house: House = await House.findOne({ name: 'home' }).populate('car.users').exec() as House;
Middlewares (pre & post hooks)
@method({ pre: ['save'], post: ['remove']})public preSavePostRemove() {}
Validations
@modelexport class ValidateTest extends TypedModel { public static EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; @property({ validate: [ValidateTest.validateEmail1, 'Uh oh, "{PATH}" invalid 1.'] }) public email1: string; @property({ validate: [{ validator: ValidateTest.validateEmail2_1, message: 'Uh oh, "{PATH}" invalid 2-1.' }, { validator: ValidateTest.validateEmail2_2, message: 'Uh oh, "{PATH}" invalid 2-2.' }] }) public email2: string; @property({ validate: ValidateTest.validateEmail3 }) public email3: string; @property({ validate: { isAsync: true, validator: ValidateTest.validateEmail4, message: 'Uh oh, "{PATH}" invalid 4.' } }) public email4: string; public static validateEmail1(value) { // Object is mapped to object return ValidateTest.EMAIL_REGEX.test(value); } public static validateEmail2_1(value) { // Object is mapped to object return value.length > 2; } public static validateEmail2_2(value) { // Object is mapped to object return ValidateTest.EMAIL_REGEX.test(value); } public static validateEmail3(value) { // Object is mapped to object return ValidateTest.EMAIL_REGEX.test(value); } public static validateEmail4(value, callback) { // Object is mapped to object // Support mongoose isAsync callback let result = ValidateTest.EMAIL_REGEX.test(value); callback(result, 'Overwriting error'); } @method<ValidateTest>({ validate: ['email2', 'email3'], message: 'Validating for both email2 and email3' }) public validateEmail2AndEmail3(value, callback) { let result = ValidateTest.EMAIL_REGEX.test(value); callback(result, 'Overwriting error'); }}
createdAt & updatedAt
let user = await User.findOne({}); // Created datelet creationDate = user.createdAt; // Updated datelet updateDate = user.updatedAt;
Hiding fields from toJSON() and toObject()
@modelexport class House extends TypedModel implements IHouse { @property({ hidden: true }) name: string; @property({ subdoc: true, hidden: ['make'], make: String, model: String, color: { r: Number, g: Number, b: Number }, users: [{ ref: User }] }) car: ICar; @property([{ subdoc: { autoIndex: false }, hidden: ['owner'], name: String, color: { hidden: ['r'], r: Number, g: Number, b: Number }, owner: { refer: User }, windows: [{ hidden: true, subdoc: true, installer: { ref: User } }], computer: { hidden: ['users'], subdoc: true, users: [{ ref: User }], system: String, color: { r: Number, g: Number, b: Number } } }]) rooms: IRoom[];}
You can overwrite this when you run toJSON or toObject with the following option (default false).
const house: House = await House.findOne({ name: 'home' }).populate('rooms.computer').exec() as House;const json = house.toJSON({ showHidden: true})
License
Licensed under MIT.
Credits
Based on the work of @megahertz/mongoose-model