Giter Site home page Giter Site logo

feathers-hooks-common's Introduction

feathers-hooks-common

npm GitHub Workflow Status libraries.io npm GitHub license Discord

A collection of useful hooks to use with Feathers services.

NOTE: This is the version for Feathers v5. For Feathers v4 use feathers-hooks-common v6

npm install feathers-hooks-common --save

Documentation

For the full list and API of available hooks and utilities, refer to the feathers-hooks-common documentation.

Tests

npm test to run tests.

License

See LICENSE.

feathers-hooks-common's People

Contributors

adamvr avatar antarasi avatar beeplin avatar bertho-zero avatar bitflower avatar brane53 avatar corymsmith avatar daffl avatar danieledler avatar dekelev avatar diastremskii avatar eddyystop avatar ekryski avatar fratzinger avatar github-actions[bot] avatar greenkeeper[bot] avatar j2l4e avatar kidkarolis avatar m4n0v31 avatar marshallswain avatar mdartic avatar nickbolles avatar nikitavlaznev avatar protoss78 avatar rodeyseijkens avatar rybaczewa avatar sean-nicholas avatar steffenlanger avatar superbarne avatar uulwake avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

feathers-hooks-common's Issues

Add defer Hook

Essentially it would take a hook function, and run it outside of the rest of the hooks - so that if an error occurs, it doesn't break the request, or if it will potentially take a long time it won't cause lags. Where I've used something like this is for analytics or low priority notifications.

Obviously you could just bake the functionality directly into the hook, but if you're consuming 3rd party hooks, or have a hook you sometimes want to depend on, that wouldn't work.

Is this common enough to be bundled into hooks-common / is there a pattern like this already for feathers?

I'm thinking something like this:

function defer(handler) {
  return function(hook) {
    setImmediate(() => handler.call(this, hook));
    return hook;
  }
}

app.service('users').after({
  create: [ defer(notify('User Created')) ]
});

Happy to make a PR

Support more complex populates

This hook (https://www.npmjs.com/package/feathers-populate-hook) has come up in the community and does some great things but also I think overcomplicates things a bit.

A few things that we've seen a need for through experience or what people have mentioned:

  • The ability to specify a location on the hook object to put the populated object. (ie. params, data, result). We frequently will populate an entity into params so that fields can be referenced for later but they are not the entity that is actively being modified. An example, would be:

    After a payment we need to email a receipt. So we need to have the products bought, the user to send to, and the company they belong to in order to get the shipping address. The order is the entity being modified by a create but we need to populate both the user and the business to hook.params for reference when setting up the email payload.

  • Being able to select fields from a populated entity. (this could just be a second hook that plucks those attributes, and likely should be)

  • Being able to populate multiple entities (ie. user and business). I think this can be resolved by simply registering the populate hook twice.

  • Being able to populate multiple of the same entity (ie. all comments on a post)

  • Being able to allow the client to specify what to populate and which fields via $populate. This would be very useful but starts to tread into GraphQL territory and might become unnecessarily complex or an expensive operation.

Now this may be more of a meta issue to discuss the possibility and viability of supporting these requirements. Not all of the requirements are valid in my mind or can be easily solved as I had mentioned above.

Proposed Interface

commonHooks.populate({
    user: { // destination key
        service: 'users', // can be a string or an actual service object
        field: 'adminId', // field to use for query, only required if different from destination key
        on: 'result' // where to put on the hook object. could be 'data', 'params', or 'result'
        multiple: true // defaults to false and returns one,
        query: { // defaults to {service.id: this.field} but can be any standard Feathers query object
            $select: ['name', 'email']
        }
    }
})

Typo in Readme

Seems utils can be imported from feathers-hooks-common/lib/utils, rather than feathers-hooks-common/utils as per README.

Not sure if this is an issue with the readme or the implementation, or my system could be just screwing up - but when I look for a util function e.g. checkContext, I need to look for as per above, rather than what's documented.

Add to doc that JS can create array of hooks

The new features originally described here have been removed. An array of hooks can be created using JS and then

service.before({
  create: hookArray
});

I'll add this to the docs.

JS can continue to use common.iff() as that's cleaner for async than writing JS code.

Filter returned items keeping those that satisfy some criteria

Idea for new hook arising from a comment by someone

"This may be a total noob question. How to restrict a complete hierarchy of services to a user.
I have a budget that has items in it, each item has events
how to assure that all items and events can only be viewed / edited by their owner ?
I thought it would be auth.restrictToOwner() but that does not work on find()"

softDelete "Cannot read property 'hasOwnProperty' of undefined"

Module version

node 7.1.0

  "dependencies": {
    "feathers": "^2.0.2",
    "feathers-hooks": "^1.6.1",
    "feathers-hooks-common": "^2.0.1",
    "feathers-rest": "^1.5.2"
  }

The code

const feathers = require('feathers');
const rest = require('feathers-rest');
const hooks = require('feathers-hooks');
const commonHooks = require('feathers-hooks-common');

const app = feathers()
  .configure(rest())
  .configure(hooks());

const data = [{
  text: 'one'
}, {
  text: 'two',
  deleted: true
}];

const messageService = app.service('/messages', {
  find(params) {
    return Promise.resolve(data);
  }
});

messageService.before({
  find: commonHooks.softDelete()
});

app.listen(3000);

The error

TypeError: Cannot read property 'hasOwnProperty' of undefined
    at /Volumes/Data/development/dev/two/node_modules/feathers-hooks-common/lib/utils.js:49:16
    at Array.reduce (native)
    at setByDot (/Volumes/Data/development/dev/two/node_modules/feathers-hooks-common/lib/utils.js:47:16)
    at Object.<anonymous> (/Volumes/Data/development/dev/two/node_modules/feathers-hooks-common/lib/new.js:31:27)
    at process._tickCallback (internal/process/next_tick.js:103:7)

Validate hook too limited

I'd like to do some async validation and use app and some services to do so, but the default validation hook is only useful for very basic stuff that can be done locally with the data provided.

I started wrapping my validation in a higher order function to pass the app, but hooks/index.js that feathers generates are straight up functions.

use a slug instead of id in service methods

Idea for a new hook:

I still think it can be implemented with a fairly simple hook for all cases by setting hook.id:

function slugId(name, idField = '_id') {
const methods = [ 'get', 'update', 'patch', 'remove' ];

return function(hook) {
if(methods.indexOf(hook.method) !== -1) {
const query = {
[name]: hook.id
};

  return hook.service.find({
    query,
    paginate: false
  }).then(result => {
    if(result.length === 1) {
      hook.id = result[0][idField];
    }

    return hook;
  });
}

}
}
This can then be initialized for all services with

app.hooks({
before: slugId('slug')
});

Proposal for populates++ hook

An attempt at a design to start discussion.

  • @ekryski 's proposal is basically used when we get to populating one item with another.
  • Populate definitions may be stored together. This way they can easily refer to one another.
  • Once an item or group of items is added, they can be further populated using 'expandBy'. This is recursive.
  • This may eventually be related to models for feathers services.

I'm hoping for

  • Comments on concept and packaging
  • Improvements to naming.
const populates = {

  // a straight forward set of populates taken from MichaelErmer/feathers-populate-hook
  message: {
    user: { // what @ekryski proposed. Temp renames to localField & foreignField for clarity.
      service: 'users', localField: '_id', foreignField: 'userId',
      on: 'result', multiple: false, query: {}
    },
    comments: {
      service: 'comments', localField: '_id', foreignField: 'commentIds' /* is array */,
      on: 'result', multiple: true, query: {}
    },
    categories: {},
    creator: {},
  },

  // populate a room with all its messages, further populating each message.
  room: {
    creator: {},
    messages: {
      service: 'messages', localField: '_id', foreignField: 'messageIds',
      on: 'result', multiple: true, query: {},
      expandBy: populates.message, // expand every message with populates.message
    },
    owners: {},
  },

  // populate an organization with its rooms, partially further populating each room
  organization: {
    owners: {},
    rooms: {
      service: 'rooms', localField: '_id', foreignField: 'roomIds',
      on: 'result', multiple: true, query: {},
      expandBy: [populates.room, 'messages', 'owners'], // don't expand using populates.room.creator
    }
  },
};

const organizations = app.service('organizations');

organizations.after({
  find: [
    // the fancy populating
    hook.population(populates.organization),

    // You can continue to populate an item the same way as with the current populate
    hook.population({
      creator: {
         service: 'users', localField: '_id', foreignField: 'creatorId',
         on: 'result', multiple: false, query: {}
      }
    }),
  ]
});

Update to latest plugin infrastructure

We just released a new plugin generator at https://github.com/feathersjs/generator-feathers-plugin that includes some changes that all other repositories should be updated to as well so that they all share the same infrastructure. Changes for this repository would include:

  • We decided to go with the standard code style with semicolons which will be checked through the semistandard CLI. No separate ESLint configuration necessary. This is open for discussion (the Airbnb style would be another option).
  • Automated changelog using GitHub Changelog Generator: Although the current changelog here points out that it isn't very useful to just dump commit logs into it we had a good experience with this changelog generator as long as every change is properly submitted as a separate pull request. The main reason for this is that maintaining changelogs by hand for multiple projects can become very tedious in the long term.
  • Rename _spec.js files to .test.js (for consistency with the other repositories)
  • Run CI with Node 4, 6 and latest (the Node versions that Feathers will officially support starting next year)
  • Code coverage reporting to code climate (the reason @ekryski pointed out was because it supports code quality and coverage reports where Coveralls only does coverage)

@eddyystop I can create a pull request with those changes if they are ok for you.

Improve setByDot

function betterSetByDot(item, key, value, ifDelete) {
  if (!key.contains(',')) {
    item[key] = value;
    if (value === undefined && ifDelete) {
      delete item[key];
    }
    return;
  }
  
  setByDot(item, key, value, ifDelete);
}

and then update serialize

dedup / debounce calls

Idea for a new hook from bede

"Has anyone here had to develop a pattern for โ€˜debouncingโ€™ (not quite debouncing) requests using feathers-client? e.g. I have two calls to the same find, but only care about the result of one:"

app('projects').find(โ€ฆ);
// While request is happening request is made again
app('projects').find(โ€ฆ);
// Should just return promise from first request

disable hook seems to have wrong true/false logic

Code here means to disable when the result is false, not to disable when the result is true.

if (typeof realm === 'function') {
    return function (hook) {
      const result = realm(hook);
      const update = check => {
        if (!check) {
          throw new errors.MethodNotAllowed(`Calling '${hook.method}' not allowed. (disable)`);
        }
      };
  }

But in docs here it seems to be opposite logic:

  // Disable the remove hook if the user is not an admin
  remove: hooks.disable(function(hook) {
    return !hook.params.user.isAdmin
  })

Either the doc or the code is wrong. I guest it's the code which is wrong.

Hooks shouldn't be arrow functions

Hooks should not return arrow functions because the context (this) is the surrounding context, not the service (which is being set on normal functions).

Funcs in promisify.js

fnPromisifyCallback is documented. The other fn* funcs are not.

Decide if we want the others to be available or not.

New populate support `setByDot`

While the new populate hook supports getByDot with the parent and child fields, it does not permit a dot string in the nameAs field. This would also be helpful to have so that all fields can be populated directly on their parentField.

Suggestion: `$count` special query param? Where it literally just returns the number in place of `hook.result`.

What do you guys think about a $count special query param? Where it literally just returns the number in place of hook.result. The reason I ask is knowing to set $limit: 0 is a bit esoteric and feels like a hack. Plus with the new populate hook if you wanted to just get a count of the related items you can do

function(hook) {
  if(hook.params.query.$count) {
    hook.params.query.$limit = 0;
    delete hook.params.query.$count;
  }
}

softDelete undefined.patch issue

I just noticed that in the published npm code, the softDelete is compiled from

return this.patch(hook.id, hook.data, hook.params).then(data => {

to

return undefined.patch(hook.id, hook.data, hook.params).then(function (data) {

Don't think that's correct. I'll dig around more tomorrow if I get a minute.

utility hook to trim data

It would be nice to have a hook that deeply inspects hook.data and removes whitespace. Node already has the String.trim function, so this would be a fairly straightforward hook to implement.

e.g, before:

hook.data = {
  foo: '    not trimmed    ',
  bar: {
    baz: '   needs  trimming\t\n   '
  },
  bam: 'trimmed'
};

and after:

hook.data = {
  foo: 'not trimmed',
  bar: {
    baz: 'needs  trimming'
  },
  bam: 'trimmed'
}

Normalize hook.result from mongoose and sequelize

Should we have every hook that uses hook.result first normalize it?

      // If it's a mongoose model then
      if (typeof obj.toObject === 'function') {
        obj = obj.toObject(); 
      }
      // If it's a Sequelize model   
      else if (typeof obj.toJSON === 'function') {
        obj = obj.toJSON();
      }

I believe there is an app wide call to do this after the CRUD operation. But it seems we keep getting questions on Slack from people who are new to Feathers.

Should each hook be in their own repository?

Recently I've been wondering if the best way to maintain this growing number of hooks would be to migrate each one into their own repository/module (potentially in a separate feathers-hooks organization):

  • We are already having discussions about major releases related to common hooks. I have the feeling that major versions of this repository could grow very quickly. Updating major versions is a pain for both the maintainers (as I mentioned you have to do more than just make the release e.g. make it public somewhere, document migration, update examples, guides etc.) and users (you have to find out if the version changed, what broke, update your code etc.).
  • A breaking major release of feathers-hooks-common could be because of an API change of any of the bundled hooks. People will have to go through all the upgrading steps just to find out that they weren't even using that hook.
  • There is a chance this repository might become a kitchen sink of hooks that are not related at all (other than they are hooks). Issues can pile up quickly and I think many will be related to the same more complex and most used hooks (e.g. populate).
  • It will be easier to split up the documentation into categories since each hook will just be its own module and can be moved around/deprecated/updated independently from each other.
  • The only downside I can think of is having to maintain a lot of different modules but we are kind of doing that already. As long as we script repository updates and use the same plugin infrastructure I think we should be good though. In this case we can probably even automate creation by using the GitHub API, initializing a new plugin and copying each hook file and test and publishing it.

Alternative: Another option would be a monorepo structure like Babel but I am not too familiar with that (it seems you will have to automate everything in a very similar way).

Do Code Climate analysis again

Code Climate complains about duplicate code even if its in different modules. I found that did not work well for me with unit tests as I want each test to be as obvious as possible. I'll look into how to ignore test files.

Change response code

exports.after = {
all: [],
find: [function (hook) {
hook.app.response.statusCode = 418
}],
...

Read service using a slug instead of just _id

I still think it can be implemented with a fairly simple hook for all cases by setting hook.id:

function slugId(name, idField = '_id') {
const methods = [ 'get', 'update', 'patch', 'remove' ];

return function(hook) {
if(methods.indexOf(hook.method) !== -1) {
const query = {
[name]: hook.id
};

  return hook.service.find({
    query,
    paginate: false
  }).then(result => {
    if(result.length === 1) {
      hook.id = result[0][idField];
    }

    return hook;
  });
}

}
}
This can then be initialized for all services with

app.hooks({
before: slugId('slug')
});

Validate sync function do nothing

From documentation:

Sync functions return either an error object or null.

The following validation function do nothing.

function messageValidator(values) {
  return new Error('Error message');
}

Promise functions should throw on an error.

Throw on an error or reject with an error object?

Proposal for permissions in populate++

This thread concerns integrating the new feathers-permissions with the new populate++, serialize, etc.

Here are my thoughts.

(1) Populate: Let's take Purchase Requests as an example. Accounting UIs want to show things like invoices and general ledger numbers along with the PR. Purchasing UIs want to show things like receiving slips along with the PR. I would argue that the app decides what type of populate it needs and the hooks have almost no decisions to make.

We can't of course allow a curl command to get whatever it wants, even if another curl was first used to authenticate to a random user. So perhaps populate++ can ensure an Accounting PR populate is at least requested by a user/API/IoT that has Accounting permissions.

So maybe we can have

const populations = {
  favorites: {
    // alternative syntax
    permissions: 'acct',
    permissions: ['aaa', 'bbb'],
    permissions: (hook) => true,
    include: {

Do we allow permissions just at the base record, or do we allow them on every include'd table? I would like populate structures to be composable though I'm not sure how effective that would be in practice. permissions at every table would be needed.

(2) Serialize: Let's take Payroll as an example, and that we have a populated Employee item. The Payroll Manager is allowed to see the salaries of other department heads, while Payroll Clerks are not. The situation can grow into something more complicated.

I think the server has to decide on the proper serialization and call hook.serialize(serializers.employee_manager) with the correct serialization schema from

const serializers = {
  employee_manager: { ... },
  employee_clerk: { ... },

We can standardize this with something like

const serializerPermissions = {
  employee : [
    { permissions: ['payroll-mgr'], serializer: serializers.employee_manager },
    { permissions: 'payroll-clerk', serializer: serializers.employee_clerk },
  ]
}

hooks.serializeWithPermissions((hook) => serializers.employee_manager)
// or
hooks.serializeWithPermissions(serializerPermissions.employee)

The serializer decision occurs only at the base record.

(3) If populate and serialize do have different permission needs, then @ekryski 's proposal to separate the hooks was even more smart.

(4) Concerns

  • We are allowing only ors on the permissions.
  • I need to read up on feathers-permissions (sic).

Populate hook: parentField and childField are confusing

If we have a system of categories and subcategories, we have:

/*
Category (parent)
    v
Category (child)
*/

{
  service: 'category',
  nameAs: 'subCategories',
  parentField: 'category_id',
  childField: 'id',
  include: [ ... ],
}

/*
Category (child)
    v
Category (parent)
*/

{
  service: 'category',
  nameAs: 'parentCategories',
  parentField: 'parent_id', // but this field is on the child in this case
  childField: 'id', // but this field is on the parent in this case
  include: [ ... ],
}

I think that as for relational databases, like mySQL, you should rename parentField to localField and childField to foreignField.

Which will give:

/*
Category (parent)
    v
Category (child)
*/

{
  service: 'category',
  nameAs: 'subCategories',
  localField: 'category_id',
  foreignField: 'id',
  include: [ ... ],
}

/*
Category (child)
    v
Category (parent)
*/

{
  service: 'category',
  nameAs: 'parentCategories',
  localField: 'parent_id',
  foreignField: 'id',
  include: [ ... ],
}

"if else" hook

Steps to reproduce

(First please check that this issue is not already solved as described
here
)

  • Tell us what broke. The more detailed the better.
  • If you can, please create a simple example that reproduces the issue and link to a gist, jsbin, repo, etc.

Expected behavior

    common.iff(
      custom.isSuperAdmin(),
      common.pluck('username', 'firstName', 'lastName', 'money'),
      common.pluck('username', 'firstName', 'lastName'),
    )

Allow an else condition on iff hook. Above is not possible. Adding a third parameter for the else condition. In some cases, this might give the possibility to chain iff hook.

Actual behavior

Above code is not possible.

System configuration

Tell us about the applicable parts of your setup.

Module versions (especially the part that's not working):

NodeJS version: 7.2.0

Operating System: macOS 10.12

Set model relations for sequelize

Some DBs can specify relationships in their method calls. This would be significantly faster than using the populate hook. So is there a hook that could aid this for such DBs?

Potential approaches:

(1) https://github.com/BestBuy/api-playground/blob/master/src/services/products/hooks/index.js

(2) https://github.com/lionelrudaz/api.wellnow.ch
https://github.com/seyz/jsonapi-serializer

(3) From j214e on slack channel

let _app;

/* usage:
  setModelRelations(app, {
    categories:{
      hasMany: {
        products: {foreignKey: 'belongsToCategory'}
      }
    }
  })
*/

export function setModelRelations(app, relations) {
  _app = app;

  for (let service in relations) {
    if (relations.hasOwnProperty(service)) {
      if (relations[service].hasMany) {
        const targets = relations[service].hasMany;

        for(let targetService in targets){
          if(targets.hasOwnProperty(targetService)){
            _hasMany(service, targetService, targets[targetService]);
          }
        }
      }

      if (relations[service].belongsTo) {
        const targets = relations[service].belongsTo;

        for(let targetService in targets){
          if(targets.hasOwnProperty(targetService)){
            _belongsTo(service, targetService, targets[targetService]);
          }
        }
      }
    }
  }
}

function _model(service){
  return _app.service(service).Model
}

function _belongsTo(service, targetService, options){
  _model(service).belongsTo(_model(targetService),options)
}

function _hasMany(service, targetService, options){
  _model(service).hasMany(_model(targetService),options)
}

Example to add to docs

// service.get(someOtherField, params)
app.service('myservice').hooks({
  before: {
    get(hook) {
      return this.find({
        query: { somOtherField: hook.id }
      }).then(page => {
        const { data } = page;
        if(data.length === 0) {
          throw new Error('Not found');
        }

        hook.result = data[0];

        return hook
      });
    }
  }
});

Support an array of hooks

const { iff, isProvider, do } from 'feathers-hooks-common';

service.before({
  create: iff(isProvider('external'), do([hook1, hook2, hook3]),
});
  • Multiple iff() avoided for the same condition.
  • feathers-hook-common creates a promise chain out of the array using code from feathers-hooks. feathers-hooks uses that chain without changes to itself.

Comments @daffl ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.