Skip to content

Instantly share code, notes, and snippets.

@KidkArolis
Created June 26, 2020 18:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save KidkArolis/98c5305dfe3c0f259d34cc1061eeb522 to your computer and use it in GitHub Desktop.
Save KidkArolis/98c5305dfe3c0f259d34cc1061eeb522 to your computer and use it in GitHub Desktop.
/*
Install this hook in app.hooks.js
module.exports = app => {
app.hooks({
before: {
all: [
addParamsForward()
]
}
})
}
Then use whenever you want to forward params, e.g. in a hook:
const { app, params } = context
app.service('another-service').find({ query: { id: 'x' }, ...params.forward() })
app.service('another-service').get(id, params.forward())
app.service('another-service').patch(id, {smth: 'x' }, params.forward())
*/
const { has } = require('lodash')
const paramsSeen = new WeakSet()
module.exports = function addParamsForward() {
return context => {
if (paramsSeen.has(context.params)) {
throw new Error(
`Params should never be shared across services, always use params.forward(). Fix calls to ${context.path}#${context.method}`,
)
}
paramsSeen.add(context.params)
context.params.forward = ({ only, omit = [] } = {}) => {
const { params } = context
const forwardedParams = {
caller: {
path: context.path,
method: context.method,
type: context.type,
},
requestId: context.params.requestId,
forwarded: true,
}
;(
only || [
'authenticated',
'user',
'transaction',
'skipEvents',
'permissions',
]
).forEach(param => {
if (has(params, param) && !omit.includes(param)) {
forwardedParams[param] = params[param]
}
})
if (forwardedParams.authenticated) {
forwardedParams.authentication = params.authentication
}
return forwardedParams
}
}
}
@bwgjoseph
Copy link

Hi, should line 62 be params.authenticated instead?

@KidkArolis
Copy link
Author

No! In case you want to say only forward transaction in some internal call, e.g. params.forward({ only: ['transaction'] }), then that means you explicitly did not want to attached .authentication.

@bwgjoseph
Copy link

Hi @KidkArolis,

I have tried to use the hook, but met with some strange error.

I'm using typescript, and the example is using Post and Comment. I added your hook into app.hook.before.all and I have a before hook in Post service that creates comments.

const createComment = async (context: HookContext) => {

  const { data, app, params } = context;

  // logged out with no issue
  console.log(params.forward());
  // const forward = { ...params.forward() };

  (app as Application)
      .service('comments')
      .create(
        { text: data.comments[0]},
        // if I pass as such, it will throw TypeError: Found non-callable @@iterator
        ...params.forward()
      );

  delete context.data.comments;

  context.data.createdBy = params.user?.email;

  return context;
};

So I switched to this

// so I set it here, and use it below
  const forward = { ...params.forward() };

  (app as Application)
      .service('comments')
      .create(
        { text: data.comments[0]},
        forward
      );

It works just fine, in the comments service, I can grab the params being passed down from Post. However, this works if this is a single call only. If I were do this...

const forward = { ...params.forward() };

  const commentsPromise = data.comments?.map(async (comment: string) => {
    (app as Application)
      .service('comments')
      .create({
        text: comment,
      }, forward);
  });

  if (commentsPromise) {
    await Promise.all(commentsPromise);
  }

This only works for the first call, subsequent call will throw the following error

Error: Params should never be shared across services, always use params.forward(). Fix calls to comments#create

Any idea on the two issue I face? Appreciate your help

@KidkArolis
Copy link
Author

@bwgjoseph, this is a convention I enforce in my Feathers applications - to never share the same params object between different service calls. This is because params object might get mutated in hooks affecting other services unexpectedly.

In your case, simple to fix, call params.forward() for each call, e.g.:

const commentsPromise = data.comments?.map(async (comment: string) => {
    (app as Application)
      .service('comments')
      .create({
        text: comment,
      }, params.forward());
  });

  if (commentsPromise) {
    await Promise.all(commentsPromise);
  }

@bwgjoseph
Copy link

@KidkArolis,

ah! it works, but still can't figure out why using spread ...params.forward() will trigger the non-callable @@iterator error. Other than that, it looks good now.

If you know what's causing the error, do let me know.

Thanks!

@KidkArolis
Copy link
Author

Because you can only spread properties in an object:

let a = { x: 'y' }
let b = { ...a }

Or you can spread arrays as arguments:

let a = ['x', 'y', 'z']
example(...a)

You can't spread an object as arguments:

// not valid!!
// throws non-callable @@iterator / is not iterable
let a = { x: 'y' }
example(...a)

In Feathers, params is an object, params.forward() is also an object, so:

// invalid
service.get(1, ...params)
service.get(1, ...params.forward())

// valid
service.get(1, params)
service.get(1, params.forward())
service.get(1, { ...params })
service.get(1, { ...params.forward() })

// invalid
service.find(...params)
service.find(...params.forward())

// valid
service.find({ query: { email: 'x', }, ...params })
service.find({ query: { email: 'x', }, ...params.forward() })

@bwgjoseph
Copy link

That's it.

Thank you so much.

@bwgjoseph
Copy link

Hello @KidkArolis,

I noticed that in the forwardedParams.caller.type is always before, is that intended? I would imagine that it should be whoever called the forward()?

// I have a hook x at the after-remove hook of service y
// now I call to remove
app.service('y').remove(id)

// within hook x, it calls service z (note hook x is in after-remove hook of service y)
app.service('z').remove(anotherId, params.forward());

So when I logs out the forwardedParams before the return in addParamsForward(), the caller.type is before.

@KidkArolis
Copy link
Author

@bwgjoseph you're right, it could be fixed by readding addParamsForward() hook in after all, so that it uses updated the closed over context when computing forwardedParams.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment