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

Put the caller's transaction back in the params on end/rollback #256

Merged
merged 4 commits into from
Jul 13, 2021
Merged
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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,27 @@ try {
}
```

### Waiting for transactions to complete

Sometimes it can be important to know when the transaction has been completed (committed or rolled back). For example, we might want to wait for transaction to complete before we send out any realtime events. This can be done by awaiting on the `transaction.committed` promise which will always resolve to either `true` in case the transaction has been committed, or `false` in case the transaction has been rejected.

```js
app.service('messages').publish((data, context) => {
const { transaction } = context.params

if (transaction) {
const success = await transaction.committed
if (!success) {
return []
}
}

return app.channel(`rooms/${data.roomId}`)
})
```

This also works with nested service calls and nested transactions. For example, if a service calls `transaction.start()` and passes the transaction param to a nested service call, which also calls `transaction.start()` in it's own hooks, they will share the top most `committed` promise that will resolve once all of the transactions have succesfully committed.

## License

Copyright (c) 2019
Expand Down
20 changes: 16 additions & 4 deletions lib/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const start = (options = {}) => {

return hook => {
const { transaction } = hook.params;
const parent = transaction;
const knex = transaction ? transaction.trx : options.getKnex(hook);

if (!knex) {
Expand All @@ -22,6 +23,15 @@ const start = (options = {}) => {
return new Promise((resolve, reject) => {
const transaction = {};

if (parent) {
transaction.parent = parent;
transaction.committed = parent.committed;
} else {
transaction.committed = new Promise(resolve => {
transaction.resolve = resolve;
});
}

transaction.starting = true;
transaction.promise = knex.transaction(trx => {
transaction.trx = trx;
Expand Down Expand Up @@ -51,13 +61,14 @@ const end = () => {
return;
}

const { trx, id, promise } = transaction;
const { trx, id, promise, parent } = transaction;

hook.params = { ...hook.params, transaction: undefined };
hook.params = { ...hook.params, transaction: parent };
transaction.starting = false;

return trx.commit()
.then(() => promise)
.then(() => transaction.resolve && transaction.resolve(true))
.then(() => debug('ended transaction %s', id))
.then(() => hook);
};
Expand All @@ -71,13 +82,14 @@ const rollback = () => {
return;
}

const { trx, id, promise } = transaction;
const { trx, id, promise, parent } = transaction;

hook.params = { ...hook.params, transaction: undefined };
hook.params = { ...hook.params, transaction: parent };
transaction.starting = false;

return trx.rollback(ROLLBACK)
.then(() => promise)
.then(() => transaction.resolve && transaction.resolve(false))
.then(() => debug('rolled back transaction %s', id))
.then(() => hook);
};
Expand Down
94 changes: 94 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,100 @@ describe('Feathers Knex Service', () => {
expect(created).to.have.length(1);
expect(created[0]).to.have.property('name', 'Success');
});

it('allows waiting for transaction to complete', async () => {
const app = feathers();

let seq = []

app.hooks({
before: [
transaction.start({ getKnex: () => db }),
context => {
seq.push(`${context.path}: waiting for trx to be committed`)
context.params.transaction.committed.then((success) => {
seq.push(`${context.path}: committed ${success}`)
})
},
async context => {
seq.push(`${context.path}: another hook`)
}
],
after: [
transaction.end(),
context => { seq.push(`${context.path}: trx ended`) }
],
error: [
transaction.rollback(),
context => { seq.push(`${context.path}: trx rolled back`) }
]
});

app.use('/people', people);

app.use('/test', {
create: async (data, params) => {
await app.service('/people').create({ name: 'Foo' }, { ...params });

if (data.throw) {
throw new TypeError('Deliberate');
}
}
});

expect(seq).to.eql([])

await expect(app.service('/test').create({ throw: true })).to.eventually.be.rejectedWith(TypeError, 'Deliberate');

expect(seq).to.eql([
'test: waiting for trx to be committed',
'test: another hook',
'people: waiting for trx to be committed',
'people: another hook',
'people: trx ended',
'test: committed false',
'people: committed false',
'test: trx rolled back',
])

seq = []

expect(await app.service('/people').find()).to.have.length(0);

expect(seq).to.eql([
'people: waiting for trx to be committed',
'people: another hook',
'people: committed true',
'people: trx ended'
])

seq = []

await expect(app.service('/test').create({}))
.to.eventually.be.fulfilled;

expect(seq).to.eql([
'test: waiting for trx to be committed',
'test: another hook',
'people: waiting for trx to be committed',
'people: another hook',
'people: trx ended',
'test: committed true',
'people: committed true',
'test: trx ended',
])

seq = []

expect(await app.service('/people').find()).to.have.length(1);

expect(seq).to.eql([
'people: waiting for trx to be committed',
'people: another hook',
'people: committed true',
'people: trx ended'
])
});
});

testSuite(app, errors, 'users');
Expand Down