Skip to content

Commit

Permalink
Rewrite internals + simplify decorators api
Browse files Browse the repository at this point in the history
  • Loading branch information
robak86 committed Dec 16, 2017
1 parent 7d93cdb commit e74277b
Show file tree
Hide file tree
Showing 63 changed files with 3,076 additions and 726 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,11 @@
#CHANGELOG

## 0.0.3
- move graphql to peerDependencies
- upgrade dependencies

## 0.4.0
- complete rewrite of internals
- upgrade dependencies
- remove `@args` and `@argsType` decorators in favor of `@params` and `@input`. [**Breaking change**]

19 changes: 11 additions & 8 deletions README.md
Expand Up @@ -3,7 +3,10 @@
[![Build Status](https://travis-ci.org/robak86/gql-schema.svg?branch=master)](https://travis-ci.org/robak86/gql-schema)
[![Coverage Status](https://coveralls.io/repos/github/robak86/gql-schema/badge.svg?branch=master)](https://coveralls.io/github/robak86/gql-schema?branch=master)

Yet another experimental library for defining graphql schemas using decorators. Alpha version - use at your own risk.
Yet another library for defining graphql schemas using decorators.

**Warning**: This software is still at an early stage of development. Use at your own risk!


## Getting started

Expand Down Expand Up @@ -58,7 +61,7 @@ main();
## ```@type``` decorator

```typescript
import {type, field, list, nonNull, nonNullItems, resolve, description, id, argsType, args} from 'gql-schema';
import {type, field, list, nonNull, nonNullItems, resolve, description, id, input, params} from 'gql-schema';
import {GraphQLString, GraphQLInt} from "graphql";

const resolveFunction = (_, args:SomeParams, ctx):Partial<SomeType> => {
Expand Down Expand Up @@ -87,7 +90,7 @@ class SomeType {
nonNullableListWithNullItemsForbidden: string[]
}

@argsType()
@input()
class SomeParams {
@field(GraphQLString) @nonNull()
someParam:string
Expand All @@ -96,7 +99,7 @@ class SomeParams {
@type()
class Query {
@field(SomeType) @nonNull()
@args(SomeParams) @resolve(resolveFunction)
@params(SomeParams) @resolve(resolveFunction)
someData:SomeType
}
```
Expand Down Expand Up @@ -124,7 +127,7 @@ type Query {
## ```@input``` decorator

```typescript
import {field, input, nonNull, args, argsType, resolve, type} from 'gql-schema';
import {field, input, nonNull, params, input, resolve, type} from 'gql-schema';
import {GraphQLString} from "graphql";

const createUser = (_, args:CreateUserParams, ctx):Partial<User> => {
Expand Down Expand Up @@ -152,7 +155,7 @@ class NewUserAddressParams {
city:string;
}

@argsType()
@input()
class CreateUserParams {
@field(NewUserParams) @nonNull()
userParams:NewUserParams;
Expand Down Expand Up @@ -187,7 +190,7 @@ class User {
@type()
class Mutation {
@field(User) @nonNull()
@args(CreateUserParams) @resolve(createUser)
@params(CreateUserParams) @resolve(createUser)
createUser:User
}
```
Expand Down Expand Up @@ -244,7 +247,7 @@ class BackgroundJob {
Given annotated classes will produce following schema definition.

```graphql schema
type BackgroundJob{
type BackgroundJob {
status: Status!
}

Expand Down
49 changes: 49 additions & 0 deletions ROADMAP.md
@@ -0,0 +1,49 @@
# ROADMAP

### Add support for inheritance
- investigate possible pitfalls

```typescript
class PersistedObject {
@id()
id:string

@field(CustomGraphQLDateScalar)
createdAt:Date

@field(CustomGraphQLDateScalar)
updatedAt:Date
}

@type()
class User extends PersistedObject {}
```

should generate:

```graphql
type User {
id: ID
createdAt: CustomGraphQLDateScalar
updateAt: CustomGraphQLDateScalar
}
```

### Infer basic types from ts metadata
- investigate if it makes sense, because this feature would be very limited - only for couple of types.

### Improve error handling and error messages
- add assertions for all setters in fields metadata
- add validation of field configuration
- add more descriptive logs for all errors thrown from Metadata classes during native object creation
- assert one metadata object is attached to class

### Refactoring
- use one convention for private fields
- currently all metadata classes knows how to build graphql types - investigate if extracting this into separate "strategy like" classes makes sens
- getOrCreateForClass and getForClass can be merged to one method
```typescript
class SomeMetadata {
static getForClass(attachIfMissing:boolean = false){}
}
```
14 changes: 0 additions & 14 deletions TODO.md

This file was deleted.

5 changes: 0 additions & 5 deletions lib/abstract/IGraphQLMetadata.ts

This file was deleted.

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

This file was deleted.

2 changes: 1 addition & 1 deletion lib/decorators/enum.ts
@@ -1,7 +1,7 @@
import {enumsRegistry} from "../registry/typesRegistry";

/***
* It makes enum being acceptable as type for @field decorator
* It makes native typescript enum being acceptable as type for @field decorator
* @param {string} name
* @param {Object} enumType
* @param props
Expand Down
114 changes: 47 additions & 67 deletions lib/decorators/fields.ts
@@ -1,67 +1,47 @@
import {FieldConfig, FieldsMetadata} from "../metadata/FieldsMetadata";
import {invariant} from "../utils/core";
import * as _ from 'lodash';
import {GraphQLID} from "graphql";
import {ArgsType, FieldType} from "./typesInferention";


function patchField(target, propertyKey, partialConfig:Partial<FieldConfig>) {
let fieldsMetadata:FieldsMetadata = FieldsMetadata.getOrCreateForClass(target.constructor);
fieldsMetadata.patchConfig(propertyKey, partialConfig);
}

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 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 nonNull = ():PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {nonNull: true})
};

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

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

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

export const resolve = (resolve:Function):PropertyDecorator => {
return (target:Object, propertyKey:string) => patchField(target, propertyKey, {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 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
})
}
};
import {GraphQLFieldResolver, GraphQLID} from "graphql";
import {FieldType} from "../types-conversion/TypeProxy";
import {ArgsType} from "../types-conversion/ArgumentsTypeProxy";
import {createFieldDecorator} from "./helpers";

export const id = ():PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setType(GraphQLID)
});

export const field = (type:FieldType):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setType(type);
});

export const fieldThunk = (thunkType:() => (FieldType)):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setTypeThunk(thunkType);
});

export const nonNull = ():PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setNonNullConstraint()
});

export const nonNullItems = ():PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setNonNullItemsConstraint()
});
export const description = (description:string):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setDescription(description)
});

export const params = (argsType:ArgsType):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setParamsType(argsType)
});

export const paramsThunk = (argsType:() => ArgsType):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setParamsThunk(argsType)
});

export const resolve = <T>(resolve:GraphQLFieldResolver<any, any>):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setResolver(resolve);
});

export const list = (type:FieldType):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setListType(type)
});

export const listThunk = (thunkType:() => FieldType):PropertyDecorator => createFieldDecorator(fieldConfig => {
fieldConfig.setListTypeThunk(thunkType)
});
13 changes: 13 additions & 0 deletions lib/decorators/helpers.ts
@@ -0,0 +1,13 @@
import {FieldsMetadata} from "../fields-metadata/FieldsMetadata";
import {FieldConfig} from "../fields-metadata/FieldConfig";

export function createFieldDecorator(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}`)
}
}
}
12 changes: 5 additions & 7 deletions lib/decorators/input.ts
@@ -1,12 +1,10 @@
import {Type as Klass} from "../utils/types";
import {InputConfig, InputObjectTypeMetadata} from "../metadata/InputObjectTypeMetadata";
import {ClassType as Klass} from "../utils/types";
import {InputConfig, ParamsMetadata} from "../types-metadata/ParamsMetadata";


export const input = (config:InputConfig = {}) => {
export const input = (config:Partial<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;
}
};
5 changes: 2 additions & 3 deletions lib/decorators/interface.ts
@@ -1,10 +1,9 @@
import {InterfaceTypeMetadata, TypeConfig} from "../metadata/InterfaceTypeMetadata";
import {InterfaceTypeMetadata, InterfaceTypeConfig} from "../types-metadata/InterfaceTypeMetadata";

export const interfaceType = (config:TypeConfig<any, any> = {}):ClassDecorator => {
export const interfaceType = (config:Partial<InterfaceTypeConfig> = {}):ClassDecorator => {
return <TFunction extends Function>(klass:TFunction):TFunction => {
let objectTypeMetadata = InterfaceTypeMetadata.getOrCreateForClass(klass);
objectTypeMetadata.setConfig(config);

return klass;
};
};
7 changes: 3 additions & 4 deletions lib/decorators/type.ts
@@ -1,10 +1,9 @@
import {ObjectTypeMetadata, TypeConfig} from "../metadata/ObjectTypeMetadata";
import {TypeConfigParams, TypeMetadata} from "../types-metadata/TypeMetadata";

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

return klass;
};
};

0 comments on commit e74277b

Please sign in to comment.