diff --git a/src/decorators/Extensions.ts b/src/decorators/Extensions.ts new file mode 100644 index 000000000..175e3b7db --- /dev/null +++ b/src/decorators/Extensions.ts @@ -0,0 +1,28 @@ +import { MethodAndPropDecorator } from "./types"; +import { SymbolKeysNotSupportedError } from "../errors"; +import { getMetadataStorage } from "../metadata/getMetadataStorage"; + +export function Extensions( + extensions: Record, +): MethodAndPropDecorator & ClassDecorator; +export function Extensions( + extensions: Record, +): MethodDecorator | PropertyDecorator | ClassDecorator { + return (targetOrPrototype, propertyKey, descriptor) => { + if (typeof propertyKey === "symbol") { + throw new SymbolKeysNotSupportedError(); + } + if (propertyKey) { + getMetadataStorage().collectExtensionsFieldMetadata({ + target: targetOrPrototype.constructor, + fieldName: propertyKey, + extensions, + }); + } else { + getMetadataStorage().collectExtensionsClassMetadata({ + target: targetOrPrototype as Function, + extensions, + }); + } + }; +} diff --git a/src/decorators/index.ts b/src/decorators/index.ts index 3d2c77065..720d8f11c 100644 --- a/src/decorators/index.ts +++ b/src/decorators/index.ts @@ -6,6 +6,7 @@ export { createParamDecorator } from "./createParamDecorator"; export { createMethodDecorator } from "./createMethodDecorator"; export { Ctx } from "./Ctx"; export { Directive } from "./Directive"; +export { Extensions } from "./Extensions"; export { registerEnumType } from "./enums"; export { Field, FieldOptions } from "./Field"; export { FieldResolver } from "./FieldResolver"; diff --git a/src/metadata/definitions/class-metadata.ts b/src/metadata/definitions/class-metadata.ts index e15fd7b81..3e988b2d7 100644 --- a/src/metadata/definitions/class-metadata.ts +++ b/src/metadata/definitions/class-metadata.ts @@ -8,5 +8,6 @@ export interface ClassMetadata { description?: string; isAbstract?: boolean; directives?: DirectiveMetadata[]; + extensions?: Record; simpleResolvers?: boolean; } diff --git a/src/metadata/definitions/extensions-metadata.ts b/src/metadata/definitions/extensions-metadata.ts new file mode 100644 index 000000000..add06299c --- /dev/null +++ b/src/metadata/definitions/extensions-metadata.ts @@ -0,0 +1,10 @@ +export interface ExtensionsClassMetadata { + target: Function; + extensions: Record; +} + +export interface ExtensionsFieldMetadata { + target: Function; + fieldName: string; + extensions: Record; +} diff --git a/src/metadata/definitions/field-metadata.ts b/src/metadata/definitions/field-metadata.ts index 0c0855cf7..4398860c5 100644 --- a/src/metadata/definitions/field-metadata.ts +++ b/src/metadata/definitions/field-metadata.ts @@ -17,5 +17,6 @@ export interface FieldMetadata { roles?: any[]; middlewares?: Array>; directives?: DirectiveMetadata[]; + extensions?: Record; simple?: boolean; } diff --git a/src/metadata/definitions/index.ts b/src/metadata/definitions/index.ts index 70a7d6ea9..ff67f5906 100644 --- a/src/metadata/definitions/index.ts +++ b/src/metadata/definitions/index.ts @@ -2,6 +2,7 @@ export * from "./authorized-metadata"; export * from "./class-metadata"; export * from "./directive-metadata"; export * from "./enum-metadata"; +export * from "./extensions-metadata"; export * from "./field-metadata"; export * from "./middleware-metadata"; export * from "./param-metadata"; diff --git a/src/metadata/definitions/resolver-metadata.ts b/src/metadata/definitions/resolver-metadata.ts index be349ba34..43c0845a9 100644 --- a/src/metadata/definitions/resolver-metadata.ts +++ b/src/metadata/definitions/resolver-metadata.ts @@ -22,6 +22,7 @@ export interface BaseResolverMetadata { roles?: any[]; middlewares?: Array>; directives?: DirectiveMetadata[]; + extensions?: Record; } export interface ResolverMetadata extends BaseResolverMetadata { diff --git a/src/metadata/metadata-storage.ts b/src/metadata/metadata-storage.ts index 74b3c2d0c..755a89f18 100644 --- a/src/metadata/metadata-storage.ts +++ b/src/metadata/metadata-storage.ts @@ -1,6 +1,8 @@ import { ResolverMetadata, ClassMetadata, + ExtensionsClassMetadata, + ExtensionsFieldMetadata, FieldMetadata, ParamMetadata, FieldResolverMetadata, @@ -20,6 +22,7 @@ import { mapMiddlewareMetadataToArray, mapSuperFieldResolverHandlers, ensureReflectMetadataExists, + flattenExtensions, } from "./utils"; import { ObjectClassMetadata } from "./definitions/object-class-metdata"; import { InterfaceClassMetadata } from "./definitions/interface-class-metadata"; @@ -40,6 +43,8 @@ export class MetadataStorage { middlewares: MiddlewareMetadata[] = []; classDirectives: DirectiveClassMetadata[] = []; fieldDirectives: DirectiveFieldMetadata[] = []; + classExtensions: ExtensionsClassMetadata[] = []; + fieldExtensions: ExtensionsFieldMetadata[] = []; private resolverClasses: ResolverClassMetadata[] = []; private fields: FieldMetadata[] = []; @@ -108,6 +113,13 @@ export class MetadataStorage { this.fieldDirectives.push(definition); } + collectExtensionsClassMetadata(definition: ExtensionsClassMetadata) { + this.classExtensions.push(definition); + } + collectExtensionsFieldMetadata(definition: ExtensionsFieldMetadata) { + this.fieldExtensions.push(definition); + } + build() { // TODO: disable next build attempts @@ -143,6 +155,8 @@ export class MetadataStorage { this.middlewares = []; this.classDirectives = []; this.fieldDirectives = []; + this.classExtensions = []; + this.fieldExtensions = []; this.resolverClasses = []; this.fields = []; @@ -167,6 +181,7 @@ export class MetadataStorage { field.directives = this.fieldDirectives .filter(it => it.target === field.target && it.fieldName === field.name) .map(it => it.directive); + field.extensions = this.findFieldExtensions(field.target, field.name); }); def.fields = fields; } @@ -175,6 +190,9 @@ export class MetadataStorage { .filter(it => it.target === def.target) .map(it => it.directive); } + if (!def.extensions) { + def.extensions = this.findClassExtensions(def.target); + } }); } @@ -196,6 +214,7 @@ export class MetadataStorage { def.directives = this.fieldDirectives .filter(it => it.target === def.target && it.fieldName === def.methodName) .map(it => it.directive); + def.extensions = this.findFieldExtensions(def.target, def.methodName); }); } @@ -206,6 +225,7 @@ export class MetadataStorage { def.directives = this.fieldDirectives .filter(it => it.target === def.target && it.fieldName === def.methodName) .map(it => it.directive); + def.extensions = this.findFieldExtensions(def.target, def.methodName); def.getObjectType = def.kind === "external" ? this.resolverClasses.find(resolver => resolver.target === def.target)!.getObjectType @@ -236,6 +256,7 @@ export class MetadataStorage { middlewares: def.middlewares!, params: def.params!, directives: def.directives, + extensions: def.extensions, }; this.collectClassFieldMetadata(fieldMetadata); objectType.fields!.push(fieldMetadata); @@ -286,4 +307,16 @@ export class MetadataStorage { } return authorizedField.roles; } + + private findClassExtensions(target: Function): Record { + return this.classExtensions + .filter(entry => entry.target === target) + .reduce(flattenExtensions, {}); + } + + private findFieldExtensions(target: Function, fieldName: string): Record { + return this.fieldExtensions + .filter(entry => entry.target === target && entry.fieldName === fieldName) + .reduce(flattenExtensions, {}); + } } diff --git a/src/metadata/utils.ts b/src/metadata/utils.ts index 5e100d492..14d79f10c 100644 --- a/src/metadata/utils.ts +++ b/src/metadata/utils.ts @@ -3,6 +3,8 @@ import { BaseResolverMetadata, MiddlewareMetadata, FieldResolverMetadata, + ExtensionsClassMetadata, + ExtensionsFieldMetadata, } from "./definitions"; import { Middleware } from "../interfaces/Middleware"; import { isThrowing } from "../helpers/isThrowing"; @@ -57,3 +59,10 @@ export function ensureReflectMetadataExists() { throw new ReflectMetadataMissingError(); } } + +export function flattenExtensions( + extensions: Record, + entry: ExtensionsClassMetadata | ExtensionsFieldMetadata, +): Record { + return { ...extensions, ...entry.extensions }; +} diff --git a/src/schema/schema-generator.ts b/src/schema/schema-generator.ts index f649d1b33..90e0d8f06 100644 --- a/src/schema/schema-generator.ts +++ b/src/schema/schema-generator.ts @@ -282,6 +282,7 @@ export abstract class SchemaGenerator { name: objectType.name, description: objectType.description, astNode: getObjectTypeDefinitionNode(objectType.name, objectType.directives), + extensions: objectType.extensions, interfaces: () => { let interfaces = interfaceClasses.map( interfaceClass => @@ -330,6 +331,7 @@ export abstract class SchemaGenerator { deprecationReason: field.deprecationReason, astNode: getFieldDefinitionNode(field.name, type, field.directives), extensions: { + ...field.extensions, complexity: field.complexity, }, }; @@ -379,6 +381,7 @@ export abstract class SchemaGenerator { type: new GraphQLInputObjectType({ name: inputType.name, description: inputType.description, + extensions: inputType.extensions, fields: () => { let fields = inputType.fields!.reduce( (fieldsMap, field) => { @@ -399,6 +402,7 @@ export abstract class SchemaGenerator { type, defaultValue: field.typeOptions.defaultValue, astNode: getInputValueDefinitionNode(field.name, type, field.directives), + extensions: field.extensions, }; return fieldsMap; }, @@ -497,6 +501,7 @@ export abstract class SchemaGenerator { deprecationReason: handler.deprecationReason, astNode: getFieldDefinitionNode(handler.schemaName, type, handler.directives), extensions: { + ...handler.extensions, complexity: handler.complexity, }, };