├── .babelrc
├── .editorconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── SUMMARY.md
├── book.json
├── docs
├── README.md
├── adapters.md
├── basics
│ ├── README.md
│ ├── adapter.md
│ ├── api-function.md
│ ├── data-source.md
│ └── fetch-api-call.md
├── contributing.md
├── recipes
│ ├── README.md
│ ├── api-enchantment.md
│ ├── auto-invoke-methods.md
│ ├── data-source-immutable.md
│ ├── data-source-type-checking.md
│ ├── default-response.md
│ ├── file-upload.md
│ ├── fixtures.md
│ ├── generate-crud-methods.md
│ ├── prepare-process.md
│ └── validation.md
└── testing.md
├── example
├── weather-axios-tcomb
│ ├── index.js
│ └── model.js
└── weather-fetch-adapter
│ └── index.js
├── lerna.json
├── package-lock.json
├── package.json
├── packages
├── bivrost-axios-adapter
│ ├── .babelrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── __tests__
│ │ └── adapter.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── index.js
│ └── yarn.lock
├── bivrost-delay-adapter
│ ├── .babelrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── __tests__
│ │ └── adapter.js
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ └── index.js
├── bivrost-fetch-adapter
│ ├── .babelrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── index.js
│ └── yarn.lock
├── bivrost-local-storage-adapter
│ ├── .babelrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ └── index.js
├── bivrost-save-blob-adapter
│ ├── .babelrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── index.js
│ └── yarn.lock
├── bivrost
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── __tests__
│ │ ├── caches.js
│ │ ├── classes.js
│ │ ├── createRequestTemplate.js
│ │ ├── http-bin-api.js
│ │ ├── localstorage-mock.js
│ │ ├── mockDataSource.js
│ │ ├── promiseCache.js
│ │ └── promiseDeduplicator.js
│ ├── bivrost-3.2.4.tgz
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── data
│ │ │ ├── cache.js
│ │ │ └── source.js
│ │ ├── http
│ │ │ ├── api.js
│ │ │ ├── clientRequest.js
│ │ │ └── createRequestTemplate.js
│ │ └── utils
│ │ │ ├── mockDataSource.js
│ │ │ ├── promiseCache.js
│ │ │ └── promiseDeduplicator.js
│ └── yarn.lock
└── fetch-api-call
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── __tests__
│ └── index.js
│ ├── fetch-api-call-3.2.4.tgz
│ ├── node.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── createInterceptors.js
│ ├── index.js
│ ├── node.js
│ └── setup.js
│ └── yarn.lock
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset=utf-8
3 | end_of_line=lf
4 | trim_trailing_whitespace=true
5 | insert_final_newline=true
6 | indent_style=space
7 | indent_size=2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /_book
5 | /npm-debug.log
6 | /node_modules
7 | /data
8 | /http
9 | /coverage
10 | /utils
11 |
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /npm-debug.log
5 | /node_modules
6 | /coverage
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | install:
3 | - yarn add lerna
4 | - lerna bootstrap --npm-client=yarn
5 | - yarn
6 |
7 | node_js:
8 | - "4"
9 | - "5"
10 | - "6"
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "coverage/": true,
4 | "_book/": true,
5 | "packages/bivrost/data/": true,
6 | "packages/bivrost/http/": true,
7 | "packages/bivrost/utils/": true,
8 |
9 | "packages/bivrost-axios-adapter/coverage/": true,
10 | "packages/bivrost-axios-adapter/index.js": true,
11 |
12 | "packages/bivrost-delay-adapter/coverage/": true,
13 | "packages/bivrost-delay-adapter/index.js": true,
14 |
15 | "packages/bivrost-fetch-adapter/coverage/": true,
16 | "packages/bivrost-fetch-adapter/index.js": true,
17 |
18 | "packages/bivrost-local-storage-adapter/coverage/": true,
19 | "packages/bivrost-local-storage-adapter/index.js": true,
20 |
21 | "packages/bivrost-save-blob-adapter/coverage/": true,
22 | "packages/bivrost-save-blob-adapter/index.js": true,
23 |
24 | "packages/fetch-api-call/coverage/": true,
25 | "packages/fetch-api-call/index.js": true,
26 | "packages/fetch-api-call/createInterceptors.js": true,
27 | "packages/fetch-api-call/setup.js": true,
28 | "packages/fetch-api-call/node.js": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | [DOCS/CONTRIBUTING](https://tuchk4.github.io/bivrost/docs/contributing.html)
4 |
5 | ---
6 |
7 | ## Our Pledge
8 |
9 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
10 |
11 | ## Our Standards
12 |
13 | Examples of behavior that contributes to creating a positive environment include:
14 |
15 | * Using welcoming and inclusive language
16 | * Being respectful of differing viewpoints and experiences
17 | * Gracefully accepting constructive criticism
18 | * Focusing on what is best for the community
19 | * Showing empathy towards other community members
20 |
21 | Examples of unacceptable behavior by participants include:
22 |
23 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
24 | * Trolling, insulting/derogatory comments, and personal or political attacks
25 | * Public or private harassment
26 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
27 | * Other conduct which could reasonably be considered inappropriate in a professional setting
28 |
29 | ## Our Responsibilities
30 |
31 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
32 |
33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
34 |
35 | ## Scope
36 |
37 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
38 |
39 | ## Enforcement
40 |
41 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at valeriy.sorokobatko@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
42 |
43 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
44 |
45 | ## Attribution
46 |
47 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
48 |
49 | [homepage]: http://contributor-covenant.org
50 | [version]: http://contributor-covenant.org/version/1/4/
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Valerii Sorokobatko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Bivrost](http://tuchk4.github.io/bivrost/)
2 |
3 | Bivrost allows to organize a simple interface to asynchronous APIs.
4 |
5 | [](https://travis-ci.org/tuchk4/bivrost)
6 | [](https://www.npmjs.com/package/bivrost)
7 |
8 | # Fetch API Call
9 |
10 | Easiet way to use fetch with interceptros and awesome api endpoint declaration.
11 | [bivrost/packages/fetch-api-call](https://github.com/tuchk4/bivrost/tree/master/packages/fetch-api-call)
12 |
13 | ## Bivrost
14 |
15 | The main idea of Bivrost is grouping several API methods into data-sources.
16 |
17 | [Bivrost full documentation and recipes](https://tuchk4.github.io/bivrost/)
18 |
19 | ## Installation
20 |
21 | ```
22 | yarn add bivrost
23 | ```
24 |
25 | ## The gist
26 |
27 | That’s it! Create api function for github api.
28 |
29 | ```js
30 | import api from 'bivrost/http/api'
31 | import fetchAdapter from 'bivrost-fetch-adapter';
32 |
33 | const githubApi = api({
34 | protocol: 'https:'
35 | host: 'api.github.com',
36 | adapter: fetchAdapter()
37 | });
38 |
39 | //define API method
40 | const repositoryList = githubApi('GET /users/:user/repos'),
41 |
42 | //call API method
43 | repositoryList({ user: 'tuchk4' })
44 | .then(repositories => console.log(repositories));
45 | ```
46 |
47 | Create data source that contain few github api methods (get repositories list
48 | and get repository info) and its invoke chain.
49 |
50 | ```js
51 | import DataSource from 'bivrost/data/source';
52 | import githubApi from './github-api';
53 | import tcomb from 'tcomb';
54 |
55 | class GihtubRepositories extends DataSource {
56 | // define invoke method chain. Default chain is - ['api', 'process']
57 | static steps = ['api', 'immutable'];
58 |
59 | // "define "api" step
60 | static api = {
61 | repos: githubApi('GET /users/:user/repos'),
62 | repoInfo: githubApi('GET /repos/:user/:repository'),
63 | };
64 |
65 | // step function will be executed for each method
66 | static immutable = response => Immutable.fromJSON(response);
67 |
68 | // define data source public methods that invokes steps methods
69 | getRepositories(user) {
70 | return this.invoke('repos', {
71 | user,
72 | });
73 | }
74 |
75 | getRepositoryInfo(user, repository) {
76 | return this.invoke('repoInfo', {
77 | user,
78 | repository,
79 | });
80 | }
81 | }
82 | ```
83 |
84 | Extends GihtubRepositories and define username. Now all requests will be done
85 | for facebook's github group.
86 |
87 | ```js
88 | import GihtubRepositories from './github-repositories';
89 |
90 | const FACEBOOK_GITHUB_ACCOUNT = 'facebook';
91 |
92 | class FacebookRepositories extends GihtubRepositories {
93 | getRepositories() {
94 | return super.getRepositories(FACEBOOK_GITHUB_ACCOUNT);
95 | }
96 |
97 | getRepositoryInfo(repository) {
98 | return super.getRepositoryInfo(FACEBOOK_GITHUB_ACCOUNT, repository);
99 | }
100 | }
101 | ```
102 |
103 | ### [Contributing](docs/contributing.md)
104 |
105 | Project is open for new ideas and features:
106 |
107 | * new adapters
108 | * new api functions
109 | * data source features
110 | * feedback is very matter
111 |
112 | ---
113 |
114 | ## Docs
115 |
116 | * [Basics](/docs/basics/README.md)
117 | * [Api function](/docs/basics/api-function.md)
118 | * [Adapter](/docs/basics/adapter.md)
119 | * [Data source](/docs/basics/data-source.md)
120 | * [Recipes](/docs/recipes/README.md)
121 | * [Api enchantment](/docs/recipes/api-enchantment.md)
122 | * [Generate DataSource invoke functions](/docs/recipes/data-source-auto-invoke.md)
123 | * [Data source immutable output](/docs/recipes/data-source-immutable.md)
124 | * [Data source type checking (tcomb)](/docs/recipes/data-source-type-checking.md)
125 | * [CRUD Data source](/docs/recipes/generate-crud-methods.md)
126 | * [File upload](/docs/recipes/file-upload.md)
127 | * [Fixtures](/docs/recipes/fixtures.md)
128 | * [Default api response](/docs/recipes/default-response.md)
129 | * [Prepare request and process response](/docs/recipes/prepare-process.md)
130 | * [Testing](/docs/testing.md)
131 | * [Contributing](docs/contributing.md)
132 |
133 | #### Adapters
134 |
135 | * [Fetch adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-fetch-adapter)
136 | * [Axios adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-axios-adapter)
137 | * [Delay adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-delay-adapter)
138 | * [Local storage adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-local-storage-adapter)
139 | * [Save blob adapter adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-save-blob-adapter)
140 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | docs/README.md
--------------------------------------------------------------------------------
/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitbook": ">=3.2.1",
3 | "title": "Bivrost",
4 | "plugins": [
5 | "edit-link",
6 | "prism",
7 | "-highlight",
8 | "github",
9 | "ga",
10 | "anker-enable"
11 | ],
12 | "pluginsConfig": {
13 | "edit-link": {
14 | "base": "https://github.com/tuchk4/bivrost/tree/master",
15 | "label": "Edit This Page"
16 | },
17 | "github": {
18 | "url": "https://github.com/tuchk4/bivrost"
19 | },
20 | "ga": {
21 | "token": "UA-89488143-1"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Table of content
2 |
3 | * [Basics](/docs/basics/README.md)
4 | * [Api Function](/docs/basics/api-function.md)
5 | * [Adapter](/docs/basics/adapter.md)
6 | * [Data Source](/docs/basics/data-source.md)
7 | * [Fetch API Call](/docs/basics/fetch-api-call.md)
8 |
9 | - [Bivrost at GitHub](https://github.com/tuchk4/bivrost)
10 |
11 | * [Recipes](/docs/recipes/README.md)
12 | * [Api Enchantment](/docs/recipes/api-enchantment.md)
13 | * [Auto Invoke Methods](/docs/recipes/auto-invoke-methods.md)
14 | * [Immutable Output](/docs/recipes/data-source-immutable.md)
15 | * [Type checking with tcomb](/docs/recipes/data-source-type-checking.md)
16 | * [CRUD Data Source](/docs/recipes/generate-crud-methods.md)
17 | * [File Upload](/docs/recipes/file-upload.md)
18 | * [Fixtures](/docs/recipes/fixtures.md)
19 | * [Default Api Response](/docs/recipes/default-response.md)
20 | * [Prepare Request and Process Response](/docs/recipes/prepare-process.md)
21 | * [Data Validation](/docs/recipes/validation.md)
22 |
23 | ### Adapters
24 |
25 | * [Adapeters List](/docs/adapters.md)
26 | * [Fetch Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
27 | * [Axios Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
28 | * [Delay Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
29 | * [LocalStorage Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
30 | * [Save Blob Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
31 |
32 | - [Testing](/docs/testing.md)
33 |
34 | * [Contributing](docs/contributing.md)
35 |
--------------------------------------------------------------------------------
/docs/adapters.md:
--------------------------------------------------------------------------------
1 | # Adapters
2 |
3 | * [Fetch Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
4 | * [Axios Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
5 | * [Delay Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
6 | * [LocalStorage Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
7 | * [Save Blob Adapter Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
8 |
--------------------------------------------------------------------------------
/docs/basics/README.md:
--------------------------------------------------------------------------------
1 | # Bivrost basics
2 |
3 | * [Api function](/docs/basics/api-function.md)
4 | * [Adapter](/docs/basics/adapter.md)
5 | * [Data source](/docs/basics/data-source.md)
6 | * [Fetch API Call](/docs/basics/fetch-api-call.md)
7 |
--------------------------------------------------------------------------------
/docs/basics/adapter.md:
--------------------------------------------------------------------------------
1 | # Adapter
2 |
3 | The main goal of Adapter is make some magic and return function that execute
4 | query according to passed url and Request object or enchant another Adapter.
5 |
6 | In most cases - adapter is using to adapt api function for used approaches or
7 | libraries (fetch, axios, rxjs, local storage) and execute query.
8 |
9 | Also adapter is using for enchanting other adapters - add additional delays for
10 | testing, log api calls, save request response as blob, filter params etc. For
11 | example there are
12 | [delayAdapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter),
13 | [saveBlobAdater](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter),
14 | [localStorage](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
15 | adapter are enchanters.
16 |
17 | ##
18 |
19 | ### [#](#adapter-list) Adapter list
20 |
21 | * [Fetch
22 | adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
23 | * [Axios
24 | adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
25 | * [Delay
26 | adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
27 | * [Local storage
28 | adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
29 | * [Save blob adapter
30 | adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
31 |
32 | ##
33 |
34 | ### [#](#under-the-hood) Under the hood
35 |
36 | _api_ function does not guarantee that XHR method will be called. It just call
37 | adapter with generated config. What magic will be done depends on used adapter.
38 | In case of _localStorageAdapter_ - it will save and get data from
39 | [localStorage](https://developer.mozilla.org/en/docs/Web/API/Window/localStorage).
40 |
41 | ```js
42 | import fetchAdapter from 'bivrost-fetch-adapter';
43 | const fetch = fetchAdapter(options);
44 | ```
45 |
46 | Options depends on the adapter. But in most cases options are:
47 |
48 | * _headers_ - requests headers. NOTE that headers could be also set with
49 | interceptors
50 | * _interceptors_ - adapter interceptors - request / response /error
51 | * [Axios Adapter
52 | Options](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter#adapter-options)
53 | * [Fetch Adapter
54 | Options](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter#configuration)
55 |
56 | Example:
57 |
58 | ```js
59 | import bivrostApi from 'bivrost/http/api'
60 | import fetchAdapter from 'bivrost-fetch-adapter';
61 |
62 | const api = bivrostApi({
63 | protocol: 'http:'
64 | host: 'localhost:3001',
65 | adapter: fetchAdapter()
66 | });
67 |
68 | const createUser = api('POST /user?:hash&:version');
69 |
70 | createUser({
71 | name: 'john_doe',
72 | hash: 'eecab3',
73 | version: 'v1.2.0'
74 | });
75 | ```
76 |
77 | As the result _api_ function will call adapter with generated config and returns
78 | its response.
79 |
80 | ```json
81 | {
82 | "method": "POST",
83 | "path": "/user?hash=eecab3&version=v1.2.0",
84 | "body": {
85 | "name": "John Doe"
86 | }
87 | }
88 | ```
89 |
90 | Next two example will produce same result:
91 |
92 | * Using api function:
93 |
94 | ```js
95 | import bivrostApi from 'bivrost/http/api'
96 | import fetchAdapter from 'bivrost-fetch-adapter';
97 |
98 | const api = bivrostApi({
99 | protocol: 'http:'
100 | host: 'localhost:3001',
101 | adapter: fetchAdapter()
102 | });
103 |
104 | const createUser = api('POST /user?:hash&:version');
105 |
106 | return createUser({
107 | name: 'john_doe',
108 | hash: 'eecab3',
109 | version: 'v1.2.0'
110 | });
111 | ```
112 |
113 | * Using adapter directly:
114 |
115 | ```js
116 | import fetchAdapter from 'bivrost-fetch-adapter';
117 |
118 | const fetch = fetchAdapter();
119 |
120 | return fetch({
121 | method: 'POST',
122 | path: 'http://localhost:3001/user?hash=eecab3&version=v1.2.0',
123 | body: {
124 | name: 'John Doe',
125 | },
126 | });
127 | ```
128 |
--------------------------------------------------------------------------------
/docs/basics/api-function.md:
--------------------------------------------------------------------------------
1 | # Api function
2 |
3 | _Api_ is simple HTTP client wrapper that lets us define REST methods in single line of code.
4 |
5 | ```js
6 | import api from 'bivrost/http/api'
7 | import fetchAdapter from 'bivrost-fetch-adapter';
8 |
9 | const githubApi = api({
10 | protocol: 'https:'
11 | host: 'api.github.com',
12 | adapter: fetchAdapter()
13 | });
14 | ```
15 |
16 | Options:
17 |
18 | * _protocol_ - http protocol. Available values - **http:** an **https:**
19 | * _host_ - server hostname
20 | * _prefix_ - api url prefix. Useful when there are multiple api versions:
21 | * _/user_
22 | * _/v1/user_
23 | * _/v2/user_
24 |
25 | Example:
26 |
27 | ```js
28 | // Create a new repository for the authenticated user
29 | const createRepository = githubApi('POST /user/repos');
30 |
31 | createRepository({
32 | name: 'My new repo',
33 | }).then(response => {});
34 |
35 | // List all public repositories
36 | const getRepositoryList = githubApi('GET /repositories');
37 |
38 | // Stringify url according to params
39 | getRepositoryList.stringify({
40 | since: 364,
41 | }); // https://api.github.com/repositories?since=364
42 |
43 | getRepositoryList({
44 | since: 364, // The integer ID of the last Repository that you've seen.
45 | }).then(response => {});
46 |
47 | // List of users repositories
48 | const getRepositoryInfo = githubApi('GET /repos/:owner/:repo');
49 | getUserRepositories({
50 | owner: 'tuchk4',
51 | repo: 'bivrost',
52 | }).then(response => {});
53 | ```
54 |
55 | ##
56 |
57 | ### [#](#api-definition) Api url definition and placeholders
58 |
59 | * `PUT /user/:id` - _id_ parameter is required. _id_ will be removed from request body. All other parameters will be passed to request body.
60 |
61 | ```js
62 | const updateUser = api('PUT /user/:id');
63 | updateUser({
64 | id: 1,
65 | name: 'Valerii',
66 | });
67 |
68 | // Make PUT request to /user/1
69 | // with request body
70 | // name=Valerii
71 | ```
72 |
73 | * `POST /user?:hash&:version` - _hash_ and _version_ parameters are required. These parameters will be removed from request body and passed to query. All other parameters will be passed to request body.
74 |
75 | ```js
76 | const createUser = api('POST /user?:hash&:version&');
77 |
78 | createUser({
79 | name: 'John Doe',
80 | hash: 'eecab3',
81 | version: 'v1.2.0',
82 | });
83 |
84 | // Make POST request to /user?hash=eecab3&version=v1.2.0
85 | // with request body
86 | // name=tuchk4
87 | ```
88 |
89 | * `GET /users` - for _GET_ requests all parameters are passed to query.
90 |
91 | ```js
92 | const getUsers = api('GET /users');
93 |
94 | getUsers({
95 | group: 'admin',
96 | orderBy: 'loginDate',
97 | });
98 |
99 | // Make GET request to /users?group=admin&orderBy=loginDate
100 | ```
101 |
102 | ##
103 |
104 | ### [#](#adapters) Under the hood: adapters
105 |
106 | * [Adapters](/docs/basics/adapter.md)
107 |
108 | `POST /user?:hash&:version` does not guarantee that XHR POST method will be called. What will be done depends on used adapter - _api_ just calls adapter function with generated config. In case of _localStorage_ adapter - it will save or load data to localStorage.
109 |
--------------------------------------------------------------------------------
/docs/basics/data-source.md:
--------------------------------------------------------------------------------
1 | # Data source
2 |
3 | A DataSource - is a group of several API methods and its configuration. Every method call passes with the configured steps. Result of each step will be passed as arguments to the next step and whole steps chain will be cached if cache is enabled.
4 |
5 | A DataSource provide methods for:
6 |
7 | * steps configuration
8 | * steps executing
9 | * cache manipulations
10 | * request deduplication
11 |
12 | ###
13 |
14 | ### [#](#this-gist) The gist
15 |
16 | ```js
17 | import DataSource from 'bivrost/data/source';
18 | import bivrostApi from 'bivrost/http/api'
19 | import fetchAdapter from 'bivrost-fetch-adapter';
20 |
21 | import { Record } from 'immutable';
22 |
23 | const api = bivrostApi({
24 | host: 'localhost',
25 | adapter: fetchAdapter()
26 | });
27 |
28 | const User = Record({
29 | id: null,
30 | name: ''
31 | });
32 |
33 | class UsersDataSource extends DataSource {
34 | static steps = ['validate', 'api', 'model'];
35 |
36 | static cache = {
37 | loadAll: {
38 | enabled: true,
39 | isGlobal: true,
40 | ttl: 60000
41 | }
42 | };
43 |
44 | static validate = {
45 | update: ({ id }) => {
46 | if (!id) {
47 | throw new Error('"ID" field is required for user update');
48 | }
49 | }
50 | }
51 |
52 | static api = {
53 | loadAll: api('GET /users')
54 | update: api('PUT /user/:id')
55 | }
56 |
57 | static model = {
58 | update: User
59 | }
60 |
61 | update(user) {
62 | return this.invoke('update', user);
63 | }
64 |
65 | loadAll(filters) {
66 | return this.invoke('loadAll', filters);
67 | }
68 | }
69 |
70 | usersDataSource.loadAll({});
71 |
72 | // if previous loadAll call IS NOT finished:
73 | // - will not trigger step chain because of deduplication
74 | usersDataSource.loadAll({});
75 |
76 | // if previous loadAll call IS finished - will not trigger step chain.
77 | // - will not trigger step chain because of enabled cache
78 | usersDataSource.loadAll({});
79 | ```
80 |
81 | ###
82 |
83 | ### [#](#constructor) constructor({ headers, context, steps, options })
84 |
85 | As constructor arguments (all arguments are optional):
86 |
87 | * _headers_ - additional headers
88 | * _steps_ - step sequence
89 | * _context_ - context object. Final context is calculated with:
90 | * DataSource context (passed as argument to DataSource constructor)
91 | * [invoke()](#invoke) context (passed as third param to invoke function)
92 | * _options_ - any data will be saved in data source instance
93 |
94 | ```js
95 | import DataSource from 'bivrost/data/source';
96 |
97 | class AppDataSource extends DataSource {
98 | constructor() {
99 | super({
100 | options: {},
101 | steps: ['validate', 'serialize', 'api'],
102 | context: {},
103 | headers: {},
104 | });
105 | }
106 | }
107 | ```
108 |
109 | ###
110 |
111 | ### [#](#invoke) invoke(method: string, params: object, context: object)
112 |
113 | ```js
114 | DataSource.invoke((method: string), (params: object), (context: object));
115 | ```
116 |
117 | Now Bivrost generates [Auto Invoke Methods](/docs/recipes/auto-invoke-methods.md)
118 |
119 | Invoke is a data source's function that execute _method_ chain and pass _params_ as initial arguments and _context_ as second.
120 |
121 | Method's chain - sequence of steps where each step is a _function_ and its result is an argument for the next step (like lodash's flow).
122 |
123 | _context_ - is useful when you need to pass the same object to all steps. For example - dynamic _params_ validation or step's configuration.
124 |
125 | Steps sequence could configured as second argument to data source constructor or as property _steps_. If there is no method configuration at step - it will be skipped.
126 |
127 | Default steps sequence:
128 |
129 | * _prepare_ - used for request data transformation and serialization
130 | * _api_ - api call
131 | * _process_ - process the response
132 |
133 | ##
134 |
135 | ### [#](#invoke-steps) Invoke steps
136 |
137 | As property:
138 |
139 | ```js
140 | import DataSource from 'bivrost/data/source';
141 |
142 | class AppDataSource extends DataSource {
143 | static steps = ['validate', 'serialize', 'api'];
144 | }
145 | ```
146 |
147 | In this example - there are three steps for _invoke_ function - _validate_, _serialize_ and _api_. Steps sequence and naming - just a developer fantasy and depends on application architecture and requirements.
148 |
149 | ##
150 |
151 | ### [#](#step-configuration) Step configuration
152 |
153 | Step could be configured as _object_ or as _function_.
154 |
155 | * If step is configured as object:
156 |
157 | ```js
158 | class UserDataSource extends DataSource {
159 | static steps = ['api'];
160 |
161 | static api = {
162 | loadAll: api('GET /users'),
163 | };
164 |
165 | loadAll() {
166 | return invoke('loadAll');
167 | }
168 | }
169 | ```
170 |
171 | * If step is configured as function - it will be executed for all methods:
172 |
173 | ```js
174 | class UserDataSource extends DataSource {
175 | static steps = ['api', 'immutable'];
176 |
177 | static immutable = response => Immutable.fromJSON(response);
178 |
179 | static api = {
180 | loadAll: api('GET /users'),
181 | };
182 |
183 | loadAll(params) {
184 | return this.invoke('loadAll', params);
185 | }
186 | }
187 | ```
188 |
189 | ##
190 |
191 | ### [#](#cache) Cache
192 |
193 | Define default cache config for all data source methods:
194 |
195 | ```js
196 | class UserDataSource extends DataSource {
197 | static defaultCache = {
198 | enabled: true,
199 | isGlobal: true,
200 | ttl: 60000,
201 | };
202 | }
203 | ```
204 |
205 | Define cache config for specific data source method:
206 |
207 | ```js
208 | class UserDataSource extends DataSource {
209 | static cache = {
210 | loadAll: {
211 | enabled: true,
212 | isGlobal: true,
213 | ttl: 60000,
214 | },
215 | };
216 | }
217 | ```
218 |
219 | Configuration is almost same as invoke steps. Cache options:
220 |
221 | * _enabled: boolean_ - enable / disable cache for method
222 | * _isGlobal: boolean_ - enable / disable global method cache. If _true_ - cache will be shared between all data source instances.
223 | * _ttl: integer_ - cache lifetime in miliseconds
224 |
225 | Cache methods:
226 |
227 | * _getCacheKey(method: string, params: object)_ - Hook for cache key generating. By default cache key is `JSON.stringify(params)``
228 |
229 | * _clearCache(method: string)_ - Clear method caches. If _method_ argument is not specified - clear all data source caches.
230 |
231 | ##
232 |
233 | ### [#](#debug-logs) Debug logs
234 |
235 | ```js
236 | const appDataSource = new AppDataSource();
237 | appDataSource.enableDebugLogs();
238 | appDataSource.disableDebugLogs();
239 | ```
240 |
241 | If logs are enabled - data source will post messages to console for each step with its parameters. [bows](https://www.npmjs.com/package/bows) is used for logging and thats why `localStorage.debug = true` should be set in your console to see messages.
242 |
243 | 
244 |
--------------------------------------------------------------------------------
/docs/basics/fetch-api-call.md:
--------------------------------------------------------------------------------
1 | # [Fetch API Call](https://github.com/tuchk4/bivrost/tree/master/packages/fetch-api-call)
2 |
3 | Awesome fetch api for browser and nodejs with interceptors easy way to declare
4 | api endpoints. Provide very easy way to setup api for different
5 | backends/services.
6 |
7 | ## [#](#advantages) Advantages
8 |
9 | * Support interceptors
10 | * Support multiple instances wiht its own interceptors
11 | * Easy way to define headers for specific requests
12 | * Declrative api end points
13 |
14 | ```js
15 | const login = api('POST /login');
16 | login({});
17 |
18 | const loadStatistics = api('GET /statistics');
19 | loadStatistics({
20 | filter: {},
21 | });
22 |
23 | const loadUser = api('GET /user/:id');
24 | laodUser({
25 | id: 1,
26 | });
27 | ```
28 |
29 | ### [#](#install) Install
30 |
31 | ```
32 | yarn add fetch-api-call
33 | ```
34 |
35 | ### [#](#api-declaration) Api Declaration
36 |
37 | ```js
38 | import fetchApiCall from 'fetch-api-call';
39 |
40 | const { api } = fetchApiCall({
41 | protocol: process.env.API_PROTOCOL,
42 | host: process.env.API_HOST,
43 | });
44 | ```
45 |
46 | Options:
47 |
48 | * _protocol_ - http protocol. Available values - **http:** an **https:**
49 | * _host_ - server hostname
50 | * _headers_ - requests headers. NOTE that headers could be also set with
51 | interceptors
52 | * _mode_ - more details at -
53 | https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
54 | * _prefix_ - api url prefix. Useful when there are multiple api versions:
55 | * _/user_
56 | * _/v1/user_
57 | * _/v2/user_
58 |
59 | **api** - function to define api methods in single line of code.
60 |
61 | ```js
62 | const apiCall = api('HTTP_METHOD /api/path/:with/:placeholders');
63 | ```
64 |
65 | _apiCall(params = {}, headers = {})_ - function takes two optional params:
66 |
67 | * _params_ - api params. Also is used for building final api entrypoint. More
68 | detauls see at
69 | [Api Url Definition and Placeholders](https://tuchk4.github.io/bivrost/docs/basics/api-function.html#api-definition).
70 | * _headers_ - certain request headers.
71 |
72 | Examples:
73 |
74 | ```js
75 | // user login api
76 | const login = api('POST /login');
77 | login({
78 | username,
79 | password,
80 | });
81 |
82 | // user logout api
83 | const logout = api('POST /logout');
84 | logout();
85 |
86 | // get user api
87 | const getUser = api('GET /user/:id');
88 | getUser({
89 | id: 1,
90 | });
91 | ```
92 |
93 | ### [#](#interceptors) Interceptors
94 |
95 | ```js
96 | import fetchApiCall from 'fetch-api-call';
97 |
98 | const { api, interceptors } = fetchApiCall({
99 | protocol: process.env.API_PROTOCOL,
100 | host: process.env.API_HOST,
101 | });
102 | ```
103 |
104 | **interceptors** methods:
105 |
106 | * _addRequestInterceptor(request)_
107 | * _addResponseInterceptor(response)_
108 | * _addErrorInterceptor(error)_
109 |
110 | Each methods return function to remove interceptor.
111 |
112 | Iterceptors will be called for all request done with _api_.
113 |
114 | ```js
115 | // user login api
116 | const login = api('POST /login');
117 | let removeAcessTokenIterceptor = null;
118 |
119 | login({}).then(({ acessToken }) => {
120 | removeAcessTokenIterceptor = iterceptors.addRequestInterceptor(request => {
121 | request.headers.set('Acess-Token', acessToken);
122 | return request;
123 | });
124 | });
125 |
126 | // user logout api
127 | const logout = api('POST /logout');
128 | logout().then(() => {
129 | removeAcessTokenIterceptor();
130 | });
131 |
132 | // get user api
133 | const getUser = api('GET /user/:id');
134 | getUser({
135 | id: 1,
136 | });
137 | ```
138 |
139 | ### [#](#request-headers) Request Headers
140 |
141 | This is useful when using `fetch-api-call` on NodeJS. In some cases it is not
142 | possible to use interceptors to set auth headers becasue it will work for all
143 | request. We should specify certain request with certain header.
144 |
145 | ```js
146 | const getStatistics = api('GET /statistics');
147 | getStatistics(
148 | {},
149 | {
150 | headers: {
151 | MyCustomHeader: '123zxc',
152 | },
153 | }
154 | );
155 | ```
156 |
157 | ### [#](#multiple-api-instances) Multiple Api Instances
158 |
159 | Very useful if there is microservices backend architecture.
160 |
161 | ```js
162 | import fetchApiCall from 'fetch-api-call';
163 |
164 | const Auth = fetchApiCall({
165 | host: process.env.AUTH_API_HOST,
166 | });
167 |
168 | const Users = fetchApiCall({
169 | host: process.env.USERS_API_HOST,
170 | });
171 |
172 | const Data = fetchApiCall({
173 | host: process.env.DATA_API_HOST,
174 | prefix: 'v2',
175 | });
176 |
177 | const login = Auth.api('POST /login').then(({ accessToken }) => {
178 | Users.interceptors.addRequest(res => {
179 | request.headers.set('Acess-Token', acessToken);
180 | return request;
181 | });
182 |
183 | Data.interceptors.addRequest(res => {
184 | request.headers.set('Acess-Token', acessToken);
185 | return request;
186 | });
187 | });
188 |
189 | const loadStatisitcs = Data.api('GET /statisitcs');
190 | loadStatisitcs({
191 | filter: {
192 | //...
193 | },
194 | });
195 | ```
196 |
197 | ### [#](#custom-setup) Custom Setup
198 |
199 | ```js
200 | import setup from 'fetch-api-call/setup';
201 | import fetchAdapter from 'bivrost-fetch-adapter';
202 |
203 | const createApi = setup({
204 | headers: {
205 | 'Content-Type': 'application/json',
206 | },
207 | mode: 'cors',
208 | adapter: fetchAdapter,
209 | interceptors: {
210 | resposne: res => res,
211 | request: req => req,
212 | error: err => err,
213 | },
214 | });
215 |
216 | const api = createApi({
217 | protocol: process.env.API_PROTOCOL,
218 | host: process.env.API_HOST,
219 | });
220 |
221 | const login = api('POST /login');
222 | ```
223 |
224 | Options:
225 |
226 | * _headers_ - default headers
227 | * _mode_ - default mode. More details at -
228 | https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
229 | * _adapter_ - default HTTP adapter. See more at -
230 | https://tuchk4.github.io/bivrost/docs/adapters.html
231 | * _interceptors_ - initial interceptors. NOTE that this is NOT DEFAULT
232 | interceptors.
233 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Project is open for new ideas and features:
4 |
5 | * new adapters
6 | * new api functions
7 | * data source features
8 | * feedback is very matter
9 |
10 | ## Source code
11 |
12 | Source code is located in `/packages/${package}/src` directory. Before
13 | publishing to npm code is automatically transpiled using
14 | [babel](https://babeljs.io/) and npm `pre-publish` hook.
15 |
16 | ## Development
17 |
18 | Use `npm link` feature in your projects to get up to date local bivrost code and
19 | `yarn dev` to start watchers for code auto transpiling.
20 |
21 | ## Tests
22 |
23 | [jest](https://facebook.github.io/jest/) - painless javascript unit testing.
24 |
25 | * `yarn test` - execute tests + code coverage report
26 | * `yarn test-dev` start file system watchers
27 |
28 | ## Commands
29 |
30 | * `yarn run dev` - start watchers and transpile code with babel
31 | * `yarn run docs:watch` - start local server with
32 | [gitbook](https://toolchain.gitbook.com/) and generate html docs
33 |
34 | ---
35 |
36 | ## Our Pledge
37 |
38 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
39 |
40 | ## Our Standards
41 |
42 | Examples of behavior that contributes to creating a positive environment include:
43 |
44 | * Using welcoming and inclusive language
45 | * Being respectful of differing viewpoints and experiences
46 | * Gracefully accepting constructive criticism
47 | * Focusing on what is best for the community
48 | * Showing empathy towards other community members
49 |
50 | Examples of unacceptable behavior by participants include:
51 |
52 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
53 | * Trolling, insulting/derogatory comments, and personal or political attacks
54 | * Public or private harassment
55 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
56 | * Other conduct which could reasonably be considered inappropriate in a professional setting
57 |
58 | ## Our Responsibilities
59 |
60 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
61 |
62 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
63 |
64 | ## Scope
65 |
66 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
67 |
68 | ## Enforcement
69 |
70 | The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
71 |
72 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
73 |
74 | ## Attribution
75 |
76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
77 |
78 | [homepage]: http://contributor-covenant.org
79 | [version]: http://contributor-covenant.org/version/1/4/
80 |
--------------------------------------------------------------------------------
/docs/recipes/README.md:
--------------------------------------------------------------------------------
1 | # Table of content
2 |
3 | * [Api enchantment](/docs/recipes/api-enchantment.md)
4 | * [Auto Invoke Methods](/docs/recipes/auto-invoke-methods.md)
5 | * [Data source immutable output](/docs/recipes/data-source-immutable.md)
6 | * [Data source type checking with tcomb](/docs/recipes/data-source-type-checking.md)
7 | * [CRUD Data source](/docs/recipes/generate-crud-methods.md)
8 | * [File upload](/docs/recipes/file-upload.md)
9 | * [Fixtures](/docs/recipes/fixtures.md)
10 | * [Default api response](/docs/recipes/default-response.md)
11 | * [Prepare request and process response](/docs/recipes/prepare-process.md)
12 | * [Data validation](/docs/recipes/validation.md)
13 |
--------------------------------------------------------------------------------
/docs/recipes/api-enchantment.md:
--------------------------------------------------------------------------------
1 | # Api enchantment
2 |
3 | ##
4 |
5 | ### [#](#api-for-services) Create api for popular services
6 |
7 | For example twitter api:
8 |
9 | ```js
10 | // twitter-api.js
11 | import api from 'bivrost/http/api';
12 |
13 | export default (authToken, config = {}) => {
14 | const adapter = fetchAdapter();
15 |
16 | let twitterApi = api({
17 | protocol: 'https:',
18 | host: 'api.twitter.com'
19 | prefix: '1.1',
20 | ...config,
21 | adapter: fetchAdapter({
22 | headers: {
23 | ...config.headers,
24 | TwitterOAuth: authToken
25 | }
26 | });
27 | });
28 |
29 | // Returns the 20 most recent mentions (tweets containing a users’s @screen_name)
30 | // for the authenticating user.
31 | twitterApi.mentionsTimeline = apiInstance('GET /statuses/mentions_timeline');
32 |
33 | // Returns a single Tweet, specified by the id parameter.
34 | twitterApi.show = apiInstance('GET /statuses/show/:id');
35 |
36 | return twitterApi;
37 | }
38 | ```
39 |
40 | Usage:
41 |
42 | ```js
43 | import createTwitterApi from 'twitter-api';
44 |
45 | const twitterApi = createTwitterApi(AUTH_TOKEN);
46 |
47 | // manual call
48 | twitterApi('GET /statuses/mentions_timeline');
49 |
50 | // shortcut call
51 | twitterApi.mentionsTimeline();
52 |
53 | twitterApi.show({
54 | id: TWEET_ID,
55 | });
56 | ```
57 |
58 | Same could be implemented for
59 |
60 | * Github - https://developer.github.com/v3/
61 | * Open weather api - https://openweathermap.org/api
62 | * Slack chat - https://api.slack.com/
63 | * Reddit - https://www.reddit.com/dev/api/
64 | * Twitter - https://dev.twitter.com/rest/public
65 | * etc.
66 |
67 | ##
68 |
69 | ### [#](#group-api-backend) Group api by backend services
70 |
71 | Especially it is very useful for microservice architecture.
72 |
73 | * _src/data/api/auth.js_ - api for auth service service
74 | * _src/data/api/users.js_ - api for application users service
75 | * _src/data/api/notifications.js_ - api for notification service
76 |
77 | Also such api functions could be easily shared between other applications.
78 |
--------------------------------------------------------------------------------
/docs/recipes/auto-invoke-methods.md:
--------------------------------------------------------------------------------
1 | # Auto Invoke Methods
2 |
3 | ```js
4 | import DataSource from 'bivrost/data/source';
5 |
6 | class UsersDataSource extends DataSource {
7 | static api = {
8 | loadAll: api('GET /users'),
9 | load: api('GET /users/:id'),
10 | create: api('POST /users'),
11 | };
12 | }
13 |
14 | const usersDataSource = new UsersDataSource();
15 |
16 | // Generated methods:
17 | usersDataSource.invokeLoad({});
18 | usersDataSource.invokeLoadAll({});
19 | usersDataSource.invokeCreate({});
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/recipes/data-source-immutable.md:
--------------------------------------------------------------------------------
1 | # Data source immutable output
2 |
3 | [Immutable](https://facebook.github.io/immutable-js/) - Immutable.js provides
4 | many Persistent Immutable data structures including: List, Stack, Map,
5 | OrderedMap, Set, OrderedSet and Record.
6 |
7 | Make each methods response immutable:
8 |
9 | ```js
10 | class UserDataSource extends DataSource {
11 | static steps = ['api', 'immutable'];
12 |
13 | static immutable = response => Immutable.fromJSON(response);
14 |
15 | static api = {
16 | list: api('GET /users'),
17 | };
18 |
19 | loadAll(params) {
20 | return this.invoke('list', params);
21 | }
22 | }
23 | ```
24 |
25 | Use Immutable [Records](https://facebook.github.io/immutable-js/docs/#/Record)
26 | and [Maps](https://facebook.github.io/immutable-js/docs/#/Map):
27 |
28 | ```js
29 | import { Record, Map } from 'immutable';
30 |
31 | const User = Record({
32 | id: null,
33 | name: '',
34 | });
35 |
36 | class UserDataSource extends DataSource {
37 | static steps = ['validate', 'api', 'model'];
38 |
39 | static api = {
40 | update: api('PUT /users/:id'),
41 | loadAll: api('GET /users'),
42 | };
43 |
44 | static model = {
45 | update: response => new User(response),
46 |
47 | loadAll: response => {
48 | return response.reduce((map, user) => {
49 | map.set({
50 | [user.id]: user,
51 | });
52 |
53 | return map;
54 | }, Map());
55 | },
56 | };
57 |
58 | update(user) {
59 | return this.invoke('update', user);
60 | }
61 | }
62 | ```
63 |
--------------------------------------------------------------------------------
/docs/recipes/data-source-type-checking.md:
--------------------------------------------------------------------------------
1 | # Data source with type checking
2 |
3 | [tcomb](https://github.com/gcanti/tcomb) - is a library for Node.js and the
4 | browser which allows you to check the types of JavaScript values at runtime with
5 | a simple and concise syntax. It's great for Domain Driven Design and for adding
6 | safety to your internal code.
7 |
8 | Using _tcomb_ we could check _api_ input and output data in realtime and disable
9 | it for production env.
10 |
11 | ```js
12 | import DataSource from 'bivrost/data/source';
13 | import tcomb from 'tcomb';
14 |
15 | class UserDataSource extends DataSource {
16 | static steps = ['input', 'api', 'output'];
17 |
18 | static input = {
19 | user: tcomb.struct({
20 | id: tcomb.Number,
21 | }),
22 | };
23 |
24 | static output = {
25 | user: tcomb.struct({
26 | id: tcomb.Number,
27 | name: tcomb.String,
28 | lastname: tcomb.maybe(tcomb.String),
29 | }),
30 | };
31 |
32 | static api = {
33 | user: api('GET /user/:id'),
34 | };
35 |
36 | loadUser({ id }) {
37 | return this.invoke('user', {
38 | id,
39 | });
40 | }
41 | }
42 |
43 | const usersDataSource = new UserDataSource();
44 |
45 | // OK
46 | usersDataSource.loadUser({
47 | id: 1,
48 | });
49 |
50 | // Throw exception that ID attribute must be a Number.
51 | usersDataSource.loadUser({
52 | id: '1',
53 | });
54 | ```
55 |
--------------------------------------------------------------------------------
/docs/recipes/default-response.md:
--------------------------------------------------------------------------------
1 | # Default api response
2 |
3 | NOTE: This is wrong to define default output because this is the server
4 | liability
5 |
6 | ```js
7 | class UsersDataSource extends DataSource {
8 | static steps = ['api', 'defaultResponse'];
9 |
10 | static api = {
11 | list: api('GET /users'),
12 | };
13 |
14 | static defaultResponse = {
15 | list: users => (users ? users : []),
16 | };
17 |
18 | loadAll(params) {
19 | return this.invoke('list', params);
20 | }
21 | }
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/recipes/file-upload.md:
--------------------------------------------------------------------------------
1 | # File upload
2 |
3 | Use [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) to
4 | upload files.
5 |
6 | ```js
7 | import bivrostApi from 'bivrost/http/api';
8 | import DataSource from 'bivrost/data/source';
9 | import fetchAdapter from 'bivrost-fetch-adapter';
10 |
11 | const api = bivrostApi({
12 | host: 'localhost:3001',
13 | adapter: fetchAdapter(),
14 | });
15 |
16 | class ImagesDataSource extends DataSource {
17 | static api = {
18 | save: api('POST /images', {
19 | headers: {
20 | 'content-type': 'application/x-www-form-urlencoded',
21 | },
22 | }),
23 | };
24 |
25 | saveImage(images) {
26 | return this.invoke('save', images);
27 | }
28 | }
29 |
30 | const formData = new FormData();
31 | formData.append('image', imageUploadInput.files[0]);
32 |
33 | const imagesDataSource = new ImagesDataSource();
34 | imagesDataSource.saveImage(formData).then(response => {
35 | console.log('images are saved');
36 | });
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/recipes/fixtures.md:
--------------------------------------------------------------------------------
1 | # Fixtures
2 |
3 | Change invoke steps according to current env
4 |
5 | ```js
6 | import ALL_USERS_FIXTURE form 'fixtures/all-users';
7 |
8 | class UserDataSource extends DataSource {
9 |
10 | static steps = process.env.NODE_ENV === 'development'
11 | ? ['fixture', 'immutable']
12 | : ['api', 'immutable'];
13 |
14 | immutable = response => Immutable.fromJSON(response);
15 |
16 | static fixture = {
17 | loadAll: ALL_USERS_FIXTURE
18 | };
19 |
20 | static api = {
21 | loadAll: api('GET /users')
22 | };
23 |
24 | loadAll(params) {
25 | return this.invoke('loadAll', params);
26 | }
27 | }
28 | ```
29 |
--------------------------------------------------------------------------------
/docs/recipes/generate-crud-methods.md:
--------------------------------------------------------------------------------
1 | # Generate CRUD methods
2 |
3 | * [Create data source with CRUD method](#create-crud-ds)
4 | * [Extends CRUD data source](#extends-crud-ds)
5 |
6 | Crud data source factory:
7 |
8 | ```js
9 | import DataSource from 'bivrost/data/source';
10 |
11 | export default (entity, api) => {
12 | const normalized = `${entity.toLowerCase()}s`;
13 |
14 | return class CrudDataSource extends DataSource {
15 | static api = {
16 | loadAll: api(`GET /${normalized}`),
17 | load: api(`GET /${normalized}/:id`),
18 | create: api(`POST /${normalized}`),
19 | update: api(`PUT /${normalized}/:id`),
20 | delete: api(`DELETE /${normalized}/:id`),
21 | };
22 |
23 | load({ id }) {
24 | return this.invoke('load');
25 | }
26 |
27 | loadAll({ filters }) {
28 | return this.invoke('loadAll', { filters });
29 | }
30 |
31 | create({ user }) {
32 | return this.invoke('create', { user });
33 | }
34 |
35 | update({ user }) {
36 | return this.invoke('update', { user });
37 | }
38 |
39 | delete({ id }) {
40 | return this.invoke('delete', { id });
41 | }
42 | };
43 | };
44 | ```
45 |
46 | ##
47 |
48 | ### [#](#create-crud-ds) Create data source with CRUD methods
49 |
50 | ```js
51 | import bivrostApi from 'bivrost/http/api'
52 | import fetchAdapter from 'bivrost-fetch-adapter';
53 | import createCrudDataSource from './create-crud-datasource.js';
54 |
55 | const api = bivrostApi({
56 | host: 'localhost',
57 | adapter: fetchAdapter()
58 | });
59 |
60 | const UsersDataSource = createCrudDataSource('user', api)
61 | const users = new UsersDataSource();
62 |
63 | users.loadAll(); // GET /users
64 |
65 | users.load({
66 | id: 1
67 | }); // GET /users/1
68 |
69 | users.create({
70 | name: 'John Doe'
71 | }); // POST /users
72 |
73 |
74 | users.update({
75 | id: 1
76 | name: 'John Doe'
77 | }); // PUT /users/1
78 |
79 | users.delete({
80 | id: 1
81 | }); // DELETE /users/1
82 | ```
83 |
84 | ##
85 |
86 | ### [#](#extends-crud-ds) Extends CRUD data source
87 |
88 | ```js
89 | import tcomb from 'tcomb';
90 |
91 | import bivrostApi from 'bivrost/http/api';
92 | import fetchAdapter from 'bivrost-fetch-adapter';
93 | import createCrudDataSource from './create-crud-datasource.js';
94 |
95 | const api = bivrostApi({
96 | host: 'localhost',
97 | adapter: fetchAdapter(),
98 | });
99 |
100 | const UsersDataSource = createCrudDataSource('user', api);
101 |
102 | class EnchantedUsers extends UsersDataSource {
103 | static api = {
104 | ...UsersDataSource.api,
105 | ping: api('GET /users/:id/ping'),
106 | };
107 |
108 | ping({ id }) {
109 | return this.invoke('ping', { id });
110 | }
111 |
112 | create({ user }) {
113 | return super.create({ user }).then(user => {
114 | console.log('user was create');
115 |
116 | return user;
117 | });
118 | }
119 | }
120 | ```
121 |
--------------------------------------------------------------------------------
/docs/recipes/prepare-process.md:
--------------------------------------------------------------------------------
1 | # Prepare request and process response
2 |
3 | Response processing in some cases are important to get more usable format
4 | according to current application.
5 |
6 | For example - auto add users avatars using http://placehold.it service.
7 |
8 | ```js
9 | class UsersDataSource extends DataSource {
10 | static steps = ['api', 'process'];
11 |
12 | static api = {
13 | list: api ('GET /users')
14 | };
15 |
16 | static process = {
17 | list: users => users.map(user => {
18 | return {
19 | ...user,
20 | avatar: user.avatar ? user.avatar : 'http://placehold.it/30x30'
21 | }
22 | });
23 | };
24 |
25 | loadAll(params) {
26 | return this.invoke('list', params);
27 | }
28 | }
29 | ```
30 |
31 | Prepare requests is very useful for requests params transformation
32 | (serialization): covert form camelCase to snake_case, rename some props, auto
33 | adding props etc.
34 |
35 | ```js
36 | class UsersDataSource extends DataSource {
37 | static steps = ['prepare', 'api', 'process'];
38 |
39 | static prepare = {
40 | list: params => ({
41 | l: [params.limit, params.offset],
42 | o: params.order,
43 | }),
44 | };
45 |
46 | static api = {
47 | list: api('GET /users'),
48 | };
49 |
50 | loadAll(params) {
51 | return this.invoke('list', params);
52 | }
53 | }
54 | const usersDataSource = new UsersDataSource();
55 | usersDataSource.loadAll({
56 | limit: 100,
57 | offset: 2,
58 | order: 'id',
59 | }); // GET /users?l=100,2&o=id
60 | ```
61 |
--------------------------------------------------------------------------------
/docs/recipes/validation.md:
--------------------------------------------------------------------------------
1 | # Validation
2 |
3 | Throw error or return rejected promise at any step. There are some useful
4 | libraries for data validation:
5 |
6 | * [tcomb](https://github.com/gcanti/tcomb) - Type checking and DDD. There is
7 | additional example with tcomb -
8 | [Data source type checking with tcomb](/docs/recipes/data-source-type-checking.md)
9 | * [Joi](https://www.npmjs.com/package/joi) - Obejct schema validation
10 | * [Validator](https://github.com/chriso/validator.js) - String validation and
11 | sanitization
12 |
13 | ```js
14 | import ALL_USERS_FIXTURE form 'fixtures/all-users';
15 |
16 | class UserDataSource extends DataSource {
17 |
18 | static steps = ['validation', 'api'];
19 |
20 | static validation = {
21 | create: user => {
22 | if (!user.id) {
23 | // throw new Error('"id" is required');
24 | return Promise.reject('"id" is required');
25 | }
26 |
27 | return user;
28 | }
29 | }
30 |
31 | static api = {
32 | loadAll: api('GET /users'),
33 | create: api('POST /users')
34 | };
35 |
36 | loadAll(params) {
37 | return this.invoke('loadAll', params);
38 | }
39 |
40 | create(user) {
41 | return this.invoke('create', user);
42 | }
43 | }
44 | ```
45 |
--------------------------------------------------------------------------------
/docs/testing.md:
--------------------------------------------------------------------------------
1 | ## Mock data source steps and methods
2 |
3 | Suggest to use [jest](https://facebook.github.io/jest/) for testing. It is
4 | really painless javascript unit testing library with code coverage and perfects
5 | mocks.
6 |
7 | Example data source:
8 |
9 | ```js
10 | export default class UsersDataSource extends DataSource {
11 | static steps = ['serialize', 'api'];
12 |
13 | static serialize = {
14 | loadAll: ({ groupId }) => ({
15 | g: groupId,
16 | }),
17 | };
18 |
19 | static api = {
20 | loadAll: api('GET /users'),
21 | };
22 |
23 | loadAll(props) {
24 | return this.invoke(props);
25 | }
26 | }
27 | ```
28 |
29 | Example data source test:
30 |
31 | ```js
32 | import UsersDataSource from 'data/sources/users';
33 | import mockDataSource from 'bivrost/utils/mock-data-source';
34 |
35 | describe('Datasource steps mock', () => {
36 | let ds = null;
37 |
38 | beforeEach(() => {
39 | ds = new UsersDataSource();
40 | });
41 |
42 | it('should mock ds steps', () => {
43 | const mocks = mockDataSource(ds, step => jest.fn(step));
44 |
45 | ds
46 | .loadAll({
47 | groupId: 5,
48 | })
49 | .then(() => {
50 | expect(mocks.serialize.loadAll.mock.calls.length).toEqual(1);
51 | expect(mocks.serialize.loadAll.mock.calls[0][0]).toEqual({
52 | groupId: 5,
53 | });
54 |
55 | expect(mocks.api.loadAll.mock.calls[0][0]).toEqual({
56 | g: 5,
57 | });
58 | });
59 | });
60 | });
61 | ```
62 |
--------------------------------------------------------------------------------
/example/weather-axios-tcomb/index.js:
--------------------------------------------------------------------------------
1 | import t from 'tcomb';
2 | import axios from 'axios';
3 | import axiosAdapter from 'bivrost-axios-adapter';
4 | import api from 'bivrost/http/api';
5 | import DataSource from 'bivrost/data/source';
6 | import { TWeatherForecast, TWeatherCurrent } from './model';
7 |
8 | //setup Api client
9 | const weatherApi = api({
10 | base: 'http://api.openweathermap.org', //server
11 | prefix: '/data/2.5/', //path to API root
12 | adapter: axiosAdapter(axios), //HTTP client adapter
13 | });
14 |
15 | class WeatherDataSource extends DataSource {
16 | steps = ['input', 'api', 'output'];
17 |
18 | cache = {
19 | dailyForecast: {
20 | enabled: true, //The results of `dailyForecast` method call will be cached
21 | ttl: 60 * 60 * 1000, //for an hour.
22 | isGlobal: true, //Share the same cache for all instances of WeatherDataSource. (default - no)
23 | },
24 | current: {
25 | enabled: true,
26 | ttl: 15 * 60 * 1000,
27 | isGlobal: true,
28 | },
29 | };
30 |
31 | input = {
32 | dailyForecast: t.struct({ q: t.Str }), //input data is checked against tcomb structure
33 | current: t.struct({ q: t.Str }),
34 | };
35 |
36 | api = {
37 | dailyForecast: weatherApi('GET /forecast/daily'),
38 | current: weatherApi('GET /weather'),
39 | };
40 |
41 | output = {
42 | dailyForecast: TWeatherForecast, //output data is checked against tcomb structure
43 | current: TWeatherCurrent,
44 | };
45 |
46 | dailyForecast(city) {
47 | return this.invoke('dailyForecast', { q: city });
48 | }
49 |
50 | current(city) {
51 | return this.invoke('current', { q: city });
52 | }
53 | }
54 |
55 | //create dataSource
56 | var weather = new WeatherDataSource();
57 |
58 | //call datasource methods
59 | function printWeatherForecast() {
60 | return weather
61 | .dailyForecast('Kiev')
62 | .then(forecast => console.log('WEATHER FORECAST:', forecast))
63 | .catch(error => console.error(error));
64 | }
65 |
66 | function printCurrentWeather() {
67 | return weather
68 | .current('Kiev')
69 | .then(current => console.log('CURRENT WEATHER:', current))
70 | .catch(error => console.error(error));
71 | }
72 |
73 | printWeatherForecast()
74 | .then(printCurrentWeather)
75 | .catch(error => console.error(error));
76 |
--------------------------------------------------------------------------------
/example/weather-axios-tcomb/model.js:
--------------------------------------------------------------------------------
1 | import t from 'tcomb';
2 |
3 | const MaybeNum = t.maybe(t.Num);
4 |
5 | const TWeatherInfo = t.struct({
6 | id: t.Num,
7 | main: t.Str,
8 | description: t.Str,
9 | icon: t.Str,
10 | });
11 |
12 | const TForecastItem = t.struct({
13 | dt: t.Num,
14 | temp: t.struct({
15 | day: MaybeNum,
16 | min: MaybeNum,
17 | max: MaybeNum,
18 | night: MaybeNum,
19 | eve: MaybeNum,
20 | morn: MaybeNum,
21 | }),
22 | pressure: MaybeNum,
23 | humidity: MaybeNum,
24 | weather: t.list(TWeatherInfo),
25 | speed: MaybeNum,
26 | deg: MaybeNum,
27 | clouds: MaybeNum,
28 | rain: MaybeNum,
29 | });
30 |
31 | const TLatLon = t.struct({
32 | lon: t.Num,
33 | lat: t.Num,
34 | });
35 |
36 | const TCity = t.struct({
37 | id: t.Num,
38 | name: t.Str,
39 | coord: TLatLon,
40 | country: t.Str,
41 | population: t.Num,
42 | });
43 |
44 | export const TWeatherForecast = t.struct({
45 | cod: t.Str,
46 | message: t.Num,
47 | city: TCity,
48 | cnt: t.Num,
49 | list: t.list(TForecastItem),
50 | });
51 |
52 | export const TWeatherCurrent = t.struct({
53 | coord: TLatLon,
54 | weather: t.list(TWeatherInfo),
55 | sys: t.struct({
56 | message: t.Num,
57 | country: t.Str,
58 | sunrise: t.Num,
59 | sunset: t.Num,
60 | }),
61 | base: t.Str,
62 |
63 | main: t.struct({
64 | temp: MaybeNum,
65 | temp_min: MaybeNum,
66 | temp_max: MaybeNum,
67 | pressure: MaybeNum,
68 | sea_level: MaybeNum,
69 | grnd_level: MaybeNum,
70 | humidity: MaybeNum,
71 | }),
72 | wind: t.Obj,
73 | clouds: t.Obj,
74 | rain: t.Obj,
75 | dt: t.Num,
76 | id: t.Num,
77 | name: t.Str,
78 | cod: t.Num,
79 | });
80 |
--------------------------------------------------------------------------------
/example/weather-fetch-adapter/index.js:
--------------------------------------------------------------------------------
1 | import fetchAdapter from 'bivrost-fetch-adapter'; // native browser's fetch method
2 | import api from 'bivrost/http/api';
3 | import DataSource from 'bivrost/data/source';
4 |
5 | //setup Api client
6 | const weatherApi = api({
7 | base: 'http://api.openweathermap.org', //server
8 | prefix: '/data/2.5/', //path to API root
9 | adapter: fetchAdapter(), //HTTP client adapter
10 | });
11 |
12 | class WeatherDataSource extends DataSource {
13 | steps = ['input', 'api', 'output'];
14 |
15 | cache = {
16 | dailyForecast: {
17 | enabled: true, //The results of `dailyForecast` method call will be cached
18 | ttl: 60 * 60 * 1000, //for an hour.
19 | isGlobal: true, //Share the same cache for all instances of WeatherDataSource. (default - no)
20 | },
21 | current: {
22 | enabled: true,
23 | ttl: 15 * 60 * 1000,
24 | isGlobal: true,
25 | },
26 | };
27 |
28 | api = {
29 | dailyForecast: weatherApi('GET /forecast/daily'),
30 | current: weatherApi('GET /weather'),
31 | };
32 |
33 | dailyForecast(city) {
34 | return this.invoke('dailyForecast', { q: city });
35 | }
36 |
37 | current(city) {
38 | return this.invoke('current', { q: city });
39 | }
40 | }
41 |
42 | //create dataSource
43 | var weather = new WeatherDataSource();
44 |
45 | //call datasource methods
46 | function printWeatherForecast() {
47 | return weather
48 | .dailyForecast('Kiev')
49 | .then(forecast => console.log('WEATHER FORECAST:', forecast))
50 | .catch(error => console.error(error));
51 | }
52 |
53 | function printCurrentWeather() {
54 | return weather
55 | .current('Kiev')
56 | .then(current => console.log('CURRENT WEATHER:', current))
57 | .catch(error => console.error(error));
58 | }
59 |
60 | printWeatherForecast()
61 | .then(printCurrentWeather)
62 | .catch(error => console.error(error));
63 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*"
4 | ],
5 | "version": "independent"
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost",
3 | "version": "1.0.0",
4 | "description":
5 | "Bivrost allows to organize a simple interface to asynchronous APIs.",
6 | "keywords": ["data-source", "api", "fetch", "axios", "local-storage"],
7 | "author": "tuchk4 ",
8 | "scripts": {
9 | "dev": "lerna run dev --stream --parallel",
10 | "test": "jest",
11 | "test-dev": "jest --watch",
12 | "docs:clean": "rimraf _book",
13 | "docs:prepare": "gitbook install",
14 | "docs:build":
15 | "npm run docs:prepare && rm -rf _book && ./node_modules/.bin/gitbook build",
16 | "docs:watch": "npm run docs:prepare && ./node_modules/.bin/gitbook serve",
17 | "docs:publish":
18 | "npm run docs:build && cd _book && git init && git commit --allow-empty -m 'Update docs' && git checkout -b gh-pages && git add . && git commit -am 'Update docs' && git push git@github.com:tuchk4/bivrost gh-pages --force",
19 | "precommit": "lint-staged",
20 | "format":
21 | "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules|coverage)/**/*.js'"
22 | },
23 | "jest": {
24 | "rootDir": "./packages",
25 | "setupFiles": ["./bivrost/__tests__/localstorage-mock.js"],
26 | "modulePathIgnorePatterns": [
27 | "./bivrost/__tests__/http-bin-api.js",
28 | "./bivrost/__tests__/localstorage-mock.js"
29 | ]
30 | },
31 | "repository": {
32 | "type": "git",
33 | "url": "git+https://github.com/tuchk4/bivrost.git"
34 | },
35 | "devDependencies": {
36 | "babel-cli": "^6.26.0",
37 | "babel-jest": "^21.2.0",
38 | "babel-polyfill": "^6.26.0",
39 | "babel-preset-es2015": "^6.3.13",
40 | "babel-preset-stage-0": "^6.3.13",
41 | "gitbook-cli": "^2.3.2",
42 | "gitbook-plugin-ga": "^2.0.0",
43 | "husky": "^0.14.3",
44 | "jest": "^21.2.1",
45 | "lerna": "^3.4.1",
46 | "lint-staged": "^5.0.0",
47 | "prettier": "^1.8.2"
48 | },
49 | "license": "ISC",
50 | "bugs": {
51 | "url": "https://github.com/frankland/bivrost/issues"
52 | },
53 | "homepage": "https://github.com/frankland/bivrost",
54 | "lint-staged": {
55 | "*.js": ["prettier --trailing-comma es5 --single-quote --write", "git add"]
56 | },
57 | "dependencies": {
58 | "gitbook-plugin-anker-enable": "0.0.4",
59 | "gitbook-plugin-edit-link": "^2.0.2",
60 | "gitbook-plugin-github": "^2.0.0",
61 | "gitbook-plugin-prism": "^2.3.0"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /node_modules
5 |
6 | /coverage
7 | /index.js
8 |
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /__tests__
5 | /npm-debug.log
6 | /node_modules
7 | /src
8 | /test
9 | /coverage
10 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/README.md:
--------------------------------------------------------------------------------
1 | # Bivrost axios adapter
2 |
3 | [](https://travis-ci.org/tuchk4/bivrost-axios-adapter)
4 | [](https://npmjs.org/package/bivrost-axios-adapter)
5 |
6 | Bivrost Adapter for [axios](https://github.com/mzabriskie/axios) function.
7 |
8 | ```
9 | npm i --save axios bivrost-axios-adapter
10 | ```
11 |
12 | ### Usage
13 |
14 | With [Bivrost](https://github.com/tuchk4/bivrost):
15 |
16 | ```js
17 | import DataSource from 'bivrost/data/source';
18 | import bivrostApi from 'bivrost/http/api';
19 | import axiosAdapter from 'bivrost-axios-adapter';
20 |
21 | const api = bivrostApi({
22 | host: 'localhost',
23 | adapter: axiosAdapter(),
24 | });
25 |
26 | class UsersDataSource extends DataSource {
27 | static api = {
28 | loadAll: api('GET /users'),
29 | };
30 |
31 | loadUsers(filters) {
32 | return this.invoke('loadAll', filters);
33 | }
34 | }
35 | ```
36 |
37 | Direct calls:
38 |
39 | ```js
40 | import axiosAdapter from 'bivrost-axios-adapter';
41 |
42 | const api = axiosAdapter({});
43 |
44 | const options = {
45 | method: 'GET',
46 | query: {
47 | groupId: 10,
48 | },
49 | headers: {
50 | 'Content-Type': 'application/json',
51 | },
52 | };
53 |
54 | api('/users', options)
55 | .then(data => {
56 | // ...
57 | })
58 | .catch(response => {
59 | // ...
60 | });
61 | ```
62 |
63 | ### Adapter options
64 |
65 | ```js
66 | import axiosAdapter from 'bivrost-axios-adapter';
67 |
68 | const api = axiosAdapter({
69 | // default options for each request with created adapter
70 | options: {
71 | withCredentials: false,
72 | xsrfCookieName: 'XSRF-TOKEN',
73 | },
74 | });
75 | ```
76 |
77 | Available options:
78 |
79 | * transformRequest - allows changes to the request data before it is sent to the
80 | server
81 | * transformResponse - allows changes to the response data to be made before
82 | * headers - are custom headers to be sent
83 | * paramsSerializer - is an optional function in charge of serializing `params`
84 | * timeout - specifies the number of milliseconds before the request times out
85 | * withCredentials - indicates whether or not cross-site Access-Control requests
86 | * auth - indicates that HTTP Basic auth should be used, and supplies credentials
87 | * responseType - indicates the type of data that the server will respond with
88 | * xsrfCookieName - is the name of the cookie to use as a value for xsrf token
89 | * xsrfHeaderName - is the name of the http header that carries the xsrf token
90 | value
91 |
92 | More details - https://www.npmjs.com/package/axios#request-api
93 |
94 | ### Interceptors
95 |
96 | The main difference between axios-adapter interceptors and axios
97 | interceptorsis - is that interceptors could be specified for each adpater
98 | separately.
99 |
100 | ```js
101 | import axiosAdapter from 'bivrost-axios-adapter';
102 |
103 | const api = axiosAdapter({
104 | interceptors: {
105 | request: request => {
106 | // ...
107 | },
108 |
109 | response: response => {
110 | // ...
111 | },
112 |
113 | error: response => {
114 | // ...
115 | },
116 | },
117 | });
118 | ```
119 |
120 | Interceptor example:
121 |
122 | ```js
123 | import axiosAdapter from 'bivrost-axios-adapter';
124 |
125 | const api = axiosAdapter({
126 | interceptors: {
127 | response: httpResponse => httpResponse.data,
128 | error: httpErrorResponse => Promise.reject(httpErrorResponse),
129 | },
130 | });
131 | ```
132 |
133 | ---
134 |
135 | [Bivrost](https://github.com/tuchk4/bivrost) allows to organize a simple
136 | interface to asyncronous APIs.
137 |
138 | #### Other adapters
139 |
140 | * [Fetch Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
141 | * [Axios Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
142 | * [Delay Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
143 | * [LocalStorage Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
144 | * [Save Blob Adapter Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
145 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/__tests__/adapter.js:
--------------------------------------------------------------------------------
1 | import axiosAdapter from '../src';
2 |
3 | const httpbin = 'http://httpbin.org';
4 |
5 | describe('Adapter', () => {
6 | const requestInterceptor = jest.fn(req => {
7 | return req;
8 | });
9 |
10 | const responsetInterceptor = jest.fn(res => {
11 | return res;
12 | });
13 |
14 | const axiosApi = axiosAdapter({
15 | interceptors: {
16 | request: requestInterceptor,
17 | response: responsetInterceptor,
18 | },
19 | });
20 |
21 | const query = {
22 | foo: 'bar',
23 | };
24 |
25 | it('GET request', () => {
26 | return axiosApi(`${httpbin}/get`, {
27 | method: 'GET',
28 | query: {
29 | ...query,
30 | },
31 | }).then(response => {
32 | expect(response.data.args).toEqual({
33 | ...query,
34 | });
35 |
36 | expect(requestInterceptor.mock.calls.length).toEqual(1);
37 | expect(responsetInterceptor.mock.calls.length).toEqual(1);
38 | });
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-axios-adapter",
3 | "version": "3.0.2",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "axios": {
8 | "version": "0.17.1",
9 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz",
10 | "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=",
11 | "requires": {
12 | "follow-redirects": "^1.2.5",
13 | "is-buffer": "^1.1.5"
14 | }
15 | },
16 | "debug": {
17 | "version": "3.1.0",
18 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
19 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
20 | "requires": {
21 | "ms": "2.0.0"
22 | }
23 | },
24 | "follow-redirects": {
25 | "version": "1.4.1",
26 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz",
27 | "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==",
28 | "requires": {
29 | "debug": "^3.1.0"
30 | }
31 | },
32 | "is-buffer": {
33 | "version": "1.1.6",
34 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
35 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
36 | },
37 | "ms": {
38 | "version": "2.0.0",
39 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
40 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-axios-adapter",
3 | "version": "3.2.2",
4 | "description": "Bivrost adapter for axios",
5 | "keywords": [
6 | "bivrost",
7 | "data-source",
8 | "api",
9 | "axios",
10 | "local-storage",
11 | "fetch-api-call",
12 | "response",
13 | "request",
14 | "call steps"
15 | ],
16 | "main": "index.js",
17 | "scripts": {
18 | "prepublish": "../../node_modules/.bin/babel -d ./ ./src",
19 | "dev": "../../node_modules/.bin/babel -d ./ ./src --watch"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/tuchk4/bivrost.git"
24 | },
25 | "author": "tuchk4 ",
26 | "license": "ISC",
27 | "bugs": {
28 | "url": "https://github.com/tuchk4/bivrost/issues"
29 | },
30 | "homepage": "hhttps://github.com/tuchk4/bivrost",
31 | "dependencies": {
32 | "axios": "^0.17.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/src/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const DEFAULT_ADAPTER_OPTIONS = {};
4 |
5 | const DEFAULT_ADAPTER_INTERCEPTORS = {};
6 |
7 | export default function fetchAdapter({ interceptors = {}, ...options } = {}) {
8 | const adapterOptions = {
9 | ...DEFAULT_ADAPTER_OPTIONS,
10 | ...options,
11 | };
12 |
13 | const adapterIntinterceptors = {
14 | ...DEFAULT_ADAPTER_INTERCEPTORS,
15 | ...interceptors,
16 | };
17 |
18 | if (adapterIntinterceptors.request) {
19 | axios.interceptors.request.use(adapterIntinterceptors.request);
20 | }
21 |
22 | const responseInterceptors = [];
23 | if (adapterIntinterceptors.response) {
24 | responseInterceptors.push(adapterIntinterceptors.response);
25 | }
26 |
27 | if (adapterIntinterceptors.error) {
28 | if (!responseInterceptors.length) {
29 | responseInterceptors.push(res => res);
30 | }
31 |
32 | responseInterceptors.push(adapterIntinterceptors.error);
33 | }
34 |
35 | axios.interceptors.response.use(...responseInterceptors);
36 |
37 | return function(url, requestOptions = {}) {
38 | const config = {
39 | ...adapterOptions,
40 | ...requestOptions,
41 | headers: {
42 | ...(adapterOptions.headers || {}),
43 | ...(requestOptions.headers || {}),
44 | },
45 | };
46 |
47 | if (requestOptions.body) {
48 | config.data = requestOptions.body;
49 | }
50 |
51 | if (requestOptions.query) {
52 | config.params = requestOptions.query;
53 | }
54 |
55 | return axios({
56 | ...config,
57 | url,
58 | method: requestOptions.method,
59 | });
60 | };
61 | }
62 |
--------------------------------------------------------------------------------
/packages/bivrost-axios-adapter/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | axios@^0.17.1:
6 | version "0.17.1"
7 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d"
8 | dependencies:
9 | follow-redirects "^1.2.5"
10 | is-buffer "^1.1.5"
11 |
12 | debug@^3.1.0:
13 | version "3.1.0"
14 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
15 | dependencies:
16 | ms "2.0.0"
17 |
18 | follow-redirects@^1.2.5:
19 | version "1.2.6"
20 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.6.tgz#4dcdc7e4ab3dd6765a97ff89c3b4c258117c79bf"
21 | dependencies:
22 | debug "^3.1.0"
23 |
24 | is-buffer@^1.1.5:
25 | version "1.1.6"
26 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
27 |
28 | ms@2.0.0:
29 | version "2.0.0"
30 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
31 |
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 |
5 | /node_modules
6 | /coverage
7 | /index.js
8 |
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /__tests__
4 | /.idea
5 | /npm-debug.log
6 | /node_modules
7 | /src
8 | /coverage
9 |
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/README.md:
--------------------------------------------------------------------------------
1 | # Bivrost delay adapter
2 |
3 | Bivrost adapter enchantment to add request/respone delays for UI tests
4 |
5 | ```
6 | yarn add bivrost-delay-adapter
7 | ```
8 |
9 | ### Usage
10 |
11 | ```js
12 | import DataSource from 'bivrost/data/source';
13 | import axiosAdapter from 'bivrost-axios-adapter';
14 | import delayAdapter from 'bivrost-delay-adapter';
15 |
16 | const delayedAxios = delayAdapter(axiosAdapter(), {
17 | request: 1000, // add 1sec delay before request
18 | response: 1000 // add 1sec delay after response
19 | error: 1000 // add 1sec delay after response with error
20 | });
21 |
22 | const api = bivrostApi({
23 | host: 'localhost',
24 | adapter: delayedAxios()
25 | });
26 |
27 | class UsersDataSource extends DataSource {
28 | static api = {
29 | loadAll: api('GET /users')
30 | }
31 |
32 | loadUsers(filters) {
33 | return this.invoke('loadAll', filters);
34 | }
35 | }
36 |
37 | const usersDataSource = new UsersDataSource();
38 |
39 | // Will load users with 1sec delay before request and 1sec delay after response
40 | usersDataSource.loadUsers().then(() => {
41 | console.log('done');
42 | });
43 | ```
44 |
45 | ---
46 |
47 | [Bivrost](https://github.com/tuchk4/bivrost) allows to organize a simple
48 | interface to asyncronous APIs.
49 |
50 | #### Other adapters
51 |
52 | * [Fetch Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
53 | * [Axios Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
54 | * [Delay Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
55 | * [LocalStorage Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
56 | * [Save Blob Adapter Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
57 |
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/__tests__/adapter.js:
--------------------------------------------------------------------------------
1 | import delayAdapter from '../src';
2 |
3 | jest.useFakeTimers();
4 |
5 | describe('Delay adapter', () => {
6 | const resolveAdapter = jest.fn(() => Promise.resolve());
7 | const rejectAdapter = jest.fn(() => Promise.reject());
8 |
9 | const options = {
10 | all: {
11 | request: 100,
12 | response: 200,
13 | error: 300,
14 | },
15 | withoutError: {
16 | request: 100,
17 | response: 200,
18 | },
19 | onlyRequest: {
20 | request: 100,
21 | },
22 | };
23 |
24 | beforeEach(() => {
25 | setTimeout.mockClear();
26 | });
27 |
28 | it('resolve delay call', () => {
29 | const apiOptions = {
30 | ...options.all,
31 | };
32 |
33 | const delayApi = delayAdapter(resolveAdapter, apiOptions);
34 |
35 | const p = delayApi().then(() => {
36 | expect(setTimeout.mock.calls.length).toBe(2);
37 | expect(setTimeout.mock.calls[1][1]).toBe(apiOptions.response);
38 | });
39 |
40 | expect(setTimeout.mock.calls.length).toBe(1);
41 | expect(setTimeout.mock.calls[0][1]).toBe(apiOptions.request);
42 |
43 | // run request timer
44 | jest.runOnlyPendingTimers();
45 |
46 | setImmediate(() => {
47 | // run response timer
48 | jest.runOnlyPendingTimers();
49 | });
50 |
51 | return p;
52 | });
53 |
54 | it('reject delay call', () => {
55 | const apiOptions = {
56 | ...options.all,
57 | };
58 |
59 | const delayApi = delayAdapter(rejectAdapter, apiOptions);
60 |
61 | const p = delayApi().then(() => {
62 | expect(setTimeout.mock.calls.length).toBe(2);
63 | expect(setTimeout.mock.calls[1][1]).toBe(apiOptions.error);
64 | });
65 |
66 | expect(setTimeout.mock.calls.length).toBe(1);
67 | expect(setTimeout.mock.calls[0][1]).toBe(apiOptions.request);
68 |
69 | // run request timer
70 | jest.runAllTimers();
71 |
72 | setImmediate(() => {
73 | // run response timer
74 | jest.runAllTimers();
75 | });
76 |
77 | return p;
78 | });
79 |
80 | it('reject delay call without error timer', () => {
81 | const apiOptions = {
82 | ...options.withoutError,
83 | };
84 |
85 | const delayApi = delayAdapter(rejectAdapter, apiOptions);
86 |
87 | const p = delayApi().then(() => {
88 | expect(setTimeout.mock.calls.length).toBe(2);
89 | expect(setTimeout.mock.calls[1][1]).toBe(apiOptions.response);
90 | });
91 |
92 | expect(setTimeout.mock.calls.length).toBe(1);
93 | expect(setTimeout.mock.calls[0][1]).toBe(apiOptions.request);
94 |
95 | // run request timer
96 | jest.runOnlyPendingTimers();
97 |
98 | setImmediate(() => {
99 | // run response timer
100 | jest.runOnlyPendingTimers();
101 | });
102 |
103 | return p;
104 | });
105 |
106 | it('resolve delay call with only request timer', () => {
107 | const apiOptions = {
108 | ...options.onlyRequest,
109 | };
110 |
111 | const delayApi = delayAdapter(rejectAdapter, apiOptions);
112 |
113 | const p = delayApi().then(() => {
114 | expect(setTimeout.mock.calls.length).toBe(1);
115 | });
116 |
117 | expect(setTimeout.mock.calls.length).toBe(1);
118 | expect(setTimeout.mock.calls[0][1]).toBe(apiOptions.request);
119 |
120 | // run request timer
121 | jest.runOnlyPendingTimers();
122 |
123 | return p;
124 | });
125 | });
126 |
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-delay-adapter",
3 | "version": "3.0.1",
4 | "lockfileVersion": 1
5 | }
6 |
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-delay-adapter",
3 | "version": "3.2.2",
4 | "description": "Bivrost adapter enchantment to add request/respone delays for UI tests",
5 | "keywords": [
6 | "bivrost",
7 | "data-source",
8 | "api",
9 | "fetch",
10 | "axios",
11 | "local-storage",
12 | "fetch-api-call",
13 | "response",
14 | "request",
15 | "call steps"
16 | ],
17 | "main": "index.js",
18 | "scripts": {
19 | "prepublish": "../../node_modules/.bin/babel -d ./ ./src",
20 | "dev": "../../node_modules/.bin/babel -d ./ ./src --watch"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/tuchk4/bivrost.git"
25 | },
26 | "author": "tuchk4 ",
27 | "license": "ISC",
28 | "bugs": {
29 | "url": "https://github.com/tuchk4/bivrost/issues"
30 | },
31 | "homepage": "hhttps://github.com/tuchk4/bivrost"
32 | }
33 |
--------------------------------------------------------------------------------
/packages/bivrost-delay-adapter/src/index.js:
--------------------------------------------------------------------------------
1 | const DEFAULT_OPTIONS = {
2 | response: 0,
3 | request: 0,
4 | error: 0,
5 | };
6 |
7 | export default function delayAdapter(adapter, options = {}) {
8 | const delayOptions = {
9 | ...DEFAULT_OPTIONS,
10 | ...options,
11 | };
12 |
13 | const delayProxy = (isError, data) => {
14 | let delay = delayOptions.response;
15 | if (isError && delayOptions.error) {
16 | delay = delayOptions.error;
17 | }
18 |
19 | if (delay) {
20 | return new Promise(resolve => {
21 | setTimeout(() => {
22 | resolve(data);
23 | }, delay);
24 | });
25 | } else {
26 | return data;
27 | }
28 | };
29 |
30 | return function delayApi(...apiArguments) {
31 | const api = () =>
32 | adapter(...apiArguments).then(
33 | delayProxy.bind(null, false),
34 | delayProxy.bind(null, true)
35 | );
36 |
37 | if (delayOptions.request) {
38 | return new Promise(resolve => {
39 | setTimeout(() => {
40 | resolve(api());
41 | }, delayOptions.request);
42 | });
43 | } else {
44 | return api();
45 | }
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 |
5 | /node_modules
6 | /index.js
7 |
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /__tests__
4 | /.idea
5 | /npm-debug.log
6 | /node_modules
7 | /src
8 | /test
9 |
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/README.md:
--------------------------------------------------------------------------------
1 | # Bivrost fetch adapter
2 |
3 | Adapter for browser's native fetch function.
4 |
5 | ```
6 | yarn add bivrost-fetch-adapter
7 | ```
8 |
9 | ### Usage
10 |
11 | With [Bivrost](https://github.com/tuchk4/bivrost):
12 |
13 | ```js
14 | import DataSource from 'bivrost/data/source';
15 | import bivrostApi from 'bivrost/http/api';
16 | import fetchAdapter from 'bivrost-fetch-adapter';
17 |
18 | const api = bivrostApi({
19 | host: 'localhost',
20 | adapter: fetchAdapter(),
21 | });
22 |
23 | class UsersDataSource extends DataSource {
24 | static api = {
25 | loadAll: api('GET /users'),
26 | };
27 |
28 | loadUsers(filters) {
29 | return this.invoke('loadAll', filters);
30 | }
31 | }
32 | ```
33 |
34 | Direct calls:
35 |
36 | ```js
37 | import fetchAdapter from 'bivrost-fetch-adapter';
38 |
39 | const api = fetchAdapter();
40 |
41 | const options = {
42 | method: 'GET',
43 | query: {
44 | groupId: 10,
45 | },
46 | headers: {
47 | 'Content-Type': 'application/json',
48 | },
49 | };
50 |
51 | api('/users', options) // /users?groupId=10
52 | .then(json => {
53 | // ...
54 | })
55 | .catch(response => {
56 | // ...
57 | });
58 |
59 | const options = {
60 | method: 'POST',
61 | body: {
62 | name: 'kek',
63 | },
64 | headers: {
65 | 'Content-Type': 'application/json',
66 | },
67 | };
68 |
69 | api('/user/1', options)
70 | .then(json => {
71 | // ...
72 | })
73 | .catch(response => {
74 | // ...
75 | });
76 | ```
77 |
78 | ### Configuration
79 |
80 | ```js
81 | import fetchAdapter from 'bivrost-fetch-adapter';
82 |
83 | const api = fetchAdapter({
84 | // default options for each request with created adapter
85 | options: {
86 | mode: 'cors',
87 | redirect: 'follow',
88 | },
89 | });
90 | ```
91 |
92 | Available options:
93 |
94 | * _headers_ - associated Headers object
95 | * _referrer_ - referrer of the request
96 | * _mode_ - cors, no-cors, same-origin
97 | * _credentials_ - should cookies go with the request? omit, same-origin
98 | * _redirect_ - follow, error, manual
99 | * _integrity_ - subresource integrity value
100 | * _cache_ - cache mode (default, reload, no-cache)
101 |
102 | ### Interceptors
103 |
104 | ```js
105 | import fetchAdapter from 'bivrost-fetch-adapter';
106 |
107 | const api = fetchAdapter({
108 | interceptors: {
109 | // takes Request instance as argument
110 | request: request => {
111 | // ...
112 | },
113 |
114 | error: error => {
115 | // ...
116 | },
117 |
118 | // takes Response instance as argument
119 | response: response => {
120 | // ...
121 | },
122 | },
123 | });
124 | ```
125 |
126 | * Request object documentation -
127 | https://developer.mozilla.org/en-US/docs/Web/API/Request
128 | * Response object documentation -
129 | https://developer.mozilla.org/en-US/docs/Web/API/Response
130 |
131 | NOTE: If there is a network error or another reason why the HTTP request
132 | couldn't be fulfilled, the fetch() promise will be rejected with a reference
133 | to that error.
134 |
135 | Interceptor example:
136 |
137 | ```js
138 | import fetchAdapter from 'bivrost-fetch-adapter';
139 |
140 | const api = fetchAdapter({
141 | interceptors: {
142 | request: request => {
143 | request.headers.set('Content-Type', 'application/json');
144 | request.headers.set('access_token', Auth.accessToken);
145 |
146 | return request;
147 | },
148 |
149 | error: error => Promise.reject(error);
150 | response: response => Promise.resolve(response)
151 | }
152 | });
153 | ```
154 |
155 | ### Fetch polyfill
156 |
157 | Github whatwg-fetch - https://github.com/github/fetch
158 |
159 | ---
160 |
161 | [Bivrost](https://github.com/tuchk4/bivrost) allows to organize a simple
162 | interface to asyncronous APIs.
163 |
164 | #### Other adapters
165 |
166 | * [Fetch Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
167 | * [Axios Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
168 | * [Delay Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
169 | * [LocalStorage Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
170 | * [Save Blob Adapter Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
171 |
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-fetch-adapter",
3 | "version": "3.1.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "asynckit": {
8 | "version": "0.4.0",
9 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
10 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
11 | },
12 | "combined-stream": {
13 | "version": "1.0.6",
14 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
15 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
16 | "requires": {
17 | "delayed-stream": "~1.0.0"
18 | }
19 | },
20 | "delayed-stream": {
21 | "version": "1.0.0",
22 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
23 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
24 | },
25 | "encoding": {
26 | "version": "0.1.12",
27 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
28 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
29 | "requires": {
30 | "iconv-lite": "~0.4.13"
31 | }
32 | },
33 | "form-data": {
34 | "version": "2.3.2",
35 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
36 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
37 | "requires": {
38 | "asynckit": "^0.4.0",
39 | "combined-stream": "1.0.6",
40 | "mime-types": "^2.1.12"
41 | }
42 | },
43 | "iconv-lite": {
44 | "version": "0.4.23",
45 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
46 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
47 | "requires": {
48 | "safer-buffer": ">= 2.1.2 < 3"
49 | }
50 | },
51 | "is-stream": {
52 | "version": "1.1.0",
53 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
54 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
55 | },
56 | "isomorphic-fetch": {
57 | "version": "2.2.1",
58 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
59 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
60 | "requires": {
61 | "node-fetch": "^1.0.1",
62 | "whatwg-fetch": ">=0.10.0"
63 | }
64 | },
65 | "mime-db": {
66 | "version": "1.33.0",
67 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
68 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
69 | },
70 | "mime-types": {
71 | "version": "2.1.18",
72 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
73 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
74 | "requires": {
75 | "mime-db": "~1.33.0"
76 | }
77 | },
78 | "node-fetch": {
79 | "version": "1.7.3",
80 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
81 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
82 | "requires": {
83 | "encoding": "^0.1.11",
84 | "is-stream": "^1.0.1"
85 | }
86 | },
87 | "qs": {
88 | "version": "6.5.2",
89 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
90 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
91 | },
92 | "safer-buffer": {
93 | "version": "2.1.2",
94 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
95 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
96 | },
97 | "whatwg-fetch": {
98 | "version": "2.0.4",
99 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
100 | "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-fetch-adapter",
3 | "version": "3.2.2",
4 | "description": "Bivrost adapter using browser's native fetch function",
5 | "keywords": [
6 | "bivrost",
7 | "data-source",
8 | "api",
9 | "fetch",
10 | "axios",
11 | "local-storage",
12 | "fetch-api-call",
13 | "response",
14 | "request",
15 | "call steps"
16 | ],
17 | "main": "index.js",
18 | "scripts": {
19 | "prepublish": "../../node_modules/.bin/babel -d ./ ./src",
20 | "dev": "../../node_modules/.bin/babel -d ./ ./src --watch"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/tuchk4/bivrost.git"
25 | },
26 | "author": "tuchk4 ",
27 | "license": "ISC",
28 | "bugs": {
29 | "url": "https://github.com/tuchk4/bivrost/issues"
30 | },
31 | "homepage": "hhttps://github.com/tuchk4/bivrost",
32 | "dependencies": {
33 | "isomorphic-fetch": "2.2.1",
34 | "qs": "^6.5.1"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/src/index.js:
--------------------------------------------------------------------------------
1 | import qs from 'qs';
2 | import 'isomorphic-fetch';
3 |
4 | const DEFAULT_ADAPTER_OPTIONS = {
5 | queryFormat: {
6 | arrayFormat: 'brackets',
7 | },
8 | };
9 |
10 | const filterNullValues = obj =>
11 | Object.keys(obj).reduce((map, key) => {
12 | if (obj[key]) {
13 | map[key] = obj[key];
14 | }
15 |
16 | return map;
17 | }, {});
18 |
19 | export default function fetchAdapter({ interceptors = {}, ...options } = {}) {
20 | const adapterOptions = {
21 | ...DEFAULT_ADAPTER_OPTIONS,
22 | ...options,
23 | };
24 |
25 | return function(url, requestOptions = {}) {
26 | const config = {
27 | ...adapterOptions,
28 | ...requestOptions,
29 | headers: new Headers(
30 | filterNullValues({
31 | ...(adapterOptions.headers || {}),
32 | ...(requestOptions.headers || {}),
33 | })
34 | ),
35 | };
36 |
37 | if (requestOptions.body) {
38 | if (requestOptions.body.forEach instanceof Function) {
39 | config.body = requestOptions.body;
40 | } else {
41 | config.body = JSON.stringify(requestOptions.body);
42 | }
43 | }
44 |
45 | let queryString = '';
46 | if (requestOptions.query) {
47 | queryString = qs.stringify(requestOptions.query, config.queryFormat);
48 | }
49 |
50 | let request = new Request(`${url}${queryString ? `?${queryString}` : ''}`, {
51 | method: requestOptions.method,
52 | ...config,
53 | });
54 |
55 | if (interceptors.request) {
56 | request = interceptors.request(request);
57 | }
58 |
59 | return fetch(request).then(
60 | response => {
61 | if (response.ok) {
62 | return interceptors.response
63 | ? interceptors.response(response)
64 | : response;
65 | } else {
66 | return interceptors.error
67 | ? interceptors.error(response)
68 | : Promise.reject(response);
69 | }
70 | },
71 | error => {
72 | return interceptors.error
73 | ? interceptors.error(error)
74 | : Promise.reject(error);
75 | }
76 | );
77 | };
78 | }
79 |
--------------------------------------------------------------------------------
/packages/bivrost-fetch-adapter/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | asynckit@^0.4.0:
6 | version "0.4.0"
7 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
8 |
9 | combined-stream@^1.0.5:
10 | version "1.0.5"
11 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
12 | dependencies:
13 | delayed-stream "~1.0.0"
14 |
15 | delayed-stream@~1.0.0:
16 | version "1.0.0"
17 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
18 |
19 | encoding@^0.1.11:
20 | version "0.1.12"
21 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
22 | dependencies:
23 | iconv-lite "~0.4.13"
24 |
25 | form-data@^2.3.1:
26 | version "2.3.1"
27 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
28 | dependencies:
29 | asynckit "^0.4.0"
30 | combined-stream "^1.0.5"
31 | mime-types "^2.1.12"
32 |
33 | iconv-lite@~0.4.13:
34 | version "0.4.19"
35 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
36 |
37 | is-stream@^1.0.1:
38 | version "1.1.0"
39 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
40 |
41 | isomorphic-fetch@2.2.1:
42 | version "2.2.1"
43 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
44 | dependencies:
45 | node-fetch "^1.0.1"
46 | whatwg-fetch ">=0.10.0"
47 |
48 | mime-db@~1.30.0:
49 | version "1.30.0"
50 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
51 |
52 | mime-types@^2.1.12:
53 | version "2.1.17"
54 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
55 | dependencies:
56 | mime-db "~1.30.0"
57 |
58 | node-fetch@^1.0.1:
59 | version "1.7.3"
60 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
61 | dependencies:
62 | encoding "^0.1.11"
63 | is-stream "^1.0.1"
64 |
65 | qs@^6.5.1:
66 | version "6.5.1"
67 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
68 |
69 | whatwg-fetch@>=0.10.0:
70 | version "2.0.3"
71 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
72 |
--------------------------------------------------------------------------------
/packages/bivrost-local-storage-adapter/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/bivrost-local-storage-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 |
5 | /node_modules
6 | /index.js
7 |
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
--------------------------------------------------------------------------------
/packages/bivrost-local-storage-adapter/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /__tests__
5 | /npm-debug.log
6 | /node_modules
7 | /src
8 | /test
9 |
--------------------------------------------------------------------------------
/packages/bivrost-local-storage-adapter/README.md:
--------------------------------------------------------------------------------
1 | # Bivrost localStorage adapter
2 |
3 | Bivrost adapter. Allows to work with localStorage as with REST backend
4 |
5 | ```
6 | yarn add bivrost-local-storage-adapter
7 | ```
8 |
9 | ## Usage
10 |
11 | With [Bivrost](https://github.com/tuchk4/bivrost):
12 |
13 | ```js
14 | import DataSource from 'bivrost/data/source';
15 | import bivrostApi from 'bivrost/http/api';
16 | import localStorageAdapter from 'bivrost-local-storage-adapter';
17 |
18 | const api = bivrostApi({
19 | adapter: localStorageAdapter({
20 | namespace: 'my-application',
21 | }),
22 | });
23 |
24 | class UsersDataSource extends DataSource {
25 | steps = ['api'];
26 |
27 | api = {
28 | loadAll: api('GET /users'),
29 | create: api('POST /users'),
30 | };
31 |
32 | loadUsers() {
33 | return this.invoke('loadAll');
34 | }
35 |
36 | createUser(user) {
37 | return this.invoke('create', user);
38 | }
39 | }
40 | ```
41 |
42 | Direct calls:
43 |
44 | ```js
45 | import localStorageAdapter from 'bivrost-local-storage-adapter';
46 |
47 | const localStorageAdapter = localStorageAdapter({
48 | namespace: 'my-application',
49 | });
50 |
51 | localStorageAdapter('/users/1', {
52 | method: 'POST',
53 | body: {
54 | name: 'John Doe',
55 | },
56 | }).then(() => console.log('saved to localStorage'));
57 |
58 | localStorageAdapter('/users/1', {
59 | method: 'GET',
60 | }).then(user => {
61 | expect(user).toEqual({
62 | name: 'John Doe',
63 | });
64 | });
65 | ```
66 |
67 | ---
68 |
69 | [Bivrost](https://github.com/tuchk4/bivrost) allows to organize a simple
70 | interface to asyncronous APIs.
71 |
72 | #### Other adapters
73 |
74 | * [Fetch Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
75 | * [Axios Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
76 | * [Delay Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
77 | * [LocalStorage Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
78 | * [Save Blob Adapter Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
79 |
--------------------------------------------------------------------------------
/packages/bivrost-local-storage-adapter/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-localstorage-adapter",
3 | "version": "3.0.1",
4 | "lockfileVersion": 1
5 | }
6 |
--------------------------------------------------------------------------------
/packages/bivrost-local-storage-adapter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-localstorage-adapter",
3 | "version": "3.2.2",
4 | "description": "Bivrost adapter. Allows to work with localStorage as with REST backend",
5 | "keywords": [
6 | "bivrost",
7 | "data-source",
8 | "api",
9 | "fetch",
10 | "axios",
11 | "local-storage",
12 | "fetch-api-call",
13 | "response",
14 | "request",
15 | "call steps"
16 | ],
17 | "main": "index.js",
18 | "scripts": {
19 | "prepublish": "../../node_modules/.bin/babel -d ./ ./src",
20 | "dev": "../../node_modules/.bin/babel -d ./ ./src --watch"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/tuchk4/bivrost.git"
25 | },
26 | "author": "tuchk4 ",
27 | "license": "ISC",
28 | "bugs": {
29 | "url": "https://github.com/tuchk4/bivrost/issues"
30 | },
31 | "homepage": "hhttps://github.com/tuchk4/bivrost"
32 | }
33 |
--------------------------------------------------------------------------------
/packages/bivrost-local-storage-adapter/src/index.js:
--------------------------------------------------------------------------------
1 | const load = namespace => {
2 | let data = localStorage.getItem(namespace);
3 | let processed = {};
4 |
5 | if (data) {
6 | processed = JSON.parse(data);
7 | }
8 |
9 | return processed;
10 | };
11 |
12 | const save = (namespace, data) => {
13 | var prepared = JSON.stringify(data);
14 | localStorage.setItem(namespace, prepared);
15 | };
16 |
17 | export default function localStorageAdapter(config = {}) {
18 | const namespace = config.namespace;
19 |
20 | if (!namespace) {
21 | throw new Error(
22 | 'Bivrost localStorage adapter: namespace should be defined'
23 | );
24 | }
25 |
26 | return function(url, requestOptions = {}) {
27 | let action = null;
28 |
29 | switch (requestOptions.method) {
30 | case 'GET':
31 | action = (id, query, body) => {
32 | return new Promise(resolve => {
33 | let db = load(namespace);
34 |
35 | resolve(db[id]);
36 | });
37 | };
38 |
39 | break;
40 |
41 | case 'POST' || 'PUT':
42 | action = (id, query, body) => {
43 | return new Promise(resolve => {
44 | let db = load(namespace);
45 | db[id] = body;
46 | save(namespace, db);
47 |
48 | resolve(db[id]);
49 | });
50 | };
51 |
52 | break;
53 |
54 | case 'DELETE':
55 | action = (id, query, body) => {
56 | return new Promise(resolve => {
57 | let db = load(namespace);
58 |
59 | delete db[id];
60 | save(namespace, db);
61 |
62 | resolve();
63 | });
64 | };
65 |
66 | break;
67 |
68 | default:
69 | throw new Error('method is not allowed');
70 | }
71 |
72 | return action(url, requestOptions.query, requestOptions.body);
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 |
5 | /node_modules
6 | /index.js
7 |
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /npm-debug.log
5 | /node_modules
6 | /src
7 | /test
8 | /__tests__
9 |
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/README.md:
--------------------------------------------------------------------------------
1 | # Bivrost save blob adapter
2 |
3 | Bivrost adapter. Allows to save api resposne as blob.
4 |
5 | ```
6 | yarn add bivrost-save-blob-adapter
7 | ```
8 |
9 | ## Usage
10 |
11 | With [Bivrost](https://github.com/tuchk4/bivrost):
12 |
13 | ```js
14 | import DataSource from 'bivrost/data/source';
15 | import bivrostApi from 'bivrost/http/api';
16 | import axiosAdapter from 'bivrost-axios-adapter';
17 | import saveBlobAdapter from 'bivrost-save-blob-adapter';
18 |
19 | const api = bivrostApi({
20 | host: 'localhost',
21 | adapter: saveBlobAdapter(axiosAdapter()),
22 | });
23 |
24 | class UsersDataSource extends DataSource {
25 | steps = ['api'];
26 |
27 | api = {
28 | loadAll: api('GET /users'),
29 | };
30 |
31 | saveUsersJSON(filters) {
32 | return this.invoke('loadAll', filters).then(download => {
33 | // call download function and generate filename
34 | download((url, params, response) => `users-${response.length}.json`);
35 | });
36 | }
37 | }
38 |
39 | const usersDataSource = new UsersDataSource();
40 |
41 | // Will save GET /users response as "users.json" file
42 | usersDataSource.saveUsersJSON().then(() => {
43 | console.log('file saved');
44 | });
45 | ```
46 |
47 | Direct calls:
48 |
49 | ```js
50 | import axios from 'axios';
51 | import axiosAdapter from 'bivrost-axios-adapter';
52 | import saveBlobAdapter from 'bivrost-save-blob-adapter';
53 |
54 | const axiosAdapter = axiosAdapter(axios);
55 | const saveBlob = saveBlobAdapter(axiosAdapter);
56 |
57 | const requestOptions = {
58 | method: 'GET',
59 | };
60 |
61 | saveBlob('/report/exel', requestOptions).then(download => {
62 | download(() => 'exel.xls');
63 | }); // browser will save and download response as file
64 | ```
65 |
66 | ---
67 |
68 | [Bivrost](https://github.com/tuchk4/bivrost) allows to organize a simple
69 | interface to asyncronous APIs.
70 |
71 | #### Other adapters
72 |
73 | * [Fetch Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-fetch-adapter)
74 | * [Axios Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-axios-adapter)
75 | * [Delay Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-delay-adapter)
76 | * [LocalStorage Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-local-storage-adapter)
77 | * [Save Blob Adapter Adapter](https://github.com/tuchk4/bivrost/tree/master/packages/bivrost-save-blob-adapter)
78 |
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-save-blob-adapter",
3 | "version": "3.0.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "browser-filesaver": {
8 | "version": "1.1.1",
9 | "resolved": "https://registry.npmjs.org/browser-filesaver/-/browser-filesaver-1.1.1.tgz",
10 | "integrity": "sha1-HDUbL1dvWwDx0p1EIcTAQo7uX6E="
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost-save-blob-adapter",
3 | "version": "3.2.2",
4 | "description": "Bivrost adapter. Allows to save api resposne as blob",
5 | "keywords": [
6 | "bivrost",
7 | "data-source",
8 | "api",
9 | "fetch",
10 | "axios",
11 | "local-storage",
12 | "fetch-api-call",
13 | "response",
14 | "request",
15 | "call steps"
16 | ],
17 | "main": "index.js",
18 | "scripts": {
19 | "prepublish": "../../node_modules/.bin/babel -d ./ ./src",
20 | "dev": "../../node_modules/.bin/babel -d ./ ./src --watch"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/tuchk4/bivrost.git"
25 | },
26 | "author": "tuchk4 ",
27 | "license": "ISC",
28 | "bugs": {
29 | "url": "https://github.com/tuchk4/bivrost/issues"
30 | },
31 | "homepage": "hhttps://github.com/tuchk4/bivrost",
32 | "dependencies": {
33 | "browser-filesaver": "^1.1.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/src/index.js:
--------------------------------------------------------------------------------
1 | import fileSaver from 'browser-filesaver';
2 |
3 | const isFunction = func =>
4 | func && {}.toString.call(func) === '[object Function]';
5 |
6 | export default function saveBlobAdapter(adapter) {
7 | return (url, params) => {
8 | return adapter(url, params).then(response => {
9 | return getFileName => {
10 | const filename = getFileName(url, params, response);
11 |
12 | if (!filename) {
13 | throw new Error('Bivrost save blob adapter: Empty file name');
14 | }
15 |
16 | const blob = new Blob([JSON.stringify(response)]);
17 |
18 | fileSaver.saveAs(blob, filename);
19 |
20 | return response;
21 | };
22 | });
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/packages/bivrost-save-blob-adapter/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | browser-filesaver@^1.1.1:
6 | version "1.1.1"
7 | resolved "https://registry.yarnpkg.com/browser-filesaver/-/browser-filesaver-1.1.1.tgz#1c351b2f576f5b00f1d29d4421c4c0428eee5fa1"
8 |
--------------------------------------------------------------------------------
/packages/bivrost/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /_bookg
5 | /node_modules
6 | /data
7 | /http
8 | /coverage
9 | /utils
10 |
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
--------------------------------------------------------------------------------
/packages/bivrost/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /__tests__
5 | /npm-debug.log
6 | /node_modules
7 | /coverage
8 |
9 |
--------------------------------------------------------------------------------
/packages/bivrost/README.md:
--------------------------------------------------------------------------------
1 | # [Bivrost](http://tuchk4.github.io/bivrost/)
2 |
3 | Bivrost allows to organize a simple interface to asynchronous APIs.
4 |
5 | [](https://travis-ci.org/tuchk4/bivrost)
6 | [](https://www.npmjs.com/package/bivrost)
7 |
8 | # Fetch API Call
9 |
10 | Easiet way to use fetch with interceptros and awesome api endpoint declaration.
11 | [bivrost/packages/fetch-api-call](https://github.com/tuchk4/bivrost/tree/master/packages/fetch-api-call)
12 |
13 | ## Bivrost
14 |
15 | The main idea of Bivrost is grouping several API methods into data-sources.
16 |
17 | [Bivrost full documentation and recipes](https://tuchk4.github.io/bivrost/)
18 |
19 | ## Installation
20 |
21 | ```
22 | yarn add bivrost
23 | ```
24 |
25 | ## The gist
26 |
27 | That’s it! Create api function for github api.
28 |
29 | ```js
30 | import api from 'bivrost/http/api'
31 | import fetchAdapter from 'bivrost-fetch-adapter';
32 |
33 | const githubApi = api({
34 | protocol: 'https:'
35 | host: 'api.github.com',
36 | adapter: fetchAdapter()
37 | });
38 |
39 | //define API method
40 | const repositoryList = githubApi('GET /users/:user/repos'),
41 |
42 | //call API method
43 | repositoryList({ user: 'tuchk4' })
44 | .then(repositories => console.log(repositories));
45 | ```
46 |
47 | Create data source that contain few github api methods (get repositories list
48 | and get repository info) and its invoke chain.
49 |
50 | ```js
51 | import DataSource from 'bivrost/data/source';
52 | import githubApi from './github-api';
53 | import tcomb from 'tcomb';
54 |
55 | class GihtubRepositories extends DataSource {
56 | // define invoke method chain. Default chain is - ['api', 'process']
57 | static steps = ['api', 'immutable'];
58 |
59 | // "define "api" step
60 | static api = {
61 | repos: githubApi('GET /users/:user/repos'),
62 | repoInfo: githubApi('GET /repos/:user/:repository'),
63 | };
64 |
65 | // step function will be executed for each method
66 | static immutable = response => Immutable.fromJSON(response);
67 |
68 | // define data source public methods that invokes steps methods
69 | getRepositories(user) {
70 | return this.invoke('repos', {
71 | user,
72 | });
73 | }
74 |
75 | getRepositoryInfo(user, repository) {
76 | return this.invoke('repoInfo', {
77 | user,
78 | repository,
79 | });
80 | }
81 | }
82 | ```
83 |
84 | Extends GihtubRepositories and define username. Now all requests will be done
85 | for facebook's github group.
86 |
87 | ```js
88 | import GihtubRepositories from './github-repositories';
89 |
90 | const FACEBOOK_GITHUB_ACCOUNT = 'facebook';
91 |
92 | class FacebookRepositories extends GihtubRepositories {
93 | getRepositories() {
94 | return super.getRepositories(FACEBOOK_GITHUB_ACCOUNT);
95 | }
96 |
97 | getRepositoryInfo(repository) {
98 | return super.getRepositoryInfo(FACEBOOK_GITHUB_ACCOUNT, repository);
99 | }
100 | }
101 | ```
102 |
103 | ### [Contributing](docs/contributing.md)
104 |
105 | Project is open for new ideas and features:
106 |
107 | * new adapters
108 | * new api functions
109 | * data source features
110 | * feedback is very matter
111 |
112 | ---
113 |
114 | ## Docs
115 |
116 | * [Basics](/docs/basics/README.md)
117 | * [Api function](/docs/basics/api-function.md)
118 | * [Adapter](/docs/basics/adapter.md)
119 | * [Data source](/docs/basics/data-source.md)
120 | * [Recipes](/docs/recipes/README.md)
121 | * [Api enchantment](/docs/recipes/api-enchantment.md)
122 | * [Generate DataSource invoke functions](/docs/recipes/data-source-auto-invoke.md)
123 | * [Data source immutable output](/docs/recipes/data-source-immutable.md)
124 | * [Data source type checking (tcomb)](/docs/recipes/data-source-type-checking.md)
125 | * [CRUD Data source](/docs/recipes/generate-crud-methods.md)
126 | * [File upload](/docs/recipes/file-upload.md)
127 | * [Fixtures](/docs/recipes/fixtures.md)
128 | * [Default api response](/docs/recipes/default-response.md)
129 | * [Prepare request and process response](/docs/recipes/prepare-process.md)
130 | * [Testing](/docs/testing.md)
131 | * [Contributing](docs/contributing.md)
132 |
133 | #### Adapters
134 |
135 | * [Fetch adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-fetch-adapter)
136 | * [Axios adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-axios-adapter)
137 | * [Delay adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-delay-adapter)
138 | * [Local storage adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-local-storage-adapter)
139 | * [Save blob adapter adapter](https://github.com/tuchk4/bivrost/tree/master/packages/packages/bivrost-save-blob-adapter)
140 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/caches.js:
--------------------------------------------------------------------------------
1 | import DataSource from '../src/data/source';
2 |
3 | describe('Cache', () => {
4 | it('default cache', () => {
5 | class DS1 extends DataSource {
6 | static defaultCache = {
7 | isGlobal: true,
8 | ttl: 1,
9 | };
10 |
11 | static cache = {
12 | foo: { enabled: true },
13 | };
14 | }
15 |
16 | class DS2 extends DS1 {
17 | static defaultCache = {
18 | ttl: 10,
19 | };
20 | }
21 |
22 | let ds2 = new DS2();
23 | expect(ds2.getCache('foo').ttl).toBe(10);
24 |
25 | let ds1 = new DS1();
26 | expect(ds1.getCache('foo').ttl).toBe(1);
27 |
28 | expect(ds1.constructor.defaultCache).toEqual({
29 | ttl: 1,
30 | isGlobal: true,
31 | });
32 | expect(DS1.defaultCache).toEqual({
33 | ttl: 1,
34 | isGlobal: true,
35 | });
36 |
37 | expect(ds2.constructor.defaultCache).toEqual({
38 | ttl: 10,
39 | });
40 | expect(DS2.defaultCache).toEqual({
41 | ttl: 10,
42 | });
43 | });
44 |
45 | it('should be able to work per-class', () => {
46 | class DS extends DataSource {
47 | static cache = {
48 | foo: {
49 | isGlobal: true,
50 | enabled: true,
51 | },
52 | };
53 | }
54 |
55 | let ds0 = new DS();
56 | let ds1 = new DS();
57 |
58 | expect(ds0.getCache('foo')).toBe(ds1.getCache('foo'));
59 | });
60 |
61 | it('should be able to work per-instance', () => {
62 | class DS extends DataSource {
63 | static cache = {
64 | foo: {
65 | isGlobal: false,
66 | enabled: true,
67 | },
68 | };
69 | }
70 |
71 | let ds0 = new DS();
72 | let ds1 = new DS();
73 |
74 | expect(ds0.getCache('foo')).not.toBe(ds1.getCache('foo'));
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/classes.js:
--------------------------------------------------------------------------------
1 | import DataSource from '../src/data/source';
2 | import httpBinApi from './http-bin-api';
3 |
4 | function getClass() {
5 | class DS_A extends DataSource {
6 | static prepare = {
7 | post: req => ({ foo: req.Foo, bar: req.Bar }),
8 | get: req => ({ foo: req.Foo, bar: req.Bar }),
9 | };
10 |
11 | static process = {
12 | post: res => JSON.parse(res.data.data),
13 | get: res => res.args,
14 | };
15 |
16 | static cache = {
17 | get: {
18 | enabled: true,
19 | },
20 | };
21 |
22 | post(params) {
23 | return this.invoke('post', params);
24 | }
25 |
26 | get(params) {
27 | return this.invoke('get', params);
28 | }
29 | }
30 |
31 | class DS_B extends DS_A {
32 | highLevelMethod() {
33 | return this.post({
34 | Foo: this.options.a,
35 | Bar: 200,
36 | });
37 | }
38 | }
39 |
40 | class DS_C extends DS_B {
41 | static api = {
42 | post: httpBinApi('POST /post'),
43 | get: httpBinApi('GET /get'),
44 | };
45 | }
46 |
47 | return DS_C;
48 | }
49 |
50 | describe('Classes', () => {
51 | it('should generate auto invoke methods', () => {
52 | const ds = new class extends DataSource {
53 | static api = {
54 | load_user: httpBinApi('POST /post'),
55 | };
56 | }();
57 |
58 | return ds
59 | .invokeLoadUser({
60 | foo: 1,
61 | })
62 | .then(res => {
63 | expect(res.data.json).toEqual({
64 | foo: 1,
65 | });
66 | });
67 | });
68 |
69 | it('should make request with empty obj', () => {
70 | const ds = new class extends DataSource {
71 | static api = {
72 | load_user: httpBinApi('POST /post'),
73 | };
74 | }();
75 |
76 | return ds.invokeLoadUser().then(res => {
77 | expect(res.data.json).toEqual({});
78 | });
79 | });
80 |
81 | it('should pass context', () => {
82 | const processMock = jest.fn(props => ({
83 | ...props.data.json,
84 | fromProcess: 3,
85 | }));
86 |
87 | const prepareMock = jest.fn(props => {
88 | return {
89 | ...props,
90 | fromPrepare: 2,
91 | };
92 | });
93 |
94 | const ds = new class extends DataSource {
95 | static prepare = {
96 | load_user: prepareMock,
97 | };
98 |
99 | static process = {
100 | load_user: processMock,
101 | };
102 |
103 | static api = {
104 | load_user: httpBinApi('POST /post'),
105 | };
106 | }();
107 |
108 | return ds.invokeLoadUser({ bar: 1 }, { foo: 1 }).then(res => {
109 | expect(res.fromProcess).toEqual(3);
110 |
111 | expect(prepareMock.mock.calls.length).toEqual(1);
112 | expect(processMock.mock.calls.length).toEqual(1);
113 |
114 | expect(prepareMock.mock.calls[0][1]).toEqual({
115 | foo: 1,
116 | });
117 |
118 | expect(processMock.mock.calls[0][1]).toEqual({
119 | foo: 1,
120 | });
121 | });
122 | });
123 |
124 | it('should done XHR request with correct result', () => {
125 | let DS = getClass();
126 |
127 | let ds = new DS({
128 | options: {
129 | a: 8889,
130 | },
131 | });
132 |
133 | return ds
134 | .highLevelMethod()
135 | .catch(e => e)
136 | .then(res => {
137 | expect(res).toEqual({
138 | bar: 200,
139 | foo: 8889,
140 | });
141 | });
142 | });
143 |
144 | it('should pass headers from context', () => {
145 | const loadUserMock = jest.fn();
146 |
147 | const ds = new class extends DataSource {
148 | static api = {
149 | load_user: loadUserMock,
150 | };
151 | }({
152 | headers: {
153 | abc: 'zxc',
154 | },
155 | });
156 |
157 | return ds
158 | .invokeLoadUser({
159 | foo: 1,
160 | })
161 | .then(res => {
162 | expect(loadUserMock.mock.calls[0][0]).toEqual({
163 | foo: 1,
164 | });
165 | expect(loadUserMock.mock.calls[0][1]).toEqual({
166 | headers: {
167 | abc: 'zxc',
168 | },
169 | });
170 | });
171 | });
172 | });
173 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/createRequestTemplate.js:
--------------------------------------------------------------------------------
1 | import createRequestTemplate from '../src/http/createRequestTemplate';
2 | import api from '../src/http/api';
3 |
4 | describe('Request template', () => {
5 | let getPOSTrequest = null;
6 | let getGETrequest = null;
7 |
8 | beforeEach(() => {
9 | getPOSTrequest = createRequestTemplate('POST /user/:id');
10 | getGETrequest = createRequestTemplate('GET /user/:id');
11 | });
12 |
13 | it('stringify - 1', () => {
14 | const getUser = api({
15 | host: 'localhost',
16 | })('GET /user/:id');
17 |
18 | expect(
19 | getUser.stringify({
20 | id: 1,
21 | })
22 | ).toEqual('http://localhost/user/1');
23 | });
24 |
25 | it('stringify - 2', () => {
26 | const getUser = api({
27 | host: 'localhost',
28 | })('GET /user/:id');
29 |
30 | expect(
31 | getUser.stringify({
32 | id: 1,
33 | group: 5,
34 | })
35 | ).toEqual('http://localhost/user/1?group=5');
36 | });
37 |
38 | it('stringify - 3', () => {
39 | const getUser = api({
40 | host: 'localhost',
41 | })('GET /user?:id&:group');
42 |
43 | expect(
44 | getUser.stringify({
45 | id: 1,
46 | group: 5,
47 | })
48 | ).toEqual('http://localhost/user?id=1&group=5');
49 | });
50 |
51 | it('apply POST request', () => {
52 | const request = getPOSTrequest({
53 | id: 1,
54 | name: 'Thor',
55 | });
56 |
57 | expect(request).toEqual({
58 | query: {},
59 | path: '/user/1',
60 | method: 'POST',
61 | body: {
62 | name: 'Thor',
63 | },
64 | });
65 | });
66 |
67 | it('apply GET request', () => {
68 | const request = getGETrequest({
69 | id: 1,
70 | name: 'Thor',
71 | });
72 |
73 | expect(request).toEqual({
74 | query: {
75 | name: 'Thor',
76 | },
77 | path: '/user/1',
78 | method: 'GET',
79 | });
80 | });
81 |
82 | it('should throw exception if not enough bound params', () => {
83 | expect(() => {
84 | getGETrequest({
85 | name: 'Thor',
86 | });
87 | }).toThrow();
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/http-bin-api.js:
--------------------------------------------------------------------------------
1 | import axiosAdapter from 'bivrost-axios-adapter';
2 | import api from '../src/http/api';
3 |
4 | const httpBinApi = api({
5 | protocol: 'https:',
6 | host: 'httpbin.org',
7 | adapter: axiosAdapter({
8 | interceptors: {
9 | request: request => {
10 | return request;
11 | },
12 | response: response => {
13 | return response;
14 | },
15 | },
16 | }),
17 | });
18 |
19 | export default httpBinApi;
20 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/localstorage-mock.js:
--------------------------------------------------------------------------------
1 | // workaround for mocked window
2 | const localStorageMock = (function() {
3 | let store = {};
4 |
5 | return {
6 | getItem: key => store[key] || null,
7 | setItem: (key, value) => {
8 | store[key] = value.toString();
9 | },
10 | clear: () => {
11 | store = {};
12 | },
13 | };
14 | })();
15 |
16 | Object.defineProperty(window, 'localStorage', {
17 | value: localStorageMock,
18 | });
19 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/mockDataSource.js:
--------------------------------------------------------------------------------
1 | import DataSource from '../src/data/source';
2 | import mockDs from '../src/utils/mockDataSource';
3 |
4 | class DS extends DataSource {
5 | static steps = ['serialize', 'api'];
6 |
7 | static serialize = {
8 | loadAll: ({ groupId }) =>
9 | Promise.resolve({
10 | g: groupId,
11 | }),
12 | };
13 |
14 | static api = {
15 | loadAll: input => Promise.resolve(input),
16 | };
17 |
18 | loadAll(props) {
19 | return this.invoke('loadAll', props);
20 | }
21 | }
22 |
23 | describe('Datasource steps mock', () => {
24 | let ds = null;
25 |
26 | beforeEach(() => {
27 | ds = new DS();
28 | });
29 |
30 | it('should mock ds steps', () => {
31 | const mocks = mockDs(ds, step => jest.fn(step));
32 |
33 | ds
34 | .loadAll({
35 | groupId: 5,
36 | })
37 | .then(() => {
38 | expect(mocks.serialize.loadAll.mock.calls.length).toEqual(1);
39 | expect(mocks.serialize.loadAll.mock.calls[0][0]).toEqual({
40 | groupId: 5,
41 | });
42 |
43 | expect(mocks.api.loadAll.mock.calls[0][0]).toEqual({
44 | g: 5,
45 | });
46 | });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/promiseCache.js:
--------------------------------------------------------------------------------
1 | import promiseCache from '../src/utils/promiseCache';
2 | import promiseDedup from '../src/utils/promiseDeduplicator';
3 |
4 | import Cache from '../src/data/cache';
5 |
6 | describe('Promise cache', () => {
7 | const promiseCreator = jest.fn(() => {
8 | return Promise.resolve('Odin');
9 | });
10 |
11 | it('should cached result as Promise', done => {
12 | const cache = new Cache();
13 | const key = 123;
14 |
15 | const firstPromise = promiseCache(cache, key, promiseCreator);
16 |
17 | setImmediate(() => {
18 | const secondPromise = promiseCache(cache, key, promiseCreator);
19 |
20 | Promise.all([firstPromise, secondPromise])
21 | .then(([firstResult, secondResult]) => {
22 | expect(firstResult).toEqual(secondResult);
23 | expect(promiseCreator.mock.calls.length).toBe(1);
24 | })
25 | .then(done, done);
26 | });
27 | });
28 |
29 | it('should work with promise deduplicator', () => {
30 | const cache = new Cache();
31 | const key = 123;
32 |
33 | const promiseCreator = jest.fn(() => {
34 | return Promise.resolve('Odin');
35 | });
36 |
37 | const dedupPromise = promiseDedup(key, promiseCreator);
38 |
39 | const firstPromise = promiseCache(cache, key, () => dedupPromise);
40 | const secondPromise = promiseCache(cache, key, () => dedupPromise);
41 |
42 | return Promise.all([firstPromise, secondPromise]).then(
43 | ([firstResult, secondResult]) => {
44 | expect(firstResult).toEqual(secondResult);
45 | expect(promiseCreator.mock.calls.length).toBe(1);
46 | }
47 | );
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/packages/bivrost/__tests__/promiseDeduplicator.js:
--------------------------------------------------------------------------------
1 | import promiseDedup from '../src/utils/promiseDeduplicator';
2 |
3 | describe('Promise deduplicator', () => {
4 | const key1 = 123;
5 | const key2 = 456;
6 |
7 | const promiseCreator = () => {
8 | return Promise.resolve();
9 | };
10 |
11 | it('dedup promises with same kay should be cached', () => {
12 | const dedupPromise1 = promiseDedup(key1, promiseCreator);
13 | const dedupPromise2 = promiseDedup(key1, promiseCreator);
14 |
15 | expect(dedupPromise1).toBe(dedupPromise2);
16 | });
17 |
18 | it('dedup promises wiht different keys should de different', () => {
19 | const dedupPromise1 = promiseDedup(key1, promiseCreator);
20 | const dedupPromise2 = promiseDedup(key2, promiseCreator);
21 |
22 | expect(dedupPromise1).not.toBe(dedupPromise2);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/packages/bivrost/bivrost-3.2.4.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/bivrost/f6d6d5973bee77c02ee050dd34ed19a5f96b0a42/packages/bivrost/bivrost-3.2.4.tgz
--------------------------------------------------------------------------------
/packages/bivrost/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost",
3 | "version": "3.2.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "asynckit": {
8 | "version": "0.4.0",
9 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
10 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
11 | },
12 | "axios": {
13 | "version": "0.17.1",
14 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz",
15 | "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=",
16 | "dev": true,
17 | "requires": {
18 | "follow-redirects": "^1.2.5",
19 | "is-buffer": "^1.1.5"
20 | }
21 | },
22 | "bivrost-axios-adapter": {
23 | "version": "3.0.2",
24 | "resolved": "https://registry.npmjs.org/bivrost-axios-adapter/-/bivrost-axios-adapter-3.0.2.tgz",
25 | "integrity": "sha512-/G/XFZPhulpijAk4Wak1WufJFVX94L5kZhMigEnTK4r4N0ZOzMG5sAzPUVrPO/YGGCT9X0k6NM2QRvnWrgirFQ==",
26 | "dev": true,
27 | "requires": {
28 | "axios": "^0.17.1"
29 | }
30 | },
31 | "combined-stream": {
32 | "version": "1.0.6",
33 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
34 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
35 | "requires": {
36 | "delayed-stream": "~1.0.0"
37 | }
38 | },
39 | "debug": {
40 | "version": "3.1.0",
41 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
42 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
43 | "dev": true,
44 | "requires": {
45 | "ms": "2.0.0"
46 | }
47 | },
48 | "delayed-stream": {
49 | "version": "1.0.0",
50 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
51 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
52 | },
53 | "follow-redirects": {
54 | "version": "1.4.1",
55 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz",
56 | "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==",
57 | "dev": true,
58 | "requires": {
59 | "debug": "^3.1.0"
60 | }
61 | },
62 | "form-data": {
63 | "version": "2.3.2",
64 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
65 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
66 | "requires": {
67 | "asynckit": "^0.4.0",
68 | "combined-stream": "1.0.6",
69 | "mime-types": "^2.1.12"
70 | }
71 | },
72 | "is-buffer": {
73 | "version": "1.1.6",
74 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
75 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
76 | "dev": true
77 | },
78 | "lodash.camelcase": {
79 | "version": "4.3.0",
80 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
81 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
82 | },
83 | "mime-db": {
84 | "version": "1.33.0",
85 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
86 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
87 | },
88 | "mime-types": {
89 | "version": "2.1.18",
90 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
91 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
92 | "requires": {
93 | "mime-db": "~1.33.0"
94 | }
95 | },
96 | "ms": {
97 | "version": "2.0.0",
98 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
99 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
100 | "dev": true
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/packages/bivrost/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bivrost",
3 | "version": "3.2.5",
4 | "main": "index.js",
5 | "description":
6 | "Bivrost allows to organize a simple interface to asynchronous APIs.",
7 | "keywords": [
8 | "rest",
9 | "data-source",
10 | "api",
11 | "fetch",
12 | "axios",
13 | "local-storage",
14 | "fetch-api-call",
15 | "response",
16 | "request",
17 | "call steps"
18 | ],
19 | "scripts": {
20 | "prepublish": "../../node_modules/.bin/babel -d ./ ./src",
21 | "dev": "../../node_modules/.bin/babel -d ./ ./src --watch"
22 | },
23 | "devDependencies": {
24 | "bivrost-axios-adapter": "^3.2.2"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/tuchk4/bivrost.git"
29 | },
30 | "author": "tuchk4 ",
31 | "license": "ISC",
32 | "bugs": {
33 | "url": "https://github.com/tuchk4/bivrost/issues"
34 | },
35 | "homepage": "hhttps://github.com/tuchk4/bivrost",
36 | "dependencies": {
37 | "lodash.camelcase": "^4.3.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/bivrost/src/data/cache.js:
--------------------------------------------------------------------------------
1 | class CacheRecord {
2 | constructor(value, ttl) {
3 | this.value = value;
4 | this.exp = ttl + Date.now();
5 | }
6 |
7 | isExpired(now = Date.now()) {
8 | return now > this.exp;
9 | }
10 | }
11 |
12 | export default class Cache {
13 | records = new Map();
14 |
15 | constructor({ ttl = 60000 } = {}) {
16 | this.ttl = ttl;
17 | }
18 |
19 | get(id) {
20 | if (this.has(id)) {
21 | return this.records.get(id).value;
22 | }
23 |
24 | return null;
25 | }
26 |
27 | has(id) {
28 | let has = false;
29 |
30 | if (this.records.has(id)) {
31 | const record = this.records.get(id);
32 |
33 | if (record.isExpired()) {
34 | this.records.delete(id);
35 | has = false;
36 | } else {
37 | has = true;
38 | }
39 | }
40 |
41 | return has;
42 | }
43 |
44 | put(id, value, ttl = this.ttl) {
45 | const record = new CacheRecord(value, ttl);
46 | this.records.set(id, record);
47 | }
48 |
49 | clear(id) {
50 | if (id) {
51 | this.records.delete(id);
52 | } else {
53 | this.records = new Map();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/packages/bivrost/src/data/source.js:
--------------------------------------------------------------------------------
1 | import camelCase from 'lodash.camelcase';
2 | import promiseCache from '../utils/promiseCache';
3 | import Cache from './cache';
4 |
5 | const DEFAULT_STEPS = ['prepare', 'api', 'process'];
6 | const DEFAULT_METHOD_CACHE_CONFIG = {
7 | isGlobal: false,
8 | enabled: false,
9 | ttl: 60000,
10 | };
11 |
12 | const isFunction = func =>
13 | func && {}.toString.call(func) === '[object Function]';
14 |
15 | const getMethodCacheName = (instance, method) =>
16 | `${instance.constructor.uid}@${method}`;
17 |
18 | const buildCaches = instance => {
19 | const caches = new Map();
20 | const cacheConfig = instance.constructor.cache || {};
21 | const defaultCacheConfig = {
22 | ...DEFAULT_METHOD_CACHE_CONFIG,
23 | ...instance.constructor.defaultCache,
24 | };
25 |
26 | for (let method of Object.keys(cacheConfig)) {
27 | const config = {
28 | ...defaultCacheConfig,
29 | ...cacheConfig[method],
30 | };
31 |
32 | let cache = null;
33 | const cacheMehtodName = getMethodCacheName(instance, method);
34 |
35 | if (config.enabled) {
36 | if (config.isGlobal) {
37 | if (!instance.constructor.caches.hasOwnProperty(cacheMehtodName)) {
38 | instance.constructor.caches[cacheMehtodName] = new Cache(config);
39 | }
40 | cache = instance.constructor.caches[cacheMehtodName];
41 | } else {
42 | cache = new Cache(config);
43 | }
44 |
45 | caches.set(cacheMehtodName, cache);
46 | }
47 | }
48 |
49 | return caches;
50 | };
51 |
52 | const _steps = Symbol('steps');
53 | const _caches = Symbol('caches');
54 | const _debugLogs = Symbol('debug logs');
55 |
56 | let uid = 1;
57 |
58 | export default class Source {
59 | static caches = [];
60 |
61 | constructor(
62 | { options = {}, steps = DEFAULT_STEPS, context = {}, headers = {} } = {}
63 | ) {
64 | if (!this.constructor.uid) {
65 | this.constructor.uid = uid++;
66 | }
67 |
68 | this.context = context;
69 | this.headers = headers;
70 | this.options = options;
71 |
72 | this[_steps] = this.constructor.steps || steps;
73 | this[_caches] = buildCaches(this);
74 |
75 | if (this.constructor.api) {
76 | for (const id of Object.keys(this.constructor.api)) {
77 | this[camelCase(`invoke ${id}`)] = this.invoke.bind(this, id);
78 | }
79 | }
80 | }
81 |
82 | getSteps() {
83 | return this[_steps];
84 | }
85 |
86 | enableDebugLogs() {
87 | this[_debugLogs] = true;
88 | }
89 |
90 | disableDebugLogs() {
91 | this[_debugLogs] = false;
92 | }
93 |
94 | getCache(method) {
95 | const cacheMehtodName = getMethodCacheName(this, method);
96 | return this[_caches].get(cacheMehtodName);
97 | }
98 |
99 | invoke(method, params = {}, context = {}) {
100 | let log = null;
101 |
102 | if (this[_debugLogs]) {
103 | log = (...args) => {
104 | console.log('Bivrost --> ', ...args);
105 | };
106 |
107 | log(`"${method}" call argumengts`, params, context);
108 | }
109 |
110 | const fn = (params, context) => {
111 | let stepsPromise = Promise.resolve(params);
112 |
113 | for (let stepId of this[_steps]) {
114 | let step = null;
115 |
116 | if (isFunction(stepId)) {
117 | step = stepId;
118 | } else {
119 | const stepConfig = this.constructor[stepId] || {};
120 | step = isFunction(stepConfig) ? stepConfig : stepConfig[method];
121 |
122 | if (!step) {
123 | continue;
124 | }
125 | }
126 |
127 | stepsPromise = stepsPromise.then(input => {
128 | if (log) {
129 | log(`"${stepId}" call`, input, context);
130 | }
131 |
132 | return Promise.resolve(null)
133 | .then(() => step(input, context))
134 | .then(output => {
135 | if (log) {
136 | log(`"${stepId}" response`, output);
137 | }
138 |
139 | return output;
140 | })
141 | .catch(error => {
142 | if (log) {
143 | log(`"${stepId}" error`, error);
144 | }
145 |
146 | return Promise.reject(error);
147 | });
148 | });
149 | }
150 |
151 | return stepsPromise;
152 | };
153 |
154 | const cache = this.getCache(method);
155 |
156 | const methodContext = {
157 | ...this.context,
158 | ...context,
159 | };
160 |
161 | const headers = {
162 | ...(this.context.headers || {}),
163 | ...(this.headers || {}),
164 | ...(context.headers || {}),
165 | };
166 |
167 | if (Object.keys(headers).length) {
168 | methodContext.headers = headers;
169 | }
170 |
171 | if (!cache) {
172 | return fn(params, methodContext);
173 | } else {
174 | const key = this.getCacheKey(method, params, methodContext);
175 |
176 | if (log) {
177 | if (cache.has(key)) {
178 | log(`get from cache by key "${key}"`);
179 | } else {
180 | log('cache miss');
181 | }
182 | }
183 |
184 | return promiseCache(cache, key, () => fn(params, methodContext));
185 | }
186 | }
187 |
188 | getCacheKey(method, params = {}, context = {}) {
189 | return JSON.stringify(params);
190 | }
191 |
192 | clearCache(method, params) {
193 | let cacheKey = null;
194 | if (params) {
195 | cacheKey = this.getCacheKey(method, params);
196 | }
197 |
198 | let caches = method ? [this.getCache(method)] : this[_caches].values();
199 |
200 | if (this[_debugLogs]) {
201 | if (cacheKey) {
202 | console.log(`Bivrost ---> clear cache for "${method}@${cacheKey}"`);
203 | } else {
204 | console.log(`Bivrost ---> clear all "${method}" caches`);
205 | }
206 | }
207 |
208 | for (let cache of caches) {
209 | if (cache) {
210 | cache.clear(cacheKey);
211 | }
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/packages/bivrost/src/http/api.js:
--------------------------------------------------------------------------------
1 | import clientRequest from './clientRequest';
2 |
3 | export const CLIENT_REQUEST_SETUP_ERROR = 'CLIENT_REQUEST_SETUP_ERROR';
4 |
5 | function apiRequestTemplate(template, apiOptions, requestOptions) {
6 | const getRequestExecuteFunction = clientRequest(template, apiOptions);
7 |
8 | const apiRequest = function(params = {}, context = {}) {
9 | let error = null;
10 | let executeRequest = null;
11 |
12 | try {
13 | executeRequest = getRequestExecuteFunction(params);
14 | } catch (e) {
15 | error = {
16 | ok: false,
17 | message: e.message,
18 | type: CLIENT_REQUEST_SETUP_ERROR,
19 | error: e,
20 | };
21 | }
22 |
23 | if (error) {
24 | return Promise.reject(error);
25 | } else {
26 | return executeRequest({
27 | headers: {
28 | ...context.headers,
29 | ...requestOptions.headers,
30 | },
31 | });
32 | }
33 | };
34 |
35 | apiRequest.displayName = `API: ${template}`;
36 | apiRequest.stringify = params => getRequestExecuteFunction.stringify(params);
37 |
38 | return apiRequest;
39 | }
40 |
41 | export default function api(apiOptions = {}) {
42 | return (template, requestOptions = {}) =>
43 | apiRequestTemplate(template, apiOptions, requestOptions);
44 | }
45 |
--------------------------------------------------------------------------------
/packages/bivrost/src/http/clientRequest.js:
--------------------------------------------------------------------------------
1 | import qs from 'qs';
2 | import createRequestTemplate from './createRequestTemplate';
3 | import promiseDeduplicator from '../utils/promiseDeduplicator';
4 |
5 | const join = (...parts) =>
6 | parts
7 | .join('/')
8 | .replace(/[\/]+/g, '/')
9 | .replace(/\/\?/g, '?')
10 | .replace(/\/\#/g, '#')
11 | .replace(/\:\//g, '://');
12 |
13 | const buildUrl = (protocol = 'http:', host, prefix, path) =>
14 | `${protocol}//${join(host, prefix, path)}`;
15 |
16 | export default function clientRequest(template, options = {}) {
17 | const getRequest = createRequestTemplate(template);
18 |
19 | const adapter = options.adapter;
20 |
21 | function getRequestExecuteFunction(params = {}) {
22 | const request = getRequest(params);
23 | const url = buildUrl(
24 | options.protocol,
25 | options.host,
26 | options.prefix,
27 | request.path
28 | );
29 |
30 | return function executeRequest({ headers = {} } = {}) {
31 | const promiseCreator = () =>
32 | adapter(url, {
33 | ...request,
34 | headers: {
35 | ...request.headers,
36 | ...headers,
37 | },
38 | });
39 |
40 | if (options.deduplicate && request.method === 'GET') {
41 | let dedupKey = JSON.stringify([request.method, url, request.query]);
42 |
43 | return promiseDeduplicator(dedupKey, promiseCreator);
44 | } else {
45 | return promiseCreator();
46 | }
47 | };
48 | }
49 |
50 | getRequestExecuteFunction.stringify = params => {
51 | const request = getRequest(params);
52 |
53 | const url = buildUrl(
54 | options.protocol,
55 | options.host,
56 | options.prefix,
57 | request.path
58 | );
59 |
60 | const queryString = qs.stringify(request.query);
61 | return `${url}${queryString ? `?${queryString}` : ''}`;
62 | };
63 |
64 | return getRequestExecuteFunction;
65 | }
66 |
--------------------------------------------------------------------------------
/packages/bivrost/src/http/createRequestTemplate.js:
--------------------------------------------------------------------------------
1 | import Url from 'url';
2 |
3 | const getUniqueBindings = (queryBindings, pathBindings) =>
4 | new Set([...queryBindings, ...pathBindings]);
5 |
6 | const buildQuery = (queryBindings, queryDefaults, paramsMap) => {
7 | let query = {};
8 |
9 | for (let def of queryDefaults) {
10 | query[def.key] = def.value;
11 | }
12 |
13 | for (let key of queryBindings) {
14 | if (paramsMap.has(key)) {
15 | query[key] = paramsMap.get(key);
16 | }
17 | }
18 |
19 | return query;
20 | };
21 |
22 | const buildPath = (path, paramsMap) =>
23 | path.replace(/:([\w\d]+)/g, (str, paramName) => {
24 | if (!paramsMap.has(paramName)) {
25 | throw new Error(
26 | `could not build path ("${path}") - param "${paramName}" does not exist`
27 | );
28 | }
29 |
30 | return paramsMap.get(paramName);
31 | });
32 |
33 | const buildUnboundParams = (exceptParamsSet, paramsMap) => {
34 | const newParams = {};
35 |
36 | paramsMap.forEach((value, key) => {
37 | if (exceptParamsSet.has(key)) {
38 | return;
39 | }
40 |
41 | newParams[key] = value;
42 | });
43 |
44 | return newParams;
45 | };
46 |
47 | const fNot = f => a => !f(a);
48 |
49 | const getTemplateBinding = str => {
50 | let matches = str.match(/^:([\w\d]+)$/);
51 | return matches ? matches[1] : null;
52 | };
53 |
54 | const extractQueryBindings = url => {
55 | return Object.keys(url.query)
56 | .map(getTemplateBinding)
57 | .filter(it => it !== null);
58 | };
59 |
60 | const extractQueryDefaults = url => {
61 | return Object.keys(url.query)
62 | .filter(fNot(getTemplateBinding))
63 | .map(it => [it, url.query[it]]);
64 | };
65 |
66 | const extractPathBindings = url => {
67 | return url.pathname
68 | .split('/')
69 | .map(getTemplateBinding)
70 | .filter(it => it !== null);
71 | };
72 |
73 | const extractMethodAndUrl = templateString => {
74 | const parts = templateString.match(/^([a-z]+)\s(.+)$/i);
75 | let method = 'GET';
76 | let url = templateString;
77 |
78 | if (parts) {
79 | method = parts[1].toUpperCase();
80 | url = parts[2];
81 | }
82 |
83 | return [method, url];
84 | };
85 |
86 | const getParamsMap = params => {
87 | const paramsMap = new Map();
88 | for (const key of Object.keys(params)) {
89 | paramsMap.set(key, params[key]);
90 | }
91 |
92 | return paramsMap;
93 | };
94 |
95 | const parseRequestTemplate = templateString => {
96 | const [httpMethod, urlTemplate] = extractMethodAndUrl(templateString);
97 |
98 | const url = Url.parse(urlTemplate, true);
99 | const queryBindings = extractQueryBindings(url);
100 | const queryDefaults = extractQueryDefaults(url);
101 | const pathBindings = extractPathBindings(url);
102 |
103 | return {
104 | httpMethod,
105 | queryBindings,
106 | queryDefaults,
107 | pathBindings,
108 | path: url.pathname,
109 | };
110 | };
111 |
112 | const methodsWithBody = new Set(['POST', 'PUT', 'PATCH']);
113 |
114 | export default function getRequestTemplate(template) {
115 | const {
116 | httpMethod,
117 | queryBindings,
118 | queryDefaults,
119 | pathBindings,
120 | path,
121 | } = parseRequestTemplate(template);
122 |
123 | const uniqueBindings = getUniqueBindings(queryBindings, pathBindings);
124 |
125 | return function getRequest(params = {}) {
126 | const isIterable = params.forEach instanceof Function;
127 |
128 | let templateParams = null;
129 |
130 | if (isIterable) {
131 | params.forEach((value, key) => {
132 | templateParams[key] = value;
133 | });
134 | } else {
135 | templateParams = params;
136 | }
137 |
138 | let paramsMap = getParamsMap(templateParams);
139 |
140 | let request = {};
141 |
142 | let unboundQuery = {};
143 |
144 | if (methodsWithBody.has(httpMethod)) {
145 | request.body = !isIterable
146 | ? buildUnboundParams(uniqueBindings, paramsMap)
147 | : params;
148 | } else {
149 | unboundQuery = !isIterable
150 | ? buildUnboundParams(uniqueBindings, paramsMap)
151 | : params;
152 | }
153 |
154 | return {
155 | ...request,
156 | query: {
157 | ...buildQuery(queryBindings, queryDefaults, paramsMap),
158 | ...unboundQuery,
159 | },
160 | path: buildPath(path, paramsMap),
161 | method: httpMethod,
162 | };
163 | };
164 | }
165 |
--------------------------------------------------------------------------------
/packages/bivrost/src/utils/mockDataSource.js:
--------------------------------------------------------------------------------
1 | const isFunction = func =>
2 | func && {}.toString.call(func) === '[object Function]';
3 |
4 | const mockDataSource = (ds, mockFn) => {
5 | const mocks = {};
6 |
7 | for (let stepId of ds.getSteps()) {
8 | if (isFunction(stepId)) {
9 | ds.constructor[stepId] = mockFn(ds.constructor[stepId]);
10 | mocks[stepId] = ds.constructor[stepId];
11 | } else {
12 | const stepConfig = ds.constructor[stepId] || {};
13 | mocks[stepId] = {};
14 |
15 | for (let methodId of Object.keys(stepConfig)) {
16 | stepConfig[methodId] = mockFn(stepConfig[methodId]);
17 | mocks[stepId][methodId] = stepConfig[methodId];
18 | }
19 | }
20 | }
21 |
22 | return mocks;
23 | };
24 |
25 | export default mockDataSource;
26 |
--------------------------------------------------------------------------------
/packages/bivrost/src/utils/promiseCache.js:
--------------------------------------------------------------------------------
1 | export default function promiseCache(cache, key, fn, ttl) {
2 | return new Promise((resolve, reject) => {
3 | const previouslyCached = cache.get(key);
4 |
5 | if (null !== previouslyCached) {
6 | resolve(previouslyCached);
7 | return;
8 | }
9 |
10 | fn().then(
11 | result => {
12 | cache.put(key, result, ttl);
13 | resolve(result);
14 | },
15 | err => {
16 | reject(err);
17 | }
18 | );
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/packages/bivrost/src/utils/promiseDeduplicator.js:
--------------------------------------------------------------------------------
1 | import Cache from '../data/cache';
2 |
3 | /**
4 | * If the promise with given `key` is still running, it is returned instead of creating new one.
5 | * Useful for HTTP request deduplication.
6 | *
7 | * let promise1 = PromiseDeduplicator('http://example.com/', http.get.bind(http, 'http://example.com/'))
8 | * let promise2 = PromiseDeduplicator('http://example.com/', http.get.bind(http, 'http://example.com/'))
9 | * promise1 === promise2;
10 | */
11 | const cacheMap = new Map();
12 |
13 | export default function promiseDeduplicator(key, fnCreatePromise) {
14 | const cached = cacheMap.get(key);
15 |
16 | if (!cached) {
17 | const promise = fnCreatePromise().then(
18 | result => {
19 | cacheMap.delete(key);
20 |
21 | return result;
22 | },
23 | error => {
24 | cacheMap.delete(key);
25 |
26 | return Promise.reject(error);
27 | }
28 | );
29 |
30 | cacheMap.set(key, promise);
31 | return promise;
32 | } else {
33 | return cached;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/bivrost/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | asynckit@^0.4.0:
6 | version "0.4.0"
7 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
8 |
9 | combined-stream@1.0.6:
10 | version "1.0.6"
11 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
12 | dependencies:
13 | delayed-stream "~1.0.0"
14 |
15 | delayed-stream@~1.0.0:
16 | version "1.0.0"
17 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
18 |
19 | form-data@^2.3.1:
20 | version "2.3.2"
21 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
22 | dependencies:
23 | asynckit "^0.4.0"
24 | combined-stream "1.0.6"
25 | mime-types "^2.1.12"
26 |
27 | lodash.camelcase@^4.3.0:
28 | version "4.3.0"
29 | resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
30 |
31 | mime-db@~1.33.0:
32 | version "1.33.0"
33 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
34 |
35 | mime-types@^2.1.12:
36 | version "2.1.18"
37 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
38 | dependencies:
39 | mime-db "~1.33.0"
40 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /.idea
4 | /_book
5 |
6 | /node_modules
7 | /index.js
8 | /node.js
9 | /setup.js
10 | /createInterceptors.js
11 |
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
--------------------------------------------------------------------------------
/packages/fetch-api-call/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.git
3 | /__tests__
4 | /.idea
5 | /npm-debug.log
6 | /node_modules
7 | /coverage
8 | /src
9 |
10 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/README.md:
--------------------------------------------------------------------------------
1 | # Fetch API Call
2 |
3 | Awesome fetch api for browser and nodejs with interceptors easy way to declare
4 | api endpoints. Provide very easy way to setup api for different
5 | backends/services.
6 |
7 | * [Advantages](#advantages)
8 | * [Api Declaration](#api-declaration)
9 | * [Interceptors](#interceptors)
10 | * [Request Headers](#request-headers)
11 | * [Custom Setup](#custom-setup)
12 | * [Bivrost Docs](http://tuchk4.github.io/bivrost/)
13 |
14 | ---
15 |
16 | ## Advantages
17 |
18 | * Support interceptors
19 | * Support multiple instances wiht its own interceptors
20 | * Easy way to define headers for specific requests
21 | * Declrative api end points
22 |
23 | ```js
24 | const login = api('POST /login');
25 | login({});
26 |
27 | const loadStatistics = api('GET /statistics');
28 | loadStatistics({
29 | filter: {},
30 | });
31 |
32 | const loadUser = api('GET /user/:id');
33 | laodUser({
34 | id: 1,
35 | });
36 |
37 |
38 | // or even bind params to query
39 | const getUserDetails = api('POST /user/:id?:version');
40 | getUser({
41 | id: 1,
42 | data: {},
43 | version: 2
44 | });
45 | ```
46 |
47 | ## Install
48 |
49 | ```
50 | yarn add fetch-api-call
51 | ```
52 |
53 | ## Api Declaration
54 |
55 | ```js
56 | import fetchApiCall from 'fetch-api-call';
57 |
58 | const { api } = fetchApiCall({
59 | protocol: process.env.API_PROTOCOL,
60 | host: process.env.API_HOST,
61 | });
62 | ```
63 |
64 | Options:
65 |
66 | * _protocol_ - http protocol. Available values - _http:_ an _https:_
67 | * _host_ - server hostname
68 | * _headers_ - requests headers. NOTE that headers could be also set with
69 | interceptors
70 | * _mode_ - more details at -
71 | https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
72 | * _prefix_ - api url prefix. Useful when there are multiple api versions:
73 | * _/user_
74 | * _/v1/user_
75 | * _/v2/user_
76 |
77 | **api** - function to define api methods in single line of code.
78 |
79 | ```js
80 | const apiCall = api('HTTP_METHOD /api/path/:with/:placeholders');
81 | ```
82 |
83 | **apiCall(params = {}, headers = {})** - function takes two optional params:
84 |
85 | * _params_ - api params. Also is used for building final api entrypoint. More
86 | detauls see at
87 | [Api Url Definition and Placeholders](https://tuchk4.github.io/bivrost/docs/basics/api-function.html#api-definition).
88 | * _headers_ - certain request headers.
89 |
90 | Examples:
91 |
92 | ```js
93 | // user login api
94 | const login = api('POST /login');
95 | login({
96 | username,
97 | password,
98 | });
99 |
100 | // user logout api
101 | const logout = api('POST /logout');
102 | logout();
103 |
104 | // get user api
105 | const getUser = api('GET /user/:id');
106 | getUser({
107 | id: 1,
108 | });
109 | ```
110 |
111 | ## Interceptors
112 |
113 | ```js
114 | import fetchApiCall from 'fetch-api-call';
115 |
116 | const { api, interceptors } = fetchApiCall({
117 | protocol: process.env.API_PROTOCOL,
118 | host: process.env.API_HOST,
119 | });
120 | ```
121 |
122 | **interceptors** methods:
123 |
124 | * _addRequestInterceptor(request)_
125 | * _addResponseInterceptor(response)_
126 | * _addErrorInterceptor(error)_
127 |
128 | Each methods return function to remove interceptor.
129 |
130 | Iterceptors will be called for all request done with _api_.
131 |
132 | ```js
133 | const login = api('POST /login');
134 | const logout = api('POST /logout');
135 |
136 | let removeAcessTokenIterceptor = null;
137 |
138 | const { acessToken } = await login({});
139 |
140 | removeAcessTokenIterceptor = iterceptors.addRequestInterceptor(request => {
141 | request.headers.set('Acess-Token', acessToken);
142 | return request;
143 | });
144 |
145 |
146 | await logout();
147 | removeAcessTokenIterceptor();
148 | ```
149 |
150 | ## Request Headers
151 |
152 | This is useful when using `fetch-api-call` on NodeJS. In some cases it is not
153 | possible to use interceptors to set auth headers becasue it will work for all
154 | request. We should specify certain request with certain header.
155 |
156 | ```js
157 | const getStatistics = api('GET /statistics');
158 | getStatistics(
159 | {},
160 | {
161 | headers: {
162 | MyCustomHeader: '123zxc',
163 | },
164 | }
165 | );
166 | ```
167 |
168 | ## Multiple Api Instances
169 |
170 | Very useful if there is microservices backend architecture.
171 |
172 | ```js
173 | import fetchApiCall from 'fetch-api-call';
174 |
175 | const Auth = fetchApiCall({
176 | host: process.env.AUTH_API_HOST,
177 | });
178 |
179 | const Users = fetchApiCall({
180 | host: process.env.USERS_API_HOST,
181 | });
182 |
183 | const Data = fetchApiCall({
184 | host: process.env.DATA_API_HOST,
185 | prefix: 'v2',
186 | });
187 |
188 | const login = Auth.api('POST /login').then(({ accessToken }) => {
189 | Users.interceptors.addRequest(res => {
190 | request.headers.set('Acess-Token', acessToken);
191 | return request;
192 | });
193 |
194 | Data.interceptors.addRequest(res => {
195 | request.headers.set('Acess-Token', acessToken);
196 | return request;
197 | });
198 | });
199 |
200 | const loadStatisitcs = Data.api('GET /statisitcs');
201 | loadStatisitcs({
202 | filter: {
203 | //...
204 | },
205 | });
206 | ```
207 |
208 | ## Custom Setup
209 |
210 | ```js
211 | import setup from 'fetch-api-call/setup';
212 | import fetchAdapter from 'bivrost-fetch-adapter';
213 |
214 | const createApi = setup({
215 | headers: {
216 | 'Content-Type': 'application/json',
217 | },
218 | mode: 'cors',
219 | adapter: fetchAdapter,
220 | interceptors: {
221 | resposne: res => res,
222 | request: req => req,
223 | error: err => err,
224 | },
225 | deduplicate: false
226 | });
227 |
228 | const api = createApi({
229 | protocol: process.env.API_PROTOCOL,
230 | host: process.env.API_HOST,
231 | });
232 |
233 | const login = api('POST /login');
234 | ```
235 |
236 | Options:
237 |
238 | * _headers_ - default headers
239 | * _mode_ - default mode. More details at -
240 | https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
241 | * _adapter_ - default HTTP adapter. See more at -
242 | https://tuchk4.github.io/bivrost/docs/adapters.html
243 | * _interceptors_ - initial interceptors. NOTE that this is NOT DEFAULT
244 | interceptors.
245 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/__tests__/index.js:
--------------------------------------------------------------------------------
1 | import fetchApiCall from '../src';
2 |
3 | describe('fetchApiCall', () => {
4 | it('should process error', () => {
5 | const { api, interceptors } = fetchApiCall({
6 | protocol: 'http:',
7 | host: 'httpbin.org',
8 | });
9 |
10 | const badRequest = api('GET /status/:status');
11 | const thenMock = jest.fn();
12 |
13 | return badRequest({
14 | status: 402,
15 | })
16 | .then(thenMock)
17 | .catch(e => {
18 | expect(e).toEqual('Fuck you, pay me!');
19 | })
20 | .then(() => {
21 | expect(thenMock.mock.calls.length).toEqual(0);
22 | });
23 | });
24 |
25 | it('should process request options', () => {
26 | const { api, interceptors } = fetchApiCall({
27 | protocol: 'http:',
28 | host: 'httpbin.org',
29 | request: {
30 | credentials: 'include',
31 | },
32 | });
33 |
34 | const request = api('GET /response-headers');
35 |
36 | return request({
37 | status: 200,
38 | }).then(r => {
39 | console.log(r);
40 | });
41 | });
42 |
43 | it('should pass post body', () => {
44 | const { api, interceptors } = fetchApiCall({
45 | protocol: 'http:',
46 | host: 'httpbin.org',
47 | });
48 |
49 | const login = api('POST /post');
50 | const data = { foo: 'bar' };
51 |
52 | return login(data).then(res => {
53 | expect(res.json).toEqual(data);
54 | });
55 | });
56 |
57 | it('should run call with custom headers 1', () => {
58 | const { api, interceptors } = fetchApiCall({
59 | protocol: 'http:',
60 | host: 'httpbin.org',
61 | });
62 |
63 | const withCustomHeaders = api('GET /headers');
64 |
65 | return withCustomHeaders(
66 | {},
67 | {
68 | headers: { 'x-with-custom-header': '123' },
69 | }
70 | ).then(res => {
71 | expect(res.headers['X-With-Custom-Header']).toEqual('123');
72 | });
73 | });
74 |
75 | it('should run call with custom headers 2', () => {
76 | const { api, interceptors } = fetchApiCall({
77 | protocol: 'http:',
78 | host: 'httpbin.org',
79 | });
80 |
81 | const withCustomHeaders = api('GET /headers', {
82 | headers: {
83 | 'x-with-custom-header-2': '345',
84 | },
85 | });
86 |
87 | return withCustomHeaders(
88 | {},
89 | {
90 | headers: { 'x-with-custom-header': '123' },
91 | }
92 | ).then(res => {
93 | expect(res.headers['X-With-Custom-Header']).toEqual('123');
94 | expect(res.headers['X-With-Custom-Header-2']).toEqual('345');
95 | });
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/fetch-api-call-3.2.4.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/bivrost/f6d6d5973bee77c02ee050dd34ed19a5f96b0a42/packages/fetch-api-call/fetch-api-call-3.2.4.tgz
--------------------------------------------------------------------------------
/packages/fetch-api-call/node.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var createApi = require('./index');
4 | module.exports = createApi.default;
5 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fetch-api-call",
3 | "version": "3.2.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "asynckit": {
8 | "version": "0.4.0",
9 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
10 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
11 | },
12 | "bivrost": {
13 | "version": "3.2.0",
14 | "resolved": "https://registry.npmjs.org/bivrost/-/bivrost-3.2.0.tgz",
15 | "integrity": "sha512-61YUHZd1QzAkqruJJBZFXEcn5zngs29ZOCA0ccgNXacwGTLIfMyzMvgJTKslDBu7RqbOb66pYbMJrKGQKL3aPA==",
16 | "requires": {
17 | "form-data": "^2.3.1",
18 | "lodash.camelcase": "^4.3.0"
19 | }
20 | },
21 | "bivrost-fetch-adapter": {
22 | "version": "3.1.0",
23 | "resolved": "https://registry.npmjs.org/bivrost-fetch-adapter/-/bivrost-fetch-adapter-3.1.0.tgz",
24 | "integrity": "sha512-+6fgXUT30XYDn5H2T9SPu3sRKSRK9qTesw5dzXMDOTH3i6DsKEZ8+83ZohDz+8uK5YnQHd1meX2hM+5Bluso8g==",
25 | "requires": {
26 | "form-data": "^2.3.1",
27 | "isomorphic-fetch": "2.2.1",
28 | "qs": "^6.5.1"
29 | }
30 | },
31 | "combined-stream": {
32 | "version": "1.0.6",
33 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
34 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
35 | "requires": {
36 | "delayed-stream": "~1.0.0"
37 | }
38 | },
39 | "delayed-stream": {
40 | "version": "1.0.0",
41 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
42 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
43 | },
44 | "encoding": {
45 | "version": "0.1.12",
46 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
47 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
48 | "requires": {
49 | "iconv-lite": "~0.4.13"
50 | }
51 | },
52 | "form-data": {
53 | "version": "2.3.2",
54 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
55 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
56 | "requires": {
57 | "asynckit": "^0.4.0",
58 | "combined-stream": "1.0.6",
59 | "mime-types": "^2.1.12"
60 | }
61 | },
62 | "iconv-lite": {
63 | "version": "0.4.23",
64 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
65 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
66 | "requires": {
67 | "safer-buffer": ">= 2.1.2 < 3"
68 | }
69 | },
70 | "is-stream": {
71 | "version": "1.1.0",
72 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
73 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
74 | },
75 | "isomorphic-fetch": {
76 | "version": "2.2.1",
77 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
78 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
79 | "requires": {
80 | "node-fetch": "^1.0.1",
81 | "whatwg-fetch": ">=0.10.0"
82 | }
83 | },
84 | "lodash.camelcase": {
85 | "version": "4.3.0",
86 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
87 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
88 | },
89 | "mime-db": {
90 | "version": "1.33.0",
91 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
92 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
93 | },
94 | "mime-types": {
95 | "version": "2.1.18",
96 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
97 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
98 | "requires": {
99 | "mime-db": "~1.33.0"
100 | }
101 | },
102 | "node-fetch": {
103 | "version": "1.7.3",
104 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
105 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
106 | "requires": {
107 | "encoding": "^0.1.11",
108 | "is-stream": "^1.0.1"
109 | }
110 | },
111 | "qs": {
112 | "version": "6.5.2",
113 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
114 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
115 | },
116 | "regenerator-runtime": {
117 | "version": "0.11.1",
118 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
119 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
120 | },
121 | "safer-buffer": {
122 | "version": "2.1.2",
123 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
124 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
125 | },
126 | "whatwg-fetch": {
127 | "version": "2.0.4",
128 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
129 | "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fetch-api-call",
3 | "version": "3.2.5",
4 | "description": "Fetch api call supporting url params / interceptors / awesome api setup",
5 | "keywords": [
6 | "rest",
7 | "bivrost",
8 | "data-source",
9 | "api",
10 | "fetch",
11 | "axios",
12 | "local-storage",
13 | "fetch-api-call",
14 | "response",
15 | "request",
16 | "call steps"
17 | ],
18 | "browser": "index.js",
19 | "main": "node.js",
20 | "scripts": {
21 | "prepublish": "../../node_modules/.bin/babel -d ./ ./src",
22 | "dev": "../../node_modules/.bin/babel -d ./ ./src --watch"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/tuchk4/bivrost.git"
27 | },
28 | "author": "tuchk4 ",
29 | "license": "ISC",
30 | "bugs": {
31 | "url": "https://github.com/tuchk4/bivrost/issues"
32 | },
33 | "homepage": "hhttps://github.com/tuchk4/bivrost",
34 | "dependencies": {
35 | "bivrost": "^3.2.5",
36 | "bivrost-fetch-adapter": "^3.2.2",
37 | "regenerator-runtime": "^0.11.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/src/createInterceptors.js:
--------------------------------------------------------------------------------
1 | export default (defaultInterceptors = {}) => {
2 | const interceptors = {
3 | error: new Set(),
4 | request: new Set(),
5 | response: new Set(),
6 | };
7 |
8 | const api = {
9 | // -----
10 | request: request =>
11 | [...interceptors.request].reduce((request, cb) => cb(request), request),
12 |
13 | // -----
14 | error: error =>
15 | [...interceptors.error].reduce(
16 | (error, cb) => error.then(err => cb(err)),
17 | Promise.resolve(error)
18 | ),
19 |
20 | // -----
21 | response: response =>
22 | [...interceptors.response].reduce((response, cb) => {
23 | return response.then(res => cb(res));
24 | }, Promise.resolve(response)),
25 |
26 | // -----
27 | // -----
28 |
29 | addErrorInterceptor: cb => {
30 | interceptors.error.add(cb);
31 | return () => interceptors.error.delete(cb);
32 | },
33 | addRequestInterceptor: cb => {
34 | interceptors.request.add(cb);
35 | return () => interceptors.request.delete(cb);
36 | },
37 | addResponseInterceptor: cb => {
38 | interceptors.response.add(cb);
39 | return () => interceptors.response.delete(cb);
40 | },
41 | };
42 |
43 | if (defaultInterceptors.request) {
44 | api.addRequestInterceptor(defaultInterceptors.request);
45 | }
46 |
47 | if (defaultInterceptors.error) {
48 | api.addErrorInterceptor(defaultInterceptors.error);
49 | }
50 |
51 | if (defaultInterceptors.response) {
52 | api.addResponseInterceptor(defaultInterceptors.response);
53 | }
54 |
55 | return api;
56 | };
57 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/src/index.js:
--------------------------------------------------------------------------------
1 | import regeneratorRuntime from 'regenerator-runtime';
2 | import fetchAdapter from 'bivrost-fetch-adapter';
3 | import setup from './setup';
4 |
5 | function processResponse(response) {
6 | const headers = response && response.headers;
7 |
8 | if (headers) {
9 | const contentType = headers.get('content-type');
10 |
11 | if (contentType && contentType.indexOf('application/json') !== -1) {
12 | return response.json();
13 | } else if (contentType && contentType.indexOf('image') !== -1) {
14 | return response.blob();
15 | } else {
16 | return response.text();
17 | }
18 | }
19 |
20 | return response;
21 | }
22 |
23 | export default setup({
24 | headers: {
25 | 'Content-Type': 'application/json',
26 | },
27 | mode: 'cors',
28 | adapter: fetchAdapter,
29 | interceptors: {
30 | error: async error => await processResponse(error),
31 | response: async response => await processResponse(response),
32 | },
33 | });
34 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/src/node.js:
--------------------------------------------------------------------------------
1 | const createApi = require('./index');
2 | module.exports = createApi.default;
3 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/src/setup.js:
--------------------------------------------------------------------------------
1 | import regeneratorRuntime from 'regenerator-runtime';
2 | import api from 'bivrost/http/api';
3 |
4 | import createInterceptors from './createInterceptors';
5 | export default ({ adapter, ...defaults }) => ({
6 | headers,
7 | mode,
8 | // https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
9 | request = {
10 | ...defaults.request,
11 | ...request,
12 | },
13 | ...options
14 | }) => {
15 | const interceptors = createInterceptors(defaults.interceptors);
16 | return {
17 | interceptors,
18 | api: api({
19 | adapter: adapter({
20 | mode: mode ? mode : defaults.mode,
21 | headers: headers ? headers : defaults.headers,
22 | interceptors: {
23 | request: request => interceptors.request(request),
24 | error: async error => {
25 | throw await interceptors.error(error);
26 | },
27 | response: async response => await interceptors.response(response),
28 | },
29 | ...request,
30 | }),
31 | deduplicate: defaults.hasOwnProperty('deduplicate')
32 | ? defaults.deduplicate
33 | : true,
34 | ...options,
35 | }),
36 | };
37 | };
38 |
--------------------------------------------------------------------------------
/packages/fetch-api-call/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | regenerator-runtime@^0.11.0:
6 | version "0.11.1"
7 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
8 |
--------------------------------------------------------------------------------