Skip to content

Commit

Permalink
feat: support for adding interfaces to other interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
nodkz committed May 22, 2020
1 parent 1abb2ed commit 5b41ef8
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 12 deletions.
106 changes: 103 additions & 3 deletions src/InterfaceTypeComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,19 @@ import type {
import { toInputObjectType } from './utils/toInputObjectType';
import { typeByPath, type TypeInPath } from './utils/typeByPath';
import {
getComposeTypeName,
getGraphQLType,
unwrapOutputTC,
unwrapInputTC,
isTypeNameString,
cloneTypeTo,
type NamedTypeComposer,
} from './utils/typeHelpers';
import { defineFieldMap, convertObjectFieldMapToConfig } from './utils/configToDefine';
import {
defineFieldMap,
convertObjectFieldMapToConfig,
convertInterfaceArrayAsThunk,
} from './utils/configToDefine';
import { graphqlVersion } from './utils/graphqlVersion';
import type { ComposeNamedInputType, ComposeNamedOutputType } from './utils/typeHelpers';
import { printInterface, type SchemaPrinterOptions } from './utils/schemaPrinter';
Expand Down Expand Up @@ -92,6 +97,7 @@ export class InterfaceTypeComposer<TSource, TContext> {
_gqType: GraphQLInterfaceType;
_gqcFields: ObjectTypeComposerFieldConfigMap<TSource, TContext>;
_gqcInputTypeComposer: void | InputTypeComposer<TContext>;
_gqcInterfaces: Array<InterfaceTypeComposerThunked<TSource, TContext>> = [];
_gqcTypeResolvers: void | InterfaceTypeComposerResolversMap<TContext>;
_gqcExtensions: void | Extensions;

Expand Down Expand Up @@ -166,6 +172,14 @@ export class InterfaceTypeComposer<TSource, TContext> {
IFTC.addFields(fields);
}

const interfaces = (typeDef: any).interfaces;
if (Array.isArray(interfaces)) IFTC.setInterfaces(interfaces);
else if (isFunction(interfaces)) {
// rewrap interfaces `() => [i1, i2]` -> `[()=>i1, ()=>i2]`
// helps to solve hoisting problems
IFTC.setInterfaces(convertInterfaceArrayAsThunk(interfaces, sc));
}

IFTC._gqcExtensions = (typeDef: any).extensions || {};
} else {
throw new Error(
Expand Down Expand Up @@ -198,7 +212,13 @@ export class InterfaceTypeComposer<TSource, TContext> {
// it avoids recursive type use errors
this.schemaComposer.set(graphqlType, this);

if (graphqlVersion >= 14) {
if (graphqlVersion >= 15) {
this._gqcFields = convertObjectFieldMapToConfig(this._gqType._fields, this.schemaComposer);
this._gqcInterfaces = convertInterfaceArrayAsThunk(
this._gqType._interfaces,
this.schemaComposer
);
} else if (graphqlVersion >= 14) {
this._gqcFields = convertObjectFieldMapToConfig(this._gqType._fields, this.schemaComposer);
} else {
// read
Expand Down Expand Up @@ -719,7 +739,15 @@ export class InterfaceTypeComposer<TSource, TContext> {

getType(): GraphQLInterfaceType {
this._gqType.astNode = getInterfaceTypeDefinitionNode(this);
if (graphqlVersion >= 14) {
if (graphqlVersion >= 15) {
this._gqType._fields = () =>
defineFieldMap(
this._gqType,
mapEachKey(this._gqcFields, (fc, name) => this.getFieldConfig(name)),
this._gqType.astNode
);
this._gqType._interfaces = () => this.getInterfacesTypes();
} else if (graphqlVersion >= 14) {
this._gqType._fields = () =>
defineFieldMap(
this._gqType,
Expand Down Expand Up @@ -817,6 +845,7 @@ export class InterfaceTypeComposer<TSource, TContext> {
})),
extensions: { ...fieldConfig.extensions },
}));
cloned._gqcInterfaces = [...this._gqcInterfaces];
cloned._gqcTypeResolvers = new Map(this._gqcTypeResolvers);
cloned._gqcExtensions = { ...this._gqcExtensions };
cloned.setDescription(this.getDescription());
Expand Down Expand Up @@ -850,6 +879,9 @@ export class InterfaceTypeComposer<TSource, TContext> {
})),
extensions: { ...fieldConfig.extensions },
}));
cloned._gqcInterfaces = (this._gqcInterfaces.map((i) =>
i.cloneTo(anotherSchemaComposer, cloneMap)
): any);
cloned._gqcExtensions = { ...this._gqcExtensions };
cloned.setDescription(this.getDescription());

Expand Down Expand Up @@ -887,6 +919,7 @@ export class InterfaceTypeComposer<TSource, TContext> {

if (tc) {
this.addFields(tc.getFields());
this.addInterfaces(tc.getInterfaces());
} else {
throw new Error(
`Cannot merge ${inspect(
Expand Down Expand Up @@ -1105,6 +1138,65 @@ export class InterfaceTypeComposer<TSource, TContext> {
return this;
}

// -----------------------------------------------
// Sub-Interface methods
// -----------------------------------------------

getInterfaces(): Array<InterfaceTypeComposerThunked<TSource, TContext>> {
return this._gqcInterfaces;
}

getInterfacesTypes(): Array<GraphQLInterfaceType> {
return this._gqcInterfaces.map((i) => i.getType());
}

setInterfaces(
interfaces: $ReadOnlyArray<InterfaceTypeComposerDefinition<any, TContext>>
): InterfaceTypeComposer<TSource, TContext> {
this._gqcInterfaces = convertInterfaceArrayAsThunk(interfaces, this.schemaComposer);
return this;
}

hasInterface(iface: InterfaceTypeComposerDefinition<any, TContext>): boolean {
const typeName = getComposeTypeName(iface);
return !!this._gqcInterfaces.find((i) => i.getTypeName() === typeName);
}

addInterface(
iface:
| InterfaceTypeComposerDefinition<any, TContext>
| InterfaceTypeComposerThunked<any, TContext>
): InterfaceTypeComposer<TSource, TContext> {
if (!this.hasInterface(iface)) {
this._gqcInterfaces.push(
this.schemaComposer.typeMapper.convertInterfaceTypeDefinition(iface)
);
}
return this;
}

addInterfaces(
ifaces: $ReadOnlyArray<
InterfaceTypeComposerDefinition<any, TContext> | InterfaceTypeComposerThunked<any, TContext>
>
): InterfaceTypeComposer<TSource, TContext> {
if (!Array.isArray(ifaces)) {
throw new Error(
`InterfaceTypeComposer[${this.getTypeName()}].addInterfaces() accepts only array`
);
}
ifaces.forEach((iface) => this.addInterface(iface));
return this;
}

removeInterface(
iface: InterfaceTypeComposerDefinition<any, TContext>
): InterfaceTypeComposer<TSource, TContext> {
const typeName = getComposeTypeName(iface);
this._gqcInterfaces = this._gqcInterfaces.filter((i) => i.getTypeName() !== typeName);
return this;
}

// -----------------------------------------------
// Extensions methods
// -----------------------------------------------
Expand Down Expand Up @@ -1431,6 +1523,14 @@ export class InterfaceTypeComposer<TSource, TContext> {
}
}
});

this.getInterfaces().forEach((t) => {
const iftc = t instanceof ThunkComposer ? t.ofType : t;
if (!passedTypes.has(iftc) && !exclude.includes(iftc.getTypeName())) {
passedTypes.add(iftc);
iftc.getNestedTCs(opts, passedTypes);
}
});
});

return passedTypes;
Expand Down
7 changes: 6 additions & 1 deletion src/TypeMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,11 @@ export class TypeMapper<TContext> {
}

makeImplementedInterfaces(
def: ObjectTypeDefinitionNode | ObjectTypeExtensionNode
def:
| ObjectTypeDefinitionNode
| ObjectTypeExtensionNode
| InterfaceTypeDefinitionNode
| InterfaceTypeExtensionNode
): Array<InterfaceTypeComposerThunked<any, TContext>> {
return (def.interfaces || []).map((iface) => {
const name = this.getNamedTypeAST(iface).name.value;
Expand Down Expand Up @@ -1021,6 +1025,7 @@ export class TypeMapper<TContext> {
name: def.name.value,
description: getDescription(def),
fields: this.makeFieldDefMap(def),
interfaces: this.makeImplementedInterfaces(def),
astNode: def,
});
if (def.directives) {
Expand Down

0 comments on commit 5b41ef8

Please sign in to comment.