npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@secjs/orm

v1.0.4

Published

Handle your application models in Node.js. Built on top of @secjs/database.

Downloads

7

Readme

Orm ⏹️

Handle your application models in Node.js. Built on top of @secjs/database

GitHub followers GitHub stars

Installation

To use the high potential from this package you need to install first this other packages from SecJS, it keeps as dev dependency because one day @secjs/core will install everything once.

npm install @secjs/env @secjs/utils @secjs/exceptions

Then you can install the package using:

npm install @secjs/orm

Usage

Configuring @secjs/database

To use @secjs/orm you need to set up @secjs/database first and connect it to Database.

Go to @secjs/database documentation

Model

With config/database file configured and Database connection opened you can start using Model class

Setting up your Model

First you need to create your model class extending Model class and use decorators to map your columns

import { Model, Column } from '@secjs/orm'

export class Product extends Model {
  /**
   * Defines the table name in database. Default is the
   * name of your model in snake_case and plural format
   */
  static table = 'products'

  /**
   * Defines the connection that this Model will use
   * to handle the database operations. This string will be used
   * in Database.connection method from @secjs/database.
   * Default is default
   */
  static connection = 'default'

  /**
   * Defines the primary key of the Model. You will always
   * set the value in Model and not the database columName.
   * Example: primaryKey will be idName and not id!
   * Default value is id
   */
  static primaryKey = 'idName'

  /**
   * Defines the values that are authorized to be persisted
   * in database. Example: if you try to create/update the
   * createdAt column, it will not be created/updated if it's
   * not present in persistOnly array. Default is ['*']
   */
  static persistOnly = ['name', 'description']

  /**
   * The columnName option defines that idName === id in database.
   * Default is the same name of the property, in this case, idName
   */
  @Column({ columnName: 'id' })
  public idName: number

  @Column()
  public name: string

  @Column()
  public description: string

  /**
   * The defaultValue option defines that when creating an Product,
   * Model will auto set quantity as '1' if nothing is set on create
   */
  @Column({ defaultValue: 1 })
  public quantity: number

  /**
   * The isCreatedAt option defines that when creating an Product,
   * Model will auto set createdAt as 'new Date()'
   */
  @Column({ isCreatedAt: true })
  public createdAt: Date

  /**
   * The isUpdatedAt option defines that when updating an Product,
   * Model will auto set updatedAt as 'new Date()'
   */
  @Column({ isUpdatedAt: true })
  public updatedAt: Date

  /**
   * The isDeletedAt option defines that when deleting an Product,
   * Model will soft delete instead of delete and auto set deletedAt
   * as 'new Date()'
   */
  @Column({ isDeletedAt: true })
  public deletedAt: Date
}

Creating a new Product

With our database connection established and our model defined, we can start using Product model

const product = await Product.create({ name: 'iPhone 10', description: 'Nice iPhone' })

console.log(product instanceof Product) // true

console.log(product.idName) // 1
console.log(product.name) // 'iPhone10'
console.log(product.quantity) // 5
console.log(product.createdAt) // 2022-03-19T16:23:35.897Z
console.log(product.updatedAt) // 2022-03-19T16:23:35.897Z
console.log(product.deletedAt) // null

// Product instance has the toJSON method that will 
// transform the model to object
const productJson = product.toJSON()

console.log(productJson instanceof Product) // false

Get one and get many products

const product = await Product.find({ idName: 1 })
console.log(product instanceof Product) // true

const products = await Product.findMany({ idName: 1 })
console.log(products[0] instanceof Product) // true

Update products

const where = { idName: 1 }

const product = await Product.update(where, { quantity: 50 })
console.log(product instanceof Product) // true
console.log(product.quantity) // 50

Delete products

If you set a Column with isDeletedAt property, delete method will always soft delete your data, setting your column as 'new Date()'

// If Product Model didn't have isDeletedAt, it would be deleted
const product = await Product.delete({ idName: 1 })
console.log(product instanceof Product) // true
console.log(product.deletedAt) // 2022-03-19T16:30:17.130Z

// You can force the delete setting the force parameter as true
const force = true

const voidProduct = await Product.delete({ idName: 1 }, force)
console.log(voidProduct instanceof Product) // false
console.log(voidProduct) // undefined

