Skip to content

Commit

Permalink
feat: useSubscription
Browse files Browse the repository at this point in the history
  • Loading branch information
Guillaume Chau committed Nov 29, 2019
1 parent 7a0c025 commit 41b256f
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/vue-apollo-composable/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './useQuery'
export * from './useMutation'
export * from './useSubscription'
export * from './useResult'
// export * from './useLoading'
export * from './useApolloClient'
163 changes: 163 additions & 0 deletions packages/vue-apollo-composable/src/useSubscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { DocumentNode } from 'graphql'
import Vue from 'vue'
import { Ref, ref, watch, isRef, onUnmounted } from '@vue/composition-api'
import { OperationVariables, SubscriptionOptions } from 'apollo-client'
import { Observable, Subscription } from 'apollo-client/util/Observable'
import { FetchResult } from 'apollo-link'
import { ReactiveFunction } from './util/ReactiveFunction'
import { paramToRef } from './util/paramToRef'
import { paramToReactive } from './util/paramToReactive'
import { useApolloClient } from './useApolloClient'

export interface UseSubscriptionOptions <
TResult = any,
TVariables = OperationVariables
> extends Omit<SubscriptionOptions<TVariables>, 'query' | 'variables'> {
clientId?: string
enabled?: boolean
}

export function useSubscription <
TResult = any,
TVariables = OperationVariables
> (
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
) {
if (variables == null) variables = ref()
if (!options) options = {}
const documentRef = paramToRef(document)
const variablesRef = paramToReactive(variables)
const optionsRef = paramToReactive(options)

const result = ref<TResult>()
const error = ref(null)

const loading = ref(false)

// Apollo Client
const { resolveClient } = useApolloClient()

const subscription: Ref<Observable<FetchResult<TResult>>> = ref()
let observer: Subscription
let started = false

function start () {
if (started) return
started = true
loading.value = true

const client = resolveClient(currentOptions.value.clientId)

subscription.value = client.subscribe<TResult, TVariables>({
query: currentDocument,
variables: currentVariables,
...currentOptions.value,
})

observer = subscription.value.subscribe({
next: onNextResult,
error: onError,
})
}

function onNextResult (fetchResult: FetchResult<TResult>) {
result.value = fetchResult.data
loading.value = false
}

function onError (fetchError: any) {
error.value = fetchError
}

function stop () {
if (!started) return
started = false
loading.value = false

if (subscription.value) {
subscription.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
restart()
}, {
deep: true,
})

// Applying options
const currentOptions = ref<UseSubscriptionOptions<TResult, TVariables>>()
watch(() => isRef(optionsRef) ? optionsRef.value : optionsRef, value => {
currentOptions.value = value
restart()
}, {
deep: true,
})

// 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,
subscription,
}
}

0 comments on commit 41b256f

Please sign in to comment.