Typesafe filters
Is your feature request related to a problem? Please describe. Currently, a call to qb.applyFilters() (be it directly or implicitly through an EntityManager method call) can feature arbitrary names for filters, and if that filter doesn't really exist, it will be silently ignored. There's also very limited ability to check filter args, both on a definition level and a call level. No auto complete suggestions either.
All of this makes filters a likely source of runtime and logic errors.
Describe the solution you'd like
Instead of adding @Filter at the top of an entity, I think adding @Filter on a property/method, combined with forcing the return/prop type be a new specially annotated type (tentatively called FilterCond) will enable better type safety in both declaration and call level.
For convenience, a method prototype type can also be offered, e.g.
type FilterCondMethod<
T,
A extends {} = never,
E extends EntityManager = EntityManager
> = (args: A, type: 'read' | 'update' | 'delete', em: E) => FilterCond<T>;(subject to fine tuning during full implementation... as written, can't infer A from args, and omitting the default doesn't quite help either, though it errors diferently...)
f.e.
@Entity()
class MyEntity {
...
@Filter()
isExpensive: FilterCond<MyEntity> = { price: { $gt: 1000 };
@Filter({ default: true })
hasAuthor: FilterCond<MyEntity> = { author: { $ne: null } };
@Filter()
writtenBy: FilterCondMethod<MyEntity, { name: string }> = ({ name }) => ({ author: { name } });
@Filter({ default: true })
byTenant: FilterCondMethod<MyEntity, { id: number }> = function ({id}) {
id ??= GetCurrentTenantid();
return { tenant: id };
}
}The filter props will of course need to be excluded from any place where regular props are otherwise allowed and offered as suggestions.
This will allow the filter options to be checked at build time and suggested based on the prop/return type. Meanwhile, the prop's @Filter decorator will be what ultimately enforces it on a runtime level. EntitySchema definitions can remain unchanged, since the above is effectively syntax sugar for the same filter definitions. The classes associated with EntitySchema will need to feature the properties with annotated types, but their contents would still be defined in the EntitySchema.
The only real drawback of this approach is that filter names share a namespace with other props, which means you can't have a filter name named after a property, but given the value one gets in return, I think this is a small price to pay.
Describe alternatives you've considered
One could define a custom type or interface that has the known names of filters and their args, then reference these upon any call that features filters. This approach however requires more discipline from the developer when calling, and most people (myself included) wouldn't bother in most cases, and suffer the consequences later.
Additional context I've been wanting to generate filters based on an entity's indexes, and the types of properties featured in those indexes... But even while playing around with manually defining such filters, I noticed their unsafety, which made me decide I should avoid this approach, which is kind of a bummer, as locking myself into index enabled filters means forcing myself to create indexes or think the application logic through, which in turn helps keep application performance good.
I suppose an alternative approach for the same goal that is type safe even today is to generate repositories with additional methods like findBy{indexName}(indexPropValues, ...findArgs), findAndCountBy{indexName}(indexPropValues, ...findAndCountArgs), countBy{indexName}(indexPropValues, ...countArgs), findOneBy{uniqueName}(uniquePropValues, ...findOneArgs), findOneBy{uniqueName}OrFail(uniquePropValues, ...findOneOrFailArgs), nativeUpdateManyBy{indexName}(indexPropValues, ...nativeUpdateArgs), nativeUpdateBy{uniqueName}(uniquePropValues, ...nativeUpdateArgs), nativeDeleteManyBy{indexName}(indexPropValues, ...nativeDeleteArgs), nativeDeleteBy{uniqueName}(uniquePropValues, ...nativeDeleteArgs), though that means (finally) making repository generation in the first place. Regardless, the ability to have type safe filters is still valuable.
Source: mikro-orm/mikro-orm