• Stars
    star
    568
  • Rank 78,502 (Top 2 %)
  • Language
    TypeScript
  • License
    Other
  • Created about 11 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A high performance MongoDB ORM for Node.js

Iridium

A High Performance, IDE Friendly ODM for MongoDB

NPM Module Build Status Coverage Status bitHound Overall Score bitHound Code bitHound Dependencies

Iridium is designed to offer a high performance, easy to use and above all, editor friendly ODM for MongoDB on Node.js. Rather than adopting the "re-implement everything" approach often favoured by ODMs like Mongoose and friends, requiring you to learn an entirely new API and locking you into a specific coding style, Iridium tries to offer an incredibly lightweight implementation which makes your life easier where it counts and gets out of your way when you want to do anything more complex.

It also means that, if you're familiar with the MongoDB CLI you should find working with Iridium very natural, with all database methods returning promises for their results and sensible, type annotated results being provided if you wish to make use of them.

Installation

Iridium makes use of the latest set of @types TypeScript definitions files, allowing you to install everything using just a simple npm install.

npm install iridium --save

Features

  • Built with TypeScript and designed for ease of use, you'll be hard pressed to find another Node.js ORM as easy to pick up and learn when combined with a supporting editor.
  • Promises Throughout mean that you can easily start working with your favourite A+ compliant promise library and writing more readable code. Don't think promises are the way forward? Stick with callbacks, Iridium supports both.
  • Fully Tested with over 300 unit tests and over 95% test coverage, helping to ensure that Iridium always behaves the way it's supposed to.
  • Decorator Support helps describe your classes in an easy to understand and maintain manner, embrace ES7 with strong fallbacks on ES5 compliant approaches.
  • Fall into Success with Iridium's carefully designed API - which is structured to help ensure your code remains maintainable regardless of how large your project becomes.
  • Amazing Documentation which covers the full Iridium API, and is always up to date thanks to the brilliant TypeDoc project, can be found at sierrasoftworks.github.io/Iridium.

Requirements

Iridium is built on top of a number of very modern technologies, including TypeScript 2.0, JavaScript ES6 and the latest MongoDB Node.js Driver (version 2.2). You'll need to be running Node.js 6.x, or 4.x with the --harmony flag to run version 7 of Iridium. For older versions of Node.js, please considering using version 6 of Iridium instead.

For starters, you will need to be running MongoDB 2.6 or later in order to use Iridium - however we recommend you use MongoDB 3.1 due to the various performance improvements they've made. If you're working with TypeScript, you will also need to use the 2.0 compiler or risk having the Iridium type definitions break your project.

Using Iridium

Rather than opt of the usual "Look how quickly you can do something" approach, we thought it might be useful to see an example which covers most of the stuff you'd need to do in Iridium. This example covers defining your own document interfaces, a custom schema and instance type which provides some additional methods.

You'll notice that the House class extends Iridium's Instance class, which gives it methods like save() as well as change tracking when calling save() on the instance. If you'd prefer a lighter approach or to use your own home-grown implementation then you can do so by taking a look at the Custom Instances section.

import {Core, Model, Instance, Collection, Index, Property, ObjectID} from 'iridium';

interface Colour {
    r: number;
    g: number;
    b: number;
}

interface Car {
    make: string;
    model: string;
    colour: Colour;
}

interface HouseDocument {
    _id?: string;
    name: string;

    cars?: Car[];
}

@Index({ name: 1 })
@Collection('houses')
class House extends Instance<HouseDocument, House> implements HouseDocument {
    @ObjectID _id: string;
    @Property(/^.+$/)
    name: string;

    @Property([{
        make: String,
        model: String,
        colour: {
            r: Number,
            g: Number,
            b: Number
        }
    }])
    cars: Car[];

    static onCreating(doc: HouseDocument) {
        doc.cars = doc.cars || [];
    }

    addCar(make: string, model: string, colour: Colour) {
        this.cars.push({
            make: make,
            model: model,
            colour: colour
        });
    }

    get numberOfCars() {
        return this.cars.length;
    }
}

class MyDatabase extends Core {
    Houses = new Model<HouseDocument, House>(this, House);
}

var myDb = new MyDatabase({ database: 'houses_test' });

myDb.connect().then(() => myDb.Houses.insert({
        name: 'My House',
        cars: [{
            make: 'Audi',
            model: 'A4',
            colour: { r: 0, g: 0, b: 0 }
        }]
    }))
    .then(() => myDb.Houses.get())
    .then((house) => {
        house.addCar('Audi', 'S4', { r: 255, g: 255, b: 255 });
        return house.save();
    })
    .then(() => myDb.close());

Defining a Model

Iridium models are created with a reference to their Core (which provides the database connection) and an InstanceType which is composed of a constructor function as well as a number of static properties providing configuration information for the instance.

JavaScript

new Model(core, InstanceType);

TypeScript

new Model<DocumentInterface, InstanceType>(core, InstanceType);

If you're working with TypeScript, you can provide an interface for the document structure used by the database, which will allow you to get useful type hints when you are creating documents. You can also provide the InstanceType to provide useful type information for any instances which are retrieved from the database. This information is carried through all promises and callbacks you will use within Iridium and it makes your life significantly easier.

Typically you will expose your models as variables on a custom Core implementation like this.

class MyCore extends Core {
    MyModel = new Model<MyDocumentInterface, MyInstanceType>(this, MyInstanceType);
}

The InstanceType Constructor

The InstanceType constructor is responsible for creating objects which represent a document retrieved from the database. It also provides a number of configuration details which are used to determine how Iridium works with the model.

There are two approaches to defining an instance constructor - the first is to create a true wrapper like the one provided by Iridium.Instance which offers helper methods like save() and remove(), which comes in very handy for writing concise descriptive code, while the other approach is to simply return the document received from the database - great for performance or security purposes.

TypeScript

interface Document {
    _id?: string;
}

class InstanceType {
    constructor(model: Model<Document, Instance>, document: Document, isNew: boolean = true, isPartial: boolean = false) {

    }

    _id: string;

    static schema: Iridium.Schema = {
        _id: false
    };

    static collection = 'myCollection';
}

JavaScript

module.exports = function(model, document, isNew, isPartial) {

}

module.exports.collection = 'myCollection';
module.exports.schema = {
    _id: false
};

Configuration Options

As we mentioned, configuration of a model is conducted through static properties on its constructor. These configuration options include the schema which is used to validate that all data inserted into the database through Iridium meets certain conditions, the collection which specifies the name of the MongoDB collection into which the documents are stashed and a couple of others worth noting.

Schema

Iridium uses Skmatc for schema validation, you can read more about it on its project page but we'll give a quick rundown of the way you make use of it here.

The model's schema is defined using an object in which keys represent their document property counterparts while the values represent a validation rule. You can also make use of the @Property decorator to automatically build up your schema object.

TypeScript

class InstanceType {
    _id: string;
    email: string;

    static schema: Iridium.Schema = {
        _id: false,
        email: /^.+@.+$/
    };
}
class InstanceType extends Iridium.Instance<any, InstanceType> {
    @Iridium.ObjectID
    _id: string;
    
    @Iridium.Property(String)
    email: string;
}

JavaScript

function InstanceType() {}

InstanceType.schema = {
    _id: false,
    email: /^.+@.+$/
};

The Iridium Instance Class

Instead of implementing your own instance constructor every time, Iridium offers a powerful and tested instance base class which provides a number of useful helper methods and a diff algorithm allowing you to make changes in a POCO manner.

To use it, simply inherit from it (if you need any computed properties or custom methods) or provide it as your instance type when instantiating the model.

TypeScript

class InstanceType extends Iridium.Instance<Document, InstanceType> {
    _id: string;
}

new Iridium.Model<Document, InstanceType>(core, InstanceType);

JavaScript

function InstanceType() {
    Iridium.Instance.apply(this, arguments);
}

require('util').inherits(InstanceType, Iridium.Instance);

new Iridium.Model(core, InstanceType);

If you've used the Iridium.Instance constructor then you'll have a couple of useful helper methods available to you. These include save(), refresh(), update(), remove() and delete() which do more or less what it says on the can - refresh and update are synonyms for one another as are remove and delete.

You'll also find first() and select() which allow you to select the first, or all, entr(y|ies) in a collection which match a predicate - ensuring that this maps to the instance itself within the predicate - helping to make comparisons somewhat easier within JavaScript ES5.

Best Practices

There are a number of best practices which you should keep in mind when working with Iridium to help get the best possible experience. For starters, Iridium is built up of a number of smaller components - namely the validation, transform and caching layers.

Validation Layer

The validation layer allows you to plug in your own custom validators, or simply make use of the built in ones, to quickly validate your documents against a strongly defined schema. It is designed to enable you to quickly generate meaningful and human readable validation messages, minimizing the need for error translation within your application.

Custom validators can be added either using the validators property or by using the @Validate decorator on your instance class.

@Iridium.Validate('myValidator', function(schema, data, path) {
    return this.assert(data == 42)
})
export class InstanceType extends Iridium.Instance<any, InstanceType> {
    @Iridium.Property('myValidator')
    myProperty: number;
}
var skmatc = require('skmatc');

function InstanceType() {
    Iridium.Instance.apply(this, arguments);
}

require('util').inherits(InstanceType, Iridium.Instance);

InstanceType.validators = [
    skmatc.create(function(schema) {
        return schema === 'myValidator';
    }, function(data, schema, path) {
        return data === 42;
    })
];

InstanceType.schema = {
    myProperty: 'myValidator'
};

Iridium expects validators to operate in a read-only mode, modifying documents within your validators (while possible) is strongly discouraged as it can lead to some strange side effects and isn't guaranteed to behave the same way between releases. If you need to make changes to documents, take a look at the Transform Layer.

Transform Layer

The transform layer is designed to make changes to the documents you store within MongoDB as well as the data presented to your application. A good example is the way in which ObjectIDs are treated, within your application they appear as plain strings - allowing you to quickly and easily perform many different operations with them. However, when you attempt to save an ObjectID field to the database, it is automatically converted into the correct ObjectID object before being persisted.

The transform layer allows you to register your own transforms both on a per-model and per-property basis. In the case of a model, the transform is given the whole document and is expected to return the transformed document. Property transforms work the same, except that they are presented with, and expected to return, the value of a single top-level property.

The easiest way to add a transform is using the @Transform decorator, however if you are working in a language which doesn't yet support decorators then you can easily use the transforms property on your instance class.

@Iridium.Transform(document => {
    document.lastFetched = new Date();
}, document => {
    document.lastFetched && delete document.lastFetched;
    return document;
})
export class InstanceType extends Iridium.Instance<any, InstanceType> {
    @Iridium.Transform(data => data.toUpperCase(), data => data.toLowerCase())
    email: string;
}
function InstanceType() {
    Iridium.Instance.apply(this, arguments);
}

require('util').inherits(InstanceType, Iridium.Instance);

InstanceType.transforms = {
    $document: {
        fromDB: document => {
            document.lastFetched = new Date();
        },
        toDB: document => {
            document.lastFetched && delete document.lastFetched;
            return document;
        }
    },
    email: {
        fromDB: value => value.toUpperCase(),
        toDB: value => value.toLowerCase()
    }
};

Transform Gotchas

It is important to note that property transforms are lazily evaluated on field access, rather than when the document is retrieved from the database. This is done for performance reasons, but has the side effect that complex objects which are the targets of property transforms must be re-assigned to the field if you wish to trigger the toDB transform function.

Let's take the following model definition for our example, here we have a GeoJSON representation of a location but we want our application to use the data in a {lat,lng} style object. In this case we can use a transform which translates from one form to another to accomplish our task.

import {inspect} from "util";

export class InstanceType extends Iridium.Instance<any, InstanceType> {
    // Converts a GeoJSON object into a simple {lat, lng} object and back.
    @Iridium.Transform(
        data => { lat: data.coordinates[1], lng: data.coordinates[0] },
        data => { type: "Point", coordinates: [data.lng, data.lat] }
    )
    position: {
        lat: number;
        lng: number;
    };
}

db.Model.findOne().then(instance => {
    console.log(inspect(instance.position)); // { lat: 1, lng: 2 }
    console.log(inspect(instance.document.position)); // { type: "Point", coordinates: [2, 1] }

    let pos = instance.pos;
    pos.lat = 3;
    console.log(inspect(pos)); // { lat: 3, lng: 2 }
    console.log(inspect(instance.position)); // { lat: 1, lng: 2 }
    console.log(inspect(instance.document.position)); // { type: "Point", coordinates: [2, 1] }

    instance.position = pos
    console.log(inspect(instance.position)); // { lat: 3, lng: 2 }
    console.log(inspect(instance.document.position)); // { type: "Point", coordinates: [2, 3] }
});

Useful Transform Tricks

There are a couple of clever tricks you can do using transforms to enable additional functionality within Iridium. An example would be cleaning your documents of properties not defined within your schemas whenever they are saved to the database.

Strict Schemas

Let's say you want to only insert values which appear in your schemas - an example would be if you accept documents from a REST API and don't wish to manually cherry-pick the properties you are going to insert. It could also simply be a way of lazily cleaning up old properties from documents as your schema evolves over time, helping to avoid complications if someone forgets to clean up the database after making changes to the schema. This can be easily achieved using the $document transform.

@Iridium.Transform(document => document, (document, property, model) => {
    Object.keys(document).forEach(key => {
        if(!model.schema.hasOwnProperty(key)) delete document[key];
    });
    
    return document;
})
export class InstanceType extends Iridium.Instance<any, InstanceType> {
    
}
function InstanceType() {
    Iridium.Instance.apply(this, arguments);
}

require('util').inherits(InstanceType, Iridium.Instance);

InstanceType.transforms = {
    $document: {
        fromDB: document => document,
        toDB: (document, property, model) => {
            Object.keys(document).forEach(key => {
                if(!model.schema.hasOwnProperty(key)) delete document[key];
            });
            
            return document;
        }
    }
};

More Repositories

1

tailscale-udm

Run Tailscale on your Unifi Dream Machine
Shell
362
star
2

bash-cli

A command line framework built using nothing but Bash and compatible with anything
Shell
96
star
3

multicast

A multicast channel library for Go with a simple API and familiar semantics
Go
25
star
4

vue-template

A Vue.js web application template designed to be as lightweight as possible while offering an extensive set of features
TypeScript
24
star
5

Skmatc

Skmatc (schematic) is the powerful JavaScript object validation framework powering Iridium
JavaScript
23
star
6

sentry-go

A beautifully simple Sentry client which makes reporting errors a joy! Full support for breadcrumbs and stacktraces with an elegant and easy to remember API.
Go
20
star
7

minback-postgres

A container which provides the ability to backup a PostgreSQL database to Minio on demand
Dockerfile
19
star
8

git-tool

Stop worrying about where your code is saved and start being more productive with this cross-platform CLI (with auto-complete and GitHub integration).
Rust
15
star
9

Lithium

Lithium is a licensing protocol which provides the ability to provide time locked, floating and leased licensing both over the internet and through an intranet server.
C#
13
star
10

connor

Connor is a condition evaluator for Go inspired by MongoDB's query language
Go
12
star
11

minback-mongo

A container which provides the ability to backup a MongoDB database to Minio on demand
Shell
10
star
12

markout

Native Markdown support in Outlook
TypeScript
9
star
13

minback-mysql

A MySQL backup container which ships the backup to S3
Dockerfile
9
star
14

honeypot

A service designed to track malicious SSH login attempts
Go
6
star
15

RackMan

A Node.js cluster manager for high performance horizontally scaled web applications, powering all our servers
JavaScript
5
star
16

roadmap

Manage your project and team road maps in YAML
Go
5
star
17

