-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fixes #28 and #32 #35
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,7 @@ function computeFinalArgs( | |
export function compileFieldResolver( | ||
target: Function, | ||
fieldName: string, | ||
isGetter: boolean, | ||
): GraphQLFieldResolver<any, any> { | ||
// const config = fieldsRegistry.get(target, fieldName); | ||
const injectors = injectorRegistry.getAll(target)[fieldName]; | ||
|
@@ -70,8 +71,18 @@ export function compileFieldResolver( | |
|
||
return async (source: any, args = null, context = null, info = null) => { | ||
await performHooksExecution(beforeHooks, source, args, context, info); | ||
const instanceField = | ||
(source && source[fieldName]) || target.prototype[fieldName]; | ||
|
||
if (isGetter) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole block is hard to understand for me. Could we make it external function that would abstract it away with proper name. It seems like the point here is to get target property. |
||
const valueFromGetter = source[fieldName]; | ||
await performHooksExecution(afterHooks, source, args, context, info); | ||
return valueFromGetter; | ||
} | ||
let instanceField; | ||
if (source && source.hasOwnProperty(fieldName)) { | ||
instanceField = source[fieldName]; | ||
} else { | ||
instanceField = target.prototype[fieldName]; | ||
} | ||
|
||
if (typeof instanceField !== 'function') { | ||
await performHooksExecution(afterHooks, source, args, context, info); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,13 @@ export function Field(options?: FieldOptions): PropertyDecorator { | |
...options, | ||
}; | ||
|
||
const descriptor = Object.getOwnPropertyDescriptor( | ||
targetInstance, | ||
fieldName, | ||
); | ||
|
||
fieldsRegistry.set(targetInstance.constructor, fieldName, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it can be calculated from |
||
isGetter: descriptor && !!descriptor.get, | ||
...finalConfig, | ||
}); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ export interface FieldInnerConfig { | |
property: string; | ||
description?: string; | ||
isNullable?: boolean; | ||
isGetter?: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm quite strongly against having it explicitly here. It looks like you might define it. But it's part of business logic that could propably be calculated 'on the fly' when needed. Config is what user or system want it to be. Getter is implementation detail, so I'd say it's not part of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. like I've mentioned-detecting getter always using |
||
type?: any; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,10 +31,15 @@ export function inferTypeByTarget(target: Function, key?: string) { | |
} | ||
|
||
const returnType = Reflect.getMetadata('design:returntype', target, key); | ||
|
||
if (returnType) { | ||
return returnType; | ||
} | ||
|
||
const descriptor = Object.getOwnPropertyDescriptor(target, key); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this function is already quite complicated and not obvious. Making external function like |
||
if (descriptor && descriptor.get) { | ||
return Reflect.getMetadata('design:type', target, key); | ||
} | ||
const targetField = (target as any)[key]; | ||
|
||
if (targetField && typeof targetField === 'function') { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,16 +3,25 @@ import { ObjectType, Field, compileObjectType } from '~/domains'; | |
|
||
describe('Fields based on getters', () => { | ||
it('Will work with getter field', async () => { | ||
let foo: Foo; | ||
@ObjectType({ description: 'Simple product object type' }) | ||
class Foo { | ||
a: number; | ||
@Field() | ||
get bar(): number { | ||
return 42; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's good way to perform tests by throwing errors to stop them. There might be different ways to accomplish this like adding instance field and then returning result like |
||
if (this !== foo) { | ||
throw new Error( | ||
'getter must be resolved with an instance as a context ', | ||
); | ||
} | ||
return this.a + 21; | ||
} | ||
} | ||
|
||
const { bar } = compileObjectType(Foo).getFields(); | ||
const resolvedValue = await bar.resolve(new Foo(), null, null, null); | ||
foo = new Foo(); | ||
foo.a = 21; | ||
const resolvedValue = await bar.resolve(foo, null, null, null); | ||
expect(resolvedValue).toEqual(42); | ||
expect(bar.type).toEqual(GraphQLFloat); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,11 +14,31 @@ describe('Field', () => { | |
@ObjectType() | ||
class Foo { | ||
@Field() bar: string = 'baz'; | ||
@Field() undef: boolean = undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say this test is out of scope of |
||
@Field() b1: boolean = false; | ||
@Field() b2: boolean = true; | ||
@Field() n0: number = 0; | ||
@Field() nMax: number = Number.MAX_SAFE_INTEGER; | ||
} | ||
const compiled = compileObjectType(Foo); | ||
const barField = compiled.getFields().bar; | ||
|
||
expect(await barField.resolve(new Foo(), {}, null, null)).toEqual('baz'); | ||
const barField = compiled.getFields().bar; | ||
const undefField = compiled.getFields().undef; | ||
const b1Field = compiled.getFields().b1; | ||
const b2Field = compiled.getFields().b2; | ||
const n0Field = compiled.getFields().n0; | ||
const nMaxField = compiled.getFields().nMax; | ||
|
||
const foo = new Foo(); | ||
|
||
expect(await barField.resolve(foo, {}, null, null)).toEqual('baz'); | ||
expect(await undefField.resolve(foo, {}, null, null)).toEqual(undefined); | ||
expect(await b1Field.resolve(foo, {}, null, null)).toEqual(false); | ||
expect(await b2Field.resolve(foo, {}, null, null)).toEqual(true); | ||
expect(await n0Field.resolve(foo, {}, null, null)).toEqual(0); | ||
expect(await nMaxField.resolve(foo, {}, null, null)).toEqual( | ||
9007199254740991 | ||
); | ||
}); | ||
|
||
it('Resolves fields with function resolver', async () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be replaced in favor of using
Object.getOwnPropertyDescriptor
to detect it automatically? Passing it over like this might make it harder to maintainThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it could, but doing
Object.getOwnPropertyDescriptor
is performance costly. This might sound like a micro optimisation, but it's not. See for example chaijs/chai#127 (comment).Having it detected only once is worth the faster performance IMHO. Decorators always run statically at startup, so it doesn't make sense to support a scenario where someone would change a property into a getter at runtime.