Skip to content
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

Feature: Infer schema type automatically. #11487

Closed
Closed
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b7f70f8
add tsafe package to devDependencies
mohammad0-0ahmad Mar 6, 2022
8c891e4
create utilities-types helps to infer schema type
mohammad0-0ahmad Mar 6, 2022
3c2fd6b
Make some changes that obtain schema type & create some tests
mohammad0-0ahmad Mar 6, 2022
c6b6324
Remove tsafe & rewrite related tests
mohammad0-0ahmad Mar 7, 2022
9f30615
Reimplement auto DocType inference
mohammad0-0ahmad Mar 7, 2022
cc33fc2
Merge branch 'Automattic:master' into master
mohammad0-0ahmad Mar 7, 2022
d5f11c7
Make Model instance show correct type instead of "any"
mohammad0-0ahmad Mar 7, 2022
083ce6f
Refact ApplyBasicQueryCasting type to make schema properties types vi…
mohammad0-0ahmad Mar 7, 2022
f33b24a
Make schema props type visible
mohammad0-0ahmad Mar 7, 2022
c3ae6c5
Make InferredSchemaType support nested schemas
mohammad0-0ahmad Mar 7, 2022
b7152f7
Make statics functions auto typed by supplying them to schema options
mohammad0-0ahmad Mar 7, 2022
e6aa4f1
Merge https://github.com/Automattic/mongoose
mohammad0-0ahmad Mar 7, 2022
92fc04a
Merge branch 'Automattic:master'
mohammad0-0ahmad Mar 7, 2022
d5cbee3
Refact Schema & Model types
mohammad0-0ahmad Mar 9, 2022
11b6bed
Refact some infer-doc-type import lines
mohammad0-0ahmad Mar 9, 2022
6020771
Create some tests for Model functions
mohammad0-0ahmad Mar 10, 2022
d5059d5
Infer document instance methods from methods object supplied in schem…
mohammad0-0ahmad Mar 10, 2022
8fe4262
Use auto infered docType as default type every where
mohammad0-0ahmad Mar 10, 2022
9f4b974
Improve ResolvePathType ability to resolve correct type of types stri…
mohammad0-0ahmad Mar 11, 2022
4a5d3fa
create & implement FlexibleObject & refact typos
mohammad0-0ahmad Mar 12, 2022
a57b871
create & implement FlexibleObject & refact typos
mohammad0-0ahmad Mar 12, 2022
e5e42b7
Merge branch 'master' of https://github.com/mohammad0-0ahmad-forks/mo…
mohammad0-0ahmad Mar 12, 2022
6d4b08a
Merge branch 'master' of https://github.com/Automattic/mongoose
mohammad0-0ahmad Mar 12, 2022
3e23cae
Delete AnyKeys type & refact create & insertMany types
mohammad0-0ahmad Mar 12, 2022
15a9213
Support obtaining enum type
mohammad0-0ahmad Mar 13, 2022
638a0b6
Support custom typeKey
mohammad0-0ahmad Mar 13, 2022
c938e1e
Refact previous commit
mohammad0-0ahmad Mar 13, 2022
ce0f299
Provide JSDoc comments for inferschematype types.
mohammad0-0ahmad Mar 22, 2022
bc62a77
Merge branch 'master' of https://github.com/Automattic/mongoose
mohammad0-0ahmad Mar 23, 2022
6b424d4
Merge branch 'master' of https://github.com/Automattic/mongoose
mohammad0-0ahmad Mar 23, 2022
8fe3bd2
chore: release 6.2.8
vkarpov15 Mar 23, 2022
2df2e8b
test: add coverage for #11480
vkarpov15 Mar 23, 2022
dc1ede2
date expires option - add unit, behavior, example
boly38 Mar 23, 2022
204495c
fix typo
AbdelrahmanHafez Mar 23, 2022
b621612
Merge pull request #11557 from boly38/patch-1
AbdelrahmanHafez Mar 23, 2022
5127024
perf(document+model): make a few small optimizations re: #11380
vkarpov15 Mar 23, 2022
7f7735c
quick fix re: #103800
vkarpov15 Mar 23, 2022
f4a112b
style: fix lint re: #11380
vkarpov15 Mar 23, 2022
93dd492
add tsafe package to devDependencies
mohammad0-0ahmad Mar 6, 2022
2743a05
create utilities-types helps to infer schema type
mohammad0-0ahmad Mar 6, 2022
4ea70f0
Make some changes that obtain schema type & create some tests
mohammad0-0ahmad Mar 6, 2022
8ce5c39
Remove tsafe & rewrite related tests
mohammad0-0ahmad Mar 7, 2022
85e4c1c
Reimplement auto DocType inference
mohammad0-0ahmad Mar 7, 2022
6961e1c
Make Model instance show correct type instead of "any"
mohammad0-0ahmad Mar 7, 2022
0795b69
Refact ApplyBasicQueryCasting type to make schema properties types vi…
mohammad0-0ahmad Mar 7, 2022
40cf9a0
Make schema props type visible
mohammad0-0ahmad Mar 7, 2022
2139bce
Make InferredSchemaType support nested schemas
mohammad0-0ahmad Mar 7, 2022
622071b
Make statics functions auto typed by supplying them to schema options
mohammad0-0ahmad Mar 7, 2022
3c539a9
Refact Schema & Model types
mohammad0-0ahmad Mar 9, 2022
3fc1cb7
Refact some infer-doc-type import lines
mohammad0-0ahmad Mar 9, 2022
f1c0a9c
Create some tests for Model functions
mohammad0-0ahmad Mar 10, 2022
b4c5e0a
Infer document instance methods from methods object supplied in schem…
mohammad0-0ahmad Mar 10, 2022
c9aec3a
Use auto infered docType as default type every where
mohammad0-0ahmad Mar 10, 2022
818bac0
Improve ResolvePathType ability to resolve correct type of types stri…
mohammad0-0ahmad Mar 11, 2022
239846e
create & implement FlexibleObject & refact typos
mohammad0-0ahmad Mar 12, 2022
3696661
Delete AnyKeys type & refact create & insertMany types
mohammad0-0ahmad Mar 12, 2022
86558a5
Support obtaining enum type
mohammad0-0ahmad Mar 13, 2022
52551a9
Support custom typeKey
mohammad0-0ahmad Mar 13, 2022
605a089
Refact previous commit
mohammad0-0ahmad Mar 13, 2022
797e1dc
Provide JSDoc comments for inferschematype types.
mohammad0-0ahmad Mar 22, 2022
bc4953c
Merge branch 'master' of https://github.com/Automattic/mongoose
mohammad0-0ahmad Mar 23, 2022
f2f38f8
Merge branch 'master' of https://github.com/mohammad0-0ahmad-forks/mo…
mohammad0-0ahmad Mar 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ function Schema(obj, options) {
this.inherits = {};
this.callQueue = [];
this._indexes = [];
this.methods = {};
this.methods = (options && options.methods) || {};
mohammad0-0ahmad marked this conversation as resolved.
Show resolved Hide resolved
this.methodOptions = {};
this.statics = {};
this.statics = (options && options.statics) || {};
this.tree = {};
this.query = {};
this.childSchemas = [];
Expand Down
8 changes: 8 additions & 0 deletions test/document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11198,4 +11198,12 @@ describe('document', function() {

await doc.validate();
});

it('should give an instance function back rather than undefined (m0_0a)', function M0_0aModelJS() {
const testSchema = new mongoose.Schema({}, { methods: { instanceFn() { return 'Returned from DocumentInstanceFn'; } } });
const TestModel = mongoose.model('TestModel', testSchema);
const TestDocument = new TestModel({});
assert.equal(TestDocument.instanceFn(), 'Returned from DocumentInstanceFn');
});

});
10 changes: 9 additions & 1 deletion test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8485,4 +8485,12 @@ function pick(obj, keys) {
}
}
return newObj;
}
}

describe('Check if statics functions that is supplied in schema option is availabe (m0_0a)', function() {
it('should give a static function back rather than undefined', function M0_0aModelJS() {
const testSchema = new mongoose.Schema({}, { statics: { staticFn() { return 'Returned from staticFn'; } } });
Copy link
Collaborator

Choose a reason for hiding this comment

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

so do we add a new option?
Probably also means that we have to modify the docs too.

@vkarpov15
Is adding statics here ok? Or does it could it break something with loadClass or so?

const TestModel = mongoose.model('TestModel', testSchema);
assert.equal(TestModel.staticFn(), 'Returned from staticFn');
});
});
2 changes: 1 addition & 1 deletion test/types/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Schema, model, Document, Types } from 'mongoose';
const schema: Schema = new Schema({ name: { type: 'String' } });

interface ITest extends Document {
_id?: Types.ObjectId;
_id?: Types.ObjectId | string;
name?: string;
}

Expand Down
8 changes: 7 additions & 1 deletion test/types/document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ function gh11435() {
const ItemSchema = new Schema<Item>({ name: String });

ItemSchema.pre('validate', function preValidate() {
expectType<Model<unknown>>(this.model('Item1'));
expectType<Model<Item>>(this.model('Item1'));
});

const AutoTypedItemSchema = new Schema({ name: String });

AutoTypedItemSchema.pre('validate', function preValidate() {
expectType<Model<Item>>(this.model('Item1'));
});
}
4 changes: 2 additions & 2 deletions test/types/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Schema, model, Model, Document, SaveOptions, Query, Aggregate, HydratedDocument, PreSaveMiddlewareFunction } from 'mongoose';
import { expectError, expectType } from 'tsd';
import { expectAssignable, expectError } from 'tsd';

