Skip to content

Commit 31059ca

Browse files
committed
feat(compose-cli): introspectionOptions
1 parent 0685d1d commit 31059ca

File tree

8 files changed

+282
-1
lines changed

8 files changed

+282
-1
lines changed

.changeset/sparkly-lions-make.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@graphql-mesh/compose-cli': minor
3+
---
4+
5+
Add `introspectionOptions` to `loadGraphQLHTTPSubgraph` with some defaults.
6+
Previously, it was not possible to configure the introspection query options.
7+
By default it was ignoring deprecated input fields and not including descriptions.
8+
Now it includes descriptions and deprecated input fields by default.
9+
And you can still override the defaults by providing your own options.
10+
11+
```ts
12+
import { loadGraphQLHTTPSubgraph } from '@graphql-mesh/compose-cli';
13+
14+
loadGraphQLHTTPSubgraph('my-subgraph', {
15+
source: 'http://my-subgraph/graphql',
16+
introspectionOptions: {
17+
descriptions: true,
18+
specifiedByUrl: false,
19+
directiveIsRepeatable: false,
20+
schemaDescription: false,
21+
inputValueDeprecation: true,
22+
oneOf: false, // New in GraphQL 16
23+
},
24+
});
25+
```
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`should compose the appropriate schema 1`] = `
4+
"schema
5+
@link(url: "https://specs.apollo.dev/link/v1.0")
6+
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
7+
8+
9+
10+
11+
12+
13+
14+
{
15+
query: Query
16+
17+
18+
}
19+
20+
21+
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
22+
23+
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
24+
25+
26+
directive @join__field(
27+
graph: join__Graph
28+
requires: join__FieldSet
29+
provides: join__FieldSet
30+
type: String
31+
external: Boolean
32+
override: String
33+
usedOverridden: Boolean
34+
35+
36+
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
37+
38+
39+
40+
directive @join__implements(
41+
graph: join__Graph!
42+
interface: String!
43+
) repeatable on OBJECT | INTERFACE
44+
45+
directive @join__type(
46+
graph: join__Graph!
47+
key: join__FieldSet
48+
extension: Boolean! = false
49+
resolvable: Boolean! = true
50+
isInterfaceObject: Boolean! = false
51+
) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
52+
53+
directive @join__unionMember(
54+
graph: join__Graph!
55+
member: String!
56+
) repeatable on UNION
57+
58+
scalar join__FieldSet
59+
60+
61+
62+
directive @link(
63+
url: String
64+
as: String
65+
for: link__Purpose
66+
import: [link__Import]
67+
) repeatable on SCHEMA
68+
69+
scalar link__Import
70+
71+
enum link__Purpose {
72+
"""
73+
\`SECURITY\` features provide metadata necessary to securely resolve fields.
74+
"""
75+
SECURITY
76+
77+
"""
78+
\`EXECUTION\` features provide metadata necessary for operation execution.
79+
"""
80+
EXECUTION
81+
}
82+
83+
84+
85+
86+
87+
88+
89+
90+
enum join__Graph {
91+
SUBGRAPH_A @join__graph(name: "subgraph-a", url: "http://localhost:<subgraph-a_port>/graphql")
92+
}
93+
94+
scalar TransportOptions @join__type(graph: SUBGRAPH_A)
95+
96+
type Query @join__type(graph: SUBGRAPH_A) {
97+
rootField(
98+
deprecatedArg: DeprecatedArgEnum @deprecated(reason: "No longer needed")
99+
otherArg: Int
100+
): RootFieldReturnType
101+
}
102+
103+
type RootFieldReturnType @join__type(graph: SUBGRAPH_A) {
104+
id: ID!
105+
}
106+
107+
enum DeprecatedArgEnum @join__type(graph: SUBGRAPH_A) {
108+
VALUE1 @join__enumValue(graph: SUBGRAPH_A)
109+
VALUE2 @join__enumValue(graph: SUBGRAPH_A)
110+
}
111+
"
112+
`;
113+
114+
exports[`should execute Query rootField 1`] = `
115+
{
116+
"data": {
117+
"rootField": {
118+
"id": "root",
119+
},
120+
},
121+
}
122+
`;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createTenv, type Container } from '@e2e/tenv';
2+
3+
const { compose, service, serve, container } = createTenv(__dirname);
4+
5+
it('should compose the appropriate schema', async () => {
6+
const { result } = await compose({
7+
services: [await service('subgraph-a')],
8+
maskServicePorts: true,
9+
});
10+
expect(result).toMatchSnapshot();
11+
});
12+
13+
it.concurrent.each([
14+
{
15+
name: 'Query rootField',
16+
query: /* GraphQL */ `
17+
query {
18+
rootField(deprecatedArg: VALUE1, otherArg: 1) {
19+
id
20+
}
21+
}
22+
`,
23+
},
24+
])('should execute $name', async ({ query }) => {
25+
const { output } = await compose({
26+
output: 'graphql',
27+
services: [await service('subgraph-a')],
28+
});
29+
const { execute } = await serve({ supergraph: output });
30+
await expect(execute({ query })).resolves.toMatchSnapshot();
31+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Opts } from '@e2e/opts';
2+
import { defineConfig, loadGraphQLHTTPSubgraph } from '@graphql-mesh/compose-cli';
3+
4+
const opts = Opts(process.argv);
5+
6+
export const composeConfig = defineConfig({
7+
subgraphs: [
8+
{
9+
sourceHandler: loadGraphQLHTTPSubgraph('subgraph-a', {
10+
endpoint: `http://localhost:${opts.getServicePort('subgraph-a')}/graphql`,
11+
}),
12+
},
13+
],
14+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@e2e/deprecated-merging",
3+
"private": true,
4+
"devDependencies": {
5+
"@graphql-hive/gateway": "^2.0.0",
6+
"graphql": "^16.9.0",
7+
"graphql-yoga": "^5.13.4"
8+
}
9+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { createServer } from 'node:http';
2+
import { createSchema, createYoga } from 'graphql-yoga';
3+
import { Opts } from '@e2e/opts';
4+
5+
const opts = Opts(process.argv);
6+
const port = opts.getServicePort('subgraph-a');
7+
8+
createServer(
9+
createYoga({
10+
schema: createSchema({
11+
typeDefs: /* GraphQL */ `
12+
type Query {
13+
rootField(
14+
deprecatedArg: DeprecatedArgEnum @deprecated(reason: "No longer needed")
15+
otherArg: Int
16+
): RootFieldReturnType
17+
}
18+
19+
enum DeprecatedArgEnum {
20+
VALUE1
21+
VALUE2
22+
}
23+
24+
type RootFieldReturnType {
25+
id: ID!
26+
}
27+
`,
28+
resolvers: {
29+
Query: {
30+
rootField: (_parent, _args, _context, _info) => {
31+
return { id: 'root' };
32+
},
33+
},
34+
},
35+
}),
36+
}),
37+
).listen(port, () => {
38+
console.log(`Subgraph A service listening on http://localhost:${port}`);
39+
});

packages/compose-cli/src/loadGraphQLHTTPSubgraph.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
parse,
1717
visit,
1818
type DocumentNode,
19+
type IntrospectionOptions,
1920
type IntrospectionQuery,
2021
} from 'graphql';
2122
import { stringInterpolator } from '@graphql-mesh/string-interpolation';
@@ -97,6 +98,25 @@ export interface GraphQLSubgraphLoaderHTTPConfiguration {
9798
* @default 'http'
9899
*/
99100
transportKind?: 'http';
101+
102+
/**
103+
* While introspecting the schema, you can customize the introspection query by providing these options.
104+
* This is useful if you have a GraphQL server that does not support some of the newer features of GraphQL.
105+
*
106+
* By default, Mesh has the following options;
107+
*
108+
* ```json
109+
* {
110+
* "descriptions": true,
111+
* "specifiedByUrl": false,
112+
* "directiveIsRepeatable": false,
113+
* "schemaDescription": false,
114+
* "inputValueDeprecation": true,
115+
* "oneOf": false
116+
* }
117+
* ```
118+
*/
119+
introspectionOptions?: IntrospectionOptions;
100120
}
101121

102122
function fixExtends(node: DocumentNode) {
@@ -218,6 +238,15 @@ function fixExtends(node: DocumentNode) {
218238
});
219239
}
220240

241+
export const DEFAULT_INTROSPECTION_OPTIONS: Required<IntrospectionOptions> = {
242+
descriptions: true,
243+
specifiedByUrl: false,
244+
directiveIsRepeatable: false,
245+
schemaDescription: false,
246+
inputValueDeprecation: true,
247+
oneOf: false,
248+
};
249+
221250
export function loadGraphQLHTTPSubgraph(
222251
subgraphName: string,
223252
{
@@ -233,6 +262,8 @@ export function loadGraphQLHTTPSubgraph(
233262
source,
234263
schemaHeaders,
235264

265+
introspectionOptions = DEFAULT_INTROSPECTION_OPTIONS,
266+
236267
federation = false,
237268

238269
transportKind = 'http',
@@ -303,7 +334,7 @@ export function loadGraphQLHTTPSubgraph(
303334
...schemaHeaders,
304335
},
305336
body: JSON.stringify({
306-
query: getIntrospectionQuery(),
337+
query: getIntrospectionQuery(introspectionOptions),
307338
}),
308339
}),
309340
res => {

yarn.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3940,6 +3940,16 @@ __metadata:
39403940
languageName: unknown
39413941
linkType: soft
39423942

3943+
"@e2e/deprecated-merging@workspace:e2e/deprecated-merging":
3944+
version: 0.0.0-use.local
3945+
resolution: "@e2e/deprecated-merging@workspace:e2e/deprecated-merging"
3946+
dependencies:
3947+
"@graphql-hive/gateway": "npm:^2.0.0"
3948+
graphql: "npm:^16.9.0"
3949+
graphql-yoga: "npm:^5.13.4"
3950+
languageName: unknown
3951+
linkType: soft
3952+
39433953
"@e2e/esm-config-in-cjs-project@workspace:e2e/esm-config-in-cjs-project":
39443954
version: 0.0.0-use.local
39453955
resolution: "@e2e/esm-config-in-cjs-project@workspace:e2e/esm-config-in-cjs-project"

0 commit comments

Comments
 (0)