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

moron

v0.1.5

Published

A simple ORM for Node.js

Downloads

17

Readme

Build Status Coverage Status

Introduction

Moron.js is a Node.js ORM built around the wonderful SQL query builder knex. All databases supported by knex are supported by moron.js. SQLite3, Postgres and MySQL are fully tested.

What moron.js gives you:

What moron.js doesn't give you:

  • A custom query DSL. SQL is used everywhere
  • Automatic database schema creation and migration. It is useful for the simple things, but usually just gets in your way when doing anything non-trivial. Moron.js leaves the schema related things to you. knex has a great migration tool that we recommend for this job.

Topics

Installation

npm install moron

Getting started

Best way to get started is to use one of the example projects:

git clone [email protected]:Vincit/moron.js.git moron
cd moron/examples/express
npm install
# We use knex for migrations.
npm install knex -g
knex migrate:latest
npm start

The express example project is a simple express server. The example-requests.sh file contains a bunch of curl commands for you to start playing with the REST API.

cat example-requests.sh

We also have an ES7 version of the express example project. It uses Babel for the ES7 --> ES5 transpiling.

git clone [email protected]:Vincit/moron.js.git moron
cd moron/examples/express-es7
npm install
# We use knex for migrations.
npm install knex -g
knex migrate:latest
# This runs the Babel transpiler and executes the app.
npm start

Also our API documentation contains a lot of examples.

Query examples

The Person model used in the examples is defined here.

All queries are started with one of the Model methods query(), $query() or $relatedQuery(). All these methods return a QueryBuilder instance that can be used just like a knex QueryBuilder.

Insert a Person model to the database:

Person
  .query()
  .insert({firstName: 'Jennifer', lastName: 'Lawrence'})
  .then(function (jennifer) {
    console.log(jennifer instanceof Person); // --> true
    console.log(jennifer.firstName); // --> 'Jennifer'
    console.log(jennifer.fullName()); // --> 'Jennifer Lawrence'
  })
  .catch(function (err) {
    console.log('oh noes');
  });

Fetch all Persons from the database:

Person
  .query()
  .then(function (persons) {
    console.log(persons[0] instanceof Person); // --> true
    console.log('there are', persons.length, 'Persons in total');
  })
  .catch(function (err) {
    console.log('oh noes');
  });

The return value of the .query() method is an instance of QueryBuilder that has all the methods a knex QueryBuilder has. Here is a simple example that uses some of them:

Person
  .query()
  .where('age', '>', 40)
  .andWhere('age', '<', 60)
  .andWhere('firstName', 'Jennifer')
  .orderBy('lastName')
  .then(function (middleAgedJennifers) {
    console.log('The last name of the first middle aged Jennifer is');
    console.log(middleAgedJennifers[0].lastName);
  });

Update models:

Person
  .query()
  .patch({lastName: 'Dinosaur'});
  .where('age', '>', 60)
  .then(function (patch) {
    console.log('all persons over 60 years old are now dinosaurs');
    console.log(patch.lastName); // --> Dinosaur.
  })
  .catch(function (err) {
    console.log(err.stack);
  });

While the static .query() method can be used to create a query to a whole table .$relatedQuery() method can be used to query a single relation. .$relatedQuery() returns an instance of QueryBuilder just like the .query() method.

var jennifer;
Person
  .query()
  .where('firstName', 'Jennifer')
  .first()
  .then(function (person) {
    jennifer = person;
    return jennifer
      .$relatedQuery('pets')
      .where('species', 'dog')
      .orderBy('name');
  })
  .then(function (jennifersDogs) {
    console.log(jennifersDogs[0] instanceof Animal); // --> true
    console.log(jennifer.pets === jennifersDogs); // --> true
    console.log('Jennifer has', jennifersDogs.length, 'dogs');
  })
  .catch(function (err) {
    console.log(err.stack);
  });

Insert a related model:

