diff --git a/.changeset/short-ladybugs-develop.md b/.changeset/short-ladybugs-develop.md index 55458687a72..6f80876e656 100644 --- a/.changeset/short-ladybugs-develop.md +++ b/.changeset/short-ladybugs-develop.md @@ -3,14 +3,12 @@ '@graphql-tools/schema': minor '@graphql-tools/stitch': minor '@graphql-tools/utils': minor -'@graphql-tools/code-file-loader': major --- -`schemaExtensions` option has been added to `mergeSchemas`, `makeExecutableSchema` and `stitchSchemas` configurations +- `schemaExtensions` option has been added to `mergeSchemas`, `makeExecutableSchema` and `stitchSchemas` configurations Breaking Changes; - Move `mergeSchemas` and `MergeSchemasConfig` from `@graphql-tools/merge` to `@graphql-tools/schema` package to prevent circular dependency between them. - `mergeSchemasAsync` has been removed. - Move `NamedDefinitionNode`, `resetComments`, `collectComment`, `pushComment` and `printComment` from `@graphql-tools/merge` to `@graphql-tools/utils`. -- Code File Loader no more returns multiple sources for each plucked GraphQL SDL. This breaks other tools such as GraphQL Code Generator. diff --git a/.changeset/witty-parrots-help.md b/.changeset/witty-parrots-help.md new file mode 100644 index 00000000000..15cfe67ab98 --- /dev/null +++ b/.changeset/witty-parrots-help.md @@ -0,0 +1,5 @@ +--- +'@graphql-tools/code-file-loader': patch +--- + +"@graphql-tools/code-file-loader" no more returns multiple sources for each plucked GraphQL SDL. This breaks other tools such as GraphQL Code Generator. diff --git a/packages/merge/src/typedefs-mergers/comments.ts b/packages/merge/src/typedefs-mergers/comments.ts deleted file mode 100644 index 1ae8ec9508b..00000000000 --- a/packages/merge/src/typedefs-mergers/comments.ts +++ /dev/null @@ -1,404 +0,0 @@ -import { Maybe } from '@graphql-tools/utils'; -import { - getDescription, - StringValueNode, - FieldDefinitionNode, - ASTNode, - NameNode, - TypeNode, - visit, - VisitFn, -} from 'graphql'; -import type { ASTVisitor } from 'graphql/language/visitor'; -import { NamedDefinitionNode } from './merge-nodes'; - -const MAX_LINE_LENGTH = 80; - -let commentsRegistry: { - [path: string]: string[]; -} = {}; - -export function resetComments(): void { - commentsRegistry = {}; -} - -export function collectComment(node: NamedDefinitionNode): void { - const entityName = node.name?.value; - if (entityName == null) { - return; - } - - pushComment(node, entityName); - - switch (node.kind) { - case 'EnumTypeDefinition': - if (node.values) { - for (const value of node.values) { - pushComment(value, entityName, value.name.value); - } - } - break; - - case 'ObjectTypeDefinition': - case 'InputObjectTypeDefinition': - case 'InterfaceTypeDefinition': - if (node.fields) { - for (const field of node.fields) { - pushComment(field, entityName, field.name.value); - - if (isFieldDefinitionNode(field) && field.arguments) { - for (const arg of field.arguments) { - pushComment(arg, entityName, field.name.value, arg.name.value); - } - } - } - } - break; - } -} - -export function pushComment(node: any, entity: string, field?: string, argument?: string): void { - const comment = getDescription(node, { commentDescriptions: true }); - - if (typeof comment !== 'string' || comment.length === 0) { - return; - } - - const keys = [entity]; - - if (field) { - keys.push(field); - - if (argument) { - keys.push(argument); - } - } - - const path = keys.join('.'); - - if (!commentsRegistry[path]) { - commentsRegistry[path] = []; - } - - commentsRegistry[path].push(comment); -} - -export function printComment(comment: string): string { - return '\n# ' + comment.replace(/\n/g, '\n# '); -} - -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * NOTE: ==> This file has been modified just to add comments to the printed AST - * This is a temp measure, we will move to using the original non modified printer.js ASAP. - */ - -// import { visit, VisitFn } from 'graphql'; - -/** - * Given maybeArray, print an empty string if it is null or empty, otherwise - * print all items together separated by separator if provided - */ -function join(maybeArray?: readonly any[], separator?: string) { - return maybeArray ? maybeArray.filter(x => x).join(separator || '') : ''; -} - -function hasMultilineItems(maybeArray: Maybe>): boolean { - return maybeArray?.some(str => str.includes('\n')) ?? false; -} - -function addDescription(cb: VisitFn): VisitFn { - return ( - node: { description?: StringValueNode; name?: NameNode; type?: TypeNode; kind: string }, - _key, - _parent, - path, - ancestors - ) => { - const keys: string[] = []; - const parent = path.reduce((prev, key) => { - if (['fields', 'arguments', 'values'].includes(key as any) && prev.name) { - keys.push(prev.name.value); - } - - return prev[key]; - }, ancestors[0]); - - const key = [...keys, parent?.name?.value].filter(Boolean).join('.'); - const items: string[] = []; - - if (node.kind.includes('Definition') && commentsRegistry[key]) { - items.push(...commentsRegistry[key]); - } - - return join([...items.map(printComment), node.description, (cb as any)(node)], '\n'); - }; -} - -function indent(maybeString?: string) { - return maybeString && ` ${maybeString.replace(/\n/g, '\n ')}`; -} - -/** - * Given array, print each item on its own line, wrapped in an - * indented "{ }" block. - */ -function block(array?: readonly any[]) { - return array && array.length !== 0 ? `{\n${indent(join(array, '\n'))}\n}` : ''; -} - -/** - * If maybeString is not null or empty, then wrap with start and end, otherwise - * print an empty string. - */ -function wrap(start: string, maybeString: any, end?: string) { - return maybeString ? start + maybeString + (end || '') : ''; -} - -/** - * Print a block string in the indented block form by adding a leading and - * trailing blank line. However, if a block string starts with whitespace and is - * a single-line, adding a leading blank line would strip that whitespace. - */ -function printBlockString(value: string, isDescription = false) { - const escaped = value.replace(/"""/g, '\\"""'); - return (value[0] === ' ' || value[0] === '\t') && value.indexOf('\n') === -1 - ? `"""${escaped.replace(/"$/, '"\n')}"""` - : `"""\n${isDescription ? escaped : indent(escaped)}\n"""`; -} - -const printDocASTReducer: ASTVisitor = { - Name: { leave: node => node.value }, - Variable: { leave: node => '$' + node.name }, - - // Document - - Document: { - leave: node => join(node.definitions, '\n\n'), - }, - - OperationDefinition: { - leave: node => { - const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')'); - const prefix = join([node.operation, join([node.name, varDefs]), join(node.directives, ' ')], ' '); - - // the query short form. - return prefix + ' ' + node.selectionSet; - }, - }, - - VariableDefinition: { - leave: ({ variable, type, defaultValue, directives }) => - variable + ': ' + type + wrap(' = ', defaultValue) + wrap(' ', join(directives, ' ')), - }, - - SelectionSet: { leave: ({ selections }) => block(selections) }, - - Field: { - leave({ alias, name, arguments: args, directives, selectionSet }) { - const prefix = wrap('', alias, ': ') + name; - let argsLine = prefix + wrap('(', join(args, ', '), ')'); - - if (argsLine.length > MAX_LINE_LENGTH) { - argsLine = prefix + wrap('(\n', indent(join(args, '\n')), '\n)'); - } - - return join([argsLine, join(directives, ' '), selectionSet], ' '); - }, - }, - - Argument: { leave: ({ name, value }) => name + ': ' + value }, - - // Fragments - - FragmentSpread: { - leave: ({ name, directives }) => '...' + name + wrap(' ', join(directives, ' ')), - }, - - InlineFragment: { - leave: ({ typeCondition, directives, selectionSet }) => - join(['...', wrap('on ', typeCondition), join(directives, ' '), selectionSet], ' '), - }, - - FragmentDefinition: { - leave: ({ name, typeCondition, variableDefinitions, directives, selectionSet }) => - // Note: fragment variable definitions are experimental and may be changed - // or removed in the future. - `fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` + - `on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` + - selectionSet, - }, - - // Value - - IntValue: { leave: ({ value }) => value }, - FloatValue: { leave: ({ value }) => value }, - StringValue: { - leave: ({ value, block: isBlockString }) => (isBlockString ? printBlockString(value) : JSON.stringify(value)), - }, - BooleanValue: { leave: ({ value }) => (value ? 'true' : 'false') }, - NullValue: { leave: () => 'null' }, - EnumValue: { leave: ({ value }) => value }, - ListValue: { leave: ({ values }) => '[' + join(values, ', ') + ']' }, - ObjectValue: { leave: ({ fields }) => '{' + join(fields, ', ') + '}' }, - ObjectField: { leave: ({ name, value }) => name + ': ' + value }, - - // Directive - - Directive: { - leave: ({ name, arguments: args }) => '@' + name + wrap('(', join(args, ', '), ')'), - }, - - // Type - - NamedType: { leave: ({ name }) => name }, - ListType: { leave: ({ type }) => '[' + type + ']' }, - NonNullType: { leave: ({ type }) => type + '!' }, - - // Type System Definitions - - SchemaDefinition: { - leave: ({ description, directives, operationTypes }: any) => - wrap('', description, '\n') + join(['schema', join(directives, ' '), block(operationTypes)], ' '), - }, - - OperationTypeDefinition: { - leave: ({ operation, type }) => operation + ': ' + type, - }, - - ScalarTypeDefinition: { - leave: ({ description, name, directives }) => - wrap('', description, '\n') + join(['scalar', name, join(directives, ' ')], ' '), - }, - - ObjectTypeDefinition: { - leave: ({ description, name, interfaces, directives, fields }) => - wrap('', description, '\n') + - join(['type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], ' '), - }, - - FieldDefinition: { - leave: ({ description, name, arguments: args, type, directives }) => - wrap('', description, '\n') + - name + - (hasMultilineItems(args as any as string[]) - ? wrap('(\n', indent(join(args, '\n')), '\n)') - : wrap('(', join(args, ', '), ')')) + - ': ' + - type + - wrap(' ', join(directives, ' ')), - }, - - InputValueDefinition: { - leave: ({ description, name, type, defaultValue, directives }) => - wrap('', description, '\n') + join([name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')], ' '), - }, - - InterfaceTypeDefinition: { - leave: ({ description, name, interfaces, directives, fields }: any) => - wrap('', description, '\n') + - join( - ['interface', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], - ' ' - ), - }, - - UnionTypeDefinition: { - leave: ({ description, name, directives, types }) => - wrap('', description, '\n') + join(['union', name, join(directives, ' '), wrap('= ', join(types, ' | '))], ' '), - }, - - EnumTypeDefinition: { - leave: ({ description, name, directives, values }) => - wrap('', description, '\n') + join(['enum', name, join(directives, ' '), block(values)], ' '), - }, - - EnumValueDefinition: { - leave: ({ description, name, directives }) => - wrap('', description, '\n') + join([name, join(directives, ' ')], ' '), - }, - - InputObjectTypeDefinition: { - leave: ({ description, name, directives, fields }) => - wrap('', description, '\n') + join(['input', name, join(directives, ' '), block(fields)], ' '), - }, - - DirectiveDefinition: { - leave: ({ description, name, arguments: args, repeatable, locations }) => - wrap('', description, '\n') + - 'directive @' + - name + - (hasMultilineItems(args as any as string[]) - ? wrap('(\n', indent(join(args, '\n')), '\n)') - : wrap('(', join(args, ', '), ')')) + - (repeatable ? ' repeatable' : '') + - ' on ' + - join(locations, ' | '), - }, - - SchemaExtension: { - leave: ({ directives, operationTypes }) => - join(['extend schema', join(directives, ' '), block(operationTypes)], ' '), - }, - - ScalarTypeExtension: { - leave: ({ name, directives }) => join(['extend scalar', name, join(directives, ' ')], ' '), - }, - - ObjectTypeExtension: { - leave: ({ name, interfaces, directives, fields }) => - join( - ['extend type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], - ' ' - ), - }, - - InterfaceTypeExtension: { - leave: ({ name, interfaces, directives, fields }: any) => - join( - ['extend interface', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], - ' ' - ), - }, - - UnionTypeExtension: { - leave: ({ name, directives, types }) => - join(['extend union', name, join(directives, ' '), wrap('= ', join(types, ' | '))], ' '), - }, - - EnumTypeExtension: { - leave: ({ name, directives, values }) => join(['extend enum', name, join(directives, ' '), block(values)], ' '), - }, - - InputObjectTypeExtension: { - leave: ({ name, directives, fields }) => join(['extend input', name, join(directives, ' '), block(fields)], ' '), - }, -}; - -const printDocASTReducerWithComments = Object.keys(printDocASTReducer).reduce( - (prev, key) => ({ - ...prev, - [key]: { - leave: addDescription(printDocASTReducer[key].leave), - }, - }), - {} as typeof printDocASTReducer -); - -/** - * Converts an AST into a string, using one set of reasonable - * formatting rules. - */ -export function printWithComments(ast: ASTNode) { - return visit(ast, printDocASTReducerWithComments); -} - -function isFieldDefinitionNode(node: any): node is FieldDefinitionNode { - return node.kind === 'FieldDefinition'; -} diff --git a/packages/merge/src/typedefs-mergers/index.ts b/packages/merge/src/typedefs-mergers/index.ts index d51bec38aa5..44e1b0f5323 100644 --- a/packages/merge/src/typedefs-mergers/index.ts +++ b/packages/merge/src/typedefs-mergers/index.ts @@ -1,5 +1,4 @@ export * from './arguments'; -export * from './comments'; export * from './directives'; export * from './enum-values'; export * from './enum'; diff --git a/packages/merge/src/typedefs-mergers/merge-nodes.ts b/packages/merge/src/typedefs-mergers/merge-nodes.ts index 58a4e28a093..be999a6cc4d 100644 --- a/packages/merge/src/typedefs-mergers/merge-nodes.ts +++ b/packages/merge/src/typedefs-mergers/merge-nodes.ts @@ -7,9 +7,8 @@ import { mergeUnion } from './union'; import { mergeInputType } from './input-type'; import { mergeInterface } from './interface'; import { mergeDirective } from './directives'; -import { collectComment } from './comments'; import { mergeSchemaDefs } from './schema-def'; -import { NamedDefinitionNode } from '@graphql-tools/utils'; +import { NamedDefinitionNode, collectComment } from '@graphql-tools/utils'; export const schemaDefSymbol = 'SCHEMA_DEF_SYMBOL'; diff --git a/packages/merge/src/typedefs-mergers/merge-typedefs.ts b/packages/merge/src/typedefs-mergers/merge-typedefs.ts index 9ccd5112993..a04734fa17c 100644 --- a/packages/merge/src/typedefs-mergers/merge-typedefs.ts +++ b/packages/merge/src/typedefs-mergers/merge-typedefs.ts @@ -11,12 +11,13 @@ import { } from 'graphql'; import { CompareFn, defaultStringComparator, isSourceTypes, isStringTypes } from './utils'; import { MergedResultMap, mergeGraphQLNodes, schemaDefSymbol } from './merge-nodes'; -import { resetComments, printWithComments } from './comments'; import { getDocumentNodeFromSchema, GetDocumentNodeFromSchemaOptions, isDocumentNode, TypeSource, + resetComments, + printWithComments, } from '@graphql-tools/utils'; import { DEFAULT_OPERATION_TYPE_NAME_MAP } from './schema-def'; diff --git a/packages/mock/src/MockStore.ts b/packages/mock/src/MockStore.ts index 7104fbf216d..111dbfc5b9b 100644 --- a/packages/mock/src/MockStore.ts +++ b/packages/mock/src/MockStore.ts @@ -579,3 +579,46 @@ function assertIsDefined(value: T, message?: string): asserts value is NonNul process.env['NODE_ENV'] === 'production' ? 'Invariant failed:' : `Invariant failed: ${message || ''}` ); } + +/** + * Will create `MockStore` for the given `schema`. + * + * A `MockStore` will generate mock values for the given schem when queried. + * + * It will stores generated mocks, so that, provided with same arguments + * the returned values will be the same. + * + * Its API also allows to modify the stored values. + * + * Basic example: + * ```ts + * store.get('User', 1, 'name'); + * // > "Hello World" + * store.set('User', 1, 'name', 'Alexandre'); + * store.get('User', 1, 'name'); + * // > "Alexandre" + * ``` + * + * The storage key will correspond to the "key field" + * of the type. Field with name `id` or `_id` will be + * by default considered as the key field for the type. + * However, use `typePolicies` to precise the field to use + * as key. + */ +export function createMockStore(options: { + /** + * The `schema` to based mocks on. + */ + schema: GraphQLSchema; + + /** + * The mocks functions to use. + */ + mocks?: IMocks; + + typePolicies?: { + [typeName: string]: TypePolicy; + }; +}): IMockStore { + return new MockStore(options); +} diff --git a/packages/mock/src/addMocksToSchema.ts b/packages/mock/src/addMocksToSchema.ts index b1b24219690..3185cbae5ab 100644 --- a/packages/mock/src/addMocksToSchema.ts +++ b/packages/mock/src/addMocksToSchema.ts @@ -13,7 +13,7 @@ import { mapSchema, MapperKind, IResolvers, getRootTypeNames } from '@graphql-to import { addResolversToSchema } from '@graphql-tools/schema'; import { isRef, IMockStore, IMocks, TypePolicy } from './types'; import { copyOwnProps, isObject } from './utils'; -import { createMockStore } from '.'; +import { createMockStore } from './MockStore'; type IMockOptions = { schema: GraphQLSchema; diff --git a/packages/mock/src/index.ts b/packages/mock/src/index.ts index 8c27ca65f47..bbf200b8b56 100644 --- a/packages/mock/src/index.ts +++ b/packages/mock/src/index.ts @@ -1,53 +1,5 @@ -import { GraphQLSchema } from 'graphql'; - -import { IMockStore, IMocks, TypePolicy } from './types'; -import { MockStore } from './MockStore'; - export * from './MockStore'; export * from './addMocksToSchema'; export * from './mockServer'; export * from './types'; export * from './MockList'; - -/** - * Will create `MockStore` for the given `schema`. - * - * A `MockStore` will generate mock values for the given schem when queried. - * - * It will stores generated mocks, so that, provided with same arguments - * the returned values will be the same. - * - * Its API also allows to modify the stored values. - * - * Basic example: - * ```ts - * store.get('User', 1, 'name'); - * // > "Hello World" - * store.set('User', 1, 'name', 'Alexandre'); - * store.get('User', 1, 'name'); - * // > "Alexandre" - * ``` - * - * The storage key will correspond to the "key field" - * of the type. Field with name `id` or `_id` will be - * by default considered as the key field for the type. - * However, use `typePolicies` to precise the field to use - * as key. - */ -export function createMockStore(options: { - /** - * The `schema` to based mocks on. - */ - schema: GraphQLSchema; - - /** - * The mocks functions to use. - */ - mocks?: IMocks; - - typePolicies?: { - [typeName: string]: TypePolicy; - }; -}): IMockStore { - return new MockStore(options); -} diff --git a/packages/mock/src/types.ts b/packages/mock/src/types.ts index 92db067b0ba..db65b16d0ac 100644 --- a/packages/mock/src/types.ts +++ b/packages/mock/src/types.ts @@ -14,7 +14,8 @@ export type TypePolicy = { /** * The name of the field that should be used as store `key`. * - * If `false`, no field will be used and we'll generate a random string + * If `false`, no field will be used and `id` or `_id` will be used, + * otherwise we'll generate a random string * as key. */ keyFieldName?: string | false;