Skip to content

types: avoid FlattenMaps by default on toObject(), toJSON(), lean() #15518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: vkarpov15/schema-create
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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