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

tiller

v1.0.1

Published

MongoDB ODM for TypeScript

Downloads

118

Readme

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