diff --git a/.changeset/sour-ways-ring.md b/.changeset/sour-ways-ring.md new file mode 100644 index 00000000000..806903d1ae1 --- /dev/null +++ b/.changeset/sour-ways-ring.md @@ -0,0 +1,10 @@ +--- +'@graphql-tools/load': minor +'@graphql-tools/code-file-loader': minor +'@graphql-tools/graphql-file-loader': minor +'@graphql-tools/json-file-loader': minor +'@graphql-tools/module-loader': minor +'@graphql-tools/utils': minor +--- + +Introduce caching in loaders diff --git a/packages/load/src/load-typedefs/load-file.ts b/packages/load/src/load-typedefs/load-file.ts index 9bca990a6d5..cb8f14cacae 100644 --- a/packages/load/src/load-typedefs/load-file.ts +++ b/packages/load/src/load-typedefs/load-file.ts @@ -1,6 +1,28 @@ -import { Source, debugLog } from '@graphql-tools/utils'; +import { Source, debugLog, Cacheable } from '@graphql-tools/utils'; import { LoadTypedefsOptions } from '../load-typedefs'; +function makeCacheable( + fn: (pointer: TPointer, options?: TOptions) => Promise, + pointer: TPointer, + options: TOptions +): Promise { + if (options?.cacheable) { + return options.cacheable(fn, pointer, options); + } + return fn(pointer, options); +} + +function makeCacheableSync( + fn: (pointer: TPointer, options?: TOptions) => Source | never, + pointer: TPointer, + options: TOptions +): Source | never { + if (options?.cacheableSync) { + return options.cacheableSync(fn, pointer, options); + } + return fn(pointer, options); +} + export async function loadFile(pointer: string, options: LoadTypedefsOptions): Promise { const cached = useCache({ pointer, options }); @@ -13,7 +35,7 @@ export async function loadFile(pointer: string, options: LoadTypedefsOptions): P const canLoad = await loader.canLoad(pointer, options); if (canLoad) { - const loadedValue = await loader.load(pointer, options); + const loadedValue = await makeCacheable(loader.load.bind(loader), pointer, options); return loadedValue; } } catch (error) { @@ -37,7 +59,7 @@ export function loadFileSync(pointer: string, options: LoadTypedefsOptions): Sou const canLoad = loader.canLoadSync && loader.loadSync && loader.canLoadSync(pointer, options); if (canLoad) { - return loader.loadSync(pointer, options); + return makeCacheableSync(loader.loadSync.bind(loader), pointer, options); } } catch (error) { debugLog(`Failed to find any GraphQL type definitions in: ${pointer} - ${error.message}`); diff --git a/packages/loaders/code-file/src/index.ts b/packages/loaders/code-file/src/index.ts index d354225ceae..ebfc9848386 100644 --- a/packages/loaders/code-file/src/index.ts +++ b/packages/loaders/code-file/src/index.ts @@ -50,6 +50,8 @@ const FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.vue']; * Supported extensions include: `.ts`, `.tsx`, `.js`, `.jsx`, `.vue` */ export class CodeFileLoader implements UniversalLoader { + cacheable = true; + loaderId(): string { return 'code-file'; } @@ -100,7 +102,7 @@ export class CodeFileLoader implements UniversalLoader { const sdl = await gqlPluckFromCodeString(normalizedFilePath, content, options.pluckConfig); if (sdl) { - return parseSDL({ pointer, sdl, options }); + return parseSDL({ pointer: normalizedFilePath, sdl, options }); } } catch (e) { debugLog(`Failed to load schema from code file "${normalizedFilePath}": ${e.message}`); @@ -115,7 +117,7 @@ export class CodeFileLoader implements UniversalLoader { } const loaded = await tryToLoadFromExport(normalizedFilePath); - const source = resolveSource(pointer, loaded, options); + const source = resolveSource(normalizedFilePath, loaded, options); if (source) { return source; @@ -143,7 +145,7 @@ export class CodeFileLoader implements UniversalLoader { const sdl = gqlPluckFromCodeStringSync(normalizedFilePath, content, options.pluckConfig); if (sdl) { - return parseSDL({ pointer, sdl, options }); + return parseSDL({ pointer: normalizedFilePath, sdl, options }); } } catch (e) { debugLog(`Failed to load schema from code file "${normalizedFilePath}": ${e.message}`); @@ -158,7 +160,7 @@ export class CodeFileLoader implements UniversalLoader { } const loaded = tryToLoadFromExportSync(normalizedFilePath); - const source = resolveSource(pointer, loaded, options); + const source = resolveSource(normalizedFilePath, loaded, options); if (source) { return source; diff --git a/packages/loaders/graphql-file/src/index.ts b/packages/loaders/graphql-file/src/index.ts index 4004650a7e6..48cc7fb249b 100644 --- a/packages/loaders/graphql-file/src/index.ts +++ b/packages/loaders/graphql-file/src/index.ts @@ -55,6 +55,8 @@ function isGraphQLImportFile(rawSDL: string) { * ``` */ export class GraphQLFileLoader implements UniversalLoader { + cacheable = true; + loaderId(): string { return 'graphql-file'; } @@ -94,16 +96,19 @@ export class GraphQLFileLoader implements UniversalLoader { + loadSync(pointer: SchemaPointerSingle | DocumentPointerSingle, options: GraphQLFileLoaderOptions): Source { const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || processCwd(), pointer); - const rawSDL: string = await readFile(normalizedFilePath, { encoding: 'utf8' }); - + const rawSDL = readFileSync(normalizedFilePath, { encoding: 'utf8' }); return this.handleFileContent(rawSDL, pointer, options); } - loadSync(pointer: SchemaPointerSingle | DocumentPointerSingle, options: GraphQLFileLoaderOptions): Source { + async load( + pointer: SchemaPointerSingle | DocumentPointerSingle, + options: GraphQLFileLoaderOptions + ): Promise { const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || processCwd(), pointer); - const rawSDL = readFileSync(normalizedFilePath, { encoding: 'utf8' }); + const rawSDL: string = await readFile(normalizedFilePath, { encoding: 'utf8' }); + return this.handleFileContent(rawSDL, pointer, options); } diff --git a/packages/loaders/json-file/src/index.ts b/packages/loaders/json-file/src/index.ts index b3d030f0c83..6539163c626 100644 --- a/packages/loaders/json-file/src/index.ts +++ b/packages/loaders/json-file/src/index.ts @@ -43,6 +43,8 @@ export interface JsonFileLoaderOptions extends SingleFileOptions {} * ``` */ export class JsonFileLoader implements DocumentLoader { + cacheable = true; + loaderId(): string { return 'json-file'; } diff --git a/packages/loaders/module/src/index.ts b/packages/loaders/module/src/index.ts index 568247487f2..0d6dcee98b3 100644 --- a/packages/loaders/module/src/index.ts +++ b/packages/loaders/module/src/index.ts @@ -40,6 +40,8 @@ function extractData( * ``` */ export class ModuleLoader implements UniversalLoader { + cacheable = true; + loaderId() { return 'module-loader'; } diff --git a/packages/utils/src/loaders.ts b/packages/utils/src/loaders.ts index 42ca6abe8a1..15d0a232773 100644 --- a/packages/utils/src/loaders.ts +++ b/packages/utils/src/loaders.ts @@ -9,9 +9,23 @@ export interface Source { location?: string; } +export interface Cacheable { + cacheable?( + fn: (pointer: TPointer, options?: TOptions) => PromiseLike, + pointer: TPointer, + options?: TOptions + ): Promise; + cacheableSync?( + fn: (pointer: TPointer, options?: TOptions) => Source | never, + pointer: TPointer, + options?: TOptions + ): Source | never; +} + export type SingleFileOptions = GraphQLParseOptions & GraphQLSchemaValidationOptions & - BuildSchemaOptions & { + BuildSchemaOptions & + Cacheable & { cwd?: string; }; @@ -24,6 +38,7 @@ export type DocumentPointer = WithList; export type DocumentPointerSingle = ElementOf; export interface Loader { + cacheable?: boolean; loaderId(): string; canLoad(pointer: TPointer, options?: TOptions): Promise; canLoadSync?(pointer: TPointer, options?: TOptions): boolean;