-
Notifications
You must be signed in to change notification settings - Fork 4
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
Reuse @constraint directive for adding entity field validations? #1
Comments
Thx. So first off I'm not an expert on TypeORM at all so take this as you will. As far as I can tell TypeORM does not handle validation at all, they offload that to a separate library: http://typeorm.io/#/validation In that document it seems to show that they don't even require validation even if you choose to decorate your fields it appears you still have to manually opt in on all transactions (or more realistically create your own method to enforce that behavior). So keeping that in mind it would probably be pretty simple to do exactly what you are asking if you are willing to accept the need for creating your own validation enforcing method which it seems was the direction you were suggesting. Without double checking to the library that builds out that schema object from your example I think your code would need a very slight modification. Your code as is just returns all fields that are either Primary OR have a directive called constraints applied. You probably want to just return the fields that have the constraint. I'm more than happy to post the code if you'd like assuming I'm not missunderstanding something. To wire it all up should be pretty straight forward from there. The library they suggest in their docs is actually just a decorator wrapper around https://github.com/chriso/validator.js but to be honest at this point seeing how type orm doesn't really enforce this the exact location in the pipeline that you would want to enforce that is debatable. Personally since it seems Type ORM doesn't enforce those validations I'd probably go at the resolvers directly either with middleware or a wrapper around the auto generated resolvers either way would be simple to incorporate and achieve the same thing. |
I also just realizing that the library you posted probably does all that for you already. So since typeorm doesn't seem to handle stuff directly as near as I can tell and it seems I just suggested what you library already does I'm not sure exactly a best course of action for that. |
Thanks. I'm just trying to understand your code and see if I can built on it. I'd like to generate the following kind of output, however I can't seem to find any template to generate an Entity. @Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
@Length(10, 20)
title: string;
///...
} What I have found so far: export const getEntitySchemas = schema =>
Object.keys(getEntities(schema)).map(
entityKey => getEntitySchema(entityKey, schema[entityKey]),
[]
); Which is used in const connection = createConnection({
// ...
entities: getEntitySchemas(jsSchema).map(schema => new EntitySchema(schema)), Looking at
So I don't quite see how that works with the nested map you are returning and how to incorporate the validation decorators? To get the directive values, I was thinking something like this: /** @type {function(Field) => { Directive } */
export const getFieldConstraints = path(["directives", consts.CONSTRAINT]); Then in schema: const columns = getColumns(type);
return {
name: type.directives.Entity.name || name,
columns: Object.keys(columns).reduce((columns, fieldName) => {
const field = type.fields[fieldName];
const constraints = getFieldConstraints(field);
return {
...columns,
[fieldName]: {
constraints,
...field.directives,
primary: isPrimary(fieldName, type), But now I see you are already spreading Hmm... would be nice to add the class validator using these decorators on update and create mutations. Something like the following (optional or configurable, ideally) create: async (input: any) => {
// lookup class name
const Entity = entities[type]
// create instance of class from input object
const entityInstance = new Entity(input)
// validate instance
const errors = await validate(entityInstance)
// save instance if no errors, otherwise signal validation error (both pluggable handlers)
!errors ? save(post) : validationError(Entity, input, errors)
} |
Ah, looks like some docs in this issue // Post.json
{
"entity": "./Domain/Post.js"
"name": "Post",
"table": {
"name": "post"
},
"columns": {
"id": {
"type": "int",
"primary": true,
"generated": true
},
"title": {
"type": "varchar"
},
"text": {
"type": "varchar"
}
}
} Can be used like this: entitySchemas: [
Object.assign({ target: Post }, require(__dirname + "/Schemas/Post.json"))
] Or with entities as you have done. Interesting! But still not entirely clear if this supports the validation decorators as well. |
Originally when I wrote this I was building templates for the Models but then I found they support schema objects instead which opened it up to both ts and js and honestly made the code a ton simpler since I wasn't having to template at all I was just building an object. Since your original post I checked if they support validation at all in their schemas directly and it doesn't seem so which makes a ton of sense since they don't even directly support it in their own Model classes they just point out that you can use it side by side and direct call it before an insertion if you wish. The way the supporting library for this works is it absorbs any and all directives you write automatically hanging them in field.directives so if you add @constraint to your schema it will be auto picked up in the schema object that is built you can just read it out of the field however you wish. If that library you pointed out already checks all those validations before hitting mutations (queries too?) then that would be equivalent to their decorators since they require you to call validate(obj) manually if you want validation then if that directive library works as i assume it does you'd be doing the exact same thing just automatically. |
Thanks, but "The way the supporting library for this works..." what supporting library exactly? I first assumed it was come generic decorator builder, but then I would have to do some transformation into the schema object as well, so that:
length: [10, 20] Ideally automatically transformed to @Length(4, 20) Your thoughts? |
Sorry I ment to post this library https://github.com/jjwtay/graphSchemaToJson This libary currently uses that to read an graphql executable schema and convert it into plain old javascript object purely of data that includes ALL the directives and such. In the readme there are these lines for the example usage: import { schemaToJS } from 'graphschematojson' const jsSchema = schemaToJS(schema) Which is what I was referencing to and that object will have your field.directives.constraint whenever u apply it. Your comment about generated not exactly mapping 1-1 with PrimaryGeneratedColumn is correct and in src/entity.js the field generated is set but also the field primary. |
OK, I also found a few related issues for "Look at existing decorator - it just stores some metadata which is used later in ORM logic. You can use e.g. custom subscriber to act on yours metadata." export function UpdateDateColumn(options?: ColumnOptions): Function {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: "updateDate",
options: options ? options : {}
} as ColumnMetadataArgs);
};
} The export class MetadataArgsStorage {
// -------------------------------------------------------------------------
// Properties
// -------------------------------------------------------------------------
readonly tables: TableMetadataArgs[] = [];
readonly trees: TreeMetadataArgs[] = [];
readonly entityRepositories: EntityRepositoryMetadataArgs[] = [];
readonly transactionEntityManagers: TransactionEntityMetadataArgs[] = [];
readonly transactionRepositories: TransactionRepositoryMetadataArgs[] = [];
readonly namingStrategies: NamingStrategyMetadataArgs[] = [];
readonly entitySubscribers: EntitySubscriberMetadataArgs[] = [];
readonly indices: IndexMetadataArgs[] = [];
readonly uniques: UniqueMetadataArgs[] = [];
readonly checks: CheckMetadataArgs[] = [];
readonly columns: ColumnMetadataArgs[] = [];
readonly generations: GeneratedMetadataArgs[] = [];
readonly relations: RelationMetadataArgs[] = [];
readonly joinColumns: JoinColumnMetadataArgs[] = [];
readonly joinTables: JoinTableMetadataArgs[] = [];
readonly entityListeners: EntityListenerMetadataArgs[] = [];
readonly relationCounts: RelationCountMetadataArgs[] = [];
readonly relationIds: RelationIdMetadataArgs[] = [];
readonly embeddeds: EmbeddedMetadataArgs[] = [];
readonly inheritances: InheritanceMetadataArgs[] = [];
readonly discriminatorValues: DiscriminatorValueMetadataArgs[] = [];
//...
} The mechanics are starting to make sense now. I understand how to go from decorator to metadata, such as {
// ...
generated: true,
primary: true
} However not clear how it transforms/loads and parses this metadata to create the decorators or |
I now found the export class EntityMetadataBuilder {
/**
* Builds a complete entity metadatas for the given entity classes.
*/
build(entityClasses?: Function[]): EntityMetadata[] { |
Hmm, I looked into const args: ValidationMetadataArgs = {
type: ValidationTypes.WHITELIST,
target: object.constructor,
propertyName: propertyName,
validationOptions: validationOptions
};
getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); So if I cold just populate |
Great project!!
Curious if we could reuse the
@constraint
directive graphql-constraint-directive for adding validation constraints to the generated TypeORM entities as well. Could be awesome! Write once, "validate everywhere" ;)What would it take?
Looking at:
I would assume it requires adding a similar function
And then incorporating the constraints in the output (templates for query and mutation?)
No experience with
ramda
however, so a bit hard to follow that code...Cheers
The text was updated successfully, but these errors were encountered: