UNPKG

20.4 kBJavaScriptView Raw
1import { asArray, isDocumentString, parseGraphQLSDL, getDocumentNodeFromSchema, fixSchemaAst, printSchemaWithDirectives, compareStrings } from '@graphql-tools/utils';
2import { cwd, env } from 'process';
3import { isSchema, Kind, Source, print } from 'graphql';
4import pLimit from 'p-limit';
5import importFrom from 'import-from';
6import { printWithComments, resetComments, mergeSchemasAsync, mergeSchemas } from '@graphql-tools/merge';
7
8function normalizePointers(unnormalizedPointerOrPointers) {
9 const ignore = [];
10 const pointerOptionMap = {};
11 const handlePointer = (rawPointer, options = {}) => {
12 if (rawPointer.startsWith('!')) {
13 ignore.push(rawPointer.replace('!', ''));
14 }
15 else {
16 pointerOptionMap[rawPointer] = options;
17 }
18 };
19 for (const rawPointer of asArray(unnormalizedPointerOrPointers)) {
20 if (typeof rawPointer === 'string') {
21 handlePointer(rawPointer);
22 }
23 else if (typeof rawPointer === 'object') {
24 for (const [path, options] of Object.entries(rawPointer)) {
25 handlePointer(path, options);
26 }
27 }
28 else {
29 throw new Error(`Invalid pointer '${rawPointer}'.`);
30 }
31 }
32 return { ignore, pointerOptionMap };
33}
34
35function applyDefaultOptions(options) {
36 options.cache = options.cache || {};
37 options.cwd = options.cwd || cwd();
38 options.sort = 'sort' in options ? options.sort : true;
39}
40
41async function loadFile(pointer, options) {
42 var _a;
43 let results = (_a = options.cache) === null || _a === void 0 ? void 0 : _a[pointer];
44 if (!results) {
45 results = [];
46 await Promise.all(options.loaders.map(async (loader) => {
47 try {
48 const loaderResults = await loader.load(pointer, options);
49 loaderResults === null || loaderResults === void 0 ? void 0 : loaderResults.forEach(result => results.push(result));
50 }
51 catch (error) {
52 if (env['DEBUG']) {
53 console.error(`Failed to find any GraphQL type definitions in: ${pointer} - ${error.message}`);
54 }
55 throw error;
56 }
57 }));
58 if (options.cache) {
59 options.cache[pointer] = results;
60 }
61 }
62 return results;
63}
64function loadFileSync(pointer, options) {
65 var _a;
66 let results = (_a = options.cache) === null || _a === void 0 ? void 0 : _a[pointer];
67 if (!results) {
68 results = [];
69 for (const loader of options.loaders) {
70 try {
71 // We check for the existence so it is okay to force non null
72 const loaderResults = loader.loadSync(pointer, options);
73 loaderResults === null || loaderResults === void 0 ? void 0 : loaderResults.forEach(result => results.push(result));
74 }
75 catch (error) {
76 if (env['DEBUG']) {
77 console.error(`Failed to find any GraphQL type definitions in: ${pointer} - ${error.message}`);
78 }
79 throw error;
80 }
81 }
82 if (options.cache) {
83 options.cache[pointer] = results;
84 }
85 }
86 return results;
87}
88
89/**
90 * Converts a string to 32bit integer
91 */
92function stringToHash(str) {
93 let hash = 0;
94 if (str.length === 0) {
95 return hash;
96 }
97 let char;
98 for (let i = 0; i < str.length; i++) {
99 char = str.charCodeAt(i);
100 // tslint:disable-next-line: no-bitwise
101 hash = (hash << 5) - hash + char;
102 // tslint:disable-next-line: no-bitwise
103 hash = hash & hash;
104 }
105 return hash;
106}
107function useStack(...fns) {
108 return (input) => {
109 function createNext(i) {
110 if (i >= fns.length) {
111 return () => { };
112 }
113 return function next() {
114 fns[i](input, createNext(i + 1));
115 };
116 }
117 fns[0](input, createNext(1));
118 };
119}
120function useLimit(concurrency) {
121 return pLimit(concurrency);
122}
123
124function getCustomLoaderByPath(path, cwd) {
125 try {
126 const requiredModule = importFrom(cwd, path);
127 if (requiredModule) {
128 if (requiredModule.default && typeof requiredModule.default === 'function') {
129 return requiredModule.default;
130 }
131 if (typeof requiredModule === 'function') {
132 return requiredModule;
133 }
134 }
135 }
136 catch (e) { }
137 return null;
138}
139async function useCustomLoader(loaderPointer, cwd) {
140 let loader;
141 if (typeof loaderPointer === 'string') {
142 loader = await getCustomLoaderByPath(loaderPointer, cwd);
143 }
144 else if (typeof loaderPointer === 'function') {
145 loader = loaderPointer;
146 }
147 if (typeof loader !== 'function') {
148 throw new Error(`Failed to load custom loader: ${loaderPointer}`);
149 }
150 return loader;
151}
152function useCustomLoaderSync(loaderPointer, cwd) {
153 let loader;
154 if (typeof loaderPointer === 'string') {
155 loader = getCustomLoaderByPath(loaderPointer, cwd);
156 }
157 else if (typeof loaderPointer === 'function') {
158 loader = loaderPointer;
159 }
160 if (typeof loader !== 'function') {
161 throw new Error(`Failed to load custom loader: ${loaderPointer}`);
162 }
163 return loader;
164}
165
166function useQueue(options) {
167 const queue = [];
168 const limit = (options === null || options === void 0 ? void 0 : options.concurrency) ? pLimit(options.concurrency) : async (fn) => fn();
169 return {
170 add(fn) {
171 queue.push(() => limit(fn));
172 },
173 runAll() {
174 return Promise.all(queue.map(fn => fn()));
175 },
176 };
177}
178function useSyncQueue() {
179 const queue = [];
180 return {
181 add(fn) {
182 queue.push(fn);
183 },
184 runAll() {
185 for (const fn of queue) {
186 fn();
187 }
188 },
189 };
190}
191
192const CONCURRENCY_LIMIT = 50;
193async function collectSources({ pointerOptionMap, options, }) {
194 const sources = [];
195 const queue = useQueue({ concurrency: CONCURRENCY_LIMIT });
196 const { addSource, collect } = createHelpers({
197 sources,
198 stack: [collectDocumentString, collectCustomLoader, collectFallback],
199 });
200 for (const pointer in pointerOptionMap) {
201 const pointerOptions = pointerOptionMap[pointer];
202 collect({
203 pointer,
204 pointerOptions,
205 pointerOptionMap,
206 options,
207 addSource,
208 queue: queue.add,
209 });
210 }
211 await queue.runAll();
212 return sources;
213}
214function collectSourcesSync({ pointerOptionMap, options, }) {
215 const sources = [];
216 const queue = useSyncQueue();
217 const { addSource, collect } = createHelpers({
218 sources,
219 stack: [collectDocumentString, collectCustomLoaderSync, collectFallbackSync],
220 });
221 for (const pointer in pointerOptionMap) {
222 const pointerOptions = pointerOptionMap[pointer];
223 collect({
224 pointer,
225 pointerOptions,
226 pointerOptionMap,
227 options,
228 addSource,
229 queue: queue.add,
230 });
231 }
232 queue.runAll();
233 return sources;
234}
235function createHelpers({ sources, stack }) {
236 const addSource = ({ source }) => {
237 sources.push(source);
238 };
239 const collect = useStack(...stack);
240 return {
241 addSource,
242 collect,
243 };
244}
245function addResultOfCustomLoader({ pointer, result, addSource, }) {
246 if (isSchema(result)) {
247 addSource({
248 source: {
249 location: pointer,
250 schema: result,
251 document: getDocumentNodeFromSchema(result),
252 },
253 pointer,
254 noCache: true,
255 });
256 }
257 else if (result.kind && result.kind === Kind.DOCUMENT) {
258 addSource({
259 source: {
260 document: result,
261 location: pointer,
262 },
263 pointer,
264 });
265 }
266 else if (result.document) {
267 addSource({
268 source: {
269 location: pointer,
270 ...result,
271 },
272 pointer,
273 });
274 }
275}
276function collectDocumentString({ pointer, pointerOptions, options, addSource, queue }, next) {
277 if (isDocumentString(pointer)) {
278 return queue(() => {
279 const source = parseGraphQLSDL(`${stringToHash(pointer)}.graphql`, pointer, {
280 ...options,
281 ...pointerOptions,
282 });
283 addSource({
284 source,
285 pointer,
286 });
287 });
288 }
289 next();
290}
291function collectCustomLoader({ pointer, pointerOptions, queue, addSource, options, pointerOptionMap }, next) {
292 if (pointerOptions.loader) {
293 return queue(async () => {
294 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
295 // @ts-ignore TODO options.cwd is possibly undefined, but it seems like no test covers this path
296 const loader = await useCustomLoader(pointerOptions.loader, options.cwd);
297 const result = await loader(pointer, { ...options, ...pointerOptions }, pointerOptionMap);
298 if (!result) {
299 return;
300 }
301 addResultOfCustomLoader({ pointer, result, addSource });
302 });
303 }
304 next();
305}
306function collectCustomLoaderSync({ pointer, pointerOptions, queue, addSource, options, pointerOptionMap }, next) {
307 if (pointerOptions.loader) {
308 return queue(() => {
309 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
310 // @ts-ignore TODO options.cwd is possibly undefined, but it seems like no test covers this path
311 const loader = useCustomLoaderSync(pointerOptions.loader, options.cwd);
312 const result = loader(pointer, { ...options, ...pointerOptions }, pointerOptionMap);
313 if (result) {
314 addResultOfCustomLoader({ pointer, result, addSource });
315 }
316 });
317 }
318 next();
319}
320function collectFallback({ queue, pointer, options, pointerOptions, addSource }) {
321 return queue(async () => {
322 const sources = await loadFile(pointer, {
323 ...options,
324 ...pointerOptions,
325 });
326 if (sources) {
327 for (const source of sources) {
328 addSource({ source, pointer });
329 }
330 }
331 });
332}
333function collectFallbackSync({ queue, pointer, options, pointerOptions, addSource }) {
334 return queue(() => {
335 const sources = loadFileSync(pointer, {
336 ...options,
337 ...pointerOptions,
338 });
339 if (sources) {
340 for (const source of sources) {
341 addSource({ source, pointer });
342 }
343 }
344 });
345}
346
347/**
348 * @internal
349 */
350const filterKind = (content, filterKinds) => {
351 if (content && content.definitions && content.definitions.length && filterKinds && filterKinds.length > 0) {
352 const invalidDefinitions = [];
353 const validDefinitions = [];
354 for (const definitionNode of content.definitions) {
355 if (filterKinds.includes(definitionNode.kind)) {
356 invalidDefinitions.push(definitionNode);
357 }
358 else {
359 validDefinitions.push(definitionNode);
360 }
361 }
362 if (invalidDefinitions.length > 0) {
363 if (env['DEBUG']) {
364 for (const d of invalidDefinitions) {
365 console.log(`Filtered document of kind ${d.kind} due to filter policy (${filterKinds.join(', ')})`);
366 }
367 }
368 }
369 return {
370 kind: Kind.DOCUMENT,
371 definitions: validDefinitions,
372 };
373 }
374 return content;
375};
376
377function parseSource({ partialSource, options, pointerOptionMap, addValidSource }) {
378 if (partialSource) {
379 const input = prepareInput({
380 source: partialSource,
381 options,
382 pointerOptionMap,
383 });
384 parseSchema(input);
385 parseRawSDL(input);
386 if (input.source.document) {
387 useKindsFilter(input);
388 useComments(input);
389 collectValidSources(input, addValidSource);
390 }
391 }
392}
393//
394function prepareInput({ source, options, pointerOptionMap, }) {
395 let specificOptions = {
396 ...options,
397 };
398 if (source.location) {
399 specificOptions = {
400 ...specificOptions,
401 ...pointerOptionMap[source.location],
402 };
403 }
404 return { source: { ...source }, options: specificOptions };
405}
406function parseSchema(input) {
407 if (input.source.schema) {
408 input.source.schema = fixSchemaAst(input.source.schema, input.options);
409 input.source.rawSDL = printSchemaWithDirectives(input.source.schema, input.options);
410 }
411}
412function parseRawSDL(input) {
413 if (input.source.rawSDL) {
414 input.source.document = parseGraphQLSDL(input.source.location, input.source.rawSDL, input.options).document;
415 }
416}
417function useKindsFilter(input) {
418 if (input.options.filterKinds) {
419 input.source.document = filterKind(input.source.document, input.options.filterKinds);
420 }
421}
422function useComments(input) {
423 if (!input.source.rawSDL && input.source.document) {
424 input.source.rawSDL = printWithComments(input.source.document);
425 resetComments();
426 }
427}
428function collectValidSources(input, addValidSource) {
429 var _a;
430 if (((_a = input.source.document) === null || _a === void 0 ? void 0 : _a.definitions) && input.source.document.definitions.length > 0) {
431 addValidSource(input.source);
432 }
433}
434
435const CONCURRENCY_LIMIT$1 = 100;
436/**
437 * Asynchronously loads any GraphQL documents (i.e. executable documents like
438 * operations and fragments as well as type system definitions) from the
439 * provided pointers.
440 * @param pointerOrPointers Pointers to the sources to load the documents from
441 * @param options Additional options
442 */
443async function loadTypedefs(pointerOrPointers, options) {
444 const { ignore, pointerOptionMap } = normalizePointers(pointerOrPointers);
445 options.ignore = asArray(options.ignore || []);
446 options.ignore.push(...ignore);
447 applyDefaultOptions(options);
448 const sources = await collectSources({
449 pointerOptionMap,
450 options,
451 });
452 const validSources = [];
453 // If we have few k of files it may be an issue
454 const limit = useLimit(CONCURRENCY_LIMIT$1);
455 await Promise.all(sources.map(partialSource => limit(() => parseSource({
456 partialSource,
457 options,
458 pointerOptionMap,
459 addValidSource(source) {
460 validSources.push(source);
461 },
462 }))));
463 return prepareResult({ options, pointerOptionMap, validSources });
464}
465/**
466 * Synchronously loads any GraphQL documents (i.e. executable documents like
467 * operations and fragments as well as type system definitions) from the
468 * provided pointers.
469 * @param pointerOrPointers Pointers to the sources to load the documents from
470 * @param options Additional options
471 */
472function loadTypedefsSync(pointerOrPointers, options) {
473 const { ignore, pointerOptionMap } = normalizePointers(pointerOrPointers);
474 options.ignore = asArray(options.ignore || []);
475 options.ignore.push(...ignore);
476 applyDefaultOptions(options);
477 const sources = collectSourcesSync({
478 pointerOptionMap,
479 options,
480 });
481 const validSources = [];
482 for (const partialSource of sources) {
483 parseSource({
484 partialSource,
485 options,
486 pointerOptionMap,
487 addValidSource(source) {
488 validSources.push(source);
489 },
490 });
491 }
492 return prepareResult({ options, pointerOptionMap, validSources });
493}
494//
495function prepareResult({ options, pointerOptionMap, validSources, }) {
496 const pointerList = Object.keys(pointerOptionMap);
497 if (pointerList.length > 0 && validSources.length === 0) {
498 throw new Error(`
499 Unable to find any GraphQL type definitions for the following pointers:
500 ${pointerList.map(p => `
501 - ${p}
502 `)}`);
503 }
504 return options.sort
505 ? validSources.sort((left, right) => compareStrings(left.location, right.location))
506 : validSources;
507}
508
509/**
510 * Kinds of AST nodes that are included in executable documents
511 */
512const OPERATION_KINDS = [Kind.OPERATION_DEFINITION, Kind.FRAGMENT_DEFINITION];
513/**
514 * Kinds of AST nodes that are included in type system definition documents
515 */
516const NON_OPERATION_KINDS = Object.keys(Kind)
517 .reduce((prev, v) => [...prev, Kind[v]], [])
518 .filter(v => !OPERATION_KINDS.includes(v));
519/**
520 * Asynchronously loads executable documents (i.e. operations and fragments) from
521 * the provided pointers. The pointers may be individual files or a glob pattern.
522 * The files themselves may be `.graphql` files or `.js` and `.ts` (in which
523 * case they will be parsed using graphql-tag-pluck).
524 * @param pointerOrPointers Pointers to the files to load the documents from
525 * @param options Additional options
526 */
527function loadDocuments(pointerOrPointers, options) {
528 return loadTypedefs(pointerOrPointers, { noRequire: true, filterKinds: NON_OPERATION_KINDS, ...options });
529}
530/**
531 * Synchronously loads executable documents (i.e. operations and fragments) from
532 * the provided pointers. The pointers may be individual files or a glob pattern.
533 * The files themselves may be `.graphql` files or `.js` and `.ts` (in which
534 * case they will be parsed using graphql-tag-pluck).
535 * @param pointerOrPointers Pointers to the files to load the documents from
536 * @param options Additional options
537 */
538function loadDocumentsSync(pointerOrPointers, options) {
539 return loadTypedefsSync(pointerOrPointers, { noRequire: true, filterKinds: NON_OPERATION_KINDS, ...options });
540}
541
542/**
543 * Asynchronously loads a schema from the provided pointers.
544 * @param schemaPointers Pointers to the sources to load the schema from
545 * @param options Additional options
546 */
547async function loadSchema(schemaPointers, options) {
548 const sources = await loadTypedefs(schemaPointers, {
549 filterKinds: OPERATION_KINDS,
550 ...options,
551 });
552 const { schemas, typeDefs } = collectSchemasAndTypeDefs(sources);
553 const mergeSchemasOptions = {
554 schemas,
555 typeDefs,
556 ...options,
557 };
558 const schema = await mergeSchemasAsync(mergeSchemasOptions);
559 if (options === null || options === void 0 ? void 0 : options.includeSources) {
560 includeSources(schema, sources);
561 }
562 return schema;
563}
564/**
565 * Synchronously loads a schema from the provided pointers.
566 * @param schemaPointers Pointers to the sources to load the schema from
567 * @param options Additional options
568 */
569function loadSchemaSync(schemaPointers, options) {
570 const sources = loadTypedefsSync(schemaPointers, {
571 filterKinds: OPERATION_KINDS,
572 ...options,
573 });
574 const { schemas, typeDefs } = collectSchemasAndTypeDefs(sources);
575 const mergeSchemasOptions = {
576 schemas,
577 typeDefs,
578 ...options,
579 };
580 const schema = mergeSchemas(mergeSchemasOptions);
581 if (options === null || options === void 0 ? void 0 : options.includeSources) {
582 includeSources(schema, sources);
583 }
584 return schema;
585}
586function includeSources(schema, sources) {
587 const finalSources = [];
588 for (const source of sources) {
589 if (source.rawSDL) {
590 finalSources.push(new Source(source.rawSDL, source.location));
591 }
592 else if (source.document) {
593 finalSources.push(new Source(print(source.document), source.location));
594 }
595 }
596 schema.extensions = {
597 ...schema.extensions,
598 sources: finalSources,
599 extendedSources: sources,
600 };
601}
602function collectSchemasAndTypeDefs(sources) {
603 const schemas = [];
604 const typeDefs = [];
605 for (const source of sources) {
606 if (source.schema) {
607 schemas.push(source.schema);
608 }
609 else if (source.document) {
610 typeDefs.push(source.document);
611 }
612 }
613 return {
614 schemas,
615 typeDefs,
616 };
617}
618
619export { NON_OPERATION_KINDS, OPERATION_KINDS, filterKind, loadDocuments, loadDocumentsSync, loadSchema, loadSchemaSync, loadTypedefs, loadTypedefsSync };