Skip to content

Commit

Permalink
[WIP] Rewrite internals + simplify decorators api
Browse files Browse the repository at this point in the history
  • Loading branch information
robak86 committed Dec 15, 2017
1 parent 7d93cdb commit 3a648d8
Show file tree
Hide file tree
Showing 44 changed files with 813 additions and 569 deletions.
13 changes: 12 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,15 @@ class SomeMetadata {

}
}
```
```


* add more descriptive logs for all errors thrown from Metadata classes (especially from inferring types)
* replace argsType with input and args with params. TypeMetadata should return ObjectType or ArgsType depending on the context

params will call setArguments method on template class () and setArguments(target) will set context on target. (information that is used for detecting if type is used as input or as args)

Refactor
- Add validation , add assertions for all setters in fields metadata
- add memoization of build types inside of metadata classes

9 changes: 0 additions & 9 deletions lib/decorators/argsType.ts

This file was deleted.

95 changes: 40 additions & 55 deletions lib/decorators/fields.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,52 @@
import {FieldConfig, FieldsMetadata} from "../metadata/FieldsMetadata";
import {invariant} from "../utils/core";
import * as _ from 'lodash';
import {GraphQLID} from "graphql";
import {GraphQLFieldResolver, GraphQLID} from "graphql";
import {ArgsType, FieldType} from "./typesInferention";
import {FieldsMetadata} from "../metadata/FieldsMetadata";
import {FieldConfig} from "../metadata/FieldConfig";


function patchField(target, propertyKey, partialConfig:Partial<FieldConfig>) {
let fieldsMetadata:FieldsMetadata = FieldsMetadata.getOrCreateForClass(target.constructor);
fieldsMetadata.patchConfig(propertyKey, partialConfig);
function createDecorator(updateFieldsMetadata:(fieldConfig:FieldConfig) => void):PropertyDecorator {
return (target:Object, propertyKey:string) => {
try {
let meta = FieldsMetadata.getOrCreateForClass(target.constructor);
updateFieldsMetadata(meta.getField(propertyKey));
} catch (e) {
throw new Error(`Class: ${target.constructor.name}, property: ${propertyKey}. Error message: ${e.message}`)
}
}
}

export const id = ():PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {
type: GraphQLID
})
};

export const field = (type:FieldType):PropertyDecorator => {
invariant(!!type, `@field decorator called with undefined or null value`);
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {type})
};
export const id = ():PropertyDecorator => createDecorator(fieldConfig => fieldConfig.setType(GraphQLID));

export const fieldLazy = (thunkType:() => (FieldType)):PropertyDecorator => {
invariant(_.isFunction(thunkType), `@fieldThunk decorator called with non function param`);
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {thunkType})
};
export const field = (type:FieldType):PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setType(type);
});

export const nonNull = ():PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {nonNull: true})
};
export const fieldThunk = (thunkType:() => (FieldType)):PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setTypeThunk(thunkType);
});

export const nonNullItems = ():PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {nonNullItem: true})
};
export const nonNull = ():PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setNonNullConstraint()
});

export const description = (description:string):PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {description})
};
export const nonNullItems = ():PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setNonNullItemsConstraint()
});
export const description = (description:string):PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setDescription(description)
});

export const args = (argsType:ArgsType):PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {args: argsType})
};
export const params = (argsType:ArgsType):PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setParamsType(argsType)
});

export const resolve = (resolve:Function):PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {resolve})
};
export const resolve = <T>(resolve:GraphQLFieldResolver<any, any>):PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setResolver(resolve);
});

export const list = (type:FieldType):PropertyDecorator => {
invariant(!!type, `@array decorator called with undefined or null value`);
return (target:Object, propertyKey:string) => {
patchField(target, propertyKey, {
array: true,
type
})
}
};
export const list = (type:FieldType):PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setListType(type)
});

