# rethink

> rethinkdb odm

Latest version **1.4.0** (published 2016-01-06) · ISC license · 0 weekly downloads

## Install

```sh
npm install rethink
pnpm add rethink
yarn add rethink
bun add rethink
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.4.0 |
| Published | 2016-01-06 |
| First published | 2016-01-06 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | none |
| Module format | CommonJS |
| Node | >=4.0.0 <5.0.0 |
| Dependencies | 7 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Vladimir Popov |
| Maintainers | rusintez |
| Keywords | rethinkdb, odm |

## Links

- npm: https://www.npmjs.com/package/rethink
- Repository: git@github.com:seedalpha/rethink
- Homepage: https://github.com/seedalpha/rethink
- Issues: https://github.com/seedalpha/rethink/issues
- npm.io page: https://npm.io/package/rethink

## Dependencies (7)

- [debug](https://npm.io/package/debug.md) ^2.2.0
- [node-veee](https://npm.io/package/node-veee.md) ^1.0.1
- [rethinkdb](https://npm.io/package/rethinkdb.md) ^2.1.1
- [inflection](https://npm.io/package/inflection.md) ^1.7.1
- [seed-queue](https://npm.io/package/seed-queue.md) ^2.0.0
- [seed-extend](https://npm.io/package/seed-extend.md) ^1.0.0
- [reconnect-rethinkdb](https://npm.io/package/reconnect-rethinkdb.md) 0.0.2

## Recent versions

- 1.4.0 (latest) — 2016-01-06

## README

# rethink

rethinkdb ODM. It's conventions and api highly resemble but are not limited to `mongoose` ODM for mongodb.

### Architecture

##### Rethink

Main module `Rethink` is responsible for initalisation of `schema`, `models` and database connection. To simplify the API rethink will queue all db operations until connection is established.

```javascript
var r = new Rethink();

var schema = r.schema({ 
  name: r.types.string()
});

var User = r.model('User', schema);

User.create({ name: 'Ben' }, cb);

r.connect('localhost:28015');
```

##### Schema

Module `Schema` has a few thin, but capacitive api layers: 

###### validation
validation is powered by `node-veee`, check https://github.com/node-veee/veee for details

###### before hooks

`validate` will be fired before object is validated. It will be invoked asynchronously:

```javascript
schema.before('validate', function(object, done) {});
```

`create` will be fired after validation. It will be invoked asynchronously:

```javascript
schema.before('create', function(object, done) {});
```

`update` will be fired after validation. It will be invoked asynchronously:

```javascript
schema.before('update', function(object, done) {});
```

`save` will be fired after `create` or `update`, but before object is saved to db. It will be invoked asynchronously:

```javascript
schema.before('save', function(object, done) {});
```

`remove` hook is fired before object removal. It will be invoked asynchronously:

```javascript
schema.before('remove', function(id, done) {});
```

`query` hook is fired before results are retrieved from database. It will be invoked asynchronously:

```javascript
schema.before('query', function(params, done) {
  // params.query
  // params.options
  // params.schema
});
```

###### after hooks

`validate` will be fired after object is validated. It will be invoked asynchronously:

```javascript
schema.after('validate', function(object, done) {});
```

`save` will be fired before `create` or `update`, but after object is saved to db. It will be invoked synchronously:

```javascript
schema.after('save', function(object) {});
```

`create` will be invoked synchronously:

```javascript
schema.after('create', function(object) {});
```

`update` will be invoked synchronously:

```javascript
schema.after('update', function(object) {});
```

`remove` hook is fired after object removal and will be invoked synchronously:

```javascript
schema.after('remove', function(id) {});
```

`query` hook is fired after results have been retrieved from database and it will be invoked asynchronously:

```javascript
schema.after('query', function(params, done) {
  // params.query
  // params.options
  // params.results
  // params.schema
});
```

###### hook error handling

All asynchronous callbacks, eg. `done`, will accept an optional error argument that will stop execution chain.

```javascript
schema.before('save', function(article, done) {
  if (article.user !== req.session.user) {
    return done(new Error('Unauthorized'));
  }
  done();
});
```

###### plugins

schema plugins use node-style `.use` pattern

```javascript
schema
  .use(plugin)
  .use(anotherPlugin);
```

###### model prototype extension

its possible to extend default model api with static methods from schema.

```javascript
schema.statics.greet = function() {
  console.log('hello');
}

var Person = r.model('Person', schema);

Person.hello(); // will log 'hello'
```

By design schema should not be altered after it's built, eg.:

```javascript
var Post = r.model('Post', schema);

schema.statics.publish = function() {...}

