|
import { PluginFunction, Types } from '@graphql-codegen/plugin-helpers'; |
|
import { concatAST, OperationDefinitionNode, visit } from 'graphql'; |
|
|
|
export interface PgqlPluginConfig { |
|
gqlImportPath?: string; // default 'src/lib/pgql.js' |
|
documentsImportPath?: string; // default './graphql' |
|
withTypes?: boolean; // default true |
|
} |
|
|
|
interface Operation { |
|
name: string; |
|
type: 'query' | 'mutation' | 'subscription'; |
|
documentName: string; |
|
variablesType: string; |
|
resultType: string; |
|
} |
|
|
|
class PgqlGenerator { |
|
private operations: Operation[] = []; |
|
private config: Required<PgqlPluginConfig>; |
|
|
|
constructor(config: PgqlPluginConfig, documents: Types.DocumentFile[]) { |
|
this.config = { |
|
gqlImportPath: 'src/lib/pgql.js', |
|
documentsImportPath: './graphql', |
|
withTypes: true, |
|
...config, |
|
}; |
|
|
|
this.extractOperations(documents); |
|
} |
|
|
|
/* ---------- 1. Scan all GraphQL operations ---------- */ |
|
private extractOperations(documents: Types.DocumentFile[]): void { |
|
const allAst = concatAST(documents.map((d) => d.document!)); |
|
|
|
visit(allAst, { |
|
OperationDefinition: (node: OperationDefinitionNode) => { |
|
if (!node.name) return; |
|
|
|
const opSuffix = |
|
node.operation === 'query' |
|
? 'Query' |
|
: node.operation === 'mutation' |
|
? 'Mutation' |
|
: 'Subscription'; |
|
|
|
const operationName = node.name.value; |
|
const capitalizedName = operationName.charAt(0).toUpperCase() + operationName.slice(1); |
|
const documentName = `${capitalizedName}Document`; |
|
|
|
this.operations.push({ |
|
name: operationName, |
|
type: node.operation, |
|
documentName, |
|
variablesType: `${capitalizedName}${opSuffix}Variables`, |
|
resultType: `${capitalizedName}${opSuffix}`, |
|
}); |
|
}, |
|
}); |
|
} |
|
|
|
/* ---------- 2. Generate imports ---------- */ |
|
private getImports(): string { |
|
const imports: string[] = []; |
|
|
|
imports.push( |
|
`import { gqlQueryAs, GqlRequestIdentifier, GqlQueryAsOptions } from '${this.config.gqlImportPath}';`, |
|
`import { ExecutionResult } from 'postgraphile/graphql';` |
|
); |
|
|
|
return imports.join('\n'); |
|
} |
|
|
|
/* ---------- 3. One property per operation ---------- */ |
|
private buildSdkMethod(op: Operation): string { |
|
const { name, documentName, variablesType, resultType } = op; |
|
const methodName = name; |
|
|
|
// Does this op actually accept variables? |
|
const needsVars = !['{}', 'Exact<{}>', variablesType.replace(/Variables$/, '')].includes( |
|
variablesType |
|
); |
|
|
|
const paramsWithVars = `( |
|
requestContext: GqlRequestIdentifier, |
|
variables: ${variablesType}, |
|
operationName?: string, |
|
options?: GqlQueryAsOptions |
|
): Promise<ExecutionResult<${resultType}>>`; |
|
|
|
const paramsWithoutVars = `( |
|
requestContext: GqlRequestIdentifier, |
|
operationName?: string, |
|
options?: GqlQueryAsOptions |
|
): Promise<ExecutionResult<${resultType}>>`; |
|
|
|
const params = needsVars ? paramsWithVars : paramsWithoutVars; |
|
const varsArg = needsVars ? 'variables' : 'undefined'; |
|
|
|
return `${methodName}: async ${params} => { |
|
return gqlQueryAs( |
|
requestContext, |
|
${documentName}, |
|
${varsArg}, |
|
operationName || '${name}', |
|
options |
|
); |
|
}`; |
|
} |
|
|
|
/* ---------- 4. Collect everything into sdk ---------- */ |
|
private generateSdkObject(): string { |
|
if (!this.operations.length) return '// No GraphQL operations found'; |
|
|
|
const methods = this.operations.map((o) => this.buildSdkMethod(o)).join(',\n\n'); |
|
return `export const sdk = { |
|
${methods} |
|
} as const; |
|
|
|
export type GqlSdk = typeof sdk;`; |
|
} |
|
|
|
/* ---------- 5. Public entry ---------- */ |
|
public generate(): string { |
|
return [ |
|
'// Generated GraphQL SDK (auto-generated – do not edit)', |
|
'', |
|
this.getImports(), |
|
'', |
|
this.generateSdkObject(), |
|
].join('\n'); |
|
} |
|
} |
|
|
|
/* ---------- Plugin entry-point ---------- */ |
|
export const plugin: PluginFunction<PgqlPluginConfig> = (_schema, documents, config = {}) => { |
|
const generator = new PgqlGenerator(config, documents); |
|
return generator.generate(); |
|
}; |