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

New loaders #387

Merged
merged 9 commits into from
Dec 29, 2019
Merged
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
38 changes: 19 additions & 19 deletions packages/common/src/fix-schema-ast.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { GraphQLSchema, buildASTSchema, parse, BuildSchemaOptions } from 'graphql';
import { GraphQLSchema, buildASTSchema, parse, BuildSchemaOptions, ParseOptions } from 'graphql';
import { printSchemaWithDirectives } from '.';

export function fixSchemaAst(schema: GraphQLSchema, options?: BuildSchemaOptions) {
if (!schema.astNode) {
Object.defineProperty(schema, 'astNode', {
get: () => {
return buildASTSchema(parse(printSchemaWithDirectives(schema)), {
commentDescriptions: true,
...(options || {}),
}).astNode;
},
});
Object.defineProperty(schema, 'extensionASTNodes', {
get: () => {
return buildASTSchema(parse(printSchemaWithDirectives(schema)), {
commentDescriptions: true,
...(options || {}),
}).extensionASTNodes;
},
});
export function fixSchemaAst(schema: GraphQLSchema, options: BuildSchemaOptions) {
if (!schema.astNode || !schema.extensionASTNodes) {
const schemaWithValidAst = buildASTSchema(
parse(printSchemaWithDirectives(schema), {
noLocation: true,
...(options || {}),
}),
{
commentDescriptions: true,
...(options || {}),
}
);
if (!schema.astNode) {
schema.astNode = schemaWithValidAst.astNode;
}
if (!schema.extensionASTNodes) {
schema.extensionASTNodes = schemaWithValidAst.extensionASTNodes;
}
}
return schema;
}
2 changes: 2 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export * from './get-fields-with-directives';
export * from './validate-documents';
export * from './resolvers-composition';
export * from './fix-schema-ast';
export * from './parse-graphql-json';
export * from './parse-graphql-sdl';
22 changes: 15 additions & 7 deletions packages/common/src/loaders.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { DocumentNode, GraphQLSchema } from 'graphql';
import { DocumentNode, GraphQLSchema, ParseOptions, BuildSchemaOptions } from 'graphql';
import { GraphQLSchemaValidationOptions } from 'graphql/type/schema';

export declare class Source {
document: DocumentNode;
document?: DocumentNode;
schema?: GraphQLSchema;
rawSDL?: string;
location?: string;
constructor({ document, location, schema }: { document: DocumentNode; location?: string; schema?: GraphQLSchema });
constructor({ document, location, schema }: { document?: DocumentNode; location?: string; schema?: GraphQLSchema });
}

export type SingleFileOptions = ParseOptions &
GraphQLSchemaValidationOptions &
BuildSchemaOptions & {
noRequire?: boolean;
cwd?: string;
};

export type WithList<T> = T | T[];
export type ElementOf<TList> = TList extends Array<infer TElement> ? TElement : never;
export type SchemaPointer = WithList<string>;
Expand All @@ -16,12 +24,12 @@ export type DocumentGlobPathPointer = string;
export type DocumentPointer = WithList<DocumentGlobPathPointer>;
export type DocumentPointerSingle = ElementOf<DocumentPointer>;

export interface Loader<TPointer = string, TOptions = any> {
export interface Loader<TPointer = string, TOptions extends SingleFileOptions = SingleFileOptions> {
loaderId(): string;
canLoad(pointer: TPointer, options?: TOptions): Promise<boolean>;
load(pointer: TPointer, options?: TOptions): Promise<Source | null>;
}

export type SchemaLoader<TOptions = any> = Loader<SchemaPointerSingle, TOptions>;
export type DocumentLoader<TOptions = any> = Loader<DocumentPointerSingle, TOptions>;
export type UniversalLoader<TOptions = any> = Loader<SchemaPointerSingle | DocumentPointerSingle, TOptions>;
export type SchemaLoader<TOptions extends SingleFileOptions = SingleFileOptions> = Loader<SchemaPointerSingle, TOptions>;
export type DocumentLoader<TOptions extends SingleFileOptions = SingleFileOptions> = Loader<DocumentPointerSingle, TOptions>;
export type UniversalLoader<TOptions extends SingleFileOptions = SingleFileOptions> = Loader<SchemaPointerSingle | DocumentPointerSingle, TOptions>;
43 changes: 43 additions & 0 deletions packages/common/src/parse-graphql-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Source as GraphQLSource, buildClientSchema, parse, ParseOptions } from 'graphql';
import { printSchemaWithDirectives, Source } from '.';
import { GraphQLSchemaValidationOptions } from 'graphql/type/schema';

function stripBOM(content: string): string {
content = content.toString();
// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
// because the buffer-to-string conversion in `fs.readFileSync()`
// translates it to FEFF, the UTF-16 BOM.
if (content.charCodeAt(0) === 0xfeff) {
content = content.slice(1);
}

return content;
}

function parseBOM(content: string): any {
return JSON.parse(stripBOM(content));
}

export function parseGraphQLJSON(location: string, jsonContent: string, options: ParseOptions & GraphQLSchemaValidationOptions): Source {
let parsedJson = parseBOM(jsonContent);

if (parsedJson['data']) {
parsedJson = parsedJson['data'];
}

if (parsedJson.kind === 'Document') {
const document = parsedJson;
return {
location,
document,
};
} else if (parsedJson.__schema) {
const schema = buildClientSchema(parsedJson, options);
return {
location,
document: parse(printSchemaWithDirectives(schema), options),
schema,
};
}
throw new Error(`Not valid JSON content`);
}
22 changes: 22 additions & 0 deletions packages/common/src/parse-graphql-sdl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ParseOptions, DocumentNode, parse, Kind, Source as GraphQLSource } from 'graphql';

export function parseGraphQLSDL(location: string, rawSDL: string, options: ParseOptions) {
let document: DocumentNode;
try {
document = parse(new GraphQLSource(rawSDL, location), options);
} catch (e) {
if (e.message.includes('EOF')) {
document = {
kind: Kind.DOCUMENT,
definitions: [],
};
} else {
throw e;
}
}
return {
location,
document,
rawSDL,
};
}
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"@graphql-toolkit/schema-merging": "0.7.5",
"aggregate-error": "3.0.1",
"globby": "10.0.1",
"graphql-import": "0.7.1",
"is-glob": "4.0.1",
"tslib": "1.10.0",
"valid-url": "1.0.9",
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/documents.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Loader, Source } from '@graphql-toolkit/common';
import { Source } from '@graphql-toolkit/common';
import { Kind } from 'graphql';
import { LoadTypedefsOptions, loadTypedefsUsingLoaders, UnnormalizedTypeDefPointer } from './load-typedefs';
import { LoadTypedefsOptions, loadTypedefs, UnnormalizedTypeDefPointer } from './load-typedefs';

export const OPERATION_KINDS = [Kind.OPERATION_DEFINITION, Kind.FRAGMENT_DEFINITION];
export const NON_OPERATION_KINDS = Object.keys(Kind)
.reduce((prev, v) => [...prev, Kind[v]], [])
.filter(v => !OPERATION_KINDS.includes(v));

export async function loadDocumentsUsingLoaders(loaders: Loader[], documentDef: UnnormalizedTypeDefPointer | UnnormalizedTypeDefPointer[], options: LoadTypedefsOptions = {}, cwd = process.cwd()): Promise<Source[]> {
return await loadTypedefsUsingLoaders(loaders, documentDef, { ...options, skipGraphQLImport: true, noRequire: true }, NON_OPERATION_KINDS, cwd);
export function loadDocuments(documentDef: UnnormalizedTypeDefPointer | UnnormalizedTypeDefPointer[], options: LoadTypedefsOptions): Promise<Source[]> {
return loadTypedefs(documentDef, { noRequire: true, filterKinds: NON_OPERATION_KINDS, ...options });
}
33 changes: 23 additions & 10 deletions packages/core/src/import-parser/definition.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { keyBy, uniqBy, includes } from 'lodash';
import { TypeDefinitionNode, TypeNode, NamedTypeNode, DirectiveNode, DirectiveDefinitionNode, InputValueDefinitionNode, FieldDefinitionNode } from 'graphql';
import { keyBy, uniqBy, includes, reverse } from 'lodash';
import { TypeDefinitionNode, TypeNode, NamedTypeNode, DirectiveNode, DirectiveDefinitionNode, InputValueDefinitionNode, FieldDefinitionNode, SchemaDefinitionNode } from 'graphql';

const builtinTypes = ['String', 'Float', 'Int', 'Boolean', 'ID'];

const builtinDirectives = ['deprecated', 'skip', 'include', 'key', 'external', 'requires', 'provides'];

export type ValidDefinitionNode = DirectiveDefinitionNode | TypeDefinitionNode;
export type ValidDefinitionNode = DirectiveDefinitionNode | TypeDefinitionNode | SchemaDefinitionNode;

export interface DefinitionMap {
[key: string]: ValidDefinitionNode;
Expand All @@ -23,17 +23,17 @@ export interface DefinitionMap {
export function completeDefinitionPool(allDefinitions: ValidDefinitionNode[], definitionPool: ValidDefinitionNode[], newTypeDefinitions: ValidDefinitionNode[]): ValidDefinitionNode[] {
const visitedDefinitions: { [name: string]: boolean } = {};
while (newTypeDefinitions.length > 0) {
const schemaMap: DefinitionMap = keyBy(allDefinitions, d => d.name.value);
const schemaMap: DefinitionMap = keyBy(reverse(allDefinitions), d => ('name' in d ? d.name.value : 'schema'));
const newDefinition = newTypeDefinitions.shift();
if (visitedDefinitions[newDefinition.name.value]) {
if (visitedDefinitions['name' in newDefinition ? newDefinition.name.value : 'schema']) {
continue;
}

const collectedTypedDefinitions = collectNewTypeDefinitions(allDefinitions, definitionPool, newDefinition, schemaMap);
newTypeDefinitions.push(...collectedTypedDefinitions);
definitionPool.push(...collectedTypedDefinitions);

visitedDefinitions[newDefinition.name.value] = true;
visitedDefinitions['name' in newDefinition ? newDefinition.name.value : 'schema'] = true;
}

return uniqBy(definitionPool, 'name.value');
Expand Down Expand Up @@ -73,7 +73,7 @@ function collectNewTypeDefinitions(allDefinitions: ValidDefinitionNode[], defini

if (newDefinition.kind === 'UnionTypeDefinition') {
newDefinition.types.forEach(type => {
if (!definitionPool.some(d => d.name.value === type.name.value)) {
if (!definitionPool.some(d => 'name' in d && d.name.value === type.name.value)) {
const typeName = type.name.value;
const typeMatch = schemaMap[typeName];
if (!typeMatch) {
Expand All @@ -87,7 +87,7 @@ function collectNewTypeDefinitions(allDefinitions: ValidDefinitionNode[], defini
if (newDefinition.kind === 'ObjectTypeDefinition') {
// collect missing interfaces
newDefinition.interfaces.forEach(int => {
if (!definitionPool.some(d => d.name.value === int.name.value)) {
if (!definitionPool.some(d => 'name' in d && d.name.value === int.name.value)) {
const interfaceName = int.name.value;
const interfaceMatch = schemaMap[interfaceName];
if (!interfaceMatch) {
Expand All @@ -105,14 +105,27 @@ function collectNewTypeDefinitions(allDefinitions: ValidDefinitionNode[], defini
});
}

if (newDefinition.kind === 'SchemaDefinition') {
newDefinition.operationTypes.forEach(operationType => {
if (!definitionPool.some(d => 'name' in d && d.name.value === operationType.type.name.value)) {
const typeName = operationType.type.name.value;
const typeMatch = schemaMap[typeName];
if (!typeMatch) {
throw new Error(`Couldn't find type ${typeName} in any of the schemas.`);
}
newTypeDefinitions.push(schemaMap[operationType.type.name.value]);
}
});
}

return newTypeDefinitions;

function collectNode(node: FieldDefinitionNode | InputValueDefinitionNode) {
const nodeType = getNamedType(node.type);
const nodeTypeName = nodeType.name.value;

// collect missing argument input types
if (!definitionPool.some(d => d.name.value === nodeTypeName) && !includes(builtinTypes, nodeTypeName)) {
if (!definitionPool.some(d => 'name' in d && d.name.value === nodeTypeName) && !includes(builtinTypes, nodeTypeName)) {
const argTypeMatch = schemaMap[nodeTypeName];
if (!argTypeMatch) {
throw new Error(`Field ${node.name.value}: Couldn't find type ${nodeTypeName} in any of the schemas.`);
Expand All @@ -125,7 +138,7 @@ function collectNewTypeDefinitions(allDefinitions: ValidDefinitionNode[], defini

function collectDirective(directive: DirectiveNode) {
const directiveName = directive.name.value;
if (!definitionPool.some(d => d.name.value === directiveName) && !includes(builtinDirectives, directiveName)) {
if (!definitionPool.some(d => 'name' in d && d.name.value === directiveName) && !includes(builtinDirectives, directiveName)) {
const directive = schemaMap[directiveName] as DirectiveDefinitionNode;
if (!directive) {
throw new Error(`Directive ${directiveName}: Couldn't find type ${directiveName} in any of the schemas.`);
Expand Down
Loading