Skip to content

Commit ef7bada

Browse files
authored
Merge pull request #15588 from ssalbdivad/optimize-inference
optimize InferRawDocType
2 parents 65e9dcc + 6933db7 commit ef7bada

File tree

12 files changed

+740
-432
lines changed

12 files changed

+740
-432
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,6 @@ list.out
7373

7474
data
7575
fle-cluster-config.json
76+
77+
# @ark/attest (type testing)
78+
.attest

benchmarks/typescript/infer.bench.mts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { bench } from '@ark/attest';
2+
import type { Schema, InferRawDocType } from 'mongoose';
3+
4+
bench.baseline(() => {
5+
const baselineDefinition = {
6+
foo: {
7+
type: String
8+
},
9+
bar: {
10+
type: Number
11+
},
12+
dob: {
13+
type: Date,
14+
required: true
15+
}
16+
};
17+
type BaselineType = InferRawDocType<typeof baselineDefinition>;
18+
// force lazily evaluated properties to be checked
19+
type Foo = BaselineType[keyof BaselineType];
20+
});
21+
22+
const schemaDefinition = {
23+
email: {
24+
type: String,
25+
required: true
26+
},
27+
id: {
28+
type: Number,
29+
required: true
30+
},
31+
dateOfBirth: {
32+
type: Date
33+
}
34+
};
35+
36+
// force lazily evaluated properties to be checked
37+
type ValueOf<T> = T extends unknown ? T[keyof T] : never;
38+
39+
bench('InferRawDocType (basic)', () => {
40+
type UserType = InferRawDocType<typeof schemaDefinition>;
41+
// force lazily evaluated properties to be checked
42+
type Value = ValueOf<UserType>;
43+
// original 501
44+
}).types([320, 'instantiations']);
45+
46+
bench('InferRawDocType (mixed)', () => {
47+
type T = InferRawDocType<{
48+
foo: typeof Schema.Types.Mixed;
49+
bar: {};
50+
baz: ObjectConstructor;
51+
}>;
52+
// force lazily evaluated properties to be checked
53+
type Value = ValueOf<T>;
54+
// original 1626
55+
}).types([535, 'instantiations']);
56+
57+
bench('InferRawDocType (nested)', () => {
58+
type T = InferRawDocType<{
59+
foo: {
60+
1: {
61+
2: {
62+
type: String;
63+
};
64+
};
65+
};
66+
bar: {
67+
3: {
68+
4: {
69+
type: 'string';
70+
};
71+
};
72+
};
73+
baz: {
74+
5: {
75+
6: {
76+
type: 'String';
77+
};
78+
};
79+
};
80+
}>;
81+
82+
type Value = ValueOf<ValueOf<ValueOf<T>>>;
83+
// original 4214
84+
}).types([2097, 'instantiations']);

lib/schemaType.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ function SchemaType(path, options, instance) {
4444
this[schemaTypeSymbol] = true;
4545
this.path = path;
4646
this.instance = instance;
47+
this.schemaName = this.constructor.schemaName;
4748
this.validators = [];
4849
this.getters = this.constructor.hasOwnProperty('getters') ?
4950
this.constructor.getters.slice() :

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"sift": "17.1.3"
3030
},
3131
"devDependencies": {
32+
"@ark/attest": "0.48.2",
3233
"@babel/core": "7.28.0",
3334
"@babel/preset-env": "7.28.0",
3435
"@mongodb-js/mongodb-downloader": "^0.4.2",
@@ -111,7 +112,8 @@
111112
"test-encryption": "mocha --exit ./test/encryption/*.test.js",
112113
"tdd": "mocha ./test/*.test.js --inspect --watch --recursive --watch-files ./**/*.{js,ts}",
113114
"test-coverage": "nyc --reporter=html --reporter=text npm test",
114-
"ts-benchmark": "cd ./benchmarks/typescript/simple && npm install && npm run benchmark | node ../../../scripts/tsc-diagnostics-check"
115+
"ts-benchmark": "cd ./benchmarks/typescript/simple && npm install && npm run benchmark | node ../../../scripts/tsc-diagnostics-check",
116+
"attest-benchmark": "node ./benchmarks/typescript/infer.bench.mts"
115117
},
116118
"main": "./index.js",
117119
"types": "./types/index.d.ts",

