I think we need a slight restructure of the hook object that provides a bit firmer “contract” for what can be returned/modified and also helps people avoid common pitfalls. Overall the current structure has worked very well but after a bunch of real-world usage and feedback from the community I think we could improve things.
Some issues that I’ve noticed:
hook.params
can be a bit of a dumping ground and can be harder to reason as to what is in params, especially as an app grows or hook chains become complex.hook.params
that are required for things to work properly (ie. authenticated
, provider
) is sketchy and easy to accidentally overwrite causing much problems that can be catastrophic and hard to debugconst query = { name: 'John' };
// the incorrect way.
// easy to mess up, returns everything (especially if pagination is off)
app.service('users').find(query)
// the correct way
app.service('users').find({ query })
null
as the id. This is really not good!// this is super sketchy an empty object for the `id` field and/or
// explicit `params.removeAll = true` should be required
// (you can currently write your own hook to do this)
app.service('users').remove(null, params)
Personally I’d like to see the hook object look more like this:
{
id: 1,
app,
service: 'users',
method: 'get',
transport: 'rest',
authenticated: true,
headers: {},
params: {},
query: {},
client: {},
data: {},
result: { // always return data even when not paginated you just don't have other keys
data: [],
errors: [{ data, error }], // for failed records in bulk changes
}
}
I realize this is a VERY big breaking change that touches every pretty much every module but there are reasons for it:
hook.client
should exist for special client params (ip, mac address, os, app version, etc.)hook.params.provider
should be renamed to transport
to reflect our nomenclature in the docs and moved to the root of the hook and read-only.hook.results.data
. This reduces boilerplate we need to write to check if a response is paginated in each hook.query
outside of hook.params.query
would not only actually separate the query
object from params but would I think cognitively help people separate them and also make it easier to reason from the client side what is being sent.This would also change service calls so that they are always:
app.service('users').find(query, params)
app.service('users').get(id or query, params)
app.service('users').patch(id or query (cannot be null), data, params)
app.service('users').update(id or query, data, params)
app.service('users').create(data, params)
app.service('users').remove(id or query (cannot be null), params)
It would be quite a bit of work but I think would:
null
inadvertently and overwriting/removing all data for a serviceSince this touches pretty much every module and would be a breaking change we'd need to be able to roll it out incrementally. In order to do this I think we can:
I think we can create a hook(s) that you can include at the app or service level that would be able to map a new hook format back to the legacy one in order to keep things backwards compatible and ease migration.
Obviously learning from our previous issues with the feathers-authentication
1.x release we'd likely want to do pre-releases for these until all the dependent pieces are ready and documented.
If we are to proceed, I think the next release (Crow) is the best time to do it as will have brought feathers-hooks
into core feathers itself, and we may be looking to rename feathers-hooks-common
to feathers-hooks
.
Obviously this is a proposal and likely to change. I've talked with @daffl and @corymsmith about this at length over the last year but I would :heart: some input from the community and the rest of the @feathersjs/core-team. Feel free to comment or simply give a 👍 or 👎 .
I'm sure there are more related issues as some of the problems highlighted in here have come up a quite few times in Github and Slack.
These are good ideas to document for the future.
We have permissions, improved filtering, Sequelize ORM handling, integrating feathers-authentication-management that we need to first design and code with our limited resources.
They might have a bigger benefit for Feathers without causing disruption.
As an aside, renaming feathers-hooks-common
to feathers-hooks
will cause a world of confusion.
Always have results be returned as hook.results.data. This reduces boilerplate we need to write to check if a response is paginated in each hook.
This probably needs a separate issue of its own for discussion, to look at how existing hooks handle get
/find
data/results, but perhaps there could be a helper method on the hook that would apply a function to either a single item or an array/object of items as appropriate (e.g. hook.each(item => { item.updatedAt = Date.now() }
).
This would make it easier for a single hook to be applied when handling single items (get
, patch
, etc) or multiple items (find
, etc).
The proposal is a good one when just considering find
results, still.
@hubgit The getItems and replaceItems hook utilities already exist to do what you suggest. See https://docs.feathersjs.com/api/hooks-common.html#util-getitems-replaceitems
As an update we've run into some issues with the this
context in feathers-hooks-common
so an addition to this proposal is to not use this
anywhere and instead rename hook
-> context
.
@ekryski We also need to account for hook.errors
. We could normalize this to always be an array. That would help with situations like bulk inserts on create
where there are several records that fail, as is the case with https://github.com/feathersjs/feathers-mongoose/pull/199
100% agree with the proposals, especially for the new query syntax.
all for the changes to hook.results
to always have the data array! It would be great to not have to check that in our hooks :+1:
Much of this seems to be achievable with one or more hook. It would be a hack compared to making the changes to the service library, but it would be a start.
Normalizing the result.data
through a hook would be hard though. If it's added to the apps hook, it would run after all the service hooks, which would be too late.
Would it be possible to add a universal after hook that runs before all service hooks instead of after?
Currently I have done following using an app level after
hook.
result: { // always return data even when not paginated you just don't have other keys
data: [],
errors: [{ data, error }], // for failed records in bulk changes
}
Although, it does not feel right (to me atleast) to check if it is not a find
call:
if (context.result && context.method != 'find') {
context.result = { data: context.result };
}
Most helpful comment
all for the changes to
hook.results
to always have the data array! It would be great to not have to check that in our hooks :+1: