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

noboxout-orm

v0.0.9

Published

Noboxout ORM (object relational mapper) with MYSQL backend

Downloads

15

Readme

noboxout-orm (norm) Build Status

NPM

Introduction

Object-relational mapping (ORM, O/RM, and O/R mapping) in computer software is a programming technique for converting data between incompatible type systems in object-oriented programming languages. This creates, in effect, a "virtual object database" that can be used from within the programming language.

Basically the Glue between database and Javascript Objects.

Why norm?

Why there are so many glues out there? :)

Three main things (Pros)

  • Coding style (Classical object oriented inheritance using node-class)
  • Transactions
  • Error handling done right.

Cons:

  • Only Mysql and Memcached available

Norm object

  • setup(Object: configuration)
  norm = require("noboxout-orm").Norm;

  norm.setup({
      database: {
          host     : "127.0.0.1",
          user     : "travis",
          password : "",
          database: "norm_test"
      },
      cache: {
          host: "127.0.0.1",
          port: 11211,
          config: {
              poolSize: 25
          }
      }
  });
  • define (String entity_name, Object class_definition, Object table_definition)

    Define a model.

    All models are stored in norm.models with the given entity_name (case-sensitive)

    All table definitions are stored are norm.tables

  • reserve (callback(Error err, Connection con))

    Reserve a new connection to Database and Cache

    If you are working on a HTTP server environment, this should (must in our case) be call at the beginning of a request.

  • release (Connection con)

    Release given connection

  • registerError (code String, tokens Array, new_error String, entity Entity)

    Rewrite errors to something meaningful for your app.

    note use a null value inside the tokens to match anything "*"

    see Error Handling

  • sync()

    Synchronize database schema, DROPPING EVERYTHING FIRST, there is no incremental sync (and no plans atm)

  • verbose/debug/log/info/warn/err(...)

    Colored log to stdout/stderr . @See Log section

Define models

Let's start!


    var User;

    User = norm.define("User", {
        id: norm.Number.LENGTH(10).UNSIGNED,
        login: norm.String.LENGTH(100),
        // sha1 in binary
        password: norm.String.Binary(64),
        email: norm.String.LENGTH(255),

        // you can define more properties that wont be mapped
        online: true,

        // this is the constructor.
        // if declared, remember to call this.__parent(data)
        initialize: function (data) {
            this.__parent(data); // it's mandatory!

            // do your staff
        }
    }, {
        // NOTE!!
        // Norm has column prefix.
        // It's highly recommended to avoid collisions
        // norm didn't handle column name collisions
        prefix: "user_",

        // by default is the Class name lowercased
        tableName: "users",

        // primaryKey is "id" by default
        primaryKey: "id"
    });

DATABASE MAPPING TYPES

  • Common for every type below.

    • NOTNULL
    • DEFAULT(Mixed)
    • UNIQUE
    • COMMENTS(String)
    • GROUPS(Array of strings)
  • Number

    • UNSIGNED
    • ZERIFILL
    • LENGTH(Number)
  • Decimal

    • UNSIGNED
    • ZERIFILL
    • LENGTH(Number)
    • DECIMALS(Number)
  • Enum

    • CHARSET(String)
    • COLLATION(String)
    • VALUES(Array of strings)
  • Date

  • String

    • CHARSET(String)
    • COLLATION(String)
    • LENGTH(Number[0-255])
  • Text

    • CHARSET(String)
    • COLLATION(String)
  • Binary

    • LENGTH(number)

In future version we will let you setup and specific type like CHAR via DBTYPE(TYPE) This has some risks, so we avoid it in the current release.

Model/Entity functions

To avoid collisions, we prefix all functions with "$"

