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

fix(NODE-4583): revert nested union type support #3383

Merged
merged 2 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ export type {
KeysOfOtherType,
MatchKeysAndValues,
NestedPaths,
NestedPathsOfType,
NonObjectIdLikeDocument,
NotAcceptedFields,
NumericType,
Expand Down
158 changes: 80 additions & 78 deletions src/mongo_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export type WithoutId<TSchema> = Omit<TSchema, '_id'>;
export type Filter<TSchema> =
| Partial<TSchema>
| ({
[Property in Join<NestedPaths<WithId<TSchema>, true>, '.'>]?: Condition<
PropertyType<WithId<TSchema>, Property, true>
[Property in Join<NestedPaths<WithId<TSchema>>, '.'>]?: Condition<
PropertyType<WithId<TSchema>, Property>
>;
} & RootFilterOperators<WithId<TSchema>>);

Expand Down Expand Up @@ -261,9 +261,19 @@ export type OnlyFieldsOfType<TSchema, FieldType = any, AssignableType = FieldTyp
>;

/** @public */
export type MatchKeysAndValues<TSchema> = Readonly<{
[Property in Join<NestedPaths<TSchema, false>, '.'>]?: PropertyType<TSchema, Property, false>;
}>;
export type MatchKeysAndValues<TSchema> = Readonly<
{
[Property in Join<NestedPaths<TSchema>, '.'>]?: PropertyType<TSchema, Property>;
} & {
[Property in `${NestedPathsOfType<TSchema, any[]>}.$${`[${string}]` | ''}`]?: ArrayElement<
PropertyType<TSchema, Property extends `${infer Key}.$${string}` ? Key : never>
>;
} & {
[Property in `${NestedPathsOfType<TSchema, Record<string, any>[]>}.$${
| `[${string}]`
| ''}.${string}`]?: any; // Could be further narrowed
}
>;

/** @public */
export type AddToSetOperators<Type> = {
Expand Down Expand Up @@ -464,83 +474,75 @@ export type Join<T extends unknown[], D extends string> = T extends []
: string;

/** @public */
export type PropertyType<
Type,
Property extends string,
AllowToSkipArrayIndex extends boolean
> = Type extends unknown
? string extends Property
? Type extends Map<string, infer MapType>
export type PropertyType<Type, Property extends string> = string extends Property
? unknown
: Property extends keyof Type
? Type[Property]
: Property extends `${number}`
? Type extends ReadonlyArray<infer ArrayType>
? ArrayType
: unknown
: Property extends `${infer Key}.${infer Rest}`
? Key extends `${number}`
? Type extends ReadonlyArray<infer ArrayType>
? PropertyType<ArrayType, Rest>
: unknown
: Key extends keyof Type
? Type[Key] extends Map<string, infer MapType>
? MapType
: never
:
| (AllowToSkipArrayIndex extends false
? never
: Type extends ReadonlyArray<infer ArrayType>
? PropertyType<ArrayType, Property, AllowToSkipArrayIndex>
: never)
| (Property extends keyof Type
? Type[Property]
: Property extends `${number | `$${'' | `[${string}]`}`}`
? Type extends ReadonlyArray<infer ArrayType>
? ArrayType
: never
: Property extends `${infer Key}.${infer Rest}`
? Key extends `${number | `$${'' | `[${string}]`}`}`
? Type extends ReadonlyArray<infer ArrayType>
? PropertyType<ArrayType, Rest, AllowToSkipArrayIndex>
: never
: Key extends keyof Type
? PropertyType<Type[Key], Rest, AllowToSkipArrayIndex>
: never
: never)
: never;
: PropertyType<Type[Key], Rest>
: unknown
: unknown;

/**
* @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, AllowToSkipArrayIndex extends boolean> = Type extends unknown
? Type extends
| string
| number
| boolean
| Date
| RegExp
| Buffer
| Uint8Array
| ((...args: any[]) => any)
| { _bsontype: string }
? never
: Type extends ReadonlyArray<infer ArrayType>
? [
...(
| (AllowToSkipArrayIndex extends true ? [] : never)
| [number | `$${'' | `[${string}]`}`]
),
...([] | NestedPaths<ArrayType, AllowToSkipArrayIndex>)
]
: Type extends Map<string, any>
? [string]
: Type extends object
? {
[Key in Extract<keyof Type, string>]: 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<infer ArrayType> // 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<Type[Key], AllowToSkipArrayIndex>)]
: // child is not structured the same as the parent
[Key, ...([] | NestedPaths<Type[Key], AllowToSkipArrayIndex>)];
}[Extract<keyof Type, string>]
: never
: never;
export type NestedPaths<Type> = Type extends
| string
| number
| boolean
| Date
| RegExp
| Buffer
| Uint8Array
| ((...args: any[]) => any)
| { _bsontype: string }
? []
: Type extends ReadonlyArray<infer ArrayType>
? [] | [number, ...NestedPaths<ArrayType>]
: Type extends Map<string, any>
? [string]
: Type extends object
? {
[Key in Extract<keyof Type, string>]: 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<infer ArrayType> // 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<Type[Key]>]
: // child is not structured the same as the parent
[Key, ...NestedPaths<Type[Key]>] | [Key];
}[Extract<keyof Type, string>]
: [];

/**
* @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<TSchema, Type> = KeysOfAType<
{
[Property in Join<NestedPaths<TSchema>, '.'>]: PropertyType<TSchema, Property>;
},
Type
>;
8 changes: 4 additions & 4 deletions test/types/community/collection/filterQuery.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,6 @@ nonSpecifiedCollection.find({
}
});

// NODE-4513: improves support for union types and array operators
type MyArraySchema = {
nested: { array: { a: number; b: boolean }[] };
something: { a: number } | { b: boolean };
Expand All @@ -433,9 +432,10 @@ expectAssignable<Filter<MyArraySchema>>({
expectAssignable<Filter<MyArraySchema>>({
'something.a': 2
});
expectError<Filter<MyArraySchema>>({
'something.a': false
});
// TODO: NODE-4513: Nested union types don't error in this case.
// expectError<Filter<MyArraySchema>>({
// 'something.a': false
// });
expectAssignable<Filter<MyArraySchema>>({
'something.b': false
});