Defining Relationships

We can use Decorators to define OneToOne, OneToMany and ManyToMany relations in our Model

User Model

import { Role } from './Role'
import { Product } from './Product'
import { Model, Column, HasMany, ManyToMany } from '@secjs/orm'

export class User extends Model {
  static persistOnly = ['name']

  @Column()
  public id: number

  @Column()
  public name: string

  /**
   * The primaryKey option defines the primaryKey of your Model.
   * In this case the default value would be id, because the
   * primaryKey of User model is id.
   *
   * The foreignKey options defines the foreignKey of your RelationModel.
   * In this case the default value would be userId. The name of
   * your Model in lower case (user) with Id in the end.
   */
  @HasMany(() => Product, { primaryKey: 'id', foreignKey: 'userId' })
  public products: Product[]

  /**
   * WARN - In ManyToMany relations you don't need to define the Pivot model
   *
   * pivotTableName: The name of your pivot table - Default is user_role
   * localPrimaryKey: The PK of your Model - Default is id
   * pivotLocalForeignKey: The FK of your Model in the Pivot Table - Default is userId
   * relationPrimaryKey: The PK of your RelationModel - Default is id
   * pivotRelationForeignKey: The FK of your RelationModel in the Pivot Table - Default is roleId
   */
  @ManyToMany(() => Role, {
    pivotTableName: 'user_role'
  })
  public roles: Role[]
}

Product Model

import { User } from './User'
import { Model, Column, BelongsTo } from '@secjs/orm'

export class Product extends Model {
  static persistOnly = ['name', 'quantity']

  @Column()
  public id: number

  @Column({ defaultValue: 'Product' })
  public name: string

  @Column({ defaultValue: 1 })
  public quantity: number

  // The FK
  @Column()
  public userId: number

  /**
   * The primaryKey option defines the primaryKey of your RelationModel.
   * In this case the default value would be id, because the
   * primaryKey of User model is id.
   *
   * The foreignKey options defines the foreignKey of your Model.
   * In this case the default value would be userId. The name of
   * your RelationModel in lower case (user) with Id in the end.
   */
  @BelongsTo(() => User, { foreignKey: 'userId', primaryKey: 'id' })
  public user: User
}

Role Model

import { User } from './User'
import { Model, Column, ManyToMany } from '@secjs/orm'

export class Role extends Model {
  static persistOnly = ['name']

  @Column()
  public id: number

  @Column({ defaultValue: 'Customer' })
  public name: string

  /**
   * WARN - To map the other side of a ManyToMany relation you need to set the
   * pivotTableName property. If you do not set the pivotTableName here and
   * try to make an include query it would make the query in role_user pivotTable
   */
  @ManyToMany(() => User, {
    pivotTableName: 'user_role'
  })
  public users: User[]
}

ProductDetail Model

import { Product } from './Product'
import { Model, Column, BelongsTo } from '@secjs/orm'

export class ProductDetail extends Model {
  static persistOnly = ['detail']

  @Column()
  public id: number

  @Column()
  public detail: string

  // The FK
  @Column()
  public productId: number

  @BelongsTo(() => Product)
  public product: Product
}

Get relationships using load and query builder

We can use load instance method to get the relations from User

const user = await User.find()

// Load roles and products with productDetails from User
await user.load('roles', 'products.productDetails')

console.log(user.roles[0] instanceof Role) // true
console.log(user.products[0] instanceof Product) // true
console.log(user.products[0].productDetails[0] instanceof ProductDetail) // true

But we can use query builder from User too

const user = await User
  .query()
  .includes('roles')
  // Sub query
  .includes('products', async (query) => {
    query.orderBy('detail', 'asc').includes('productDetails')
  })
  .get()

console.log(user.roles[0] instanceof Role) // true
console.log(user.products[0] instanceof Product) // true
console.log(user.products[0].productDetails[0] instanceof ProductDetail) // true

If you have mapped right your models with HasMany and BelongsTo you can also take the user from Product model. Example using paginate method

const page = 0
const limit = 1

const { meta, links, data } = await Product
  .query()
  .includes('user')
  .paginate(page, limit, '/products')

console.log(meta)
/**
 * itemCount: 1
 * totalItems: 1
 * totalPages: 1
 * currentPage: 0
 * itemsPerPage: 1
 */

