[ ] Regression
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.
Now it's not possible to create a "partial" type from a GraphQL type; here with "partial type" I mean a type with all fields nullable.
I'd like to be able to create a partial version of an existing GraphQL type, that is a copy of that type with the only difference that all fields are nullable.
In my API I need to handle various complex entities with CRUD operations; specifically the input Create and Update operations are the same but for Create the fields are all mandatory and for Update they are all nullable (I may want to update only some of them).
I managed to get this in type-graphql by accessing the MetadataStorage and actually copying all the FieldMetadata with the nullable option set, but with the new LazyMetadataStorage this is not possible anymore.
Nest GraphQL version: 7.0.12
I managed to get this in type-graphql by accessing the MetadataStorage and actually copying all the FieldMetadata with the nullable option set
You've basically used a non-public API to accomplish this. I suppose there's a way to achieve something similar, but using a different technique (without accessing a private API). I'll think about how we can make it more straightforward for everyone.
Yes I did because I had no alternatives. Maybe this could be accomplished using directives?
@Carlovan I had the same issue, I had to do the following to get the fields loaded.
getGraphqlFieldsForType<T>(objType: Class<T>): PropertyMetadata[] | undefined {
LazyMetadataStorage.load([objType]);
TypeMetadataStorage.compile();
let graphqlObjType = this.getGraphqlObjectMetadata(objType);
if (!graphqlObjType) {
graphqlObjType = TypeMetadataStorage.getInputTypesMetadata().find(o => o.target === objType);
}
return graphqlObjType?.properties;
}
export function PartialType<T>(TClass: Class<T>): Class<Partial<T>> {
const graphQLFields = getMetadataStorage().getGraphqlFieldsForType(TClass);
if (!(graphQLFields && graphQLFields.length)) {
throw new Error(`Unable to find fields for graphql type ${TClass.name}`);
}
@ObjectType({ isAbstract: true })
abstract class PartialObjectType {}
graphQLFields.forEach(f => Field(f.typeFn, { ...f.options, nullable: true })(PartialObjectType.prototype, f.name));
return PartialObjectType as Class<Partial<T>>;
}
PartialType function has been added in 7.1.0 release.
We have also added OmitType and PickType functions which can mimic the behavior of built-in TypeScript types (respectively Omit and Pick).
Example:
@InputType()
class CreateUserInput {
@Field()
email: string;
@Field()
password: string;
@Field()
firstName: string
}
and to create the UpdateUserInput class based on the CreateUserInput, but with all properties marked as optional, use the following code:
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {}
If you want to exclude, let's say, the email property (because, for instance, email has to be unique and cannot be modified), compose OmitType and PartialType functions, as follows:
@InputType()
export class UpdateUserInput extends PartialType(OmitType(CreateUserInput, ['email'])) {}
@kamilmysliwiec thanks for the quick turn around on this. Unfortunately this doesnt work for my use case :(
In my use case someone can provide a ObjectType and I need to create a partial InputType from it. For a little more detail when a user provides a base DTO for CRUD operations if they do not provide a Create or Update DTO, I just create a partial InputType from the original DTO.
Here is what I had.
import { Class } from '@nestjs-query/core';
import { Field, ObjectType, InputType } from '@nestjs/graphql';
import { getMetadataStorage } from '../metadata';
export function PartialType<T>(TClass: Class<T>): Class<Partial<T>> {
const graphQLFields = getMetadataStorage().getGraphqlFieldsForType(TClass);
if (!(graphQLFields && graphQLFields.length)) {
throw new Error(`Unable to find fields for graphql type ${TClass.name}`);
}
@ObjectType({ isAbstract: true })
abstract class PartialObjectType {}
graphQLFields.forEach(f => Field(f.typeFn, { ...f.options, nullable: true })(PartialObjectType.prototype, f.name));
return PartialObjectType as Class<Partial<T>>;
}
export function PartialInputType<T>(TClass: Class<T>): Class<Partial<T>> {
// the getGraphqlFieldsForType uses the code snippet I provided earlier.
const graphQLFields = getMetadataStorage().getGraphqlFieldsForType(TClass);
if (!(graphQLFields && graphQLFields.length)) {
throw new Error(`Unable to find fields for graphql type ${TClass.name}`);
}
@InputType({ isAbstract: true })
class PartialInptType {}
graphQLFields.forEach(f => Field(f.typeFn, { ...f.options, nullable: true })(PartialInptType.prototype, f.name));
return PartialInptType as Class<Partial<T>>;
}
I see in your new implementation you preserve the original type decorator, and it find the fields just fine, however its annotated with @ObjectType({isAbstract: true}) and when the schema is generated in graphql-schema-factory.js my InputType is missing the fields.
Thank you again for the work you are putting into this!
In my use case someone can provide a ObjectType and I need to create a partial InputType from it.
In 7.1.2, all utility functions (PartialType, PickType, OmitType) will accept an additional decorator that lets you explicitly pass the decorator factory to be applied to the class.
Example (where User is an ObjectType):
@InputType()
export class CreateUserInput extends PartialType(User, InputType) {}
@kamilmysliwiec that was the exact solution I was thinking :)
I cant thank you enough for fixing this, your awesome!
@kamilmysliwiec Is there any way to allow partial types to also include fields from extended abstract types (which use type-graphql's @ObjectType({ isAbstract: true }) syntax)?
For example:
@ObjectType()
export class User extends BaseType {
@Field()
email: string
@Field()
name: string
}
```ts
@ObjectType({ isAbstract: true })
export abstract class BaseType {
@Field(type => ID)
id: string
@Field(type => GraphQLISODateTime)
createdAt: Date
@Field(type => GraphQLISODateTime)
updatedAt: Date
}
When creating a partial user input type, like so:
```ts
@InputType()
export class UserPartialInput extends PartialType(User, InputType) {}
The resulting partial input type will only include fields from User and not from the base BaseType class:
input UserPartialInput {
email: String
name: String
}
@amille14 fixed in 7.3.4 :)
This might be an issue of its own, but reading this conversation and the documentation on docs.nestjs.com gives two very different impressions regarding the decorator argument. On this thread I read it basically as "you should pass in the decorator you want the type to act as" where as the docs say pretty much the opposite, that you should provide the decorator that is being used. I might be misunderstanding something, but I thought it's worth bringing up nonetheless as this thread helped me solve an issue I couldn't quite grasp from reading the docs.
Quote from the docs that mislead me:
The PartialType() function takes an optional second argument that is a reference to the decorator factory of the type being extended. In the example above, we are extending CreateUserInput which is annotated with the @InputType() decorator. We didn't need to pass InputType as the second argument since it's the default value. If you want to extend a class decorated with @ObjectType, pass ObjectType as the second argument.
@dotellie docs are wrong. We're tracking the update here https://github.com/nestjs/docs.nestjs.com/pull/1167
am I right when I expected that field of partialType might be defined and be as null? f.e. when client send a variable with null? and I might to write it in db?
Most helpful comment
PartialTypefunction has been added in 7.1.0 release.We have also added
OmitTypeandPickTypefunctions which can mimic the behavior of built-in TypeScript types (respectivelyOmitandPick).Example:
and to create the
UpdateUserInputclass based on theCreateUserInput, but with all properties marked as optional, use the following code:If you want to exclude, let's say, the
emailproperty (because, for instance, email has to be unique and cannot be modified), composeOmitTypeandPartialTypefunctions, as follows: