diff --git a/packages/transforms/transfer-schema/package.json b/packages/transforms/transfer-schema/package.json new file mode 100644 index 0000000000000..be86eb38ff803 --- /dev/null +++ b/packages/transforms/transfer-schema/package.json @@ -0,0 +1,44 @@ +{ + "name": "@graphql-mesh/transform-transfer-schema", + "version": "0.1.2", + "license": "MIT", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "exports": { + ".": { + "require": { + "types": "./dist/typings/index.d.cts", + "default": "./dist/cjs/index.js" + }, + "import": { + "types": "./dist/typings/index.d.ts", + "default": "./dist/esm/index.js" + }, + "default": { + "types": "./dist/typings/index.d.ts", + "default": "./dist/esm/index.js" + } + }, + "./package.json": "./package.json" + }, + "typings": "dist/typings/index.d.ts", + "peerDependencies": { + "@graphql-mesh/types": "^0.91.12", + "@graphql-mesh/utils": "^0.43.20", + "graphql": "*" + }, + "dependencies": { + "micromatch": "4.0.4" + }, + "devDependencies": { + "@types/micromatch": "4.0.2" + }, + "publishConfig": { + "access": "public", + "directory": "dist" + }, + "sideEffects": false, + "typescript": { + "definition": "dist/typings/index.d.ts" + } +} diff --git a/packages/transforms/transfer-schema/src/index.ts b/packages/transforms/transfer-schema/src/index.ts new file mode 100644 index 0000000000000..b68e7c8900d95 --- /dev/null +++ b/packages/transforms/transfer-schema/src/index.ts @@ -0,0 +1,138 @@ +import { GraphQLArgumentConfig, GraphQLFieldConfig, GraphQLObjectType, GraphQLSchema } from 'graphql'; +import { MeshTransform, MeshTransformOptions, YamlConfig } from '@graphql-mesh/types'; +import { mapSchema, pruneSchema, MapperKind } from '@graphql-tools/utils'; +import { matcher } from 'micromatch'; + +export default class TransferSchemaTransform implements MeshTransform { + noWrap = true; + private additionalFieldsMap: Map>; + private additionalArgsMap: Map>; + private additionalFieldsConfig: Map }>; + private additionalArgsConfig: Map; + private removalFieldsMap: Map>; + private removalArgsMap: Map>; + + constructor(options: MeshTransformOptions) { + const { config } = options; + this.additionalFieldsMap = new Map(); + this.additionalArgsMap = new Map(); + this.additionalFieldsConfig = new Map(); + this.additionalArgsConfig = new Map(); + this.removalFieldsMap = new Map(); + this.removalArgsMap = new Map(); + + for (const transfer of config.transfers) { + const [fromType, fromFieldNameOrGlob, fromArgsGlob] = transfer.from.split('.'); + + const rawGlob = fromArgsGlob || fromFieldNameOrGlob; + const fixedGlob = + rawGlob.includes('{') && !rawGlob.includes(',') ? rawGlob.replace('{', '').replace('}', '') : rawGlob; + const polishedGlob = fixedGlob.split(', ').join(',').trim(); + + const mapName = fromArgsGlob ? 'ArgsMap' : 'FieldsMap'; + const mapKey = fromArgsGlob ? `${fromType}.${fromFieldNameOrGlob}` : fromType; + + if (transfer.action === 'move') { + const currentRemovalRules = this[`removal${mapName}`].get(mapKey) || new Set(); + currentRemovalRules.add(polishedGlob); + this[`removal${mapName}`].set(mapKey, currentRemovalRules); + } + + const currentAdditionalRules = this[`additional${mapName}`].get(mapKey) || new Set(); + currentAdditionalRules.add(`${transfer.to}.${polishedGlob}`); + this[`additional${mapName}`].set(mapKey, currentAdditionalRules); + } + } + + handleAdditions(rulesArray: Set, value: string, config: any): void { + for (const rule of rulesArray) { + const [toType, toField, pattern] = rule.split('.'); + const usePattern = pattern || toField; + const mapIdentifier = pattern ? 'ArgsConfig' : 'FieldsConfig'; + const mapKey = pattern ? `${toType}.${toField}` : toType; + + const isMatch = matcher(usePattern); + if (isMatch(value)) { + const currentAdditionalConfigs = this[`additional${mapIdentifier}`].get(mapKey) || {}; + this[`additional${mapIdentifier}`].set(mapKey, { ...currentAdditionalConfigs, [value]: config }); + break; + } + } + } + + matchInSet(rulesSet: Set, value: string): true | undefined { + for (const rule of rulesSet) { + const isMatch = matcher(rule); + if (isMatch(value)) return true; + } + return undefined; + } + + transformSchema(schema: GraphQLSchema) { + // initial mapSchema is necessary to store fields/args configs that will be attached to Types/fields in second map + const schemaWithRemovals = mapSchema(schema, { + [MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName, typeName) => { + const additionalFieldRules = this.additionalFieldsMap.get(typeName); + const additionalArgRules = this.additionalArgsMap.get(`${typeName}.${fieldName}`); + const removalFieldRules = this.removalFieldsMap.get(typeName); + const removalArgRules = this.removalArgsMap.get(`${typeName}.${fieldName}`); + const hasAdditionalFieldRules = Boolean(additionalFieldRules); + const hasAdditionalArgRules = Boolean(additionalArgRules); + const hasRemovalFieldRules = Boolean(removalFieldRules && removalFieldRules.size); + const hasRemovalArgRules = Boolean(removalArgRules && removalArgRules.size); + + // handle field addition + if (hasAdditionalFieldRules) { + this.handleAdditions(additionalFieldRules, fieldName, fieldConfig); + } + + // handle args addition + if (hasAdditionalArgRules) { + for (const [argName, argConfig] of Object.entries(fieldConfig.args)) { + this.handleAdditions(additionalArgRules, argName, argConfig); + } + } + + // handle field removal + if (hasRemovalFieldRules && this.matchInSet(removalFieldRules, fieldName)) return null; + + // handle args removal + if (hasRemovalArgRules) { + const newArgs = Object.entries(fieldConfig.args).reduce((args, [argName, argConfig]) => { + return this.matchInSet(removalArgRules, argName) ? args : { ...args, [argName]: argConfig }; + }, {}); + + return { ...fieldConfig, args: newArgs }; + } + + return undefined; + }, + }); + + const schemaWithAdditions = mapSchema(schemaWithRemovals, { + ...(this.additionalFieldsConfig.size && { + [MapperKind.OBJECT_TYPE]: type => { + const additionalFieldsConfigMap = this.additionalFieldsConfig.get(type.toString()); + + if (!additionalFieldsConfigMap) return undefined; + + const newConfig = { ...type.toConfig() }; + newConfig.fields = { ...newConfig.fields, ...additionalFieldsConfigMap }; + + return new GraphQLObjectType(newConfig); + }, + }), + ...(this.additionalArgsConfig.size && { + [MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName, typeName) => { + const additionalArgsConfigMap = this.additionalArgsConfig.get(`${typeName}.${fieldName}`); + + if (!additionalArgsConfigMap) return undefined; + + return { ...fieldConfig, args: { ...(fieldConfig.args || {}), ...additionalArgsConfigMap } }; + }, + }), + }); + + return pruneSchema(schemaWithAdditions); + } +} diff --git a/packages/transforms/transfer-schema/test/__snapshots__/transfer-schema.spec.ts.snap b/packages/transforms/transfer-schema/test/__snapshots__/transfer-schema.spec.ts.snap new file mode 100644 index 0000000000000..67134112b75bd --- /dev/null +++ b/packages/transforms/transfer-schema/test/__snapshots__/transfer-schema.spec.ts.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transfer-schema transform should copy selected arguments correctly 1`] = ` +"type Query { + books(title: String, author: String): [Book] + addBook(title: String, author: String): Book +} + +type Mutation { + ourBooks: [Book] +} + +type Book { + title: String! + author: String! +}" +`; + +exports[`transfer-schema transform should move all fields when using whildcard "*" 1`] = ` +"type Query { + books: [Book] + addBook(title: String, author: String): Book + ourBooks: [Book] +} + +type Book { + title: String! + author: String! +}" +`; + +exports[`transfer-schema transform should move and copy fields correctly 1`] = ` +"type Query { + books: [Book] + ourBooks: [Book] +} + +type Mutation { + ourBooks: [Book] + addBook(title: String, author: String): Book +} + +type Book { + title: String! + author: String! +}" +`; + +exports[`transfer-schema transform should move selected arguments correctly 1`] = ` +"type Query { + books(title: String, author: String): [Book] + addBook: Book +} + +type Mutation { + ourBooks: [Book] +} + +type Book { + title: String! + author: String! +}" +`; diff --git a/packages/transforms/transfer-schema/test/transfer-schema.spec.ts b/packages/transforms/transfer-schema/test/transfer-schema.spec.ts new file mode 100644 index 0000000000000..920d91edb3951 --- /dev/null +++ b/packages/transforms/transfer-schema/test/transfer-schema.spec.ts @@ -0,0 +1,296 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { printSchema, GraphQLObjectType } from 'graphql'; +import InMemoryLRUCache from '@graphql-mesh/cache-localforage'; +import { MeshPubSub } from '@graphql-mesh/types'; +import { makeExecutableSchema } from '@graphql-tools/schema'; + +import TransferFieldTransform from '../src'; +import { DefaultLogger, PubSub, defaultImportFn } from '@graphql-mesh/utils'; + +describe('transfer-schema transform', () => { + const schemaDefs = /* GraphQL */ ` + type Query { + books: [Book] + addBook(title: String, author: String): Book + } + + type Mutation { + ourBooks: [Book] + } + + type Book { + title: String! + author: String! + } + `; + let cache: InMemoryLRUCache; + let pubsub: MeshPubSub; + const baseDir: string = undefined; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + cache = new InMemoryLRUCache(); + pubsub = new PubSub(); + }); + + it('should move and copy fields correctly', async () => { + const transform = new TransferFieldTransform({ + config: { + transfers: [ + { + from: 'Mutation.ourBooks', + to: 'Query', + }, + { + from: 'Query.addBook', + to: 'Mutation', + action: 'move', + }, + ], + }, + cache, + pubsub, + baseDir, + apiName: '', + importFn: defaultImportFn, + logger: new DefaultLogger(), + }); + const schema = makeExecutableSchema({ + typeDefs: schemaDefs, + }); + const transformedSchema = transform.transformSchema(schema); + + // test copy + expect((transformedSchema.getType('Mutation') as GraphQLObjectType).getFields().ourBooks.type.toString()).toBe( + '[Book]' + ); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().ourBooks.type.toString()).toBe( + '[Book]' + ); + + // test move + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().addBook).toBeUndefined(); + expect((transformedSchema.getType('Mutation') as GraphQLObjectType).getFields().addBook.type.toString()).toBe( + 'Book' + ); + expect(printSchema(transformedSchema)).toMatchSnapshot(); + }); + + it('should move all fields when using whildcard "*"', async () => { + const transform = new TransferFieldTransform({ + config: { + transfers: [ + { + from: 'Mutation.*', + to: 'Query', + action: 'move', + }, + ], + }, + cache, + pubsub, + baseDir, + apiName: '', + importFn: defaultImportFn, + logger: new DefaultLogger(), + }); + const schema = makeExecutableSchema({ + typeDefs: schemaDefs, + }); + const transformedSchema = transform.transformSchema(schema); + + expect(transformedSchema.getType('Mutation') as GraphQLObjectType).toBeUndefined(); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.type.toString()).toBe('[Book]'); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().addBook.type.toString()).toBe('Book'); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().ourBooks.type.toString()).toBe( + '[Book]' + ); + expect(printSchema(transformedSchema)).toMatchSnapshot(); + }); + + it('should move selected arguments correctly', async () => { + const transform = new TransferFieldTransform({ + config: { + transfers: [ + { + from: 'Query.addBook.{title, author}', + to: 'Query.books', + action: 'move', + }, + ], + }, + cache, + pubsub, + baseDir, + apiName: '', + importFn: defaultImportFn, + logger: new DefaultLogger(), + }); + const schema = makeExecutableSchema({ + typeDefs: schemaDefs, + }); + const transformedSchema = transform.transformSchema(schema); + + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().addBook.args).toHaveLength(0); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.args).toHaveLength(2); + const titleArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'title'); + const authorArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'author'); + expect(titleArg).toBeDefined(); + expect(titleArg.type.toString()).toBe('String'); + expect(authorArg).toBeDefined(); + expect(authorArg.type.toString()).toBe('String'); + expect(printSchema(transformedSchema)).toMatchSnapshot(); + }); + + it('should copy selected arguments correctly', async () => { + const transform = new TransferFieldTransform({ + config: { + transfers: [ + { + from: 'Query.addBook.{title, author}', + to: 'Query.books', + }, + ], + }, + cache, + pubsub, + baseDir, + apiName: '', + importFn: defaultImportFn, + logger: new DefaultLogger(), + }); + const schema = makeExecutableSchema({ + typeDefs: schemaDefs, + }); + const transformedSchema = transform.transformSchema(schema); + + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().addBook.args).toHaveLength(2); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.args).toHaveLength(2); + const titleArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'title'); + const authorArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'author'); + expect(titleArg).toBeDefined(); + expect(titleArg.type.toString()).toBe('String'); + expect(authorArg).toBeDefined(); + expect(authorArg.type.toString()).toBe('String'); + expect(printSchema(transformedSchema)).toMatchSnapshot(); + }); + + it('should move all arguments when using wildcard "*"', async () => { + const transform = new TransferFieldTransform({ + config: { + transfers: [ + { + from: 'Query.addBook.*', + to: 'Query.books', + action: 'move', + }, + ], + }, + cache, + pubsub, + baseDir, + apiName: '', + importFn: defaultImportFn, + logger: new DefaultLogger(), + }); + const schema = makeExecutableSchema({ + typeDefs: schemaDefs, + }); + const transformedSchema = transform.transformSchema(schema); + + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().addBook.args).toHaveLength(0); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.args).toHaveLength(2); + const titleArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'title'); + const authorArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'author'); + expect(titleArg).toBeDefined(); + expect(titleArg.type.toString()).toBe('String'); + expect(authorArg).toBeDefined(); + expect(authorArg.type.toString()).toBe('String'); + }); + + it('should copy all arguments when using wildcard "*"', async () => { + const transform = new TransferFieldTransform({ + config: { + transfers: [ + { + from: 'Query.addBook.*', + to: 'Query.books', + }, + ], + }, + cache, + pubsub, + baseDir, + apiName: '', + importFn: defaultImportFn, + logger: new DefaultLogger(), + }); + const schema = makeExecutableSchema({ + typeDefs: schemaDefs, + }); + const transformedSchema = transform.transformSchema(schema); + + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().addBook.args).toHaveLength(2); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.args).toHaveLength(2); + const titleArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'title'); + const authorArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'author'); + expect(titleArg).toBeDefined(); + expect(titleArg.type.toString()).toBe('String'); + expect(authorArg).toBeDefined(); + expect(authorArg.type.toString()).toBe('String'); + }); + + it('should copy all arguments but blacklisted ones', async () => { + const transform = new TransferFieldTransform({ + config: { + transfers: [ + { + from: 'Query.addBook.!title', + to: 'Query.books', + }, + ], + }, + cache, + pubsub, + baseDir, + apiName: '', + importFn: defaultImportFn, + logger: new DefaultLogger(), + }); + const schema = makeExecutableSchema({ + typeDefs: schemaDefs, + }); + const transformedSchema = transform.transformSchema(schema); + + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().addBook.args).toHaveLength(2); + expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.args).toHaveLength(1); + const titleArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'title'); + const authorArg = (transformedSchema.getType('Query') as GraphQLObjectType) + .getFields() + .books.args.find(({ name }) => name === 'author'); + expect(titleArg).toBeUndefined(); + expect(authorArg).toBeDefined(); + expect(authorArg.type.toString()).toBe('String'); + }); +}); diff --git a/packages/transforms/transfer-schema/yaml-config.graphql b/packages/transforms/transfer-schema/yaml-config.graphql new file mode 100644 index 0000000000000..456714126f06e --- /dev/null +++ b/packages/transforms/transfer-schema/yaml-config.graphql @@ -0,0 +1,24 @@ +extend type Transform { + """ + Transformer to transfer (move or copy) GraphQL parts of GraphQL schema across Types and Fields + """ + transferSchema: TransferSchemaTransformConfig +} + +type TransferSchemaTransformConfig @md { + """ + Array of rules to transfer fields or args + """ + transfers: [TransferSchemaTransformObject!]! +} + +enum TransferSchemaAction { + move + copy +} + +type TransferSchemaTransformObject @md { + from: String! + to: String! + action: TransferSchemaAction +} diff --git a/packages/transforms/transfer-schema/yarn.lock b/packages/transforms/transfer-schema/yarn.lock new file mode 100644 index 0000000000000..b198b7a50f576 --- /dev/null +++ b/packages/transforms/transfer-schema/yarn.lock @@ -0,0 +1,144 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@graphql-mesh/types@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@graphql-mesh/types/-/types-0.53.0.tgz#48efcba10ba3a8dfe2b96568faf3dc8dcf4e729b" + integrity sha512-qdl8ml/k3iGtEzmwPzYxpQf8uI035D5/KzoltbTFe7nQPp2/7zP7+hux4YDCFTg4nKJM4oTdjhO0QJKX8rjGCA== + dependencies: + "@graphql-tools/delegate" "8.2.3" + "@graphql-tools/utils" "8.3.0" + "@graphql-typed-document-node/core" "3.1.0" + fetchache "0.1.1" + +"@graphql-tools/batch-execute@^8.1.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-8.3.1.tgz#0b74c54db5ac1c5b9a273baefc034c2343ebbb74" + integrity sha512-63kHY8ZdoO5FoeDXYHnAak1R3ysMViMPwWC2XUblFckuVLMUPmB2ONje8rjr2CvzWBHAW8c1Zsex+U3xhKtGIA== + dependencies: + "@graphql-tools/utils" "^8.5.1" + dataloader "2.0.0" + tslib "~2.3.0" + value-or-promise "1.0.11" + +"@graphql-tools/delegate@8.2.3": + version "8.2.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-8.2.3.tgz#70550f9536563a98a449818869eec530c2a05408" + integrity sha512-CS0B7oAA3n8mHhufY54j0/oxBK0aQcWy8oYyGT36ZM+MaTXQL5Wf6AlzMqvCtmBnQxGOyn/b2o0wsZ41HVH3nA== + dependencies: + "@graphql-tools/batch-execute" "^8.1.1" + "@graphql-tools/schema" "^8.2.0" + "@graphql-tools/utils" "^8.3.0" + dataloader "2.0.0" + tslib "~2.3.0" + value-or-promise "1.0.11" + +"@graphql-tools/merge@^8.2.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.2.1.tgz#bf83aa06a0cfc6a839e52a58057a84498d0d51ff" + integrity sha512-Q240kcUszhXiAYudjuJgNuLgy9CryDP3wp83NOZQezfA6h3ByYKU7xI6DiKrdjyVaGpYN3ppUmdj0uf5GaXzMA== + dependencies: + "@graphql-tools/utils" "^8.5.1" + tslib "~2.3.0" + +"@graphql-tools/schema@^8.2.0": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.3.1.tgz#1ee9da494d2da457643b3c93502b94c3c4b68c74" + integrity sha512-3R0AJFe715p4GwF067G5i0KCr/XIdvSfDLvTLEiTDQ8V/hwbOHEKHKWlEBHGRQwkG5lwFQlW1aOn7VnlPERnWQ== + dependencies: + "@graphql-tools/merge" "^8.2.1" + "@graphql-tools/utils" "^8.5.1" + tslib "~2.3.0" + value-or-promise "1.0.11" + +"@graphql-tools/utils@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.3.0.tgz#382111bc4a93f248e3641740a5300f44286bffae" + integrity sha512-ksE0RxS0AFllo6KIJjvQsRgcUAzoyZUgUrDbCngv4SaQwyX9YxTfddTLN4uQmbiZB9h25fPp/Xgeyaa3ARCzgg== + dependencies: + tslib "~2.3.0" + +"@graphql-tools/utils@^8.3.0", "@graphql-tools/utils@^8.5.1", "@graphql-tools/utils@^8.5.2": + version "8.5.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.5.2.tgz#fa775d92c19237f648105f7d4aeeeb63ba3d257f" + integrity sha512-wxA51td/759nQziPYh+HxE0WbURRufrp1lwfOYMgfK4e8Aa6gCa1P1p6ERogUIm423NrIfOVau19Q/BBpHdolw== + dependencies: + tslib "~2.3.0" + +"@graphql-typed-document-node/core@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950" + integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg== + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +dataloader@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.0.0.tgz#41eaf123db115987e21ca93c005cd7753c55fe6f" + integrity sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ== + +fetchache@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/fetchache/-/fetchache-0.1.1.tgz#013eef193b4a219a355dea5ca02a720565d8bcdc" + integrity sha512-ots7Jv408zPDxCaKGTeRajzuiullinmfzzLV/dA+U81BQTtbspCiy/GTHPekIIyWAkKvP/GYP0WVKzF6ljtdEg== + dependencies: + flatstr "1.0.12" + http-cache-semantics "4.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +flatstr@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" + integrity sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw== + +http-cache-semantics@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@~2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +value-or-promise@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" + integrity sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg== diff --git a/packages/types/src/config-schema.json b/packages/types/src/config-schema.json index 71f00c95a55b0..f5f40741461dc 100644 --- a/packages/types/src/config-schema.json +++ b/packages/types/src/config-schema.json @@ -395,6 +395,10 @@ } ] }, + "transferSchema": { + "$ref": "#/definitions/TransferSchemaTransformConfig", + "description": "Transformer to transfer (move or copy) GraphQL parts of GraphQL schema across Types and Fields" + }, "typeMerging": { "$ref": "#/definitions/TypeMergingConfig", "description": "[Type Merging](https://www.graphql-tools.com/docs/stitch-type-merging) Configuration" @@ -3807,6 +3811,41 @@ }, "required": ["resolver", "composer"] }, + "TransferSchemaTransformConfig": { + "additionalProperties": false, + "type": "object", + "title": "TransferSchemaTransformConfig", + "properties": { + "transfers": { + "type": "array", + "items": { + "$ref": "#/definitions/TransferSchemaTransformObject" + }, + "additionalItems": false, + "description": "Array of rules to transfer fields or args" + } + }, + "required": ["transfers"] + }, + "TransferSchemaTransformObject": { + "additionalProperties": false, + "type": "object", + "title": "TransferSchemaTransformObject", + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "action": { + "type": "string", + "enum": ["move", "copy"], + "description": "Allowed values: move, copy" + } + }, + "required": ["from", "to"] + }, "TypeMergingConfig": { "additionalProperties": false, "type": "object", diff --git a/packages/types/src/config.ts b/packages/types/src/config.ts index 65690582adf51..5cbe26bd8c34c 100644 --- a/packages/types/src/config.ts +++ b/packages/types/src/config.ts @@ -1044,6 +1044,7 @@ export interface Transform { * Transformer to apply composition to resolvers (Any of: ResolversCompositionTransform, Any) */ resolversComposition?: ResolversCompositionTransform | any; + transferSchema?: TransferSchemaTransformConfig; typeMerging?: TypeMergingConfig; [k: string]: any; } @@ -1461,6 +1462,23 @@ export interface ResolversCompositionTransformObject { */ composer: any; } +/** + * Transformer to transfer (move or copy) GraphQL parts of GraphQL schema across Types and Fields + */ +export interface TransferSchemaTransformConfig { + /** + * Array of rules to transfer fields or args + */ + transfers: TransferSchemaTransformObject[]; +} +export interface TransferSchemaTransformObject { + from: string; + to: string; + /** + * Allowed values: move, copy + */ + action?: 'move' | 'copy'; +} /** * [Type Merging](https://www.graphql-tools.com/docs/stitch-type-merging) Configuration */ diff --git a/website/src/generated-markdown/TransferSchemaTransformConfig.generated.md b/website/src/generated-markdown/TransferSchemaTransformConfig.generated.md new file mode 100644 index 0000000000000..7379317811bf8 --- /dev/null +++ b/website/src/generated-markdown/TransferSchemaTransformConfig.generated.md @@ -0,0 +1,5 @@ + +* `transfers` (type: `Array of Object`, required) - Array of rules to transfer fields or args: + * `from` (type: `String`, required) + * `to` (type: `String`, required) + * `action` (type: `String (move | copy)`) \ No newline at end of file diff --git a/website/src/generated-markdown/TransferSchemaTransformObject.generated.md b/website/src/generated-markdown/TransferSchemaTransformObject.generated.md new file mode 100644 index 0000000000000..8905ac23567ac --- /dev/null +++ b/website/src/generated-markdown/TransferSchemaTransformObject.generated.md @@ -0,0 +1,4 @@ + +* `from` (type: `String`, required) +* `to` (type: `String`, required) +* `action` (type: `String (move | copy)`) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4052ca7d044f8..50838ecc1cf2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2969,7 +2969,7 @@ "@ardatan/aggregate-error" "0.0.1" camel-case "4.1.1" -"@graphql-tools/utils@8.9.0", "@graphql-tools/utils@^8.5.2", "@graphql-tools/utils@^8.8.0": +"@graphql-tools/utils@8.9.0", "@graphql-tools/utils@^8.8.0": version "8.9.0" resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.9.0.tgz#c6aa5f651c9c99e1aca55510af21b56ec296cdb7" integrity sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg== @@ -2984,6 +2984,13 @@ "@graphql-typed-document-node/core" "^3.1.1" tslib "^2.4.0" +"@graphql-tools/utils@^8.5.2": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.5.3.tgz#404062e62cae9453501197039687749c4885356e" + integrity sha512-HDNGWFVa8QQkoQB0H1lftvaO1X5xUaUDk1zr1qDe0xN1NL0E/CrQdJ5UKLqOvH4hkqVUPxQsyOoAZFkaH6rLHg== + dependencies: + tslib "~2.3.0" + "@graphql-tools/wrap@6.1.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-6.1.0.tgz#52451574dc667a423b66ad05720add03cbecb7b8" @@ -4984,6 +4991,11 @@ dependencies: "@types/node" "*" +"@types/braces@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.1.tgz#5a284d193cfc61abb2e5a50d36ebbc50d942a32b" + integrity sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ== + "@types/cacheable-request@^6.0.1": version "6.0.3" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" @@ -5327,6 +5339,13 @@ resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.4.tgz#d1cad61ccc803b3c248c3d9990a2a6880bef537f" integrity sha512-qCYrNdpKwN6YO6FVnx+ulfqifKlE3lQGsNhvDaW9Oxzyob/cRLBJWow8GHBBD4NxQ7BVvtsATgLsX0vZAWmtrg== +"@types/micromatch@4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.2.tgz#ce29c8b166a73bf980a5727b1e4a4d099965151d" + integrity sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA== + dependencies: + "@types/braces" "*" + "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -7058,7 +7077,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -14734,6 +14753,14 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" +micromatch@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -19666,6 +19693,11 @@ tslib@~2.0.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== +tslib@~2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tslib@~2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"