Skip to content

Commit e2534f2

Browse files
committed
types: better support for custom type keys in #15582
1 parent 0b78bbf commit e2534f2

File tree

5 files changed

+157
-39
lines changed

5 files changed

+157
-39
lines changed

test/document.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14597,6 +14597,9 @@ describe('document', function() {
1459714597
let obj = doc.toObject();
1459814598
assert.ok(obj.hasOwnProperty('__v'));
1459914599

14600+
obj = doc.toObject();
14601+
assert.ok(obj.hasOwnProperty('__v'));
14602+
1460014603
// toObject({ versionKey: false }) removes versionKey
1460114604
obj = doc.toObject({ versionKey: false });
1460214605
assert.ok(!obj.hasOwnProperty('__v'));

test/types/document.test.ts

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
HydratedArraySubdocument,
99
HydratedSingleSubdocument,
1010
DefaultSchemaOptions,
11-
ObtainSchemaGeneric
11+
ObtainSchemaGeneric,
12+
ResolveSchemaOptions
1213
} from 'mongoose';
1314
import { DeleteResult } from 'mongodb';
1415
import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd';
@@ -478,32 +479,133 @@ async function gh15316() {
478479
}
479480

480481
async function gh15578() {
481-
interface RawDocType {
482+
function withDocType() {
483+
interface RawDocType {
482484
_id: Types.ObjectId;
483485
testProperty: number;
486+
}
487+
488+
const ASchema = new Schema<RawDocType>({
489+
testProperty: Number
490+
});
491+
492+
const AModel = model<RawDocType>('YourModel', ASchema);
493+
494+
const a = new AModel({ testProperty: 8 });
495+
const toObjectFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ flattenObjectIds: true });
496+
const toObjectWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: true, flattenObjectIds: true });
497+
const toObjectWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: false, flattenObjectIds: true });
498+
const toJSONFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ flattenObjectIds: true });
499+
const toJSONWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: true, flattenObjectIds: true });
500+
const toJSONWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: false, flattenObjectIds: true });
501+
502+
const objWithoutVersionKey = a.toObject({ versionKey: false });
503+
const jsonWithoutVersionKey = a.toJSON({ versionKey: false });
504+
expectError(objWithoutVersionKey.__v);
505+
expectError(jsonWithoutVersionKey.__v);
506+
507+
const objWithVersionKey = a.toObject();
508+
const jsonWithVersionKey = a.toJSON();
509+
expectType<number>(objWithVersionKey.__v);
510+
expectType<number>(jsonWithVersionKey.__v);
484511
}
485512

486-
const ASchema = new Schema<RawDocType>({
487-
testProperty: Number
488-
});
513+
function withDocTypeAndVersionKey() {
514+
interface RawDocType {
515+
_id: Types.ObjectId;
516+
testProperty: number;
517+
}
518+
519+
const schemaOptions = { versionKey: 'taco' } as const;
520+
521+
type ModelType = Model<RawDocType, {}, {}, {}, HydratedDocument<RawDocType, {}, {}, {}, typeof schemaOptions>>;
522+
523+
const ASchema = new Schema<RawDocType, ModelType, {}, {}, {}, {}, typeof schemaOptions>({
524+
testProperty: Number
525+
}, schemaOptions);
526+
527+
const AModel = model<RawDocType, ModelType>('YourModel', ASchema);
528+
529+
const a = new AModel({ testProperty: 8 });
530+
const toObjectFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ flattenObjectIds: true });
531+
const toObjectWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: true, flattenObjectIds: true });
532+
const toObjectWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: false, flattenObjectIds: true });
533+
const toJSONFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ flattenObjectIds: true });
534+
const toJSONWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: true, flattenObjectIds: true });
535+
const toJSONWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: false, flattenObjectIds: true });
536+
537+
const objWithoutVersionKey = a.toObject({ versionKey: false });
538+
const jsonWithoutVersionKey = a.toJSON({ versionKey: false });
539+
expectError(objWithoutVersionKey.taco);
540+
expectError(jsonWithoutVersionKey.taco);
541+
542+
const objWithVersionKey = a.toObject();
543+
const jsonWithVersionKey = a.toJSON();
544+
expectType<number>(objWithVersionKey.taco);
545+
expectType<number>(jsonWithVersionKey.taco);
546+
}
489547

490-
const AModel = model<RawDocType>('YourModel', ASchema);
491-
492-
const a = new AModel({ testProperty: 8 });
493-
const toObjectFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ flattenObjectIds: true });
494-
const toObjectWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: true, flattenObjectIds: true });
495-
const toObjectWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: false, flattenObjectIds: true });
496-
const toJSONFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ flattenObjectIds: true });
497-
const toJSONWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: true, flattenObjectIds: true });
498-
const toJSONWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: false, flattenObjectIds: true });
499-
500-
const objWithoutVersionKey = a.toObject({ versionKey: false });
501-
const jsonWithoutVersionKey = a.toJSON({ versionKey: false });
502-
expectError(objWithoutVersionKey.__v);
503-
expectError(jsonWithoutVersionKey.__v);
504-
505-
const objWithVersionKey = a.toObject();
506-
const jsonWithVersionKey = a.toJSON();
507-
expectType<number>(objWithVersionKey.__v);
508-
expectType<number>(jsonWithVersionKey.__v);
548+
function autoInferred() {
549+
interface RawDocType {
550+
_id: Types.ObjectId;
551+
testProperty?: number | null;
552+
}
553+
554+
const ASchema = new Schema({
555+
testProperty: Number
556+
});
557+
558+
const AModel = model('YourModel', ASchema);
559+
560+
const a = new AModel({ testProperty: 8 });
561+
const toObjectFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ flattenObjectIds: true });
562+
const toObjectWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: true, flattenObjectIds: true });
563+
const toObjectWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: false, flattenObjectIds: true });
564+
const toJSONFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ flattenObjectIds: true });
565+
const toJSONWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: true, flattenObjectIds: true });
566+
const toJSONWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: false, flattenObjectIds: true });
567+
568+
const objWithoutVersionKey = a.toObject({ versionKey: false });
569+
const jsonWithoutVersionKey = a.toJSON({ versionKey: false });
570+
expectError(objWithoutVersionKey.__v);
571+
expectError(jsonWithoutVersionKey.__v);
572+
573+
const objWithVersionKey = a.toObject();
574+
const jsonWithVersionKey = a.toJSON();
575+
expectType<number>(objWithVersionKey.__v);
576+
expectType<number>(jsonWithVersionKey.__v);
577+
}
578+
579+
function autoInferredWithCustomVersionKey() {
580+
interface RawDocType {
581+
_id: Types.ObjectId;
582+
testProperty?: number | null;
583+
}
584+
585+
const ASchema = new Schema({
586+
testProperty: Number
587+
}, { versionKey: 'taco' });
588+
589+
const AModel = model('YourModel', ASchema);
590+
591+
type TSchemaOptions = ResolveSchemaOptions<ObtainSchemaGeneric<typeof ASchema, 'TSchemaOptions'>>;
592+
593+
const a = new AModel({ testProperty: 8 });
594+
const toObjectFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ flattenObjectIds: true });
595+
const toObjectWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: true, flattenObjectIds: true });
596+
const toObjectWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toObject({ virtuals: false, flattenObjectIds: true });
597+
const toJSONFlattened: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ flattenObjectIds: true });
598+
const toJSONWithVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: true, flattenObjectIds: true });
599+
const toJSONWithoutVirtuals: Omit<RawDocType, '_id'> & { _id: string } = a.toJSON({ virtuals: false, flattenObjectIds: true });
600+
601+
const objWithoutVersionKey = a.toObject({ versionKey: false });
602+
const jsonWithoutVersionKey = a.toJSON({ versionKey: false });
603+
expectError(objWithoutVersionKey.taco);
604+
expectError(jsonWithoutVersionKey.taco);
605+
606+
const objWithVersionKey = a.toObject();
607+
const jsonWithVersionKey = a.toJSON();
608+
expectType<number>(objWithVersionKey.taco);
609+
expectType<number>(jsonWithVersionKey.taco);
610+
}
509611
}

test/types/schema.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ function gh11997() {
643643
function gh12003() {
644644
const baseSchemaOptions = {
645645
versionKey: false
646-
};
646+
} as const;
647647

648648
const BaseSchema = new Schema({
649649
name: String
@@ -653,6 +653,7 @@ function gh12003() {
653653

654654
type TSchemaOptions = ResolveSchemaOptions<ObtainSchemaGeneric<typeof BaseSchema, 'TSchemaOptions'>>;
655655
expectType<'type'>({} as TSchemaOptions['typeKey']);
656+
expectType<false>({} as TSchemaOptions['versionKey']);
656657

657658
expectType<{ name?: string | null }>({} as BaseSchemaType);
658659
}

types/document.d.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -257,16 +257,16 @@ declare module 'mongoose' {
257257

258258
/** The return value of this method is used in calls to JSON.stringify(doc). */
259259
toJSON(options: ToObjectOptions & { versionKey: false, virtuals: true, flattenObjectIds: true }): Omit<ObjectIdToString<FlattenMaps<Require_id<DocType & TVirtuals>>>, '__v'>;
260-
toJSON(options: ToObjectOptions & { virtuals: true, flattenObjectIds: true }): ObjectIdToString<FlattenMaps<Default__v<Require_id<DocType & TVirtuals>, TSchemaOptions>>>;
260+
toJSON(options: ToObjectOptions & { virtuals: true, flattenObjectIds: true }): ObjectIdToString<FlattenMaps<Default__v<Require_id<DocType & TVirtuals>, ResolveSchemaOptions<TSchemaOptions>>>>;
261261
toJSON(options: ToObjectOptions & { versionKey: false, virtuals: true }): Omit<Require_id<DocType & TVirtuals>, '__v'>;
262262
toJSON(options: ToObjectOptions & { versionKey: false, flattenObjectIds: true }): ObjectIdToString<FlattenMaps<Omit<Require_id<DocType>, '__v'>>>;
263-
toJSON(options: ToObjectOptions & { virtuals: true }): Default__v<Require_id<DocType & TVirtuals>, TSchemaOptions>;
263+
toJSON(options: ToObjectOptions & { virtuals: true }): Default__v<Require_id<DocType & TVirtuals>, ResolveSchemaOptions<TSchemaOptions>>;
264264
toJSON(options: ToObjectOptions & { versionKey: false }): Omit<Require_id<DocType & TVirtuals>, '__v'>;
265-
toJSON(options?: ToObjectOptions & { flattenMaps?: true, flattenObjectIds?: false }): FlattenMaps<Default__v<Require_id<DocType>, TSchemaOptions>>;
266-
toJSON(options: ToObjectOptions & { flattenObjectIds: false }): FlattenMaps<Default__v<Require_id<DocType>, TSchemaOptions>>;
267-
toJSON(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<FlattenMaps<Default__v<Require_id<DocType>, TSchemaOptions>>>;
268-
toJSON(options: ToObjectOptions & { flattenMaps: false }): Default__v<Require_id<DocType>, TSchemaOptions>;
269-
toJSON(options: ToObjectOptions & { flattenMaps: false; flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType>, TSchemaOptions>>;
265+
toJSON(options?: ToObjectOptions & { flattenMaps?: true, flattenObjectIds?: false }): FlattenMaps<Default__v<Require_id<DocType>, ResolveSchemaOptions<TSchemaOptions>>>;
266+
toJSON(options: ToObjectOptions & { flattenObjectIds: false }): FlattenMaps<Default__v<Require_id<DocType>, ResolveSchemaOptions<TSchemaOptions>>>;
267+
toJSON(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<FlattenMaps<Default__v<Require_id<DocType>, ResolveSchemaOptions<TSchemaOptions>>>>;
268+
toJSON(options: ToObjectOptions & { flattenMaps: false }): Default__v<Require_id<DocType>, ResolveSchemaOptions<TSchemaOptions>>;
269+
toJSON(options: ToObjectOptions & { flattenMaps: false, flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType>, ResolveSchemaOptions<TSchemaOptions>>>;
270270

271271
toJSON<T = Default__v<Require_id<DocType>, TSchemaOptions>>(options?: ToObjectOptions & { flattenMaps?: true, flattenObjectIds?: false }): FlattenMaps<T>;
272272
toJSON<T = Default__v<Require_id<DocType>, TSchemaOptions>>(options: ToObjectOptions & { flattenObjectIds: false }): FlattenMaps<T>;
@@ -276,14 +276,14 @@ declare module 'mongoose' {
276276

277277
/** Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)). */
278278
toObject(options: ToObjectOptions & { versionKey: false, virtuals: true, flattenObjectIds: true }): Omit<ObjectIdToString<Require_id<DocType & TVirtuals>>, '__v'>;
279-
toObject(options: ToObjectOptions & { virtuals: true, flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>, TSchemaOptions>>;
279+
toObject(options: ToObjectOptions & { virtuals: true, flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>, ResolveSchemaOptions<TSchemaOptions>>>;
280280
toObject(options: ToObjectOptions & { versionKey: false, flattenObjectIds: true }): Omit<ObjectIdToString<Require_id<DocType & TVirtuals>>, '__v'>;
281281
toObject(options: ToObjectOptions & { versionKey: false, virtuals: true }): Omit<Require_id<DocType & TVirtuals>, '__v'>;
282-
toObject(options: ToObjectOptions & { virtuals: true }): Default__v<Require_id<DocType & TVirtuals>, TSchemaOptions>;
282+
toObject(options: ToObjectOptions & { virtuals: true }): Default__v<Require_id<DocType & TVirtuals>, ResolveSchemaOptions<TSchemaOptions>>;
283283
toObject(options: ToObjectOptions & { versionKey: false }): Omit<Require_id<DocType & TVirtuals>, '__v'>;
284-
toObject(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>, TSchemaOptions>>;
285-
toObject(options?: ToObjectOptions): Default__v<Require_id<DocType>, TSchemaOptions>;
286-
toObject<T>(options?: ToObjectOptions): Default__v<Require_id<T>, TSchemaOptions>;
284+
toObject(options: ToObjectOptions & { flattenObjectIds: true }): ObjectIdToString<Default__v<Require_id<DocType & TVirtuals>, ResolveSchemaOptions<TSchemaOptions>>>;
285+
toObject(options?: ToObjectOptions): Default__v<Require_id<DocType>, ResolveSchemaOptions<TSchemaOptions>>;
286+
toObject<T>(options?: ToObjectOptions): Default__v<Require_id<T>, ResolveSchemaOptions<TSchemaOptions>>;
287287

288288
/** Clears the modified state on the specified path. */
289289
unmarkModified<T extends keyof DocType>(path: T): void;

types/index.d.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,21 @@ declare module 'mongoose' {
140140
? IfAny<U, T & { _id: Types.ObjectId }, T & Required<{ _id: U }>>
141141
: T & { _id: Types.ObjectId };
142142

143-
export type Default__v<T, TSchemaOptions = {}> = TSchemaOptions extends { versionKey: false } ? T : T extends { __v?: infer U }
143+
export type Default__v<T, TSchemaOptions = {}> = TSchemaOptions extends { versionKey: false }
144144
? T
145-
: T & { __v: number };
145+
: TSchemaOptions extends { versionKey: infer VK }
146+
? (
147+
// If VK is a *literal* string, add that property
148+
T & {
149+
[K in VK as K extends string
150+
? (string extends K ? never : K) // drop if wide string
151+
: never
152+
]: number
153+
}
154+
)
155+
: T extends { __v?: infer U }
156+
? T
157+
: T & { __v: number };
146158

147159
/** Helper type for getting the hydrated document type from the raw document type. The hydrated document type is what `new MyModel()` returns. */
148160
export type HydratedDocument<

0 commit comments

Comments
 (0)