From b4cb8220e4565e744bb617abff7cb22eefcb82eb Mon Sep 17 00:00:00 2001 From: joe-re Date: Fri, 1 Dec 2017 00:18:02 +0900 Subject: [PATCH 1/2] TypeScript support --- .gitignore | 1 + .npmignore | 1 + package.json | 15 ++- types/index.d.ts | 4 + types/test/App.ts | 194 +++++++++++++++++++++++++++++++++++++++ types/test/index.ts | 19 ++++ types/test/tsconfig.json | 28 ++++++ types/vue-apollo.d.ts | 77 ++++++++++++++++ types/vue.d.ts | 15 +++ yarn.lock | 96 +++++++++++++++++++ 10 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 types/index.d.ts create mode 100644 types/test/App.ts create mode 100644 types/test/index.ts create mode 100644 types/test/tsconfig.json create mode 100644 types/vue-apollo.d.ts create mode 100644 types/vue.d.ts diff --git a/.gitignore b/.gitignore index c2658d7d..c4d3bd69 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ +types/test/*.js diff --git a/.npmignore b/.npmignore index 8eba6c8d..fa50248c 100644 --- a/.npmignore +++ b/.npmignore @@ -1 +1,2 @@ src/ +types/test/ diff --git a/package.json b/package.json index c7aeeaa7..d283df4f 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,15 @@ "main": "dist/vue-apollo.umd.js", "module": "dist/vue-apollo.esm.js", "unpkg": "dist/vue-apollo.min.js", + "typings": "types/index.d.ts", "scripts": { "build": "npm run build:browser && npm run build:es && npm run build:umd", "build:browser": "rollup --config build/rollup.config.browser.js", "build:es": "rollup --config build/rollup.config.es.js", "build:umd": "rollup --config build/rollup.config.umd.js", "prepublish": "npm run build", - "dev": "npm-watch" + "dev": "npm-watch", + "test:types": "tsc -p types/test" }, "watch": { "build": "src/*.js" @@ -40,6 +42,11 @@ "lodash.throttle": "^4.1.1" }, "devDependencies": { + "@types/graphql": "^0.11.7", + "apollo-cache-inmemory": "^1.1.1", + "apollo-client": "^2.0.3", + "apollo-link": "^1.0.3", + "apollo-link-http": "^1.2.0", "babel-core": "^6.26.0", "babel-eslint": "^7.1.1", "babel-plugin-external-helpers": "^6.22.0", @@ -51,6 +58,8 @@ "eslint-plugin-node": "^5.2.1", "eslint-plugin-promise": "^3.4.0", "eslint-plugin-standard": "^3.0.1", + "graphql": "^0.11.7", + "graphql-tag": "^2.5.0", "npm-watch": "^0.3.0", "rimraf": "^2.6.1", "rollup": "^0.50.0", @@ -59,6 +68,8 @@ "rollup-plugin-node-resolve": "^3.0.0", "rollup-plugin-replace": "^2.0.0", "rollup-plugin-uglify": "^2.0.1", - "uglify-es": "^3.1.6" + "typescript": "^2.6.2", + "uglify-es": "^3.1.6", + "vue": "^2.5.9" } } diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..f99e2796 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,4 @@ +import './vue' +import { VueApollo } from './vue-apollo'; + +export default VueApollo; \ No newline at end of file diff --git a/types/test/App.ts b/types/test/App.ts new file mode 100644 index 00000000..803fde54 --- /dev/null +++ b/types/test/App.ts @@ -0,0 +1,194 @@ +// this example src is https://github.com/Akryum/vue-apollo-example +import gql from 'graphql-tag'; +import Vue from 'vue'; +const pageSize = 10; +const SUB_QUERY = gql`subscription tags($type: String!) { + tagAdded(type: $type) { + id + label + type + } +}`; +export default Vue.extend({ + data () { + return { + newTag: null, + updateCount: 0, + type: 'City', + skipQuery: false, + loading: 0, + tagsLoading: 0, + tagsPageLoading: 0, + showTag: 'random', + showMoreEnabled: true, + page: 0, + _type: '' + } + }, + apollo: { + $client: 'a', + $loadingKey: 'loading', + tags() { + return { + query: gql`query tagList ($type: String!) { + tags(type: $type) { + id + label + } + }`, + // Reactive variables + variables () { + return { + type: this.type, + }; + }, + manual: true, + pollInterval: 300, + result (result) { + this.updateCount ++; + }, + skip () { + return this.skipQuery + }, + fetchPolicy: 'cache-and-network', + subscribeToMore: [{ + document: SUB_QUERY, + variables () { + return { type: this.type, } + }, + updateQuery: (previousResult, { subscriptionData }) => { + console.log('new tag', subscriptionData.data.tagAdded) + if (previousResult.tags.find((tag: any) => tag.id === subscriptionData.data.tagAdded.id)) { + return previousResult + } + return { + tags: [ + ...previousResult.tags, + subscriptionData.data.tagAdded, + ], + } + }, + }], + } + }, + randomTag: { + query () { + if (this.showTag === 'random') { + return gql`{ + randomTag { + id + label + type + } + }` + } else if (this.showTag === 'last') { + return gql`{ + randomTag: lastTag { + id + label + type + } + }` + } + }, + }, + tagsPage: { + // GraphQL Query + query: gql`query tagsPage ($page: Int!, $pageSize: Int!) { + tagsPage(page: $page, size: $pageSize) { + tags { + id + label + type + } + hasMore + } + }`, + variables: { + page: 0, + pageSize, + }, + }, + }, + methods: { + addTag() { + const newTag = this.newTag; + this.$apollo.mutate({ + mutation: gql`mutation ($type: String!, $label: String!) { + addTag(type: $type, label: $label) { + id + label + } + }`, + variables: { type: this.type, label: newTag, }, + updateQueries: { + tagList: (previousResult, { mutationResult }) => { + const { data } = mutationResult; + if (!data) { return previousResult } + if (previousResult.tags.find((tag: any) => tag.id === data.addTag.id)) { + return previousResult + } + return { tags: [ ...previousResult.tags, data.addTag ] }; + }, + }, + optimisticResponse: { + __typename: 'Mutation', + addTag: { + __typename: 'Tag', + id: -1, + label: newTag, + type: this.type, + }, + }, + }).then((data) => { + console.log(data); + }).catch((error) => { + console.error(error); + this.newTag = newTag; + }); + }, + showMore() { + this.page ++; + this.$apollo.queries.tagsPage.fetchMore({ + variables: { + page: this.page, + pageSize, + }, + // Mutate the previous result + updateQuery: (previousResult: any, result: { fetchMoreResult: any }) => { + const { fetchMoreResult } = result; + const newTags = fetchMoreResult.tagsPage.tags; + const hasMore = fetchMoreResult.tagsPage.hasMore; + this.showMoreEnabled = hasMore; + return { + tagsPage: { + __typename: previousResult.tagsPage.__typename, + tags: [ + ...previousResult.tagsPage.tags, + // Add the new tags + ...newTags, + ], + hasMore, + }, + }; + }, + }); + }, + refetchTags () { + this.$apollo.queries.tags.refetch() + }, + }, +mounted() { + const observer = this.$apollo.subscribe({ + query: SUB_QUERY, + variables: { + type: 'Companies', + }, + }); + observer.subscribe({ + next(data) { + console.log('this.$apollo.subscribe', data); + }, + }); + }, +}); \ No newline at end of file diff --git a/types/test/index.ts b/types/test/index.ts new file mode 100644 index 00000000..f179784c --- /dev/null +++ b/types/test/index.ts @@ -0,0 +1,19 @@ +import Vue from 'vue' + +import 'isomorphic-fetch' +import { ApolloClient } from 'apollo-client' +import { HttpLink } from 'apollo-link-http' +import { ApolloLink, split } from 'apollo-link' +import { getMainDefinition } from 'apollo-utilities' + +import VueApollo from '../index' +import App from './App' + +const httpLink = new HttpLink({ uri: 'https://dummy.test.com' }) +const cache: any = 'dummy cache'; +const apolloClient = new ApolloClient({ link: httpLink, cache, connectToDevTools: true }) +const apolloProvider = new VueApollo({ defaultClient: apolloClient }) + +Vue.use(VueApollo) + +new Vue({ el: '#app', apolloProvider, render: h => h(App), }) \ No newline at end of file diff --git a/types/test/tsconfig.json b/types/test/tsconfig.json new file mode 100644 index 00000000..9ebbc08a --- /dev/null +++ b/types/test/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "es5", + "es6", + "dom", + "es2015.core", + "es2015.collection", + "es2015.generator", + "es2015.iterable", + "es2015.promise", + "es2015.proxy", + "es2015.reflect", + "es2015.symbol", + "es2015.symbol.wellknown", + "esnext.asynciterable" + ], + "module": "es2015", + "moduleResolution": "node", + "experimentalDecorators": true, + "strict": true + }, + "include": [ + "*.ts", + "../*.d.ts" + ] +} diff --git a/types/vue-apollo.d.ts b/types/vue-apollo.d.ts new file mode 100644 index 00000000..2020ee0e --- /dev/null +++ b/types/vue-apollo.d.ts @@ -0,0 +1,77 @@ +import Vue, { PluginObject, PluginFunction } from 'vue'; +import { DocumentNode } from 'graphql'; +import { ApolloClient } from 'apollo-client'; +import { WatchQueryOptions, MutationOptions, SubscriptionOptions, SubscribeToMoreOptions, ObservableQuery, NetworkStatus } from 'apollo-client' +import { DataProxy } from 'apollo-cache'; +import { subscribe } from 'graphql/subscription/subscribe'; + +// include Omit type from https://github.com/Microsoft/TypeScript/issues/12215 +type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]; +type Omit = { [P in Diff]?: T[P] }; + +export class VueApollo implements PluginObject<{}> { + [key: string]: any; + install: PluginFunction<{}>; + constructor (options: {defaultClient: ApolloClient<{}>}); + static install(pVue: typeof Vue, options?:{} | undefined): void; +} + +type ApolloVueThisType = V & { [key: string]: any }; +type VariableFn = ((this: ApolloVueThisType) => Object) | Object; +type ApolloVueUpdateQueryFn = (this: ApolloVueThisType, previousQueryResult: { [key: string]: any }, options: { + error: any, + subscriptionData: { data: any; }; + variables?: { [key: string]: any; }; +}) => Object; + +interface ApolloVueSubscribeToMoreOptions { + document: DocumentNode; + variables?: VariableFn; + updateQuery?: ApolloVueUpdateQueryFn; + onError?: (error: Error) => void; +} + +type _WatchQueryOptions = Omit; // exclude query prop because it causes type incorrectly error +export interface VueApolloQueryOptions extends _WatchQueryOptions { + query: ((this: ApolloVueThisType) => DocumentNode) | DocumentNode; + variables?: VariableFn; + update?: (this: ApolloVueThisType, data: R) => any; + result?: (this: ApolloVueThisType, data: R, loader: any, netWorkStatus: NetworkStatus) => void; + error?: (this: ApolloVueThisType, error: any) => void; + loadingKey?: string; + watchLoading?: (isLoading: boolean, countModifier: number) => void; + skip?: (this: ApolloVueThisType) => boolean | boolean; + manual?: boolean; + subscribeToMore?: ApolloVueSubscribeToMoreOptions | ApolloVueSubscribeToMoreOptions[]; +} + +export interface VueApolloMutationOptions extends MutationOptions { + mutation: DocumentNode; + variables?: VariableFn; + optimisticResponse?: ((this: ApolloVueThisType) => any) | Object; +} + +export interface VueApolloSubscriptionOptions extends SubscriptionOptions { + query: DocumentNode; + variables?: VariableFn; + result?: (this: V, data: R) => void; +} + +type Query = (key: string, options: VueApolloQueryOptions) => void; +type Mutate = (params: VueApolloMutationOptions) => Promise; +type Subscribe = (params: SubscriptionOptions) => ObservableQuery; +export interface ApolloProperty { + [key: string]: Query | Mutate | Subscribe; // smart query + queries: any; + mutate: Mutate; + subscribe: Subscribe; +} +type QueryComponentProperty = ((this: ApolloVueThisType) => VueApolloQueryOptions) | VueApolloQueryOptions +type SubscribeComponentProperty = VueApolloSubscriptionOptions | { [key: string]: VueApolloSubscriptionOptions } + +export interface VueApolloComponentOption { + [key: string]: QueryComponentProperty | SubscribeComponentProperty | string | undefined; + $subscribe?: SubscribeComponentProperty; + $client?: string; + $loadingKey?: string; +} diff --git a/types/vue.d.ts b/types/vue.d.ts new file mode 100644 index 00000000..183a284a --- /dev/null +++ b/types/vue.d.ts @@ -0,0 +1,15 @@ +import Vue from "vue"; +import { VueApollo, VueApolloComponentOption, ApolloProperty } from './vue-apollo'; + +declare module "vue/types/options" { + interface ComponentOptions { + apolloProvider?: VueApollo; + apollo?: VueApolloComponentOption; + } +} + +declare module "vue/types/vue" { + interface Vue { + $apollo: ApolloProperty; + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 633855cb..e7839760 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,18 @@ # yarn lockfile v1 +"@types/async@2.0.45": + version "2.0.45" + resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.45.tgz#0cfe971d7ed5542695740338e0455c91078a0e83" + +"@types/graphql@^0.11.7": + version "0.11.7" + resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.11.7.tgz#da39a2f7c74e793e32e2bb7b3b68da1691532dd5" + +"@types/zen-observable@0.5.3", "@types/zen-observable@^0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.3.tgz#91b728599544efbb7386d8b6633693a3c2e7ade5" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -75,6 +87,54 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +apollo-cache-inmemory@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.1.1.tgz#1511f00eb845da88504abf867f408c3026a909ba" + dependencies: + apollo-cache "^1.0.1" + apollo-utilities "^1.0.2" + graphql-anywhere "^4.0.1" + +apollo-cache@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.0.1.tgz#66c16141173bc752d3ad3dce990310c10dfc4076" + dependencies: + apollo-utilities "^1.0.2" + +apollo-client@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.0.3.tgz#f99f32e2c851bbd52da1e1b113ce8f6a0cf94945" + dependencies: + "@types/zen-observable" "^0.5.3" + apollo-cache "^1.0.1" + apollo-link "^1.0.0" + apollo-link-dedup "^1.0.0" + apollo-utilities "^1.0.2" + symbol-observable "^1.0.2" + zen-observable "^0.6.0" + optionalDependencies: + "@types/async" "2.0.45" + +apollo-link-dedup@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.2.tgz#bab659dde41f8dd627839142d4dad90e55251110" + +apollo-link-http@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.2.0.tgz#48464c2ebfa6474f7a89908696827d66b2deb5cc" + +apollo-link@^1.0.0, apollo-link@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.0.3.tgz#759c36abeeb99e227eca45f919ee07fb8fee911e" + dependencies: + "@types/zen-observable" "0.5.3" + apollo-utilities "^1.0.0" + zen-observable "^0.6.0" + +apollo-utilities@^1.0.0, apollo-utilities@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.2.tgz#bcf348a7e613e82e2624ddb5be2b9f6bf1259c6d" + aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1530,6 +1590,22 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" +graphql-anywhere@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.0.1.tgz#eb53ed5c56ef42e21d34dc22951e3da38f88a342" + dependencies: + apollo-utilities "^1.0.2" + +graphql-tag@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.5.0.tgz#b43bfd8b5babcd2c205ad680c03e98b238934e0f" + +graphql@^0.11.7: + version "0.11.7" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.11.7.tgz#e5abaa9cb7b7cccb84e9f0836bf4370d268750c6" + dependencies: + iterall "1.1.3" + har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" @@ -1812,6 +1888,10 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" +iterall@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" + js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -2819,6 +2899,10 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" +symbol-observable@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32" + table@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" @@ -2926,6 +3010,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typescript@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + uglify-es@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.1.6.tgz#b0f818c055a7e9538abc2286e70c743f2938311f" @@ -3005,6 +3093,10 @@ vlq@^0.2.1: version "0.2.3" resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" +vue@^2.5.9: + version "2.5.9" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.9.tgz#b2380cd040915dca69881dafd121d760952e65f7" + which@^1.2.9: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" @@ -3056,3 +3148,7 @@ xtend@~4.0.1: yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +zen-observable@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.6.0.tgz#8a6157ed15348d185d948cfc4a59d90a2c0f70ee" From 230078abaf787cd0bd6b7fe8b731847bfe5aa5b9 Mon Sep 17 00:00:00 2001 From: joe-re Date: Fri, 22 Dec 2017 18:18:23 +0900 Subject: [PATCH 2/2] set defaultOptions key to VueApollo constructor --- types/vue-apollo.d.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/types/vue-apollo.d.ts b/types/vue-apollo.d.ts index 2020ee0e..87007eb6 100644 --- a/types/vue-apollo.d.ts +++ b/types/vue-apollo.d.ts @@ -9,10 +9,19 @@ import { subscribe } from 'graphql/subscription/subscribe'; type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]; type Omit = { [P in Diff]?: T[P] }; +type VueApolloOptions = { + $skip?: boolean, + $skipAllQueries?: boolean, + $skipAllSubscriptions?: boolean, + $client?: string, + $loadingKey?: string, + $error?: Function +} + export class VueApollo implements PluginObject<{}> { [key: string]: any; install: PluginFunction<{}>; - constructor (options: {defaultClient: ApolloClient<{}>}); + constructor (options: { defaultClient: ApolloClient<{}>, defaultOptions?: VueApolloOptions }); static install(pVue: typeof Vue, options?:{} | undefined): void; } @@ -69,9 +78,7 @@ export interface ApolloProperty { type QueryComponentProperty = ((this: ApolloVueThisType) => VueApolloQueryOptions) | VueApolloQueryOptions type SubscribeComponentProperty = VueApolloSubscriptionOptions | { [key: string]: VueApolloSubscriptionOptions } -export interface VueApolloComponentOption { - [key: string]: QueryComponentProperty | SubscribeComponentProperty | string | undefined; +export interface VueApolloComponentOption extends VueApolloOptions { + [key: string]: QueryComponentProperty | SubscribeComponentProperty | string | boolean | Function | undefined; $subscribe?: SubscribeComponentProperty; - $client?: string; - $loadingKey?: string; }