Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Executing services methods using transaction, implementation of issue #91 #113

Merged
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,56 @@ todos

You can run this example by using `node server` and going to [localhost:8080/todos](http://localhost:8080/todos). You should see an empty array. That's because you don't have any Todos yet but you now have full CRUD for your new todos service!

# Transaction Support
A example of using the transaction hooks:
```javascript
// A common hooks file
import { hooks } from 'feathers-knex';

const { transaction } = hooks;

const logger = require('./hooks/logger');

module.exports = {
before: {
all: [transaction.start({ dbServiceName: 'knexClient' })],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},

after: {
all: [logger(), transaction.end()],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},

error: {
all: [transaction.rollback(), logger()],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
```

To use the transactions feature, you must ensure that the three hooks (start, commit and rollback) are being used.

At the start of any request, a new transaction will be started. All the changes made during the request to the services that are using the `feathers-knex` will use the transaction. At the end of the request, if sucessful, the changes will be commited. If an error occurs, the changes will be forfeit, all the `creates`, `patches`, `updates` and `deletes` are not going to be commited.

The object that contains `transaction` is stored in the `params.transaction` of each request. If you must extend the `feathers-knex` service, use the `params.transaction.trx` to make your database calls, or simply call the method `this.db(params)` inside a class that extends de `feathers-knex` service.


## License

Copyright (c) 2016
Expand Down
50 changes: 50 additions & 0 deletions src/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const debug = require('debug')('feathers-knex-transaction');

const start = (options) => {
const { dbServiceName } = options;
debug('started transaction system with %s service', dbServiceName);
return (hook) =>
new Promise(resolve =>
hook.app.get(dbServiceName).transaction(trx => {
const id = Date.now();
hook.params.transaction = {
trx,
id
};
debug('started a new transaction %s', id);
return resolve(hook);
})
);
};

const end = (options) => {
return (hook) => {
if (hook.params.transaction) {
const { trx, id } = hook.params.transaction;
return trx.commit()
.then(() => debug('finished transaction %s with success', id))
.then(hook);
}
return hook;
};
};

const rollback = (options) => {
return (hook) => {
if (hook.params.transaction) {
const { trx, id } = hook.params.transaction;
return trx.rollback()
.then(() => debug('rolling back transaction %s', id))
.then(hook);
}
return hook;
};
};

export default {
transaction: {
start,
end,
rollback
}
};
29 changes: 18 additions & 11 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import filter from 'feathers-query-filters';
import isPlainObject from 'is-plain-object';
import { errors } from 'feathers-errors';
import errorHandler from './error-handler';
import hooks from './hooks';

const debug = require('debug')('feathers-knex');

Expand Down Expand Up @@ -45,7 +46,12 @@ class Service {

// NOTE (EK): We need this method so that we return a new query
// instance each time, otherwise it will reuse the same query.
db () {
db (params = {}) {
if (params.transaction) {
const { trx, id } = params.transaction;
debug('ran %s with transaction %s', this.table, id);
return trx(this.table);
}
return this.knex(this.table);
}

Expand Down Expand Up @@ -99,13 +105,13 @@ class Service {
});
}

createQuery (paramsQuery = {}) {
const { filters, query } = filter(paramsQuery);
let q = this.db().select([`${this.table}.*`]);
createQuery (params = {}) {
const { filters, query } = filter(params.query || {});
let q = this.db(params).select([`${this.table}.*`]);

// $select uses a specific find syntax, so it has to come first.
if (filters.$select) {
q = this.db().select(...filters.$select.concat(`${this.table}.${this.id}`));
q = this.db(params).select(...filters.$select.concat(`${this.table}.${this.id}`));
}

// build up the knex query out of the query params
Expand All @@ -123,7 +129,7 @@ class Service {

_find (params, count, getFilter = filter) {
const { filters, query } = getFilter(params.query || {});
const q = params.knex || this.createQuery(params.query);
const q = params.knex || this.createQuery(params);

// Handle $limit
if (filters.$limit) {
Expand Down Expand Up @@ -158,7 +164,7 @@ class Service {
}

if (count) {
let countQuery = this.db().count(`${this.id} as total`);
let countQuery = this.db(params).count(`${this.id} as total`);

this.knexify(countQuery, query);

Expand Down Expand Up @@ -201,7 +207,7 @@ class Service {
}

_create (data, params) {
return this.db().insert(data, this.id).then(rows => {
return this.db(params).insert(data, this.id).then(rows => {
const id = typeof data[this.id] !== 'undefined' ? data[this.id] : rows[0];
return this._get(id, params);
}).catch(errorHandler);
Expand Down Expand Up @@ -230,7 +236,7 @@ class Service {
query[this.id] = id;
}

let q = this.db();
let q = this.db(params);

this.knexify(q, query);

Expand Down Expand Up @@ -286,7 +292,7 @@ class Service {
// NOTE (EK): Delete id field so we don't update it
delete newObject[this.id];

return this.db().where(this.id, id).update(newObject).then(() => {
return this.db(params).where(this.id, id).update(newObject).then(() => {
// NOTE (EK): Restore the id field so we can return it to the client
newObject[this.id] = id;
return newObject;
Expand All @@ -305,7 +311,7 @@ class Service {

return this._find(params).then(page => {
const items = page.data;
const query = this.db();
const query = this.db(params);

this.knexify(query, params.query);

Expand All @@ -328,4 +334,5 @@ export default function init (options) {
return new Service(options);
}

init.hooks = hooks;
init.Service = Service;