@graphql-tools/external-fragment-loader 0.0.0-stage → 0.0.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/esm/resolve.js ADDED
@@ -0,0 +1,569 @@
1
+ import { existsSync, promises as fsPromises, readFileSync } from 'fs';
2
+ import { basename, join, resolve } from 'path';
3
+ import globby from 'globby';
4
+ import { Kind, parse, print, visit } from 'graphql';
5
+ import memoizee from 'memoizee';
6
+ import unixify from 'unixify';
7
+ import { gqlPluckFromCodeStringSync, } from '@graphql-tools/graphql-tag-pluck';
8
+ const { readFile } = fsPromises;
9
+ const packageMapData = new WeakMap();
10
+ // --- Core logic ---
11
+ const DEFAULT_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'graphql', 'gql'];
12
+ const GQL_EXTENSIONS = ['graphql', 'gql'];
13
+ const DEFAULT_SCAN_INTERNAL_DIRS = ['src'];
14
+ const DEFAULT_EXCLUDE_PATTERNS = ['**/__generated__/**', '**/node_modules/**'];
15
+ function getSourceGlob(extensions) {
16
+ return extensions.length === 1 ? `**/*.${extensions[0]}` : `**/*.{${extensions.join(',')}}`;
17
+ }
18
+ function isGraphQLFile(filePath) {
19
+ return GQL_EXTENSIONS.some(ext => filePath.endsWith(`.${ext}`));
20
+ }
21
+ function extractFragmentsAndSpreads(rawSDLs) {
22
+ const sources = rawSDLs.map(rawSDL => ({
23
+ rawSDL,
24
+ document: parse(rawSDL, { noLocation: true }),
25
+ }));
26
+ const definitions = [];
27
+ const spreads = new Set();
28
+ const spreadsPerFragment = new Map();
29
+ for (const source of sources) {
30
+ visit(source.document, {
31
+ [Kind.FRAGMENT_DEFINITION](node) {
32
+ definitions.push({
33
+ name: node.name.value,
34
+ typeCondition: node.typeCondition.name.value,
35
+ });
36
+ const fragmentSpreads = spreadsPerFragment.get(node.name.value) ?? new Set();
37
+ visit(node, {
38
+ [Kind.FRAGMENT_SPREAD](spread) {
39
+ fragmentSpreads.add(spread.name.value);
40
+ },
41
+ });
42
+ spreadsPerFragment.set(node.name.value, fragmentSpreads);
43
+ },
44
+ [Kind.FRAGMENT_SPREAD](node) {
45
+ spreads.add(node.name.value);
46
+ },
47
+ });
48
+ }
49
+ return { sources, definitions, spreads, spreadsPerFragment };
50
+ }
51
+ function createPackageMapBuilder() {
52
+ return {
53
+ fragments: new Map(),
54
+ spreadsPerFile: new Map(),
55
+ defsPerFile: new Map(),
56
+ sourcesPerFile: new Map(),
57
+ spreadsPerFragment: new Map(),
58
+ };
59
+ }
60
+ function registerFile({ builder, packageDir, filePath, info, }) {
61
+ builder.sourcesPerFile.set(filePath, info.sources);
62
+ if (info.definitions.length === 0 && info.spreads.size === 0)
63
+ return;
64
+ builder.defsPerFile.set(filePath, info.definitions);
65
+ builder.spreadsPerFile.set(filePath, info.spreads);
66
+ for (const [fragmentName, fragmentSpreads] of info.spreadsPerFragment) {
67
+ const spreads = builder.spreadsPerFragment.get(fragmentName) ?? new Set();
68
+ for (const spread of fragmentSpreads) {
69
+ spreads.add(spread);
70
+ }
71
+ builder.spreadsPerFragment.set(fragmentName, spreads);
72
+ }
73
+ for (const def of info.definitions) {
74
+ const existing = builder.fragments.get(def.name);
75
+ if (existing && existing.filePath !== filePath) {
76
+ throw new Error(`Duplicate fragment "${def.name}" within package at "${packageDir}": ` +
77
+ `defined in "${existing.filePath}" and "${filePath}".`);
78
+ }
79
+ builder.fragments.set(def.name, { filePath, typeCondition: def.typeCondition });
80
+ }
81
+ }
82
+ function parseFile({ filePath, content, pluckConfig, fileContentFilter, }) {
83
+ if (fileContentFilter && !fileContentFilter(content, filePath))
84
+ return;
85
+ let rawSDLs = [content];
86
+ if (!isGraphQLFile(filePath)) {
87
+ let pluckedSources;
88
+ try {
89
+ // Code files are scanned opportunistically, so skip files that cannot be
90
+ // plucked. GraphQL parsing below must still surface invalid documents.
91
+ pluckedSources = gqlPluckFromCodeStringSync(filePath, content, pluckConfig);
92
+ }
93
+ catch {
94
+ return undefined;
95
+ }
96
+ rawSDLs = pluckedSources.map(source => source.body);
97
+ }
98
+ return extractFragmentsAndSpreads(rawSDLs);
99
+ }
100
+ // --- Memoized functions ---
101
+ function createPackageFragmentMap(builder) {
102
+ const map = {
103
+ fragments: builder.fragments,
104
+ spreadsPerFile: builder.spreadsPerFile,
105
+ defsPerFile: builder.defsPerFile,
106
+ };
107
+ packageMapData.set(map, {
108
+ sourcesPerFile: builder.sourcesPerFile,
109
+ spreadsPerFragment: builder.spreadsPerFragment,
110
+ });
111
+ return map;
112
+ }
113
+ function getPackageMapData(map) {
114
+ const data = packageMapData.get(map);
115
+ if (!data) {
116
+ throw new Error('External fragment package map metadata is missing.');
117
+ }
118
+ return data;
119
+ }
120
+ function readPackageJsonDepsRaw(packageJsonPath, includeDevDependencies) {
121
+ try {
122
+ const content = readFileSync(packageJsonPath, 'utf8');
123
+ const { dependencies = {}, devDependencies = {} } = JSON.parse(content);
124
+ const allDeps = includeDevDependencies ? { ...dependencies, ...devDependencies } : dependencies;
125
+ return { names: Object.keys(allDeps), record: allDeps };
126
+ }
127
+ catch (err) {
128
+ if (err.code === 'ENOENT') {
129
+ return { names: [], record: {} };
130
+ }
131
+ throw err;
132
+ }
133
+ }
134
+ function buildPackageFragmentMapRaw(packageDir, scanInternalDirs, extensions, excludePatterns, pluckConfig, fileContentFilter) {
135
+ const builder = createPackageMapBuilder();
136
+ const dirs = scanInternalDirs
137
+ .map(folder => resolve(packageDir, folder))
138
+ .filter(dir => existsSync(dir));
139
+ if (dirs.length === 0) {
140
+ return createPackageFragmentMap(builder);
141
+ }
142
+ const sourceGlob = getSourceGlob(extensions);
143
+ const ignorePatterns = excludePatterns.map(p => `!${p}`);
144
+ const allFiles = dirs.flatMap(dir => globby.sync([unixify(sourceGlob), ...ignorePatterns.map(unixify)], {
145
+ cwd: dir,
146
+ absolute: true,
147
+ }));
148
+ for (const filePath of allFiles) {
149
+ const content = readFileSync(filePath, 'utf8');
150
+ const info = parseFile({ filePath, content, pluckConfig, fileContentFilter });
151
+ if (info)
152
+ registerFile({ builder, packageDir, filePath, info });
153
+ }
154
+ return createPackageFragmentMap(builder);
155
+ }
156
+ async function buildPackageFragmentMapAsyncRaw(packageDir, scanInternalDirs, extensions, excludePatterns, pluckConfig, fileContentFilter) {
157
+ const builder = createPackageMapBuilder();
158
+ const dirs = scanInternalDirs
159
+ .map(folder => resolve(packageDir, folder))
160
+ .filter(dir => existsSync(dir));
161
+ if (dirs.length === 0) {
162
+ return createPackageFragmentMap(builder);
163
+ }
164
+ const sourceGlob = getSourceGlob(extensions);
165
+ const ignorePatterns = excludePatterns.map(p => `!${p}`);
166
+ const allFiles = (await Promise.all(dirs.map(dir => globby([unixify(sourceGlob), ...ignorePatterns.map(unixify)], {
167
+ cwd: dir,
168
+ absolute: true,
169
+ })))).flat();
170
+ await Promise.all(allFiles.map(async (filePath) => {
171
+ const content = await readFile(filePath, 'utf8');
172
+ const info = parseFile({ filePath, content, pluckConfig, fileContentFilter });
173
+ if (info)
174
+ registerFile({ builder, packageDir, filePath, info });
175
+ }));
176
+ return createPackageFragmentMap(builder);
177
+ }
178
+ let readPackageJsonDeps = memoizee(readPackageJsonDepsRaw, { primitive: true });
179
+ let buildPackageFragmentMap = memoizee(buildPackageFragmentMapRaw, {
180
+ primitive: true,
181
+ });
182
+ let buildPackageFragmentMapAsync = memoizee(buildPackageFragmentMapAsyncRaw, {
183
+ primitive: true,
184
+ promise: true,
185
+ });
186
+ let appliedCacheTTL;
187
+ function initCache(cacheTTL) {
188
+ if (cacheTTL === appliedCacheTTL)
189
+ return;
190
+ const ttlOpt = cacheTTL != null && cacheTTL !== Infinity ? { maxAge: cacheTTL } : undefined;
191
+ readPackageJsonDeps = memoizee(readPackageJsonDepsRaw, {
192
+ primitive: true,
193
+ ...ttlOpt,
194
+ });
195
+ buildPackageFragmentMap = memoizee(buildPackageFragmentMapRaw, {
196
+ primitive: true,
197
+ ...ttlOpt,
198
+ });
199
+ buildPackageFragmentMapAsync = memoizee(buildPackageFragmentMapAsyncRaw, {
200
+ primitive: true,
201
+ promise: true,
202
+ ...ttlOpt,
203
+ });
204
+ appliedCacheTTL = cacheTTL;
205
+ }
206
+ /**
207
+ * Clears all internal caches.
208
+ */
209
+ export function clearCache() {
210
+ readPackageJsonDeps.clear();
211
+ buildPackageFragmentMap.clear();
212
+ buildPackageFragmentMapAsync.clear();
213
+ appliedCacheTTL = undefined;
214
+ }
215
+ function getPackageFragmentMap(args, cacheResult) {
216
+ if (cacheResult) {
217
+ return buildPackageFragmentMap(...args);
218
+ }
219
+ return buildPackageFragmentMap._get(...args) ?? buildPackageFragmentMapRaw(...args);
220
+ }
221
+ function getPackageFragmentMapAsync(args, cacheResult) {
222
+ if (cacheResult) {
223
+ return buildPackageFragmentMapAsync(...args);
224
+ }
225
+ return buildPackageFragmentMapAsync._get(...args) ?? buildPackageFragmentMapAsyncRaw(...args);
226
+ }
227
+ // --- Shared helpers ---
228
+ function findPackageDir(packageName, externalPackagesDirs) {
229
+ for (const searchPath of externalPackagesDirs) {
230
+ const candidate = join(searchPath, packageName);
231
+ if (existsSync(join(candidate, 'package.json'))) {
232
+ return candidate;
233
+ }
234
+ }
235
+ return null;
236
+ }
237
+ function collectTransitiveDeps({ packageName, externalPackagesDirs, filter, includeDevDependencies, packageDependencyFilter, visited = new Set(), result = new Set(), }) {
238
+ if (visited.has(packageName))
239
+ return result;
240
+ visited.add(packageName);
241
+ const pkgDir = findPackageDir(packageName, externalPackagesDirs);
242
+ if (!pkgDir)
243
+ return result;
244
+ const packageJsonPath = join(pkgDir, 'package.json');
245
+ const { names: deps, record: depsRecord } = readPackageJsonDeps(packageJsonPath, includeDevDependencies);
246
+ if (!packageDependencyFilter || packageDependencyFilter(depsRecord)) {
247
+ result.add(packageName);
248
+ }
249
+ for (const dep of deps.filter(filter)) {
250
+ collectTransitiveDeps({
251
+ packageName: dep,
252
+ externalPackagesDirs,
253
+ filter,
254
+ includeDevDependencies,
255
+ packageDependencyFilter,
256
+ visited,
257
+ result,
258
+ });
259
+ }
260
+ return result;
261
+ }
262
+ function findMissingFragments(rootMap) {
263
+ const missing = new Set();
264
+ for (const spreads of rootMap.spreadsPerFile.values()) {
265
+ for (const spread of spreads) {
266
+ if (!rootMap.fragments.has(spread)) {
267
+ missing.add(spread);
268
+ }
269
+ }
270
+ }
271
+ return missing;
272
+ }
273
+ /**
274
+ * Resolves missing fragments to provider files, following their external spreads.
275
+ * Examples, 'A' is the fragment name being looked up:
276
+ * - Need A; only `file_a.graphql` defines A, with no spreads -> [file_a.graphql].
277
+ * - Need A; no provider defines A -> throws a missing fragment error.
278
+ * - Need A; two packages define A -> throws a duplicate fragment error.
279
+ * - Need A; A in `file_a.graphql` spreads B, provided by `file_b.graphql` -> [file_a.graphql, file_b.graphql].
280
+ */
281
+ function findExternalFragments({ missingFragments, depMaps, rootMap, rootPackageName, filterToRequiredFragments, }) {
282
+ const globalIndex = new Map();
283
+ for (const [depName, map] of depMaps) {
284
+ for (const [fragName, info] of map.fragments) {
285
+ const entry = {
286
+ packageName: depName,
287
+ filePath: info.filePath,
288
+ typeCondition: info.typeCondition,
289
+ };
290
+ const existing = globalIndex.get(fragName);
291
+ if (existing) {
292
+ existing.push(entry);
293
+ }
294
+ else {
295
+ globalIndex.set(fragName, [entry]);
296
+ }
297
+ }
298
+ }
299
+ const resolvedFiles = new Map();
300
+ const requiredFragmentNamesPerFile = new Map();
301
+ const resolvedFragmentNames = new Set();
302
+ const localFragmentNames = new Set(rootMap.fragments.keys());
303
+ const unresolved = new Set(missingFragments);
304
+ while (unresolved.size > 0) {
305
+ const nextUnresolved = new Set();
306
+ const errors = [];
307
+ for (const fragName of unresolved) {
308
+ if (resolvedFragmentNames.has(fragName) || localFragmentNames.has(fragName))
309
+ continue;
310
+ const entries = globalIndex.get(fragName);
311
+ if (!entries) {
312
+ errors.push(`Fragment "${fragName}" is spread in "${rootPackageName}" but not defined in any of its transitive dependencies.`);
313
+ }
314
+ else if (entries.length > 1) {
315
+ const providers = entries.map(e => `"${e.packageName}" (${e.filePath})`).join(' and ');
316
+ errors.push(`Duplicate fragment "${fragName}" found in ${providers}.`);
317
+ }
318
+ else {
319
+ const entry = entries[0];
320
+ resolvedFragmentNames.add(fragName);
321
+ const requiredFragmentNames = requiredFragmentNamesPerFile.get(entry.filePath) ?? new Set();
322
+ requiredFragmentNames.add(fragName);
323
+ requiredFragmentNamesPerFile.set(entry.filePath, requiredFragmentNames);
324
+ const depMap = depMaps.get(entry.packageName);
325
+ if (!resolvedFiles.has(entry.filePath)) {
326
+ const defs = depMap.defsPerFile.get(entry.filePath) || [];
327
+ resolvedFiles.set(entry.filePath, {
328
+ filePath: entry.filePath,
329
+ definitions: defs,
330
+ packageName: entry.packageName,
331
+ });
332
+ }
333
+ const spreads = filterToRequiredFragments
334
+ ? getPackageMapData(depMap).spreadsPerFragment.get(fragName)
335
+ : depMap.spreadsPerFile.get(entry.filePath);
336
+ for (const spread of spreads ?? []) {
337
+ if (!resolvedFragmentNames.has(spread) && !localFragmentNames.has(spread)) {
338
+ nextUnresolved.add(spread);
339
+ }
340
+ }
341
+ }
342
+ }
343
+ if (errors.length > 0) {
344
+ throw new Error(errors.join('\n'));
345
+ }
346
+ if (nextUnresolved.size === 0)
347
+ break;
348
+ unresolved.clear();
349
+ for (const s of nextUnresolved)
350
+ unresolved.add(s);
351
+ }
352
+ return [...resolvedFiles.values()].map(file => {
353
+ const depMap = depMaps.get(file.packageName);
354
+ const sources = getPackageMapData(depMap).sourcesPerFile.get(file.filePath) ?? [];
355
+ const requiredFragmentNames = requiredFragmentNamesPerFile.get(file.filePath) ?? new Set();
356
+ return {
357
+ ...file,
358
+ // Duplicate checks must use the same fragment names as the filtered sources.
359
+ definitions: filterToRequiredFragments
360
+ ? file.definitions.filter(definition => requiredFragmentNames.has(definition.name))
361
+ : file.definitions,
362
+ sources: filterToRequiredFragments ? filterParsedSources(sources, requiredFragmentNames) : [],
363
+ };
364
+ });
365
+ }
366
+ function filterParsedSources(sources, requiredFragmentNames) {
367
+ const filteredSources = [];
368
+ for (const source of sources) {
369
+ const definitions = source.document.definitions.filter(definition => definition.kind === Kind.FRAGMENT_DEFINITION &&
370
+ requiredFragmentNames.has(definition.name.value));
371
+ if (definitions.length === 0) {
372
+ continue;
373
+ }
374
+ const document = definitions.length === source.document.definitions.length
375
+ ? source.document
376
+ : { kind: Kind.DOCUMENT, definitions };
377
+ filteredSources.push({
378
+ rawSDL: definitions.length === source.document.definitions.length ? source.rawSDL : print(document),
379
+ document,
380
+ });
381
+ }
382
+ return filteredSources;
383
+ }
384
+ function detectDuplicateFragments(rootMap, resolvedFiles, rootPackageName) {
385
+ const registry = new Map();
386
+ for (const [fragName, info] of rootMap.fragments) {
387
+ registry.set(fragName, { packageName: rootPackageName, filePath: info.filePath });
388
+ }
389
+ const errors = [];
390
+ for (const resolved of resolvedFiles) {
391
+ for (const def of resolved.definitions) {
392
+ const existing = registry.get(def.name);
393
+ if (existing) {
394
+ errors.push(`Duplicate fragment "${def.name}" found in "${resolved.packageName}" (${resolved.filePath}) ` +
395
+ `and "${existing.packageName}" (${existing.filePath}).`);
396
+ }
397
+ registry.set(def.name, { packageName: resolved.packageName, filePath: resolved.filePath });
398
+ }
399
+ }
400
+ if (errors.length > 0) {
401
+ throw new Error(errors.join('\n'));
402
+ }
403
+ }
404
+ function getPackageNameFromDir(packageDir) {
405
+ try {
406
+ const pkg = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
407
+ return pkg.name || basename(packageDir) || 'unknown';
408
+ }
409
+ catch (err) {
410
+ // A missing package.json can use the directory name, but malformed JSON,
411
+ // permission errors, and unexpected failures should remain visible.
412
+ if (err.code === 'ENOENT') {
413
+ return basename(packageDir) || 'unknown';
414
+ }
415
+ throw err;
416
+ }
417
+ }
418
+ function normalizeOptions(options) {
419
+ const externalPackagesDirs = options.externalPackagesDirs;
420
+ const filter = options.packageNameFilter ?? (() => true);
421
+ const packageDependencyFilter = options.packageDependencyFilter;
422
+ const includeDevDependencies = options.includeDevDependencies ?? true;
423
+ const scanInternalDirs = options.scanInternalDirs ?? DEFAULT_SCAN_INTERNAL_DIRS;
424
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
425
+ const excludePatterns = options.excludePatterns ?? DEFAULT_EXCLUDE_PATTERNS;
426
+ const invalidateTargetPackageCache = options.invalidateTargetPackageCache ?? false;
427
+ return {
428
+ externalPackagesDirs,
429
+ filter,
430
+ packageDependencyFilter,
431
+ includeDevDependencies,
432
+ scanInternalDirs,
433
+ extensions,
434
+ excludePatterns,
435
+ invalidateTargetPackageCache,
436
+ };
437
+ }
438
+ function getTransitiveDeps(packageDir, { externalPackagesDirs, filter, packageDependencyFilter, includeDevDependencies, }) {
439
+ const packageJsonPath = join(packageDir, 'package.json');
440
+ const { names: rootDeps } = readPackageJsonDeps(packageJsonPath, includeDevDependencies);
441
+ const filteredRootDeps = rootDeps.filter(filter);
442
+ const transitiveDeps = new Set();
443
+ for (const dep of filteredRootDeps) {
444
+ collectTransitiveDeps({
445
+ packageName: dep,
446
+ externalPackagesDirs,
447
+ filter,
448
+ includeDevDependencies,
449
+ packageDependencyFilter,
450
+ visited: new Set(),
451
+ result: transitiveDeps,
452
+ });
453
+ }
454
+ return transitiveDeps;
455
+ }
456
+ function resolveFromMaps(options) {
457
+ const resolvedFiles = findExternalFragments(options);
458
+ detectDuplicateFragments(options.rootMap, resolvedFiles, options.rootPackageName);
459
+ return resolvedFiles;
460
+ }
461
+ function toPublicResolvedFiles(resolvedFiles) {
462
+ return resolvedFiles.map(({ filePath, definitions, packageName }) => ({
463
+ filePath,
464
+ definitions,
465
+ packageName,
466
+ }));
467
+ }
468
+ function getPackageMapArgs(packageDir, options, { scanInternalDirs, extensions, excludePatterns }) {
469
+ return [
470
+ packageDir,
471
+ scanInternalDirs,
472
+ extensions,
473
+ excludePatterns,
474
+ options.pluckConfig,
475
+ options.fileContentFilter,
476
+ ];
477
+ }
478
+ function invalidateTargetPackageCacheIfRequested(options, { includeDevDependencies, invalidateTargetPackageCache }, rootPackageMapArgs, deleteMap) {
479
+ if (!invalidateTargetPackageCache)
480
+ return;
481
+ deleteMap(...rootPackageMapArgs);
482
+ readPackageJsonDeps.delete(join(options.packageDir, 'package.json'), includeDevDependencies);
483
+ }
484
+ function createResolutionContext(packageDir, normalized, rootMap) {
485
+ const missingFragments = findMissingFragments(rootMap);
486
+ if (missingFragments.size === 0)
487
+ return;
488
+ const rootPackageName = getPackageNameFromDir(packageDir);
489
+ const transitiveDeps = getTransitiveDeps(packageDir, normalized);
490
+ if (transitiveDeps.size === 0) {
491
+ const errors = [...missingFragments].map(frag => `Fragment "${frag}" is spread in "${rootPackageName}" but no transitive dependencies were found to search.`);
492
+ throw new Error(errors.join('\n'));
493
+ }
494
+ return { rootMap, rootPackageName, missingFragments, transitiveDeps };
495
+ }
496
+ function getDependencyPackages(options, normalized, packageNames) {
497
+ const dependencies = [];
498
+ for (const name of packageNames) {
499
+ const packageDir = findPackageDir(name, normalized.externalPackagesDirs);
500
+ if (packageDir) {
501
+ dependencies.push({ name, args: getPackageMapArgs(packageDir, options, normalized) });
502
+ }
503
+ }
504
+ return dependencies;
505
+ }
506
+ // --- Public API ---
507
+ /**
508
+ * Resolves cross-package GraphQL fragment dependencies across external packages.
509
+ * Async version — reads files in parallel for better performance on large codebases.
510
+ */
511
+ export async function resolveExternalFragmentsWithSources(options, filterToRequiredFragments = true) {
512
+ const normalized = normalizeOptions(options);
513
+ initCache(options.cacheTTL);
514
+ const rootPackageMapArgs = getPackageMapArgs(options.packageDir, options, normalized);
515
+ invalidateTargetPackageCacheIfRequested(options, normalized, rootPackageMapArgs, buildPackageFragmentMapAsync.delete);
516
+ // Consumer packages are intentionally not inserted into the cache. If this
517
+ // package was already encountered as a provider, reuse that cached map.
518
+ const rootMap = await getPackageFragmentMapAsync(rootPackageMapArgs, false);
519
+ const context = createResolutionContext(options.packageDir, normalized, rootMap);
520
+ if (!context)
521
+ return [];
522
+ const depMaps = new Map();
523
+ await Promise.all(getDependencyPackages(options, normalized, context.transitiveDeps).map(async (dependency) => {
524
+ const map = await getPackageFragmentMapAsync(dependency.args, true);
525
+ depMaps.set(dependency.name, map);
526
+ }));
527
+ return resolveFromMaps({
528
+ rootMap: context.rootMap,
529
+ depMaps,
530
+ rootPackageName: context.rootPackageName,
531
+ missingFragments: context.missingFragments,
532
+ filterToRequiredFragments,
533
+ });
534
+ }
535
+ export async function resolveExternalFragments(options) {
536
+ const resolvedFiles = await resolveExternalFragmentsWithSources(options, false);
537
+ return toPublicResolvedFiles(resolvedFiles);
538
+ }
539
+ /**
540
+ * Resolves cross-package GraphQL fragment dependencies across external packages.
541
+ * Sync version.
542
+ */
543
+ export function resolveExternalFragmentsSyncWithSources(options, filterToRequiredFragments = true) {
544
+ const normalized = normalizeOptions(options);
545
+ initCache(options.cacheTTL);
546
+ const rootPackageMapArgs = getPackageMapArgs(options.packageDir, options, normalized);
547
+ invalidateTargetPackageCacheIfRequested(options, normalized, rootPackageMapArgs, buildPackageFragmentMap.delete);
548
+ // Consumer packages are intentionally not inserted into the cache. If this
549
+ // package was already encountered as a provider, reuse that cached map.
550
+ const rootMap = getPackageFragmentMap(rootPackageMapArgs, false);
551
+ const context = createResolutionContext(options.packageDir, normalized, rootMap);
552
+ if (!context)
553
+ return [];
554
+ const depMaps = new Map();
555
+ for (const dependency of getDependencyPackages(options, normalized, context.transitiveDeps)) {
556
+ depMaps.set(dependency.name, getPackageFragmentMap(dependency.args, true));
557
+ }
558
+ return resolveFromMaps({
559
+ rootMap: context.rootMap,
560
+ depMaps,
561
+ rootPackageName: context.rootPackageName,
562
+ missingFragments: context.missingFragments,
563
+ filterToRequiredFragments,
564
+ });
565
+ }
566
+ export function resolveExternalFragmentsSync(options) {
567
+ const resolvedFiles = resolveExternalFragmentsSyncWithSources(options, false);
568
+ return toPublicResolvedFiles(resolvedFiles);
569
+ }
package/package.json CHANGED
@@ -1,6 +1,65 @@
1
1
  {
2
2
  "name": "@graphql-tools/external-fragment-loader",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.0",
4
+ "description": "A GraphQL loader that resolves cross-package fragment dependencies from external packages",
5
+ "sideEffects": false,
6
+ "peerDependencies": {
7
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
8
+ },
9
+ "dependencies": {
10
+ "@graphql-tools/graphql-tag-pluck": "8.3.39",
11
+ "@graphql-tools/utils": "^12.0.3",
12
+ "globby": "^11.0.3",
13
+ "memoizee": "^0.4.17",
14
+ "tslib": "^2.4.0",
15
+ "unixify": "^1.0.0"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "ardatan/graphql-tools",
20
+ "directory": "packages/loaders/external-fragment"
21
+ },
22
+ "author": "Igor Kusakov <igor@kusakov.com>",
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=16.0.0"
26
+ },
27
+ "main": "cjs/index.js",
28
+ "module": "esm/index.js",
29
+ "typings": "typings/index.d.ts",
30
+ "typescript": {
31
+ "definition": "typings/index.d.ts"
32
+ },
33
+ "type": "module",
34
+ "exports": {
35
+ ".": {
36
+ "require": {
37
+ "types": "./typings/index.d.cts",
38
+ "default": "./cjs/index.js"
39
+ },
40
+ "import": {
41
+ "types": "./typings/index.d.ts",
42
+ "default": "./esm/index.js"
43
+ },
44
+ "default": {
45
+ "types": "./typings/index.d.ts",
46
+ "default": "./esm/index.js"
47
+ }
48
+ },
49
+ "./*": {
50
+ "require": {
51
+ "types": "./typings/*.d.cts",
52
+ "default": "./cjs/*.js"
53
+ },
54
+ "import": {
55
+ "types": "./typings/*.d.ts",
56
+ "default": "./esm/*.js"
57
+ },
58
+ "default": {
59
+ "types": "./typings/*.d.ts",
60
+ "default": "./esm/*.js"
61
+ }
62
+ },
63
+ "./package.json": "./package.json"
64
+ }
6
65
  }
