diff --git a/package.json b/package.json index f538492..fb23d5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nuxt-graphql-client", - "version": "0.2.34", + "version": "0.2.35", "keywords": [ "vue", "nuxt", diff --git a/src/module.ts b/src/module.ts index 05165a5..1591c36 100644 --- a/src/module.ts +++ b/src/module.ts @@ -2,17 +2,32 @@ import { relative, resolve } from 'node:path' import { existsSync, statSync } from 'fs' import { defu } from 'defu' import { upperFirst } from 'scule' -import { useLogger, addPlugin, addImportsDir, addTemplate, resolveFiles, createResolver, defineNuxtModule } from '@nuxt/kit' +import { + useLogger, + addPlugin, + addImportsDir, + addTemplate, + resolveFiles, + createResolver, + defineNuxtModule, +} from '@nuxt/kit' import { name, version } from '../package.json' import generate from './generate' import { mapDocsToClients, extractGqlOperations } from './utils' -import type { GqlConfig, GqlClient, GqlCodegen, TokenStorageOpts } from './types' +import type { + GqlConfig, + GqlClient, + GqlCodegen, + TokenStorageOpts, + GqlError, +} from './types' import { prepareContext, mockTemplate } from './context' import type { GqlContext } from './context' const logger = useLogger('nuxt-graphql-client') export type ModuleOptions = Partial<GqlConfig> +export type { GqlError } export default defineNuxtModule<GqlConfig>({ meta: { @@ -20,8 +35,8 @@ export default defineNuxtModule<GqlConfig>({ version, configKey: 'graphql-client', compatibility: { - nuxt: '^3.7.0' - } + nuxt: '^3.7.0', + }, }, defaults: { clients: {}, @@ -29,9 +44,9 @@ export default defineNuxtModule<GqlConfig>({ codegen: true, autoImport: true, tokenStorage: true, - functionPrefix: 'Gql' + functionPrefix: 'Gql', }, - async setup (opts, nuxt) { + async setup(opts, nuxt) { const resolver = createResolver(import.meta.url) const srcResolver = createResolver(nuxt.options.srcDir) @@ -41,7 +56,8 @@ export default defineNuxtModule<GqlConfig>({ {}, nuxt.options.runtimeConfig.public['graphql-client'], nuxt.options.runtimeConfig.public.gql, - opts) + opts + ) const codegenDefaults: GqlCodegen = { silent: true, @@ -51,24 +67,32 @@ export default defineNuxtModule<GqlConfig>({ disableOnBuild: false, onlyOperationTypes: true, avoidOptionals: false, - maybeValue: 'T | null' + maybeValue: 'T | null', } - config.codegen = !!config.codegen && defu<GqlCodegen, [GqlCodegen]>(config.codegen, codegenDefaults) - - config.tokenStorage = !!config.tokenStorage && defu<TokenStorageOpts, [TokenStorageOpts]>(config.tokenStorage, { - mode: 'cookie', - cookieOptions: { - maxAge: 60 * 60 * 24 * 7, - secure: process.env.NODE_ENV === 'production' - } - }) + config.codegen = + !!config.codegen && + defu<GqlCodegen, [GqlCodegen]>(config.codegen, codegenDefaults) + + config.tokenStorage = + !!config.tokenStorage && + defu<TokenStorageOpts, [TokenStorageOpts]>(config.tokenStorage, { + mode: 'cookie', + cookieOptions: { + maxAge: 60 * 60 * 24 * 7, + secure: process.env.NODE_ENV === 'production', + }, + }) const ctx: GqlContext = { clientOps: {}, fnImports: [], clients: Object.keys(config.clients!), - codegen: !config?.codegen ? false : !(!nuxt.options._prepare && !nuxt.options.dev) ? (nuxt.options._prepare || nuxt.options.dev) : !config?.codegen?.disableOnBuild + codegen: !config?.codegen + ? false + : !(!nuxt.options._prepare && !nuxt.options.dev) + ? nuxt.options._prepare || nuxt.options.dev + : !config?.codegen?.disableOnBuild, } if (!ctx?.clients?.length) { @@ -76,7 +100,8 @@ export default defineNuxtModule<GqlConfig>({ process.env.GQL_HOST || nuxt.options.runtimeConfig.public.GQL_HOST const clientHost = - process.env.GQL_CLIENT_HOST || nuxt.options.runtimeConfig.public.GQL_CLIENT_HOST + process.env.GQL_CLIENT_HOST || + nuxt.options.runtimeConfig.public.GQL_CLIENT_HOST if (!host) { logger.warn('No GraphQL clients configured. Skipping module setup.') @@ -84,72 +109,119 @@ export default defineNuxtModule<GqlConfig>({ } ctx.clients = ['default'] - config.clients = !clientHost ? { default: host } : { default: { host, clientHost } } + config.clients = !clientHost + ? { default: host } + : { default: { host, clientHost } } } // @ts-ignore nuxt.options.runtimeConfig['graphql-client'] = { clients: {} } - nuxt.options.runtimeConfig.public['graphql-client'] = defu(nuxt.options.runtimeConfig.public['graphql-client'], { clients: {} }) + nuxt.options.runtimeConfig.public['graphql-client'] = defu( + nuxt.options.runtimeConfig.public['graphql-client'], + { clients: {} } + ) const clientDefaults = { token: { type: 'Bearer', name: 'Authorization' }, proxyCookies: true, tokenStorage: config.tokenStorage, - preferGETQueries: config?.preferGETQueries ?? false + preferGETQueries: config?.preferGETQueries ?? false, } as GqlClient<object> - const defaultClient = (config?.clients?.default && 'default') || Object.keys(config.clients!)[0] + const defaultClient = + (config?.clients?.default && 'default') || Object.keys(config.clients!)[0] for (const [k, v] of Object.entries(config.clients!)) { - const conf = defu<GqlClient<object>, [GqlClient<object>]>(typeof v !== 'object' - ? { host: v } - : { ...v, token: typeof v.token === 'string' ? { value: v.token } : v.token }, { - ...clientDefaults, - ...(typeof v === 'object' && typeof v.token !== 'string' && v?.token?.type === null && { token: { ...clientDefaults.token, type: null } }) - }) - - const runtimeHost = k === defaultClient ? process.env.GQL_HOST : process.env?.[`GQL_${k.toUpperCase()}_HOST`] - if (runtimeHost) { conf.host = runtimeHost } + const conf = defu<GqlClient<object>, [GqlClient<object>]>( + typeof v !== 'object' + ? { host: v } + : { + ...v, + token: typeof v.token === 'string' ? { value: v.token } : v.token, + }, + { + ...clientDefaults, + ...(typeof v === 'object' && + typeof v.token !== 'string' && + v?.token?.type === null && { + token: { ...clientDefaults.token, type: null }, + }), + } + ) + + const runtimeHost = + k === defaultClient + ? process.env.GQL_HOST + : process.env?.[`GQL_${k.toUpperCase()}_HOST`] + if (runtimeHost) { + conf.host = runtimeHost + } - const runtimeClientHost = k === defaultClient ? process.env.GQL_CLIENT_HOST : process.env?.[`GQL_${k.toUpperCase()}_CLIENT_HOST`] - if (runtimeClientHost) { conf.clientHost = runtimeClientHost } + const runtimeClientHost = + k === defaultClient + ? process.env.GQL_CLIENT_HOST + : process.env?.[`GQL_${k.toUpperCase()}_CLIENT_HOST`] + if (runtimeClientHost) { + conf.clientHost = runtimeClientHost + } if (!conf?.host) { logger.warn(`GraphQL client (${k}) is missing it's host.`) return } - const runtimeToken = k === defaultClient ? process.env.GQL_TOKEN : process.env?.[`GQL_${k.toUpperCase()}_TOKEN`] - if (runtimeToken) { conf.token = { ...conf.token, value: runtimeToken } } + const runtimeToken = + k === defaultClient + ? process.env.GQL_TOKEN + : process.env?.[`GQL_${k.toUpperCase()}_TOKEN`] + if (runtimeToken) { + conf.token = { ...conf.token, value: runtimeToken } + } - const runtimeTokenName = k === defaultClient ? process.env.GQL_TOKEN_NAME : process.env?.[`GQL_${k.toUpperCase()}_TOKEN_NAME`] - if (runtimeTokenName) { conf.token = { ...conf.token, name: runtimeTokenName } } + const runtimeTokenName = + k === defaultClient + ? process.env.GQL_TOKEN_NAME + : process.env?.[`GQL_${k.toUpperCase()}_TOKEN_NAME`] + if (runtimeTokenName) { + conf.token = { ...conf.token, name: runtimeTokenName } + } - if (conf.tokenStorage) { conf.tokenStorage.name = conf.tokenStorage?.name || `gql:${k}` } + if (conf.tokenStorage) { + conf.tokenStorage.name = conf.tokenStorage?.name || `gql:${k}` + } const schema = conf?.schema && srcResolver.resolve(conf.schema) if (schema && !existsSync(schema)) { delete conf.schema - logger.warn(`[nuxt-graphql-client] The Schema provided for the (${k}) GraphQL Client does not exist. \`host\` will be used as fallback.`) + logger.warn( + `[nuxt-graphql-client] The Schema provided for the (${k}) GraphQL Client does not exist. \`host\` will be used as fallback.` + ) } ctx.clientOps![k] = [] config.clients![k] = JSON.parse(JSON.stringify(conf)) - nuxt.options.runtimeConfig.public['graphql-client'].clients![k] = JSON.parse(JSON.stringify(conf)) + nuxt.options.runtimeConfig.public['graphql-client'].clients![k] = + JSON.parse(JSON.stringify(conf)) if (conf?.token?.value) { // @ts-ignore - nuxt.options.runtimeConfig['graphql-client'].clients[k] = { token: conf.token } + nuxt.options.runtimeConfig['graphql-client'].clients[k] = { + token: conf.token, + } if (!conf?.retainToken) { - (nuxt.options.runtimeConfig.public['graphql-client'] as GqlConfig).clients![k].token!.value = undefined + ;( + nuxt.options.runtimeConfig.public['graphql-client'] as GqlConfig + ).clients![k].token!.value = undefined } } } // Resolve all document path layers that extend the default layer - const documentPaths = nuxt.options._layers.map(layer => layer.config.srcDir) + const documentPaths = nuxt.options._layers.map( + (layer) => layer.config.srcDir + ) if (config.documentPaths) { for (const path of config.documentPaths) { @@ -164,10 +236,14 @@ export default defineNuxtModule<GqlConfig>({ } const gqlMatch = '**/*.{gql,graphql}' - async function generateGqlTypes (hmrDoc?: string) { + async function generateGqlTypes(hmrDoc?: string) { const documents: string[] = [] for await (const path of documentPaths) { - const files = (await resolveFiles(path, [gqlMatch, '!**/schemas'], { followSymbolicLinks: false })).filter(allowDocument) + const files = ( + await resolveFiles(path, [gqlMatch, '!**/schemas'], { + followSymbolicLinks: false, + }) + ).filter(allowDocument) documents.push(...files) } @@ -182,26 +258,41 @@ export default defineNuxtModule<GqlConfig>({ if (ctx.clientDocs) { const clientDocs = !hmrDoc ? ctx.clientDocs - : Object.keys(ctx.clientDocs) - .filter(k => ctx.clientDocs?.[k]?.some(e => e.endsWith(hmrDoc))) - .reduce((acc, k) => ({ ...acc, [k]: ctx.clientDocs?.[k] }), {}) as Record<string, string[]> + : (Object.keys(ctx.clientDocs) + .filter((k) => + ctx.clientDocs?.[k]?.some((e) => e.endsWith(hmrDoc)) + ) + .reduce( + (acc, k) => ({ ...acc, [k]: ctx.clientDocs?.[k] }), + {} + ) as Record<string, string[]>) const codegenResult = ctx?.codegen ? await generate({ - clients: config.clients as GqlConfig['clients'], - plugins, - documents, - resolver: srcResolver, - clientDocs, - ...(typeof config.codegen !== 'boolean' && config.codegen) - }).then(output => output.reduce<Record<string, string>>((acc, c) => ({ ...acc, [c.filename.split('.ts')[0]]: c.content }), {})) + clients: config.clients as GqlConfig['clients'], + plugins, + documents, + resolver: srcResolver, + clientDocs, + ...(typeof config.codegen !== 'boolean' && config.codegen), + }).then((output) => + output.reduce<Record<string, string>>( + (acc, c) => ({ + ...acc, + [c.filename.split('.ts')[0]]: c.content, + }), + {} + ) + ) : ctx.clients!.reduce<Record<string, string>>((acc, k) => { - if (!clientDocs?.[k]?.length) { return acc } + if (!clientDocs?.[k]?.length) { + return acc + } - const entries = extractGqlOperations(ctx?.clientDocs?.[k] || []) + const entries = extractGqlOperations(ctx?.clientDocs?.[k] || []) - return { ...acc, [k]: mockTemplate(entries) } - }, {}) + return { ...acc, [k]: mockTemplate(entries) } + }, {}) ctx.template = defu(codegenResult, ctx.template) } @@ -212,24 +303,31 @@ export default defineNuxtModule<GqlConfig>({ addPlugin(resolver.resolve('runtime/plugin')) if (config.autoImport) { - nuxt.options.alias['#gql'] = resolver.resolve(nuxt.options.buildDir, 'gql') - nuxt.options.alias['#gql/*'] = resolver.resolve(nuxt.options.buildDir, 'gql', '*') + nuxt.options.alias['#gql'] = resolver.resolve( + nuxt.options.buildDir, + 'gql' + ) + nuxt.options.alias['#gql/*'] = resolver.resolve( + nuxt.options.buildDir, + 'gql', + '*' + ) addTemplate({ filename: 'gql.mjs', - getContents: () => ctx.generateImports?.() || '' + getContents: () => ctx.generateImports?.() || '', }) addTemplate({ filename: 'gql/index.d.ts', - getContents: () => ctx.generateDeclarations?.() || '' + getContents: () => ctx.generateDeclarations?.() || '', }) for (const client of ctx.clients) { addTemplate({ write: ctx.codegen, filename: `gql/${client}.${ctx.codegen ? 'ts' : 'mjs'}`, - getContents: () => ctx.template?.[client] || '' + getContents: () => ctx.template?.[client] || '', }) } @@ -241,37 +339,47 @@ export default defineNuxtModule<GqlConfig>({ } nuxt.hook('nitro:config', (nitro) => { - if (nitro.imports === false) { return } + if (nitro.imports === false) { + return + } nitro.externals = nitro.externals || {} nitro.externals.inline = nitro.externals.inline || [] nitro.externals.inline.push(resolver.resolve('runtime')) - const clientSdks = Object.entries(ctx.clientDocs || {}).reduce<string[]>((acc, [client, docs]) => { - const entries = extractGqlOperations(docs) - - return [...acc, `${client}: ` + mockTemplate(entries).replace('export ', '')] - }, []) - nitro.virtual = nitro.virtual || {} nitro.virtual['#gql-nitro'] = [ - 'const clientSdks = {' + clientSdks + '}', + "import { getSdk } from '#gql/default'", 'const config = ' + JSON.stringify(config.clients), 'const ops = ' + JSON.stringify(ctx.clientOps), 'const clients = {}', - 'const useGql = (op, variables = undefined) => {', - ' const client = Object.keys(ops).find(k => ops[k].includes(op))', - ' return clientSdks[client](clients?.[client])[op](variables)', + 'const useGql = (op, ...params) => {', + " const key = Object.keys(ops).find(k => ops[k].includes(op)) || 'default'", + ' const client = clients[key]', + ' const sdk = getSdk(client)', + ' const document = sdk[op]', + ' return document(...params)', '}', - ctx.fns?.map(fn => `export const ${config.functionPrefix + upperFirst(fn)} = (...params) => useGql('${fn}', ...params)`).join('\n'), - 'export default { clients, config }' + ctx.fns + ?.map( + (fn) => + `export const ${ + config.functionPrefix + upperFirst(fn) + } = (...params) => useGql('${fn}', ...params)` + ) + .join('\n'), + 'export default { clients, config }', ].join('\n') nitro.imports = defu(nitro.imports, { - presets: [{ - from: '#gql-nitro', - imports: ctx.fns?.map(fn => config.functionPrefix + upperFirst(fn)) - }] + presets: [ + { + from: '#gql-nitro', + imports: ctx.fns?.map( + (fn) => config.functionPrefix + upperFirst(fn) + ), + }, + ], }) nitro.plugins = nitro.plugins || [] @@ -279,7 +387,10 @@ export default defineNuxtModule<GqlConfig>({ }) const allowDocument = (f: string) => { - const isSchema = f.match(/([^/]+)\.(gql|graphql)$/)?.[0]?.toLowerCase().includes('schema') + const isSchema = f + .match(/([^/]+)\.(gql|graphql)$/)?.[0] + ?.toLowerCase() + .includes('schema') return !isSchema && !!statSync(srcResolver.resolve(f)).size } @@ -287,9 +398,13 @@ export default defineNuxtModule<GqlConfig>({ if (config.watch) { nuxt.hook('builder:watch', async (event, path) => { path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path)) - if (!path.match(/\.(gql|graphql)$/)) { return } + if (!path.match(/\.(gql|graphql)$/)) { + return + } - if (event !== 'unlink' && !allowDocument(path)) { return } + if (event !== 'unlink' && !allowDocument(path)) { + return + } const start = Date.now() await generateGqlTypes(path) @@ -309,7 +424,7 @@ export default defineNuxtModule<GqlConfig>({ delete config.define['typeof document'] } }) - } + }, }) declare module '@nuxt/schema' {