refactor(types): rewritten most types

This commit is contained in:
Guillaume Chau
2019-09-12 19:58:42 +02:00
parent 268d4a56f8
commit ae2e2a30d7
8 changed files with 345 additions and 164 deletions
-2
View File
@@ -9,8 +9,6 @@ export class ApolloProvider {
this.watchLoading = options.watchLoading
this.errorHandler = options.errorHandler
this.prefetch = options.prefetch
this.prefetchQueries = []
}
provide (key = '$apolloProvider') {
+4 -4
View File
@@ -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)
+10 -7
View File
@@ -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<V extends Vue = Vue> = VueApolloComponentOption<V> | typeof Vue | AsyncComponent;
export type VueApolloComponent<V extends Vue = Vue> = VueApolloComponentOptions<V> | typeof Vue | AsyncComponent;
export class ApolloProvider<TCacheShape=any> {
provide: (key?: string) => this
constructor (options: {
defaultClient: ApolloClient<TCacheShape>,
defaultOptions?: VueApolloOptions<any>,
defaultOptions?: VueApolloComponentOptions<Vue>,
clients?: { [key: string]: ApolloClient<TCacheShape> },
watchLoading?: WatchLoading<any>,
errorHandler?: ErrorHandler<any>
watchLoading?: WatchLoading,
errorHandler?: ErrorHandler,
prefetch?: boolean
})
clients: { [key: string]: ApolloClient<TCacheShape> }
defaultClient: ApolloClient<TCacheShape>
prefetchQueries: any
}
+61 -69
View File
@@ -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<T extends Property, U extends Property> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]?: T[P] };
/* Component options */
type ApolloVueThisType<V> = V & { [key: string]: any };
type VariableFn<V> = ((this: ApolloVueThisType<V>) => Object) | Object;
type ApolloVueUpdateQueryFn<V> = (this: ApolloVueThisType<V>, previousQueryResult: { [key: string]: any }, options: {
error: any,
subscriptionData: { data: any; };
variables?: { [key: string]: any; };
}) => Object;
interface ApolloVueSubscribeToMoreOptions<V> {
document: DocumentNode;
variables?: VariableFn<V>;
updateQuery?: ApolloVueUpdateQueryFn<V>;
onError?: (error: Error) => void;
export interface AllVueApolloComponentSpecialOptions<Instance> {
$skip: boolean
$skipAllQueries: boolean
$skipAllSubscriptions: boolean
$deep: boolean
$client: string
$loadingKey: string
$watchLoading: WatchLoading
$error: ErrorHandler
$query: Partial<VueApolloQueryDefinition<Instance>>
$subscribe: VueApolloSubscriptionProperty
}
export type WatchLoading<V> = (this: ApolloVueThisType<V>, isLoading: boolean, countModifier: number) => void
export type VueApolloComponentSpecialOptions<Instance> =
Partial<AllVueApolloComponentSpecialOptions<Instance>>
export interface ErrorResponse {
graphQLErrors?: ReadonlyArray<GraphQLError>;
networkError?: Error | ServerError | ServerParseError;
}
export type ErrorHandler<V> = (this: ApolloVueThisType<V>, error: ErrorResponse) => void
type _WatchQueryOptions = Omit<WatchQueryOptions, 'query'>; // exclude query prop because it causes type incorrectly error
interface ExtendableVueApolloQueryOptions<V, R> extends _WatchQueryOptions {
update?: (this: ApolloVueThisType<V>, data: R) => any;
result?: (this: ApolloVueThisType<V>, data: ApolloQueryResult<R>, loader: any, netWorkStatus: NetworkStatus) => void;
error?: ErrorHandler<V>;
loadingKey?: string;
watchLoading?: WatchLoading<V>;
skip?: ((this: ApolloVueThisType<V>) => boolean) | boolean;
manual?: boolean;
subscribeToMore?: ApolloVueSubscribeToMoreOptions<V> | ApolloVueSubscribeToMoreOptions<V>[];
prefetch?: ((context: any) => any) | boolean;
deep?: boolean;
}
export interface VueApolloQueryOptions<V, R> extends ExtendableVueApolloQueryOptions<V, R> {
query: ((this: ApolloVueThisType<V>) => DocumentNode) | DocumentNode;
variables?: VariableFn<V>;
client?: String
export interface VueApolloComponentOptions<Instance>
extends VueApolloComponentSpecialOptions<Instance> {
[key: string] : VueApolloQueryProperty<Instance> |
VueApolloComponentSpecialOptions<Instance>[keyof VueApolloComponentSpecialOptions<Instance>]
}
export interface VueApolloMutationOptions<V, R> extends MutationOptions<R> {
mutation: DocumentNode;
variables?: VariableFn<V>;
optimisticResponse?: ((this: ApolloVueThisType<V>) => 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<Instance> =
DocumentNode |
VueApolloQueryDefinition<Instance> |
(() => VueApolloQueryDefinition<Instance> | null)
// exclude query prop because it causes type incorrectly error
type WatchQueryOptionsWithoutQuery = Omit<WatchQueryOptions, 'query'>;
export interface VueApolloQueryDefinition<Instance = Vue, R = any> extends WatchQueryOptionsWithoutQuery {
query: DocumentNode | (() => DocumentNode | null)
variables?: QueryVariables
update?: (data: R) => any
result?: (result: ApolloQueryResult<R>, 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<Instance>)[]
}
export interface VueApolloSubscriptionOptions<V, R> extends SubscriptionOptions {
query: DocumentNode;
variables?: VariableFn<V>;
skip?: (this: ApolloVueThisType<V>) => boolean | boolean;
result?: (this: V, data: FetchResult<R>) => void;
/* Subscriptions */
interface VueApolloSubscribeToMoreOptions extends SubscribeToMoreOptions {
variables?: QueryVariables;
}
type QueryComponentProperty<V> = ((this: ApolloVueThisType<V>) => VueApolloQueryOptions<V, any>) | VueApolloQueryOptions<V, any>
type SubscribeComponentProperty<V> = VueApolloSubscriptionOptions<V, any> | { [key: string]: VueApolloSubscriptionOptions<V, any> }
export type VueApolloOptions<V> = {
$skip?: boolean,
$skipAllQueries?: boolean,
$skipAllSubscriptions?: boolean,
$deep?: boolean,
$client?: string,
$loadingKey?: string,
$watchLoading?: WatchLoading<V>,
$error?: ErrorHandler<V>,
$query?: ExtendableVueApolloQueryOptions<V, any>
interface VueApolloSubscriptionDefinition extends SubscriptionOptions {
variables?: QueryVariables
}
export interface VueApolloComponentOption<V> extends VueApolloOptions<V> {
[key: string]: QueryComponentProperty<V> | SubscribeComponentProperty<V> | ExtendableVueApolloQueryOptions<V, any> | string | boolean | Function | undefined;
$subscribe?: SubscribeComponentProperty<V>;
}
export type VueApolloSubscriptionProperty =
VueApolloSubscriptionDefinition |
{ [key: string]: VueApolloSubscriptionDefinition }
+197 -31
View File
@@ -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' })
},
});
})
+2 -1
View File
@@ -19,7 +19,8 @@
"module": "es2015",
"moduleResolution": "node",
"experimentalDecorators": true,
"strict": true
"strict": true,
"noImplicitAny": true
},
"include": [
"*.ts",
+61 -46
View File
@@ -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<V> {
skip: boolean;
refresh(): void;
start(): void;
stop(): void;
skip: boolean
refresh(): void
start(): void
stop(): void
}
export interface SmartQuery<V> extends SmartApollo<V> {
loading: boolean;
fetchMore: ObservableQuery<any>['fetchMore'];
subscribeToMore: ObservableQuery<any>['subscribeToMore'];
refetch: ObservableQuery<any>['refetch'];
setVariables: ObservableQuery<any>['setVariables'];
setOptions: ObservableQuery<any>['setOptions'];
startPolling: ObservableQuery<any>['startPolling'];
stopPolling: ObservableQuery<any>['stopPolling'];
type PickedObservableQuery = Pick<ObservableQuery,
'fetchMore' |
'subscribeToMore' |
'refetch' |
'setVariables' |
'setOptions' |
'startPolling' |
'stopPolling'
>
export interface SmartQuery<V> extends SmartApollo<V>, PickedObservableQuery {
loading: boolean
}
export interface SmartSubscription<V> extends SmartApollo<V> {
}
export interface DollarApollo<V> {
vm: V;
queries: Record<string, SmartQuery<V>>;
subscriptions: Record<string, SmartSubscription<V>>;
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<R=any>(): ApolloClient<R>;
query<R=any>(options: VueApolloQueryOptions<V, R>): Promise<ApolloQueryResult<R>>;
mutate<R=any>(options: VueApolloMutationOptions<V, R>): Promise<FetchResult<R>>;
subscribe<R=any>(options: VueApolloSubscriptionOptions<V, R>): Observable<FetchResult<R>>;
addSmartQuery<R=any>(key: string, options: VueApolloQueryOptions<V, R>): SmartQuery<V>;
addSmartSubscription<R=any>(key: string, options: VueApolloSubscriptionOptions<V, R>): SmartSubscription<V>;
interface ClientOptions {
client?: string
}
export function willPrefetch (component: VueApolloComponent, contextCallback?: boolean): VueApolloComponent
interface ApolloClientMethods<R> {
query: (options: QueryOptions & ClientOptions) => ReturnType<ApolloClient<R>['query']>
watchQuery: (options: WatchQueryOptions & ClientOptions) => ReturnType<ApolloClient<R>['watchQuery']>
mutate: (options: MutationOptions & ClientOptions) => ReturnType<ApolloClient<R>['mutate']>
subscribe: (options: SubscriptionOptions & ClientOptions) => ReturnType<ApolloClient<R>['subscribe']>
}
export interface DollarApollo<V, R = any> extends ApolloClientMethods<R> {
vm: V
queries: Record<string, SmartQuery<V>>
subscriptions: Record<string, SmartSubscription<V>>
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<R = any>(): ApolloClient<R>
addSmartQuery<R = any>(key: string, options: VueApolloQueryDefinition<V, R>): SmartQuery<V>
addSmartSubscription<R = any>(key: string, options: VueApolloSubscriptionDefinition): SmartSubscription<V>
}
+10 -4
View File
@@ -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<V extends Vue> {
interface ComponentOptions<V extends Vue, Data, Methods, Computed, PropsDef, Props> {
apolloProvider?: ApolloProvider
apollo?: VueApolloComponentOption<V>
apollo?: VueApolloComponentOptions<
Data extends DataDef<infer D, any, any>
? CombinedVueInstance<V, D, Methods, Computed, Props>
: CombinedVueInstance<V, Data, Methods, Computed, Props>
>
}
}