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.
@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
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?)
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.
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.
Can you verify please that you use the very same version of class-validator in both places?
Closing this, as no response from the author.
Sir,
Hmmm, I thought I had responded to this issue using my work address.
Perhaps that failed, somehow; I used github before I took my current job so
email management has been ... interesting. That, plus my wife's neck
surgery and my rotator cuff surgery has delayed my email responses this
year.
In answer to your original question, yes, we are using the same classes
across modules in a node.js environment. We have a common set of objects to
validate used across several products, a product-general set of objects to
validate within the product, and specific services sharing the common and
product-general objects. We have currently made this work by explicitly
coding calls to each object's validator and maintenance has been costing us
some time.
Sorry for the delay and disruption,
--Mark Flanagan
On Sat, Mar 17, 2018 at 12:37 PM, Attila Oláh notifications@github.com
wrote:
Closed #132 https://github.com/typestack/class-validator/issues/132.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/typestack/class-validator/issues/132#event-1526905124,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AT0DrYb7eHpOP4l1HlkA-zcBYUWzWYzXks5tfTvdgaJpZM4QEjU7
.
@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
I am not a fan of any global objects. We will find some other way.
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.
@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?
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.
We recently updated to NPM 5 which seems to do the node_module flattening.
We will retest.
Thanks!
On Mon, Mar 19, 2018 at 4:07 PM, Attila Oláh notifications@github.com
wrote:
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.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/typestack/class-validator/issues/132#issuecomment-374353825,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AT0DrT91oVBR9fW3KDZY685rdpIGNh6jks5tgA_wgaJpZM4QEjU7
.
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 😃
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.
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.
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/
[email protected]
my-custom-module
src
project-module
Since there will be one version/instance of class-validator it should work.
@mjflan Some possible culprit and an example has been added to https://github.com/typestack/class-validator/issues/181#issuecomment-375466913
I will close this also as solved, please let me know if you have any more questions.
@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 😉
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.
As it's the "same" as having them directly in the node_modules folder from the running code perspective.
Nope, bynpm linki mean you have a folder with the package that has package.json and own node_modules folder. So asrequirelooks for the nearestnode_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).
@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?
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.)
Added a quick console.warn but will figure out something proper for this.
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.
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.
@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 https://github.com/typestack/class-validator/issues/261#issuecomment-516427490 for more details.
I'm not sure how to work around this?
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.
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.
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.
Happy testing https://www.npmjs.com/package/class-validator/v/0.12.0-rc.0. Please give me a feedback.
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.
Most helpful comment
@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-validatordecorators?