Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
52c7573
types: avoid FlattenMaps by default on toObject(), toJSON(), lean()
vkarpov15 Jul 5, 2025
da378db
types: add TTransformOptions for InferRawDocType so RawDocType and Le…
vkarpov15 Jul 6, 2025
9340115
Merge branch 'vkarpov15/schema-create' into vkarpov15/gh-13523-3
vkarpov15 Jul 7, 2025
db76c64
fix tests
vkarpov15 Jul 7, 2025
357e0ee
Merge branch 'vkarpov15/schema-create' into vkarpov15/gh-13523-3
vkarpov15 Jul 7, 2025
4cd119f
merge fixes
vkarpov15 Jul 7, 2025
5d49e29
Merge branch 'vkarpov15/schema-create' into vkarpov15/gh-13523-3
vkarpov15 Jul 11, 2025
f21175f
merge conflict cleanup
vkarpov15 Jul 11, 2025
b7a76d5
Merge branch '9.0' into vkarpov15/gh-13523-3
vkarpov15 Oct 13, 2025
c30375d
some merge conflict fixes
vkarpov15 Oct 13, 2025
3b9b6c8
fix THydratedDocumentType params
vkarpov15 Oct 13, 2025
64027dc
fix some missing overrides
vkarpov15 Oct 13, 2025
72eba8c
fix: clean up a couple of more merge conflict issues
vkarpov15 Oct 13, 2025
31778f1
couple of more fixes
vkarpov15 Oct 13, 2025
d565fb1
quick test fix
vkarpov15 Oct 13, 2025
0c7ce68
clean up a couple of more type issues
vkarpov15 Oct 13, 2025
b579111
fix enum handling in InferRawDocType
vkarpov15 Oct 13, 2025
47bb200
fix out of date test
vkarpov15 Oct 13, 2025
5671e58
fix up test inconsistency
vkarpov15 Oct 15, 2025
659c098
types: clean up a couple of more test failures due to missing TVirtuals
vkarpov15 Oct 16, 2025
d2b0ace
use RawDocType in default THydratedDocumentType only if not any
vkarpov15 Oct 16, 2025
4345355
apply id virtual correctly and fix remaining tests
vkarpov15 Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions test/types/lean.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ async function gh13010() {
expectType<Record<string, string>>(country.name);
}

async function gh13010_1() {
const schema = Schema.create({
name: { required: true, type: Map, of: String }
});

const CountryModel = model('Country', schema);

await CountryModel.create({
name: {
en: 'Croatia',
ru: 'Хорватия'
}
});

const country = await CountryModel.findOne().lean().orFail().exec();
expectType<Record<string, string | undefined>>(country.name);
}

async function gh13345_1() {
const imageSchema = new Schema({
url: { required: true, type: String }
Expand Down
2 changes: 1 addition & 1 deletion test/types/maps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function gh10575() {
function gh10872(): void {
const doc = new Test({});

doc.toJSON().map1.foo;
doc.toJSON({ flattenMaps: true }).map1.foo;
}

function gh13755() {
Expand Down
7 changes: 5 additions & 2 deletions test/types/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import mongoose, {
WithLevel1NestedPaths,
createConnection,
connection,
model
model,
ObtainSchemaGeneric
} from 'mongoose';
import { expectAssignable, expectError, expectType } from 'tsd';
import { AutoTypedSchemaType, autoTypedSchema } from './schema.test';
Expand Down Expand Up @@ -575,12 +576,14 @@ async function gh12319() {
);

const ProjectModel = model('Project', projectSchema);
const doc = new ProjectModel();
doc.doSomething();

type ProjectModelHydratedDoc = HydratedDocumentFromSchema<
typeof projectSchema
>;

expectType<ProjectModelHydratedDoc>(await ProjectModel.findOne().orFail());
expectAssignable<ProjectModelHydratedDoc>(await ProjectModel.findOne().orFail());
}

function findWithId() {
Expand Down
31 changes: 24 additions & 7 deletions test/types/schema.create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,8 @@ export function autoTypedSchema() {
objectId2?: Types.ObjectId | null;
objectId3?: Types.ObjectId | null;
customSchema?: Int8 | null;
map1?: Map<string, string> | null;
map2?: Map<string, number> | null;
map1?: Record<string, string | undefined> | null;
map2?: Record<string, number | undefined> | null;
array1: string[];
array2: any[];
array3: any[];
Expand Down Expand Up @@ -734,17 +734,26 @@ function gh12030() {
} & { _id: Types.ObjectId }>;
} & { _id: Types.ObjectId }>({} as InferSchemaType<typeof Schema3>);

type RawDocType3 = ObtainSchemaGeneric<typeof Schema3, 'DocType'>;
type HydratedDoc3 = ObtainSchemaGeneric<typeof Schema3, 'THydratedDocumentType'>;
expectType<
HydratedDocument<{
users: Types.DocumentArray<
{ credit: number; username?: string | null; } & { _id: Types.ObjectId },
Types.Subdocument<Types.ObjectId, unknown, { credit: number; username?: string | null; } & { _id: Types.ObjectId }> & { credit: number; username?: string | null; } & { _id: Types.ObjectId }
Types.Subdocument<
Types.ObjectId,
unknown,
{ credit: number; username?: string | null; } & { _id: Types.ObjectId }
> & { credit: number; username?: string | null; } & { _id: Types.ObjectId }
>;
} & { _id: Types.ObjectId }>
} & { _id: Types.ObjectId }, {}, {}, {}, RawDocType3>
>({} as HydratedDoc3);
expectType<
Types.Subdocument<Types.ObjectId, unknown, { credit: number; username?: string | null; } & { _id: Types.ObjectId }> & { credit: number; username?: string | null; } & { _id: Types.ObjectId }
Types.Subdocument<
Types.ObjectId,
unknown,
{ credit: number; username?: string | null; } & { _id: Types.ObjectId }
> & { credit: number; username?: string | null; } & { _id: Types.ObjectId }
>({} as HydratedDoc3['users'][0]);

const Schema4 = Schema.create({
Expand Down Expand Up @@ -1164,6 +1173,9 @@ function maps() {
const doc = new Test({ myMap: { answer: 42 } });
expectType<Map<string, number>>(doc.myMap);
expectType<number | undefined>(doc.myMap!.get('answer'));

const obj = doc.toObject();
expectType<Record<string, number | undefined>>(obj.myMap);
}

function gh13514() {
Expand Down Expand Up @@ -1697,7 +1709,12 @@ async function gh14950() {
const doc = await TestModel.findOne().orFail();

expectType<string>(doc.location!.type);
expectType<number[]>(doc.location!.coordinates);
expectType<Types.Array<number>>(doc.location!.coordinates);

const lean = await TestModel.findOne().lean().orFail();

expectType<string>(lean.location!.type);
expectType<number[]>(lean.location!.coordinates);
}

async function gh14902() {
Expand Down Expand Up @@ -1748,7 +1765,7 @@ async function gh14451() {
subdocProp?: string | undefined | null
} | null,
docArr: { nums: number[], times: string[] }[],
myMap?: Record<string, string> | null | undefined,
myMap?: Record<string, string | undefined> | null | undefined,
_id: string
}>({} as TestJSON);
}
Expand Down
35 changes: 23 additions & 12 deletions types/document.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,23 +256,34 @@ declare module 'mongoose' {
set(value: string | Record<string, any>): this;

/** The return value of this method is used in calls to JSON.stringify(doc). */
toJSON(options: ToObjectOptions & { flattenMaps: true, flattenObjectIds: true, virtuals: true }): FlattenMaps<ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>>>>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should those options be listed in ToObjectOptions with documentation?

toJSON(options: ToObjectOptions & { flattenMaps: true, flattenObjectIds: true }): FlattenMaps<ObjectIdToString<Default__v<Require_id<DocType>>>>;
toJSON(options: ToObjectOptions & { flattenMaps: true, virtuals: true }): FlattenMaps<Default__v<Require_id<DocType & TVirtuals>>>;
toJSON(options: ToObjectOptions & { flattenObjectIds: true, virtuals: true }): ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>>>;
toJSON(options: ToObjectOptions & { flattenMaps: true }): FlattenMaps<Default__v<Require_id<DocType>>>;
toJSON(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType>>>;
toJSON(options: ToObjectOptions & { virtuals: true }): Default__v<Require_id<DocType & TVirtuals>>;
toJSON(options?: ToObjectOptions & { flattenMaps?: true, flattenObjectIds?: false }): FlattenMaps<Default__v<Require_id<DocType>>>;
toJSON(options: ToObjectOptions & { flattenObjectIds: false }): FlattenMaps<Default__v<Require_id<DocType>>>;
toJSON(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<FlattenMaps<Default__v<Require_id<DocType>>>>;
toJSON(options: ToObjectOptions & { flattenMaps: false }): Default__v<Require_id<DocType>>;
toJSON(options: ToObjectOptions & { flattenMaps: false; flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType>>>;

toJSON<T = Default__v<Require_id<DocType>>>(options?: ToObjectOptions & { flattenMaps?: true, flattenObjectIds?: false }): FlattenMaps<T>;
toJSON<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenObjectIds: false }): FlattenMaps<T>;
toJSON<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<FlattenMaps<T>>;
toJSON<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenMaps: false }): T;
toJSON<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenMaps: false; flattenObjectIds: true }): ObjectIdToString<T>;
toJSON(options?: ToObjectOptions): Default__v<Require_id<DocType>>;

toJSON<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenMaps: true, flattenObjectIds: true }): FlattenMaps<ObjectIdToString<T>>;
toJSON<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<T>;
toJSON<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenMaps: true }): FlattenMaps<T>;
toJSON<T = Default__v<Require_id<DocType>>>(options?: ToObjectOptions): T;

/** Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)). */
toObject(options: ToObjectOptions & { flattenMaps: true, flattenObjectIds: true, virtuals: true }): FlattenMaps<ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>>>>;
toObject(options: ToObjectOptions & { flattenMaps: true, flattenObjectIds: true }): FlattenMaps<ObjectIdToString<Default__v<Require_id<DocType>>>>;
toObject(options: ToObjectOptions & { flattenMaps: true, virtuals: true }): FlattenMaps<Default__v<Require_id<DocType & TVirtuals>>>;
toObject(options: ToObjectOptions & { flattenObjectIds: true, virtuals: true }): ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>>>;
toObject(options: ToObjectOptions & { flattenMaps: true }): FlattenMaps<Default__v<Require_id<DocType>>>;
toObject(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType>>>;
toObject(options: ToObjectOptions & { virtuals: true }): Default__v<Require_id<DocType & TVirtuals>>;
toObject(options?: ToObjectOptions): Default__v<Require_id<DocType>>;
toObject<T>(options?: ToObjectOptions): Default__v<Require_id<T>>;

toObject<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenMaps: true, flattenObjectIds: true }): FlattenMaps<ObjectIdToString<T>>;
toObject<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<T>;
toObject<T = Default__v<Require_id<DocType>>>(options: ToObjectOptions & { flattenMaps: true }): FlattenMaps<T>;
toObject<T = Default__v<Require_id<DocType>>>(options?: ToObjectOptions): T;

/** Clears the modified state on the specified path. */
unmarkModified<T extends keyof DocType>(path: T): void;
Expand Down
62 changes: 39 additions & 23 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,22 @@ declare module 'mongoose' {
collection?: string,
options?: CompileModelOptions
): Model<
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>,
ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'>,
HydratedDocument<
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'> & ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'>
>,
TSchema
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>,
ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'>,
// If first schema generic param is set, that means we have an explicit raw doc type,
// so user should also specify a hydrated doc type if the auto inferred one isn't correct.
IsItRecordAndNotAny<ObtainSchemaGeneric<TSchema, 'EnforcedDocType'>> extends true
? ObtainSchemaGeneric<TSchema, 'THydratedDocumentType'>
: HydratedDocument<
InferSchemaType<TSchema>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'> & ObtainSchemaGeneric<TSchema, 'TInstanceMethods'>,
ObtainSchemaGeneric<TSchema, 'TQueryHelpers'>,
ObtainSchemaGeneric<TSchema, 'TVirtuals'>
>,
TSchema,
ObtainSchemaGeneric<TSchema, 'TLeanResultType'>
> & ObtainSchemaGeneric<TSchema, 'TStaticMethods'>;

export function model<T>(name: string, schema?: Schema<T, any, any> | Schema<T & Document, any, any>, collection?: string, options?: CompileModelOptions): Model<T>;
Expand Down Expand Up @@ -147,24 +152,26 @@ declare module 'mongoose' {

/** Helper type for getting the hydrated document type from the raw document type. The hydrated document type is what `new MyModel()` returns. */
export type HydratedDocument<
DocType,
HydratedDocPathsType,
TOverrides = {},
TQueryHelpers = {},
TVirtuals = {}
TVirtuals = {},
RawDocType = HydratedDocPathsType
> = IfAny<
DocType,
HydratedDocPathsType,
any,
TOverrides extends Record<string, never> ?
Document<unknown, TQueryHelpers, DocType, TVirtuals> & Default__v<Require_id<DocType>> :
Document<unknown, TQueryHelpers, RawDocType, TVirtuals> & Default__v<Require_id<HydratedDocPathsType>> :
IfAny<
TOverrides,
Document<unknown, TQueryHelpers, DocType, TVirtuals> & Default__v<Require_id<DocType>>,
Document<unknown, TQueryHelpers, DocType, TVirtuals> & MergeType<
Default__v<Require_id<DocType>>,
Document<unknown, TQueryHelpers, RawDocType, TVirtuals> & Default__v<Require_id<HydratedDocPathsType>>,
Document<unknown, TQueryHelpers, RawDocType, TVirtuals> & MergeType<
Default__v<Require_id<HydratedDocPathsType>>,
TOverrides
>
>
>;

export type HydratedSingleSubdocument<
DocType,
TOverrides = {}
Expand Down Expand Up @@ -274,8 +281,9 @@ declare module 'mongoose' {
ObtainDocumentType<any, RawDocType, ResolveSchemaOptions<TSchemaOptions>>,
ResolveSchemaOptions<TSchemaOptions>
>,
THydratedDocumentType = HydratedDocument<FlatRecord<DocType>, TVirtuals & TInstanceMethods, {}, TVirtuals>,
TSchemaDefinition = SchemaDefinition<SchemaDefinitionType<RawDocType>, RawDocType, THydratedDocumentType>
THydratedDocumentType = HydratedDocument<DocType, TVirtuals & TInstanceMethods, {}, TVirtuals>,
TSchemaDefinition = SchemaDefinition<SchemaDefinitionType<RawDocType>, RawDocType, THydratedDocumentType>,
LeanResultType = IsItRecordAndNotAny<RawDocType> extends true ? RawDocType : Default__v<Require_id<BufferToBinary<FlattenMaps<DocType>>>>
>
extends events.EventEmitter {
/**
Expand All @@ -291,7 +299,13 @@ declare module 'mongoose' {
InferRawDocType<TSchemaDefinition, ResolveSchemaOptions<TSchemaOptions>>,
ResolveSchemaOptions<TSchemaOptions>
>,
THydratedDocumentType extends AnyObject = HydratedDocument<InferHydratedDocType<TSchemaDefinition, ResolveSchemaOptions<TSchemaOptions>>>
THydratedDocumentType extends AnyObject = HydratedDocument<
InferHydratedDocType<TSchemaDefinition, ResolveSchemaOptions<TSchemaOptions>>,
TSchemaOptions extends { methods: infer M } ? M : {},
TSchemaOptions extends { query: any } ? TSchemaOptions['query'] : {},
TSchemaOptions extends { virtuals: any } ? TSchemaOptions['virtuals'] : {},
RawDocType
>
>(def: TSchemaDefinition): Schema<
RawDocType,
Model<RawDocType, any, any, any>,
Expand All @@ -305,7 +319,8 @@ declare module 'mongoose' {
ResolveSchemaOptions<TSchemaOptions>
>,
THydratedDocumentType,
TSchemaDefinition
TSchemaDefinition,
BufferToBinary<RawDocType>
>;

static create<
Expand All @@ -329,7 +344,8 @@ declare module 'mongoose' {
ResolveSchemaOptions<TSchemaOptions>
>,
THydratedDocumentType,
TSchemaDefinition
TSchemaDefinition,
BufferToBinary<RawDocType>
>;

/** Adds key path / schema type pairs to this schema. */
Expand Down
4 changes: 2 additions & 2 deletions types/inferrawdoctype.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ declare module 'mongoose' {
PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? UUID :
PathValueType extends 'double' | 'Double' | typeof Schema.Types.Double ? Types.Double :
IfEquals<PathValueType, Schema.Types.UUID> extends true ? Buffer :
PathValueType extends MapConstructor | 'Map' ? Map<string, ResolveRawPathType<Options['of']>> :
IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? Map<string, ResolveRawPathType<Options['of']>> :
PathValueType extends MapConstructor | 'Map' ? Record<string, ResolveRawPathType<Options['of']> | undefined> :
IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? Record<string, ResolveRawPathType<Options['of']> | undefined> :
PathValueType extends ArrayConstructor ? any[] :
PathValueType extends typeof Schema.Types.Mixed ? any:
IfEquals<PathValueType, ObjectConstructor> extends true ? any:
Expand Down
5 changes: 3 additions & 2 deletions types/inferschematype.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ declare module 'mongoose' {
* @param {TSchema} TSchema A generic of schema type instance.
* @param {alias} alias Targeted generic alias.
*/
type ObtainSchemaGeneric<TSchema, alias extends 'EnforcedDocType' | 'M' | 'TInstanceMethods' | 'TQueryHelpers' | 'TVirtuals' | 'TStaticMethods' | 'TSchemaOptions' | 'DocType' | 'THydratedDocumentType' | 'TSchemaDefinition'> =
TSchema extends Schema<infer EnforcedDocType, infer M, infer TInstanceMethods, infer TQueryHelpers, infer TVirtuals, infer TStaticMethods, infer TSchemaOptions, infer DocType, infer THydratedDocumentType, infer TSchemaDefinition>
type ObtainSchemaGeneric<TSchema, alias extends 'EnforcedDocType' | 'M' | 'TInstanceMethods' | 'TQueryHelpers' | 'TVirtuals' | 'TStaticMethods' | 'TSchemaOptions' | 'DocType' | 'THydratedDocumentType' | 'TSchemaDefinition' | 'TLeanResultType'> =
TSchema extends Schema<infer EnforcedDocType, infer M, infer TInstanceMethods, infer TQueryHelpers, infer TVirtuals, infer TStaticMethods, infer TSchemaOptions, infer DocType, infer THydratedDocumentType, infer TSchemaDefinition, infer TLeanResultType>
? {
EnforcedDocType: EnforcedDocType;
M: M;
Expand All @@ -69,6 +69,7 @@ declare module 'mongoose' {
DocType: DocType;
THydratedDocumentType: THydratedDocumentType;
TSchemaDefinition: TSchemaDefinition;
TLeanResultType: TLeanResultType;
}[alias]
: unknown;

Expand Down
Loading
Loading