console.log(links)
/**
 * first: '/products?limit=1'
 * previous: '/products?page=0&limit=1'
 * next: '/products?page=1&limit=1'
 * last: '/products?page=1&limit=1'
 */

console.log(data instanceof Product) // true
console.log(data.user instanceof User) // true

Creating your own methods/static methods using the query builder

You can define your own methods in your Model and use the query builder to get what you want

import { User } from './User'
import { Model, Column, BelongsTo } from '@secjs/orm'

export class Product extends Model {
  static persistOnly = ['name', 'quantity']

  @Column()
  public id: number

  @Column({ defaultValue: 'Product' })
  public name: string

  @Column({ defaultValue: 1 })
  public quantity: number

  @Column()
  public userId: number

  @BelongsTo(() => User, { foreignKey: 'userId', primaryKey: 'id' })
  public user: User

  /**
   * You can define it as static and use it like this -> Product.getAllWithUser(1, 2, 3)
   */
  static async getAllWithUser(...quantityIn: number[]) {
    return this
      .query()
      .select('id', 'name', 'quantity')
      .whereIn('quantity', ...quantityIn)
      .orderBy('name', 'ASC')
      .includes('user')
      .getMany()
  }

  /**
   * Or you can define it as an instance method and use it like this
   * -> const product = await Product.find() -> await product.getAllWithUser(1, 2, 3)
   */
  async getAllWithUser(quantityIn: number[]) {
    // WARN - Do not use Model here, it wont work. 
    // You need to use your defined model, in this case, Product.
    return Product
      .query()
      .select('id', 'name', 'quantity')
      .whereIn('quantity', quantityIn)
      .orderBy('name', 'ASC')
      .includes('user')
      .getMany()
  }
}

Factory method

The factory method from models can be used to generate a massive number of models. You can use this to work in the tests of your application

const { id } = await User.create({ name: 'João' })

// Will create ten products in database with name Test and the owner will the user João
const products = await Product.factory().count(10).create<Product[]>({ name: 'Test', userId: id })
// Will make ten products fake objects with name Test and the owner will the user João
const fakeProducts = await Product.factory().count(10).make<Product[]>({ name: 'Test', userId: id })

To solve the problem of repetitive data you can define in your model the static method definition. And use the this.faker property to mock values

import { User } from './User'
import { Model, Column, BelongsTo } from '@secjs/orm'

export class Product extends Model {
  // Factories ignore the persistOnly rule
  static persistOnly = ['name', 'quantity']

  static async definition() {
    return {
      name: this.faker.name.firstName(),
      quantity: this.faker.datatype.number(),
      /**
       * Define that User factory should return only the value from idPrimary.
       * If you don't set the returning key, factory will return all the data from User
       */
      userId: User.factory('idPrimary'),
    }
  }

  @Column()
  public id: number

  @Column({ defaultValue: 'Product' })
  public name: string

  @Column({ defaultValue: 1 })
  public quantity: number

  @Column()
  public userId: number

  @BelongsTo(() => User, { foreignKey: 'userId', primaryKey: 'id' })
  public user: User
}

Now you can use create method normally

import { Product } from './Product'

const { id } = await User.create({ name: 'João' })

// Will create ten products in database with mocked values and 
// the owner will be different for each one of then
const productsDifOwner = await Product.factory().count(10).create<Product[]>()

// Will create ten products in database with mocked values and 
// the owner will be the same for each one of then
const productsSameOwner = await Product.factory().count(10).create<Product[]>({ userId: id })

Assertions with Factory

Factory has some assertions that you can make to use in tests

const factory = User.factory()

await factory.count(10).create<User[]>()

// Assert that user table has twenty users
await factory.assertCount(10) // true

// Assert that exists at least one user with name 'João'
await factory.assertHas({ name: 'João' }) // true

// Assert that does not exist any user with name 'Victor'
await factory.assertMissing({ name: 'Victor' }) // true

// Assert that exists one user with id 1
await factory.assertExists({ id: 1 }) // true

// Assert that does not exists one user with id 9999
await factory.assertNotExists({ id: 9999 }) // true

License

Made with 🖤 by jlenon7 :wave: