Skip to content

🚲 Utility to declarative describing queries to API

License

Notifications You must be signed in to change notification settings

ahtohbi4/query-map

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

QueryMap

NPM version Status of devDependency Travis Build Status

— What a hell is that? 🙄

The library is to a declarative description of the map of requests to your REST API.

Installation

$ npm install query-map --save
// or $ yarn add query-map

Usage

— Stop, dude! But why do I need in this "bicycle"? 🤔

With the "bicycle" you can create a nested map of queries with an inheritance of configurations from level to level.

— Ok, but how?

Well look:

1. Create an instance

const qm = new QueryMap(
    'https://example.com/api',
     { config: { headers: { 'Content-Type': 'application/json' } } }
 );
Syntax
new QueryMap([baseUrl, baseOptions]);
  • baseUrl — a declaration of base URL (see URL description);
  • [baseOptions] — a base options for all nested queries:
    • [baseOptions.config={}] — a configuration of fetch-request (see specification),
    • [baseOptions.validateConfig] — a function to validate the config before starting a request (see Validation),
    • [baseOptions.validateResponse] — a function to validate response (see Validation).

2. Describe a map of requests to your API as a tree using method apply()

Note! The method doesn't mutate instance created above. It returns a new level of description.

export const API = qm.apply('', {}, {
    projects: qm.apply('/projects', {}, {
        create: qm.apply('/create', {
            config: { method: 'put' },
            validateConfig: ({ body: { name } = {} }) => (typeof name === 'string' && name !== '') ? undefined : {
                name: 'Parameter "name" should be a non-empty string.',
            },
        }),
        get: qm.apply(''),
        item: qm.apply({
            pattern: '/{id}',
            validate: ({ id }) => (typeof id === 'number' && id > 0) ? undefined : {
                id: 'Parameter "id" should be a positive number.'
            },
        }, {}, {
            get: qm.apply(''),
            update: qm.apply('', { config: { method: 'post' } }),
        }),
    }),
});

console.dir(API);
// -> {
//     projects: {
//         create: (urlParams, config) => {...},
//         get: (urlParams, config) => {...},
//         item: {
//             get: (urlParams, config) => {...},
//             update: (urlParams, config) => {...},
//         }
//     }
// }
Syntax
<instanceof QueryMap>.apply(url[, options, subtree])
  • url — a declaration of URL for the level (see URL description);
  • [options] — an object with:
    • [options.config={}] — a configuration of fetch-request (see specification),
    • [options.validateConfig] — a function to validate the config before starting a request (see Validation),
    • [options.validateResponse] — a function to validate response (see Validation);
  • [subtree] — an object with nested queries declaration.

3. Use the map

Now you can use the map:

API.projects.item.get();
// -> Error: { id: 'Parameter "id" should be a positive number.' }

API.projects.item.get({ id: 42 });
// -> <Request 'GET https://example.com/api/projects/42'>

API.projects.item.update({ id: 42 }, { body: JSON.stringify({ name: 'A new name' }) });
// -> <Request 'POST https://example.com/api/projects/42'>

URL description

A result URL of each query is built from the parts of each level starts from the root. The parts can be both static and dynamic.

The static one is a simple string valid as a part of the URL. For example /projects/create.

const qm = new QueryMap('https://example.com/api');
const getProjectsList = qm.apply('/projects');

getProjectsList();
// -> <Request 'GET https://example.com/api/projects'>

The dynamic one is the same string with special placeholders in braces. For example /projects/{id}. Pass the URL parameters as an object when performing a request. If some of them will not be found in the URL-pattern it will be passed as GET-parameters.

const qm = new QueryMap('https://example.com/api');
const getProject = qm.apply('/projects/{id}');
const getProjectsList = qm.apply('/projects/page/{page=1}');

getProject({ id: 42, foo: 'baz' });
// -> <Request 'GET https://example.com/api/projects/42?foo=baz'>
getProjectsList();
// -> <Request 'GET https://example.com/api/projects/page/1'>

In addition, you can define a function to validate the URL parameters (see Validation). In this case, instead of a string, you should pass an object with the next parameters:

  • pattern — is the same string as seen above (static or dynamic part of the URL) and
  • validate — function to validate.
const qm = new QueryMap('https://example.com/api');
const getProject = qm.apply({
    pattern: '/projects/{id}',
    validate: ({ id }) => (typeof id === 'number' && id > 0) ? undefined : {
        id: 'Parameter "id" should be a positive number.'
    },
});

Validation (URL-params, result config and response)

You can validate:

  • URL parameters by passing validation function as parameter validate in URL description;
  • result config before request by passing validation function as parameter validateConfig;
  • response object by passing validation function as parameter validateResponse.

The each kind of validator describing above is collected in a separate array from level to level. After you call a query function the arrays will be applying at a certain point of its execution. It means that each of the validation function from the arrays will be called and return undefined (if validation passed) or an object with errors.

About

🚲 Utility to declarative describing queries to API

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages