Skip to content

Commit

Permalink
feat(): support interface level directives and implements array
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Mar 17, 2021
1 parent 1a03203 commit 5213077
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 3 deletions.
5 changes: 5 additions & 0 deletions lib/decorators/interface-type.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export interface InterfaceTypeOptions {
* Custom implementation of the "resolveType" function.
*/
resolveType?: ResolveTypeFn<any, any>;
/**
* Interfaces implemented by this interface.
*/
implements?: Function | Function[] | (() => Function | Function[]);
}

/**
Expand Down Expand Up @@ -58,6 +62,7 @@ export function InterfaceType(
name: name || target.name,
target,
...options,
interfaces: options.implements,
};
TypeMetadataStorage.addInterfaceMetadata(metadata);
};
Expand Down
2 changes: 1 addition & 1 deletion lib/decorators/object-type.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface ObjectTypeOptions {
*/
isAbstract?: boolean;
/**
* Interfaces implemented by this object.
* Interfaces implemented by this object type.
*/
implements?: Function | Function[] | (() => Function | Function[]);
}
Expand Down
20 changes: 19 additions & 1 deletion lib/schema-builder/factories/ast-definition-node.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
GraphQLOutputType,
InputObjectTypeDefinitionNode,
InputValueDefinitionNode,
InterfaceTypeDefinitionNode,
ObjectTypeDefinitionNode,
parse,
} from 'graphql';
Expand Down Expand Up @@ -56,6 +57,23 @@ export class AstDefinitionNodeFactory {
};
}

createInterfaceTypeNode(
name: string,
directiveMetadata?: DirectiveMetadata[],
): InterfaceTypeDefinitionNode | undefined {
if (isEmpty(directiveMetadata)) {
return;
}
return {
kind: 'InterfaceTypeDefinition',
name: {
kind: 'Name',
value: name,
},
directives: directiveMetadata.map(this.createDirectiveNode),
};
}

createFieldNode(
name: string,
type: GraphQLOutputType,
Expand Down Expand Up @@ -110,7 +128,7 @@ export class AstDefinitionNodeFactory {
const parsed = parse(`type String ${directive.sdl}`);
const definitions = parsed.definitions as ObjectTypeDefinitionNode[];
const directives = definitions
.filter(item => item.directives && item.directives.length > 0)
.filter((item) => item.directives && item.directives.length > 0)
.map(({ directives }) => directives)
.reduce((acc, item) => [...acc, ...item]);

Expand Down
55 changes: 54 additions & 1 deletion lib/schema-builder/factories/interface-definition.factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Injectable } from '@nestjs/common';
import { isUndefined } from '@nestjs/common/utils/shared.utils';
import { GraphQLFieldConfigMap, GraphQLInterfaceType } from 'graphql';
import {
GraphQLFieldConfigMap,
GraphQLInterfaceType,
GraphQLObjectType,
} from 'graphql';
import { BuildSchemaOptions } from '../../interfaces';
import { ReturnTypeCannotBeResolvedError } from '../errors/return-type-cannot-be-resolved.error';
import { InterfaceMetadata } from '../metadata/interface.metadata';
Expand All @@ -10,13 +14,15 @@ import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
import { TypeMetadataStorage } from '../storages/type-metadata.storage';
import { getInterfacesArray } from '../utils/get-interfaces-array.util';
import { ArgsFactory } from './args.factory';
import { AstDefinitionNodeFactory } from './ast-definition-node.factory';
import { OutputTypeFactory } from './output-type.factory';
import { ResolveTypeFactory } from './resolve-type.factory';

export interface InterfaceTypeDefinition {
target: Function;
type: GraphQLInterfaceType;
isAbstract: boolean;
interfaces: Function[];
}

@Injectable()
Expand All @@ -28,21 +34,43 @@ export class InterfaceDefinitionFactory {
private readonly orphanedReferenceRegistry: OrphanedReferenceRegistry,
private readonly typeFieldsAccessor: TypeFieldsAccessor,
private readonly argsFactory: ArgsFactory,
private readonly astDefinitionNodeFactory: AstDefinitionNodeFactory,
) {}

public create(
metadata: InterfaceMetadata,
options: BuildSchemaOptions,
): InterfaceTypeDefinition {
const prototype = Object.getPrototypeOf(metadata.target);
const getParentType = () => {
const parentTypeDefinition =
this.typeDefinitionsStorage.getObjectTypeByTarget(prototype) ||
this.typeDefinitionsStorage.getInterfaceByTarget(prototype);
return parentTypeDefinition ? parentTypeDefinition.type : undefined;
};
const resolveType = this.createResolveTypeFn(metadata);
console.log({
interfaces: getInterfacesArray(metadata.interfaces),
metadata,
});
return {
target: metadata.target,
isAbstract: metadata.isAbstract || false,
interfaces: getInterfacesArray(metadata.interfaces),
type: new GraphQLInterfaceType({
name: metadata.name,
description: metadata.description,
fields: this.generateFields(metadata, options),
interfaces: this.generateInterfaces(metadata, getParentType),
resolveType,
/**
* AST node has to be manually created in order to define directives
* (more on this topic here: https://github.com/graphql/graphql-js/issues/1343)
*/
astNode: this.astDefinitionNodeFactory.createInterfaceTypeNode(
metadata.name,
metadata.directives,
),
}),
};
}
Expand Down Expand Up @@ -125,4 +153,29 @@ export class InterfaceDefinitionFactory {
return fields;
};
}

private generateInterfaces(
metadata: InterfaceMetadata,
getParentType: () => GraphQLObjectType | GraphQLInterfaceType,
) {
const prototype = Object.getPrototypeOf(metadata.target);

return () => {
const interfaces: GraphQLInterfaceType[] = getInterfacesArray(
metadata.interfaces,
).map(
(item: Function) =>
this.typeDefinitionsStorage.getInterfaceByTarget(item).type,
);
if (!isUndefined(prototype)) {
const parentClass = getParentType();
if (!parentClass) {
return interfaces;
}
const parentInterfaces = parentClass.getInterfaces?.() ?? [];
return Array.from(new Set([...interfaces, ...parentInterfaces]));
}
return interfaces;
};
}
}
1 change: 1 addition & 0 deletions lib/schema-builder/metadata/interface.metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { ClassMetadata } from './class.metadata';

export interface InterfaceMetadata extends ClassMetadata {
resolveType?: ResolveTypeFn;
interfaces?: Function | Function[] | (() => Function | Function[]);
}

0 comments on commit 5213077

Please sign in to comment.