From 27801890f8c716273e92766833814f4a49cac098 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Sat, 16 Sep 2023 17:08:01 +0000
Subject: [PATCH 01/17] Projection Type Should handle all exclusion and
inclusions
---
test/types/queries.test.ts | 14 +++++++++++++-
test/types/schema.test.ts | 2 +-
types/index.d.ts | 23 +++++++++++++++++++++--
types/query.d.ts | 4 ++--
4 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index 35ff6f24d6e..cbc875a21eb 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -15,7 +15,8 @@ import {
QuerySelector,
InferSchemaType,
ProjectionFields,
- QueryOptions
+ QueryOptions,
+ ProjectionType
} from 'mongoose';
import { ModifyResult, ObjectId } from 'mongodb';
import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd';
@@ -160,6 +161,17 @@ const p1: Record = Test.find().projection('age docs.id');
const p2: Record | null = Test.find().projection();
const p3: null = Test.find().projection(null);
+expectError(Test.find({ }, { name: 'ss' })); // Only 0 and 1 are allowed
+expectError(Test.find({ }, { name: 3 })); // Only 0 and 1 are allowed
+expectError(Test.find({ }, { name: true, age: false, endDate: true, tags: 1 })); // Exclusion in a inclusion projection is not allowed
+expectError(Test.find({ }, { name: true, age: false, endDate: true })); // Inclusion in a exclusion projection is not allowed
+expectError(Test.find({ }, { name: false, age: false, tags: false, child: { name: false }, docs: { myId: false, id: true } })); // Inclusion in a exclusion projection is not allowed in nested objects and arrays
+expectError(Test.find({ }, { tags: { something: 1 } })); // array of strings or numbers should only be allowed to be a boolean or 1 and 0
+Test.find({}, { name: true, age: true, endDate: true, tags: 1, child: { name: true }, docs: { myId: true, id: true } }); // This should be allowed
+Test.find({}, { name: 1, age: 1, endDate: 1, tags: 1, child: { name: 1 }, docs: { myId: 1, id: 1 } }); // This should be allowed
+Test.find({}, { _id: 0, name: 1, age: 1, endDate: 1, tags: 1, child: 1, docs: 1 }); // _id is an exception and should be allowed to be excluded
+Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: 0, docs: 0 }); // This should be allowed
+Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: { name: 0 }, docs: { myId: 0, id: 0 } }); // This should be allowed
// Sorting
Test.find().sort();
Test.find().sort('-name');
diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts
index 9d1cb073f51..26f10c69875 100644
--- a/test/types/schema.test.ts
+++ b/test/types/schema.test.ts
@@ -770,7 +770,7 @@ function pluginOptions() {
}
const schema = new Schema({});
- expectType>(schema.plugin(pluginFunction)); // test that chaining would be possible
+ expectType(schema.plugin(pluginFunction)); // test that chaining would be possible
// could not add strict tests that the parameters are inferred correctly, because i dont know how this would be done in tsd
diff --git a/types/index.d.ts b/types/index.d.ts
index 1c3879d476f..73e2ff233ea 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -651,8 +651,27 @@ declare module 'mongoose' {
export type ReturnsNewDoc = { new: true } | { returnOriginal: false } | { returnDocument: 'after' };
- export type ProjectionElementType = number | string;
- export type ProjectionType = { [P in keyof T]?: ProjectionElementType } | AnyObject | string;
+ type Projector = T extends Array ? Projector : T extends object ? {
+ [K in keyof T]?: T[K] extends object ? Projector | Element : Element;
+ } : Element;
+ type InclusionProjection = Projector & { _id?: true | 1 | false | 0 };
+ type ExclusionProjection = Projector & { _id?: false | 0 };
+
+ type NestedRequired = T extends Array
+ ? Array>
+ : T extends object
+ ? {
+ [K in keyof T]-?: NestedRequired;
+ }
+ : T;
+ type NestedPartial = T extends object
+ ? {
+ [K in keyof T]?: NestedPartial;
+ }
+ : T;
+
+ // Bazi vaghta chizaye chert o pert pas midan
+ export type ProjectionType = NestedPartial>> | NestedPartial>> | string | string[] | ((...agrs: any) => any);
export type SortValues = SortOrder;
diff --git a/types/query.d.ts b/types/query.d.ts
index 78df823f5c0..c6f3bcb63c5 100644
--- a/types/query.d.ts
+++ b/types/query.d.ts
@@ -45,7 +45,7 @@ declare module 'mongoose' {
MongooseBaseQueryOptionKeys | 'timestamps'
>;
- type ProjectionFields = { [Key in keyof DocType]?: any } & Record;
+ type ProjectionFields = { [Key in keyof DocType]?: any } & Record; // Test
type QueryWithHelpers<
ResultType,
@@ -716,7 +716,7 @@ declare module 'mongoose' {
post(fn: Function): this;
/** Get/set the current projection (AKA fields). Pass `null` to remove the current projection. */
- projection(fields?: ProjectionFields | string): ProjectionFields;
+ projection(fields?: ProjectionFields | string): ProjectionFields; //
projection(fields: null): null;
projection(): ProjectionFields | null;
From 2ce0ce2cb864e27018cb183d87b83b104929a589 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Thu, 19 Oct 2023 14:48:07 +0000
Subject: [PATCH 02/17] fix tests
---
test/types/queries.test.ts | 1 +
types/index.d.ts | 24 ++++++++++++------------
types/query.d.ts | 4 ++--
3 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index cbc875a21eb..c4e8a184ec6 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -172,6 +172,7 @@ Test.find({}, { name: 1, age: 1, endDate: 1, tags: 1, child: { name: 1 }, docs:
Test.find({}, { _id: 0, name: 1, age: 1, endDate: 1, tags: 1, child: 1, docs: 1 }); // _id is an exception and should be allowed to be excluded
Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: 0, docs: 0 }); // This should be allowed
Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: { name: 0 }, docs: { myId: 0, id: 0 } }); // This should be allowed
+Test.find({}, { name: 1, age: 1, _id: 0 }); // This should be allowed since _id is an exception
// Sorting
Test.find().sort();
Test.find().sort('-name');
diff --git a/types/index.d.ts b/types/index.d.ts
index 73e2ff233ea..b9faf077c2a 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -654,24 +654,24 @@ declare module 'mongoose' {
type Projector = T extends Array ? Projector : T extends object ? {
[K in keyof T]?: T[K] extends object ? Projector | Element : Element;
} : Element;
- type InclusionProjection = Projector & { _id?: true | 1 | false | 0 };
- type ExclusionProjection = Projector & { _id?: false | 0 };
+ type _IDType = { _id?: boolean | 1 | 0 };
+ type InclusionProjection = NestedPartial, true | 1> & _IDType>;
+ type ExclusionProjection = NestedPartial, false | 0> & _IDType>;
type NestedRequired = T extends Array
- ? Array>
- : T extends object
- ? {
- [K in keyof T]-?: NestedRequired;
- }
- : T;
+ ? Array>
+ : T extends object
+ ? {
+ [K in keyof T]-?: NestedRequired;
+ }
+ : T;
type NestedPartial = T extends object
- ? {
+ ? {
[K in keyof T]?: NestedPartial;
}
- : T;
+ : T;
- // Bazi vaghta chizaye chert o pert pas midan
- export type ProjectionType = NestedPartial>> | NestedPartial>> | string | string[] | ((...agrs: any) => any);
+ export type ProjectionType = (InclusionProjection | ExclusionProjection) & AnyObject | string | ((...agrs: any) => any);
export type SortValues = SortOrder;
diff --git a/types/query.d.ts b/types/query.d.ts
index c6f3bcb63c5..0b0ce1a907a 100644
--- a/types/query.d.ts
+++ b/types/query.d.ts
@@ -128,7 +128,7 @@ declare module 'mongoose' {
updatedAt?: boolean;
}
- interface QueryOptions extends
+ interface QueryOptions extends
PopulateOption,
SessionOption {
arrayFilters?: { [key: string]: any }[];
@@ -160,7 +160,7 @@ declare module 'mongoose' {
* Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
*/
overwriteImmutable?: boolean;
- projection?: ProjectionType;
+ projection?: ProjectionType;
/**
* if true, returns the full ModifyResult rather than just the document
*/
From 8e482efba27eebf9ef227391866028cafa20ca88 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Thu, 19 Oct 2023 14:51:27 +0000
Subject: [PATCH 03/17] remove comment
---
types/query.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/types/query.d.ts b/types/query.d.ts
index 0b0ce1a907a..64ee6b90b1c 100644
--- a/types/query.d.ts
+++ b/types/query.d.ts
@@ -716,7 +716,7 @@ declare module 'mongoose' {
post(fn: Function): this;
/** Get/set the current projection (AKA fields). Pass `null` to remove the current projection. */
- projection(fields?: ProjectionFields | string): ProjectionFields; //
+ projection(fields?: ProjectionFields | string): ProjectionFields;
projection(fields: null): null;
projection(): ProjectionFields | null;
From 6ad92934b8004a3f1fdc269896f69d205dd42113 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Thu, 19 Oct 2023 15:06:51 +0000
Subject: [PATCH 04/17] remove test
---
types/query.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/types/query.d.ts b/types/query.d.ts
index 64ee6b90b1c..2b97db8b5ce 100644
--- a/types/query.d.ts
+++ b/types/query.d.ts
@@ -45,7 +45,7 @@ declare module 'mongoose' {
MongooseBaseQueryOptionKeys | 'timestamps'
>;
- type ProjectionFields = { [Key in keyof DocType]?: any } & Record; // Test
+ type ProjectionFields = { [Key in keyof DocType]?: any } & Record;
type QueryWithHelpers<
ResultType,
From df08fc70637edb545f8e560637b9cbb0df2dbb9f Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Fri, 20 Oct 2023 06:04:44 +0000
Subject: [PATCH 05/17] fields which are not in schema should be allowed
---
test/types/queries.test.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index c4e8a184ec6..13b5a776146 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -173,6 +173,7 @@ Test.find({}, { _id: 0, name: 1, age: 1, endDate: 1, tags: 1, child: 1, docs: 1
Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: 0, docs: 0 }); // This should be allowed
Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: { name: 0 }, docs: { myId: 0, id: 0 } }); // This should be allowed
Test.find({}, { name: 1, age: 1, _id: 0 }); // This should be allowed since _id is an exception
+Test.find({}, { someOtherField: 1 }); // This should be allowed since it's not a field in the schema
// Sorting
Test.find().sort();
Test.find().sort('-name');
From 83fc8f6b545fe9c6678a27d6b718da9b314beadf Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Fri, 20 Oct 2023 06:42:30 +0000
Subject: [PATCH 06/17] support $slice and $elemMatch
---
test/types/queries.test.ts | 6 ++++++
types/index.d.ts | 3 ++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index 13b5a776146..1105eeffce8 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -174,6 +174,12 @@ Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: 0, docs: 0 }); // T
Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: { name: 0 }, docs: { myId: 0, id: 0 } }); // This should be allowed
Test.find({}, { name: 1, age: 1, _id: 0 }); // This should be allowed since _id is an exception
Test.find({}, { someOtherField: 1 }); // This should be allowed since it's not a field in the schema
+expectError(Test.find({}, { name: { $slice: 1 }})); // $slice should only be allowed on arrays
+Test.find({}, { tags: { $slice: 1 } }); // $slice should be allowed on arrays
+Test.find({}, { tags: { $slice: [1, 2] } }); // $slice with the format of [ , ] should also be allowed on arrays
+expectError(Test.find({}, { age: { $elemMatch: {} } })); // $elemMatch should not be allowed on non arrays
+Test.find({}, { tags: { $elemMatch: {} } }); // $elemMatch should be allowed on arrays
+expectError(Test.find({}, { tags: { $slice: 1, $elemMatch: {} } })); // $elemMatch and $slice should not be allowed together
// Sorting
Test.find().sort();
Test.find().sort('-name');
diff --git a/types/index.d.ts b/types/index.d.ts
index b9faf077c2a..24221d9b85e 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -651,7 +651,8 @@ declare module 'mongoose' {
export type ReturnsNewDoc = { new: true } | { returnOriginal: false } | { returnDocument: 'after' };
- type Projector = T extends Array ? Projector : T extends object ? {
+ type ArrayOperators = { $slice: number | [number, number], $elemMatch?: undefined } | { $elemMatch: object, $slice?: undefined }
+ type Projector = T extends Array ? Projector | ArrayOperators : T extends object ? {
[K in keyof T]?: T[K] extends object ? Projector | Element : Element;
} : Element;
type _IDType = { _id?: boolean | 1 | 0 };
From 5b11b44d4a85300c352b8f1a07dd14c4140a98d6 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Fri, 20 Oct 2023 21:43:24 +0000
Subject: [PATCH 07/17] $slice and $elemMatch should be allowed with inclusion
and exclusion queries
---
test/types/queries.test.ts | 6 +++++-
types/index.d.ts | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index 1105eeffce8..6822582f4b8 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -174,12 +174,16 @@ Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: 0, docs: 0 }); // T
Test.find({}, { name: 0, age: 0, endDate: 0, tags: 0, child: { name: 0 }, docs: { myId: 0, id: 0 } }); // This should be allowed
Test.find({}, { name: 1, age: 1, _id: 0 }); // This should be allowed since _id is an exception
Test.find({}, { someOtherField: 1 }); // This should be allowed since it's not a field in the schema
-expectError(Test.find({}, { name: { $slice: 1 }})); // $slice should only be allowed on arrays
+expectError(Test.find({}, { name: { $slice: 1 } })); // $slice should only be allowed on arrays
Test.find({}, { tags: { $slice: 1 } }); // $slice should be allowed on arrays
Test.find({}, { tags: { $slice: [1, 2] } }); // $slice with the format of [ , ] should also be allowed on arrays
expectError(Test.find({}, { age: { $elemMatch: {} } })); // $elemMatch should not be allowed on non arrays
Test.find({}, { tags: { $elemMatch: {} } }); // $elemMatch should be allowed on arrays
expectError(Test.find({}, { tags: { $slice: 1, $elemMatch: {} } })); // $elemMatch and $slice should not be allowed together
+Test.find({}, { age: 1, tags: { $slice: 5 } }); // $slice should be allowed in inclusion projection
+Test.find({}, { age: 0, tags: { $slice: 5 } }); // $slice should be allowed in exclusion projection
+Test.find({}, { age: 1, tags: { $elemMatch: {} } }); // $elemMatch should be allowed in inclusion projection
+Test.find({}, { age: 0, tags: { $elemMatch: {} } }); // $elemMatch should be allowed in exclusion projection
// Sorting
Test.find().sort();
Test.find().sort('-name');
diff --git a/types/index.d.ts b/types/index.d.ts
index 24221d9b85e..03aad958449 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -651,7 +651,7 @@ declare module 'mongoose' {
export type ReturnsNewDoc = { new: true } | { returnOriginal: false } | { returnDocument: 'after' };
- type ArrayOperators = { $slice: number | [number, number], $elemMatch?: undefined } | { $elemMatch: object, $slice?: undefined }
+ type ArrayOperators = { $slice: number | [number, number], $elemMatch?: undefined } | { $elemMatch: object, $slice?: undefined };
type Projector = T extends Array ? Projector | ArrayOperators : T extends object ? {
[K in keyof T]?: T[K] extends object ? Projector | Element : Element;
} : Element;
From 9daf0cab2eb29d39a2341b34dc27324419301658 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Wed, 25 Oct 2023 14:04:35 +0000
Subject: [PATCH 08/17] feat: :sparkles: support dot notation and a combination
of object and dot notation projection
---
test/types/queries.test.ts | 10 +++++
types/index.d.ts | 91 +++++++++++++++++++++++++++++++++-----
2 files changed, 89 insertions(+), 12 deletions(-)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index 6822582f4b8..9168d214342 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -55,6 +55,9 @@ interface ISubdoc {
myId?: Types.ObjectId;
id?: number;
tags?: string[];
+ profiles: {
+ name?: string
+ }
}
interface ITest {
@@ -184,6 +187,13 @@ Test.find({}, { age: 1, tags: { $slice: 5 } }); // $slice should be allowed in i
Test.find({}, { age: 0, tags: { $slice: 5 } }); // $slice should be allowed in exclusion projection
Test.find({}, { age: 1, tags: { $elemMatch: {} } }); // $elemMatch should be allowed in inclusion projection
Test.find({}, { age: 0, tags: { $elemMatch: {} } }); // $elemMatch should be allowed in exclusion projection
+expectError(Test.find({}, { 'docs.id': 11 })); // Dot notation should be allowed and does not accept any
+expectError(Test.find({}, { docs: { id: '1' } })); // Dot notation should be able to use a combination with objects
+Test.find({}, { docs: { id: false } }); // Dot notation should be allowed with valid values - should correctly handle arrays
+Test.find({}, { docs: { id: true } }); // Dot notation should be allowed with valid values - should correctly handle arrays
+Test.find({}, { child: 1 }); // Dot notation should be able to use a combination with objects
+Test.find({}, { 'docs.profiles': { name: 1 } }); // should support a combination of dot notation and objects
+expectError(Test.find({}, { 'docs.profiles': { name: 'aa' } })); // should support a combination of dot notation and objects
// Sorting
Test.find().sort();
Test.find().sort('-name');
diff --git a/types/index.d.ts b/types/index.d.ts
index 03aad958449..cc7d0ac5e6d 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -651,13 +651,18 @@ declare module 'mongoose' {
export type ReturnsNewDoc = { new: true } | { returnOriginal: false } | { returnDocument: 'after' };
- type ArrayOperators = { $slice: number | [number, number], $elemMatch?: undefined } | { $elemMatch: object, $slice?: undefined };
- type Projector = T extends Array ? Projector | ArrayOperators : T extends object ? {
- [K in keyof T]?: T[K] extends object ? Projector | Element : Element;
- } : Element;
+ type ArrayOperators = { $slice: number | [number, number]; $elemMatch?: undefined } | { $elemMatch: object; $slice?: undefined };
+ type Projector = T extends Array
+ ? Projector | ArrayOperators
+ : T extends object
+ ? {
+ [K in keyof T]?: T[K] extends object ? Projector | Element : Element;
+ }
+ : Element;
type _IDType = { _id?: boolean | 1 | 0 };
- type InclusionProjection = NestedPartial, true | 1> & _IDType>;
- type ExclusionProjection = NestedPartial, false | 0> & _IDType>;
+ type InclusionProjection = NestedPartial>, true | 1> & _IDType>;
+ type ExclusionProjection = NestedPartial>, false | 0> & _IDType>;
+ type ProjectionUnion = InclusionProjection | ExclusionProjection;
type NestedRequired = T extends Array
? Array>
@@ -666,14 +671,76 @@ declare module 'mongoose' {
[K in keyof T]-?: NestedRequired;
}
: T;
- type NestedPartial = T extends object
- ? {
- [K in keyof T]?: NestedPartial;
- }
- : T;
+ type NestedPartial = T extends Array
+ ? Array>
+ : T extends object
+ ? {
+ [K in keyof T]?: NestedPartial;
+ }
+ : T;
+
+ /** https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object/58436959#58436959 for dot nested implementation it is used and then modified */
+ type DotPrefix = T extends '' ? '' : `.${T}`;
- export type ProjectionType = (InclusionProjection | ExclusionProjection) & AnyObject | string | ((...agrs: any) => any);
+ /** https://stackoverflow.com/questions/75419012/how-do-i-limit-the-level-of-nesting-on-a-recursive-type-in-typescript */
+ type Length = T extends { length: infer L } ? L : never;
+ type BuildTuple = T extends { length: L } ? T : BuildTuple;
+ type MinusOne = BuildTuple extends [...infer U, unknown] ? Length : never;
+ /**
+ * Generates a union from dot path in object
+ * We have to give it the Depth to prevent infinite recursion and also prevent slow compilation when the object is too much nested
+ */
+ type DotNestedKeys = (
+ Depth extends 0
+ ? never
+ : T extends object
+ ? { [K in Exclude]: `${K}` | `${K}${DotPrefix>>}` }[Exclude]
+ : ''
+ ) extends infer D
+ ? Extract
+ : never;
+ type FindDottedPathType = Path extends `${infer K}.${infer R}`
+ ? K extends keyof T
+ ? FindDottedPathType ? U : T[K], R>
+ : never
+ : Path extends keyof T
+ ? T[Path]
+ : never;
+ type ExtractNestedArrayElement = T extends (infer U)[]
+ ? ExtractNestedArrayElement
+ : T extends object
+ ? { [K in keyof T]: ExtractNestedArrayElement }
+ : T;
+ type DotnotationMaximumDepth = 4;
+ /**
+ * Create dot path for nested objects
+ * It creates dot notation for arrays similar to mongodb. For example { a: { c: { b: number}[] }[] } => 'a.c.b': number, 'a.c': { b: number }[]
+ */
+ type DotKeys = {
+ [key in DotNestedKeys, DotnotationMaximumDepth>]?: FindDottedPathType, key>;
+ };
+
+ /**
+ * This types are equivalent to primary types
+ */
+ type SpecialTypes = DateSchemaDefinition | Date | DateConstructor | Types.Buffer | Types.Decimal128 | Types.Buffer | BooleanSchemaDefinition | NumberSchemaDefinition;
+ type Replacer = T extends SpecialTypes ? string : T;
+ /**
+ * Date type is like a Primitiv type for us and we do not want to project something inside it.
+ * ObjectId is also similar.
+ */
+ type ReplaceSpecialTypes = T extends SpecialTypes
+ ? string
+ : T extends Array
+ ? Array>
+ : T extends object
+ ? {
+ [K in keyof T]?: ReplaceSpecialTypes;
+ }
+ : Replacer;
+
+ export type ProjectionType = (ProjectionUnion> & AnyObject) | string | ((...agrs: any) => any);
export type SortValues = SortOrder;
export type SortOrder = -1 | 1 | 'asc' | 'ascending' | 'desc' | 'descending';
From e94956bdc672b0d2da54b8c712f1e0a5a145e5d2 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Fri, 27 Oct 2023 14:31:05 +0000
Subject: [PATCH 09/17] should not project methods inside Date type
---
test/types/queries.test.ts | 1 +
types/index.d.ts | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index 9168d214342..aca346ef972 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -194,6 +194,7 @@ Test.find({}, { docs: { id: true } }); // Dot notation should be allowed with va
Test.find({}, { child: 1 }); // Dot notation should be able to use a combination with objects
Test.find({}, { 'docs.profiles': { name: 1 } }); // should support a combination of dot notation and objects
expectError(Test.find({}, { 'docs.profiles': { name: 'aa' } })); // should support a combination of dot notation and objects
+expectError(Test.find({}, { endDate: { toString: 1 } }));
// Sorting
Test.find().sort();
Test.find().sort('-name');
diff --git a/types/index.d.ts b/types/index.d.ts
index cc7d0ac5e6d..7bd4446de80 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -724,7 +724,7 @@ declare module 'mongoose' {
/**
* This types are equivalent to primary types
*/
- type SpecialTypes = DateSchemaDefinition | Date | DateConstructor | Types.Buffer | Types.Decimal128 | Types.Buffer | BooleanSchemaDefinition | NumberSchemaDefinition;
+ type SpecialTypes = DateSchemaDefinition | Date | globalThis.Date | DateConstructor | Types.Buffer | Types.Decimal128 | Types.Buffer | BooleanSchemaDefinition | NumberSchemaDefinition;
type Replacer = T extends SpecialTypes ? string : T;
/**
* Date type is like a Primitiv type for us and we do not want to project something inside it.
From 0c1d13a4c5de7856df240ae6be937640003aeef1 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Fri, 27 Oct 2023 14:34:27 +0000
Subject: [PATCH 10/17] use replacer type
---
types/index.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/types/index.d.ts b/types/index.d.ts
index 7bd4446de80..624dccb4690 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -731,7 +731,7 @@ declare module 'mongoose' {
* ObjectId is also similar.
*/
type ReplaceSpecialTypes = T extends SpecialTypes
- ? string
+ ? Replacer
: T extends Array
? Array>
: T extends object
From 66665e3e1c643bd521a0b5549174c8272d1de384 Mon Sep 17 00:00:00 2001
From: Poorshad
Date: Sat, 22 Mar 2025 12:33:58 +0100
Subject: [PATCH 11/17] use generic to fix Types.Subdocument type
---
types/types.d.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/types/types.d.ts b/types/types.d.ts
index 9c56959182e..eb6965ecafc 100644
--- a/types/types.d.ts
+++ b/types/types.d.ts
@@ -60,7 +60,7 @@ declare module 'mongoose' {
class Decimal128 extends mongodb.Decimal128 { }
- class DocumentArray = Types.Subdocument, any, T> & T> extends Types.Array {
+ class DocumentArray = Types.Subdocument, any, T> & T> extends Types.Array {
/** DocumentArray constructor */
constructor(values: AnyObject[]);
@@ -85,7 +85,7 @@ declare module 'mongoose' {
class ObjectId extends mongodb.ObjectId {
}
- class Subdocument extends Document {
+ class Subdocument extends Document {
$isSingleNested: true;
/** Returns the top level document of this sub-document. */
From 3ff4f4d55b9e3dc8ffe4e322080b0bc44c8a9282 Mon Sep 17 00:00:00 2001
From: Poorshad Shaddel
Date: Fri, 25 Apr 2025 19:23:49 +0200
Subject: [PATCH 12/17] Update types/index.d.ts
Co-authored-by: hasezoey
---
types/index.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/types/index.d.ts b/types/index.d.ts
index 624dccb4690..c86d43d1a9e 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -727,7 +727,7 @@ declare module 'mongoose' {
type SpecialTypes = DateSchemaDefinition | Date | globalThis.Date | DateConstructor | Types.Buffer | Types.Decimal128 | Types.Buffer | BooleanSchemaDefinition | NumberSchemaDefinition;
type Replacer = T extends SpecialTypes ? string : T;
/**
- * Date type is like a Primitiv type for us and we do not want to project something inside it.
+ * Date type is like a Primitive type for us and we do not want to project something inside it.
* ObjectId is also similar.
*/
type ReplaceSpecialTypes = T extends SpecialTypes
From 05e2a453501909fdd46c9779e98c0c76550c3cb2 Mon Sep 17 00:00:00 2001
From: Poorshad
Date: Fri, 25 Apr 2025 20:08:29 +0200
Subject: [PATCH 13/17] feat: add tsodc for recursive projector type
---
types/index.d.ts | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/types/index.d.ts b/types/index.d.ts
index e809700ee78..c19abe48762 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -656,6 +656,22 @@ declare module 'mongoose' {
export type ReturnsNewDoc = { new: true } | { returnOriginal: false } | { returnDocument: 'after' };
type ArrayOperators = { $slice: number | [number, number]; $elemMatch?: undefined } | { $elemMatch: object; $slice?: undefined };
+ /**
+ * This Type Assigns `Element | undefined` recursively to the `T` type.
+ * if it is an array it will do this to the element of the array, if it is an object it will do this for the properties of the object.
+ * `Element` is the truthy or falsy values that are going to be used as the value of the projection.(1 | true or 0 | false)
+ * For the elements of the array we will use: `Element | `undefined` | `ArrayOperators`
+ * @example
+ * type CalculatedType = Projector<{ a: string, b: number, c: { d: string }, d: string[] }, true>
+ * type CalculatedType = {
+ a?: true | undefined;
+ b?: true | undefined;
+ c?: true | {
+ d?: true | undefined;
+ } | undefined;
+ d?: true | ArrayOperators | undefined;
+ }
+ */
type Projector = T extends Array
? Projector | ArrayOperators
: T extends object
From ed36d2d73ef854f4cb445a390eed539ba66914f7 Mon Sep 17 00:00:00 2001
From: Poorshad
Date: Fri, 25 Apr 2025 20:11:06 +0200
Subject: [PATCH 14/17] use explicit type
---
test/types/schema.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts
index d7570ae0999..ee11e1f4e6f 100644
--- a/test/types/schema.test.ts
+++ b/test/types/schema.test.ts
@@ -770,7 +770,7 @@ function pluginOptions() {
}
const schema = new Schema({});
- expectType(schema.plugin(pluginFunction)); // test that chaining would be possible
+ expectType>(schema.plugin(pluginFunction)); // test that chaining would be possible
// could not add strict tests that the parameters are inferred correctly, because i dont know how this would be done in tsd
From 19b490deee3f8b8b3add8b30aed4afaea3c4ff52 Mon Sep 17 00:00:00 2001
From: Poorshad
Date: Fri, 25 Apr 2025 20:11:46 +0200
Subject: [PATCH 15/17] add a sample of manual casting using ProjectionType
---
test/types/queries.test.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index aca346ef972..7bc02e29fac 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -195,6 +195,8 @@ Test.find({}, { child: 1 }); // Dot notation should be able to use a combination
Test.find({}, { 'docs.profiles': { name: 1 } }); // should support a combination of dot notation and objects
expectError(Test.find({}, { 'docs.profiles': { name: 'aa' } })); // should support a combination of dot notation and objects
expectError(Test.find({}, { endDate: { toString: 1 } }));
+// Manual Casting using ProjectionType
+Test.find({}, { docs: { unknownParams: 1 } } as ProjectionType);
// Sorting
Test.find().sort();
Test.find().sort('-name');
From ac66c7ba841b223254f8f77db0526b27be6d5adc Mon Sep 17 00:00:00 2001
From: Poorshad
Date: Fri, 25 Apr 2025 23:17:58 +0200
Subject: [PATCH 16/17] leave the queryOptions projection as it is
For building the Projection, we need the interface, not the DocType
---
types/query.d.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/types/query.d.ts b/types/query.d.ts
index 6ee6b234858..c1f16ea193d 100644
--- a/types/query.d.ts
+++ b/types/query.d.ts
@@ -128,7 +128,7 @@ declare module 'mongoose' {
updatedAt?: boolean;
}
- interface QueryOptions extends
+ interface QueryOptions extends
PopulateOption,
SessionOption {
arrayFilters?: { [key: string]: any }[];
@@ -160,7 +160,7 @@ declare module 'mongoose' {
* Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
*/
overwriteImmutable?: boolean;
- projection?: ProjectionType;
+ projection?: { [P in keyof DocType]?: number | string } | AnyObject | string;
/**
* if true, returns the full ModifyResult rather than just the document
*/
From d94f1b7dee8a83bc5005d1ac65ea5e67f5a9f069 Mon Sep 17 00:00:00 2001
From: Poorshad
Date: Sat, 26 Apr 2025 10:57:07 +0200
Subject: [PATCH 17/17] reduce maxDotNotationDepth to improve performance and
reduce instantiations
---
types/index.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/types/index.d.ts b/types/index.d.ts
index c19abe48762..357ffd5cf2d 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -732,7 +732,7 @@ declare module 'mongoose' {
: T extends object
? { [K in keyof T]: ExtractNestedArrayElement }
: T;
- type DotnotationMaximumDepth = 4;
+ type DotnotationMaximumDepth = 3;
/**
* Create dot path for nested objects
* It creates dot notation for arrays similar to mongodb. For example { a: { c: { b: number}[] }[] } => 'a.c.b': number, 'a.c': { b: number }[]