Post.publish(); // won't work
```

###### example

```javascript
var schema = r.schema({
  username: { type: r.types.string().alphanum(), index: true },
  password: r.types.string().min(8).max(32)
});

schema.before('save', function(record, done) {
  record.user = req.session.user;
  done();
});

schema.after('save', function(record) {
  log('Record saved %j', record);
});

schema.statics.findById = function(id, cb) {
  this.find({ id: id }, cb);
}

schema.use(timestamps);
```

##### Table

Module `Table` is used (internally) to automatically create a backing table, manage table indexes and wait (lock) until rethinkdb builds them. Table name is lower-cased pluralised version of a model name, eg. model: 'Person', table: 'people'.

##### Model

`Model` provides a node callback-style api to manipulate records in a single rethinkdb table. Model api could be extended, using `schema.statics` object.

```javascript
var Person = r.model('Person', schema);

Person.create(...);
Person.update(...);
Person.remove(...);
Person.find(...);
Person.findOne(...);
```

### API

#### Rethink

##### constructor([{Object} options]):Rethink

Initialize a new instance of `Rethink` (with options hash)

```javascript
var r = new Rethink();
```

##### schema({Object} definition[, {Object} options]):Schema

Create a new schema, using definition and optionally pass options to it

```javascript
var schema = r.schema({
  name: { type: r.types.string().required(), index: true, default: 'anonymous' }
});
```

##### model({String} name, {Schema} schema):Model

Create a new model using singular case-sensitive name and a schema

```javascript
var User = r.model('User', schema);
```

##### connect({String} dbUrl[, {Object} options])

Connect to rethinkdb instance using database url (e.g. 'host:port') and options

```javascript
r.connect('localhost:28015');
```

##### before({String} hook, {Function} fn({Rethink} rethink)):Rethink

Create a before hook for one of the actions. Only used to attach to `buildSchema` hook.

```javascript
rethink.before('buildSchema', function(schema) {
  schema.use(somePlugin);
});
```

##### use({Function} plugin({Rethink} rethink[, {Object} options])[, {Object} options])):Rethink

Attach a plugin to rethink. Mainly used for attaching a plugin to an internal `before:buildSchema` hook.

```javascript
r.use(plugin, options);

function plugin(rethink, options) {
  
  rethink.Schema.prototype.hello = function() {
    console.log('World');
  }
  
  // will fire everytime before schema build
  // equivalent of attaching a plugin to every schema
  rethink.before('buildSchema', function(schema) {
    schema.use(timestamps, options);
  });
}

function timestamps(schema, options) {
  schema.before('create', function(record, done) {
    var now = (new Date).toISOString();
    record.createdAt = now;
    record.updatedAt = now;
    done();
  });
  
  schema.before('update', function(record, done) {
    record.updatedAt = (new Date).toISOString();
    done();
  });
};
```
##### types

Access to `node-veee` validation types

```javascript
rethink.types.string().required();
```

##### Schema

Access to schema class

```javascript
rethink.Schema.prototype.hello = function() {
  return 'world';
}
```

##### Static Properties

`Types` — access to `node-veee` validation types

```javascript
Rethink.Types.string().required();
```

`Schema` — access to schema class

```javacript
var schema = new Rethink.Schema({
  name: Rethink.Types.string().min(3)
});
```

`Plugins` — access to built-in plugins

```javascript
var rethink = new Rethink();

rethink
  .use(Rethink.Plugins.Timestamps)
  .use(Rethink.Plugins.Populate);  
```

#### Schema

##### constructor({Object} params):Schema

`{Object} type`: `node-veee` validator for a given type

```javascript
var schema = new Rethink.Schema({
  startsAt: { type: Rethink.Types.date() } 
});

// shorthand
var schema = new Rethink.Schema({
  startsAt: Rethink.Types.date()
});
```

`{Boolean|Object} index`: used by `Table` to make sure rethink indexes a given field

```javascript
var postSchema = new Rethink.Schema({
  authorId: { index: true }
});

var markerSchema = new Rethink.Schema({
  position: { index: { geo: true } }
});
```

`{*} default`: set default value to field if fnot specified. Default also has support for sync functions with length 1 and async with length 2.

```javascript

// primitives
var postSchema = new Rethink.Schema({
  title: { default: 'Untitled Post' }
});

// sync
var postSchema = new Rethink.Schema({
  createdAt: { 
    default: function(object) {
      return (new Date).toISOString();
    }
  }
});

// async
var ticker = new Rethink.Schema({
  price: {
    default: function(object, cb) {
      request(YAHOO_FINANCE_TICKER_PRICE + object.name, function(err, body, response) {
        if (err) return cb(err);
        cb(null, body.price);
      });
    }
  }
});
```

##### before({String} action, {Function} hook({Object|Array} data, {Function} done({Error} err))):Schema

Create a before hook for one of the actions

```javascript
schema.before('save', function(record, done) {
  record.updatedAt = Date.now();
  done();
});
```

##### after({String} action, {Function} hook({Object|Array} data, {Function} done({Error} err))):Schema

Create an after hook for one of the actions

```javascript
schema.after('query', function(records, done) {
  records.forEach(function(record) {
    record.updatedAt = new Date(record.updatedAt); // deserialize date
  });
  done();
});
```

##### statics

Define a static model method

```javascript

schema.static.findByUsername = function(username, cb) {
  this.findOne({ username: username }, cb);
}

var User = r.model('User', schema);

User.findByUsername('peter', function(err, user) {});
```

##### use({Function} plugin({Schema} schema[, {Object} options])[, {Object} options]):Schema

Attach a plugin to schema

```javascript
var timestamps = function(schema) {
  schema.before('create', function(record, done) {
    record.createdAt = Date.now();
    done();
  });
  
  schema.before('update', function(record, done) {
    record.updatedAt = Date.now();
    done();
  });
}

schema.use(timestamps);
```

##### Instance Properties

`types` — Access to `node-veee` validation types

```javascript
schema.types.string().required();
```

#### Model

##### create({Object} object, {Function} callback(err, object))

Create an object

```javascript
User.create({ name: 'Peter' }, function(err, user) {});
```

##### update({Object} object, {Function} callback(err, object))

Update an object

```javascript
User.update({ id: '123434-1233-1231-123124', name: 'Peter' }, function(err, user) {});
```

##### remove({String} id, {Function} callback(err, id))

Remove an object using an id

```javascript
User.remove('123434-1233-1231-123124', function(err, userId) {});
```

##### find({Object} query[, {Object} options], {Function} callback(err, records))

Find model records using a query and options

```javascript
User.find({ name: 'Peter' }, { limit: 2, skip: 0, order: 'name' }, function(err, users) {});
```

##### findOne({Object} query[, {Object} options], {Function} callback(err, record))

Find one model record using a query and options

```javascript
User.find({ name: 'Peter' }, function(err, user) {});
```

### Plugins

#### Architecture

Rethink is made to be pluggable, in fact, most of rethink functionality could be (and is) implemented as plugins.
Plugin is just a function that takes `rethink` and `options` as arguments and is able to plug into `Schema`, `Model` and `Rethink` prototypes and hooks.

```javascript
// plugins/safe-delete.js

function updateSchema(schema, options) {
  schema._fields.deletedAt = { 
    type: schema.types.string().isodate().optional(),
    default: ''
  }
  
  schema.before('query', function(params, done) {
    if (!params.options.includeDeleted) {
      extend(params.query, { deletedAt: '' }); 
    }
    done();
  });
  
  schema.statics.remove = function(id, cb) {
    this.update({ 
      id: id, 
      deletedAt: (new Date).toISOString()
    }, function(err, record) {
      if (err) return cb(err);
      if (!record) return cb(new Error('record not found'));
      cb(null, 1);
    });
  }
}

// expose rethink plugin
exports = module.exports = function(rethink, options) {
  rethink.before('buildSchema', function(schema) {
    updateSchema(schema, options);
  });
}

// expose schema plugin
exports.safeDelete = function(schema, options) {
  updateSchema(schema, options);
}

// app.js

// use safe delete plugin on all schema
var safeDelete = require('./plugins/safe-delete');
rethink.use(safeDelete); 

// use safe delete plugin only on one schema 
var safeDelete = require('./plugins/safe-delete').safeDelete;
schema.use(safeDelete); 
```

#### Populate

Populate plugin introduces application-side deep table joins to rethink.

```javascript

// inject schema instance methods
rethink.use(Rethink.Plugins.Populate);

var User = rethink.model('User', rethink.schema({ 
  name: rethink.types.string(),
}).hasOne('company'));

var Company = rethink.model('Company', rethink.schema({
  name: rethink.types.string()
}).hasMany('users', { refField: 'company' }));

User.find({}, { populate: 'company' }, function(err, users) {
  // each user object will contain corresponded company
});

User.find({}, { populate: { field: 'company', populate: 'users' }, function(err, users) {
  // each user object will contain corresponded company
  // corresponded company will contain all users of that company
});
```

##### Advanced
```javascript
var schema = rethink.schema({
  user: { 
    type: schema.types.string(),
    // population options
    field: 'user',  // source of join
    ref: 'User',    // foregin model name
    refField: 'id', // foreign field to match source of join
    destination: 'user' // destination of model query
    single: true        // will treat relationship as a single object
    index: true         // all relational fields must be indexed
  },
  comments: {
    // population options
    field: 'id',        // source of join
    ref:   'Comment',   // foreign model name
    refField: 'reference',  // foreign field to match source of joins
    destination: 'comments' // destination of model query
    single: false           // will treat realtionship as array
    virtual: true           // exclude from object validation
  }
});

// shorthands, do same above
// !!!note: refField is required to be specified for `hasMany` relationships
schema.hasOne('user'); 
schema.hasMany('comments', { refField: 'reference' });

var Reference = rethink.model('Reference', schema);

Reference.find({}, { populate: ['user', 'comments'] }, function(err, references) {
  // each reference object will contain user object
  // each reference object will contain comments array
  // eg. references:
  // [{
  //   id: "123",
  //   user: {
  //     id: "345",
  //     name: "John Doe"
  //   },
  //   comments: [{
  //     id: "238",
  //     user: "555",
  //     message: "hello"
  //   }]
  // }]
});
```
##### Query time population

to make use of advanced population you could define population options in query

```javascript
// get all references of this user
Reference.find({ user: user.id }, { 
  populate: {
    field: 'id',
    ref: 'Bookmark',
    refField: 'reference',
    destination: 'bookmark',
    query: { user: user.id } // only get reference bookmark of this user,
    options: {} // in case of limit, offset, order, etc.
    single: true
  }
}, function(err, references) {
  // references:
  // [{
  //   id: "123",
  //   user: "555",
  //   bookmark: {
  //     id: "932",
  //     reference: "123",
  //     user: "555"
  //   }
  // }]
});
```

#### Timestamps

Timestamps plugin adds `createdAt` and `updatedAt` ISO 8601 dates to schema and helps to manage them accordingly

```javascript

rethink.use(Rethink.Plugins.Timestamps);

var User = rethink.model(rethink.schema({
  name: rethink.schema.string()
}));

User.create({ name: 'Vlad' }, function(err, user) {
  // user:
  // {
  //   id: "123",
  //   name: "Vlad",
  //   createdAt: "2015-09-23T17:35:22.124Z",
  //   updatedAt: "2015-09-23T17:35:22.124Z"
  // }
});

...

User.update({ id: '123', name: 'Vladimir' }, function(err, user) {
  // user:
  // {
  //   id: "123",
  //   name: "Vladimir",
  //   createdAt: "2015-09-23T17:35:22.124Z",
  //   updatedAt: "2015-09-23T17:36:23.416Z"
  // }
});

```

### Installation

    $ npm install rethink --save

### Usage

```javascript

// require
var Rethink = require('rethink');

// initialize
var r = new Rethink();

// connect to database
r.connect(url, connectionOptions);

// define schema
var companySchema = r.schema({
  name:       r.types.string().required(),
  address:    r.types.object().keys({
    country:  r.types.string(),
    city:     r.types.string(),
    street:   r.types.string(),
    block:    r.types.string()
  }).optional()
}, schemaOptions);

var userSchema = r.schema({
  username:   { type: Rethink.Types.string(), index: true },
  password:   r.types.string().min(6).max(32).alphanum(),
  company:    { type: r.types.uuid(), ref: 'Company' },
  gender:     { type: r.types.string(), default: 'not-specified' }
}, schemaOptions);

// use hooks
userSchema.before('save', function(record, done) {
  // do stuff with record
  done();
});

userSchema.after('query', function(records, done) {
  // do stuff with records
  done();
});

// use plugins
userSchema.use(plugin, options);

// define static (helper) methods
userSchema.statics.byEmail = function(email, options, cb) {
  return this.find({ email: email }, options, cb);
}

// create model (table)
var User    = r.model('User', userSchema);
var Company = r.model('Company', companySchema);

// create records
Company.create({ name: 'seedalpha' }, function(err, company) {
  User.create({
    username: 'john',
    password: '123456',
    company: company.id
  }, function(err, user) {
    // ...
  });
});

// search records
User.find({ username: 'john' }, { limit: 1 }, function(err, user) {
  // ...
});
```

### Roadmap

- live query support
- make use of rethinkdb indexes
- rethink options
- spec tests
- 100% coverage
- opensource?
- move all rethinkdb calls out into an engine
- implement es-index as a plugin
- parallel index creation, table creation (speedup startup time)
- rethink eventmietter api
- model eventemitter api

### Development

    $ git clone git@github.com:seedalpha/rethink.git
    $ cd rethink
    $ npm install
    $ npm test
    $ npm run coverage

### Author

Vladimir Popov <vlad@seedalpha.net>

### License

©2015 SeedAlpha

---
_Source: https://npm.io/package/rethink · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
