Skip to content

Commit

Permalink
Add minimal inheritance support
Browse files Browse the repository at this point in the history
  • Loading branch information
robak86 committed Jan 6, 2018
1 parent 46897b5 commit e507c7d
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 6 deletions.
42 changes: 36 additions & 6 deletions lib/fields-metadata/FieldsMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
import 'reflect-metadata';
import {metadataGet, metadataGetOrSet} from "../utils/metadataFactories";
import {metadataGet} from "../utils/metadataFactories";
import {FieldConfig} from "./FieldConfig";
import {getSuperClass} from "../utils/core";

const FIELDS_METADATA_KEY = '__FIELDS_METADATA_KEY';

export class FieldsMetadata {
static getForClass = metadataGet<FieldsMetadata>(FIELDS_METADATA_KEY);
static getOrCreateForClass = metadataGetOrSet(FIELDS_METADATA_KEY, FieldsMetadata);
static getOrCreateForClass(klass):FieldsMetadata {
let attributesMetadata = Reflect.getOwnMetadata(FIELDS_METADATA_KEY, klass);

if (!attributesMetadata) {
attributesMetadata = new FieldsMetadata();
Reflect.defineMetadata(FIELDS_METADATA_KEY, attributesMetadata, klass);

protected _fields:{ [fieldName:string]:FieldConfig } = {};

let superClass = getSuperClass(klass);
if (superClass) {
attributesMetadata.setParent(FieldsMetadata.getOrCreateForClass(superClass));
}
}

return attributesMetadata;
}


private parent:FieldsMetadata;
protected _ownFields:{ [fieldName:string]:FieldConfig } = {};

getField(fieldName:string):FieldConfig {
return this._fields[fieldName] || (this._fields[fieldName] = new FieldConfig())
return this._ownFields[fieldName] || (this._ownFields[fieldName] = new FieldConfig())
}

getFields():{ [fieldName:string]:FieldConfig } {
return this._fields;
let parentFields = this.parent ? this.parent.getFields() : {};

//TODO: consider deep merge of field configuration. In current implementation ownField configuration overrides completely field config from parent
return {
...parentFields,
...this._ownFields
}
}
}

private setParent(fieldsMetadata:FieldsMetadata) {
this.parent = fieldsMetadata;
}
}



2 changes: 2 additions & 0 deletions lib/fields-metadata/TypeValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export class TypeValue<T> {
private _type:T;
private _typeThunk:Thunk<T>;

//TODO: validate if both _type and _typeThunk are being set

setType(type:T) {
this._type = type;
}
Expand Down
5 changes: 5 additions & 0 deletions lib/utils/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export function invariant(condition:boolean, message:string) {
}
}

export function getSuperClass(klass) {
let superClass = Object.getPrototypeOf(klass);
return superClass.name ? superClass : null;
}

export function propagateErrorWithContext<T>(ctx:string, fn:() => T):T {
try {
return fn();
Expand Down
25 changes: 25 additions & 0 deletions spec/fields-metadata/FieldsMetadata.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {FieldsMetadata} from "../../lib/fields-metadata/FieldsMetadata";
import {GraphQLString} from "graphql";
import {expect} from 'chai'

describe(`FieldsMetadata`, () => {
describe('inheritance', () => {
class ParentClass {}

let fieldsMetadata = FieldsMetadata.getOrCreateForClass(ParentClass);
let parentField = fieldsMetadata.getField('parentField');
parentField.setType(GraphQLString);
parentField.setDescription('Parent Field Description');

it('inherits all parent class properties', () => {
class ChildClass extends ParentClass {}

let fieldsMetadata = FieldsMetadata.getOrCreateForClass(ChildClass);
let fields = fieldsMetadata.getFields();

expect(fields).to.have.keys(['parentField']);
});

//TODO: add more test cases
})
});
146 changes: 146 additions & 0 deletions spec/integration/inheritance.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {buildASTSchema, GraphQLInt, GraphQLString, parse, printType} from "graphql";
import {expect} from 'chai';
import {GraphQLSchema} from "graphql/type/schema";
import {createSchema, field, id, list, nonNull, nonNullItems, type} from "../../lib";


@type()
class Mutation {
@field(GraphQLString)
unused:string;
}


@type()
class ListResult {
@field(GraphQLInt) @nonNull()
totalCount:number;
}

@type()
class PersistedObject {
@id() @nonNull()
id:string;

@field(GraphQLInt)
createdAt:number; //for simplification store as integer timestamp instead of custom scalar

@field(GraphQLInt)
updatedAt:number; //for simplification store as integer timestamp instead of custom scalar
}

@type()
class User extends PersistedObject {
@field(GraphQLString)
email:string
}

@type()
class UsersList extends ListResult {
@list(User) @nonNull() @ nonNullItems()
users:User[]
}

@type()
class Product extends PersistedObject {
@field(GraphQLString)
productName:string
}

@type()
class ProductsList extends ListResult {
@list(Product) @nonNull() @ nonNullItems()
products:Product[]
}


@type()
class Query {
@field(UsersList) @nonNull()
users:UsersList;

@field(ProductsList) @nonNull()
products:ProductsList;
}


function createdSchemaFromDecoratedClasses():GraphQLSchema {
return createSchema(Query, Mutation);
}

function createSchemaFromDefinition():GraphQLSchema {
const definition = `
type User {
id: ID!
createdAt: Int
updatedAt: Int
email: String
}
type Product {
id: ID!
createdAt: Int
updatedAt: Int
productName: String
}
type UsersList {
totalCount: Int!
users: [User!]!
}
type ProductsList {
totalCount: Int!
products: [Product!]!
}
type Query {
users: UsersList!
products: ProductsList!
}
type Mutation {
unused:String
}
`;
return buildASTSchema(parse(definition));
}


function expectTypesEqual(typeName:string) {
expect(printType(createdSchemaFromDecoratedClasses().getType(typeName)))
.to.eql(printType(createSchemaFromDefinition().getType(typeName)));
}

describe.only("building schema", () => {

describe("type Query", () => {
it("generates proper type", () => {
expectTypesEqual('Query');
});
});

describe("type User", () => {
it("generates proper type", () => {
expectTypesEqual('User');
});
});

describe("type Product", () => {
it("generates proper type", () => {
expectTypesEqual('Product');
});
});

describe("type UsersList", () => {
it("generates proper type", () => {
expectTypesEqual('UsersList');
});
});

describe("input ProductsList", () => {
it("generates proper type", () => {
expectTypesEqual('ProductsList');
});
});
});

0 comments on commit e507c7d

Please sign in to comment.