interface ITest extends Document {
name?: string;
Expand Down Expand Up @@ -81,6 +81,6 @@ const Test = model<ITest>('Test', schema);

function gh11257(): void {
schema.pre('save', { document: true }, function() {
expectType<HydratedDocument<ITest>>(this);
expectAssignable<HydratedDocument<ITest>>(this);
});
}
51 changes: 49 additions & 2 deletions test/types/models.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Schema, Document, Model, Types, connection, model } from 'mongoose';
import { expectError } from 'tsd';
import { expectAssignable, expectError, expectType } from 'tsd';
import { M0_0aAutoTypedSchemaType, m0_0aSchema } from './schema.test';

function conventionalSyntax(): void {
interface ITest extends Document {
Expand Down Expand Up @@ -209,4 +210,50 @@ function inheritance() {

Project.createCollection({ expires: '5 seconds' });
Project.createCollection({ expireAfterSeconds: 5 });
expectError(Project.createCollection({ expireAfterSeconds: '5 seconds' }));
expectError(Project.createCollection({ expireAfterSeconds: '5 seconds' }));

export async function m0_0aModel() {
const AutoTypeSchema = m0_0aSchema();
const AutoTypeModel = model('AutoTypeModel', AutoTypeSchema);

/* -------------------------------------------------------------------------- */
/* Model-functions-test */
/* -------------------------------------------------------------------------- */
// Create should works with arbitrary objects.
const randomObject = await AutoTypeModel.create({ unExistKey: 'unExistKey', description: 'st' });
expectType<string>(randomObject.unExistKey);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This shows intellisense for both doc type and new inserted props.
@vkarpov15 What do you think about this test?

expectType<M0_0aAutoTypedSchemaType['schema']['userName']>(randomObject.userName);

const testDoc1 = await AutoTypeModel.create({ userName: 'M0_0a' });
expectType<M0_0aAutoTypedSchemaType['schema']['userName']>(testDoc1.userName);
expectType<M0_0aAutoTypedSchemaType['schema']['description']>(testDoc1.description);

const testDoc2 = await AutoTypeModel.findOne({ userName: 'M0_0a' });
expectType<M0_0aAutoTypedSchemaType['schema']['userName'] | undefined>(testDoc2?.userName);
expectType<M0_0aAutoTypedSchemaType['schema']['description'] | undefined>(testDoc2?.description);

/* -------------------------------------------------------------------------- */
/* Model-statics-functions-test */
/* -------------------------------------------------------------------------- */

expectType<ReturnType<M0_0aAutoTypedSchemaType['statics']['staticFn']>>(AutoTypeModel.staticFn());

/* -------------------------------------------------------------------------- */
/* Instance-Test */
/* -------------------------------------------------------------------------- */

const AutoTypeModelInstance = new AutoTypeModel({});

expectType<M0_0aAutoTypedSchemaType['schema']['userName']>(AutoTypeModelInstance.userName);
expectType<M0_0aAutoTypedSchemaType['schema']['favoritDrink']>(AutoTypeModelInstance.favoritDrink);
expectType<M0_0aAutoTypedSchemaType['schema']['favoritColorMode']>(AutoTypeModelInstance.favoritColorMode);

/* -------------------------------------------------------------------------- */
/* Document-Instance-Methods */
/* -------------------------------------------------------------------------- */

expectType<ReturnType<M0_0aAutoTypedSchemaType['methods']['instanceFn']>>(AutoTypeModelInstance.instanceFn());


return AutoTypeModel;
}
169 changes: 167 additions & 2 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Schema, Document, SchemaDefinition, Model, Types } from 'mongoose';
import { expectType, expectNotType, expectError } from 'tsd';
import { Schema, Document, SchemaDefinition, Model, Types, SchemaType, InferSchemaType } from 'mongoose';
import { expectType, expectError } from 'tsd';

enum Genre {
Action,
Expand Down Expand Up @@ -305,3 +305,168 @@ function gh11435(): void {
new Schema({}, { expires: '5 seconds' });
expectError(new Schema({}, { expireAfterSeconds: '5 seconds' }));
new Schema({}, { expireAfterSeconds: 5 });

export type M0_0aAutoTypedSchemaType = {
schema: {
userName: string;
description?: string;
nested?: {
age: number;
hobby?: string
},
favoritDrink?: 'Tea' | 'Coffee',
favoritColorMode:'dark' | 'light'
}
, statics: {
staticFn:()=>'Returned from staticFn'
},
methods: {
instanceFn : ()=> 'Returned from DocumentInstanceFn'
}
}

export function m0_0aSchema() {
const AutoTypedSchema = new Schema({
userName: {
type: String,
required: [true, 'userName is required']
},
description: String,
nested: new Schema({
age: {
type: Number,
required: true
},
hobby: {
type: String,
required: false
}
}),
favoritDrink: {
type: String,
enum: ['Coffee', 'Tea']
},
favoritColorMode: {
type: String,
enum: {
values: ['dark', 'light'],
message: '{VALUE} is not supported'
},
required: true
}
}, {
statics: {
staticFn() { return 'Returned from staticFn'; }
},
methods: {
instanceFn() { return 'Returned from DocumentInstanceFn'; }
}
});

type InferredSchemaType = InferSchemaType<typeof AutoTypedSchema>

expectType<M0_0aAutoTypedSchemaType['schema']>({} as InferredSchemaType);

expectError<M0_0aAutoTypedSchemaType['schema'] & { doesNotExist: boolean; }>({} as InferredSchemaType);

// Test auto schema type obtaining with all possible path types.

class Int8 extends SchemaType {
constructor(key, options) {
super(key, options, 'Int8');
}
cast(val) {
let _val = Number(val);
if (isNaN(_val)) {
throw new Error('Int8: ' + val + ' is not a number');
}
_val = Math.round(_val);
if (_val < -0x80 || _val > 0x7F) {
throw new Error('Int8: ' + val +
' is outside of the range of valid 8-bit ints');
}
return _val;
}
}

type TestSchemaType = {
string1?: string;
string2?: string;
string3?: string;
string4?: string;
number1?: number;
number2?: number;
number3?: number;
number4?: number;
date1?: Date;
date2?: Date;
date3?: Date;
date4?: Date;
buffer1?: Buffer;
buffer2?: Buffer;
buffer3?: Buffer;
buffer4?: Buffer;
boolean1?: boolean;
boolean2?: boolean;
boolean3?: boolean;
boolean4?: boolean;
mixed1?: Schema.Types.Mixed;
mixed2?: Schema.Types.Mixed;
mixed3?: Schema.Types.Mixed;
objectId1?: Schema.Types.ObjectId;
objectId2?: Schema.Types.ObjectId;
objectId3?: Schema.Types.ObjectId;
customSchema?:Int8;
map1?: Map<string, string>;
map2?: Map<string, number>;
}

const TestSchema = new Schema({
string1: String,
string2: 'String',
string3: 'string',
string4: Schema.Types.String,
number1: Number,
number2: 'Number',
number3: 'number',
number4: Schema.Types.Number,
date1: Date,
date2: 'Date',
date3: 'date',
date4: Schema.Types.Date,
buffer1: Buffer,
buffer2: 'Buffer',
buffer3: 'buffer',
buffer4: Schema.Types.Buffer,
boolean1: Boolean,
boolean2: 'Boolean',
boolean3: 'boolean',
boolean4: Schema.Types.Boolean,
mixed1: Object,
mixed2: {},
mixed3: Schema.Types.Mixed,
objectId1: Schema.Types.ObjectId,
objectId2: 'ObjectId',
objectId3: 'objectId',
customSchema: Int8,
map1: { type: Map, of: String },
map2: { type: Map, of: Number }
});

type InferredTestSchemaType = InferSchemaType<typeof TestSchema>

expectType<TestSchemaType>({} as InferredTestSchemaType);

const SchemaWithCustomTypeKey = new Schema({
name: {
customKTypeKey: String,
required: true
}
}, {
typeKey: 'customKTypeKey'
});

expectType<string>({} as InferSchemaType<typeof SchemaWithCustomTypeKey>['name']);

return AutoTypedSchema;
}
2 changes: 1 addition & 1 deletion types/document.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ declare module 'mongoose' {
modelName: string;

/** Returns the model with the given name on this document's associated connection. */
model<ModelType = Model<unknown>>(name: string): ModelType;
model<ModelType = Model<DocType>>(name: string): ModelType;

/**
* Overwrite all values in this document with the values of `obj`, except
Expand Down