allow to register Arg at runtime dynamically
it is currently possible to register a Field onto a class at runtime, by invoking manually like this:
const decoratorFunction = Field({
type,
name: relationName,
isNullable
})
decoratorFunction(prototype, fieldName)
however this is not possible with Arg because for dynamically created method, we don't have any metadata. The compileFieldArgs doesn't register any arguments, because calling
var inferedRawArgs = Reflect.getMetadata('design:paramtypes', target.prototype, fieldName);
obviously returns undefined and the whole compileFieldArgs function just returns without doing anything more.
It would be great If we could have some kind of
{registerDynamicArgument} from 'typegql'
registerDynamicArgument(myClass, "myMethod", "myArgumentName", {
isNullable: true,
type: String
})
Could you describe use case for this a bit more?
Also, note you could just:
import { Arg } from 'typegql'
const paramIndex = 3;
Arg({
type: String,
isNullable: true
})(targetClass, fieldName, paramIndex);
And I think it's very similar to what you've written in your example :) The only difference is that you'd need to set paramIndex as number instead of name.
@pie6k I tried that exact thing on a dynamically added property and it did not work. It might work if I call it on a property which is defined at design time. I'd like to add both the property and the param decorator at runtime.
I will put together a testcase.
Could you please describe why exactly you need this?
sure. For our production API at @leaplabs we use objection.js as our abstraction over a database. We have many models-around 40 with many relations between them. Usually a model has at least 1, but we have many which have 4-6 and more.
We could have gone and added a relation @Field() resolver for each relation in every model, but that would just be copy pasting the same code all over the place. So instead we've got a custom class decorator which we put on a objection.js class. This decorator at runtime adds @Field() resolver for each relation in that class by inspecting static relationMappings on that class.
It basically adds a @Field() which gives us the relations themselves and it adds another @Field() for getting a count of those related entities.
For these fields which are added at runtime I would like to have Arguments as well and that's where I face this issue.
I've added test case and it works:
it('Will allow registering argument at runtime', () => {
@ObjectType()
class Foo {
@Field()
bar(
baz: string,
bazRequired: string,
): string {
return baz;
}
}
Arg({type: String, isNullable: true})(Foo.prototype, 'bar', 0);
Arg({type: String, isNullable: false})(Foo.prototype, 'bar', 1);
const [bazArg, bazRequiredArg] = compileObjectType(
Foo,
).getFields().bar.args;
expect(bazArg.type).toBe(GraphQLString);
expect(bazRequiredArg.type).toEqual(new GraphQLNonNull(GraphQLString));
});
Note that decorator is fired on target.prototype and it might be reason it didnt work for you :)
@pie6k that works indeed, but my issue is when I have a Field added at runtime also. So it would need to be like:
it('Will allow registering a Field and Arg at runtime', () => {
@ObjectType()
class Foo { }
Foo.prototype.bar = function(baz: string, bazRequired: string) {}
Field({
type: String,
isNullable
})(Foo.prototype, 'bar')
Arg({type: String, isNullable: true})(Foo.prototype, 'bar', 0);
Arg({type: String, isNullable: false})(Foo.prototype, 'bar', 1);
const [bazArg, bazRequiredArg] = compileObjectType(
Foo,
).getFields().bar.args;
expect(bazArg.type).toBe(GraphQLString);
expect(bazRequiredArg.type).toEqual(new GraphQLNonNull(GraphQLString));
});
this is failing for me.