Code Monkey home page Code Monkey logo

mercury-api's Introduction

Build status Coverage Status Quality Gate

NPM dependencies Last commit Last release

NPM downloads License

Mercury Logo Mercury Api

Overview

This package provides a Mercury Api origin.

  • Mercury queries based on query strings and url parameters.
  • Built-in cache. Different caches are provided for different queryStrings and url parameters.
  • Reactivity to CRUD actions. When a "create", "update" or "delete" method is called over an instance, the instance cache is cleaned.

Api

import { Api } from "@xbyorange/mercury-api"

new Api(url, options)

  • Arguments
    • url - <String>. Api url. Parameters can be defined using ":parameterName". Please refer to the path-to-regexp package for further info.
    • options - <Object> Containing options:
      • tags - <String or Array of Strings> - The api instance is added to correspondant groups using these tags. Afterwards, configuration, headers, etc. can be changed for certain groups using the sources object methods described in the mercury docs, or the api object methods described below. The "api" tag is added automatically to all Api instances.
      • baseUrl - <String> - Added as prefix to all requests.
      • createMethod - <String> - HTTP method to be used in axios requests for create method.
      • readMethod - <String> - HTTP method to be used in axios requests for read method.
      • updateMethod - <String> - HTTP method to be used in axios requests for update method.
      • deleteMethod - <String> - HTTP method to be used in axios requests for delete method.
      • authErrorStatus - <Number> - Status code that will be considered as an authentication error. When detected, the authErrorHandler function will be executed instead of returning an error. See the authentication example below.
      • authErrorHandler - <Function> - Handler that will be executed when an authentication error is received.
        • Arguments:
          • origin - origin instance As first argument, the function will receive the origin itself. This will allow to set custom authentication headers after executing login, as example.
          • retry - _<Function> As second argument, a retry function is received, it has to be executed in order to retry the authentication failed request.
        • Returns: This function should return a Promise rejected with an error, or the execution of the received retry method.
      • onBeforeRequest - <Function> - Handler that will be executed before any source method. Useful, for example, to set origin headers just before each request is executed.
        • Arguments:
          • origin - origin instance As first argument, the function will receive the origin itself.
      • onceBeforeRequest - <Function> - Handler that will be executed once time just before the first request. Useful to set origin configuration, for example.
        • Arguments:
          • origin - origin instance As first argument, the function will receive the origin itself.
      • expirationTime - <Number> - The cache will be automatically cleaned each expirationTime miliseconds.
      • retries - <Number> - Number of retries that will be executed when a request fails before definitively failing. Requests will be retried for network errors or a 5xx error on an idempotent request (GET, PUT, DELETE).
      • cache - <Boolean> - Defines whether the resource will use cache or not. If false, all "read" executions will be sent to the server.
      • fullResponse - <Boolean> - If true, the full response object will be used as value, so you can consult headers, etc. If false, only the response.data will be returned, which is the default behavior.
      • validateStatus - <Function> - Function that will be used to determine if response status has to be considered as failed or success.
        • Arguments:
          • status - <Number> - Status code of the request.
        • Returns: Should return true for success requests, and false for failed ones.
      • validateResponse - <Function> - Function that will be used to determine if response has to be considered as failed or success.
        • Arguments:
          • response - <Object> - Response of the request.
        • Returns: Should return a Promise resolves for success requests, and a Promise rejected with an error for failed ones.
      • errorHandler - <Function> - Function used to parse errors. The returned value will be setted as error in the origin error property.
        • Arguments:
          • error - <Error> - Error object produced by a failed request.
        • Returns: Should return a rejected Promise, containing the new Error.
      • defaultValue - <Any> - Default value of origin until real data is requested.

Methods

query

api.query(queryObject)

  • Arguments
    • queryObject - <Object> containing properties:
      • queryString - <Object> Keys of the object will be passed as query strings in the url of the request.
      • urlParams - <Object> Keys of the object will be replaced by correspondant url parameters defined in the url as ":param". Please refer to the path-to-regexp package for further info.
  • Returns - New queried api instance having all methods described in this chapter.

clean cache

api.clean()

  • The cache of a queried api resource will be automatically cleaned when the update or delete methods are executed for that query.
  • If update or delete methods are executed over the origin without query, cache of all queried resources will be cleaned too.
  • All cache will be cleaned if the create method is executed.

setHeaders

Define headers that will be applied to all subsequent requests.

api.setHeaders(headers)

  • Arguments
    • headers - <Object> Each property in the object will be applied as a header.
booksCollection.setHeaders({
  Authorization: `Bearer ${token}`
});

addHeaders

Add a new header. Current headers will be extended with received headers object, and applied to all subsequent requests:

