diff --git a/src/index.ts b/src/index.ts index 6f8fa3d227f..f6370884670 100644 --- a/src/index.ts +++ b/src/index.ts @@ -321,7 +321,6 @@ export type { KeysOfOtherType, MatchKeysAndValues, NestedPaths, - NestedPathsOfType, NonObjectIdLikeDocument, NotAcceptedFields, NumericType, diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 124e9ce9e35..10217082682 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -68,8 +68,8 @@ export type WithoutId = Omit; export type Filter = | Partial | ({ - [Property in Join>, '.'>]?: Condition< - PropertyType, Property> + [Property in Join, true>, '.'>]?: Condition< + PropertyType, Property, true> >; } & RootFilterOperators>); @@ -261,19 +261,9 @@ export type OnlyFieldsOfType; /** @public */ -export type MatchKeysAndValues = Readonly< - { - [Property in Join, '.'>]?: PropertyType; - } & { - [Property in `${NestedPathsOfType}.$${`[${string}]` | ''}`]?: ArrayElement< - PropertyType - >; - } & { - [Property in `${NestedPathsOfType[]>}.$${ - | `[${string}]` - | ''}.${string}`]?: any; // Could be further narrowed - } ->; +export type MatchKeysAndValues = Readonly<{ + [Property in Join, '.'>]?: PropertyType; +}>; /** @public */ export type AddToSetOperators = { @@ -474,75 +464,83 @@ export type Join = T extends [] : string; /** @public */ -export type PropertyType = string extends Property - ? unknown - : Property extends keyof Type - ? Type[Property] - : Property extends `${number}` - ? Type extends ReadonlyArray - ? ArrayType - : unknown - : Property extends `${infer Key}.${infer Rest}` - ? Key extends `${number}` - ? Type extends ReadonlyArray - ? PropertyType - : unknown - : Key extends keyof Type - ? Type[Key] extends Map +export type PropertyType< + Type, + Property extends string, + AllowToSkipArrayIndex extends boolean +> = Type extends unknown + ? string extends Property + ? Type extends Map ? MapType - : PropertyType - : unknown - : unknown; + : never + : + | (AllowToSkipArrayIndex extends false + ? never + : Type extends ReadonlyArray + ? PropertyType + : never) + | (Property extends keyof Type + ? Type[Property] + : Property extends `${number | `$${'' | `[${string}]`}`}` + ? Type extends ReadonlyArray + ? ArrayType + : never + : Property extends `${infer Key}.${infer Rest}` + ? Key extends `${number | `$${'' | `[${string}]`}`}` + ? Type extends ReadonlyArray + ? PropertyType + : never + : Key extends keyof Type + ? PropertyType + : never + : never) + : never; /** * @public * returns tuple of strings (keys to be joined on '.') that represent every path into a schema * https://docs.mongodb.com/manual/tutorial/query-embedded-documents/ */ -export type NestedPaths = Type extends - | string - | number - | boolean - | Date - | RegExp - | Buffer - | Uint8Array - | ((...args: any[]) => any) - | { _bsontype: string } - ? [] - : Type extends ReadonlyArray - ? [] | [number, ...NestedPaths] - : Type extends Map - ? [string] - : Type extends object - ? { - [Key in Extract]: Type[Key] extends Type // type of value extends the parent - ? [Key] - : // for a recursive union type, the child will never extend the parent type. - // but the parent will still extend the child - Type extends Type[Key] - ? [Key] - : Type[Key] extends ReadonlyArray // handling recursive types with arrays - ? Type extends ArrayType // is the type of the parent the same as the type of the array? - ? [Key] // yes, it's a recursive array type - : // for unions, the child type extends the parent - ArrayType extends Type - ? [Key] // we have a recursive array union - : // child is an array, but it's not a recursive array - [Key, ...NestedPaths] - : // child is not structured the same as the parent - [Key, ...NestedPaths] | [Key]; - }[Extract] - : []; - -/** - * @public - * returns keys (strings) for every path into a schema with a value of type - * https://docs.mongodb.com/manual/tutorial/query-embedded-documents/ - */ -export type NestedPathsOfType = KeysOfAType< - { - [Property in Join, '.'>]: PropertyType; - }, - Type ->; +export type NestedPaths = Type extends unknown + ? Type extends + | string + | number + | boolean + | Date + | RegExp + | Buffer + | Uint8Array + | ((...args: any[]) => any) + | { _bsontype: string } + ? never + : Type extends ReadonlyArray + ? [ + ...( + | (AllowToSkipArrayIndex extends true ? [] : never) + | [number | `$${'' | `[${string}]`}`] + ), + ...([] | NestedPaths) + ] + : Type extends Map + ? [string] + : Type extends object + ? { + [Key in Extract]: Type[Key] extends Type // type of value extends the parent + ? [Key] + : // for a recursive union type, the child will never extend the parent type. + // but the parent will still extend the child + Type extends Type[Key] + ? [Key] + : Type[Key] extends ReadonlyArray // handling recursive types with arrays + ? Type extends ArrayType // is the type of the parent the same as the type of the array? + ? [Key] // yes, it's a recursive array type + : // for unions, the child type extends the parent + ArrayType extends Type + ? [Key] // we have a recursive array union + : // child is an array, but it's not a recursive array + [Key, ...([] | NestedPaths)] + : // child is not structured the same as the parent + [Key, ...([] | NestedPaths)]; + }[Extract] + : never + : never; diff --git a/test/types/community/collection/filterQuery.test-d.ts b/test/types/community/collection/filterQuery.test-d.ts index dd178685141..8f5836712fc 100644 --- a/test/types/community/collection/filterQuery.test-d.ts +++ b/test/types/community/collection/filterQuery.test-d.ts @@ -12,7 +12,7 @@ import { } from 'bson'; import { expectAssignable, expectError, expectNotType, expectType } from 'tsd'; -import { Collection, Filter, MongoClient, WithId } from '../../../../src'; +import { Collection, Filter, MongoClient, UpdateFilter, WithId } from '../../../../src'; /** * test the Filter type using collection.find() method @@ -406,3 +406,36 @@ nonSpecifiedCollection.find({ hello: 'world' } }); + +// NODE-4513: improves support for union types and array operators +type MyArraySchema = { + nested: { array: { a: number; b: boolean }[] }; + something: { a: number } | { b: boolean }; +}; + +// "element" now refers to the name used in arrayFilters, it can be any string +expectAssignable>({ + $set: { 'nested.array.$[element]': { a: 2, b: false } } +}); +expectAssignable>({ + $set: { 'nested.array.$[element]': { a: 2, b: false } } +}); + +// Specifying an identifier in the brackets is optional +expectAssignable>({ + $set: { 'nested.array.$[].a': 2 } +}); +expectAssignable>({ + $set: { 'nested.array.$[].a': 2 } +}); + +// Union usage examples +expectAssignable>({ + 'something.a': 2 +}); +expectError>({ + 'something.a': false +}); +expectAssignable>({ + 'something.b': false +});