-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Type safe creation of models #11820
Type safe creation of models #11820
Conversation
Codecov Report
@@ Coverage Diff @@
## master #11820 +/- ##
===========================================
+ Coverage 74.85% 96.34% +21.48%
===========================================
Files 86 95 +9
Lines 8373 9220 +847
===========================================
+ Hits 6268 8883 +2615
+ Misses 2105 337 -1768
Continue to review full report at Codecov.
|
@@ -648,6 +646,57 @@ export interface Silent { | |||
silent?: boolean; | |||
} | |||
|
|||
type FunctionKeys<T> = ({[P in keyof T]: T[P] extends Function ? P : never })[keyof T]; | |||
type ArrayType<T> = NonNullable<T> extends Array<infer R> ? R : any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure where to put these helper types. The custom Omit
type was defined in model.d.ts
as well, but it doesn't feel like the right place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to leave them here for now, we can worry about refactoring on another time
Hello, thank you very much for this PR! Are you testing that type safety is indeed being guaranteed? In other words, is there a test to catch what you mentioned? User.create({
anything: 'is allowed here' // :(
}); From a quick look, your PR does not seem to include a test that ensures a compilation error in the above case... Let me know if I'm mistaken, thanks! |
Hey @papb, you're right, there is no test verifying this behavior. Unfortunately it seems so that the test setup only allows positive testing. Since just the tsc cli is used to check the typings, a compiler error would cause to fail this kind of tests, but this is what we would expect from a negative test here. Or probably I’ve overseen something? I could offer to create another PR which allows such test cases by using something like https://www.npmjs.com/package/tsd or something similar - but we should discuss this before - maybe in another issue(?) |
Shouldn't this be done for |
@RobinBuschmann excellent idea, please see #11829 |
Probably a good idea! However should be done in another PR I think - PR welcome :) |
@@ -1,6 +1,6 @@ | |||
import { Association, DataTypes, HasOne, Model, Sequelize } from 'sequelize'; | |||
|
|||
class MyModel extends Model { | |||
class MyModel extends Model<MyModel> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for this? Those T
s in Model<T = any, T2 = any>
doesn't seem to be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first generic type is used with this PR in order to get autocompletion for new MyModel(/* here */)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RobinBuschmann ah, so this means that I'm wrong here? good to know, please mention it on that PR so we don't forget about that, thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RobinBuschmann Oh, I see. For me, it is very confusing that it takes class that is being created on the same line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GuskiS I know what you mean, but unfortunately it isn't possible to use this
for the constructor params (TS2526: A 'this' type is available only in a non-static member of a class or interface.
), which makes sense, since the type of this
isn't determined at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, can't we do something like this? https://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgLIE9UHsAmEA2AgmGFMAEYCukAzsgN4BQyywOAXMiJQLbnQBuZskgAPMJxqlQAcyEBfRowT44NOgCE1EbHnwAeACoA+BksXLV6tJlwFkEcRBA5N23QX0YPREmSq0psA8AA74EDzOYHTedr7SARB0TIzyQA
This way you won't have to remove the model stuff from the type.
MyModelAttributes
interface could be reused for other parts of the codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @GuskiS, yes this is an option. But then you would need to define interfaces for every model. For each interface it is very likely that it is only implemented by one class and you would have to define the properties one more time on the model itself. So inferring the value types of the model class itself is much more comfortable for the consumer of this library. And in addition nothing prevents you from defining an interface anyway. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, that makes sense.
However, in my app we still create that interface because we are using it to define express server's request body type. Using the Model type in this is would be incorrect.
Anyways, that was just a thought and an example :) Waiting for this get merged 👼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps both are achievable? Default the attributes to ModelValues<M>
, but allow them to be overridden using the pattern @GuskiS is proposing?
Greetings from #11457! 👋 I believe there's some crossover with the discussion that was happening there and the work that's been done here. Taking a peek at what's been committed so far, it appears that the original issue I opened my PR for (type safety for the create mixin methods) isn't being covered here yet, it should be relatively trivial to add support for those with the work that's being done here. @RobinBuschmann Let me know if you want any help with any of this. Totally willing to help test this against one or two of my codebases as well if you need some external validation. 🍻 |
@todd Thank you 😬 You and @RobinBuschmann please feel free to ping me if you need something to make this PR move forward :) |
@todd Thanks for pointing this out. I think we should focus on the static model functions here and create a new PR for the mixins afterwards. What do you think? @papb Sry, for the late reply.
It is still blocked by #11829 :( |
@RobinBuschmann Sounds good to me. More than happy to tackle that when the time is right. In the mean time, let me know if I can be of any help here. |
Howdy folks - any update on what's happening here? Anything I can pick up? |
@RobinBuschmann Roger that. Let me know when I can be useful. |
@@ -0,0 +1,22 @@ | |||
import {User} from './models/User'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only test file which includes enhanced testing. Which means, that it tests
- if type inference works
- for just the target model values
- for included models
- for included models where included models is an array
- if it throws in case of
- wrong property name in target model values
- right property name but wrong type
- wrong property name in included models
- wrong property name in included models, where included models is an array.
Since all other functions like findOrBuild
, bulkBuild
and so on are using the same type, which is ModelValues <M>
, they mostly testing if type inference work and if it throws an error if anything is wrong.
@papb @SimonSchick Is this an issue |
@SimonSchick / @eseliger / @papb Please merge this PR if it looks OK (Typescript stuff) |
Hi guys, sorry to bother. Any update on this? |
@SimonSchick @papb Hi guys, is this PR ready to move forward ? Really looking forward this typing inference. Thanks ! |
I guess it's conflicted now: #12405 I don't understand though, it looks like the other PR was created 4 days ago and has already been merged and released. Looks like it delivers the same stuff as this one which was opened in January and left to stagnate with no interest |
Hi! I'm the author of the other PR (#12405) - I aimed to replicate not only the type-safe creation of models but also type-safe querying (find, insert, update) as much as possible. As a case study, I managed to update my Sequelize 4 code (for https://wanderlog.com) to the new set of typings with purely search/replace (with some regexps), and about an hour or so of cleanup. This is because the new typings also use the 2 same generic types as the Sequelize v4 typings: |
I'll close this PR as it looks like #12405 resolved the same issue. If there is anything in this PR that isn't covered by the current version of the codebase, could you either open a new PR with those changes or update this PR (I'll reopen it). |
Pull Request check-list
Please make sure to review and check all of these items:
npm run test
ornpm run test-DIALECT
pass with this change (including linting)?Motivation
Currently there is no way to create model instances type safely. So no matter what properties are defined on model, for every object which gets passed to
build
,create
and so on the compiler doesn't complain:Description of change
This PR changes this by implementing the types
ModelAttributeKeys<M>
andModelValues<M>
. (So this PR only includes changes of typings)ModelAttributeKeys<M>
creates a union type ofM
(a class type extendingModel
), which includes all keys that are likely to be attribute keys. This is done by removing all function keys ofM
and all keys of base modelModel
ModelValues<M>
usesModelAttributeKeys<M>
to create a new type which includes all model attribute properties.Omit
type, since it is already built in with used TypeScript version)Examples
If a new instance of
User
is created now we're getting autocompletion and type safety:This also works for included models:
For instantiation with
new
, generic type ofModel
need to be set explicitly:class User extends Model<User> { ... }
:fixes sequelize/sequelize-typescript#726
fixes sequelize/sequelize-typescript#728