6 Commits

Author SHA1 Message Date
Kristaps Fabians Geikins 31657ada67 package changes for published fork 2022-11-16 13:19:35 +02:00
Kristaps Fabians Geikins d19a090df2 gaa 2022-11-16 13:19:25 +02:00
Kristaps Fabians Geikins abe8089fdd fix: making sure tryFirstResolve/reject are always invoked 2022-11-15 17:26:09 +02:00
Kristaps Fabians Geikins 773260b97e Merge pull request #1 from fabis94/v4
onServerPrefetchFix move to new fork
2022-11-15 16:59:31 +02:00
Kristaps Fabians Geikins 9830ea7607 fix: onServerPrefetch promise refactor 2022-11-11 12:31:50 +02:00
Kristaps Fabians Geikins 7e06de77aa fix: onServerPrefetch getting stuck prevention 2022-11-11 12:29:16 +02:00
262 changed files with 12962 additions and 22060 deletions
+92
View File
@@ -0,0 +1,92 @@
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
- image: cypress/base:14
environment:
## this enables colors in the output
TERM: xterm
working_directory: ~/repo
steps:
- checkout
- run: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
- run: |
echo 'export NVM_DIR=$HOME/.nvm' >> $BASH_ENV
echo 'source $NVM_DIR/nvm.sh' >> $BASH_ENV
- run: |
nvm install 16
nvm use 16
nvm alias default 16
- run: npm install -g pnpm
# Download and cache dependencies
- restore_cache:
keys:
- v11-dependencies-{{ checksum "pnpm-lock.yaml" }}
# fallback to using the latest cache if no exact match is found
- v11-dependencies-
- run: pnpm install --frozen-lockfile
- save_cache:
paths:
- node_modules
- packages/docs/node_modules
- packages/test-e2e/node_modules
- packages/test-e2e-composition/node_modules
- packages/test-e2e-composable-vue3/node_modules
- packages/test-ssr/node_modules
- packages/test-ssr-composition/node_modules
- packages/vue-apollo-components/node_modules
- packages/vue-apollo-composable/node_modules
- packages/vue-apollo-option/node_modules
- packages/vue-apollo-ssr/node_modules
- packages/vue-apollo-util/node_modules
- ~/.cache
- ~/.pnpm-store
key: v11-dependencies-{{ checksum "pnpm-lock.yaml" }}
# run tests!
- run: pnpm run lint
- run: pnpm run build
- run: cd packages/vue-apollo-option && pnpm run test:types
- run: cd packages/vue-apollo-option && pnpm run test:unit
- run: cd packages/vue-apollo-composable && pnpm run test:types
- run: cd packages/test-e2e && pnpm run test:e2e
# - run: cd packages/test-e2e-composition && pnpm run test:e2e
- run: cd packages/test-e2e-composable-vue3 && pnpm run test:e2e
# - run: cd packages/test-ssr && pnpm run test:e2e
# - run: cd packages/test-ssr-composition && pnpm run test:e2e
- store_artifacts:
path: packages/test-e2e/tests/e2e/videos
- store_artifacts:
path: packages/test-e2e/tests/e2e/screenshots
- store_artifacts:
path: packages/test-e2e-composition/tests/e2e/videos
- store_artifacts:
path: packages/test-e2e-composition/tests/e2e/screenshots
- store_artifacts:
path: packages/test-e2e-composable-vue3/tests/e2e/videos
- store_artifacts:
path: packages/test-e2e-composable-vue3/tests/e2e/screenshots
- store_artifacts:
path: packages/test-ssr/tests/e2e/videos
- store_artifacts:
path: packages/test-ssr/tests/e2e/screenshots
- store_artifacts:
path: packages/test-ssr-composition/tests/e2e/videos
- store_artifacts:
path: packages/test-ssr-composition/tests/e2e/screenshots
+3 -3
View File
@@ -37,9 +37,9 @@ module.exports = {
'@typescript-eslint/no-use-before-define': 'off',
'comma-dangle': ['error', 'always-multiline'],
'vue/no-multiple-template-root': 'off',
'indent': 'off',
indent: 'off',
'@typescript-eslint/indent': ['error', 2],
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
quotes: ['error', 'single', { allowTemplateLiterals: true }],
'no-use-before-define': 'warn',
'accessor-pairs': 'off',
'no-async-promise-executor': 'off',
@@ -73,7 +73,7 @@ module.exports = {
'packages/*/types/test/**/*.ts',
],
rules: {
'camelcase': 'off',
camelcase: 'off',
'no-unused-expressions': 'off',
'array-callback-return': 'warn',
},
-29
View File
@@ -1,29 +0,0 @@
name: Publish Nightlies
on:
pull_request:
push:
branches:
- '**'
tags:
- '!**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set alternate npm integrity keys
run: |
echo COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" >> $GITHUB_ENV
- uses: actions/checkout@v4
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 23
cache: pnpm
- run: pnpm install
- name: Build
run: pnpm build
- run: pnpx pkg-pr-new publish './packages/*'
+2 -3
View File
@@ -1,4 +1,4 @@
name: Check PR title
name: "Check PR title"
on:
pull_request_target:
@@ -8,9 +8,8 @@ on:
- synchronize
jobs:
check-pr-title:
main:
runs-on: ubuntu-latest
name: Check PR title
steps:
# Please look up the latest version from
# https://github.com/amannn/action-semantic-pull-request/releases
+4 -3
View File
@@ -3,7 +3,7 @@ name: Create release
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
build:
@@ -13,12 +13,13 @@ jobs:
- name: Checkout code
uses: actions/checkout@master
with:
fetch-depth: 0 # Fetch all tags
fetch-depth: 0
- name: Create Release for Tag
id: release_tag
uses: Akryum/release-tag@v4.0.7
uses: Akryum/release-tag@conventional
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
preset: angular
-67
View File
@@ -1,67 +0,0 @@
name: E2E composable
on:
push:
branches:
- main
- v4
- feat/*
- fix/*
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
runs-on: ubuntu-latest
name: Build and test
env:
dir: ./packages/test-e2e-composable-vue3
steps:
- name: Set alternate npm integrity keys
run: |
echo COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" >> $GITHUB_ENV
- uses: actions/checkout@v4
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 23
cache: pnpm
- uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: cypress-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- run: pnpm install
- name: Build
run: pnpm run build
- name: Build app
working-directory: ${{env.dir}}
run: pnpm run build
- name: E2E tests
working-directory: ${{env.dir}}
run: pnpm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots
path: ${{env.dir}}/tests/e2e/screenshots
- uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-videos
path: ${{env.dir}}/tests/e2e/videos
-63
View File
@@ -1,63 +0,0 @@
name: E2E options/components
on:
push:
branches:
- main
- v4
- feat/*
- fix/*
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
runs-on: ubuntu-latest
name: Build and test
env:
dir: ./packages/test-e2e
steps:
- name: Set alternate npm integrity keys
run: |
echo COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" >> $GITHUB_ENV
- uses: actions/checkout@v4
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 23
cache: pnpm
- uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: cypress-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- run: pnpm install
- name: Build
run: pnpm run build
- name: E2E tests
working-directory: ${{env.dir}}
run: pnpm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots
path: ${{env.dir}}/tests/e2e/screenshots
- uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-videos
path: ${{env.dir}}/tests/e2e/videos
-63
View File
@@ -1,63 +0,0 @@
name: E2E composable + SSR
on:
push:
branches:
- main
- v4
- feat/*
- fix/*
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
runs-on: ubuntu-latest
name: Build and test
env:
dir: ./packages/test-e2e-ssr
steps:
- name: Set alternate npm integrity keys
run: |
echo COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" >> $GITHUB_ENV
- uses: actions/checkout@v4
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 23
cache: pnpm
- uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: cypress-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- run: pnpm install
- name: Build
run: pnpm run build
- name: E2E tests
working-directory: ${{env.dir}}
run: pnpm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots
path: ${{env.dir}}/tests/e2e/screenshots
- uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-videos
path: ${{env.dir}}/tests/e2e/videos
-44
View File
@@ -1,44 +0,0 @@
name: Main continuous tests
on:
push:
branches:
- main
- v4
- feat/*
- fix/*
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
runs-on: ubuntu-latest
name: Build and test
steps:
- name: Set alternate npm integrity keys
run: |
echo COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" >> $GITHUB_ENV
- uses: actions/checkout@v4
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 23
cache: pnpm
- run: pnpm install
- name: Lint
run: pnpm run lint
- name: Build
run: pnpm run build
- name: Types
run: pnpm run test:types
- name: Unit tests
run: pnpm run test:unit
-3
View File
@@ -1,5 +1,2 @@
.idea
node_modules/
dist/
cache/
.eslintcache
-426
View File
@@ -1,429 +1,3 @@
# Changelog
## v4.2.2
[compare changes](https://github.com/vuejs/apollo/compare/v4.2.1...v4.2.2)
### 🩹 Fixes
- Memory leak in SSR caused by global tracking ([#1582](https://github.com/vuejs/apollo/pull/1582))
- Augment `vue` rather than `@vue/runtime-core` ([#1576](https://github.com/vuejs/apollo/pull/1576))
- UseMutations onError Event hook gets triggered too early ([#1585](https://github.com/vuejs/apollo/pull/1585))
### 📖 Documentation
- Fix $skipAll mention ([#1573](https://github.com/vuejs/apollo/pull/1573))
### 🏡 Chore
- Update deps + jest to vitest ([d421887](https://github.com/vuejs/apollo/commit/d421887))
### ✅ Tests
- **lint:** Switch to eslint 9 and antfu config ([99ca23b](https://github.com/vuejs/apollo/commit/99ca23b))
### 🤖 CI
- Update gh actions to use corepack ([f2578cb](https://github.com/vuejs/apollo/commit/f2578cb))
- Update upload artifact ([35fb59c](https://github.com/vuejs/apollo/commit/35fb59c))
- Fix cypress ([df96345](https://github.com/vuejs/apollo/commit/df96345))
- Rename job ([d99865f](https://github.com/vuejs/apollo/commit/d99865f))
- Add name to pr title job ([6bfd0ec](https://github.com/vuejs/apollo/commit/6bfd0ec))
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Jeroen De Jong ([@thumbnail](http://github.com/thumbnail))
- Mark Florian <markrian@gmail.com>
- ChatonDeAru (Romain) ([@chatondearu](http://github.com/chatondearu))
- Kristaps Fabians Geikins <fabis94@live.com>
## v4.2.1
### 🩹 Fixes
- Improved pinia support (#1571)
### 📖 Documentation
- Update broken circleci badge (9622392)
- Readme smaller logo (ff836ea)
- Use nightly.akryum.dev (7f3cf7d)
### 🏡 Chore
- Specify pnpm version in package.json (732e66e)
### ❤️ Contributors
- Nick Messing ([@nickmessing](http://github.com/nickmessing))
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
## v4.2.0
### 🚀 Enhancements
- Add updateQuery to useQuery (#1552)
### 🩹 Fixes
- UseMutations onDone Event hook gets triggered too early (#1559)
- (@vue/apollo-option) memory leak in wrapped ssrRender (#1553)
- Reuse previous result, fix #1483 (#1569, #1483)
- ResolveClient throwing too soon, fix #1557 (#1570, #1557)
### 📖 Documentation
- Add github link to documentation (#1549)
- Note about continuous releases (51e09e7)
### 🏡 Chore
- Switch some tests to script setup (c8e5106)
### 🤖 CI
- Nightly releases (319f6ec)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Matt Garrett <mattga@gmail.com>
- Mobsean ([@mobsean](http://github.com/mobsean))
- Leonardo Santos ([@syllomex](http://github.com/syllomex))
- Alex Liu ([@Mini-ghost](http://github.com/Mini-ghost))
## v4.1.0
### 🩹 Fixes
- Change teardown to use onScopeDispose (#1545)
### 📖 Documentation
- **useQuery:** Document refetch with new variables (#1564)
### 🏡 Chore
- Updqte pnpm to v9 (827ea6e)
### ✅ Tests
- UseSubscription (0f5ae61)
- Fix subscription test (#1547)
### 🤖 CI
- Update versions (fe66840)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Nick Messing ([@nickmessing](http://github.com/nickmessing))
## v4.0.2
### 🩹 Fixes
- Use shallowRef on result & error (08f0fcd)
### 📖 Documentation
- Remove mentions of fetchResults, fix #1060 (#1060)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
## v4.0.1
### 🩹 Fixes
- Use hasInjectionContext in useApolloClient before calling inject (#1529)
- **useLazyQuery:** Load() on server, fix #1495 (#1495)
### ✅ Tests
- Split into outsideComponent.cy.ts (48d0ac2)
- Build test app in test command (500d6e4)
### 🤖 CI
- Use GITHUB_OUTPUT envvar instead of set-output command (#1530)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Arun Sathiya <arun@arun.blog>
- Dawid Kopys <dewke17@gmail.com>
## v4.0.0
### 🚀 Enhancements
- **useLazyQuery:** Add interface for lazy query return (#1523)
### 🩹 Fixes
- Improve esm support, fix #1524 (#1524)
- Import serializeJs using default import instead of a namespace import (#1485)
- **options:** Use exponential backoff on subscribe error retry (b17817e)
- **ApolloMutation:** Return result in `mutate` (ddf9aa0)
- Prefetch type (f8568e8)
### 📖 Documentation
- Update vitepress + enable detailed search by default (fb66dce)
### 🏡 Chore
- Update sheep (9be63fa)
### ✅ Tests
- Fragment (062f72a)
### 🤖 CI
- Update node and pnpm (ca3f2f4)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Dawid Kopys <dewke17@gmail.com>
- Yury Savin <yury@savin.dev>
## v4.0.0-beta.12
### 🚀 Enhancements
- New context params in event hook handlers (0be5d9b)
### 🩹 Fixes
- Use shallowRef for apollo query (76f19f6)
### 📖 Documentation
- Missing curly brace (#1512)
- Added missing createClient import in example when creating Graph… (#1513)
- Import createApolloProvider is missing (#1515)
### 🏡 Chore
- Moved resolutions to the root of the workspace (#1508)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Hassan <hassanfayyaz19@gmail.com>
- Mekraldi
- Vitaliy
## v4.0.0-beta.11
### 🩹 Fixes
- Remove console.log, console log remained in code #1507 (#1507)
### 📖 Documentation
- Update README.md logo (68addf8)
- Update vitepress + fix components API menu (f545763)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
## v4.0.0-beta.10
### 🚀 Enhancements
- Support effect scope outside of component, fix #1505 (#1505)
- **useLazyQuery:** Load returns Promise, fix #1486 (#1486)
### 🩹 Fixes
- Apollo components should have emits (#1504)
### 🌊 Types
- Extended "enabled" option type (#1492)
### 🏡 Chore
- Package test-e2e-composable-vue3, update deps, migrate to vite (#1488)
- Upgrade vitepress to 1.0 RC (daffd75)
- Seq test (995131d)
### ✅ Tests
- **lint:** Fix (1ac1372)
- Ssr (574bd8f)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Viktor ([@websitevirtuoso](http://github.com/websitevirtuoso))
- Vitaliy
- Forgottencsc <forgottencosecant@outlook.com>
## v4.0.0-beta.9
### 🩹 Fixes
- Don't call debounced restart too much (1adf135)
### 🏡 Chore
- Update throttle-debounce (500cc49)
- Update deps (f47759e)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
## v4.0.0-beta.8
### 🚀 Enhancements
- **useQuery:** Nullable query (auto disable) (28f3520)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
## v4.0.0-beta.7
### 🩹 Fixes
- **ssr:** Hydration mismatch with keepPreviousResult (87188c4)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
## v4.0.0-beta.6
### 🚀 Enhancements
- KeepPreviousResult (e794c1e)
### 📖 Documentation
- ProvideApolloClient (#1442)
### 🏡 Chore
- Update graphql to 16 in repo (4dcfa20)
- Typo in test component file (bfca616)
- Update lockfile version (2077502)
### ✅ Tests
- Update server (13bfbbe)
- Update pnpm version (722fa0f)
- Test-server package (f1ebe70)
- Migrate server to typescript (97c1402)
- Fix (c881439)
### ❤️ Contributors
- Stefan Schneider <stefan.schneider@gmx.net>
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
## v4.0.0-beta.5
### 🚀 Enhancements
- UseLazyQuery load returns boolean to make is easier to refetch (dcb1768)
- **ts:** Update types to account for changes in TypeScript 4.8 (#1454)
- Allow global tracking outside of components (5967e16)
### 🩹 Fixes
- Don't call variables if query is disabled + fix enabling race conditions, fix #1243, fix #1422 (#1243, #1422)
- Events not registered in case of immediate trigger, fix #1154 (#1154)
- @vue/apollo-composable ESM settings, fix #1462 (#1463, #1462)
- Avoid multiple on error calls without usage of errorPolicy 'all' (#1461)
- Ssr export paths, fix #1469 (#1469)
- Initialize currentDocument early, fix #1325 (#1325)
- **ts:** Allow null on `userLazyQuery` `load` fn, fix #1386 (#1386)
- **ssr:** Handle result/error set before serverPrefetch call, fix #1429 (#1429)
### 📖 Documentation
- Subscriptions configuration docs updated to describe graphql-ws configuration. (#1449)
### 🏡 Chore
- Update lockfile to v6.0 (81ea32c)
- Update sheep/release-tag (cf7917e)
### ✅ Tests
- Config cypress downloads (32c95de)
- Demo useLazyQuery with immediate load (53554b8)
- Enabled (db7d79c)
### 🤖 CI
- Switch to github actions (25c31d2)
- Enable on v4 branch (bc3d80c)
### ❤️ Contributors
- Guillaume Chau ([@Akryum](http://github.com/Akryum))
- Gibran Amparan ([@gibranamparan](http://github.com/gibranamparan))
- Alessia Bellisario <alessia@apollographql.com>
- Dominik Klein <dk@zammad.com>
- Changwan Jun ([@wan2land](http://github.com/wan2land))
# [4.0.0-beta.4](https://github.com/vuejs/vue-apollo/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2023-02-22)
### Features
* improve ESM support ([2623b32](https://github.com/vuejs/vue-apollo/commit/2623b32d6c999cfa677b3b36969bd6b5b782d387))
# [4.0.0-beta.3](https://github.com/vuejs/vue-apollo/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2023-02-21)
### Bug Fixes
* **ssr:** error not bubbling up ([18fe206](https://github.com/vuejs/vue-apollo/commit/18fe206761eba0af05971dff34113d5396e6e6bf))
# [4.0.0-beta.2](https://github.com/vuejs/vue-apollo/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2023-02-03)
### Bug Fixes
* **@vue/apollo-option:** ssr cleanup function fails to run ([#1424](https://github.com/vuejs/vue-apollo/issues/1424)) ([#1425](https://github.com/vuejs/vue-apollo/issues/1425)) ([8dfe93b](https://github.com/vuejs/vue-apollo/commit/8dfe93b82679fac42b8d1509febc97e7faeed1e0))
* hydration error, revert [#1388](https://github.com/vuejs/vue-apollo/issues/1388), fix [#1432](https://github.com/vuejs/vue-apollo/issues/1432) ([9302d4d](https://github.com/vuejs/vue-apollo/commit/9302d4d4a55541bb49292463d8176d0527c06ce9))
* ignore next result only if not loading ([1e24d21](https://github.com/vuejs/vue-apollo/commit/1e24d2110c3ea6ee80590c2b6578fef45a2e448e))
* typo in useResult deprecation message ([#1414](https://github.com/vuejs/vue-apollo/issues/1414)) ([3728928](https://github.com/vuejs/vue-apollo/commit/372892855d76622128ac560e8fadc689c50675bc))
# [4.0.0-beta.1](https://github.com/vuejs/vue-apollo/compare/v4.0.0-alpha.20...v4.0.0-beta.1) (2022-10-05)
+2 -2
View File
@@ -10,10 +10,10 @@ pnpm install
Go to a package in `packages`.
Build the library:
Build the library with watching:
```
pnpm run build
pnpm run dev
```
Run tests:
+6 -11
View File
@@ -1,13 +1,14 @@
<p align="center">
<img src="./packages/docs/src/public/hero.svg" width="256">
</p>
# Apollo and GraphQL for Vue.js
[![npm](https://img.shields.io/npm/v/@vue/apollo-composable.svg) ![npm](https://img.shields.io/npm/dm/@vue/apollo-composable.svg)](https://www.npmjs.com/package/@vue/apollo-composable)
[![apollo3](https://img.shields.io/badge/apollo-3.x-blue.svg)](https://www.apollographql.com/)
[![vue3](https://img.shields.io/badge/vue-3-brightgreen.svg)](https://vuejs.org/)
![GitHub branch check runs](https://img.shields.io/github/check-runs/vuejs/apollo/v4)
[![CircleCI branch](https://img.shields.io/circleci/build/github/vuejs/vue-apollo/v4.svg)](https://circleci.com/gh/vuejs/vue-apollo/tree/v4)
<p align="center">
<img src="https://cdn-images-1.medium.com/max/400/1*H9AANoofLqjS10Xd5TwRYw.png">
</p>
:book: Documentation [**for Vue 3**](http://v4.apollo.vuejs.org) | [for Vue 2](https://apollo.vuejs.org/)
@@ -15,12 +16,6 @@
[:heart: Sponsor me!](https://github.com/sponsors/Akryum)
## Continuous Releases
You can install builds from any commit on the main branch from [here](https://nightly.akryum.dev/vuejs/vue-apollo) or from any Pull Request.
## Monorepo
In this monorepository:
| Package | Description |
-58
View File
@@ -1,58 +0,0 @@
// eslint.config.mjs
import antfu from '@antfu/eslint-config'
export default antfu({
ignores: [
'node_modules/',
'dist/',
'generated/',
'!.*',
'schema.graphql',
'.test-todo/',
'**/types/test/',
],
rules: {
'ts/no-use-before-define': 'warn',
'unused-imports/no-unused-vars': 'warn',
'accessor-pairs': 'off',
},
}, {
files: [
'packages/docs/**',
],
rules: {
'no-dupe-keys': 'off',
'no-new': 'off',
'no-console': 'off',
},
}, {
files: [
'packages/test-*/**',
'**/*.test.*',
],
rules: {
'antfu/no-top-level-await': 'off',
'no-console': 'off',
'unused-imports/no-unused-vars': 'off',
'node/prefer-global/process': 'off',
'import/no-mutable-exports': 'off',
},
languageOptions: {
globals: {
cy: false,
expect: false,
describe: false,
it: false,
before: false,
},
},
}, {
files: [
'**/tests/types/**',
],
rules: {
'ts/no-unused-expressions': 'off',
},
})
+16 -27
View File
@@ -1,34 +1,35 @@
{
"name": "vue-apollo-monorepo",
"version": "4.2.2",
"version": "4.0.0-beta.1",
"private": true,
"packageManager": "pnpm@10.6.2+sha512.47870716bea1572b53df34ad8647b42962bc790ce2bf4562ba0f643237d7302a3d6a8ecef9e4bdfc01d23af1969aa90485d4cebb0b9638fa5ef1daef656f6c1b",
"scripts": {
"build": "pnpm run -r --filter \"vue-apollo*\" --filter \"@vue/apollo*\" build",
"test": "pnpm run -r --sequential test",
"test:unit": "pnpm run -r test:unit",
"test:types": "pnpm run -r test:types",
"lint": "eslint . --cache",
"release": "pnpm run build && pnpm run test && sheep release -b v4",
"docs:dev": "pnpm run -r --filter \"private-vue-apollo-docs\" dev"
"test": "pnpm run -r test",
"lint": "eslint . --ext js,vue,ts",
"release": "pnpm run build && pnpm run test && sheep release -b v4"
},
"devDependencies": {
"@akryum/sheep": "^0.5.1",
"@antfu/eslint-config": "^4.7.0",
"@akryum/sheep": "^0.3.3",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vue/eslint-config-standard": "^6.1.0",
"@vue/eslint-config-typescript": "^7.0.0",
"conventional-changelog-cli": "^2.2.2",
"core-js": "^3.23.2",
"esbuild": "^0.25.0",
"esbuild-node-externals": "^1.18.0",
"eslint": "^9.21.0",
"typescript": "^5.8.2"
"esbuild": "^0.8.57",
"esbuild-node-externals": "^1.4.1",
"eslint": "^7.32.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^7.20.0",
"typescript": "^4.7.4"
},
"pnpm": {
"overrides": {
"eslint-scope": "^5",
"graphql": "^15",
"serialize-javascript": "^6.0.0",
"subscriptions-transport-ws": "^0.9"
},
@@ -50,18 +51,6 @@
"esbuild": "*",
"vue": "*"
}
},
"onlyBuiltDependencies": [
"@apollo/protobufjs",
"core-js",
"core-js-pure",
"cypress",
"esbuild",
"nodemon",
"vue-demi"
]
},
"resolutions": {
"js-yaml": "^3.13.1"
}
}
}
+3 -4
View File
@@ -1,16 +1,15 @@
{
"name": "private-vue-apollo-docs",
"type": "module",
"version": "4.0.0-alpha.16",
"private": true,
"scripts": {
"dev": "vitepress dev src",
"build": "vitepress build src"
},
"devDependencies": {
"vitepress": "^0.22.4"
},
"dependencies": {
"vue-github-button": "^3.0.3"
},
"devDependencies": {
"vitepress": "^1.0.0-rc.36"
}
}
+340 -341
View File
@@ -7,341 +7,334 @@ export default defineConfig({
head: [
['link', { rel: 'icon', href: '/favicon.png' }],
],
themeConfig: {
lastUpdated: true,
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/apollo' },
],
footer: {
message: `Released under the MIT License.`,
copyright: `Copyright © 2015-present Guillaume Chau`,
},
editLink: {
pattern: 'https://github.com/vuejs/apollo/edit/v4/packages/docs/src/:path',
},
nav: [
{
text: 'Guide',
items: [
{
text: 'Getting started',
link: '/guide/',
},
{
text: 'Option API',
link: '/guide-option/',
},
{
text: 'Composition API',
link: '/guide-composable/',
},
{
text: 'Component API',
link: '/guide-components/',
},
{
text: 'Advanced topics',
link: '/guide-advanced/',
},
],
},
{
text: 'API Reference',
link: '/api/',
},
{
text: 'Migration',
link: '/migration/',
},
{
text: 'CLI plugin',
link: 'https://github.com/Akryum/vue-cli-plugin-apollo',
},
{
text: 'Sponsor',
link: 'https://github.com/sponsors/Akryum',
},
],
sidebar: {
'/guide/': [
{
text: 'Introduction',
link: '/guide/',
},
{
text: 'Installation',
link: '/guide/installation',
},
],
'/guide-option/': [
{
text: 'Option API Guide',
collapsable: false,
items: [
{
text: 'Introduction',
link: '/guide-option/',
},
{
text: 'Setup',
link: '/guide-option/setup',
},
],
},
{
text: 'Basics',
collapsable: false,
items: [
{
text: 'Usage in Vue components',
link: '/guide-option/usage',
},
{
text: 'Queries',
link: '/guide-option/queries',
},
{
text: 'Mutations',
link: '/guide-option/mutations',
},
{
text: 'Subscriptions',
link: '/guide-option/subscriptions',
},
],
},
{
text: 'Advanced',
collapsable: false,
items: [
{
text: 'Special options',
link: '/guide-option/special-options',
},
{
text: 'Pagination',
link: '/guide-option/pagination',
},
{
text: 'Multiple clients',
link: '/guide-option/multiple-clients',
},
],
},
],
'/guide-composable/': [
{
text: 'Composition API Guide',
collapsable: false,
items: [
{
text: 'Introduction',
link: '/guide-composable/',
},
{
text: 'Setup',
link: '/guide-composable/setup',
},
],
},
{
text: 'Fetching data',
collapsable: false,
items: [
{
text: 'Queries',
link: '/guide-composable/query',
},
{
text: 'Mutations',
link: '/guide-composable/mutation',
},
{
text: 'Subscriptions',
link: '/guide-composable/subscription',
},
{
text: 'Pagination',
link: '/guide-composable/pagination',
},
{
text: 'Fragments',
link: '/guide-composable/fragments',
},
{
text: 'Error handling',
link: '/guide-composable/error-handling',
},
],
},
],
'/guide-components/': [
{
text: 'Components Guide',
collapsable: false,
items: [
{
text: 'Introduction',
link: '/guide-components/',
},
{
text: 'Setup',
link: '/guide-components/setup',
},
],
},
{
text: 'Usage',
collapsable: false,
items: [
{
text: 'Queries',
link: '/guide-components/query',
},
{
text: 'Mutations',
link: '/guide-components/mutation',
},
{
text: 'Subscribe to a Query',
link: '/guide-components/subscribe-to-more',
},
],
},
],
'/guide-advanced/': [
{
text: 'Advanced topics',
collapsable: false,
items: [
{
text: 'Local state',
link: '/guide-advanced/local-state',
},
{
text: 'Server-Side Rendering',
link: '/guide-advanced/ssr',
},
{
text: 'Testing',
link: '/guide-advanced/testing',
},
],
},
],
'/api/': [
{
text: 'Option API',
collapsable: false,
items: [
{
text: 'createApolloProvider',
link: '/api/apollo-provider',
},
{
text: '$apollo',
link: '/api/dollar-apollo',
},
{
text: 'Reactive queries',
link: '/api/smart-query',
},
{
text: 'Reactive subscriptions',
link: '/api/smart-subscription',
},
],
},
{
text: 'Composition API',
collapsable: false,
items: [
{
text: 'useQuery',
link: '/api/use-query',
},
{
text: 'useLazyQuery',
link: '/api/use-lazy-query',
},
{
text: 'useMutation',
link: '/api/use-mutation',
},
{
text: 'useSubscription',
link: '/api/use-subscription',
},
{
text: 'useApolloClient',
link: '/api/use-apollo-client',
},
{
text: 'Loading utilities',
link: '/api/use-loading',
},
],
},
{
text: 'Components',
collapsable: false,
items: [
{
text: 'ApolloQuery',
link: '/api/apollo-query',
},
{
text: 'ApolloMutation',
link: '/api/apollo-mutation',
},
{
text: 'ApolloSubscribeToMore',
link: '/api/apollo-subscribe-to-more',
},
],
},
{
text: 'Advanced',
collapsable: false,
items: [
{
text: 'ApolloSSR',
link: '/api/ssr',
},
],
},
],
'/migration/': [
{
text: 'Migration guide',
link: '/migration/',
},
],
},
search: {
provider: 'local',
options: {
detailedView: true,
},
},
},
locales: {
'root': {
label: 'English',
'/': {
lang: 'en-US',
title: 'Vue Apollo',
description: '🚀 Integrate GraphQL in your Vue.js apps!',
},
'zh-cn': {
label: '简体中文',
'/zh-cn/': {
lang: 'zh-CN',
title: 'Vue Apollo',
description: '🚀 在你的 Vue.js 应用中集成 GraphQL',
themeConfig: {
lastUpdated: {
message: '上次更新时间',
},
},
themeConfig: {
repo: 'Akryum/vue-apollo',
docsBranch: 'v4',
docsDir: 'packages/docs/src',
editLinks: true,
editLinkText: 'Suggest changes to this page',
locales: {
'/': {
selectText: 'Languages',
label: 'English',
lastUpdated: 'Last Updated',
nav: [
{
text: 'Guide',
items: [
{
text: 'Getting started',
link: '/guide/',
},
{
text: 'Option API',
link: '/guide-option/',
},
{
text: 'Composition API',
link: '/guide-composable/',
},
{
text: 'Component API',
link: '/guide-components/',
},
{
text: 'Advanced topics',
link: '/guide-advanced/',
},
],
},
{
text: 'API Reference',
link: '/api/',
},
{
text: 'Migration',
link: '/migration/',
},
{
text: 'CLI plugin',
link: 'https://github.com/Akryum/vue-cli-plugin-apollo',
},
{
text: 'Sponsor',
link: 'https://github.com/sponsors/Akryum',
},
],
sidebarDepth: 2,
sidebar: {
'/guide/': [
{
text: 'Introduction',
link: '/guide/',
},
{
text: 'Installation',
link: '/guide/installation',
},
],
'/guide-option/': [
{
text: 'Option API Guide',
collapsable: false,
children: [
{
text: 'Introduction',
link: '/guide-option/',
},
{
text: 'Setup',
link: '/guide-option/setup',
},
],
},
{
text: 'Basics',
collapsable: false,
children: [
{
text: 'Usage in Vue components',
link: '/guide-option/usage',
},
{
text: 'Queries',
link: '/guide-option/queries',
},
{
text: 'Mutations',
link: '/guide-option/mutations',
},
{
text: 'Subscriptions',
link: '/guide-option/subscriptions',
},
],
},
{
text: 'Advanced',
collapsable: false,
children: [
{
text: 'Special options',
link: '/guide-option/special-options',
},
{
text: 'Pagination',
link: '/guide-option/pagination',
},
{
text: 'Multiple clients',
link: '/guide-option/multiple-clients',
},
],
},
],
'/guide-composable/': [
{
text: 'Composition API Guide',
collapsable: false,
children: [
{
text: 'Introduction',
link: '/guide-composable/',
},
{
text: 'Setup',
link: '/guide-composable/setup',
},
],
},
{
text: 'Fetching data',
collapsable: false,
children: [
{
text: 'Queries',
link: '/guide-composable/query',
},
{
text: 'Mutations',
link: '/guide-composable/mutation',
},
{
text: 'Subscriptions',
link: '/guide-composable/subscription',
},
{
text: 'Pagination',
link: '/guide-composable/pagination',
},
{
text: 'Fragments',
link: '/guide-composable/fragments',
},
{
text: 'Error handling',
link: '/guide-composable/error-handling',
},
],
},
],
'/guide-components/': [
{
text: 'Components Guide',
collapsable: false,
children: [
{
text: 'Introduction',
link: '/guide-components/',
},
{
text: 'Setup',
link: '/guide-components/setup',
},
],
},
{
text: 'Usage',
collapsable: false,
children: [
{
text: 'Queries',
link: '/guide-components/query',
},
{
text: 'Mutations',
link: '/guide-components/mutation',
},
{
text: 'Subscribe to a Query',
link: '/guide-components/subscribe-to-more',
},
],
},
],
'/guide-advanced/': [
{
text: 'Advanced topics',
collapsable: false,
children: [
{
text: 'Local state',
link: '/guide-advanced/local-state',
},
{
text: 'Server-Side Rendering',
link: '/guide-advanced/ssr',
},
{
text: 'Testing',
link: '/guide-advanced/testing',
},
],
},
],
'/api/': [
{
text: 'Option API',
collapsable: false,
children: [
{
text: 'createApolloProvider',
link: '/api/apollo-provider',
},
{
text: '$apollo',
link: '/api/dollar-apollo',
},
{
text: 'Reactive queries',
link: '/api/smart-query',
},
{
text: 'Reactive subscriptions',
link: '/api/smart-subscription',
},
],
},
{
text: 'Composition API',
collapsable: false,
children: [
{
text: 'useQuery',
link: '/api/use-query',
},
{
text: 'useLazyQuery',
link: '/api/use-lazy-query',
},
{
text: 'useMutation',
link: '/api/use-mutation',
},
{
text: 'useSubscription',
link: '/api/use-subscription',
},
{
text: 'useApolloClient',
link: '/api/use-apollo-client',
},
{
text: 'Loading utilities',
link: '/api/use-loading',
},
],
},
{
text: 'Components',
collapsable: false,
children: [
{
text: '<ApolloQuery>',
link: '/api/apollo-query',
},
{
text: '<ApolloMutation>',
link: '/api/apollo-mutation',
},
{
text: '<ApolloSubscribeToMore>',
link: '/api/apollo-subscribe-to-more',
},
],
},
{
text: 'Advanced',
collapsable: false,
children: [
{
text: 'ApolloSSR',
link: '/api/ssr',
},
],
},
],
'/migration/': [
{
text: 'Migration guide',
link: '/migration/',
},
],
},
},
'/zh-cn/': {
selectText: '选择语言',
label: '简体中文',
lastUpdated: '上次更新时间',
nav: [
{
text: '指南',
@@ -385,6 +378,7 @@ export default defineConfig({
link: 'https://github.com/sponsors/Akryum',
},
],
sidebarDepth: 3,
sidebar: {
'/zh-cn/guide/': [
{
@@ -400,7 +394,7 @@ export default defineConfig({
{
text: '选项 API 指南',
collapsable: false,
items: [
children: [
{
text: 'Introduction',
link: '/zh-cn/guide-option/',
@@ -414,7 +408,7 @@ export default defineConfig({
{
text: '基础',
collapsable: false,
items: [
children: [
{
text: 'Usage in Vue components',
link: '/zh-cn/guide-option/usage',
@@ -436,7 +430,7 @@ export default defineConfig({
{
text: '进阶',
collapsable: false,
items: [
children: [
{
text: 'Special options',
link: '/zh-cn/guide-option/special-options',
@@ -456,7 +450,7 @@ export default defineConfig({
{
text: '组合 API 指南',
collapsable: false,
items: [
children: [
{
text: 'Introduction',
link: '/zh-cn/guide-composable/',
@@ -470,7 +464,7 @@ export default defineConfig({
{
text: '获取数据',
collapsable: false,
items: [
children: [
{
text: 'Queries',
link: '/zh-cn/guide-composable/query',
@@ -502,7 +496,7 @@ export default defineConfig({
{
text: '组件指南',
collapsable: false,
items: [
children: [
{
text: 'Introduction',
link: '/zh-cn/guide-components/',
@@ -516,7 +510,7 @@ export default defineConfig({
{
text: '用法',
collapsable: false,
items: [
children: [
{
text: 'Queries',
link: '/zh-cn/guide-components/query',
@@ -536,7 +530,7 @@ export default defineConfig({
{
text: '进阶主题',
collapsable: false,
items: [
children: [
{
text: 'Local state',
link: '/zh-cn/guide-advanced/local-state',
@@ -556,7 +550,7 @@ export default defineConfig({
{
text: '选项 API',
collapsable: false,
items: [
children: [
{
text: 'createApolloProvider',
link: '/zh-cn/api/apollo-provider',
@@ -578,7 +572,7 @@ export default defineConfig({
{
text: '组合 API',
collapsable: false,
items: [
children: [
{
text: 'useQuery',
link: '/zh-cn/api/use-query',
@@ -608,17 +602,17 @@ export default defineConfig({
{
text: '组件',
collapsable: false,
items: [
children: [
{
text: 'ApolloQuery',
text: '<ApolloQuery>',
link: '/zh-cn/api/apollo-query',
},
{
text: 'ApolloMutation',
text: '<ApolloMutation>',
link: '/zh-cn/api/apollo-mutation',
},
{
text: 'ApolloSubscribeToMore',
text: '<ApolloSubscribeToMore>',
link: '/zh-cn/api/apollo-subscribe-to-more',
},
],
@@ -626,7 +620,7 @@ export default defineConfig({
{
text: '进阶',
collapsable: false,
items: [
children: [
{
text: 'ApolloSSR',
link: '/zh-cn/api/ssr',
@@ -643,6 +637,11 @@ export default defineConfig({
},
},
},
algolia: {
appId: 'X6FFODVB9N',
apiKey: 'cc89b1eff7e2fc6e6c0bbf8b066ab488',
indexName: 'apollo-vuejs',
},
},
vite: {
+4 -4
View File
@@ -1,11 +1,11 @@
import DefaultTheme from 'vitepress/theme'
import SponsorButton from './components/SponsorButton.vue'
import './styles/index.pcss'
import DefaultTheme from 'vitepress/dist/client/theme-default'
import SponsorButton from './components/SponsorButton.vue'
export default {
...DefaultTheme,
enhanceApp({ app }) {
enhanceApp ({ app }) {
app.component('SponsorButton', SponsorButton)
},
}
@@ -1,6 +1,3 @@
:root {
--vp-c-brand-1: #5591d8;
--vp-c-brand-2: #336cb0;
--vp-c-brand-3: #1f4c80;
--vp-c-brand-soft: rgba(42, 95, 156, 0.14);
.home .hero img {
max-width: 80vw;
}
+1 -1
View File
@@ -209,7 +209,7 @@ See [apollo context](https://www.apollographql.com/docs/react/api/link/apollo-li
Signature:
```ts
function mutate(options = null): Promise<FetchResult>
mutate(options = null): Promise<FetchResult>
```
- `options`: [mutation options](https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.mutate).
+2 -2
View File
@@ -26,12 +26,12 @@ const apolloProvider = createApolloProvider({
},
// Watch loading state for all queries
// See 'Smart Query > options > watchLoading' for detail
watchLoading(isLoading, countModifier) {
watchLoading (isLoading, countModifier) {
loading += countModifier
console.log('Global loading', loading, countModifier)
},
// Global error handler for all smart queries and subscriptions
errorHandler(error) {
errorHandler (error) {
console.log('Global error handler')
console.error(error)
},
+52 -58
View File
@@ -24,63 +24,61 @@ Each query declared in the `apollo` definition (that is, which doesn't start wit
Example:
```js
export default {
// Apollo-specific options
apollo: {
apollo: {
// Advanced query with parameters
// The 'variables' method is watched by vue
pingMessage: {
query: gql`query PingMessage($message: String!) {
pingMessage: {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// Reactive parameters
variables() {
// Reactive parameters
variables () {
// Use vue reactive properties here
return {
message: this.pingInput,
}
},
// Polling interval in milliseconds
pollInterval: 10000,
// Or, set polling interval as a vue reactive property
pollInterval() {
return this.pollInterval
},
// Variables: deep object watch
deep: false,
// We use a custom update callback because
// the field names don't match
// By default, the 'pingMessage' attribute
// would be used on the 'data' result object
// Here we know the result is in the 'ping' attribute
// considering the way the apollo server works
update(data) {
console.log(data)
// The returned value will update
// the vue property 'pingMessage'
return data.ping
},
// Optional result hook
result({ data, loading, networkStatus }) {
console.log('We got some result!')
},
// Error handling
error(error) {
console.error('We\'ve got an error!', error)
},
// Loading state
// loadingKey is the name of the data property
// that will be incremented when the query is loading
// and decremented when it no longer is.
loadingKey: 'loadingQueriesCount',
// watchLoading will be called whenever the loading state changes
watchLoading(isLoading, countModifier) {
return {
message: this.pingInput,
}
},
// Polling interval in milliseconds
pollInterval: 10000,
// Or, set polling interval as a vue reactive property
pollInterval() {
return this.pollInterval;
},
// Variables: deep object watch
deep: false,
// We use a custom update callback because
// the field names don't match
// By default, the 'pingMessage' attribute
// would be used on the 'data' result object
// Here we know the result is in the 'ping' attribute
// considering the way the apollo server works
update (data) {
console.log(data)
// The returned value will update
// the vue property 'pingMessage'
return data.ping
},
// Optional result hook
result ({ data, loading, networkStatus }) {
console.log('We got some result!')
},
// Error handling
error (error) {
console.error('We\'ve got an error!', error)
},
// Loading state
// loadingKey is the name of the data property
// that will be incremented when the query is loading
// and decremented when it no longer is.
loadingKey: 'loadingQueriesCount',
// watchLoading will be called whenever the loading state changes
watchLoading (isLoading, countModifier) {
// isLoading is a boolean
// countModifier is either 1 or -1
},
},
},
}
},
```
If you use `ES2015`, you can also write the `update` like this:
@@ -92,18 +90,14 @@ update: data => data.ping
Manual mode example:
```js
export default {
apollo: {
myQuery: {
query: gql`...`,
manual: true,
result({ data, loading }) {
if (!loading) {
this.items = data.items
}
},
{
query: gql`...`,
manual: true,
result ({ data, loading }) {
if (!loading) {
this.items = data.items
}
}
},
}
```
+3 -3
View File
@@ -17,7 +17,7 @@ const states = ApolloSSR.getStates(clientsObject, options)
`options` defaults to:
```js
const defaultOptions = {
{
// Prefix for the keys of each apollo client state
exportNamespace: '',
}
@@ -34,14 +34,14 @@ const js = ApolloSSR.exportStates(clientsObject, options)
`options` defaults to:
```js
const defaultOptions = {
{
// Global variable name
globalName: '__APOLLO_STATE__',
// Global object on which the variable is set
attachTo: 'window',
// Prefix for the keys of each apollo client state
exportNamespace: '',
// By default we use sanitize js library to prevent XSS
// By default we use sanitize js library to prevent XSS
// pass true here will perform a standard JSON.stringify on the states
useUnsafeSerializer: false,
}
+1 -21
View File
@@ -4,24 +4,4 @@ Extends [useQuery](./use-query.md)
## Additional Return
- `load(document?, variables?, options?)`: function to start querying. Returns `Promise<Result>` if it is the first time the query is called, `false` otherwise.
Example:
```js
const { load, refetch } = useLazyQuery(query, variables, options)
function fetchOrRefetch() {
load() || refetch()
}
async function waitForLoad() {
try {
const result = await load()
// do something with result
}
catch (error) {
// handle error
}
}
```
- `load(document?, variables?, options?)`: function to start querying.
+2 -2
View File
@@ -12,8 +12,8 @@ import { useQuery, useQueryLoading } from '@vue/apollo-composable'
export default {
setup () {
const { result: one } = useQuery(/* ... */)
const { result: two } = useQuery(/* ... */)
const { result: one } = useQuery(...)
const { result: two } = useQuery(...)
const loading = useQueryLoading()
return {
+2 -2
View File
@@ -47,6 +47,6 @@
- `called`: boolean `Ref` holding `true` if the mutation was already called.
- `onDone(handler)`: Event hook called when the mutation successfully completes. Handler is called with: `result` (mutation result) and `context` which is an object with `client` (ApolloClient instance).
- `onDone`: Event hook called when the mutation successfully completes.
- `onError(handler)`: Event hook called when an error occurs. Handler is called with: `error` and `context` which is an object with `client` (ApolloClient instance).
- `onError`: Event hook called when an error occurs.
+5 -4
View File
@@ -28,6 +28,8 @@
- `network-only`: return result from network, fail if network call doesn't succeed, save to cache.
- `no-cache`: return result from network, fail if network call doesn't succeed, don't save to cache.
- `fetchResults`: Whether or not to fetch results.
- `metadata`: Arbitrary metadata stored in the store with this query. Designed for debugging, developer tools, etc.
- `notifyOnNetworkStatusChange`: Whether or not updates to the network status should trigger next on the observer of this query.
@@ -40,8 +42,6 @@
- `throttle`: Throttle interval in ms.
- `keepPreviousResult`: (default: `false`) Whether or not to keep previous result when the query is fetch again (for example when a variable changes). This can be useful to prevent a flash of empty content.
## Return
- `result`: result data object.
@@ -58,6 +58,7 @@
- `subscribeToMore(options)`: Add a subscription to the query, useful to add new data received from the server in real-time. See [Subscription](../guide-composable/subscription#subscribetomore).
- `onResult(handler)`: Event hook called when a new result is available. Handler is called with: `result` (query result) and `context` which is an object with `client` (ApolloClient instance).
- `onResult(handler)`: Event hook called when a new result is available.
- `onError(handler)`: Event hook called when an error occurs.
- `onError(handler)`: Event hook called when an error occurs. Handler is called with: `error` and `context` which is an object with `client` (ApolloClient instance).
+3 -2
View File
@@ -33,6 +33,7 @@
- `variables`: Ref holding the variables object.
- `onResult(handler)`: Event hook called when a new result is available. Handler is called with: `result` (new result) and `context` which is an object with `client` (ApolloClient instance).
- `onResult(handler)`: Event hook called when a new result is available.
- `onError(handler)`: Event hook called when an error occurs.
- `onError(handler)`: Event hook called when an error occurs. Handler is called with: `error` and `context` which is an object with `client` (ApolloClient instance).
+34 -36
View File
@@ -17,7 +17,7 @@ Just how creating a GraphQL schema is the first step toward defining our data mo
Let's create a local schema to describe an item that will serve as a single element of todo-items list. This item should have some text, some property to define if it's already done or not and also an ID to distinguish one todo-item from another. So, it should be an object with three properties:
```js
const obj = {
{
id: 'uniqueId',
text: 'some text',
done: false
@@ -27,9 +27,9 @@ const obj = {
Now we're ready to add an `Item` type to our local GraphQL schema.
```js
// main.js
//main.js
import gql from 'graphql-tag'
import gql from 'graphql-tag';
export const typeDefs = gql`
type Item {
@@ -37,7 +37,7 @@ export const typeDefs = gql`
text: String!
done: Boolean!
}
`
`;
```
`gql` here stands for the JavaScript template literal tag that parses GraphQL query strings.
@@ -63,7 +63,7 @@ You can not only create a local schema from scratch but also add a local **virtu
Imagine we have a type `User` in our remote schema:
```gql
```js
type User {
name: String!
age: Int!
@@ -77,7 +77,7 @@ export const schema = gql`
extend type User {
twitter: String
}
`
`;
```
Now, when querying a user, we will need to specify `twitter` field is local:
@@ -89,7 +89,7 @@ const userQuery = gql`
age
twitter @client
}
`
`;
```
## Initializing an Apollo cache
@@ -151,7 +151,7 @@ Querying local cache is very similar to [sending GraphQL queries to remote serve
```js
// App.vue
import gql from 'graphql-tag'
import gql from 'graphql-tag';
const todoItemsQuery = gql`
{
@@ -161,7 +161,7 @@ const todoItemsQuery = gql`
done
}
}
`
`;
```
The main difference with queries to remote API is `@client` directive. This directive specifies that this query should not be executed against remote GraqhQL API. Instead, Apollo client should fetch results from the local cache.
@@ -169,13 +169,13 @@ The main difference with queries to remote API is `@client` directive. This dire
Now, we can use this query in our Vue component as a usual Apollo query:
```js
export default {
apollo: {
todoItems: {
query: todoItemsQuery
}
},
}
// App.vue
apollo: {
todoItems: {
query: todoItemsQuery
}
},
```
## Change local data with mutations
@@ -213,7 +213,7 @@ const checkItemMutation = gql`
mutation($id: ID!) {
checkItem(id: $id) @client
}
`
`;
```
We defined a _local_ mutation (because we have a `@client` directive here) that will accept a unique identifier as a parameter. Now, we need a _resolver_: a function that resolves a value for a type or field in a schema.
@@ -228,14 +228,13 @@ Let's add a resolver to our main file:
const resolvers = {
Mutation: {
checkItem: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: todoItemsQuery })
const currentItem = data.todoItems.find(item => item.id === id)
currentItem.done = !currentItem.done
cache.writeQuery({ query: todoItemsQuery, data })
return currentItem.done
const data = cache.readQuery({ query: todoItemsQuery });
const currentItem = data.todoItems.find(item => item.id === id);
currentItem.done = !currentItem.done;
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
}
}
};
```
What are we doing here?
@@ -260,27 +259,26 @@ const resolvers = {
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
},
}
};
const apolloClient = new ApolloClient({
cache,
typeDefs,
resolvers,
})
});
```
After this, we can use the mutation in our Vue component like normal [mutations](../guide-option/mutations.md):
```js
export default {
methods: {
checkItem(id) {
this.$apollo.mutate({
mutation: checkItemMutation,
variables: { id }
})
},
}
// App.vue
methods: {
checkItem(id) {
this.$apollo.mutate({
mutation: checkItemMutation,
variables: { id }
});
},
}
```
+1 -1
View File
@@ -75,7 +75,7 @@ export default {
description
}
}`,
variables() {
variables () {
return {
id: this.id,
}
+6 -5
View File
@@ -68,6 +68,7 @@ const sourceSchema = `
twitter: String
}
type Query {
allHeroes: [VueHero]
}
@@ -75,14 +76,14 @@ const sourceSchema = `
type Mutation {
addHero(hero: HeroInput!): VueHero!
deleteHero(name: String!): Boolean
}
}
`
```
Next step is to create an executable schema with `graphql-tools` method:
```js
import { makeExecutableSchema } from 'graphql-tools'
// ...
...
const schema = makeExecutableSchema({
typeDefs: sourceSchema,
})
@@ -91,7 +92,7 @@ After this you need to add mock functions to schema:
```js
import { addMockFunctionsToSchema } from 'graphql-tools'
// ...
...
addMockFunctionsToSchema({
schema,
})
@@ -114,7 +115,7 @@ const query = `
Call GraphQL query in the test case, save response to component data and then check if rendered component matches a snapshot:
```js
graphql(schema, query).then((result) => {
graphql(schema, query).then(result => {
wrapper.setData(result.data)
expect(wrapper.element).toMatchSnapshot()
})
@@ -152,4 +153,4 @@ addMockFunctionsToSchema({
```
You can test mutations in the same way.
---
---
+11 -11
View File
@@ -1,6 +1,6 @@
# ApolloQuery
You can use the `ApolloQuery` (or `apollo-query`) component to have watched Apollo queries directly in your template.
You can use the `ApolloQuery` (or `apollo-query`) component to have watched Apollo queries directly in your template.
After reading this page, see the [API Reference](../api/apollo-query.md) for all the possible options.
## Query gql tag
@@ -95,19 +95,19 @@ If you are not using [vue-cli-plugin-apollo](https://github.com/Akryum/vue-cli-p
// vue.config.js
module.exports = {
chainWebpack: (config) => {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap((options) => {
options.transpileOptions = {
transforms: {
dangerousTaggedTemplateString: true,
},
}
return options
})
.loader('vue-loader')
.tap(options => {
options.transpileOptions = {
transforms: {
dangerousTaggedTemplateString: true,
},
}
return options
})
}
}
```
+1 -2
View File
@@ -25,6 +25,7 @@ const apolloClient = new ApolloClient({
cache,
uri: 'http://localhost:4042/graphql',
})
```
::: warning
@@ -36,8 +37,6 @@ Use the `@apollo/client/core` import path otherwise you will also import React.
The provider holds the Apollo client instances that can then be used by all the child components.
```js
import { createApolloProvider } from '@vue/apollo-option'
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
})
@@ -66,20 +66,18 @@ See [API Reference](../api/apollo-subscribe-to-more.md) for all the available op
Add a new item to the cache:
```js
export default {
methods: {
onMessageAdded(previousResult, { subscriptionData }) {
// The previous result is immutable
const newResult = {
...previousResult,
messages: [
...previousResult.messages,
// Add the question to the list
subscriptionData.data.messageAdded,
],
}
return newResult
methods: {
onMessageAdded (previousResult, { subscriptionData }) {
// The previous result is immutable
const newResult = {
...previousResult,
messages: [
...previousResult.messages,
// Add the question to the list
subscriptionData.data.messageAdded,
],
}
return newResult
}
}
```
@@ -87,26 +85,23 @@ export default {
Remove an item from the cache:
```js
export default {
methods: {
onMessageAdded(previousResult, { subscriptionData }) {
const removedMessage = subscriptionData.data.messageRemoved
const index = previousResult.messages.findIndex(
m => m.id === removedMessage.id
)
methods: {
onMessageAdded (previousResult, { subscriptionData }) {
const removedMessage = subscriptionData.data.messageRemoved
const index = previousResult.messages.findIndex(
m => m.id === removedMessage.id
)
if (index === -1)
return previousResult
if (index === -1) return previousResult
// The previous result is immutable
const newResult = {
...previousResult,
messages: [...previousResult.messages],
}
// Remove the question from the list
newResult.messages.splice(index, 1)
return newResult
// The previous result is immutable
const newResult = {
...previousResult,
messages: [...previousResult.messages],
}
// Remove the question from the list
newResult.messages.splice(index, 1)
return newResult
}
}
```
@@ -61,16 +61,14 @@ When using Apollo Link, the ability to handle network errors is way more powerfu
import { onError } from '@apollo/client/link/error'
const link = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
)
}
if (networkError)
console.log(`[Network error]: ${networkError}`)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
```
@@ -80,7 +78,7 @@ You can also use the `logErrorMessages` function from the `@vue/apollo-util` pac
import { onError } from '@apollo/client/link/error'
import { logErrorMessages } from '@vue/apollo-util'
const link = onError((error) => {
const link = onError(error => {
logErrorMessages(error)
})
```
@@ -95,7 +93,7 @@ If you are using Webpack or Vue CLI, it's a good idea to only use it in developm
import { onError } from '@apollo/client/link/error'
import { logErrorMessages } from '@vue/apollo-util'
const link = onError((error) => {
const link = onError(error => {
if (process.env.NODE_ENV !== 'production') {
logErrorMessages(error)
}
@@ -107,19 +105,19 @@ That way it will be dropped when compiling the project for production.
Full example:
```js
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client/core'
import { onError } from '@apollo/client/link/error'
import { logErrorMessages } from '@vue/apollo-util'
const cache = new InMemoryCache()
// HTTP connection to the API
const httpLink = createHttpLink({
let httpLink = createHttpLink({
uri: 'http://localhost:4242/graphql',
})
// Handle errors
const errorLink = onError((error) => {
const errorLink = onError(error => {
if (process.env.NODE_ENV !== 'production') {
logErrorMessages(error)
}
@@ -120,8 +120,8 @@ export const entryFragment = gql`
If our fragments include sub-fragments then we can pass them into the `gql` helper:
```js
import { entryFragment as RepoInfoEntryFragment } from './RepoInfo.vue'
import { entryFragment as VoteButtonsEntryFragment } from './VoteButtons.vue'
import { entryFragment as RepoInfoEntryFragment } from './RepoInfo.vue'
export const entryFragment = gql`
fragment FeedEntry on Entry {
@@ -216,11 +216,11 @@ Read the documentation about how to [extract possibleTypes automatically](https:
2. Use `possibleTypes.json` to configure your cache during construction. Then, you pass your newly configured cache to `ApolloClient` to complete the process.
```js
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client/core'
import possibleTypes from './possibleTypes.json'
const cache = new InMemoryCache({ possibleTypes })
const httpLink = createHttpLink({ uri })
const httpLink = createHttpLink({ uri });
const client = new ApolloClient({
cache,
@@ -487,9 +487,9 @@ const { mutate: sendMessage, error: sendMessageError } = useMutation(gql`
This is called when the mutation successfully completes.
```js
const { onDone } = useMutation(/* ... */)
const { onDone } = useMutation(...)
onDone((result) => {
onDone(result => {
console.log(result.data)
})
```
@@ -575,9 +575,9 @@ This is triggered when an error occurs during the mutation.
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useMutation(/* ... */)
const { onError } = useMutation(...)
onError((error) => {
onError(error => {
logErrorMessages(error)
})
```
@@ -11,7 +11,7 @@ In this article, we'll cover the technical details of using Apollo to implement
In Apollo, the easiest way to do pagination is with a function called `fetchMore`, which is returned by the `useQuery` composition function. This basically allows you to do a new GraphQL query and merge the result into the original result.
```js
const { fetchMore } = useQuery(/* ... */)
const { fetchMore } = useQuery(...)
```
You can specify what query and variables to use for the new query, and how to merge the new query result with the existing data on the client. How exactly you do that will determine what kind of pagination you are implementing.
+11 -107
View File
@@ -461,7 +461,7 @@ This also means you can pass `props` from `setup` directly, since `props` is alr
export default {
props: ['id'],
setup(props) {
setup (props) {
const { result } = useQuery(gql`
query getUserById ($id: ID!) {
user (id: $id) {
@@ -525,7 +525,7 @@ const { result } = useQuery(gql`
id: id.value
}))
function selectUser(id) {
function selectUser (id) {
id.value = id
}
```
@@ -602,7 +602,7 @@ const { result } = useQuery(gql`
enabled: enabled.value,
}))
function enableQuery() {
function enableQuery () {
enabled.value = true
}
```
@@ -700,63 +700,6 @@ export default {
</template>
```
### Providing new variables to `refetch`
You call `refetch` with a new set of variables like so:
```vue{41}
<script>
import { useQuery } from '@vue/apollo-composable'
import gql from 'graphql-tag'
export default {
setup () {
const { result, loading, error, refetch } = useQuery(gql`
query getUsers($search: String) {
users(search: $search) {
id
firstname
lastname
email
}
}
`)
const users = computed(() => result.value?.users)
return {
users,
loading,
error,
refetch,
}
},
}
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<ul v-else-if="users">
<li v-for="user of users" :key="user.id">
{{ user.firstname }} {{ user.lastname }}
</li>
<button
@click="refetch({ search: 'some search input' })"
>
Search "some search input"
</button>
</ul>
</template>
```
::: warning
If you provide new values for **some** of your original query's variables but not **all** of them, `refetch` uses each omitted variable's original value.
:::
## Event hooks
`useQuery` returns event hooks allowing you to execute code when a specific event occurs.
@@ -766,9 +709,9 @@ If you provide new values for **some** of your original query's variables but no
This is called whenever a new result is available.
```js
const { onResult } = useQuery(/* ... */)
const { onResult } = useQuery(...)
onResult((queryResult) => {
onResult(queryResult => {
console.log(queryResult.data)
console.log(queryResult.loading)
console.log(queryResult.networkStatus)
@@ -791,9 +734,9 @@ useQuery(gql`
It is triggered when an error occurs:
```js
const { onError } = useQuery(/* ... */)
const { onError } = useQuery(...)
onError((error) => {
onError(error => {
console.log(error.graphQLErrors)
console.log(error.networkError)
})
@@ -804,9 +747,9 @@ You can use the `logErrorMessages` function from the `@vue/apollo-util` package
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useQuery(/* ... */)
const { onError } = useQuery(...)
onError((error) => {
onError(error => {
logErrorMessages(error)
})
```
@@ -820,9 +763,9 @@ If you are using Webpack or Vue CLI, it's a good idea to only use it in developm
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useQuery(/* ... */)
const { onError } = useQuery(...)
onError((error) => {
onError(error => {
if (process.env.NODE_ENV !== 'production') {
logErrorMessages(error)
}
@@ -881,42 +824,3 @@ export default {
</div>
</template>
```
### Getting the result
`load()` returns a Promise to the result of the first request if it's the first time the query is activated.
```js
const { result, load, refetch } = useLazyQuery(gql`
query list {
list
}
`)
// ...
async function myLoad() {
try {
const result = await load()
}
catch (e) {
// Handle error
}
}
```
### Refetch lazy query
`load()` returns `false` if it is not the first time the query is activated. You can use this to refetch the query with `refetch()` in case the user clicks on the button again, meaning `load()` returns `false`.
```js
const { result, load, refetch } = useLazyQuery(gql`
query list {
list
}
`)
// ...
function loadOrRefetch() {
load() || refetch()
}
```
+9 -14
View File
@@ -21,11 +21,11 @@ yarn add @vue/apollo-composable
In your root instance, you need to provide a default Apollo Client instance:
```js
import { DefaultApolloClient } from '@vue/apollo-composable'
import { provide } from '@vue/composition-api'
import { DefaultApolloClient } from '@vue/apollo-composable'
const app = new Vue({
setup() {
setup () {
provide(DefaultApolloClient, apolloClient)
},
@@ -40,11 +40,11 @@ In the rest of the guide, we will show code examples with Vue 3. If you need Vue
### Vue 3
```js
import { createApp, provide, h } from 'vue'
import { DefaultApolloClient } from '@vue/apollo-composable'
import { createApp, h, provide } from 'vue'
const app = createApp({
setup() {
setup () {
provide(DefaultApolloClient, apolloClient)
},
@@ -57,11 +57,11 @@ const app = createApp({
You can also provide multiple Apollo Client instances to be used in your application. In this case, it's recommended to provide a `default` one:
```js
import { ApolloClients } from '@vue/apollo-composable'
import { provide } from 'vue'
import { ApolloClients } from '@vue/apollo-composable'
const app = new Vue({
setup() {
setup () {
provide(ApolloClients, {
default: apolloClient,
})
@@ -90,20 +90,15 @@ When using e.g. `useQuery` outside of vue contexts, the clients cannot be inject
Use `provideApolloClient` for a single default client:
```js
import { provideApolloClient } from '@vue/apollo-composable'
import { provideApolloClient } from "@vue/apollo-composable";
const query = provideApolloClient(apolloClient)(() => useQuery(gql`
query hello {
hello
}
`))
const hello = computed(() => query.result.value?.hello ?? '')
provideApolloClient(apolloClient)
```
Use `provideApolloClients` for multiple clients:
```js
import { provideApolloClients } from '@vue/apollo-composable'
import { provideApolloClients } from "@vue/apollo-composable";
provideApolloClients({
default: apolloClient,
@@ -55,80 +55,12 @@ A future version of Apollo or GraphQL might include support for live queries, wh
In this article, we'll explain how to set it up on the client, but you'll also need a server implementation. You can [read about how to use subscriptions with a JavaScript server](https://www.apollographql.com/docs/graphql-subscriptions/setup), or enjoy subscriptions set up out of the box if you are using a GraphQL backend as a service like [Graphcool](https://www.graph.cool/docs/tutorials/worldchat-subscriptions-example-ui0eizishe/).
The GraphQL spec does not define a specific protocol for sending subscription requests. The first popular JavaScript library to implement subscriptions over WebSocket is called *subscriptions-transport-ws*. This library is no longer actively maintained. Its successor is a library called *graphql-ws*. The two libraries do not use the same WebSocket subprotocol, so you need to make sure that your server and clients all use the same library.
Let's look at how to add support for this transport to Apollo Client.
Apollo Client supports both *graphql-ws* and *subscriptions-transport-ws*. Apollo [documentation](https://www.apollographql.com/docs/react/data/subscriptions/#choosing-a-subscription-library) suggest to use the newer library *graphql-ws*, but in case you need it, here its explained how to do it with both.
### The new library: **graphql-ws**
Let's look at how to add support for this transport to Apollo Client using a link set up for newest library [graphql-ws](https://github.com/enisdenjo/graphql-ws). First, install:
```bash
npm install graphql-ws
```
Then initialize a GraphQL web socket link:
First, initialize a GraphQL web socket link:
```js
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4000/graphql',
})
)
```
We need to either use the `GraphQLWsLink` or the `HttpLink` depending on the operation type:
```js
import { HttpLink, split } from '@apollo/client/core'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions' // <-- This one uses graphql-ws
import { getMainDefinition } from '@apollo/client/utilities'
import { createClient } from 'graphql-ws'
// Create an http link:
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
})
// Create a GraphQLWsLink link:
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:5000/',
})
)
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition'
&& definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
// Create the apollo client with cache implementation.
const apolloClient = new ApolloClient({
link,
cache: new InMemoryCache(),
})
```
The apollo client is the one that will be provided to the vue app, see the [setup section](https://v4.apollo.vuejs.org/guide-composable/setup.html) for more details.
Now, queries and mutations will go over HTTP as normal, but subscriptions will be done over the websocket transport.
### The old library: **subscriptions-transport-ws**
If you need to use [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) because your server still uses that protocol, instead of installing graphql-ws, install:
```bash
npm install subscriptions-transport-ws
```
And then initialize a GraphQL web socket link:
```js
import { WebSocketLink } from '@apollo/client/link/ws' // <-- This one uses subscriptions-transport-ws
import { WebSocketLink } from "@apollo/client/link/ws"
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000/`,
@@ -137,7 +69,44 @@ const wsLink = new WebSocketLink({
}
})
```
The rest of the configuration (creating a httpLink and link) is the same as described above for graphql-ws.
We need to either use the `WebSocketLink` or the `HttpLink` depending on the operation type:
```js
import { HttpLink, split } from "@apollo/client/core"
import { WebSocketLink } from "@apollo/client/link/ws"
import { getMainDefinition } from "@apollo/client/utilities"
// Create an http link:
const httpLink = new HttpLink({
uri: "http://localhost:3000/graphql"
})
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000/`,
options: {
reconnect: true
}
})
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
)
},
wsLink,
httpLink
)
```
Now, queries and mutations will go over HTTP as normal, but subscriptions will be done over the websocket transport.
## useSubscription
@@ -266,7 +235,7 @@ With a ref:
```js
const variables = ref({
channelId: 'abc'
channelId: "abc"
})
const { result } = useSubscription(
@@ -286,7 +255,7 @@ With a reactive object:
```js
const variables = reactive({
channelId: 'abc'
channelId: "abc"
})
const { result } = useSubscription(
@@ -305,7 +274,7 @@ const { result } = useSubscription(
With a function (which will automatically be made reactive):
```js
const channelId = ref('abc')
const channelId = ref("abc")
const { result } = useSubscription(
gql`
@@ -338,7 +307,7 @@ const { result } = useSubscription(
`,
null,
{
fetchPolicy: 'no-cache'
fetchPolicy: "no-cache"
}
)
```
@@ -357,7 +326,7 @@ const { result } = useSubscription(
`,
null,
() => ({
fetchPolicy: 'no-cache'
fetchPolicy: "no-cache"
})
)
```
@@ -391,7 +360,7 @@ function enableSub() {
You can retrieve the loading and error stats from `useSubscription`:
```js
const { loading, error } = useSubscription(/* ... */)
const { loading, error } = useSubscription(...)
```
### Event hooks
@@ -401,9 +370,9 @@ const { loading, error } = useSubscription(/* ... */)
This is called when a new result is received from the server:
```js
const { onResult } = useSubscription(/* ... */)
const { onResult } = useSubscription(...)
onResult((result, context) => {
onResult(result => {
console.log(result.data)
})
```
@@ -415,50 +384,13 @@ This is triggered when an error occurs:
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useSubscription(/* ... */)
const { onError } = useSubscription(...)
onError((error, context) => {
onError(error => {
logErrorMessages(error)
})
```
### Update the cache
Using `onResult`, you can update the Apollo cache with the new data:
```js
const { onResult } = useSubscription(/* ... */)
onResult((result, { client }) => {
const query = {
query: gql`query getMessages ($channelId: ID!) {
messages(channelId: $channelId) {
id
text
}
}`,
variables: {
channelId: '123',
},
}
// Read the query
let data = client.readQuery(query)
// Update cached data
data = {
...data,
messages: [...data.messages, result.data.messageAdded],
}
// Write back the new result for the query
client.writeQuery({
...query,
data,
})
})
```
## subscribeToMore
With GraphQL subscriptions your client will be alerted on push from the server and you should choose the pattern that fits your application the most:
@@ -613,7 +545,7 @@ subscribeToMore(() => ({
channelId: props.channelId
},
updateQuery: (previousResult, { subscriptionData }) => {
const tmp = [...previousResult]
const tmp = [...previousResult]
tmp.messages.push(subscriptionData.data.messageAdded)
return tmp
}
@@ -625,16 +557,15 @@ subscribeToMore(() => ({
In many cases it is necessary to authenticate clients before allowing them to receive subscription results. To do this, the `SubscriptionClient` constructor accepts a `connectionParams` field, which passes a custom object that the server can use to validate the connection before setting up any subscriptions.
```js
import { WebSocketLink } from '@apollo/client/link/ws'
import { WebSocketLink } from "@apollo/client/link/ws"
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000/`,
options: {
reconnect: true,
connectionParams: {
authToken: user.authToken,
authToken: user.authToken,
},
}
})
```
@@ -19,8 +19,8 @@ const defaultOptions = {
}
const clientAOptions = {
// You can use `https` for secure connection (recommended in production)
httpEndpoint: 'http://localhost:4000/graphql',
// You can use `https` for secure connection (recommended in production)
httpEndpoint: 'http://localhost:4000/graphql',
}
const clientBOptions = {
@@ -28,29 +28,28 @@ const clientBOptions = {
}
// Call this in the Vue app file
export function createProvider(options = {}) {
const createA = createApolloClient({
export function createProvider (options = {}) {
const createA= createApolloClient({
...defaultOptions,
...clientAOptions,
})
});
const createB = createApolloClient({
...defaultOptions,
...clientBOptions,
})
});
const a = createA.apolloClient
const b = createB.apolloClient
const a = createA.apolloClient;
const b = createB.apolloClient;
// Create vue apollo provider
const apolloProvider = createApolloProvider({
clients: {
a,
b
},
}
defaultClient: a,
})
}
})
```
In the component `apollo` option, you can define the client for all the queries, subscriptions and mutations with `$client` (only for this component):
@@ -66,12 +65,8 @@ export default {
You can also specify the client in individual queries, subscriptions and mutations with the `client` property in the options:
```js
export default {
apollo: {
tags: {
query: gql`...`,
client: 'b',
}
}
tags: {
query: gql`...`,
client: 'b',
}
```
+55 -57
View File
@@ -11,74 +11,69 @@ You shouldn't send the `__typename` fields in the variables, so it is not recomm
:::
```js
export default {
methods: {
addTag() {
methods: {
addTag() {
// We save the user input in case of an error
const newTag = this.newTag
// We clear it early to give the UI a snappy feel
this.newTag = ''
// Call to the graphql mutation
this.$apollo.mutate({
const newTag = this.newTag
// We clear it early to give the UI a snappy feel
this.newTag = ''
// Call to the graphql mutation
this.$apollo.mutate({
// Query
mutation: gql`mutation ($label: String!) {
mutation: gql`mutation ($label: String!) {
addTag(label: $label) {
id
label
}
}`,
// Parameters
variables: {
// Parameters
variables: {
label: newTag,
},
// Update the cache with the result
// The query will be updated with the optimistic response
// and then with the real result of the mutation
update: (store, { data: { addTag } }) => {
// Read the data from our cache for this query.
let data = store.readQuery({ query: TAGS_QUERY })
// Add our tag from the mutation to the end
data = {
...data,
tags: [
...data.tags,
addTag,
],
}
// Write our data back to the cache.
store.writeQuery({ query: TAGS_QUERY, data })
},
// Optimistic UI
// Will be treated as a 'fake' result as soon as the request is made
// so that the UI can react quickly and the user be happy
optimisticResponse: {
__typename: 'Mutation',
addTag: {
__typename: 'Tag',
id: -1,
label: newTag,
},
// Update the cache with the result
// The query will be updated with the optimistic response
// and then with the real result of the mutation
update: (store, { data: { addTag } }) => {
// Read the data from our cache for this query.
let data = store.readQuery({ query: TAGS_QUERY })
// Add our tag from the mutation to the end
data = {
...data,
tags: [
...data.tags,
addTag,
],
}
// Write our data back to the cache.
store.writeQuery({ query: TAGS_QUERY, data })
},
// Optimistic UI
// Will be treated as a 'fake' result as soon as the request is made
// so that the UI can react quickly and the user be happy
optimisticResponse: {
__typename: 'Mutation',
addTag: {
__typename: 'Tag',
id: -1,
label: newTag,
},
},
}).then((data) => {
},
}).then((data) => {
// Result
console.log(data)
}).catch((error) => {
console.log(data)
}).catch((error) => {
// Error
console.error(error)
// We restore the initial user input
this.newTag = newTag
})
},
console.error(error)
// We restore the initial user input
this.newTag = newTag
})
},
}
},
```
## Server-side example
```js
// Fake word generator
import faker from 'faker'
export const schema = `
type Tag {
id: Int
@@ -99,15 +94,18 @@ schema {
}
`
// Fake word generator
import faker from 'faker'
// Let's generate some tags
let id = 0
const tags = []
var id = 0
var tags = []
for (let i = 0; i < 42; i++) {
addTag(faker.random.word())
}
function addTag(label) {
const t = {
function addTag (label) {
let t = {
id: id++,
label,
}
@@ -117,12 +115,12 @@ function addTag(label) {
export const resolvers = {
Query: {
tags(root, args, context) {
tags (root, args, context) {
return tags
},
},
Mutation: {
addTag(root, { label }, context) {
addTag (root, { label }, context) {
console.log(`adding tag '${label}'`)
return addTag(label)
},
+96 -127
View File
@@ -23,14 +23,12 @@ import gql from 'graphql-tag'
Put the [gql](https://github.com/apollographql/graphql-tag) query directly as the value:
```js
export default {
apollo: {
apollo: {
// Simple query that will update the 'hello' vue property
hello: gql`query {
hello: gql`query {
hello
}`,
},
}
},
```
You can then access the smart query with `this.$apollo.queries.<name>`.
@@ -38,14 +36,12 @@ You can then access the smart query with `this.$apollo.queries.<name>`.
You can initialize the property in your vue component's `data` hook:
```js
export default {
data() {
return {
data () {
return {
// Initialize your apollo data
hello: '',
}
hello: '',
},
}
},
```
Server-side, add the corresponding schema and resolver:
@@ -63,7 +59,7 @@ schema {
export const resolvers = {
Query: {
hello(root, args, context) {
hello (root, args, context) {
return 'Hello world!'
},
},
@@ -90,26 +86,22 @@ You can then use your property as usual in your vue component:
Please note that a common beginner's mistake is to use a data name different from the field name in the query, e.g.:
```js
export default {
apollo: {
world: gql`query {
apollo: {
world: gql`query {
hello
}`
}
}
```
Notice how `world` is different from `hello`; `vue-apollo` won't guess which data you want to put in the component from the query result. By default, it will just try the name you are using for the data in the component (which is the key in the `apollo` object), in this case `world`. If the names don't match, you can use `update` option to tell `vue-apollo` what to use as data from the result:
```js
export default {
apollo: {
world: {
query: gql`query {
apollo: {
world: {
query: gql`query {
hello
}`,
update: data => data.hello
}
update: data => data.hello
}
}
```
@@ -117,12 +109,10 @@ export default {
You can also rename the field in the GraphQL document directly:
```js
export default {
apollo: {
world: gql`query {
apollo: {
world: gql`query {
world: hello
}`
}
}
```
@@ -133,22 +123,20 @@ In this example, we rename the `hello` field to `world` so that `vue-apollo` can
You can add variables (and other parameters) to your `gql` query by declaring `query` and `variables` in an object instead of just the GraphQL query:
```js
export default {
// Apollo-specific options
apollo: {
apollo: {
// Query with parameters
ping: {
ping: {
// gql query
query: gql`query PingMessage($message: String!) {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// Static parameters
variables: {
message: 'Meow',
},
// Static parameters
variables: {
message: 'Meow',
},
},
}
},
```
You can use the apollo `watchQuery` options in the object, like:
@@ -161,34 +149,30 @@ See the [apollo doc](https://www.apollographql.com/docs/react/api/core/apolloCli
For example, you could add the `fetchPolicy` apollo option like this:
```js
export default {
apollo: {
apollo: {
// Query with parameters
ping: {
query: gql`query PingMessage($message: String!) {
ping: {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
variables: {
message: 'Meow'
},
// Additional options here
fetchPolicy: 'cache-and-network',
variables: {
message: 'Meow'
},
// Additional options here
fetchPolicy: 'cache-and-network',
},
}
},
```
Again, you can initialize your property in your vue component:
```js
export default {
data() {
return {
data () {
return {
// Initialize your apollo data
ping: '',
}
},
}
ping: '',
}
},
```
Server-side, add the corresponding schema and resolver:
@@ -206,7 +190,7 @@ schema {
export const resolvers = {
Query: {
ping(root, { message }, context) {
ping (root, { message }, context) {
return `Answering ${message}`
},
},
@@ -231,24 +215,22 @@ And then use it in your vue component:
Use a function instead to make the parameters reactive with vue properties:
```js
export default {
// Apollo-specific options
apollo: {
apollo: {
// Query with parameters
ping: {
query: gql`query PingMessage($message: String!) {
ping: {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// Reactive parameters
variables() {
// Reactive parameters
variables () {
// Use vue reactive properties here
return {
return {
message: this.pingInput,
}
},
}
},
},
}
},
```
This will re-fetch the query each time a parameter changes, for example:
@@ -284,26 +266,24 @@ Or for this specific `ping` query:
You can use a function which will be called once when the component is created and it must return the option object:
```js
export default {
// Apollo-specific options
apollo: {
apollo: {
// Query with parameters
ping() {
ping () {
// This is called once when the component is created
// It must return the option object
return {
return {
// gql query
query: gql`query PingMessage($message: String!) {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// Static parameters
variables: {
message: 'Meow',
},
}
},
// Static parameters
variables: {
message: 'Meow',
},
}
},
}
},
```
::: tip
@@ -315,36 +295,31 @@ This also works for [subscriptions](./subscriptions.md).
You can use a function for the `query` option. This will update the graphql query definition automatically:
```js
export default {
apollo: {
// The featured tag can be either a random tag or the last added tag
featuredTag: {
query() {
// Here you can access the component instance with 'this'
if (this.showTag === 'random') {
return gql`{
// The featured tag can be either a random tag or the last added tag
featuredTag: {
query () {
// Here you can access the component instance with 'this'
if (this.showTag === 'random') {
return gql`{
randomTag {
id
label
type
}
}`
}
else if (this.showTag === 'last') {
return gql`{
} else if (this.showTag === 'last') {
return gql`{
lastTag {
id
label
type
}
}`
}
},
// We need this to assign the value of the 'featuredTag' component property
update: data => data.randomTag || data.lastTag,
},
}
}
}
},
// We need this to assign the value of the 'featuredTag' component property
update: data => data.randomTag || data.lastTag,
},
```
::: tip
@@ -356,30 +331,28 @@ This also works for [subscriptions](./subscriptions.md).
If the query is skipped, it will disable it and the result will not be updated anymore. You can use the `skip` option:
```js
export default {
// Apollo-specific options
apollo: {
tags: {
apollo: {
tags: {
// GraphQL Query
query: gql`query tagList ($type: String!) {
query: gql`query tagList ($type: String!) {
tags(type: $type) {
id
label
}
}`,
// Reactive variables
variables() {
return {
type: this.type,
}
},
// Disable the query
skip() {
return this.skipQuery
},
// Reactive variables
variables () {
return {
type: this.type,
}
},
// Disable the query
skip () {
return this.skipQuery
},
},
}
},
```
Here, `skip` will be called automatically when the `skipQuery` component property changes.
@@ -397,29 +370,24 @@ If the query `skip` becomes `false`, the query will automatically execute again.
Here is a reactive query example using polling:
```js
export default {
// Apollo-specific options
apollo: {
apollo: {
// 'tags' data property on vue instance
tags: {
query: gql`query tagList {
tags: {
query: gql`query tagList {
tags {
id,
label
}
}`,
pollInterval: 300, // ms
},
pollInterval: 300, // ms
},
}
},
```
Here is how the server-side looks like:
```js
// Fake word generator
import casual from 'casual'
export const schema = `
type Tag {
id: Int
@@ -435,15 +403,18 @@ schema {
}
`
// Fake word generator
import casual from 'casual'
// Let's generate some tags
let id = 0
const tags = []
var id = 0
var tags = []
for (let i = 0; i < 42; i++) {
addTag(casual.word)
}
function addTag(label) {
const t = {
function addTag (label) {
let t = {
id: id++,
label,
}
@@ -465,12 +436,10 @@ export const resolvers = {
You can manually add a smart query with the `$apollo.addSmartQuery(key, options)` method:
```js
export default {
created() {
this.$apollo.addSmartQuery('comments', {
created () {
this.$apollo.addSmartQuery('comments', {
// Same options like above
})
}
})
}
```
+1
View File
@@ -25,6 +25,7 @@ const apolloClient = new ApolloClient({
cache,
uri: 'http://localhost:4042/graphql',
})
```
::: warning
@@ -1,8 +1,9 @@
# Special options
The special options begin with `$` in the `apollo` object.
- `$skipAll` to disable all queries and subscriptions (see below)
- `$skip` to disable all queries and subscriptions (see below)
- `$skipAllQueries` to disable all queries (see below)
- `$skipAllSubscriptions` to disable all subscriptions (see below)
- `$deep` to watch with `deep: true` on the properties above when a function is provided
@@ -58,21 +59,17 @@ this.$apollo.skipAll = true
You can also declare these properties in the `apollo` option of the component. They can be booleans:
```js
export default {
apollo: {
$skipAll: true
}
apollo: {
$skipAll: true
}
```
Or reactive functions:
```js
export default {
apollo: {
$skipAll() {
return this.foo === 42
}
apollo: {
$skipAll () {
return this.foo === 42
}
}
```
+97 -151
View File
@@ -1,47 +1,26 @@
# Subscriptions
## Setup
*For the server implementation, you can take a look at [this simple example](https://github.com/Akryum/apollo-server-example).*
## Client setup
The GraphQL spec does not define a specific protocol for sending subscription requests. The first popular JavaScript library to implement subscriptions over WebSocket is called *subscriptions-transport-ws*. This library is no longer actively maintained. Its successor is a library called *graphql-ws*. The two libraries do not use the same WebSocket subprotocol, so you need to make sure that your server and clients all use the same library.
Apollo Client supports both *graphql-ws* and *subscriptions-transport-ws*. Apollo [documentation](https://www.apollographql.com/docs/react/data/subscriptions/#choosing-a-subscription-library) suggest to use the newer library *graphql-ws*, but in case you need it, here its explained how to do it with both.
### The new library: **graphql-ws**
Let's look at how to add support for this transport to Apollo Client using a link set up for newest library [graphql-ws](https://github.com/enisdenjo/graphql-ws). First, install:
```bash
npm install graphql-ws
```
Then initialize a GraphQL web socket link:
```js
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4000/graphql',
})
)
```
We need to either use the `GraphQLWsLink` or the `HttpLink` depending on the operation type:
```js
import { HttpLink, split } from '@apollo/client/core'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions' // <-- This one uses graphql-ws
import { ApolloClient, HttpLink, InMemoryCache, split } from '@apollo/client/core'
import { WebSocketLink } from '@apollo/client/link/ws'
import { getMainDefinition } from '@apollo/client/utilities'
// Create an http link:
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
// You should use an absolute URL here
uri: 'http://localhost:3020/graphql',
})
// Create a GraphQLWsLink link:
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:5000/',
})
)
// Create the subscription websocket link
const wsLink = new WebSocketLink({
uri: 'ws://localhost:3000/subscriptions',
options: {
reconnect: true,
},
})
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
@@ -49,70 +28,47 @@ const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition'
&& definition.operation === 'subscription'
)
return definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
},
wsLink,
httpLink
)
// Create the apollo client with cache implementation.
// Create the apollo client
const apolloClient = new ApolloClient({
link,
cache: new InMemoryCache(),
connectToDevTools: true,
})
```
The apollo client is the one that will be provided to the vue app, see the [setup section](https://v4.apollo.vuejs.org/guide-composable/setup.html) for more details.
Now, queries and mutations will go over HTTP as normal, but subscriptions will be done over the websocket transport.
### The old library: **subscriptions-transport-ws**
If you need to use [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) because your server still uses that protocol, instead of installing graphql-ws, install:
```bash
npm install subscriptions-transport-ws
```
And then initialize a GraphQL web socket link:
```js
import { WebSocketLink } from '@apollo/client/link/ws' // <-- This one uses subscriptions-transport-ws
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000/`,
options: {
reconnect: true
}
})
```
The rest of the configuration (creating a httpLink and link) is the same as described above for graphql-ws.
## Subscribe To More
If you need to update a smart query result from a subscription, the best way is using the `subscribeToMore` smart query method. It will create [Smart Subscriptions](../api/smart-subscription.md) that are linked to the smart query. Just add a `subscribeToMore` to your smart query:
```js
export default {
apollo: {
tags: {
query: TAGS_QUERY,
subscribeToMore: {
document: gql`subscription name($param: String!) {
apollo: {
tags: {
query: TAGS_QUERY,
subscribeToMore: {
document: gql`subscription name($param: String!) {
itemAdded(param: $param) {
id
label
}
}`,
// Variables passed to the subscription. Since we're using a function,
// they are reactive
variables() {
return {
param: this.param,
}
},
// Mutate the previous result
updateQuery: (previousResult, { subscriptionData }) => {
// Variables passed to the subscription. Since we're using a function,
// they are reactive
variables () {
return {
param: this.param,
}
},
// Mutate the previous result
updateQuery: (previousResult, { subscriptionData }) => {
// Here, return the new result from the previous with the new data
},
}
},
}
}
}
@@ -208,37 +164,35 @@ The methods below are suitable for a 'notify' use case.
You can declare [Smart Subscriptions](../api/smart-subscription.md) in the `apollo` option with the `$subscribe` keyword:
```js
export default {
apollo: {
apollo: {
// Subscriptions
$subscribe: {
$subscribe: {
// When a tag is added
tagAdded: {
query: gql`subscription tags($type: String!) {
tagAdded: {
query: gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`,
// Reactive variables
variables() {
// Reactive variables
variables () {
// This works just like regular queries
// and will re-subscribe with the right variables
// each time the values change
return {
type: this.type,
}
},
// Result hook
// Don't forget to destructure `data`
result({ data }) {
console.log(data.tagAdded)
},
return {
type: this.type,
}
},
// Result hook
// Don't forget to destructure `data`
result ({ data }) {
console.log(data.tagAdded)
},
},
},
}
},
```
You can then access the subscription with `this.$apollo.subscriptions.<name>`.
@@ -251,29 +205,27 @@ When server supports live queries and uses subscriptions to update them, like [H
use simple subscriptions for reactive queries:
```js
export default {
data() {
return {
tags: [],
}
},
apollo: {
$subscribe: {
tags: {
query: gql`subscription {
data () {
return {
tags: [],
};
},
apollo: {
$subscribe: {
tags: {
query: gql`subscription {
tags {
id
label
type
}
}`,
result({ data }) {
this.tags = data.tags
},
result ({ data }) {
this.tags = data.tags;
},
},
},
}
},
```
## Skipping the subscription
@@ -281,39 +233,37 @@ export default {
If the subscription is skipped, it will disable it and it will not be updated anymore. You can use the `skip` option:
```js
export default {
// Apollo-specific options
apollo: {
apollo: {
// Subscriptions
$subscribe: {
$subscribe: {
// When a tag is added
tags: {
query: gql`subscription tags($type: String!) {
tags: {
query: gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`,
// Reactive variables
variables() {
return {
type: this.type,
}
},
// Result hook
result(data) {
// Let's update the local data
this.tags.push(data.tagAdded)
},
// Skip the subscription
skip() {
return this.skipSubscription
// Reactive variables
variables () {
return {
type: this.type,
}
},
// Result hook
result (data) {
// Let's update the local data
this.tags.push(data.tagAdded)
},
// Skip the subscription
skip () {
return this.skipSubscription
}
},
},
}
},
```
Here, `skip` will be called automatically when the `skipSubscription` component property changes.
@@ -329,12 +279,10 @@ this.$apollo.subscriptions.tags.skip = true
You can manually add a smart subscription with the `$apollo.addSmartSubscription(key, options)` method:
```js
export default {
created() {
this.$apollo.addSmartSubscription('tagAdded', {
created () {
this.$apollo.addSmartSubscription('tagAdded', {
// Same options like '$subscribe' above
})
}
})
}
```
@@ -347,9 +295,8 @@ Internally, this method is called for each entry of the `$subscribe` object in t
Use the `$apollo.subscribe()` method to subscribe to a GraphQL subscription that will get killed automatically when the component is destroyed. It will **NOT** create a Smart Subscription.
```js
export default {
mounted() {
const subQuery = gql`subscription tags($type: String!) {
mounted () {
const subQuery = gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
@@ -357,21 +304,20 @@ export default {
}
}`
const observer = this.$apollo.subscribe({
query: subQuery,
variables: {
type: 'City',
},
})
const observer = this.$apollo.subscribe({
query: subQuery,
variables: {
type: 'City',
},
})
observer.subscribe({
next(data) {
console.log(data)
},
error(error) {
console.error(error)
},
})
},
}
observer.subscribe({
next (data) {
console.log(data)
},
error (error) {
console.error(error)
},
})
},
```
-9
View File
@@ -46,12 +46,3 @@ Is your company using `vue-apollo` or `vue-cli-plugin-apollo` to build awesome a
[<img src="https://conf.vuejs.org/img/logo-48.png" alt="icon" width="16" height="16"/> VueConf 2017 demo](https://github.com/Akryum/vueconf-2017-demo) &amp; [slides](http://slides.com/akryum/graphql#/)
[<img src="https://github.com/fluidicon.png" alt="icon" width="16" height="16"/> Devfest Summit Example](https://github.com/Akryum/devfest-nantes-2017) (with lots of features like SSR, OAuth, Realtime updates, Apollo Engine...)
<style scoped>
a > img {
max-width: none;
width: 16px;
height: 16px;
display: inline-block;
}
</style>
+1 -1
View File
@@ -94,7 +94,7 @@ If you are using Webstorm, it's recommended to install the [JS GraphQL extension
Then configure it by creating a `.graphqlconfig` file in the root folder of the Vue project:
```json
```graphqlconfig
{
"name": "Untitled GraphQL Schema",
"schemaPath": "./path/to/schema.graphql",
+7 -16
View File
@@ -1,31 +1,22 @@
---
layout: home
hero:
name: Vue Apollo
text: GraphQL
tagline: Effortless GraphQL in your Vue app!
image:
src: /hero.svg
alt: Vue Apollo
actions:
- theme: brand
text: Get Started
link: /guide/
home: true
sidebar: false
heroImage: /logo.png
actionText: Get Started →
actionLink: /guide/
features:
- title: Automatic updates
details: Don't think about updating the UI or refetching the queries!
icon: ✨
- title: Supports all Vue APIs
details: Option API, Composition API or Components
icon: 🧩
- title: SSR-ready
details: Run your queries on the server before rendering the page HTML
icon: 🌐
footer: LICENCE MIT - Created by Guillaume CHAU (@Akryum)
---
<SponsorButton/>
<h2 style="text-align: center; font-size: 2rem;">Sponsors</h2>
## Sponsors
<p align="center">
<a href="https://guillaume-chau.info/sponsors/" target="_blank">
+5 -5
View File
@@ -23,19 +23,19 @@ npm install --save @vue/apollo-option @apollo/client
Before:
```js
import { InMemoryCache } from 'apollo-cache-inmemory'
import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import Vue from 'vue'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'
```
After:
```js
import Vue from 'vue'
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core'
import { createApolloProvider } from '@vue/apollo-option'
import Vue from 'vue'
```
### Apollo Setup
@@ -89,8 +89,8 @@ const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition'
&& operation === 'subscription'
return kind === 'OperationDefinition' &&
operation === 'subscription'
},
wsLink,
httpLink
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

-81
View File
@@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="2430"
viewBox="26.69 28.9 1173.9 1144.1"
width="2500"
version="1.1"
id="svg2"
sodipodi:docname="hero.svg"
xml:space="preserve"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs2"><linearGradient
id="linearGradient1"
inkscape:collect="always"><stop
style="stop-color:#1c4472;stop-opacity:1;"
offset="0"
id="stop1" /><stop
style="stop-color:#1c4472;stop-opacity:0.35686275;"
offset="0.62594885"
id="stop3" /><stop
style="stop-color:#1c4472;stop-opacity:0;"
offset="1"
id="stop2" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient1"
id="radialGradient2"
cx="613.64"
cy="600.94999"
fx="613.64"
fy="600.94999"
r="588.52881"
gradientTransform="matrix(1,0,0,0.97199998,0,16.826614)"
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
id="namedview2"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.33900387"
inkscape:cx="665.18415"
inkscape:cy="1623.8753"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="1920"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" /><rect
style="fill:url(#radialGradient2);stroke-width:13.183;stroke-linecap:round;paint-order:markers stroke fill;opacity:0.17374599"
id="rect1"
width="1177.0576"
height="1144.1"
x="25.111193"
y="28.9"
rx="11.299753"
ry="11.299753" /><path
d="m 593.93786,204.45219 c -1.52718,0.0694 -7.28877,0.55534 -12.84211,0.97184 -69.4168,5.13684 -139.80545,30.82107 -197.49081,72.05464 -19.08963,13.6057 -34.15307,26.37839 -50.95193,43.17725 -96.55877,96.6282 -136.3346,234.90648 -106.1383,369.228 12.3562,55.11694 38.73457,110.58096 73.72065,155.14656 54.90869,70.04155 130.15651,119.3969 214.56734,140.77727 34.36132,8.74653 62.89163,12.28679 98.91895,12.28679 35.5414,0 61.71154,-3.19319 95.58694,-11.45379 48.66118,-11.93968 91.42193,-31.58464 134.18268,-61.50329 40.9559,-28.73855 79.06572,-69.0003 106.41595,-112.38579 35.26373,-56.08879 55.32518,-118.00857 60.25378,-185.96764 1.0413,-14.29985 0.4165,-49.70243 -1.1107,-64.27995 -3.0543,-28.94681 -10.82899,-67.12606 -15.34108,-75.10898 -1.80484,-3.12376 -6.87227,-7.14994 -10.62078,-8.46885 -1.31891,-0.4165 -4.23441,-0.76359 -6.59459,-0.76359 -10.41252,0 -19.15903,8.6771 -19.15903,18.88137 0,2.42959 1.11066,8.2606 2.70724,14.57753 13.74453,53.45094 14.85521,112.17755 3.12376,165.76732 -14.99403,68.65323 -48.45293,129.18468 -98.91891,178.8177 -23.53231,23.1158 -46.2316,40.19233 -75.59491,56.9912 -68.51439,39.2205 -153.68881,54.07569 -233.58755,40.74766 C 494.39416,943.88 441.7068,920.55596 391.65729,881.61313 368.125,863.28709 339.45585,833.09079 321.68515,807.82307 c -30.68223,-43.59376 -49.9801,-89.54768 -59.97613,-142.9292 -5.06741,-27.21139 -6.80284,-63.30813 -4.44266,-92.67145 3.60967,-44.56558 14.78577,-85.938 33.73656,-125.15849 8.74653,-18.25662 14.16103,-27.76672 24.64296,-43.52434 21.38039,-32.34823 49.70245,-62.61396 81.00943,-86.63217 36.72148,-28.25264 80.31523,-49.70243 124.3949,-61.22563 23.80997,-6.2475 42.83018,-9.37126 66.98722,-11.10668 87.39576,-6.24752 172.36192,18.88137 241.57049,71.56872 5.13684,3.88735 10.55134,8.12178 12.00911,9.44069 l 2.63783,2.29076 -1.04125,2.91551 c -5.34509,14.99402 -1.38833,33.66715 9.85719,46.7175 5.76159,6.66402 16.17411,12.91152 24.99005,14.99403 6.03924,1.45775 16.52117,1.45775 22.62985,0 15.47995,-3.67909 29.91864,-16.9377 34.50015,-31.65407 5.13684,-16.45177 2.15192,-32.69531 -8.39942,-45.8845 -11.66204,-14.57753 -31.72348,-21.10271 -50.25776,-16.24353 l -4.02616,1.04125 -5.69218,-4.72035 c -23.11581,-19.36728 -51.09078,-37.27682 -80.5235,-51.50726 -47.75877,-23.1158 -95.72577,-35.54141 -150.35681,-38.87342 -10.48193,-0.62475 -35.26374,-0.76358 -41.99716,-0.20825 z"
id="path1"
style="fill:#1c426d;fill-opacity:1;stroke-width:0.694168" /><g
transform="matrix(3.0101768,0,0,-3.0101768,145.88562,1137.4985)"
id="g10"><g
transform="translate(178.06,235.01)"
id="g4"
style="fill:#173960;fill-opacity:1"><path
d="M 0,0 -22.669,-39.264 -45.338,0 h -75.491 L -22.669,-170.02 75.491,0 Z"
fill="#41b883"
id="path2"
style="fill:#1c4472;fill-opacity:1" /></g><g
transform="translate(178.06,235.01)"
id="g8"
style="fill:#2a5f9c;fill-opacity:1"><path
d="M 0,0 -22.669,-39.264 -45.338,0 H -81.565 L -22.669,-102.01 36.227,0 Z"
fill="#34495e"
id="path6"
style="fill:#2a5f9c;fill-opacity:1" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

-51
View File
@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="2430"
viewBox="26.69 28.9 1173.9 1144.1"
width="2500"
version="1.1"
id="svg2"
sodipodi:docname="vue-apollo.svg"
xml:space="preserve"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs2" /><sodipodi:namedview
id="namedview2"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.47942387"
inkscape:cx="870.83691"
inkscape:cy="1406.897"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="1920"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="g10" /><path
d="m 585.25763,29.7 c -2.2,0.1 -10.5,0.8 -18.5,1.4 -100,7.4 -201.4,44.4 -284.5,103.8 -27.5,19.6 -49.2,38 -73.4,62.2 C 69.757632,336.3 12.457632,535.5 55.957632,729 c 17.8,79.4 55.799998,159.3 106.199998,223.5 79.1,100.9 187.5,172 309.1,202.8 49.5,12.6 90.6,17.7 142.5,17.7 51.2,0 88.9,-4.6 137.7,-16.5 70.1,-17.2 131.7,-45.5 193.3,-88.6 58.99997,-41.4 113.89997,-99.4 153.29997,-161.9 50.8,-80.8 79.7,-170 86.8,-267.9 1.5,-20.6 0.6,-71.6 -1.6,-92.6 -4.4,-41.7 -15.6,-96.7 -22.1,-108.2 -2.6,-4.5 -9.9,-10.3 -15.3,-12.2 -1.9,-0.6 -6.1,-1.1 -9.5,-1.1 -15,0 -27.6,12.5 -27.6,27.2 0,3.5 1.6,11.9 3.9,21 19.8,77 21.4,161.6 4.5,238.8 -21.6,98.9 -69.8,186.1 -142.49997,257.6 -33.9,33.3 -66.6,57.9 -108.9,82.1 -98.7,56.5 -221.4,77.9 -336.5,58.7 -87.4,-14.5 -163.3,-48.1 -235.4,-104.2 -33.9,-26.4 -75.2,-69.9 -100.8,-106.3 -44.2,-62.8 -72,-129 -86.4,-205.9 -7.299998,-39.2 -9.799998,-91.2 -6.4,-133.5 5.2,-64.2 21.3,-123.8 48.6,-180.3 12.6,-26.3 20.4,-40 35.5,-62.7 30.8,-46.6 71.6,-90.2 116.7,-124.8 52.9,-40.7 115.7,-71.6 179.2,-88.2 34.3,-9 61.7,-13.5 96.5,-16 125.9,-9 248.3,27.2 348,103.1 7.4,5.6 15.2,11.7 17.3,13.6 l 3.8,3.3 -1.5,4.2 c -7.7,21.6 -2,48.5 14.2,67.3 8.3,9.6 23.3,18.6 36,21.6 8.69997,2.1 23.79997,2.1 32.59997,0 22.3,-5.3 43.1,-24.4 49.7,-45.6 7.4,-23.7 3.1,-47.1 -12.1,-66.1 -16.8,-21 -45.7,-30.4 -72.39997,-23.4 l -5.8,1.5 -8.2,-6.8 c -33.3,-27.9 -73.6,-53.7 -116,-74.2 -68.8,-33.3 -137.9,-51.2 -216.6,-56 -15.1,-0.9 -50.8,-1.1 -60.5,-0.3 z"
id="path1"
style="fill:#1c426d;fill-opacity:1" /><g
transform="matrix(4.3363805,0,0,-4.3363805,-60.194502,1373.8216)"
id="g10"><g
transform="translate(178.06,235.01)"
id="g4"
style="fill:#173960;fill-opacity:1"><path
d="M 0,0 -22.669,-39.264 -45.338,0 h -75.491 L -22.669,-170.02 75.491,0 Z"
fill="#41b883"
id="path2"
style="fill:#1c4472;fill-opacity:1" /></g><g
transform="translate(178.06,235.01)"
id="g8"
style="fill:#2a5f9c;fill-opacity:1"><path
d="M 0,0 -22.669,-39.264 -45.338,0 H -81.565 L -22.669,-102.01 36.227,0 Z"
fill="#34495e"
id="path6"
style="fill:#2a5f9c;fill-opacity:1" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

@@ -209,7 +209,7 @@ export default {
签名:
```ts
function mutate(options = null): Promise<FetchResult>
mutate(options = null): Promise<FetchResult>
```
- `options`: [变更选项](https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.mutate).
@@ -24,12 +24,12 @@ const apolloProvider = createApolloProvider({
},
// 查看所有查询的加载状态
// 详见 '智能查询 > 选项 > watchLoading'
watchLoading(isLoading, countModifier) {
watchLoading (isLoading, countModifier) {
loading += countModifier
console.log('Global loading', loading, countModifier)
},
// 所有智能查询和订阅的全局错误处理函数
errorHandler(error) {
errorHandler (error) {
console.log('Global error handler')
console.error(error)
},
+47 -53
View File
@@ -24,58 +24,56 @@
示例:
```js
export default {
// Apollo 具体选项
apollo: {
apollo: {
// 带参数的高级查询
// vue 会侦听 'variables' 方法
pingMessage: {
query: gql`query PingMessage($message: String!) {
pingMessage: {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// 响应式参数
variables() {
// 响应式参数
variables () {
// 在这里使用 vue 的响应式属性
return {
message: this.pingInput,
}
},
// 轮询间隔,以毫秒为单位
pollInterval: 10000,
// 也可以通过 vue 响应式属性设置轮询间隔
pollInterval() {
return this.pollInterval
},
// 变量:深度对象侦听
deep: false,
// 我们使用自定义的 update 回调函数,因为字段名称不匹配
// 默认情况下,将使用 'data' 结果对象上的 'pingMessage' 属性
// 考虑到 apollo 服务端的工作方式,我们知道结果是在 'ping' 属性中
update(data) {
console.log(data)
// 返回的值将更新 vue 属性 'pingMessage'
return data.ping
},
// 可选结果钩子
result({ data, loading, networkStatus }) {
console.log('We got some result!')
},
// 错误处理
error(error) {
console.error('We\'ve got an error!', error)
},
// 加载状态
// loadingKey 是数据属性的名称
// 在查询正在加载时将递增,不再加载时递减
loadingKey: 'loadingQueriesCount',
// 当加载状态发生变化时会调用 watchLoading
watchLoading(isLoading, countModifier) {
return {
message: this.pingInput,
}
},
// 轮询间隔,以毫秒为单位
pollInterval: 10000,
// 也可以通过 vue 响应式属性设置轮询间隔
pollInterval() {
return this.pollInterval;
},
// 变量:深度对象侦听
deep: false,
// 我们使用自定义的 update 回调函数,因为字段名称不匹配
// 默认情况下,将使用 'data' 结果对象上的 'pingMessage' 属性
// 考虑到 apollo 服务端的工作方式,我们知道结果是在 'ping' 属性中
update (data) {
console.log(data)
// 返回的值将更新 vue 属性 'pingMessage'
return data.ping
},
// 可选结果钩子
result ({ data, loading, networkStatus }) {
console.log('We got some result!')
},
// 错误处理
error (error) {
console.error('We\'ve got an error!', error)
},
// 加载状态
// loadingKey 是数据属性的名称
// 在查询正在加载时将递增,不再加载时递减
loadingKey: 'loadingQueriesCount',
// 当加载状态发生变化时会调用 watchLoading
watchLoading (isLoading, countModifier) {
// isLoading 是一个布尔值
// countModifier 为 1 或 -1
},
},
},
}
},
```
如果你使用 `ES2015``update` 也可以这样写:
@@ -87,18 +85,14 @@ update: data => data.ping
手动模式示例:
```js
export default {
apollo: {
myData: {
query: gql`...`,
manual: true,
result({ data, loading }) {
if (!loading) {
this.items = data.items
}
},
{
query: gql`...`,
manual: true,
result ({ data, loading }) {
if (!loading) {
this.items = data.items
}
}
},
}
```
+2 -2
View File
@@ -17,7 +17,7 @@ const states = ApolloSSR.getStates(apolloProvider, options)
`options` 的默认值是:
```js
const defaultOptions = {
{
// 每个 apollo 客户端状态的 key 的前缀
exportNamespace: '',
}
@@ -34,7 +34,7 @@ const js = ApolloSSR.exportStates(apolloProvider, options)
`options` 的默认值是:
```js
const defaultOptions = {
{
// 全局变量名
globalName: '__APOLLO_STATE__',
// 变量设置到的全局对象
+2 -2
View File
@@ -12,8 +12,8 @@ import { useQuery, useQueryLoading } from '@vue/apollo-composable'
export default {
setup () {
const { result: one } = useQuery(/* ... */)
const { result: two } = useQuery(/* ... */)
const { result: one } = useQuery(...)
const { result: two } = useQuery(...)
const loading = useQueryLoading()
return {
+3
View File
@@ -28,6 +28,8 @@
- `network-only`:从网络返回结果并保存到缓存,如果网络调用未成功则失败。
- `no-cache`:从网络返回结果但不保存到缓存,如果网络调用未成功则失败。
- `fetchResults`:是否获取结果。
- `metadata`:当前查询在存储中的任意元数据。可用于调试、开发人员工具等场景。
- `notifyOnNetworkStatusChange`:网络状态更新时是否应在此查询的观察者上触发下一步。
@@ -59,3 +61,4 @@
- `onResult(handler)`:有新结果可用时调用的事件钩子。
- `onError(handler)`:发生错误时调用的事件钩子。
@@ -36,3 +36,4 @@
- `onResult(handler)`:有新结果可用时调用的事件钩子。
- `onError(handler)`:发生错误时调用的事件钩子。
@@ -13,7 +13,7 @@
让我们创建一个本地 schema 来描述将在 todo 列表中作为单个事项的元素。在这个事项中应该有一些文本、一些定义它是否已经完成的属性、以及一个 ID 来区分不同的待办事项。所以,它应该是一个具有三个属性的对象:
```js
const item = {
{
id: 'uniqueId',
text: 'some text',
done: false
@@ -23,9 +23,9 @@ const item = {
现在我们可以将 `Item` 类型添加到本地 GraphQL schema 中。
```js
// main.js
//main.js
import gql from 'graphql-tag'
import gql from 'graphql-tag';
export const typeDefs = gql`
type Item {
@@ -33,7 +33,7 @@ export const typeDefs = gql`
text: String!
done: Boolean!
}
`
`;
```
这里的 `gql` 代表解析 GraphQL 查询字符串的 JavaScript 模板字符串标签。
@@ -59,7 +59,7 @@ const apolloClient = new ApolloClient({
想象我们的远程 schema 中有一个 `User` 类型:
```gql
```js
type User {
name: String!
age: Int!
@@ -73,7 +73,7 @@ export const schema = gql`
extend type User {
twitter: String
}
`
`;
```
现在,在查询用户时,我们需要指定 `twitter` 是本地字段:
@@ -85,7 +85,7 @@ const userQuery = gql`
age
twitter @client
}
`
`;
```
## 初始化 Apollo 缓存
@@ -147,7 +147,7 @@ cache.writeData({
```js
// App.vue
import gql from 'graphql-tag'
import gql from 'graphql-tag';
const todoItemsQuery = gql`
{
@@ -157,7 +157,7 @@ const todoItemsQuery = gql`
done
}
}
`
`;
```
`@client` 指令是与发送到远程 API 的查询的主要区别。该指令指定 Apollo 客户端不应在远程 GraqhQL API 上执行此查询,而是应该从本地缓存中获取结果。
@@ -165,13 +165,13 @@ const todoItemsQuery = gql`
现在,我们可以在 Vue 组件中像普通的 Apollo 查询一样使用此查询:
```js
export default {
apollo: {
todoItems: {
query: todoItemsQuery
}
},
}
// App.vue
apollo: {
todoItems: {
query: todoItemsQuery
}
},
```
## 使用变更修改本地数据
@@ -209,7 +209,7 @@ const checkItemMutation = gql`
mutation($id: ID!) {
checkItem(id: $id) @client
}
`
`;
```
我们定义了一个**本地**变更(因为在这里写了一个 `@client` 指令),它将接受一个唯一的标识符作为参数。现在,我们需要一个**解析器**:一个解析 schema 中类型或字段的值的函数。
@@ -224,14 +224,13 @@ const checkItemMutation = gql`
const resolvers = {
Mutation: {
checkItem: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: todoItemsQuery })
const currentItem = data.todoItems.find(item => item.id === id)
currentItem.done = !currentItem.done
cache.writeQuery({ query: todoItemsQuery, data })
return currentItem.done
const data = cache.readQuery({ query: todoItemsQuery });
const currentItem = data.todoItems.find(item => item.id === id);
currentItem.done = !currentItem.done;
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
}
}
};
```
在这里我们做了什么?
@@ -256,7 +255,6 @@ const resolvers = {
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
}
};
const apolloClient = new ApolloClient({
@@ -269,14 +267,14 @@ const apolloClient = new ApolloClient({
现在,我们可以在 Vue 组件中像普通的 Apollo [变更](../guide-option/mutations.md) 一样使用此变更:
```js
export default {
methods: {
checkItem(id) {
this.$apollo.mutate({
mutation: checkItemMutation,
variables: { id }
})
},
}
// App.vue
methods: {
checkItem(id) {
this.$apollo.mutate({
mutation: checkItemMutation,
variables: { id }
});
},
}
```
@@ -71,7 +71,7 @@ export default {
description
}
}`,
variables() {
variables () {
return {
id: this.id,
}
@@ -64,6 +64,7 @@ const sourceSchema = `
twitter: String
}
type Query {
allHeroes: [VueHero]
}
@@ -71,14 +72,14 @@ const sourceSchema = `
type Mutation {
addHero(hero: HeroInput!): VueHero!
deleteHero(name: String!): Boolean
}
}
`
```
下一步是使用 `graphql-tools` 方法创建可执行的 schema
```js
import { makeExecutableSchema } from 'graphql-tools'
// ...
...
const schema = makeExecutableSchema({
typeDefs: sourceSchema,
})
@@ -87,7 +88,7 @@ const schema = makeExecutableSchema({
```js
import { addMockFunctionsToSchema } from 'graphql-tools'
// ...
...
addMockFunctionsToSchema({
schema,
})
@@ -110,7 +111,7 @@ const query = `
在测试用例中调用 GraphQL 查询,保存响应到组件数据中,然后检查渲染完成的组件是否与快照匹配:
```js
graphql(schema, query).then((result) => {
graphql(schema, query).then(result => {
wrapper.setData(result.data)
expect(wrapper.element).toMatchSnapshot()
})
@@ -148,4 +149,4 @@ addMockFunctionsToSchema({
```
你可以用同样的方法来测试变更。
---
---
@@ -96,19 +96,19 @@ export default {
// vue.config.js
module.exports = {
chainWebpack: (config) => {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap((options) => {
options.transpileOptions = {
transforms: {
dangerousTaggedTemplateString: true,
},
}
return options
})
.loader('vue-loader')
.tap(options => {
options.transpileOptions = {
transforms: {
dangerousTaggedTemplateString: true,
},
}
return options
})
}
}
```
@@ -25,6 +25,7 @@ const apolloClient = new ApolloClient({
cache,
uri: 'http://localhost:4042/graphql',
})
```
::: warning
@@ -63,17 +63,15 @@ export default {
将新项添加到缓存中:
```js
export default {
methods: {
onMessageAdded(previousResult, { subscriptionData }) {
methods: {
onMessageAdded (previousResult, { subscriptionData }) {
// 之前的结果是不可变的
const newResult = {
messages: [...previousResult.messages],
}
// 添加问题到列表中
newResult.messages.push(subscriptionData.data.messageAdded)
return newResult
const newResult = {
messages: [...previousResult.messages],
}
// 添加问题到列表中
newResult.messages.push(subscriptionData.data.messageAdded)
return newResult
}
}
```
@@ -81,25 +79,22 @@ export default {
从缓存中删除一项:
```js
export default {
methods: {
onMessageAdded(previousResult, { subscriptionData }) {
const removedMessage = subscriptionData.data.messageRemoved
const index = previousResult.messages.findIndex(
m => m.id === removedMessage.id
)
methods: {
onMessageAdded (previousResult, { subscriptionData }) {
const removedMessage = subscriptionData.data.messageRemoved
const index = previousResult.messages.findIndex(
m => m.id === removedMessage.id
)
if (index === -1)
return previousResult
if (index === -1) return previousResult
// 之前的结果是不可变的
const newResult = {
messages: [...previousResult.messages],
}
// 从列表中移除问题
newResult.messages.splice(index, 1)
return newResult
// 之前的结果是不可变的
const newResult = {
messages: [...previousResult.messages],
}
// 从列表中移除问题
newResult.messages.splice(index, 1)
return newResult
}
}
```
@@ -61,16 +61,14 @@ export default {
import { onError } from '@apollo/client/link/error'
const link = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
)
}
if (networkError)
console.log(`[Network error]: ${networkError}`)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
```
@@ -80,7 +78,7 @@ const link = onError(({ graphQLErrors, networkError }) => {
import { onError } from '@apollo/client/link/error'
import { logErrorMessages } from '@vue/apollo-util'
const link = onError((error) => {
const link = onError(error => {
logErrorMessages(error)
})
```
@@ -95,7 +93,7 @@ const link = onError((error) => {
import { onError } from '@apollo/client/link/error'
import { logErrorMessages } from '@vue/apollo-util'
const link = onError((error) => {
const link = onError(error => {
if (process.env.NODE_ENV !== 'production') {
logErrorMessages(error)
}
@@ -120,8 +120,8 @@ export const entryFragment = gql`
如果我们的片段包含子片段,那么我们可以将它们传递到 `gql` 中:
```js
import { entryFragment as RepoInfoEntryFragment } from './RepoInfo.vue'
import { entryFragment as VoteButtonsEntryFragment } from './VoteButtons.vue'
import { entryFragment as RepoInfoEntryFragment } from './RepoInfo.vue'
export const entryFragment = gql`
fragment FeedEntry on Entry {
@@ -216,11 +216,11 @@ query {
2. 在构建过程中,使用 `possibleTypes.json` 来配置缓存。然后,将新配置的缓存传递给 `ApolloClient` 以完成这个过程。
```js
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client/core'
import possibleTypes from './possibleTypes.json'
const cache = new InMemoryCache({ possibleTypes })
const httpLink = createHttpLink({ uri })
const httpLink = createHttpLink({ uri });
const client = new ApolloClient({
cache,
@@ -304,7 +304,7 @@ editMessage()
### 更新所有其他缓存
如果某个变更修改了多个实体,或者它创建或删除了一个或多个实体,Apollo Client 将**不会**自动更新缓存以反映该变更所做的更改。相反,你应该使用选项中的 `update` 函数来更新缓存。
如果某个变更修改了多个实体,或者它创建或删除了一个或多个实体,Apollo Client 将**不会**自动更新缓存以反映该变更所做的更改。相反,你应该使用选项中的 `update` 函数来更新缓存。
这个 `update` 函数的目的是修改缓存的数据,以匹配服务端变更所做出的更改。
@@ -470,9 +470,9 @@ const { mutate: sendMessage, error: sendMessageError } = useMutation(gql`
在变更成功完成时调用。
```js
const { onDone } = useMutation(/* ... */)
const { onDone } = useMutation(...)
onDone((result) => {
onDone(result => {
console.log(result.data)
})
```
@@ -552,9 +552,9 @@ export default {
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useMutation(/* ... */)
const { onError } = useMutation(...)
onError((error) => {
onError(error => {
logErrorMessages(error)
})
```
@@ -11,7 +11,7 @@
在 Apollo 中,最简单的分页方法是使用由 `useQuery` 组合函数返回的 `fetchMore` 函数。这基本上可以使你执行新的 GraphQL 查询并将结果合并到原始结果中。
```js
const { fetchMore } = useQuery(/* ... */)
const { fetchMore } = useQuery(...)
```
你可以指定要用于新查询的查询和变量,以及如何将新查询结果与客户端上的现有数据合并。具体的操作将决定你要实现什么样的分页。
@@ -422,7 +422,7 @@ function selectUser (id) {
export default {
props: ['id'],
setup(props) {
setup (props) {
const { result } = useQuery(gql`
query getUserById ($id: ID!) {
user (id: $id) {
@@ -486,7 +486,7 @@ const { result } = useQuery(gql`
id: id.value
}))
function selectUser(id) {
function selectUser (id) {
id.value = id
}
```
@@ -563,7 +563,7 @@ const { result } = useQuery(gql`
enabled: enabled.value,
}))
function enableQuery() {
function enableQuery () {
enabled.value = true
}
```
@@ -670,9 +670,9 @@ export default {
将在有新结果可用时调用。
```js
const { onResult } = useQuery(/* ... */)
const { onResult } = useQuery(...)
onResult((queryResult) => {
onResult(queryResult => {
console.log(queryResult.data)
console.log(queryResult.loading)
console.log(queryResult.networkStatus)
@@ -695,9 +695,9 @@ useQuery(gql`
发生错误时触发:
```js
const { onError } = useQuery(/* ... */)
const { onError } = useQuery(...)
onError((error) => {
onError(error => {
console.log(error.graphQLErrors)
console.log(error.networkError)
})
@@ -708,9 +708,9 @@ onError((error) => {
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useQuery(/* ... */)
const { onError } = useQuery(...)
onError((error) => {
onError(error => {
logErrorMessages(error)
})
```
@@ -724,9 +724,9 @@ onError((error) => {
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useQuery(/* ... */)
const { onError } = useQuery(...)
onError((error) => {
onError(error => {
if (process.env.NODE_ENV !== 'production') {
logErrorMessages(error)
}
@@ -19,11 +19,11 @@ yarn add @vue/apollo-composable
你需要在根实例中提供一个默认的 Apollo Client 实例:
```js
import { DefaultApolloClient } from '@vue/apollo-composable'
import { provide } from 'vue'
import { DefaultApolloClient } from '@vue/apollo-composable'
const app = new Vue({
setup() {
setup () {
provide(DefaultApolloClient, apolloClient)
},
@@ -36,11 +36,11 @@ const app = new Vue({
你也可以提供为应用提供多个可用的 Apollo Client 实例。在这种情况下,建议提供一个 `default` 值:
```js
import { ApolloClients } from '@vue/apollo-composable'
import { provide } from 'vue'
import { ApolloClients } from '@vue/apollo-composable'
const app = new Vue({
setup() {
setup () {
provide(ApolloClients, {
default: apolloClient,
})
@@ -60,7 +60,7 @@ subscription onMessageAdded($channelId: ID!) {
首先,初始化 GraphQL websocket 连接:
```js
import { WebSocketLink } from '@apollo/client/link/ws'
import { WebSocketLink } from "@apollo/client/link/ws"
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000/`,
@@ -73,13 +73,13 @@ const wsLink = new WebSocketLink({
We need to either use the `WebSocketLink` or the `HttpLink` depending on the operation type:
```js
import { HttpLink, split } from '@apollo/client/core'
import { WebSocketLink } from '@apollo/client/link/ws'
import { getMainDefinition } from '@apollo/client/utilities'
import { HttpLink, split } from "@apollo/client/core"
import { WebSocketLink } from "@apollo/client/link/ws"
import { getMainDefinition } from "@apollo/client/utilities"
// 创建一个 http 连接:
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
uri: "http://localhost:3000/graphql"
})
// 创建一个 WebSocket 连接:
@@ -96,8 +96,8 @@ const link = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition'
&& definition.operation === 'subscription'
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
)
},
wsLink,
@@ -234,7 +234,7 @@ export default {
```js
const variables = ref({
channelId: 'abc'
channelId: "abc"
})
const { result } = useSubscription(
@@ -254,7 +254,7 @@ const { result } = useSubscription(
```js
const variables = reactive({
channelId: 'abc'
channelId: "abc"
})
const { result } = useSubscription(
@@ -273,7 +273,7 @@ const { result } = useSubscription(
使用函数(将会是响应式的):
```js
const channelId = ref('abc')
const channelId = ref("abc")
const { result } = useSubscription(
gql`
@@ -306,7 +306,7 @@ const { result } = useSubscription(
`,
null,
{
fetchPolicy: 'no-cache'
fetchPolicy: "no-cache"
}
)
```
@@ -325,7 +325,7 @@ const { result } = useSubscription(
`,
null,
() => ({
fetchPolicy: 'no-cache'
fetchPolicy: "no-cache"
})
)
```
@@ -359,7 +359,7 @@ function enableSub() {
你可以从 `useSubscription` 中检索加载和错误状态:
```js
const { loading, error } = useSubscription(/* ... */)
const { loading, error } = useSubscription(...)
```
### 事件钩子
@@ -369,9 +369,9 @@ const { loading, error } = useSubscription(/* ... */)
从服务端收到新结果时调用。
```js
const { onResult } = useSubscription(/* ... */)
const { onResult } = useSubscription(...)
onResult((result) => {
onResult(result => {
console.log(result.data)
})
```
@@ -383,9 +383,9 @@ onResult((result) => {
```js
import { logErrorMessages } from '@vue/apollo-util'
const { onError } = useSubscription(/* ... */)
const { onError } = useSubscription(...)
onError((error) => {
onError(error => {
logErrorMessages(error)
})
```
@@ -483,7 +483,7 @@ subscribeToMore(() => ({
在后一种情况下,订阅将随着选项的更改自动重新启动。
现在你可以为 GraphQL 文档放置相关的订阅,并在必要时添加变量:
现在你可以为 GraphQL 文档放置相关的订阅,并在必要时添加变量:
```vue{21-33}
<script>
@@ -555,19 +555,18 @@ subscribeToMore(() => ({
在很多情况下,在允许客户端接收订阅结果之前,有必要对客户端进行身份验证。为此 `SubscriptionClient` 构造函数接受一个 `connectionParams` 字段,该字段传递一个自定义对象,服务端可以在设置任何订阅之前使用该对象来验证连接。
```js
import { WebSocketLink } from '@apollo/client/link/ws'
import { WebSocketLink } from "@apollo/client/link/ws"
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000/`,
options: {
reconnect: true,
connectionParams: {
authToken: user.authToken,
authToken: user.authToken,
},
},
})
```
::: tip
`connectionParams` 可以用于可能需要的任何其他用途,不仅可以进行身份验证,还可以使用 [SubscriptionsServer](https://www.apollographql.com/docs/graphql-subscriptions/authentication) 在服务端检查负载。
`connectionParams` 可以用于可能需要的任何其他用途,不仅可以进行身份验证,还可以使用 [SubscriptionsServer](https://www.apollographql.com/docs/graphql-subscriptions/authentication) 在服务端检查负载。
:::
@@ -19,8 +19,8 @@ const defaultOptions = {
}
const clientAOptions = {
// 你可以使用 `https` 进行安全连接(在生产环境中推荐)
httpEndpoint: 'http://localhost:4000/graphql',
// 你可以使用 `https` 进行安全连接(在生产环境中推荐)
httpEndpoint: 'http://localhost:4000/graphql',
}
const clientBOptions = {
@@ -28,29 +28,28 @@ const clientBOptions = {
}
// 在 Vue 应用程序文件中调用此方法
export function createProvider(options = {}) {
const createA = createApolloClient({
export function createProvider (options = {}) {
const createA= createApolloClient({
...defaultOptions,
...clientAOptions,
})
});
const createB = createApolloClient({
...defaultOptions,
...clientBOptions,
})
});
const a = createA.apolloClient
const b = createB.apolloClient
const a = createA.apolloClient;
const b = createB.apolloClient;
// 创建 vue apollo provider
const apolloProvider = createApolloProvider({
clients: {
a,
b
},
}
defaultClient: a,
})
}
})
```
在组件的 `apollo` 选项中,你可以使用 `$client` 为所有的查询、订阅和变更定义要使用的客户端(仅限在此组件内):
@@ -66,12 +65,8 @@ export default {
你也可以在单个查询,订阅和变更的选项中使用 `client` 属性来指定客户端:
```js
export default {
apollo: {
tags: {
query: gql`...`,
client: 'b',
}
}
tags: {
query: gql`...`,
client: 'b',
}
```
@@ -11,66 +11,61 @@
:::
```js
export default {
methods: {
addTag() {
methods: {
addTag() {
// 保存用户输入以防止错误
const newTag = this.newTag
// 将其清除以尽早更新用户页面
this.newTag = ''
// 调用 graphql 变更
this.$apollo.mutate({
const newTag = this.newTag
// 将其清除以尽早更新用户页面
this.newTag = ''
// 调用 graphql 变更
this.$apollo.mutate({
// 查询语句
mutation: gql`mutation ($label: String!) {
mutation: gql`mutation ($label: String!) {
addTag(label: $label) {
id
label
}
}`,
// 参数
variables: {
// 参数
variables: {
label: newTag,
},
// 用结果更新缓存
// 查询将先通过乐观响应、然后再通过真正的变更结果更新
update: (store, { data: { addTag } }) => {
// 从缓存中读取这个查询的数据
const data = store.readQuery({ query: TAGS_QUERY })
// 将变更中的标签添加到最后
data.tags.push(addTag)
// 将数据写回缓存
store.writeQuery({ query: TAGS_QUERY, data })
},
// 乐观 UI
// 将在请求产生时作为“假”结果,使用户界面能够快速更新
optimisticResponse: {
__typename: 'Mutation',
addTag: {
__typename: 'Tag',
id: -1,
label: newTag,
},
// 用结果更新缓存
// 查询将先通过乐观响应、然后再通过真正的变更结果更新
update: (store, { data: { addTag } }) => {
// 从缓存中读取这个查询的数据
const data = store.readQuery({ query: TAGS_QUERY })
// 将变更中的标签添加到最后
data.tags.push(addTag)
// 将数据写回缓存
store.writeQuery({ query: TAGS_QUERY, data })
},
// 乐观 UI
// 将在请求产生时作为“假”结果,使用户界面能够快速更新
optimisticResponse: {
__typename: 'Mutation',
addTag: {
__typename: 'Tag',
id: -1,
label: newTag,
},
},
}).then((data) => {
},
}).then((data) => {
// 结果
console.log(data)
}).catch((error) => {
console.log(data)
}).catch((error) => {
// 错误
console.error(error)
// 恢复初始用户输入
this.newTag = newTag
})
},
console.error(error)
// 恢复初始用户输入
this.newTag = newTag
})
},
}
},
```
## 服务端示例
```js
// 假数据生成器
import faker from 'faker'
export const schema = `
type Tag {
id: Int
@@ -91,15 +86,18 @@ schema {
}
`
// 假数据生成器
import faker from 'faker'
// 生成一些标签
let id = 0
const tags = []
var id = 0
var tags = []
for (let i = 0; i < 42; i++) {
addTag(faker.random.word())
}
function addTag(label) {
const t = {
function addTag (label) {
let t = {
id: id++,
label,
}
@@ -109,15 +107,15 @@ function addTag(label) {
export const resolvers = {
Query: {
tags(root, args, context) {
tags (root, args, context) {
return tags
},
},
Mutation: {
addTag(root, { label }, context) {
addTag (root, { label }, context) {
console.log(`adding tag '${label}'`)
return addTag(label)
},
},
}
```
```
+97 -128
View File
@@ -23,12 +23,10 @@ import gql from 'graphql-tag'
直接将 [gql](https://github.com/apollographql/graphql-tag) 查询作为值:
```js
export default {
apollo: {
apollo: {
// 简单的查询,将更新 'hello' 这个 vue 属性
hello: gql`{hello}`,
},
}
hello: gql`{hello}`,
},
```
接下来你可以通过 `this.$apollo.queries.<name>` 访问这个查询。
@@ -36,14 +34,12 @@ export default {
你可以在 vue 组件的 `data` 钩子中初始化属性:
```js
export default {
data() {
return {
data () {
return {
// 初始化你的 apollo 数据
hello: '',
}
hello: '',
},
}
},
```
在服务端添加相应的 schema 和解析器:
@@ -61,7 +57,7 @@ schema {
export const resolvers = {
Query: {
hello(root, args, context) {
hello (root, args, context) {
return 'Hello world!'
},
},
@@ -88,26 +84,22 @@ export const resolvers = {
请注意,初学者常见的错误是使用与查询中的字段名不相同的数据名称,例如:
```js
export default {
apollo: {
world: gql`query {
apollo: {
world: gql`query {
hello
}`
}
}
```
注意 `world``hello` 的不同之处:`vue-apollo` 不会去猜测你想要将哪些数据从查询结果中放入组件中。默认情况下,它只会尝试你在组件中使用的数据名称(即 `apollo` 对象中的键),在本例中为 `world`。如果名称不匹配,你可以使用 `update` 选项来告诉 `vue-apollo` 在结果中使用什么样的数据:
```js
export default {
apollo: {
world: {
query: gql`query {
apollo: {
world: {
query: gql`query {
hello
}`,
update: data => data.hello
}
update: data => data.hello
}
}
```
@@ -115,12 +107,10 @@ export default {
你也可以直接在 GraphQL 文档中重命名该字段:
```js
export default {
apollo: {
world: gql`query {
apollo: {
world: gql`query {
world: hello
}`
}
}
```
@@ -131,22 +121,20 @@ export default {
你可以通过在对象中声明 `query``variables` 将变量(及其他参数)添加到 `gql` 查询中:
```js
export default {
// Apollo 具体选项
apollo: {
apollo: {
// 带参数的查询
ping: {
ping: {
// gql 查询
query: gql`query PingMessage($message: String!) {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// 静态参数
variables: {
message: 'Meow',
},
// 静态参数
variables: {
message: 'Meow',
},
},
}
},
```
你可以在这个对象中使用 apollo 的 `watchQuery` 中的选项,比如:
@@ -159,34 +147,30 @@ export default {
例如,你可以像这样添加 `fetchPolicy` apollo 选项:
```js
export default {
apollo: {
apollo: {
// 带参数的查询
ping: {
query: gql`query PingMessage($message: String!) {
ping: {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
variables: {
message: 'Meow'
},
// 在这里加入其他选项
fetchPolicy: 'cache-and-network',
variables: {
message: 'Meow'
},
// 在这里加入其他选项
fetchPolicy: 'cache-and-network',
},
}
},
```
同样的,你可以在 vue 组件中初始化属性:
```js
export default {
data() {
return {
data () {
return {
// 初始化你的 apollo 数据
ping: '',
}
},
}
ping: '',
}
},
```
在服务端添加相应的 schema 和解析器:
@@ -204,7 +188,7 @@ schema {
export const resolvers = {
Query: {
ping(root, { message }, context) {
ping (root, { message }, context) {
return `Answering ${message}`
},
},
@@ -243,26 +227,24 @@ export const resolvers = {
你可以使用将在创建组件时被调用一次的函数,并且它必须返回选项对象:
```js
export default {
// Apollo 具体选项
apollo: {
apollo: {
// 带参数的查询
ping() {
ping () {
// 它将在创建组件时被调用一次
// 必须返回选项对象
return {
return {
// gql 查询
query: gql`query PingMessage($message: String!) {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// 静态参数
variables: {
message: 'Meow',
},
}
},
// 静态参数
variables: {
message: 'Meow',
},
}
},
}
},
```
::: tip
@@ -274,36 +256,31 @@ export default {
你可以使用函数定义 `query` 选项。这将自动更新 graphql 查询的定义:
```js
export default {
apollo: {
// 特定标签可以是随机标签或最后添加的标签
featuredTag: {
query() {
// 这里你可以用'this' 访问组件实例
if (this.showTag === 'random') {
return gql`{
// 特定标签可以是随机标签或最后添加的标签
featuredTag: {
query () {
// 这里你可以用'this' 访问组件实例
if (this.showTag === 'random') {
return gql`{
randomTag {
id
label
type
}
}`
}
else if (this.showTag === 'last') {
return gql`{
} else if (this.showTag === 'last') {
return gql`{
lastTag {
id
label
type
}
}`
}
},
// 为 'featuredTag' 这个组件属性赋值
update: data => data.randomTag || data.lastTag,
},
}
}
}
},
// 为 'featuredTag' 这个组件属性赋值
update: data => data.randomTag || data.lastTag,
},
```
::: tip
@@ -315,24 +292,22 @@ export default {
使用函数使 vue 属性能够响应式的提供给参数:
```js
export default {
// Apollo 具体选项
apollo: {
apollo: {
// 带参数的查询
ping: {
query: gql`query PingMessage($message: String!) {
ping: {
query: gql`query PingMessage($message: String!) {
ping(message: $message)
}`,
// 响应式参数
variables() {
// 响应式参数
variables () {
// 在这里使用 vue 响应式属性
return {
return {
message: this.pingInput,
}
},
}
},
},
}
},
```
在每次参数更改时,将重新获取查询,例如:
@@ -354,30 +329,28 @@ export default {
如果查询被跳过,它将被禁用且结果将不再被更新。你可以使用 `skip` 选项:
```js
export default {
// Apollo 具体选项
apollo: {
tags: {
apollo: {
tags: {
// GraphQL 查询
query: gql`query tagList ($type: String!) {
query: gql`query tagList ($type: String!) {
tags(type: $type) {
id
label
}
}`,
// 响应式变量
variables() {
return {
type: this.type,
}
},
// 禁用这个查询
skip() {
return this.skipQuery
},
// 响应式变量
variables () {
return {
type: this.type,
}
},
// 禁用这个查询
skip () {
return this.skipQuery
},
},
}
},
```
在这里,当 `skipQuery` 组件属性改变时,`skip` 将被自动调用。
@@ -395,29 +368,24 @@ this.$apollo.queries.tags.skip = true
这里是一个使用轮询的响应式查询示例:
```js
export default {
// Apollo 具体选项
apollo: {
apollo: {
// vue 实例上的 'tags' 数据属性
tags: {
query: gql`query tagList {
tags: {
query: gql`query tagList {
tags {
id,
label
}
}`,
pollInterval: 300, // 毫秒
},
pollInterval: 300, // 毫秒
},
}
},
```
这里是服务端的定义:
```js
// 假数据生成器
import casual from 'casual'
export const schema = `
type Tag {
id: Int
@@ -433,15 +401,18 @@ schema {
}
`
// 假数据生成器
import casual from 'casual'
// 生成一些标签
let id = 0
const tags = []
var id = 0
var tags = []
for (let i = 0; i < 42; i++) {
addTag(casual.word)
}
function addTag(label) {
const t = {
function addTag (label) {
let t = {
id: id++,
label,
}
@@ -451,7 +422,7 @@ function addTag(label) {
export const resolvers = {
Query: {
tags(root, args, context) {
tags (root, args, context) {
return tags
},
},
@@ -463,12 +434,10 @@ export const resolvers = {
你可以使用 `$apollo.addSmartQuery(key, options)` 方法手动添加智能查询:
```js
export default {
created() {
this.$apollo.addSmartQuery('comments', {
created () {
this.$apollo.addSmartQuery('comments', {
// 选项同上文
})
}
})
}
```
@@ -25,6 +25,7 @@ const apolloClient = new ApolloClient({
cache,
uri: 'http://localhost:4042/graphql',
})
```
::: warning
@@ -58,21 +58,17 @@ this.$apollo.skipAll = true
你也可以在组件的 `apollo` 选项中声明这些属性。它们可以是布尔值:
```js
export default {
apollo: {
$skipAll: true
}
apollo: {
$skipAll: true
}
```
或是响应式函数:
```js
export default {
apollo: {
$skipAll() {
return this.foo === 42
}
apollo: {
$skipAll () {
return this.foo === 42
}
}
```
@@ -28,8 +28,8 @@ const link = split(
// 根据操作类型分割
({ query }) => {
const definition = getMainDefinition(query)
return definition.kind === 'OperationDefinition'
&& definition.operation === 'subscription'
return definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
},
wsLink,
httpLink
@@ -48,29 +48,27 @@ const apolloClient = new ApolloClient({
如果你需要更新一个来自订阅的智能查询结果,最好的方式是使用 `subscribeToMore` 查询方法。它将创建链接到智能查询的 [智能订阅](../api/smart-subscription.md)。你只需要将 `subscribeToMore` 添加到智能查询中:
```js
export default {
apollo: {
tags: {
query: TAGS_QUERY,
subscribeToMore: {
document: gql`subscription name($param: String!) {
apollo: {
tags: {
query: TAGS_QUERY,
subscribeToMore: {
document: gql`subscription name($param: String!) {
itemAdded(param: $param) {
id
label
}
}`,
// 传递给订阅的变量
// 由于我们使用了函数,因此它们是响应式的
variables() {
return {
param: this.param,
}
},
// 变更之前的结果
updateQuery: (previousResult, { subscriptionData }) => {
// 传递给订阅的变量
// 由于我们使用了函数,因此它们是响应式的
variables () {
return {
param: this.param,
}
},
// 变更之前的结果
updateQuery: (previousResult, { subscriptionData }) => {
// 在这里用之前的结果和新数据组合成新的结果
},
}
},
}
}
}
@@ -166,36 +164,34 @@ this.$watch(() => this.type, (type, oldType) => {
你可以在 `apollo` 选项中使用 `$subscribe` 关键字来声明 [智能订阅](../api/smart-subscription.md)
```js
export default {
apollo: {
apollo: {
// 订阅
$subscribe: {
$subscribe: {
// 当添加一个标签时
tagAdded: {
query: gql`subscription tags($type: String!) {
tagAdded: {
query: gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`,
// 响应式变量
variables() {
// 响应式变量
variables () {
// 像常规查询一样运作
// 在每次改变值时都会使用正确的变量重新订阅
return {
type: this.type,
}
},
// 结果钩子
// 不要忘记对 `data` 进行解构
result({ data }) {
console.log(data.tagAdded)
},
return {
type: this.type,
}
},
// 结果钩子
// 不要忘记对 `data` 进行解构
result ({ data }) {
console.log(data.tagAdded)
},
},
},
}
},
```
你可以使用 `this.$apollo.subscriptions.<name>` 访问这个订阅。
@@ -207,29 +203,27 @@ export default {
当服务端支持实时查询并使用订阅更新它们时,例如 [Hasura](https://hasura.io/),你可以使用简单订阅来实现响应式查询:
```js
export default {
data() {
return {
tags: [],
}
},
apollo: {
$subscribe: {
tags: {
query: gql`subscription {
data () {
return {
tags: [],
};
},
apollo: {
$subscribe: {
tags: {
query: gql`subscription {
tags {
id
label
type
}
}`,
result({ data }) {
this.tags = data.tags
},
result ({ data }) {
this.tags = data.tags;
},
},
},
}
},
```
## 跳过订阅
@@ -237,39 +231,37 @@ export default {
如果订阅被跳过,它将被禁用且不再被更新。你可以使用 `skip` 选项:
```js
export default {
// Apollo 具体选项
apollo: {
apollo: {
// 订阅
$subscribe: {
$subscribe: {
// 当添加一个标签时
tags: {
query: gql`subscription tags($type: String!) {
tags: {
query: gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`,
// 响应式变量
variables() {
return {
type: this.type,
}
},
// 结果钩子
result(data) {
// 更新本地数据
this.tags.push(data.tagAdded)
},
// 跳过这个订阅
skip() {
return this.skipSubscription
// 响应式变量
variables () {
return {
type: this.type,
}
},
// 结果钩子
result (data) {
// 更新本地数据
this.tags.push(data.tagAdded)
},
// 跳过这个订阅
skip () {
return this.skipSubscription
}
},
},
}
},
```
在这里,当 `skipSubscription` 组件属性改变时,`skip` 将被自动调用。
@@ -285,12 +277,10 @@ this.$apollo.subscriptions.tags.skip = true
你可以使用 `$apollo.addSmartSubscription(key, options)` 方法手动添加智能订阅:
```js
export default {
created() {
this.$apollo.addSmartSubscription('tagAdded', {
created () {
this.$apollo.addSmartSubscription('tagAdded', {
// 选项同 '$subscribe'
})
}
})
}
```
@@ -303,9 +293,8 @@ export default {
使用 `$apollo.subscribe()` 方法来创建一个 GraphQL 订阅,当组件被销毁时将自动终止。它**不会**创建智能订阅。
```js
export default {
mounted() {
const subQuery = gql`subscription tags($type: String!) {
mounted () {
const subQuery = gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
@@ -313,21 +302,20 @@ export default {
}
}`
const observer = this.$apollo.subscribe({
query: subQuery,
variables: {
type: 'City',
},
})
const observer = this.$apollo.subscribe({
query: subQuery,
variables: {
type: 'City',
},
})
observer.subscribe({
next(data) {
console.log(data)
},
error(error) {
console.error(error)
},
})
},
}
observer.subscribe({
next (data) {
console.log(data)
},
error (error) {
console.error(error)
},
})
},
```
@@ -4,7 +4,6 @@ node_modules
/tests/e2e/videos/
/tests/e2e/screenshots/
/tests/e2e/downloads/
# local env files
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
}
@@ -1,26 +1,22 @@
import axios from 'axios'
import { defineConfig } from 'cypress'
import vitePreprocessor from 'cypress-vite'
import axios from 'axios'
export default defineConfig({
module.exports = defineConfig({
fixturesFolder: 'tests/e2e/fixtures',
screenshotsFolder: 'tests/e2e/screenshots',
videosFolder: 'tests/e2e/videos',
downloadsFolder: 'tests/e2e/downloads',
e2e: {
baseUrl: 'http://localhost:8080',
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on) {
setupNodeEvents (on, config) {
on('task', {
'db:reset': async function () {
async 'db:reset' () {
await axios.get('http://localhost:4042/_reset')
return true
},
})
on('file:preprocessor', vitePreprocessor())
},
specPattern: 'tests/e2e/specs/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'tests/e2e/support/index.ts',
supportFile: 'tests/e2e/support/index.js',
},
})
+30 -28
View File
@@ -3,40 +3,42 @@
"version": "4.0.0-alpha.16",
"private": true,
"scripts": {
"dev": "vite --port 8080",
"build": "vite build",
"preview": "vite preview --port 8080",
"test": "pnpm run build && pnpm run test:e2e",
"test:e2e": "start-server-and-test preview http://localhost:8080 test:e2e:run",
"test:e2e:run": "start-server-and-test api 'http-get://localhost:4042/graphql?query=%7B__typename%7D' test:e2e:cy",
"test:e2e:cy": "cypress run --headless",
"test:e2e:dev": "cypress open",
"api": "test-server --simulate-latency 50",
"api:dev": "test-server --simulate-latency 500"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test": "pnpm run test:e2e && kill-port 4042",
"test:e2e": "start-server-and-test api http://localhost:4042/.well-known/apollo/server-health test:e2e:client",
"test:e2e:client": "vue-cli-service test:e2e --mode production --headless",
"test:e2e:dev": "start-server-and-test api http://localhost:4042/.well-known/apollo/server-health test:e2e:dev:client",
"test:e2e:dev:client": "vue-cli-service test:e2e --mode development",
"api": "node server.js"
},
"dependencies": {
"@apollo/client": "^3.7.16",
"@apollo/client": "^3.6.9",
"@vue/apollo-composable": "workspace:*",
"@vue/apollo-util": "workspace:*",
"graphql": "^16.7.1",
"apollo-server-express": "^2.25.4",
"core-js": "^3.23.2",
"cors": "^2.8.5",
"express": "^4.18.1",
"graphql": "^15.8.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.15.0",
"pinia": "^2.1.6",
"test-server": "workspace:*",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
"regenerator-runtime": "^0.13.9",
"shortid": "^2.2.16",
"vue": "^3.2.37",
"vue-demi": "^0.13.1",
"vue-router": "^4.0.16"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"cypress": "^12.17.0",
"cypress-vite": "^1.4.1",
"postcss": "^8.4.25",
"start-server-and-test": "^2.0.0",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",
"vite": "^4.4.2",
"vue-tsc": "^1.8.3"
"@babel/core": "^7.18.5",
"@types/shortid": "^0.0.29",
"@vue/cli-plugin-babel": "^5.0.6",
"@vue/cli-plugin-e2e-cypress": "^5.0.6",
"@vue/cli-plugin-typescript": "^5.0.6",
"@vue/cli-service": "^5.0.6",
"axios": "^0.24.0",
"cypress": "^10.2.0",
"kill-port": "^1.6.1",
"start-server-and-test": "^1.14.0",
"tailwindcss": "^1.9.6"
}
}
@@ -1,6 +1,8 @@
/* eslint-disable */
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
plugins: [
require('tailwindcss')(),
require('autoprefixer')(),
],
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -4,13 +4,14 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong> We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue. </strong>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
<!-- built files will be auto injected -->
</body>
</html>
@@ -1,11 +1,7 @@
import { makeExecutableSchema } from '@graphql-tools/schema'
import { PubSub, withFilter } from 'graphql-subscriptions'
import gql from 'graphql-tag'
import shortid from 'shortid'
import { channels } from './data.js'
import { GraphQLErrorWithCode } from './util.js'
const pubsub = new PubSub()
const express = require('express')
const cors = require('cors')
const { gql, ApolloServer, ApolloError, PubSub, withFilter } = require('apollo-server-express')
const shortid = require('shortid')
const typeDefs = gql`
type Channel {
@@ -51,38 +47,48 @@ type Subscription {
}
`
interface AddMessageInput {
channelId: string
text: string
const pubsub = new PubSub()
let channels = []
function resetDatabase () {
channels = [
{
id: 'general',
label: 'General',
messages: [],
},
{
id: 'random',
label: 'Random',
messages: [],
},
]
}
interface UpdateMessageInput {
id: string
channelId: string
text: string
}
resetDatabase()
const resolvers = {
Query: {
hello: () => 'Hello world!',
channels: () => channels,
channel: (root: any, { id }: { id: string }) => channels.find(c => c.id === id),
channel: (root, { id }) => channels.find(c => c.id === id),
list: () => ['a', 'b', 'c'],
good: () => 'good',
bad: async () => {
bad: () => {
throw new Error('An error')
},
},
Mutation: {
addMessage: (root: any, { input }: { input: AddMessageInput }) => {
addMessage: (root, { input }) => {
const channel = channels.find(c => c.id === input.channelId)
if (!channel) {
throw new GraphQLErrorWithCode(`Channel ${input.channelId} not found`, 'not-found')
throw new ApolloError(`Channel ${input.channelId} not found`, 'not-found')
}
const message = {
id: shortid(),
channel,
channel: channel,
text: input.text,
}
channel.messages.push(message)
@@ -90,15 +96,12 @@ const resolvers = {
return message
},
updateMessage: (root: any, { input }: { input: UpdateMessageInput }) => {
updateMessage: (root, { input }) => {
const channel = channels.find(c => c.id === input.channelId)
if (!channel) {
throw new GraphQLErrorWithCode(`Channel ${input.channelId} not found`, 'not-found')
throw new ApolloError(`Channel ${input.channelId} not found`, 'not-found')
}
const message = channel.messages.find(m => m.id === input.id)
if (!message) {
throw new GraphQLErrorWithCode(`Message ${input.id} not found`, 'not-found')
}
Object.assign(message, {
text: input.text,
})
@@ -124,7 +127,27 @@ const resolvers = {
},
}
export const schema = makeExecutableSchema({
const app = express()
app.use(cors('*'))
app.get('/_reset', (req, res) => {
resetDatabase()
res.status(200).end()
})
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => new Promise(resolve => {
setTimeout(() => resolve({}), 50)
}),
})
server.applyMiddleware({ app })
app.listen({
port: 4042,
}, () => {
console.log('🚀 Server ready at http://localhost:4042')
})
@@ -1,9 +1,6 @@
import { ApolloClient, createHttpLink, InMemoryCache, split } from '@apollo/client/core'
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client/core'
import { onError } from '@apollo/client/link/error'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { getMainDefinition } from '@apollo/client/utilities'
import { logErrorMessages } from '@vue/apollo-util'
import { createClient } from 'graphql-ws'
const cache = new InMemoryCache()
@@ -13,32 +10,12 @@ const httpLink = createHttpLink({
uri: 'http://localhost:4042/graphql',
})
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4042/graphql',
}))
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
if (definition.kind === 'OperationDefinition'
&& definition.operation === 'subscription') {
console.log(`Subscribing to ${definition.name?.value ?? 'anonymous'}`)
}
return (
definition.kind === 'OperationDefinition'
&& definition.operation === 'subscription'
)
},
wsLink,
httpLink,
)
// Handle errors
const errorLink = onError((error) => {
const errorLink = onError(error => {
logErrorMessages(error)
})
export const apolloClient = new ApolloClient({
cache,
link: errorLink.concat(splitLink),
link: errorLink.concat(httpLink),
})
@@ -2,4 +2,4 @@
@tailwind components;
@tailwind utilities;
@tailwind utilities;
@@ -1,21 +1,19 @@
<script lang="ts" setup>
import { computed } from 'vue'
import ChannelList from './ChannelList.vue'
import GlobalLoading from './GlobalLoading.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const displayChannels = computed(() => !route.matched.some(r => r.meta?.layout === 'blank'))
</script>
<template>
<GlobalLoading />
<div class="flex h-screen items-stretch bg-gray-100">
<ChannelList
v-if="displayChannels"
class="w-1/4 border-r border-gray-200"
/>
<ChannelList class="w-1/4 border-r border-gray-200" />
<router-view class="flex-1 overflow-auto" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ChannelList from './ChannelList.vue'
export default defineComponent({
name: 'App',
components: {
ChannelList,
},
})
</script>
@@ -1,26 +1,31 @@
<script lang="ts" setup>
<script lang="ts">
import gql from 'graphql-tag'
import { useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
import { defineComponent, computed } from 'vue'
interface Channel {
id: string
label: string
}
const { result, loading } = useQuery<{ channels: Channel[] }>(gql`
query channels {
channels {
...channel
}
}
export default defineComponent({
setup () {
const { result, loading } = useQuery<{ channels: Channel[] }>(gql`
query channels {
channels {
id
label
}
}
`)
const channels = computed(() => result.value?.channels ?? [])
fragment channel on Channel {
id
label
}
`)
const channels = computed(() => result.value?.channels ?? [])
return {
loading,
channels,
}
},
})
</script>
<template>
@@ -1,43 +0,0 @@
<script lang="ts" setup>
import { useChannels } from '@/stores/channel'
const channelStore = useChannels()
</script>
<template>
<div
v-if="channelStore.loading"
class="p-12 text-gray-500"
>
Loading channels...
</div>
<div
v-else
class="flex flex-col bg-white"
>
<router-link
v-for="channel of channelStore.channels"
:key="channel.id"
v-slot="{ href, navigate, isActive }"
:to="{
name: 'channel',
params: {
id: channel.id,
},
}"
custom
>
<a
:href="href"
class="channel-link px-4 py-2 hover:bg-green-100 text-green-700"
:class="{
'bg-green-200 hover:bg-green-300 text-green-900': isActive,
}"
@click="navigate"
>
# {{ channel.label }}
</a>
</router-link>
</div>
</template>
@@ -1,51 +0,0 @@
<script lang="ts" setup>
import { useChannels } from '@/stores/channel'
import { onBeforeUnmount, ref, watch } from 'vue'
const channels = ref<any[]>([])
let unwatch: (() => void) | undefined
setTimeout(() => {
const channelStore = useChannels()
unwatch = watch(() => channelStore.channels, (newChannels) => {
channels.value = newChannels
}, {
immediate: true,
})
}, 0)
onBeforeUnmount(() => {
unwatch?.()
})
</script>
<template>
<div
v-if="channels"
class="flex flex-col bg-white"
>
<router-link
v-for="channel of channels"
:key="channel.id"
v-slot="{ href, navigate, isActive }"
:to="{
name: 'channel',
params: {
id: channel.id,
},
}"
custom
>
<a
:href="href"
class="channel-link px-4 py-2 hover:bg-green-100 text-green-700"
:class="{
'bg-green-200 hover:bg-green-300 text-green-900': isActive,
}"
@click="navigate"
>
# {{ channel.label }}
</a>
</router-link>
</div>
</template>
@@ -1,25 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue'
import ChannelListPinia from './ChannelListPinia.vue'
const isComponentVisible = ref(true)
</script>
<template>
<div>
<button
data-test-id="channel-list-toggle"
@click="isComponentVisible = !isComponentVisible"
>
Toggle ChannelListPinia component
</button>
<div
v-if="isComponentVisible"
data-test-id="channel-list-container"
>
<ChannelListPinia />
</div>
</div>
</template>
@@ -22,16 +22,12 @@ export default defineComponent({
const { result, loading, refetch } = useQuery(gql`
query channel ($id: ID!) {
channel (id: $id) {
...channelView
}
}
fragment channelView on Channel {
id
label
messages {
id
text
label
messages {
id
text
}
}
}
`, () => ({
@@ -67,46 +63,42 @@ export default defineComponent({
</script>
<template>
<div>
<div
v-if="loading"
class="loading-channel fixed top-0 left-0 w-full flex"
>
<div class="px-4 py-2 rounded-b mx-auto bg-white border-b border-l border-r border-gray-200">
Loading channel...
</div>
<div
v-if="loading"
class="loading-channel"
>
Loading channel...
</div>
<div
v-else
class="flex flex-col"
>
<div class="flex-none p-6 border-b border-gray-200 bg-white">
Currently viewing # {{ channel.label }}
<a
class="text-green-500 cursor-pointer"
data-test-id="refetch"
@click="refetch()"
>Refetch</a>
</div>
<div
v-if="channel"
class="flex flex-col h-full"
ref="messagesEl"
class="flex-1 overflow-y-auto"
>
<div class="flex-none p-6 border-b border-gray-200 bg-white">
Currently viewing # {{ channel.label }}
<a
class="text-green-500 cursor-pointer"
data-test-id="refetch"
@click="refetch()"
>Refetch</a>
</div>
<div
ref="messagesEl"
class="flex-1 overflow-y-auto"
>
<MessageItem
v-for="message of channel.messages"
:key="message.id"
:message="message"
class="m-2"
/>
</div>
<MessageForm
:channel-id="channel.id"
class="flex-none m-2 mt-0"
<MessageItem
v-for="message of channel.messages"
:key="message.id"
:message="message"
class="m-2"
/>
</div>
<MessageForm
:channel-id="channel.id"
class="flex-none m-2 mt-0"
/>
</div>
</template>
@@ -1,68 +0,0 @@
<script lang="ts">
import gql from 'graphql-tag'
import { useQuery } from '@vue/apollo-composable'
import { defineComponent, computed, ref } from 'vue'
export default defineComponent({
setup () {
const selectedId = ref<{ id: string } | null>(null)
const { result, loading } = useQuery(gql`
query channel ($id: ID!) {
channel (id: $id) {
id
label
messages {
id
}
}
}
`, () => ({
// Should not throw since it will not be called if the query is disabled
id: selectedId.value!.id,
}), () => ({
fetchPolicy: 'no-cache',
enabled: !!selectedId.value,
}))
const channel = computed(() => result.value?.channel)
function load () {
selectedId.value = { id: 'general' }
}
return {
load,
loading,
channel,
}
},
})
</script>
<template>
<div class="m-6">
<div>
<button
class="bg-green-200 rounded-lg p-4"
@click="load()"
>
Load channel
</button>
</div>
<div
v-if="loading"
class="loading"
>
Loading...
</div>
<div
v-if="channel"
data-test-id="data"
>
<div>Loaded channel: {{ channel.label }}</div>
<div>Messages: {{ channel.messages.length }}</div>
</div>
</div>
</template>
@@ -1,16 +0,0 @@
<script lang="ts" setup>
import { useGlobalQueryLoading } from '@vue/apollo-composable'
const loading = useGlobalQueryLoading()
</script>
<template>
<div
class="fixed bg-white p-4 rounded-br top-0 right-0"
data-test-id="global-loading"
>
<div v-if="loading">
Global loading...
</div>
</div>
</template>
@@ -1,115 +0,0 @@
<script lang="ts" setup>
import { useApolloClient, useQuery } from '@vue/apollo-composable'
import gql from 'graphql-tag'
import { computed, ref } from 'vue'
import MessageItem from './MessageItem.vue'
interface Channel {
id: string
label: string
}
const keepPreviousResult = ref(false)
const channelsQuery = useQuery<{ channels: Channel[] }>(gql`
query channels {
channels {
id
label
}
}
`)
const channels = computed(() => channelsQuery.result.value?.channels ?? [])
const selectedChannelId = ref<string | null>(null)
const selectedChannelQuery = useQuery(gql`
query channel ($id: ID!) {
channel (id: $id) {
id
label
messages {
id
text
}
}
}
`, () => ({
id: selectedChannelId.value,
}), () => ({
enabled: !!selectedChannelId.value,
fetchPolicy: 'cache-and-network',
keepPreviousResult: keepPreviousResult.value,
}))
const selectedChannel = computed(() => selectedChannelQuery.result.value?.channel)
const { client: apolloClient } = useApolloClient()
function clearCache () {
apolloClient.clearStore()
}
</script>
<template>
<div class="h-full flex flex-col">
<div class="p-4">
<label>
<input
v-model="keepPreviousResult"
type="checkbox"
>
keepPreviousResult
</label>
<button
class="ml-4 underline"
@click="clearCache"
>
Clear cache
</button>
</div>
<div class="flex h-full">
<div class="flex flex-col">
<button
v-for="channel of channels"
:key="channel.id"
class="channel-btn p-4"
:class="{
'bg-green-200': selectedChannelId === channel.id,
}"
@click="selectedChannelId = channel.id"
>
{{ channel.label }}
</button>
</div>
<div
v-if="selectedChannel"
class="the-channel flex flex-col w-full h-full overflow-auto"
>
<div class="flex-none p-6 border-b border-gray-200 bg-white">
# {{ selectedChannel.label }}
</div>
<div class="flex-1 overflow-y-auto">
<MessageItem
v-for="message of selectedChannel.messages"
:key="message.id"
:message="message"
class="m-2"
/>
</div>
</div>
<div
v-else
class="no-data"
>
No data
</div>
</div>
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More