From ff24d8689c9c0457337b23179febaf214a97e76b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 23 Oct 2024 13:44:23 -0400 Subject: [PATCH] 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