Skip to content
This repository was archived by the owner on Dec 5, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ node_modules
.lock-wscript

dist/
.idea
.nyc_output
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# feathers-rethinkdb

> __Unmaintained:__ This project is no longer maintained
<!-- > __Unmaintained:__ This project is no longer maintained

[![Greenkeeper badge](https://badges.greenkeeper.io/feathersjs-ecosystem/feathers-rethinkdb.svg)](https://greenkeeper.io/)

[![Build Status](https://travis-ci.org/feathersjs-ecosystem/feathers-rethinkdb.png?branch=master)](https://travis-ci.org/feathersjs-ecosystem/feathers-rethinkdb)
[![Dependency Status](https://img.shields.io/david/feathersjs-ecosystem/feathers-rethinkdb.svg?style=flat-square)](https://david-dm.org/feathersjs-ecosystem/feathers-rethinkdb)
[![Download Status](https://img.shields.io/npm/dm/feathers-rethinkdb.svg?style=flat-square)](https://www.npmjs.com/package/feathers-rethinkdb)
-->

[feathers-rethinkdb](https://github.com/feathersjs-ecosystem/feathers-rethinkdb) is a database adapter for [RethinkDB](https://rethinkdb.com), a real-time database.

```bash
$ npm install --save rethinkdbdash feathers-rethinkdb
$ npm install --save rethinkdb-ts feathers-rethinkdb
```

> __Important:__ `feathers-rethinkdb` implements the [Feathers Common database adapter API](https://docs.feathersjs.com/api/databases/common.html) and [querying syntax](https://docs.feathersjs.com/api/databases/querying.html).
Expand All @@ -22,17 +23,17 @@ $ npm install --save rethinkdbdash feathers-rethinkdb

### `service(options)`

Returns a new service instance initialized with the given options. For more information on initializing the driver see the [RethinkDBdash documentation](https://github.com/neumino/rethinkdbdash).
Returns a new service instance initialized with the given options. For more information on initializing the driver see the [rethinkdb-ts documentation](https://github.com/rethinkdb/rethinkdb-ts).

```js
const r = require('rethinkdbdash')({
const r = require('rethinkdb-ts')({
db: 'feathers'
});
const service = require('feathers-rethinkdb');

app.use('/messages', service({
Model: r,
db: 'someotherdb', //must be on the same connection as rethinkdbdash
db: 'someotherdb', //only needed if other db, must be on the same connection as rethinkdbdash
name: 'messages',
// Enable pagination
paginate: {
Expand Down Expand Up @@ -292,6 +293,6 @@ Since the service already sends real-time events for all changes the recommended

## License

Copyright (c) 2017
Copyright (c) 2017-2020

Licensed under the [MIT license](LICENSE).
133 changes: 88 additions & 45 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,80 @@
const Proto = require('uberproto');
const { _, select, hooks, filterQuery } = require('@feathersjs/commons');
const { _, hooks } = require('@feathersjs/commons');
const { AdapterService, select } = require('@feathersjs/adapter-commons');
const errors = require('@feathersjs/errors');

const { createFilter } = require('./parse');
const { createFilter, filterObject } = require('./parse');

const { processHooks, getHooks, createHookObject } = hooks;
const BASE_EVENTS = ['created', 'updated', 'patched', 'removed'];

// Create the service.
class Service {
class Service extends AdapterService {
constructor (options) {
if (!options) {
throw new Error('RethinkDB options have to be provided.');
}

if (options.Model) {
options.r = options.Model;
} else {
throw new Error('You must provide the RethinkDB object on options.Model');
}

if (!options.r._poolMaster._options.db) {
throw new Error('You must provide an instance of r that is preconfigured with a db. You can override the db for the current query by specifying db: in service options');
}

// if no options.db on service use default from pool master
if (!options.db) {
options.db = options.r._poolMaster._options.db;
if (options.r.pool && options.r.pool.connParam.db) {
options.db = options.r.pool.connParam.db;
}
else if (!options.r.pool && options.r.singleconnection.db) {
options.db = options.r.singleconnection.db;
} else {
throw new Error('You must provide db on options.db or use connectPool');
}
}

if (!options.name) {
throw new Error('You must provide a table name on options.name');
throw new Error('You have to provide a table name on options.name');
}

if (!options.id) {
options.id = 'id';
}

if (!options.whitelist) {
options.whitelist = [
'$contains',
'$search',
'$and',
'$eq'
];
}

super(options);

this.type = 'rethinkdb';
this.id = options.id || 'id';
this.table = options.r.db(options.db).table(options.name);
this.options = options;
this.watch = options.watch !== undefined ? options.watch : true;
this.paginate = options.paginate || {};
this.events = this.watch ? BASE_EVENTS.concat(options.events) : options.events || [];
}

extend (obj) {
return Proto.extend(obj, this);
get Model () {
return this.options.Model;
}

_getOrFind (id, params) {
if (id === null) {
return this._find(params);
}

return this._get(id, params);
}

init (opts = {}) {
let r = this.options.r;
let t = this.options.name;
let db = this.options.db;
const r = this.options.r;
const t = this.options.name;
const db = this.options.db;

return r.dbList().contains(db) // create db if not exists
.do(dbExists => r.branch(dbExists, {created: 0}, r.dbCreate(db)))
.do(dbExists => r.branch(dbExists, { created: 0 }, r.dbCreate(db)))
.run().then(() => {
return r.db(db).tableList().contains(t) // create table if not exists
.do(tableExists => r.branch(
tableExists, {created: 0},
tableExists, { created: 0 },
r.db(db).tableCreate(t, opts))
).run();
});
Expand All @@ -67,9 +85,9 @@ class Service {
}

createQuery (originalQuery) {
const { filters, query } = filterQuery(originalQuery || {});
const { filters, query } = this.filterQuery(originalQuery || {});

let r = this.options.r;
const r = this.options.r;
let rq = this.table.filter(this.createFilter(query));

// Handle $select
Expand All @@ -94,9 +112,9 @@ class Service {
_find (params = {}) {
const paginate = typeof params.paginate !== 'undefined' ? params.paginate : this.paginate;
// Prepare the special query params.
const { filters } = filterQuery(params.query || {}, paginate);
const { filters } = this.filterQuery(params || {}, paginate);

let q = params.rethinkdb || this.createQuery(params.query);
let q = params.rethinkdb || this.createQuery(params);
let countQuery;

// For pagination, count has to run as a separate query, but without limit.
Expand All @@ -108,18 +126,30 @@ class Service {
if (filters.$skip) {
q = q.skip(filters.$skip);
}

let limit;
// Handle $limit AFTER the count query and $skip.
if (typeof filters.$limit !== 'undefined') {
q = q.limit(filters.$limit);
limit = filters.$limit;
q = q.limit(limit);
} else if (paginate && paginate.default) {
limit = paginate.default;
q = q.limit(limit);
}

if (limit && paginate && paginate.max && limit > paginate.max) {
limit = paginate.max;
}

q = q.run();

// Execute the query
return Promise.all([q, countQuery]).then(([data, total]) => {
if (paginate.default) {
if (paginate && paginate.default) {
return {
total,
data,
limit: filters.$limit,
limit: limit,
skip: filters.$skip || 0
};
}
Expand Down Expand Up @@ -161,7 +191,11 @@ class Service {

create (data, params = {}) {
const idField = this.id;
return this.table.insert(data, params.rethinkdb).run().then(res => {

let options = Object.assign({}, params);
options = filterObject(options, ['returnChanges', 'durability', 'conflict']);

return this.table.insert(data, options).run().then(res => {
if (data[idField]) {
if (res.errors) {
return Promise.reject(new errors.Conflict('Duplicate primary key', res.errors));
Expand Down Expand Up @@ -189,6 +223,8 @@ class Service {

patch (id, data, params = {}) {
let query;
let options = Object.assign({ returnChanges: true }, params);
options = filterObject(options, ['returnChanges', 'durability', 'nonAtomic']);

if (id !== null && id !== undefined) {
query = this._get(id);
Expand All @@ -201,7 +237,6 @@ class Service {
// Find the original record(s), first, then patch them.
return query.then(getData => {
let query;
let options = Object.assign({ returnChanges: true }, params.rethinkdb);

if (Array.isArray(getData)) {
query = this.table.getAll(...getData.map(item => item[this.id]));
Expand All @@ -210,14 +245,15 @@ class Service {
}

return query.update(data, options).run().then(response => {
let changes = response.changes.map(change => change.new_val);
const changes = response.changes.map(change => change.new_val);
return changes.length === 1 ? changes[0] : changes;
});
}).then(select(params, this.id));
}

update (id, data, params = {}) {
let options = Object.assign({ returnChanges: true }, params.rethinkdb);
let options = Object.assign({ returnChanges: true }, params);
options = filterObject(options, ['returnChanges', 'durability', 'nonAtomic']);

if (Array.isArray(data) || id === null) {
return Promise.reject(new errors.BadRequest('Not replacing multiple records. Did you mean `patch`?'));
Expand All @@ -234,13 +270,14 @@ class Service {

remove (id, params = {}) {
let query;
let options = Object.assign({ returnChanges: true }, params.rethinkdb);
let options = Object.assign({ returnChanges: true }, params);
options = filterObject(options, ['returnChanges', 'durability', 'nonAtomic']);

// You have to pass id=null to remove all records.
if (id !== null && id !== undefined) {
query = this.table.get(id);
} else if (id === null) {
query = this.createQuery(params.query);
query = this.createQuery(params);
} else {
return Promise.reject(new Error('You must pass either an id or params to remove.'));
}
Expand All @@ -249,7 +286,7 @@ class Service {
.run()
.then(res => {
if (res.changes && res.changes.length) {
let changes = res.changes.map(change => change.old_val);
const changes = res.changes.map(change => change.old_val);
return changes.length === 1 ? changes[0] : changes;
} else {
return [];
Expand All @@ -272,7 +309,7 @@ class Service {
// so we have to do it manually
runHooks = (method, data) => {
const service = this;
const args = [ { query: {} } ];
const args = [{ query: {} }];
const hookData = {
app,
service,
Expand All @@ -295,6 +332,7 @@ class Service {
}

const hookObject = createHookObject(method, args, hookData);
hookObject.type = 'after'; //somehow this gets lost e. g. for protect('password') in createHookObject but is needed in processHooks isHookObject
const hookChain = getHooks(app, this, 'after', method);

return processHooks.call(this, hookChain, hookObject);
Expand All @@ -310,14 +348,16 @@ class Service {
// then emit the event
if (data.old_val === null) {
runHooks('create', data.new_val)
.then(hook => this.emit('created', hook.result));
.then(hook => {
this.emit('created', hook);
});
} else if (data.new_val === null) {
runHooks('remove', data.old_val)
.then(hook => this.emit('removed', hook.result));
.then(hook => this.emit('removed', hook));
} else {
runHooks('patch', data.new_val).then(hook => {
this.emit('updated', hook.result);
this.emit('patched', hook.result);
this.emit('updated', hook);
this.emit('patched', hook);
});
}
});
Expand All @@ -330,8 +370,11 @@ class Service {

setup (app) {
const rethinkInit = app.get('rethinkInit') || Promise.resolve();
const r = this.options.r;

rethinkInit.then(() => this.watchChangefeeds(app));
rethinkInit
.then(() => r.pool.waitForHealthy())
.then(() => this.watchChangefeeds(app));
}
}

Expand Down
15 changes: 11 additions & 4 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ exports.createFilter = function createFilter (query, r) {
r.expr(selector).contains(buildNestedQueryPredicate(field, doc)).not()
);
} else if (mappings[type]) {
const selectorArray = Array.isArray(selector) ? selector : [ selector ];
const selectorArray = Array.isArray(selector) ? selector : [selector];
const method = mappings[type];

matcher = matcher.and(buildNestedQueryPredicate(field, doc)[method](...selectorArray));
Expand All @@ -62,12 +62,19 @@ exports.createFilter = function createFilter (query, r) {
};

function buildNestedQueryPredicate (field, doc) {
var fields = field.split('.');
var searchFunction = doc(fields[0]);
let fields = field.split('.');
let searchFunction = doc(fields[0]);

for (var i = 1; i < fields.length; i++) {
for (let i = 1; i < fields.length; i++) {
searchFunction = searchFunction(fields[i]);
}

return searchFunction;
}

exports.filterObject = function(obj, keys) {
return Object.keys(obj)
.filter(k => keys.includes(k))
.map(k => Object.assign({}, {[k]: obj[k]}))
.reduce((res, o) => Object.assign(res, o), {});
};
2 changes: 0 additions & 2 deletions mocha.opts

This file was deleted.

Loading