test/schemaname.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const start = require('./common');
5+
const Schema = start.mongoose.Schema;
6+
7+
describe('schemaname', function() {
8+
it('exists on constructor', function() {
9+
const schema = new Schema({ name: String });
10+
const name = schema.path('name');
11+
assert.equal(name.constructor.schemaName, 'String');
12+
});
13+
it('exists on instance', function() {
14+
const schema = new Schema({ name: String });
15+
const name = schema.path('name');
16+
assert.equal(name.schemaName, 'String');
17+
});
18+
});

test/types/inferrawdoctype.test.ts

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InferRawDocType } from 'mongoose';
1+
import { InferRawDocType, type InferSchemaType, type ResolveTimestamps, type Schema, type Types } from 'mongoose';
22
import { expectType, expectError } from 'tsd';
33

44
function gh14839() {
@@ -20,6 +20,110 @@ function gh14839() {
2020
}
2121
};
2222

23-
type UserType = InferRawDocType< typeof schemaDefinition>;
24-
expectType<{ email: string, password: string, dateOfBirth: Date }>({} as UserType);
23+
type UserType = InferRawDocType<typeof schemaDefinition>;
24+
expectType<{ email: string; password: string; dateOfBirth: Date }>({} as UserType);
25+
}
26+
27+
function optionality() {
28+
const schemaDefinition = {
29+
name: {
30+
type: String,
31+
required: true
32+
},
33+
dateOfBirth: {
34+
type: Number
35+
}
36+
};
37+
38+
type UserType = InferRawDocType<typeof schemaDefinition>;
39+
expectType<{ name: string; dateOfBirth?: number | null | undefined }>({} as UserType);
40+
}
41+
42+
type SchemaOptionsWithTimestamps<t> = {
43+
typeKey: 'type';
44+
id: true;
45+
_id: true;
46+
timestamps: t;
47+
versionKey: '__v';
48+
};
49+
50+
function Timestamps() {
51+
const schemaDefinition = {
52+
name: {
53+
type: String,
54+
required: true
55+
},
56+
dateOfBirth: {
57+
type: Number
58+
}
59+
};
60+
61+
type UserType = InferRawDocType<typeof schemaDefinition, SchemaOptionsWithTimestamps<true>>;
62+
expectType<{
63+
name: string;
64+
dateOfBirth?: number | null | undefined;
65+
createdAt: NativeDate;
66+
updatedAt: NativeDate;
67+
}>({} as UserType);
68+
69+
type Resolved = ResolveTimestamps<
70+
{ foo: true },
71+
{
72+
timestamps: {
73+
createdAt: 'bar';
74+
};
75+
}
76+
>;
77+
78+
expectType<Resolved>(
79+
{} as {
80+
foo: true;
81+
bar: NativeDate;
82+
}
83+
);
84+
}
85+
86+
function DefinitionTypes() {
87+
type Actual = InferRawDocType<{
88+
lowercaseString: 'string';
89+
uppercaseString: 'String';
90+
stringConstructor: typeof String;
91+
schemaConstructor: typeof Schema.Types.String;
92+
stringInstance: String;
93+
schemaInstance: Schema.Types.String;
94+
}>;
95+
96+
expectType<{
97+
lowercaseString?: string | null | undefined;
98+
uppercaseString?: string | null | undefined;
99+
stringConstructor?: string | null | undefined;
100+
schemaConstructor?: string | null | undefined;
101+
stringInstance?: string | null | undefined;
102+
schemaInstance?: string | null | undefined;
103+
}>({} as Actual);
104+
}
105+
106+
function MoreDefinitionTypes() {
107+
type Actual = InferRawDocType<{
108+
// ensure string literals are not inferred as string
109+
numberString: 'number';
110+
// ensure a schema constructor with methods is not assignable to an empty one
111+
objectIdConstructor: typeof Schema.Types.ObjectId;
112+
// ensure a schema instance with methods is not assignable to an empty one
113+
objectIdInstance: Schema.Types.ObjectId;
114+
}>;
115+
116+
expectType<{
117+
numberString?: number | null | undefined;
118+
// these should not fallback to Boolean, which has no methods
119+
objectIdConstructor?: Types.ObjectId | null | undefined;
120+
objectIdInstance?: Types.ObjectId | null | undefined;
121+
}>({} as Actual);
122+
}
123+
124+
function HandlesAny() {
125+
type ActualShallow = InferRawDocType<any>;
126+
expectType<{ [x: PropertyKey]: any }>({} as ActualShallow);
127+
type ActualNested = InferRawDocType<Record<string, any>>;
128+
expectType<{ [x: string]: any }>({} as ActualNested);
25129
}

