From 8f82a027ca175a25beff782ab8816ceb7fab8189 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Sun, 11 Dec 2022 16:36:20 +0900 Subject: [PATCH 01/15] Add document transform. Document transform changes documents before executing plugins. --- packages/graphql-codegen-cli/src/codegen.ts | 10 +++ .../graphql-codegen-cli/tests/codegen.spec.ts | 77 +++++++++++++++++++ .../document-transform-config.js | 19 +++++ .../document-transform-context.js | 9 +++ .../document-transform-validation.js | 9 +++ .../custom-plugins/document-transform.js | 19 +++++ packages/graphql-codegen-core/src/codegen.ts | 24 ++++-- .../src/transform-document.ts | 65 ++++++++++++++++ packages/presets/client/src/index.ts | 4 + .../presets/gql-tag-operations/src/index.ts | 4 + packages/presets/graphql-modules/src/index.ts | 2 + packages/utils/plugins-helpers/src/types.ts | 16 ++++ 12 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js create mode 100644 packages/graphql-codegen-core/src/transform-document.ts diff --git a/packages/graphql-codegen-cli/src/codegen.ts b/packages/graphql-codegen-cli/src/codegen.ts index 6baad3c78a8..002f68131e6 100644 --- a/packages/graphql-codegen-cli/src/codegen.ts +++ b/packages/graphql-codegen-cli/src/codegen.ts @@ -321,6 +321,14 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config, filename), }; + const documentTransformPlugins = await Promise.all( + normalizeConfig(outputConfig.documentTransformPlugins).map(async pluginConfig => { + const name = Object.keys(pluginConfig)[0]; + const plugin = await getPluginByName(name, pluginLoader); + return { [name]: { plugin, config: Object.values(pluginConfig)[0] } }; + }) + ); + const outputs: Types.GenerateOptions[] = preset ? await context.profiler.run( async () => @@ -335,6 +343,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, + documentTransformPlugins, }), `Build Generates Section: ${filename}` ) @@ -349,6 +358,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, + documentTransformPlugins, }, ]; diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index c09420af63f..71c0ae5c4bc 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1086,4 +1086,81 @@ describe('Codegen Executor', () => { const output = await executeCodegen(config); expect(output[0].content).toContain('DocumentNode'); }); + + describe('Document Transform', () => { + it('Should transform documents', async () => { + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransformPlugins: ['./tests/custom-plugins/document-transform.js'], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('export type BarQuery'); + }); + + it('Should accept config in per-plugin', async () => { + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query root { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransformPlugins: [ + { + './tests/custom-plugins/document-transform-config.js': { + queryName: 'test', + }, + }, + ], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('export type TestQuery'); + }); + + it('Should allow plugin context to be accessed and modified', async () => { + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query root { f }`, + generates: { + 'out1.ts': { + documentTransformPlugins: ['./tests/custom-plugins/document-transform-context.js'], + plugins: ['./tests/custom-plugins/document-transform-context.js'], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('Hello world!'); + }); + + it('Should execute validation before transform documents and throw when it fails', async () => { + try { + await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + generates: { + 'out1.ts': { + plugins: ['typescript'], + documentTransformPlugins: ['./tests/custom-plugins/document-transform-validation.js'], + }, + }, + }); + throw new Error(SHOULD_NOT_THROW_STRING); + } catch (e) { + expect(e.message).not.toBe(SHOULD_NOT_THROW_STRING); + expect(e.message).toContain( + 'Document transform "./tests/custom-plugins/document-transform-validation.js" validation failed' + ); + expect(e.message).toContain('Invalid!'); + } + }); + }); }); diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js new file mode 100644 index 00000000000..543dc938846 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js @@ -0,0 +1,19 @@ +module.exports = { + plugin: () => {}, // Nothing to do + transformDocuments: (_schema, documents, config) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + name: { kind: 'Name', value: config.queryName }, + }, + ], + }, + }, + ]; + return newDocuments; + }, +}; diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js new file mode 100644 index 00000000000..78f93d5e186 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js @@ -0,0 +1,9 @@ +module.exports = { + plugin: (_schema, _documents, _config, { pluginContext }) => { + return `Hello ${pluginContext.myPluginInfo}!`; + }, + transformDocuments: (_schema, documents, _config, { pluginContext }) => { + pluginContext.myPluginInfo = 'world'; + return documents; + }, +}; diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js new file mode 100644 index 00000000000..7cf73aa5758 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js @@ -0,0 +1,9 @@ +module.exports = { + plugin: () => {}, // Nothing to do + transformDocuments: (_schema, documents) => { + return documents; + }, + validateBeforeTransformDocuments: () => { + throw new Error('Invalid!'); + }, +}; diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js new file mode 100644 index 00000000000..5c87acdd424 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js @@ -0,0 +1,19 @@ +module.exports = { + plugin: () => {}, // Nothing to do + transformDocuments: (_schema, documents) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + name: { kind: 'Name', value: 'bar' }, + }, + ], + }, + }, + ]; + return newDocuments; + }, +}; diff --git a/packages/graphql-codegen-core/src/codegen.ts b/packages/graphql-codegen-core/src/codegen.ts index 36f97ca7aa2..a1521c9d8bf 100644 --- a/packages/graphql-codegen-core/src/codegen.ts +++ b/packages/graphql-codegen-core/src/codegen.ts @@ -20,6 +20,7 @@ import { shouldValidateDocumentsAgainstSchema, shouldValidateDuplicateDocuments, } from './utils.js'; +import { transformDocuments } from './transform-document.js'; export async function codegen(options: Types.GenerateOptions): Promise { const documents = options.documents || []; @@ -72,7 +73,18 @@ export async function codegen(options: Types.GenerateOptions): Promise { const schemaDocumentNode = mergeNeeded || !options.schema ? getCachedDocumentNodeFromSchema(schemaInstance) : options.schema; - if (schemaInstance && documents.length > 0 && shouldValidateDocumentsAgainstSchema(skipDocumentsValidation)) { + const transformedDocuments = await transformDocuments({ + ...options, + schema: schemaDocumentNode, + schemaAst: schemaInstance, + profiler, + }); + + if ( + schemaInstance && + transformedDocuments.length > 0 && + shouldValidateDocumentsAgainstSchema(skipDocumentsValidation) + ) { const ignored = ['NoUnusedFragments', 'NoUnusedVariables', 'KnownDirectives']; if (typeof skipDocumentsValidation === 'object' && skipDocumentsValidation.ignoreRules) { ignored.push(...asArray(skipDocumentsValidation.ignoreRules)); @@ -88,18 +100,18 @@ export async function codegen(options: Types.GenerateOptions): Promise { const rules = specifiedRules.filter(rule => !ignored.some(ignoredRule => rule.name.startsWith(ignoredRule))); const schemaHash = extractHashFromSchema(schemaInstance); - if (!schemaHash || !options.cache || documents.some(d => typeof d.hash !== 'string')) { + if (!schemaHash || !options.cache || transformedDocuments.some(d => typeof d.hash !== 'string')) { return Promise.resolve( validateGraphQlDocuments( schemaInstance, - [...documents.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], + [...transformedDocuments.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], rules ) ); } const cacheKey = [schemaHash] - .concat(documents.map(doc => doc.hash)) + .concat(transformedDocuments.map(doc => doc.hash)) .concat(JSON.stringify(fragments)) .join(','); @@ -107,7 +119,7 @@ export async function codegen(options: Types.GenerateOptions): Promise { Promise.resolve( validateGraphQlDocuments( schemaInstance, - [...documents.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], + [...transformedDocuments.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], rules ) ) @@ -148,7 +160,7 @@ export async function codegen(options: Types.GenerateOptions): Promise { parentConfig: options.config, schema: schemaDocumentNode, schemaAst: schemaInstance, - documents: options.documents, + documents: transformedDocuments, outputFilename: options.filename, allPlugins: options.plugins, skipDocumentsValidation: options.skipDocumentsValidation, diff --git a/packages/graphql-codegen-core/src/transform-document.ts b/packages/graphql-codegen-core/src/transform-document.ts new file mode 100644 index 00000000000..d9d9ae6fcb9 --- /dev/null +++ b/packages/graphql-codegen-core/src/transform-document.ts @@ -0,0 +1,65 @@ +import { createNoopProfiler, Types } from '@graphql-codegen/plugin-helpers'; +import { buildASTSchema, GraphQLSchema } from 'graphql'; + +export async function transformDocuments(options: Types.GenerateOptions): Promise { + const documentTransformPlugins = options.documentTransformPlugins || []; + let documents = options.documents; + if (documentTransformPlugins.length === 0) { + return documents; + } + + const profiler = options.profiler ?? createNoopProfiler(); + const outputSchema: GraphQLSchema = options.schemaAst || buildASTSchema(options.schema, options.config as any); + + for (const documentTransformPlugin of documentTransformPlugins) { + const name = Object.keys(documentTransformPlugin)[0]; + const transformPlugin = documentTransformPlugin[name].plugin; + const pluginConfig = documentTransformPlugin[name].config; + + const config = + typeof pluginConfig !== 'object' + ? pluginConfig + : { + ...options.config, + ...pluginConfig, + }; + + if ( + transformPlugin.validateBeforeTransformDocuments && + typeof transformPlugin.validateBeforeTransformDocuments === 'function' + ) { + try { + await profiler.run( + async () => + transformPlugin.validateBeforeTransformDocuments( + outputSchema, + options.documents, + config, + options.filename, + options.plugins, + options.pluginContext + ), + `Document transform ${name} validate` + ); + } catch (e) { + throw new Error( + `Document transform "${name}" validation failed: \n + ${e.message} + ` + ); + } + } + + if (transformPlugin.transformDocuments && typeof transformPlugin.transformDocuments === 'function') { + await profiler.run(async () => { + documents = await transformPlugin.transformDocuments(outputSchema, documents, config, { + outputFile: options.filename, + allPlugins: options.plugins, + pluginContext: options.pluginContext, + }); + }, `Document transform ${name} execution`); + } + } + + return documents; +} diff --git a/packages/presets/client/src/index.ts b/packages/presets/client/src/index.ts index e173326f836..1a67c0a725d 100644 --- a/packages/presets/client/src/index.ts +++ b/packages/presets/client/src/index.ts @@ -167,6 +167,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -190,6 +191,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -204,6 +206,7 @@ export const preset: Types.OutputPreset = { ...forwardedConfig, }, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, { filename: `${options.baseOutputDir}gql${gqlArtifactFileExtension}`, @@ -215,6 +218,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'graphql', }, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, ...(fragmentMaskingFileGenerateConfig ? [fragmentMaskingFileGenerateConfig] : []), ...(indexFileGenerateConfig ? [indexFileGenerateConfig] : []), diff --git a/packages/presets/gql-tag-operations/src/index.ts b/packages/presets/gql-tag-operations/src/index.ts index 4aff0f2f37a..762b21877fa 100644 --- a/packages/presets/gql-tag-operations/src/index.ts +++ b/packages/presets/gql-tag-operations/src/index.ts @@ -207,6 +207,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -230,6 +231,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -241,6 +243,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, { filename: `${options.baseOutputDir}/gql${gqlArtifactFileExtension}`, @@ -253,6 +256,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'gql', }, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, ...(fragmentMaskingFileGenerateConfig ? [fragmentMaskingFileGenerateConfig] : []), ...(indexFileGenerateConfig ? [indexFileGenerateConfig] : []), diff --git a/packages/presets/graphql-modules/src/index.ts b/packages/presets/graphql-modules/src/index.ts index c85fd262789..8ab10f24f8c 100644 --- a/packages/presets/graphql-modules/src/index.ts +++ b/packages/presets/graphql-modules/src/index.ts @@ -69,6 +69,7 @@ export const preset: Types.OutputPreset = { enumsAsTypes: true, }, schemaAst: options.schemaAst!, + documentTransformPlugins: options.documentTransformPlugins, }; const baseTypesFilename = baseTypesPath.replace(/\.(js|ts|d.ts)$/, ''); @@ -120,6 +121,7 @@ export const preset: Types.OutputPreset = { }, config: options.config, schemaAst: options.schemaAst, + documentTransformPlugins: options.documentTransformPlugins, }; }); diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 8ec12e84a0a..79db5a75b4b 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -19,6 +19,7 @@ export namespace Types { pluginContext?: { [key: string]: any }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; + documentTransformPlugins?: ConfiguredDocumentTransformPlugin[]; } export type FileOutput = { @@ -232,6 +233,9 @@ export namespace Types { | 'import-types'; export type PresetNames = `${PresetNamesBase}-preset` | PresetNamesBase; + export interface ConfiguredDocumentTransformPlugin { + [name: string]: { config: PluginConfig; plugin: CodegenPlugin }; + } /** * @additionalProperties false */ @@ -316,6 +320,11 @@ export namespace Types { * For more details: https://graphql-code-generator.com/docs/config-reference/lifecycle-hooks */ hooks?: Partial; + /** + * @description Specifies plugins that have the transformDocuments function. + * Document transform plugin changes documents before executing plugins. + */ + documentTransformPlugins?: OutputConfig[]; } /* Output Builder Preset */ @@ -340,6 +349,7 @@ export namespace Types { }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; + documentTransformPlugins: ConfiguredDocumentTransformPlugin[]; }; export type OutputPreset = { @@ -628,4 +638,10 @@ export interface CodegenPlugin { plugin: PluginFunction; addToSchema?: AddToSchemaResult | ((config: T) => AddToSchemaResult); validate?: PluginValidateFn; + transformDocuments?: TransformDocumentsFunction; + validateBeforeTransformDocuments?: PluginValidateFn; } + +export type TransformDocumentsFunction = ( + ...params: Parameters> +) => Types.Promisable; From a0daebe06f1e929ad643f04360b1eb8add2408e4 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Mon, 12 Dec 2022 19:32:08 +0900 Subject: [PATCH 02/15] Create changesets --- .changeset/short-toes-relax.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/short-toes-relax.md diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md new file mode 100644 index 00000000000..ef747433864 --- /dev/null +++ b/.changeset/short-toes-relax.md @@ -0,0 +1,10 @@ +--- +"@graphql-codegen/cli": minor +"@graphql-codegen/core": minor +"@graphql-codegen/plugin-helpers": minor +"@graphql-codegen/client-preset": minor +"@graphql-codegen/gql-tag-operations-preset": minor +"@graphql-codegen/graphql-modules-preset": minor +--- + +Add a feature to transform documents From c9b298fd5f5d8c8d54c59ccec574e47d2737dd50 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Wed, 21 Dec 2022 16:10:55 +0900 Subject: [PATCH 03/15] Rename `documentTransformPlugins` to `documentTransforms` --- packages/graphql-codegen-cli/src/codegen.ts | 8 ++++---- packages/graphql-codegen-cli/tests/codegen.spec.ts | 8 ++++---- .../graphql-codegen-core/src/transform-document.ts | 12 ++++++------ packages/presets/client/src/index.ts | 8 ++++---- packages/presets/gql-tag-operations/src/index.ts | 8 ++++---- packages/presets/graphql-modules/src/index.ts | 4 ++-- packages/utils/plugins-helpers/src/types.ts | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/graphql-codegen-cli/src/codegen.ts b/packages/graphql-codegen-cli/src/codegen.ts index 002f68131e6..b43b0334275 100644 --- a/packages/graphql-codegen-cli/src/codegen.ts +++ b/packages/graphql-codegen-cli/src/codegen.ts @@ -321,8 +321,8 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config, filename), }; - const documentTransformPlugins = await Promise.all( - normalizeConfig(outputConfig.documentTransformPlugins).map(async pluginConfig => { + const documentTransforms = await Promise.all( + normalizeConfig(outputConfig.documentTransforms).map(async pluginConfig => { const name = Object.keys(pluginConfig)[0]; const plugin = await getPluginByName(name, pluginLoader); return { [name]: { plugin, config: Object.values(pluginConfig)[0] } }; @@ -343,7 +343,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, - documentTransformPlugins, + documentTransforms, }), `Build Generates Section: ${filename}` ) @@ -358,7 +358,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, - documentTransformPlugins, + documentTransforms, }, ]; diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index 71c0ae5c4bc..e1bbbc92f0b 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1095,7 +1095,7 @@ describe('Codegen Executor', () => { generates: { 'out1.ts': { plugins: ['typescript', 'typescript-operations'], - documentTransformPlugins: ['./tests/custom-plugins/document-transform.js'], + documentTransforms: ['./tests/custom-plugins/document-transform.js'], }, }, }); @@ -1111,7 +1111,7 @@ describe('Codegen Executor', () => { generates: { 'out1.ts': { plugins: ['typescript', 'typescript-operations'], - documentTransformPlugins: [ + documentTransforms: [ { './tests/custom-plugins/document-transform-config.js': { queryName: 'test', @@ -1132,7 +1132,7 @@ describe('Codegen Executor', () => { documents: `query root { f }`, generates: { 'out1.ts': { - documentTransformPlugins: ['./tests/custom-plugins/document-transform-context.js'], + documentTransforms: ['./tests/custom-plugins/document-transform-context.js'], plugins: ['./tests/custom-plugins/document-transform-context.js'], }, }, @@ -1149,7 +1149,7 @@ describe('Codegen Executor', () => { generates: { 'out1.ts': { plugins: ['typescript'], - documentTransformPlugins: ['./tests/custom-plugins/document-transform-validation.js'], + documentTransforms: ['./tests/custom-plugins/document-transform-validation.js'], }, }, }); diff --git a/packages/graphql-codegen-core/src/transform-document.ts b/packages/graphql-codegen-core/src/transform-document.ts index d9d9ae6fcb9..01a8540f320 100644 --- a/packages/graphql-codegen-core/src/transform-document.ts +++ b/packages/graphql-codegen-core/src/transform-document.ts @@ -2,19 +2,19 @@ import { createNoopProfiler, Types } from '@graphql-codegen/plugin-helpers'; import { buildASTSchema, GraphQLSchema } from 'graphql'; export async function transformDocuments(options: Types.GenerateOptions): Promise { - const documentTransformPlugins = options.documentTransformPlugins || []; + const documentTransforms = options.documentTransforms || []; let documents = options.documents; - if (documentTransformPlugins.length === 0) { + if (documentTransforms.length === 0) { return documents; } const profiler = options.profiler ?? createNoopProfiler(); const outputSchema: GraphQLSchema = options.schemaAst || buildASTSchema(options.schema, options.config as any); - for (const documentTransformPlugin of documentTransformPlugins) { - const name = Object.keys(documentTransformPlugin)[0]; - const transformPlugin = documentTransformPlugin[name].plugin; - const pluginConfig = documentTransformPlugin[name].config; + for (const documentTransform of documentTransforms) { + const name = Object.keys(documentTransform)[0]; + const transformPlugin = documentTransform[name].plugin; + const pluginConfig = documentTransform[name].config; const config = typeof pluginConfig !== 'object' diff --git a/packages/presets/client/src/index.ts b/packages/presets/client/src/index.ts index 589f28a51fe..37ca1b73781 100644 --- a/packages/presets/client/src/index.ts +++ b/packages/presets/client/src/index.ts @@ -168,7 +168,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -192,7 +192,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -207,7 +207,7 @@ export const preset: Types.OutputPreset = { ...forwardedConfig, }, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, { filename: `${options.baseOutputDir}gql${gqlArtifactFileExtension}`, @@ -219,7 +219,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'graphql', }, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, ...(fragmentMaskingFileGenerateConfig ? [fragmentMaskingFileGenerateConfig] : []), ...(indexFileGenerateConfig ? [indexFileGenerateConfig] : []), diff --git a/packages/presets/gql-tag-operations/src/index.ts b/packages/presets/gql-tag-operations/src/index.ts index 762b21877fa..a8283cc277f 100644 --- a/packages/presets/gql-tag-operations/src/index.ts +++ b/packages/presets/gql-tag-operations/src/index.ts @@ -207,7 +207,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -231,7 +231,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -243,7 +243,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, { filename: `${options.baseOutputDir}/gql${gqlArtifactFileExtension}`, @@ -256,7 +256,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'gql', }, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, ...(fragmentMaskingFileGenerateConfig ? [fragmentMaskingFileGenerateConfig] : []), ...(indexFileGenerateConfig ? [indexFileGenerateConfig] : []), diff --git a/packages/presets/graphql-modules/src/index.ts b/packages/presets/graphql-modules/src/index.ts index 8ab10f24f8c..bfa77492f5c 100644 --- a/packages/presets/graphql-modules/src/index.ts +++ b/packages/presets/graphql-modules/src/index.ts @@ -69,7 +69,7 @@ export const preset: Types.OutputPreset = { enumsAsTypes: true, }, schemaAst: options.schemaAst!, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; const baseTypesFilename = baseTypesPath.replace(/\.(js|ts|d.ts)$/, ''); @@ -121,7 +121,7 @@ export const preset: Types.OutputPreset = { }, config: options.config, schemaAst: options.schemaAst, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; }); diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index e2589e99b6a..b58fcec7c79 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -19,7 +19,7 @@ export namespace Types { pluginContext?: { [key: string]: any }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; - documentTransformPlugins?: ConfiguredDocumentTransformPlugin[]; + documentTransforms?: ConfiguredDocumentTransform[]; } export type FileOutput = { @@ -233,7 +233,7 @@ export namespace Types { | 'import-types'; export type PresetNames = `${PresetNamesBase}-preset` | PresetNamesBase; - export interface ConfiguredDocumentTransformPlugin { + export interface ConfiguredDocumentTransform { [name: string]: { config: PluginConfig; plugin: CodegenPlugin }; } /** @@ -324,7 +324,7 @@ export namespace Types { * @description Specifies plugins that have the transformDocuments function. * Document transform plugin changes documents before executing plugins. */ - documentTransformPlugins?: OutputConfig[]; + documentTransforms?: OutputConfig[]; } /* Output Builder Preset */ @@ -349,7 +349,7 @@ export namespace Types { }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; - documentTransformPlugins: ConfiguredDocumentTransformPlugin[]; + documentTransforms: ConfiguredDocumentTransform[]; }; export type OutputPreset = { From 175f43791cacdb6149d325b75e4fbf2917382d17 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Wed, 21 Dec 2022 23:24:22 +0900 Subject: [PATCH 04/15] Add more information to changesets --- .changeset/short-toes-relax.md | 62 +++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md index ef747433864..ac835119187 100644 --- a/.changeset/short-toes-relax.md +++ b/.changeset/short-toes-relax.md @@ -7,4 +7,64 @@ "@graphql-codegen/graphql-modules-preset": minor --- -Add a feature to transform documents +Add a feature to transform documents. + +Plugin will have the following functions: +```my-plugin.js +module.exports = { + plugin: () => { + return 'hello' + }, + transformDocuments: (_schema, documents) => { + // Make some changes to the documents + return documents; + }, + validateBeforeTransformDocuments: () => { + // Raises an error if necessary + }, +}; +``` + +Use it as follows: + +```ts +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransformPlugins: ['./my-plugin.js'], + plugins: ['./my-plugin.js'] + } + } +} +export default config +``` + +For example, to remove a `@localOnlyDirective` directive from documents: + +```my-plugin.js +const { visit, print } = require('graphql') + +module.exports = { + plugin(schema, documents, config) { + // Output `documents` as an example. + return documents.map(documentFile => `${print(documentFile.document)}`).join('\n') + }, + transformDocuments(schema, documents, config) { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Directive: { + leave(node) { + if (node.name.value === 'localOnlyDirective') return null + } + } + }) + return documentFile + }) + } +} +``` From 5a1e155b6cb3f60a47626a4abd8090c34ab2c8b3 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Thu, 22 Dec 2022 00:33:45 +0900 Subject: [PATCH 05/15] Add document-transform.mdx --- .../src/pages/docs/custom-codegen/_meta.json | 1 + .../custom-codegen/document-transform.mdx | 117 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 website/src/pages/docs/custom-codegen/document-transform.mdx diff --git a/website/src/pages/docs/custom-codegen/_meta.json b/website/src/pages/docs/custom-codegen/_meta.json index dce618ba955..9a93ef04b6f 100644 --- a/website/src/pages/docs/custom-codegen/_meta.json +++ b/website/src/pages/docs/custom-codegen/_meta.json @@ -4,5 +4,6 @@ "validate-configuration": "Validate Configuration", "extend-schema": "Extend Schema", "using-visitor": "Using Visitor Pattern", + "document-transform": "Document Transform", "contributing": "Contributing" } diff --git a/website/src/pages/docs/custom-codegen/document-transform.mdx b/website/src/pages/docs/custom-codegen/document-transform.mdx new file mode 100644 index 00000000000..b316d0c1c75 --- /dev/null +++ b/website/src/pages/docs/custom-codegen/document-transform.mdx @@ -0,0 +1,117 @@ +import { Callout } from '@theguild/components' + +# Document Transform + +## Basic Usage + +Each plugin can also provide a function `transformDocuments` for transforming `documents`: + +```js {5-14} +const { visit } = require('graphql') + +module.exports = { + plugin() {}, // Nothing to do + transformDocuments(schema, documents, config) { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Field(node) { + // This function triggered per each field + } + }) + return documentFile + }) + } +} +``` + +You can specify the plugin with `documentTransforms` field in your config: + +```ts {9} +import { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'http://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: ['./my-transform.js'], + plugins: [] + } + } +} + +export default config +``` + + + If you specify it with only the `plugins` field and not with the `documentTransforms` field, the `transformDocuments` + function will not be executed. + + +## Example + +For example, let's remove a `@loacalOnlyDirective` directive from documents: + +```js +const { visit, print } = require('graphql') + +module.exports = { + plugin(schema, documents, config) { + // Output `documents` as an example. + return documents.map(documentFile => `${print(documentFile.document)}`).join('\n') + }, + transformDocuments(schema, documents, config) { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Directive: { + leave(node) { + if (node.name.value === 'localOnlyDirective') return null + } + } + }) + return documentFile + }) + } +} +``` + +If you want to execute the `plugin` function as above, you specify it with the `plugins` field as well as the `documentTransforms` field in your config: + +```ts {9-10} +import { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'http://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: ['./my-transform.js'], + plugins: ['./my-transform.js'] + } + } +} + +export default config +``` + +## Validation + +Plugin can also validate before executing the `transformDocuments`: + +```js {20-24} +const { visit } = require('graphql') + +module.exports = { + plugin() {}, + transformDocuments(schema, documents, config) { + return documents + }, + validateBeforeTransformDocuments(schema, documents, config) { + if (config.somethingWrong) { + throw new Error(`Something wrong!`) + } + } +} +``` From 613dd93680955f4a9f44e76cf4a6d4f130995c8d Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Thu, 22 Dec 2022 00:37:26 +0900 Subject: [PATCH 06/15] Fix the changeset --- .changeset/short-toes-relax.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md index ac835119187..50cbf77c202 100644 --- a/.changeset/short-toes-relax.md +++ b/.changeset/short-toes-relax.md @@ -10,7 +10,7 @@ Add a feature to transform documents. Plugin will have the following functions: -```my-plugin.js +```js module.exports = { plugin: () => { return 'hello' @@ -46,7 +46,7 @@ export default config For example, to remove a `@localOnlyDirective` directive from documents: -```my-plugin.js +```js const { visit, print } = require('graphql') module.exports = { From e045aba1d36884d8e84c21003ee03088340cb0a3 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Sun, 11 Dec 2022 16:36:20 +0900 Subject: [PATCH 07/15] Add document transform. Document transform changes documents before executing plugins. --- packages/graphql-codegen-cli/src/codegen.ts | 10 +++ .../graphql-codegen-cli/tests/codegen.spec.ts | 77 +++++++++++++++++++ .../document-transform-config.js | 19 +++++ .../document-transform-context.js | 9 +++ .../document-transform-validation.js | 9 +++ .../custom-plugins/document-transform.js | 19 +++++ packages/graphql-codegen-core/src/codegen.ts | 24 ++++-- .../src/transform-document.ts | 65 ++++++++++++++++ packages/presets/client/src/index.ts | 4 + .../presets/gql-tag-operations/src/index.ts | 4 + packages/presets/graphql-modules/src/index.ts | 2 + packages/utils/plugins-helpers/src/types.ts | 16 ++++ 12 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js create mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js create mode 100644 packages/graphql-codegen-core/src/transform-document.ts diff --git a/packages/graphql-codegen-cli/src/codegen.ts b/packages/graphql-codegen-cli/src/codegen.ts index 34e848a9f61..6cdf8765a91 100644 --- a/packages/graphql-codegen-cli/src/codegen.ts +++ b/packages/graphql-codegen-cli/src/codegen.ts @@ -316,6 +316,14 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config), }; + const documentTransformPlugins = await Promise.all( + normalizeConfig(outputConfig.documentTransformPlugins).map(async pluginConfig => { + const name = Object.keys(pluginConfig)[0]; + const plugin = await getPluginByName(name, pluginLoader); + return { [name]: { plugin, config: Object.values(pluginConfig)[0] } }; + }) + ); + const outputs: Types.GenerateOptions[] = preset ? await context.profiler.run( async () => @@ -330,6 +338,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, + documentTransformPlugins, }), `Build Generates Section: ${filename}` ) @@ -344,6 +353,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, + documentTransformPlugins, }, ]; diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index 7c4c511d2ae..6ee3e995d6a 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1086,4 +1086,81 @@ describe('Codegen Executor', () => { const output = await executeCodegen(config); expect(output[0].content).toContain('DocumentNode'); }); + + describe('Document Transform', () => { + it('Should transform documents', async () => { + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransformPlugins: ['./tests/custom-plugins/document-transform.js'], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('export type BarQuery'); + }); + + it('Should accept config in per-plugin', async () => { + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query root { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransformPlugins: [ + { + './tests/custom-plugins/document-transform-config.js': { + queryName: 'test', + }, + }, + ], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('export type TestQuery'); + }); + + it('Should allow plugin context to be accessed and modified', async () => { + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query root { f }`, + generates: { + 'out1.ts': { + documentTransformPlugins: ['./tests/custom-plugins/document-transform-context.js'], + plugins: ['./tests/custom-plugins/document-transform-context.js'], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('Hello world!'); + }); + + it('Should execute validation before transform documents and throw when it fails', async () => { + try { + await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + generates: { + 'out1.ts': { + plugins: ['typescript'], + documentTransformPlugins: ['./tests/custom-plugins/document-transform-validation.js'], + }, + }, + }); + throw new Error(SHOULD_NOT_THROW_STRING); + } catch (e) { + expect(e.message).not.toBe(SHOULD_NOT_THROW_STRING); + expect(e.message).toContain( + 'Document transform "./tests/custom-plugins/document-transform-validation.js" validation failed' + ); + expect(e.message).toContain('Invalid!'); + } + }); + }); }); diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js new file mode 100644 index 00000000000..543dc938846 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js @@ -0,0 +1,19 @@ +module.exports = { + plugin: () => {}, // Nothing to do + transformDocuments: (_schema, documents, config) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + name: { kind: 'Name', value: config.queryName }, + }, + ], + }, + }, + ]; + return newDocuments; + }, +}; diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js new file mode 100644 index 00000000000..78f93d5e186 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js @@ -0,0 +1,9 @@ +module.exports = { + plugin: (_schema, _documents, _config, { pluginContext }) => { + return `Hello ${pluginContext.myPluginInfo}!`; + }, + transformDocuments: (_schema, documents, _config, { pluginContext }) => { + pluginContext.myPluginInfo = 'world'; + return documents; + }, +}; diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js new file mode 100644 index 00000000000..7cf73aa5758 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js @@ -0,0 +1,9 @@ +module.exports = { + plugin: () => {}, // Nothing to do + transformDocuments: (_schema, documents) => { + return documents; + }, + validateBeforeTransformDocuments: () => { + throw new Error('Invalid!'); + }, +}; diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js new file mode 100644 index 00000000000..5c87acdd424 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js @@ -0,0 +1,19 @@ +module.exports = { + plugin: () => {}, // Nothing to do + transformDocuments: (_schema, documents) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + name: { kind: 'Name', value: 'bar' }, + }, + ], + }, + }, + ]; + return newDocuments; + }, +}; diff --git a/packages/graphql-codegen-core/src/codegen.ts b/packages/graphql-codegen-core/src/codegen.ts index b8d8d64ae87..67c4a09e858 100644 --- a/packages/graphql-codegen-core/src/codegen.ts +++ b/packages/graphql-codegen-core/src/codegen.ts @@ -19,6 +19,7 @@ import { shouldValidateDocumentsAgainstSchema, shouldValidateDuplicateDocuments, } from './utils.js'; +import { transformDocuments } from './transform-document.js'; export async function codegen(options: Types.GenerateOptions): Promise { const documents = options.documents || []; @@ -71,7 +72,18 @@ export async function codegen(options: Types.GenerateOptions): Promise { const schemaDocumentNode = mergeNeeded || !options.schema ? getCachedDocumentNodeFromSchema(schemaInstance) : options.schema; - if (schemaInstance && documents.length > 0 && shouldValidateDocumentsAgainstSchema(skipDocumentsValidation)) { + const transformedDocuments = await transformDocuments({ + ...options, + schema: schemaDocumentNode, + schemaAst: schemaInstance, + profiler, + }); + + if ( + schemaInstance && + transformedDocuments.length > 0 && + shouldValidateDocumentsAgainstSchema(skipDocumentsValidation) + ) { const ignored = ['NoUnusedFragments', 'NoUnusedVariables', 'KnownDirectives']; if (typeof skipDocumentsValidation === 'object' && skipDocumentsValidation.ignoreRules) { ignored.push(...asArray(skipDocumentsValidation.ignoreRules)); @@ -87,18 +99,18 @@ export async function codegen(options: Types.GenerateOptions): Promise { const rules = specifiedRules.filter(rule => !ignored.some(ignoredRule => rule.name.startsWith(ignoredRule))); const schemaHash = extractHashFromSchema(schemaInstance); - if (!schemaHash || !options.cache || documents.some(d => typeof d.hash !== 'string')) { + if (!schemaHash || !options.cache || transformedDocuments.some(d => typeof d.hash !== 'string')) { return Promise.resolve( validateGraphQlDocuments( schemaInstance, - [...documents.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], + [...transformedDocuments.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], rules ) ); } const cacheKey = [schemaHash] - .concat(documents.map(doc => doc.hash)) + .concat(transformedDocuments.map(doc => doc.hash)) .concat(JSON.stringify(fragments)) .join(','); @@ -106,7 +118,7 @@ export async function codegen(options: Types.GenerateOptions): Promise { Promise.resolve( validateGraphQlDocuments( schemaInstance, - [...documents.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], + [...transformedDocuments.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], rules ) ) @@ -141,7 +153,7 @@ export async function codegen(options: Types.GenerateOptions): Promise { parentConfig: options.config, schema: schemaDocumentNode, schemaAst: schemaInstance, - documents: options.documents, + documents: transformedDocuments, outputFilename: options.filename, allPlugins: options.plugins, skipDocumentsValidation: options.skipDocumentsValidation, diff --git a/packages/graphql-codegen-core/src/transform-document.ts b/packages/graphql-codegen-core/src/transform-document.ts new file mode 100644 index 00000000000..d9d9ae6fcb9 --- /dev/null +++ b/packages/graphql-codegen-core/src/transform-document.ts @@ -0,0 +1,65 @@ +import { createNoopProfiler, Types } from '@graphql-codegen/plugin-helpers'; +import { buildASTSchema, GraphQLSchema } from 'graphql'; + +export async function transformDocuments(options: Types.GenerateOptions): Promise { + const documentTransformPlugins = options.documentTransformPlugins || []; + let documents = options.documents; + if (documentTransformPlugins.length === 0) { + return documents; + } + + const profiler = options.profiler ?? createNoopProfiler(); + const outputSchema: GraphQLSchema = options.schemaAst || buildASTSchema(options.schema, options.config as any); + + for (const documentTransformPlugin of documentTransformPlugins) { + const name = Object.keys(documentTransformPlugin)[0]; + const transformPlugin = documentTransformPlugin[name].plugin; + const pluginConfig = documentTransformPlugin[name].config; + + const config = + typeof pluginConfig !== 'object' + ? pluginConfig + : { + ...options.config, + ...pluginConfig, + }; + + if ( + transformPlugin.validateBeforeTransformDocuments && + typeof transformPlugin.validateBeforeTransformDocuments === 'function' + ) { + try { + await profiler.run( + async () => + transformPlugin.validateBeforeTransformDocuments( + outputSchema, + options.documents, + config, + options.filename, + options.plugins, + options.pluginContext + ), + `Document transform ${name} validate` + ); + } catch (e) { + throw new Error( + `Document transform "${name}" validation failed: \n + ${e.message} + ` + ); + } + } + + if (transformPlugin.transformDocuments && typeof transformPlugin.transformDocuments === 'function') { + await profiler.run(async () => { + documents = await transformPlugin.transformDocuments(outputSchema, documents, config, { + outputFile: options.filename, + allPlugins: options.plugins, + pluginContext: options.pluginContext, + }); + }, `Document transform ${name} execution`); + } + } + + return documents; +} diff --git a/packages/presets/client/src/index.ts b/packages/presets/client/src/index.ts index 06dc482ba2f..1a3ea3e8e13 100644 --- a/packages/presets/client/src/index.ts +++ b/packages/presets/client/src/index.ts @@ -240,6 +240,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -266,6 +267,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -280,6 +282,7 @@ export const preset: Types.OutputPreset = { ...forwardedConfig, }, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, { filename: `${options.baseOutputDir}gql${gqlArtifactFileExtension}`, @@ -291,6 +294,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'graphql', }, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, ...(isPersistedOperations ? [ diff --git a/packages/presets/gql-tag-operations/src/index.ts b/packages/presets/gql-tag-operations/src/index.ts index aba70beef8f..f423976ec68 100644 --- a/packages/presets/gql-tag-operations/src/index.ts +++ b/packages/presets/gql-tag-operations/src/index.ts @@ -207,6 +207,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -230,6 +231,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], + documentTransformPlugins: options.documentTransformPlugins, }; } @@ -241,6 +243,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, { filename: `${options.baseOutputDir}/gql${gqlArtifactFileExtension}`, @@ -253,6 +256,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'gql', }, documents: sources, + documentTransformPlugins: options.documentTransformPlugins, }, ...(fragmentMaskingFileGenerateConfig ? [fragmentMaskingFileGenerateConfig] : []), ...(indexFileGenerateConfig ? [indexFileGenerateConfig] : []), diff --git a/packages/presets/graphql-modules/src/index.ts b/packages/presets/graphql-modules/src/index.ts index 0361d669380..1eb9ebdf186 100644 --- a/packages/presets/graphql-modules/src/index.ts +++ b/packages/presets/graphql-modules/src/index.ts @@ -69,6 +69,7 @@ export const preset: Types.OutputPreset = { enumsAsTypes: true, }, schemaAst: options.schemaAst!, + documentTransformPlugins: options.documentTransformPlugins, }; const baseTypesFilename = baseTypesPath.replace(/\.(js|ts|d.ts)$/, ''); @@ -120,6 +121,7 @@ export const preset: Types.OutputPreset = { }, config: options.config, schemaAst: options.schemaAst, + documentTransformPlugins: options.documentTransformPlugins, }; }); diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 4bb91a48c17..54782549a87 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -19,6 +19,7 @@ export namespace Types { pluginContext?: { [key: string]: any }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; + documentTransformPlugins?: ConfiguredDocumentTransformPlugin[]; } export type FileOutput = { @@ -232,6 +233,9 @@ export namespace Types { | 'import-types'; export type PresetNames = `${PresetNamesBase}-preset` | PresetNamesBase; + export interface ConfiguredDocumentTransformPlugin { + [name: string]: { config: PluginConfig; plugin: CodegenPlugin }; + } /** * @additionalProperties false */ @@ -316,6 +320,11 @@ export namespace Types { * For more details: https://graphql-code-generator.com/docs/config-reference/lifecycle-hooks */ hooks?: Partial; + /** + * @description Specifies plugins that have the transformDocuments function. + * Document transform plugin changes documents before executing plugins. + */ + documentTransformPlugins?: OutputConfig[]; } /* Output Builder Preset */ @@ -340,6 +349,7 @@ export namespace Types { }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; + documentTransformPlugins: ConfiguredDocumentTransformPlugin[]; }; export type OutputPreset = { @@ -643,4 +653,10 @@ export interface CodegenPlugin { plugin: PluginFunction; addToSchema?: AddToSchemaResult | ((config: T) => AddToSchemaResult); validate?: PluginValidateFn; + transformDocuments?: TransformDocumentsFunction; + validateBeforeTransformDocuments?: PluginValidateFn; } + +export type TransformDocumentsFunction = ( + ...params: Parameters> +) => Types.Promisable; From 6d0d3bde24d5a9d2f57dee317be7d992281596d7 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Mon, 12 Dec 2022 19:32:08 +0900 Subject: [PATCH 08/15] Create changesets --- .changeset/short-toes-relax.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/short-toes-relax.md diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md new file mode 100644 index 00000000000..ef747433864 --- /dev/null +++ b/.changeset/short-toes-relax.md @@ -0,0 +1,10 @@ +--- +"@graphql-codegen/cli": minor +"@graphql-codegen/core": minor +"@graphql-codegen/plugin-helpers": minor +"@graphql-codegen/client-preset": minor +"@graphql-codegen/gql-tag-operations-preset": minor +"@graphql-codegen/graphql-modules-preset": minor +--- + +Add a feature to transform documents From 85adec53fb47df118afe9c7405bb6d3444890efe Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Wed, 21 Dec 2022 16:10:55 +0900 Subject: [PATCH 09/15] Rename `documentTransformPlugins` to `documentTransforms` --- packages/graphql-codegen-cli/src/codegen.ts | 8 ++++---- packages/graphql-codegen-cli/tests/codegen.spec.ts | 8 ++++---- .../graphql-codegen-core/src/transform-document.ts | 12 ++++++------ packages/presets/client/src/index.ts | 8 ++++---- packages/presets/gql-tag-operations/src/index.ts | 8 ++++---- packages/presets/graphql-modules/src/index.ts | 4 ++-- packages/utils/plugins-helpers/src/types.ts | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/graphql-codegen-cli/src/codegen.ts b/packages/graphql-codegen-cli/src/codegen.ts index 6cdf8765a91..f66e32c852a 100644 --- a/packages/graphql-codegen-cli/src/codegen.ts +++ b/packages/graphql-codegen-cli/src/codegen.ts @@ -316,8 +316,8 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config), }; - const documentTransformPlugins = await Promise.all( - normalizeConfig(outputConfig.documentTransformPlugins).map(async pluginConfig => { + const documentTransforms = await Promise.all( + normalizeConfig(outputConfig.documentTransforms).map(async pluginConfig => { const name = Object.keys(pluginConfig)[0]; const plugin = await getPluginByName(name, pluginLoader); return { [name]: { plugin, config: Object.values(pluginConfig)[0] } }; @@ -338,7 +338,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, - documentTransformPlugins, + documentTransforms, }), `Build Generates Section: ${filename}` ) @@ -353,7 +353,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom pluginMap, pluginContext, profiler: context.profiler, - documentTransformPlugins, + documentTransforms, }, ]; diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index 6ee3e995d6a..30b845c70aa 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1095,7 +1095,7 @@ describe('Codegen Executor', () => { generates: { 'out1.ts': { plugins: ['typescript', 'typescript-operations'], - documentTransformPlugins: ['./tests/custom-plugins/document-transform.js'], + documentTransforms: ['./tests/custom-plugins/document-transform.js'], }, }, }); @@ -1111,7 +1111,7 @@ describe('Codegen Executor', () => { generates: { 'out1.ts': { plugins: ['typescript', 'typescript-operations'], - documentTransformPlugins: [ + documentTransforms: [ { './tests/custom-plugins/document-transform-config.js': { queryName: 'test', @@ -1132,7 +1132,7 @@ describe('Codegen Executor', () => { documents: `query root { f }`, generates: { 'out1.ts': { - documentTransformPlugins: ['./tests/custom-plugins/document-transform-context.js'], + documentTransforms: ['./tests/custom-plugins/document-transform-context.js'], plugins: ['./tests/custom-plugins/document-transform-context.js'], }, }, @@ -1149,7 +1149,7 @@ describe('Codegen Executor', () => { generates: { 'out1.ts': { plugins: ['typescript'], - documentTransformPlugins: ['./tests/custom-plugins/document-transform-validation.js'], + documentTransforms: ['./tests/custom-plugins/document-transform-validation.js'], }, }, }); diff --git a/packages/graphql-codegen-core/src/transform-document.ts b/packages/graphql-codegen-core/src/transform-document.ts index d9d9ae6fcb9..01a8540f320 100644 --- a/packages/graphql-codegen-core/src/transform-document.ts +++ b/packages/graphql-codegen-core/src/transform-document.ts @@ -2,19 +2,19 @@ import { createNoopProfiler, Types } from '@graphql-codegen/plugin-helpers'; import { buildASTSchema, GraphQLSchema } from 'graphql'; export async function transformDocuments(options: Types.GenerateOptions): Promise { - const documentTransformPlugins = options.documentTransformPlugins || []; + const documentTransforms = options.documentTransforms || []; let documents = options.documents; - if (documentTransformPlugins.length === 0) { + if (documentTransforms.length === 0) { return documents; } const profiler = options.profiler ?? createNoopProfiler(); const outputSchema: GraphQLSchema = options.schemaAst || buildASTSchema(options.schema, options.config as any); - for (const documentTransformPlugin of documentTransformPlugins) { - const name = Object.keys(documentTransformPlugin)[0]; - const transformPlugin = documentTransformPlugin[name].plugin; - const pluginConfig = documentTransformPlugin[name].config; + for (const documentTransform of documentTransforms) { + const name = Object.keys(documentTransform)[0]; + const transformPlugin = documentTransform[name].plugin; + const pluginConfig = documentTransform[name].config; const config = typeof pluginConfig !== 'object' diff --git a/packages/presets/client/src/index.ts b/packages/presets/client/src/index.ts index 1a3ea3e8e13..061fc8e6e70 100644 --- a/packages/presets/client/src/index.ts +++ b/packages/presets/client/src/index.ts @@ -240,7 +240,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -267,7 +267,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -282,7 +282,7 @@ export const preset: Types.OutputPreset = { ...forwardedConfig, }, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, { filename: `${options.baseOutputDir}gql${gqlArtifactFileExtension}`, @@ -294,7 +294,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'graphql', }, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, ...(isPersistedOperations ? [ diff --git a/packages/presets/gql-tag-operations/src/index.ts b/packages/presets/gql-tag-operations/src/index.ts index f423976ec68..9efbfdc1034 100644 --- a/packages/presets/gql-tag-operations/src/index.ts +++ b/packages/presets/gql-tag-operations/src/index.ts @@ -207,7 +207,7 @@ export const preset: Types.OutputPreset = { unmaskFunctionName: fragmentMaskingConfig.unmaskFunctionName, }, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -231,7 +231,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: [], - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; } @@ -243,7 +243,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, { filename: `${options.baseOutputDir}/gql${gqlArtifactFileExtension}`, @@ -256,7 +256,7 @@ export const preset: Types.OutputPreset = { gqlTagName: options.presetConfig.gqlTagName || 'gql', }, documents: sources, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }, ...(fragmentMaskingFileGenerateConfig ? [fragmentMaskingFileGenerateConfig] : []), ...(indexFileGenerateConfig ? [indexFileGenerateConfig] : []), diff --git a/packages/presets/graphql-modules/src/index.ts b/packages/presets/graphql-modules/src/index.ts index 1eb9ebdf186..a17717572eb 100644 --- a/packages/presets/graphql-modules/src/index.ts +++ b/packages/presets/graphql-modules/src/index.ts @@ -69,7 +69,7 @@ export const preset: Types.OutputPreset = { enumsAsTypes: true, }, schemaAst: options.schemaAst!, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; const baseTypesFilename = baseTypesPath.replace(/\.(js|ts|d.ts)$/, ''); @@ -121,7 +121,7 @@ export const preset: Types.OutputPreset = { }, config: options.config, schemaAst: options.schemaAst, - documentTransformPlugins: options.documentTransformPlugins, + documentTransforms: options.documentTransforms, }; }); diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 54782549a87..4ea059388d7 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -19,7 +19,7 @@ export namespace Types { pluginContext?: { [key: string]: any }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; - documentTransformPlugins?: ConfiguredDocumentTransformPlugin[]; + documentTransforms?: ConfiguredDocumentTransform[]; } export type FileOutput = { @@ -233,7 +233,7 @@ export namespace Types { | 'import-types'; export type PresetNames = `${PresetNamesBase}-preset` | PresetNamesBase; - export interface ConfiguredDocumentTransformPlugin { + export interface ConfiguredDocumentTransform { [name: string]: { config: PluginConfig; plugin: CodegenPlugin }; } /** @@ -324,7 +324,7 @@ export namespace Types { * @description Specifies plugins that have the transformDocuments function. * Document transform plugin changes documents before executing plugins. */ - documentTransformPlugins?: OutputConfig[]; + documentTransforms?: OutputConfig[]; } /* Output Builder Preset */ @@ -349,7 +349,7 @@ export namespace Types { }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; - documentTransformPlugins: ConfiguredDocumentTransformPlugin[]; + documentTransforms: ConfiguredDocumentTransform[]; }; export type OutputPreset = { From 95d5710956cc4e9f5de719d9891fb80b0ac2db5c Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Wed, 21 Dec 2022 23:24:22 +0900 Subject: [PATCH 10/15] Add more information to changesets --- .changeset/short-toes-relax.md | 62 +++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md index ef747433864..ac835119187 100644 --- a/.changeset/short-toes-relax.md +++ b/.changeset/short-toes-relax.md @@ -7,4 +7,64 @@ "@graphql-codegen/graphql-modules-preset": minor --- -Add a feature to transform documents +Add a feature to transform documents. + +Plugin will have the following functions: +```my-plugin.js +module.exports = { + plugin: () => { + return 'hello' + }, + transformDocuments: (_schema, documents) => { + // Make some changes to the documents + return documents; + }, + validateBeforeTransformDocuments: () => { + // Raises an error if necessary + }, +}; +``` + +Use it as follows: + +```ts +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransformPlugins: ['./my-plugin.js'], + plugins: ['./my-plugin.js'] + } + } +} +export default config +``` + +For example, to remove a `@localOnlyDirective` directive from documents: + +```my-plugin.js +const { visit, print } = require('graphql') + +module.exports = { + plugin(schema, documents, config) { + // Output `documents` as an example. + return documents.map(documentFile => `${print(documentFile.document)}`).join('\n') + }, + transformDocuments(schema, documents, config) { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Directive: { + leave(node) { + if (node.name.value === 'localOnlyDirective') return null + } + } + }) + return documentFile + }) + } +} +``` From ed3ef46048545d2afe831dbc429d38dfd936bca6 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Thu, 22 Dec 2022 00:33:45 +0900 Subject: [PATCH 11/15] Add document-transform.mdx --- .../src/pages/docs/custom-codegen/_meta.json | 1 + .../custom-codegen/document-transform.mdx | 117 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 website/src/pages/docs/custom-codegen/document-transform.mdx diff --git a/website/src/pages/docs/custom-codegen/_meta.json b/website/src/pages/docs/custom-codegen/_meta.json index dce618ba955..9a93ef04b6f 100644 --- a/website/src/pages/docs/custom-codegen/_meta.json +++ b/website/src/pages/docs/custom-codegen/_meta.json @@ -4,5 +4,6 @@ "validate-configuration": "Validate Configuration", "extend-schema": "Extend Schema", "using-visitor": "Using Visitor Pattern", + "document-transform": "Document Transform", "contributing": "Contributing" } diff --git a/website/src/pages/docs/custom-codegen/document-transform.mdx b/website/src/pages/docs/custom-codegen/document-transform.mdx new file mode 100644 index 00000000000..b316d0c1c75 --- /dev/null +++ b/website/src/pages/docs/custom-codegen/document-transform.mdx @@ -0,0 +1,117 @@ +import { Callout } from '@theguild/components' + +# Document Transform + +## Basic Usage + +Each plugin can also provide a function `transformDocuments` for transforming `documents`: + +```js {5-14} +const { visit } = require('graphql') + +module.exports = { + plugin() {}, // Nothing to do + transformDocuments(schema, documents, config) { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Field(node) { + // This function triggered per each field + } + }) + return documentFile + }) + } +} +``` + +You can specify the plugin with `documentTransforms` field in your config: + +```ts {9} +import { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'http://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: ['./my-transform.js'], + plugins: [] + } + } +} + +export default config +``` + + + If you specify it with only the `plugins` field and not with the `documentTransforms` field, the `transformDocuments` + function will not be executed. + + +## Example + +For example, let's remove a `@loacalOnlyDirective` directive from documents: + +```js +const { visit, print } = require('graphql') + +module.exports = { + plugin(schema, documents, config) { + // Output `documents` as an example. + return documents.map(documentFile => `${print(documentFile.document)}`).join('\n') + }, + transformDocuments(schema, documents, config) { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Directive: { + leave(node) { + if (node.name.value === 'localOnlyDirective') return null + } + } + }) + return documentFile + }) + } +} +``` + +If you want to execute the `plugin` function as above, you specify it with the `plugins` field as well as the `documentTransforms` field in your config: + +```ts {9-10} +import { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'http://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: ['./my-transform.js'], + plugins: ['./my-transform.js'] + } + } +} + +export default config +``` + +## Validation + +Plugin can also validate before executing the `transformDocuments`: + +```js {20-24} +const { visit } = require('graphql') + +module.exports = { + plugin() {}, + transformDocuments(schema, documents, config) { + return documents + }, + validateBeforeTransformDocuments(schema, documents, config) { + if (config.somethingWrong) { + throw new Error(`Something wrong!`) + } + } +} +``` From f16fc4c7973a7d51689151539db363bd3d03faba Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Thu, 22 Dec 2022 00:37:26 +0900 Subject: [PATCH 12/15] Fix the changeset --- .changeset/short-toes-relax.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md index ac835119187..50cbf77c202 100644 --- a/.changeset/short-toes-relax.md +++ b/.changeset/short-toes-relax.md @@ -10,7 +10,7 @@ Add a feature to transform documents. Plugin will have the following functions: -```my-plugin.js +```js module.exports = { plugin: () => { return 'hello' @@ -46,7 +46,7 @@ export default config For example, to remove a `@localOnlyDirective` directive from documents: -```my-plugin.js +```js const { visit, print } = require('graphql') module.exports = { From 483d1e58b906aa7ebd4d68d076d01117ae4aa83d Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Sun, 12 Feb 2023 17:09:14 +0900 Subject: [PATCH 13/15] Remove `validateBeforeTransformDocuments` function to simplify API --- .changeset/short-toes-relax.md | 3 -- .../graphql-codegen-cli/tests/codegen.spec.ts | 6 +-- .../document-transform-validation.js | 5 +-- .../src/transform-document.ts | 44 ++++++------------- packages/utils/plugins-helpers/src/types.ts | 1 - .../custom-codegen/document-transform.mdx | 8 ++-- 6 files changed, 20 insertions(+), 47 deletions(-) diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md index 50cbf77c202..a20a2cbd757 100644 --- a/.changeset/short-toes-relax.md +++ b/.changeset/short-toes-relax.md @@ -19,9 +19,6 @@ module.exports = { // Make some changes to the documents return documents; }, - validateBeforeTransformDocuments: () => { - // Raises an error if necessary - }, }; ``` diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index 30b845c70aa..68a8564e394 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1142,7 +1142,7 @@ describe('Codegen Executor', () => { expect(output[0].content).toContain('Hello world!'); }); - it('Should execute validation before transform documents and throw when it fails', async () => { + it('should throw an understandable error if it fails.', async () => { try { await executeCodegen({ schema: SIMPLE_TEST_SCHEMA, @@ -1157,9 +1157,9 @@ describe('Codegen Executor', () => { } catch (e) { expect(e.message).not.toBe(SHOULD_NOT_THROW_STRING); expect(e.message).toContain( - 'Document transform "./tests/custom-plugins/document-transform-validation.js" validation failed' + 'Document transform "./tests/custom-plugins/document-transform-validation.js" failed' ); - expect(e.message).toContain('Invalid!'); + expect(e.message).toContain('Something Wrong!'); } }); }); diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js index 7cf73aa5758..30f488197de 100644 --- a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js @@ -1,9 +1,6 @@ module.exports = { plugin: () => {}, // Nothing to do transformDocuments: (_schema, documents) => { - return documents; - }, - validateBeforeTransformDocuments: () => { - throw new Error('Invalid!'); + throw new Error('Something Wrong!'); }, }; diff --git a/packages/graphql-codegen-core/src/transform-document.ts b/packages/graphql-codegen-core/src/transform-document.ts index 01a8540f320..0f368c5f3a3 100644 --- a/packages/graphql-codegen-core/src/transform-document.ts +++ b/packages/graphql-codegen-core/src/transform-document.ts @@ -17,48 +17,30 @@ export async function transformDocuments(options: Types.GenerateOptions): Promis const pluginConfig = documentTransform[name].config; const config = - typeof pluginConfig !== 'object' - ? pluginConfig - : { + typeof pluginConfig === 'object' + ? { ...options.config, ...pluginConfig, - }; + } + : pluginConfig; - if ( - transformPlugin.validateBeforeTransformDocuments && - typeof transformPlugin.validateBeforeTransformDocuments === 'function' - ) { + if (transformPlugin.transformDocuments && typeof transformPlugin.transformDocuments === 'function') { try { - await profiler.run( - async () => - transformPlugin.validateBeforeTransformDocuments( - outputSchema, - options.documents, - config, - options.filename, - options.plugins, - options.pluginContext - ), - `Document transform ${name} validate` - ); + await profiler.run(async () => { + documents = await transformPlugin.transformDocuments(outputSchema, documents, config, { + outputFile: options.filename, + allPlugins: options.plugins, + pluginContext: options.pluginContext, + }); + }, `Document transform ${name} execution`); } catch (e) { throw new Error( - `Document transform "${name}" validation failed: \n + `Document transform "${name}" failed: \n ${e.message} ` ); } } - - if (transformPlugin.transformDocuments && typeof transformPlugin.transformDocuments === 'function') { - await profiler.run(async () => { - documents = await transformPlugin.transformDocuments(outputSchema, documents, config, { - outputFile: options.filename, - allPlugins: options.plugins, - pluginContext: options.pluginContext, - }); - }, `Document transform ${name} execution`); - } } return documents; diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 4ea059388d7..cb50834ab50 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -654,7 +654,6 @@ export interface CodegenPlugin { addToSchema?: AddToSchemaResult | ((config: T) => AddToSchemaResult); validate?: PluginValidateFn; transformDocuments?: TransformDocumentsFunction; - validateBeforeTransformDocuments?: PluginValidateFn; } export type TransformDocumentsFunction = ( diff --git a/website/src/pages/docs/custom-codegen/document-transform.mdx b/website/src/pages/docs/custom-codegen/document-transform.mdx index b316d0c1c75..21f05d063e3 100644 --- a/website/src/pages/docs/custom-codegen/document-transform.mdx +++ b/website/src/pages/docs/custom-codegen/document-transform.mdx @@ -98,20 +98,18 @@ export default config ## Validation -Plugin can also validate before executing the `transformDocuments`: +The `validate` function is not executed before the `transformDocuments` function in the plugin. If validation is necessary, you can simply throw an error in the `transformDocuments`. -```js {20-24} +```js {6-8} const { visit } = require('graphql') module.exports = { plugin() {}, transformDocuments(schema, documents, config) { - return documents - }, - validateBeforeTransformDocuments(schema, documents, config) { if (config.somethingWrong) { throw new Error(`Something wrong!`) } + return documents } } ``` From 64e8376bd0d3d18f7406e34f1aece5a78d88a9db Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Sun, 19 Feb 2023 17:54:55 +0900 Subject: [PATCH 14/15] Rewrite to not use plugin --- .changeset/short-toes-relax.md | 122 ++++++---- packages/graphql-codegen-cli/src/codegen.ts | 19 +- .../src/documentTransforms.ts | 82 +++++++ .../graphql-codegen-cli/tests/codegen.spec.ts | 223 +++++++++++++++++- .../document-transform.js | 3 +- .../test-config.js} | 3 +- .../document-transform-context.js | 4 - .../document-transform-validation.js | 6 - packages/graphql-codegen-core/src/codegen.ts | 22 ++ .../src/transform-document.ts | 32 ++- packages/presets/client/src/index.ts | 1 + packages/utils/plugins-helpers/src/types.ts | 46 +++- website/src/pages/docs/advanced/_meta.json | 3 +- .../docs/advanced/document-transform.mdx | 167 +++++++++++++ .../src/pages/docs/custom-codegen/_meta.json | 1 - .../custom-codegen/document-transform.mdx | 115 --------- 16 files changed, 627 insertions(+), 222 deletions(-) create mode 100644 packages/graphql-codegen-cli/src/documentTransforms.ts rename packages/graphql-codegen-cli/tests/{custom-plugins => custom-document-transforms}/document-transform.js (80%) rename packages/graphql-codegen-cli/tests/{custom-plugins/document-transform-config.js => custom-document-transforms/test-config.js} (79%) delete mode 100644 packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js create mode 100644 website/src/pages/docs/advanced/document-transform.mdx delete mode 100644 website/src/pages/docs/custom-codegen/document-transform.mdx diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md index a20a2cbd757..d1d8d2f4853 100644 --- a/.changeset/short-toes-relax.md +++ b/.changeset/short-toes-relax.md @@ -7,61 +7,97 @@ "@graphql-codegen/graphql-modules-preset": minor --- -Add a feature to transform documents. +Introduce a new feature called DocumentTransform. -Plugin will have the following functions: -```js -module.exports = { - plugin: () => { - return 'hello' - }, - transformDocuments: (_schema, documents) => { - // Make some changes to the documents - return documents; +DocumentTransform is a functionality that allows you to modify `documents` before they are processed by plugins. You can use functions passed to the `documentTransforms` option to make changes to GraphQL documents or extend the schema, as needed. + +To use this feature, you can write `documentTransforms` as follows: + +```ts +import type { CodegenConfig } from '@graphql-codegen/cli'; + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [ + { + transform: ({ documents }) => { + // Make some changes to the documents + return documents; + }, + addToSchema: 'extend type Query { test: String! }', + }, + ], + }, }, }; +export default config; ``` -Use it as follows: +For instance, to remove a `@localOnlyDirective` directive from `documents`, you can write the following code: -```ts -import type { CodegenConfig } from '@graphql-codegen/cli' +```js +import type { CodegenConfig } from '@graphql-codegen/cli'; +import { visit } from 'graphql'; const config: CodegenConfig = { - schema: 'https://localhost:4000/graphql', - documents: ['src/**/*.tsx'], - generates: { - './src/gql/': { - preset: 'client', - documentTransformPlugins: ['./my-plugin.js'], - plugins: ['./my-plugin.js'] - } - } -} -export default config + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [ + { + transform: ({ documents }) => { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Directive: { + leave(node) { + if (node.name.value === 'localOnlyDirective') return null; + }, + }, + }); + return documentFile; + }); + }, + }, + ], + }, + }, +}; +export default config; ``` -For example, to remove a `@localOnlyDirective` directive from documents: +DocumentTransform can also be specified by file name. You can create a custom file for a specific transformation and pass it to `documentTransforms`. -```js -const { visit, print } = require('graphql') +Let's create the document transform as a file: +```js module.exports = { - plugin(schema, documents, config) { - // Output `documents` as an example. - return documents.map(documentFile => `${print(documentFile.document)}`).join('\n') + transform: ({ documents }) => { + // Make some changes to the documents + return documents; }, - transformDocuments(schema, documents, config) { - return documents.map(documentFile => { - documentFile.document = visit(documentFile.document, { - Directive: { - leave(node) { - if (node.name.value === 'localOnlyDirective') return null - } - } - }) - return documentFile - }) - } -} +}; +``` + +Then, you can specify the file name as follows: + +```ts +import type { CodegenConfig } from '@graphql-codegen/cli'; + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: ['./my-document-transform.js'], + }, + }, +}; +export default config; ``` diff --git a/packages/graphql-codegen-cli/src/codegen.ts b/packages/graphql-codegen-cli/src/codegen.ts index f66e32c852a..9dc115ef394 100644 --- a/packages/graphql-codegen-cli/src/codegen.ts +++ b/packages/graphql-codegen-cli/src/codegen.ts @@ -18,6 +18,7 @@ import { CodegenContext, ensureContext, shouldEmitLegacyCommonJSImports } from ' import { getPluginByName } from './plugins.js'; import { getPresetByName } from './presets.js'; import { debugLog, printLogs } from './utils/debugging.js'; +import { getDocumentTransform } from './documentTransforms.js'; /** * Poor mans ESM detection. @@ -316,13 +317,17 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom emitLegacyCommonJSImports: shouldEmitLegacyCommonJSImports(config), }; - const documentTransforms = await Promise.all( - normalizeConfig(outputConfig.documentTransforms).map(async pluginConfig => { - const name = Object.keys(pluginConfig)[0]; - const plugin = await getPluginByName(name, pluginLoader); - return { [name]: { plugin, config: Object.values(pluginConfig)[0] } }; - }) - ); + const documentTransforms = Array.isArray(outputConfig.documentTransforms) + ? await Promise.all( + outputConfig.documentTransforms.map(async (config, index) => { + return await getDocumentTransform( + config, + makeDefaultLoader(context.cwd), + `the element at index ${index} of the documentTransforms` + ); + }) + ) + : []; const outputs: Types.GenerateOptions[] = preset ? await context.profiler.run( diff --git a/packages/graphql-codegen-cli/src/documentTransforms.ts b/packages/graphql-codegen-cli/src/documentTransforms.ts new file mode 100644 index 00000000000..542020cfc7d --- /dev/null +++ b/packages/graphql-codegen-cli/src/documentTransforms.ts @@ -0,0 +1,82 @@ +import { resolve } from 'path'; +import { Types } from '@graphql-codegen/plugin-helpers'; + +export async function getDocumentTransform( + documentTransform: Types.OutputDocumentTransform, + loader: Types.PackageLoaderFn, + defaultName: string +): Promise { + if (typeof documentTransform === 'string') { + const transformObject = await getDocumentTransformByName(documentTransform, loader); + return { name: documentTransform, transformObject }; + } + if (isTransformObject(documentTransform)) { + return { name: defaultName, transformObject: documentTransform }; + } + if (isTransformFileConfig(documentTransform)) { + const name = Object.keys(documentTransform)[0]; + const transformObject = await getDocumentTransformByName(name, loader); + return { name, transformObject, config: Object.values(documentTransform)[0] }; + } + throw new Error( + ` + An unknown format document transform: '${defaultName}'. + ` + ); +} + +function isTransformObject(config: Types.OutputDocumentTransform): config is Types.DocumentTransformObject { + return typeof config === 'object' && config.transform && typeof config.transform === 'function'; +} + +function isTransformFileConfig(config: Types.OutputDocumentTransform): config is Types.DocumentTransformFileConfig { + const keys = Object.keys(config); + return keys.length === 1 && typeof keys[0] === 'string'; +} + +export async function getDocumentTransformByName( + name: string, + loader: Types.PackageLoaderFn +): Promise { + const possibleNames = [ + `@graphql-codegen/${name}`, + `@graphql-codegen/${name}-document-transform`, + name, + resolve(process.cwd(), name), + ]; + + const possibleModules = possibleNames.concat(resolve(process.cwd(), name)); + + for (const moduleName of possibleModules) { + try { + return await loader(moduleName); + } catch (err) { + if (err.code !== 'MODULE_NOT_FOUND' && err.code !== 'ERR_MODULE_NOT_FOUND') { + throw new Error( + ` + Unable to load document transform matching '${name}'. + Reason: + ${err.message} + ` + ); + } + } + } + + const possibleNamesMsg = possibleNames + .map(name => + ` + - ${name} + `.trimEnd() + ) + .join(''); + + throw new Error( + ` + Unable to find document transform matching '${name}' + Install one of the following packages: + + ${possibleNamesMsg} + ` + ); +} diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index 19df1eb05b5..0034eec334f 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1,8 +1,9 @@ import { join } from 'path'; import { useMonorepo } from '@graphql-codegen/testing'; import { mergeTypeDefs } from '@graphql-tools/merge'; -import { buildASTSchema, buildSchema, GraphQLObjectType, parse, print } from 'graphql'; +import { buildASTSchema, buildSchema, GraphQLObjectType, parse, print, OperationDefinitionNode, Kind } from 'graphql'; import { createContext, executeCodegen } from '../src/index.js'; +import { Types } from '@graphql-codegen/plugin-helpers'; const SHOULD_NOT_THROW_STRING = 'SHOULD_NOT_THROW'; const SIMPLE_TEST_SCHEMA = `type MyType { f: String } type Query { f: String }`; @@ -164,7 +165,7 @@ describe('Codegen Executor', () => { } }); - it.only('Should throw when one output has no plugins or preset defined', async () => { + it('Should throw when one output has no plugins or preset defined', async () => { expect.assertions(1); try { await executeCodegen({ @@ -178,7 +179,7 @@ describe('Codegen Executor', () => { } }); - it.only('Should throw when one output has no plugins defined', async () => { + it('Should throw when one output has no plugins defined', async () => { expect.assertions(1); try { await executeCodegen({ @@ -194,7 +195,7 @@ describe('Codegen Executor', () => { } }); - it.only('Should succeed when one output has no plugins but preset defined', async () => { + it('Should succeed when one output has no plugins but preset defined', async () => { await executeCodegen({ schema: SIMPLE_TEST_SCHEMA, generates: { @@ -1113,13 +1114,30 @@ describe('Codegen Executor', () => { describe('Document Transform', () => { it('Should transform documents', async () => { + const transform: Types.DocumentTransformFunction = ({ documents }) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + name: { kind: Kind.NAME, value: 'bar' }, + } as OperationDefinitionNode, + ], + }, + }, + ]; + return newDocuments; + }; + const output = await executeCodegen({ schema: SIMPLE_TEST_SCHEMA, documents: `query foo { f }`, generates: { 'out1.ts': { plugins: ['typescript', 'typescript-operations'], - documentTransforms: ['./tests/custom-plugins/document-transform.js'], + documentTransforms: [{ transform }], }, }, }); @@ -1128,7 +1146,62 @@ describe('Codegen Executor', () => { expect(output[0].content).toContain('export type BarQuery'); }); - it('Should accept config in per-plugin', async () => { + it('Should allow users to set config', async () => { + const generateDocumentTransform: (config: { queryName: string }) => Types.DocumentTransformObject = ({ + queryName, + }) => { + return { + transform: ({ documents }) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + name: { kind: Kind.NAME, value: queryName }, + } as OperationDefinitionNode, + ], + }, + }, + ]; + return newDocuments; + }, + }; + }; + + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransforms: [generateDocumentTransform({ queryName: 'test' })], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('export type TestQuery'); + }); + + it('Should transform documents when specifying files', async () => { + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query root { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransforms: ['./tests/custom-document-transforms/document-transform.js'], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain('export type BarQuery'); + }); + + it('Should allow users to set config when specifying files', async () => { const output = await executeCodegen({ schema: SIMPLE_TEST_SCHEMA, documents: `query root { f }`, @@ -1137,7 +1210,7 @@ describe('Codegen Executor', () => { plugins: ['typescript', 'typescript-operations'], documentTransforms: [ { - './tests/custom-plugins/document-transform-config.js': { + './tests/custom-document-transforms/test-config.js': { queryName: 'test', }, }, @@ -1156,7 +1229,14 @@ describe('Codegen Executor', () => { documents: `query root { f }`, generates: { 'out1.ts': { - documentTransforms: ['./tests/custom-plugins/document-transform-context.js'], + documentTransforms: [ + { + transform: ({ pluginContext, documents }) => { + pluginContext.myPluginInfo = 'world'; + return documents; + }, + }, + ], plugins: ['./tests/custom-plugins/document-transform-context.js'], }, }, @@ -1170,21 +1250,140 @@ describe('Codegen Executor', () => { try { await executeCodegen({ schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, generates: { 'out1.ts': { plugins: ['typescript'], - documentTransforms: ['./tests/custom-plugins/document-transform-validation.js'], + documentTransforms: [ + { + transform: () => { + throw new Error('Something Wrong!'); + }, + }, + ], }, }, }); throw new Error(SHOULD_NOT_THROW_STRING); } catch (e) { expect(e.message).not.toBe(SHOULD_NOT_THROW_STRING); - expect(e.message).toContain( - 'Document transform "./tests/custom-plugins/document-transform-validation.js" failed' - ); + expect(e.message).toContain('DocumentTransform "the element at index 0 of the documentTransforms" failed'); expect(e.message).toContain('Something Wrong!'); } }); + + it('should be able to extend the schema.', async () => { + const documentTransform: Types.DocumentTransformObject = { + transform: ({ documents }) => { + return documents; + }, + addToSchema: `extend type Query { test: String! }`, + }; + + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransforms: [documentTransform], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain(`test: Scalars['String']`); + }); + }); + + it('should be able to dynamically extend the schema.', async () => { + const documentTransform: Types.DocumentTransformObject = { + transform: ({ documents }) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: 'test' }, + }, + ], + }, + } as OperationDefinitionNode, + ], + }, + }, + ]; + return newDocuments; + }, + addToSchema: ({ schema, schemaAst, documents }) => { + // Check that the arguments schema, schemaAST, and documents exist correctly. + // These following are only needed as a test and is of no importance for the extension of the schema in this case. + const fieldObjectTypeFromSchema = schema.definitions.find( + node => node.kind === Kind.OBJECT_TYPE_DEFINITION && node.fields.find(field => field.name.value === 'f') + ); + const fieldFromSchemaAST = schemaAst.getQueryType().getFields()['f']; + const operationDefinitionNode = documents[0].document!.definitions[0] as OperationDefinitionNode; + + if (operationDefinitionNode.name.value === 'foo' && fieldObjectTypeFromSchema && fieldFromSchemaAST) { + return `extend type Query { test: String! }`; + } + return ''; + }, + }; + + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, + generates: { + 'out1.ts': { + plugins: ['typescript', 'typescript-operations'], + documentTransforms: [documentTransform], + }, + }, + }); + + expect(output.length).toBe(1); + expect(output[0].content).toContain(`test: Scalars['String']`); + expect(output[0].content).toContain(`export type FooQuery = { __typename?: 'Query', test: string }`); + }); + + it('Should transform documents with client-preset', async () => { + const transform: Types.DocumentTransformFunction = ({ documents }) => { + const newDocuments = [ + { + document: { + ...documents[0].document, + definitions: [ + { + ...documents[0].document.definitions[0], + name: { kind: Kind.NAME, value: 'bar' }, + } as OperationDefinitionNode, + ], + }, + }, + ]; + return newDocuments; + }; + + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [{ transform }], + }, + }, + }); + + const fileOutput = output.find(file => file.filename === './src/gql/graphql.ts'); + expect(fileOutput.content).toContain('export type BarQuery'); }); }); diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js b/packages/graphql-codegen-cli/tests/custom-document-transforms/document-transform.js similarity index 80% rename from packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js rename to packages/graphql-codegen-cli/tests/custom-document-transforms/document-transform.js index 5c87acdd424..347e0ea6992 100644 --- a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform.js +++ b/packages/graphql-codegen-cli/tests/custom-document-transforms/document-transform.js @@ -1,6 +1,5 @@ module.exports = { - plugin: () => {}, // Nothing to do - transformDocuments: (_schema, documents) => { + transform: ({ documents }) => { const newDocuments = [ { document: { diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js b/packages/graphql-codegen-cli/tests/custom-document-transforms/test-config.js similarity index 79% rename from packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js rename to packages/graphql-codegen-cli/tests/custom-document-transforms/test-config.js index 543dc938846..5944d539b1b 100644 --- a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-config.js +++ b/packages/graphql-codegen-cli/tests/custom-document-transforms/test-config.js @@ -1,6 +1,5 @@ module.exports = { - plugin: () => {}, // Nothing to do - transformDocuments: (_schema, documents, config) => { + transform: ({ documents, config }) => { const newDocuments = [ { document: { diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js index 78f93d5e186..2d61040fef4 100644 --- a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js +++ b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-context.js @@ -2,8 +2,4 @@ module.exports = { plugin: (_schema, _documents, _config, { pluginContext }) => { return `Hello ${pluginContext.myPluginInfo}!`; }, - transformDocuments: (_schema, documents, _config, { pluginContext }) => { - pluginContext.myPluginInfo = 'world'; - return documents; - }, }; diff --git a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js b/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js deleted file mode 100644 index 30f488197de..00000000000 --- a/packages/graphql-codegen-cli/tests/custom-plugins/document-transform-validation.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugin: () => {}, // Nothing to do - transformDocuments: (_schema, documents) => { - throw new Error('Something Wrong!'); - }, -}; diff --git a/packages/graphql-codegen-core/src/codegen.ts b/packages/graphql-codegen-core/src/codegen.ts index 67c4a09e858..b325109ffc0 100644 --- a/packages/graphql-codegen-core/src/codegen.ts +++ b/packages/graphql-codegen-core/src/codegen.ts @@ -44,6 +44,27 @@ export async function codegen(options: Types.GenerateOptions): Promise { } } + const documentTransforms = Array.isArray(options.documentTransforms) ? options.documentTransforms : []; + for (const documentTransform of documentTransforms) { + const addToSchema = documentTransform.transformObject.addToSchema; + const partialSchema = + typeof addToSchema === 'function' + ? addToSchema({ + schema: options.schema, + schemaAst: options.schemaAst, + documents: options.documents, + config: { + ...options.config, + ...documentTransform.config, + }, + pluginContext: options.pluginContext, + }) + : addToSchema; + if (partialSchema) { + additionalTypeDefs.push(partialSchema); + } + } + const federationInConfig: boolean = pickFlag('federation', options.config); const isFederation = prioritize(federationInConfig, false); @@ -74,6 +95,7 @@ export async function codegen(options: Types.GenerateOptions): Promise { const transformedDocuments = await transformDocuments({ ...options, + documentTransforms, schema: schemaDocumentNode, schemaAst: schemaInstance, profiler, diff --git a/packages/graphql-codegen-core/src/transform-document.ts b/packages/graphql-codegen-core/src/transform-document.ts index 0f368c5f3a3..68244350071 100644 --- a/packages/graphql-codegen-core/src/transform-document.ts +++ b/packages/graphql-codegen-core/src/transform-document.ts @@ -1,45 +1,43 @@ import { createNoopProfiler, Types } from '@graphql-codegen/plugin-helpers'; -import { buildASTSchema, GraphQLSchema } from 'graphql'; export async function transformDocuments(options: Types.GenerateOptions): Promise { const documentTransforms = options.documentTransforms || []; let documents = options.documents; - if (documentTransforms.length === 0) { + if (documentTransforms.length === 0 || options.documents.length === 0) { return documents; } const profiler = options.profiler ?? createNoopProfiler(); - const outputSchema: GraphQLSchema = options.schemaAst || buildASTSchema(options.schema, options.config as any); for (const documentTransform of documentTransforms) { - const name = Object.keys(documentTransform)[0]; - const transformPlugin = documentTransform[name].plugin; - const pluginConfig = documentTransform[name].config; - const config = - typeof pluginConfig === 'object' + typeof documentTransform.config === 'object' ? { ...options.config, - ...pluginConfig, + ...documentTransform.config, } - : pluginConfig; - - if (transformPlugin.transformDocuments && typeof transformPlugin.transformDocuments === 'function') { + : {}; + const { transform } = documentTransform.transformObject; + if (transform && typeof transform === 'function') { + const name = documentTransform.name; try { await profiler.run(async () => { - documents = await transformPlugin.transformDocuments(outputSchema, documents, config, { - outputFile: options.filename, - allPlugins: options.plugins, + documents = await transform({ + documents, + schema: options.schema, + config, pluginContext: options.pluginContext, }); - }, `Document transform ${name} execution`); + }, `DocumentTransform "${name}" execution`); } catch (e) { throw new Error( - `Document transform "${name}" failed: \n + `DocumentTransform "${name}" failed: \n ${e.message} ` ); } + } else { + throw new Error(`Missing 'transform' function in "${documentTransform.name}"`); } } diff --git a/packages/presets/client/src/index.ts b/packages/presets/client/src/index.ts index 061fc8e6e70..f72e1b6392c 100644 --- a/packages/presets/client/src/index.ts +++ b/packages/presets/client/src/index.ts @@ -318,6 +318,7 @@ export const preset: Types.OutputPreset = { schema: options.schema, config: {}, documents: sources, + documentTransforms: options.documentTransforms, }, ] : []), diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 6732e3525c4..3a15b347ce4 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -233,9 +233,6 @@ export namespace Types { | 'import-types'; export type PresetNames = `${PresetNamesBase}-preset` | PresetNamesBase; - export interface ConfiguredDocumentTransform { - [name: string]: { config: PluginConfig; plugin: CodegenPlugin }; - } /** * @additionalProperties false */ @@ -321,10 +318,9 @@ export namespace Types { */ hooks?: Partial; /** - * @description Specifies plugins that have the transformDocuments function. - * Document transform plugin changes documents before executing plugins. + * @description DocumentTransform changes documents before executing plugins. */ - documentTransforms?: OutputConfig[]; + documentTransforms?: OutputDocumentTransform[]; } /* Output Builder Preset */ @@ -349,7 +345,7 @@ export namespace Types { }; profiler?: Profiler; cache?(namespace: string, key: string, factory: () => Promise): Promise; - documentTransforms: ConfiguredDocumentTransform[]; + documentTransforms?: ConfiguredDocumentTransform[]; }; export type OutputPreset = { @@ -620,6 +616,37 @@ export namespace Types { skipValidationAgainstSchema?: boolean; } | boolean; + + export type DocumentTransformFunction = (options: { + documents: Types.DocumentFile[]; + schema: DocumentNode; + config: Config; + pluginContext?: { [key: string]: any }; + }) => Types.Promisable; + + export type DocumentTransformAddToSchemaFunction = (options: { + documents: Types.DocumentFile[]; + schema: DocumentNode; + schemaAst?: GraphQLSchema; + config: Config; + pluginContext?: { [key: string]: any }; + }) => AddToSchemaResult; + + export type DocumentTransformObject = { + transform: DocumentTransformFunction; + addToSchema?: AddToSchemaResult | DocumentTransformAddToSchemaFunction; + }; + + export type DocumentTransformFileName = string; + export type DocumentTransformFileConfig = { [name: DocumentTransformFileName]: T }; + export type DocumentTransformFile = DocumentTransformFileName | DocumentTransformFileConfig; + + export type OutputDocumentTransform = DocumentTransformObject | DocumentTransformFile; + export type ConfiguredDocumentTransform = { + name: string; + transformObject: DocumentTransformObject; + config?: T; + }; } export function isComplexPluginOutput(obj: Types.PluginOutput): obj is Types.ComplexPluginOutput { @@ -653,9 +680,4 @@ export interface CodegenPlugin { plugin: PluginFunction; addToSchema?: AddToSchemaResult | ((config: T) => AddToSchemaResult); validate?: PluginValidateFn; - transformDocuments?: TransformDocumentsFunction; } - -export type TransformDocumentsFunction = ( - ...params: Parameters> -) => Types.Promisable; diff --git a/website/src/pages/docs/advanced/_meta.json b/website/src/pages/docs/advanced/_meta.json index 2d72cd594ab..4ca35c9e8ba 100644 --- a/website/src/pages/docs/advanced/_meta.json +++ b/website/src/pages/docs/advanced/_meta.json @@ -2,5 +2,6 @@ "generated-files-colocation": "Generated files colocation", "programmatic-usage": "Programmatic Usage", "how-does-it-work": "How does it work?", - "profiler": "Profiler" + "profiler": "Profiler", + "document-transform": "Document Transform" } diff --git a/website/src/pages/docs/advanced/document-transform.mdx b/website/src/pages/docs/advanced/document-transform.mdx new file mode 100644 index 00000000000..52ad56b0a3d --- /dev/null +++ b/website/src/pages/docs/advanced/document-transform.mdx @@ -0,0 +1,167 @@ +import { Callout } from '@theguild/components' + +# Document Transform + +Document transform is a feature that allows you to modify `documents` before they are used by plugins. +You can use functions passed to the `documentTransforms` option to make changes to GraphQL `documents` or extend the schema, as needed. + +## Basic Usage + +Document transform has `transform` function and optional `addToSchema` function. + +Let's specify an object containing those functions as the `documentTransforms` option as follows: + +```ts {9-17} +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [ + { + transform: ({ documents }) => { + // Make some changes to the documents + return documents + }, + addToSchema: 'extend type Query { test: String! }' + } + ] + } + } +} +export default config +``` + +For instance, to remove a `@localOnlyDirective` directive from `documents`, you can write the following code: + +```ts {2, 13-22} +import type { CodegenConfig } from '@graphql-codegen/cli' +import { visit } from 'graphql' + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [ + { + transform: ({ documents }) => { + return documents.map(documentFile => { + documentFile.document = visit(documentFile.document, { + Directive: { + leave(node) { + if (node.name.value === 'localOnlyDirective') return null + } + } + }) + return documentFile + }) + } + } + ] + } + } +} +export default config +``` + +## How to specify by name + +The document transform can also be specified by file name. You can create a custom file and pass it to `documentTransforms`. + +Let's create a file named `./my-document-transform.js`: + +```js +module.exports = { + transform: ({ documents }) => { + // Make some changes to the documents + return documents + } +} +``` + +Then, you can specify the file name as follows: + +```ts {9} +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: ['./my-document-transform.js'] + } + } +} +export default config +``` + +## Configuration + +If you want to change the behavior of a document transform at runtime, you can simply prepare a function that generates the document transform and write the configuration in its argument. + +```ts {3-10, 18} +import type { CodegenConfig } from '@graphql-codegen/cli' + +const generateDocumentTransform: (config: { queryName: string }) => Types.DocumentTransformObject = ({ queryName }) => { + return { + transform: ({ documents }) => { + // Modify something in documents using `queryName`. + return documents + } + } +} + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [generateDocumentTransform({ queryName: 'test' })] + } + } +} +export default config +``` + +If you want to specify the document transform by file, do the following: + +```ts {11-13} +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'https://localhost:4000/graphql', + documents: ['src/**/*.tsx'], + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [ + { + './my-document-transform.js': { + queryName: 'test' + } + } + ] + } + } +} +export default config +``` + +In this case, you can retrieve the `queryName` from the `config` argument of the `transform` function as follows: + +```js +module.exports = { + transform: ({ documents, config }) => { + // Modify something in documents using `config.queryName`. + return documents + } +} +``` diff --git a/website/src/pages/docs/custom-codegen/_meta.json b/website/src/pages/docs/custom-codegen/_meta.json index 9a93ef04b6f..dce618ba955 100644 --- a/website/src/pages/docs/custom-codegen/_meta.json +++ b/website/src/pages/docs/custom-codegen/_meta.json @@ -4,6 +4,5 @@ "validate-configuration": "Validate Configuration", "extend-schema": "Extend Schema", "using-visitor": "Using Visitor Pattern", - "document-transform": "Document Transform", "contributing": "Contributing" } diff --git a/website/src/pages/docs/custom-codegen/document-transform.mdx b/website/src/pages/docs/custom-codegen/document-transform.mdx deleted file mode 100644 index 21f05d063e3..00000000000 --- a/website/src/pages/docs/custom-codegen/document-transform.mdx +++ /dev/null @@ -1,115 +0,0 @@ -import { Callout } from '@theguild/components' - -# Document Transform - -## Basic Usage - -Each plugin can also provide a function `transformDocuments` for transforming `documents`: - -```js {5-14} -const { visit } = require('graphql') - -module.exports = { - plugin() {}, // Nothing to do - transformDocuments(schema, documents, config) { - return documents.map(documentFile => { - documentFile.document = visit(documentFile.document, { - Field(node) { - // This function triggered per each field - } - }) - return documentFile - }) - } -} -``` - -You can specify the plugin with `documentTransforms` field in your config: - -```ts {9} -import { CodegenConfig } from '@graphql-codegen/cli' - -const config: CodegenConfig = { - schema: 'http://localhost:4000/graphql', - documents: ['src/**/*.tsx'], - generates: { - './src/gql/': { - preset: 'client', - documentTransforms: ['./my-transform.js'], - plugins: [] - } - } -} - -export default config -``` - - - If you specify it with only the `plugins` field and not with the `documentTransforms` field, the `transformDocuments` - function will not be executed. - - -## Example - -For example, let's remove a `@loacalOnlyDirective` directive from documents: - -```js -const { visit, print } = require('graphql') - -module.exports = { - plugin(schema, documents, config) { - // Output `documents` as an example. - return documents.map(documentFile => `${print(documentFile.document)}`).join('\n') - }, - transformDocuments(schema, documents, config) { - return documents.map(documentFile => { - documentFile.document = visit(documentFile.document, { - Directive: { - leave(node) { - if (node.name.value === 'localOnlyDirective') return null - } - } - }) - return documentFile - }) - } -} -``` - -If you want to execute the `plugin` function as above, you specify it with the `plugins` field as well as the `documentTransforms` field in your config: - -```ts {9-10} -import { CodegenConfig } from '@graphql-codegen/cli' - -const config: CodegenConfig = { - schema: 'http://localhost:4000/graphql', - documents: ['src/**/*.tsx'], - generates: { - './src/gql/': { - preset: 'client', - documentTransforms: ['./my-transform.js'], - plugins: ['./my-transform.js'] - } - } -} - -export default config -``` - -## Validation - -The `validate` function is not executed before the `transformDocuments` function in the plugin. If validation is necessary, you can simply throw an error in the `transformDocuments`. - -```js {6-8} -const { visit } = require('graphql') - -module.exports = { - plugin() {}, - transformDocuments(schema, documents, config) { - if (config.somethingWrong) { - throw new Error(`Something wrong!`) - } - return documents - } -} -``` From 2a8e14fb2799909aac7885d6819c717b03f1a6c6 Mon Sep 17 00:00:00 2001 From: Kyohei Nakamori Date: Tue, 21 Feb 2023 09:00:25 +0900 Subject: [PATCH 15/15] Remove `addToSchema` in document transform --- .changeset/short-toes-relax.md | 3 +- .../graphql-codegen-cli/tests/codegen.spec.ts | 109 +++--------------- packages/graphql-codegen-core/src/codegen.ts | 22 +--- packages/utils/plugins-helpers/src/types.ts | 9 -- .../docs/advanced/document-transform.mdx | 7 +- 5 files changed, 19 insertions(+), 131 deletions(-) diff --git a/.changeset/short-toes-relax.md b/.changeset/short-toes-relax.md index d1d8d2f4853..d352658a3ed 100644 --- a/.changeset/short-toes-relax.md +++ b/.changeset/short-toes-relax.md @@ -9,7 +9,7 @@ Introduce a new feature called DocumentTransform. -DocumentTransform is a functionality that allows you to modify `documents` before they are processed by plugins. You can use functions passed to the `documentTransforms` option to make changes to GraphQL documents or extend the schema, as needed. +DocumentTransform is a functionality that allows you to modify `documents` before they are processed by plugins. You can use functions passed to the `documentTransforms` option to make changes to GraphQL documents. To use this feature, you can write `documentTransforms` as follows: @@ -28,7 +28,6 @@ const config: CodegenConfig = { // Make some changes to the documents return documents; }, - addToSchema: 'extend type Query { test: String! }', }, ], }, diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index 0034eec334f..e15a70bbf45 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1272,33 +1272,8 @@ describe('Codegen Executor', () => { } }); - it('should be able to extend the schema.', async () => { - const documentTransform: Types.DocumentTransformObject = { - transform: ({ documents }) => { - return documents; - }, - addToSchema: `extend type Query { test: String! }`, - }; - - const output = await executeCodegen({ - schema: SIMPLE_TEST_SCHEMA, - documents: `query foo { f }`, - generates: { - 'out1.ts': { - plugins: ['typescript', 'typescript-operations'], - documentTransforms: [documentTransform], - }, - }, - }); - - expect(output.length).toBe(1); - expect(output[0].content).toContain(`test: Scalars['String']`); - }); - }); - - it('should be able to dynamically extend the schema.', async () => { - const documentTransform: Types.DocumentTransformObject = { - transform: ({ documents }) => { + it('Should transform documents with client-preset', async () => { + const transform: Types.DocumentTransformFunction = ({ documents }) => { const newDocuments = [ { document: { @@ -1306,84 +1281,28 @@ describe('Codegen Executor', () => { definitions: [ { ...documents[0].document.definitions[0], - selectionSet: { - kind: Kind.SELECTION_SET, - selections: [ - { - kind: Kind.FIELD, - name: { kind: Kind.NAME, value: 'test' }, - }, - ], - }, + name: { kind: Kind.NAME, value: 'bar' }, } as OperationDefinitionNode, ], }, }, ]; return newDocuments; - }, - addToSchema: ({ schema, schemaAst, documents }) => { - // Check that the arguments schema, schemaAST, and documents exist correctly. - // These following are only needed as a test and is of no importance for the extension of the schema in this case. - const fieldObjectTypeFromSchema = schema.definitions.find( - node => node.kind === Kind.OBJECT_TYPE_DEFINITION && node.fields.find(field => field.name.value === 'f') - ); - const fieldFromSchemaAST = schemaAst.getQueryType().getFields()['f']; - const operationDefinitionNode = documents[0].document!.definitions[0] as OperationDefinitionNode; - - if (operationDefinitionNode.name.value === 'foo' && fieldObjectTypeFromSchema && fieldFromSchemaAST) { - return `extend type Query { test: String! }`; - } - return ''; - }, - }; - - const output = await executeCodegen({ - schema: SIMPLE_TEST_SCHEMA, - documents: `query foo { f }`, - generates: { - 'out1.ts': { - plugins: ['typescript', 'typescript-operations'], - documentTransforms: [documentTransform], - }, - }, - }); - - expect(output.length).toBe(1); - expect(output[0].content).toContain(`test: Scalars['String']`); - expect(output[0].content).toContain(`export type FooQuery = { __typename?: 'Query', test: string }`); - }); + }; - it('Should transform documents with client-preset', async () => { - const transform: Types.DocumentTransformFunction = ({ documents }) => { - const newDocuments = [ - { - document: { - ...documents[0].document, - definitions: [ - { - ...documents[0].document.definitions[0], - name: { kind: Kind.NAME, value: 'bar' }, - } as OperationDefinitionNode, - ], + const output = await executeCodegen({ + schema: SIMPLE_TEST_SCHEMA, + documents: `query foo { f }`, + generates: { + './src/gql/': { + preset: 'client', + documentTransforms: [{ transform }], }, }, - ]; - return newDocuments; - }; + }); - const output = await executeCodegen({ - schema: SIMPLE_TEST_SCHEMA, - documents: `query foo { f }`, - generates: { - './src/gql/': { - preset: 'client', - documentTransforms: [{ transform }], - }, - }, + const fileOutput = output.find(file => file.filename === './src/gql/graphql.ts'); + expect(fileOutput.content).toContain('export type BarQuery'); }); - - const fileOutput = output.find(file => file.filename === './src/gql/graphql.ts'); - expect(fileOutput.content).toContain('export type BarQuery'); }); }); diff --git a/packages/graphql-codegen-core/src/codegen.ts b/packages/graphql-codegen-core/src/codegen.ts index b325109ffc0..e1da67b94ee 100644 --- a/packages/graphql-codegen-core/src/codegen.ts +++ b/packages/graphql-codegen-core/src/codegen.ts @@ -44,27 +44,6 @@ export async function codegen(options: Types.GenerateOptions): Promise { } } - const documentTransforms = Array.isArray(options.documentTransforms) ? options.documentTransforms : []; - for (const documentTransform of documentTransforms) { - const addToSchema = documentTransform.transformObject.addToSchema; - const partialSchema = - typeof addToSchema === 'function' - ? addToSchema({ - schema: options.schema, - schemaAst: options.schemaAst, - documents: options.documents, - config: { - ...options.config, - ...documentTransform.config, - }, - pluginContext: options.pluginContext, - }) - : addToSchema; - if (partialSchema) { - additionalTypeDefs.push(partialSchema); - } - } - const federationInConfig: boolean = pickFlag('federation', options.config); const isFederation = prioritize(federationInConfig, false); @@ -93,6 +72,7 @@ export async function codegen(options: Types.GenerateOptions): Promise { const schemaDocumentNode = mergeNeeded || !options.schema ? getCachedDocumentNodeFromSchema(schemaInstance) : options.schema; + const documentTransforms = Array.isArray(options.documentTransforms) ? options.documentTransforms : []; const transformedDocuments = await transformDocuments({ ...options, documentTransforms, diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 3a15b347ce4..60446962171 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -624,17 +624,8 @@ export namespace Types { pluginContext?: { [key: string]: any }; }) => Types.Promisable; - export type DocumentTransformAddToSchemaFunction = (options: { - documents: Types.DocumentFile[]; - schema: DocumentNode; - schemaAst?: GraphQLSchema; - config: Config; - pluginContext?: { [key: string]: any }; - }) => AddToSchemaResult; - export type DocumentTransformObject = { transform: DocumentTransformFunction; - addToSchema?: AddToSchemaResult | DocumentTransformAddToSchemaFunction; }; export type DocumentTransformFileName = string; diff --git a/website/src/pages/docs/advanced/document-transform.mdx b/website/src/pages/docs/advanced/document-transform.mdx index 52ad56b0a3d..475acb99e50 100644 --- a/website/src/pages/docs/advanced/document-transform.mdx +++ b/website/src/pages/docs/advanced/document-transform.mdx @@ -3,11 +3,11 @@ import { Callout } from '@theguild/components' # Document Transform Document transform is a feature that allows you to modify `documents` before they are used by plugins. -You can use functions passed to the `documentTransforms` option to make changes to GraphQL `documents` or extend the schema, as needed. +You can use functions passed to the `documentTransforms` option to make changes to GraphQL `documents`. ## Basic Usage -Document transform has `transform` function and optional `addToSchema` function. +Document transform has `transform` function. Let's specify an object containing those functions as the `documentTransforms` option as follows: @@ -25,8 +25,7 @@ const config: CodegenConfig = { transform: ({ documents }) => { // Make some changes to the documents return documents - }, - addToSchema: 'extend type Query { test: String! }' + } } ] }