Open miguelbogota opened 3 years ago
You can achieve the same thing through composition, no?
function sharedUserFields(t) {
t.nonNull.string("username");
t.nonNull.string("password");
}
export const AddUserInput = inputObjectType({
name: "AddUserInput",
definition(t) {
sharedUserFields(t);
},
});
export const UpdateUserInput = inputObjectType({
name: "UpdateUserInput",
definition(t) {
t.nonNull.id("id");
sharedUserFields(t);
},
});
You could even have a helper that composes multiple of these functions
function sharedUserFields(t) {
t.nonNull.string("username");
t.nonNull.string("password");
}
function idField(t) {
t.nonNull.id("id")
}
export const AddUserInput = inputObjectType({
name: "AddUserInput",
definition: composeFields(sharedUserFields),
});
export const UpdateUserInput = inputObjectType({
name: "UpdateUserInput",
definition: composeFields(sharedUserFields, idField),
});
Hey @tom-sherman, In your example the fields are nonNull
for both AddUser and UpdateUser. Which is not accurate. The idea is to make them nullable in updateUser, since I would like to update only the username or only the password. Not necessarily both.
I came up with the following solution. But it would be a good idea to add some docs regarding this.
interface ValidationArgs<T extends string> {
t: InputDefinitionBlock<T>;
name: string;
required?: boolean;
}
// Have a required/optional field for each type in one file.
const validateString = <T extends string>({ t, name, required = true }: ValidationArgs<T>) =>
required ? t.nonNull.string(name) : t.string(name);
const validateId = <T extends string>({ t, name, required = true }: ValidationArgs<T>) =>
required ? t.nonNull.id(name) : t.id(name);
// ------------
// Create a HOF in another file requesting the required property and pass it down.
const sharedInputs = <T extends string>(required: boolean, t: InputDefinitionBlock<T>) => {
validateString({ t, required, name: "username" });
validateString({ t, required, name: "password" });
};
// ------------
// Use the HOF and pass the required.
const AddUserInput = inputObjectType({
name: "AddUserInput",
definition(t) {
sharedInputs(true, t);
},
});
const UpdateUserInput = inputObjectType({
name: "UpdateUserInput",
definition(t) {
validateId({ t, required: true, name: "id" });
sharedInputs(false, t);
},
});
Or with a field you can have a single require/optional field like:
export interface FieldArgs<T extends string> extends NexusInputFieldConfig<T, string> {
nonNull?: boolean;
}
// Have a required/optional field for only the field type.
export const field = <T extends string>(
t: InputDefinitionBlock<T>,
name: string,
{ nonNull = true, ...config }: FieldArgs<T>,
) => {
return nonNull ? t.nonNull.field(name, config) : t.field(name, config);
};
interface SharedInputsArgs<T extends string> {
nonNull: boolean;
t: InputDefinitionBlock<T>;
}
// ------------
// Create a HOF in another file requesting the required property and pass it down.
const sharedInputs = <T extends string>({ nonNull, t }: SharedInputsArgs<T>) => {
field(t, "username", { nonNull, type: "String" });
field(t, "password", { nonNull, type: "String" });
};
// ------------
// Use the HOF and pass the required.
const AddUserInput = inputObjectType({
name: "AddUserInput",
definition(t) {
sharedInputs({ t, nonNull: true });
},
});
const UpdateUserInput = inputObjectType({
name: "UpdateUserInput",
definition(t) {
field(t, "id", { nonNull: true, type: "ID" });
sharedInputs({ t, nonNull: false });
},
});
I don't think we will add that in nexus since there is a chance that input inheritance makes its way in the official graphql spec and that would require a breaking change in the library. But I will check with @tgriesser if can add that code in a "recipes" section of the doc.
This is part of the spec isn't it? https://spec.graphql.org/October2021/#sec-Input-Object-Extensions Would really love this :)
We all know nexus uses a functional programming approach to GraphQL Schemas. I think that's why too many people love it (Me included). But I think having some inheritance added to the inputObjectType can be amazing to have a smaller code.
Take this as an example:
To add a new user you need to have the username and password. And to update it you are required to have the id but the username and password are optionals.
This code can be shorten with some sort of partial inheritance.
This is a simple example but in real apps the list of properties can be longer and repeat the properties twice (1 for adding and 1 for updating) is just a lot of boiler plate code.