From 4f19991baccc616960c9b58c16e7559f6ed4a691 Mon Sep 17 00:00:00 2001 From: jer-sen <2118189+jer-sen@users.noreply.github.com> Date: Mon, 8 Aug 2022 17:41:57 +0200 Subject: [PATCH 1/5] Fix type for nested objects in query & update - Support union types (... extends unknown ? ... : never) - Fail if path is wrong (never vs unknown) - Support array operators in the middle of a path (number replaced by number | `$${'' | `[${string}]`}` and NestedPathsOfType removed) --- src/mongo_types.ts | 130 ++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 73 deletions(-) diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 124e9ce9e3..265f361739 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -264,14 +264,6 @@ export type OnlyFieldsOfType = Readonly< { [Property in Join, '.'>]?: PropertyType; - } & { - [Property in `${NestedPathsOfType}.$${`[${string}]` | ''}`]?: ArrayElement< - PropertyType - >; - } & { - [Property in `${NestedPathsOfType[]>}.$${ - | `[${string}]` - | ''}.${string}`]?: any; // Could be further narrowed } >; @@ -474,75 +466,67 @@ 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}` +export type PropertyType = Type extends unknown + ? string extends Property + ? never + : Property extends keyof Type + ? Type[Property] + : Property extends `${number | `$${'' | `[${string}]`}`}` ? Type extends ReadonlyArray - ? PropertyType - : unknown - : Key extends keyof Type - ? Type[Key] extends Map - ? MapType - : PropertyType - : unknown - : unknown; + ? ArrayType + : never + : Property extends `${infer Key}.${infer Rest}` + ? Key extends `${number | `$${'' | `[${string}]`}`}` + ? Type extends ReadonlyArray + ? PropertyType + : never + : Key extends keyof Type + ? Type[Key] extends Map + ? MapType + : 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 } + ? [] + : Type extends ReadonlyArray + ? [] | [number | `$${'' | `[${string}]`}`, ...NestedPaths, ...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] + : [] + : never; From 029029503e6573ff9d5e8f4571e41ef3ecfb3d95 Mon Sep 17 00:00:00 2001 From: jer-sen <2118189+jer-sen@users.noreply.github.com> Date: Mon, 8 Aug 2022 17:49:12 +0200 Subject: [PATCH 2/5] Remove NestedPathsOfType from index --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6f8fa3d227..f637088467 100644 --- a/src/index.ts +++ b/src/index.ts @@ -321,7 +321,6 @@ export type { KeysOfOtherType, MatchKeysAndValues, NestedPaths, - NestedPathsOfType, NonObjectIdLikeDocument, NotAcceptedFields, NumericType, From f71b19fddf2c1a479f49a2c77c81d0b6a6056e2c Mon Sep 17 00:00:00 2001 From: jer-sen <2118189+jer-sen@users.noreply.github.com> Date: Tue, 9 Aug 2022 17:55:12 +0200 Subject: [PATCH 3/5] Allow to skip array index & disallow empty nested path --- src/mongo_types.ts | 94 ++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 265f361739..c85e4bca0f 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,11 +261,9 @@ export type OnlyFieldsOfType; /** @public */ -export type MatchKeysAndValues = Readonly< - { - [Property in Join, '.'>]?: PropertyType; - } ->; +export type MatchKeysAndValues = Readonly<{ + [Property in Join, '.'>]?: PropertyType; +}>; /** @public */ export type AddToSetOperators = { @@ -466,26 +464,36 @@ export type Join = T extends [] : string; /** @public */ -export type PropertyType = Type extends unknown +export type PropertyType< + Type, + Property extends string, + AllowToSkipArrayIndex extends boolean +> = Type extends unknown ? string extends Property ? 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 - ? Type[Key] extends Map - ? MapType - : PropertyType - : never - : 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 + ? Type[Key] extends Map + ? MapType + : PropertyType + : never + : never) : never; /** @@ -493,20 +501,26 @@ export type PropertyType = Type extends unknown * 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 unknown +export type NestedPaths = Type extends unknown ? Type extends - | string - | number - | boolean - | Date - | RegExp - | Buffer - | Uint8Array - | ((...args: any[]) => any) - | { _bsontype: string } - ? [] + | string + | number + | boolean + | Date + | RegExp + | Buffer + | Uint8Array + | ((...args: any[]) => any) + | { _bsontype: string } + ? never : Type extends ReadonlyArray - ? [] | [number | `$${'' | `[${string}]`}`, ...NestedPaths, ...NestedPaths] + ? [ + ...( + | (AllowToSkipArrayIndex extends true ? [] : never) + | [number | `$${'' | `[${string}]`}`] + ), + ...([] | NestedPaths) + ] : Type extends Map ? [string] : Type extends object @@ -524,9 +538,9 @@ export type NestedPaths = Type extends unknown ArrayType extends Type ? [Key] // we have a recursive array union : // child is an array, but it's not a recursive array - [Key, ...NestedPaths] + [Key, ...([] | NestedPaths)] : // child is not structured the same as the parent - [Key, ...NestedPaths] | [Key]; + [Key, ...([] | NestedPaths)]; }[Extract] - : [] + : never : never; From 87c6968032bd92f2e59589373050314f6243b302 Mon Sep 17 00:00:00 2001 From: jer-sen <2118189+jer-sen@users.noreply.github.com> Date: Thu, 11 Aug 2022 09:26:43 +0200 Subject: [PATCH 4/5] Fix for Map in schema type --- src/mongo_types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mongo_types.ts b/src/mongo_types.ts index c85e4bca0f..1021708268 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -470,7 +470,9 @@ export type PropertyType< AllowToSkipArrayIndex extends boolean > = Type extends unknown ? string extends Property - ? never + ? Type extends Map + ? MapType + : never : | (AllowToSkipArrayIndex extends false ? never @@ -489,9 +491,7 @@ export type PropertyType< ? PropertyType : never : Key extends keyof Type - ? Type[Key] extends Map - ? MapType - : PropertyType + ? PropertyType : never : never) : never; From 7d5cbb4f6966559d1da45ba7ae91d32ab250db27 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 12 Aug 2022 10:53:52 -0400 Subject: [PATCH 5/5] test: add examples of $[identifier] and union usage --- .../collection/filterQuery.test-d.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/types/community/collection/filterQuery.test-d.ts b/test/types/community/collection/filterQuery.test-d.ts index dd17868514..8f5836712f 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 +});