diff --git a/test/types/check-types-filename.js b/test/types/check-types-filename.js index 8dc0f3f6de..303c0b05dd 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 467e619236..90bcfce7f5 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,35 @@ 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 }, + myBuf: { type: Buffer, required: true }, + subdoc: { + type: new Schema({ + subdocProp: Date + }) + }, + docArr: [{ nums: [Number], times: [{ type: Date }] }], + myMap: { + type: Map, + of: String + } + }); + + const Test = model('Test', exampleSchema); + + type TestJSON = JSONSerialized>; + expectType<{ + myId?: string | undefined | null, + myRequiredId: string, + myBuf: { type: 'buffer', data: number[] }, + subdoc?: { + subdocProp?: string | undefined | null + } | null, + docArr: { nums: number[], times: string[] }[], + myMap?: Record | null | undefined + }>({} as TestJSON); +} diff --git a/types/index.d.ts b/types/index.d.ts index c6655b802f..cabb62630f 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,76 @@ 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[] } + : 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; + + /** + * 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 + : 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; + + /** + * 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 + : T[K] extends (NativeDate | null | undefined) + ? string | null | undefined + : T[K] extends Types.DocumentArray + ? Types.DocumentArray> + : T[K] extends Types.Subdocument + ? HydratedSingleSubdocument> + : 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 + : 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< + BufferToJSON< + 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