From ff24d8689c9c0457337b23179febaf214a97e76b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 23 Oct 2024 13:44:23 -0400 Subject: [PATCH 1/3] types: add JSONSerialized helper that can convert HydratedDocument to JSON output type Fix #14451 --- test/types/check-types-filename.js | 2 +- test/types/schema.test.ts | 26 ++++++++++++++++++ types/index.d.ts | 44 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/test/types/check-types-filename.js b/test/types/check-types-filename.js index 8dc0f3f6de8..303c0b05dd0 100644 --- a/test/types/check-types-filename.js +++ b/test/types/check-types-filename.js @@ -18,7 +18,7 @@ const checkFolder = (folder) => { } continue; } else { - console.error('File ' + entry + ' is not having a valid file-extension.\n'); + console.error('File ' + entry + ' does not have a valid extension, must be .d.ts or .gitignore.\n'); process.exit(1); } } diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 467e6192363..cc16a523412 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -10,6 +10,7 @@ import { InferRawDocType, InferSchemaType, InsertManyOptions, + JSONSerialized, ObtainDocumentType, ObtainSchemaGeneric, ResolveSchemaOptions, @@ -1681,3 +1682,28 @@ async function gh14902() { expectType(doc.image); expectType(doc.subdoc!.testBuf); } + +async function gh14451() { + const exampleSchema = new Schema({ + myId: { type: 'ObjectId' }, + myRequiredId: { type: 'ObjectId', required: true }, + subdoc: { + type: new Schema({ + subdocProp: Date + }) + } + // docArr: [{ nums: [Number], times: [Date] }] + }); + + const Test = model('Test', exampleSchema); + + type TestJSON = JSONSerialized>; + expectType<{ + myId?: string | undefined | null, + myRequiredId: string, + subdoc?: { + subdocProp?: string | undefined | null + } | null, + // docArr: { nums: number[], times: string[] }[] + }>({} as TestJSON); +} diff --git a/types/index.d.ts b/types/index.d.ts index c6655b802fa..2b557a7d3d2 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -718,6 +718,50 @@ declare module 'mongoose' { : BufferToBinary; } : T; + export type ObjectIdToString = T extends TreatAsPrimitives ? T : T extends Record ? { + [K in keyof T]: T[K] extends mongodb.ObjectId + ? string + : T[K] extends (mongodb.ObjectId | null | undefined) + ? string | null | undefined + : T[K] extends Types.DocumentArray + ? Types.DocumentArray> + : T[K] extends Types.Subdocument + ? HydratedSingleSubdocument> + : ObjectIdToString; + } : T; + + export type DateToString = T extends TreatAsPrimitives ? T : T extends Record ? { + [K in keyof T]: T[K] extends NativeDate + ? string + : T[K] extends (NativeDate | null | undefined) + ? string | null | undefined + : T[K] extends Types.DocumentArray + ? Types.DocumentArray> + : T[K] extends Types.Subdocument + ? HydratedSingleSubdocument> + : DateToString; + } : T; + + export type SubdocsToPOJOs = T extends TreatAsPrimitives ? T : T extends Record ? { + [K in keyof T]: T[K] extends NativeDate + ? string + : T[K] extends (NativeDate | null | undefined) + ? string | null | undefined + : T[K] extends Types.DocumentArray + ? ItemType + : T[K] extends Types.Subdocument + ? SubdocType + : SubdocsToPOJOs; + } : T; + + export type JSONSerialized = SubdocsToPOJOs< + FlattenMaps< + ObjectIdToString< + DateToString + > + > + >; + /** * Separate type is needed for properties of union type (for example, Types.DocumentArray | undefined) to apply conditional check to each member of it * https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types From 50b2670d0102fa0686890e2fe6e282a6bb5b694c Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 23 Oct 2024 14:03:03 -0400 Subject: [PATCH 2/3] types: handle buffers in JSONSerialized and fix issue with DocumentArrays --- test/types/schema.test.ts | 8 +++++--- types/index.d.ts | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index cc16a523412..2639c19f6cd 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1687,12 +1687,13 @@ async function gh14451() { const exampleSchema = new Schema({ myId: { type: 'ObjectId' }, myRequiredId: { type: 'ObjectId', required: true }, + myBuf: { type: Buffer, required: true }, subdoc: { type: new Schema({ subdocProp: Date }) - } - // docArr: [{ nums: [Number], times: [Date] }] + }, + docArr: [{ nums: [Number], times: [{ type: Date }] }] }); const Test = model('Test', exampleSchema); @@ -1701,9 +1702,10 @@ async function gh14451() { expectType<{ myId?: string | undefined | null, myRequiredId: string, + myBuf: { type: 'buffer', data: number[] }, subdoc?: { subdocProp?: string | undefined | null } | null, - // docArr: { nums: number[], times: string[] }[] + docArr: { nums: number[], times: string[] }[] }>({} as TestJSON); } diff --git a/types/index.d.ts b/types/index.d.ts index 2b557a7d3d2..1f9692b14d1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -718,6 +718,18 @@ declare module 'mongoose' { : BufferToBinary; } : T; + export type BufferToJSON = T extends TreatAsPrimitives ? T : T extends Record ? { + [K in keyof T]: T[K] extends Buffer + ? { type: 'buffer', data: number[] } + : T[K] extends (Buffer | null | undefined) + ? { type: 'buffer', data: number[] } | null | undefined + : T[K] extends Types.DocumentArray + ? Types.DocumentArray> + : T[K] extends Types.Subdocument + ? HydratedSingleSubdocument + : BufferToBinary; + } : T; + export type ObjectIdToString = T extends TreatAsPrimitives ? T : T extends Record ? { [K in keyof T]: T[K] extends mongodb.ObjectId ? string @@ -748,7 +760,7 @@ declare module 'mongoose' { : T[K] extends (NativeDate | null | undefined) ? string | null | undefined : T[K] extends Types.DocumentArray - ? ItemType + ? ItemType[] : T[K] extends Types.Subdocument ? SubdocType : SubdocsToPOJOs; @@ -756,8 +768,10 @@ declare module 'mongoose' { export type JSONSerialized = SubdocsToPOJOs< FlattenMaps< - ObjectIdToString< - DateToString + BufferToJSON< + ObjectIdToString< + DateToString + > > > >; From 858cc0aeb92ffa9506d3d25f8b19d4bd4638084e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 25 Oct 2024 13:39:05 -0400 Subject: [PATCH 3/3] add docs and test coverage for maps --- test/types/schema.test.ts | 9 +++++++-- types/index.d.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 2639c19f6cd..90bcfce7f5f 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1693,7 +1693,11 @@ async function gh14451() { subdocProp: Date }) }, - docArr: [{ nums: [Number], times: [{ type: Date }] }] + docArr: [{ nums: [Number], times: [{ type: Date }] }], + myMap: { + type: Map, + of: String + } }); const Test = model('Test', exampleSchema); @@ -1706,6 +1710,7 @@ async function gh14451() { subdoc?: { subdocProp?: string | undefined | null } | null, - docArr: { nums: number[], times: string[] }[] + docArr: { nums: number[], times: string[] }[], + myMap?: Record | null | undefined }>({} as TestJSON); } diff --git a/types/index.d.ts b/types/index.d.ts index 1f9692b14d1..cabb62630fc 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -706,6 +706,9 @@ declare module 'mongoose' { [K in keyof T]: FlattenProperty; }; + /** + * Converts any Buffer properties into mongodb.Binary instances, which is what `lean()` returns + */ export type BufferToBinary = T extends TreatAsPrimitives ? T : T extends Record ? { [K in keyof T]: T[K] extends Buffer ? mongodb.Binary @@ -718,6 +721,9 @@ declare module 'mongoose' { : BufferToBinary; } : T; + /** + * Converts any Buffer properties into { type: 'buffer', data: [1, 2, 3] } format for JSON serialization + */ export type BufferToJSON = T extends TreatAsPrimitives ? T : T extends Record ? { [K in keyof T]: T[K] extends Buffer ? { type: 'buffer', data: number[] } @@ -730,6 +736,9 @@ declare module 'mongoose' { : BufferToBinary; } : T; + /** + * Converts any ObjectId properties into strings for JSON serialization + */ export type ObjectIdToString = T extends TreatAsPrimitives ? T : T extends Record ? { [K in keyof T]: T[K] extends mongodb.ObjectId ? string @@ -742,6 +751,9 @@ declare module 'mongoose' { : ObjectIdToString; } : T; + /** + * Converts any Date properties into strings for JSON serialization + */ export type DateToString = T extends TreatAsPrimitives ? T : T extends Record ? { [K in keyof T]: T[K] extends NativeDate ? string @@ -754,6 +766,9 @@ declare module 'mongoose' { : DateToString; } : T; + /** + * Converts any Mongoose subdocuments (single nested or doc arrays) into POJO equivalents + */ export type SubdocsToPOJOs = T extends TreatAsPrimitives ? T : T extends Record ? { [K in keyof T]: T[K] extends NativeDate ? string