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
feat(types): add InferAttributes
utility type
#13909
Conversation
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.
Have some comments, but not on the content itself. I do not feel experienced enough on this to really comment on that
Woops, did not notice it was in draft still. So some of my comments might be obvious to you |
No no, thank you for the review :) I did parts of it in a hurry to go eat and didn't notice most of the things you mentioned |
Co-authored-by: Rik Smale <13023439+WikiRik@users.noreply.github.com>
The mapped types I'm using in this PR require TS >= 4.1. We'd need to drop support for TS <= 4.1 to merge this. I'm on board with it. It's more than a year old now. The question is: do we want to consider that a semver major or minor? |
We had this discussion in another PR as well and I had difficulty with finding a good support plan for TypeScript versions. One that I did find was DefinitelyTyped which tests on TypeScript versions that are less than 2 years old. But if you find other examples I would be fine with shortening the support window. Since TypeScript releases come out approximately every three months and the minority of users use TypeScript, I would make this a semver minor change. |
We can use |
Before we merge this, I'm also thinkings of ways to solve the I have a solution that uses a type as branding to mark attributes as optional in TCreationAttributes: It's a bit hacky but so far it works. If we implement this as well as #14302, in v7 we should be able to simplify the Model definition to simply: class User extends Model<User> {}
It's implemented like this:declare const CreationAttributeBrand: unique symbol;
type CreationAttribute = { [CreationAttributeBrand]: true };
export type CreationOptional<T> = T & CreationAttribute;
export type CreationAttributesOf<M extends Model, Excluded extends string = ''> = {
[Key in keyof M as
Key extends `_${string}` ? never
: M[Key] extends Fn ? never
: Key extends keyof Model ? never
: Key extends Excluded ? never
: Key
]: M[Key] extends CreationOptional<infer Val> ? (Val | undefined)
: M[Key]
}; and the definition of type UndefinedPropertiesOf<T> = {
[P in keyof T]-?: undefined extends T[P] ? P : never
}[keyof T];
type OptionalUndefined<T extends object> = Optional<T, UndefinedPropertiesOf<T>>;
class Model {
public static create<
M extends Model,
O extends CreateOptions<M['_attributes']> = CreateOptions<M['_attributes']>
>(
this: ModelStatic<M>,
values?: OptionalUndefined<M['_creationAttributes']>,
options?: O
): Promise<O extends { returning: false } | { ignoreDuplicates: true } ? void : M>;
} |
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.
Not an issue with this PR, but I think these associations are a bit too complex - its very easy to forget some.
works like `AttributesOf` but fields tagged as `CreationOptional` will accept undefined
I've added a solution for creation attributes, but I went with a different direction than the branded type as it was causing issues. Instead I went with a second option for class User extends Model<
AttributesOf<User, { omit: 'projects' }>,
AttributesOf<User, { omit: 'projects', optional: 'id' }>
> {
declare id: number;
declare name: string;
declare projects?: Project[];
} Of course this double definition is due to backward compatibility requirements in v6. In v7, I'll make it look like this instead: class User extends Model<User, { omit: 'projects', creationOptional: 'id' }> {
declare id: number;
declare name: string;
declare projects?: Project[];
} Unless we find a solution for branded types |
Oh no, I found a way to make a branded type that works. I'll revert some things. Updated way of doing this is therefore back to: class User extends Model<AttributesOf<User, { omit: 'projects' }>, CreationAttributesOf<User, { omit: 'projects' }>> {
declare id: CreationOptional<number>;
declare name: string;
declare projects?: Project[];
} I've also added class User extends Model<AttributesOf<User>, CreationAttributesOf<User>> {
declare id: CreationOptional<number>;
declare name: string;
declare projects?: NonAttribute<Project[]>;
} |
Well that's annoying, esdoc* fails on numeric separators & optional catch bindings. I'm not sure why it didn't complain about it during the eslint PR. I'll revert these changes and we'll do them again once #13914 is finalized |
esbuild or esdoc? |
esdoc* :) |
I think I figured what the issue was. It's not related to the new linting rules. It just did not like seeing HTML comments in a markdown file. |
I think this is good to go now, just need a review 🙂 |
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.
Looks good. Few small remarks. I want to wait for the approval of @allawesome497 before we merge
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.
looks fine to me
*/ | ||
_attributes: TModelAttributes; | ||
_attributes: TModelAttributes; // TODO [>6]: make this a non-exported symbol (same as the one in model.d.ts) |
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.
Maybe make this private
? (same with the other places where this comment is)
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 can't yet, it would break user code, but I can in v7
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.
Maybe add that to the TODO as well?
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.
Now that I think of it, if it's private
, Attribute<M>
will not be able to access it. It's going to have to be a public internal Symbol I think
@WikiRik small documentation update: I've added the v6 expected release version, fixed the documentation using the old utility types, and documented If there are no issues with these changes, we can merge |
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.
New changes look good so we can merge (possibly after the TODOs have been updated, but I can always approve again)
Co-authored-by: Rik Smale <13023439+WikiRik@users.noreply.github.com>
🎉 This PR is included in version 7.0.0-alpha.6 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Co-authored-by: Rik Smale <13023439+WikiRik@users.noreply.github.com>
Co-authored-by: Rik Smale <13023439+WikiRik@users.noreply.github.com>
Pull Request Checklist
npm run test
ornpm run test-DIALECT
pass with this change (including linting)?Description Of Change
This PR introduces a few new utility types:
InferAttributes<M, opts>
: Used to drastically reduce the boilerplate needed to define Model Attributes in the class-based approach.InferCreationAttributes<M, opts>
: LikeInferAttributes
but fields tagged withCreationOptional<x>
are optional.CreationOptional<x>
: A branded type used to mark an attribute as optional in Creation Attributes.NonAttribute<x>
: A branded type used to omit a property from the attribute list.Attributes<M>
: An alias forM['_attributes']
for public use. Also forward compatible with planned v7 changes.CreationAttributes<M>
: Same asAttributes
but types that acceptundefined
are alsooptional
.To reduce risk of collision between
Model
and its subclasses,Model#_attributes
andModel#_creationAttributes
have been deprecated (I'd like to replace them with symbols in v7). UseAttributes<Model>
andCreationAttributes<Model>
instead.In v7, we should replace these two with non-exported unique symbols.