UNPKG

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