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
How to fix the incompatibility with Public Class Fields #14300
Comments
We can resolve the issue by adding this piece of code at the end of // If the user declared class properties with the same name as one of the model's attributes (which happens due to typing and decorators),
// that class property will shadow the attribute's getter/setter (which lives on the prototype).
// This deletes that class property.
for (const attributeName of this.modelDefinition.attributes.keys()) {
delete instance[attributeName];
} This means I'll open a PR once #15431 has been merged About the deprecation noticeℹ️ The deprecation notice added in Sequelize 7 points to this comment. If you don't understand why you're getting the notice, you're likely in one of the following two situations: Option 1: You're instantiating your model directlySay you have a model called Option 2: You have a custom constructor on your modelSequelize checks that you're not using instantiating your model directly by passing a secret to the constructor parameter. If you want to write a custom constructor in your model, make sure to pass the third parameter to the parent constructor, like this: class User extends Model {
constructor(values, options, secret) {
super(values, options, secret);
// your custom constructor logic
}
} |
Yet another RFC because this would be a large change. Promise at some point I'll start writing actual code instead of just describing how it could work
The Problem
There is an incompatibility in Sequelize between Public Class Fields & Sequelize attributes.
I've documented it in greater details here: #13861
Here are also a few issues I found that are caused by this:
"sequelize": "^6.3.4",
#12603To sum up the problem, if I do the following:
user.name
won't be equal to'zoé'
, it will beundefined
.This is because
User.init
addsget name()
&set name()
toUser.prototype
, which the public class field then shadows.The current solution
The only way, currently, to avoid the problem is to not add a public class field that share the same name as one of the model's attribute. In TypeScript this is done using
declare
on a public class field:transpiles to:
And, thankfully,
declare
is also compatible with decorators:transpiles to:
The unresolved Problem
Babel emits public class fields when they're decorated, even if they're tagged with
declare
. Meaning projects like sequelize-typescript break when using babel instead of tsc.As public class fields become more mainstream, and decorators move slowly towards standardization, it's very likely that transpilers will start outputting public class fields if they're decorated. At some point, decorators won't be transpiled at all.At that point, sequelize will break down.To be future-proof,We need to become compatible with public class fields.But how?
I've been searching for a solution, and so far the only I've found is: drop
Model#dataValues
, store data directly in the model.Instead of
We'd have
dataValues
is removed, all properties are at the top level.previousDataValues
&User
instead.#previousDataValues
) or using symbols (Symbol('uniqno')
).Detecting changes (
Model#save()
)As specified above, with the new system, model dirty-ness would be detected by comparing previous and current values, instead of detected by calling a setter.
For that end, we could either:
previousDataValues
would need to be a deep copy of current data values to avoid accidental mutations).A compare algorithm needs to support (will update as I discover them):
A deep diff would resolves issues like:
What about
Model#changed
It would still be there, as a mechanism to bypass the diff (force marking something as dirty when our deep equal implementation fails to detect it), but it would only be able to set it to true. As such I propose a method rename:
setChanged(key): this
: forces the model to consider an attribute as changedhasChanged(key): boolean
: returns whether the model considers the attribute changed (first by checking if it's force marked as dirty, then by doing a deep equal).Things to sort first
We're blocked by the TypeScript Migration. We should migrate the Model class before working on this.
Attributes can accept multiple different types. Making a deep equal test difficult.
DATE
can sometimes beDate
, sometimes bestring
.BIGINT
can sometimes benumber
, sometimesbigint
, sometimesstring
.We'll need to provide a mechanism to ensure the type is always the same, and the one the user expects. I've started thoughts on this here https://gist.github.com/ephys/50a6a05be7322c64645be716fea6186a#support-for-alternative-js-types
The text was updated successfully, but these errors were encountered: