├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── batch.md └── monorepo.md ├── lerna.json ├── package.json ├── packages ├── apollo-fetch-upload │ ├── .gitignore │ ├── .npmrc │ ├── changelog.md │ ├── package.json │ ├── readme.md │ ├── src │ │ └── index.ts │ ├── test │ │ └── src │ │ │ └── index.ts │ ├── tsconfig.main.json │ ├── tsconfig.module.json │ ├── tsconfig.test.json │ └── tslint.json └── apollo-fetch │ ├── changelog.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── apollo-fetch.ts │ ├── index.ts │ └── types.ts │ ├── tests │ ├── apollo-fetch.ts │ └── tests.ts │ ├── tsconfig.base.json │ ├── tsconfig.json │ ├── tsconfig.test.json │ └── tslint.json └── renovate.json /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | **Intended outcome:** 11 | 14 | 15 | **Actual outcome:** 16 | 21 | 22 | **How to reproduce the issue:** 23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | TODO: 9 | 10 | - [ ] If this PR is a new feature, reference an issue where a consensus about the design was reached (not necessary for small changes) 11 | - [ ] Make sure all of the significant new logic is covered by tests 12 | - [ ] Rebase your changes on master so that they can be merged easily 13 | - [ ] Make sure all tests and linter rules pass 14 | - [ ] Update CHANGELOG.md with your change 15 | - [ ] Add your name and email to the AUTHORS file (optional) 16 | - [ ] If this was a change that affects the external API, update the docs and post a link to the PR in the discussion 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Yarn Integrity file 47 | .yarn-integrity 48 | 49 | # 50 | dist 51 | 52 | # lockfiles 53 | yarn.lock 54 | package-lock.json 55 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | tests/ 3 | .travis.yml 4 | rollup.config.js 5 | tsconfig.json 6 | tsconfig.base.json 7 | tsconfig.test.json 8 | tslint.json 9 | typings.d.ts 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 6 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | notifications: 13 | email: false 14 | 15 | before_install: 16 | - npm install -g coveralls lerna 17 | - lerna run -- prune 18 | 19 | install: 20 | - npm run bootstrap 21 | 22 | script: 23 | - npm test 24 | 25 | after_success: 26 | - npm run coverage 27 | - coveralls < ./coverage/lcov.info || true 28 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Mocha Tests", 8 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 9 | "args": [ 10 | "--reporter", 11 | "spec", 12 | "--trace-warnings", 13 | "--full-trace", 14 | "${workspaceRoot}/**/dist/tests/tests.js" 15 | ], 16 | "outFiles": ["${workspaceRoot}/**/dist/**/*.js"], 17 | "sourceMaps": true, 18 | "internalConsoleOptions": "openOnSessionStart", 19 | "stopOnEntry": false, 20 | "protocol": "inspector" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.rulers": [140], 4 | "files.trimTrailingWhitespace": true, 5 | "files.insertFinalNewline": true, 6 | "files.exclude": { 7 | "**/.git": true, 8 | "**/.DS_Store": true, 9 | "**/node_modules": true, 10 | "dist": true, 11 | "coverage": true, 12 | "npm": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "tasks": [ 4 | { 5 | "taskName": "tsc", 6 | "command": "tsc", 7 | "isShellCommand": true, 8 | "args": [ 9 | "-p", 10 | "tsconfig.json" 11 | ], 12 | "showOutput": "silent", 13 | "problemMatcher": "$tsc" 14 | }, 15 | { 16 | "taskName": "watch-tsc", 17 | "command": "lerna", 18 | "isShellCommand": true, 19 | "isBuildCommand": true, 20 | "isBackground": true, 21 | "args": [ 22 | "run", 23 | "watch" 24 | ], 25 | "showOutput": "always", 26 | "problemMatcher": "$tsc-watch" 27 | // "problemMatcher": { 28 | // "owner": "typescript", 29 | // "fileLocation": "relative", 30 | // "pattern": { 31 | // "regexp": "^([^\\s].*)\\((\\d+|\\,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", 32 | // "file": 1, 33 | // "location": 2, 34 | // "severity": 3, 35 | // "code": 4, 36 | // "message": 5 37 | // }, 38 | // "background": { 39 | // "activeOnStart": true, 40 | // "beginsPattern": "^\\s*\\d{1,2}:\\d{1,2}:\\d{1,2}(?: AM| PM)? - File change detected\\. Starting incremental compilation\\.\\.\\.", 41 | // "endsPattern": "^\\s*\\d{1,2}:\\d{1,2}:\\d{1,2}(?: AM| PM)? - Compilation complete\\. Watching for file changes\\." 42 | // } 43 | // } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Apollo Contributor Guide 2 | 3 | Excited about Apollo and want to make it better? We’re excited too! 4 | 5 | Apollo is a community of developers just like you, striving to create the best tools and libraries around GraphQL. We welcome anyone who wants to contribute or provide constructive feedback, no matter the age or level of experience. If you want to help but don't know where to start, let us know, and we'll find something for you. 6 | 7 | Oh, and if you haven't already, sign up for the [Apollo Slack](http://www.apollodata.com/#slack). 8 | 9 | Here are some ways to contribute to the project, from easiest to most difficult: 10 | 11 | * [Reporting bugs](#reporting-bugs) 12 | * [Improving the documentation](#improving-the-documentation) 13 | * [Responding to issues](#responding-to-issues) 14 | * [Small bug fixes](#small-bug-fixes) 15 | * [Suggesting features](#feature-requests) 16 | * [Big pull requests](#big-prs) 17 | 18 | ## Issues 19 | 20 | ### Reporting bugs 21 | 22 | If you encounter a bug, please file an issue on GitHub via the repository of the sub-project you think contains the bug. If an issue you have is already reported, please add additional information or add a 👍 reaction to indicate your agreement. 23 | 24 | While we will try to be as helpful as we can on any issue reported, please include the following to maximize the chances of a quick fix: 25 | 26 | 1. **Intended outcome:** What you were trying to accomplish when the bug occurred, and as much code as possible related to the source of the problem. 27 | 2. **Actual outcome:** A description of what actually happened, including a screenshot or copy-paste of any related error messages, logs, or other output that might be related. Places to look for information include your browser console, server console, and network logs. Please avoid non-specific phrases like “didn’t work” or “broke”. 28 | 3. **How to reproduce the issue:** Instructions for how the issue can be reproduced by a maintainer or contributor. Be as specific as possible, and only mention what is necessary to reproduce the bug. If possible, build a reproduction with our [error template](https://github.com/apollographql/react-apollo-error-template) to isolate the exact circumstances in which the bug occurs. Avoid speculation over what the cause might be. 29 | 30 | Creating a good reproduction really helps contributors investigate and resolve your issue quickly. In many cases, the act of creating a minimal reproduction illuminates that the source of the bug was somewhere outside the library in question, saving time and effort for everyone. 31 | 32 | ### Improving the documentation 33 | 34 | Improving the documentation, examples, and other open source content can be the easiest way to contribute to the library. If you see a piece of content that can be better, open a PR with an improvement, no matter how small! If you would like to suggest a big change or major rewrite, we’d love to hear your ideas but please open an issue for discussion before writing the PR. 35 | 36 | ### Responding to issues 37 | 38 | In addition to reporting issues, a great way to contribute to Apollo is to respond to other peoples' issues and try to identify the problem or help them work around it. If you’re interested in taking a more active role in this process, please go ahead and respond to issues. And don't forget to say "Hi" on Apollo Slack! 39 | 40 | ### Small bug fixes 41 | 42 | For a small bug fix change (less than 20 lines of code changed), feel free to open a pull request. We’ll try to merge it as fast as possible and ideally publish a new release on the same day. The only requirement is, make sure you also add a test that verifies the bug you are trying to fix. 43 | 44 | ### Suggesting features 45 | 46 | Most of the features in Apollo came from suggestions by you, the community! We welcome any ideas about how to make Apollo better for your use case. Unless there is overwhelming demand for a feature, it might not get implemented immediately, but please include as much information as possible that will help people have a discussion about your proposal: 47 | 48 | 1. **Use case:** What are you trying to accomplish, in specific terms? Often, there might already be a good way to do what you need and a new feature is unnecessary, but it’s hard to know without information about the specific use case. 49 | 2. **Could this be a plugin?** In many cases, a feature might be too niche to be included in the core of a library, and is better implemented as a companion package. If there isn’t a way to extend the library to do what you want, could we add additional plugin APIs? It’s important to make the case for why a feature should be part of the core functionality of the library. 50 | 3. **Is there a workaround?** Is this a more convenient way to do something that is already possible, or is there some blocker that makes a workaround unfeasible? 51 | 52 | Feature requests will be labeled as such, and we encourage using GitHub issues as a place to discuss new features and possible implementation designs. Please refrain from submitting a pull request to implement a proposed feature until there is consensus that it should be included. This way, you can avoid putting in work that can’t be merged in. 53 | 54 | Once there is a consensus on the need for a new feature, proceed as listed below under “Big PRs”. 55 | 56 | ## Big PRs 57 | 58 | This includes: 59 | 60 | - Big bug fixes 61 | - New features 62 | 63 | For significant changes to a repository, it’s important to settle on a design before starting on the implementation. This way, we can make sure that major improvements get the care and attention they deserve. Since big changes can be risky and might not always get merged, it’s good to reduce the amount of possible wasted effort by agreeing on an implementation design/plan first. 64 | 65 | 1. **Open an issue.** Open an issue about your bug or feature, as described above. 66 | 2. **Reach consensus.** Some contributors and community members should reach an agreement that this feature or bug is important, and that someone should work on implementing or fixing it. 67 | 3. **Agree on intended behavior.** On the issue, reach an agreement about the desired behavior. In the case of a bug fix, it should be clear what it means for the bug to be fixed, and in the case of a feature, it should be clear what it will be like for developers to use the new feature. 68 | 4. **Agree on implementation plan.** Write a plan for how this feature or bug fix should be implemented. What modules need to be added or rewritten? Should this be one pull request or multiple incremental improvements? Who is going to do each part? 69 | 5. **Submit PR.** In the case where multiple dependent patches need to be made to implement the change, only submit one at a time. Otherwise, the others might get stale while the first is reviewed and merged. Make sure to avoid “while we’re here” type changes - if something isn’t relevant to the improvement at hand, it should be in a separate PR; this especially includes code style changes of unrelated code. 70 | 6. **Review.** At least one core contributor should sign off on the change before it’s merged. Look at the “code review” section below to learn about factors are important in the code review. If you want to expedite the code being merged, try to review your own code first! 71 | 7. **Merge and release!** 72 | 73 | ### Code review guidelines 74 | 75 | It’s important that every piece of code in Apollo packages is reviewed by at least one core contributor familiar with that codebase. Here are some things we look for: 76 | 77 | 1. **Required CI checks pass.** This is a prerequisite for the review, and it is the PR author's responsibility. As long as the tests don’t pass, the PR won't get reviewed. 78 | 2. **Simplicity.** Is this the simplest way to achieve the intended goal? If there are too many files, redundant functions, or complex lines of code, suggest a simpler way to do the same thing. In particular, avoid implementing an overly general solution when a simple, small, and pragmatic fix will do. 79 | 3. **Testing.** Do the tests ensure this code won’t break when other stuff changes around it? When it does break, will the tests added help us identify which part of the library has the problem? Did we cover an appropriate set of edge cases? Look at the test coverage report if there is one. Are all significant code paths in the new code exercised at least once? 80 | 4. **No unnecessary or unrelated changes.** PRs shouldn’t come with random formatting changes, especially in unrelated parts of the code. If there is some refactoring that needs to be done, it should be in a separate PR from a bug fix or feature, if possible. 81 | 5. **Code has appropriate comments.** Code should be commented, or written in a clear “self-documenting” way. 82 | 6. **Idiomatic use of the language.** In TypeScript, make sure the typings are specific and correct. In ES2015, make sure to use imports rather than require and const instead of var, etc. Ideally a linter enforces a lot of this, but use your common sense and follow the style of the surrounding code. 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 - 2017 Meteor Development Group, Inc. 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 | # DEPRECATED 2 | 3 | Apollo Client 2.0 no longer uses `apollo-fetch` but [`apollo-link`](https://github.com/apollographql/apollo-link) instead. See https://www.apollographql.com/docs/react/2.0-migration.html for an example. 4 | 5 | [This module is deprecated and will not receive further updates.](https://github.com/apollographql/apollo-fetch/issues/80) 6 | 7 | # apollo-fetch [![npm version](https://badge.fury.io/js/apollo-fetch.svg)](https://badge.fury.io/js/apollo-fetch) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://www.apollostack.com/#slack) 8 | 9 | 10 | `apollo-fetch` is a lightweight client for GraphQL requests that supports middleware and afterware that modify requests and responses. 11 | 12 | By default `apollo-fetch` uses `cross-fetch`, but you have the option of using a custom fetch function. 13 | 14 | If you are interested in contributing, please read the [documentation on repository structure](docs/monorepo.md) and [Contributor Guide](CONTRIBUTING.md). 15 | 16 | # Installation 17 | 18 | ``` 19 | npm install apollo-fetch --save 20 | ``` 21 | 22 | To use `apollo-fetch` in a web browser or mobile app, you'll need a build system capable of loading NPM packages on the client. 23 | Some common choices include Browserify, Webpack, and Meteor +1.3. 24 | 25 | # Usage 26 | 27 | To create a fetch function capable of supporting middleware and afterware, use `createApolloFetch`: 28 | 29 | ```js 30 | import { createApolloFetch } from 'apollo-fetch'; 31 | 32 | const uri = 'http://api.githunt.com/graphql'; 33 | const apolloFetch = createApolloFetch({ uri }); 34 | ``` 35 | 36 | To execute the fetch function, call `apolloFetch` directly in the following way: 37 | 38 | ```js 39 | apolloFetch({ query, variables, operationName }) //all apolloFetch arguments are optional 40 | .then(result => { 41 | const { data, errors, extensions } = result; 42 | //GraphQL errors and extensions are optional 43 | }) 44 | .catch(error => { 45 | //respond to a network error 46 | }); 47 | ``` 48 | 49 | ### Middleware and Afterware 50 | 51 | Middleware and Afterware are added with `use` and `useAfter` directly to `apolloFetch`: 52 | 53 | ```js 54 | const apolloFetch = createApolloFetch(); 55 | 56 | const middleware = ({ request, options }, next) => { ... next(); }; 57 | 58 | const afterware = ({ response, options }, next) => { ... next(); }; 59 | 60 | apolloFetch.use(middleware); 61 | apolloFetch.useAfter(afterware); 62 | ``` 63 | 64 | Middleware and Afterware can be chained together in any order: 65 | 66 | ```js 67 | const apolloFetch = createApolloFetch(); 68 | apolloFetch 69 | .use(middleware1) 70 | .use(middleware2) 71 | .useAfter(afterware1) 72 | .useAfter(afterware2) 73 | .use(middleware3); 74 | ``` 75 | 76 | ### Custom Fetch 77 | 78 | For mocking and other fetching behavior, you may pass a fetch into `createApolloFetch`: 79 | 80 | ```js 81 | const customFetch = createFileFetch(); 82 | const apolloFetch = createApolloFetch({ customFetch }); 83 | ``` 84 | 85 | ### Custom GraphQL to Fetch Translation 86 | 87 | To modify how GraphQL requests are incorporated in the fetch options, you may pass a transformation function into `createApolloFetch`. 88 | `apollo-fetch` exports `constructDefaultOptions` to allow conditional creation of the fetch options. 89 | These transformations can be useful for servers that have different formatting for batches or other extra capabilities. 90 | 91 | ```js 92 | //requestOrRequests: GraphQLRequest | GraphQLRequest[] 93 | //options: RequestInit 94 | const constructOptions = (requestOrRequests, options) => { 95 | return { 96 | ...options, 97 | body: JSON.stringify(requestOrRequests), 98 | } 99 | }; 100 | const apolloFetch = createApolloFetch({ constructOptions }); 101 | 102 | //simplified usage inside apolloFetch 103 | fetch(uri, constructOptions(requestOrRequests, options)); //requestOrRequests and options are the results from middleware 104 | ``` 105 | 106 | ### Batched Requests 107 | 108 | Batched requests are also supported by the fetch function returned by `createApolloFetch`, please refer the [batched request guide](docs/batch.md) for a complete description. 109 | 110 | # Examples 111 | 112 | ### Simple GraphQL Query 113 | 114 | ```js 115 | import { createApolloFetch } from 'apollo-fetch'; 116 | 117 | const uri = 'http://api.githunt.com/graphql'; 118 | 119 | const query = ` 120 | query CurrentUser { 121 | currentUser { 122 | login, 123 | } 124 | } 125 | ` 126 | const apolloFetch = createApolloFetch({ uri }); 127 | 128 | apolloFetch({ query }).then(...).catch(...); 129 | ``` 130 | 131 | ### Simple GraphQL Mutation with Variables 132 | 133 | ```js 134 | import { createApolloFetch } from 'apollo-fetch'; 135 | 136 | const uri = 'http://api.githunt.com/graphql'; 137 | 138 | const query = ` 139 | mutation SubmitRepo ($repoFullName: String!) { 140 | submitRepository (repoFullName: $repoFullName) { 141 | id, 142 | score, 143 | } 144 | } 145 | `; 146 | 147 | const variables = { 148 | repoFullName: 'apollographql/apollo-fetch', 149 | }; 150 | 151 | const apolloFetch = createApolloFetch({ uri }); 152 | 153 | apolloFetch({ query, variables }).then(...).catch(...); 154 | ``` 155 | 156 | ### Middleware 157 | 158 | A GraphQL mutation with authentication middleware. 159 | Middleware has access to the GraphQL query and the options passed to fetch. 160 | 161 | ```js 162 | import { createApolloFetch } from 'apollo-fetch'; 163 | 164 | const uri = 'http://api.githunt.com/graphql'; 165 | 166 | const apolloFetch = createApolloFetch({ uri }); 167 | 168 | apolloFetch.use(({ request, options }, next) => { 169 | if (!options.headers) { 170 | options.headers = {}; // Create the headers object if needed. 171 | } 172 | options.headers['authorization'] = 'created token'; 173 | 174 | next(); 175 | }); 176 | 177 | apolloFetch(...).then(...).catch(...); 178 | ``` 179 | 180 | ### Afterware 181 | 182 | Afterware to check the response status and logout on a 401. 183 | The afterware has access to the raw reponse always and parsed response when the data is proper JSON. 184 | 185 | ```js 186 | import { createApolloFetch } from 'apollo-fetch'; 187 | 188 | const uri = 'http://api.githunt.com/graphql'; 189 | 190 | const apolloFetch = createApolloFetch({ uri }); 191 | 192 | apolloFetch.useAfter(({ response }, next) => { 193 | if (response.status === 401) { 194 | logout(); 195 | } 196 | next(); 197 | }); 198 | 199 | apolloFetch(...).then(...).catch(...); 200 | ``` 201 | 202 | ### Mocking a Fetch Call 203 | 204 | This example uses a custom fetch to mock an unauthorized(401) request with a non-standard response body. 205 | `apollo-fetch` replaces the call to `fetch` with `customFetch`, which both follow the standard [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). 206 | 207 | ```js 208 | const customFetch = () => new Promise((resolve, reject) => { 209 | const init = { 210 | status: 401, 211 | statusText: 'Unauthorized', 212 | }; 213 | const body = JSON.stringify({ 214 | data: { 215 | user: null, 216 | } 217 | }); 218 | resolve(new Response(body, init)); 219 | } 220 | 221 | const apolloFetch = createApolloFetch({ customFetch }); 222 | ``` 223 | 224 | ### Error Handling 225 | 226 | All responses are passed to the afterware regardless of the http status code. 227 | Network errors, `FetchError`, are thrown after the afterware is run and if no parsed response is received. 228 | 229 | This example shows an afterware that can receive a 401 with an unparsable response and return a valid `FetchResult`. 230 | Currently all other status codes that have an uparsable response would throw an error. 231 | This means if a server returns a parsable GraphQL result on a 403 for example, the result would be passed to `then` without error. 232 | Errors in Middleware and Afterware are propagated without modification. 233 | 234 | ```js 235 | import { createApolloFetch } from 'apollo-fetch'; 236 | 237 | const uri = 'http://api.githunt.com/graphql'; 238 | 239 | const apolloFetch = createApolloFetch({ uri }); 240 | 241 | apolloFetch.useAfter(({ response }, next) => { 242 | //response.raw will be a non-null string 243 | //response.parsed may be a FetchResult or undefined 244 | 245 | if (response.status === 401 && !response.parsed) { 246 | //set parsed response to valid FetchResult 247 | response.parsed = { 248 | data: { user: null }, 249 | }; 250 | } 251 | 252 | next(); 253 | }); 254 | 255 | //Here catch() receives all responses with unparsable data 256 | apolloFetch(...).then(...).catch(...); 257 | ``` 258 | 259 | ### Apollo Integration 260 | 261 | `apollo-fetch` is the first part of [Apollo Client's](https://github.com/apollographql/apollo-client) future network stack. 262 | If you would like to try it out today, 263 | you may replace the network interface with the following: 264 | 265 | ```js 266 | import ApolloClient from 'apollo-client'; 267 | import { createApolloFetch } from 'apollo-fetch'; 268 | import { print } from 'graphql/language/printer'; 269 | 270 | const uri = 'http://api.githunt.com/graphql'; 271 | 272 | const apolloFetch = createApolloFetch({ uri }); 273 | 274 | const networkInterface = { 275 | query: (req) => apolloFetch({...req, query: print(req.query)}), 276 | }; 277 | 278 | const client = new ApolloClient({ 279 | networkInterface, 280 | }); 281 | ``` 282 | 283 | # API 284 | 285 | `createApolloFetch` is a factory for `ApolloFetch`, a fetch function with middleware and afterware capabilities. 286 | [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) and [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) follow the MDN standard fetch API. 287 | 288 | ```js 289 | createApolloFetch(options: FetchOptions): ApolloFetch 290 | 291 | FetchOptions { 292 | uri?: string; 293 | customFetch?: (request: RequestInfo, init: RequestInit) => Promise; 294 | constructOptions?: (requestOrRequests: GraphQLRequest | GraphQLRequest[], options: RequestInit) => RequestInit; 295 | } 296 | /* 297 | * defaults: 298 | * uri = '/graphql' 299 | * customFetch = fetch from cross-fetch 300 | * constructOptions = constructDefaultOptions(exported from apollo-fetch) 301 | */ 302 | ``` 303 | 304 | `ApolloFetch`, a fetch function with middleware, afterware, and batched request capabilities. 305 | For information on batch usage, see the [batched request documentation](docs/batch.md). 306 | 307 | ```js 308 | ApolloFetch { 309 | (operation: GraphQLRequest): Promise; 310 | use: (middlewares: MiddlewareInterface) => ApolloFetch; 311 | useAfter: (afterwares: AfterwareInterface) => ApolloFetch; 312 | 313 | //Batched requests are described in the docs/batch.md 314 | (operation: GraphQLRequest[]): Promise; 315 | batchUse: (middlewares: BatchMiddlewareInterface) => ApolloFetch; 316 | batchUseAfter: (afterwares: BatchAfterwareInterface) => ApolloFetch; 317 | } 318 | ``` 319 | 320 | `GraphQLRequest` is the argument to an `ApolloFetch` call. 321 | `query` is optional to support persistent queries based on only an `operationName`. 322 | 323 | ```js 324 | GraphQLRequest { 325 | query?: string; 326 | variables?: object; 327 | operationName?: string; 328 | } 329 | ``` 330 | 331 | `FetchResult` is the return value of an `ApolloFetch` call 332 | 333 | ```js 334 | FetchResult { 335 | data: any; 336 | errors?: any; 337 | extensions?: any; 338 | } 339 | ``` 340 | 341 | Middleware used by `ApolloFetch` 342 | 343 | ```js 344 | MiddlewareInterface: (request: RequestAndOptions, next: Function) => void 345 | 346 | RequestAndOptions { 347 | request: GraphQLRequest; 348 | options: RequestInit; 349 | } 350 | ``` 351 | 352 | Afterware used by `ApolloFetch` 353 | 354 | ```js 355 | AfterwareInterface: (response: ResponseAndOptions, next: Function) => void 356 | 357 | ResponseAndOptions { 358 | response: ParsedResponse; 359 | options: RequestInit; 360 | } 361 | ``` 362 | 363 | `ParsedResponse` adds `raw` (the body from the `.text()` call) to the fetch result, and `parsed` (the parsed JSON from `raw`) to the fetch's standard [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). 364 | 365 | ```js 366 | ParsedResponse extends Response { 367 | raw: string; 368 | parsed?: any; 369 | } 370 | ``` 371 | 372 | A `FetchError` is returned from a failed call to `ApolloFetch` 373 | is standard [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that contains the response and a possible parse error. 374 | The `parseError` is generated when the raw response is not valid JSON (when `JSON.parse()` throws) and the Afterware does not add an object to the response's `parsed` property. 375 | Errors in Middleware and Afterware are propagated without modification. 376 | 377 | ```js 378 | FetchError extends Error { 379 | response: ParsedResponse; 380 | parseError?: Error; 381 | } 382 | ``` 383 | -------------------------------------------------------------------------------- /docs/batch.md: -------------------------------------------------------------------------------- 1 | # Batched Requests in Apollo Fetch 2 | 3 | `ApolloFetch` supports batched GraphQL requests with middleware and afterware without any additional work. 4 | 5 | ## Usage 6 | 7 | `createApolloFetch` returns a function that implements the `ApolloFetch` interface, which overloads the function to accept an array of `GraphQL` requests in addition to the standard single request. 8 | This example sends `request` twice with a single network request. 9 | 10 | ```js 11 | const apolloFetch = createApolloFetch({ uri }); 12 | const request = { query, variables, operationName }; 13 | 14 | apolloFetch([request, request]) 15 | .then(results => { 16 | results.map(({ data, error, extensions }) => { 17 | //access each GraphQL result independently 18 | }); 19 | }) 20 | .catch(error => { 21 | //respond to a network error 22 | }); 23 | ``` 24 | 25 | Batch middleware and afterware are added to an `ApolloFetch` with `batchUse` and `batchUseAfter`. 26 | Middleware has access to the array of requests in `request`. 27 | Afterware has access to the single network response, which should contain an array of GraphQL results in `parsed`. 28 | 29 | ```js 30 | const apolloFetch = createApolloFetch(); 31 | 32 | const batchMiddleware = ({ requests, options }, next) => { ... next(); }; 33 | 34 | const batchAfterware = ({ response, options }, next) => { ... next(); }; 35 | 36 | apolloFetch.batchUse(batchMiddleware); 37 | apolloFetch.batchUseAfter(batchAfterware); 38 | ``` 39 | 40 | Batch middleware and afterware exhibit the same chaining semantics as the single request variant. 41 | 42 | ```js 43 | const apolloFetch = createApolloFetch(); 44 | apolloFetch 45 | .batchUse(middleware1) 46 | .batchUse(middleware2) 47 | .batchUseAfter(afterware1) 48 | .batchUseAfter(afterware2) 49 | .batchUse(middleware3); 50 | ``` 51 | 52 | ### Batch Formatting 53 | 54 | Not all GraphQL servers support batching out of the box, so `apollo-fetch` creates a request that is compatible with [`apollo-server`](https://github.com/apollographql/apollo-server). 55 | The default behavior of translating a GraphQL request into fetch options, a simple `JSON.stringify(requestOrRequests)`, is found in `constructDefaultOptions`. 56 | 57 | For other formatting needs, `createApolloFetch` accepts a `constructOptions` parameter that will set the options that are passed to the underlying `fetch` function. 58 | This is an example of another batch format, where the array of requests is broken into separate arrays for queries and variables and then `stringify`'d. 59 | 60 | ```js 61 | import { 62 | constructDefaultOptions, 63 | } from 'apollo-fetch'; 64 | 65 | function constructOptions(requestOrRequests, options){ 66 | if(Array.isArray(requestOrRequests)) { 67 | //custom batching 68 | const requests = { 69 | queries: requestOrRequests.map(req => req.query), 70 | variables:requestOrRequests.map(req => req.variables), 71 | }; 72 | return { 73 | ...options, 74 | body: JSON.stringify(requests), 75 | } 76 | } else { 77 | //single requests 78 | return constructDefaultOptions(requestOrRequests, options); 79 | } 80 | } 81 | ``` 82 | 83 | ### Error Handling 84 | 85 | Errors are handled in the same manner as a single request, so all responses are passed to afterware including network errors. 86 | An additional error, `BatchError`, is thrown when the `parsed` property of the `ParsedResponse` returned from afterware is not an array. 87 | 88 | ## Batch API 89 | 90 | `ApolloFetch` supports batched requests with middleware and afterware. 91 | 92 | ```js 93 | ApolloFetch { 94 | (operation: GraphQLRequest[]): Promise; 95 | batchUse: (middlewares: BatchMiddlewareInterface) => ApolloFetch; 96 | batchUseAfter: (afterwares: BatchAfterwareInterface) => ApolloFetch; 97 | 98 | //Single Requests 99 | (operation: GraphQLRequest): Promise; 100 | use: (middlewares: MiddlewareInterface) => ApolloFetch; 101 | useAfter: (afterwares: AfterwareInterface) => ApolloFetch; 102 | } 103 | ``` 104 | 105 | Batch Middleware used by `ApolloFetch` has access to the requests and options passed to `constructOptions`. 106 | [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) follow the MDN standard fetch API. 107 | 108 | ```js 109 | BatchMiddlewareInterface: (requests: RequestsAndOptions, next: Function) => void 110 | 111 | RequestsAndOptions { 112 | requests: GraphQLRequest[]; 113 | options: RequestInit; 114 | } 115 | ``` 116 | 117 | Batch Afterware used by `ApolloFetch` has access to the single Response. 118 | 119 | ```js 120 | BatchAfterwareInterface: (response: ResponseAndOptions, next: Function) => void 121 | 122 | ResponseAndOptions { 123 | response: ParsedResponse; 124 | options: RequestInit; 125 | } 126 | 127 | //parsed is passed to afterware unchecked and should be an array 128 | ParsedResponse extends Response { 129 | raw: string; 130 | parsed?: any; 131 | } 132 | ``` 133 | 134 | `BatchError` is thrown when afterware finishes if the `parsed` property of the `ParsedResponse` is not an array for a batched request. 135 | 136 | ```js 137 | export interface BatchError extends Error { 138 | response: ParsedResponse; 139 | } 140 | ``` 141 | -------------------------------------------------------------------------------- /docs/monorepo.md: -------------------------------------------------------------------------------- 1 | # apollo-fetch Repository Structure 2 | 3 | `apollo-fetch` is a [lerna](https://github.com/lerna/lerna) project that contains multiple `ApolloFetch`'s including the core `apollo-fetch` package. 4 | 5 | ## Creating a new fetch 6 | 7 | If you are creating a new fetch and would like to included in the lerna repository, please open an Issue stating why your fetch should be included. 8 | 9 | When creating a new fetch, copying the `apollo-fetch` folder into `/packages` will provide a good starting point for a new fetch. 10 | Make sure that the new fetch takes advantage of integration tests with `apollo-fetch`. 11 | 12 | In order to include your fetch in the repository's coverage, include a `coverage:test` script in your `package.json`. 13 | 14 | ## Commands 15 | 16 | A couple of useful commands that you will want to run in the root of the repository during your development are: 17 | 18 | * `npm run bootstrap`: installs all dependencies in packages, symlinks shared dependencies, and builds modules 19 | * `lerna bootstrap`: installs all dependencies in packages and symlinks shared dependencies 20 | * `lerna run test`: tests all projects in the lerna project 21 | * `npm run coverage`: runs coverage for all lerna packages, depends on each module to have a `coverage:test` script. 22 | 23 | Within a project, you are welcome to include your preferred workflow. 24 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "independent", 7 | "hoist": true 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "license": "MIT", 4 | "repository": { 5 | "type": "git", 6 | "url": "git+https://github.com/apollographql/apollo-fetch.git" 7 | }, 8 | "scripts": { 9 | "bootstrap": "npm i && lerna bootstrap && lerna run -- build", 10 | "test": "lerna run -- test", 11 | "lint-fix": 12 | "prettier --trailing-comma all --single-quote --write \"packages/*/{src,tests,benchmark}/**/*.{j,t}s*\"", 13 | "lint-staged": "lint-staged", 14 | "coverage": "nyc --reporter=lcov lerna run -- coverage:test" 15 | }, 16 | "lint-staged": { 17 | "*.ts*": [ 18 | "prettier --trailing-comma all --single-quote --write", 19 | "git add" 20 | ], 21 | "*.json*": ["prettier --write", "git add"] 22 | }, 23 | "pre-commit": "lint-staged", 24 | "devDependencies": { 25 | "@types/chai": "4.1.7", 26 | "@types/chai-as-promised": "7.1.0", 27 | "@types/fetch-mock": "5.12.2", 28 | "@types/mocha": "2.2.48", 29 | "chai": "4.2.0", 30 | "chai-as-promised": "7.1.1", 31 | "fetch-mock": "5.13.1", 32 | "graphql": "0.13.2", 33 | "graphql-tag": "2.10.0", 34 | "lerna": "2.11.0", 35 | "lint-staged": "6.1.1", 36 | "lodash": "4.17.11", 37 | "mocha": "4.1.0", 38 | "nyc": "11.9.0", 39 | "pre-commit": "1.2.2", 40 | "prettier": "1.15.3", 41 | "rimraf": "2.6.2", 42 | "sinon": "4.5.0", 43 | "source-map-support": "0.5.9", 44 | "tslint": "5.10.0", 45 | "typescript": "2.6.2" 46 | }, 47 | "nyc": { 48 | "exclude": ["**/tests"] 49 | }, 50 | "//": { 51 | "//": "lerna run