@@ -0,0 +1,5 @@
1
+ export { default } from './loader.cjs';
2
+ export { ExternalFragmentLoader } from './loader.cjs';
3
+ export type { ExternalFragmentLoaderOptions } from './options.cjs';
4
+ export { resolveExternalFragments, resolveExternalFragmentsSync, clearCache } from './resolve.cjs';
5
+ export type { ResolvedExternalFile, PackageFragmentMap, ExternalFragmentResolverOptions, } from './resolve.cjs';
@@ -0,0 +1,5 @@
1
+ export { default } from './loader.js';
2
+ export { ExternalFragmentLoader } from './loader.js';
3
+ export type { ExternalFragmentLoaderOptions } from './options.js';
4
+ export { resolveExternalFragments, resolveExternalFragmentsSync, clearCache } from './resolve.js';
5
+ export type { ResolvedExternalFile, PackageFragmentMap, ExternalFragmentResolverOptions, } from './resolve.js';
@@ -0,0 +1,25 @@
1
+ import type { DocumentNode } from 'graphql';
2
+ import type { Loader, Source } from '@graphql-tools/utils';
3
+ import type { ExternalFragmentLoaderOptions } from './options.cjs';
4
+ /**
5
+ * Custom-loader entry point for `@graphql-tools/load`.
6
+ *
7
+ * Custom loaders must return a single DocumentNode (or Source). By default,
8
+ * use the synchronous resolver so this entry point works for both
9
+ * `loadTypedefs` and `loadTypedefsSync`. Set `options.async` to use the
10
+ * asynchronous resolver from an asynchronous load call.
11
+ */
12
+ type AsyncExternalFragmentLoaderOptions = ExternalFragmentLoaderOptions & {
13
+ async: true;
14
+ };
15
+ type SyncExternalFragmentLoaderOptions = ExternalFragmentLoaderOptions & {
16
+ async?: false;
17
+ };
18
+ export default function externalFragmentLoader(_pointer: string, options: AsyncExternalFragmentLoaderOptions): Promise<DocumentNode>;
19
+ export default function externalFragmentLoader(_pointer: string, options: SyncExternalFragmentLoaderOptions): DocumentNode;
20
+ export default function externalFragmentLoader(_pointer: string, options: ExternalFragmentLoaderOptions): DocumentNode | Promise<DocumentNode>;
21
+ export declare class ExternalFragmentLoader implements Loader<ExternalFragmentLoaderOptions> {
22
+ load(_pointer: string, options: ExternalFragmentLoaderOptions): Promise<Source[]>;
23
+ loadSync(_pointer: string, options: ExternalFragmentLoaderOptions): Source[];
24
+ }
25
+ export {};