diff --git a/package-lock.json b/package-lock.json index abedc6f4f6f..fd4a0fada06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ ], "devDependencies": { "@apollo/client": "4.0.10", + "@apollo/subgraph": "^2.12.2", "@babel/core": "7.28.5", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-explicit-resource-management": "7.27.4", @@ -111,6 +112,16 @@ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "license": "MIT" }, + "node_modules/@apollo/cache-control-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", + "integrity": "sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, "node_modules/@apollo/client": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.0.10.tgz", @@ -153,6 +164,56 @@ } } }, + "node_modules/@apollo/federation-internals": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@apollo/federation-internals/-/federation-internals-2.12.2.tgz", + "integrity": "sha512-8L3gsYkiwLJhiRVRMzXhJOV6oVMYGdCioYcio66yjaXevfcfsnEUVXfC378ma7+9GBjBjp0YzbDargOg+0FXJA==", + "dev": true, + "license": "Elastic-2.0", + "dependencies": { + "@types/uuid": "^9.0.0", + "chalk": "^4.1.0", + "js-levenshtein": "^1.1.6", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "graphql": "^16.5.0" + } + }, + "node_modules/@apollo/federation-internals/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@apollo/subgraph": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@apollo/subgraph/-/subgraph-2.12.2.tgz", + "integrity": "sha512-/07xSi1Sn4B3+qm+4K4En5m7eBdE1vfevyd6HcwKqcmNo4OLGYo+88h+CfvizuwMdtORq0DDsPSmVCCR1w8wsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apollo/cache-control-types": "^1.0.2", + "@apollo/federation-internals": "2.12.2" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "graphql": "^16.5.0" + } + }, "node_modules/@ardatan/relay-compiler": { "version": "12.0.3", "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.3.tgz", @@ -2652,6 +2713,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2668,6 +2730,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2684,6 +2747,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2700,6 +2764,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2716,6 +2781,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2732,6 +2798,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2748,6 +2815,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2764,6 +2832,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2780,6 +2849,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2796,6 +2866,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2812,6 +2883,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2828,6 +2900,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2844,6 +2917,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2860,6 +2934,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2876,6 +2951,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2892,6 +2968,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2908,6 +2985,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2924,6 +3002,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2940,6 +3019,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2956,6 +3036,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2972,6 +3053,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2988,6 +3070,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3004,6 +3087,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3020,6 +3104,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3036,6 +3121,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3052,6 +3138,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3861,6 +3948,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3883,6 +3971,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3905,6 +3994,7 @@ "os": [ "darwin" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3921,6 +4011,7 @@ "os": [ "darwin" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3937,6 +4028,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3953,6 +4045,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3969,6 +4062,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3985,6 +4079,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -4001,6 +4096,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -4017,6 +4113,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -4033,6 +4130,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -4049,6 +4147,7 @@ "os": [ "linux" ], + "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -4065,6 +4164,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4087,6 +4187,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4109,6 +4210,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4131,6 +4233,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4153,6 +4256,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4175,6 +4279,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4197,6 +4302,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4219,6 +4325,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4238,6 +4345,7 @@ ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/runtime": "^1.7.0" }, @@ -4260,6 +4368,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4279,6 +4388,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -4298,6 +4408,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -7374,6 +7485,13 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/valid-url": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/valid-url/-/valid-url-1.0.7.tgz", @@ -15919,6 +16037,16 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 0109f361bee..edd244b91ce 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ }, "devDependencies": { "@apollo/client": "4.0.10", + "@apollo/subgraph": "^2.12.2", "@babel/core": "7.28.5", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-explicit-resource-management": "7.27.4", diff --git a/packages/merge/tests/merge-typedefs.spec.ts b/packages/merge/tests/merge-typedefs.spec.ts index 8bde0b66576..3d7f0a2d7f2 100644 --- a/packages/merge/tests/merge-typedefs.spec.ts +++ b/packages/merge/tests/merge-typedefs.spec.ts @@ -12,9 +12,10 @@ import { print, } from 'graphql'; import gql from 'graphql-tag'; +import { buildSubgraphSchema } from '@apollo/subgraph'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { stitchSchemas } from '@graphql-tools/stitch'; -import { assertSome } from '@graphql-tools/utils'; +import { assertSome, printSchemaWithDirectives } from '@graphql-tools/utils'; import { mergeDirectives, mergeGraphQLTypes, mergeTypeDefs } from '../src/index.js'; import { stripWhitespaces } from './utils.js'; @@ -1847,4 +1848,55 @@ describe('Merge TypeDefs', () => { // const merged = mergeTypeDefs([ast]); // expect(print(merged)).toBeSimilarString(print(ast)); // }) + it('should allow duplicate directives', () => { + const schemaWithFedDeclaration = gql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@shareable", "@tag"] + ) + + scalar DateTimeISO @tag(name: "nameA") @tag(name: "nameB") + `; + const schemaWithConflictingScalar = gql` + scalar DateTimeISO + `; + + const schemaWithExpandedFederatedTagDirective = gql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@shareable", "@tag"] + ) + + directive @tag( + name: String! + ) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + scalar DateTimeISO @tag(name: "nameA") @tag(name: "nameB") + `; + + const schemaWithoutExpandedTagDirective = buildSubgraphSchema({ + typeDefs: mergeTypeDefs([schemaWithFedDeclaration, schemaWithConflictingScalar]), + }); + const schemaWithExpandedTagDirective = buildSubgraphSchema({ + typeDefs: mergeTypeDefs([ + schemaWithExpandedFederatedTagDirective, + schemaWithConflictingScalar, + ]), + }); + + const problematicSchema = printSchemaWithDirectives(schemaWithoutExpandedTagDirective); + const successfulSchema = printSchemaWithDirectives(schemaWithExpandedTagDirective); + + expect(successfulSchema).toContain('nameA'); + expect(successfulSchema).toContain('nameB'); + console.log('firstSchemaPasses'); + + expect(problematicSchema).toContain('nameB'); + // should fail, as only the last directive was kept by mergeTypeDefs + console.log( + 'test will fail, nameA not found, due to mergeTypeDefs stripping the first tag directive', + ); + expect(problematicSchema).toContain('nameA'); + }); }); diff --git a/packages/utils/tests/print-schema-with-directives.spec.ts b/packages/utils/tests/print-schema-with-directives.spec.ts index f2d53d05f3d..bf56bb23abc 100644 --- a/packages/utils/tests/print-schema-with-directives.spec.ts +++ b/packages/utils/tests/print-schema-with-directives.spec.ts @@ -15,6 +15,9 @@ import { specifiedDirectives, } from 'graphql'; import { GraphQLJSON } from 'graphql-scalars'; +import { gql } from '@apollo/client'; +import { buildSubgraphSchema } from '@apollo/subgraph'; +import { mergeTypeDefs } from '@graphql-tools/merge'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { stitchSchemas } from '@graphql-tools/stitch'; import { RenameTypes, wrapSchema } from '@graphql-tools/wrap';