Skip to content

Commit 8c4db2a

Browse files
ikusakov2ikusakoveddeee888
authored
New feature: schema can be passed pre-parsed to generate() function as GraphQLSchema (#10617)
* Adding feature to pass parsed schema directly to generate() function * Add changeset --------- Co-authored-by: Igor Kusakov <[email protected]> Co-authored-by: Eddy Nguyen <[email protected]>
1 parent 916d023 commit 8c4db2a

6 files changed

Lines changed: 165 additions & 12 deletions

File tree

.changeset/weak-webs-lie.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphql-codegen/plugin-helpers': minor
3+
'@graphql-codegen/cli': minor
4+
---
5+
6+
Allow GraphQLSchema to be passed directly to generate({schema: ...}) function

packages/graphql-codegen-cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"@graphql-tools/json-file-loader": "^8.0.26",
5555
"@graphql-tools/load": "^8.1.8",
5656
"@graphql-tools/url-loader": "^9.0.6",
57+
"@graphql-tools/merge": "^9.0.6",
5758
"@graphql-tools/utils": "^11.0.0",
5859
"@inquirer/prompts": "^7.8.2",
5960
"@whatwg-node/fetch": "^0.10.0",
@@ -76,7 +77,6 @@
7677
"yargs": "^17.0.0"
7778
},
7879
"devDependencies": {
79-
"@graphql-tools/merge": "9.0.6",
8080
"@parcel/watcher": "^2.1.0",
8181
"@types/is-glob": "4.0.4",
8282
"@types/js-yaml": "4.0.9",

packages/graphql-codegen-cli/src/codegen.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
Types,
1414
} from '@graphql-codegen/plugin-helpers';
1515
import { NoTypeDefinitionsFound } from '@graphql-tools/load';
16-
import { DocumentNode, GraphQLError, GraphQLSchema } from 'graphql';
16+
import { buildASTSchema, DocumentNode, GraphQLError, GraphQLSchema, isSchema } from 'graphql';
17+
import { mergeTypeDefs } from '@graphql-tools/merge';
1718
import { Listr, ListrTask } from 'listr2';
1819
import { CodegenContext, ensureContext } from './config.js';
1920
import { getPluginByName } from './plugins.js';
@@ -85,6 +86,19 @@ export async function executeCodegen(
8586

8687
const cache = createCache();
8788

89+
// We need a simple string to uniqually identify the provided GraphQLSchema objects for the above cache.
90+
// Because JavaScript does not provide access to its internal object ids, we need a workaround.
91+
// Below is a common way to get unique ids for objects in JavaScript,
92+
// by using a WeakMap and autoincrementing the id.
93+
const jsObjectIds = new WeakMap<GraphQLSchema, number>();
94+
let jsObjectIdCounter = 0;
95+
function getJsObjectId(schema: GraphQLSchema): number {
96+
if (!jsObjectIds.has(schema)) {
97+
jsObjectIds.set(schema, jsObjectIdCounter++);
98+
}
99+
return jsObjectIds.get(schema)!;
100+
}
101+
88102
function wrapTask(task: () => void | Promise<void>, source: string, taskName: string, ctx: Ctx) {
89103
return () =>
90104
context.profiler.run(async () => {
@@ -232,19 +246,32 @@ export async function executeCodegen(
232246
async () => {
233247
debugLog(`[CLI] Loading Schemas`);
234248
const schemaPointerMap: any = {};
249+
const parsedSchemas: GraphQLSchema[] = [];
235250
const allSchemaDenormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
236251

237252
for (const denormalizedPtr of allSchemaDenormalizedPointers) {
238-
if (typeof denormalizedPtr === 'string') {
253+
if (isSchema(denormalizedPtr)) {
254+
parsedSchemas.push(denormalizedPtr);
255+
} else if (typeof denormalizedPtr === 'string') {
239256
schemaPointerMap[denormalizedPtr] = {};
240257
} else if (typeof denormalizedPtr === 'object') {
241258
Object.assign(schemaPointerMap, denormalizedPtr);
242259
}
243260
}
244261

245-
const hash = JSON.stringify(schemaPointerMap);
262+
const hash = JSON.stringify(schemaPointerMap) + parsedSchemas.map(getJsObjectId).join(',');
246263
const result = await cache('schema', hash, async () => {
247-
const outputSchemaAst = await context.loadSchema(schemaPointerMap);
264+
// collect parsed schemas
265+
const schemasToMerge: GraphQLSchema[] = [...parsedSchemas];
266+
// collect schemas, provided by pointers
267+
if (Object.keys(schemaPointerMap).length) {
268+
schemasToMerge.push(await context.loadSchema(schemaPointerMap));
269+
}
270+
// merge all collected schemas into one
271+
const outputSchemaAst =
272+
schemasToMerge.length === 1
273+
? schemasToMerge[0]
274+
: buildASTSchema(mergeTypeDefs(schemasToMerge));
248275
const outputSchema = getCachedDocumentNodeFromSchema(outputSchemaAst);
249276
return {
250277
outputSchemaAst,

packages/graphql-codegen-cli/tests/codegen.spec.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,125 @@ describe('Codegen Executor', () => {
700700
});
701701
});
702702

703+
describe('Parsed GraphQLSchema input', () => {
704+
it('should accept a GraphQLSchema instance as root schema', async () => {
705+
const schema = buildSchema(SIMPLE_TEST_SCHEMA);
706+
const { result } = await executeCodegen({
707+
schema,
708+
generates: {
709+
'out1.ts': { plugins: ['typescript'] },
710+
},
711+
});
712+
713+
expect(result.length).toBe(1);
714+
expect(result[0].content).toContain('MyType');
715+
});
716+
717+
it('should accept a GraphQLSchema instance in an array', async () => {
718+
const schema = buildSchema(SIMPLE_TEST_SCHEMA);
719+
const { result } = await executeCodegen({
720+
schema: [schema],
721+
generates: {
722+
'out1.ts': { plugins: ['typescript'] },
723+
},
724+
});
725+
726+
expect(result.length).toBe(1);
727+
expect(result[0].content).toContain('MyType');
728+
});
729+
730+
it('should merge a GraphQLSchema instance with an SDL string', async () => {
731+
const schema = buildSchema(SIMPLE_TEST_SCHEMA);
732+
const { result } = await executeCodegen({
733+
schema: [schema, `type Post { id: ID! }`],
734+
generates: {
735+
'out1.ts': { plugins: ['typescript'] },
736+
},
737+
});
738+
739+
expect(result.length).toBe(1);
740+
expect(result[0].content).toContain('MyType');
741+
expect(result[0].content).toContain('Post');
742+
});
743+
744+
it('should merge two GraphQLSchema instances', async () => {
745+
const schemaA = buildSchema(SIMPLE_TEST_SCHEMA);
746+
const schemaB = buildSchema(`type Post { id: ID! } type Query { posts: [Post] }`);
747+
const { result } = await executeCodegen({
748+
schema: [schemaA, schemaB],
749+
generates: {
750+
'out1.ts': { plugins: ['typescript'] },
751+
},
752+
});
753+
754+
expect(result.length).toBe(1);
755+
expect(result[0].content).toContain('MyType');
756+
expect(result[0].content).toContain('Post');
757+
});
758+
759+
it('should accept a GraphQLSchema instance as output-level schema', async () => {
760+
const schema = buildSchema(SIMPLE_TEST_SCHEMA);
761+
const { result } = await executeCodegen({
762+
generates: {
763+
'out1.ts': {
764+
schema,
765+
plugins: ['typescript'],
766+
},
767+
},
768+
});
769+
770+
expect(result.length).toBe(1);
771+
expect(result[0].content).toContain('MyType');
772+
});
773+
774+
it('should merge root SDL string with output-level GraphQLSchema instance', async () => {
775+
const outputSchema = buildSchema(`type Post { id: ID! } type Query { posts: [Post] }`);
776+
const { result } = await executeCodegen({
777+
schema: SIMPLE_TEST_SCHEMA,
778+
generates: {
779+
'out1.ts': {
780+
schema: outputSchema,
781+
plugins: ['typescript'],
782+
},
783+
},
784+
});
785+
786+
expect(result.length).toBe(1);
787+
expect(result[0].content).toContain('MyType');
788+
expect(result[0].content).toContain('Post');
789+
});
790+
791+
it('should work with documents when schema is a GraphQLSchema instance', async () => {
792+
const schema = buildSchema(SIMPLE_TEST_SCHEMA);
793+
const { result } = await executeCodegen({
794+
schema,
795+
documents: `query root { f }`,
796+
generates: {
797+
'out1.ts': { plugins: ['typescript', 'typescript-operations'] },
798+
},
799+
});
800+
801+
expect(result.length).toBe(1);
802+
expect(result[0].content).toContain('RootQuery');
803+
});
804+
805+
it('should preserve custom scalars from a GraphQLSchema instance', async () => {
806+
const schema = buildSchema(`
807+
scalar DateTime
808+
type Query { now: DateTime }
809+
`);
810+
const { result } = await executeCodegen({
811+
schema,
812+
generates: {
813+
'out1.ts': { plugins: ['typescript'] },
814+
},
815+
});
816+
817+
expect(result.length).toBe(1);
818+
expect(result[0].content).toContain('DateTime');
819+
});
820+
});
821+
703822
describe('Custom schema loader', () => {
704823
it('Should allow custom loaders to load schema on root level', async () => {
705824
await executeCodegen({

packages/utils/plugins-helpers/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ export namespace Types {
211211
| LocalSchemaPathWithOptions
212212
| SchemaGlobPath
213213
| SchemaWithLoader
214-
| SchemaFromCodeFile;
214+
| SchemaFromCodeFile
215+
| GraphQLSchema;
215216

216217
/* Document Definitions */
217218
export type OperationDocumentGlobPath = string;

yarn.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4860,16 +4860,16 @@
48604860
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
48614861
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
48624862

4863-
"@types/unist@*", "@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
4864-
version "2.0.11"
4865-
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4"
4866-
integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
4867-
4868-
"@types/unist@^3.0.0":
4863+
"@types/unist@*", "@types/unist@^3.0.0":
48694864
version "3.0.0"
48704865
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.0.tgz#988ae8af1e5239e89f9fbb1ade4c935f4eeedf9a"
48714866
integrity sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==
48724867

4868+
"@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
4869+
version "2.0.11"
4870+
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4"
4871+
integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
4872+
48734873
"@types/ws@^8.0.0":
48744874
version "8.5.4"
48754875
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5"

0 commit comments

Comments
 (0)