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

feat(core): simplify entity definition and rework typings of FilterQuery #193

Merged
merged 1 commit into from Oct 10, 2019

Conversation

B4nan
Copy link
Member

@B4nan B4nan commented Oct 9, 2019

Now it is no longer needed to merge entities with IEntity interface, that was polluting entity's interface with internal methods.
New interfaces IdentifiedEntity<T>, UuidEntity<T> and MongoEntity<T> are introduced, that should be implemented by entities. They
are not adding any new properties or methods, keeping the entity's interface clean.

This also introduces new strictly typed FilterQuery<T> implementation based on information provided by those new interfaces (type of primary key
in particular). It is not used in query builder, as there string keys needs to be supported for aliasing to work ({ 'a.books': 1 }).

BREAKING CHANGES:
IEntity interface no longer has public methods like toJSON(), toObject() or init(). One can use wrap() method provided by ORM that
will enhance property type when needed with those methods (await wrap(book.author).init()). To keep all methods available on the
entity, you can still use interface merging with WrappedEntity<T, PK> that both extends IEntity<T, PK> and defines all those methods.
FilterQuery now does not allow using smart query operators. You can either cast your condition as any or use object syntax instead
(instead of { 'age:gte': 18 } use { age: { $gte: 18 } }).

Closes #124, #171

@B4nan B4nan added this to the 3.0 milestone Oct 9, 2019
@B4nan
Copy link
Member Author

B4nan commented Oct 9, 2019

Example of entity definition in SQL drivers (integer AI primary key):

@Entity()
export class FooBar implements IdEntity<FooBar> {

  @PrimaryKey()
  id: number;

  @Property()
  name: string;

}

For mongo it is a bit more complicated as now both _id and id will need to be defined:

@Entity()
export class FooBar implements MongoEntity<FooBar> {

  @PrimaryKey()
  _id: ObjectId;

  @SerializedPrimaryKey()
  id: string;

  @Property()
  name: string;

}

@B4nan
Copy link
Member Author

B4nan commented Oct 9, 2019

Not yet fully polished, but would love to hear your feedback @lookfirst, @vimmerru, @pepakriz.

Available for testing via dev tag (3.0.0-dev.1).

@B4nan B4nan force-pushed the query-typing branch 2 times, most recently from 036bc40 to 732144c Compare October 9, 2019 22:22
@lookfirst
Copy link
Contributor

Damn, no wonder you've been so quiet recently. Good work!

@pepakriz
Copy link
Contributor

I have just a little experience with the orm code, but it looks very impressive!

@B4nan B4nan mentioned this pull request Oct 10, 2019
59 tasks
@thynson
Copy link

thynson commented Oct 10, 2019

Wow, you made it so quickly. Excellent work!

@lookfirst
Copy link
Contributor

What if I want an id: string?

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

What if I want an id: string?

There is still IEntity interface for that, where you specify what property is PK and its type will be used. There is also PrimaryKeyType symbol that can be used to make sure the type is correctly set in FilterQuery, but it should not be needed (at least with TS 3.6 it looks like that, I added that symbol when developing it on TS 3.5 but it might work there as well).

@Entity()
export class FooBar implements IEntity<Test2, 'id'> {

  @PrimaryKey()
  id: string;

}

Btw just found out there are still some issues with FilterQuery, it allows to put anything into property on the top level (nested conditions are correctly checked though):

const ok: FilterQuery<Author2> = { books: { author: { born: new Date() } } };
const fail: FilterQuery<Author2> = { books: { undef: { born: new Date() } } };
const shouldFail: FilterQuery<Author2> = { undef: { author: { born: new Date() } } };

@lookfirst
Copy link
Contributor

Yea, don't worry about supporting older versions of TS. Happy to stay current with that. Running 3.6.2 and may even switch to the 3.7 beta soon to get all the awesome ? improvements.

Re: IEntity, I must admit that it feels awkward to have to switch between two different interfaces and formats just because I'm using a string or number primary key.

As always, excellent work. Super appreciate.

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

Re: IEntity, I must admit that it feels awkward to have to switch between two different interfaces and formats just because I'm using a string or number primary key.

You can use IEntity for all entities if you want. Those extra interfaces are there just to simplify things, usually one will use the same type of PKs in all entities imho.

export type IEntity<T = any, PK extends keyof T = keyof T> = { [K in PK]: T[K] };
export type WrappedEntity<T, PK extends keyof T> = IWrappedEntity<T, PK> & IEntity<T, PK>;
export type IdentifiedEntity<T extends { id: number }> = IEntity<T, 'id'>;
export type UuidEntity<T extends { uuid: string }> = IEntity<T, 'uuid'>;
export type MongoEntity<T extends { _id: IPrimaryKey; id: string }> = IEntity<T, 'id' | '_id'>;

@lookfirst
Copy link
Contributor

Maybe IEntity should be AnyEntity and IdentifiedEntity should be NumericEntity

@lookfirst
Copy link
Contributor

Or NumberEntity

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

I picked IdentifiedEntity because of the requirement for id property... I think we could support both number and string there, important is to know the property name, its type can be inferred.

Not sure about AnyEntity, lets wait for other opinions, I am not against renaming it, I know the name is too generic (but - generic as the interface itself). It is used to define any entity, so maybe it could be good name.

@thynson
Copy link

thynson commented Oct 10, 2019

Why not

type IdentifiedEntity<T extends { id: R}, R=number> = IEntity<T, 'id'>;

so user has the ability to specify the type of primary key

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

That's pretty much what I just said in last comment :] Will make this happen, was thinking about it anyway...

I even think we could just have (other types of PK than string and number are not supported, mongo requires _id property so no need to take ObjectId into account here):

type IdentifiedEntity<T extends { id: number | string }> = IEntity<T, 'id'>;

@lookfirst
Copy link
Contributor

I am not a fan of prefixing interfaces with I, it is a cheapsauce naming technique. interfaces are the things you use, not just look at.

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

I am not a fan of prefixing interfaces with I, it is a cheapsauce naming technique. interfaces are the things you use, not just look at.

Me neither, but... naming is hard, you know :D

Let's call it AnyEntity unless someone proposes something better.

@lookfirst
Copy link
Contributor

Change...

export type IEntity<T = any, PK extends keyof T = keyof T> = { [K in PK]: T[K] };
export type IdentifiedEntity<T extends { id: number }> = IEntity<T, 'id'>;

into...

export type AnyEntity<T = any, PK extends keyof T = keyof T> = { [K in PK]: T[K] };
export type IdEntity<T extends { id: number | string }> = AnyEntity<T, 'id'>;

Seems sane and now is a good time given the breaking changes...

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

Ok done with the FilterQuery issue hopefully, also renamed IEntity and IdentifiedEntity to AnyEntity and IdEntity plus IdEntity now allows using both number and string PK.

Published as 3.0.0-dev.3.

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

Found one more small issue with FilterQuery: using operators with properties of Date type are failing (e.g. { born: { $gte: new Date('...') } }), but that should be hopefully easy to fix.

edit: Right, it was just wrong OperatorMap definition as $gte and other comparison operators can be used with more than just a number... released 3.0.0-dev.4 with the fix.

@pepakriz
Copy link
Contributor

I'm not sure about FilterQuery typing. The current behavior is unfriendly with private properties for example. I've been thinking about typing and I'd like to have cli command which generates TS types. This principle is used by prisma and it has one great advantage - you're not limited by TS. Generated types are simple as possible without TS magic which can break something every TS version. So I'd completely prefer this way.

@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

Well that was first approach I was thinking about, but I don't like the idea of generating them only via CLI, it would have to be somehow automated (which could work with enabled caching probably). Still current approach looks good to me, so I am not very keen to rewrite that... Will test it for couple more days in the wild, but so far it works good.

The current behavior is unfriendly with private properties for example.

Not sure if that is important, as private props are not really supported by the ORM (there is no mechanism that would call your setters instead of just assigning the value internally). Any other issue with current approach?

Generated types are simple as possible without TS magic

Still they would be pretty complex as well I think, although definitely less generic/more specific. I did not pay much attention to prisma yet, but from what I saw, they have more strict rules on the query type, e.g. they do not allow query by PK ({ author: { id: 1 } } instead of { author: 1 } ).

Let's see what others are thinking.

Edit: One more note - I don't wanna postpone v3 release for too long, but I am definitely open for discussion about this topic for future versions. That said, I think the current approach will be enough for v3.

@lookfirst
Copy link
Contributor

Example project updated, all tests pass. No complaints. =) After midnight here, so I'll do my main project tomorrow morning.

Now it is no longer needed to merge entities with IEntity interface, that was polluting entity's interface with internal methods.
New interfaces IdEntity<T>, UuidEntity<T> and MongoEntity<T> are introduced, that should be implemented by entities. They
are not adding any new properties or methods, keeping the entity's interface clean.

This also introduces new strictly typed FilterQuery<T> implementation based on information provided by those new interfaces.

BREAKING CHANGES:
IEntity has been renamed to AnyEntity and it no longer has public methods like toJSON(), toObject() or init(). One can use wrap() method provided by ORM that
will enhance property type when needed with those methods (`await wrap(book.author).init()`). To keep all methods available on the
entity, you can still use interface merging with WrappedEntity that both extends AnyEntity and defines all those methods.
FilterQuery now does not allow using smart query operators. You can either cast your condition as any or use object syntax instead
(instead of `{ 'age:gte': 18 }` use `{ age: { $gte: 18 } }`).

Closes #124, #171
@B4nan
Copy link
Member Author

B4nan commented Oct 10, 2019

Merging this now and closing both connected issues, but feel free to continue with discussion. For bugs please open new issues.

@B4nan B4nan merged commit a343763 into dev Oct 10, 2019
@B4nan B4nan deleted the query-typing branch October 10, 2019 22:19
B4nan added a commit that referenced this pull request Nov 20, 2019
* fix(core): disable auto flushing by default [BC] (#79)
* feat(core): use knex to generate sql + enable connection pooling [BC] (#76)
* fix(mapping): remove deprecated `fk` option from 1:m and m:1 decorators (#87)
* feat(mapping): allow overriding getClassName() in NamingStrategy (#88)
* chore: remove @Class jsdoc from tests as it does not work in WS anymore
* chore: use ObjectId instead of deprecated ObjectID in mongo
* feat(metadata): create instance of metadata instead of static one [BC] (#91)
* feat(hooks): add onInit hook fired after entity is created (#92)
* feat(core): add support for virtual property getters (#93)
* feat(sql): support multiple conditions in JOINs (#94)
* feat(schema): use knex in schema generator (#81)
* feat(schema): add basic entity generator (#98)
* chore: update roadmap
* fix(mapping): do not override user defined nullable value in m:1 and 1:1
* feat(schema): add basic schema update (#97)
* feat(cli): add basic CLI tool (#102)
* feat(core): improve logging - add namespaces, colors and highlighting (#109)
* refactor: improve handling of unknown types in entity/schema generators
* feat(mongo): improve query logging, use `inspect` instead of `stringify`
* feat(metadata): improve validation during metadata discovery
* fix(schema): prefer user-defined collection names in naming strategy
* feat(core): add support for read connections (#116)
* feat(core): add `Reference<T>` wrapper to allow improved type safety (#117)
* feat(drivers): add support for MariaDB (#120)
* docs: update docs for v3
* fix(mapping): remove obsolete parameter in UnderscoreNamingStrategy (#134)
* docs: fix typo in reference wrapper section (#137)
* docs: comment out comments (#141)
* refactor(core): propagate reference PK from entity instead of its clone
* feat(core): add findOneOrFail method to entity manager and repository (#142)
* refactor(mapping): allow passing Reference to Reference.create()
* feat(mapping): add NoopNamingStrategy
* refactor(cli): make MIKRO_ORM_CLI env variable highest priority
* refactor(validation): validate that entitiesDirs are not file globs
* feat(cli): add debug command to help with setting up the CLI
* docs: merge entity reference and reference wrapper sections
* chore: fix build
* docs: fix link to mysql integration tests (#147)
* refactor: rename NoopNamingStrategy to EntityCaseNamingStrategy
* refactor: allow replication in postgres
* refactor: add dependency versions to debug cli command output
* refactor: change sqlite dependency to sqlite3 as that is now used (knex)
* refactor: change sqlite dependency to sqlite3 in docs
* refactor: use globby to check for paths in cli debug command
* refactor: allow default export in cli-config
* docs: fix schema generator docs (get query methods vs run query methods)
* fix(core): always query inverse side of 1:1 association
* feat(validation): warn when failing to get metadata of an entity
* feat(logging): allow logging full query including params (#155)
* refactor: return entity from IEntity.assign()
* refactor: improve auto-detection of ts-node
* chore: change schema in default postgres connection string (#154)
* chore: fix links to repository and homepage after org transfer
* feat(mapping): add type-safe way to define relationships
* feat(postgres): use timestamps with time zone by default
* refactor: use relative paths in metadata cache
* feat(core): allow populating all relations via `populate: true`
* feat(validation): validate one to one relationship metadata
* feat(mapping): auto-wire missing references from owner to inverse side
* refactor(core): make em.find() where parameter required
* feat(core): add `em.findAndCount()` method
* docs: add `findAndCount` method and describe query object in EM section
* feat(core): allow empty where condition in `em.count()`
* feat(core): allow filtering and sorting by nested query
* docs: declare correct type of codeblock (#170)
* feat(core): add support for eager loading
* docs: declare correct type of codeblock (#173)
* feat(cli): rename default config name to mikro-orm.config.js
* feat(core): allow whitelisting entity fields in em.find()
* fix(core): allow object constructor parameters in entities
* feat(core): allow assigning PK to undefined/null
* fix(core): do not use request context in transactional/user forks
* fix(core): auto-wire 1:1 owner to inverse side
* feat(core): add support for deep nested conditions with operators (#185)
* fix(cli): rename `--no-fk` schema command parameter to `--fk-checks`
* fix(core): ignore inverse side of 1:1 when computing change set
* feat(drivers): add native UUID postgres type (#188)
* chore(types): improve types in EntityGenerator (#192)
* feat(metadata): auto-detect optional properties
* docs: fix bash highlighting
* fix(generator): fixed default values and types for nullable properties (#191)
* feat(core): add @repository decorator
* feat(core): simplify entity definition and rework typings of FilterQuery (#193)
* docs: add basic upgrading guide (WIP)
* fix(query-builder): fix malformed query when populate and join are used
* chore: export @SerializedPrimaryKey decorator, omit some internal types
* feat(core): propagate nested where and orderBy when populating
* refactor: use Set for QB flags
* refactor: em `transactional` method return value & improve types (#197)
* refactor: em and connection generic types (#198)
* chore: remove unused import
* refactor: add support for self referencing when auto-joining
* fix(core): always init collections when creating entity via em.create()
* tests: fix running tests on windows (#199)
* refactor(typings): support nested collections in FilterQuery
* docs: improve entity definition section, add more examples
* docs: add deployment sections
* refactor: add Reference.get(prop) method
* feat(query-builder): allow mapping to entities directly via getResult()
* feat(core): add support for filtering and ordering of Collection items
* feat(core): add support for bundling with Webpack (#200)
* chore: fix code style and increase timeout for CI tests
* docs: add section about bundling with webpack (#202)
* refactor: enable strictPropertyInitialization
* docs: improve code style of webpack section examples
* chore: hide knex's warning about sqlite FK failures
* refactor: try inlining deep conditions before renaming
* refactor: simplify webpack support test
* feat(drivers): allow passing additional driver options
* feat(core): use composite PK in many to many relations (#204)
* fix(serializing): add check for circular references in toObject()
* feat(core): do not require entity attribute in collection decorators (#207)
* feat(core): add support for migrations via umzug (#209)
* test: 'fix' memory leak in graceful-fs (#211)
* refactor: implement allOrNothing in Migrator
* docs: add note about possible TS inference problems with FilterQuery
* docs: add section about unit of work to readme
* refactor: run on init hooks after hydration
* fix(core): make sure constructor params are sniffer from the constructor
* fix(core): allow persisting 1:1 from inverse side
* test: run tests in ci on node 8, 10, 12 and 13
* test: speed up MikroORM.init validation tests
* chore: remove lgtm alert in MigrationGenerator
* docs: update deployment section (#223)
* refactor: do not use some umzug types that are badly defined
* feat(schema): allow dropping migrations table via `schema:drop` cli cmd
* fix(schema): do not make FK fields nullable if not needed
* docs: add section with limitations of migrations
* chore: fix build, remove stub of new docs
* fix(serializing): do not ignore already visited collection items
* feat(cli): add cache:generate command to warm up production cache
* feat(core): add support for enums via `@Enum()` decorator (#232)
* docs: add not about enums to deployment section
* docs: use `!` and `?` in entity props definition
* refactor: remove `AUTO_GROUP_BY` flag from query builder
* fix(core): fix querying by m:n primary keys
* refactor(core): do not propagate flat order by
* chore(dependencies): downgrade ts-morph to v4 as v5 requires TS 3.7
* perf(sql): use multi-insert when populating m:n collections
* fix(sql): support self-referencing m:n in pivot tables
* docs: add link to realworld example app
* docs: add link to migrations section into sidebar
* chore: fix typo and remove unreachable getReference() signature
* feat(schema): add support for create/drop database (#237)
* feat(cli): add `database:import` command to run external sql dumps
* feat(sqlite): ensure the directory with database exists
* chore: update mongodb to 3.3 and use new unified topology (#241)
* feat(metadata): add `ReflectMetadataProvider`, rename the `ts-morph` one (#240)
* refactor: fix create/drop db commands in mariadb driver
* chore: move typescript to dev dependencies
* chore: improve upgrading docs
* chore: add note about v2 docs
* chore: add script to generate the changelog
* chore: update changelog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants