Skip to content
This repository has been archived by the owner on Oct 23, 2024. It is now read-only.

freight-hub/tiller

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NPM Module Build Status

Tiller - MongoDB ODM for TypeScript

Tiller is an ODM for MongoDB, written in TypeScript with an ActiveRecord-inspired API.

Capabilities of Tiller include:

  • Modeling entire typed hierarchies through sub-documents or references
  • Ability to include serializers/deserializers to modify the structure of a JSON document in the database
  • Modern async/await API to evade callback hell
  • Add indexes right in the class definition
  • Extensible validation layer

Installation

$ npm install tiller --save
$ tsd install mongodb --save

Make sure that you compile to ES6 JavaScript.

Getting Started

    import 'source-map-support/register'
    import {DB, Document, collection, Collection, document, embed} from 'tiller';

    @document()
    export class Cannon {
        power:number

        shoot() {
            console.log('wumm')
        }
    }

    @collection('spaceShips')
    export class SpaceShip extends Collection {
        name:string

        @embed(Cannon)
        cannons:Array<Cannon>

        constructor(name?:string) {
            super()
            this.name = name;
        }

        fly() {
            console.log('flyiiiinng...');
        }
    }

    (async () => {
        await DB.connect('testdb')
        let ship = new SpaceShip('SpaceShip1')
        ship.cannons = [new Cannon()];

        // Save to the database
        await ship.save();

        // Load from the database
        ship = await SpaceShip.findOne<SpaceShip>({name: 'SpaceShip1'})
        ship.fly();                 // flyiiiinng
        ship.cannons[0].shoot();    // wumm

        await DB.disconnect(true)
    })().catch((e) => {
        console.error(e.stack)
    })

Manual

Creating Collections

Making a JS class being stored as MongoDB collection is as easy as adding the @collection() decorator and inheriting from Collection:

@collection()
export class MyModel extends Collection {

}

Optionall you can pass a parameter to the decorator that names the collection:

@collection('myCollectionName')

The API to interact with the database is heavily inspired by ActiveRecord:

let objs:Promise<Array<MyModel>> = MyModel.find<MyModel>({key: ...})
// or using await:
let objs:Array<MyModel> = await MyModel.find<MyModel>({key: ...})

let objs:MyModel = await MyModel.findOne<MyModel>({key: ...})

let objs:MyModel = await MyModel.get<MyModel>(idValue)

myModel.save()

Adding indexes

Indexes can easily be added using the @index decorator. They are also supported on subdocuments:

@collection()
class MyModel extends Collection {

    @index({unique: true})
    myId:number

    @index()
    name:string
}

Indexes spanning multiple fields are currently not supported.

Collection instance states & lifecycle hooks

Using the functions isNew() and isSaved() you can find out whether an instance of Collection has been saved in the database already:

let obj = new MyModel()
assert(!obj.isSaved() && obj.isNew())
await obj.save()
assert(obj.isSaved() && !obj.isNew())

To perform pre- or post-save actions you can override the Collection methods beforeSave() and afterSave().

Using non-ObjectId _ids

You can use non-ObjectId types for _id by redefining _id:

@collection()
export class MyModel extends Collection {
    _id:string
}

Saving Objects

#save()

Regardless of whether an instance is a new completely new instance, was loaded from a database with (find() or similar) or an upsert should be performed - the #save() method will do:

// Saves a new instance
var obj1 = new MyModel()
obj1.save()

// Updates an instance
var obj1 = MyModel.get<MyModel>(...)
obj1.save()

// Upsert an instance
var obj1 = new MyModel()
obj1._id =
obj1.save(undefined, true)

Saving Referenced Objects

TODO

Finding Objects

Collection#get()

Using Collection#get() you can fetch a single object using its _id value. You have to use ObjectID if you use standard _id values:

obj = await MyModel.get<MyModel>(new ObjectID("56f90cf44c57a9c97f1ac295"))

// or using numeric _id's
obj = await ModelWithStandardId.get<ModelWithStandardId>(123)

Collection#find()

The static method #find(selector:any, limit?:number, sort?:any) will return an array of objects that match selector. Optionally you can specify a limit (or set this parameter to null) and a sort order.

// Finding all objects with key == 'value'
objs = await MyModel.find<MyModel>({key: 'value'})

// Limiting the returned objects to 3
objs = await MyModel.find<MyModel>({key: 'value'}, 3)

// Sorting the returned objects to id, not limiting the returned objects
objs = await MyModel.find<MyModel>({key: 'value'}, null, {id: 1})

Of course #find() doesn't just return Objects. It rebuilds entire object hierarchies, and sets the correct object prototypes.

Collection#findOne()

Collection#findOne(selector) is similar to Collection#find(), but it returns only the first found document.

Deleting Objects

Collection#destroy()

An object can be destroyed using the #destroy() method:

await obj.destroy();

Validating Objects

Tiller also contains a basic, but extensible, validation layer. Use the @validate decorator to add a schema to your model:

@collection()
export class House extends Collection {

   @validate({required: true})
   name:string

   @validate({type: ['red', 'white']})
   color:string
}

house = new House();
house.isValid() // false

house.name = 'My House';
house.isValid() // true

house.color = 'brown';
house.isValid() // false
house.validate() // {color: ...}

house.color = 'red';
house.isValid() // true
house.validate() // {}

Refer to js-schema for details about supported types.

Working with Plain old JavaScript Objects

To recreate a typed object hierarchy from JSON/JavaScript Objects Collection.create() can be used. It will also recreate embedded and referenced documents.

let myModel = MyCollection.create<MyCollection>({foo: 'bar', child: {a: 'b'}})

Roadmap

  • DIRTY Tracking to improve save speed
  • Remove problems with two documents/collections named equally
  • Implement batch operations
  • Add Continous Integration build
  • Complete Readme: References, Embedded Documents
  • Keep upward references in @document
  • Implement $lookup aggregation for fast loading of references
  • Implement support for aggregation queries
  • Support Model-level hooks, to support external service-based model validation, i.e. MyModel.addHook('afterSave', ...)
  • Add chai plugins: expect(myModel).to.be.valid/invalid
  • Use mongodb connection string to connect
  • Subclassing
  • Support aggregation
  • Audits (async, bg)
  • Inverse Reference

Bugs

  • Arrays of Referenced Objects are supported, but not arrays of arrays of referenced objects

Issues

  • "Required" validation of references, when we don't deep-save