Skip to content

Commit 905b28b

Browse files
committed
BREAKING CHANGE: allow null for optional fields in TypeScript
Fix #12748 Re: #12781
1 parent d5e9176 commit 905b28b

File tree

5 files changed

+80
-70
lines changed

5 files changed

+80
-70
lines changed

test/types/models.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ function gh12100() {
477477
const TestModel = model('test', schema_with_string_id);
478478
const obj = new TestModel();
479479

480-
expectType<string>(obj._id);
480+
expectType<string | null>(obj._id);
481481
})();
482482

483483
(async function gh12094() {
@@ -644,7 +644,7 @@ async function gh13705() {
644644
const schema = new Schema({ name: String });
645645
const TestModel = model('Test', schema);
646646

647-
type ExpectedLeanDoc = (mongoose.FlattenMaps<{ name?: string }> & { _id: mongoose.Types.ObjectId });
647+
type ExpectedLeanDoc = (mongoose.FlattenMaps<{ name?: string | null }> & { _id: mongoose.Types.ObjectId });
648648

649649
const findByIdRes = await TestModel.findById('0'.repeat(24), undefined, { lean: true });
650650
expectType<ExpectedLeanDoc | null>(findByIdRes);

test/types/queries.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ async function gh12342_manual() {
393393

394394
async function gh12342_auto() {
395395
interface Project {
396-
name?: string, stars?: number
396+
name?: string | null,
397+
stars?: number | null
397398
}
398399

399400
const ProjectSchema = new Schema({
@@ -483,8 +484,8 @@ async function gh13224() {
483484
const UserModel = model('User', userSchema);
484485

485486
const u1 = await UserModel.findOne().select(['name']).orFail();
486-
expectType<string | undefined>(u1.name);
487-
expectType<number | undefined>(u1.age);
487+
expectType<string | undefined | null>(u1.name);
488+
expectType<number | undefined | null>(u1.age);
488489
expectAssignable<Function>(u1.toObject);
489490

490491
const u2 = await UserModel.findOne().select<{ name?: string }>(['name']).orFail();

test/types/querycursor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type ITest = ReturnType<(typeof Test)['hydrate']>;
1010
Test.find().cursor().
1111
eachAsync(async(doc: ITest) => {
1212
expectType<Types.ObjectId>(doc._id);
13-
expectType<string | undefined>(doc.name);
13+
expectType<string | undefined | null>(doc.name);
1414
}).
1515
then(() => console.log('Done!'));
1616

test/types/schema.test.ts

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -372,50 +372,50 @@ export function autoTypedSchema() {
372372
}
373373

374374
type TestSchemaType = {
375-
string1?: string;
376-
string2?: string;
377-
string3?: string;
378-
string4?: string;
375+
string1?: string | null;
376+
string2?: string | null;
377+
string3?: string | null;
378+
string4?: string | null;
379379
string5: string;
380-
number1?: number;
381-
number2?: number;
382-
number3?: number;
383-
number4?: number;
380+
number1?: number | null;
381+
number2?: number | null;
382+
number3?: number | null;
383+
number4?: number | null;
384384
number5: number;
385-
date1?: Date;
386-
date2?: Date;
387-
date3?: Date;
388-
date4?: Date;
385+
date1?: Date | null;
386+
date2?: Date | null;
387+
date3?: Date | null;
388+
date4?: Date | null;
389389
date5: Date;
390-
buffer1?: Buffer;
391-
buffer2?: Buffer;
392-
buffer3?: Buffer;
393-
buffer4?: Buffer;
394-
boolean1?: boolean;
395-
boolean2?: boolean;
396-
boolean3?: boolean;
397-
boolean4?: boolean;
390+
buffer1?: Buffer | null;
391+
buffer2?: Buffer | null;
392+
buffer3?: Buffer | null;
393+
buffer4?: Buffer | null;
394+
boolean1?: boolean | null;
395+
boolean2?: boolean | null;
396+
boolean3?: boolean | null;
397+
boolean4?: boolean | null;
398398
boolean5: boolean;
399-
mixed1?: any;
400-
mixed2?: any;
401-
mixed3?: any;
402-
objectId1?: Types.ObjectId;
403-
objectId2?: Types.ObjectId;
404-
objectId3?: Types.ObjectId;
405-
customSchema?: Int8;
406-
map1?: Map<string, string>;
407-
map2?: Map<string, number>;
399+
mixed1?: any | null;
400+
mixed2?: any | null;
401+
mixed3?: any | null;
402+
objectId1?: Types.ObjectId | null;
403+
objectId2?: Types.ObjectId | null;
404+
objectId3?: Types.ObjectId | null;
405+
customSchema?: Int8 | null;
406+
map1?: Map<string, string> | null;
407+
map2?: Map<string, number> | null;
408408
array1: string[];
409409
array2: any[];
410410
array3: any[];
411411
array4: any[];
412412
array5: any[];
413413
array6: string[];
414-
array7?: string[];
415-
array8?: string[];
416-
decimal1?: Types.Decimal128;
417-
decimal2?: Types.Decimal128;
418-
decimal3?: Types.Decimal128;
414+
array7?: string[] | null;
415+
array8?: string[] | null;
416+
decimal1?: Types.Decimal128 | null;
417+
decimal2?: Types.Decimal128 | null;
418+
decimal3?: Types.Decimal128 | null;
419419
};
420420

421421
const TestSchema = new Schema({
@@ -546,17 +546,17 @@ export function autoTypedSchema() {
546546
export type AutoTypedSchemaType = {
547547
schema: {
548548
userName: string;
549-
description?: string;
549+
description?: string | null;
550550
nested?: {
551551
age: number;
552-
hobby?: string
553-
},
554-
favoritDrink?: 'Tea' | 'Coffee',
552+
hobby?: string | null
553+
} | null,
554+
favoritDrink?: 'Tea' | 'Coffee' | null,
555555
favoritColorMode: 'dark' | 'light'
556-
friendID?: Types.ObjectId;
556+
friendID?: Types.ObjectId | null;
557557
nestedArray: Types.DocumentArray<{
558558
date: Date;
559-
messages?: number;
559+
messages?: number | null;
560560
}>
561561
}
562562
, statics: {
@@ -634,7 +634,7 @@ function gh12003() {
634634
type TSchemaOptions = ResolveSchemaOptions<ObtainSchemaGeneric<typeof BaseSchema, 'TSchemaOptions'>>;
635635
expectType<'type'>({} as TSchemaOptions['typeKey']);
636636

637-
expectType<{ name?: string }>({} as BaseSchemaType);
637+
expectType<{ name?: string | null }>({} as BaseSchemaType);
638638
}
639639

640640
function gh11987() {
@@ -670,7 +670,7 @@ function gh12030() {
670670
}
671671
]>;
672672
expectType<{
673-
username?: string
673+
username?: string | null
674674
}[]>({} as A);
675675

676676
type B = ObtainDocumentType<{
@@ -682,13 +682,13 @@ function gh12030() {
682682
}>;
683683
expectType<{
684684
users: {
685-
username?: string
685+
username?: string | null
686686
}[];
687687
}>({} as B);
688688

689689
expectType<{
690690
users: {
691-
username?: string
691+
username?: string | null
692692
}[];
693693
}>({} as InferSchemaType<typeof Schema1>);
694694

@@ -710,7 +710,7 @@ function gh12030() {
710710
expectType<{
711711
users: Types.DocumentArray<{
712712
credit: number;
713-
username?: string;
713+
username?: string | null;
714714
}>;
715715
}>({} as InferSchemaType<typeof Schema3>);
716716

@@ -719,7 +719,7 @@ function gh12030() {
719719
data: { type: { role: String }, default: {} }
720720
});
721721

722-
expectType<{ data: { role?: string } }>({} as InferSchemaType<typeof Schema4>);
722+
expectType<{ data: { role?: string | null } }>({} as InferSchemaType<typeof Schema4>);
723723

724724
const Schema5 = new Schema({
725725
data: { type: { role: Object }, default: {} }
@@ -744,7 +744,7 @@ function gh12030() {
744744
track?: {
745745
backupCount: number;
746746
count: number;
747-
};
747+
} | null;
748748
}>({} as InferSchemaType<typeof Schema6>);
749749

750750
}
@@ -821,7 +821,7 @@ function gh12450() {
821821
});
822822

823823
expectType<{
824-
user?: Types.ObjectId;
824+
user?: Types.ObjectId | null;
825825
}>({} as InferSchemaType<typeof ObjectIdSchema>);
826826

827827
const Schema2 = new Schema({
@@ -836,14 +836,14 @@ function gh12450() {
836836
decimalValue: { type: Schema.Types.Decimal128 }
837837
});
838838

839-
expectType<{ createdAt: Date, decimalValue?: Types.Decimal128 }>({} as InferSchemaType<typeof Schema3>);
839+
expectType<{ createdAt: Date, decimalValue?: Types.Decimal128 | null }>({} as InferSchemaType<typeof Schema3>);
840840

841841
const Schema4 = new Schema({
842842
createdAt: { type: Date },
843843
decimalValue: { type: Schema.Types.Decimal128 }
844844
});
845845

846-
expectType<{ createdAt?: Date, decimalValue?: Types.Decimal128 }>({} as InferSchemaType<typeof Schema4>);
846+
expectType<{ createdAt?: Date | null, decimalValue?: Types.Decimal128 | null }>({} as InferSchemaType<typeof Schema4>);
847847
}
848848

849849
function gh12242() {
@@ -867,13 +867,13 @@ function testInferTimestamps() {
867867
// an error "Parameter type { createdAt: Date; updatedAt: Date; name?: string | undefined; }
868868
// is not identical to argument type { createdAt: NativeDate; updatedAt: NativeDate; } &
869869
// { name?: string | undefined; }"
870-
expectType<{ createdAt: Date, updatedAt: Date } & { name?: string }>({} as WithTimestamps);
870+
expectType<{ createdAt: Date, updatedAt: Date } & { name?: string | null }>({} as WithTimestamps);
871871

872872
const schema2 = new Schema({
873873
name: String
874874
}, {
875875
timestamps: true,
876-
methods: { myName(): string | undefined {
876+
methods: { myName(): string | undefined | null {
877877
return this.name;
878878
} }
879879
});
@@ -883,7 +883,7 @@ function testInferTimestamps() {
883883
// an error "Parameter type { createdAt: Date; updatedAt: Date; name?: string | undefined; }
884884
// is not identical to argument type { createdAt: NativeDate; updatedAt: NativeDate; } &
885885
// { name?: string | undefined; }"
886-
expectType<{ name?: string }>({} as WithTimestamps2);
886+
expectType<{ name?: string | null }>({} as WithTimestamps2);
887887
}
888888

889889
function gh12431() {
@@ -893,25 +893,25 @@ function gh12431() {
893893
});
894894

895895
type Example = InferSchemaType<typeof testSchema>;
896-
expectType<{ testDate?: Date, testDecimal?: Types.Decimal128 }>({} as Example);
896+
expectType<{ testDate?: Date | null, testDecimal?: Types.Decimal128 | null }>({} as Example);
897897
}
898898

899899
async function gh12593() {
900900
const testSchema = new Schema({ x: { type: Schema.Types.UUID } });
901901

902902
type Example = InferSchemaType<typeof testSchema>;
903-
expectType<{ x?: Buffer }>({} as Example);
903+
expectType<{ x?: Buffer | null }>({} as Example);
904904

905905
const Test = model('Test', testSchema);
906906

907907
const doc = await Test.findOne({ x: '4709e6d9-61fd-435e-b594-d748eb196d8f' }).orFail();
908-
expectType<Buffer | undefined>(doc.x);
908+
expectType<Buffer | undefined | null>(doc.x);
909909

910910
const doc2 = new Test({ x: '4709e6d9-61fd-435e-b594-d748eb196d8f' });
911-
expectType<Buffer | undefined>(doc2.x);
911+
expectType<Buffer | undefined | null>(doc2.x);
912912

913913
const doc3 = await Test.findOne({}).orFail().lean();
914-
expectType<Buffer | undefined>(doc3.x);
914+
expectType<Buffer | undefined | null>(doc3.x);
915915

916916
const arrSchema = new Schema({ arr: [{ type: Schema.Types.UUID }] });
917917

@@ -978,7 +978,7 @@ function gh12611() {
978978
expectType<{
979979
description: string;
980980
skills: Types.ObjectId[];
981-
anotherField?: string;
981+
anotherField?: string | null;
982982
}>({} as Props);
983983
}
984984

@@ -1181,7 +1181,7 @@ function gh13702() {
11811181
function gh13780() {
11821182
const schema = new Schema({ num: Schema.Types.BigInt });
11831183
type InferredType = InferSchemaType<typeof schema>;
1184-
expectType<bigint | undefined>(null as unknown as InferredType['num']);
1184+
expectType<bigint | undefined | null>(null as unknown as InferredType['num']);
11851185
}
11861186

11871187
function gh13800() {

types/inferschematype.d.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ declare module 'mongoose' {
2424
* @param {EnforcedDocType} EnforcedDocType A generic type enforced by user "provided before schema constructor".
2525
* @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition".
2626
*/
27-
type ObtainDocumentType<DocDefinition, EnforcedDocType = any, TSchemaOptions extends Record<any, any> = DefaultSchemaOptions> =
28-
IsItRecordAndNotAny<EnforcedDocType> extends true ? EnforcedDocType : {
29-
[K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
30-
OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)]: ObtainDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']>;
27+
type ObtainDocumentType<
28+
DocDefinition,
29+
EnforcedDocType = any,
30+
TSchemaOptions extends Record<any, any> = DefaultSchemaOptions
31+
> = IsItRecordAndNotAny<EnforcedDocType> extends true ?
32+
EnforcedDocType :
33+
{
34+
[
35+
K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
36+
OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)
37+
]: IsPathRequired<DocDefinition[K], TSchemaOptions['typeKey']> extends true ?
38+
ObtainDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']> :
39+
ObtainDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']> | null;
3140
};
3241

3342
/**

0 commit comments

Comments
 (0)