diff --git a/src/apollo-provider.js b/src/apollo-provider.js index 4f3ea62..45a6fd1 100644 --- a/src/apollo-provider.js +++ b/src/apollo-provider.js @@ -9,8 +9,6 @@ export class ApolloProvider { this.watchLoading = options.watchLoading this.errorHandler = options.errorHandler this.prefetch = options.prefetch - - this.prefetchQueries = [] } provide (key = '$apolloProvider') { diff --git a/src/dollar-apollo.js b/src/dollar-apollo.js index 76d1dc6..8fc8785 100644 --- a/src/dollar-apollo.js +++ b/src/dollar-apollo.js @@ -19,10 +19,6 @@ export class DollarApollo { return this.vm.$apolloProvider } - query (options) { - return this.getClient(options).query(options) - } - getClient (options = null) { if (!options || !options.client) { if (typeof this.client === 'object') { @@ -48,6 +44,10 @@ export class DollarApollo { return client } + query (options) { + return this.getClient(options).query(options) + } + watchQuery (options) { const observable = this.getClient(options).watchQuery(options) const _subscribe = observable.subscribe.bind(observable) diff --git a/types/apollo-provider.d.ts b/types/apollo-provider.d.ts index aded893..f5eb94f 100644 --- a/types/apollo-provider.d.ts +++ b/types/apollo-provider.d.ts @@ -1,22 +1,25 @@ /* eslint no-unused-vars: 0 */ import Vue, { AsyncComponent } from 'vue' -import { VueApolloComponentOption } from './options' import { ApolloClient } from 'apollo-client'; -import { WatchLoading, ErrorHandler, VueApolloOptions } from './options' +import { + VueApolloComponentOptions, + WatchLoading, + ErrorHandler +} from './options' -export type VueApolloComponent = VueApolloComponentOption | typeof Vue | AsyncComponent; +export type VueApolloComponent = VueApolloComponentOptions | typeof Vue | AsyncComponent; export class ApolloProvider { provide: (key?: string) => this constructor (options: { defaultClient: ApolloClient, - defaultOptions?: VueApolloOptions, + defaultOptions?: VueApolloComponentOptions, clients?: { [key: string]: ApolloClient }, - watchLoading?: WatchLoading, - errorHandler?: ErrorHandler + watchLoading?: WatchLoading, + errorHandler?: ErrorHandler, + prefetch?: boolean }) clients: { [key: string]: ApolloClient } defaultClient: ApolloClient - prefetchQueries: any } diff --git a/types/options.d.ts b/types/options.d.ts index a923edb..29cdec1 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -1,94 +1,86 @@ +import Vue from 'vue' import { WatchQueryOptions, + OperationVariables, MutationOptions, SubscriptionOptions, SubscribeToMoreOptions, ObservableQuery, NetworkStatus, ApolloQueryResult, + ApolloError } from 'apollo-client'; import { FetchResult } from 'apollo-link'; import { ServerError, ServerParseError } from 'apollo-link-http-common'; import { DocumentNode, GraphQLError } from 'graphql'; -// include Omit type from https://github.com/Microsoft/TypeScript/issues/12215 -type Property = string | number | symbol; -type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]; -type Omit = { [P in Diff]?: T[P] }; +/* Component options */ -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; +export interface AllVueApolloComponentSpecialOptions { + $skip: boolean + $skipAllQueries: boolean + $skipAllSubscriptions: boolean + $deep: boolean + $client: string + $loadingKey: string + $watchLoading: WatchLoading + $error: ErrorHandler + $query: Partial> + $subscribe: VueApolloSubscriptionProperty } -export type WatchLoading = (this: ApolloVueThisType, isLoading: boolean, countModifier: number) => void +export type VueApolloComponentSpecialOptions = + Partial> -export interface ErrorResponse { - graphQLErrors?: ReadonlyArray; - networkError?: Error | ServerError | ServerParseError; -} -export type ErrorHandler = (this: ApolloVueThisType, error: ErrorResponse) => void - -type _WatchQueryOptions = Omit; // exclude query prop because it causes type incorrectly error - -interface ExtendableVueApolloQueryOptions extends _WatchQueryOptions { - update?: (this: ApolloVueThisType, data: R) => any; - result?: (this: ApolloVueThisType, data: ApolloQueryResult, loader: any, netWorkStatus: NetworkStatus) => void; - error?: ErrorHandler; - loadingKey?: string; - watchLoading?: WatchLoading; - skip?: ((this: ApolloVueThisType) => boolean) | boolean; - manual?: boolean; - subscribeToMore?: ApolloVueSubscribeToMoreOptions | ApolloVueSubscribeToMoreOptions[]; - prefetch?: ((context: any) => any) | boolean; - deep?: boolean; -} -export interface VueApolloQueryOptions extends ExtendableVueApolloQueryOptions { - query: ((this: ApolloVueThisType) => DocumentNode) | DocumentNode; - variables?: VariableFn; - client?: String +export interface VueApolloComponentOptions + extends VueApolloComponentSpecialOptions { + [key: string] : VueApolloQueryProperty | + VueApolloComponentSpecialOptions[keyof VueApolloComponentSpecialOptions] } -export interface VueApolloMutationOptions extends MutationOptions { - mutation: DocumentNode; - variables?: VariableFn; - optimisticResponse?: ((this: ApolloVueThisType) => R) | R; - client?: String +/* Special component options */ + +export type WatchLoading = (isLoading: boolean, countModifier: number) => void +export type ErrorHandler = (error: ApolloError) => void + +/* Query */ + +type QueryVariables = (() => OperationVariables) | OperationVariables; + +export type VueApolloQueryProperty = + DocumentNode | + VueApolloQueryDefinition | + (() => VueApolloQueryDefinition | null) + +// exclude query prop because it causes type incorrectly error +type WatchQueryOptionsWithoutQuery = Omit; + +export interface VueApolloQueryDefinition extends WatchQueryOptionsWithoutQuery { + query: DocumentNode | (() => DocumentNode | null) + variables?: QueryVariables + update?: (data: R) => any + result?: (result: ApolloQueryResult, key: string) => void + error?: ErrorHandler + manual?: boolean + loadingKey?: string + watchLoading?: WatchLoading + skip?: (() => boolean) | boolean + prefetch?: ((context: any) => any) | boolean + client?: string + deep?: boolean + subscribeToMore?: VueApolloSubscribeToMoreOptions | (VueApolloSubscribeToMoreOptions & ThisType)[] } -export interface VueApolloSubscriptionOptions extends SubscriptionOptions { - query: DocumentNode; - variables?: VariableFn; - skip?: (this: ApolloVueThisType) => boolean | boolean; - result?: (this: V, data: FetchResult) => void; +/* Subscriptions */ + +interface VueApolloSubscribeToMoreOptions extends SubscribeToMoreOptions { + variables?: QueryVariables; } -type QueryComponentProperty = ((this: ApolloVueThisType) => VueApolloQueryOptions) | VueApolloQueryOptions -type SubscribeComponentProperty = VueApolloSubscriptionOptions | { [key: string]: VueApolloSubscriptionOptions } - -export type VueApolloOptions = { - $skip?: boolean, - $skipAllQueries?: boolean, - $skipAllSubscriptions?: boolean, - $deep?: boolean, - $client?: string, - $loadingKey?: string, - $watchLoading?: WatchLoading, - $error?: ErrorHandler, - $query?: ExtendableVueApolloQueryOptions +interface VueApolloSubscriptionDefinition extends SubscriptionOptions { + variables?: QueryVariables } -export interface VueApolloComponentOption extends VueApolloOptions { - [key: string]: QueryComponentProperty | SubscribeComponentProperty | ExtendableVueApolloQueryOptions | string | boolean | Function | undefined; - $subscribe?: SubscribeComponentProperty; -} +export type VueApolloSubscriptionProperty = + VueApolloSubscriptionDefinition | + { [key: string]: VueApolloSubscriptionDefinition } diff --git a/types/test/App.ts b/types/test/App.ts index b6a9bf3..c02d38f 100644 --- a/types/test/App.ts +++ b/types/test/App.ts @@ -1,14 +1,177 @@ // this example src is https://github.com/Akryum/vue-apollo-example -import gql from 'graphql-tag'; -import Vue from 'vue'; -const pageSize = 10; +import gql from 'graphql-tag' +import Vue from 'vue' +import { OperationVariables } from 'apollo-client' +import { VueApolloQueryDefinition } from '../options' +import { DocumentNode } from 'graphql' + +const pageSize = 10 + const SUB_QUERY = gql`subscription tags($type: String!) { tagAdded(type: $type) { id label type } -}`; +}` + +export const hey = Vue.extend({ + props: { + meow: String, + }, + + data () { + return { + waf: 'waf', + loading: 0 + } + }, + + apollo: { + $client: 'foo', + $query: { + fetchPolicy: 'cache-only' + }, + foo: gql`query`, + message: { + query: gql`query`, + // https://vuejs.org/v2/guide/typescript.html#Annotating-Return-Types + variables (): OperationVariables { + this.hello.toUpperCase() + this.meow + return { + hello: this.hello.toUpperCase() + } + }, + update: data => data.foo.bar, + result (result, key) { + this.meow + console.log(this.hello.toUpperCase()) + }, + error (error) { + console.error(error.graphQLErrors, error.networkError) + }, + manual: false, + loadingKey: 'loading', + watchLoading (isLoading, countModifier) { + this.loading += countModifier + if (isLoading) { + console.log('isLoading') + } + }, + // https://vuejs.org/v2/guide/typescript.html#Annotating-Return-Types + skip (): boolean { + return this.meow === 'meow' + }, + prefetch: true, + client: 'api2', + deep: true, + /* Apollo options */ + fetchPolicy: 'cache-only', + errorPolicy: 'all', + returnPartialData: true, + /* Subscriptions */ + subscribeToMore: { + document: gql`subscription`, + // https://vuejs.org/v2/guide/typescript.html#Annotating-Return-Types + variables (): OperationVariables { + return { + foo: this.hello, + } + }, + updateQuery (previousResult, options) { + return { + ...previousResult, + foo: options.subscriptionData.data.foo + } + } + } + }, + + testMultiSubs: { + query: gql`query`, + subscribeToMore: [ + { + document: gql`subscription`, + variables: { + foo: 'bar' + }, + updateQuery (previousResult, options) { + return { + ...previousResult, + foo: options.subscriptionData.data.foo + } + } + }, + { + document: gql`subscription`, + // https://vuejs.org/v2/guide/typescript.html#Annotating-Return-Types + variables (): OperationVariables { + return { + foo: this.hello, + } + }, + updateQuery (previousResult, options) { + return { + ...previousResult, + foo: options.subscriptionData.data.foo + } + } + } + ], + }, + + // https://vuejs.org/v2/guide/typescript.html#Annotating-Return-Types + tags (): VueApolloQueryDefinition { + this.hello.toUpperCase() + this.meow + return { + query: gql`query`, + // https://vuejs.org/v2/guide/typescript.html#Annotating-Return-Types + variables: (): OperationVariables => { + this.hello.toUpperCase() + this.meow + return { + hello: this.hello.toUpperCase() + } + }, + result: () => { + console.log(this.hello.toUpperCase()) + } + } + }, + + $subscribe: { + tagAdded: { + query: gql`subscription`, + variables (): OperationVariables { + return { + foo: this.meow + } + } + } + } + }, + + computed: { + // https://vuejs.org/v2/guide/typescript.html#Annotating-Return-Types + hello (): string { + return this.waf === 'waf' ? 'waf waf' : 'hello' + } + }, + + created () { + this.$apollo.mutate({ + mutation: gql`mutation {}`, + variables: { + hello: this.hello + }, + }) + this.hello.toUpperCase() + this.$apollo.vm.hello.toUpperCase() + } +}) + export default Vue.extend({ data () { return { @@ -31,7 +194,7 @@ export default Vue.extend({ loadingKey: 'loading', fetchPolicy: 'cache-first' }, - tags() { + tags(): VueApolloQueryDefinition { return { query: gql`query tagList ($type: String!) { tags(type: $type) { @@ -40,24 +203,26 @@ export default Vue.extend({ } }`, // Reactive variables - variables () { + variables: (): OperationVariables => { return { type: this.type, - }; + } }, manual: true, pollInterval: 300, - result (result) { - this.updateCount ++; + result: (result) => { + this.updateCount ++ }, - skip () { + skip: (): boolean => { return this.skipQuery }, fetchPolicy: 'cache-and-network', subscribeToMore: [{ document: SUB_QUERY, - variables () { - return { type: this.type, } + variables: (): OperationVariables => { + return { + type: this.type, + } }, updateQuery: (previousResult, { subscriptionData }) => { console.log('new tag', subscriptionData.data.tagAdded) @@ -75,7 +240,7 @@ export default Vue.extend({ } }, randomTag: { - query () { + query (): DocumentNode | null { if (this.showTag === 'random') { return gql`{ randomTag { @@ -93,6 +258,7 @@ export default Vue.extend({ } }` } + return null }, }, tagsPage: { @@ -115,7 +281,7 @@ export default Vue.extend({ }, methods: { addTag() { - const newTag = this.newTag; + const newTag = this.newTag this.$apollo.mutate({ mutation: gql`mutation ($type: String!, $label: String!) { addTag(type: $type, label: $label) { @@ -126,12 +292,12 @@ export default Vue.extend({ variables: { type: this.type, label: newTag, }, updateQueries: { tagList: (previousResult, { mutationResult }) => { - const { data } = 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 ] }; + return { tags: [ ...previousResult.tags, data.addTag ] } }, }, optimisticResponse: { @@ -144,14 +310,14 @@ export default Vue.extend({ }, }, }).then((data) => { - console.log(data); + console.log(data) }).catch((error) => { - console.error(error); - this.newTag = newTag; - }); + console.error(error) + this.newTag = newTag + }) }, showMore() { - this.page ++; + this.page ++ this.$apollo.queries.tagsPage.fetchMore({ variables: { page: this.page, @@ -159,10 +325,10 @@ export default Vue.extend({ }, // 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; + const { fetchMoreResult } = result + const newTags = fetchMoreResult.tagsPage.tags + const hasMore = fetchMoreResult.tagsPage.hasMore + this.showMoreEnabled = hasMore return { tagsPage: { __typename: previousResult.tagsPage.__typename, @@ -173,9 +339,9 @@ export default Vue.extend({ ], hasMore, }, - }; + } }, - }); + }) }, refetchTags () { this.$apollo.queries.tags.refetch() @@ -187,14 +353,14 @@ export default Vue.extend({ variables: { type: 'Companies', }, - }); + }) observer.subscribe({ next(data) { - console.log('this.$apollo.subscribe', data); + console.log('this.$apollo.subscribe', data) }, - }); + }) // enable to specify client when execute request this.$apollo.query({ query: gql`query mockQuery { id }`, client: 'test' }) }, -}); \ No newline at end of file +}) diff --git a/types/test/tsconfig.json b/types/test/tsconfig.json index 9ebbc08..f94c656 100644 --- a/types/test/tsconfig.json +++ b/types/test/tsconfig.json @@ -19,7 +19,8 @@ "module": "es2015", "moduleResolution": "node", "experimentalDecorators": true, - "strict": true + "strict": true, + "noImplicitAny": true }, "include": [ "*.ts", diff --git a/types/vue-apollo.d.ts b/types/vue-apollo.d.ts index f805ee8..2279827 100644 --- a/types/vue-apollo.d.ts +++ b/types/vue-apollo.d.ts @@ -1,65 +1,80 @@ -import Vue, { PluginObject, PluginFunction } from 'vue'; -import { ApolloClient, ObservableQuery, ApolloQueryResult } from 'apollo-client'; -import { FetchResult } from 'apollo-link'; -import { Observable } from 'apollo-client/util/Observable'; +import Vue, { PluginObject, PluginFunction } from 'vue' +import { + ApolloClient, + ObservableQuery, + ApolloQueryResult, + QueryOptions, + WatchQueryOptions, + MutationOptions, + SubscriptionOptions, +} from 'apollo-client' +import { FetchResult } from 'apollo-link' +import { Observable } from 'apollo-client/util/Observable' import { ApolloProvider, VueApolloComponent } from './apollo-provider' import { - VueApolloQueryOptions, - VueApolloMutationOptions, - VueApolloSubscriptionOptions, - VueApolloOptions, + VueApolloQueryDefinition, + VueApolloSubscriptionDefinition, WatchLoading, ErrorHandler, -} from './options'; -import { GraphQLError } from 'graphql'; +} from './options' +import { GraphQLError } from 'graphql' export class VueApollo extends ApolloProvider implements PluginObject<{}>{ - [key: string]: any; - install: PluginFunction<{}>; + [key: string]: any + install: PluginFunction<{}> - static install(pVue: typeof Vue, options?:{} | undefined): void; + static install(pVue: typeof Vue, options?:{} | undefined): void } interface SmartApollo { - skip: boolean; - refresh(): void; - start(): void; - stop(): void; + skip: boolean + refresh(): void + start(): void + stop(): void } -export interface SmartQuery extends SmartApollo { - loading: boolean; - fetchMore: ObservableQuery['fetchMore']; - subscribeToMore: ObservableQuery['subscribeToMore']; - refetch: ObservableQuery['refetch']; - setVariables: ObservableQuery['setVariables']; - setOptions: ObservableQuery['setOptions']; - startPolling: ObservableQuery['startPolling']; - stopPolling: ObservableQuery['stopPolling']; +type PickedObservableQuery = Pick + +export interface SmartQuery extends SmartApollo, PickedObservableQuery { + loading: boolean } export interface SmartSubscription extends SmartApollo { } -export interface DollarApollo { - vm: V; - queries: Record>; - subscriptions: Record>; - readonly provider: ApolloProvider; - readonly loading: boolean; - - // writeonly not yet implemented in TypeScript: https://github.com/Microsoft/TypeScript/issues/21759 - /* writeonly */ skipAllQueries: boolean; - /* writeonly */ skipAllSubscriptions: boolean; - /* writeonly */ skipAll: boolean; - - getClient(): ApolloClient; - query(options: VueApolloQueryOptions): Promise>; - mutate(options: VueApolloMutationOptions): Promise>; - subscribe(options: VueApolloSubscriptionOptions): Observable>; - - addSmartQuery(key: string, options: VueApolloQueryOptions): SmartQuery; - addSmartSubscription(key: string, options: VueApolloSubscriptionOptions): SmartSubscription; +interface ClientOptions { + client?: string } -export function willPrefetch (component: VueApolloComponent, contextCallback?: boolean): VueApolloComponent +interface ApolloClientMethods { + query: (options: QueryOptions & ClientOptions) => ReturnType['query']> + watchQuery: (options: WatchQueryOptions & ClientOptions) => ReturnType['watchQuery']> + mutate: (options: MutationOptions & ClientOptions) => ReturnType['mutate']> + subscribe: (options: SubscriptionOptions & ClientOptions) => ReturnType['subscribe']> +} + +export interface DollarApollo extends ApolloClientMethods { + vm: V + queries: Record> + subscriptions: Record> + readonly provider: ApolloProvider + readonly loading: boolean + + // writeonly not yet implemented in TypeScript: https://github.com/Microsoft/TypeScript/issues/21759 + /* writeonly */ skipAllQueries: boolean + /* writeonly */ skipAllSubscriptions: boolean + /* writeonly */ skipAll: boolean + + getClient(): ApolloClient + + addSmartQuery(key: string, options: VueApolloQueryDefinition): SmartQuery + addSmartSubscription(key: string, options: VueApolloSubscriptionDefinition): SmartSubscription +} diff --git a/types/vue.d.ts b/types/vue.d.ts index 1ccbf4a..e563e99 100644 --- a/types/vue.d.ts +++ b/types/vue.d.ts @@ -1,12 +1,18 @@ import Vue from 'vue' +import { CombinedVueInstance } from 'vue/types/vue' import { DollarApollo } from './vue-apollo' -import { VueApolloComponentOption } from './options' -import { ApolloProvider } from './apollo-provider'; +import { VueApolloComponentOptions } from './options' +import { ApolloProvider } from './apollo-provider' declare module 'vue/types/options' { - interface ComponentOptions { + + interface ComponentOptions { apolloProvider?: ApolloProvider - apollo?: VueApolloComponentOption + apollo?: VueApolloComponentOptions< + Data extends DataDef + ? CombinedVueInstance + : CombinedVueInstance + > } }