Skip to content
This repository has been archived by the owner on Dec 5, 2022. It is now read-only.

Change rethinkdbdash to rethinkdb-ts, minor reworks #136

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
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.