├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .github
└── FUNDING.yml
├── .gitignore
├── .npmignore
├── README.md
├── client-addon
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── README.md
├── babel.config.js
├── package.json
├── public
│ └── .gitkeep
├── src
│ ├── components
│ │ ├── ApolloView.vue
│ │ ├── GraphqlPlayground.vue
│ │ ├── SimpleGraph.vue
│ │ └── widgets
│ │ │ ├── EngineKeyMetrics.vue
│ │ │ ├── EngineKeyMetricsErrorPercentage.vue
│ │ │ ├── EngineKeyMetricsP95Time.vue
│ │ │ ├── EngineKeyMetricsQueries.vue
│ │ │ ├── EngineKeyMetricsQuery.vue
│ │ │ ├── EngineKeyMetricsRequestRate.vue
│ │ │ └── EngineKeyMetricsView.vue
│ ├── main.js
│ └── utils
│ │ └── math.js
├── vue.config.js
└── yarn.lock
├── docs
├── .vuepress
│ ├── config.js
│ ├── public
│ │ ├── favicon.png
│ │ └── screenshot.png
│ └── style.styl
├── README.md
└── guide
│ ├── auth.md
│ ├── client-state.md
│ ├── configuration.md
│ ├── directives.md
│ ├── engine.md
│ ├── env.md
│ ├── express-middleware.md
│ ├── index.md
│ ├── injected-commands.md
│ ├── manual-changes.md
│ ├── mocks.md
│ ├── server-prod.md
│ ├── server.md
│ └── webpack.md
├── generator
├── index.js
└── templates
│ ├── api-server
│ ├── default
│ │ └── apollo-server
│ │ │ ├── context.js
│ │ │ ├── data-sources.js
│ │ │ ├── directives.js
│ │ │ ├── mocks.js
│ │ │ ├── resolvers.js
│ │ │ ├── schema.graphql
│ │ │ └── type-defs.js
│ └── examples
│ │ └── apollo-server
│ │ ├── server.js
│ │ └── utils
│ │ ├── db.js
│ │ └── upload.js
│ └── vue-apollo
│ ├── default
│ ├── apollo.config.js
│ └── src
│ │ └── vue-apollo.js
│ └── examples
│ └── src
│ ├── components
│ └── ApolloExample.vue
│ └── graphql
│ ├── AddMessage.gql
│ ├── FileFragment.gql
│ ├── Files.gql
│ ├── HelloWorld.gql
│ ├── MessageAdded.gql
│ ├── MessageFragment.gql
│ ├── Messages.gql
│ └── UploadFile.gql
├── graphql-client
├── index.js
└── src
│ └── index.js
├── graphql-server
└── index.js
├── index.js
├── logo.png
├── operations
└── engine
│ └── key-metrics
│ ├── error-percentage.js
│ ├── p95-time.js
│ └── request-rate.js
├── package.json
├── prompts.js
├── screenshot.png
├── types.d.ts
├── ui-public
├── apollo-engine.png
├── publish-task.png
├── view-tip.png
└── vue-apollo-graphql.png
├── ui
├── configs.js
├── index.js
├── tasks.js
├── views.js
└── widgets.js
├── utils
├── check-schema.js
├── engine-api.js
├── generate-schema.js
├── index.js
├── load-env.js
├── load.js
└── publish-schema.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "modules": "commonjs"
5 | }]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | templates/
3 | dist/
4 | client-addon/
5 | client-addon-dist/
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Use only this configuration
3 | root: true,
4 | // File parser
5 | parser: 'vue-eslint-parser',
6 | parserOptions: {
7 | // Use babel-eslint for JavaScript
8 | parser: 'babel-eslint',
9 | ecmaVersion: 2017,
10 | // With import/export syntax
11 | sourceType: 'module',
12 | },
13 | // Environment global objects
14 | env: {
15 | browser: true,
16 | es6: true,
17 | jest: true,
18 | },
19 | extends: [
20 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
21 | 'standard',
22 | // https://github.com/vuejs/eslint-plugin-vue#priority-c-recommended-minimizing-arbitrary-choices-and-cognitive-overhead
23 | 'plugin:vue/recommended',
24 | ],
25 | rules: {
26 | 'comma-dangle': ['error', 'always-multiline'],
27 |
28 | // Vue
29 |
30 | // Error
31 | 'vue/html-closing-bracket-newline': ['error', {
32 | singleline: 'never',
33 | multiline: 'always',
34 | }],
35 | 'vue/html-closing-bracket-spacing': ['error', {
36 | startTag: 'never',
37 | endTag: 'never',
38 | selfClosingTag: 'never',
39 | }],
40 | 'vue/max-attributes-per-line': ['error', {
41 | singleline: 2,
42 | multiline: {
43 | max: 1,
44 | allowFirstLine: false,
45 | },
46 | }],
47 | // Warn
48 | 'vue/require-default-prop': 'warn',
49 | 'vue/require-prop-types': 'warn',
50 | },
51 | }
52 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: Akryum
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | client-addon-dist/
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | client-addon/
3 | .babelrc
4 | .eslintignore
5 | .eslintrc.js
6 | yarn.lock
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-cli-plugin-apollo
2 |
3 | [ ](https://www.npmjs.com/package/vue-cli-plugin-apollo)
4 | [](https://github.com/vuejs/vue-cli)
5 | [](https://www.apollographql.com/)
6 |
7 | **:rocket: Start building a Vue app with Apollo and GraphQL in 2 minutes!**
8 |
9 | This is a vue-cli 3.x plugin to add Apollo and GraphQL in your Vue project.
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## Sponsors
24 |
25 | [](https://guillaume-chau.info/sponsors)
26 |
27 |
28 |
29 | 
30 |
31 |
32 |
33 | ## :star: Features
34 |
35 | - Automatically integrate [vue-apollo](https://github.com/Akryum/vue-apollo) into your Vue app
36 | - Embed Apollo client config (upgradable and customizable)
37 | - Websockets
38 | - File uploads
39 | - Client state with [apollo-link-state](https://github.com/apollographql/apollo-link-state)
40 | - Included optional Graphql Server (upgradable and customizable):
41 | - Dead simple GraphQL API sources generated into your project (with import/export support)
42 | - Upgradable service running [apollo-server](https://www.apollographql.com/docs/apollo-server/)
43 | - Websocket subscriptions support
44 | - Optional automatic mocking
45 | - [Apollo Engine](https://www.apollographql.com/engine) support
46 | - GraphQL playground integrated in the CLI UI
47 | - Configuration screen in the CLI UI
48 | - Server-Side Rendering with [@akryum/vue-cli-plugin-ssr](https://github.com/Akryum/vue-cli-plugin-ssr)
49 | - Included optional example component with:
50 | - Watched query
51 | - Mutation
52 | - Realtime subscription using Websockets
53 | - Fully working image gallery with image upload
54 | - GraphQL validation using ESLint
55 |
--------------------------------------------------------------------------------
/client-addon/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | 'extends': [
7 | 'plugin:vue/essential',
8 | '@vue/standard',
9 | ],
10 | rules: {
11 | 'no-console': process.env.NODE_ENV === 'production' ? 'warning' : 'off',
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
13 | 'comma-dangle': ['error', 'always-multiline'],
14 | },
15 | parserOptions: {
16 | parser: 'babel-eslint',
17 | },
18 | globals: {
19 | 'ClientAddonApi': false,
20 | 'mapSharedData': false,
21 | 'Vue': false,
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/client-addon/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/client-addon/.postcssrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {},
4 | },
5 | }
6 |
--------------------------------------------------------------------------------
/client-addon/README.md:
--------------------------------------------------------------------------------
1 | # Client addon for vue-apollo cli plugin
2 |
3 | ```
4 | yarn serve
5 | yarn build
6 | ```
7 |
--------------------------------------------------------------------------------
/client-addon/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset',
4 | ],
5 | }
6 |
--------------------------------------------------------------------------------
/client-addon/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client-addon",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@vue/cli-ui": "^3.0.0",
12 | "@vue/ui": "^0.5.6",
13 | "core-js": "^3.1.2",
14 | "d3": "^5.7.0",
15 | "vue": "^2.5.16"
16 | },
17 | "devDependencies": {
18 | "@vue/cli-plugin-babel": "^4.0.4",
19 | "@vue/cli-plugin-eslint": "^4.0.4",
20 | "@vue/cli-service": "^4.0.4",
21 | "@vue/cli-ui": "^3.0.0",
22 | "@vue/eslint-config-standard": "^4.0.0",
23 | "eslint": "^4.19.1",
24 | "eslint-plugin-vue": "^5.2.3",
25 | "stylus": "^0.54.5",
26 | "stylus-loader": "^3.0.2",
27 | "vue-template-compiler": "^2.5.16"
28 | },
29 | "browserslist": [
30 | "> 1%",
31 | "last 2 versions"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/client-addon/public/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/client-addon/public/.gitkeep
--------------------------------------------------------------------------------
/client-addon/src/components/ApolloView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Apollo Engine
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/client-addon/src/components/GraphqlPlayground.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
An error occured
6 |
7 |
8 |
9 |
10 |
GraphQL server not running
11 |
12 |
13 |
14 |
17 |
Starting server...
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
99 |
100 |
126 |
--------------------------------------------------------------------------------
/client-addon/src/components/SimpleGraph.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
58 |
59 |
80 |
--------------------------------------------------------------------------------
/client-addon/src/components/widgets/EngineKeyMetrics.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
14 |
15 |
An error occured while fetching metrics
16 |
17 |
18 |
22 |
26 |
No available data
27 |
28 |
29 |
35 |
36 |
37 |
38 |
39 |
134 |
135 |
149 |
--------------------------------------------------------------------------------
/client-addon/src/components/widgets/EngineKeyMetricsErrorPercentage.vue:
--------------------------------------------------------------------------------
1 |
71 |
72 |
86 |
--------------------------------------------------------------------------------
/client-addon/src/components/widgets/EngineKeyMetricsP95Time.vue:
--------------------------------------------------------------------------------
1 |
62 |
63 |
77 |
--------------------------------------------------------------------------------
/client-addon/src/components/widgets/EngineKeyMetricsQueries.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | No queries to display
5 |
6 |
7 |
13 |
14 |
15 |
16 |
17 |
38 |
--------------------------------------------------------------------------------
/client-addon/src/components/widgets/EngineKeyMetricsQuery.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ id }}
4 |
{{ query.name || query.signature }}
5 |
6 | {{ formatNumber(query.value) }}
7 | {{ unit }}
8 |
9 |
10 |
11 |
12 |
39 |
40 |
66 |
--------------------------------------------------------------------------------
/client-addon/src/components/widgets/EngineKeyMetricsRequestRate.vue:
--------------------------------------------------------------------------------
1 |
75 |
76 |
80 |
--------------------------------------------------------------------------------
/client-addon/src/components/widgets/EngineKeyMetricsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
{{ title }}
13 |
14 |
18 | {{ formatNumber(mainStat.value) }}
19 | {{ unit }}
20 |
21 |
22 |
23 |
{{ timeRangeLabel}} median
24 |
25 |
26 |
27 |
31 |
32 |
33 |
34 |
{{ queriesTitle }}
35 |
39 |
40 |
41 |
42 |
47 |
48 |
49 |
50 |
51 |
126 |
127 |
219 |
--------------------------------------------------------------------------------
/client-addon/src/main.js:
--------------------------------------------------------------------------------
1 | import GraphqlPlayground from './components/GraphqlPlayground.vue'
2 | import EngineKeyMetrics from './components/widgets/EngineKeyMetrics.vue'
3 |
4 | import ApolloView from './components/ApolloView.vue'
5 |
6 | ClientAddonApi.component('org.akryum.vue-apollo.components.playground', GraphqlPlayground)
7 | ClientAddonApi.component('org.akryum.vue-apollo.components.widgets.engine-key-metrics', EngineKeyMetrics)
8 |
9 | ClientAddonApi.addRoutes('org.akryum.vue-apollo', [
10 | {
11 | path: '',
12 | name: 'org.akryum.vue-apollo.routes.apollo',
13 | component: ApolloView,
14 | },
15 | ])
16 |
--------------------------------------------------------------------------------
/client-addon/src/utils/math.js:
--------------------------------------------------------------------------------
1 | export function round (value) {
2 | return Math.round(value * 1000) / 1000
3 | }
4 |
5 | export function formatNumber (value) {
6 | if (Number.isNaN(value) || value == null) return 0
7 | let result = value
8 | const units = ['B', 'M', 'k']
9 | const l = units.length
10 | for (let i = 0; i < l; i++) {
11 | const j = l - i
12 | if (result > 1000 ** j) {
13 | result /= 1000 ** j
14 | return `${round(result)}${units[i]}`
15 | }
16 | }
17 | return round(result)
18 | }
19 |
--------------------------------------------------------------------------------
/client-addon/vue.config.js:
--------------------------------------------------------------------------------
1 | const { clientAddonConfig } = require('@vue/cli-ui')
2 |
3 | module.exports = {
4 | ...clientAddonConfig({
5 | id: 'vue-apollo',
6 | port: 8043,
7 | }),
8 | outputDir: '../client-addon-dist',
9 | }
10 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | base: '/',
3 | serviceWorker: true,
4 | head: [
5 | ['link', { rel: 'icon', href: '/favicon.png' }],
6 | ],
7 | locales: {
8 | '/': {
9 | lang: 'en-US',
10 | title: 'Vue CLI Apollo plugin',
11 | description: '⚡️ Integrate GraphQL in your Vue.js apps!',
12 | },
13 | },
14 | themeConfig: {
15 | repo: 'Akryum/vue-cli-plugin-apollo',
16 | docsDir: 'docs',
17 | editLinks: true,
18 | serviceWorker: {
19 | updatePopup: true,
20 | },
21 | locales: {
22 | '/': {
23 | selectText: 'Languages',
24 | label: 'English',
25 | lastUpdated: 'Last Updated',
26 | nav: [
27 | {
28 | text: 'Guide',
29 | link: '/guide/',
30 | },
31 | {
32 | text: 'vue-apollo',
33 | link: 'https://github.com/Akryum/vue-apollo',
34 | },
35 | {
36 | text: 'Patreon',
37 | link: 'https://www.patreon.com/akryum',
38 | },
39 | ],
40 | sidebarDepth: 3,
41 | sidebar: {
42 | '/guide/': [
43 | '',
44 | 'server',
45 | 'injected-commands',
46 | 'env',
47 | 'webpack',
48 | 'manual-changes',
49 | {
50 | title: 'Advanced',
51 | collapsable: false,
52 | children: [
53 | 'configuration',
54 | 'client-state',
55 | 'auth',
56 | 'mocks',
57 | 'directives',
58 | 'engine',
59 | 'express-middleware',
60 | 'server-prod',
61 | ],
62 | },
63 | ],
64 | },
65 | },
66 | },
67 | },
68 | }
69 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/docs/.vuepress/public/favicon.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/docs/.vuepress/public/screenshot.png
--------------------------------------------------------------------------------
/docs/.vuepress/style.styl:
--------------------------------------------------------------------------------
1 | .home .hero img
2 | max-width 80vw
3 |
4 | .gold-sponsor,
5 | .silver-sponsor,
6 | .bronze-sponsor
7 | margin 0 20px
8 |
9 | .gold-sponsor
10 | max-width 400px !important
11 |
12 | .silver-sponsor
13 | max-width 200px !important
14 |
15 | .bronze-sponsor
16 | max-width 100px !important
17 | max-height 50px !important
18 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | heroImage: /screenshot.png
4 | actionText: Get Started →
5 | actionLink: /guide/
6 | features:
7 | - title: Auto-pilot
8 | details: Automatically integrate vue-apollo into your Vue app!
9 | - title: Builtin Apollo client config
10 | details: Get started with a zero-config, customizable & upgradable Apollo client
11 | - title: Embedded Apollo Server
12 | details: Create a fullstack GraphQL app in minutes! Supports websocket subscriptions and more!
13 | footer: LICENCE MIT - Created by Guillaume CHAU (@Akryum)
14 | ---
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ## Sponsors
23 |
24 | [](https://guillaume-chau.info/sponsors)
25 |
26 | ## Quick Start
27 |
28 | ```bash
29 | vue add apollo
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/guide/auth.md:
--------------------------------------------------------------------------------
1 | # Authorization Header
2 |
3 | By default, `createApolloClient` will retrieve the `Authorization` header value from `localStorage`. You can override this behavior with the `getAuth` option:
4 |
5 | ```js
6 | const options = {
7 | // ...
8 |
9 | getAuth: (tokenName) => getUserToken(),
10 | }
11 |
12 | const { apolloClient } = createApolloClient(options)
13 | ```
14 |
15 | If you use cookies, you can return `undefined`.
16 |
17 | Example `apolloserver/context.js` that validates the token and set `userId` on resolvers context:
18 |
19 | ```js
20 | import users from './connectors/users'
21 |
22 | // Context passed to all resolvers (third argument)
23 | // req => Query
24 | // connection => Subscription
25 | // eslint-disable-next-line no-unused-vars
26 | export default ({ req, connection }) => {
27 | // If the websocket context was already resolved
28 | if (connection && connection.context) return connection.context
29 |
30 | let token
31 | // HTTP
32 | if (req) token = req.get('Authorization')
33 | // Websocket
34 | if (connection) token = connection.authorization
35 |
36 | // User validation
37 | let userId
38 | if (token && users.validateToken(token)) {
39 | userId = token.userId
40 | }
41 |
42 | return {
43 | token,
44 | userId,
45 | }
46 | }
47 | ```
--------------------------------------------------------------------------------
/docs/guide/client-state.md:
--------------------------------------------------------------------------------
1 | # Client state
2 |
3 | You can use [local state](https://www.apollographql.com/docs/tutorial/local-state/) for client-only local data with the related options of `createApolloClient`:
4 |
5 | ```js
6 | import gql from 'graphql-tag'
7 | import { createApolloClient } from 'vue-cli-plugin-apollo/graphql-client'
8 |
9 | const options = {
10 | // ...
11 |
12 | typeDefs: gql`
13 | type Query {
14 | connected: Boolean!
15 | }
16 | `,
17 | resolvers: {
18 | Mutation: {
19 | connectedSet: (root, { value }, { cache }) => {
20 | const data = {
21 | connected: value,
22 | }
23 | cache.writeData({ data })
24 | },
25 | },
26 | },
27 | onCacheInit: cache => {
28 | const data = {
29 | connected: false,
30 | }
31 | cache.writeData({ data })
32 | },
33 | }
34 |
35 | const { apolloClient } = createApolloClient(options)
36 | ```
37 |
38 | Then you need to use the `@client` directive:
39 |
40 | ```graphql
41 | query isConnected {
42 | connected @client
43 | }
44 | ```
45 |
46 | ```graphql
47 | mutation setConnected ($value: Boolean!) {
48 | connectedSet (value: $value) @client
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/guide/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | ## createApolloClient options
4 |
5 | ```js
6 | createApolloClient({
7 | // URL to the HTTP API
8 | httpEndpoint,
9 | // Url to the Websocket API
10 | wsEndpoint: null,
11 | // Token used in localstorage
12 | tokenName: 'apollo-token',
13 | // Enable this if you use Query persisting with Apollo Engine
14 | persisting: false,
15 | // Or, advanced persisting options, see https://github.com/apollographql/apollo-link-persisted-queries#options
16 | // Example:
17 | // persisting: {
18 | // generateHash: query => sha256()
19 | // .update(print(query))
20 | // .digest('hex'),
21 | //},
22 | // Is currently Server-Side Rendering or not
23 | ssr: false,
24 | // Only use Websocket for all requests (including queries and mutations)
25 | websocketsOnly: false,
26 | // Custom starting link.
27 | // If you want to replace the default HttpLink, set `defaultHttpLink` to false
28 | link: null,
29 | // Custom pre-auth links
30 | // Useful if you want, for example, to set a custom middleware for refreshing an access token.
31 | preAuthLinks: [],
32 | // If true, add the default HttpLink.
33 | // Disable it if you want to replace it with a terminating link using `link` option.
34 | defaultHttpLink: true,
35 | // Options for the default HttpLink
36 | httpLinkOptions: {},
37 | // Custom Apollo cache implementation (default is apollo-cache-inmemory)
38 | cache: null,
39 | // Options for the default cache
40 | inMemoryCacheOptions: {},
41 | // Additional Apollo client options
42 | apollo: {},
43 | // apollo-link-state options
44 | clientState: null,
45 | // Function returning Authorization header token
46 | getAuth: defaultGetAuth,
47 | })
48 | ```
49 |
50 | ## Plugin options
51 |
52 | The GraphQL API Server can be configured via the `pluginOptions` in `vue.config.js`:
53 |
54 | ``` js
55 | module.exports = {
56 | // Other options...
57 | pluginOptions: {
58 | // Apollo-related options
59 | apollo: {
60 | // Enable automatic mocking
61 | enableMocks: true,
62 | // Enable Apollo Engine
63 | enableEngine: true,
64 | // Enable ESLint for `.gql` files
65 | lintGQL: false,
66 |
67 | /* Other options (with default values) */
68 |
69 | // Base folder for the server source files
70 | serverFolder: './apollo-server',
71 | // Cross-Origin options
72 | cors: '*',
73 | // Requests timeout (ms)
74 | timeout: 120000,
75 | // Integrated apollo engine
76 | integratedEngine: true,
77 | // For enable typescript server files
78 | // if you don't have @vue/cli-plugin-typescript
79 | typescript: true,
80 | // Apollo server options (will be merged with the included default options)
81 | serverOptions: {
82 | // ...
83 | },
84 | },
85 | },
86 | }
87 | ```
88 |
89 | See [Apollo Server options](https://www.apollographql.com/docs/apollo-server/v2/api/apollo-server.html#constructor-options-lt-ApolloServer-gt).
90 |
--------------------------------------------------------------------------------
/docs/guide/directives.md:
--------------------------------------------------------------------------------
1 | # Directives
2 |
3 | A GraphQL directive is an annotation to either a GraphQL schema or a GraphQL document (for queries). It allows to modify the behavior of the API in a declaractive way.
4 |
5 | The usage syntax is with the 'at' character: `@myDirective`.
6 |
7 | [More documentation](https://www.apollographql.com/docs/graphql-tools/schema-directives.html)
8 |
9 | You can add custom GraphQL directives in the `./apollo-server/directives.js` file.
10 |
11 | ```js
12 | export default {
13 | // Now you can use '@private' in the schema
14 | private: PrivateDirective
15 | }
16 | ```
17 |
18 | Here is an example directive:
19 |
20 | ```js
21 | const { SchemaDirectiveVisitor } = require('graphql-tools')
22 | const { defaultFieldResolver } = require('graphql')
23 |
24 | module.exports = class PrivateDirective extends SchemaDirectiveVisitor {
25 | visitFieldDefinition (field) {
26 | const { resolve = defaultFieldResolver } = field
27 | field.resolve = (root, args, context, info) => {
28 | if (!context.userId) throw new Error('Unauthorized')
29 | return resolve(root, args, context, info)
30 | }
31 | }
32 | }
33 | ```
34 |
35 | On the GraphQL server schema, use the directive like this:
36 |
37 | ```grahpql
38 | type Query {
39 | messages: [Message] @private
40 | }
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/guide/engine.md:
--------------------------------------------------------------------------------
1 | # Apollo Engine
2 |
3 | [Apollo Engine](https://www.apollographql.com/engine) is a commercial product from Apollo. It enables lots of additional features like monitoring, error reporting, caching and query persisting.
4 |
5 | Create a key at [engine.apollographql.com](https://engine.apollographql.com) (it's free!).
6 |
--------------------------------------------------------------------------------
/docs/guide/env.md:
--------------------------------------------------------------------------------
1 | # Env variables
2 |
3 | - **`VUE_APP_GRAPHQL_HTTP`**
4 |
5 | The url to the graphql HTTP endpoint, default: `http://localhost:4000`
6 |
7 | - **`VUE_APP_GRAPHQL_WS`**
8 |
9 | The url to the graphql Websockets endpoint for subscriptions, default: `ws://localhost:4000`
10 |
11 | ## With the GraphQL server enabled
12 |
13 | - **`VUE_APP_GRAPHQL_HOST`**
14 |
15 | Hostname of GraphQL API Server, default: `localhost`
16 |
17 | - **`VUE_APP_GRAPHQL_PATH`**
18 |
19 | Path on which to serve graphQL, defaultL `/graphql`
20 |
21 | - **`VUE_APP_GRAPHQL_PORT`**
22 |
23 | Port of the GraphQL API Server, default: `4000`
24 |
25 | - **`VUE_APP_APOLLO_ENGINE_KEY`**
26 |
27 | API key for [Apollo Engine](https://engine.apollographql.com)
28 |
29 | - **`VUE_APP_APOLLO_ENGINE_TAG`**
30 |
31 | Queries made to the API will be marked with this Schema Tag. Useful for segmenting queries (for example 'test', 'staging', 'prod').
32 |
33 | - **`VUE_APP_GRAPHQL_SUBSCRIPTIONS_PATH`**
34 |
35 | Subscriptions path, default: `/graphql`
36 |
37 | ## Advanced
38 |
39 | - **`APOLLO_ENGINE_API_ENDPOINT`**
40 |
41 | Endpoint to use to the Engine requests.
42 |
43 | - **`APOLLO_ENGINE_FRONTEND`**
44 |
45 | URL to the Engine website.
46 |
--------------------------------------------------------------------------------
/docs/guide/express-middleware.md:
--------------------------------------------------------------------------------
1 | # Express middleware
2 |
3 | If you need to add express middlewares into the GraphQL server, you can create a `./apollo-server/server.js` file:
4 |
5 | ```js
6 | import path from 'path'
7 | import express from 'express'
8 |
9 | const distPath = path.resolve(__dirname, '../../dist')
10 |
11 | export default app => {
12 | app.use(express.static(distPath))
13 | }
14 | ```
15 |
--------------------------------------------------------------------------------
/docs/guide/index.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | ::: warning
4 | This plugin is intended to be used in a project created with Vue CLI 3.
5 | :::
6 |
7 | Add the plugin to the project:
8 |
9 | ```bash
10 | vue add apollo
11 | ```
12 |
13 | ::: tip
14 | An example `ApolloExample.vue` component alongside some GraphQL query files will be added into your sources if you chose to include the examples.
15 | :::
16 |
17 | Start your app:
18 |
19 | ```
20 | npm run serve
21 | ```
22 |
23 | [Recommended VS Code extension](https://github.com/prismagraphql/vscode-graphql)
24 |
25 | **Updating `vue-cli-plugin-apollo` will also update both Apollo Client and its configuration for you! :+1:**
26 |
27 | Read the [vue-apollo doc](https://github.com/Akryum/vue-apollo).
28 |
29 | ## Sponsors
30 |
31 | [](https://guillaume-chau.info/sponsors)
32 |
33 | ## Become a sponsor
34 |
35 | Is your company using vue-apollo or vue-cli-plugin-apollo to build awesome apps? Join the other patrons and become a sponsor to add your logo on this documentation! Supporting me on Patreon allows me to work less for a job and to work more on Free Open Source Software such as vue-apollo! Thank you!
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/docs/guide/injected-commands.md:
--------------------------------------------------------------------------------
1 | # Injected Commands
2 |
3 | - **`vue-cli-service apollo:watch`**
4 |
5 | Run the GraphQL API server with info from `./apollo-server` and watch the files to restart itself automatically.
6 |
7 | - **`vue-cli-service apollo:run`**
8 |
9 | Run the GraphQL API server with info from `./apollo-server` once.
10 |
11 | - **`vue-cli-service apollo:schema:generate`**
12 |
13 | (WIP) Generates GraphQL and JSON files from the running API, useful for integration with other tools like IDE plugins.
14 |
15 | - **`vue-cli-service apollo:schema:publish`**
16 |
17 | Publish schema to Apollo Engine
18 |
--------------------------------------------------------------------------------
/docs/guide/manual-changes.md:
--------------------------------------------------------------------------------
1 | # Manual code changes
2 |
3 | In case the plugin isn't able to modify the file containing the root Vue instance:
4 |
5 | Import the provider:
6 |
7 | ```js
8 | import { createProvider } from './vue-apollo'
9 | ```
10 |
11 | Then in the root instance, set the `apolloProvider` option:
12 |
13 | ```js
14 | new Vue({
15 | el: '#app',
16 | // Add this line
17 | apolloProvider: createProvider(),
18 | })
19 | ```
20 |
--------------------------------------------------------------------------------
/docs/guide/mocks.md:
--------------------------------------------------------------------------------
1 | # Mocks
2 |
3 | You can enable automatic mocking on the GraphQL API Server. It can be [customized](https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks) in the `./apollo-server/mocks.js` file generated in your project.
4 |
5 | Enable it in `vue.config.js`:
6 |
7 | ``` js
8 | module.exports = {
9 | // Other options...
10 | pluginOptions: {
11 | // Apollo-related options
12 | apollo: {
13 | // Enable automatic mocking
14 | enableMocks: true,
15 | },
16 | },
17 | }
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/guide/server-prod.md:
--------------------------------------------------------------------------------
1 | # Running the GraphQL server in production
2 |
3 | ## Production app
4 |
5 | ```
6 | cross-env NODE_ENV=production yarn run apollo:run --mode production
7 | ```
8 |
9 | If you deploy on now.sh, add the following script to your `package.json`:
10 |
11 | ```json
12 | {
13 | "scripts": {
14 | "start": "cross-env NODE_ENV=production yarn run apollo:run --mode production"
15 | }
16 | }
17 | ```
18 |
19 | ## Library published on npm
20 |
21 | If your project is meant to be used as a package installed from npm, you will need to move `vue-cli-plugin-apollo` from the `devDependencies` field to `dependencies` in your `package.json` file. Then you can run the server:
22 |
23 | ```js
24 | const server = require('vue-cli-plugin-apollo/graphql-server')
25 |
26 | const opts = {
27 | host: 'localhost',
28 | port: 4000,
29 | graphqlPath: '/graphql',
30 | subscriptionsPath: '/graphql',
31 | enableMocks: false,
32 | enableEngine: false,
33 | cors: '*',
34 | timeout: 1000000,
35 | quiet: true,
36 | paths: {
37 | typeDefs: require.resolve('some-folder/apollo-server/type-defs.js'),
38 | resolvers: require.resolve('some-folder/apollo-server/resolvers.js'),
39 | context: require.resolve('some-folder/apollo-server/context.js'),
40 | pubsub: require.resolve('some-folder/apollo-server/pubsub.js'),
41 | server: require.resolve('some-folder/apollo-server/server.js'),
42 | directives: require.resolve('some-folder/apollo-server/directives.js')
43 | dataSources: require.resolve('some-folder/apollo-server/data-sources.js')
44 | }
45 | }
46 |
47 | server(opts, () => {
48 | console.log('Server is running!')
49 | })
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/guide/server.md:
--------------------------------------------------------------------------------
1 | # Server Usage
2 |
3 | If you enabled the GraphQL API Server, start it alongside the client:
4 |
5 | ```
6 | npm run apollo
7 | ```
8 |
9 | You can edit the files generated in the `./apollo-server` folder:
10 |
11 | - `schema.graphql` contains the Schema written with the [schema definition language](https://github.com/facebook/graphql/blob/master/spec/Section%203%20--%20Type%20System.md).
12 | - `resolvers.js` declares the [Apollo resolvers](https://www.apollographql.com/docs/graphql-tools/resolvers.html).
13 | - `context.js` allows injecting a context object into all the resolvers (third argument).
14 | - `mocks.js` defines the custom resolvers used for mocking ([more info](https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks)).
15 | - `directives.js` defines the custom schema directives ([more info](https://www.apollographql.com/docs/graphql-tools/schema-directives.html))).
16 |
17 | The server will be automatically restarted when a change is detected.
18 |
19 | To run the server only once, use this command:
20 |
21 | ```
22 | npm run run-graphql-api
23 | ```
24 |
25 | **Updating `vue-cli-plugin-apollo` will also update the GraphQL Server service :+1:**
26 |
--------------------------------------------------------------------------------
/docs/guide/webpack.md:
--------------------------------------------------------------------------------
1 | # Injected webpack-chain Rules
2 |
3 | - `config.rule('gql')`: Loader for GraphQL files.
4 |
--------------------------------------------------------------------------------
/generator/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const {
3 | hasYarn,
4 | } = require('@vue/cli-shared-utils')
5 | const chalk = require('chalk')
6 |
7 | module.exports = (api, options, rootOptions) => {
8 | api.extendPackage({
9 | dependencies: {
10 | 'vue-apollo': '^3.0.0-beta.11',
11 | },
12 | devDependencies: {
13 | 'graphql-tag': '^2.9.0',
14 | },
15 | })
16 |
17 | // Vue config
18 | if (options.addServer) {
19 | // Modify vue config
20 | api.extendPackage({
21 | dependencies: {
22 | 'graphql-type-json': '^0.2.1',
23 | },
24 | scripts: {
25 | apollo: 'vue-cli-service apollo:dev --generate-schema',
26 | 'apollo:start': 'vue-cli-service apollo:start',
27 | 'apollo:schema:generate': 'vue-cli-service apollo:schema:generate',
28 | // 'apollo:client:check': 'vue-cli-service apollo:client:check',
29 | 'apollo:schema:publish': 'vue-cli-service apollo:schema:publish',
30 | },
31 | vue: {
32 | pluginOptions: {
33 | apollo: {
34 | enableMocks: options.addMocking,
35 | enableEngine: options.addApolloEngine,
36 | },
37 | },
38 | },
39 | })
40 | }
41 |
42 | api.render('./templates/vue-apollo/default', {
43 | ...options,
44 | })
45 |
46 | if (options.addExamples) {
47 | api.render('./templates/vue-apollo/examples', {
48 | ...options,
49 | })
50 | }
51 |
52 | if (options.addServer) {
53 | api.render('./templates/api-server/default', {
54 | ...options,
55 | })
56 |
57 | if (options.addExamples) {
58 | api.extendPackage({
59 | dependencies: {
60 | lowdb: '^1.0.0',
61 | mkdirp: '^0.5.1',
62 | shortid: '^2.2.8',
63 | },
64 | })
65 |
66 | api.render('./templates/api-server/examples', {
67 | ...options,
68 | })
69 | }
70 | }
71 |
72 | if (options.addServer && api.hasPlugin('eslint')) {
73 | api.extendPackage({
74 | devDependencies: {
75 | 'eslint-plugin-graphql': '^2.1.1',
76 | },
77 | eslintConfig: {
78 | plugins: [
79 | 'graphql',
80 | ],
81 | rules: {
82 | 'graphql/template-strings': ['error', {
83 | env: 'literal',
84 | projectName: 'app',
85 | schemaJsonFilepath: 'node_modules/.temp/graphql/schema.json',
86 | }],
87 | },
88 | },
89 | })
90 | }
91 |
92 | // Modify main.js
93 | try {
94 | const tsPath = api.resolve('src/main.ts')
95 | const jsPath = api.resolve('src/main.js')
96 |
97 | const tsExists = fs.existsSync(tsPath)
98 | const jsExists = fs.existsSync(jsPath)
99 |
100 | if (!tsExists && !jsExists) {
101 | throw new Error('No entry found')
102 | }
103 |
104 | const file = tsExists ? 'src/main.ts' : 'src/main.js'
105 | api.injectImports(file, 'import { createProvider } from \'./vue-apollo\'')
106 | api.injectRootOptions(file, 'apolloProvider: createProvider(),')
107 | } catch (e) {
108 | api.exitLog('Your main file couldn\'t be modified. You will have to edit the code yourself: https://github.com/Akryum/vue-cli-plugin-apollo#manual-code-changes', 'warn')
109 | }
110 |
111 | api.onCreateComplete(async () => {
112 | const execa = require('execa')
113 |
114 | function run (program, args) {
115 | return execa(program, args, {
116 | preferLocal: true,
117 | })
118 | }
119 |
120 | if (options.addExamples) {
121 | const appPath = api.resolve('src/App.vue')
122 | if (fs.existsSync(appPath)) {
123 | let content = fs.readFileSync(appPath, { encoding: 'utf8' })
124 | content = content.replace(/HelloWorld/gi, 'ApolloExample')
125 | fs.writeFileSync(appPath, content, { encoding: 'utf8' })
126 | }
127 | }
128 |
129 | if (options.addServer) {
130 | // Git ignore
131 | {
132 | const gitignorePath = api.resolve('.gitignore')
133 | let content
134 |
135 | if (fs.existsSync(gitignorePath)) {
136 | content = fs.readFileSync(gitignorePath, { encoding: 'utf8' })
137 | } else {
138 | content = ''
139 | }
140 |
141 | if (content.indexOf('/live/') === -1) {
142 | content += '\n/live/\n'
143 |
144 | fs.writeFileSync(gitignorePath, content, { encoding: 'utf8' })
145 | }
146 | }
147 |
148 | await run('vue-cli-service', [
149 | 'apollo:schema:generate',
150 | ])
151 | }
152 |
153 | if (options.addApolloEngine) {
154 | const updateVariable = (content, key, value) => {
155 | if (content.indexOf(`${key}=`) === -1) {
156 | content += `${key}=${value}\n`
157 | } else {
158 | content = content.replace(new RegExp(`${key}=(.*)\\n`), `${key}=${value}\n`)
159 | }
160 | return content
161 | }
162 |
163 | {
164 | // Modify .env.local file
165 | const envPath = api.resolve('.env.local')
166 | let content = ''
167 |
168 | if (fs.existsSync(envPath)) {
169 | content = fs.readFileSync(envPath, { encoding: 'utf8' })
170 | }
171 |
172 | content = updateVariable(content, 'VUE_APP_APOLLO_ENGINE_KEY', options.apolloEngineKey)
173 |
174 | fs.writeFileSync(envPath, content, { encoding: 'utf8' })
175 | }
176 |
177 | {
178 | // Modify .env file
179 | const envPath = api.resolve('.env')
180 | let content = ''
181 |
182 | if (fs.existsSync(envPath)) {
183 | content = fs.readFileSync(envPath, { encoding: 'utf8' })
184 | }
185 |
186 | content = updateVariable(content, 'VUE_APP_APOLLO_ENGINE_SERVICE', options.apolloEngineService)
187 | content = updateVariable(content, 'VUE_APP_APOLLO_ENGINE_TAG', options.apolloEngineTag)
188 |
189 | fs.writeFileSync(envPath, content, { encoding: 'utf8' })
190 | }
191 | }
192 |
193 | // Schema publish
194 | if (options.publishSchema) {
195 | await run('vue-cli-service', [
196 | 'apollo:schema:publish',
197 | ])
198 | }
199 |
200 | if (options.addServer) {
201 | api.exitLog(`Start the GraphQL API Server with ${chalk.cyan(`${hasYarn() ? 'yarn' : 'npm'} run apollo`)}`, 'info')
202 | if (options.addMocking) {
203 | api.exitLog(`Customize the mocks in ${chalk.cyan('apollo-server/mocks.js')}`, 'info')
204 | }
205 | if (options.addApolloEngine) {
206 | api.exitLog(`The Apollo Engine API key has been added to ${chalk.cyan('.env.local')}`, 'info')
207 | }
208 | }
209 | })
210 | }
211 |
--------------------------------------------------------------------------------
/generator/templates/api-server/default/apollo-server/context.js:
--------------------------------------------------------------------------------
1 | <% if (addExamples) { _%>
2 | import { db } from './utils/db'
3 | import { processUpload } from './utils/upload'
4 | <%_ } %>
5 |
6 | // Context passed to all resolvers (third argument)
7 | // req => Query
8 | // connection => Subscription
9 | // eslint-disable-next-line no-unused-vars
10 | export default ({ req, connection }) => {
11 | return {
12 | <% if (addExamples) { _%>
13 | db,
14 | processUpload,
15 | <%_ } else { %>
16 | // Put objects here
17 | <%_ } %>
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/generator/templates/api-server/default/apollo-server/data-sources.js:
--------------------------------------------------------------------------------
1 | export default function() {
2 | return {};
3 | }
--------------------------------------------------------------------------------
/generator/templates/api-server/default/apollo-server/directives.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // Schema directives
3 | // https://www.apollographql.com/docs/graphql-tools/schema-directives.html
4 | }
5 |
--------------------------------------------------------------------------------
/generator/templates/api-server/default/apollo-server/mocks.js:
--------------------------------------------------------------------------------
1 | // Enable mocking in vue.config.js with `"pluginOptions": { "enableMocks": true }`
2 | // Customize mocking: https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks
3 | export default {
4 | // Mock resolvers here
5 | }
6 |
--------------------------------------------------------------------------------
/generator/templates/api-server/default/apollo-server/resolvers.js:
--------------------------------------------------------------------------------
1 | import GraphQLJSON from 'graphql-type-json'
2 | <% if (addExamples) { _%>
3 | import shortid from 'shortid'
4 | <%_ } %>
5 |
6 | export default {
7 | JSON: GraphQLJSON,
8 |
9 | <% if (addExamples) { _%>
10 | Counter: {
11 | countStr: counter => `Current count: ${counter.count}`,
12 | },
13 | <%_ } %>
14 |
15 | Query: {
16 | hello: (root, { name }) => `Hello ${name || 'World'}!`,
17 | <% if (addExamples) { _%>
18 | messages: (root, args, { db }) => db.get('messages').value(),
19 | uploads: (root, args, { db }) => db.get('uploads').value(),
20 | <%_ } %>
21 | },
22 |
23 | Mutation: {
24 | myMutation: (root, args, context) => {
25 | const message = 'My mutation completed!'
26 | context.pubsub.publish('hey', { mySub: message })
27 | return message
28 | },
29 | <% if (addExamples) { _%>
30 | addMessage: (root, { input }, { pubsub, db }) => {
31 | const message = {
32 | id: shortid.generate(),
33 | text: input.text,
34 | }
35 |
36 | db
37 | .get('messages')
38 | .push(message)
39 | .last()
40 | .write()
41 |
42 | pubsub.publish('messages', { messageAdded: message })
43 |
44 | return message
45 | },
46 |
47 | singleUpload: (root, { file }, { processUpload }) => processUpload(file),
48 | multipleUpload: (root, { files }, { processUpload }) => Promise.all(files.map(processUpload)),
49 | <%_ } %>
50 | },
51 |
52 | Subscription: {
53 | mySub: {
54 | subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator('hey'),
55 | },
56 | <% if (addExamples) { _%>
57 | counter: {
58 | subscribe: (parent, args, { pubsub }) => {
59 | const channel = Math.random().toString(36).substring(2, 15) // random channel name
60 | let count = 0
61 | setInterval(() => pubsub.publish(
62 | channel,
63 | {
64 | // eslint-disable-next-line no-plusplus
65 | counter: { count: count++ },
66 | }
67 | ), 2000)
68 | return pubsub.asyncIterator(channel)
69 | },
70 | },
71 |
72 | messageAdded: {
73 | subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator('messages'),
74 | },
75 | <%_ } %>
76 | },
77 | }
78 |
--------------------------------------------------------------------------------
/generator/templates/api-server/default/apollo-server/schema.graphql:
--------------------------------------------------------------------------------
1 | "Included scalars"
2 | scalar JSON
3 | scalar Upload
4 |
5 | <% if (addExamples) { _%>
6 | "It will increment!"
7 | type Counter {
8 | "Number of increments"
9 | count: Int!
10 | "Full message for testing"
11 | countStr: String
12 | }
13 |
14 | "A text message send by users"
15 | type Message {
16 | id: ID!
17 | "Message content"
18 | text: String!
19 | }
20 |
21 | "Input from user to create a message"
22 | input MessageInput {
23 | "Message content"
24 | text: String!
25 | }
26 |
27 | type File {
28 | id: ID!
29 | path: String!
30 | filename: String!
31 | mimetype: String!
32 | encoding: String!
33 | }
34 | <%_ } %>
35 |
36 | type Query {
37 | "Test query with a parameter"
38 | hello(name: String): String!
39 | <% if (addExamples) { _%>
40 | "List of messages sent by users"
41 | messages: [Message]
42 | uploads: [File]
43 | <%_ } %>
44 | }
45 |
46 | type Mutation {
47 | myMutation: String!
48 | <% if (addExamples) { _%>
49 | "Add a message and publish it on 'messages' subscription channel"
50 | addMessage (input: MessageInput!): Message!
51 | singleUpload (file: Upload!): File!
52 | multipleUpload (files: [Upload!]!): [File!]!
53 | <%_ } %>
54 | }
55 |
56 | type Subscription {
57 | mySub: String!
58 | <% if (addExamples) { _%>
59 | "This will update every 2 seconds"
60 | counter: Counter!
61 | "When a new message is added"
62 | messageAdded: Message!
63 | <%_ } %>
64 | }
65 |
--------------------------------------------------------------------------------
/generator/templates/api-server/default/apollo-server/type-defs.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | export default fs.readFileSync(path.resolve(__dirname, './schema.graphql'), { encoding: 'utf8' })
5 |
--------------------------------------------------------------------------------
/generator/templates/api-server/examples/apollo-server/server.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import express from 'express'
3 |
4 | export default app => {
5 | app.use('/files', express.static(path.resolve(__dirname, '../live/uploads')))
6 | }
7 |
--------------------------------------------------------------------------------
/generator/templates/api-server/examples/apollo-server/utils/db.js:
--------------------------------------------------------------------------------
1 | import Lowdb from 'lowdb'
2 | import FileSync from 'lowdb/adapters/FileSync'
3 | import mkdirp from 'mkdirp'
4 | import { resolve } from 'path'
5 |
6 | mkdirp(resolve(__dirname, '../../live'))
7 |
8 | export const db = new Lowdb(new FileSync(resolve(__dirname, '../../live/db.json')))
9 |
10 | // Seed an empty DB
11 | db.defaults({
12 | messages: [],
13 | uploads: [],
14 | }).write()
15 |
--------------------------------------------------------------------------------
/generator/templates/api-server/examples/apollo-server/utils/upload.js:
--------------------------------------------------------------------------------
1 | import { createWriteStream } from 'fs'
2 | import { resolve } from 'path'
3 | import { sync } from 'mkdirp'
4 | import { generate } from 'shortid'
5 | import { db } from './db'
6 |
7 | const uploadDir = resolve(__dirname, '../../live/uploads')
8 |
9 | // Ensure upload directory exists
10 | sync(uploadDir)
11 |
12 | const storeUpload = async ({ stream, filename }) => {
13 | const id = generate()
14 | const file = `${id}-${filename}`
15 | const path = `${uploadDir}/${file}`
16 | const urlPath = `files/${file}`
17 |
18 | return new Promise((resolve, reject) =>
19 | stream
20 | .pipe(createWriteStream(path))
21 | .on('finish', () => resolve({ id, path: urlPath }))
22 | .on('error', reject),
23 | )
24 | }
25 |
26 | const recordFile = file =>
27 | db
28 | .get('uploads')
29 | .push(file)
30 | .last()
31 | .write()
32 |
33 | export async function processUpload (file) {
34 | const { stream, filename, mimetype, encoding } = await file
35 | const { id, path } = await storeUpload({ stream, filename })
36 | return recordFile({ id, filename, mimetype, encoding, path })
37 | }
38 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/default/apollo.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | // Load .env files
4 | const { loadEnv } = require('vue-cli-plugin-apollo/utils/load-env')
5 | const env = loadEnv([
6 | path.resolve(__dirname, '.env'),
7 | path.resolve(__dirname, '.env.local')
8 | ])
9 |
10 | module.exports = {
11 | client: {
12 | service: env.VUE_APP_APOLLO_ENGINE_SERVICE,
13 | includes: ['src/**/*.{js,jsx,ts,tsx,vue,gql}']
14 | },
15 | service: {
16 | name: env.VUE_APP_APOLLO_ENGINE_SERVICE,
17 | localSchemaFile: path.resolve(__dirname, './node_modules/.temp/graphql/schema.json')
18 | },
19 | engine: {
20 | endpoint: process.env.APOLLO_ENGINE_API_ENDPOINT,
21 | apiKey: env.VUE_APP_APOLLO_ENGINE_KEY
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/default/src/vue-apollo.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueApollo from 'vue-apollo'
3 | import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'
4 |
5 | // Install the vue plugin
6 | Vue.use(VueApollo)
7 |
8 | // Name of the localStorage item
9 | const AUTH_TOKEN = 'apollo-token'
10 |
11 | // Http endpoint
12 | const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000/graphql'
13 | <% if (addExamples) { _%>
14 | // Files URL root
15 | export const filesRoot = process.env.VUE_APP_FILES_ROOT || httpEndpoint.substr(0, httpEndpoint.indexOf('/graphql'))
16 |
17 | Vue.prototype.$filesRoot = filesRoot
18 | <%_ } %>
19 | // Config
20 | const defaultOptions = {
21 | // You can use `https` for secure connection (recommended in production)
22 | httpEndpoint,
23 | // You can use `wss` for secure connection (recommended in production)
24 | // Use `null` to disable subscriptions
25 | wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql',
26 | // LocalStorage token
27 | tokenName: AUTH_TOKEN,
28 | // Enable Automatic Query persisting with Apollo Engine
29 | persisting: false,
30 | // Use websockets for everything (no HTTP)
31 | // You need to pass a `wsEndpoint` for this to work
32 | websocketsOnly: false,
33 | // Is being rendered on the server?
34 | ssr: false,
35 |
36 | // Override default apollo link
37 | // note: don't override httpLink here, specify httpLink options in the
38 | // httpLinkOptions property of defaultOptions.
39 | // link: myLink
40 |
41 | // Override default cache
42 | // cache: myCache
43 |
44 | // Override the way the Authorization header is set
45 | // getAuth: (tokenName) => ...
46 |
47 | // Additional ApolloClient options
48 | // apollo: { ... }
49 |
50 | // Client local data (see apollo-link-state)
51 | // clientState: { resolvers: { ... }, defaults: { ... } }
52 | }
53 |
54 | // Call this in the Vue app file
55 | export function createProvider (options = {}) {
56 | // Create apollo client
57 | const { apolloClient, wsClient } = createApolloClient({
58 | ...defaultOptions,
59 | ...options,
60 | })
61 | apolloClient.wsClient = wsClient
62 |
63 | // Create vue apollo provider
64 | const apolloProvider = new VueApollo({
65 | defaultClient: apolloClient,
66 | defaultOptions: {
67 | $query: {
68 | // fetchPolicy: 'cache-and-network',
69 | },
70 | },
71 | errorHandler (error) {
72 | // eslint-disable-next-line no-console
73 | console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
74 | },
75 | })
76 |
77 | return apolloProvider
78 | }
79 |
80 | // Manually call this when user log in
81 | export async function onLogin (apolloClient, token) {
82 | if (typeof localStorage !== 'undefined' && token) {
83 | localStorage.setItem(AUTH_TOKEN, token)
84 | }
85 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
86 | try {
87 | await apolloClient.resetStore()
88 | } catch (e) {
89 | // eslint-disable-next-line no-console
90 | console.log('%cError on cache reset (login)', 'color: orange;', e.message)
91 | }
92 | }
93 |
94 | // Manually call this when user log out
95 | export async function onLogout (apolloClient) {
96 | if (typeof localStorage !== 'undefined') {
97 | localStorage.removeItem(AUTH_TOKEN)
98 | }
99 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
100 | try {
101 | await apolloClient.resetStore()
102 | } catch (e) {
103 | // eslint-disable-next-line no-console
104 | console.log('%cError on cache reset (logout)', 'color: orange;', e.message)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/components/ApolloExample.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
19 |
20 |
21 | Loading...
22 |
23 |
24 | An error occured
25 |
26 |
27 | {{ data.hello }}
28 |
29 |
30 | No result :(
31 |
32 |
33 |
34 |
35 |
38 |
42 |
43 |
44 |
45 |
50 | {{ message.text }}
51 |
52 |
53 |
54 |
55 |
56 |
66 |
67 |
76 |
77 |
78 |
79 |
80 |
85 |
![]()
86 |
87 |
88 |
89 |
90 |
91 |
98 |
99 |
100 |
101 |
102 |
151 |
152 |
200 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/AddMessage.gql:
--------------------------------------------------------------------------------
1 | #import "./MessageFragment.gql"
2 |
3 | mutation addMessage ($input: MessageInput!) {
4 | addMessage (input: $input) {
5 | ...Message
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/FileFragment.gql:
--------------------------------------------------------------------------------
1 | fragment file on File {
2 | id
3 | path
4 | filename
5 | mimetype
6 | encoding
7 | }
8 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/Files.gql:
--------------------------------------------------------------------------------
1 | #import "./FileFragment.gql"
2 |
3 | query files {
4 | files: uploads {
5 | ...file
6 | }
7 | }
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/HelloWorld.gql:
--------------------------------------------------------------------------------
1 | query HelloWorld ($name: String) {
2 | hello (name: $name)
3 | }
4 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/MessageAdded.gql:
--------------------------------------------------------------------------------
1 | #import "./MessageFragment.gql"
2 |
3 | subscription messageAdded {
4 | messageAdded {
5 | ...Message
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/MessageFragment.gql:
--------------------------------------------------------------------------------
1 | fragment Message on Message {
2 | id
3 | text
4 | }
5 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/Messages.gql:
--------------------------------------------------------------------------------
1 | #import "./MessageFragment.gql"
2 |
3 | query messages {
4 | messages {
5 | ...Message
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/generator/templates/vue-apollo/examples/src/graphql/UploadFile.gql:
--------------------------------------------------------------------------------
1 | #import "./FileFragment.gql"
2 |
3 | mutation uploadFile ($file: Upload!) {
4 | singleUpload (file: $file) {
5 | ...file
6 | }
7 | }
--------------------------------------------------------------------------------
/graphql-client/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./dist/index')
2 |
--------------------------------------------------------------------------------
/graphql-client/src/index.js:
--------------------------------------------------------------------------------
1 | import { ApolloClient } from 'apollo-client'
2 | import { split, from } from 'apollo-link'
3 | import { createUploadLink } from 'apollo-upload-client'
4 | import { InMemoryCache } from 'apollo-cache-inmemory'
5 | import { SubscriptionClient } from 'subscriptions-transport-ws'
6 | import MessageTypes from 'subscriptions-transport-ws/dist/message-types'
7 | import { WebSocketLink } from 'apollo-link-ws'
8 | import { getMainDefinition } from 'apollo-utilities'
9 | import { createPersistedQueryLink } from 'apollo-link-persisted-queries'
10 | import { setContext } from 'apollo-link-context'
11 | import { withClientState } from 'apollo-link-state'
12 |
13 | // Create the apollo client
14 | export function createApolloClient ({
15 | // Client ID if using multiple Clients
16 | clientId = 'defaultClient',
17 | // URL to the HTTP API
18 | httpEndpoint,
19 | // Url to the Websocket API
20 | wsEndpoint = null,
21 | // Token used in localstorage
22 | tokenName = 'apollo-token',
23 | // Enable this if you use Query persisting with Apollo Engine
24 | persisting = false,
25 | // Is currently Server-Side Rendering or not
26 | ssr = false,
27 | // Only use Websocket for all requests (including queries and mutations)
28 | websocketsOnly = false,
29 | // Custom starting link.
30 | // If you want to replace the default HttpLink, set `defaultHttpLink` to false
31 | link = null,
32 | // Custom pre-auth links
33 | // Useful if you want, for example, to set a custom middleware for refreshing an access token.
34 | preAuthLinks = [],
35 | // If true, add the default HttpLink.
36 | // Disable it if you want to replace it with a terminating link using `link` option.
37 | defaultHttpLink = true,
38 | // Options for the default HttpLink
39 | httpLinkOptions = {},
40 | // Custom Apollo cache implementation (default is apollo-cache-inmemory)
41 | cache = null,
42 | // Options for the default cache
43 | inMemoryCacheOptions = {},
44 | // Additional Apollo client options
45 | apollo = {},
46 | // apollo-link-state options
47 | clientState = null,
48 | // Function returning Authorization header token
49 | getAuth = defaultGetAuth,
50 | // Local Schema
51 | typeDefs = undefined,
52 | // Local Resolvers
53 | resolvers = undefined,
54 | // Hook called when you should write local state in the cache
55 | onCacheInit = undefined,
56 | }) {
57 | let wsClient, authLink, stateLink
58 | const disableHttp = websocketsOnly && !ssr && wsEndpoint
59 |
60 | // Apollo cache
61 | if (!cache) {
62 | cache = new InMemoryCache(inMemoryCacheOptions)
63 | }
64 |
65 | if (!disableHttp) {
66 | const httpLink = createUploadLink({
67 | uri: httpEndpoint,
68 | ...httpLinkOptions,
69 | })
70 |
71 | if (!link) {
72 | link = httpLink
73 | } else if (defaultHttpLink) {
74 | link = from([link, httpLink])
75 | }
76 |
77 | // HTTP Auth header injection
78 | authLink = setContext(async (_, { headers }) => {
79 | const Authorization = await getAuth(tokenName)
80 | const authorizationHeader = Authorization ? { Authorization } : {}
81 | return {
82 | headers: {
83 | ...headers,
84 | ...authorizationHeader,
85 | },
86 | }
87 | })
88 |
89 | // Concat all the http link parts
90 | link = authLink.concat(link)
91 |
92 | if (preAuthLinks.length) {
93 | link = from(preAuthLinks).concat(authLink)
94 | }
95 | }
96 |
97 | // On the server, we don't want WebSockets and Upload links
98 | if (!ssr) {
99 | // If on the client, recover the injected state
100 | if (typeof window !== 'undefined') {
101 | // eslint-disable-next-line no-underscore-dangle
102 | const state = window.__APOLLO_STATE__
103 | if (state && state[clientId]) {
104 | // Restore state
105 | cache.restore(state[clientId])
106 | }
107 | }
108 |
109 | if (!disableHttp) {
110 | let persistingOpts = {}
111 | if (typeof persisting === 'object' && persisting != null) {
112 | persistingOpts = persisting
113 | persisting = true
114 | }
115 | if (persisting === true) {
116 | link = createPersistedQueryLink(persistingOpts).concat(link)
117 | }
118 | }
119 |
120 | // Web socket
121 | if (wsEndpoint) {
122 | wsClient = new SubscriptionClient(wsEndpoint, {
123 | reconnect: true,
124 | connectionParams: () => {
125 | const Authorization = getAuth(tokenName)
126 | return Authorization ? { Authorization, headers: { Authorization } } : {}
127 | },
128 | })
129 |
130 | // Create the subscription websocket link
131 | const wsLink = new WebSocketLink(wsClient)
132 |
133 | if (disableHttp) {
134 | link = link ? link.concat(wsLink) : wsLink
135 | } else {
136 | link = split(
137 | // split based on operation type
138 | ({ query }) => {
139 | const { kind, operation } = getMainDefinition(query)
140 | return kind === 'OperationDefinition' &&
141 | operation === 'subscription'
142 | },
143 | wsLink,
144 | link,
145 | )
146 | }
147 | }
148 | }
149 |
150 | if (clientState) {
151 | console.warn('clientState is deprecated, see https://vue-cli-plugin-apollo.netlify.com/guide/client-state.html')
152 | stateLink = withClientState({
153 | cache,
154 | ...clientState,
155 | })
156 | link = from([stateLink, link])
157 | }
158 |
159 | const apolloClient = new ApolloClient({
160 | link,
161 | cache,
162 | // Additional options
163 | ...(ssr ? {
164 | // Set this on the server to optimize queries when SSR
165 | ssrMode: true,
166 | } : {
167 | // This will temporary disable query force-fetching
168 | ssrForceFetchDelay: 100,
169 | // Apollo devtools
170 | connectToDevTools: process.env.NODE_ENV !== 'production',
171 | }),
172 | typeDefs,
173 | resolvers,
174 | ...apollo,
175 | })
176 |
177 | // Re-write the client state defaults on cache reset
178 | if (stateLink) {
179 | apolloClient.onResetStore(stateLink.writeDefaults)
180 | }
181 |
182 | if (onCacheInit) {
183 | onCacheInit(cache)
184 | apolloClient.onResetStore(() => onCacheInit(cache))
185 | }
186 |
187 | return {
188 | apolloClient,
189 | wsClient,
190 | stateLink,
191 | }
192 | }
193 |
194 | export function restartWebsockets (wsClient) {
195 | // Copy current operations
196 | const operations = Object.assign({}, wsClient.operations)
197 |
198 | // Close connection
199 | wsClient.close(true)
200 |
201 | // Open a new one
202 | wsClient.connect()
203 |
204 | // Push all current operations to the new connection
205 | Object.keys(operations).forEach(id => {
206 | wsClient.sendMessage(
207 | id,
208 | MessageTypes.GQL_START,
209 | operations[id].options,
210 | )
211 | })
212 | }
213 |
214 | function defaultGetAuth (tokenName) {
215 | if (typeof window !== 'undefined') {
216 | // get the authentication token from local storage if it exists
217 | const token = window.localStorage.getItem(tokenName)
218 | // return the headers to the context so httpLink can read them
219 | return token ? `Bearer ${token}` : ''
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/graphql-server/index.js:
--------------------------------------------------------------------------------
1 | const http = require('http')
2 | const chalk = require('chalk')
3 | const express = require('express')
4 | const { ApolloServer, gql } = require('apollo-server-express')
5 | const { PubSub } = require('graphql-subscriptions')
6 | const merge = require('deepmerge')
7 |
8 | const { defaultValue, autoCall } = require('../utils')
9 |
10 | // eslint-disable-next-line no-global-assign
11 | require = require('esm')(module)
12 |
13 | module.exports = (options, cb = null) => {
14 | const { load } = require('../utils/load')(options)
15 |
16 | // Default options
17 | options = merge({
18 | integratedEngine: false,
19 | }, options)
20 |
21 | // Express app
22 | const app = express()
23 |
24 | // Customize those files
25 | let typeDefs = load(options.paths.typeDefs)
26 | const resolvers = load(options.paths.resolvers)
27 | const context = load(options.paths.context)
28 | const schemaDirectives = load(options.paths.directives)
29 | let pubsub
30 | try {
31 | pubsub = load(options.paths.pubsub)
32 | } catch (e) {
33 | if (process.env.NODE_ENV !== 'production' && !options.quiet) {
34 | console.log(chalk.yellow('Using default PubSub implementation for subscriptions.'))
35 | console.log(chalk.grey('You should provide a different implementation in production (for example with Redis) by exporting it in \'apollo-server/pubsub.js\'.'))
36 | }
37 | }
38 | let dataSources
39 | try {
40 | dataSources = load(options.paths.dataSources)
41 | } catch (e) {
42 | console.error(e)
43 | }
44 |
45 | // GraphQL API Server
46 |
47 | // Realtime subscriptions
48 | if (!pubsub) pubsub = new PubSub()
49 |
50 | // Customize server
51 | try {
52 | const serverModule = load(options.paths.server)
53 | serverModule(app)
54 | } catch (e) {
55 | console.error(e)
56 | // No file found
57 | }
58 |
59 | // Apollo server options
60 |
61 | typeDefs = processSchema(typeDefs)
62 |
63 | let apolloServerOptions = {
64 | typeDefs,
65 | resolvers,
66 | schemaDirectives,
67 | dataSources,
68 | tracing: true,
69 | cacheControl: true,
70 | engine: !options.integratedEngine,
71 | // Resolvers context from POST
72 | context: async ({ req, connection }) => {
73 | let contextData
74 | try {
75 | if (connection) {
76 | contextData = await autoCall(context, { connection })
77 | } else {
78 | contextData = await autoCall(context, { req })
79 | }
80 | } catch (e) {
81 | console.error(e)
82 | throw e
83 | }
84 | contextData = Object.assign({}, contextData, { pubsub })
85 | return contextData
86 | },
87 | // Resolvers context from WebSocket
88 | subscriptions: {
89 | path: options.subscriptionsPath,
90 | onConnect: async (connection, websocket) => {
91 | let contextData = {}
92 | try {
93 | contextData = await autoCall(context, {
94 | connection,
95 | websocket,
96 | })
97 | contextData = Object.assign({}, contextData, { pubsub })
98 | } catch (e) {
99 | console.error(e)
100 | throw e
101 | }
102 | return contextData
103 | },
104 | },
105 | }
106 |
107 | // Automatic mocking
108 | if (options.enableMocks) {
109 | // Customize this file
110 | apolloServerOptions.mocks = load(options.paths.mocks)
111 | apolloServerOptions.mockEntireSchema = false
112 |
113 | if (!options.quiet) {
114 | if (process.env.NODE_ENV === 'production') {
115 | console.warn('Automatic mocking is enabled, consider disabling it with the \'enableMocks\' option.')
116 | } else {
117 | console.log('✔️ Automatic mocking is enabled')
118 | }
119 | }
120 | }
121 |
122 | // Apollo Engine
123 | if (options.enableEngine && options.integratedEngine) {
124 | if (options.engineKey) {
125 | apolloServerOptions.engine = {
126 | apiKey: options.engineKey,
127 | schemaTag: options.schemaTag,
128 | ...options.engineOptions || {},
129 | }
130 | console.log('✔️ Apollo Engine is enabled')
131 | } else if (!options.quiet) {
132 | console.log(chalk.yellow('Apollo Engine key not found.') + `To enable Engine, set the ${chalk.cyan('VUE_APP_APOLLO_ENGINE_KEY')} env variable.`)
133 | console.log('Create a key at https://engine.apollographql.com/')
134 | console.log('You may see `Error: Must provide document` errors (query persisting tries).')
135 | }
136 | } else {
137 | apolloServerOptions.engine = false
138 | }
139 |
140 | // Final options
141 | apolloServerOptions = merge(apolloServerOptions, defaultValue(options.serverOptions, {}))
142 |
143 | // Apollo Server
144 | const server = new ApolloServer(apolloServerOptions)
145 |
146 | // Express middleware
147 | server.applyMiddleware({
148 | app,
149 | path: options.graphqlPath,
150 | cors: options.cors,
151 | // gui: {
152 | // endpoint: graphqlPath,
153 | // subscriptionEndpoint: graphqlSubscriptionsPath,
154 | // },
155 | })
156 |
157 | // Start server
158 | const httpServer = http.createServer(app)
159 | httpServer.setTimeout(options.timeout)
160 | server.installSubscriptionHandlers(httpServer)
161 |
162 | httpServer.listen({
163 | host: options.host || 'localhost',
164 | port: options.port,
165 | }, () => {
166 | if (!options.quiet) {
167 | console.log(`✔️ GraphQL Server is running on ${chalk.cyan(`http://localhost:${options.port}${options.graphqlPath}`)}`)
168 | if (process.env.NODE_ENV !== 'production' && !process.env.VUE_CLI_API_MODE) {
169 | console.log(`✔️ Type ${chalk.cyan('rs')} to restart the server`)
170 | }
171 | }
172 |
173 | cb && cb()
174 | })
175 | }
176 |
177 | function processSchema (typeDefs) {
178 | if (Array.isArray(typeDefs)) {
179 | return typeDefs.map(processSchema)
180 | }
181 |
182 | if (typeof typeDefs === 'string') {
183 | // Convert schema to AST
184 | typeDefs = gql(typeDefs)
185 | }
186 |
187 | // Remove upload scalar (it's already included in Apollo Server)
188 | removeFromSchema(typeDefs, 'ScalarTypeDefinition', 'Upload')
189 |
190 | return typeDefs
191 | }
192 |
193 | function removeFromSchema (document, kind, name) {
194 | const definitions = document.definitions
195 | const index = definitions.findIndex(
196 | def => def.kind === kind && def.name.kind === 'Name' && def.name.value === name,
197 | )
198 | if (index !== -1) {
199 | definitions.splice(index, 1)
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk')
2 |
3 | const COMMAND_OPTIONS = {
4 | '-h, --host': 'specify server host',
5 | '-p, --port': 'specify server port',
6 | '--run [command]': 'run another command in parallel',
7 | '--mock': 'enables mocks',
8 | '--engine': 'enables Apollo Engine',
9 | '--delay': 'delays run by a small duration',
10 | '--generate-schema': 'auto-generate JSON and GraphQL schema files',
11 | }
12 |
13 | const SCHEMA_OPTIONS = {
14 | '--endpoint [endpoint]': 'URL of running server or path to JSON schema file',
15 | '--key [key]': 'Engine service key',
16 | '--tag [tag]': 'Schema Tag',
17 | }
18 |
19 | const DEFAULT_GENERATE_OUTPUT = './node_modules/.temp/graphql/schema'
20 |
21 | function nullable (value) {
22 | return value == null ? {} : value
23 | }
24 |
25 | module.exports = (api, options) => {
26 | const apolloOptions = nullable(nullable(options.pluginOptions).apollo)
27 | const useThreads = process.env.NODE_ENV === 'production' && options.parallel
28 | const cacheDirectory = api.resolve('node_modules/.cache/cache-loader')
29 | const { generateCacheIdentifier } = require('./utils')
30 |
31 | api.chainWebpack(config => {
32 | const rule = config.module
33 | .rule('gql')
34 | .test(/\.(gql|graphql)$/)
35 | .use('cache-loader')
36 | .loader('cache-loader')
37 | .options({ cacheDirectory })
38 | .end()
39 |
40 | if (useThreads) {
41 | rule
42 | .use('thread-loader')
43 | .loader('thread-loader')
44 | }
45 |
46 | rule
47 | .use('gql-loader')
48 | .loader('graphql-tag/loader')
49 | .end()
50 |
51 | if (api.hasPlugin('eslint') && config.module.rules.has('eslint')) {
52 | if (apolloOptions.lintGQL) {
53 | const id = generateCacheIdentifier(api.resolve('.'))
54 |
55 | config.module
56 | .rule('eslint')
57 | .test(/\.(vue|(j|t)sx?|gql|graphql)$/)
58 | .use('eslint-loader')
59 | .tap(options => {
60 | options.extensions.push('.gql', '.graphql')
61 | return {
62 | ...options,
63 | cacheIdentifier: options.cacheIdentifier + id,
64 | }
65 | })
66 | } else if (apolloOptions.lintGQL !== false) {
67 | console.log('To enable GQL files in ESLint, set the `pluginOptions.apollo.lintGQL` project option to `true` in `vue.config.js`. Put `false` to hide this message.')
68 | console.log('You also need to install `eslint-plugin-graphql` and enable it in your ESLint configuration.')
69 | }
70 | }
71 |
72 | config.resolve
73 | .extensions
74 | .prepend('.mjs')
75 |
76 | config.module
77 | .rule('mjs')
78 | .test(/\.mjs$/)
79 | .include
80 | .add(/node_modules/)
81 | .end()
82 | .type('javascript/auto')
83 |
84 | // Add string template tag transform to Bublé
85 | config.module
86 | .rule('vue')
87 | .use('vue-loader')
88 | .loader('vue-loader')
89 | .tap(options => {
90 | options.transpileOptions = options.transpileOptions || {}
91 | options.transpileOptions.transforms = options.transpileOptions.transforms || {}
92 | options.transpileOptions.transforms.dangerousTaggedTemplateString = true
93 | return options
94 | })
95 | })
96 |
97 | api.registerCommand('apollo:dev', {
98 | description: 'Run the Apollo server and watch the sources to restart automatically',
99 | usage: 'vue-cli-service apollo:dev [options]',
100 | options: COMMAND_OPTIONS,
101 | details: 'For more info, see https://github.com/Akryum/vue-cli-plugin-apollo',
102 | }, args => {
103 | const {
104 | runParallelCommand,
105 | getFlatArgs,
106 | runWatch,
107 | sendIpcMessage,
108 | } = require('./utils')
109 |
110 | runParallelCommand(args)
111 |
112 | if (args['generate-schema']) {
113 | const execa = require('execa')
114 | execa('vue-cli-service apollo:schema:generate', ['--watch'], {
115 | stdio: ['inherit', 'inherit', 'inherit'],
116 | cleanup: true,
117 | shell: true,
118 | })
119 | }
120 |
121 | // Pass the args along
122 | const flatArgs = getFlatArgs(args, ['_', 'run', 'delay', 'generate-schema'])
123 |
124 | return runWatch(api, options, {
125 | script: 'apollo:start',
126 | args: ['--delay', ...flatArgs],
127 | onStart: () => {
128 | sendIpcMessage({
129 | error: false,
130 | })
131 | },
132 | onCrash: () => {
133 | console.log(chalk.bold(chalk.red('💥 GraphQL API crashed!')))
134 | sendIpcMessage({
135 | urls: null,
136 | error: true,
137 | })
138 | },
139 | onRestart: () => {
140 | console.log(chalk.bold(chalk.green('⏳ GraphQL API is restarting...')))
141 | sendIpcMessage({
142 | error: false,
143 | })
144 | },
145 | })
146 | })
147 |
148 | api.registerCommand('apollo:start', {
149 | description: 'Run the Apollo server',
150 | usage: 'vue-cli-service apollo:start [options]',
151 | options: COMMAND_OPTIONS,
152 | details: 'For more info, see https://github.com/Akryum/vue-cli-plugin-apollo',
153 | }, args => {
154 | const {
155 | runParallelCommand,
156 | sendIpcMessage,
157 | getServerOptions,
158 | } = require('./utils')
159 |
160 | runParallelCommand(args)
161 |
162 | if (args['generate-schema']) {
163 | const execa = require('execa')
164 | execa('vue-cli-service apollo:schema:generate', {
165 | stdio: ['inherit', 'inherit', 'inherit'],
166 | cleanup: true,
167 | shell: true,
168 | })
169 | }
170 |
171 | const run = () => {
172 | let server = require('./graphql-server')
173 | server = server.default || server
174 |
175 | const opts = getServerOptions(api, options, args)
176 |
177 | server(opts, () => {
178 | sendIpcMessage({
179 | urls: {
180 | playground: `http://localhost:${opts.port}${opts.graphqlPath}`,
181 | },
182 | })
183 | })
184 | }
185 |
186 | if (args.delay) {
187 | setTimeout(run, 300)
188 | } else {
189 | run()
190 | }
191 | })
192 |
193 | api.registerCommand('apollo:schema:generate', {
194 | description: 'Generates full schema JSON and GraphQL files',
195 | usage: 'vue-cli-service apollo:schema:generate [options]',
196 | options: {
197 | '--watch': 'Watch server files and re-generate schema JSON',
198 | '--output [path]': 'Path to the output files',
199 | },
200 | details: 'For more info, see https://github.com/Akryum/vue-cli-plugin-apollo',
201 | }, async args => {
202 | if (args.watch) {
203 | const {
204 | getFlatArgs,
205 | runWatch,
206 | } = require('./utils')
207 |
208 | const flatArgs = getFlatArgs(args, ['watch'])
209 | return runWatch(api, options, {
210 | script: 'apollo:schema:generate',
211 | args: flatArgs,
212 | })
213 | } else {
214 | const {
215 | getServerOptions,
216 | } = require('./utils')
217 |
218 | const opts = getServerOptions(api, options, args)
219 |
220 | const output = args.output || DEFAULT_GENERATE_OUTPUT
221 | const jsonOutput = `${output}.json`
222 | const graphqlOutput = `${output}.graphql`
223 |
224 | const generateSchema = require('./utils/generate-schema')
225 | await generateSchema({
226 | paths: opts.paths,
227 | jsonOutput,
228 | graphqlOutput,
229 | typescript: opts.typescript,
230 | })
231 | }
232 | })
233 |
234 | api.registerCommand('apollo:client:check', {
235 | description: 'Compare schema from Apollo Engine',
236 | usage: 'vue-cli-service apollo:client:check [options]',
237 | options: SCHEMA_OPTIONS,
238 | details: 'For more info, see https://github.com/Akryum/vue-cli-plugin-apollo',
239 | }, async args => {
240 | throw new Error('Not implemented yet')
241 |
242 | /* eslint-disable no-unreachable */
243 |
244 | const endpoint = args.endpoint || `${DEFAULT_GENERATE_OUTPUT}.json`
245 | const key = args.key || process.env.VUE_APP_APOLLO_ENGINE_KEY
246 | const tag = args.tag || process.env.VUE_APP_APOLLO_ENGINE_TAG
247 | const engineEndpoint = process.env.APOLLO_ENGINE_API_ENDPOINT
248 |
249 | await autoGenerateSchema(endpoint)
250 |
251 | const checkSchema = require('./utils/check-schema')
252 | await checkSchema({
253 | endpoint,
254 | key,
255 | tag,
256 | engineEndpoint,
257 | })
258 | })
259 |
260 | api.registerCommand('apollo:schema:publish', {
261 | description: 'Publish schema to Apollo Engine',
262 | usage: 'vue-cli-service apollo:schema:publish [options]',
263 | options: SCHEMA_OPTIONS,
264 | details: 'For more info, see https://github.com/Akryum/vue-cli-plugin-apollo',
265 | }, async args => {
266 | const endpoint = args.endpoint || `${DEFAULT_GENERATE_OUTPUT}.json`
267 | const key = args.key || process.env.VUE_APP_APOLLO_ENGINE_KEY
268 | const tag = args.tag || process.env.VUE_APP_APOLLO_ENGINE_TAG
269 | const engineEndpoint = process.env.APOLLO_ENGINE_API_ENDPOINT
270 |
271 | await autoGenerateSchema(endpoint)
272 |
273 | const publishSchema = require('./utils/publish-schema')
274 | await publishSchema({
275 | endpoint,
276 | key,
277 | tag,
278 | engineEndpoint,
279 | })
280 | })
281 |
282 | async function autoGenerateSchema (endpoint) {
283 | // Auto-generate if json file doesn't exist
284 | if (endpoint.match(/\.json$/i)) {
285 | const fs = require('fs')
286 | const file = api.resolve(endpoint)
287 | if (!fs.existsSync(file)) {
288 | const path = require('path')
289 | const output = path.join(path.dirname(file), path.basename(file, path.extname(file)))
290 | const execa = require('execa')
291 | await execa('vue-cli-service apollo:schema:generate', [
292 | '--output',
293 | output,
294 | ], {
295 | stdio: ['inherit', 'inherit', 'inherit'],
296 | cleanup: true,
297 | shell: true,
298 | })
299 | const { info } = require('@vue/cli-shared-utils')
300 | info(`The JSON schema was automatically generated in '${file}'.`, 'apollo')
301 | }
302 | }
303 | }
304 | }
305 |
306 | module.exports.defaultModes = {
307 | 'apollo:dev': 'development',
308 | }
309 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/logo.png
--------------------------------------------------------------------------------
/operations/engine/key-metrics/error-percentage.js:
--------------------------------------------------------------------------------
1 | const gql = require('graphql-tag')
2 |
3 | module.exports = gql`
4 | query errorPercentageKeyMetrics(
5 | $serviceId: ID!
6 | $timeFrom: Timestamp!
7 | $timeTo: Timestamp
8 | $resolution: Resolution
9 | $filter: ServiceQueryStatsFilter
10 | ) {
11 | service(id: $serviceId) {
12 | id
13 | name
14 | stats(from: $timeFrom, to: $timeTo, resolution: $resolution) {
15 | globalStats: queryStats(
16 | filter: $filter
17 | ) {
18 | timestamp
19 | metrics {
20 | cachedRequestsCount
21 | uncachedRequestsCount
22 | requestsWithErrorsCount
23 | }
24 | }
25 | queriesStats: queryStats(
26 | limit: 4
27 | orderBy: [{ column: REQUESTS_WITH_ERRORS_COUNT, direction: DESCENDING }]
28 | filter: $filter
29 | ) {
30 | timestamp
31 | group: groupBy {
32 | queryId
33 | queryName
34 | querySignature
35 | }
36 | metrics {
37 | cachedRequestsCount
38 | uncachedRequestsCount
39 | requestsWithErrorsCount
40 | }
41 | }
42 | }
43 | }
44 | }
45 | `
46 |
--------------------------------------------------------------------------------
/operations/engine/key-metrics/p95-time.js:
--------------------------------------------------------------------------------
1 | const gql = require('graphql-tag')
2 |
3 | module.exports = gql`
4 | query p95TimeKeyMetrics(
5 | $serviceId: ID!
6 | $timeFrom: Timestamp!
7 | $timeTo: Timestamp
8 | $resolution: Resolution
9 | $filter: ServiceQueryStatsFilter
10 | ) {
11 | service(id: $serviceId) {
12 | id
13 | name
14 | stats(from: $timeFrom, to: $timeTo, resolution: $resolution) {
15 | globalStats: queryStats(
16 | filter: $filter
17 | ) {
18 | timestamp
19 | metrics {
20 | totalLatencyHistogram {
21 | p95Time: durationMs(percentile: 0.95)
22 | }
23 | }
24 | }
25 | queriesStats: queryStats(
26 | limit: 4
27 | filter: $filter
28 | ) {
29 | timestamp
30 | group: groupBy {
31 | queryId
32 | queryName
33 | querySignature
34 | }
35 | metrics {
36 | totalLatencyHistogram {
37 | p95Time: durationMs(percentile: 0.95)
38 | }
39 | }
40 | }
41 | }
42 | }
43 | }
44 | `
45 |
--------------------------------------------------------------------------------
/operations/engine/key-metrics/request-rate.js:
--------------------------------------------------------------------------------
1 | const gql = require('graphql-tag')
2 |
3 | module.exports = gql`
4 | query requestRateKeyMetrics(
5 | $serviceId: ID!
6 | $timeFrom: Timestamp!
7 | $timeTo: Timestamp
8 | $resolution: Resolution
9 | $filter: ServiceQueryStatsFilter
10 | ) {
11 | service(id: $serviceId) {
12 | id
13 | name
14 | stats(from: $timeFrom, to: $timeTo, resolution: $resolution) {
15 | globalStats: queryStats(
16 | filter: $filter
17 | ) {
18 | timestamp
19 | metrics {
20 | uncachedRequestsCount
21 | cachedRequestsCount
22 | }
23 | }
24 | queriesStats: queryStats(
25 | limit: 4
26 | orderBy: [{ column: UNCACHED_REQUESTS_COUNT, direction: DESCENDING }]
27 | filter: $filter
28 | ) {
29 | timestamp
30 | group: groupBy {
31 | queryId
32 | queryName
33 | querySignature
34 | }
35 | metrics {
36 | uncachedRequestsCount
37 | cachedRequestsCount
38 | }
39 | }
40 | }
41 | }
42 | }
43 | `
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-cli-plugin-apollo",
3 | "version": "0.22.2",
4 | "description": "vue-cli 3 plugin to add Apollo and GraphQL",
5 | "main": "index.js",
6 | "types": "./types.d.ts",
7 | "scripts": {
8 | "dev": "nodemon --exec 'npm run build' --watch graphql-client/src",
9 | "build": "yarn build:graphql-client && yarn build:client-addon",
10 | "build:graphql-client": "babel graphql-client/src --out-dir graphql-client/dist",
11 | "build:client-addon": "cd ./client-addon && yarn build && cd ../",
12 | "prepublishOnly": "npm run test && npm run build",
13 | "test": "npm run test:eslint && cd ./client-addon npm run lint",
14 | "test:eslint": "eslint --ext .js .",
15 | "docs:dev": "vuepress dev docs",
16 | "docs:build": "vuepress build docs"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/Akryum/vue-cli-plugin-apollo.git"
21 | },
22 | "keywords": [
23 | "vue",
24 | "vue-cli",
25 | "apollo",
26 | "graphql"
27 | ],
28 | "author": "Guillaume Chau ",
29 | "license": "ISC",
30 | "bugs": {
31 | "url": "https://github.com/Akryum/vue-cli-plugin-apollo/issues"
32 | },
33 | "homepage": "https://github.com/Akryum/vue-cli-plugin-apollo#readme",
34 | "dependencies": {
35 | "apollo": "^2.28.2",
36 | "apollo-cache-inmemory": "^1.6.6",
37 | "apollo-client": "^2.6.10",
38 | "apollo-link": "^1.2.14",
39 | "apollo-link-context": "^1.0.20",
40 | "apollo-link-http": "^1.5.17",
41 | "apollo-link-persisted-queries": "^0.2.2",
42 | "apollo-link-state": "^0.4.2",
43 | "apollo-link-ws": "^1.0.20",
44 | "apollo-server-express": "^2.14.2",
45 | "apollo-upload-client": "^13.0.0",
46 | "apollo-utilities": "^1.3.4",
47 | "chalk": "^4.0.0",
48 | "deepmerge": "^4.2.2",
49 | "dotenv": "^8.2.0",
50 | "esm": "^3.2.25",
51 | "execa": "^4.0.2",
52 | "express": "^4.17.1",
53 | "fs-extra": "^9.0.1",
54 | "graphql": "^15.1.0",
55 | "graphql-subscriptions": "^1.1.0",
56 | "graphql-tag": "^2.10.3",
57 | "graphql-tools": "^6.0.9",
58 | "node-fetch": "^2.6.0",
59 | "nodemon": "^2.0.4",
60 | "subscriptions-transport-ws": "^0.9.16",
61 | "ts-node": "^8.10.2"
62 | },
63 | "devDependencies": {
64 | "@babel/cli": "^7.4.4",
65 | "@babel/core": "^7.4.5",
66 | "@babel/preset-env": "^7.4.5",
67 | "babel-eslint": "^10.0.1",
68 | "eslint": "^7.2.0",
69 | "eslint-config-standard": "^14.0.0",
70 | "eslint-plugin-import": "^2.8.0",
71 | "eslint-plugin-node": "^11.0.0",
72 | "eslint-plugin-promise": "^4.0.1",
73 | "eslint-plugin-standard": "^4.0.0",
74 | "eslint-plugin-vue": "^6.0.0",
75 | "vuepress": "^0.14.8"
76 | },
77 | "peerDependencies": {
78 | "@vue/cli-shared-utils": "^3.0.0 || ^4.0.0-0",
79 | "typescript": ">=2.0"
80 | },
81 | "peerDependenciesMeta": {
82 | "typescript": {
83 | "optional": true
84 | }
85 | },
86 | "engines": {
87 | "node": ">=10"
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/prompts.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | type: 'confirm',
4 | name: 'addExamples',
5 | message: 'Add example code',
6 | description: 'This will generate a component, graphql files and an example schema (if server is added).',
7 | default: false,
8 | },
9 | {
10 | type: 'confirm',
11 | name: 'addServer',
12 | message: 'Add a GraphQL API Server?',
13 | description: 'Generate GraphQL server files in a `apollo-server` folder.',
14 | group: 'GraphQL Server',
15 | default: false,
16 | },
17 | {
18 | type: 'confirm',
19 | name: 'addMocking',
20 | message: 'Enable automatic mocking?',
21 | description: 'Missing resolvers will be automatically mocked.',
22 | group: 'GraphQL Server',
23 | default: false,
24 | when: answers => answers.addServer,
25 | },
26 | {
27 | type: 'confirm',
28 | name: 'addApolloEngine',
29 | message: 'Configure Apollo Engine?',
30 | link: 'http://engine.apollographql.com/',
31 | group: 'GraphQL Server',
32 | default: false,
33 | },
34 | {
35 | type: 'input',
36 | name: 'apolloEngineService',
37 | message: 'Apollo Service ID (create one at https://engine.apollographql.com):',
38 | group: 'GraphQL Server',
39 | validate: input => !!input,
40 | when: answers => answers.addApolloEngine,
41 | },
42 | {
43 | type: 'input',
44 | name: 'apolloEngineKey',
45 | message: 'API Key:',
46 | description: 'It should look like this: \'service:name-xxxx:xxxxxxxxxxxxxxxxxxxxxx\'',
47 | group: 'GraphQL Server',
48 | validate: input => !!input,
49 | when: answers => answers.addApolloEngine,
50 | },
51 | {
52 | type: 'input',
53 | name: 'apolloEngineTag',
54 | message: 'Default Schema Tag:',
55 | description: 'You can have data over multiples tags, which is useful when having several env like staging and production.',
56 | group: 'GraphQL Server',
57 | when: answers => answers.addApolloEngine,
58 | },
59 | {
60 | type: 'confirm',
61 | name: 'publishSchema',
62 | message: 'Publish schema on Apollo Engine?',
63 | group: 'GraphQL Server',
64 | default: false,
65 | when: answers => answers.addApolloEngine,
66 | },
67 | ]
68 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/screenshot.png
--------------------------------------------------------------------------------
/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'vue-cli-plugin-apollo/graphql-client' {
2 | import { ApolloClient, ApolloClientOptions, Resolvers } from 'apollo-client'
3 | import { DocumentNode } from 'apollo-link'
4 | import { SubscriptionClient } from 'subscriptions-transport-ws'
5 | import { ClientStateConfig } from 'apollo-link-state'
6 | import { InMemoryCacheConfig } from 'apollo-cache-inmemory'
7 |
8 | export interface ApolloClientClientConfig {
9 | // URL to the HTTP API
10 | httpEndpoint?: string
11 | // Url to the Websocket API
12 | wsEndpoint?: string
13 | // Token used in localstorage
14 | tokenName?: string
15 | // Enable this if you use Query persisting with Apollo Engine
16 | persisting?: boolean
17 | // Is currently Server-Side Rendering or not
18 | ssr?: boolean
19 | // Only use Websocket for all requests (including queries and mutations)
20 | websocketsOnly?: boolean
21 | // Custom starting link.
22 | // If you want to replace the default HttpLink, set `defaultHttpLink` to false
23 | link?: string
24 | // If true, add the default HttpLink.
25 | // Disable it if you want to replace it with a terminating link using `link` option.
26 | defaultHttpLink?: boolean
27 | // Options for the default HttpLink
28 | httpLinkOptions?: object
29 | // Custom Apollo cache implementation (default is apollo-cache-inmemory)
30 | cache?: any | false
31 | // Options for the default cache
32 | inMemoryCacheOptions?: InMemoryCacheConfig
33 | // Additional Apollo client options
34 | apollo?: ApolloClientOptions
35 | // apollo-link-state options
36 | clientState?: ClientStateConfig
37 | // Function returning Authorization header token
38 | getAuth?: (tokenName: string) => string | void
39 | // Local Schema
40 | typeDefs?: string | string[] | DocumentNode | DocumentNode[]
41 | // Local Resolvers
42 | resolvers?: Resolvers | Resolvers[]
43 | // Hook called when you should write local state in the cache
44 | onCacheInit?: (cache: any) => void
45 | }
46 |
47 | export function createApolloClient(
48 | config: ApolloClientClientConfig
49 | ): {
50 | apolloClient: ApolloClient
51 | wsClient: SubscriptionClient
52 | // stateLink: withClientState
53 | }
54 |
55 | export function restartWebsockets(wsClient: SubscriptionClient): void
56 | }
57 |
58 | declare module 'vue-cli-plugin-apollo/graphql-server' {
59 | // eslint-disable-next-line import/no-duplicates
60 | import { Resolvers } from 'apollo-client'
61 | import { ContextFunction, Context } from 'apollo-server-core'
62 | import { ExpressContext } from 'apollo-server-express/dist/ApolloServer'
63 | import { DataSources } from 'apollo-server-core/dist/graphqlOptions'
64 | import {
65 | ApolloServerExpressConfig,
66 | SchemaDirectiveVisitor
67 | } from 'apollo-server-express'
68 | // eslint-disable-next-line import/no-duplicates
69 | import { DocumentNode } from 'apollo-link'
70 | import { PubSubEngine } from 'graphql-subscriptions'
71 | import { Express } from 'express'
72 |
73 | export interface ApolloServerOption> {
74 | host?: string
75 | port: number
76 | graphqlPath: string
77 | subscriptionsPath: string
78 | // Enable automatic mocking
79 | enableMocks?: boolean
80 | // Enable Apollo Engine
81 | enableEngine?: boolean
82 | engineKey?: string
83 | // Base folder for the server source files
84 | serverFolder?: string
85 | // Cross-Origin options
86 | cors?: string
87 | // Requests timeout (ms)
88 | timeout?: number
89 | // Integrated apollo engine
90 | integratedEngine?: boolean
91 | // For enable typescript server files
92 | // if you don't have @vue/cli-plugin-typescript
93 | typescript?: boolean
94 | // Apollo server options (will be merged with the included default options)
95 | serverOptions: ApolloServerExpressConfig
96 | quiet?: boolean
97 | paths: {
98 | typeDefs: string | string[] | DocumentNode | DocumentNode[]
99 | resolvers: Resolvers | Resolvers[]
100 | context: ContextFunction | Context
101 | pubsub?: PubSubEngine
102 | server?: (app: Express) => void
103 | directives: Record
104 | dataSources?: () => DataSources
105 | }
106 | }
107 |
108 | function ApolloServer(
109 | options: ApolloServerOption,
110 | cb?: () => void
111 | ): string | string[] | DocumentNode | DocumentNode[]
112 |
113 | export default ApolloServer
114 | }
115 |
--------------------------------------------------------------------------------
/ui-public/apollo-engine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/ui-public/apollo-engine.png
--------------------------------------------------------------------------------
/ui-public/publish-task.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/ui-public/publish-task.png
--------------------------------------------------------------------------------
/ui-public/view-tip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/ui-public/view-tip.png
--------------------------------------------------------------------------------
/ui-public/vue-apollo-graphql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-cli-plugin-apollo/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/ui-public/vue-apollo-graphql.png
--------------------------------------------------------------------------------
/ui/configs.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = api => {
4 | const CONFIG = 'org.akryum.vue-apollo.configs.apollo'
5 |
6 | // Config file
7 | api.describeConfig({
8 | id: CONFIG,
9 | name: 'Apollo Server',
10 | description: 'Integrated GraphQL server',
11 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo#configuration',
12 | files: {
13 | vue: {
14 | js: ['vue.config.js'],
15 | },
16 | graphql: {
17 | yaml: ['.graphqlconfig.yml'],
18 | },
19 | },
20 | onRead: ({ data, cwd }) => {
21 | return {
22 | prompts: [
23 | {
24 | name: 'enableMocks',
25 | message: 'Mocking',
26 | description: 'Enable auto-mocking for quick prototyping',
27 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo#mocks',
28 | type: 'confirm',
29 | file: 'vue',
30 | default: false,
31 | value: getConfigData(data).enableMocks,
32 | },
33 | {
34 | name: 'serverFolder',
35 | message: 'Server folder',
36 | description: 'Folder containing the server source files',
37 | type: 'input',
38 | file: 'vue',
39 | default: './apollo-server',
40 | value: getConfigData(data).serverFolder,
41 | },
42 | {
43 | name: 'cors',
44 | message: 'CORS',
45 | description: 'Allow access to other origins',
46 | type: 'input',
47 | file: 'vue',
48 | default: '*',
49 | value: stringOnly(getConfigData(data).cors),
50 | },
51 | {
52 | name: 'timeout',
53 | message: 'Response timeout',
54 | description: 'Time before a Query request is timed out (in ms)',
55 | type: 'input',
56 | file: 'vue',
57 | default: '120000',
58 | transformer: value => value.toString(),
59 | filter: value => parseInt(value),
60 | value: getConfigData(data).timeout,
61 | },
62 | {
63 | name: 'enableEngine',
64 | group: 'Apollo Engine',
65 | message: 'Apollo Engine',
66 | description: 'Enable Apollo Engine, a cloud monitoring service',
67 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo#apollo-engine',
68 | type: 'confirm',
69 | file: 'vue',
70 | default: false,
71 | value: getConfigData(data).enableEngine,
72 | },
73 | {
74 | name: 'integratedEngine',
75 | group: 'Apollo Engine',
76 | message: 'Integrated Engine layer',
77 | description: 'Uncheck this if you want to use an external Engine container/layer',
78 | link: 'https://www.apollographql.com/docs/apollo-server/v2/migration-engine.html#With-a-Running-Engine-Proxy',
79 | type: 'confirm',
80 | file: 'vue',
81 | when: answers => answers.enableEngine,
82 | default: true,
83 | value: getConfigData(data).integratedEngine,
84 | },
85 | ],
86 | }
87 | },
88 | onWrite: async ({ api, prompts, cwd }) => {
89 | const result = {}
90 | for (const prompt of prompts.filter(p => p.raw.file === 'vue')) {
91 | result[`pluginOptions.apollo.${prompt.id}`] = await api.getAnswer(prompt.id)
92 | }
93 | api.setData('vue', result)
94 |
95 | // Update app manifest
96 |
97 | const serverFolder = result['pluginOptions.apollo.serverFolder'] || prompts.find(p => p.id === 'serverFolder').raw.default
98 | api.setData('graphql', {
99 | 'projects.app.schemaPath': path.join(serverFolder, 'schema.graphql'),
100 | })
101 | },
102 | })
103 | }
104 |
105 | function getConfigData (data) {
106 | return (data.vue && data.vue.pluginOptions && data.vue.pluginOptions.apollo) || {}
107 | }
108 |
109 | function stringOnly (value) {
110 | return typeof value === 'string' ? value : undefined
111 | }
112 |
--------------------------------------------------------------------------------
/ui/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = api => {
4 | if (process.env.VUE_CLI_PLUGIN_DEV) {
5 | api.addClientAddon({
6 | id: 'org.akryum.vue-apollo.client-addon',
7 | url: 'http://localhost:8043/index.js',
8 | })
9 | } else {
10 | api.addClientAddon({
11 | id: 'org.akryum.vue-apollo.client-addon',
12 | path: path.resolve(__dirname, '../client-addon-dist'),
13 | })
14 | }
15 |
16 | require('./configs')(api)
17 | require('./tasks')(api)
18 | require('./widgets')(api)
19 | // require('./views')(api)
20 | }
21 |
--------------------------------------------------------------------------------
/ui/tasks.js:
--------------------------------------------------------------------------------
1 | const ifDef = (value, cb) => typeof value !== 'undefined' && cb(value)
2 |
3 | module.exports = api => {
4 | const {
5 | getSharedData,
6 | setSharedData,
7 | addSuggestion,
8 | removeSuggestion,
9 | storageGet,
10 | storageSet,
11 | } = api.namespace('org.akryum.vue-apollo.')
12 |
13 | const ENGINE_FRONTEND = process.env.APOLLO_ENGINE_FRONTEND || 'https://engine.apollographql.com'
14 |
15 | setSharedData('engine.frontend', ENGINE_FRONTEND)
16 |
17 | const { loadEnv } = require('../utils/load-env')
18 | const env = loadEnv([
19 | api.resolve('.env'),
20 | api.resolve('.env.local'),
21 | ])
22 |
23 | function resetData (force = false) {
24 | if (force || !getSharedData('running')) {
25 | setSharedData('running', false)
26 | setSharedData('urls', null)
27 | setSharedData('error', false)
28 | }
29 | }
30 |
31 | api.onProjectOpen(() => {
32 | resetData()
33 | })
34 |
35 | function onGraphqlServerMessage ({ data }) {
36 | const message = data['org.akryum.vue-apollo']
37 | if (message) {
38 | ifDef(message.urls, value => setSharedData('urls', value))
39 | ifDef(message.error, value => setSharedData('error', value))
40 | }
41 | }
42 |
43 | const commonOptions = ({
44 | prompts = [],
45 | views = [],
46 | onBeforeRun = undefined,
47 | onRun = undefined,
48 | onExit = undefined,
49 | } = {}) => ({
50 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo#injected-commands',
51 | views: [
52 | {
53 | id: 'org.akryum.vue-apollo.views.playground',
54 | label: 'Playground',
55 | icon: 'gamepad',
56 | component: 'org.akryum.vue-apollo.components.playground',
57 | },
58 | ...views,
59 | ],
60 | defaultView: 'org.akryum.vue-apollo.views.playground',
61 | prompts: [
62 | {
63 | name: 'host',
64 | type: 'input',
65 | default: '',
66 | message: 'Server host',
67 | },
68 | {
69 | name: 'port',
70 | type: 'input',
71 | default: '',
72 | message: 'Server port',
73 | },
74 | {
75 | name: 'mock',
76 | type: 'confirm',
77 | default: false,
78 | message: 'Force enable auto-mocks',
79 | description: 'Auto-mocks will return fake data for missing resolvers',
80 | },
81 | {
82 | name: 'engine',
83 | type: 'confirm',
84 | default: false,
85 | message: 'Force enable Apollo Engine',
86 | },
87 | {
88 | name: 'generate-schema',
89 | type: 'confirm',
90 | default: false,
91 | message: 'Auto-generate schma JSON and GraphQL files',
92 | },
93 | ...prompts,
94 | ],
95 | onBeforeRun: async ({ answers, args }) => {
96 | // Args
97 | if (answers.host) args.push('--host', answers.host)
98 | if (answers.port) args.push('--port', answers.port)
99 | if (answers.mock) args.push('--mock')
100 | if (answers.engine) args.push('--engine')
101 | if (answers['generate-schema']) args.push('--generate-schema')
102 |
103 | onBeforeRun && await onBeforeRun({ answers, args })
104 | },
105 | onRun: () => {
106 | api.ipcOn(onGraphqlServerMessage)
107 | setSharedData('running', true)
108 |
109 | onRun && onRun()
110 | },
111 | onExit: () => {
112 | api.ipcOff(onGraphqlServerMessage)
113 | resetData(true)
114 |
115 | onExit && onExit()
116 | },
117 | })
118 |
119 | const DEV_TASK = /vue-cli-service apollo:dev/
120 | const START_TASK = /vue-cli-service apollo:start/
121 | const GENERATE_SCHEMA_TASK = /vue-cli-service apollo:schema:generate/
122 | const CHECK_SCHEMA_TASK = /vue-cli-service apollo:client:check/
123 | const PUBLISH_SCHEMA_TASK = /vue-cli-service apollo:schema:publish/
124 |
125 | const devOptions = commonOptions({
126 | onBeforeRun: async ({ answers, args }) => {
127 | console.log(args)
128 | },
129 | })
130 |
131 | api.describeTask({
132 | match: DEV_TASK,
133 | description: 'Run and watch the GraphQL server',
134 | ...devOptions,
135 | })
136 |
137 | api.describeTask({
138 | match: START_TASK,
139 | description: 'Run the GraphQL server',
140 | ...commonOptions(),
141 | })
142 |
143 | api.describeTask({
144 | match: GENERATE_SCHEMA_TASK,
145 | description: 'Generates full schema JSON and GraphQL files',
146 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo#injected-commands',
147 | prompts: [
148 | {
149 | name: 'watch',
150 | type: 'confirm',
151 | default: false,
152 | message: 'Watch mode',
153 | description: 'Generates automatically when server files change',
154 | },
155 | ],
156 | onBeforeRun: async ({ answers, args }) => {
157 | // Args
158 | if (answers.watch) args.push('--watch')
159 | },
160 | })
161 |
162 | const schemaCommonPrompts = [
163 | {
164 | name: 'endpoint',
165 | type: 'input',
166 | default: '',
167 | message: 'Endpoint',
168 | description: 'URL to running GraphQL server or path to JSON schema file',
169 | },
170 | {
171 | name: 'key',
172 | type: 'input',
173 | default: '',
174 | message: 'Engine service key',
175 | description: 'The unique API key associated with the Engine service of your project',
176 | link: 'https://engine.apollographql.com',
177 | },
178 | {
179 | name: 'tag',
180 | type: 'input',
181 | default: '',
182 | message: 'Schema Tag',
183 | description: 'You can have data over multiples tags, which is useful when having several env like staging and production.',
184 | },
185 | ]
186 |
187 | const schemaCommonOnBeforeRun = async ({ answers, args }) => {
188 | if (answers.endpoint) args.push('--endpoint', answers.endpoint)
189 | if (answers.key) args.push('--key', answers.key)
190 | if (answers.tag) args.push('--tag', answers.tag)
191 | }
192 |
193 | api.describeTask({
194 | match: CHECK_SCHEMA_TASK,
195 | description: 'Check schema and compare it to the published schema on Apollo Engine',
196 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo#injected-commands',
197 | prompts: [
198 | ...schemaCommonPrompts,
199 | ],
200 | onBeforeRun: async ({ answers, args }) => {
201 | await schemaCommonOnBeforeRun({ answers, args })
202 | },
203 | })
204 |
205 | api.describeTask({
206 | match: PUBLISH_SCHEMA_TASK,
207 | description: 'Publish schema to Apollo Engine',
208 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo#injected-commands',
209 | prompts: [
210 | ...schemaCommonPrompts,
211 | ],
212 | onBeforeRun: async ({ answers, args }) => {
213 | await schemaCommonOnBeforeRun({ answers, args })
214 | },
215 | })
216 |
217 | const WELCOME = 'suggestions.welcome'
218 | const WELCOME_DISABLED = 'suggestions.welcome.disabled'
219 | const OPEN_ENGINE = 'suggestions.open-engine'
220 | const VIEW_TIP = 'suggestions.view-tip'
221 | const PUBLISH_SCHEMA_TIP = 'suggestions.publish-schema'
222 | const PUBLISH_SCHEMA_TIP_DISABLED = 'suggestions.publish-schema.disabled'
223 |
224 | if (!storageGet(WELCOME_DISABLED)) {
225 | addSuggestion({
226 | id: WELCOME,
227 | type: 'action',
228 | label: 'Vue Apollo installed!',
229 | message: `Apollo GraphQL is now integrated into your project.
230 |
231 | - You can configure the Apollo Server by going to 'Configurations'.
232 | - Run the Apollo Server in the 'Tasks' page.
233 | - An Apollo Engine analytics widget is also available in the Dashboard.
234 |
`,
235 | image: '/_plugin/vue-cli-plugin-apollo/vue-apollo-graphql.png',
236 | link: 'https://github.com/Akryum/vue-cli-plugin-apollo',
237 | handler () {
238 | storageSet(WELCOME_DISABLED, true)
239 | },
240 | })
241 | }
242 |
243 | api.onViewOpen(({ view }) => {
244 | if (view.id === 'org.akryum.vue-apollo.apollo') {
245 | addApolloEngineSuggestion()
246 | } else if (view.id !== 'vue-project-tasks') {
247 | removeTaskSuggestions()
248 | }
249 | })
250 |
251 | api.onTaskOpen(({ task }) => {
252 | if ([
253 | DEV_TASK,
254 | // DEV_CLIENT_TASK,
255 | START_TASK,
256 | GENERATE_SCHEMA_TASK,
257 | PUBLISH_SCHEMA_TASK,
258 | ].includes(task.match)) {
259 | // addViewTipSuggestion()
260 | addApolloEngineSuggestion()
261 | if (task.match !== PUBLISH_SCHEMA_TASK && !storageGet(PUBLISH_SCHEMA_TIP_DISABLED)) {
262 | addPublishSchemaSuggestion()
263 | }
264 | } else {
265 | removeTaskSuggestions()
266 | }
267 | })
268 |
269 | // function addViewTipSuggestion () {
270 | // addSuggestion({
271 | // id: VIEW_TIP,
272 | // type: 'action',
273 | // label: 'Apollo GraphQL page',
274 | // message: 'Check out the Apollo GraphQL page for more tools and info!',
275 | // image: '/_plugin/vue-cli-plugin-apollo/view-tip.png',
276 | // handler () {
277 | // api.requestRoute({
278 | // name: 'org.akryum.vue-apollo.routes.apollo',
279 | // })
280 | // },
281 | // })
282 | // }
283 |
284 | function addApolloEngineSuggestion () {
285 | addSuggestion({
286 | id: OPEN_ENGINE,
287 | type: 'action',
288 | label: 'Open Apollo Engine',
289 | message: 'Apollo Engine is a cloud service that provides deep insights into your GraphQL layer, with performance and error analytics.',
290 | image: '/_plugin/vue-cli-plugin-apollo/apollo-engine.png',
291 | link: 'https://www.apollographql.com/engine',
292 | actionLink: `${ENGINE_FRONTEND}/service/${env.VUE_APP_APOLLO_ENGINE_SERVICE}`,
293 | // handler () {
294 | // openBrowser('https://engine.apollographql.com/')
295 | // return {
296 | // keep: true,
297 | // }
298 | // },
299 | })
300 | }
301 |
302 | function addPublishSchemaSuggestion () {
303 | addSuggestion({
304 | id: PUBLISH_SCHEMA_TIP,
305 | type: 'action',
306 | label: 'Publish your schema',
307 | message: 'You can publish your schema to Apollo Engine with the \'apollo:schema:publish\' task.',
308 | image: '/_plugin/vue-cli-plugin-apollo/publish-task.png',
309 | handler () {
310 | api.requestRoute({
311 | name: 'project-task-details',
312 | params: {
313 | id: `${api.getCwd()}:apollo:schema:publish`,
314 | },
315 | })
316 | storageSet(PUBLISH_SCHEMA_TIP_DISABLED, true)
317 | },
318 | })
319 | }
320 |
321 | function removeTaskSuggestions () {
322 | [OPEN_ENGINE, VIEW_TIP, PUBLISH_SCHEMA_TIP].forEach(id => removeSuggestion(id))
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/ui/views.js:
--------------------------------------------------------------------------------
1 | module.exports = api => {
2 | api.addView({
3 | id: 'org.akryum.vue-apollo.apollo',
4 | name: 'org.akryum.vue-apollo.routes.apollo',
5 | tooltip: 'Apollo GraphQL',
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/ui/widgets.js:
--------------------------------------------------------------------------------
1 | module.exports = api => {
2 | const gql = require('graphql-tag')
3 | const { registerWidget, onAction } = api.namespace('org.akryum.vue-apollo.widgets.')
4 |
5 | const { loadEnv } = require('../utils/load-env')
6 | const env = loadEnv([
7 | api.resolve('.env'),
8 | api.resolve('.env.local'),
9 | ])
10 |
11 | async function queryEngine ({ query, variables }) {
12 | const { execute } = require('../utils/engine-api')
13 | const { loadEnv } = require('../utils/load-env')
14 | const env = loadEnv([
15 | api.resolve('.env'),
16 | api.resolve('.env.local'),
17 | ])
18 | const key = env.VUE_APP_APOLLO_ENGINE_KEY
19 | return execute({
20 | query,
21 | variables,
22 | key,
23 | })
24 | }
25 |
26 | // Key metrics
27 | {
28 | registerWidget({
29 | id: 'engine-key-metrics',
30 | title: 'Engine Key Metrics',
31 | description: 'Get Engine analytics at a glance',
32 | longDescription: 'Displays key metrics of the Apollo Engine metrics. You can choose between different types of metrics.',
33 | link: 'https://www.apollographql.com/engine',
34 | component: 'org.akryum.vue-apollo.components.widgets.engine-key-metrics',
35 | minWidth: 2,
36 | minHeight: 1,
37 | maxWidth: 3,
38 | maxHeight: 2,
39 | defaultWidth: 3,
40 | defaultHeight: 2,
41 | needsUserConfig: true,
42 | onConfigOpen: async ({ context }) => {
43 | let allServices = []
44 |
45 | const { data } = await queryEngine({
46 | query: gql`
47 | {
48 | allServices {
49 | id
50 | name
51 | createdAt
52 | }
53 | }
54 | `,
55 | })
56 | allServices = data.allServices
57 |
58 | return {
59 | prompts: [
60 | {
61 | name: 'service',
62 | type: 'list',
63 | message: 'Select your Engine service',
64 | choices: allServices.map(service => ({
65 | name: service.name,
66 | value: service.id,
67 | })),
68 | default: env.VUE_APP_APOLLO_ENGINE_SERVICE,
69 | validate: input => !!input,
70 | },
71 | {
72 | name: 'tag',
73 | type: 'list',
74 | choices: async answers => {
75 | const service = allServices.find(s => s.id === answers.service)
76 | if (!service) return []
77 |
78 | const { data } = await queryEngine({
79 | query: gql`
80 | query ($id: ID!) {
81 | service (id: $id) {
82 | id
83 | schemaTags {
84 | tag
85 | }
86 | }
87 | }
88 | `,
89 | variables: {
90 | id: service.id,
91 | },
92 | })
93 |
94 | return data.service.schemaTags.map(tag => ({
95 | name: tag.tag,
96 | value: tag.tag,
97 | })).concat([
98 | {
99 | name: 'untagged',
100 | value: null,
101 | },
102 | ])
103 | },
104 | message: 'Schema Tag',
105 | default: env.VUE_APP_APOLLO_ENGINE_TAG,
106 | },
107 | {
108 | name: 'type',
109 | type: 'list',
110 | message: 'Metrics type',
111 | choices: [
112 | { name: 'Request rate', value: 'requestRate' },
113 | { name: 'p95 Service time', value: 'p95Time' },
114 | { name: 'Error percentage', value: 'errorPercentage' },
115 | ],
116 | default: 'requestRate',
117 | validate: input => !!input,
118 | },
119 | {
120 | name: 'timeRange',
121 | type: 'list',
122 | message: 'Time range',
123 | choices: [
124 | { name: 'Last hour', value: 3600 },
125 | { name: 'Last day', value: 86400 },
126 | { name: 'Last week', value: 86400 * 7 },
127 | { name: 'Last month', value: 86400 * 30 },
128 | ],
129 | default: 3600,
130 | validate: input => !!input,
131 | },
132 | ],
133 | }
134 | },
135 | })
136 |
137 | const operations = {
138 | requestRate: require('../operations/engine/key-metrics/request-rate'),
139 | p95Time: require('../operations/engine/key-metrics/p95-time'),
140 | errorPercentage: require('../operations/engine/key-metrics/error-percentage'),
141 | }
142 |
143 | const resolutions = {
144 | 3600: 'R1M',
145 | 86400: 'R15M',
146 | [86400 * 7]: 'R6H',
147 | [86400 * 30]: 'R1D',
148 | }
149 |
150 | onAction('actions.query-engine', async params => {
151 | const query = operations[params.type]
152 | const { data, errors } = await queryEngine({
153 | query,
154 | variables: {
155 | serviceId: params.service,
156 | timeFrom: `-${params.timeRange}`,
157 | timeTo: '-0',
158 | resolution: resolutions[params.timeRange],
159 | filter: {
160 | schemaTag: params.tag,
161 | },
162 | ...params.otherVariables || {},
163 | },
164 | })
165 | if (errors) {
166 | throw new Error(`${errors.map(e => e.message).join(', ')}`)
167 | }
168 | return data
169 | })
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/utils/check-schema.js:
--------------------------------------------------------------------------------
1 | module.exports = async ({ endpoint, key, tag, engineEndpoint }) => {
2 | const execa = require('execa')
3 | const { logWithSpinner, stopSpinner, done } = require('@vue/cli-shared-utils')
4 |
5 | logWithSpinner('📡', 'Comparing schema from Engine...')
6 | await execa('apollo', [
7 | 'client:check',
8 | `--endpoint=${endpoint}`,
9 | `--key=${key}`,
10 | ...(tag ? [`--tag=${tag}`] : []),
11 | ...(engineEndpoint ? [`--engine=${engineEndpoint}`] : []),
12 | ], {
13 | stdio: ['inherit', 'inherit', 'inherit'],
14 | })
15 | stopSpinner()
16 | done('Checked schema on Engine')
17 | }
18 |
--------------------------------------------------------------------------------
/utils/engine-api.js:
--------------------------------------------------------------------------------
1 | const { createHttpLink } = require('apollo-link-http')
2 | const fetch = require('node-fetch')
3 | const { toPromise, execute } = require('apollo-link')
4 | const { print } = require('graphql')
5 |
6 | const ENGINE_ENDPOINT = process.env.APOLLO_ENGINE_API_ENDPOINT || 'https://engine-graphql.apollographql.com/api/graphql/'
7 |
8 | const engineLink = createHttpLink({
9 | uri: ENGINE_ENDPOINT,
10 | fetch,
11 | })
12 |
13 | exports.execute = async ({ query, variables, key }) => {
14 | const response = await toPromise(
15 | execute(engineLink, {
16 | query,
17 | variables,
18 | context: {
19 | headers: {
20 | 'X-Api-Key': key,
21 | uri: ENGINE_ENDPOINT,
22 | },
23 | },
24 | }),
25 | )
26 | if (process.env.VUE_APP_CLI_UI_DEBUG) {
27 | console.log(`${ENGINE_ENDPOINT}\n`, print(query), `\nKey: ${key}`, '\nVariables:\n', variables, '\nResponse:\n', response)
28 | }
29 | if (response.errors) {
30 | const error = new Error('Errors were returned from API')
31 | error.response = response
32 | console.log(response.errors)
33 | throw error
34 | }
35 | return response
36 | }
37 |
--------------------------------------------------------------------------------
/utils/generate-schema.js:
--------------------------------------------------------------------------------
1 | module.exports = async (options) => {
2 | const path = require('path')
3 | const fs = require('fs-extra')
4 | const { logWithSpinner, stopSpinner, done } = require('@vue/cli-shared-utils')
5 | const { graphql, getIntrospectionQuery, printSchema } = require('graphql')
6 | const { makeExecutableSchema } = require('graphql-tools')
7 | const { load } = require('./load')(options)
8 |
9 | // JS Schema
10 | const typeDefs = load(options.paths.typeDefs)
11 | const resolvers = load(options.paths.resolvers)
12 | const schemaDirectives = load(options.paths.directives)
13 | const schema = makeExecutableSchema({
14 | typeDefs,
15 | resolvers,
16 | schemaDirectives,
17 | allowUndefinedInResolve: true,
18 | })
19 |
20 | // JSON schema
21 | logWithSpinner('📄', 'Generating JSON file...')
22 | await fs.ensureDir(path.dirname(options.jsonOutput))
23 | const result = await graphql(schema, getIntrospectionQuery())
24 | if (result.errors) {
25 | throw new Error(`Generating JSON failed: ${result.errors.map(e => e.message).join(' | ')}`)
26 | }
27 | fs.writeFileSync(
28 | options.jsonOutput,
29 | JSON.stringify(result, null, 2),
30 | )
31 | stopSpinner()
32 | done(`Generated ${options.jsonOutput}`)
33 |
34 | // GraphQL schema
35 | logWithSpinner('📄', 'Generating GraphQL file...')
36 | await fs.ensureDir(path.dirname(options.graphqlOutput))
37 | fs.writeFileSync(
38 | options.graphqlOutput,
39 | printSchema(schema),
40 | )
41 | stopSpinner()
42 | done(`Generated ${options.graphqlOutput}`)
43 | }
44 |
--------------------------------------------------------------------------------
/utils/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const chalk = require('chalk')
3 |
4 | const DEFAULT_SERVER_FOLDER = './apollo-server'
5 |
6 | let ipc, ipcTimer
7 |
8 | function defaultValue (provided, value) {
9 | return provided == null ? value : provided
10 | }
11 |
12 | function nullable (value) {
13 | return value == null ? {} : value
14 | }
15 |
16 | function autoCall (fn, ...context) {
17 | if (typeof fn === 'function') {
18 | return fn(...context)
19 | }
20 | return fn
21 | }
22 |
23 | function generateCacheIdentifier (context) {
24 | const fs = require('fs')
25 | const path = require('path')
26 |
27 | const graphqlConfigFile = path.join(context, '.graphqlconfig')
28 | if (fs.existsSync(graphqlConfigFile)) {
29 | try {
30 | const graphqlConfig = JSON.parse(fs.readFileSync(graphqlConfigFile, { encoding: 'utf8' }))
31 | const schemaFile = path.join(context, graphqlConfig.schemaPath)
32 | return fs.statSync(schemaFile).mtimeMs
33 | } catch (e) {
34 | console.error('Invalid .graphqlconfig file')
35 | }
36 | }
37 | }
38 |
39 | function sendIpcMessage (message) {
40 | const { IpcMessenger } = require('@vue/cli-shared-utils')
41 | if (!ipc && IpcMessenger) {
42 | ipc = new IpcMessenger()
43 | ipc.connect()
44 | }
45 | if (ipc) {
46 | ipc.send({
47 | 'org.akryum.vue-apollo': message,
48 | })
49 | clearTimeout(ipcTimer)
50 | ipcTimer = setTimeout(() => {
51 | ipc.disconnect()
52 | ipc = null
53 | }, 3000)
54 | }
55 | }
56 |
57 | function getFlatArgs (args, ignore) {
58 | const flatArgs = []
59 | for (const key in args) {
60 | if (ignore.includes(key)) continue
61 | const value = args[key]
62 | if (value) {
63 | flatArgs.push(`--${key}`)
64 | if (value !== true) {
65 | flatArgs.push(JSON.stringify(value))
66 | }
67 | }
68 | }
69 | for (const arg of args._) {
70 | flatArgs.push(JSON.stringify(arg))
71 | }
72 | return flatArgs
73 | }
74 |
75 | function runParallelCommand ({ run }) {
76 | if (run) {
77 | const execa = require('execa')
78 | const [file, ...commandArgs] = run.split(' ')
79 | execa(file, commandArgs, {
80 | cleanup: true,
81 | stdio: ['inherit', 'inherit', 'inherit'],
82 | })
83 | }
84 | }
85 |
86 | function runWatch (api, options, {
87 | script,
88 | args,
89 | ext = 'js mjs json graphql gql ts',
90 | onStart = undefined,
91 | onRestart = undefined,
92 | onCrash = undefined,
93 | }) {
94 | const { hasYarn } = require('@vue/cli-shared-utils')
95 | const nodemon = require('nodemon')
96 |
97 | const cmd = hasYarn() ? 'yarn' : 'npm'
98 | const { baseFolder } = getBasicServerOptions(api, options, args)
99 |
100 | return new Promise((resolve, reject) => {
101 | nodemon({
102 | exec: `${cmd} run ${script} ${cmd === 'npm' ? '-- ' : ''}${args.join(' ')}`,
103 | watch: [
104 | api.resolve(baseFolder),
105 | ],
106 | ignore: [
107 | api.resolve(path.join(baseFolder, 'live')),
108 | ],
109 | ext,
110 | })
111 |
112 | onStart && onStart()
113 |
114 | nodemon.on('restart', () => {
115 | onRestart && onRestart()
116 | })
117 |
118 | nodemon.on('crash', () => {
119 | onCrash && onCrash()
120 | console.log(chalk.red(' Waiting for changes...'))
121 | })
122 |
123 | nodemon.on('stdout', (...args) => {
124 | console.log(chalk.grey(...args))
125 | })
126 |
127 | nodemon.on('stderr', (...args) => {
128 | console.log(chalk.grey(...args))
129 | })
130 |
131 | nodemon.on('quit', () => {
132 | resolve()
133 | process.exit()
134 | })
135 | })
136 | }
137 |
138 | function getBasicServerOptions (api, options, args) {
139 | const apolloOptions = nullable(nullable(options.pluginOptions).apollo)
140 | const baseFolder = defaultValue(apolloOptions.serverFolder, DEFAULT_SERVER_FOLDER)
141 | return {
142 | apolloOptions,
143 | baseFolder,
144 | }
145 | }
146 |
147 | function getServerOptions (api, options, args) {
148 | // Env
149 | const host = args.host || process.env.VUE_APP_GRAPHQL_HOST || 'localhost'
150 | process.env.VUE_APP_GRAPHQL_HOST = host
151 | const port = args.port || process.env.VUE_APP_GRAPHQL_PORT || 4000
152 | process.env.VUE_APP_GRAPHQL_PORT = port
153 | const graphqlPath = process.env.VUE_APP_GRAPHQL_PATH || '/graphql'
154 | const subscriptionsPath = process.env.VUE_APP_GRAPHQL_SUBSCRIPTIONS_PATH || '/graphql'
155 | const engineKey = process.env.VUE_APP_APOLLO_ENGINE_KEY || null
156 | const schemaTag = process.env.VUE_APP_APOLLO_ENGINE_TAG
157 |
158 | // Plugin options
159 | const { apolloOptions, baseFolder } = getBasicServerOptions(api, options, args)
160 |
161 | const engineOptions = Object.assign({}, apolloOptions.engineOptions)
162 | if (!engineOptions.endpointUrl && process.env.APOLLO_ENGINE_TRACING_ENDPOINT) {
163 | engineOptions.endpointUrl = process.env.APOLLO_ENGINE_TRACING_ENDPOINT
164 | }
165 |
166 | return {
167 | host,
168 | port,
169 | graphqlPath,
170 | subscriptionsPath,
171 | engineKey,
172 | typescript: api.hasPlugin('typescript') || defaultValue(apolloOptions.typescript, false),
173 | enableMocks: defaultValue(args.mock, apolloOptions.enableMocks),
174 | enableEngine: defaultValue(args.engine, apolloOptions.enableEngine),
175 | cors: defaultValue(apolloOptions.cors, '*'),
176 | timeout: defaultValue(apolloOptions.timeout, 120000),
177 | integratedEngine: defaultValue(apolloOptions.integratedEngine, true),
178 | schemaTag,
179 | engineOptions,
180 | serverOptions: apolloOptions.apolloServer,
181 | paths: {
182 | typeDefs: api.resolve(`${baseFolder}/type-defs`),
183 | resolvers: api.resolve(`${baseFolder}/resolvers`),
184 | context: api.resolve(`${baseFolder}/context`),
185 | mocks: api.resolve(`${baseFolder}/mocks`),
186 | pubsub: api.resolve(`${baseFolder}/pubsub`),
187 | server: api.resolve(`${baseFolder}/server`),
188 | apollo: api.resolve(`${baseFolder}/apollo`),
189 | engine: api.resolve(`${baseFolder}/engine`),
190 | directives: api.resolve(`${baseFolder}/directives`),
191 | dataSources: api.resolve(`${baseFolder}/data-sources`),
192 | },
193 | apolloOptions,
194 | baseFolder,
195 | }
196 | }
197 |
198 | module.exports = {
199 | defaultValue,
200 | nullable,
201 | autoCall,
202 | generateCacheIdentifier,
203 | sendIpcMessage,
204 | getFlatArgs,
205 | runParallelCommand,
206 | runWatch,
207 | getServerOptions,
208 | }
209 |
--------------------------------------------------------------------------------
/utils/load-env.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | exports.loadEnv = (paths) => paths.reduce((res, file) => {
4 | if (fs.existsSync(file)) {
5 | Object.assign(res, parse(fs.readFileSync(file, 'utf-8')))
6 | }
7 | return res
8 | }, {})
9 |
10 | function parse (src) {
11 | const res = {}
12 | src.split('\n').forEach(line => {
13 | // matching "KEY' and 'VAL' in 'KEY=VAL'
14 | const keyValueArr = line.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/)
15 | // matched?
16 | if (keyValueArr != null) {
17 | const key = keyValueArr[1]
18 | let value = keyValueArr[2] || ''
19 |
20 | // expand newlines in quoted values
21 | const len = value ? value.length : 0
22 | if (len > 0 && value.charAt(0) === '"' && value.charAt(len - 1) === '"') {
23 | value = value.replace(/\\n/gm, '\n')
24 | }
25 |
26 | // remove any surrounding quotes and extra spaces
27 | value = value.replace(/(^['"]|['"]$)/g, '').trim()
28 |
29 | res[key] = value
30 | }
31 | })
32 | return res
33 | }
34 |
--------------------------------------------------------------------------------
/utils/load.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-global-assign
2 | require = require('esm')(module)
3 |
4 | module.exports = function (options) {
5 | if (options.typescript) require('ts-node/register/transpile-only')
6 |
7 | return {
8 | load,
9 | }
10 | }
11 |
12 | function load (file) {
13 | const module = require(file)
14 | if (module.default) {
15 | return module.default
16 | }
17 | return module
18 | }
19 |
--------------------------------------------------------------------------------
/utils/publish-schema.js:
--------------------------------------------------------------------------------
1 | module.exports = async ({ endpoint, key, tag, engineEndpoint }) => {
2 | const execa = require('execa')
3 | const { logWithSpinner, stopSpinner, done } = require('@vue/cli-shared-utils')
4 |
5 | logWithSpinner('⬆️', 'Publishing schema to Engine...')
6 | await execa('apollo', [
7 | 'schema:publish',
8 | `--endpoint=${endpoint}`,
9 | `--key=${key}`,
10 | ...(tag ? [`--tag=${tag}`] : []),
11 | ...(engineEndpoint ? [`--engine=${engineEndpoint}`] : []),
12 | ], {
13 | stdio: ['inherit', 'inherit', 'inherit'],
14 | })
15 | stopSpinner()
16 | done('Published schema to Engine')
17 | }
18 |
--------------------------------------------------------------------------------