Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Introduce caching in loaders #2816

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/sour-ways-ring.md
Original file line number Diff line number Diff line change
@@ -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
28 changes: 25 additions & 3 deletions packages/load/src/load-typedefs/load-file.ts
Original file line number Diff line number Diff line change
@@ -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<TPointer, TOptions extends Cacheable>(
fn: (pointer: TPointer, options?: TOptions) => Promise<Source | never>,
pointer: TPointer,
options: TOptions
): Promise<Source | never> {
if (options?.cacheable) {
return options.cacheable(fn, pointer, options);
}
return fn(pointer, options);
}

function makeCacheableSync<TPointer, TOptions extends Cacheable>(
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<Source> {
const cached = useCache({ pointer, options });

Expand All @@ -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) {
Expand All @@ -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}`);
Expand Down
10 changes: 6 additions & 4 deletions packages/loaders/code-file/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CodeFileLoaderOptions> {
cacheable = true;

loaderId(): string {
return 'code-file';
}
Expand Down Expand Up @@ -100,7 +102,7 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
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}`);
Expand All @@ -115,7 +117,7 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
}

const loaded = await tryToLoadFromExport(normalizedFilePath);
const source = resolveSource(pointer, loaded, options);
const source = resolveSource(normalizedFilePath, loaded, options);

if (source) {
return source;
Expand Down Expand Up @@ -143,7 +145,7 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
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}`);
Expand All @@ -158,7 +160,7 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
}

const loaded = tryToLoadFromExportSync(normalizedFilePath);
const source = resolveSource(pointer, loaded, options);
const source = resolveSource(normalizedFilePath, loaded, options);

if (source) {
return source;
Expand Down
15 changes: 10 additions & 5 deletions packages/loaders/graphql-file/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ function isGraphQLImportFile(rawSDL: string) {
* ```
*/
export class GraphQLFileLoader implements UniversalLoader<GraphQLFileLoaderOptions> {
cacheable = true;

loaderId(): string {
return 'graphql-file';
}
Expand Down Expand Up @@ -94,16 +96,19 @@ export class GraphQLFileLoader implements UniversalLoader<GraphQLFileLoaderOptio
return false;
}

async load(pointer: SchemaPointerSingle | DocumentPointerSingle, options: GraphQLFileLoaderOptions): Promise<Source> {
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<Source> {
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);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/loaders/json-file/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export interface JsonFileLoaderOptions extends SingleFileOptions {}
* ```
*/
export class JsonFileLoader implements DocumentLoader {
cacheable = true;

loaderId(): string {
return 'json-file';
}
Expand Down
2 changes: 2 additions & 0 deletions packages/loaders/module/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ function extractData(
* ```
*/
export class ModuleLoader implements UniversalLoader {
cacheable = true;

loaderId() {
return 'module-loader';
}
Expand Down
17 changes: 16 additions & 1 deletion packages/utils/src/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,23 @@ export interface Source {
location?: string;
}

export interface Cacheable {
cacheable?<TPointer, TOptions>(
fn: (pointer: TPointer, options?: TOptions) => PromiseLike<Source | never>,
pointer: TPointer,
options?: TOptions
): Promise<Source | never>;
cacheableSync?<TPointer, TOptions>(
fn: (pointer: TPointer, options?: TOptions) => Source | never,
pointer: TPointer,
options?: TOptions
): Source | never;
}

export type SingleFileOptions = GraphQLParseOptions &
GraphQLSchemaValidationOptions &
BuildSchemaOptions & {
BuildSchemaOptions &
Cacheable & {
cwd?: string;
};

Expand All @@ -24,6 +38,7 @@ export type DocumentPointer = WithList<DocumentGlobPathPointer>;
export type DocumentPointerSingle = ElementOf<DocumentPointer>;

export interface Loader<TPointer = string, TOptions extends SingleFileOptions = SingleFileOptions> {
cacheable?: boolean;
loaderId(): string;
canLoad(pointer: TPointer, options?: TOptions): Promise<boolean>;
canLoadSync?(pointer: TPointer, options?: TOptions): boolean;
Expand Down