@depup/graphql-codegen__plugin-helpers 6.2.0-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/changes.json +18 -0
- package/cjs/federation.js +436 -0
- package/cjs/getCachedDocumentNodeFromSchema.js +5 -0
- package/cjs/helpers.js +178 -0
- package/cjs/index.js +13 -0
- package/cjs/oldVisit.js +19 -0
- package/cjs/package.json +1 -0
- package/cjs/profiler.js +49 -0
- package/cjs/resolve-external-module-and-fn.js +31 -0
- package/cjs/types.js +6 -0
- package/cjs/utils.js +35 -0
- package/esm/federation.js +430 -0
- package/esm/getCachedDocumentNodeFromSchema.js +2 -0
- package/esm/helpers.js +168 -0
- package/esm/index.js +8 -0
- package/esm/oldVisit.js +16 -0
- package/esm/profiler.js +45 -0
- package/esm/resolve-external-module-and-fn.js +27 -0
- package/esm/types.js +3 -0
- package/esm/utils.js +29 -0
- package/package.json +76 -0
- package/typings/federation.d.cts +102 -0
- package/typings/federation.d.ts +102 -0
- package/typings/getCachedDocumentNodeFromSchema.d.cts +2 -0
- package/typings/getCachedDocumentNodeFromSchema.d.ts +2 -0
- package/typings/helpers.d.cts +13 -0
- package/typings/helpers.d.ts +13 -0
- package/typings/index.d.cts +9 -0
- package/typings/index.d.ts +9 -0
- package/typings/oldVisit.d.cts +9 -0
- package/typings/oldVisit.d.ts +9 -0
- package/typings/profiler.d.cts +28 -0
- package/typings/profiler.d.ts +28 -0
- package/typings/resolve-external-module-and-fn.d.cts +1 -0
- package/typings/resolve-external-module-and-fn.d.ts +1 -0
- package/typings/types.d.cts +635 -0
- package/typings/types.d.ts +635 -0
- package/typings/utils.d.cts +6 -0
- package/typings/utils.d.ts +6 -0
package/cjs/oldVisit.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.oldVisit = oldVisit;
|
|
4
|
+
const graphql_1 = require("graphql");
|
|
5
|
+
function oldVisit(root, { enter: enterVisitors, leave: leaveVisitors, ...newVisitor }) {
|
|
6
|
+
if (typeof enterVisitors === 'object') {
|
|
7
|
+
for (const key in enterVisitors) {
|
|
8
|
+
newVisitor[key] ||= {};
|
|
9
|
+
newVisitor[key].enter = enterVisitors[key];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (typeof leaveVisitors === 'object') {
|
|
13
|
+
for (const key in leaveVisitors) {
|
|
14
|
+
newVisitor[key] ||= {};
|
|
15
|
+
newVisitor[key].leave = leaveVisitors[key];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return (0, graphql_1.visit)(root, newVisitor);
|
|
19
|
+
}
|
package/cjs/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
package/cjs/profiler.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNoopProfiler = createNoopProfiler;
|
|
4
|
+
exports.createProfiler = createProfiler;
|
|
5
|
+
function createNoopProfiler() {
|
|
6
|
+
return {
|
|
7
|
+
run(fn) {
|
|
8
|
+
return Promise.resolve().then(() => fn());
|
|
9
|
+
},
|
|
10
|
+
collect() {
|
|
11
|
+
return [];
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function createProfiler() {
|
|
16
|
+
const events = [];
|
|
17
|
+
return {
|
|
18
|
+
collect() {
|
|
19
|
+
return events;
|
|
20
|
+
},
|
|
21
|
+
run(fn, name, cat) {
|
|
22
|
+
let startTime;
|
|
23
|
+
return Promise.resolve()
|
|
24
|
+
.then(() => {
|
|
25
|
+
startTime = process.hrtime();
|
|
26
|
+
})
|
|
27
|
+
.then(() => fn())
|
|
28
|
+
.then(value => {
|
|
29
|
+
const duration = process.hrtime(startTime);
|
|
30
|
+
// Trace Event Format documentation:
|
|
31
|
+
// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
|
|
32
|
+
const event = {
|
|
33
|
+
name,
|
|
34
|
+
cat,
|
|
35
|
+
ph: 'X',
|
|
36
|
+
ts: hrtimeToMicroseconds(startTime),
|
|
37
|
+
pid: 1,
|
|
38
|
+
tid: 0,
|
|
39
|
+
dur: hrtimeToMicroseconds(duration),
|
|
40
|
+
};
|
|
41
|
+
events.push(event);
|
|
42
|
+
return value;
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function hrtimeToMicroseconds(hrtime) {
|
|
48
|
+
return (hrtime[0] * 1e9 + hrtime[1]) / 1000;
|
|
49
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveExternalModuleAndFn = resolveExternalModuleAndFn;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const module_1 = require("module");
|
|
6
|
+
const process_1 = require("process");
|
|
7
|
+
const changeCaseAll = tslib_1.__importStar(require("change-case-all"));
|
|
8
|
+
function resolveExternalModuleAndFn(pointer) {
|
|
9
|
+
if (typeof pointer === 'function') {
|
|
10
|
+
return pointer;
|
|
11
|
+
}
|
|
12
|
+
let [moduleName, functionName] = pointer.split('#');
|
|
13
|
+
// Temp workaround until v2
|
|
14
|
+
if (moduleName === 'change-case') {
|
|
15
|
+
moduleName = 'change-case-all';
|
|
16
|
+
}
|
|
17
|
+
let loadedModule;
|
|
18
|
+
if (moduleName === 'change-case-all') {
|
|
19
|
+
loadedModule = changeCaseAll;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// we have to use a path to a filename here (it does not need to exist.)
|
|
23
|
+
// https://github.com/dotansimha/graphql-code-generator/issues/6553
|
|
24
|
+
const cwdRequire = (0, module_1.createRequire)((0, process_1.cwd)() + '/index.js');
|
|
25
|
+
loadedModule = cwdRequire(moduleName);
|
|
26
|
+
if (!(functionName in loadedModule) && typeof loadedModule !== 'function') {
|
|
27
|
+
throw new Error(`${functionName} couldn't be found in module ${moduleName}!`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return loadedModule[functionName] || loadedModule;
|
|
31
|
+
}
|
package/cjs/types.js
ADDED
package/cjs/utils.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeOutputs = mergeOutputs;
|
|
4
|
+
exports.isWrapperType = isWrapperType;
|
|
5
|
+
exports.getBaseType = getBaseType;
|
|
6
|
+
exports.removeNonNullWrapper = removeNonNullWrapper;
|
|
7
|
+
const graphql_1 = require("graphql");
|
|
8
|
+
function mergeOutputs(content) {
|
|
9
|
+
const result = { content: '', prepend: [], append: [] };
|
|
10
|
+
if (Array.isArray(content)) {
|
|
11
|
+
for (const item of content) {
|
|
12
|
+
if (typeof item === 'string') {
|
|
13
|
+
result.content += item;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
result.content += item.content;
|
|
17
|
+
result.prepend.push(...(item.prepend || []));
|
|
18
|
+
result.append.push(...(item.append || []));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return [...result.prepend, result.content, ...result.append].join('\n');
|
|
23
|
+
}
|
|
24
|
+
function isWrapperType(t) {
|
|
25
|
+
return (0, graphql_1.isListType)(t) || (0, graphql_1.isNonNullType)(t);
|
|
26
|
+
}
|
|
27
|
+
function getBaseType(type) {
|
|
28
|
+
if (isWrapperType(type)) {
|
|
29
|
+
return getBaseType(type.ofType);
|
|
30
|
+
}
|
|
31
|
+
return type;
|
|
32
|
+
}
|
|
33
|
+
function removeNonNullWrapper(type) {
|
|
34
|
+
return (0, graphql_1.isNonNullType)(type) ? type.ofType : type;
|
|
35
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { astFromInterfaceType, astFromObjectType, getRootTypeNames, MapperKind, mapSchema } from '@graphql-tools/utils';
|
|
2
|
+
import { GraphQLInterfaceType, GraphQLObjectType, isObjectType, parse, } from 'graphql';
|
|
3
|
+
import { oldVisit } from './index.js';
|
|
4
|
+
import { getBaseType } from './utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Federation Spec
|
|
7
|
+
*/
|
|
8
|
+
export const federationSpec = parse(/* GraphQL */ `
|
|
9
|
+
scalar _FieldSet
|
|
10
|
+
|
|
11
|
+
directive @external on FIELD_DEFINITION
|
|
12
|
+
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
|
|
13
|
+
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
|
|
14
|
+
directive @key(fields: _FieldSet!) on OBJECT | INTERFACE
|
|
15
|
+
`);
|
|
16
|
+
/**
|
|
17
|
+
* Adds `__resolveReference` in each ObjectType and InterfaceType involved in Federation.
|
|
18
|
+
* We do this to utilise the existing FieldDefinition logic of the plugin, which includes many logic:
|
|
19
|
+
* - mapper
|
|
20
|
+
* - return type
|
|
21
|
+
* @param schema
|
|
22
|
+
*/
|
|
23
|
+
export function addFederationReferencesToSchema(schema) {
|
|
24
|
+
const setFederationMeta = ({ meta, typeName, update, }) => {
|
|
25
|
+
meta[typeName] = {
|
|
26
|
+
...(meta[typeName] ||
|
|
27
|
+
{
|
|
28
|
+
hasResolveReference: false,
|
|
29
|
+
resolvableKeyDirectives: [],
|
|
30
|
+
referenceSelectionSets: [],
|
|
31
|
+
referenceSelectionSetsString: '',
|
|
32
|
+
}),
|
|
33
|
+
...update,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const getReferenceSelectionSets = ({ resolvableKeyDirectives, fields, }) => {
|
|
37
|
+
const referenceSelectionSets = [];
|
|
38
|
+
// @key() @key() - "primary keys" in Federation
|
|
39
|
+
// A reference may receive one primary key combination at a time, so they will be combined with `|`
|
|
40
|
+
const primaryKeys = resolvableKeyDirectives.map(extractReferenceSelectionSet);
|
|
41
|
+
referenceSelectionSets.push({ directive: '@key', selectionSets: [...primaryKeys] });
|
|
42
|
+
const requiresPossibleTypes = [];
|
|
43
|
+
for (const fieldNode of Object.values(fields)) {
|
|
44
|
+
// Look for @requires and see what the service needs and gets
|
|
45
|
+
const directives = getDirectivesByName('requires', fieldNode.astNode);
|
|
46
|
+
for (const directive of directives) {
|
|
47
|
+
const requires = extractReferenceSelectionSet(directive);
|
|
48
|
+
requiresPossibleTypes.push(requires);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
referenceSelectionSets.push({ directive: '@requires', selectionSets: requiresPossibleTypes });
|
|
52
|
+
return referenceSelectionSets;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Function to find all combinations of selection sets and push them into the `result`
|
|
56
|
+
* This is used for `@requires` directive because depending on the operation selection set, different
|
|
57
|
+
* combination of fields are sent from the router.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* Input: [
|
|
61
|
+
* { a: true },
|
|
62
|
+
* { b: true },
|
|
63
|
+
* { c: true },
|
|
64
|
+
* { d: true},
|
|
65
|
+
* ]
|
|
66
|
+
* Output: [
|
|
67
|
+
* { a: true },
|
|
68
|
+
* { a: true, b: true },
|
|
69
|
+
* { a: true, c: true },
|
|
70
|
+
* { a: true, d: true },
|
|
71
|
+
* { a: true, b: true, c: true },
|
|
72
|
+
* { a: true, b: true, d: true },
|
|
73
|
+
* { a: true, c: true, d: true },
|
|
74
|
+
* { a: true, b: true, c: true, d: true }
|
|
75
|
+
*
|
|
76
|
+
* { b: true },
|
|
77
|
+
* { b: true, c: true },
|
|
78
|
+
* { b: true, d: true },
|
|
79
|
+
* { b: true, c: true, d: true }
|
|
80
|
+
*
|
|
81
|
+
* { c: true },
|
|
82
|
+
* { c: true, d: true },
|
|
83
|
+
*
|
|
84
|
+
* { d: true },
|
|
85
|
+
* ]
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
function findAllSelectionSetCombinations(selectionSets, result) {
|
|
89
|
+
if (selectionSets.length === 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (let baseIndex = 0; baseIndex < selectionSets.length; baseIndex++) {
|
|
93
|
+
const base = selectionSets.slice(0, baseIndex + 1);
|
|
94
|
+
const rest = selectionSets.slice(baseIndex + 1, selectionSets.length);
|
|
95
|
+
const currentSelectionSet = base.reduce((acc, selectionSet) => {
|
|
96
|
+
acc = { ...acc, ...selectionSet };
|
|
97
|
+
return acc;
|
|
98
|
+
}, {});
|
|
99
|
+
if (baseIndex === 0) {
|
|
100
|
+
result.push(currentSelectionSet);
|
|
101
|
+
}
|
|
102
|
+
for (const selectionSet of rest) {
|
|
103
|
+
result.push({ ...currentSelectionSet, ...selectionSet });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const next = selectionSets.slice(1, selectionSets.length);
|
|
107
|
+
if (next.length > 0) {
|
|
108
|
+
findAllSelectionSetCombinations(next, result);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const printReferenceSelectionSets = ({ typeName, baseFederationType, referenceSelectionSets, }) => {
|
|
112
|
+
const referenceSelectionSetStrings = referenceSelectionSets.reduce((acc, { directive, selectionSets: originalSelectionSets }) => {
|
|
113
|
+
const result = [];
|
|
114
|
+
let selectionSets = originalSelectionSets;
|
|
115
|
+
if (directive === '@requires') {
|
|
116
|
+
selectionSets = [];
|
|
117
|
+
findAllSelectionSetCombinations(originalSelectionSets, selectionSets);
|
|
118
|
+
if (selectionSets.length > 0) {
|
|
119
|
+
result.push('Record<PropertyKey, never>');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const referenceSelectionSet of selectionSets) {
|
|
123
|
+
result.push(`GraphQLRecursivePick<${baseFederationType}, ${JSON.stringify(referenceSelectionSet)}>`);
|
|
124
|
+
}
|
|
125
|
+
if (result.length === 0) {
|
|
126
|
+
return acc;
|
|
127
|
+
}
|
|
128
|
+
if (result.length === 1) {
|
|
129
|
+
acc.push(result.join(' | '));
|
|
130
|
+
return acc;
|
|
131
|
+
}
|
|
132
|
+
acc.push(`( ${result.join('\n | ')} )`);
|
|
133
|
+
return acc;
|
|
134
|
+
}, []);
|
|
135
|
+
return `\n ( { __typename: '${typeName}' }\n & ${referenceSelectionSetStrings.join('\n & ')} )`;
|
|
136
|
+
};
|
|
137
|
+
const federationMeta = {};
|
|
138
|
+
const transformedSchema = mapSchema(schema, {
|
|
139
|
+
[MapperKind.INTERFACE_TYPE]: type => {
|
|
140
|
+
const node = astFromInterfaceType(type, schema);
|
|
141
|
+
const federationDetails = checkTypeFederationDetails(node, schema);
|
|
142
|
+
if (federationDetails && federationDetails.resolvableKeyDirectives.length > 0) {
|
|
143
|
+
const typeConfig = type.toConfig();
|
|
144
|
+
typeConfig.fields = {
|
|
145
|
+
[resolveReferenceFieldName]: {
|
|
146
|
+
type,
|
|
147
|
+
},
|
|
148
|
+
...typeConfig.fields,
|
|
149
|
+
};
|
|
150
|
+
const referenceSelectionSets = getReferenceSelectionSets({
|
|
151
|
+
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives,
|
|
152
|
+
fields: typeConfig.fields,
|
|
153
|
+
});
|
|
154
|
+
const referenceSelectionSetsString = printReferenceSelectionSets({
|
|
155
|
+
typeName: type.name,
|
|
156
|
+
baseFederationType: `FederationTypes['${type.name}']`, // FIXME: run convertName on FederationTypes
|
|
157
|
+
referenceSelectionSets,
|
|
158
|
+
});
|
|
159
|
+
setFederationMeta({
|
|
160
|
+
meta: federationMeta,
|
|
161
|
+
typeName: type.name,
|
|
162
|
+
update: {
|
|
163
|
+
hasResolveReference: true,
|
|
164
|
+
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives,
|
|
165
|
+
referenceSelectionSets,
|
|
166
|
+
referenceSelectionSetsString,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
return new GraphQLInterfaceType(typeConfig);
|
|
170
|
+
}
|
|
171
|
+
return type;
|
|
172
|
+
},
|
|
173
|
+
[MapperKind.OBJECT_TYPE]: type => {
|
|
174
|
+
const node = astFromObjectType(type, schema);
|
|
175
|
+
const federationDetails = checkTypeFederationDetails(node, schema);
|
|
176
|
+
if (federationDetails && federationDetails.resolvableKeyDirectives.length > 0) {
|
|
177
|
+
const typeConfig = type.toConfig();
|
|
178
|
+
const referenceSelectionSets = getReferenceSelectionSets({
|
|
179
|
+
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives,
|
|
180
|
+
fields: typeConfig.fields,
|
|
181
|
+
});
|
|
182
|
+
typeConfig.fields = {
|
|
183
|
+
[resolveReferenceFieldName]: {
|
|
184
|
+
type,
|
|
185
|
+
},
|
|
186
|
+
...typeConfig.fields,
|
|
187
|
+
};
|
|
188
|
+
const referenceSelectionSetsString = printReferenceSelectionSets({
|
|
189
|
+
typeName: type.name,
|
|
190
|
+
baseFederationType: `FederationTypes['${type.name}']`, // FIXME: run convertName on FederationTypes
|
|
191
|
+
referenceSelectionSets,
|
|
192
|
+
});
|
|
193
|
+
setFederationMeta({
|
|
194
|
+
meta: federationMeta,
|
|
195
|
+
typeName: type.name,
|
|
196
|
+
update: {
|
|
197
|
+
hasResolveReference: true,
|
|
198
|
+
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives,
|
|
199
|
+
referenceSelectionSets,
|
|
200
|
+
referenceSelectionSetsString,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
return new GraphQLObjectType(typeConfig);
|
|
204
|
+
}
|
|
205
|
+
return type;
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
transformedSchema,
|
|
210
|
+
federationMeta,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Removes Federation Spec from GraphQL Schema
|
|
215
|
+
* @param schema
|
|
216
|
+
* @param config
|
|
217
|
+
*/
|
|
218
|
+
export function removeFederation(schema) {
|
|
219
|
+
return mapSchema(schema, {
|
|
220
|
+
[MapperKind.QUERY]: queryType => {
|
|
221
|
+
const queryTypeConfig = queryType.toConfig();
|
|
222
|
+
delete queryTypeConfig.fields._entities;
|
|
223
|
+
delete queryTypeConfig.fields._service;
|
|
224
|
+
return new GraphQLObjectType(queryTypeConfig);
|
|
225
|
+
},
|
|
226
|
+
[MapperKind.UNION_TYPE]: unionType => {
|
|
227
|
+
const unionTypeName = unionType.name;
|
|
228
|
+
if (unionTypeName === '_Entity' || unionTypeName === '_Any') {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
return unionType;
|
|
232
|
+
},
|
|
233
|
+
[MapperKind.OBJECT_TYPE]: objectType => {
|
|
234
|
+
if (objectType.name === '_Service') {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return objectType;
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const resolveReferenceFieldName = '__resolveReference';
|
|
242
|
+
export class ApolloFederation {
|
|
243
|
+
enabled = false;
|
|
244
|
+
schema;
|
|
245
|
+
providesMap;
|
|
246
|
+
/**
|
|
247
|
+
* `fieldsToGenerate` is a meta object where the keys are object type names
|
|
248
|
+
* and the values are fields that must be generated for that object.
|
|
249
|
+
*/
|
|
250
|
+
fieldsToGenerate;
|
|
251
|
+
meta = {};
|
|
252
|
+
constructor({ enabled, schema, meta }) {
|
|
253
|
+
this.enabled = enabled;
|
|
254
|
+
this.schema = schema;
|
|
255
|
+
this.providesMap = this.createMapOfProvides();
|
|
256
|
+
this.fieldsToGenerate = {};
|
|
257
|
+
this.meta = meta;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Excludes types definde by Federation
|
|
261
|
+
* @param typeNames List of type names
|
|
262
|
+
*/
|
|
263
|
+
filterTypeNames(typeNames) {
|
|
264
|
+
return this.enabled ? typeNames.filter(t => t !== '_FieldSet') : typeNames;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Excludes `__resolveReference` fields
|
|
268
|
+
* @param fieldNames List of field names
|
|
269
|
+
*/
|
|
270
|
+
filterFieldNames(fieldNames) {
|
|
271
|
+
return this.enabled ? fieldNames.filter(t => t !== resolveReferenceFieldName) : fieldNames;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Decides if directive should not be generated
|
|
275
|
+
* @param name directive's name
|
|
276
|
+
*/
|
|
277
|
+
skipDirective(name) {
|
|
278
|
+
return this.enabled && ['external', 'requires', 'provides', 'key'].includes(name);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Decides if scalar should not be generated
|
|
282
|
+
* @param name directive's name
|
|
283
|
+
*/
|
|
284
|
+
skipScalar(name) {
|
|
285
|
+
return this.enabled && name === '_FieldSet';
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* findFieldNodesToGenerate
|
|
289
|
+
* @description Function to find field nodes to generate.
|
|
290
|
+
* In a normal setup, all fields must be generated.
|
|
291
|
+
* However, in a Federatin setup, a field should not be generated if:
|
|
292
|
+
* - The field is marked as `@external` and there is no `@provides` path to the field
|
|
293
|
+
* - The parent object is marked as `@external` and there is no `@provides` path to the field
|
|
294
|
+
*/
|
|
295
|
+
findFieldNodesToGenerate({ node, }) {
|
|
296
|
+
const nodeName = node.name.value;
|
|
297
|
+
if (this.fieldsToGenerate[nodeName]) {
|
|
298
|
+
return this.fieldsToGenerate[nodeName];
|
|
299
|
+
}
|
|
300
|
+
const fieldNodes = (node.fields || []).map(field => field.node);
|
|
301
|
+
if (!this.enabled) {
|
|
302
|
+
return fieldNodes;
|
|
303
|
+
}
|
|
304
|
+
// If the object is marked with `@external`, fields to generate are those with `@provides`
|
|
305
|
+
if (this.isExternal(node)) {
|
|
306
|
+
const fieldNodesWithProvides = fieldNodes.reduce((acc, fieldNode) => {
|
|
307
|
+
if (this.hasProvides(node, fieldNode.name.value)) {
|
|
308
|
+
acc.push(fieldNode);
|
|
309
|
+
return acc;
|
|
310
|
+
}
|
|
311
|
+
return acc;
|
|
312
|
+
}, []);
|
|
313
|
+
this.fieldsToGenerate[nodeName] = fieldNodesWithProvides;
|
|
314
|
+
return fieldNodesWithProvides;
|
|
315
|
+
}
|
|
316
|
+
// If the object is not marked with `@external`, fields to generate are:
|
|
317
|
+
// - the fields without `@external`
|
|
318
|
+
// - the `@external` fields with `@provides`
|
|
319
|
+
const fieldNodesWithoutExternalOrHasProvides = fieldNodes.reduce((acc, fieldNode) => {
|
|
320
|
+
if (!this.isExternal(fieldNode)) {
|
|
321
|
+
acc.push(fieldNode);
|
|
322
|
+
return acc;
|
|
323
|
+
}
|
|
324
|
+
if (this.isExternal(fieldNode) && this.hasProvides(node, fieldNode.name.value)) {
|
|
325
|
+
acc.push(fieldNode);
|
|
326
|
+
return acc;
|
|
327
|
+
}
|
|
328
|
+
return acc;
|
|
329
|
+
}, []);
|
|
330
|
+
this.fieldsToGenerate[nodeName] = fieldNodesWithoutExternalOrHasProvides;
|
|
331
|
+
return fieldNodesWithoutExternalOrHasProvides;
|
|
332
|
+
}
|
|
333
|
+
isResolveReferenceField(fieldNode) {
|
|
334
|
+
const name = typeof fieldNode.name === 'string' ? fieldNode.name : fieldNode.name.value;
|
|
335
|
+
return this.enabled && name === resolveReferenceFieldName;
|
|
336
|
+
}
|
|
337
|
+
addFederationTypeGenericIfApplicable({ genericTypes, typeName, federationTypesType, }) {
|
|
338
|
+
if (!this.getMeta()[typeName]) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const typeRef = `${federationTypesType}['${typeName}']`;
|
|
342
|
+
genericTypes.push(`FederationReferenceType extends ${typeRef} = ${typeRef}`);
|
|
343
|
+
}
|
|
344
|
+
getMeta() {
|
|
345
|
+
return this.meta;
|
|
346
|
+
}
|
|
347
|
+
isExternal(node) {
|
|
348
|
+
return getDirectivesByName('external', node).length > 0;
|
|
349
|
+
}
|
|
350
|
+
hasProvides(node, fieldName) {
|
|
351
|
+
const fields = this.providesMap[node.name.value];
|
|
352
|
+
if (fields?.length) {
|
|
353
|
+
return fields.includes(fieldName);
|
|
354
|
+
}
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
createMapOfProvides() {
|
|
358
|
+
const providesMap = {};
|
|
359
|
+
for (const typename of Object.keys(this.schema.getTypeMap())) {
|
|
360
|
+
const objectType = this.schema.getType(typename);
|
|
361
|
+
if (isObjectType(objectType)) {
|
|
362
|
+
for (const field of Object.values(objectType.getFields())) {
|
|
363
|
+
const provides = getDirectivesByName('provides', field.astNode)
|
|
364
|
+
.map(extractReferenceSelectionSet)
|
|
365
|
+
.reduce((prev, curr) => [...prev, ...Object.keys(curr)], []); // FIXME: this is not taking into account nested selection sets e.g. `company { taxCode }`
|
|
366
|
+
const ofType = getBaseType(field.type);
|
|
367
|
+
providesMap[ofType.name] ||= [];
|
|
368
|
+
providesMap[ofType.name].push(...provides);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return providesMap;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Checks if Object Type is involved in Federation. Based on `@key` directive
|
|
377
|
+
* @param node Type
|
|
378
|
+
*/
|
|
379
|
+
function checkTypeFederationDetails(node, schema) {
|
|
380
|
+
const name = node.name.value;
|
|
381
|
+
const directives = node.directives;
|
|
382
|
+
const rootTypeNames = getRootTypeNames(schema);
|
|
383
|
+
const isNotRoot = !rootTypeNames.has(name);
|
|
384
|
+
const isNotIntrospection = !name.startsWith('__');
|
|
385
|
+
const keyDirectives = directives.filter(d => d.name.value === 'key');
|
|
386
|
+
const check = isNotRoot && isNotIntrospection && keyDirectives.length > 0;
|
|
387
|
+
if (!check) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
const resolvableKeyDirectives = keyDirectives.filter(d => {
|
|
391
|
+
for (const arg of d.arguments) {
|
|
392
|
+
if (arg.name.value === 'resolvable' && arg.value.kind === 'BooleanValue' && arg.value.value === false) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
});
|
|
398
|
+
return { resolvableKeyDirectives };
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Extracts directives from a node based on directive's name
|
|
402
|
+
* @param name directive name
|
|
403
|
+
* @param node ObjectType or Field
|
|
404
|
+
*/
|
|
405
|
+
function getDirectivesByName(name, node) {
|
|
406
|
+
return node?.directives?.filter(d => d.name.value === name) || [];
|
|
407
|
+
}
|
|
408
|
+
function extractReferenceSelectionSet(directive) {
|
|
409
|
+
const arg = directive.arguments.find(arg => arg.name.value === 'fields');
|
|
410
|
+
const { value } = arg.value;
|
|
411
|
+
return oldVisit(parse(`{${value}}`), {
|
|
412
|
+
leave: {
|
|
413
|
+
SelectionSet(node) {
|
|
414
|
+
return node.selections.reduce((accum, field) => {
|
|
415
|
+
accum[field.name] = field.selection;
|
|
416
|
+
return accum;
|
|
417
|
+
}, {});
|
|
418
|
+
},
|
|
419
|
+
Field(node) {
|
|
420
|
+
return {
|
|
421
|
+
name: node.name.value,
|
|
422
|
+
selection: node.selectionSet || true,
|
|
423
|
+
};
|
|
424
|
+
},
|
|
425
|
+
Document(node) {
|
|
426
|
+
return node.definitions.find((def) => def.kind === 'OperationDefinition' && def.operation === 'query').selectionSet;
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
}
|