export const listLazy = (thunkType:() => FieldType):PropertyDecorator => {
invariant(!!thunkType, `@arrayThunk decorator called with undefined or null value`);
return (target:Object, propertyKey:string) => {
patchField(target, propertyKey, {
array: true,
thunkType
})
}
};
export const listThunk = (thunkType:() => FieldType):PropertyDecorator => createDecorator(fieldConfig => {
fieldConfig.setListTypeThunk(thunkType)
});
13 changes: 10 additions & 3 deletions lib/decorators/input.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import {Type as Klass} from "../utils/types";
import {InputConfig, InputObjectTypeMetadata} from "../metadata/InputObjectTypeMetadata";
import {ParamsMetadata} from "../metadata/ParamsMetadata";

export interface InputConfig {
name?: string;
description?: string;
}

export const input = (config:InputConfig = {}) => {
return <T_FUNCTION extends Klass<any>>(klass:T_FUNCTION):T_FUNCTION => {
let inputMetadata = InputObjectTypeMetadata.getOrCreateForClass(klass);
inputMetadata.setConfig(config);
let paramsMetadata = ParamsMetadata.getOrCreateForClass(klass);

paramsMetadata.setConfig({
...config
});

return klass;
}
Expand Down
8 changes: 4 additions & 4 deletions lib/decorators/type.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {ObjectTypeMetadata, TypeConfig} from "../metadata/ObjectTypeMetadata";
import {TypeMetadata} from "../metadata/TypeMetadata";
import {TypeConfigParams} from "../metadata/TypeConfig";

export const type = (config:TypeConfig<any, any> = {}):ClassDecorator => {
export const type = (config:TypeConfigParams<any, any> = {}):ClassDecorator => {
return <TFunction extends Function>(klass:TFunction):TFunction => {
let objectTypeMetadata = ObjectTypeMetadata.getOrCreateForClass(klass);
let objectTypeMetadata = TypeMetadata.getOrCreateForClass(klass);
objectTypeMetadata.setConfig(config);

return klass;
};
};
53 changes: 25 additions & 28 deletions lib/decorators/typesInferention.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
import {Type} from "../utils/types";
import {GraphQLInputObjectType, GraphQLType, isType} from "graphql";
import {getMetadata} from "../utils/metadata";
import * as _ from 'lodash';
import {enumsRegistry} from "../registry/typesRegistry";
import {GraphQLFieldConfigArgumentMap, GraphQLType} from "graphql";

export type ArgsType =
GraphQLInputObjectType |
GraphQLFieldConfigArgumentMap |
Type<any>

export type FieldType =
GraphQLType |
Type<any> |
Object;

export function inferGraphQLType(klass:FieldType, allowedMetaData:Function[] = []) {
if (isType(klass)) {
return klass;
}

if (enumsRegistry.hasEnum(klass)) {
return enumsRegistry.getGraphQLEnumType(klass);
}

if (_.isFunction(klass)) {
let metadata = getMetadata(klass as any);
if (allowedMetaData.length > 0) {
let validType = allowedMetaData.some(metadataClass => metadata instanceof metadataClass);
if (!validType) {
throw Error(`Annotated ${klass.name} passed as type property is incompatible: Allowed types are ${allowedMetaData}`)
}
}

return metadata.toGraphQLType();
}

throw new Error(`Cannot infer type for ${JSON.stringify(klass)}`)
}
// export function inferGraphQLType(klass:FieldType, allowedMetaData:Function[] = []) {
// if (isType(klass)) {
// return klass;
// }
//
// if (enumsRegistry.hasEnum(klass)) {
// return enumsRegistry.getGraphQLEnumType(klass);
// }
//
// if (_.isFunction(klass)) {
// let metadata = getMetadata(klass as any);
// if (allowedMetaData.length > 0) {
// let validType = allowedMetaData.some(metadataClass => metadata instanceof metadataClass);
// if (!validType) {
// throw Error(`Annotated ${klass.name} passed as type property is incompatible: Allowed types are ${allowedMetaData}`)
// }
// }
//
// return metadata.toGraphQLType();
// }
//
// throw new Error(`Cannot infer type for ${JSON.stringify(klass)}`)
// }
8 changes: 4 additions & 4 deletions lib/factories/createSchema.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {GraphQLSchema} from "graphql";
import {ObjectTypeMetadata} from "../metadata/ObjectTypeMetadata";
import {someOrThrow} from "../utils/core";
import {TypeMetadata} from "../metadata/TypeMetadata";

export function createSchema(annotatedRootClass, annotatedMutationClass):GraphQLSchema {
let query = someOrThrow(
ObjectTypeMetadata.getForClass(annotatedRootClass),
TypeMetadata.getForClass(annotatedRootClass),
'Class provided as query root is not decorated with @type.define');


let mutation = someOrThrow(
ObjectTypeMetadata.getForClass(annotatedMutationClass),
'Class provided as query root is not decorated with @type.define')
TypeMetadata.getForClass(annotatedMutationClass),
'Class provided as query root is not decorated with @type.define');

return new GraphQLSchema({
query: query.toGraphQLType(),
Expand Down
14 changes: 6 additions & 8 deletions lib/factories/createUnion.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import {GraphQLObjectType, GraphQLUnionType, Thunk} from "graphql";

import {ObjectTypeMetadata} from "../metadata/ObjectTypeMetadata";
import * as _ from 'lodash';
import {inferGraphQLType} from "../decorators/typesInferention";
import {TypeProxy} from "../metadata/TypeProxy";


export function createUnion(name:string, types:Thunk<Array<GraphQLObjectType|Function>>, resolveType):GraphQLUnionType {
//TODO: rename to - unionType
export function createUnion(name:string, types:Thunk<Array<GraphQLObjectType | Function>>, resolveType):GraphQLUnionType {
return new GraphQLUnionType({
name,
types: _.castArray(types).map(type => inferGraphQLType(type as any, [ObjectTypeMetadata])),
resolveType: (value, context) => {
return inferGraphQLType(resolveType(value,context));
}
types: _.castArray(types).map(type => TypeProxy.inferType(type)) as any, //TODO: fix types
resolveType: (value, context) => TypeProxy.inferType(resolveType(value, context)) as any //TODO: fix types
});
}
1 change: 0 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export * from './decorators/type';
export * from './decorators/input';
export * from './decorators/fields';
export * from './decorators/argsType';
export * from './decorators/enum';
export * from './decorators/interface';

Expand Down
17 changes: 17 additions & 0 deletions lib/metadata/AbstractMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {GraphQLType} from "graphql";
import {TypeConfig, TypeConfigParams} from "./TypeConfig";
import {FieldConfig} from "./FieldConfig";

export abstract class AbstractMetadata<T extends FieldConfig = FieldConfig> {
protected config:TypeConfig = new TypeConfig();

constructor(protected klass) {
this.config.setName(this.klass.name);
}

setConfig(config:TypeConfigParams<any, any>) {
this.config.setConfig(config);
}

abstract toGraphQLType():GraphQLType
}
17 changes: 0 additions & 17 deletions lib/metadata/ArgumentMapMetadata.ts

This file was deleted.

30 changes: 30 additions & 0 deletions lib/metadata/ArgumentsTypeProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {GraphQLFieldConfigArgumentMap, GraphQLType, Thunk} from "graphql";
import {FieldType} from "../decorators/typesInferention";
import * as _ from "lodash";
import {getMetadata} from "../utils/metadata";
import {resolveThunk} from "../utils/graphql";
import {ParamsMetadata} from "./ParamsMetadata";

export class ArgumentsTypeProxy {
private _type:FieldType;
private _typeThunk:Thunk<FieldType>;

setType(type:FieldType) {
this._type = type;
}

setTypeThunk(typeThunk:Thunk<FieldType>) {
this._typeThunk = typeThunk;
}

toGraphQLType():GraphQLFieldConfigArgumentMap {
let type:GraphQLType = (this._type || resolveThunk(this._typeThunk)) as GraphQLType;

if (_.isFunction(type)) {
let metadata = getMetadata(type as any) as ParamsMetadata;
return metadata.toGraphQLArgumentMap();
}

throw new Error(`Cannot infer type for ${JSON.stringify(type)}`)
}
}
Loading

0 comments on commit 3a648d8

Please sign in to comment.