test/types/schema.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1640,7 +1640,6 @@ function gh13215() {
16401640
>;
16411641
type User = {
16421642
userName: string;
1643-
} & {
16441643
date: Date;
16451644
};
16461645

types/index.d.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -566,11 +566,17 @@ declare module 'mongoose' {
566566
static ObjectId: typeof Schema.Types.ObjectId;
567567
}
568568

569-
export type NumberSchemaDefinition = typeof Number | 'number' | 'Number' | typeof Schema.Types.Number;
570-
export type StringSchemaDefinition = typeof String | 'string' | 'String' | typeof Schema.Types.String;
571-
export type BooleanSchemaDefinition = typeof Boolean | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean;
572-
export type DateSchemaDefinition = DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date;
573-
export type ObjectIdSchemaDefinition = 'ObjectId' | 'ObjectID' | typeof Schema.Types.ObjectId;
569+
export type NumberSchemaDefinition = typeof Number | 'number' | 'Number' | typeof Schema.Types.Number | Schema.Types.Number;
570+
export type StringSchemaDefinition = typeof String | 'string' | 'String' | typeof Schema.Types.String | Schema.Types.String;
571+
export type BooleanSchemaDefinition = typeof Boolean | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean | Schema.Types.Boolean;
572+
export type DateSchemaDefinition = DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date | Schema.Types.Date;
573+
export type ObjectIdSchemaDefinition = 'ObjectId' | 'ObjectID' | typeof Schema.Types.ObjectId | Schema.Types.ObjectId | Types.ObjectId;
574+
export type BufferSchemaDefinition = typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer;
575+
export type Decimal128SchemaDefinition = 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 | Schema.Types.Decimal128 | Types.Decimal128;
576+
export type BigintSchemaDefinition = 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | Schema.Types.BigInt | typeof BigInt | BigInt;
577+
export type UuidSchemaDefinition = 'uuid' | 'UUID' | typeof Schema.Types.UUID | Schema.Types.UUID;
578+
export type MapSchemaDefinition = MapConstructor | 'Map' | typeof Schema.Types.Map;
579+
export type UnionSchemaDefinition = 'Union' | 'union' | typeof Schema.Types.Union;
574580

575581
export type SchemaDefinitionWithBuiltInClass<T> = T extends number
576582
? NumberSchemaDefinition
@@ -582,7 +588,9 @@ declare module 'mongoose' {
582588
? DateSchemaDefinition
583589
: (Function | string);
584590

585-
export type SchemaDefinitionProperty<T = undefined, EnforcedDocType = any, THydratedDocumentType = HydratedDocument<EnforcedDocType>> = SchemaDefinitionWithBuiltInClass<T>
591+
export type SchemaDefinitionProperty<T = undefined, EnforcedDocType = any, THydratedDocumentType = HydratedDocument<EnforcedDocType>> =
592+
// ThisType intersection here avoids corrupting ThisType for SchemaTypeOptions (see test gh11828)
593+
| SchemaDefinitionWithBuiltInClass<T> & ThisType<EnforcedDocType>
586594
| SchemaTypeOptions<T extends undefined ? any : T, EnforcedDocType, THydratedDocumentType>
587595
| typeof SchemaType
588596
| Schema<any, any, any>

0 commit comments

Comments
 (0)