Skip to content

Commit

Permalink
feat(typescript-effect): make plugin output mode configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
vecerek committed May 2, 2024
1 parent b5348b9 commit 3141c9a
Show file tree
Hide file tree
Showing 8 changed files with 1,282 additions and 130 deletions.
44 changes: 44 additions & 0 deletions packages/plugins/typescript/effect/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { RawClientSideBasePluginConfig } from '@graphql-codegen/visitor-plugin-common';

/**
* @description This plugin generates a fully-typed, ready-to-use SDK written with [`effect`](https://effect.website).
*/

export interface RawEffectPluginConfig extends RawClientSideBasePluginConfig {
/**
* @description Allows you to override the output mode of the `typescript-effect` codegen plugin. To generate the GraphQL client in a separate file, use the `client-only` and `operations-only` modes, respectively.
* @default 'mixed'
*
* @exampleMarkdown
* ```ts filename="codegen.ts"
* import type { CodegenConfig } from '@graphql-codegen/cli';
*
* const config: CodegenConfig = {
* // ...
* generates: {
* 'path/to/client.ts': {
* plugins: ['typescript-effect'],
* config: {
* mode: 'client-only'
* },
* },
* 'path/to/sdk.ts': {
* plugins: ['typescript', 'typescript-operations', 'typescript-effect'],
* config: {
* mode: 'operations-only',
* relativeClientImportPath: './client.js',
* },
* },
* },
* };
* export default config;
* ```
*/
mode?: 'client-only' | 'operations-only' | 'mixed';

/**
* @description Specifies the relative import path of the graphql client file. A required config when the mode is set to `operations-only`, ignored in all other cases.
* @default undefined
*/
relativeClientImportPath?: string;
}
25 changes: 17 additions & 8 deletions packages/plugins/typescript/effect/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import {
type PluginValidateFn,
type Types,
} from '@graphql-codegen/plugin-helpers';
import {
type LoadedFragment,
type RawClientSideBasePluginConfig,
} from '@graphql-codegen/visitor-plugin-common';
import type { LoadedFragment } from '@graphql-codegen/visitor-plugin-common';
import type { RawEffectPluginConfig } from './config.js';
import { EffectVisitor } from './visitor.js';

