UNPKG

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