At Entity level

  • $create (Connection con = null): Instance

    Create a new instance and assign a connection if passed.

    Note: Do not store your entity in database.

  • $unique (Array: columns, String: uq_name): this

    Add a unique to definition

  • $hasOne (Entity target_entity, Object options): this

    Add a relation to target_entity given options. With this method can be mapped:

    • OneToOne unidirectional & bidirectional
    • OneToMany unidirectional & bidirectional

    options (Object)

    • foreignKey: String

      ForeignKey name

      Default: (prefix + primaryKey) of target entity

    • property: String

      Property name in the root entity

      Default: target entity name lowercased

    • unique: Boolean

      Set foreignKey unique. See defining relations

      Default: true

    • refColumn: String

      Property name in the target entity

      Default: root entity name lowercased

    • notNull: Boolean

      Default: false

    • eager: Boolean

      Fetch relation in root by default?

      Not working atm, in todo list.

      Default: false

    • refEager: Boolean

      Fetch relation target entity by default?

      Not working atm, in todo list.

      Default: false

  • $hasMany (Entity target_entity, Object options) : this

    Add a relation to target_entity given options.

    With this method can be mapped:

    • ManyToOne unidirectional & bidirectional

    options it's the same object as $hasOne:Options

  • $get (String id_pk, Object options, Work work = null) -> Work

    Get Entity from cache/database.

    If work is provided 'get' will be append it to it.

    options (Object)

    • eager: Boolean|Undefined

      undefined, default behavior as defined by the model

      false, no eager

      true, all eager

    • callback Function that will be called when get is finished

    • name key in the result object

  • $delete (String id_pk, Object options, Work work = null) -> Work

    Build a Query to delete a single row in database given the primary key.

    If work is provided 'delete' will be append it to it.

    options (Object)

    • callback:Function

      Function that will be called when find is finished

    • name: String

      Key in the result object

  • $find (Object where, Object options, Work work = null) -> Work

    Build a Query to retrieve any number of rows.

    SQL Where is created by keys as columns equals values as value

    If work is provided 'find' will be append it to it.

    options (Object)

    • callback:Function (optional)

      Function that will be called when find is finished

    • name: String (optional)

      Key in the result object

  • $search (String id_pk, Object options, Work work = null) -> Work

    Like $find but handles Strings as LIKE (not equal). Useful for text search

    Notice that it could be slow... LIKE is slow.

    If work is provided 'search' will be append it to it.

    options (Object)

    • callback:Function (optional)

      Function that will be called when search is finished

    • name: String (optional)

      Key in the result object

  • $exists (String id_pk, Object options, Work work = null) -> Work

    Perform a count to database (it's not cached!) to check if given primary key exists.

    If work is provided 'exists' will be append it to it.

    options (Object)

    • callback:Function (optional)

      Function that will be called when exists is finished

    • name: String (optional)

      Key in the result object

  • $export (Entity entity, Array group = []) -> Object

    Serialize the Entity to a plain object. No relation exported.

    Use group to filter columns. See GROUPS

    Override this method to have custom behaviors.

    Should not be used directly, use the instance method $export, that will handle relations and groups.

  • $exportRelation (Entity: entity, Relation: rel, array:group = []) -> Object

    Export given relation, by default loop every item and do $export.

    Override this method to have custom behaviors.

    Should not be used directly, use the instance method $export, that will handle relations and groups.

  • $createTable -> Array

    return the create table as string.

    It's recommended to use norm.sync() to avoid foreignKey issues.

At Instance Level

  • $merge (Object obj, Array groups = null)

    "unserialize" an entity.

    Use group to filter columns. See GROUPS

  • $export (groups, Boolean|Array: export_relations)

    Export current Entity to a plain object.

    Use group to filter columns. See GROUPS export_relations true, export all relations false, no export relations Array, list of relations to be included.

  • $fetch ([Array relations = null,] Function callback) -> Work

    Retrieve relations.

    relations

    • if null, retrieve all relations
    • if Array, white-list of relation to be fetched.

    Note $fetch does not receive/return a work, a callback must be provided.

Define relations

Defining relations is made at the model/entity level using hasOne & hasMany (there is no belongTo, not needed, see the examples)

OneToOne relation unidirectional

var User = norm.define("User" /* ... */);
var Session = norm.define("Session" /* ... */);

User.hasOne(Session);

// equivalent to do:
User.hasOne(Session, {
    foreignKey: "sess_id", // Sessions' (prefix + pk)
    property: "session", // tableName
    unique: true, // OneToOne, key is unique
    refColumn: "id" // User's pk
});

// usage

var u = User.$create(); // notice $, we try to mark everything with "$" prefix
u.session = Session.$create();
// set vars
u.$store(norm_connection, function() {/*...*/});

OneToOne relation bidirectional

var User = norm.define("User" /* ... */);
var Session = norm.define("Session" /* ... */);

User.hasOne(Session, {refProperty: "owner"});

ManyToOne relation unidirectional

var Person = norm.define("Person" /* ... */);
var Country = norm.define("Country" /* ... */);

Person.hasOne(Country, {unique: false});

ManyToOne relation bidirectional

var Person = norm.define("Person" /* ... */);
var Country = norm.define("Country" /* ... */);

Person.hasOne(Country, {unique: false, refProperty: "citizens"});

OneToMany self-relation unidirectional

var User = norm.define("User" /* ... */);

User.hasMany(User, {property: "mentors", foreignKey: "mentor_id"});

OneToMany self-relation bidirectional

var User = norm.define("User" /* ... */);

User.hasMany(User, {property: "mentors", foreignKey: "mentor_id", refProperty: "mentee"});

Util

  • Util.listFetch(Array[Entity], Object|Array fetch_options, callback): null

    Do a $fetch for every item in the list, wait responses and call callback.

  • Util.getEntity(Entity entity, Number pk_val, Connection con, fetch_options, callback): null

    Get an entity by PrimaryKey, then fetch, then call callback.

    Remember that eager is useful, but it's not cached while fetch is cached in most cases.

  • Util.cachekey(key, value)

    Create a valid cachekey given any DB-compatible, useful for Binary/Buffer values

  • Util.delCache(key): Function

    Return a function that delete from cache given key.

    Example:

  work.push(Util.delCache("my-key")); // return a function compatible with work :)

Error Handling

Norm can handle errors, and match Database error into something meaningful for you app. So you not need to handle errors, registerError and throw it.

Example:


    // setup models
    var User = norm.define("user", {
        id: norm.Number.LENGTH(10).UNSIGNED,
        name: norm.String.NOTNULL.LENGTH(255),
        initialize: function () {
            this.__parent();
        }
    }, {
        prefix: "us_",
        tableName: "user"
    });

    User.$unique(["name"], "uq_user_name");

    // register errors in norm
    norm.registerError('ER_BAD_NULL_ERROR', [ 'us_name' ], "invalid user name");
    norm.registerError('ER_DUP_ENTRY', [ null, 'uq_user_name' ], "user name is use");

    // tests / examples

    test("null test", function (t) {
        var work = User.$create().$store();
        work.exec(con, function(err, res) {

            t.equals(err.message, "invalid user name");

            t.end();
        });
    });

    test("unique test", function (t) {
        User.$create().$merge({
            name: "test-001"
        }).$store().exec(con, function(err, res) {

            User.$create().$merge({
                name: "test-001"
            }).$store().exec(con, function(err, res) {
                t.equals(err.message, "user in use");
            });

            t.end();
        });
    });

Errors in Norm have long-traces, that we manually manage for you. check test/test-norm-errors.js to see those long-traces.

This way you will see in the traces the origin of the error.

Remember to adjust Error.stackTraceLimit to something a bit greater that 10. 15 should be enough


{ [Error: invalid tag name]
  code: 'ER_BAD_NULL_ERROR',
  errno: 1048,
  sqlState: '23000',
  index: 0,
  sql: 'INSERT INTO `tag` (`tg_name`,`tg_updated_at`,`tg_created_at`,`tg_id`) VALUES(NULL,NULL,\'2014-04-10 10:41:15.835\',NULL)',
  args: null,
  tokens: [ 'tg_name' ],
  dbMessage: 'ER_BAD_NULL_ERROR: Column \'tg_name\' cannot be null' }

Error: ER_DUP_ENTRY: Duplicate entry 'test-001' for key 'uq_user_name'
    at Query.Sequence._packetToError (noboxout-orm/node_modules/mysql/lib/protocol/sequences/Sequence.js:30:14)
    at Query.ErrorPacket (noboxout-orm/node_modules/mysql/lib/protocol/sequences/Query.js:82:18)
    at Protocol._parsePacket (noboxout-orm/node_modules/mysql/lib/protocol/Protocol.js:197:24)
    at Parser.write (noboxout-orm/node_modules/mysql/lib/protocol/Parser.js:62:12)
    at Protocol.write (noboxout-orm/node_modules/mysql/lib/protocol/Protocol.js:37:16)
    at Socket.ondata (stream.js:51:26)
    at Socket.EventEmitter.emit (events.js:117:20)
    at Socket.<anonymous> (_stream_readable.js:746:14)
    at Socket.EventEmitter.emit (events.js:92:17)
    at emitReadable_ (_stream_readable.js:408:10)
    --------------------
    at Protocol._enqueue (noboxout-orm/node_modules/mysql/lib/protocol/Protocol.js:110:26)
    at PoolConnection.Connection.query (noboxout-orm/node_modules/mysql/lib/Connection.js:148:25)
    at __class.query (noboxout-orm/lib/mysql.js:85:29)
    at __class.query (noboxout-orm/lib/connection.js:103:27)
    at store_entity_cb (noboxout-orm/lib/util.js:134:13)
    at Object.storeRelations (noboxout-orm/lib/util.js:243:13)
    at Object.module.exports.storeEntity (noboxout-orm/lib/util.js:271:18)
    at Array.0 (noboxout-orm/lib/entity.js:80:22)
    at array.mapSerial.all_null (noboxout-orm/lib/work.js:129:25)
    at next (js-array-enhancements/lib/arrays.js:529:21)
Previous Error
    at __class.query (noboxout-orm/lib/connection.js:101:28)
    at store_entity_cb (noboxout-orm/lib/util.js:134:13)
    at Object.storeRelations (noboxout-orm/lib/util.js:243:13)
    at Object.module.exports.storeEntity (noboxout-orm/lib/util.js:271:18)
    at Array.0 (noboxout-orm/lib/entity.js:80:22)
    at array.mapSerial.all_null (noboxout-orm/lib/work.js:129:25)
    at next (js-array-enhancements/lib/arrays.js:529:21)
    at Object.module.exports.mapSerial (js-array-enhancements/lib/arrays.js:540:9)
    at module.exports.__class.exec (noboxout-orm/lib/work.js:106:19)
    at noboxout-orm/test/test-norm-errors.js:69:25 // <--- HERE!

Log

noboxout-orm use noboxout-log. By default print Warning/Error (logLevel 2)

Mute log

    require("noboxout-orm").Norm.logMute = true

Adjust verbosity

    require("noboxout-orm").Norm.logLevel = 4; // all
    require("noboxout-orm").Norm.logLevel = 3; // no verbose
    require("noboxout-orm").Norm.logLevel = 2; // no verbose, debug
    require("noboxout-orm").Norm.logLevel = 1; // no verbose, debug, warn
    require("noboxout-orm").Norm.logLevel = 0; // no verbose, debug, warn, error

To see bigger traces use:

    require("noboxout-orm").Norm.logTrace = Number; // 3-4 should be enough to see your code

FAQ

Why my errors do not match ?

Mysql errors change continously. We create a bash that parse "/usr/share/mysql/errmsg-utf8.txt"

Remember to edit "mysql_parse_errors.sh" if in your system errmsg-utf8.txt is not in the default folder.

``bash cd lib sh mysql_parse_errors.sh


## Install

With [npm](http://npmjs.org) do:

npm install noboxout-orm


## test (travis-ci ready!)

npm test // or cd /test node test-class.js


## license


MIT.