Person
  .query()
  .where('id', 100)
  .first()
  .then(function (person) {
    return person.$relatedQuery('pets').insert({name: 'Fluffy'});
  })
  .then(function (fluffy) {
    console.log(fully.id);
  })
  .catch(function (err) {
    console.log('something went wrong with finding the person OR inserting the pet');
    console.log(err.stack);
  });

Eager queries

Okay I said there is no custom DSL but actually we have teeny-tiny one for fetching relations eagerly. The following examples demonstrate how to use it:

Fetch one relation:

Person
  .query()
  .eager('pets')
  .then(function (persons) {
    // Each person has the `.pets` property populated with Animal objects related
    // through `pets` relation.
    console.log(persons[0].pets[0].name);
    console.log(persons[0].pets[0] instanceof Animal); // --> true
  });

Fetch multiple relations on multiple levels:

Person
  .query()
  .eager('[pets, children.[pets, children]]')
  .then(function (persons) {
    // Each person has the `.pets` property populated with Animal objects related
    // through `pets` relation. The `.children` property contains the Person's
    // children. Each child also has the `pets` and `children` relations eagerly
    // fetched.
    console.log(persons[0].pets[0].name);
    console.log(persons[1].children[2].pets[1].name);
    console.log(persons[1].children[2].children[0].name);
  });

Fetch one relation recursively:

Person
  .query()
  .eager('[pets, children.^]')
  .then(function (persons) {
    // The children relation is from Person to Person. If we want to fetch the whole
    // descendant tree of a person we can just say "fetch this relation recursively"
    // using the `.^` notation.
    console.log(persons[0].children[0].children[0].children[0].children[0].firstName);
  });

The expressions can be arbitrarily deep. See the full description here.

Because the eager expressions are strings they can be easily passed for example as a query parameter of an HTTP request. However, using such expressions opens the whole database through the API. This is not very secure. Therefore the QueryBuilder has the .allowEager method. allowEager can be used to limit the allowed eager expression to a certain subset. Like this:

expressApp.get('/persons', function (req, res, next) {
  Person
    .query()
    .allowEager('[pets, children.pets]')
    .eager(req.query.eager)
    .then(function (persons) { res.send(persons); })
    .catch(next);
});

The example above allows req.query.eager to be one of 'pets', 'children', 'children.pets', '[pets, children]' and '[pets, children.pets]'. Examples of failing eager expressions are 'movies', 'children.children' and 'notEvenAnExistingRelation'.

In addition to the .eager method, relations can be fetched using the loadRelated and $loadRelated methods of Model.

Transactions

Transactions are started by calling the moron.transaction function. Give all the models you want to use in the transaction as parameters to the transaction function. The model classes are bound to a newly created transaction and passed to the callback function. Inside this callback, all queries started through them take part in the same transaction.

The transaction is committed if the returned Promise is resolved successfully. If the returned Promise is rejected the transaction is rolled back.

moron.transaction(Person, Animal, function (Person, Animal) {

  return Person
    .query()
    .insert({firstName: 'Jennifer', lastName: 'Lawrence'})
    .then(function () {
      return Animal
        .query()
        .insert({name: 'Scrappy'});
    });

}).then(function (scrappy) {
  console.log('Jennifer and Scrappy were successfully inserted');
}).catch(function (err) {
  console.log('Something went wrong. Neither Jennifer nor Scrappy were inserted');
});

You only need to give the transaction function the model classes you use explicitly. All the related model classes are implicitly bound to the same transaction.

moron.transaction(Person, function (Person) {

  return Person
    .query()
    .insert({firstName: 'Jennifer', lastName: 'Lawrence'})
    .then(function (jennifer) {
      // This creates a query using the `Animal` model class but we
      // don't need to give `Animal` as one of the arguments to the
      // transaction function.
      return jennifer
        .$relatedQuery('pets')
        .insert({name: 'Scrappy'});
    });

}).then(function (scrappy) {
  console.log('Jennifer and Scrappy were successfully inserted');
}).catch(function (err) {
  console.log('Something went wrong. Neither Jennifer nor Scrappy were inserted');
});

The only way you can mess up with the transactions is if you explicitly start a query using a model class that is not bound to the transaction:

var Person = require('./models/Person');
var Animal = require('./models/Animal');

moron.transaction(Person, function (Person) {

  return Person
    .query()
    .insert({firstName: 'Jennifer', lastName: 'Lawrence'})
    .then(function (jennifer) {
      // OH NO! This query is executed outside the transaction
      // since the `Animal` class is not bound to the transaction.
      return Animal
        .query()
        .insert({name: 'Scrappy'});
    });

});

Documents

Moron.js makes it easy to store non-flat documents as table rows. All properties of a model that are marked as objects or arrays in the model's jsonSchema are automatically converted to JSON strings in the database and back to objects when read from the database. The database columns for the object properties can be normal text columns. Postgresql has the json and jsonb data types that can be used instead for better performance and possibility to make queries to the documents.

The address property of the Person model is defined as an object in the Person.jsonSchema:

Person
  .query()
  .insert({
    firstName: 'Jennifer',
    lastName: 'Lawrence',
    age: 24,
    address: {
      street: 'Somestreet 10',
      zipCode: '123456',
      city: 'Tampere'
    }
  })
  .then(function (jennifer) {
    console.log(jennifer.address.city); // --> Tampere
    return Person.query().where('id', jennifer.id);
  })
  .then(function (jenniferFromDb) {
    console.log(jenniferFromDb.address.city); // --> Tampere
  })
  .catch(function (err) {
    console.log('oh noes');
  });

Models

Models are created by inheriting from the Model base class. In moron.js the inheritance is done as transparently as possible. There is no custom Class abstraction making you wonder what the hell is happening. Just plain old ugly javascript inheritance.

var Model = require('moron').Model;

/**
 * @override Model
 * @constructor
 */
function Person() {
  Model.apply(this, arguments);
}

Model.extend(Person);
module.exports = Person;

// You can add custom functionality to Models just as you would
// to any javascript class.
Person.prototype.fullName = function () {
  return this.firstName + ' ' + this.lastName;
};

// Table name is the only required property.
Person.tableName = 'Person';

// This is not the database schema! Nothing is generated based on this. Whenever a
// Person object is created from a JSON object, the JSON is checked against this
// schema. For example when you call Person.fromJson({firstName: 'Jennifer'});
Person.jsonSchema = {
  type: 'object',
  required: ['firstName', 'lastName'],

  properties: {
    id: {type: 'integer'},
    parentId: {type: ['integer', 'null']},
    firstName: {type: 'string', minLength: 1, maxLength: 255},
    lastName: {type: 'string', minLength: 1, maxLength: 255},
    age: {type: 'number'},

    address: {
      type: 'object',
      properties: {
        street: {type: 'string'},
        city: {type: 'string'},
        zipCode: {type: 'string'}
      }
    }
  }
};

// This object defines the relations to other models.
Person.relationMappings = {
  pets: {
    relation: Model.OneToManyRelation,
    // The related model. This can be either a Model subclass constructor or an
    // absolute file path to a module that exports one. We use the file path version
    // here to prevent require loops.
    modelClass: __dirname + '/Animal',
    join: {
      from: 'Person.id',
      to: 'Animal.ownerId'
    }
  },

  movies: {
    relation: Model.ManyToManyRelation,
    modelClass: __dirname + '/Movie',
    join: {
      from: 'Person.id',
      // ManyToMany relation needs the `through` object to describe the join table.
      through: {
        from: 'Person_Movie.personId',
        to: 'Person_Movie.movieId'
      },
      to: 'Movie.id'
    }
  },

  children: {
    relation: Model.OneToManyRelation,
    modelClass: Person,
    join: {
      from: 'Person.id',
      to: 'Person.parentId'
    }
  }
};

Testing

To run the tests, all you need to do is configure the databases and run npm test. Check out this file for the test database configurations. If you don't want to run the tests against all databases you can just comment out configurations from the testDatabaseConfigs list.