api.addHeaders(headers)

  • Arguments
    • headers - <Object> Each property in the object will be applied as a header.
booksCollection.addHeaders({
  "Cache-Control": "no-cache"
});

config

Config method can be invoked at any time to change the configuration. The resultant configuration will be the extension of the current options with the provided ones.

api.config(options)

  • Arguments
    • options - <Object> Options object as described above.

Handling many api instances at a time.

apis object methods allow to set headers of many instances at a time. Apis can be grouped and categorized using tags (through the tags option), and this methods will be applied only to apis matching provided tags.

If no tags are provided when invoking methods, they will be applied to all api instances.

An example of multiple configuration is available in the docs.

import { apis } from "@xbyorange/mercury-api"

clean cache

Use the "mercury" sources method for cleaning many instances at a time. Use the "api" tag to clean all "mercury-api" instances:

import { sources } from "@xbyorange/mercury";

sources.getByTag("api").clean();

setHeaders

It defines headers that will be applied to all subsequent requests of api instances matching provided tags.

apis.setHeaders(headers [,tags])

  • Arguments
    • headers - <Object> Headers object to be applied as in api.setHeaders method described above.
    • tags - <String or Array of Strings> Tag/s of those apis in which the setHeaders method should be executed. If no defined, setHeaders method of all api instances will be called.
apis.setHeaders({ Authorization: `Bearer ${token}` }, ["need-auth"]);

addHeaders

It adds a new header to all api instances matching provided tags. Current headers will be extended with received headers object, and applied to all subsequent requests:

apis.addHeaders(headers [,tags])

  • Arguments
    • headers - <Object> Each property in the object will be applied as a header.
    • tags - <String or Array of Strings> Tag/s of those apis in which the addHeaders method should be executed. If no defined, addHeaders method of all api instances will be called.
apis.addHeaders({ Authorization: `Bearer ${token}` }, ["need-auth"]);

config

Use the "mercury" sources method for configuring many instances at a time. Use the "api" tag to configure all "mercury-api" instances:

import { sources } from "@xbyorange/mercury";

sources.getByTag("api").config({
  retries: 0
});

Examples

Next example will be easier to understand if you are already familiarized with the mercury syntax.

import { Selector } from "@xbyorange/mercury";
import { Api } from "@xbyorange/mercury-api";

const booksCollection = new Api("http://api.library.com/books");
const authorsCollection = new Api("http://api.library.com/authors");

const booksWithAuthors = new Selector(
  {
    source: booksCollection,
    query: author => {
      queryString: {
        author
      }
    }
  },
  authorsCollection,
  (booksResults, authorsResults) =>
      booksResults.map(book => ({
      ...book,
      authorName: authorsResults.find(author => author.id === book.author)
    }))
);

// Each book now includes an "authorName" property.
const results = await booksWithAuthors.read();

// Request is not dispatched again, now results are cached.
await booksWithAuthors.read();

console.log(booksWithAuthors.read.value); //Value still remain the same

booksCollection.create({
  author: "George Orwell",
  book: "1984"
});

// Request to books is dispatched again, as cache was cleaned. Authors are still cached.
await booksWithAuthors.read();

// Request is dispatched, but passing "author" query string
const booksOfOrwell = await booksWithAuthors.query("George Orwell").read();

// Request is already cached. It will not be repeated.
await booksWithAuthors.query("George Orwell");

More examples

The mercury library uses this origin in his examples, so you can refer to the library documentation to found more examples.

Specific api origin examples:

Usage with frameworks

React

Please refer to the react-mercury documentation to see how simple is the data-binding between React Components and mercury-api.

Connect a source to all components that need it. Mercury will fetch data only when needed, and will avoid making it more than once, no matter how many components need the data.

Contributing

Contributors are welcome. Please read the contributing guidelines and code of conduct.

mercury-api's People

Contributors

javierbrea avatar methadata avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

mercury-api's Issues

Avoid different versions coexistence

It is desirable to avoid the coexistence of different versions of the library in the same application.
When this happens, the apis methods, used to configure all origins at a time, are only working on origins related to one of the multiple versions.
When different origins depend of different versions of this library, or an application defines a version different to the origins, this bug will occur.

To avoid this, it is desirable that this library executes a check in the initialization, ensuring that no other versions of it has been loaded previously. In that case, an error trace could be printed into the console, warning the developer about the fact.

Add Cancel request feature

Possible scenario:

When you have two requests, you need to wait for the first request is resolved for can start the second. It should cancel a pending request if you leave a page before the request has been completed. For example:

You're in the Product page, there's a pending request and you go to user page. Actually, if you leave the product page, the request is kept and the user request doesn't execute until that product request would be completed.

I think we can implement the Cancel feature to avoid this behavior. So, we could manage more efficiently the requests in our application.

123
LOL

FYI: Axios' cancellation

Array format in query string parameters

Currently, when passing an array to a query string parameter it is always stringified as a list separated by commas.

It is desirable that this behavior can be configured to use another array formats, such as in the query-string npm package

For doing this, a new configuration option called queryStringArrayFormat could be added:

import { Api } from "@xbyorange/mercury-api";

const myApi =  new Api("/foo-url", {
  queryStringArrayFormat: "none"
});

fooApi.query({
  queryString: { foo: [1, 2, 3] }
}).read();

//=> 'foo=1,2'

Available formats could be the same than in the query-string npm package:

  • 'bracket': Serialize arrays using bracket representation:
//=> 'foo[]=1&foo[]=2&foo[]=3'
  • 'index': Serialize arrays using index representation:
//=> 'foo[0]=1&foo[1]=2&foo[3]=3'
  • 'comma': Serialize arrays by separating elements with comma (default behavior until next major version for maintaining retrocompatibility):
//=> 'foo=1,2,3'
  • 'none': Serialize arrays by using duplicate keys (default behavior in next major version):
//=> 'foo=1&foo=2&foo=3'

Fix events unit test

As reported by Sonar, there is a bug in the "events" unit tests. It should be fixed to ensure that unit tests are working properly, and to make Sonar analysis pass.

Sonar reported a code smell in the test/events.spec.js file, at line 186:
Add the "let", "const" or "var" keyword to this declaration of "anyCalled" to make it explicit.

Configure all api instances at a time

When you have to configure many origins at a time with the same value (a very common scenario for "baseUrl", or authentication headers, for example), you have to import all desired origins and iterate over all calling to its "config" method.
This is really annoying because for each origin you use, you have to add the new dependency to your "configurator" piece, and you have to repeat this pattern in every applications.

It would be very useful to export a method for configuring or cleaning groups of Api instances at a time. (Maybe a system based on tags will be very flexible to allow configuring or cleaning only certain subgroups).

A proposal:

import { Api } from "@xbyorange/mercury-api";

export const fooCollection = new Api("/foo-url", {
  tags: ["xbyorange-api",  "need-auth"]
})

export const fooPublicCollection = new Api("/foo-public-url", {
  tags: ["xbyorange-api"]
})

export const fooExternalApi  = new Api("/foo-url", {
  tags: ["external-api"]
})

Now, you can configure each api url separately:

import { apis } from "@xbyorange/mercury-api"

apis.config("xbyorange-api", {
  baseUrl: "https://foo-url/api"
})

apis.config("external-api", {
  baseUrl: "https://foo-external-domain"
})

To configure authentication:

import { apis } from "@xbyorange/mercury-api"

export const setAuth = token => {
  apis.setHeaders("need-auth", {
    "Authorization": `Bearer ${token}`
  })
}

First public release

Migrate library from gitlab private repository and publish it into public NPM registry

Vulnerability in Axios dependency

It is needed to upgrade the currently used Axios dependency in order to avoid a potential security vulnerability.

Dependency is currently defined as ^0.18.0. It is required to upgrade it to version 0.19.0 or later.

Infinite loop in Axios retries.

When an axios request fails, it is retried infinitely. This behavior has been reproduced with a 500 status code response.

A workaround is to set the retries option to 0.

Axios should retry requests only number of times defined in the retries option, and then fail.

After debugging the library code, it is maybe related with used versions of axios and axios-retry, or maybe with another dependency version causing an unexpected conflict. Options from mercury-api are being passed to axios-retry correctly.

Implement setters for all configurations and HTTP's request methods

I think could be a good idea to implement setters for all configurations and HTTP's request methods.

If we want to an API is prepare only to get providers list and create a new log:

import { Api } from "@xbyorange/mercury-api";

const catalog = new Api('/catalog-api')

catalog.only(['get', 'post'])

Also, we could apply this practice for tags:

import { Api } from "@xbyorange/mercury-api";

const catalog = new Api('/catalog-api')

catalog.tags(['external-api', 'internal-api'])

Can't manage full error object

Describe the bug
When having an error response, such as 400, 401 or 500, the error received in the catch, or in the component, has only the response body.

To Reproduce

In this selector for example

export const mySuperSelector = new Selector(
  {
    source: myAwesomeOrigin,
    query: query => query,
    catch: e => {
	  // e is only response body error, and you can't access to status or other properties
      return false;
    }
  },
  data => data,
  false
);

Expected behavior

You should be able to access full response error object.

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.