brillout / wildcard-api

Functions as API.
MIT License
368 stars 14 forks source link

[Enhancement] Add interceptors to client and server #38

Closed johnsonthedev closed 4 years ago

johnsonthedev commented 4 years ago

Hi, It would be cool to have interceptors for client and server similar to axios.

Cheers!

const DEBUG = process.env.NODE_ENV === "development";

axios.interceptors.request.use((config) => {
    /** In dev, intercepts request and logs it into console for dev */
    if (DEBUG) { console.info("✉️ ", config); }
    return config;
}, (error) => {
    if (DEBUG) { console.error("✉️ ", error); }
    return Promise.reject(error);
});
brillout commented 4 years ago

Hi @johnson544 !

You can do this today by wrapping your Wildcard functions:

const {endpoints} = require('@wildcard-api/server')

endpoints.getAllTrelloCards = interceptor(function () {
   // SQL/ORM query to get all Trello cards
}, myListener);

endpoints.createTrelloCard = interceptor(function () {
   // SQL/ORM query to create new Trello card
}, myListener);

function myListener({endpointName, endpointArgs, endpointResult, endpointError, context}) {
  const {user} = context;
  if( endpointError ) {
    console.error("✉️ ", 'Wildcard function error', endpointName, endpointArgs, user, endpointError);
  } else {
    console.log("✉️ ", 'New Wildcard call', endpointName, endpointArgs, user);
  }
}

// The machinery
function interceptor(wildcardFunction, callListener) {
  return async function(...endpointArgs) {
    const context = this;

    let endpointResult;
    let endpointError;
    try {
      endpointResult = await wildcardFunction.bind(context)(...endpointArgs);
    } catch(err) {
      endpointError = err;
    }

    // `context._wildcard` doesn't exist today but I can quickly implement it.
    const {endpointName} = context._wildcard;

    callListener({endpointName, endpointArgs, endpointResult, endpointError, context});

    if( endpointError ){
      throw err;
    } else {
      return endpointResult;
    }
  };
}

Does that work for you?

Although something like following would be more comfy:

const {addCallListener} = require('@wildcard-api/server')

addCallListener(myListener);

function myListener({endpointName, endpointArgs, endpointResult, endpointError, context}) {
  const {user} = context;
  if( endpointError ) {
    console.error("✉️ ", 'Wildcard function error', endpointName, endpointArgs, user, endpointError);
  } else {
    console.log("✉️ ", 'new Wildcard call', endpointName, endpointArgs, user);
  }
}

On the other hand I like Wildcard to be minimalistic; the first solution is all vanilla JavaScript — kinda cool :-).

The second solution is "easier" though.

A trade off could be to provide the interceptor code above as an official helper library e.g. at @wildcard-api/interceptor.

Let me know what you think!

johnsonthedev commented 4 years ago

As you said the second one is more comfy and I feel it gives more flexibility. e.g.

I want to run a middleware for all endpoints except one. Instead of wrapping all my functions I could create a middleware and simply check for the endpoint name:

addCallListener(myListener);
function myListener({endpointName, endpointArgs, endpointResult, endpointError, context}) {
  const {user} = context;
  if( endpointName != 'specialEndpoint' ) {
      ....
  }
 }

But I can see requirements for the first version as well. E.g. running pre hooks for a specific endpoint.

I agree with you that Wildcard should be slim! I feel best option is to do both: add the context._wildcard to the wildcard package and introduce the interceptor as a helper lib :-)

nice side effect: After a few more helper libs we can create an awesome wildcard repo :D :D :D

brillout commented 4 years ago

Check out Wildcard Intercept. It supports endpointName ;-).

@lostpebble You may be interested in Wildcard Intercept. (As always feel free to never reply, I know you are busy:).)

nice side effect: After a few more helper libs we can create an awesome wildcard repo :D :D :D

That'd be lovely indeed!