Skip to content
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

question: not working across node.js modules #132

Closed
mjflan opened this issue Oct 24, 2017 · 32 comments · Fixed by #335
Closed

question: not working across node.js modules #132

mjflan opened this issue Oct 24, 2017 · 32 comments · Fixed by #335
Assignees
Labels
type: question Questions about the usage of the library.

Comments

@mjflan
Copy link

mjflan commented Oct 24, 2017

We have a project with multiple node.js modules. Several of them use class-validator -- but they don't work together. Each module gets its own instance of the class-validator where the decorators are stored, so they can't share common class validation. I've tried the useContainer method, but that doesn't resolve the issue.

We need some sort of solution to make all the class-validator module instances work together.

@kalyuk
Copy link

kalyuk commented Dec 25, 2017

@mjflan I decided to do this by exporting the class-validator from the general module

import { BaseEntity } from 'typeorm';
import { validate, ValidatorOptions } from 'class-validator';

export * from 'class-validator';
export class BaseModel extends BaseEntity {
  public async validate(options?: ValidatorOptions) {
    const errors = await validate(this, options);
    return errors.map(({constraints, property}) => ({
      message: constraints[Object.keys(constraints)[0]],
      field: property
    }));
  }
}

and after that I import directly from my module

@NoNameProvided NoNameProvided added the type: feature Issues related to new features. label Jan 8, 2018
@NoNameProvided
Copy link
Member

Hi @mjflan!

I agree with you, we need to handle this.

Just to be sure I understand you correctly. Are you using the very same classes across your modules? (eg from a shared models module or something?)

@mjflan
Copy link
Author

mjflan commented Jan 8, 2018

Yes, exactly. We have some common classes in a shared module; e.g. data sent from one module to another or common DB schemas. The class validator in the main module does not share data with the class validator in the common module, so we have to distribute the validation calls into the objects to be validated.

We are working in typescript, so:

// Abstracted common object
import { IsString, validate as validateImpl } from "class-validator";
export interface IValidated { validate(): void; }
export class Obj implements IValidated {
    @IsString()
    public element: string = undefined;
    public async validate() { await validateWith(this, validateImpl);
}

// Abstracted utility method to run the given validator
// Throws and error if validation has errors
async function validateWith(data: Object, validator: (obj:Object, opts: ValidatorOptions) => Promise<any[]>) {
    const options: ValidatorOptions = { };
    let errors = await validator(data, options);
    if (errors.length > 0) {
        throw new Error(makeMessage(errors));
    }
}

and then we explicitly call validate on instances of this object. This works, but it requires us to explicitly add a validation method to each object and invoke validation on each imported type. If we forget, no validation.

@diegovilar
Copy link

diegovilar commented Jan 25, 2018

You said you have multiple packages that depend on class-validator, so I guess your dependency manager is not flattening completely or at all the dependency tree across the packages, and you probably end up with multiple copies of the class-validator package. Since its default container is not a true singleton (not the same instance across different class-validator installations) and, also, the getFromContainer function only accepts the class type itself as a key (not gonna be the same across different installations), you always end up with different instances of MetadataStorage across your packages.

class-validator asks the container for the instance of MetadataStorage to use, so to fix this we must make sure we always get a reference to the same instance, even across multiple installations. That can be accomplished by making the container holder a true singleton and also extending the container to work with Symbol and/or strings to query for instances it holds.

@NoNameProvided
Copy link
Member

Can you verify please that you use the very same version of class-validator in both places?

@NoNameProvided NoNameProvided added type: question Questions about the usage of the library. and removed type: feature Issues related to new features. labels Mar 17, 2018
@NoNameProvided
Copy link
Member

Closing this, as no response from the author.

@mjflan
Copy link
Author

mjflan commented Mar 19, 2018 via email

@MichalLytek
Copy link
Contributor

@NoNameProvided
I've discussed it with pleerock, he said:

probably we can make here same way as I did it in typeorm - attach everything to global node variable and doesn’t bind this functionality with container somehow

https://gitter.im/pleerock/class-validator?at=58b9c09621d548df2c886e93

@MichalLytek MichalLytek reopened this Mar 19, 2018
@NoNameProvided NoNameProvided self-assigned this Mar 19, 2018
@NoNameProvided
Copy link
Member

I am not a fan of any global objects. We will find some other way.

@NoNameProvided
Copy link
Member

In the meantime @mjflan can you confirm that you use the same class-validator version in both places? NPM should flatten them into one package.

@MichalLytek
Copy link
Contributor

@NoNameProvided flattening is a workaround, not the solution.
What if you have a separate module that store the shared (between frontend and backend) code, like DTOs with class-validator decorators?

@NoNameProvided
Copy link
Member

flattening is a workaround

Flattering is a solution, you should use the same class-validator version across modules when using it (both on backend and frontend) to prevent unexpected behavior.

What if you have a separate module that store the shared

Importing from the same version will import the same class-validator instance. So the OP way of usage should work already when using the same version across modules.

@mjflan
Copy link
Author

mjflan commented Mar 19, 2018 via email

@MichalLytek
Copy link
Contributor

Flattering is a solution, you should use the same class-validator version across modules when using it (both on backend and frontend) to prevent unexpected behavior.

So you are forcing users to do monorepo just because you don't like global variable for storying metadata (like TypeORM does)? Seems very nice 😃

@NoNameProvided
Copy link
Member

NoNameProvided commented Mar 19, 2018

I am not saying to put all of his code into one repo, I am saying to import the same class-validator version in all his repos, so when he uses them together they will use the same instance of class-validator so the metadata will be shared.

@MichalLytek
Copy link
Contributor

Nope. When you require the class, it uses local node_modules, so during development with npm link you will have this problems with class-validator. Been there, done that 😉
I've used container with weird initialization (order of require) to make it work but global metadata storage would solve all the issues.

@NoNameProvided
Copy link
Member

He has a my-custom-module private npm package, he wants to use inside his main project-module.

If both package requires the same version then npm will flattern it into this:

node_modulues/
  class-validator@x.x.x
  my-custom-module
src
  project-module

Since there will be one version/instance of class-validator it should work.

@NoNameProvided
Copy link
Member

@mjflan Some possible culprit and an example has been added to #181 (comment)

I will close this also as solved, please let me know if you have any more questions.

@MichalLytek
Copy link
Contributor

@NoNameProvided This still doesn't solve the problem with npm link when local node_modules are used instead of the project one. TypeORM doesn't have this problem as it attaches metadata storage to global scope:
https://github.com/typeorm/typeorm/blob/ebf4809544d200fc3914f53f59ab11b9bc4e8bb8/src/index.ts#L146

we should store metadata storage in a global variable otherwise it brings too much problems one of the problem is that if any entity (or any other) will be imported before consumer will call useContainer method with his own container implementation, that entity will be registered in the old old container (default one post probably) and consumer will his entity. calling useContainer before he imports any entity (or any other) is not always convenient. another reason is that when we run migrations typeorm is being called from a global package and it may load entities which register decorators in typeorm of local package this leads to impossibility of usage of entities in migrations and cli related operations

You can call pleerock a fool or implement this solution here as he adviced 😉

@MichalLytek MichalLytek reopened this Mar 23, 2018
@NoNameProvided
Copy link
Member

NoNameProvided commented Mar 23, 2018

It should work with npm link as well. As it's the "same" as having them directly in the node_modules folder from the running code perspective.

You can call pleerock a fool or implement this solution here as he adviced

He isn't a fool because we are not agreeing on something.

@MichalLytek
Copy link
Contributor

As it's the "same" as having them directly in the node_modules folder from the running code perspective.
Nope, by npm link i mean you have a folder with the package that has package.json and own node_modules folder. So as require looks for the nearest node_modules, it use the local one, not the project one.

The only workaround is to register the container in class-validator before we import decorated classes and use the same container instance across all modules which is really a pain and sometimes not possible (e.g. why Nest.js app should use also TypeDI when it has own dependency injector built-in).

@shusson
Copy link

shusson commented Apr 30, 2018

@NoNameProvided I just encountered this issue and burned a few hours trying to figure out why the validators were not working. In the end we were able to flatten our dependencies so that we were using only one class-validator. Are there any plans to make sure this stops happening accidentally? Is it possible to log warnings if there are multiple containers in use?

@NoNameProvided
Copy link
Member

I don't know about any way of detecting if a dependency is not flattened. We can add a warning tho, when there are no registered validators. (aka it has been added to another instance.)

@NoNameProvided NoNameProvided reopened this May 1, 2018
@NoNameProvided NoNameProvided added the type: feature Issues related to new features. label May 1, 2018
@NoNameProvided
Copy link
Member

Added a quick console.warn but will figure out something proper for this.

@ert78gb
Copy link

ert78gb commented May 5, 2018

We have the same issue, but we have a few helper functions to the validation so I created a new package my-validator that contains our helper functions and reexport the class-validator it is a little bit ugly solution but it guaranteed we use the same version of the class-validator in everywhere.

@pabl-o-ce
Copy link

pabl-o-ce commented Oct 24, 2018

Situation:
Im using a file for a custom validation and decorator, but for get TypeORM getManager I get a error of ConnectionNotFoundError.

import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
import { EntityManager, getManager } from 'typeorm';
import { User } from './';

@ValidatorConstraint({ async: true })
class IsUsernameAlreadyExistConstraint implements ValidatorConstraintInterface {

    validate (username: any, args: ValidationArguments): Promise<boolean> {
        const manager = getManager();
        return new Promise(async (resolve, reject) => {
            try {
                const user = await manager.createQueryBuilder(User, 'User')
                .where('User.username = :username', { username })
                .getOne();
                resolve((user) ? false : true);
            } catch (e) {
                reject(e);
            }
        });
    }

    defaultMessage (args: ValidationArguments) {
        return '💩';
    }

}

const  IsUsernameAlreadyExist = (validationOptions?: ValidationOptions) => {
    return (object: Object, propertyName: string) => {
         registerDecorator({
             target: object.constructor,
             propertyName,
             options: validationOptions,
             constraints: [],
             validator: IsUsernameAlreadyExistConstraint,
         });
    };
};

export { IsUsernameAlreadyExistConstraint, IsUsernameAlreadyExist };

what can be the posible solution for get typeorm connection using class-validator?

because I resolve separately without using validator class (but I think the idea is to use this custom decoration for validate). If any one have the idea to get connection It will be nice to hear about it.

@danloiterton
Copy link

@NoNameProvided, this is still an issue for me, and my experience lines up with @19majkel94's observations.

My NestJS API seems to use multiple instances of class-validator if I symlink to a module that also imports class-validator. Despite both packages using the same version. And it still occurs even if I try to export and re-use the class-validator instance from the module and remove it from the API's direct dependencies. Presumably NestJS references its own instance somewhere under the hood?

Please see #261 (comment) for more details.

I'm not sure how to work around this?

@JVMartin
Copy link

Great.. I now have to abandon class-validator because it literally cannot be used with any npm link workflows.

What is the point of a package meant to validate data, if you literally can't share the validators between the client, SDK, and the server, etc.?

Everyone developing packages with npm link the proper way will not be able to use this package.

@MichalLytek
Copy link
Contributor

MichalLytek commented Jan 11, 2020

Who use the broken npm link feature in 2020? Maybe it's time to move on to lerna with deps hoisting? Or at least replace npm link with https://github.com/mweststrate/relative-deps.

@zaro
Copy link

zaro commented Jan 20, 2020

since I have the same problem, one way to workaround the broken npm link is to use yarn workspaces instead. It flattens correctly, and you get all the benefit from npm link.

And btw, this is way more generic issue, as I had the problem with class-validator, class-transformer and with typeorm Generated decorator.

vlapo pushed a commit that referenced this issue Mar 24, 2020
@vlapo
Copy link
Contributor

vlapo commented Mar 24, 2020

Happy testing https://www.npmjs.com/package/class-validator/v/0.12.0-rc.0. Please give me a feedback.

@lock
Copy link

lock bot commented Apr 2, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Apr 2, 2020
@NoNameProvided NoNameProvided removed the type: feature Issues related to new features. label Aug 8, 2020
@NoNameProvided NoNameProvided changed the title Not working across node.js modules question: not working across node.js modules Aug 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: question Questions about the usage of the library.
Development

Successfully merging a pull request may close this issue.