export const plugin: PluginFunction<{}> = (
export const plugin: PluginFunction<RawEffectPluginConfig> = (
schema: GraphQLSchema,
documents: Types.DocumentFile[],
config: RawClientSideBasePluginConfig,
config: RawEffectPluginConfig,
) => {
if (config.mode === 'client-only') return EffectVisitor.clientContent();

const allAst = concatAST(documents.map(v => v.document));
const allFragments: LoadedFragment[] = [
...(
Expand Down Expand Up @@ -46,6 +46,15 @@ export const plugin: PluginFunction<{}> = (
export const validate: PluginValidateFn<any> = async (
schema: GraphQLSchema,
documents: Types.DocumentFile[],
config: RawClientSideBasePluginConfig,
config: RawEffectPluginConfig,
outputFile: string,
) => {};
) => {
if (
config.mode === 'operations-only' &&
(!config.relativeClientImportPath || config.relativeClientImportPath.length === 0)
) {
throw new Error(
`Plugin "typescript-effect" requires the "relativeClientImportPath" configuration option to be a non-empty string when "mode" is set to "operations-only"!`,
);
}
};
110 changes: 78 additions & 32 deletions packages/plugins/typescript/effect/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@ import {
type ClientSideBasePluginConfig,
ClientSideBaseVisitor,
DocumentMode,
getConfigValue,
LoadedFragment,
type RawClientSideBasePluginConfig,
} from '@graphql-codegen/visitor-plugin-common';
import type { RawEffectPluginConfig } from './config.js';

export interface EffectPluginConfig extends ClientSideBasePluginConfig {}
export type EffectPluginConfig = ClientSideBasePluginConfig &
(
| {
mode?: 'client-only' | 'mixed';
}
| {
mode: 'operations-only';
relativeClientImportPath: string;
}
);

const clientCode = `export type GraphQLSuccessResponse<A = any> = Pick<
Http.response.ClientResponse,
Expand Down Expand Up @@ -74,23 +84,21 @@ export class GraphQLClient extends Context.Tag('GraphQLClient')<
}
}`;

const additionalStaticContent = (documentMode: DocumentMode) => `
const additionalStaticContent = (config: EffectPluginConfig) => `
export type GraphQLOperationOptions = {
preferredOpName?: string;
};
type GraphQLOperationArgs = {
document: ${documentMode === DocumentMode.string ? 'string' : 'DocumentNode'};
document: ${config.documentMode === DocumentMode.string ? 'string' : 'DocumentNode'};
fallbackOperationName: string;
};
${clientCode}
${config.mode === 'operations-only' ? '' : `\n${clientCode}\n`}
const makeGraphQLOperation =
<Vars, Data>({ document, fallbackOperationName }: GraphQLOperationArgs) =>
(variables: Vars, opts?: GraphQLOperationOptions) => {
const operationName = opts?.preferredOpName ?? fallbackOperationName;
const query = ${documentMode === DocumentMode.string ? 'document' : 'print(document)'};
const query = ${config.documentMode === DocumentMode.string ? 'document' : 'print(document)'};
return Effect.flatMap(GraphQLClient, client =>
Http.request.post('').pipe(
Expand All @@ -107,7 +115,7 @@ const makeGraphQLOperation =
`;

export class EffectVisitor extends ClientSideBaseVisitor<
RawClientSideBasePluginConfig,
RawEffectPluginConfig,
EffectPluginConfig
> {
private _externalImportPrefix: string;
Expand All @@ -122,9 +130,12 @@ export class EffectVisitor extends ClientSideBaseVisitor<
constructor(
schema: GraphQLSchema,
fragments: LoadedFragment[],
rawConfig: RawClientSideBasePluginConfig,
rawConfig: RawEffectPluginConfig,
) {
super(schema, fragments, rawConfig, {});
super(schema, fragments, rawConfig, {
mode: getConfigValue(rawConfig.mode, 'mixed'),
relativeClientImportPath: getConfigValue(rawConfig.relativeClientImportPath, undefined),
});

autoBind(this);

Expand All @@ -141,31 +152,61 @@ export class EffectVisitor extends ClientSideBaseVisitor<
`import ${type === 'type' ? 'type ' : ''}* as ${namespace} from '${from}';`;

[
createNamedImport(
[
['Context', 'value'],
['Data', 'value'],
['Effect', 'value'],
['Layer', 'value'],
],
'effect',
),
createNamedImport(
[
['DocumentNode', 'type'],
['ExecutionResult', 'type'],
['print', 'value'],
],
'graphql',
),
this.config.mode === 'operations-only'
? createNamedImport([['Effect', 'value']], 'effect')
: createNamedImport(
[
['Context', 'value'],
['Data', 'value'],
['Effect', 'value'],
['Layer', 'value'],
],
'effect',
),
this.config.mode === 'operations-only'
? createNamedImport(
[
['DocumentNode', 'type'],
['print', 'value'],
],
'graphql',
)
: createNamedImport(
[
['DocumentNode', 'type'],
['ExecutionResult', 'type'],
['print', 'value'],
],
'graphql',
),
createNamesaceImport('Http', '@effect/platform/HttpClient'),
].forEach(_ => this._additionalImports.push(_));
this.config.mode === 'operations-only'
? createNamedImport(
[
['GraphQLClient', 'value'],
['GraphQLSuccessResponse', 'type'],
],
this.config.relativeClientImportPath,
)
: [],
]
.flat()
.forEach(_ => this._additionalImports.push(_));

this._externalImportPrefix = this.config.importOperationTypesFrom
? `${this.config.importOperationTypesFrom}.`
: '';
}

static clientContent(): string {
return `import { Context, Data, Effect, Layer } from 'effect';
import type { ExecutionResult } from 'graphql';
import * as Http from '@effect/platform/HttpClient';
${clientCode}
`;
}

public OperationDefinition(node: OperationDefinitionNode) {
const operationName = node.name?.value;

Expand Down Expand Up @@ -210,6 +251,13 @@ export class EffectVisitor extends ClientSideBaseVisitor<
}

public get sdkContent(): string {
if (this.config.mode === 'client-only') {
// absurd code path
throw new Error(
`Plugin "typescript-effect": unexpected call to "sdkContent". Please, report this issue in https://github.com/dotansimha/graphql-code-generator-community.`,
);
}

const allPossibleOperations = this._operationsToInclude.map(
({ node, documentVariableName, operationResultType, operationVariablesTypes }) => {
const operationName = node.name.value;
Expand All @@ -221,8 +269,6 @@ export class EffectVisitor extends ClientSideBaseVisitor<
},
);

return `${additionalStaticContent(this.config.documentMode)}\n${allPossibleOperations.join(
'\n',
)}\n`;
return `${additionalStaticContent(this.config)}\n${allPossibleOperations.join('\n')}\n`;
}
}
Loading

0 comments on commit 3141c9a

Please sign in to comment.