Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4 alpha: Add full test coverage for TypeScript hook types & fix missing overloads #895

Merged
merged 4 commits into from
Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/vue-apollo-composable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"scripts": {
"dev": "yarn build --watch",
"build": "tsc --outDir dist -d",
"prepublishOnly": "yarn build"
"prepublishOnly": "yarn test && yarn build",
"test": "yarn test:types",
"test:types": "tsc -p tests/types/"
},
"dependencies": {
"throttle-debounce": "^2.1.0"
Expand Down
7 changes: 6 additions & 1 deletion packages/vue-apollo-composable/src/useApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import ApolloClient from 'apollo-client'
export const DefaultApolloClient = Symbol('default-apollo-client')
export const ApolloClients = Symbol('apollo-clients')

export function useApolloClient<TCacheShape = any> (clientId: string = null) {
export interface UseApolloClientReturn<TCacheShape> {
resolveClient: (clientId?: string) => ApolloClient<TCacheShape>
readonly client: ApolloClient<TCacheShape>
}

export function useApolloClient<TCacheShape = any> (clientId?: string): UseApolloClientReturn<TCacheShape> {
const providedApolloClients: { [key: string]: ApolloClient<TCacheShape> } = inject(ApolloClients, null)
const providedApolloClient: ApolloClient<TCacheShape> = inject(DefaultApolloClient, null)

Expand Down
65 changes: 61 additions & 4 deletions packages/vue-apollo-composable/src/useMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,77 @@ import { ReactiveFunction } from './util/ReactiveFunction'
import { useEventHook } from './util/useEventHook'
import { trackMutation } from './util/loadingTracking'

/**
* `useMutation` options for mutations that don't require `variables`.
*/
export interface UseMutationOptions<
TResult = any,
TVariables = OperationVariables
> extends Omit<MutationOptions<TResult, TVariables>, 'mutation'> {
clientId?: string
}

export function useMutation<
/**
* `useMutation` options for mutations that don't use variables.
*/
export type UseMutationOptionsNoVariables<
TResult = any,
TVariables = OperationVariables
> = Omit<UseMutationOptions<TResult, TVariables>, 'variables'>

/**
* `useMutation` options for mutations require variables.
*/
export interface UseMutationOptionsWithVariables<
TResult = any,
TVariables = OperationVariables
> extends UseMutationOptions<TResult, TVariables> {
variables: TVariables
}

export interface UseMutationReturn<TResult, TVariables> {
mutate: (variables?: TVariables, overrideOptions?: Pick<UseMutationOptions<any, OperationVariables>, 'update' | 'optimisticResponse' | 'context' | 'updateQueries' | 'refetchQueries' | 'awaitRefetchQueries' | 'errorPolicy' | 'fetchPolicy' | 'clientId'>) => Promise<FetchResult<any, Record<string, any>, Record<string, any>>>
loading: Ref<boolean>
error: Ref<Error>
called: Ref<boolean>
onDone: (fn: (param?: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => {
off: () => void
};
onError: (fn: (param?: Error) => void) => {
off: () => void
};
};

/**
* Use a mutation that does not require variables or options.
* */
export function useMutation<TResult = any>(
document: DocumentNode | ReactiveFunction<DocumentNode>
): UseMutationReturn<TResult, undefined>

/**
* Use a mutation that does not require variables.
*/
export function useMutation<TResult = any>(
document: DocumentNode | ReactiveFunction<DocumentNode>,
options: UseMutationOptionsNoVariables<TResult, undefined> | ReactiveFunction<UseMutationOptionsNoVariables<TResult, undefined>>
): UseMutationReturn<TResult, undefined>

/**
* Use a mutation that requires variables.
*/
export function useMutation<TResult = any, TVariables extends OperationVariables = OperationVariables>(
document: DocumentNode | ReactiveFunction<DocumentNode>,
options: UseMutationOptionsWithVariables<TResult, TVariables> | ReactiveFunction<UseMutationOptionsWithVariables<TResult, TVariables>>
): UseMutationReturn<TResult, TVariables>

export function useMutation<
TResult,
TVariables extends OperationVariables
> (
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
options: UseMutationOptions<TResult, TVariables> | Ref<UseMutationOptions<TResult, TVariables>> | ReactiveFunction<UseMutationOptions<TResult, TVariables>> = null,
) {
options?: UseMutationOptions<TResult, TVariables> | Ref<UseMutationOptions<TResult, TVariables>> | ReactiveFunction<UseMutationOptions<TResult, TVariables>>,
): UseMutationReturn<TResult, TVariables> {
if (!options) options = {}

const loading = ref<boolean>(false)
Expand All @@ -34,7 +91,7 @@ export function useMutation<
// Apollo Client
const { resolveClient } = useApolloClient()

async function mutate (variables: TVariables = null, overrideOptions: Omit<UseMutationOptions, 'variables'> = {}) {
async function mutate (variables?: TVariables, overrideOptions: Omit<UseMutationOptions, 'variables'> = {}) {
let currentDocument: DocumentNode
if (typeof document === 'function') {
currentDocument = document()
Expand Down
67 changes: 61 additions & 6 deletions packages/vue-apollo-composable/src/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,70 @@ interface SubscribeToMoreItem {
unsubscribeFns: Function[]
}

export interface UseQueryReturn<TResult, TVariables> {
result: Ref<TResult>
loading: Ref<boolean>
networkStatus: Ref<number>
error: Ref<Error>
start: () => void
stop: () => void
restart: () => void
document: Ref<DocumentNode>
variables: Ref<TVariables>
options: UseQueryOptions<TResult, TVariables> | Ref<UseQueryOptions<TResult, TVariables>>
query: Ref<ObservableQuery<TResult, TVariables>>
refetch: (variables?: TVariables) => Promise<ApolloQueryResult<TResult>>
fetchMore: <K extends keyof TVariables>(options: FetchMoreQueryOptions<TVariables, K> & FetchMoreOptions<TResult, TVariables>) => Promise<ApolloQueryResult<TResult>>
subscribeToMore: <TSubscriptionVariables = OperationVariables, TSubscriptionData = TResult>(options: SubscribeToMoreOptions<TResult, TSubscriptionVariables, TSubscriptionData> | Ref<SubscribeToMoreOptions<TResult, TSubscriptionVariables, TSubscriptionData>> | ReactiveFunction<SubscribeToMoreOptions<TResult, TSubscriptionVariables, TSubscriptionData>>) => void
onResult: (fn: (param?: ApolloQueryResult<TResult>) => void) => {
off: () => void
}
onError: (fn: (param?: Error) => void) => {
off: () => void
}
}

/**
* Use a query that does not require variables or options.
* */
export function useQuery<TResult = any>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>
): UseQueryReturn<TResult, undefined>

/**
* Use a query that requires options but not variables.
*/
export function useQuery<TResult = any, TVariables extends undefined = undefined>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables,
options: UseQueryOptions<TResult, TVariables> | Ref<UseQueryOptions<TResult, TVariables>> | ReactiveFunction<UseQueryOptions<TResult, TVariables>>
): UseQueryReturn<TResult, TVariables>

/**
* Use a query that requires variables.
*/
export function useQuery<TResult = any, TVariables extends OperationVariables = OperationVariables>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables | Ref<TVariables> | ReactiveFunction<TVariables>
): UseQueryReturn<TResult, TVariables>

/**
* Use a query that requires variables and options.
*/
export function useQuery<TResult = any, TVariables extends OperationVariables = OperationVariables>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables | Ref<TVariables> | ReactiveFunction<TVariables>,
options: UseQueryOptions<TResult, TVariables> | Ref<UseQueryOptions<TResult, TVariables>> | ReactiveFunction<UseQueryOptions<TResult, TVariables>>
): UseQueryReturn<TResult, TVariables>

export function useQuery<
TResult = any,
TVariables = OperationVariables,
TCacheShape = any
TResult,
TVariables extends OperationVariables
> (
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables | Ref<TVariables> | ReactiveFunction<TVariables> = null,
options: UseQueryOptions<TResult, TVariables> | Ref<UseQueryOptions<TResult, TVariables>> | ReactiveFunction<UseQueryOptions<TResult, TVariables>> = {},
) {
variables?: TVariables | Ref<TVariables> | ReactiveFunction<TVariables>,
options?: UseQueryOptions<TResult, TVariables> | Ref<UseQueryOptions<TResult, TVariables>> | ReactiveFunction<UseQueryOptions<TResult, TVariables>>,
): UseQueryReturn<TResult, TVariables> {
// Is on server?
const vm = getCurrentInstance()
const isServer = vm.$isServer
Expand Down
80 changes: 72 additions & 8 deletions packages/vue-apollo-composable/src/useResult.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,79 @@
import { Ref, computed } from '@vue/composition-api'
import { ExtractSingleKey } from './util/ExtractSingleKey'

export type UseResultReturn<T> = Readonly<Ref<Readonly<T>>>

/**
* Resolve a `result`, returning either the first key of the `result` if there
* is only one, or the `result` itself. The `value` of the ref will be
* `undefined` until it is resolved.
*
* @example
* const { result } = useQuery(...)
* const user = useResult(result)
* // user is `void` until the query resolves
*
* @param {Ref<TResult>} result A `result` returned from `useQuery` to resolve.
* @returns Readonly ref with `void` or the resolved `result`.
*/
export function useResult<TResult, TResultKey extends keyof TResult = keyof TResult>(
result: Ref<TResult>
): UseResultReturn<void | ExtractSingleKey<TResult, TResultKey>>

/**
* Resolve a `result`, returning either the first key of the `result` if there
* is only one, or the `result` itself. The `value` of the ref will be
* `defaultValue` until it is resolved.
*
* @example
* const { result } = useQuery(...)
* const profile = useResult(result, {})
* // profile is `{}` until the query resolves
*
* @param {Ref<TResult>} result A `result` returned from `useQuery` to resolve.
* @param {TDefaultValue} defaultValue The default return value before `result` is resolved.
* @returns Readonly ref with the `defaultValue` or the resolved `result`.
*/
export function useResult<TResult, TDefaultValue, TResultKey extends keyof TResult = keyof TResult>(
result: Ref<TResult>,
defaultValue: TDefaultValue
): UseResultReturn<TDefaultValue | ExtractSingleKey<TResult, TResultKey>>

/**
* Resolve a `result`, returning the `result` mapped with the `pick` function.
* The `value` of the ref will be `defaultValue` until it is resolved.
*
* @example
* const { result } = useQuery(...)
* const comments = useResult(result, undefined, (data) => data.comments)
* // user is `undefined`, then resolves to the result's `comments`
*
* @param {Ref<TResult>} result A `result` returned from `useQuery` to resolve.
* @param {TDefaultValue} defaultValue The default return value before `result` is resolved.
* @param {(data:TResult)=>TReturnValue} pick The function that receives `result` and maps a return value from it.
* @returns Readonly ref with the `defaultValue` or the resolved and `pick`-mapped `result`
*/
export function useResult<
TResult,
TDefaultValue,
TReturnValue,
TResultKey extends keyof TResult = keyof TResult,
>(
result: Ref<TResult>,
defaultValue: TDefaultValue | undefined,
pick: (data: TResult) => TReturnValue
): UseResultReturn<TDefaultValue | TReturnValue>

export function useResult<
TReturnValue = any,
TDefaultValue = any,
TResult = any
TResult,
TDefaultValue,
TReturnValue,
> (
result: Ref<TResult>,
defaultValue: TDefaultValue = null,
pick: (data: TResult) => TReturnValue = null,
) {
return computed<TDefaultValue | TReturnValue>(() => {
defaultValue?: TDefaultValue,
pick?: (data: TResult) => TReturnValue,
): UseResultReturn<TResult | TResult[keyof TResult] | TDefaultValue | TReturnValue | undefined> {
return computed(() => {
const value = result.value
if (value) {
if (pick) {
Expand All @@ -22,7 +86,7 @@ export function useResult<
const keys = Object.keys(value)
if (keys.length === 1) {
// Automatically take the only key in result data
return value[keys[0]]
return value[keys[0] as keyof TResult]
} else {
// Return entire result data
return value
Expand Down
59 changes: 56 additions & 3 deletions packages/vue-apollo-composable/src/useSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,67 @@ export interface UseSubscriptionOptions <
debounce?: number
}

export interface UseSubscriptionReturn<TResult, TVariables> {
result: Ref<TResult>
loading: Ref<boolean>
error: Ref<Error>
start: () => void
stop: () => void
restart: () => void
document: Ref<DocumentNode>
variables: Ref<TVariables>
options: UseSubscriptionOptions<TResult, TVariables> | Ref<UseSubscriptionOptions<TResult, TVariables>>
subscription: Ref<Observable<FetchResult<TResult, Record<string, any>, Record<string, any>>>>
onResult: (fn: (param?: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => {
off: () => void
}
onError: (fn: (param?: Error) => void) => {
off: () => void
}
}


/**
* Use a subscription that does not require variables or options.
* */
export function useSubscription<TResult = any>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>
): UseSubscriptionReturn<TResult, undefined>

/**
* Use a subscription that requires options but not variables.
*/
export function useSubscription<TResult = any, TVariables extends undefined = undefined>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables,
options: UseSubscriptionOptions<TResult, TVariables> | Ref<UseSubscriptionOptions<TResult, TVariables>> | ReactiveFunction<UseSubscriptionOptions<TResult, TVariables>>
): UseSubscriptionReturn<TResult, TVariables>

/**
* Use a subscription that requires variables.
*/
export function useSubscription<TResult = any, TVariables extends OperationVariables = OperationVariables>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables | Ref<TVariables> | ReactiveFunction<TVariables>
): UseSubscriptionReturn<TResult, TVariables>

/**
* Use a subscription that requires variables and options.
*/
export function useSubscription<TResult = any, TVariables extends OperationVariables = OperationVariables>(
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables | Ref<TVariables> | ReactiveFunction<TVariables>,
options: UseSubscriptionOptions<TResult, TVariables> | Ref<UseSubscriptionOptions<TResult, TVariables>> | ReactiveFunction<UseSubscriptionOptions<TResult, TVariables>>
): UseSubscriptionReturn<TResult, TVariables>

export function useSubscription <
TResult = any,
TVariables = OperationVariables
TResult,
TVariables
> (
document: DocumentNode | Ref<DocumentNode> | ReactiveFunction<DocumentNode>,
variables: TVariables | Ref<TVariables> | ReactiveFunction<TVariables> = null,
options: UseSubscriptionOptions<TResult, TVariables> | Ref<UseSubscriptionOptions<TResult, TVariables>> | ReactiveFunction<UseSubscriptionOptions<TResult, TVariables>> = null
) {
): UseSubscriptionReturn<TResult, TVariables> {
// Is on server?
const vm = getCurrentInstance()
const isServer = vm.$isServer
Expand Down
9 changes: 9 additions & 0 deletions packages/vue-apollo-composable/src/util/ExtractSingleKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Check if a type is a union, and return true if so, otherwise false.
*/
export type IsUnion<T, U = T> = U extends any ? ([T] extends [U] ? false : true) : never

/**
* Extracts an inner type if T has a single key K, otherwise it returns T.
*/
export type ExtractSingleKey<T, K extends keyof T = keyof T> = IsUnion<K> extends true ? T : T[K]
Loading