Skip to content

Commit

Permalink
feat(): load metadata lazily by target
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Mar 2, 2020
1 parent 67ab808 commit 4cb9f39
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 45 deletions.
2 changes: 1 addition & 1 deletion lib/decorators/args.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function Args(
return (target: Object, key: string, index: number) => {
addPipesMetadata(GqlParamtype.ARGS, property, pipes, target, key, index);

LazyMetadataStorage.store(() => {
LazyMetadataStorage.store(target.constructor as Type<unknown>, () => {
const { typeFn: reflectedTypeFn, options } = reflectTypeFromMetadata({
metadataKey: 'design:paramtypes',
prototype: target,
Expand Down
4 changes: 3 additions & 1 deletion lib/decorators/mutation.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Type } from '@nestjs/common';
import { isString } from '@nestjs/common/utils/shared.utils';
import 'reflect-metadata';
import { Resolvers } from '../enums/resolvers.enum';
Expand Down Expand Up @@ -55,8 +56,9 @@ export function Mutation(
: (options && options.name) || undefined;

addResolverMetadata(Resolvers.MUTATION, name, target, key, descriptor);

if (nameOrType && !isString(nameOrType)) {
LazyMetadataStorage.store(() => {
LazyMetadataStorage.store(target.constructor as Type<unknown>, () => {
const { typeFn, options: typeOptions } = reflectTypeFromMetadata({
metadataKey: 'design:returntype',
prototype: target,
Expand Down
11 changes: 7 additions & 4 deletions lib/decorators/object-type.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,19 @@ export function ObjectType(
const interfaces = options.implements
? [].concat(options.implements)
: undefined;

return target => {
LazyMetadataStorage.store(() =>
const addObjectTypeMetadata = () =>
TypeMetadataStorage.addObjectTypeMetadata({
name: name || target.name,
target,
description: options.description,
interfaces,
isAbstract: options.isAbstract,
}),
);
});

// This function must be called eagerly to allow resolvers
// accessing the "name" property
addObjectTypeMetadata();
LazyMetadataStorage.store(addObjectTypeMetadata);
};
}
3 changes: 2 additions & 1 deletion lib/decorators/query.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Type } from '@nestjs/common';
import { isString } from '@nestjs/common/utils/shared.utils';
import 'reflect-metadata';
import { Resolvers } from '../enums/resolvers.enum';
Expand Down Expand Up @@ -57,7 +58,7 @@ export function Query(
addResolverMetadata(Resolvers.QUERY, name, target, key, descriptor);

if (nameOrType && !isString(nameOrType)) {
LazyMetadataStorage.store(() => {
LazyMetadataStorage.store(target.constructor as Type<unknown>, () => {
const { typeFn, options: typeOptions } = reflectTypeFromMetadata({
metadataKey: 'design:returntype',
prototype: target,
Expand Down
4 changes: 2 additions & 2 deletions lib/decorators/resolve-field.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SetMetadata } from '@nestjs/common';
import { SetMetadata, Type } from '@nestjs/common';
import { isFunction, isObject } from '@nestjs/common/utils/shared.utils';
import {
RESOLVER_NAME_METADATA,
Expand Down Expand Up @@ -79,7 +79,7 @@ export function ResolveField(
? { name: propertyName as string }
: {};

LazyMetadataStorage.store(() => {
LazyMetadataStorage.store(target.constructor as Type<unknown>, () => {
let typeOptions: TypeOptions, typeFn: (type?: any) => GqlTypeReference;
try {
const implicitTypeMetadata = reflectTypeFromMetadata({
Expand Down
41 changes: 21 additions & 20 deletions lib/decorators/resolver.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,25 @@ import {
addResolverMetadata,
getClassName,
getClassOrUndefined,
getResolverTypeFn,
} from './resolvers.utils';

export type ResolverTypeFn = (of?: void) => Type<any>;

/**
* Extracts the name property set through the @ObjectType() decorator (if specified)
* @param nameOrType type reference
*/
function getObjectTypeNameIfExists(nameOrType: Function): string | undefined {
const ctor = getClassOrUndefined(nameOrType);
const objectTypesMetadata = TypeMetadataStorage.getObjectTypesMetadata();
const objectMetadata = objectTypesMetadata.find(type => type.target === ctor);
if (!objectMetadata) {
return;
}
return objectMetadata.name;
}

/**
* Interface defining options that can be passed to `@Resolve()` decorator
*/
Expand Down Expand Up @@ -58,32 +73,18 @@ export function Resolver(
let name = nameOrType && getClassName(nameOrType);

if (isFunction(nameOrType)) {
// extract name from @ObjectType()
const ctor = getClassOrUndefined(nameOrType as Function);
const objectTypesMetadata = TypeMetadataStorage.getObjectTypesMetadata();
const objectMetadata = objectTypesMetadata.find(
type => type.target === ctor,
);
objectMetadata && (name = objectMetadata.name);
const objectName = getObjectTypeNameIfExists(nameOrType as Function);
objectName && (name = objectName);
}
addResolverMetadata(undefined, name, target, key, descriptor);

if (!isString(nameOrType)) {
LazyMetadataStorage.store(() => {
const getObjectType = nameOrType
? nameOrType.prototype
? () => nameOrType as Type<unknown>
: (nameOrType as ResolverTypeFn)
: () => {
throw new Error(
`No provided object type in '@Resolver' decorator for class '${
(target as Function).name
}!'`,
);
};
LazyMetadataStorage.store(target as Type<unknown>, () => {
const typeFn = getResolverTypeFn(nameOrType, target as Function);

TypeMetadataStorage.addResolverMetadata({
target: target as Function,
typeFn: getObjectType,
typeFn: typeFn,
isAbstract: (options && options.isAbstract) || false,
});
});
Expand Down
20 changes: 16 additions & 4 deletions lib/decorators/resolvers.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
RESOLVER_NAME_METADATA,
RESOLVER_TYPE_METADATA,
} from '../graphql.constants';
import { UndefinedResolverTypeError } from '../schema-builder/errors/undefined-resolver-type.error';
import { ResolverTypeFn } from './resolver.decorator';

export function addResolverMetadata(
resolver: Resolvers | string | undefined,
Expand All @@ -21,21 +23,31 @@ export function addResolverMetadata(
SetMetadata(RESOLVER_NAME_METADATA, name)(target, key, descriptor);
}

export const getClassName = (nameOrType: string | Function | Type<any>) => {
export function getClassName(nameOrType: string | Function | Type<any>) {
if (isString(nameOrType)) {
return nameOrType;
}
const classOrUndefined = getClassOrUndefined(nameOrType);
return classOrUndefined && classOrUndefined.name;
};
}

export function getResolverTypeFn(nameOrType: Function, target: Function) {
return nameOrType
? nameOrType.prototype
? () => nameOrType as Type<unknown>
: (nameOrType as ResolverTypeFn)
: () => {
throw new UndefinedResolverTypeError(target.name);
};
}

export const getClassOrUndefined = (typeOrFunc: Function | Type<any>) => {
export function getClassOrUndefined(typeOrFunc: Function | Type<any>) {
return isConstructor(typeOrFunc)
? typeOrFunc
: isFunction(typeOrFunc)
? (typeOrFunc as Function)()
: undefined;
};
}

function isConstructor(obj: any) {
return (
Expand Down
4 changes: 2 additions & 2 deletions lib/decorators/subscription.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SetMetadata } from '@nestjs/common';
import { SetMetadata, Type } from '@nestjs/common';
import { isString } from '@nestjs/common/utils/shared.utils';
import 'reflect-metadata';
import { Resolvers } from '../enums/resolvers.enum';
Expand Down Expand Up @@ -91,7 +91,7 @@ export function Subscription(
);

if (nameOrType && !isString(nameOrType)) {
LazyMetadataStorage.store(() => {
LazyMetadataStorage.store(target.constructor as Type<unknown>, () => {
const { typeFn, options: typeOptions } = reflectTypeFromMetadata({
metadataKey: 'design:returntype',
prototype: target,
Expand Down
3 changes: 0 additions & 3 deletions lib/graphql-schema-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { GRAPHQL_SDL_FILE_HEADER } from './graphql.constants';
import { BuildSchemaOptions } from './interfaces/build-schema-options.interface';
import { GraphQLSchemaFactory } from './schema-builder/graphql-schema.factory';
import { FileSystemHelper } from './schema-builder/helpers/file-system.helper';
import { LazyMetadataStorage } from './schema-builder/storages/lazy-metadata.storage';
import { ScalarsExplorerService } from './services';

@Injectable()
Expand Down Expand Up @@ -42,8 +41,6 @@ export class GraphQLSchemaBuilder {
options: BuildSchemaOptions = {},
resolvers: Function[],
) {
LazyMetadataStorage.load();

const scalarsMap = this.scalarsExplorerService.getScalarsMap();
try {
return await this.buildSchema(resolvers, autoSchemaFile, {
Expand Down
7 changes: 7 additions & 0 deletions lib/schema-builder/errors/undefined-resolver-type.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class UndefinedResolverTypeError extends Error {
constructor(name: string) {
super(
`Undefined ressolver type error. Make sure you are providing an explicit object type for the "${name}" (e.g., "@Resolver(() => Cat)").`,
);
}
}
2 changes: 1 addition & 1 deletion lib/schema-builder/graphql-schema.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class GraphQLSchemaFactory {
options: BuildSchemaOptions = {},
): Promise<GraphQLSchema> {
TypeMetadataStorage.clear();
LazyMetadataStorage.load();
LazyMetadataStorage.load(resolvers);
TypeMetadataStorage.compile(options.orphanedTypes);

this.typeDefinitionsGenerator.generate(options);
Expand Down
30 changes: 25 additions & 5 deletions lib/schema-builder/storages/lazy-metadata.storage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
import { Type } from '@nestjs/common';

interface LazyMetadataHost {
func: Function;
target?: Type<unknown>;
}

export class LazyMetadataStorageHost {
private readonly storage: Function[] = [];
private readonly storage = new Array<LazyMetadataHost>();

store(fn: Function) {
this.storage.push(fn);
store(func: Function): void;
store(target: Type<unknown>, func: Function): void;
store(targetOrFn: Type<unknown> | Function, func?: Function) {
if (func) {
this.storage.push({ target: targetOrFn as Type<unknown>, func });
} else {
this.storage.push({ func: targetOrFn });
}
}

load() {
this.storage.forEach(fn => fn());
load(types: Function[] = []) {
//console.log(this.storage, types);
this.storage.forEach(({ func, target }) => {
if (target && types.includes(target)) {
func();
} else if (!target) {
func();
}
});
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestjs/graphql",
"version": "6.6.1",
"version": "7.0.0-next.2",
"description": "Nest - modern, fast, powerful node.js web framework (@graphql)",
"author": "Kamil Mysliwiec",
"license": "MIT",
Expand Down

0 comments on commit 4cb9f39

Please sign in to comment.