inki

An agent which allows you to register new SSH keys on a host through a combination of PGP signing, an HTTP API and host-side checks.
Go
5
star
18

human-errors-rs

Errors for Rust which make your users' lives easier
Rust
4
star
19

chieftan-server

The Chieftan server implementation
Go
4
star
20

shig

Cryptographically sign and verify files using SSH keys
Go
4
star
21

grey

Lightweight OpenTelemetry native health probing system
Rust
3
star
22

hue

Control your Phillips Hue lights using the command line.
Go
3
star
23

blmain

Migrate your GitHub repositories to use a "main" branch instead of "master" (supports Azure DevOps and Travis CI)
C#
3
star
24

Isotope

Isotope is an integrated hardware USB HID emulation solution for devices with a UART - specifically a Raspberry Pi
C
3
star
25

buckle

Lightweight bootstrapping of servers, with amazing observability and practically no fluff.
Rust
3
star
26

stablehand

A tool to help keep your Rancher server clean in production environments
Go
3
star
27

GuardHouse

A flexible JSON based access control system for Node.js
JavaScript
2
star
28

vault-azfn

Run Hashicorp Vault on Azure Functions (with scale-to-zero)
HCL
2
star
29

Kong

A notification distributor designed to simplify passing of notifications between different services.
JavaScript
2
star
30

heimdall

Heimdall is a distributed availability check platform built with an emphasis on performance, flexibility and security.
Go
2
star
31

gatekeeper

Gate Keeper is a permissions management tool for Go applications
Go
2
star
32

chieftan-frontend

A web frontend for the Chieftan task automation tool
TypeScript
2
star
33

Executor

A Go task runner designed to run scripts across a wide range of platforms
Go
2
star
34

ynab-githubactions

Automatically update the value of your stock portfolio in You Need a Budget
TypeScript
2
star
35

mocha-gitlablist

Provides a custom Mocha reporter compatible with GitLab-CI
JavaScript
2
star
36

SiteForge

SiteForge is a powerful static website generator built on Node.js
JavaScript
2
star
37

rex-rs

Tool for keeping track of ideas and providing random ones on demand
Rust
2
star
38

rex-ui

A user interface for Rex, providing a random idea for something to do on demand.
TypeScript
1
star
39

burnout-rs

An anonymous burnout tracking tool for teams
Rust
1
star
40

Canal

A powerful route design helper for Express
JavaScript
1
star
41

ansible-docker

A docker image for execution of Ansible playbooks
Shell
1
star
42

update-go

Go
1
star
43

timespan-js

A C#-esque TimeSpan object for JavaScript
JavaScript
1
star
44

sshsign-go

Cryptographically sign data using your SSH keys in Go
Go
1
star
45

scheduler

A small scheduling library for Go which makes running tasks at different times easy
Go
1
star
46

Suspenders

Realtime Asynchronous WebSocket RPC Using SockJS
JavaScript
1
star
47

minback-cleanup

A backup rotation tool for Minio backups created by the various minback containers
Go
1
star
48

rex-csharp

Tool for keeping track of ideas and providing random ones on demand
C#
1
star
49

chat

A simple chat protocol to practice writing network servers and clients
Go
1
star
50

Express-DSN

Custom notification framework for Express
JavaScript
1
star
51

node-conversation

Allows fluent testing of advanced TCP servers for protocol compliance
JavaScript
1
star
52

rates

Simple rate limiting primitives for Go
Go
1
star
53

Concoction

A flexible preprocessing framework for Node.js
JavaScript
1
star
54

windows-essentials

Links to various pieces of software installed as part of a base Windows development machine
PowerShell
1
star
55

on-call

Generate fair on-call schedules with a simple, declarative, specification
Rust
1
star
56

github-automerge

Automatically merge dependabot pull requests across your entire organization.
TypeScript
1
star
57

girder

Girder is an oppinionated Go web API toolkit
Go
1
star
58

Optimum

A full stack HTML+JS+CSS minification an optimization framework for single page web applications
JavaScript
1
star