Skip to content

Commit

Permalink
Merge pull request #14981 from Automattic/vkarpov15/gh-14451
Browse files Browse the repository at this point in the history
types: add JSONSerialized helper that can convert HydratedDocument to JSON output type
  • Loading branch information
vkarpov15 authored Oct 25, 2024
2 parents f2b2816 + 858cc0a commit 6ab97f2
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 1 deletion.
2 changes: 1 addition & 1 deletion test/types/check-types-filename.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
33 changes: 33 additions & 0 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
InferRawDocType,
InferSchemaType,
InsertManyOptions,
JSONSerialized,
ObtainDocumentType,
ObtainSchemaGeneric,
ResolveSchemaOptions,
Expand Down Expand Up @@ -1681,3 +1682,35 @@ async function gh14902() {
expectType<Binary | null | undefined>(doc.image);
expectType<Binary | null | undefined>(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<InferSchemaType<typeof exampleSchema>>;
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<string, string> | null | undefined
}>({} as TestJSON);
}
73 changes: 73 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,9 @@ declare module 'mongoose' {
[K in keyof T]: FlattenProperty<T[K]>;
};

/**
* Converts any Buffer properties into mongodb.Binary instances, which is what `lean()` returns
*/
export type BufferToBinary<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[K in keyof T]: T[K] extends Buffer
? mongodb.Binary
Expand All @@ -718,6 +721,76 @@ declare module 'mongoose' {
: BufferToBinary<T[K]>;
} : T;

/**
* Converts any Buffer properties into { type: 'buffer', data: [1, 2, 3] } format for JSON serialization
*/
export type BufferToJSON<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[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<infer ItemType>
? Types.DocumentArray<BufferToBinary<ItemType>>
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
? HydratedSingleSubdocument<SubdocType>
: BufferToBinary<T[K]>;
} : T;

/**
* Converts any ObjectId properties into strings for JSON serialization
*/
export type ObjectIdToString<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[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<infer ItemType>
? Types.DocumentArray<ObjectIdToString<ItemType>>
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
? HydratedSingleSubdocument<ObjectIdToString<SubdocType>>
: ObjectIdToString<T[K]>;
} : T;

/**
* Converts any Date properties into strings for JSON serialization
*/
export type DateToString<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[K in keyof T]: T[K] extends NativeDate
? string
: T[K] extends (NativeDate | null | undefined)
? string | null | undefined
: T[K] extends Types.DocumentArray<infer ItemType>
? Types.DocumentArray<DateToString<ItemType>>
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
? HydratedSingleSubdocument<DateToString<SubdocType>>
: DateToString<T[K]>;
} : T;

/**
* Converts any Mongoose subdocuments (single nested or doc arrays) into POJO equivalents
*/
export type SubdocsToPOJOs<T> = T extends TreatAsPrimitives ? T : T extends Record<string, any> ? {
[K in keyof T]: T[K] extends NativeDate
? string
: T[K] extends (NativeDate | null | undefined)
? string | null | undefined
: T[K] extends Types.DocumentArray<infer ItemType>
? ItemType[]
: T[K] extends Types.Subdocument<unknown, unknown, infer SubdocType>
? SubdocType
: SubdocsToPOJOs<T[K]>;
} : T;

export type JSONSerialized<T> = SubdocsToPOJOs<
FlattenMaps<
BufferToJSON<
ObjectIdToString<
DateToString<T>
>
>
>
>;

/**
* 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
Expand Down

0 comments on commit 6ab97f2

Please sign in to comment.