Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Guillaume Chau
committed
Nov 28, 2019
1 parent
c0d2a87
commit 377f421
Showing
11 changed files
with
381 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# @vue/apollo-composable | ||
|
||
Experimental Apollo GraphQL functions for Vue Composition API |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"name": "@vue/apollo-composable", | ||
"version": "4.0.0-alpha.1", | ||
"description": "Apollo GraphQL for Vue Composition API", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Akryum/vue-apollo.git" | ||
}, | ||
"keywords": [ | ||
"vue", | ||
"apollo", | ||
"graphql", | ||
"composition" | ||
], | ||
"author": "Guillaume Chau <guillaume.b.chau@gmail.com>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/Akryum/vue-apollo/issues" | ||
}, | ||
"homepage": "https://github.com/Akryum/vue-apollo#readme", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"dev": "yarn build --watch", | ||
"build": "tsc --outDir dist -d", | ||
"prepublishOnly": "yarn build" | ||
}, | ||
"peerDependencies": { | ||
"@vue/composition-api": "^0.3.2", | ||
"apollo-client": "^2.0.0", | ||
"apollo-link": "^1.0.0", | ||
"graphql": "^14.5.8", | ||
"graphql-tag": "^2.0.0", | ||
"vue": "^2.6.10" | ||
}, | ||
"devDependencies": { | ||
"@vue/composition-api": "^0.3.2", | ||
"typescript": "^3.7.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './useQuery' | ||
export * from './useResult' | ||
// export * from './useLoading' | ||
export * from './useApolloClient' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { inject } from '@vue/composition-api' | ||
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) { | ||
const providedApolloClients: { [key: string]: ApolloClient<TCacheShape> } = inject(ApolloClients, null) | ||
const providedApolloClient: ApolloClient<TCacheShape> = inject(DefaultApolloClient, null) | ||
|
||
function resolveClient (clientId: string = null): ApolloClient<TCacheShape> { | ||
let resolvedClient | ||
if (clientId) { | ||
if (!providedApolloClients) { | ||
throw new Error(`No apolloClients injection found, tried to resolve '${clientId}' clientId`) | ||
} | ||
resolvedClient = providedApolloClients[clientId] | ||
} else { | ||
clientId = 'default' | ||
if (providedApolloClients) { | ||
resolvedClient = providedApolloClients.default | ||
} else { | ||
resolvedClient = providedApolloClient | ||
} | ||
} | ||
if (!resolvedClient) { | ||
throw new Error(`Apollo Client with id ${clientId} not found`) | ||
} | ||
return resolvedClient | ||
} | ||
|
||
return { | ||
resolveClient, | ||
get client () { | ||
return resolveClient(clientId) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
import { ref, watch, onUnmounted, Ref, isRef } from '@vue/composition-api' | ||
import Vue from 'vue' | ||
import { DocumentNode } from 'graphql' | ||
import ApolloClient, { OperationVariables, WatchQueryOptions, ObservableQuery, ApolloQueryResult, SubscribeToMoreOptions } from 'apollo-client' | ||
import { Subscription } from 'apollo-client/util/Observable' | ||
import { useApolloClient } from './useApolloClient' | ||
import { ReactiveFunction } from './util/ReactiveFunction' | ||
import { paramToRef } from './util/paramToRef' | ||
import { paramToReactive } from './util/paramToReactive' | ||
// import { trackQuery } from './util/loadingTracking' | ||
|
||
export interface UseQueryOptions< | ||
TResult = any, | ||
TVariables = OperationVariables | ||
> extends Omit<WatchQueryOptions<TVariables>, 'query' | 'variables'> { | ||
clientId?: string, | ||
enabled?: boolean, | ||
} | ||
|
||
export function useQuery< | ||
TResult = any, | ||
TVariables = OperationVariables, | ||
TCacheShape = any | ||
> ( | ||
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>> = {}, | ||
) { | ||
if (variables == null) variables = ref() | ||
if (options == null) options = {} | ||
const documentRef = paramToRef(document) | ||
const variablesRef = paramToReactive(variables) | ||
console.log(variablesRef) | ||
const optionsRef = paramToReactive(options) | ||
|
||
// Result | ||
/** | ||
* Result from the query | ||
*/ | ||
const result = ref<TResult>() | ||
const error = ref(null) | ||
|
||
// Loading | ||
|
||
/** | ||
* Indicates if a network request is pending | ||
*/ | ||
const loading = ref(false) | ||
// trackQuery(loading) | ||
|
||
// Apollo Client | ||
const { resolveClient } = useApolloClient() | ||
|
||
// Query | ||
|
||
let query: Ref<ObservableQuery<TResult, TVariables>> = ref() | ||
let observer: Subscription | ||
let started = false | ||
|
||
/** | ||
* Starts watching the query | ||
*/ | ||
function start () { | ||
if (started) return | ||
started = true | ||
loading.value = true | ||
|
||
const client = resolveClient(currentOptions.value.clientId) | ||
|
||
query.value = client.watchQuery<TResult, TVariables>({ | ||
query: currentDocument, | ||
variables: currentVariables, | ||
...currentOptions.value, | ||
}) | ||
|
||
observer = query.value.subscribe({ | ||
next: onNextResult, | ||
error: onError, | ||
}) | ||
|
||
for (const subOptions of subscribeToMoreItems) { | ||
subscribeToMore(subOptions) | ||
} | ||
} | ||
|
||
function onNextResult (queryResult: ApolloQueryResult<TResult>) { | ||
result.value = queryResult.data | ||
loading.value = queryResult.loading | ||
} | ||
|
||
function onError (queryError: any) { | ||
error.value = queryError | ||
} | ||
|
||
let onStopHandlers: (() => void)[] = [] | ||
|
||
/** | ||
* Stop watching the query | ||
*/ | ||
function stop () { | ||
if (!started) return | ||
started = false | ||
loading.value = false | ||
|
||
onStopHandlers.forEach(handler => handler()) | ||
onStopHandlers = [] | ||
|
||
if (query.value) { | ||
query.value.stopPolling() | ||
query.value = null | ||
} | ||
|
||
if (observer) { | ||
observer.unsubscribe() | ||
observer = null | ||
} | ||
} | ||
|
||
// Restart | ||
let restarting = false | ||
/** | ||
* Queue a restart of the query (on next tick) if it is already active | ||
*/ | ||
function restart () { | ||
if (!started || restarting) return | ||
restarting = true | ||
Vue.nextTick(() => { | ||
if (started) { | ||
stop() | ||
start() | ||
} | ||
restarting = false | ||
}) | ||
} | ||
|
||
// Applying document | ||
let currentDocument: DocumentNode | ||
watch(documentRef, value => { | ||
currentDocument = value | ||
restart() | ||
}) | ||
|
||
// Applying variables | ||
let currentVariables: TVariables | ||
watch(() => isRef(variablesRef) ? variablesRef.value : variablesRef, value => { | ||
currentVariables = value | ||
console.log('currentVariables', value) | ||
restart() | ||
}, { | ||
deep: true, | ||
}) | ||
|
||
// Applying options | ||
const currentOptions = ref<UseQueryOptions<TResult, TVariables>>() | ||
watch(() => isRef(optionsRef) ? optionsRef.value : optionsRef, value => { | ||
currentOptions.value = value | ||
restart() | ||
}, { | ||
deep: true, | ||
}) | ||
|
||
// Subscribe to more | ||
|
||
const subscribeToMoreItems = [] | ||
|
||
function subscribeToMore< | ||
TSubscriptionVariables = OperationVariables, | ||
TSubscriptionData = TResult | ||
> (options: SubscribeToMoreOptions<TResult, TSubscriptionVariables, TSubscriptionData>) { | ||
subscribeToMoreItems.push(options) | ||
addSubscribeToMore(options) | ||
} | ||
|
||
function addSubscribeToMore (options: SubscribeToMoreOptions) { | ||
if (!started) return | ||
const unsubscribe = query.value.subscribeToMore(options) | ||
onStopHandlers.push(unsubscribe) | ||
} | ||
|
||
// Internal enabled returned to user | ||
const enabled = ref(true) | ||
|
||
// Auto start & stop | ||
watch( | ||
() => enabled.value && | ||
// Enabled option | ||
(!currentOptions.value || currentOptions.value.enabled == null || currentOptions.value.enabled) | ||
, value => { | ||
if (value) { | ||
start() | ||
} else { | ||
stop() | ||
} | ||
}) | ||
|
||
// Teardown | ||
onUnmounted(stop) | ||
|
||
return { | ||
result, | ||
loading, | ||
error, | ||
enabled, | ||
start, | ||
stop, | ||
restart, | ||
document: documentRef, | ||
variables: variablesRef, | ||
options: optionsRef, | ||
query, | ||
subscribeToMore, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Ref, computed } from '@vue/composition-api' | ||
|
||
export function useResult< | ||
TResult = any | ||
> ( | ||
result: Ref<TResult>, | ||
defaultValue: any = null, | ||
pick: (data: TResult) => any = null, | ||
) { | ||
return computed(() => { | ||
const value = result.value | ||
if (value) { | ||
if (pick) { | ||
try { | ||
return pick(value) | ||
} catch (e) { | ||
// Silent error | ||
} | ||
} else { | ||
const keys = Object.keys(value) | ||
if (keys.length === 1) { | ||
// Automatically take the only key in result data | ||
return value[keys[0]] | ||
} else { | ||
// Return entire result data | ||
return value | ||
} | ||
} | ||
} | ||
return defaultValue | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type ReactiveFunction<TParam> = () => TParam |
14 changes: 14 additions & 0 deletions
14
packages/vue-apollo-composable/src/util/paramToReactive.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { Ref, isRef, reactive, computed } from '@vue/composition-api' | ||
import { ReactiveFunction } from './ReactiveFunction' | ||
|
||
export function paramToReactive<T> (param: T | Ref<T> | ReactiveFunction<T>): T | Ref<T> { | ||
if (isRef(param)) { | ||
return param | ||
} else if (typeof param === 'function') { | ||
return computed(param as ReactiveFunction<T>) | ||
} else if (param) { | ||
return reactive(param) as T | ||
} else { | ||
return param | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Ref, isRef, computed, ref } from '@vue/composition-api' | ||
import { ReactiveFunction } from './ReactiveFunction' | ||
|
||
export function paramToRef<T> (param: T | Ref<T> | ReactiveFunction<T>): Ref<T> { | ||
if (isRef(param)) { | ||
return param | ||
} else if (typeof param === 'function') { | ||
return computed(param as ReactiveFunction<T>) | ||
} else { | ||
return ref(param) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es5", | ||
"module": "commonjs", | ||
"sourceMap": true, | ||
}, | ||
"include": [ | ||
"src/**/*", | ||
], | ||
} |
Oops, something went wrong.