build-operation-for-field.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import { isObjectType, getNamedType, isUnionType, isNonNullType, isScalarType, isListType, isInterfaceType, isEnumType, Kind, } from 'graphql';
  2. import { getDefinedRootType, getRootTypeNames } from './rootTypes.js';
  3. let operationVariables = [];
  4. let fieldTypeMap = new Map();
  5. function addOperationVariable(variable) {
  6. operationVariables.push(variable);
  7. }
  8. function resetOperationVariables() {
  9. operationVariables = [];
  10. }
  11. function resetFieldMap() {
  12. fieldTypeMap = new Map();
  13. }
  14. export function buildOperationNodeForField({ schema, kind, field, models, ignore = [], depthLimit, circularReferenceDepth, argNames, selectedFields = true, }) {
  15. resetOperationVariables();
  16. resetFieldMap();
  17. const rootTypeNames = getRootTypeNames(schema);
  18. const operationNode = buildOperationAndCollectVariables({
  19. schema,
  20. fieldName: field,
  21. kind,
  22. models: models || [],
  23. ignore,
  24. depthLimit: depthLimit || Infinity,
  25. circularReferenceDepth: circularReferenceDepth || 1,
  26. argNames,
  27. selectedFields,
  28. rootTypeNames,
  29. });
  30. // attach variables
  31. operationNode.variableDefinitions = [...operationVariables];
  32. resetOperationVariables();
  33. resetFieldMap();
  34. return operationNode;
  35. }
  36. function buildOperationAndCollectVariables({ schema, fieldName, kind, models, ignore, depthLimit, circularReferenceDepth, argNames, selectedFields, rootTypeNames, }) {
  37. const type = getDefinedRootType(schema, kind);
  38. const field = type.getFields()[fieldName];
  39. const operationName = `${fieldName}_${kind}`;
  40. if (field.args) {
  41. for (const arg of field.args) {
  42. const argName = arg.name;
  43. if (!argNames || argNames.includes(argName)) {
  44. addOperationVariable(resolveVariable(arg, argName));
  45. }
  46. }
  47. }
  48. return {
  49. kind: Kind.OPERATION_DEFINITION,
  50. operation: kind,
  51. name: {
  52. kind: Kind.NAME,
  53. value: operationName,
  54. },
  55. variableDefinitions: [],
  56. selectionSet: {
  57. kind: Kind.SELECTION_SET,
  58. selections: [
  59. resolveField({
  60. type,
  61. field,
  62. models,
  63. firstCall: true,
  64. path: [],
  65. ancestors: [],
  66. ignore,
  67. depthLimit,
  68. circularReferenceDepth,
  69. schema,
  70. depth: 0,
  71. argNames,
  72. selectedFields,
  73. rootTypeNames,
  74. }),
  75. ],
  76. },
  77. };
  78. }
  79. function resolveSelectionSet({ parent, type, models, firstCall, path, ancestors, ignore, depthLimit, circularReferenceDepth, schema, depth, argNames, selectedFields, rootTypeNames, }) {
  80. if (typeof selectedFields === 'boolean' && depth > depthLimit) {
  81. return;
  82. }
  83. if (isUnionType(type)) {
  84. const types = type.getTypes();
  85. return {
  86. kind: Kind.SELECTION_SET,
  87. selections: types
  88. .filter(t => !hasCircularRef([...ancestors, t], {
  89. depth: circularReferenceDepth,
  90. }))
  91. .map(t => {
  92. return {
  93. kind: Kind.INLINE_FRAGMENT,
  94. typeCondition: {
  95. kind: Kind.NAMED_TYPE,
  96. name: {
  97. kind: Kind.NAME,
  98. value: t.name,
  99. },
  100. },
  101. selectionSet: resolveSelectionSet({
  102. parent: type,
  103. type: t,
  104. models,
  105. path,
  106. ancestors,
  107. ignore,
  108. depthLimit,
  109. circularReferenceDepth,
  110. schema,
  111. depth,
  112. argNames,
  113. selectedFields,
  114. rootTypeNames,
  115. }),
  116. };
  117. })
  118. .filter(fragmentNode => { var _a, _b; return ((_b = (_a = fragmentNode === null || fragmentNode === void 0 ? void 0 : fragmentNode.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) === null || _b === void 0 ? void 0 : _b.length) > 0; }),
  119. };
  120. }
  121. if (isInterfaceType(type)) {
  122. const types = Object.values(schema.getTypeMap()).filter((t) => isObjectType(t) && t.getInterfaces().includes(type));
  123. return {
  124. kind: Kind.SELECTION_SET,
  125. selections: types
  126. .filter(t => !hasCircularRef([...ancestors, t], {
  127. depth: circularReferenceDepth,
  128. }))
  129. .map(t => {
  130. return {
  131. kind: Kind.INLINE_FRAGMENT,
  132. typeCondition: {
  133. kind: Kind.NAMED_TYPE,
  134. name: {
  135. kind: Kind.NAME,
  136. value: t.name,
  137. },
  138. },
  139. selectionSet: resolveSelectionSet({
  140. parent: type,
  141. type: t,
  142. models,
  143. path,
  144. ancestors,
  145. ignore,
  146. depthLimit,
  147. circularReferenceDepth,
  148. schema,
  149. depth,
  150. argNames,
  151. selectedFields,
  152. rootTypeNames,
  153. }),
  154. };
  155. })
  156. .filter(fragmentNode => { var _a, _b; return ((_b = (_a = fragmentNode === null || fragmentNode === void 0 ? void 0 : fragmentNode.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) === null || _b === void 0 ? void 0 : _b.length) > 0; }),
  157. };
  158. }
  159. if (isObjectType(type) && !rootTypeNames.has(type.name)) {
  160. const isIgnored = ignore.includes(type.name) || ignore.includes(`${parent.name}.${path[path.length - 1]}`);
  161. const isModel = models.includes(type.name);
  162. if (!firstCall && isModel && !isIgnored) {
  163. return {
  164. kind: Kind.SELECTION_SET,
  165. selections: [
  166. {
  167. kind: Kind.FIELD,
  168. name: {
  169. kind: Kind.NAME,
  170. value: 'id',
  171. },
  172. },
  173. ],
  174. };
  175. }
  176. const fields = type.getFields();
  177. return {
  178. kind: Kind.SELECTION_SET,
  179. selections: Object.keys(fields)
  180. .filter(fieldName => {
  181. return !hasCircularRef([...ancestors, getNamedType(fields[fieldName].type)], {
  182. depth: circularReferenceDepth,
  183. });
  184. })
  185. .map(fieldName => {
  186. const selectedSubFields = typeof selectedFields === 'object' ? selectedFields[fieldName] : true;
  187. if (selectedSubFields) {
  188. return resolveField({
  189. type,
  190. field: fields[fieldName],
  191. models,
  192. path: [...path, fieldName],
  193. ancestors,
  194. ignore,
  195. depthLimit,
  196. circularReferenceDepth,
  197. schema,
  198. depth,
  199. argNames,
  200. selectedFields: selectedSubFields,
  201. rootTypeNames,
  202. });
  203. }
  204. return null;
  205. })
  206. .filter((f) => {
  207. var _a, _b;
  208. if (f == null) {
  209. return false;
  210. }
  211. else if ('selectionSet' in f) {
  212. return !!((_b = (_a = f.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) === null || _b === void 0 ? void 0 : _b.length);
  213. }
  214. return true;
  215. }),
  216. };
  217. }
  218. }
  219. function resolveVariable(arg, name) {
  220. function resolveVariableType(type) {
  221. if (isListType(type)) {
  222. return {
  223. kind: Kind.LIST_TYPE,
  224. type: resolveVariableType(type.ofType),
  225. };
  226. }
  227. if (isNonNullType(type)) {
  228. return {
  229. kind: Kind.NON_NULL_TYPE,
  230. // for v16 compatibility
  231. type: resolveVariableType(type.ofType),
  232. };
  233. }
  234. return {
  235. kind: Kind.NAMED_TYPE,
  236. name: {
  237. kind: Kind.NAME,
  238. value: type.name,
  239. },
  240. };
  241. }
  242. return {
  243. kind: Kind.VARIABLE_DEFINITION,
  244. variable: {
  245. kind: Kind.VARIABLE,
  246. name: {
  247. kind: Kind.NAME,
  248. value: name || arg.name,
  249. },
  250. },
  251. type: resolveVariableType(arg.type),
  252. };
  253. }
  254. function getArgumentName(name, path) {
  255. return [...path, name].join('_');
  256. }
  257. function resolveField({ type, field, models, firstCall, path, ancestors, ignore, depthLimit, circularReferenceDepth, schema, depth, argNames, selectedFields, rootTypeNames, }) {
  258. const namedType = getNamedType(field.type);
  259. let args = [];
  260. let removeField = false;
  261. if (field.args && field.args.length) {
  262. args = field.args
  263. .map(arg => {
  264. const argumentName = getArgumentName(arg.name, path);
  265. if (argNames && !argNames.includes(argumentName)) {
  266. if (isNonNullType(arg.type)) {
  267. removeField = true;
  268. }
  269. return null;
  270. }
  271. if (!firstCall) {
  272. addOperationVariable(resolveVariable(arg, argumentName));
  273. }
  274. return {
  275. kind: Kind.ARGUMENT,
  276. name: {
  277. kind: Kind.NAME,
  278. value: arg.name,
  279. },
  280. value: {
  281. kind: Kind.VARIABLE,
  282. name: {
  283. kind: Kind.NAME,
  284. value: getArgumentName(arg.name, path),
  285. },
  286. },
  287. };
  288. })
  289. .filter(Boolean);
  290. }
  291. if (removeField) {
  292. return null;
  293. }
  294. const fieldPath = [...path, field.name];
  295. const fieldPathStr = fieldPath.join('.');
  296. let fieldName = field.name;
  297. if (fieldTypeMap.has(fieldPathStr) && fieldTypeMap.get(fieldPathStr) !== field.type.toString()) {
  298. fieldName += field.type.toString().replace('!', 'NonNull');
  299. }
  300. fieldTypeMap.set(fieldPathStr, field.type.toString());
  301. if (!isScalarType(namedType) && !isEnumType(namedType)) {
  302. return {
  303. kind: Kind.FIELD,
  304. name: {
  305. kind: Kind.NAME,
  306. value: field.name,
  307. },
  308. ...(fieldName !== field.name && { alias: { kind: Kind.NAME, value: fieldName } }),
  309. selectionSet: resolveSelectionSet({
  310. parent: type,
  311. type: namedType,
  312. models,
  313. firstCall,
  314. path: fieldPath,
  315. ancestors: [...ancestors, type],
  316. ignore,
  317. depthLimit,
  318. circularReferenceDepth,
  319. schema,
  320. depth: depth + 1,
  321. argNames,
  322. selectedFields,
  323. rootTypeNames,
  324. }) || undefined,
  325. arguments: args,
  326. };
  327. }
  328. return {
  329. kind: Kind.FIELD,
  330. name: {
  331. kind: Kind.NAME,
  332. value: field.name,
  333. },
  334. ...(fieldName !== field.name && { alias: { kind: Kind.NAME, value: fieldName } }),
  335. arguments: args,
  336. };
  337. }
  338. function hasCircularRef(types, config = {
  339. depth: 1,
  340. }) {
  341. const type = types[types.length - 1];
  342. if (isScalarType(type)) {
  343. return false;
  344. }
  345. const size = types.filter(t => t.name === type.name).length;
  346. return size > config.depth;
  347. }