├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitleaks.toml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codecov.yml ├── dangerfile.ts ├── docs ├── .gitignore ├── .gitmodules ├── .nvmrc ├── README.md ├── gatsby-config.js ├── package-lock.json ├── package.json ├── public │ └── _redirects ├── source │ ├── composition.md │ ├── index.mdx │ ├── links │ │ ├── batch-http.md │ │ ├── community.md │ │ ├── context.md │ │ ├── dedup.md │ │ ├── error.md │ │ ├── http.md │ │ ├── rest.md │ │ ├── retry.md │ │ ├── schema.md │ │ ├── state.md │ │ └── ws.md │ ├── overview.md │ ├── stateful.md │ └── stateless.md └── static │ └── _redirects ├── images ├── apollo-link.png ├── base-stack.png ├── batch-link.png ├── context.png ├── generic-stack.png ├── http-link.png ├── hybrid-link.png ├── link-as-promise.png ├── logging-stack.png ├── polling-link.png ├── polling-stack.png └── split-link.png ├── lerna.json ├── netlify.toml ├── package-lock.json ├── package.json ├── packages ├── apollo-link-batch-http │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ ├── batchHttpLink.ts │ │ │ └── sharedHttpTests.ts │ │ ├── batchHttpLink.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-batch │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── batchLink.ts │ │ ├── batchLink.ts │ │ ├── batching.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-context │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-dedup │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── dedupLink.ts │ │ ├── dedupLink.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-error │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-http-common │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-http │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ ├── httpLink.ts │ │ │ └── sharedHttpTests.ts │ │ ├── httpLink.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-polling │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── pollingLink.ts │ │ ├── index.ts │ │ └── pollingLink.ts │ └── tsconfig.json ├── apollo-link-retry │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ ├── delayFunction.ts │ │ │ ├── retryFunction.ts │ │ │ └── retryLink.ts │ │ ├── delayFunction.ts │ │ ├── index.ts │ │ ├── retryFunction.ts │ │ └── retryLink.ts │ └── tsconfig.json ├── apollo-link-schema │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── schemaLink.ts │ │ └── index.ts │ └── tsconfig.json ├── apollo-link-ws │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ └── webSocketLink.ts │ │ ├── index.ts │ │ └── webSocketLink.ts │ └── tsconfig.json ├── apollo-link │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── __tests__ │ │ │ ├── link.ts │ │ │ └── linkUtils.ts │ │ ├── index.ts │ │ ├── link.ts │ │ ├── linkUtils.ts │ │ ├── test-utils.ts │ │ ├── test-utils │ │ │ ├── mockLink.ts │ │ │ ├── setContext.ts │ │ │ └── testingUtils.ts │ │ └── types.ts │ └── tsconfig.json └── zen-observable-ts │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── __tests__ │ │ ├── filter.ts │ │ ├── flatMap.ts │ │ ├── forEach.ts │ │ ├── map.ts │ │ ├── observer.ts │ │ └── reduce.ts │ ├── index.ts │ ├── types.ts │ └── zenObservable.ts │ └── tsconfig.json ├── renovate.json ├── rollup.config.js ├── scripts └── minify ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | defaults: &defaults 4 | working_directory: ~/project 5 | docker: 6 | - image: circleci/node:10 7 | 8 | attach: &attach 9 | attach_workspace: 10 | at: ~/project 11 | 12 | jobs: 13 | Build: 14 | <<: *defaults 15 | steps: 16 | - checkout 17 | - restore_cache: 18 | keys: 19 | - npm-v2-{{ .Branch }}-{{ checksum "package-lock.json" }} 20 | - npm-v2-{{ .Branch }}- 21 | - npm-v2- 22 | - run: 23 | name: install-npm 24 | command: npm install 25 | - save_cache: 26 | key: npm-v2-{{ .Branch }}-{{ checksum "package-lock.json" }} 27 | paths: 28 | - ~/.npm 29 | - persist_to_workspace: 30 | root: . 31 | paths: 32 | - . 33 | 34 | Danger: 35 | <<: *defaults 36 | steps: 37 | - <<: *attach 38 | - run: npm run danger 39 | 40 | Prettier Check: 41 | <<: *defaults 42 | steps: 43 | - <<: *attach 44 | - run: npm run lint-check 45 | 46 | Filesize: 47 | <<: *defaults 48 | steps: 49 | - <<: *attach 50 | - run: npm run build 51 | - run: npm run filesize 52 | 53 | Monorepo: 54 | <<: *defaults 55 | steps: 56 | - <<: *attach 57 | - run: 58 | name: Jest suite with coverage 59 | command: npm run test-ci && npm run coverage:upload 60 | environment: 61 | JEST_JUNIT_OUTPUT: 'reports/junit/js-test-results.xml' 62 | - store_test_results: 63 | path: reports/junit 64 | - store_artifacts: 65 | path: reports/junit 66 | 67 | 68 | workflows: 69 | version: 2 70 | Build and Test: 71 | jobs: 72 | - Build 73 | - Danger: 74 | requires: 75 | - Build 76 | - Prettier Check: 77 | requires: 78 | - Build 79 | - Filesize: 80 | requires: 81 | - Build 82 | - Monorepo: 83 | requires: 84 | - Build 85 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | **Expected Behavior** 11 | 14 | 15 | **Actual Behavior** 16 | 20 | 21 | **A _simple_ reproduction** 22 | 25 | 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | TODO: 9 | 10 | - [ ] Make sure all of new logic is covered by tests and passes linting 11 | - [ ] Update CHANGELOG.md with your change 12 | 13 | -------------------------------------------------------------------------------- /.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 | # lock files 50 | yarn.lock 51 | 52 | # Compiled 53 | dist 54 | lib 55 | 56 | .idea 57 | .vscode 58 | 59 | .rpt2_cache 60 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | 2 | [[ rules ]] 3 | id = "high-entropy-base64" 4 | [ rules.allowlist ] 5 | commits = [ 6 | "19d51b53d0e2023f84cc3e8a800bc48464c5f071", 7 | "6ebfcf041563b6b0c4baa1a12c6c0ae4a6e12948", 8 | 9 | ] 10 | -------------------------------------------------------------------------------- /.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 | "files.trimTrailingWhitespace": true, 4 | "files.insertFinalNewline": true, 5 | "files.exclude": { 6 | "**/.git": true, 7 | "**/.DS_Store": true, 8 | "node_modules": true, 9 | "dist": true, 10 | "coverage": true, 11 | "npm": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "tasks": [ 4 | { 5 | "taskName": "tsc", 6 | "command": "lerna", 7 | "isShellCommand": true, 8 | "args": ["run", "build"], 9 | "showOutput": "silent", 10 | "problemMatcher": "$tsc" 11 | }, 12 | { 13 | "taskName": "watch-tsc", 14 | "command": "lerna", 15 | "isShellCommand": true, 16 | "isBuildCommand": true, 17 | "isBackground": true, 18 | "args": ["run", "watch"], 19 | "showOutput": "always", 20 | "problemMatcher": "$tsc-watch" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /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 | # apollo-link [![npm version](https://badge.fury.io/js/apollo-link.svg)](https://badge.fury.io/js/apollo-link) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://www.apollostack.com/#slack) 2 | 3 | --- 4 | 5 | ⚠️ **THIS PROJECT HAS BEEN DEPRECATED** ⚠️ 6 | 7 | The Links in this repo have been migrated to the [apollo-client](https://github.com/apollographql/apollo-client.git) project (as of >= `@apollo/client@3.0.0`). Please refer to the [Apollo Client migration guide](https://www.apollographql.com/docs/react/migrating/apollo-client-3-migration/) for more details. All Apollo Link issues / pull requests should now be opened in the [apollo-client](https://github.com/apollographql/apollo-client.git) repo. 8 | 9 | --- 10 | 11 | `apollo-link` is a standard interface for modifying control flow of GraphQL requests and fetching GraphQL results, designed to provide a simple GraphQL client that is capable of extensions. 12 | The high level use cases of `apollo-link` are highlighted below: 13 | 14 | * fetch queries directly without normalized cache 15 | * network interface for Apollo Client 16 | * network interface for Relay Modern 17 | * fetcher for GraphiQL 18 | 19 | The apollo link interface is designed to make links composable and easy to share, each with a single purpose. In addition to the core, this repository contains links for the most common fetch methods—http, local schema, websocket—and common control flow manipulations, such as retrying and polling. For a more detailed view of extended use cases, please see this [list](http://www.apollographql.com/docs/link/links/community.html) of community created links. 20 | 21 | ## Installation 22 | 23 | `npm install apollo-link --save` 24 | 25 | To use apollo-link in a web browser or mobile app, you'll need a build system capable of loading NPM packages on the client. 26 | Some common choices include Browserify, Webpack, and Meteor +1.3. 27 | 28 | ## [Documentation](http://www.apollographql.com/docs/link/index.html) 29 | 30 | To start, begin by reading this [introduction](https://www.apollographql.com/docs/link/index.html). For a deeper understanding and to fully leverage the power of Apollo Links, please view the [concepts overview](https://www.apollographql.com/docs/link/overview.html). To see example links from around the community, check out this [list](http://www.apollographql.com/docs/link/links/community.html). If you would like your link to be featured, please open a pull request. 31 | 32 | ## Contributing 33 | 34 | Apollo Link uses Lerna to manage multiple packages. To get started contributing, run `npm run bootstrap` in the root of the repository, which will install all dependencies and connect the dependent projects with symlinks in `node_modules`. Then run `npm run build` to compile the typescript source. Finally for incremental compilation, use `npm run watch`. 35 | 36 | Your feedback and contributions are always welcome. 37 | 38 | ## Apollo Principles 39 | 40 | `apollo-link` strives to follow the Apollo design principles: 41 | 42 | 1. Incrementally adoptable 43 | 2. Universally compatible 44 | 2. Simple to get started with 45 | 3. Inspectable and understandable 46 | 4. Built for interactive apps 47 | 4. Small and flexible 48 | 5. Community driven 49 | 50 | ## Maintainers 51 | 52 | - [@hwillson](https://github.com/hwillson) (Apollo) 53 | - [@benjamn](https://github.com/benjamn) (Apollo) 54 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | parsers: 3 | javascript: 4 | enable_partials: yes 5 | status: 6 | project: 7 | default: 8 | target: "83%" 9 | patch: 10 | enabled: false 11 | -------------------------------------------------------------------------------- /dangerfile.ts: -------------------------------------------------------------------------------- 1 | // Removed import 2 | import { includes } from 'lodash'; 3 | import * as fs from 'fs'; 4 | 5 | // Setup 6 | const pr = danger.github.pr; 7 | const commits = danger.github.commits; 8 | const modified = danger.git.modified_files; 9 | const bodyAndTitle = (pr.body + pr.title).toLowerCase(); 10 | 11 | // Custom modifiers for people submitting PRs to be able to say "skip this" 12 | const trivialPR = bodyAndTitle.includes('trivial'); 13 | const acceptedNoTests = bodyAndTitle.includes('skip new tests'); 14 | 15 | const typescriptOnly = (file: string) => includes(file, '.ts'); 16 | const filesOnly = (file: string) => 17 | fs.existsSync(file) && fs.lstatSync(file).isFile(); 18 | 19 | // Custom subsets of known files 20 | const modifiedAppFiles = modified 21 | .filter(p => includes(p, 'src') || includes(p, '__tests__')) 22 | .filter(p => filesOnly(p) && typescriptOnly(p)); 23 | 24 | // Takes a list of file paths, and converts it into clickable links 25 | const linkableFiles = paths => { 26 | const repoURL = danger.github.pr.head.repo.html_url; 27 | const ref = danger.github.pr.head.ref; 28 | const links = paths.map(path => { 29 | return createLink(`${repoURL}/blob/${ref}/${path}`, path); 30 | }); 31 | return toSentence(links); 32 | }; 33 | 34 | // ["1", "2", "3"] to "1, 2 and 3" 35 | const toSentence = (array: Array): string => { 36 | if (array.length === 1) { 37 | return array[0]; 38 | } 39 | return array.slice(0, array.length - 1).join(', ') + ' and ' + array.pop(); 40 | }; 41 | 42 | // ("/href/thing", "name") to "name" 43 | const createLink = (href: string, text: string): string => 44 | `${text}`; 45 | 46 | // Raise about missing code inside files 47 | const raiseIssueAboutPaths = ( 48 | type: Function, 49 | paths: string[], 50 | codeToInclude: string, 51 | ) => { 52 | if (paths.length > 0) { 53 | const files = linkableFiles(paths); 54 | const strict = '' + codeToInclude + ''; 55 | type(`Please ensure that ${strict} is enabled on: ${files}`); 56 | } 57 | }; 58 | 59 | const authors = commits.map(x => x.author.login); 60 | const isBot = authors.some(x => ['greenkeeper', 'renovate'].indexOf(x) > -1); 61 | 62 | // Rules 63 | if (!isBot) { 64 | // make sure someone else reviews these changes 65 | // const someoneAssigned = danger.github.pr.assignee; 66 | // if (someoneAssigned === null) { 67 | // warn( 68 | // 'Please assign someone to merge this PR, and optionally include people who should review.' 69 | // ); 70 | // } 71 | 72 | // When there are app-changes and it's not a PR marked as trivial, expect 73 | // there to be CHANGELOG changes. 74 | // const changelogChanges = modified.some(x => x.indexOf('CHANGELOG') > -1); 75 | // if (modifiedAppFiles.length > 0 && !trivialPR && !changelogChanges) { 76 | // fail('No CHANGELOG added.'); 77 | // } 78 | 79 | // No PR is too small to warrant a paragraph or two of summary 80 | if (pr.body.length === 0) { 81 | fail('Please add a description to your PR.'); 82 | } 83 | 84 | const hasAppChanges = modifiedAppFiles.length > 0; 85 | 86 | const testChanges = modifiedAppFiles.filter(filepath => 87 | filepath.includes('test'), 88 | ); 89 | const hasTestChanges = testChanges.length > 0; 90 | 91 | // Warn when there is a big PR 92 | const bigPRThreshold = 500; 93 | if ( 94 | danger.github.pr.additions + danger.github.pr.deletions > 95 | bigPRThreshold 96 | ) { 97 | warn(':exclamation: Big PR'); 98 | } 99 | 100 | // Warn if there are library changes, but not tests 101 | if (hasAppChanges && !hasTestChanges) { 102 | warn( 103 | "There are library changes, but not tests. That's OK as long as you're refactoring existing code", 104 | ); 105 | } 106 | 107 | // Be careful of leaving testing shortcuts in the codebase 108 | const onlyTestFiles = testChanges.filter(x => { 109 | const content = fs.readFileSync(x).toString(); 110 | return ( 111 | content.includes('it.only') || 112 | content.includes('describe.only') || 113 | content.includes('fdescribe') || 114 | content.includes('fit(') 115 | ); 116 | }); 117 | raiseIssueAboutPaths(fail, onlyTestFiles, 'an `only` was left in the test'); 118 | } 119 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | node_modules/ 6 | public/* 7 | .cache 8 | .deploy*/ 9 | docs.json 10 | _multiconfig.yml 11 | -------------------------------------------------------------------------------- /docs/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "themes/meteor"] 2 | path = themes/meteor 3 | url = https://github.com/meteor/hexo-theme-meteor.git 4 | -------------------------------------------------------------------------------- /docs/.nvmrc: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | This is the documentation **source** for this repository. 4 | 5 | The **deployed** version of the documentation for this repository is available at: 6 | 7 | * https://www.apollographql.com/docs/link/ 8 | 9 | ## Documentation for the documentation 10 | 11 | This `README.md` is intentionally short since the [documentation for the documentation](https://docs-docs.netlify.com/docs/docs/) provides details for the documentation framework _itself_. Additional information should generally be added to that documentation rather than here in this `README.md`, in order to provide a centralized resource that benefits all documentation deployments. 12 | 13 | ## Running locally 14 | 15 | For more information, consult the documentation for the documentation, referenced above. 16 | 17 | In general though: 18 | 19 | * `npm install` in this directory 20 | * `npm start` in this directory 21 | * Open a browser to the link provided in the console. 22 | 23 | > **Important note:** Changes to the markdown source does not result in an automatic "hot reload" in the browser; it is necessary to reload the page manually in the browser to see it re-rendered. Additionally, changes to `_config.yml` require stopping the server and restarting with `npm start` again. 24 | 25 | ## Deploy previews 26 | 27 | Documentation repositories should be setup with a "deploy preview" feature which automatically provides "preview" links in the _status checks_ section of pull-requests. 28 | 29 | In the event that it's not possible to run the documentation locally, pushing changes to the branch for a pull-request can be a suitable alternative that ensures changes to the documentation are properly rendered. 30 | 31 | -------------------------------------------------------------------------------- /docs/gatsby-config.js: -------------------------------------------------------------------------------- 1 | const themeOptions = require('gatsby-theme-apollo-docs/theme-options'); 2 | 3 | module.exports = { 4 | pathPrefix: '/docs/link', 5 | plugins: [ 6 | { 7 | resolve: 'gatsby-theme-apollo-docs', 8 | options: { 9 | ...themeOptions, 10 | root: __dirname, 11 | subtitle: 'Apollo Link', 12 | description: 'A guide to using Apollo Link to customize Apollo Client', 13 | githubRepo: 'apollographql/apollo-link', 14 | sidebarCategories: { 15 | null: [ 16 | 'index', 17 | ], 18 | Concepts: [ 19 | 'overview', 20 | 'stateless', 21 | 'stateful', 22 | 'composition' 23 | ], 24 | Links: [ 25 | 'links/http', 26 | 'links/state', 27 | 'links/rest', 28 | 'links/error', 29 | 'links/context', 30 | 'links/retry', 31 | 'links/ws', 32 | 'links/batch-http', 33 | 'links/dedup', 34 | 'links/schema', 35 | 'links/community' 36 | ] 37 | }, 38 | }, 39 | }, 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "prestart": "gatsby clean", 4 | "start": "gatsby develop", 5 | "build": "gatsby build", 6 | "serve": "gatsby serve" 7 | }, 8 | "dependencies": { 9 | "gatsby": "^2.21.0", 10 | "gatsby-theme-apollo-docs": "^4.2.2", 11 | "react": "^16.9.0", 12 | "react-dom": "^16.9.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/public/_redirects: -------------------------------------------------------------------------------- 1 | / /docs/link/ 2 | -------------------------------------------------------------------------------- /docs/source/composition.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Composing Links 3 | --- 4 | 5 | Links represent small portions of how you want your GraphQL operation to be handled. In order to serve all of the needs of your app, Apollo Link is designed to be composed with other links to build complex actions as needed. Composition is managed in two main ways: additive and directional. Additive composition is how you can combine multiple links into a single chain and directional composition is how you can control which links are used depending on the operation. 6 | 7 | It's important to note that no matter how many links you have in your chain, your [terminating link](/overview/#terminating-links) has to be last. 8 | 9 | *NOTE Future composition mechanisms like `race` are being considered. If you have ideas please submit an issue or PR for the style you need!* 10 | 11 | ## Additive Composition 12 | 13 | Apollo Link ships with two ways to compose links. The first is a method called `from` which is both exported, and is on the `ApolloLink` interface. `from` takes an array of links and combines them all into a single link. For example: 14 | 15 | ```js 16 | import { ApolloLink } from 'apollo-link'; 17 | import { RetryLink } from 'apollo-link-retry'; 18 | import { HttpLink } from 'apollo-link-http'; 19 | import MyAuthLink from '../auth'; 20 | 21 | const link = ApolloLink.from([ 22 | new RetryLink(), 23 | new MyAuthLink(), 24 | new HttpLink({ uri: 'http://localhost:4000/graphql' }) 25 | ]); 26 | ``` 27 | 28 | `from` is typically used when you have many links to join together all at once. The alternative way to join links is the `concat` method which joins two links together into one. 29 | 30 | 31 | ```js 32 | import { ApolloLink } from 'apollo-link'; 33 | import { RetryLink } from 'apollo-link-retry'; 34 | import { HttpLink } from 'apollo-link-http'; 35 | 36 | const link = ApolloLink.concat(new RetryLink(), new HttpLink({ uri: 'http://localhost:4000/graphql' })); 37 | ``` 38 | 39 | ## Directional Composition 40 | 41 | Given that links are a way of implementing custom control flow for your GraphQL operation, Apollo Link provides an easy way to use different links depending on the operation itself (or any other global state). This is done using the `split` method which is exported as a function and is on the `ApolloLink` interface. Using the `split` function can be done like this: 42 | 43 | ```js 44 | import { ApolloLink } from 'apollo-link'; 45 | import { RetryLink } from 'apollo-link-retry'; 46 | import { HttpLink } from 'apollo-link-http'; 47 | 48 | const link = new RetryLink().split( 49 | (operation) => operation.getContext().version === 1, 50 | new HttpLink({ uri: "http://localhost:4000/v1/graphql" }), 51 | new HttpLink({ uri: "http://localhost:4000/v2/graphql" }) 52 | ); 53 | ``` 54 | 55 | `split` takes two required parameters and one optional one. The first argument to split is a function which receives the operation and returns `true` for the first link and `false` for the second link. The second argument is the first link to be split between. The third argument is an optional second link to send the operation to if it doesn't match. 56 | 57 | Using `split` allows for per operation based control flow for things like sending mutations to a different server or giving them more retry attempts, for using a WS link for subscriptions and Http for everything else, it can even be used to customize which links are used for an authenticated user vs a public client. 58 | 59 | ## Usage 60 | 61 | `split`, `from`, and `concat` are all exported as part of the ApolloLink interface as well as individual functions which can be used. Both are great ways to build link chains and they are identical in functionality. 62 | -------------------------------------------------------------------------------- /docs/source/links/batch-http.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-batch-http/README.md -------------------------------------------------------------------------------- /docs/source/links/community.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Community Links 3 | --- 4 | 5 | Thank you to our amazing community members who have created custom Apollo Links! If you've built a link and would like it to be featured, please send a pull request to add it to the list. 6 | 7 | * [apollo-link-webworker](https://github.com/PCreations/apollo-link-webworker) by [@PCreations](https://github.com/PCreations): Apollo link that lets you use graphql client-side only, with a webworker as a "server" supporting normal query and subscriptions 8 | * [apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client) by [@jaydenseric](https://github.com/jaydenseric): Enhances Apollo for intuitive file uploads via GraphQL mutations. 9 | * [apollo-angular-link-http](https://www.npmjs.com/package/apollo-angular-link-http) by [@kamilkisiela](https://github.com/kamilkisiela): https://www.npmjs.com/package/apollo-angular-link-http 10 | * [apollo-angular-link-headers](https://www.npmjs.com/package/apollo-angular-link-headers) by [@kamilkisiela](https://github.com/kamilkisiela): Transform key-value object into instance of HttpHeaders (@angular/common/http) 11 | * [apollo-link-redux](https://github.com/AdamYee/apollo-link-redux) by [@AdamYee](https://github.com/AdamYee): Dispatches apollo-client 1.0-ish Redux actions. 12 | * [react-apollo-network-status](https://github.com/molindo/react-apollo-network-status) by [@amannn](https://github.com/amannn): Brings information about the global network status from Apollo into React. 13 | * [apollo-link-watched-mutation](https://github.com/haytko/apollo-link-watched-mutation) by [@haytko](https://github.com/haytko): Organizes cache invalidations per query on a mutation 14 | * [apollo-link-token-refresh](https://github.com/newsiberian/apollo-link-token-refresh) by [@newsiberian](https://github.com/newsiberian): Performs expired JWT renewal 15 | * [apollo-link-response-resolver](https://github.com/lionize/apollo-link-response-resolver) by [@lionize](https://github.com/lionize): Automatically format incoming GraphQL data 16 | * [apollo-link-electron](https://github.com/firede/apollo-link-electron) by [@firede](https://github.com/firede): Get GraphQL results over IPC for Electron apps 17 | * [link-http-dataloader](https://github.com/graphcool/http-link-dataloader) by [@graphcool](https://github.com/graphcool): Batching and caching provided by dataloader 18 | * [@absinthe/socker-apollo-link](https://github.com/absinthe-graphql/absinthe-socket/tree/master/packages/socket-apollo-link) by [@absinthe-graphql](https://github.com/absinthe-graphql): Communicate over an Absinthe socket 19 | * [apollo-absinthe-upload-link](https://github.com/bytewitchcraft/apollo-absinthe-upload-link) by [@bytewitchcraft](https://github.com/bytewitchcraft): Enables file-uploading to Absinth backends 20 | * [apollo-link-logger](https://github.com/blackxored/apollo-link-logger) by [@blackxored](https://github.com/blackxored): Logger that uses similar format to redux-logger and includes performance infomation 21 | * [apollo-link-tracer](https://github.com/convoyinc/apollo-link-tracer) by [@convoyinc](https://github.com/convoyinc): Trace Apollo queries and mutations 22 | * [apollo-link-queue](https://github.com/helfer/apollo-link-queue) by [@helfer](https://github.com/helfer): Buffers requests on a toggle, such as an on/offline event 23 | * [apollo-link-optimistic](https://github.com/helfer/apollo-link-optimistic) by [@helfer](https://github.com/helfer): Returns an immediate optimistic response before returning server results 24 | * [apollo-link-serialize](https://github.com/helfer/apollo-link-serialize) by [@helfer](https://github.com/helfer): Serializes requests by key to ensure execution order 25 | * [apollo-link-debounce](https://github.com/helfer/apollo-link-debounce) by [@helfer](https://github.com/helfer): Debounce requests made within an interval 26 | * [apollo-link-defer](https://github.com/leadiq/apollo-link-defer) by [@leadiq](https://github.com/leadiq): Prepare links asynchronously, even after `ApolloClient` construction 27 | * [apollo-link-firebase](https://github.com/Canner/apollo-link-firebase) by [@canner](https://github.com/Canner): Query Firebase with Apollo 28 | * [apollo-link-computed-property](https://github.com/czystyl/apollo-link-computed-property) by [@czystyl](https://github.com/czystyl): `@computed` directive allows you to create a computed property on the query 29 | * [apollo-link-segment](https://github.com/hobochild/apollo-link-segment) by [@hobochild](https://github.com/hobochild): Automatically track apollo operations with [segment](https://segment.com/). 30 | -------------------------------------------------------------------------------- /docs/source/links/context.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-context/README.md -------------------------------------------------------------------------------- /docs/source/links/dedup.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-dedup/README.md -------------------------------------------------------------------------------- /docs/source/links/error.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-error/README.md -------------------------------------------------------------------------------- /docs/source/links/http.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-http/README.md -------------------------------------------------------------------------------- /docs/source/links/retry.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-retry/README.md -------------------------------------------------------------------------------- /docs/source/links/schema.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-schema/README.md -------------------------------------------------------------------------------- /docs/source/links/ws.md: -------------------------------------------------------------------------------- 1 | ../../../packages/apollo-link-ws/README.md -------------------------------------------------------------------------------- /docs/source/stateful.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Stateful Links 3 | --- 4 | 5 | ## Stateful Links 6 | 7 | Links are created and shared between every request in your application. Some links may share state between requests to provided added functionality. The links are called stateful links and are written using the `ApolloLink` interface. The alternative way to write links is a [stateless link](/stateless/). 8 | 9 | Stateful links typically (though are not required to) overwrite the constructor of `ApolloLink` and are required to implement a `request` function with the same signature as a stateless link. For example: 10 | 11 | ```js 12 | import { ApolloLink } from 'apollo-link'; 13 | 14 | class OperationCountLink extends ApolloLink { 15 | constructor() { 16 | super(); 17 | this.operations = 0; 18 | } 19 | request(operation, forward) { 20 | this.operations++ 21 | return forward(operation); 22 | } 23 | } 24 | 25 | const link = new OperationCountLink(); 26 | ``` 27 | 28 | This stateful implementation maintains a counter called `operations` as an instance variable. Every time a request is passed through the link, we increment `operations`. This means that `operations` counts the number of operations that have been requested of the link. 29 | 30 | Consider the case where we'd like to keep track of the requests within the link. Suppose that we call `request` on this link with an operation instance `A`. While this operation is still in-flight, we fire another operation instance `B.` Unless we're careful, it is easy to accidentally overwrite operation `A` with operation `B`. Take for example a portion of the dedup link: 31 | 32 | ```js 33 | import { ApolloLink } from 'apollo-link'; 34 | 35 | export default class DedupLink extends ApolloLink { 36 | private inFlightRequestObservables: { 37 | [key: string]: Observable; 38 | }; 39 | 40 | constructor() { 41 | super(); 42 | this.inFlightRequestObservables = {}; 43 | } 44 | 45 | public request( 46 | operation: Operation, 47 | forward: NextLink, 48 | ): Observable { 49 | const key = operation.toKey(); 50 | if (!this.inFlightRequestObservables[key]) { 51 | this.inFlightRequestObservables[key] = forward(operation); 52 | } 53 | 54 | return new Observable(observer => { 55 | const subscription = this.inFlightRequestObservables[key].subscribe({ 56 | next: (result) => { 57 | delete this.inFlightRequestObservables[key]; 58 | observer.next(result); 59 | }, 60 | error: error => { 61 | delete this.inFlightRequestObservables[key]; 62 | observer.error(error); 63 | }, 64 | complete: observer.complete.bind(observer), 65 | }); 66 | 67 | return () => { 68 | if (subscription) subscription.unsubscribe(); 69 | delete this.inFlightRequestObservables[key]; 70 | }; 71 | }); 72 | } 73 | } 74 | 75 | 76 | ``` 77 | 78 | More commonly, stateful links are used for complex control flow like batching and deduplication of operations. 79 | -------------------------------------------------------------------------------- /docs/source/stateless.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Stateless Links 3 | --- 4 | 5 | ## Stateless Links 6 | 7 | Links are created and shared between every request in your application. However, most links do the same thing for each request and don't need any knowledge about other operations being performed. These links are called stateless links because they have no shared execution state between requests. The alternative way to write links is a [stateful link](/stateful/). 8 | 9 | Stateless links can be written as simple functions wrapped in the `ApolloLink` interface. For example: 10 | 11 | ```js 12 | import { ApolloLink } from 'apollo-link'; 13 | 14 | const consoleLink = new ApolloLink((operation, forward) => { 15 | console.log(`starting request for ${operation.operationName}`); 16 | return forward(operation).map((data) => { 17 | console.log(`ending request for ${operation.operationName}`); 18 | return data; 19 | }) 20 | }) 21 | ``` 22 | 23 | Stateless links are great for things like middleware and even network requests. Adding an auth header for `apollo-link-http` is as simple as this: 24 | 25 | ```js 26 | import { ApolloLink } from 'apollo-link'; 27 | 28 | const authLink = new ApolloLink((operation, forward) => { 29 | operation.setContext(({ headers }) => ({ headers: { 30 | authorization: Meteor.userId(), // however you get your token 31 | ...headers 32 | }})); 33 | return forward(operation); 34 | }); 35 | 36 | ``` 37 | 38 | This style of link also composes well for customization using a function: 39 | 40 | ```js 41 | import { ApolloLink } from 'apollo-link'; 42 | 43 | const reportErrors = (errorCallback) => new ApolloLink((operation, forward) => { 44 | const observer = forward(operation); 45 | // errors will be sent to the errorCallback 46 | observer.subscribe({ error: errorCallback }) 47 | return observer; 48 | }); 49 | 50 | const link = reportErrors(console.error); 51 | ``` 52 | 53 | ### Extending ApolloLink 54 | 55 | Stateless links can also be written by extending the `ApolloLink` class and overwriting the constructor and request method. This is done as an alternative to the closure method shown directly above to pass details to the link. For example, the same `reportErrors` link written by extending the `ApolloLink` class: 56 | 57 | ```js 58 | import { ApolloLink } from 'apollo-link'; 59 | 60 | class ReportErrorLink extends ApolloLink { 61 | constructor(errorCallback) { 62 | super(); 63 | this.errorCallback = errorCallback; 64 | } 65 | request(operation, forward) { 66 | const observer = forward(operation); 67 | // errors will be sent to the errorCallback 68 | observer.subscribe({ error: this.errorCallback }) 69 | return observer; 70 | } 71 | } 72 | 73 | const link = new ReportErrorLink(console.error); 74 | ``` 75 | 76 | Both of these methods work equally as well for creating links! 77 | -------------------------------------------------------------------------------- /docs/static/_redirects: -------------------------------------------------------------------------------- 1 | / /docs/link/ 2 | -------------------------------------------------------------------------------- /images/apollo-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/apollo-link.png -------------------------------------------------------------------------------- /images/base-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/base-stack.png -------------------------------------------------------------------------------- /images/batch-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/batch-link.png -------------------------------------------------------------------------------- /images/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/context.png -------------------------------------------------------------------------------- /images/generic-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/generic-stack.png -------------------------------------------------------------------------------- /images/http-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/http-link.png -------------------------------------------------------------------------------- /images/hybrid-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/hybrid-link.png -------------------------------------------------------------------------------- /images/link-as-promise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/link-as-promise.png -------------------------------------------------------------------------------- /images/logging-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/logging-stack.png -------------------------------------------------------------------------------- /images/polling-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/polling-link.png -------------------------------------------------------------------------------- /images/polling-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/polling-stack.png -------------------------------------------------------------------------------- /images/split-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-link/c5c5538224f22d8c1067ef80d82125e771a32651/images/split-link.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0", 3 | "packages": ["packages/*"], 4 | "version": "independent", 5 | "hoist": false 6 | } 7 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "docs/" 3 | publish = "docs/public/" 4 | command = "gatsby build --prefix-paths && mkdir -p docs/link && mv public/* docs/link && mv docs public/ && mv public/docs/link/_redirects public" 5 | [build.environment] 6 | NPM_VERSION = "6" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "license": "MIT", 4 | "scripts": { 5 | "diff": "check-if-folder-contents-changed-in-git-commit-range", 6 | "postinstall": "lerna exec -- npm install --package-lock=false && lerna run prepare", 7 | "build": "lerna run build", 8 | "test": "lerna run test", 9 | "prelint": "npm run lint-fix", 10 | "lint": "lerna run lint", 11 | "lint-fix": "prettier --trailing-comma all --single-quote --write \"packages/*/{src,tests,test,benchmark}/**/*.{j,t}s*\"", 12 | "lint-staged": "lint-staged", 13 | "lint-check": "prettier-check --single-quote --trailing-comma all \"packages/*/{src,__tests__}/**/*.ts\"", 14 | "filesize": "lerna run filesize && bundlesize", 15 | "type-check": "lerna run type-check", 16 | "coverage": "jest --verbose --coverage", 17 | "coverage:upload": "codecov", 18 | "danger": "danger run --verbose", 19 | "predeploy": "npm install", 20 | "test-ci": "npm run coverage -- --ci --maxWorkers=2 --reporters=default --reporters=jest-junit", 21 | "deploy": "lerna publish -m \"chore: Publish\"", 22 | "watch": "trap \"kill 0\" SIGINT; for f in `ls packages`; do (cd `pwd`/packages/$f && [[ -e package.json ]] && npm run watch) & done; " 23 | }, 24 | "bundlesize": [ 25 | { 26 | "name": "apollo-link", 27 | "path": "./packages/apollo-link/lib/bundle.min.js", 28 | "maxSize": "1.1 Kb" 29 | }, 30 | { 31 | "name": "apollo-link-batch", 32 | "path": "./packages/apollo-link-batch/lib/bundle.min.js", 33 | "maxSize": "1 Kb" 34 | }, 35 | { 36 | "name": "apollo-link-batch-http", 37 | "path": "./packages/apollo-link-batch-http/lib/bundle.min.js", 38 | "maxSize": "1 Kb" 39 | }, 40 | { 41 | "name": "apollo-link-dedup", 42 | "path": "./packages/apollo-link-dedup/lib/bundle.min.js", 43 | "maxSize": "535 B" 44 | }, 45 | { 46 | "name": "apollo-link-error", 47 | "path": "./packages/apollo-link-error/lib/bundle.min.js", 48 | "maxSize": "465 B" 49 | }, 50 | { 51 | "name": "apollo-link-http", 52 | "path": "./packages/apollo-link-http/lib/bundle.min.js", 53 | "maxSize": "1.15 Kb" 54 | }, 55 | { 56 | "name": "apollo-link-polling", 57 | "path": "./packages/apollo-link-polling/lib/bundle.min.js", 58 | "maxSize": "350 B" 59 | }, 60 | { 61 | "name": "apollo-link-retry", 62 | "path": "./packages/apollo-link-retry/lib/bundle.min.js", 63 | "maxSize": "1.15 Kb" 64 | }, 65 | { 66 | "name": "apollo-link-schema", 67 | "path": "./packages/apollo-link-schema/lib/bundle.min.js", 68 | "maxSize": "400 B" 69 | }, 70 | { 71 | "name": "apollo-link-ws", 72 | "path": "./packages/apollo-link-ws/lib/bundle.min.js", 73 | "maxSize": "300 B" 74 | } 75 | ], 76 | "renovate": { 77 | "extends": [ 78 | "config:base", 79 | "schedule:nonOfficeHours", 80 | ":pinOnlyDevDependencies" 81 | ], 82 | "semanticCommits": true, 83 | "timezone": "America/New_York", 84 | "automerge": false, 85 | "labels": [ 86 | "dependencies" 87 | ] 88 | }, 89 | "jest": { 90 | "transform": { 91 | ".(ts|tsx)": "ts-jest" 92 | }, 93 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 94 | "moduleFileExtensions": [ 95 | "ts", 96 | "tsx", 97 | "js", 98 | "json" 99 | ], 100 | "testURL": "http://localhost", 101 | "testPathIgnorePatterns": [ 102 | "/node_modules/", 103 | "sharedHttpTests.ts" 104 | ] 105 | }, 106 | "lint-staged": { 107 | "*.{ts, tsx, js, jsx}": [ 108 | "prettier --trailing-comma all --single-quote --write", 109 | "git add" 110 | ], 111 | "!(package).json": [ 112 | "prettier --write", 113 | "git add" 114 | ] 115 | }, 116 | "pre-commit": "lint-staged", 117 | "pre-push": "lint-check", 118 | "dependencies": {}, 119 | "devDependencies": { 120 | "@condenast/bundlesize": "0.18.1", 121 | "@types/zen-observable": "0.8.0", 122 | "check-if-folder-contents-changed-in-git-commit-range": "1.0.1", 123 | "codecov": "3.7.1", 124 | "danger": "3.9.0", 125 | "jest": "24.9.0", 126 | "jest-junit": "6.4.0", 127 | "lerna": "3.22.1", 128 | "lint-staged": "7.3.0", 129 | "pre-commit": "1.2.2", 130 | "pre-push": "0.1.1", 131 | "prettier": "1.15.2", 132 | "prettier-check": "2.0.0", 133 | "rollup": "1.32.1", 134 | "rollup-plugin-invariant": "0.5.6", 135 | "rollup-plugin-node-resolve": "4.2.4", 136 | "rollup-plugin-sourcemaps": "0.6.2", 137 | "rollup-plugin-typescript2": "0.27.1", 138 | "terser": "3.17.0", 139 | "ts-jest": "22.4.6", 140 | "tslib": "1.13.0", 141 | "typescript": "3.0.3" 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.2.4 11 | 12 | - No changes 13 | 14 | ### 1.2.3 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 1.2.2 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | - Check for signal already present on `fetchOptions` [#584](https://github.com/apollographql/apollo-link/pull/584) 21 | 22 | ### 1.2.1 23 | - Fix typing of Operation parameters [PR#525](https://github.com/apollographql/apollo-link/pull/525) 24 | 25 | ### 1.2.0 26 | - support passing data and errors back as data to next link 27 | 28 | ### 1.1.1 29 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 30 | 31 | ### 1.1.0 32 | - share logic with apollo-link-http through apollo-link-http-core [PR#364](https://github.com/apollographql/apollo-link/pull/364) 33 | - remove apollo-fetch [PR#364](https://github.com/apollographql/apollo-link/pull/364) 34 | - GET is no longer supported for batching (it never worked anyway) [PR#490](https://github.com/apollographql/apollo-link/pull/490) 35 | 36 | ### 1.0.5 37 | - ApolloLink upgrade 38 | 39 | ### 1.0.4 40 | 41 | - Update to graphql@0.12 42 | 43 | ### 1.0.3 44 | - export options as named interface [TypeScript] 45 | 46 | ### 1.0.2 47 | - changed peer-dependency of apollo-link to actual dependency 48 | 49 | ### 1.0.1 50 | - moved to better rollup build 51 | 52 | ### 1.0.0 53 | - moved from default export to named to be consistent with rest of link ecosystem 54 | -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-batch-http 3 | description: Batch multiple operations into a single HTTP request 4 | --- 5 | 6 | `apollo-link-batch-http` is a terminating link that combines multiple GraphQL 7 | operations into a single HTTP request. This link batches together individual 8 | operations into an array that is sent to a single GraphQL endpoint. 9 | 10 | ```js 11 | import { BatchHttpLink } from "apollo-link-batch-http"; 12 | 13 | const link = new BatchHttpLink({ uri: "/graphql" }); 14 | ``` 15 | 16 | ## Options 17 | 18 | The batch http link accepts an object with some options to customize the behavior 19 | of the link. There are two different categories of options: http and batch. The 20 | http options follow the same structure as the 21 | [apollo-link-http](http#options): 22 | 23 | * `uri`: the URI key is a string endpoint -- will default to "/graphql" if not 24 | specified 25 | * `includeExtensions`: allow passing the extensions field to your graphql 26 | server, defaults to false 27 | * `fetch`: a `fetch` compatible API for making a request 28 | * `headers`: an object representing values to be sent as headers on the request 29 | * `credentials`: a string representing the credentials policy you want for the 30 | fetch call 31 | * `fetchOptions`: any overrides of the fetch options argument to pass to the 32 | fetch call. Note that you cannot use batching with the GET HTTP method. 33 | 34 | The batching options indicate how operations are batched together, the size of 35 | batches, and the maximum time a batch will wait before automatically being sent 36 | over the network. 37 | 38 | - `batchMax`: a max number of items to batch, defaults at 10 39 | - `batchInterval`: the interval at which to batch (in ms), defaults to 10 40 | - `batchKey`: a function that accepts an operation and returns a string key, 41 | which uniquely names the batch the operation belongs to, defaults to 42 | returning the same string 43 | 44 | ## Fetch polyfill 45 | 46 | The batch http link relies on having `fetch` present in your runtime environment. If you are running on react-native, or modern browsers, this should be no problem. If you are targeting an environment without `fetch` such as older browsers or the server, you will need to pass your own `fetch` to the link through the options. We recommend [`unfetch`](https://github.com/developit/unfetch) for older browsers and [`node-fetch`](https://github.com/bitinn/node-fetch) for running in Node. 47 | 48 | ## Context 49 | 50 | The Batch Http Link currently uses the context in two different ways, per batch 51 | and per query. The context fields below are used per batch and taken from the first 52 | operation in the batch. They are applied to the fetch options in a similar 53 | manner as [apollo-link-http](https://www.apollographql.com/docs/link/links/http.html#context). 54 | 55 | * `headers`: an object representing values to be sent as headers on the request 56 | * `credentials`: a string representing the credentials policy you want for the 57 | fetch call 58 | * `uri`: a string of the endpoint you want to fetch from 59 | * `fetchOptions`: any overrides of the fetch options argument to pass to the 60 | fetch call 61 | * `response`: this is the raw response from the fetch request after it is made. 62 | 63 | For each query, the `http` field is used to modify each individual query in the 64 | batch, such as persisted queries (see below) 65 | 66 | ### Persisted queries 67 | 68 | The batch http link supports an advanced GraphQL feature called persisted queries. This allows you to not send the stringified query over the wire, but instead send some kind of identifier of the query. To support this you need to attach the id somewhere to the extensions field and pass the following options to the context: 69 | 70 | ```js 71 | operation.setContext({ 72 | http: { 73 | includeExtensions: true, 74 | includeQuery: false, 75 | } 76 | }) 77 | ``` 78 | 79 | The `http` object on context currently supports two keys: 80 | 81 | * `includeExtensions`: Send the extensions object for this request. 82 | * `includeQuery`: Don't send the `query` field for this request. 83 | 84 | One way to use persisted queries is with [apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) and [Apollo Engine](https://www.apollographql.com/docs/engine/auto-persisted-queries.html). 85 | 86 | ## Errors 87 | 88 | The batch http link handles errors on a per batch basis with the same semantics found in [apollo-link-http](http#errors). 89 | 90 | ## Custom fetching 91 | 92 | You can use the `fetch` option when creating an http-link to do a lot of custom networking. This is useful if you want to modify the request based on the calculated headers or calculate the uri based on the operation: 93 | 94 | ### Custom auth 95 | 96 | ```js 97 | const customFetch = (uri, options) => { 98 | const { header } = Hawk.client.header( 99 | "http://example.com:8000/resource/1?b=1&a=2", 100 | "POST", 101 | { credentials: credentials, ext: "some-app-data" } 102 | ); 103 | options.headers.Authorization = header; 104 | return fetch(uri, options); 105 | }; 106 | 107 | const link = new BatchHttpLink({ fetch: customFetch }); 108 | ``` 109 | 110 | ### Dynamic URI 111 | 112 | ```js 113 | const customFetch = (uri, options) => { 114 | const operationNames = JSON.parse(options.body).map(operation => operation.operationName); 115 | return fetch(`${uri}/graph/graphql?opname=${operationNames}`, options); 116 | }; 117 | 118 | const link = new BatchHttpLink({ fetch: customFetch }); 119 | ``` 120 | -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-batch-http", 3 | "version": "1.2.14", 4 | "description": "Batch HTTP transport layer for GraphQL", 5 | "author": "Evans Hauser ", 6 | "contributors": [ 7 | "James Baxley ", 8 | "Jonas Helfer ", 9 | "jon wong ", 10 | "Sashko Stubailo " 11 | ], 12 | "license": "MIT", 13 | "main": "./lib/index.js", 14 | "module": "./lib/bundle.esm.js", 15 | "typings": "./lib/index.d.ts", 16 | "sideEffects": false, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/apollographql/apollo-link.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/apollographql/apollo-link/issues" 23 | }, 24 | "homepage": "https://github.com/apollographql/apollo-link#readme", 25 | "scripts": { 26 | "build": "tsc && rollup -c", 27 | "coverage": "jest --coverage", 28 | "clean": "rimraf lib/* && rimraf coverage/*", 29 | "filesize": "../../scripts/minify", 30 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 31 | "prebuild": "npm run clean", 32 | "prepare": "npm run build", 33 | "test": "npm run lint && jest", 34 | "watch": "tsc -w -p . & rollup -c -w" 35 | }, 36 | "dependencies": { 37 | "apollo-link": "file:../apollo-link", 38 | "apollo-link-batch": "file:../apollo-link-batch", 39 | "apollo-link-http-common": "file:../apollo-link-http-common", 40 | "tslib": "^1.9.3" 41 | }, 42 | "peerDependencies": { 43 | "graphql": "^0.11.0 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0" 44 | }, 45 | "devDependencies": { 46 | "@types/graphql": "14.2.3", 47 | "@types/jest": "24.9.1", 48 | "fetch-mock": "6.5.2", 49 | "graphql": "15.3.0", 50 | "graphql-tag": "2.10.1", 51 | "jest": "24.9.0", 52 | "object-to-querystring": "1.0.8", 53 | "proxyquire": "1.8.0", 54 | "rimraf": "2.7.1", 55 | "rollup": "1.32.1", 56 | "ts-jest": "22.4.6", 57 | "tslint": "5.20.1", 58 | "typescript": "3.0.3" 59 | }, 60 | "jest": { 61 | "transform": { 62 | ".(ts|tsx)": "ts-jest" 63 | }, 64 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 65 | "moduleFileExtensions": [ 66 | "ts", 67 | "tsx", 68 | "js", 69 | "json" 70 | ], 71 | "testPathIgnorePatterns": [ 72 | "/node_modules/", 73 | "sharedHttpTests.ts" 74 | ], 75 | "testURL": "http://localhost" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('batchHttp'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/src/__tests__/sharedHttpTests.ts: -------------------------------------------------------------------------------- 1 | ../../../apollo-link-http/src/__tests__/sharedHttpTests.ts -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './batchHttpLink'; 2 | -------------------------------------------------------------------------------- /packages/apollo-link-batch-http/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.1.5 11 | 12 | - No changes 13 | 14 | ### 1.1.4 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 1.1.3 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | 21 | ### 1.1.2 22 | - Fix typing of Operation parameters [PR#525](https://github.com/apollographql/apollo-link/pull/525) 23 | 24 | ### 1.1.1 25 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 26 | 27 | ### 1.1.0 28 | - add input validation logic [PR#364](https://github.com/apollographql/apollo-link/pull/364) 29 | - ensure batch observables can handle multiple subscribers [PR#364](https://github.com/apollographql/apollo-link/pull/364) 30 | 31 | ### 1.0.4 32 | - ApolloLink upgrade 33 | 34 | ### 1.0.3 35 | - export options as named interface [TypeScript] 36 | 37 | ### 1.0.2 38 | - changed peer-dependency of apollo-link to actual dependency 39 | 40 | ### 1.0.1 41 | - moved to better rollup build 42 | 43 | ### 1.0.0 44 | - moved from default export to named to be consistent with rest of link ecosystem 45 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/README.md: -------------------------------------------------------------------------------- 1 | # Batch Link 2 | 3 | ## Purpose 4 | An Apollo Link to allow batching of multiple operations into a single request. For example, the `apollo-link-batch-http` uses this link to batch operations into a single http request. 5 | 6 | ## Installation 7 | 8 | `npm install apollo-link-batch --save` 9 | 10 | ## Usage 11 | ```js 12 | import { BatchLink } from "apollo-link-batch"; 13 | 14 | const link = new BatchLink({ 15 | batchHandler: (operations: Operation[], forward: NextLink) => Observable | null 16 | }); 17 | ``` 18 | 19 | ## Options 20 | Batch Link takes an object with three options on it to customize the behavior of the link. The only required option is the batchHandler function 21 | 22 | |name|value|default|required| 23 | |---|---|---|---| 24 | |batchInterval|number|10|false| 25 | |batchMax|number|0|false| 26 | |batchHandler|(operations: Operation[], forward: NextLink) => Observable | null|NA|true| 27 | 28 | ## Context 29 | The Batch Link does not use the context for anything 30 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-batch", 3 | "version": "1.1.15", 4 | "description": "Apollo Link that performs batching and operation on batched Operations", 5 | "author": "Evans Hauser ", 6 | "contributors": [ 7 | "James Baxley ", 8 | "Jonas Helfer ", 9 | "jon wong ", 10 | "Sashko Stubailo " 11 | ], 12 | "license": "MIT", 13 | "main": "./lib/index.js", 14 | "module": "./lib/bundle.esm.js", 15 | "typings": "./lib/index.d.ts", 16 | "sideEffects": false, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/apollographql/apollo-link.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/apollographql/apollo-link/issues" 23 | }, 24 | "homepage": "https://github.com/apollographql/apollo-link#readme", 25 | "scripts": { 26 | "build": "tsc && rollup -c", 27 | "coverage": "jest --coverage", 28 | "clean": "rimraf lib/* && rimraf coverage/*", 29 | "filesize": "../../scripts/minify", 30 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 31 | "prebuild": "npm run clean", 32 | "prepare": "npm run build", 33 | "test": "npm run lint && jest", 34 | "watch": "tsc -w -p . & rollup -c -w" 35 | }, 36 | "dependencies": { 37 | "apollo-link": "file:../apollo-link", 38 | "tslib": "^1.9.3" 39 | }, 40 | "devDependencies": { 41 | "@types/graphql": "14.2.3", 42 | "@types/jest": "24.9.1", 43 | "fetch-mock": "6.5.2", 44 | "graphql": "15.3.0", 45 | "graphql-tag": "2.10.1", 46 | "jest": "24.9.0", 47 | "proxyquire": "1.8.0", 48 | "rimraf": "2.7.1", 49 | "rollup": "1.32.1", 50 | "ts-jest": "22.4.6", 51 | "tslint": "5.20.1", 52 | "typescript": "3.0.3" 53 | }, 54 | "jest": { 55 | "transform": { 56 | ".(ts|tsx)": "ts-jest" 57 | }, 58 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 59 | "moduleFileExtensions": [ 60 | "ts", 61 | "tsx", 62 | "js", 63 | "json" 64 | ], 65 | "testURL": "http://localhost" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('batch'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/src/batchLink.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloLink, 3 | Operation, 4 | FetchResult, 5 | Observable, 6 | NextLink, 7 | } from 'apollo-link'; 8 | import { OperationBatcher, BatchHandler } from './batching'; 9 | 10 | export { OperationBatcher, BatchableRequest, BatchHandler } from './batching'; 11 | 12 | export namespace BatchLink { 13 | export interface Options { 14 | /** 15 | * The interval at which to batch, in milliseconds. 16 | * 17 | * Defaults to 10. 18 | */ 19 | batchInterval?: number; 20 | 21 | /** 22 | * The maximum number of operations to include in one fetch. 23 | * 24 | * Defaults to 0 (infinite operations within the interval). 25 | */ 26 | batchMax?: number; 27 | 28 | /** 29 | * The handler that should execute a batch of operations. 30 | */ 31 | batchHandler?: BatchHandler; 32 | 33 | /** 34 | * creates the key for a batch 35 | */ 36 | batchKey?: (operation: Operation) => string; 37 | } 38 | } 39 | 40 | export class BatchLink extends ApolloLink { 41 | private batcher: OperationBatcher; 42 | 43 | constructor(fetchParams?: BatchLink.Options) { 44 | super(); 45 | 46 | const { 47 | batchInterval = 10, 48 | batchMax = 0, 49 | batchHandler = () => null, 50 | batchKey = () => '', 51 | } = fetchParams || {}; 52 | 53 | this.batcher = new OperationBatcher({ 54 | batchInterval, 55 | batchMax, 56 | batchHandler, 57 | batchKey, 58 | }); 59 | 60 | //make this link terminating 61 | if (fetchParams.batchHandler.length <= 1) { 62 | this.request = operation => this.batcher.enqueueRequest({ operation }); 63 | } 64 | } 65 | 66 | public request( 67 | operation: Operation, 68 | forward?: NextLink, 69 | ): Observable | null { 70 | return this.batcher.enqueueRequest({ 71 | operation, 72 | forward, 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './batchLink'; 2 | -------------------------------------------------------------------------------- /packages/apollo-link-batch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-context/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-context/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.0.10 11 | 12 | - No changes 13 | 14 | ### 1.0.9 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 1.0.7 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | 21 | ### 1.0.6 22 | - udate apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 23 | 24 | ### 1.0.5 25 | - ApolloLink upgrade 26 | 27 | ### 1.0.4 28 | - ApolloLink upgrade 29 | 30 | ### 1.0.3 31 | - update rollup build 32 | 33 | ### 1.0.2 34 | - changed peer-dependency of apollo-link to actual dependency 35 | 36 | ### 1.0.1 37 | - moved to better rollup build 38 | 39 | ### 1.0.0 40 | - bump to major to signal API stability 41 | 42 | ### 0.1.0 43 | - initial release 44 | -------------------------------------------------------------------------------- /packages/apollo-link-context/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-context 3 | description: Easily set a context on your operation, which is used by other links further down the chain. 4 | --- 5 | 6 | The `setContext` function takes a function that returns either an object or a promise that returns an object to set the new context of a request. 7 | 8 | It receives two arguments: the GraphQL request being executed, and the previous context. This link makes it easy to perform async look up of things like authentication tokens and more! 9 | 10 | ```js 11 | import { setContext } from "apollo-link-context"; 12 | 13 | const setAuthorizationLink = setContext((request, previousContext) => ({ 14 | headers: {authorization: "1234"} 15 | })); 16 | 17 | const asyncAuthLink = setContext( 18 | request => 19 | new Promise((success, fail) => { 20 | // do some async lookup here 21 | setTimeout(() => { 22 | success({ token: "async found token" }); 23 | }, 10); 24 | }) 25 | ); 26 | ``` 27 | 28 | ## Caching lookups 29 | 30 | Typically async actions can be expensive and may not need to be called for every request, especially when a lot of request are happening at once. You can setup your own caching and invalidation outside of the link to make it faster but still flexible! 31 | 32 | Take for example a user auth token being found, cached, then removed on a 401 response: 33 | 34 | ```js 35 | import { setContext } from "apollo-link-context"; 36 | import { onError } from "apollo-link-error"; 37 | 38 | // cached storage for the user token 39 | let token; 40 | const withToken = setContext(() => { 41 | // if you have a cached value, return it immediately 42 | if (token) return { token }; 43 | 44 | return AsyncTokenLookup().then(userToken => { 45 | token = userToken; 46 | return { token }; 47 | }); 48 | }); 49 | 50 | const resetToken = onError(({ networkError }) => { 51 | if (networkError && networkError.name ==='ServerError' && networkError.statusCode === 401) { 52 | // remove cached token on 401 from the server 53 | token = null; 54 | } 55 | }); 56 | 57 | const authFlowLink = withToken.concat(resetToken); 58 | ``` 59 | -------------------------------------------------------------------------------- /packages/apollo-link-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-context", 3 | "version": "1.0.20", 4 | "description": "An easy way to set and cache context changes for Apollo Link", 5 | "author": "James Baxley ", 6 | "license": "MIT", 7 | "main": "./lib/index.js", 8 | "module": "./lib/bundle.esm.js", 9 | "typings": "./lib/index.d.ts", 10 | "sideEffects": false, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/apollographql/apollo-link.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/apollographql/apollo-link/issues" 17 | }, 18 | "homepage": "https://github.com/apollographql/apollo-link#readme", 19 | "scripts": { 20 | "build": "tsc && rollup -c", 21 | "clean": "rimraf lib/* && rimraf coverage/*", 22 | "coverage": "jest --coverage", 23 | "filesize": "../../scripts/minify", 24 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 25 | "prebuild": "npm run clean", 26 | "prepare": "npm run build", 27 | "test": "npm run lint && jest", 28 | "watch": "tsc -w -p . & rollup -c -w" 29 | }, 30 | "dependencies": { 31 | "apollo-link": "file:../apollo-link", 32 | "tslib": "^1.9.3" 33 | }, 34 | "devDependencies": { 35 | "@types/graphql": "14.2.3", 36 | "@types/jest": "24.9.1", 37 | "graphql": "15.3.0", 38 | "graphql-tag": "2.10.1", 39 | "jest": "24.9.0", 40 | "rimraf": "2.7.1", 41 | "rollup": "1.32.1", 42 | "ts-jest": "22.4.6", 43 | "tslint": "5.20.1", 44 | "typescript": "3.0.3" 45 | }, 46 | "jest": { 47 | "transform": { 48 | ".(ts|tsx)": "ts-jest" 49 | }, 50 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 51 | "moduleFileExtensions": [ 52 | "ts", 53 | "tsx", 54 | "js", 55 | "json" 56 | ], 57 | "testURL": "http://localhost" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/apollo-link-context/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('context'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-context/src/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { ApolloLink, execute, Observable } from 'apollo-link'; 3 | 4 | import { setContext } from '../index'; 5 | 6 | const sleep = ms => new Promise(s => setTimeout(s, ms)); 7 | const query = gql` 8 | query Test { 9 | foo { 10 | bar 11 | } 12 | } 13 | `; 14 | const data = { 15 | foo: { bar: true }, 16 | }; 17 | 18 | it('can be used to set the context with a simple function', done => { 19 | const withContext = setContext(() => ({ dynamicallySet: true })); 20 | 21 | const mockLink = new ApolloLink(operation => { 22 | expect(operation.getContext().dynamicallySet).toBe(true); 23 | return Observable.of({ data }); 24 | }); 25 | 26 | const link = withContext.concat(mockLink); 27 | 28 | execute(link, { query }).subscribe(result => { 29 | expect(result.data).toEqual(data); 30 | done(); 31 | }); 32 | }); 33 | 34 | it('can be used to set the context with a function returning a promise', done => { 35 | const withContext = setContext(() => 36 | Promise.resolve({ dynamicallySet: true }), 37 | ); 38 | 39 | const mockLink = new ApolloLink(operation => { 40 | expect(operation.getContext().dynamicallySet).toBe(true); 41 | return Observable.of({ data }); 42 | }); 43 | 44 | const link = withContext.concat(mockLink); 45 | 46 | execute(link, { query }).subscribe(result => { 47 | expect(result.data).toEqual(data); 48 | done(); 49 | }); 50 | }); 51 | 52 | it('can be used to set the context with a function returning a promise that is delayed', done => { 53 | const withContext = setContext(() => 54 | sleep(25).then(() => ({ dynamicallySet: true })), 55 | ); 56 | 57 | const mockLink = new ApolloLink(operation => { 58 | expect(operation.getContext().dynamicallySet).toBe(true); 59 | return Observable.of({ data }); 60 | }); 61 | 62 | const link = withContext.concat(mockLink); 63 | 64 | execute(link, { query }).subscribe(result => { 65 | expect(result.data).toEqual(data); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('handles errors in the lookup correclty', done => { 71 | const withContext = setContext(() => 72 | sleep(5).then(() => { 73 | throw new Error('dang'); 74 | }), 75 | ); 76 | 77 | const mockLink = new ApolloLink(operation => { 78 | return Observable.of({ data }); 79 | }); 80 | 81 | const link = withContext.concat(mockLink); 82 | 83 | execute(link, { query }).subscribe(done.fail, e => { 84 | expect(e.message).toBe('dang'); 85 | done(); 86 | }); 87 | }); 88 | it('handles errors in the lookup correclty with a normal function', done => { 89 | const withContext = setContext(() => { 90 | throw new Error('dang'); 91 | }); 92 | 93 | const mockLink = new ApolloLink(operation => { 94 | return Observable.of({ data }); 95 | }); 96 | 97 | const link = withContext.concat(mockLink); 98 | 99 | execute(link, { query }).subscribe(done.fail, e => { 100 | expect(e.message).toBe('dang'); 101 | done(); 102 | }); 103 | }); 104 | 105 | it('has access to the request information', done => { 106 | const withContext = setContext(({ operationName, query, variables }) => 107 | sleep(1).then(() => 108 | Promise.resolve({ 109 | variables: variables ? true : false, 110 | operation: query ? true : false, 111 | operationName: operationName.toUpperCase(), 112 | }), 113 | ), 114 | ); 115 | 116 | const mockLink = new ApolloLink(operation => { 117 | const { variables, operation, operationName } = operation.getContext(); 118 | expect(variables).toBe(true); 119 | expect(operation).toBe(true); 120 | expect(operationName).toBe('TEST'); 121 | return Observable.of({ data }); 122 | }); 123 | 124 | const link = withContext.concat(mockLink); 125 | 126 | execute(link, { query, variables: { id: 1 } }).subscribe(result => { 127 | expect(result.data).toEqual(data); 128 | done(); 129 | }); 130 | }); 131 | it('has access to the context at execution time', done => { 132 | const withContext = setContext((_, { count }) => 133 | sleep(1).then(() => ({ count: count + 1 })), 134 | ); 135 | 136 | const mockLink = new ApolloLink(operation => { 137 | const { count } = operation.getContext(); 138 | expect(count).toEqual(2); 139 | return Observable.of({ data }); 140 | }); 141 | 142 | const link = withContext.concat(mockLink); 143 | 144 | execute(link, { query, context: { count: 1 } }).subscribe(result => { 145 | expect(result.data).toEqual(data); 146 | done(); 147 | }); 148 | }); 149 | 150 | it('unsubscribes correctly', done => { 151 | const withContext = setContext((_, { count }) => 152 | sleep(1).then(() => ({ count: count + 1 })), 153 | ); 154 | 155 | const mockLink = new ApolloLink(operation => { 156 | const { count } = operation.getContext(); 157 | expect(count).toEqual(2); 158 | return Observable.of({ data }); 159 | }); 160 | 161 | const link = withContext.concat(mockLink); 162 | 163 | let handle = execute(link, { 164 | query, 165 | context: { count: 1 }, 166 | }).subscribe(result => { 167 | expect(result.data).toEqual(data); 168 | handle.unsubscribe(); 169 | done(); 170 | }); 171 | }); 172 | 173 | it('unsubscribes without throwing before data', done => { 174 | let called; 175 | const withContext = setContext((_, { count }) => { 176 | called = true; 177 | return sleep(1).then(() => ({ count: count + 1 })); 178 | }); 179 | 180 | const mockLink = new ApolloLink(operation => { 181 | const { count } = operation.getContext(); 182 | expect(count).toEqual(2); 183 | return new Observable(obs => { 184 | setTimeout(() => { 185 | obs.next({ data }); 186 | obs.complete(); 187 | }, 25); 188 | }); 189 | }); 190 | 191 | const link = withContext.concat(mockLink); 192 | 193 | let handle = execute(link, { 194 | query, 195 | context: { count: 1 }, 196 | }).subscribe(result => { 197 | done.fail('should have unsubscribed'); 198 | }); 199 | 200 | setTimeout(() => { 201 | handle.unsubscribe(); 202 | expect(called).toBe(true); 203 | done(); 204 | }, 10); 205 | }); 206 | -------------------------------------------------------------------------------- /packages/apollo-link-context/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloLink, 3 | Observable, 4 | Operation, 5 | NextLink, 6 | GraphQLRequest, 7 | } from 'apollo-link'; 8 | 9 | export type ContextSetter = ( 10 | operation: GraphQLRequest, 11 | prevContext: any, 12 | ) => Promise | any; 13 | 14 | export function setContext(setter: ContextSetter): ApolloLink { 15 | return new ApolloLink((operation: Operation, forward: NextLink) => { 16 | const { ...request } = operation; 17 | 18 | return new Observable(observer => { 19 | let handle; 20 | Promise.resolve(request) 21 | .then(req => setter(req, operation.getContext())) 22 | .then(operation.setContext) 23 | .then(() => { 24 | handle = forward(operation).subscribe({ 25 | next: observer.next.bind(observer), 26 | error: observer.error.bind(observer), 27 | complete: observer.complete.bind(observer), 28 | }); 29 | }) 30 | .catch(observer.error.bind(observer)); 31 | 32 | return () => { 33 | if (handle) handle.unsubscribe(); 34 | }; 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /packages/apollo-link-context/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.0.11 11 | 12 | - No changes 13 | 14 | ### 1.0.10 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 1.0.9 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | 21 | ### 1.0.8 22 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 23 | 24 | ### 1.0.7 25 | - ApolloLink upgrade 26 | 27 | ### 1.0.6 28 | - ApolloLink upgrade 29 | 30 | ### 1.0.5 31 | - ApolloLink upgrade 32 | 33 | ### 1.0.4 34 | - update rollup build 35 | 36 | ### 1.0.3 37 | - changed peer-dependency of apollo-link to actual dependency 38 | 39 | ### 1.0.2 40 | - fixed bug where next observable subscription was not deduplicated 41 | - moved to better rollup build 42 | 43 | ### 1.0.1 [Unpublished] 44 | 45 | - fixed bug where next observable subscription was not deduplicated 46 | 47 | ### 1.0.0 48 | - moved from default export to named to be consistent with rest of link ecosystem 49 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-dedup 3 | description: Deduplicate matching requests before making a request 4 | --- 5 | 6 | *NOTE* This link is included by default when using apollo-client so you don't need to add it to your link chain if using apollo-client. 7 | 8 | ## Installation 9 | 10 | `npm install apollo-link-dedup --save` 11 | 12 | ## Usage 13 | ```js 14 | import { DedupLink } from "apollo-link-dedup"; 15 | 16 | const link = new DedupLink(); 17 | ``` 18 | 19 | ## Options 20 | The Dedup Link does not take any options when creating the link. 21 | 22 | ## Context 23 | The Dedup Link can be overridden by using the context on a per operation basis: 24 | - `forceFetch`: a true or false (defaults to false) to bypass deduplication per request 25 | 26 | ```js 27 | import { createHttpLink } from "apollo-link-http"; 28 | import ApolloClient from "apollo-client"; 29 | import InMemoryCache from "apollo-cache-inmemory"; 30 | 31 | const client = new ApolloClient({ 32 | link: createHttpLink({ uri: "/graphql" }), 33 | cache: new InMemoryCache() 34 | }); 35 | 36 | // a query with apollo-client that will not be deduped 37 | client.query({ 38 | query: MY_QUERY, 39 | context: { 40 | forceFetch: true 41 | } 42 | }) 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-dedup", 3 | "version": "1.0.21", 4 | "description": "Deduplicates queries that are currently on the wire", 5 | "author": "Evans Hauser ", 6 | "contributors": [ 7 | "James Baxley ", 8 | "Jonas Helfer ", 9 | "jon wong ", 10 | "Sashko Stubailo " 11 | ], 12 | "license": "MIT", 13 | "main": "./lib/index.js", 14 | "module": "./lib/bundle.esm.js", 15 | "typings": "./lib/index.d.ts", 16 | "sideEffects": false, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/apollographql/apollo-link.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/apollographql/apollo-link/issues" 23 | }, 24 | "homepage": "https://github.com/apollographql/apollo-link#readme", 25 | "scripts": { 26 | "build": "tsc && rollup -c", 27 | "clean": "rimraf lib/* && rimraf coverage/*", 28 | "coverage": "jest --coverage", 29 | "filesize": "../../scripts/minify", 30 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 31 | "prebuild": "npm run clean", 32 | "prepare": "npm run build", 33 | "test": "npm run lint && jest", 34 | "watch": "tsc -w -p . & rollup -c -w" 35 | }, 36 | "dependencies": { 37 | "apollo-link": "file:../apollo-link", 38 | "tslib": "^1.9.3" 39 | }, 40 | "devDependencies": { 41 | "@types/graphql": "14.2.3", 42 | "@types/jest": "24.9.1", 43 | "graphql": "15.3.0", 44 | "graphql-tag": "2.10.1", 45 | "jest": "24.9.0", 46 | "rimraf": "2.7.1", 47 | "rollup": "1.32.1", 48 | "ts-jest": "22.4.6", 49 | "tslint": "5.20.1", 50 | "typescript": "3.0.3" 51 | }, 52 | "jest": { 53 | "transform": { 54 | ".(ts|tsx)": "ts-jest" 55 | }, 56 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 57 | "moduleFileExtensions": [ 58 | "ts", 59 | "tsx", 60 | "js", 61 | "json" 62 | ], 63 | "testURL": "http://localhost" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('dedup'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/src/dedupLink.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloLink, 3 | Operation, 4 | NextLink, 5 | FetchResult, 6 | Observable, 7 | } from 'apollo-link'; 8 | 9 | /* 10 | * Expects context to contain the forceFetch field if no dedup 11 | */ 12 | export class DedupLink extends ApolloLink { 13 | private inFlightRequestObservables: Map< 14 | string, 15 | Observable 16 | > = new Map(); 17 | private subscribers: Map = new Map(); 18 | 19 | public request( 20 | operation: Operation, 21 | forward: NextLink, 22 | ): Observable { 23 | // sometimes we might not want to deduplicate a request, for example when we want to force fetch it. 24 | if (operation.getContext().forceFetch) { 25 | return forward(operation); 26 | } 27 | 28 | const key = operation.toKey(); 29 | 30 | if (!this.inFlightRequestObservables.get(key)) { 31 | // this is a new request, i.e. we haven't deduplicated it yet 32 | // call the next link 33 | const singleObserver = forward(operation); 34 | let subscription; 35 | 36 | const sharedObserver = new Observable(observer => { 37 | // this will still be called by each subscriber regardless of 38 | // deduplication status 39 | if (!this.subscribers.has(key)) this.subscribers.set(key, new Set()); 40 | 41 | this.subscribers.get(key).add(observer); 42 | 43 | if (!subscription) { 44 | subscription = singleObserver.subscribe({ 45 | next: result => { 46 | const subscribers = this.subscribers.get(key); 47 | this.subscribers.delete(key); 48 | this.inFlightRequestObservables.delete(key); 49 | if (subscribers) { 50 | subscribers.forEach(obs => obs.next(result)); 51 | subscribers.forEach(obs => obs.complete()); 52 | } 53 | }, 54 | error: error => { 55 | const subscribers = this.subscribers.get(key); 56 | this.subscribers.delete(key); 57 | this.inFlightRequestObservables.delete(key); 58 | if (subscribers) { 59 | subscribers.forEach(obs => obs.error(error)); 60 | } 61 | }, 62 | }); 63 | } 64 | 65 | return () => { 66 | if (this.subscribers.has(key)) { 67 | this.subscribers.get(key).delete(observer); 68 | if (this.subscribers.get(key).size === 0) { 69 | this.inFlightRequestObservables.delete(key); 70 | if (subscription) subscription.unsubscribe(); 71 | } 72 | } 73 | }; 74 | }); 75 | 76 | this.inFlightRequestObservables.set(key, sharedObserver); 77 | } 78 | 79 | // return shared Observable 80 | return this.inFlightRequestObservables.get(key); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dedupLink'; 2 | -------------------------------------------------------------------------------- /packages/apollo-link-dedup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-error/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-error/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.1.2 11 | 12 | - No changes 13 | 14 | ### 1.1.1 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | - Update types to be compatible with `@types/graphql@0.13.3` 18 | 19 | ### 1.1.0 20 | - Pass `forward` into error handler for ErrorLink to support retrying a failed request 21 | 22 | ### 1.0.9 23 | - Correct return type to FetchResult [#600](https://github.com/apollographql/apollo-link/pull/600) 24 | 25 | ### 1.0.8 26 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 27 | 28 | ### 1.0.7 29 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 30 | 31 | ### 1.0.6 32 | - ApolloLink upgrade 33 | 34 | ### 1.0.5 35 | - ApolloLink upgrade 36 | 37 | ### 1.0.4 38 | - ApolloLink upgrade 39 | 40 | ### 1.0.3 41 | - export options as named interface [TypeScript] 42 | 43 | ### 1.0.2 44 | - changed peer-dependency of apollo-link to actual dependency 45 | - graphQLErrors alias networkError.result.errors on a networkError 46 | 47 | ### 1.0.1 48 | - moved to better rollup build 49 | 50 | ### 1.0.0 51 | - Added the operation and any data to the error handler callback 52 | - changed graphqlErrors to be graphQLErrors to be consistent with Apollo Client 53 | -------------------------------------------------------------------------------- /packages/apollo-link-error/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-error 3 | description: Handle and inspect errors in your GraphQL network stack. 4 | --- 5 | 6 | Use this link to do some custom logic when a GraphQL or network error happens: 7 | 8 | ```js 9 | import { onError } from "apollo-link-error"; 10 | 11 | const link = onError(({ graphQLErrors, networkError }) => { 12 | if (graphQLErrors) 13 | graphQLErrors.forEach(({ message, locations, path }) => 14 | console.log( 15 | `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` 16 | ) 17 | ); 18 | if (networkError) console.log(`[Network error]: ${networkError}`); 19 | }); 20 | ``` 21 | 22 | Apollo Link is a system of modular components for GraphQL networking. [Read the docs](https://www.apollographql.com/docs/link/#usage) to learn how to use this link with libraries like Apollo Client and graphql-tools, or as a standalone client. 23 | 24 | ## Callback 25 | 26 | Error Link takes a function that is called in the event of an error. This function is called with an object containing the following keys: 27 | 28 | * `operation`: The Operation that errored 29 | * `response`: The result returned from lower down in the link chain 30 | * `graphQLErrors`: An array of errors from the GraphQL endpoint 31 | * `networkError`: Any error during the link execution or server response, that wasn't delivered as part of the `errors` field in the GraphQL result 32 | * `forward`: A reference to the next link in the chain. Calling `return forward(operation)` in the callback will retry the request, returning a new observable for the upstream link to subscribe to. 33 | 34 | Returns: `Observable | void` The error callback can optionally return an observable from calling `forward(operation)` if it wants to retry the request. It should not return anything else. 35 | 36 | ## Error categorization 37 | 38 | An error is passed as a `networkError` if a link further down the chain called the `error` callback on the observable. In most cases, `graphQLErrors` is the `errors` field of the result from the last `next` call. 39 | 40 | A `networkError` can contain additional fields, such as a GraphQL object in the case of a [failing HTTP status code](http#errors) from [`apollo-link-http`](http). In this situation, `graphQLErrors` is an alias for `networkError.result.errors` if the property exists. 41 | 42 | ## Retrying failed requests 43 | 44 | An error handler might want to do more than just logging errors. You can check for a certain failure condition or error code, and retry the request if rectifying the error is possible. For example, when using some form of token based authentication, there is a need to handle re-authentication when the token expires. Here is an example of how to do this using `forward()`. 45 | ```js 46 | onError(({ graphQLErrors, networkError, operation, forward }) => { 47 | if (graphQLErrors) { 48 | for (let err of graphQLErrors) { 49 | switch (err.extensions.code) { 50 | case 'UNAUTHENTICATED': 51 | // error code is set to UNAUTHENTICATED 52 | // when AuthenticationError thrown in resolver 53 | 54 | // modify the operation context with a new token 55 | const oldHeaders = operation.getContext().headers; 56 | operation.setContext({ 57 | headers: { 58 | ...oldHeaders, 59 | authorization: getNewToken(), 60 | }, 61 | }); 62 | // retry the request, returning the new observable 63 | return forward(operation); 64 | } 65 | } 66 | } 67 | if (networkError) { 68 | console.log(`[Network error]: ${networkError}`); 69 | // if you would also like to retry automatically on 70 | // network errors, we recommend that you use 71 | // apollo-link-retry 72 | } 73 | } 74 | ); 75 | ``` 76 | 77 | Here is a diagram of how the request flow looks like now: 78 | ![Diagram of request flow after retrying in error links](https://i.imgur.com/ncVAdz4.png) 79 | 80 | One caveat is that the errors from the new response from retrying the request does not get passed into the error handler again. This helps to avoid being trapped in an endless request loop when you call forward() in your error handler. 81 | 82 | ## Ignoring errors 83 | 84 | If you want to conditionally ignore errors, you can set `response.errors = undefined;` within the error handler: 85 | 86 | ```js 87 | onError(({ response, operation }) => { 88 | if (operation.operationName === "IgnoreErrorsQuery") { 89 | response.errors = undefined; 90 | } 91 | }); 92 | ``` 93 | -------------------------------------------------------------------------------- /packages/apollo-link-error/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-error", 3 | "version": "1.1.13", 4 | "description": "Error Apollo Link for GraphQL Network Stack", 5 | "author": "James Baxley ", 6 | "license": "MIT", 7 | "main": "./lib/index.js", 8 | "module": "./lib/bundle.esm.js", 9 | "typings": "./lib/index.d.ts", 10 | "sideEffects": false, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/apollographql/apollo-link.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/apollographql/apollo-link/issues" 17 | }, 18 | "homepage": "https://github.com/apollographql/apollo-link#readme", 19 | "scripts": { 20 | "build": "tsc && rollup -c", 21 | "clean": "rimraf lib/* && rimraf coverage/*", 22 | "coverage": "jest --coverage", 23 | "filesize": "../../scripts/minify", 24 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 25 | "prebuild": "npm run clean", 26 | "prepare": "npm run build", 27 | "test": "npm run lint && jest", 28 | "watch": "tsc -w -p . & rollup -c -w" 29 | }, 30 | "dependencies": { 31 | "apollo-link": "file:../apollo-link", 32 | "apollo-link-http-common": "file:../apollo-link-http-common", 33 | "tslib": "^1.9.3" 34 | }, 35 | "devDependencies": { 36 | "@types/graphql": "14.2.3", 37 | "@types/jest": "24.9.1", 38 | "graphql": "15.3.0", 39 | "graphql-tag": "2.10.1", 40 | "jest": "24.9.0", 41 | "rimraf": "2.7.1", 42 | "rollup": "1.32.1", 43 | "ts-jest": "22.4.6", 44 | "tslint": "5.20.1", 45 | "typescript": "3.0.3" 46 | }, 47 | "jest": { 48 | "transform": { 49 | ".(ts|tsx)": "ts-jest" 50 | }, 51 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 52 | "moduleFileExtensions": [ 53 | "ts", 54 | "tsx", 55 | "js", 56 | "json" 57 | ], 58 | "testURL": "http://localhost" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/apollo-link-error/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('error'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-error/src/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | import { 4 | ApolloLink, 5 | Observable, 6 | Operation, 7 | NextLink, 8 | FetchResult, 9 | ExecutionResult, 10 | } from 'apollo-link'; 11 | import { GraphQLError } from 'graphql'; 12 | import { ServerError, ServerParseError } from 'apollo-link-http-common'; 13 | 14 | export interface ErrorResponse { 15 | graphQLErrors?: ReadonlyArray; 16 | networkError?: Error | ServerError | ServerParseError; 17 | response?: ExecutionResult; 18 | operation: Operation; 19 | forward: NextLink; 20 | } 21 | 22 | export namespace ErrorLink { 23 | /** 24 | * Callback to be triggered when an error occurs within the link stack. 25 | */ 26 | export interface ErrorHandler { 27 | (error: ErrorResponse): Observable | void; 28 | } 29 | } 30 | 31 | // For backwards compatibility. 32 | export import ErrorHandler = ErrorLink.ErrorHandler; 33 | 34 | export function onError(errorHandler: ErrorHandler): ApolloLink { 35 | return new ApolloLink((operation, forward) => { 36 | return new Observable(observer => { 37 | let sub; 38 | let retriedSub; 39 | let retriedResult; 40 | 41 | try { 42 | sub = forward(operation).subscribe({ 43 | next: result => { 44 | if (result.errors) { 45 | retriedResult = errorHandler({ 46 | graphQLErrors: result.errors, 47 | response: result, 48 | operation, 49 | forward, 50 | }); 51 | 52 | if (retriedResult) { 53 | retriedSub = retriedResult.subscribe({ 54 | next: observer.next.bind(observer), 55 | error: observer.error.bind(observer), 56 | complete: observer.complete.bind(observer), 57 | }); 58 | return; 59 | } 60 | } 61 | observer.next(result); 62 | }, 63 | error: networkError => { 64 | retriedResult = errorHandler({ 65 | operation, 66 | networkError, 67 | //Network errors can return GraphQL errors on for example a 403 68 | graphQLErrors: 69 | networkError && 70 | networkError.result && 71 | networkError.result.errors, 72 | forward, 73 | }); 74 | if (retriedResult) { 75 | retriedSub = retriedResult.subscribe({ 76 | next: observer.next.bind(observer), 77 | error: observer.error.bind(observer), 78 | complete: observer.complete.bind(observer), 79 | }); 80 | return; 81 | } 82 | observer.error(networkError); 83 | }, 84 | complete: () => { 85 | // disable the previous sub from calling complete on observable 86 | // if retry is in flight. 87 | if (!retriedResult) { 88 | observer.complete.bind(observer)(); 89 | } 90 | }, 91 | }); 92 | } catch (e) { 93 | errorHandler({ networkError: e, operation, forward }); 94 | observer.error(e); 95 | } 96 | 97 | return () => { 98 | if (sub) sub.unsubscribe(); 99 | if (retriedSub) sub.unsubscribe(); 100 | }; 101 | }); 102 | }); 103 | } 104 | 105 | export class ErrorLink extends ApolloLink { 106 | private link: ApolloLink; 107 | constructor(errorHandler: ErrorLink.ErrorHandler) { 108 | super(); 109 | this.link = onError(errorHandler); 110 | } 111 | 112 | public request( 113 | operation: Operation, 114 | forward: NextLink, 115 | ): Observable | null { 116 | return this.link.request(operation, forward); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/apollo-link-error/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-http-common/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-http-common/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 0.2.6 11 | 12 | - No changes 13 | 14 | ### 0.2.5 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 0.2.4 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | 21 | ### v0.2.3 22 | - correct the warning message on no fetch found to node-fetch[PR#526](https://github.com/apollographql/apollo-link/pull/526) 23 | 24 | ### v0.2.2 25 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 26 | 27 | ### v0.2.1 28 | - Apollo link upgrade 29 | 30 | ### v0.2.0 31 | - rename serializeFetchBody to serializeFetchParameter and take a label argument 32 | -------------------------------------------------------------------------------- /packages/apollo-link-http-common/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-http-common 3 | description: Http utilities shared across Apollo Links 4 | --- 5 | 6 | This repository is used in apollo-link-http and apollo-link-http-batch. The 7 | package is public to allow easier development of links that will be a part of 8 | the main repository. Developers using this package should know that the api 9 | will change and versions may not follow SemVer. 10 | -------------------------------------------------------------------------------- /packages/apollo-link-http-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-http-common", 3 | "version": "0.2.16", 4 | "description": "Http utilities for Apollo Link shared across all links using http", 5 | "main": "./lib/index.js", 6 | "module": "./lib/bundle.esm.js", 7 | "typings": "./lib/index.d.ts", 8 | "sideEffects": false, 9 | "scripts": { 10 | "build": "tsc && rollup -c", 11 | "clean": "rimraf lib/* && rimraf coverage/*", 12 | "coverage": "jest --coverage", 13 | "filesize": "../../scripts/minify", 14 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 15 | "prebuild": "npm run clean", 16 | "prepare": "npm run build", 17 | "test": "npm run lint && jest", 18 | "watch": "tsc -w -p . & rollup -c -w" 19 | }, 20 | "keywords": [ 21 | "apollo", 22 | "http", 23 | "network" 24 | ], 25 | "author": "Evans Hauser", 26 | "license": "MIT", 27 | "dependencies": { 28 | "apollo-link": "file:../apollo-link", 29 | "ts-invariant": "^0.4.0", 30 | "tslib": "^1.9.3" 31 | }, 32 | "peerDependencies": { 33 | "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" 34 | }, 35 | "devDependencies": { 36 | "@types/graphql": "14.2.3", 37 | "@types/jest": "24.9.1", 38 | "fetch-mock": "6.5.2", 39 | "graphql": "15.3.0", 40 | "graphql-tag": "2.10.1", 41 | "jest": "24.9.0", 42 | "object-to-querystring": "1.0.8", 43 | "rimraf": "2.7.1", 44 | "rollup": "1.32.1", 45 | "ts-jest": "22.4.6", 46 | "tslint": "5.20.1", 47 | "typescript": "3.0.3" 48 | }, 49 | "repository": { 50 | "type": "git", 51 | "url": "git+https://github.com/apollographql/apollo-link.git" 52 | }, 53 | "bugs": { 54 | "url": "https://github.com/apollographql/apollo-link/issues" 55 | }, 56 | "homepage": "https://github.com/apollographql/apollo-link#readme", 57 | "jest": { 58 | "transform": { 59 | ".(ts|tsx)": "ts-jest" 60 | }, 61 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 62 | "moduleFileExtensions": [ 63 | "ts", 64 | "tsx", 65 | "js", 66 | "json" 67 | ], 68 | "testURL": "http://localhost" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/apollo-link-http-common/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('httpCommon'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-http-common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-http/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-http/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.5.7 11 | 12 | - Fix a bug where empty `apollographql-client-name` and 13 | `apollographql-client-version` headers were being included with requests 14 | when they weren't set in the `context` based `clientAwareness` object.
15 | [@hwillson](http://github.com/hwillson) in [#872](https://github.com/apollographql/apollo-link/pull/872) 16 | 17 | ### 1.5.6 18 | 19 | - If `name` or `version` client awareness settings are found in the 20 | incoming `operation` `context`, they'll be extracted and added as headers 21 | to all outgoing requests. The header names used (`apollographql-client-name` 22 | and `apollographql-client-version`) line up with the associated Apollo Server 23 | changes made in https://github.com/apollographql/apollo-server/pull/1960.
24 | [@hwillson](http://github.com/hwillson) in [#872](https://github.com/apollographql/apollo-link/pull/872) 25 | 26 | ### 1.5.5 27 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
28 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 29 | 30 | ### 1.5.4 31 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 32 | - Check for signal already present on `fetchOptions` [#584](https://github.com/apollographql/apollo-link/pull/584) 33 | 34 | ### 1.5.3 35 | - updated dependency on `apolloLink.httpCommon` [#522](https://github.com/apollographql/apollo-link/pull/522) 36 | 37 | ### 1.5.2 38 | - fix issue where execution result with only `errors` key fired the `next` event 39 | - Add missing rollup alias and make http-common exported as `apolloLink.httpCommon` [#522](https://github.com/apollographql/apollo-link/pull/522) 40 | 41 | ### 1.5.1 42 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 43 | 44 | ### 1.5.0 45 | - New useGETForQueries option: if set, uses GET for queries (but not mutations) 46 | 47 | ### 1.4.0 48 | - move logic to apollo-link-http-core [PR#364](https://github.com/apollographql/apollo-link/pull/364) 49 | - follow the spec properly for GET requests [PR#490](https://github.com/apollographql/apollo-link/pull/490) 50 | - ApolloLink upgrade 51 | 52 | ### 1.3.3 53 | - ApolloLink upgrade 54 | - Allow graphql results to fire even if there is a network error 55 | 56 | ### 1.3.2 57 | - Update to graphql@0.12 58 | 59 | ### 1.3.1 60 | - export options as named interface [TypeScript] 61 | - Fix typescript bug with destructuring of parameter in createHttpLink ([#189](https://github.com/apollographql/apollo-link/issues/189)) 62 | 63 | ### 1.3.0 64 | - changed to initially parsing response as text to improve error handling 65 | - cleaned up error handling types and added docs 66 | - changed peer-dependency of apollo-link to actual dependency 67 | 68 | ### 1.2.0 69 | - moved to better rollup build 70 | - support for persisted queries by opting out of sending the query 71 | 72 | ### v1.1.0 73 | - support dynamic endpoints using `uri` on the context 74 | - the request not attaches the raw response as `response` on the context. This can be used to access response headers or more 75 | 76 | ### v1.0.0 77 | - official release, not changes 78 | 79 | ### v0.9.0 80 | - changed `fetcherOptions` to be `fetchOptions` and added a test for using 'GET' requests 81 | 82 | ### v0.8.0 83 | - throw error on empty ExectionResult (missing) 84 | - support setting credentials, headers, and fetcherOptions in the setup of the link 85 | - removed sending of context to the server and allowed opt-in of sending extensions 86 | -------------------------------------------------------------------------------- /packages/apollo-link-http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-http", 3 | "version": "1.5.17", 4 | "description": "HTTP transport layer for GraphQL", 5 | "author": "Evans Hauser ", 6 | "contributors": [ 7 | "James Baxley ", 8 | "Jonas Helfer ", 9 | "jon wong ", 10 | "Sashko Stubailo ", 11 | "Stephen Kao " 12 | ], 13 | "license": "MIT", 14 | "main": "./lib/index.js", 15 | "module": "./lib/bundle.esm.js", 16 | "typings": "./lib/index.d.ts", 17 | "sideEffects": false, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/apollographql/apollo-link.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/apollographql/apollo-link/issues" 24 | }, 25 | "homepage": "https://github.com/apollographql/apollo-link#readme", 26 | "scripts": { 27 | "build": "tsc && rollup -c", 28 | "clean": "rimraf lib/* && rimraf coverage/*", 29 | "coverage": "jest --coverage", 30 | "filesize": "../../scripts/minify", 31 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 32 | "prebuild": "npm run clean", 33 | "prepare": "npm run build", 34 | "test": "npm run lint && jest", 35 | "watch": "tsc -w -p . & rollup -c -w" 36 | }, 37 | "dependencies": { 38 | "apollo-link": "file:../apollo-link", 39 | "apollo-link-http-common": "file:../apollo-link-http-common", 40 | "tslib": "^1.9.3" 41 | }, 42 | "peerDependencies": { 43 | "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" 44 | }, 45 | "devDependencies": { 46 | "@types/graphql": "14.2.3", 47 | "@types/jest": "24.9.1", 48 | "apollo-fetch": "0.7.0", 49 | "fetch-mock": "6.5.2", 50 | "graphql": "15.3.0", 51 | "graphql-tag": "2.10.1", 52 | "jest": "24.9.0", 53 | "object-to-querystring": "1.0.8", 54 | "rimraf": "2.7.1", 55 | "rollup": "1.32.1", 56 | "ts-jest": "22.4.6", 57 | "tslint": "5.20.1", 58 | "typescript": "3.0.3" 59 | }, 60 | "jest": { 61 | "transform": { 62 | ".(ts|tsx)": "ts-jest" 63 | }, 64 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 65 | "moduleFileExtensions": [ 66 | "ts", 67 | "tsx", 68 | "js", 69 | "json" 70 | ], 71 | "testPathIgnorePatterns": [ 72 | "/node_modules/", 73 | "sharedHttpTests.ts" 74 | ], 75 | "testURL": "http://localhost" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/apollo-link-http/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('http'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-http/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './httpLink'; 2 | -------------------------------------------------------------------------------- /packages/apollo-link-http/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.0.10 11 | 12 | - No changes 13 | 14 | ### 1.0.9 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/README.md: -------------------------------------------------------------------------------- 1 | # Polling Link 2 | 3 | ## Purpose 4 | This is a WIP for abstracting the scheduler from apollo-client to allow polling to be handled by a link 5 | 6 | ## Installation 7 | 8 | `npm install apollo-link-polling --save` 9 | 10 | 11 | ## Usage 12 | 13 | ```js 14 | import { PollingLink } from "apollo-link-polling"; 15 | 16 | const pollingLink = new PollingLink(() => 10000); // time in milliseconds 17 | ``` -------------------------------------------------------------------------------- /packages/apollo-link-polling/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-polling", 3 | "private": true, 4 | "version": "1.0.20", 5 | "description": "Polling Apollo Link for GraphQL Network Stack", 6 | "author": "Evans Hauser ", 7 | "contributors": [ 8 | "James Baxley ", 9 | "Jonas Helfer ", 10 | "jon wong ", 11 | "Sashko Stubailo " 12 | ], 13 | "license": "MIT", 14 | "main": "./lib/index.js", 15 | "module": "./lib/bundle.esm.js", 16 | "typings": "./lib/index.d.ts", 17 | "sideEffects": false, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/apollographql/apollo-link.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/apollographql/apollo-link/issues" 24 | }, 25 | "homepage": "https://github.com/apollographql/apollo-link#readme", 26 | "scripts": { 27 | "build": "tsc && rollup -c", 28 | "clean": "rimraf lib/* && rimraf coverage/*", 29 | "coverage": "jest --coverage", 30 | "filesize": "../../scripts/minify", 31 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 32 | "prebuild": "npm run clean", 33 | "prepare": "npm run build", 34 | "test": "npm run lint && jest", 35 | "watch": "tsc -w -p . & rollup -c -w" 36 | }, 37 | "dependencies": { 38 | "@types/zen-observable": "0.8.0", 39 | "apollo-link": "file:../apollo-link", 40 | "tslib": "1.13.0" 41 | }, 42 | "devDependencies": { 43 | "@types/graphql": "14.2.3", 44 | "@types/jest": "24.9.1", 45 | "graphql": "15.3.0", 46 | "graphql-tag": "2.10.1", 47 | "jest": "24.9.0", 48 | "rimraf": "2.7.1", 49 | "rollup": "1.32.1", 50 | "ts-jest": "22.4.6", 51 | "tslint": "5.20.1", 52 | "typescript": "3.0.3" 53 | }, 54 | "jest": { 55 | "transform": { 56 | ".(ts|tsx)": "ts-jest" 57 | }, 58 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 59 | "moduleFileExtensions": [ 60 | "ts", 61 | "tsx", 62 | "js", 63 | "json" 64 | ], 65 | "testURL": "http://localhost" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('polling'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/src/__tests__/pollingLink.ts: -------------------------------------------------------------------------------- 1 | import { execute, Observable } from 'apollo-link'; 2 | import gql from 'graphql-tag'; 3 | 4 | import { PollingLink } from '../pollingLink'; 5 | 6 | const query = gql` 7 | query SampleQuery { 8 | stub { 9 | id 10 | } 11 | } 12 | `; 13 | 14 | describe('PollingLink', () => { 15 | it('should construct with an interval', () => { 16 | expect(() => new PollingLink(() => null)).not.toThrow(); 17 | }); 18 | 19 | it('should construct with an interval', () => { 20 | expect(() => new PollingLink(() => 1)).not.toThrow(); 21 | }); 22 | 23 | it('should poll request', done => { 24 | let count = 0; 25 | let subscription; 26 | const spy = jest.fn(); 27 | const checkResults = () => { 28 | const calls = spy.mock.calls; 29 | calls.map((call, i) => expect(call[0].data.count).toEqual(i)); 30 | expect(calls.length).toEqual(5); 31 | done(); 32 | }; 33 | 34 | const poll = new PollingLink(() => 1).concat(() => { 35 | if (count >= 5) { 36 | subscription.unsubscribe(); 37 | checkResults(); 38 | } 39 | return Observable.of({ 40 | data: { 41 | count: count++, 42 | }, 43 | }); 44 | }); 45 | 46 | subscription = execute(poll, { query }).subscribe({ 47 | next: spy, 48 | error: error => { 49 | throw error; 50 | }, 51 | complete: () => { 52 | throw new Error(); 53 | }, 54 | }); 55 | }); 56 | 57 | it('should poll request until error', done => { 58 | let count = 0; 59 | let subscription; 60 | const error = new Error('End polling'); 61 | 62 | const spy = jest.fn(); 63 | const checkResults = actualError => { 64 | expect(error).toEqual(actualError); 65 | const calls = spy.mock.calls; 66 | calls.map((call, i) => expect(call[0].data.count).toEqual(i)); 67 | expect(calls.length).toEqual(5); 68 | done(); 69 | }; 70 | 71 | const poll = new PollingLink(() => 1).concat(() => { 72 | if (count >= 5) { 73 | return new Observable(observer => { 74 | throw error; 75 | }); 76 | } 77 | 78 | return Observable.of({ 79 | data: { 80 | count: count++, 81 | }, 82 | }); 83 | }); 84 | 85 | subscription = execute(poll, { query }).subscribe({ 86 | next: spy, 87 | error: err => checkResults(err), 88 | complete: () => { 89 | throw new Error(); 90 | }, 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pollingLink'; 2 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/src/pollingLink.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloLink, 3 | Operation, 4 | NextLink, 5 | FetchResult, 6 | Observable, 7 | } from 'apollo-link'; 8 | 9 | export namespace PollingLink { 10 | /** 11 | * Frequency (in milliseconds) that an operation should be polled on. 12 | */ 13 | export interface PollInterval { 14 | (operation: Operation): number | null; 15 | } 16 | } 17 | 18 | export class PollingLink extends ApolloLink { 19 | private pollInterval: PollingLink.PollInterval; 20 | private timer; 21 | private subscription: ZenObservable.Subscription; 22 | 23 | constructor(pollInterval: PollingLink.PollInterval) { 24 | super(); 25 | this.pollInterval = pollInterval; 26 | } 27 | 28 | public request( 29 | operation: Operation, 30 | forward: NextLink, 31 | ): Observable { 32 | return new Observable(observer => { 33 | const subscriber = { 34 | next: data => { 35 | observer.next(data); 36 | }, 37 | error: error => observer.error(error), 38 | }; 39 | 40 | const poll = () => { 41 | this.subscription.unsubscribe(); 42 | this.subscription = forward(operation).subscribe(subscriber); 43 | }; 44 | 45 | const interval = this.pollInterval(operation); 46 | if (interval !== null) { 47 | this.timer = setInterval(poll, interval); 48 | } 49 | 50 | this.subscription = forward(operation).subscribe(subscriber); 51 | 52 | return () => { 53 | if (this.timer) { 54 | clearInterval(this.timer); 55 | } 56 | this.subscription.unsubscribe(); 57 | }; 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/apollo-link-polling/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 2.2.6 11 | 12 | - No changes 13 | 14 | ### 2.2.5 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 2.2.4 19 | - Minor documentation fixes 20 | 21 | ### 2.2.3 22 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 23 | 24 | ### 2.2.2 25 | - Fix a bug where `observer` is null during onComplete, onNext, onError [#528](https://github.com/apollographql/apollo-link/pull/528) 26 | 27 | ### 2.2.0 28 | - handle Promises from `retryIf` and `attempts` [#436](https://github.com/apollographql/apollo-link/pull/436) 29 | - udate apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 30 | 31 | ### 2.1.0 32 | - add `retryIf` [PR#324](https://github.com/apollographql/apollo-link/pull/324) 33 | 34 | ### 2.0.2 35 | - ApolloLink upgrade 36 | 37 | ### 2.0.1 38 | - ApolloLink upgrade 39 | 40 | ### 2.0.0 41 | - Entirely rewritten to address a number of flaws including a new API to prevent DOSing your own server when it may be down. Thanks @nevir for the amazing work! 42 | 43 | ### 1.0.2 44 | - changed peer-dependency of apollo-link to actual dependency 45 | 46 | ### 1.0.1 47 | - moved to better rollup build 48 | 49 | ### 1.0.0 50 | - moved from default export to named to be consistent with rest of link ecosystem 51 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-retry 3 | description: Attempt an operation multiple times if it fails due to network or server errors. 4 | --- 5 | 6 | Sometimes, you're in an unreliable situation but you would rather wait longer than explicitly fail an operation. `apollo-link-retry` provides exponential backoff, and jitters delays between attempts by default. It does not (currently) handle retries for GraphQL errors in the response, only for network errors. 7 | 8 | One such use case is to hold on to a request while a network connection is offline and retry until it comes back online. 9 | 10 | ```js 11 | import { RetryLink } from "apollo-link-retry"; 12 | 13 | const link = new RetryLink(); 14 | ``` 15 | 16 | ## Options 17 | 18 | The standard retry strategy provides exponential backoff with jittering, and takes the following options, grouped into `delay` and `attempt` strategies: 19 | 20 | ### options.delay 21 | 22 | * `delay.initial`: The number of milliseconds to wait before attempting the first retry. 23 | 24 | * `delay.max`: The maximum number of milliseconds that the link should wait for any retry. 25 | 26 | * `delay.jitter`: Whether delays between attempts should be randomized. 27 | 28 | ### options.attempts 29 | 30 | * `attempts.max`: The max number of times to try a single operation before giving up. 31 | 32 | * `attempts.retryIf`: A predicate function that can determine whether a particular response should be retried. 33 | 34 | ### Default configuration 35 | 36 | The default configuration is equivalent to: 37 | 38 | ```ts 39 | new RetryLink({ 40 | delay: { 41 | initial: 300, 42 | max: Infinity, 43 | jitter: true 44 | }, 45 | attempts: { 46 | max: 5, 47 | retryIf: (error, _operation) => !!error 48 | } 49 | }); 50 | ``` 51 | 52 | ## Avoiding thundering herd 53 | 54 | Starting with `initialDelay`, the delay of each subsequent retry is increased exponentially, meaning it's multiplied by 2 each time. For example, if `initialDelay` is 100, additional retries will occur after delays of 200, 400, 800, etc. 55 | 56 | With the `jitter` option enabled, delays are randomized anywhere between 0ms (instant), and 2x the configured delay. This way you get the same result on average, but with random delays. 57 | 58 | These two features combined help alleviate [the thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem), by distributing load during major outages. Without these strategies, when your server comes back up it will be hit by all of your clients at once, possibly causing it to go down again. 59 | 60 | ## Custom Strategies 61 | 62 | Instead of the options object, you may pass a function for `delay` and/or `attempts`, which implement custom strategies for each. In both cases the function is given the same arguments (`count`, `operation`, `error`). 63 | 64 | The `attempts` function should return a boolean indicating whether the response should be retried. If yes, the `delay` function is then called, and should return the number of milliseconds to delay by. 65 | 66 | ```js 67 | import { RetryLink } from "apollo-link-retry"; 68 | 69 | const link = new RetryLink({ 70 | attempts: (count, operation, error) => { 71 | return !!error && operation.operationName != 'specialCase'; 72 | }, 73 | delay: (count, operation, error) => { 74 | return count * 1000 * Math.random(); 75 | }, 76 | }); 77 | ``` 78 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-retry", 3 | "version": "2.2.16", 4 | "description": "Retry Apollo Link for GraphQL Network Stack", 5 | "author": "Evans Hauser ", 6 | "contributors": [ 7 | "James Baxley ", 8 | "Jonas Helfer ", 9 | "jon wong ", 10 | "Sashko Stubailo " 11 | ], 12 | "license": "MIT", 13 | "main": "./lib/index.js", 14 | "module": "./lib/bundle.esm.js", 15 | "typings": "./lib/index.d.ts", 16 | "sideEffects": false, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/apollographql/apollo-link.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/apollographql/apollo-link/issues" 23 | }, 24 | "homepage": "https://github.com/apollographql/apollo-link#readme", 25 | "scripts": { 26 | "build": "tsc && rollup -c", 27 | "clean": "rimraf lib/* && rimraf coverage/*", 28 | "coverage": "jest --coverage", 29 | "filesize": "../../scripts/minify", 30 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 31 | "prebuild": "npm run clean", 32 | "prepare": "npm run build", 33 | "test": "npm run lint && jest", 34 | "watch": "tsc -w -p . & rollup -c -w" 35 | }, 36 | "dependencies": { 37 | "@types/zen-observable": "0.8.0", 38 | "apollo-link": "file:../apollo-link", 39 | "tslib": "^1.9.3" 40 | }, 41 | "devDependencies": { 42 | "@types/graphql": "14.2.3", 43 | "@types/jest": "24.9.1", 44 | "graphql": "15.3.0", 45 | "graphql-tag": "2.10.1", 46 | "jest": "24.9.0", 47 | "rimraf": "2.7.1", 48 | "rollup": "1.32.1", 49 | "ts-jest": "22.4.6", 50 | "tslint": "5.20.1", 51 | "typescript": "3.0.3", 52 | "wait-for-observables": "1.0.3" 53 | }, 54 | "jest": { 55 | "transform": { 56 | ".(ts|tsx)": "ts-jest" 57 | }, 58 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 59 | "moduleFileExtensions": [ 60 | "ts", 61 | "tsx", 62 | "js", 63 | "json" 64 | ], 65 | "testURL": "http://localhost" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('retry'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/src/__tests__/delayFunction.ts: -------------------------------------------------------------------------------- 1 | import { buildDelayFunction } from '../delayFunction'; 2 | 3 | describe('buildDelayFunction', () => { 4 | // For easy testing of just the delay component, which is all we care about in 5 | // the default implementation. 6 | interface SimpleDelayFunction { 7 | (count: number): number; 8 | } 9 | 10 | function delayRange(delayFunction: SimpleDelayFunction, count: number) { 11 | const results = []; 12 | for (let i = 1; i <= count; i++) { 13 | results.push(delayFunction(i)); 14 | } 15 | return results; 16 | } 17 | 18 | describe('without jitter', () => { 19 | it('grows exponentially up to maxDelay', () => { 20 | const delayFunction = buildDelayFunction({ 21 | jitter: false, 22 | initial: 100, 23 | max: 1000, 24 | }) as SimpleDelayFunction; 25 | 26 | expect(delayRange(delayFunction, 6)).toEqual([ 27 | 100, 28 | 200, 29 | 400, 30 | 800, 31 | 1000, 32 | 1000, 33 | ]); 34 | }); 35 | }); 36 | 37 | describe('with jitter', () => { 38 | let mockRandom, origRandom; 39 | beforeEach(() => { 40 | mockRandom = jest.fn(); 41 | origRandom = Math.random; 42 | Math.random = mockRandom; 43 | }); 44 | 45 | afterEach(() => { 46 | Math.random = origRandom; 47 | }); 48 | 49 | it('jitters, on average, exponentially up to maxDelay', () => { 50 | const delayFunction = buildDelayFunction({ 51 | jitter: true, 52 | initial: 100, 53 | max: 1000, 54 | }) as SimpleDelayFunction; 55 | 56 | mockRandom.mockReturnValue(0.5); 57 | expect(delayRange(delayFunction, 5)).toEqual([100, 200, 400, 500, 500]); 58 | }); 59 | 60 | it('can have instant retries as the low end of the jitter range', () => { 61 | const delayFunction = buildDelayFunction({ 62 | jitter: true, 63 | initial: 100, 64 | max: 1000, 65 | }) as SimpleDelayFunction; 66 | 67 | mockRandom.mockReturnValue(0); 68 | expect(delayRange(delayFunction, 5)).toEqual([0, 0, 0, 0, 0]); 69 | }); 70 | 71 | it('uses double the calculated delay as the high end of the jitter range, up to maxDelay', () => { 72 | const delayFunction = buildDelayFunction({ 73 | jitter: true, 74 | initial: 100, 75 | max: 1000, 76 | }) as SimpleDelayFunction; 77 | 78 | mockRandom.mockReturnValue(1); 79 | expect(delayRange(delayFunction, 5)).toEqual([200, 400, 800, 1000, 1000]); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/src/__tests__/retryFunction.ts: -------------------------------------------------------------------------------- 1 | import { buildRetryFunction, RetryFunction } from '../retryFunction'; 2 | import { Operation } from 'apollo-link'; 3 | 4 | describe('buildRetryFunction', () => { 5 | it('stops after hitting maxTries', () => { 6 | const retryFunction = buildRetryFunction({ max: 3 }); 7 | 8 | expect(retryFunction(2, null, {})).toEqual(true); 9 | expect(retryFunction(3, null, {})).toEqual(false); 10 | expect(retryFunction(4, null, {})).toEqual(false); 11 | }); 12 | 13 | it('skips retries if there was no error, by default', () => { 14 | const retryFunction = buildRetryFunction(); 15 | 16 | expect(retryFunction(1, null, undefined)).toEqual(false); 17 | expect(retryFunction(1, null, {})).toEqual(true); 18 | }); 19 | 20 | it('supports custom predicates, but only if max is not exceeded', () => { 21 | const stub = jest.fn(() => true); 22 | const retryFunction = buildRetryFunction({ max: 3, retryIf: stub }); 23 | 24 | expect(retryFunction(2, null, null)).toEqual(true); 25 | expect(retryFunction(3, null, null)).toEqual(false); 26 | }); 27 | 28 | it('passes the error and operation through to custom predicates', () => { 29 | const stub = jest.fn(() => true); 30 | const retryFunction = buildRetryFunction({ max: 3, retryIf: stub }); 31 | 32 | const operation = { operationName: 'foo' } as Operation; 33 | const error = { message: 'bewm' }; 34 | retryFunction(1, operation, error); 35 | expect(stub).toHaveBeenCalledWith(error, operation); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/src/delayFunction.ts: -------------------------------------------------------------------------------- 1 | import { Operation } from 'apollo-link'; 2 | 3 | /** 4 | * Advanced mode: a function that implements the strategy for calculating delays 5 | * for particular responses. 6 | */ 7 | export interface DelayFunction { 8 | (count: number, operation: Operation, error: any): number; 9 | } 10 | 11 | export interface DelayFunctionOptions { 12 | /** 13 | * The number of milliseconds to wait before attempting the first retry. 14 | * 15 | * Delays will increase exponentially for each attempt. E.g. if this is 16 | * set to 100, subsequent retries will be delayed by 200, 400, 800, etc, 17 | * until they reach maxDelay. 18 | * 19 | * Note that if jittering is enabled, this is the _average_ delay. 20 | * 21 | * Defaults to 300. 22 | */ 23 | initial?: number; 24 | 25 | /** 26 | * The maximum number of milliseconds that the link should wait for any 27 | * retry. 28 | * 29 | * Defaults to Infinity. 30 | */ 31 | max?: number; 32 | 33 | /** 34 | * Whether delays between attempts should be randomized. 35 | * 36 | * This helps avoid thundering herd type situations by better distributing 37 | * load during major outages. 38 | * 39 | * Defaults to true. 40 | */ 41 | jitter?: boolean; 42 | } 43 | 44 | export function buildDelayFunction( 45 | delayOptions?: DelayFunctionOptions, 46 | ): DelayFunction { 47 | const { initial = 300, jitter = true, max = Infinity } = delayOptions || {}; 48 | // If we're jittering, baseDelay is half of the maximum delay for that 49 | // attempt (and is, on average, the delay we will encounter). 50 | // If we're not jittering, adjust baseDelay so that the first attempt 51 | // lines up with initialDelay, for everyone's sanity. 52 | const baseDelay = jitter ? initial : initial / 2; 53 | 54 | return function delayFunction(count: number) { 55 | let delay = Math.min(max, baseDelay * 2 ** count); 56 | if (jitter) { 57 | // We opt for a full jitter approach for a mostly uniform distribution, 58 | // but bound it within initialDelay and delay for everyone's sanity. 59 | delay = Math.random() * delay; 60 | } 61 | 62 | return delay; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './retryLink'; 2 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/src/retryFunction.ts: -------------------------------------------------------------------------------- 1 | import { Operation } from 'apollo-link'; 2 | 3 | /** 4 | * Advanced mode: a function that determines both whether a particular 5 | * response should be retried. 6 | */ 7 | export interface RetryFunction { 8 | (count: number, operation: Operation, error: any): boolean | Promise; 9 | } 10 | 11 | export interface RetryFunctionOptions { 12 | /** 13 | * The max number of times to try a single operation before giving up. 14 | * 15 | * Note that this INCLUDES the initial request as part of the count. 16 | * E.g. maxTries of 1 indicates no retrying should occur. 17 | * 18 | * Defaults to 5. Pass Infinity for infinite retries. 19 | */ 20 | max?: number; 21 | 22 | /** 23 | * Predicate function that determines whether a particular error should 24 | * trigger a retry. 25 | * 26 | * For example, you may want to not retry 4xx class HTTP errors. 27 | * 28 | * By default, all errors are retried. 29 | */ 30 | retryIf?: (error: any, operation: Operation) => boolean | Promise; 31 | } 32 | 33 | export function buildRetryFunction( 34 | retryOptions?: RetryFunctionOptions, 35 | ): RetryFunction { 36 | const { retryIf, max = 5 } = retryOptions || ({} as RetryFunctionOptions); 37 | return function retryFunction(count, operation, error) { 38 | if (count >= max) return false; 39 | return retryIf ? retryIf(error, operation) : !!error; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/src/retryLink.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloLink, 3 | Observable, 4 | Operation, 5 | NextLink, 6 | FetchResult, 7 | } from 'apollo-link'; 8 | 9 | import { 10 | DelayFunction, 11 | DelayFunctionOptions, 12 | buildDelayFunction, 13 | } from './delayFunction'; 14 | import { 15 | RetryFunction, 16 | RetryFunctionOptions, 17 | buildRetryFunction, 18 | } from './retryFunction'; 19 | 20 | export namespace RetryLink { 21 | export interface Options { 22 | /** 23 | * Configuration for the delay strategy to use, or a custom delay strategy. 24 | */ 25 | delay?: DelayFunctionOptions | DelayFunction; 26 | 27 | /** 28 | * Configuration for the retry strategy to use, or a custom retry strategy. 29 | */ 30 | attempts?: RetryFunctionOptions | RetryFunction; 31 | } 32 | } 33 | 34 | /** 35 | * Tracking and management of operations that may be (or currently are) retried. 36 | */ 37 | class RetryableOperation { 38 | private retryCount: number = 0; 39 | private values: any[] = []; 40 | private error: any; 41 | private complete = false; 42 | private canceled = false; 43 | private observers: ZenObservable.Observer[] = []; 44 | private currentSubscription: ZenObservable.Subscription = null; 45 | private timerId: number; 46 | 47 | constructor( 48 | private operation: Operation, 49 | private nextLink: NextLink, 50 | private delayFor: DelayFunction, 51 | private retryIf: RetryFunction, 52 | ) {} 53 | 54 | /** 55 | * Register a new observer for this operation. 56 | * 57 | * If the operation has previously emitted other events, they will be 58 | * immediately triggered for the observer. 59 | */ 60 | public subscribe(observer: ZenObservable.Observer) { 61 | if (this.canceled) { 62 | throw new Error( 63 | `Subscribing to a retryable link that was canceled is not supported`, 64 | ); 65 | } 66 | this.observers.push(observer); 67 | 68 | // If we've already begun, catch this observer up. 69 | for (const value of this.values) { 70 | observer.next(value); 71 | } 72 | 73 | if (this.complete) { 74 | observer.complete(); 75 | } else if (this.error) { 76 | observer.error(this.error); 77 | } 78 | } 79 | 80 | /** 81 | * Remove a previously registered observer from this operation. 82 | * 83 | * If no observers remain, the operation will stop retrying, and unsubscribe 84 | * from its downstream link. 85 | */ 86 | public unsubscribe(observer: ZenObservable.Observer) { 87 | const index = this.observers.indexOf(observer); 88 | if (index < 0) { 89 | throw new Error( 90 | `RetryLink BUG! Attempting to unsubscribe unknown observer!`, 91 | ); 92 | } 93 | // Note that we are careful not to change the order of length of the array, 94 | // as we are often mid-iteration when calling this method. 95 | this.observers[index] = null; 96 | 97 | // If this is the last observer, we're done. 98 | if (this.observers.every(o => o === null)) { 99 | this.cancel(); 100 | } 101 | } 102 | 103 | /** 104 | * Start the initial request. 105 | */ 106 | public start() { 107 | if (this.currentSubscription) return; // Already started. 108 | 109 | this.try(); 110 | } 111 | 112 | /** 113 | * Stop retrying for the operation, and cancel any in-progress requests. 114 | */ 115 | public cancel() { 116 | if (this.currentSubscription) { 117 | this.currentSubscription.unsubscribe(); 118 | } 119 | clearTimeout(this.timerId); 120 | this.timerId = null; 121 | this.currentSubscription = null; 122 | this.canceled = true; 123 | } 124 | 125 | private try() { 126 | this.currentSubscription = this.nextLink(this.operation).subscribe({ 127 | next: this.onNext, 128 | error: this.onError, 129 | complete: this.onComplete, 130 | }); 131 | } 132 | 133 | private onNext = (value: any) => { 134 | this.values.push(value); 135 | for (const observer of this.observers) { 136 | if (!observer) continue; 137 | observer.next(value); 138 | } 139 | }; 140 | 141 | private onComplete = () => { 142 | this.complete = true; 143 | for (const observer of this.observers) { 144 | if (!observer) continue; 145 | observer.complete(); 146 | } 147 | }; 148 | 149 | private onError = async error => { 150 | this.retryCount += 1; 151 | 152 | // Should we retry? 153 | const shouldRetry = await this.retryIf( 154 | this.retryCount, 155 | this.operation, 156 | error, 157 | ); 158 | if (shouldRetry) { 159 | this.scheduleRetry(this.delayFor(this.retryCount, this.operation, error)); 160 | return; 161 | } 162 | 163 | this.error = error; 164 | for (const observer of this.observers) { 165 | if (!observer) continue; 166 | observer.error(error); 167 | } 168 | }; 169 | 170 | private scheduleRetry(delay) { 171 | if (this.timerId) { 172 | throw new Error(`RetryLink BUG! Encountered overlapping retries`); 173 | } 174 | 175 | this.timerId = setTimeout(() => { 176 | this.timerId = null; 177 | this.try(); 178 | }, delay); 179 | } 180 | } 181 | 182 | export class RetryLink extends ApolloLink { 183 | private delayFor: DelayFunction; 184 | private retryIf: RetryFunction; 185 | 186 | constructor(options?: RetryLink.Options) { 187 | super(); 188 | const { attempts, delay } = options || ({} as RetryLink.Options); 189 | this.delayFor = 190 | typeof delay === 'function' ? delay : buildDelayFunction(delay); 191 | this.retryIf = 192 | typeof attempts === 'function' ? attempts : buildRetryFunction(attempts); 193 | } 194 | 195 | public request( 196 | operation: Operation, 197 | nextLink: NextLink, 198 | ): Observable { 199 | const retryable = new RetryableOperation( 200 | operation, 201 | nextLink, 202 | this.delayFor, 203 | this.retryIf, 204 | ); 205 | retryable.start(); 206 | 207 | return new Observable(observer => { 208 | retryable.subscribe(observer); 209 | return () => { 210 | retryable.unsubscribe(observer); 211 | }; 212 | }); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /packages/apollo-link-retry/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.1.2 11 | 12 | - No changes 13 | 14 | ### 1.1.1 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 1.1.0 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | - allow context to be a function that accept an operation and returns a context for the resolvers [PR#363](https://github.com/apollographql/apollo-link/pull/363) 21 | 22 | ### 1.0.6 23 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 24 | 25 | ### 1.0.5 26 | - Include sourcemap in bundle 27 | 28 | ### 1.0.4 29 | - ApolloLink upgrade 30 | 31 | ### 1.0.3 32 | - ApolloLink upgrade 33 | 34 | ### 1.0.2 35 | - handle synchronous result of `execute` **Fixes #351** 36 | 37 | ### 1.0.1 38 | - export options as named interface [TypeScript] 39 | 40 | ### 1.0.0 41 | - support mocking 42 | - support server rendering 43 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-schema 3 | description: Assists with mocking and server-side rendering 4 | --- 5 | 6 | The schema link provides a [graphql execution environment](http://graphql.org/graphql-js/graphql/#graphql), which allows you to perform GraphQL operations on a provided schema. This type of behavior is commonly used for server-side rendering (SSR) to avoid network calls and mocking data. While the schema link could provide graphql results on the client, currently the graphql execution layer is [too heavy weight](https://bundlephobia.com/result?p=graphql) for practical application. To unify your state management with client-side GraphQL operations, you should use [apollo-link-state](state), because it integrates with the Apollo Client cache and is much more lightweight. 7 | 8 | ## Installation 9 | 10 | `npm install apollo-link-schema --save` 11 | 12 | ## Usage 13 | 14 | ### Server Side Rendering 15 | 16 | When performing SSR _on the same server_ you can use this library to avoid making network calls. 17 | 18 | ```js 19 | import { ApolloClient } from 'apollo-client'; 20 | import { InMemoryCache } from 'apollo-cache-inmemory'; 21 | import { SchemaLink } from 'apollo-link-schema'; 22 | 23 | import schema from './path/to/your/schema'; 24 | 25 | const graphqlClient = new ApolloClient({ 26 | ssrMode: true, 27 | cache: new InMemoryCache(), 28 | link: new SchemaLink({ schema }) 29 | }); 30 | ``` 31 | 32 | ### Mocking 33 | 34 | For more detailed information about mocking, please look the [graphql-tools documentation](https://www.apollographql.com/docs/graphql-tools/mocking.html). 35 | 36 | ```js 37 | import { ApolloClient } from 'apollo-client'; 38 | import { InMemoryCache } from 'apollo-cache-inmemory'; 39 | import { SchemaLink } from 'apollo-link-schema'; 40 | import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools'; 41 | 42 | const typeDefs = ` 43 | Query { 44 | ... 45 | } 46 | `; 47 | 48 | const mocks = { 49 | Query: () => ..., 50 | Mutation: () => ... 51 | }; 52 | 53 | const schema = makeExecutableSchema({ typeDefs }); 54 | addMockFunctionsToSchema({ 55 | schema, 56 | mocks 57 | }); 58 | 59 | const apolloCache = new InMemoryCache(window.__APOLLO_STATE__); 60 | 61 | const graphqlClient = new ApolloClient({ 62 | cache: apolloCache, 63 | link: new SchemaLink({ schema }) 64 | }); 65 | ``` 66 | 67 | ### Options 68 | 69 | The `SchemaLink` constructor can be called with an object with the following properties: 70 | 71 | * `schema`: an executable graphql schema 72 | * `rootValue`: the root value that is passed to the resolvers (i.e. the first parameter for the [rootQuery](http://graphql.org/learn/execution/#root-fields-resolvers)) 73 | * `context`: an object passed to the resolvers, following the [graphql specification](http://graphql.org/learn/execution/#root-fields-resolvers) or a function that accepts the operation and returns the resolver context. The resolver context may contain all the data-fetching connectors for an operation. 74 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-schema", 3 | "version": "1.2.5", 4 | "description": "Use a GraphQL Schema to request data", 5 | "author": "Tomas Trescak ", 6 | "contributors": [ 7 | "Evans Hauser ", 8 | "James Baxley ", 9 | "Jonas Helfer ", 10 | "jon wong ", 11 | "Sashko Stubailo ", 12 | "Zephraph " 13 | ], 14 | "license": "MIT", 15 | "main": "./lib/index.js", 16 | "module": "./lib/bundle.esm.js", 17 | "typings": "./lib/index.d.ts", 18 | "sideEffects": false, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/apollographql/apollo-link.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/apollographql/apollo-link/issues" 25 | }, 26 | "homepage": "https://github.com/apollographql/apollo-link#readme", 27 | "scripts": { 28 | "build": "tsc && rollup -c", 29 | "clean": "rimraf lib/* && rimraf coverage/*", 30 | "coverage": "jest --coverage", 31 | "filesize": "../../scripts/minify", 32 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 33 | "prebuild": "npm run clean", 34 | "prepare": "npm run build", 35 | "test": "npm run lint && jest", 36 | "watch": "tsc -w -p . & rollup -c -w" 37 | }, 38 | "dependencies": { 39 | "apollo-link": "file:../apollo-link", 40 | "tslib": "^1.9.3" 41 | }, 42 | "peerDependencies": { 43 | "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" 44 | }, 45 | "devDependencies": { 46 | "@types/graphql": "14.2.3", 47 | "@types/jest": "24.9.1", 48 | "graphql": "15.3.0", 49 | "graphql-tag": "2.10.1", 50 | "graphql-tools": "4.0.8", 51 | "jest": "24.9.0", 52 | "rimraf": "2.7.1", 53 | "rollup": "1.32.1", 54 | "ts-jest": "22.4.6", 55 | "tslint": "5.20.1", 56 | "typescript": "3.0.3" 57 | }, 58 | "jest": { 59 | "transform": { 60 | ".(ts|tsx)": "ts-jest" 61 | }, 62 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 63 | "moduleFileExtensions": [ 64 | "ts", 65 | "tsx", 66 | "js", 67 | "json" 68 | ], 69 | "testURL": "http://localhost" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('schema'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/src/__tests__/schemaLink.ts: -------------------------------------------------------------------------------- 1 | import { execute } from 'apollo-link'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 3 | import gql from 'graphql-tag'; 4 | 5 | import { SchemaLink } from '../'; 6 | 7 | const sampleQuery = gql` 8 | query SampleQuery { 9 | sampleQuery { 10 | id 11 | } 12 | } 13 | `; 14 | 15 | const sampleMutation = gql` 16 | mutation SampleMutation { 17 | stub(param: "value") { 18 | id 19 | } 20 | } 21 | `; 22 | 23 | const typeDefs = ` 24 | type Stub { 25 | id: String 26 | } 27 | 28 | type Query { 29 | sampleQuery: Stub 30 | } 31 | `; 32 | 33 | const schema = makeExecutableSchema({ typeDefs }); 34 | 35 | describe('SchemaLink', () => { 36 | const data = { data: { hello: 'world' } }; 37 | const data2 = { data: { hello: 'everyone' } }; 38 | const mockError = { throws: new TypeError('mock me') }; 39 | 40 | let subscriber; 41 | 42 | beforeEach(() => { 43 | const next = jest.fn(); 44 | const error = jest.fn(); 45 | const complete = jest.fn(); 46 | 47 | subscriber = { 48 | next, 49 | error, 50 | complete, 51 | }; 52 | }); 53 | 54 | it('raises warning if called with concat', () => { 55 | const link = new SchemaLink({ schema }); 56 | const _warn = console.warn; 57 | console.warn = warning => expect(warning['message']).toBeDefined(); 58 | expect(link.concat((operation, forward) => forward(operation))).toEqual( 59 | link, 60 | ); 61 | console.warn = _warn; 62 | }); 63 | 64 | it('throws if no arguments given', () => { 65 | expect(() => new (SchemaLink as any)()).toThrow(); 66 | }); 67 | 68 | it('correctly receives the constructor arguments', () => { 69 | let rootValue = {}; 70 | let link = new SchemaLink({ schema, rootValue }); 71 | expect(link.rootValue).toEqual(rootValue); 72 | expect(link.schema).toEqual(schema); 73 | }); 74 | 75 | it('calls next and then complete', done => { 76 | const next = jest.fn(); 77 | const link = new SchemaLink({ schema }); 78 | const observable = execute(link, { 79 | query: sampleQuery, 80 | }); 81 | observable.subscribe({ 82 | next, 83 | error: error => expect(false), 84 | complete: () => { 85 | expect(next).toHaveBeenCalledTimes(1); 86 | done(); 87 | }, 88 | }); 89 | }); 90 | 91 | it('calls error when fetch fails', done => { 92 | const badTypeDefs = 'type Query {}'; 93 | const badSchema = makeExecutableSchema({ typeDefs }); 94 | 95 | const link = new SchemaLink({ schema: badSchema }); 96 | const observable = execute(link, { 97 | query: sampleQuery, 98 | }); 99 | observable.subscribe( 100 | result => expect(false), 101 | error => { 102 | expect(error).toEqual(mockError.throws); 103 | done(); 104 | }, 105 | () => { 106 | expect(false); 107 | done(); 108 | }, 109 | ); 110 | }); 111 | 112 | it('supports query which is executed synchronously', done => { 113 | const next = jest.fn(); 114 | const link = new SchemaLink({ schema }); 115 | const introspectionQuery = gql` 116 | query IntrospectionQuery { 117 | __schema { 118 | types { 119 | name 120 | } 121 | } 122 | } 123 | `; 124 | const observable = execute(link, { 125 | query: introspectionQuery, 126 | }); 127 | observable.subscribe( 128 | next, 129 | error => expect(false), 130 | () => { 131 | expect(next).toHaveBeenCalledTimes(1); 132 | done(); 133 | }, 134 | ); 135 | }); 136 | 137 | it('passes operation context into execute with context function', done => { 138 | const next = jest.fn(); 139 | const contextValue = { some: 'value' }; 140 | const contextProvider = jest.fn(operation => operation.getContext()); 141 | const resolvers = { 142 | Query: { 143 | sampleQuery: (root, args, context) => { 144 | try { 145 | expect(context).toEqual(contextValue); 146 | } catch (error) { 147 | done.fail('Should pass context into resolver'); 148 | } 149 | }, 150 | }, 151 | }; 152 | const schemaWithResolvers = makeExecutableSchema({ 153 | typeDefs, 154 | resolvers, 155 | }); 156 | const link = new SchemaLink({ 157 | schema: schemaWithResolvers, 158 | context: contextProvider, 159 | }); 160 | const observable = execute(link, { 161 | query: sampleQuery, 162 | context: contextValue, 163 | }); 164 | observable.subscribe( 165 | next, 166 | error => done.fail("Shouldn't call onError"), 167 | () => { 168 | try { 169 | expect(next).toHaveBeenCalledTimes(1); 170 | expect(contextProvider).toHaveBeenCalledTimes(1); 171 | done(); 172 | } catch (e) { 173 | done.fail(e); 174 | } 175 | }, 176 | ); 177 | }); 178 | 179 | it('passes static context into execute', done => { 180 | const next = jest.fn(); 181 | const contextValue = { some: 'value' }; 182 | const resolver = jest.fn((root, args, context) => { 183 | try { 184 | expect(context).toEqual(contextValue); 185 | } catch (error) { 186 | done.fail('Should pass context into resolver'); 187 | } 188 | }); 189 | 190 | const resolvers = { 191 | Query: { 192 | sampleQuery: resolver, 193 | }, 194 | }; 195 | const schemaWithResolvers = makeExecutableSchema({ 196 | typeDefs, 197 | resolvers, 198 | }); 199 | const link = new SchemaLink({ 200 | schema: schemaWithResolvers, 201 | context: contextValue, 202 | }); 203 | const observable = execute(link, { 204 | query: sampleQuery, 205 | }); 206 | observable.subscribe( 207 | next, 208 | error => done.fail("Shouldn't call onError"), 209 | () => { 210 | try { 211 | expect(next).toHaveBeenCalledTimes(1); 212 | expect(resolver).toHaveBeenCalledTimes(1); 213 | done(); 214 | } catch (e) { 215 | done.fail(e); 216 | } 217 | }, 218 | ); 219 | }); 220 | }); 221 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink, Operation, FetchResult, Observable } from 'apollo-link'; 2 | import { execute } from 'graphql/execution/execute'; 3 | import { GraphQLSchema } from 'graphql'; 4 | 5 | export namespace SchemaLink { 6 | export type ResolverContextFunction = ( 7 | operation: Operation, 8 | ) => Record; 9 | 10 | export interface Options { 11 | /** 12 | * The schema to generate responses from. 13 | */ 14 | schema: GraphQLSchema; 15 | 16 | /** 17 | * The root value to use when generating responses. 18 | */ 19 | rootValue?: any; 20 | 21 | /** 22 | * A context to provide to resolvers declared within the schema. 23 | */ 24 | context?: ResolverContextFunction | Record; 25 | } 26 | } 27 | 28 | export class SchemaLink extends ApolloLink { 29 | public schema: GraphQLSchema; 30 | public rootValue: any; 31 | public context: SchemaLink.ResolverContextFunction | any; 32 | 33 | constructor({ schema, rootValue, context }: SchemaLink.Options) { 34 | super(); 35 | 36 | this.schema = schema; 37 | this.rootValue = rootValue; 38 | this.context = context; 39 | } 40 | 41 | public request(operation: Operation): Observable | null { 42 | return new Observable(observer => { 43 | Promise.resolve( 44 | execute( 45 | this.schema, 46 | operation.query, 47 | this.rootValue, 48 | typeof this.context === 'function' 49 | ? this.context(operation) 50 | : this.context, 51 | operation.variables, 52 | operation.operationName, 53 | ), 54 | ) 55 | .then(data => { 56 | if (!observer.closed) { 57 | observer.next(data); 58 | observer.complete(); 59 | } 60 | }) 61 | .catch(error => { 62 | if (!observer.closed) { 63 | observer.error(error); 64 | } 65 | }); 66 | }); 67 | } 68 | } 69 | 70 | export default SchemaLink; 71 | -------------------------------------------------------------------------------- /packages/apollo-link-schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.0.10 11 | 12 | - No changes 13 | 14 | ### 1.0.9 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 1.0.8 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | 21 | ### 1.0.7 22 | - update apollo link with zen-observable-ts [PR#515](https://github.com/apollographql/apollo-link/pull/515) 23 | 24 | ### 1.0.6 25 | - ApolloLink upgrade 26 | 27 | ### 1.0.5 28 | - ApolloLink upgrade 29 | 30 | ### 1.0.4 31 | - ApolloLink upgrade 32 | 33 | ### 1.0.3 34 | - export options as named interface [TypeScript] 35 | 36 | ### 1.0.2 37 | - changed peer-dependency of apollo-link to actual dependency 38 | 39 | ### 1.0.1 40 | - moved to better rollup build 41 | 42 | ### 1.0.0 43 | - moved from default export to named to be consistent with rest of link ecosystem 44 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: apollo-link-ws 3 | description: Send GraphQL operations over a WebSocket. Works with GraphQL Subscriptions. 4 | --- 5 | 6 | This link is particularly useful to use GraphQL Subscriptions, but it will also allow you to send GraphQL queries and mutations over WebSockets as well. 7 | 8 | ```js 9 | import { WebSocketLink } from "apollo-link-ws"; 10 | import { SubscriptionClient } from "subscriptions-transport-ws"; 11 | 12 | const GRAPHQL_ENDPOINT = "ws://localhost:3000/graphql"; 13 | 14 | const client = new SubscriptionClient(GRAPHQL_ENDPOINT, { 15 | reconnect: true 16 | }); 17 | 18 | const link = new WebSocketLink(client); 19 | ``` 20 | 21 | ## Options 22 | 23 | WS Link takes either a subscription client or an object with three options on it to customize the behavior of the link. Takes the following possible keys in the configuration object: 24 | 25 | * `uri`: a string endpoint to connect to 26 | * `options`: a set of options to pass to a new Subscription Client 27 | * `webSocketImpl`: a custom WebSocket implementation 28 | 29 | By default, this link uses the [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) library for the transport. 30 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link-ws", 3 | "version": "1.0.20", 4 | "description": "WebSocket transport layer for GraphQL", 5 | "author": "Evans Hauser ", 6 | "contributors": [ 7 | "James Baxley ", 8 | "Jonas Helfer ", 9 | "jon wong ", 10 | "Sashko Stubailo " 11 | ], 12 | "license": "MIT", 13 | "main": "./lib/index.js", 14 | "module": "./lib/bundle.esm.js", 15 | "typings": "./lib/index.d.ts", 16 | "sideEffects": false, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/apollographql/apollo-link.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/apollographql/apollo-link/issues" 23 | }, 24 | "homepage": "https://github.com/apollographql/apollo-link#readme", 25 | "scripts": { 26 | "build": "tsc && rollup -c", 27 | "clean": "rimraf lib/* && rimraf coverage/*", 28 | "coverage": "jest --coverage", 29 | "filesize": "../../scripts/minify", 30 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 31 | "prebuild": "npm run clean", 32 | "prepare": "npm run build", 33 | "test": "npm run lint && jest", 34 | "watch": "tsc -w -p . & rollup -c -w" 35 | }, 36 | "dependencies": { 37 | "apollo-link": "file:../apollo-link", 38 | "tslib": "^1.9.3" 39 | }, 40 | "peerDependencies": { 41 | "subscriptions-transport-ws": "^0.9.0" 42 | }, 43 | "devDependencies": { 44 | "@types/graphql": "14.2.3", 45 | "@types/jest": "24.9.1", 46 | "graphql": "15.3.0", 47 | "graphql-tag": "2.10.1", 48 | "jest": "24.9.0", 49 | "rimraf": "2.7.1", 50 | "rollup": "1.32.1", 51 | "subscriptions-transport-ws": "0.9.16", 52 | "ts-jest": "22.4.6", 53 | "tslint": "5.20.1", 54 | "typescript": "3.0.3" 55 | }, 56 | "jest": { 57 | "transform": { 58 | ".(ts|tsx)": "ts-jest" 59 | }, 60 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 61 | "moduleFileExtensions": [ 62 | "ts", 63 | "tsx", 64 | "js", 65 | "json" 66 | ], 67 | "testURL": "http://localhost" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('ws'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/src/__tests__/webSocketLink.ts: -------------------------------------------------------------------------------- 1 | import { SubscriptionClient } from 'subscriptions-transport-ws'; 2 | import { Observable, execute, ExecutionResult } from 'apollo-link'; 3 | 4 | import { WebSocketLink } from '../webSocketLink'; 5 | 6 | const query = ` 7 | query SampleQuery { 8 | stub { 9 | id 10 | } 11 | } 12 | `; 13 | 14 | const mutation = ` 15 | mutation SampleMutation { 16 | stub { 17 | id 18 | } 19 | } 20 | `; 21 | 22 | const subscription = ` 23 | subscription SampleSubscription { 24 | stub { 25 | id 26 | } 27 | } 28 | `; 29 | 30 | describe('WebSocketLink', () => { 31 | it('constructs', () => { 32 | const client: any = {}; 33 | client.__proto__ = SubscriptionClient.prototype; 34 | expect(() => new WebSocketLink(client)).not.toThrow(); 35 | }); 36 | 37 | // TODO some sort of dependency injection 38 | 39 | // it('should pass the correct initialization parameters to the Subscription Client', () => { 40 | // }); 41 | 42 | it('should call request on the client for a query', done => { 43 | const result = { data: { data: 'result' } }; 44 | const client: any = {}; 45 | const observable = Observable.of(result); 46 | client.__proto__ = SubscriptionClient.prototype; 47 | client.request = jest.fn(); 48 | client.request.mockReturnValueOnce(observable); 49 | const link = new WebSocketLink(client); 50 | 51 | const obs = execute(link, { query }); 52 | expect(obs).toEqual(observable); 53 | obs.subscribe(data => { 54 | expect(data).toEqual(result); 55 | expect(client.request).toHaveBeenCalledTimes(1); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('should call query on the client for a mutation', done => { 61 | const result = { data: { data: 'result' } }; 62 | const client: any = {}; 63 | const observable = Observable.of(result); 64 | client.__proto__ = SubscriptionClient.prototype; 65 | client.request = jest.fn(); 66 | client.request.mockReturnValueOnce(observable); 67 | const link = new WebSocketLink(client); 68 | 69 | const obs = execute(link, { query: mutation }); 70 | expect(obs).toEqual(observable); 71 | obs.subscribe(data => { 72 | expect(data).toEqual(result); 73 | expect(client.request).toHaveBeenCalledTimes(1); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('should call request on the subscriptions client for subscription', done => { 79 | const result = { data: { data: 'result' } }; 80 | const client: any = {}; 81 | const observable = Observable.of(result); 82 | client.__proto__ = SubscriptionClient.prototype; 83 | client.request = jest.fn(); 84 | client.request.mockReturnValueOnce(observable); 85 | const link = new WebSocketLink(client); 86 | 87 | const obs = execute(link, { query: mutation }); 88 | expect(obs).toEqual(observable); 89 | obs.subscribe(data => { 90 | expect(data).toEqual(result); 91 | expect(client.request).toHaveBeenCalledTimes(1); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('should call next with multiple results for subscription', done => { 97 | const results = [ 98 | { data: { data: 'result1' } }, 99 | { data: { data: 'result2' } }, 100 | ]; 101 | const client: any = {}; 102 | client.__proto__ = SubscriptionClient.prototype; 103 | client.request = jest.fn(() => { 104 | const copy = [...results]; 105 | return new Observable(observer => { 106 | observer.next(copy[0]); 107 | observer.next(copy[1]); 108 | }); 109 | }); 110 | 111 | const link = new WebSocketLink(client); 112 | 113 | execute(link, { query: subscription }).subscribe(data => { 114 | expect(client.request).toHaveBeenCalledTimes(1); 115 | expect(data).toEqual(results.shift()); 116 | if (results.length === 0) { 117 | done(); 118 | } 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './webSocketLink'; 2 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/src/webSocketLink.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink, Operation, FetchResult, Observable } from 'apollo-link'; 2 | 3 | import { SubscriptionClient, ClientOptions } from 'subscriptions-transport-ws'; 4 | 5 | export namespace WebSocketLink { 6 | /** 7 | * Configuration to use when constructing the subscription client (subscriptions-transport-ws). 8 | */ 9 | export interface Configuration { 10 | /** 11 | * The endpoint to connect to. 12 | */ 13 | uri: string; 14 | 15 | /** 16 | * Options to pass when constructing the subscription client. 17 | */ 18 | options?: ClientOptions; 19 | 20 | /** 21 | * A custom WebSocket implementation to use. 22 | */ 23 | webSocketImpl?: any; 24 | } 25 | } 26 | 27 | // For backwards compatibility. 28 | export import WebSocketParams = WebSocketLink.Configuration; 29 | 30 | export class WebSocketLink extends ApolloLink { 31 | private subscriptionClient: SubscriptionClient; 32 | 33 | constructor( 34 | paramsOrClient: WebSocketLink.Configuration | SubscriptionClient, 35 | ) { 36 | super(); 37 | 38 | if (paramsOrClient instanceof SubscriptionClient) { 39 | this.subscriptionClient = paramsOrClient; 40 | } else { 41 | this.subscriptionClient = new SubscriptionClient( 42 | paramsOrClient.uri, 43 | paramsOrClient.options, 44 | paramsOrClient.webSocketImpl, 45 | ); 46 | } 47 | } 48 | 49 | public request(operation: Operation): Observable | null { 50 | return this.subscriptionClient.request(operation) as Observable< 51 | FetchResult 52 | >; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/apollo-link-ws/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/**/__tests__/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/apollo-link/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/apollo-link/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 1.2.4 11 | 12 | - No changes 13 | 14 | ### 1.2.3 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 1.2.2 19 | - Update apollo-link [#559](https://github.com/apollographql/apollo-link/pull/559) 20 | - export graphql types and add @types/graphql as a regular dependency [PR#576](https://github.com/apollographql/apollo-link/pull/576) 21 | - moved @types/node to dev dependencies in package.josn to avoid collisions with other projects. [PR#540](https://github.com/apollographql/apollo-link/pull/540) 22 | 23 | ### 1.2.1 24 | - update apollo link with zen-observable-ts to remove import issues [PR#515](https://github.com/apollographql/apollo-link/pull/515) 25 | 26 | ### 1.2.0 27 | - Add `fromError` Observable helper 28 | - change import method of zen-observable for rollup compat 29 | 30 | ### 1.1.0 31 | - Expose `#execute` on ApolloLink as static 32 | 33 | ### 1.0.7 34 | - Update to graphql@0.12 35 | 36 | ### 1.0.6 37 | - update rollup 38 | 39 | ### 1.0.5 40 | - fix bug where context wasn't merged when setting it 41 | 42 | ### 1.0.4 43 | - export link util helpers 44 | 45 | ### 1.0.3 46 | - removed requiring query on initial execution check 47 | - moved to move efficent rollup build 48 | 49 | ### 1.0.1, 1.0.2 50 | 51 | - preleases for dev tool integation 52 | 53 | ### 0.8.0 54 | - added support for `extensions` on an operation 55 | 56 | ### 0.7.0 57 | - new operation API and start of changelog 58 | -------------------------------------------------------------------------------- /packages/apollo-link/README.md: -------------------------------------------------------------------------------- 1 | # apollo-link 2 | 3 | ## Purpose 4 | 5 | `apollo-link` is a standard interface for modifying control flow of GraphQL requests and fetching GraphQL results, designed to provide a simple GraphQL client that is capable of extensions. 6 | The targeted use cases of `apollo-link` are highlighted below: 7 | 8 | * fetch queries directly without normalized cache 9 | * network interface for Apollo Client 10 | * network interface for Relay Modern 11 | * fetcher for 12 | 13 | Apollo Link is the interface for creating new links in your application. 14 | 15 | The client sends a request as a method call to a link and can recieve one or more (in the case of subscriptions) responses from the server. The responses are returned using the Observer pattern. 16 | 17 | ![Apollo Link Chain](https://cdn-images-1.medium.com/max/1600/1*62VLGUaU-9ULCoBCGvgdkQ.png) 18 | 19 | Results from the server can be provided by calling `next(result)` on the observer. In the case of a network/transport error (not a GraphQL Error) the `error(err)` method can be used to indicate a response will not be recieved. If multiple responses are not supported by the link, `complete()` should be called to inform the client no further data will be provided. 20 | 21 | In the case of an intermediate link, a second argument to `request(operation, forward)` is the link to `forward(operation)` to. `forward` returns an observable and it can be returned directly or subscribed to. 22 | 23 | ```js 24 | forward(operation).subscribe({ 25 | next: result => { 26 | handleTheResult(result) 27 | }, 28 | error: error => { 29 | handleTheNetworkError(error) 30 | }, 31 | }); 32 | ``` 33 | 34 | ## Implementing a basic custom transport 35 | 36 | ```js 37 | import { ApolloLink, Observable } from 'apollo-link'; 38 | 39 | export class CustomApolloLink extends ApolloLink { 40 | request(operation /*, forward*/) { 41 | //Whether no one is listening anymore 42 | let unsubscribed = false; 43 | 44 | return new Observable(observer => { 45 | somehowGetOperationToServer(operation, (error, result) => { 46 | if (unsubscribed) return; 47 | if (error) { 48 | //Network error 49 | observer.error(error); 50 | } else { 51 | observer.next(result); 52 | observer.complete(); //If subscriptions not supported 53 | } 54 | }); 55 | 56 | function unsubscribe() { 57 | unsubscribed = true; 58 | } 59 | 60 | return unsubscribe; 61 | }); 62 | } 63 | } 64 | ``` 65 | 66 | ## Installation 67 | 68 | `npm install apollo-link --save` 69 | 70 | -------------------------------------------------------------------------------- /packages/apollo-link/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-link", 3 | "version": "1.2.14", 4 | "description": "Flexible, lightweight transport layer for GraphQL", 5 | "author": "Evans Hauser ", 6 | "contributors": [ 7 | "James Baxley ", 8 | "Jonas Helfer ", 9 | "jon wong ", 10 | "Sashko Stubailo " 11 | ], 12 | "license": "MIT", 13 | "main": "./lib/index.js", 14 | "module": "./lib/bundle.esm.js", 15 | "typings": "./lib/index.d.ts", 16 | "sideEffects": false, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/apollographql/apollo-link.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/apollographql/apollo-link/issues" 23 | }, 24 | "homepage": "https://github.com/apollographql/apollo-link#readme", 25 | "scripts": { 26 | "build": "tsc && rollup -c", 27 | "clean": "rimraf lib/* && rimraf coverage/*", 28 | "coverage": "jest --coverage", 29 | "filesize": "../../scripts/minify", 30 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 31 | "prebuild": "npm run clean", 32 | "prepare": "npm run build", 33 | "test": "npm run lint && jest", 34 | "watch": "tsc -w -p . & rollup -c -w" 35 | }, 36 | "dependencies": { 37 | "apollo-utilities": "^1.3.0", 38 | "ts-invariant": "^0.4.0", 39 | "tslib": "^1.9.3", 40 | "zen-observable-ts": "file:../zen-observable-ts" 41 | }, 42 | "peerDependencies": { 43 | "graphql": "^0.11.3 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0" 44 | }, 45 | "devDependencies": { 46 | "@types/graphql": "14.2.3", 47 | "@types/jest": "24.9.1", 48 | "@types/node": "9.6.56", 49 | "graphql": "15.3.0", 50 | "graphql-tag": "2.10.1", 51 | "jest": "24.9.0", 52 | "rimraf": "2.7.1", 53 | "rollup": "1.32.1", 54 | "ts-jest": "22.4.6", 55 | "tslint": "5.20.1", 56 | "typescript": "3.0.3" 57 | }, 58 | "jest": { 59 | "transform": { 60 | ".(ts|tsx)": "ts-jest" 61 | }, 62 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 63 | "moduleFileExtensions": [ 64 | "ts", 65 | "tsx", 66 | "js", 67 | "json" 68 | ], 69 | "testURL": "http://localhost" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/apollo-link/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('core'); 4 | -------------------------------------------------------------------------------- /packages/apollo-link/src/__tests__/linkUtils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | validateOperation, 3 | fromPromise, 4 | makePromise, 5 | fromError, 6 | } from '../linkUtils'; 7 | import Observable from 'zen-observable-ts'; 8 | 9 | describe('Link utilities:', () => { 10 | describe('validateOperation', () => { 11 | it('should throw when invalid field in operation', () => { 12 | expect(() => validateOperation({ qwerty: '' })).toThrow(); 13 | }); 14 | 15 | it('should not throw when valid fields in operation', () => { 16 | expect(() => 17 | validateOperation({ 18 | query: '1234', 19 | context: {}, 20 | variables: {}, 21 | }), 22 | ).not.toThrow(); 23 | }); 24 | }); 25 | 26 | describe('makePromise', () => { 27 | const data = { 28 | data: { 29 | hello: 'world', 30 | }, 31 | }; 32 | const error = new Error('I always error'); 33 | 34 | it('return next call as Promise resolution', () => { 35 | return makePromise(Observable.of(data)).then(result => 36 | expect(data).toEqual(result), 37 | ); 38 | }); 39 | 40 | it('return error call as Promise rejection', () => { 41 | return makePromise(fromError(error)) 42 | .then(expect.fail) 43 | .catch(actualError => expect(error).toEqual(actualError)); 44 | }); 45 | 46 | describe('warnings', () => { 47 | const spy = jest.fn(); 48 | let _warn: (message?: any, ...originalParams: any[]) => void; 49 | 50 | beforeEach(() => { 51 | _warn = console.warn; 52 | console.warn = spy; 53 | }); 54 | 55 | afterEach(() => { 56 | console.warn = _warn; 57 | }); 58 | 59 | it('return error call as Promise rejection', done => { 60 | makePromise(Observable.of(data, data)).then(result => { 61 | expect(data).toEqual(result); 62 | expect(spy).toHaveBeenCalled(); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | }); 68 | describe('fromPromise', () => { 69 | const data = { 70 | data: { 71 | hello: 'world', 72 | }, 73 | }; 74 | const error = new Error('I always error'); 75 | 76 | it('return next call as Promise resolution', () => { 77 | const observable = fromPromise(Promise.resolve(data)); 78 | return makePromise(observable).then(result => 79 | expect(data).toEqual(result), 80 | ); 81 | }); 82 | 83 | it('return Promise rejection as error call', () => { 84 | const observable = fromPromise(Promise.reject(error)); 85 | return makePromise(observable) 86 | .then(expect.fail) 87 | .catch(actualError => expect(error).toEqual(actualError)); 88 | }); 89 | }); 90 | describe('fromError', () => { 91 | it('acts as error call', () => { 92 | const error = new Error('I always error'); 93 | const observable = fromError(error); 94 | return makePromise(observable) 95 | .then(expect.fail) 96 | .catch(actualError => expect(error).toEqual(actualError)); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /packages/apollo-link/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './link'; 2 | export { 3 | createOperation, 4 | makePromise, 5 | toPromise, 6 | fromPromise, 7 | fromError, 8 | getOperationName, 9 | } from './linkUtils'; 10 | export * from './types'; 11 | 12 | import Observable from 'zen-observable-ts'; 13 | export { Observable }; 14 | -------------------------------------------------------------------------------- /packages/apollo-link/src/link.ts: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable-ts'; 2 | import { invariant, InvariantError } from 'ts-invariant'; 3 | 4 | import { 5 | GraphQLRequest, 6 | NextLink, 7 | Operation, 8 | RequestHandler, 9 | FetchResult, 10 | } from './types'; 11 | 12 | import { 13 | validateOperation, 14 | isTerminating, 15 | LinkError, 16 | transformOperation, 17 | createOperation, 18 | } from './linkUtils'; 19 | 20 | function passthrough(op, forward) { 21 | return forward ? forward(op) : Observable.of(); 22 | } 23 | 24 | function toLink(handler: RequestHandler | ApolloLink) { 25 | return typeof handler === 'function' ? new ApolloLink(handler) : handler; 26 | } 27 | 28 | export function empty(): ApolloLink { 29 | return new ApolloLink(() => Observable.of()); 30 | } 31 | 32 | export function from(links: ApolloLink[]): ApolloLink { 33 | if (links.length === 0) return empty(); 34 | return links.map(toLink).reduce((x, y) => x.concat(y)); 35 | } 36 | 37 | export function split( 38 | test: (op: Operation) => boolean, 39 | left: ApolloLink | RequestHandler, 40 | right?: ApolloLink | RequestHandler, 41 | ): ApolloLink { 42 | const leftLink = toLink(left); 43 | const rightLink = toLink(right || new ApolloLink(passthrough)); 44 | 45 | if (isTerminating(leftLink) && isTerminating(rightLink)) { 46 | return new ApolloLink(operation => { 47 | return test(operation) 48 | ? leftLink.request(operation) || Observable.of() 49 | : rightLink.request(operation) || Observable.of(); 50 | }); 51 | } else { 52 | return new ApolloLink((operation, forward) => { 53 | return test(operation) 54 | ? leftLink.request(operation, forward) || Observable.of() 55 | : rightLink.request(operation, forward) || Observable.of(); 56 | }); 57 | } 58 | } 59 | 60 | // join two Links together 61 | export const concat = ( 62 | first: ApolloLink | RequestHandler, 63 | second: ApolloLink | RequestHandler, 64 | ) => { 65 | const firstLink = toLink(first); 66 | if (isTerminating(firstLink)) { 67 | invariant.warn( 68 | new LinkError( 69 | `You are calling concat on a terminating link, which will have no effect`, 70 | firstLink, 71 | ), 72 | ); 73 | return firstLink; 74 | } 75 | const nextLink = toLink(second); 76 | 77 | if (isTerminating(nextLink)) { 78 | return new ApolloLink( 79 | operation => 80 | firstLink.request( 81 | operation, 82 | op => nextLink.request(op) || Observable.of(), 83 | ) || Observable.of(), 84 | ); 85 | } else { 86 | return new ApolloLink((operation, forward) => { 87 | return ( 88 | firstLink.request(operation, op => { 89 | return nextLink.request(op, forward) || Observable.of(); 90 | }) || Observable.of() 91 | ); 92 | }); 93 | } 94 | }; 95 | 96 | export class ApolloLink { 97 | public static empty = empty; 98 | public static from = from; 99 | public static split = split; 100 | public static execute = execute; 101 | 102 | constructor(request?: RequestHandler) { 103 | if (request) this.request = request; 104 | } 105 | 106 | public split( 107 | test: (op: Operation) => boolean, 108 | left: ApolloLink | RequestHandler, 109 | right?: ApolloLink | RequestHandler, 110 | ): ApolloLink { 111 | return this.concat(split(test, left, right || new ApolloLink(passthrough))); 112 | } 113 | 114 | public concat(next: ApolloLink | RequestHandler): ApolloLink { 115 | return concat(this, next); 116 | } 117 | 118 | public request( 119 | operation: Operation, 120 | forward?: NextLink, 121 | ): Observable | null { 122 | throw new InvariantError('request is not implemented'); 123 | } 124 | } 125 | 126 | export function execute( 127 | link: ApolloLink, 128 | operation: GraphQLRequest, 129 | ): Observable { 130 | return ( 131 | link.request( 132 | createOperation( 133 | operation.context, 134 | transformOperation(validateOperation(operation)), 135 | ), 136 | ) || Observable.of() 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /packages/apollo-link/src/linkUtils.ts: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable-ts'; 2 | 3 | import { GraphQLRequest, Operation } from './types'; 4 | import { ApolloLink } from './link'; 5 | 6 | import { getOperationName } from 'apollo-utilities'; 7 | import { invariant, InvariantError } from 'ts-invariant'; 8 | export { getOperationName }; 9 | 10 | export function validateOperation(operation: GraphQLRequest): GraphQLRequest { 11 | const OPERATION_FIELDS = [ 12 | 'query', 13 | 'operationName', 14 | 'variables', 15 | 'extensions', 16 | 'context', 17 | ]; 18 | for (let key of Object.keys(operation)) { 19 | if (OPERATION_FIELDS.indexOf(key) < 0) { 20 | throw new InvariantError(`illegal argument: ${key}`); 21 | } 22 | } 23 | 24 | return operation; 25 | } 26 | 27 | export class LinkError extends Error { 28 | public link: ApolloLink; 29 | constructor(message?: string, link?: ApolloLink) { 30 | super(message); 31 | this.link = link; 32 | } 33 | } 34 | 35 | export function isTerminating(link: ApolloLink): boolean { 36 | return link.request.length <= 1; 37 | } 38 | 39 | export function toPromise(observable: Observable): Promise { 40 | let completed = false; 41 | return new Promise((resolve, reject) => { 42 | observable.subscribe({ 43 | next: data => { 44 | if (completed) { 45 | invariant.warn( 46 | `Promise Wrapper does not support multiple results from Observable`, 47 | ); 48 | } else { 49 | completed = true; 50 | resolve(data); 51 | } 52 | }, 53 | error: reject, 54 | }); 55 | }); 56 | } 57 | 58 | // backwards compat 59 | export const makePromise = toPromise; 60 | 61 | export function fromPromise(promise: Promise): Observable { 62 | return new Observable(observer => { 63 | promise 64 | .then((value: T) => { 65 | observer.next(value); 66 | observer.complete(); 67 | }) 68 | .catch(observer.error.bind(observer)); 69 | }); 70 | } 71 | 72 | export function fromError(errorValue: any): Observable { 73 | return new Observable(observer => { 74 | observer.error(errorValue); 75 | }); 76 | } 77 | 78 | export function transformOperation(operation: GraphQLRequest): GraphQLRequest { 79 | const transformedOperation: GraphQLRequest = { 80 | variables: operation.variables || {}, 81 | extensions: operation.extensions || {}, 82 | operationName: operation.operationName, 83 | query: operation.query, 84 | }; 85 | 86 | // best guess at an operation name 87 | if (!transformedOperation.operationName) { 88 | transformedOperation.operationName = 89 | typeof transformedOperation.query !== 'string' 90 | ? getOperationName(transformedOperation.query) 91 | : ''; 92 | } 93 | 94 | return transformedOperation as Operation; 95 | } 96 | 97 | export function createOperation( 98 | starting: any, 99 | operation: GraphQLRequest, 100 | ): Operation { 101 | let context = { ...starting }; 102 | const setContext = next => { 103 | if (typeof next === 'function') { 104 | context = { ...context, ...next(context) }; 105 | } else { 106 | context = { ...context, ...next }; 107 | } 108 | }; 109 | const getContext = () => ({ ...context }); 110 | 111 | Object.defineProperty(operation, 'setContext', { 112 | enumerable: false, 113 | value: setContext, 114 | }); 115 | 116 | Object.defineProperty(operation, 'getContext', { 117 | enumerable: false, 118 | value: getContext, 119 | }); 120 | 121 | Object.defineProperty(operation, 'toKey', { 122 | enumerable: false, 123 | value: () => getKey(operation), 124 | }); 125 | 126 | return operation as Operation; 127 | } 128 | 129 | export function getKey(operation: GraphQLRequest) { 130 | // XXX We're assuming here that query and variables will be serialized in 131 | // the same order, which might not always be true. 132 | const { query, variables, operationName } = operation; 133 | return JSON.stringify([operationName, query, variables]); 134 | } 135 | -------------------------------------------------------------------------------- /packages/apollo-link/src/test-utils.ts: -------------------------------------------------------------------------------- 1 | import MockLink from './test-utils/mockLink'; 2 | import SetContextLink from './test-utils/setContext'; 3 | export * from './test-utils/testingUtils'; 4 | 5 | export { MockLink, SetContextLink }; 6 | -------------------------------------------------------------------------------- /packages/apollo-link/src/test-utils/mockLink.ts: -------------------------------------------------------------------------------- 1 | import { Operation, RequestHandler, NextLink, FetchResult } from '../types'; 2 | 3 | import Observable from 'zen-observable-ts'; 4 | 5 | import { ApolloLink } from '../link'; 6 | 7 | export default class MockLink extends ApolloLink { 8 | constructor(handleRequest: RequestHandler = () => null) { 9 | super(); 10 | this.request = handleRequest; 11 | } 12 | 13 | public request( 14 | operation: Operation, 15 | forward?: NextLink, 16 | ): Observable | null { 17 | throw Error('should be overridden'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/apollo-link/src/test-utils/setContext.ts: -------------------------------------------------------------------------------- 1 | import { Operation, NextLink, FetchResult } from '../types'; 2 | 3 | import Observable from 'zen-observable-ts'; 4 | 5 | import { ApolloLink } from '../link'; 6 | 7 | export default class SetContextLink extends ApolloLink { 8 | constructor( 9 | private setContext: ( 10 | context: Record, 11 | ) => Record = c => c, 12 | ) { 13 | super(); 14 | } 15 | 16 | public request( 17 | operation: Operation, 18 | forward: NextLink, 19 | ): Observable { 20 | operation.setContext(this.setContext(operation.getContext())); 21 | return forward(operation); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/apollo-link/src/test-utils/testingUtils.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { execute, ApolloLink } from '../link'; 3 | 4 | const sampleQuery = gql` 5 | query SampleQuery { 6 | stub { 7 | id 8 | } 9 | } 10 | `; 11 | 12 | export function checkCalls(calls: any[] = [], results: Array) { 13 | expect(calls.length).toBe(results.length); 14 | calls.map((call, i) => expect(call.data).toEqual(results[i])); 15 | } 16 | 17 | export interface TestResultType { 18 | link: ApolloLink; 19 | results?: any[]; 20 | query?: string; 21 | done?: () => void; 22 | context?: any; 23 | variables?: any; 24 | } 25 | 26 | export function testLinkResults(params: TestResultType) { 27 | const { link, context, variables } = params; 28 | const results = params.results || []; 29 | const query = params.query || sampleQuery; 30 | const done = params.done || (() => void 0); 31 | 32 | const spy = jest.fn(); 33 | execute(link, { query, context, variables }).subscribe({ 34 | next: spy, 35 | error: error => { 36 | expect(error).toEqual(results.pop()); 37 | checkCalls(spy.mock.calls[0], results); 38 | if (done) { 39 | done(); 40 | } 41 | }, 42 | complete: () => { 43 | checkCalls(spy.mock.calls[0], results); 44 | if (done) { 45 | done(); 46 | } 47 | }, 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /packages/apollo-link/src/types.ts: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable-ts'; 2 | import { DocumentNode } from 'graphql/language/ast'; 3 | import { ExecutionResult as GraphQLExecutionResult } from 'graphql'; 4 | export { DocumentNode }; 5 | 6 | export interface ExecutionResult< 7 | TData = { 8 | [key: string]: any; 9 | } 10 | > extends GraphQLExecutionResult { 11 | data?: TData | null; 12 | } 13 | 14 | export interface GraphQLRequest { 15 | query: DocumentNode; 16 | variables?: Record; 17 | operationName?: string; 18 | context?: Record; 19 | extensions?: Record; 20 | } 21 | 22 | export interface Operation { 23 | query: DocumentNode; 24 | variables: Record; 25 | operationName: string; 26 | extensions: Record; 27 | setContext: (context: Record) => Record; 28 | getContext: () => Record; 29 | toKey: () => string; 30 | } 31 | 32 | export type FetchResult< 33 | TData = { [key: string]: any }, 34 | C = Record, 35 | E = Record 36 | > = ExecutionResult & { 37 | extensions?: E; 38 | context?: C; 39 | }; 40 | 41 | export type NextLink = (operation: Operation) => Observable; 42 | export type RequestHandler = ( 43 | operation: Operation, 44 | forward: NextLink, 45 | ) => Observable | null; 46 | -------------------------------------------------------------------------------- /packages/apollo-link/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["src/**/__tests__/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | rollup.config.js 4 | .rpt2_cache 5 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ---- 4 | 5 | **NOTE:** This changelog is no longer maintained. Changes are now tracked in 6 | the top level [`CHANGELOG.md`](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md). 7 | 8 | ---- 9 | 10 | ### 0.8.11 11 | 12 | - No changes 13 | 14 | ### 0.8.10 15 | - Added `graphql` 14 to peer and dev deps; Updated `@types/graphql` to 14
16 | [@hwillson](http://github.com/hwillson) in [#789](https://github.com/apollographql/apollo-link/pull/789) 17 | 18 | ### 0.8.9 19 | - fix to stop combining require and export [PR#559](https://github.com/apollographql/apollo-link/pull/559) 20 | 21 | ### 0.8.8 22 | - revert to zen-observable 0.7 23 | 24 | ### 0.8.7 25 | - fixed typings 26 | 27 | ### 0.8.6 28 | - initial publishing mirrors `zen-observable`'s versioning 29 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 zenparsing (Kevin Smith) 4 | Copyright (c) 2016 - 2018 Meteor Development Group, Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zen-observable-ts", 3 | "version": "0.8.21", 4 | "description": "An Implementation of ES Observables in Typescript", 5 | "author": "Evans Hauser ", 6 | "contributors": [], 7 | "license": "MIT", 8 | "main": "./lib/index.js", 9 | "module": "./lib/bundle.esm.js", 10 | "typings": "./lib/index.d.ts", 11 | "sideEffects": false, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/apollographql/apollo-link.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/apollographql/apollo-link/issues" 18 | }, 19 | "homepage": "https://github.com/zenparsing/zen-observable", 20 | "scripts": { 21 | "build": "tsc && rollup -c", 22 | "clean": "rimraf lib/* && rimraf coverage/*", 23 | "coverage": "jest --coverage", 24 | "filesize": "../../scripts/minify", 25 | "lint": "tslint -c \"../../tslint.json\" -p tsconfig.json -c ../../tslint.json src/*.ts", 26 | "prebuild": "npm run clean", 27 | "prepare": "npm run build", 28 | "test": "npm run lint && jest", 29 | "watch": "tsc -w -p ." 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "24.9.1", 33 | "jest": "24.9.0", 34 | "rimraf": "2.7.1", 35 | "rollup": "1.32.1", 36 | "ts-jest": "22.4.6", 37 | "tslint": "5.20.1", 38 | "typescript": "3.0.3" 39 | }, 40 | "jest": { 41 | "transform": { 42 | ".(ts|tsx)": "ts-jest" 43 | }, 44 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 45 | "moduleFileExtensions": [ 46 | "ts", 47 | "tsx", 48 | "js", 49 | "json" 50 | ], 51 | "testURL": "http://localhost" 52 | }, 53 | "dependencies": { 54 | "tslib": "^1.9.3", 55 | "zen-observable": "^0.8.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import build from '../../rollup.config'; 2 | 3 | export default build('zenObservable'); 4 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/__tests__/filter.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from '../zenObservable'; 2 | 3 | describe('filter ', () => { 4 | it('Basics', () => { 5 | const list: Array = []; 6 | 7 | return Observable.from([1, 2, 3, 4]) 8 | .filter(x => x > 2) 9 | .forEach(x => list.push(x)) 10 | .then(() => expect(list).toEqual([3, 4])); 11 | }); 12 | 13 | it('throws on not a function', () => { 14 | const list: Array = []; 15 | return expect( 16 | () => 17 | Observable.from([1, 2, 3, 4]) 18 | .filter(1) 19 | .forEach(x => list.push(x)).then, 20 | ).toThrow(); 21 | }); 22 | 23 | it('throws on error inside function', done => { 24 | const error = new Error('thrown'); 25 | return expect(() => 26 | Observable.from([1, 2, 3, 4]) 27 | .filter(() => { 28 | throw error; 29 | }) 30 | .subscribe({ 31 | error: err => { 32 | expect(err).toEqual(error); 33 | done(); 34 | }, 35 | }), 36 | ).not.toThrow(); 37 | }); 38 | 39 | it('does not throw on closed subscription', () => { 40 | const list: Array = []; 41 | const obs = Observable.from([1, 2, 3, 4]); 42 | obs.subscribe({}).unsubscribe(); 43 | return expect( 44 | () => obs.filter(x => x > 2).forEach(x => list.push(x)).then, 45 | ).not.toThrow(); 46 | }); 47 | 48 | it('does not throw on internally closed subscription', () => { 49 | const list: Array = []; 50 | const obs = new Observable(observer => { 51 | observer.next(1); 52 | observer.next(1); 53 | observer.complete(); 54 | observer.next(1); 55 | }); 56 | 57 | return expect( 58 | () => obs.filter(x => x > 2).forEach(x => list.push(x)).then, 59 | ).not.toThrow(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/__tests__/flatMap.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from '../zenObservable'; 2 | 3 | describe.skip('flatMap', () => { 4 | it('Observable.from', done => { 5 | let list: Array = []; 6 | 7 | try { 8 | Observable.from([1, 2, 3]) 9 | .flatMap(x => { 10 | return Observable.from([x * 1, x * 2, x * 3]); 11 | }) 12 | .forEach(x => { 13 | list.push(x); 14 | }) 15 | .then(() => { 16 | expect(list).toEqual([1, 2, 3, 2, 4, 6, 3, 6, 9]); 17 | done(); 18 | }); 19 | } catch (e) { 20 | done.fail(e); 21 | } 22 | }); 23 | 24 | it('Error if return value is not observable', () => { 25 | return Observable.from([1, 2, 3]) 26 | .flatMap(() => { 27 | return 1; 28 | }) 29 | .forEach(() => null) 30 | .then(() => expect(false), () => expect(true)); 31 | }); 32 | 33 | it('throws on not a function', done => { 34 | try { 35 | expect(() => 36 | Observable.from([1, 2, 3, 4]) 37 | .flatMap(1) 38 | .forEach(x => void 0) 39 | .then(() => done.fail()), 40 | ).toThrow(); 41 | done(); 42 | } catch (e) { 43 | done.fail(e); 44 | } 45 | }); 46 | 47 | it('throws on error inside function', done => { 48 | const error = new Error('thrown'); 49 | try { 50 | return expect(() => 51 | Observable.from([1, 2, 3, 4]) 52 | .flatMap(() => { 53 | throw error; 54 | }) 55 | .subscribe({ 56 | error: err => { 57 | expect(err).toEqual(error); 58 | done(); 59 | }, 60 | }), 61 | ).toThrow(); 62 | } catch (e) { 63 | done.fail(e); 64 | } 65 | }); 66 | 67 | it('calls inner unsubscribe', done => { 68 | Observable.from(Observable.of(1)) 69 | .flatMap(x => { 70 | return new Observable(observer => done); 71 | }) 72 | .subscribe({}) 73 | .unsubscribe(); 74 | }); 75 | 76 | it('does not throw on closed subscription', () => { 77 | const list: Array = []; 78 | const obs = Observable.from([1, 2, 3, 4]); 79 | obs.subscribe({}).unsubscribe(); 80 | return expect( 81 | () => 82 | obs 83 | .flatMap(x => { 84 | return Observable.from([x * 1, x * 2, x * 3]); 85 | }) 86 | .forEach(x => { 87 | list.push(x); 88 | }).then, 89 | ).not.toThrow(); 90 | }); 91 | 92 | it('does not throw on internally closed subscription', () => { 93 | const list: Array = []; 94 | const obs = new Observable(observer => { 95 | observer.next(1); 96 | observer.next(1); 97 | observer.complete(); 98 | observer.next(1); 99 | }); 100 | obs.subscribe({}).unsubscribe(); 101 | return expect( 102 | () => 103 | obs 104 | .flatMap(x => { 105 | return Observable.from([x * 1, x * 2, x * 3]); 106 | }) 107 | .forEach(x => { 108 | list.push(x); 109 | }).then, 110 | ).not.toThrow(); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/__tests__/forEach.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from '../zenObservable'; 2 | 3 | describe('forEach ', () => { 4 | it.skip('throws on not a function', done => { 5 | try { 6 | Observable.from([1, 2, 3, 4]).forEach(1); 7 | } catch (e) { 8 | try { 9 | expect(e.message).toMatch(/not a function/); 10 | done(); 11 | } catch (e) { 12 | done.fail(e); 13 | } 14 | } 15 | }); 16 | 17 | it('throws on not a function', () => { 18 | const error = new Error('completed'); 19 | return new Observable(observer => { 20 | observer.complete(); 21 | throw error; 22 | }) 23 | .forEach(x => x) 24 | .catch(err => expect(err).toEqual(error)); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/__tests__/map.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from '../zenObservable'; 2 | 3 | describe('map', () => { 4 | it('Basics', () => { 5 | let list: Array = []; 6 | 7 | return Observable.from([1, 2, 3]) 8 | .map(x => x * 2) 9 | .forEach(x => list.push(x)) 10 | .then(() => expect(list).toEqual([2, 4, 6])); 11 | }); 12 | 13 | it('throws on not a function', done => { 14 | try { 15 | Observable.from([1, 2, 3, 4]) 16 | .map(1) 17 | .forEach(x => void 0) 18 | .then(() => done.fail()); 19 | } catch (e) { 20 | expect(e.message).toMatch(/not a function/); 21 | done(); 22 | } 23 | }); 24 | 25 | it('throws on error inside function', done => { 26 | const error = new Error('thrown'); 27 | try { 28 | Observable.from([1, 2, 3, 4]) 29 | .map(num => { 30 | expect(num).toEqual(1); 31 | debugger; 32 | throw error; 33 | }) 34 | .subscribe({ 35 | error: err => { 36 | expect(err).toEqual(error); 37 | done(); 38 | }, 39 | }); 40 | } catch (e) { 41 | done.fail(e); 42 | } 43 | }); 44 | 45 | it('does not throw on closed subscription', () => { 46 | const obs = Observable.from([1, 2, 3, 4]); 47 | obs.subscribe({}).unsubscribe(); 48 | return expect( 49 | () => obs.map(x => x * 2).forEach(x => void 0).then, 50 | ).not.toThrow(); 51 | }); 52 | 53 | it('does not throw on internally closed subscription', () => { 54 | const obs = new Observable(observer => { 55 | observer.next(1); 56 | observer.next(1); 57 | observer.complete(); 58 | observer.next(1); 59 | }); 60 | return expect( 61 | () => obs.map(x => x * 2).forEach(x => void 0).then, 62 | ).not.toThrow(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/__tests__/observer.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from '../zenObservable'; 2 | 3 | describe('of', () => { 4 | it('Basics', () => { 5 | let list: Array = []; 6 | 7 | return Observable.of(1, 2, 3) 8 | .map(x => x * 2) 9 | .forEach(x => list.push(x)) 10 | .then(() => expect(list).toEqual([2, 4, 6])); 11 | }); 12 | }); 13 | 14 | describe('subscription', () => { 15 | it('can close multiple times', () => { 16 | const sub = Observable.of(1).subscribe({}); 17 | sub.unsubscribe(); 18 | sub.unsubscribe(); 19 | }); 20 | 21 | it('can close multiple times', () => { 22 | let sub = Observable.of(1, 2).subscribe({}); 23 | sub = Observable.of(1, 2).subscribe({ 24 | next: sub.unsubscribe, 25 | }); 26 | }); 27 | }); 28 | 29 | describe('observer', () => { 30 | it('recalling next, error, complete have no effect', () => { 31 | const spy = jest.fn(); 32 | const list: Array = []; 33 | return new Observable(observer => { 34 | observer.next(1); 35 | observer.next(2); 36 | observer.next(3); 37 | observer.complete(); 38 | observer.next(4); 39 | observer.complete(); 40 | spy(); 41 | }) 42 | .map(x => x * 2) 43 | .forEach(x => list.push(x)) 44 | .then(() => expect(list).toEqual([2, 4, 6])) 45 | .then(() => expect(spy).toBeCalled()); 46 | }); 47 | 48 | it('throws on non function Observer', () => { 49 | expect(() => new Observable(1)).toThrow(); 50 | }); 51 | 52 | it('completes after error', () => { 53 | const error = new Error('completed'); 54 | return new Promise((resolve, reject) => 55 | new Observable(observer => { 56 | observer.complete(); 57 | return; 58 | }).subscribe({ 59 | complete: () => { 60 | reject(error); 61 | }, 62 | }), 63 | ).catch(err => expect(err).toEqual(error)); 64 | }); 65 | 66 | it('calling without options does not throw', () => { 67 | new Observable(observer => { 68 | observer.next(1); 69 | observer.next(2); 70 | observer.next(3); 71 | observer.complete(); 72 | }).subscribe({}); 73 | }); 74 | 75 | it('calling without options does not throw', () => { 76 | let num = 0; 77 | return new Promise((resolve, reject) => { 78 | new Observable(observer => { 79 | observer.next(1); 80 | observer.next(2); 81 | observer.next(3); 82 | observer.complete(); 83 | }).subscribe(val => expect(++num).toBe(val), reject, resolve); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/__tests__/reduce.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from '../zenObservable'; 2 | describe('reduce ', () => { 3 | it('No seed', () => { 4 | return Observable.from([1, 2, 3, 4, 5, 6]) 5 | .reduce((a, b) => { 6 | return a + b; 7 | }) 8 | .forEach(x => { 9 | expect(x).toBe(21); 10 | }); 11 | }); 12 | 13 | it('No seed - one value', () => { 14 | return Observable.from([1]) 15 | .reduce((a, b) => { 16 | return a + b; 17 | }) 18 | .forEach(x => { 19 | expect(x).toBe(1); 20 | }); 21 | }); 22 | 23 | it('No seed - empty (throws)', () => { 24 | return Observable.from([]) 25 | .reduce((a, b) => { 26 | return a + b; 27 | }) 28 | .forEach(() => null) 29 | .then(() => expect(false), () => expect(true)); 30 | }); 31 | 32 | it('Seed', () => { 33 | return Observable.from([1, 2, 3, 4, 5, 6]) 34 | .reduce((a, b) => { 35 | return a + b; 36 | }, 100) 37 | .forEach(x => { 38 | expect(x).toBe(121); 39 | }); 40 | }); 41 | 42 | it('Seed - empty', () => { 43 | return Observable.from([]) 44 | .reduce((a, b) => { 45 | return a + b; 46 | }, 100) 47 | .forEach(x => { 48 | expect(x).toBe(100); 49 | }); 50 | }); 51 | 52 | it('throws on not a function', done => { 53 | try { 54 | Observable.from([1, 2, 3, 4]) 55 | .reduce(1) 56 | .forEach(x => void 0) 57 | .then(() => done.fail()); 58 | } catch (e) { 59 | expect(e.message).toMatch(/not a function/); 60 | done(); 61 | } 62 | }); 63 | 64 | it('throws on error inside function', done => { 65 | const error = new Error('thrown'); 66 | return expect(() => 67 | Observable.from([1, 2, 3, 4]) 68 | .reduce(() => { 69 | throw error; 70 | }) 71 | .subscribe({ 72 | error: err => { 73 | expect(err).toEqual(error); 74 | done(); 75 | }, 76 | }), 77 | ).not.toThrow(); 78 | }); 79 | 80 | it('does not throw on closed subscription', () => { 81 | const obs = Observable.from([1, 2, 3, 4]); 82 | 83 | obs.subscribe({}).unsubscribe(); 84 | return expect( 85 | () => 86 | obs 87 | .reduce((a, b) => { 88 | return a + b; 89 | }, 100) 90 | .forEach(x => { 91 | expect(x).toBe(110); 92 | }).then, 93 | ).not.toThrow(); 94 | }); 95 | 96 | it('does not throw on internally closed subscription', () => { 97 | const obs = new Observable(observer => { 98 | observer.next(1); 99 | observer.next(1); 100 | observer.complete(); 101 | observer.next(1); 102 | }); 103 | return expect( 104 | () => 105 | obs 106 | .reduce((a, b) => { 107 | return a + b; 108 | }, 100) 109 | .forEach(x => { 110 | expect(x).toBe(102); 111 | }).then, 112 | ).not.toThrow(); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from './zenObservable'; 2 | 3 | export * from './zenObservable'; 4 | export default Observable; 5 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/types.ts: -------------------------------------------------------------------------------- 1 | export namespace ZenObservable { 2 | export interface SubscriptionObserver { 3 | closed: boolean; 4 | next(value: T): void; 5 | error(errorValue: any): void; 6 | complete(): void; 7 | } 8 | 9 | export interface Subscription { 10 | closed: boolean; 11 | unsubscribe(): void; 12 | } 13 | 14 | export interface Observer { 15 | start?(subscription: Subscription): any; 16 | next?(value: T): void; 17 | error?(errorValue: any): void; 18 | complete?(): void; 19 | } 20 | 21 | export type Subscriber = ( 22 | observer: SubscriptionObserver, 23 | ) => void | (() => void) | Subscription; 24 | 25 | export interface ObservableLike { 26 | subscribe?: Subscriber; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/src/zenObservable.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | import zenObservable from 'zen-observable'; 4 | 5 | namespace Observable { 6 | 7 | } 8 | 9 | import { ZenObservable } from './types'; 10 | 11 | export { ZenObservable }; 12 | 13 | export type Observer = ZenObservable.Observer; 14 | export type Subscriber = ZenObservable.Subscriber; 15 | export type ObservableLike = ZenObservable.ObservableLike; 16 | 17 | export const Observable: { 18 | new (subscriber: Subscriber): Observable; 19 | from( 20 | observable: Observable | ZenObservable.ObservableLike | ArrayLike, 21 | ): Observable; 22 | of(...args: Array): Observable; 23 | } = zenObservable; 24 | 25 | export interface Observable { 26 | subscribe( 27 | observerOrNext: ((value: T) => void) | ZenObservable.Observer, 28 | error?: (error: any) => void, 29 | complete?: () => void, 30 | ): ZenObservable.Subscription; 31 | 32 | forEach(fn: (value: T) => void): Promise; 33 | 34 | map(fn: (value: T) => R): Observable; 35 | 36 | filter(fn: (value: T) => boolean): Observable; 37 | 38 | reduce( 39 | fn: (previousValue: R | T, currentValue: T) => R | T, 40 | initialValue?: R | T, 41 | ): Observable; 42 | 43 | flatMap(fn: (value: T) => ZenObservable.ObservableLike): Observable; 44 | 45 | from( 46 | observable: Observable | ZenObservable.ObservableLike | ArrayLike, 47 | ): Observable; 48 | of(...args: Array): Observable; 49 | } 50 | -------------------------------------------------------------------------------- /packages/zen-observable-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "lib", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["src/**/__tests__/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [":pinOnlyDevDependencies"], 3 | "semanticCommits": true, 4 | "depTypes": [{ "depType": "dependencies", "pinVersions": false }], 5 | "timezone": "America/Los_Angeles", 6 | "schedule": ["after 10pm and before 5am on every weekday"], 7 | "rebaseStalePrs": true, 8 | "prCreation": "not-pending", 9 | "automerge": "minor", 10 | "labels": ["tooling", "dependencies"], 11 | "pathRules": [ 12 | { 13 | "paths": ["docs/package.json"], 14 | "extends": ["apollo-docs"] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import sourcemaps from 'rollup-plugin-sourcemaps'; 2 | import node from 'rollup-plugin-node-resolve'; 3 | import typescript from 'typescript'; 4 | import typescriptPlugin from 'rollup-plugin-typescript2'; 5 | import invariantPlugin from 'rollup-plugin-invariant'; 6 | 7 | export const globals = { 8 | // Apollo 9 | 'apollo-client': 'apollo.core', 10 | 'apollo-link': 'apolloLink.core', 11 | 'apollo-link-batch': 'apolloLink.batch', 12 | 'apollo-link-http-common': 'apolloLink.httpCommon', 13 | 'apollo-utilities': 'apolloUtilities', 14 | 'zen-observable-ts': 'apolloLink.zenObservable', 15 | 'subscriptions-transport-ws': 'subscriptions-transport-ws', 16 | 17 | // GraphQL 18 | 'graphql/language/printer': 'graphql.printer', 19 | 'graphql/execution/execute': 'graphql.execute', 20 | 21 | // TypeScript 22 | 'tslib': 'tslib', 23 | 24 | // Other 25 | 'ts-invariant': 'invariant', 26 | 'zen-observable': 'Observable', 27 | }; 28 | 29 | export default name => [ 30 | { 31 | input: 'src/index.ts', 32 | output: { 33 | file: 'lib/bundle.umd.js', 34 | format: 'umd', 35 | name: `apolloLink.${name}`, 36 | globals, 37 | sourcemap: true, 38 | exports: 'named', 39 | }, 40 | external: Object.keys(globals), 41 | onwarn, 42 | plugins: [ 43 | node({ module: true }), 44 | typescriptPlugin({ 45 | typescript, 46 | tsconfig: './tsconfig.json', 47 | tsconfigOverride: { 48 | compilerOptions: { 49 | module: "es2015", 50 | }, 51 | }, 52 | }), 53 | invariantPlugin({ 54 | errorCodes: true, 55 | }), 56 | sourcemaps() 57 | ], 58 | }, 59 | { 60 | input: 'src/index.ts', 61 | output: { 62 | file: 'lib/bundle.esm.js', 63 | format: 'esm', 64 | globals, 65 | sourcemap: true, 66 | }, 67 | external: Object.keys(globals), 68 | onwarn, 69 | plugins: [ 70 | node({ module: true }), 71 | typescriptPlugin({ 72 | typescript, 73 | tsconfig: './tsconfig.json', 74 | tsconfigOverride: { 75 | compilerOptions: { 76 | module: "es2015", 77 | }, 78 | }, 79 | }), 80 | invariantPlugin({ 81 | errorCodes: true, 82 | }), 83 | sourcemaps() 84 | ], 85 | }, 86 | { 87 | input: 'lib/bundle.esm.js', 88 | output: { 89 | file: 'lib/bundle.cjs.js', 90 | format: 'cjs', 91 | globals, 92 | sourcemap: true, 93 | }, 94 | external: Object.keys(globals), 95 | onwarn, 96 | } 97 | ]; 98 | 99 | export function onwarn(message) { 100 | const suppressed = ['UNRESOLVED_IMPORT', 'THIS_IS_UNDEFINED']; 101 | 102 | if (!suppressed.find(code => message.code === code)) { 103 | return console.warn(message.message); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /scripts/minify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { minify } = require("terser"); 6 | const sourcePath = path.join("lib", "bundle.cjs.js"); 7 | const outputPath = path.join("lib", "bundle.min.js"); 8 | 9 | const source = fs.readFileSync(sourcePath, "utf8"); 10 | const result = minify(source, { 11 | mangle: { 12 | toplevel: true 13 | }, 14 | compress: { 15 | dead_code: true, 16 | global_defs: { 17 | "@process.env.NODE_ENV": JSON.stringify("production") 18 | } 19 | } 20 | }); 21 | 22 | if (result.error) { 23 | throw result.error; 24 | } 25 | 26 | fs.writeFileSync(outputPath, result.code, "utf8"); 27 | 28 | console.log("minified", sourcePath, "=>", outputPath); 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es6", "dom"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "importHelpers": true, 8 | "removeComments": true, 9 | "sourceMap": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "noImplicitAny": false, 13 | "noUnusedParameters": false, 14 | "noUnusedLocals": true, 15 | "esModuleInterop": true, 16 | "skipLibCheck": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ban": false, 4 | "class-name": true, 5 | "eofline": true, 6 | "forin": true, 7 | "interface-name": [true, "never-prefix"], 8 | "jsdoc-format": true, 9 | "label-position": true, 10 | "member-access": true, 11 | "member-ordering": [ 12 | true, 13 | { 14 | "order": [ 15 | "static-field", 16 | "instance-field", 17 | "constructor", 18 | "public-instance-method", 19 | "protected-instance-method", 20 | "private-instance-method" 21 | ] 22 | } 23 | ], 24 | "no-any": false, 25 | "no-arg": true, 26 | "no-bitwise": true, 27 | "no-conditional-assignment": true, 28 | "no-consecutive-blank-lines": false, 29 | "no-console": [true, "log", "debug", "info", "time", "timeEnd", "trace"], 30 | "no-construct": true, 31 | "no-debugger": true, 32 | "no-duplicate-variable": true, 33 | "no-empty": true, 34 | "no-eval": true, 35 | "no-inferrable-types": false, 36 | "no-internal-module": true, 37 | "no-null-keyword": false, 38 | "no-require-imports": false, 39 | "no-shadowed-variable": true, 40 | "no-switch-case-fall-through": true, 41 | "no-trailing-whitespace": true, 42 | "no-unused-expression": true, 43 | "no-var-keyword": true, 44 | "no-var-requires": true, 45 | "object-literal-sort-keys": false, 46 | "radix": true, 47 | "switch-default": true, 48 | "triple-equals": [true, "allow-null-check"], 49 | "typedef": [ 50 | false, 51 | "call-signature", 52 | "parameter", 53 | "arrow-parameter", 54 | "property-declaration", 55 | "variable-declaration", 56 | "member-variable-declaration" 57 | ], 58 | "variable-name": [ 59 | true, 60 | "check-format", 61 | "allow-leading-underscore", 62 | "ban-keywords" 63 | ] 64 | } 65 | } 66 | --------------------------------------------------------------------------------