Skip to content

Commit

Permalink
feat: enable type hint for apollo client ids
Browse files Browse the repository at this point in the history
  • Loading branch information
Diizzayy committed Feb 29, 2024
1 parent fa2019f commit bca6596
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 49 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@
"resolutions": {
"@nuxtjs/apollo": "link:."
},
"packageManager": "pnpm@8.10.0"
"packageManager": "pnpm@8.15.2"
}
2 changes: 1 addition & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineNuxtConfig({
wsEndpoint: 'wss://nuxt-gql-server-2gl6xp7kua-ue.a.run.app/query',
httpLinkOptions: {
headers: {
'X-CUSTOM-HEADER': 123
'X-CUSTOM-HEADER': '123'
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 15 additions & 10 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Ref } from 'vue'
import { defu } from 'defu'
import { useLogger, addPlugin, addImports, addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
import GraphQLPlugin from '@rollup/plugin-graphql'
import type { PluginOption } from 'vite'
import { name, version } from '../package.json'
import type { ClientConfig, NuxtApolloConfig, ErrorResponse } from './types'
import { serializeConfig } from './serialize'
Expand All @@ -18,7 +19,7 @@ async function readConfigFile (path: string): Promise<ClientConfig> {

export type ModuleOptions = NuxtApolloConfig

export default defineNuxtModule<NuxtApolloConfig<any>>({
export default defineNuxtModule<ModuleOptions>({
meta: {
name,
version,
Expand Down Expand Up @@ -95,18 +96,22 @@ export default defineNuxtModule<NuxtApolloConfig<any>>({
filename: 'apollo.d.ts',
getContents: () => [
'import type { ClientConfig } from "@nuxtjs/apollo"',
'declare const clients: Record<string, ClientConfig>',
'declare const clientAwareness: boolean',
'declare const proxyCookies: boolean',
'declare const cookieAttributes: ClientConfig[\'cookieAttributes\']',
'export default { clients, clientAwareness, proxyCookies, cookieAttributes }'
'declare module \'#apollo\' {',
` export type ApolloClientKeys = '${Object.keys(clients).join('\' | \'')}'`,
' export const NuxtApollo: {',
' clients: Record<ApolloClientKeys, ClientConfig>',
' clientAwareness: boolean',
' proxyCookies: boolean',
' cookieAttributes: ClientConfig[\'cookieAttributes\']',
' }',
'}'
].join('\n')
})

addTemplate({
filename: 'apollo.mjs',
getContents: () => [
'export default {',
'export const NuxtApollo = {',
` proxyCookies: ${options.proxyCookies},`,
` clientAwareness: ${options.clientAwareness},`,
` cookieAttributes: ${serializeConfig(options.cookieAttributes)},`,
Expand Down Expand Up @@ -154,7 +159,7 @@ export default defineNuxtModule<NuxtApolloConfig<any>>({
config.optimizeDeps.exclude.push('@vue/apollo-composable')

config.plugins = config.plugins || []
config.plugins.push(GraphQLPlugin())
config.plugins.push(GraphQLPlugin() as PluginOption)

if (!nuxt.options.dev) { config.define = { ...config.define, __DEV__: false } }
})
Expand Down Expand Up @@ -191,7 +196,7 @@ export default defineNuxtModule<NuxtApolloConfig<any>>({

export const defineApolloClient = (config: ClientConfig) => config

export interface RuntimeModuleHooks {
export interface ModuleRuntimeHooks {
'apollo:auth': (params: { client: string, token: Ref<string | null> }) => void
'apollo:error': (error: ErrorResponse) => void
}
Expand All @@ -205,7 +210,7 @@ export interface ModulePublicRuntimeConfig {
}

declare module '#app' {
interface RuntimeNuxtHooks extends RuntimeModuleHooks {}
interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
}

declare module '@nuxt/schema' {
Expand Down
41 changes: 27 additions & 14 deletions src/runtime/composables.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { hash } from 'ohash'
import { print } from 'graphql'
import type { OperationVariables, QueryOptions, DefaultContext } from '@apollo/client'
import type { ApolloClient, OperationVariables, QueryOptions, DefaultContext } from '@apollo/client'
import type { AsyncData, AsyncDataOptions, NuxtError } from 'nuxt/app'
import type { NuxtAppApollo } from '../types'
import type { RestartableClient } from './ws'
import { ref, isRef, reactive, useCookie, useNuxtApp, useAsyncData } from '#imports'
import { NuxtApollo } from '#apollo'
import type { ApolloClientKeys } from '#apollo'

type PickFrom<T, K extends Array<string>> = T extends Array<any> ? T : T extends Record<string, any> ? keyof T extends K[number] ? T : K[number] extends never ? T : Pick<T, K[number]> : T
type KeysOf<T> = Array<T extends T ? keyof T extends string ? keyof T : never : never>
Expand All @@ -14,7 +16,7 @@ type TAsyncQuery<T> = {
key?: string
query: TQuery<T>
variables?: TVariables<T>
clientId?: string
clientId?: ApolloClientKeys
context?: DefaultContext
cache?: boolean
}
Expand All @@ -33,7 +35,7 @@ export function useAsyncQuery <
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = null,
NuxtErrorDataT = unknown
> (query: TQuery<T>, variables?: TVariables<T>, clientId?: string, context?: DefaultContext, options?: AsyncDataOptions<T, DataT, PickKeys, DefaultT>): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
> (query: TQuery<T>, variables?: TVariables<T>, clientId?: ApolloClientKeys, context?: DefaultContext, options?: AsyncDataOptions<T, DataT, PickKeys, DefaultT>): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>

export function useAsyncQuery <T> (...args: any[]) {
const { key, fn, options } = prep<T>(...args)
Expand Down Expand Up @@ -68,7 +70,7 @@ const prep = <T> (...args: any[]) => {
let variables: TVariables<T>

let cache: boolean = true
let clientId: string | undefined
let clientId: ApolloClientKeys | undefined
let context: DefaultContext

let options: AsyncDataOptions<T, T, KeysOf<T>, null> = {}
Expand All @@ -80,7 +82,7 @@ const prep = <T> (...args: any[]) => {
cache = args?.[0]?.cache ?? true
context = args?.[0]?.context
clientId = args?.[0]?.clientId

if (typeof args?.[1] === 'object') {
options = args?.[1]
}
Expand All @@ -99,7 +101,7 @@ const prep = <T> (...args: any[]) => {
if (!query) { throw new Error('@nuxtjs/apollo: no query provided') }

if (!clientId || !clients?.[clientId]) {
clientId = clients?.default ? 'default' : Object.keys(clients!)?.[0]
clientId = (clients?.default ? 'default' : Object.keys(clients!)?.[0]) as ApolloClientKeys

if (!clientId) { throw new Error('@nuxtjs/apollo: no client found') }
}
Expand All @@ -108,7 +110,7 @@ const prep = <T> (...args: any[]) => {
variables = isRef(variables) ? variables : reactive(variables)

options.watch = options.watch || []
options.watch.push(variables)
options.watch.push(variables)
}

const key = args?.[0]?.key || hash({ query: print(query), variables, clientId })
Expand All @@ -123,10 +125,20 @@ const prep = <T> (...args: any[]) => {
return { key, query, clientId, variables, fn, options }
}

export const useApollo = () => {
const nuxtApp = useNuxtApp() as NuxtAppApollo
export function useApollo (): {
clients: Record<ApolloClientKeys, ApolloClient<any>> | undefined
getToken: (client?: ApolloClientKeys) => Promise<string | null | undefined>
onLogin: (token?: string, client?: ApolloClientKeys, skipResetStore?: boolean) => Promise<void>
onLogout: (client?: ApolloClientKeys, skipResetStore?: boolean) => Promise<void>
}

export function useApollo () {
const nuxtApp = useNuxtApp() as {
_apolloClients?: Record<ApolloClientKeys, ApolloClient<any>>;
_apolloWsClients?: Record<ApolloClientKeys, RestartableClient>;
}

const getToken = async (client?: string) => {
const getToken = async (client?: ApolloClientKeys) => {
client = client || 'default'

const conf = NuxtApollo?.clients?.[client]
Expand All @@ -140,7 +152,7 @@ export const useApollo = () => {

return conf?.tokenStorage === 'cookie' ? useCookie(tokenName).value : (process.client && localStorage.getItem(tokenName)) || null
}
type TAuthUpdate = {token?: string, client?: string, mode: 'login' | 'logout', skipResetStore?: boolean}
type TAuthUpdate = {token?: string, client?: ApolloClientKeys, mode: 'login' | 'logout', skipResetStore?: boolean}
const updateAuth = async ({ token, client, mode, skipResetStore }: TAuthUpdate) => {
client = client || 'default'

Expand Down Expand Up @@ -169,6 +181,7 @@ export const useApollo = () => {

if (skipResetStore) { return }

// eslint-disable-next-line no-console
await nuxtApp?._apolloClients?.[client].resetStore().catch(e => console.log('%cError on cache reset', 'color: orange;', e.message))
}

Expand All @@ -192,14 +205,14 @@ export const useApollo = () => {
* @param {string} client - Name of the Apollo client. Defaults to `default`.
* @param {boolean} skipResetStore - If `true`, the cache will not be reset.
* */
onLogin: (token?: string, client?: string, skipResetStore?: boolean) => updateAuth({ token, client, skipResetStore, mode: 'login' }),
onLogin: (token?: string, client?: ApolloClientKeys, skipResetStore?: boolean) => updateAuth({ token, client, skipResetStore, mode: 'login' }),

/**
* Remove the auth token from the Apollo client, and optionally reset it's cache.
*
* @param {string} client - Name of the Apollo client. Defaults to `default`.
* @param {boolean} skipResetStore - If `true`, the cache will not be reset.
* */
onLogout: (client?: string, skipResetStore?: boolean) => updateAuth({ client, skipResetStore, mode: 'logout' })
onLogout: (client?: ApolloClientKeys, skipResetStore?: boolean) => updateAuth({ client, skipResetStore, mode: 'logout' })
}
}
19 changes: 12 additions & 7 deletions src/runtime/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,28 @@ import createRestartableClient from './ws'
import { useApollo } from './composables'
import { ref, useCookie, defineNuxtPlugin, useRequestHeaders } from '#imports'

import NuxtApollo from '#apollo'
import { NuxtApollo } from '#apollo'
import type { ApolloClientKeys } from '#apollo'

export default defineNuxtPlugin((nuxtApp) => {
const requestCookies = (process.server && NuxtApollo.proxyCookies && useRequestHeaders(['cookie'])) || undefined

const clients: { [key: string]: ApolloClient<any> } = {}
const clients = {} as Record<ApolloClientKeys, ApolloClient<any>>

for (const [key, clientConfig] of Object.entries(NuxtApollo.clients)) {
const getAuth = async () => {
const token = ref<string | null>()
const token = ref<string | null>(null)

await nuxtApp.callHook('apollo:auth', { token, client: key })

if (!token.value) {
if (clientConfig.tokenStorage === 'cookie') {
if (process.client) {
token.value = useCookie(clientConfig.tokenName!).value
const t = useCookie(clientConfig.tokenName!).value
if (t) { token.value = t }
} else if (requestCookies?.cookie) {
token.value = requestCookies.cookie.split(';').find(c => c.trim().startsWith(`${clientConfig.tokenName}=`))?.split('=')?.[1]
const t = requestCookies.cookie.split(';').find(c => c.trim().startsWith(`${clientConfig.tokenName}=`))?.split('=')?.[1]
if (t) { token.value = t }
}
} else if (process.client && clientConfig.tokenStorage === 'localStorage') {
token.value = localStorage.getItem(clientConfig.tokenName!)
Expand Down Expand Up @@ -82,6 +85,8 @@ export default defineNuxtPlugin((nuxtApp) => {
wsLink = new GraphQLWsLink(wsClient)

nuxtApp._apolloWsClients = nuxtApp._apolloWsClients || {}

// @ts-ignore
nuxtApp._apolloWsClients[key] = wsClient
}

Expand Down Expand Up @@ -109,7 +114,7 @@ export default defineNuxtPlugin((nuxtApp) => {

const cache = new InMemoryCache(clientConfig.inMemoryCacheOptions)

clients[key] = new ApolloClient({
clients[key as ApolloClientKeys] = new ApolloClient({
link,
cache,
...(NuxtApollo.clientAwareness && { name: key }),
Expand All @@ -121,7 +126,7 @@ export default defineNuxtPlugin((nuxtApp) => {
})

if (!clients?.default && !NuxtApollo?.clients?.default && key === Object.keys(NuxtApollo.clients)[0]) {
clients.default = clients[key]
clients.default = clients[key as ApolloClientKeys]
}

const cacheKey = `_apollo:${key}`
Expand Down
14 changes: 4 additions & 10 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import type { ClientOptions } from 'graphql-ws'
import type { ApolloClient, HttpOptions, DefaultOptions, InMemoryCacheConfig } from '@apollo/client'
import type { CookieOptions } from 'nuxt/dist/app/composables'
import type { RestartableClient } from './runtime/ws'
import type { HttpOptions, DefaultOptions, InMemoryCacheConfig } from '@apollo/client'
import type { CookieOptions } from 'nuxt/app'
export type { ErrorResponse } from '@apollo/client/link/error'

type CookieAttributes = Omit< CookieOptions, 'encode' | 'decode' | 'expires' | 'default'>;

export type NuxtAppApollo = Partial<{
_apolloClients?: Record<string, ApolloClient<any>>;
_apolloWsClients?: Record<string, RestartableClient>;
}>;

export type ClientConfig = {
/**
* The GraphQL endpoint.
Expand Down Expand Up @@ -108,7 +102,7 @@ export type ClientConfig = {
cookieAttributes?: CookieAttributes;
};

export interface NuxtApolloConfig<T = ClientConfig> {
export interface NuxtApolloConfig<T = false> {
/**
* Determine if vue-apollo composables should be automatically imported.
* @type {boolean}
Expand All @@ -119,7 +113,7 @@ export interface NuxtApolloConfig<T = ClientConfig> {
/**
* Configuration of the Apollo clients.
**/
clients?: Record< string, T extends boolean ? string | ClientConfig : ClientConfig >;
clients?: Record< string, T extends false ? string | ClientConfig : ClientConfig >;

/**
* Default options to be applied to all Apollo clients.
Expand Down

0 comments on commit bca6596

Please sign in to comment.