Closed alpharder closed 10 months ago
I've managed to reduce reproduction code to this:
import * as nodeAssert from 'node:assert/strict'; import { assert, cast } from '@deepkit/type'; type List<A = never> = ReadonlyArray<A>; type BuiltIn = // eslint-disable-next-line @typescript-eslint/ban-types | Function | Error | Date | { readonly [Symbol.toStringTag]: string; } | RegExp | Generator; type OnlyUserKeys<O> = O extends List ? keyof O & number : O extends BuiltIn ? never : keyof O & (string | number); type NestedKeyOf<ObjectType extends object> = { [Key in OnlyUserKeys<ObjectType>]: ObjectType[Key] extends object ? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}` : `${Key}`; }[OnlyUserKeys<ObjectType>]; /** * It's JSON:API spec compatible, without any particular reason. * Please note that JSON:API spec compatibility is not the main goal of this library, * so don't expect everything to be compatible with JSON:API. */ export type ResourceQuerySort<O extends object> = ( | `-${NestedKeyOf<O>}` | `${NestedKeyOf<O>}` )[]; export type ResourceQueryFilterOperatorsWithNestedQueries< TValue extends object, > = { $not?: ResourceQueryFilter<TValue>; $or?: ResourceQueryFilter<TValue>[]; $nor?: ResourceQueryFilter<TValue>[]; $and?: ResourceQueryFilter<TValue>[]; }; export type ResourceQueryFilterOperators<TValue> = { $eq?: TValue; $ne?: TValue; $lt?: TValue; $gt?: TValue; $lte?: TValue; $gte?: TValue; $in?: TValue[]; $nin?: TValue[]; $all?: TValue[]; }; export type ResourceQueryFiltersSubsetBasedOnNestedQueryOperators< TResourceObject extends object, > = ResourceQueryFilterOperatorsWithNestedQueries<TResourceObject>; export type ResourceQueryFiltersSubsetBasedOnResourceObjectKeys< TResourceObject extends object, TResourceObjectUserKeys extends keyof TResourceObject = OnlyUserKeys<TResourceObject>, > = { [TKey in TResourceObjectUserKeys]?: TResourceObject[TKey] extends object ? ResourceQueryFilter<TResourceObject[TKey]> & ResourceQueryFilterOperators<TResourceObject[TKey]> & ResourceQueryFilterOperatorsWithNestedQueries<TResourceObject[TKey]> : | TResourceObject[TKey] | ResourceQueryFilterOperators<TResourceObject[TKey]>; }; export type ResourceQueryFilter<TResourceObject extends object> = ResourceQueryFiltersSubsetBasedOnResourceObjectKeys<TResourceObject> & ResourceQueryFiltersSubsetBasedOnNestedQueryOperators<TResourceObject>; export type ResourceQuery<T extends object> = { filter?: ResourceQueryFilter<T>; sort?: ResourceQuerySort<T>; limit?: number; offset?: number; }; type MyQuery = ResourceQuery<{ k: string; t: number; Y: { yyf: string; }; }>; const validMyQuery: MyQuery = { filter: { k: 'k', t: 44, $or: [ { t: { $gte: 32 } }, { Y: { yyf: { $ne: 'ne' }, }, }, ], }, sort: ['-k', 't', '-Y.yyf'], }; /** * "validMyQuery" is a perfectly valid instance of MyQuery type, but this assertion fails due to incorrect casting * expected: { * filter: { * k: 'k', * t: 44, * '$or': [ { t: { '$gte': 32 } }, { Y: { yyf: [Object] } } ] * }, * sort: [ '-k', 't' ] * }, * actual: { filter: { '$or': [ {}, {} ] }, sort: [ undefined, undefined ] }, */ nodeAssert.deepEqual(cast<MyQuery>(validMyQuery), validMyQuery); const invalidMyQuery: MyQuery = { filter: { // @ts-expect-error Because t should only accept numbers or objects t: 'str', Y: { yyf: { // @ts-expect-error because $ne should only accept string here $ne: { IMPOSSIBLE: { IMPOSSIBLE: [], }, }, }, }, // cast should remove these, and it does NON_EXISTENT: { NON_EXISTENT: {}, }, }, // @ts-expect-error because "NON_EXISTENT" is invalid property path sort: ['-NON_EXISTENT'], }; // This should throw, but it doesn't console.log(assert<MyQuery>(invalidMyQuery));
https://github.com/deepkit/deepkit-framework/commit/fde795ee6998606b0791f936a25ee85921c6586a fixes the issue for me
I've managed to reduce reproduction code to this: