Why we moved from feathers-hooks-common to feathers-utils
This is a long way coming. One thing led to another. To understand the whole picture we need to start at the beginning.
1. getItems and replaceItems were not compatible with around hooks
Feathers v5 introduced the concept of around hooks. Some of the hooks in feathers-hooks-common were not compatible with this new concept. This was the first step towards a new mechanism that would be compatible with Feathers v5 and beyond. 'feathers-hooks-common' was not compatible with around hooks because it uses getItems and replaceItems. These two utilities return items from context.data or context.result based on wether it was in a before or after hook. This does not play well with around hooks and honestly was probably a mistake in the first place. Why not an explicit getData and getResult utility? So we developed getDataIsArray and getResultIsArray.
2. Naming issues in feathers-hooks-common
The naming of some hooks in feathers-hooks-common always felt a bit off:
alterItems:alteris not a word you see often in other libraries. While we needed to change it at least toalterDataandalterResultfor earlier described reasons, we decided to rename it totransformDataandtransformResult.discard:feathers-hooks-commonprovidesdisallowanddiscard. Two hooks that sound too similar whileomitis a much more common term. So initially we introducedomitDataandomitResultbut changed that to theomittransformer laterkeep*: This is quite similar todiscardandomit. We decided to rename it topickwhich is a more common term in the JavaScript ecosystem. So initially we introducedpickDataandpickResultbut changed that to thepicktransformer later.
3. Spread arguments
Most hooks of feathers-hooks-common use spread operator to pass arguments. This makes it impossible to add features to these hooks. We changed all applicable hooks to use no spread operator and an array instead. We also added an options object where needed. This allows us to add new features in the future without breaking changes. For example preventChanges now can take an options object as the second argument. This allows us to add new options in the future without breaking changes.
4. Hooks I don't know anyone uses anymore
The first two points above did not necessarily require a new package. But being in a massive refactoring (check out an earlier refactoring PR) we decided to remove some hooks that we don't think anyone uses anymore. In particular these are:
fgraphql: Not documented anyways. Reading the source code it's hard to understand what it does.fastJoin: This also is quite too complicated to implement. See the docspopulate/depopulate:fastJoinwas already recommended overpopulate. Nowadays it's recommended to use resolvers, or feathers-graph-populate or to use the underlying adapter (likemongoorknex) directly to populate data.keepInArray/keepQueryInArray: Too specific use case(?) probably better to implement this in user space.mongoKeys: Hooks specific for a certain database adapter should not be in a common package.serialize: Too specific use case(?), probably better to implement this with resolvers.sequelizeConvert: Never documented anyways. Same asmongoKeys, this is a hook specific for a certain database adapter and should not be in a common package.validate/validateSchema: Two hooks basically doing the same thing. It also uses ajv. It's not good to be used in a common package. We plan on bringing it back into another library likefeathers-validationor something similar.
That being said, we are open to reintroducing these hooks if someone can provide a good use case and implementation in this issue.
5. Suboptimal hook implementations
Some hooks/utils in feathers-hooks-common were not implemented in the best way:
- cache
- does only cache
getrequest nofindrequests - recommends using a very old version of
@feathers-plus/cache - does not handle varying params
- does only cache
- softDelete
- uncommon defaults with
{ deleted: true } - does not handle
.remove(null)
- uncommon defaults with
- paramsForServer
- was implemented as a utility, not a hook
It's hard to change these things without breaking changes. One way would have been to introduce new hooks like cache2 or softDelete2, deprecate the old ones and then remove and rename them im future releases. That would have been a lot of noise and would have made the package harder to maintain. Instead we decided to remove these hooks and implement them in a better way in feathers-utils. For example:
cache: A hook that cachesfindandgetrequests and handles params. It's hardly inspired by the very goodcontextCacheby @daddywarbucks.softDelete: A hook that soft deletes items. You need to definedeletedQueryandremoveDataexplicitly. In jsdoc it's recommended to use{ deletedAt: new Date() }.
6. Overall repository structure
We wanted to have a more modular structure. feathers-hooks-common has a folder for src files, a test folder and a docs folder with a single .md file for hooks and one for utilities. Especially the documentation was hard to maintain. Because every hook had a very specific structure, it was hard to keep the documentation up to date. That made additions difficult and therefore community packages emerged like feathers-fletching and @fratzinger/feathers-utils which solved similar problems.
We aimed for modular structure that makes new additions and maintenance easier. We found a pretty clean approach. Now every hook and utility has its own folder with the source code, the test file and a .md file. This makes it super easy to add new hooks and utilities.
If you want to add a new hook, simply start by copying an existing hook, modify the source code and the test file. Then update the .md file (basically just changing the title is good enough to get started). Then add the hook to the src/hooks/index.ts barrel file and you're good to go. The docs pick up the new hook automatically. The same applies to utilities. The docs are then built by analyzing the source code and jsdoc comment. This helps to keep the documentation up to date and makes it easier to add new hooks and utilities.
This allowed us to add new hooks much more easily. For example:
onDelete: A hook that runsCASCADEorSET NULLon related documents when an item is removed.setData: A hook that sets data on the context.skippable: A hook that can be skipped based on certain conditions.throwIf: A hook that throws an error if a certain condition is met.
For a more complete list check the migration guide or browse the hooks section in the docs.
7. New utilities
This is where the name feathers-hooks-common did not fit anymore. We added a few new utilities and want to add more in the future. Some new utilities are:
addSkip: A utility that adds askipproperty to the context.contextToJson: A utility that converts the context to JSON.getExposedMethodsgetPaginate: A utility that gets the pagination information from the context.iterateFindpatchBatchskipResult
For a more complete list check the migration guide or browse the utils section in the docs.
8. New transformers
While revisiting we found that some hooks basically did the same thing: use transformData or transformResult and pass in an anonymous function. We decided to introduce a few new transformers that can be used with transformData and transformResult. We don't need to add a bunch of new hooks for every use case. Instead we can use these transformers to transform the data or result in a more generic way. Some examples are:
keep('field1', 'field2'): now istransformData(pick(['field1', 'field2']))ortransformResult(pick(['field1', 'field2']))keepQuery('field1', 'field2'): now istransformQuery(pick(['field1', 'field2']))discard('field1', 'field2'): now istransformData(omit(['field1', 'field2']))ortransformResult(omit(['field1', 'field2']))discardQuery('field1', 'field2'): now istransformQuery(omit(['field1', 'field2']))lowerCase('field1', 'field2'): now istransformData(lowerCase(['field1', 'field2']))ortransformResult(lowerCase(['field1', 'field2']))setNow('field1', 'field2'): now istransformData(setNow(['field1', 'field2']))ortransformResult(setNow(['field1', 'field2']))
These hooks basically all work the same way. The decision to use explicit data and result hooks would have forced us to create two hooks for each mechanism. We planned to add more hooks like trimData, trimResult, parseDateData etc. Which adds a lot of complexity and maintenance overhead. Instead we removed these explicit hooks and decided to go with the transformers. This allows us to test each transformer separately and use them in the transformData and transformResult hooks. This also allows us to add new transformers without any noise. As you can see in the examples above the DX is not worse than before setNow() -> transformData(setNow()). In fact, it is even better because code is reused and less mental overhead is required to understand the code. Built in transformers for now are:
lowercase: Lowercases the given fields.omit: Omits the given fields.parseDate: Parses the given fields as dates.pick: Picks the given fields.setNow: Sets the current date/time on the given fields.trim: Trims the given fields.
If you have ideas for new transformers, please open an issue in the feathers-utils repository.
Conclusion
It was clear there were a lot of changes we wanted to make and a bunch of new things to add where the name feathers-hooks-common simply did not fit anymore. We considered a few names and scopes and finally came up with feathers-utils because it's short and describes this package best. The one downside of this package is, that @fratzinger used this name on npm for his own package. We agreed to switch the name to feathers-utils and he renamed his package to @fratzinger/feathers-utils. Therefore the new feathers-utils starts with version v10 to get a fresh start. We hope you like the new package and the new features. If you have any questions or suggestions, please open an issue in the github repository.
If you came this far and want to learn more about the new hooks and utilities, check out the migration guide or browse the hooks section and utils section in the docs. If you want to contribute, please open a new issue or pull request. We are looking forward to your contributions!
