diff --git a/src/module.ts b/src/module.ts index 13c7c74..d405174 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,12 +1,11 @@ -import { existsSync, statSync, readFileSync } from 'fs' +import { existsSync, statSync } from 'fs' import { defu } from 'defu' -import { parse } from 'graphql' +import { upperFirst } from 'scule' import { useLogger, addPlugin, addImportsDir, addTemplate, resolveFiles, createResolver, defineNuxtModule, extendViteConfig } from '@nuxt/kit' -import type { NameNode, DefinitionNode } from 'graphql' import { name, version } from '../package.json' import generate from './generate' -import { mapDocsToClients } from './utils' -import type { GqlConfig, GqlClient, TokenOpts, GqlCodegen, TokenStorageOpts } from './types' +import { mapDocsToClients, extractGqlOperations } from './utils' +import type { GqlConfig, GqlClient, GqlCodegen, TokenStorageOpts } from './types' import { prepareContext, mockTemplate } from './context' import type { GqlContext } from './context' @@ -190,20 +189,9 @@ export default defineNuxtModule({ ...(typeof config.codegen !== 'boolean' && config.codegen) }).then(output => output.reduce>((acc, c) => ({ ...acc, [c.filename.split('.ts')[0]]: c.content }), {})) : ctx.clients!.reduce>((acc, k) => { - const entries: Record = {} - if (!clientDocs?.[k]?.length) { return acc } - for (const doc of clientDocs?.[k] || []) { - const definitions = parse(readFileSync(doc, 'utf-8'))?.definitions as (DefinitionNode & { name: NameNode })[] - - for (const op of definitions) { - const name: string = op?.name?.value - const operation = op.loc?.source.body.slice(op.loc.start, op.loc.end) || undefined - - if (name && operation) { entries[name] = operation } - } - } + const entries = extractGqlOperations(ctx?.clientDocs?.[k] || []) return { ...acc, [k]: mockTemplate(entries) } }, {}) @@ -244,6 +232,44 @@ export default defineNuxtModule({ addImportsDir(resolver.resolve('runtime/composables')) } + nuxt.hook('nitro:config', (nitro) => { + 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((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 + '}', + '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)', + '}', + 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)) + }] + }) + + nitro.plugins = nitro.plugins || [] + nitro.plugins.push(resolver.resolve('runtime/nitro')) + }) + const allowDocument = (f: string) => { const isSchema = f.match(/([^/]+)\.(gql|graphql)$/)?.[0]?.toLowerCase().includes('schema') diff --git a/src/runtime/nitro.ts b/src/runtime/nitro.ts new file mode 100644 index 0000000..75f92e3 --- /dev/null +++ b/src/runtime/nitro.ts @@ -0,0 +1,27 @@ +import { GraphQLClient } from 'graphql-request' +import type { GqlConfig } from '../../types' +// @ts-ignore +import { defineNitroPlugin } from '#internal/nitro' +// @ts-ignore +import GqlNitro from '#gql-nitro' + +export default defineNitroPlugin(() => { + const GqlConfig: GqlConfig['clients'] = GqlNitro!.config + + for (const [client, conf] of Object.entries(GqlConfig!)) { + const serverHeaders = (typeof conf?.headers?.serverOnly === 'object' && conf?.headers?.serverOnly) || undefined + if (conf?.headers?.serverOnly) { delete conf.headers.serverOnly } + + const tokenName = conf!.token!.name! + const tokenType = conf.token!.type! + const authToken = !tokenType ? conf?.token?.value : `${tokenType} ${conf?.token?.value}` + + const headers = { + ...conf?.headers, + ...serverHeaders, + ...(conf?.token?.value && { [tokenName]: authToken }) + } + + GqlNitro.clients[client] = new GraphQLClient(conf.host, { headers }) + } +}) diff --git a/src/utils.ts b/src/utils.ts index e1bc446..30e6089 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,7 @@ +import { readFileSync } from 'fs' +import { parse } from 'graphql' +import type { DefinitionNode, NameNode } from 'graphql' + export const mapDocsToClients = (documents: string[], clients: string[]) => { const mappedDocs = new Set() @@ -27,3 +31,20 @@ export const mapDocsToClients = (documents: string[], clients: string[]) => { return acc }, {} as Record) } + +export const extractGqlOperations = (docs: string[]): Record => { + const entries: Record = {} + + for (const doc of docs) { + const definitions = parse(readFileSync(doc, 'utf-8'))?.definitions as (DefinitionNode & { name: NameNode })[] + + for (const op of definitions) { + const name: string = op?.name?.value + const operation = op.loc?.source.body.slice(op.loc.start, op.loc.end) || undefined + + if (name && operation) { entries[name] = operation } + } + } + + return entries +}