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

🐛fix: improve inferred Schema Type #12007

Merged
merged 23 commits into from Jul 1, 2022
Merged

🐛fix: improve inferred Schema Type #12007

merged 23 commits into from Jul 1, 2022

Conversation

iammola
Copy link
Contributor

@iammola iammola commented Jun 29, 2022

Fixes #12002
Fixes #12011
Fixed #12016

Summary

It uses the correct Type.ObjectId when inferring ObjectId fields (fixes the issue) and correctly infers the type for sub-documents defined with the typeKey and all sub-document arrays. Previously, the ResolvePathType helper type didn't match any Schema types in its pyramid of type checks. The only array check looped the same ResolvePathType, so it never matched any Schema in arrays and it goes deeper in arrays for deeper nested Schemas.

I'm not sure how the other change will be received, but currently, any array that isn't explicitly marked with required: true gets resolved to be optional. I understand why, but the default value for an array schema type is an empty one unless the user explicitly sets it to undefined.

So I think it'll be better if array paths that don't have their defaults set to undefined aren't marked as optional types. Mongoose already allows document fields to be skipped when creating them with a method like Model.create. So if in the result of the find* queries, the arrays are marked as possibly undefined. It could cause unnecessary errors with TS.

Example

// Given Schema
const Author = new Schema({
  contractedTo: { type: Schema.Types.ObjectId, required: true },
  tags: [{ type: String }],
  contact: {
    mail: String,
    phone: String
  },
  profile: {
    type: new Schema({
      age: Number,
      username: { type: String, required: true }
    }),
    required: true
  },
  booksReleasedPerYear: {
    type: [new Schema({
      year: { type: Number, required: true },
      count: { type: Number, required: true }
    })]
  }
})

// It should infer this type
type AuthorSchema = {
  contractedTo: Types.ObjectId;
  tags?: string[] | undefined;
  contact?: {
    mail?: string | undefined;
    phone?: string | undefined;
  } | undefined,
  profile: {
    age?: number | undefined;
    username: string;
  };
  booksReleasedPerYear?: Array<{
    year: number;
    count: number;
  }>
}

- Correctly detect types for Schema[] and Schema in typeKey
- Use correct ObjectId type

Fixes #12002
The default value for an array field is an empty array unless specifed to be undefined.

This checks if the path explicitly sets the default property to `undefined`
@iammola iammola changed the title 🐛fix: improved inferred Schema Type 🐛fix: improve inferred Schema Type Jun 29, 2022
@imranbarbhuiya
Copy link

imranbarbhuiya commented Jun 29, 2022

Does this pr fixes #12016 too?

@iammola
Copy link
Contributor Author

iammola commented Jun 29, 2022

Does this pr fixes #12016 too?

@imranbarbhuiya... I didn't get the issue in the 6.4.x and it's the same in this PR as well. I don't know how he's getting the the optional properties in the type.

@iammola
Copy link
Contributor Author

iammola commented Jun 29, 2022

To the maintainer helping me run the workflows,

If the Schema type itself was hovered in an editor like VS Code, you'll see the types are set exactly as they should be, but it was the result of the InferredSchemaType that set them to unknown for some reason. I traced it down to the cause of the problem, and the test was failing because the Schema.Types.Mixed matched this check,

PathValueType extends { [K: string]: SchemaDefinitionProperty } ? ObtainDocumentType<PathValueType, any, TypeKey> :
. The {} and ObjectConstructor types also caused issues, I was able to solve them but ultimately decided to remove their checks as well and let those three types resolve to unknown

It's meant to match nested paths that aren't Schema (I made that change in Automattic/mongoose@2f1d2d9 (#12007)).

I don't know how you feel about that, but since unknown matches all other types, I hope it can be used because I wasn't able to find a way to check for those nested paths and for those three types as well.

test/types/schema.test.ts Show resolved Hide resolved
test/types/schema.test.ts Show resolved Hide resolved
test/types/schema.test.ts Outdated Show resolved Hide resolved
test/types/schema.test.ts Show resolved Hide resolved
types/inferschematype.d.ts Show resolved Hide resolved
@mohammad0-0ahmad
Copy link
Contributor

I've created PR in your fork repo, check the changes and merge it if that will solve all mentioned issues.

@mohammad0-0ahmad
Copy link
Contributor

mohammad0-0ahmad commented Jun 30, 2022

Does this pr fixes #12016 too?

@iammola would you like to cover this issue in this PR ?
I think you can cover it by refactoring IsPathRequired helper

@mohammad0-0ahmad
Copy link
Contributor

mohammad0-0ahmad commented Jun 30, 2022

Does this pr fixes #12016 too?

@iammola would you like to cover this issue in this PR ? I think you can cover it by refactoring IsPathRequired helper

@iammola If you include it in this PR, please don't forget to mention the issue in the description. 🥇

@iammola
Copy link
Contributor Author

iammola commented Jun 30, 2022

Yeah @mohammad0-0ahmad. I'm on it.

I've been thinking what would be allowed? Should the default type match the typeKey's type or any valid SchemaDefinitionProperty (StringConstructor, NumberConstructor, ...) should make the path not undefined?

Because if the default and typeKey's types are different, does that mean it's a mixed path?

But in the case of Dates where you may default it to Date.now(). Then the default's type is a number while the typeKey's type is a Date

@mohammad0-0ahmad
Copy link
Contributor

mohammad0-0ahmad commented Jun 30, 2022

Yeah @mohammad0-0ahmad. I'm on it.

I've been thinking what would be allowed? Should the default type match the typeKey's type or any valid SchemaDefinitionProperty (StringConstructor, NumberConstructor, ...) should make the path not undefined?

Because if the default and typeKey's types are different, does that mean it's a mixed path?

But in the case of Dates where you may default it to Date.now(). Then the default's type is a number while the typeKey's type is a Date

I think we can make it match the TypeKey's type for now.
We can ask @Uzlopak @vkarpov15 @AbdelrahmanHafez.

@@ -75,7 +75,7 @@ type IsPathRequired<P, TypeKey extends TypeKeyBaseType> =
? P extends { default: undefined }
? false
: true
: P extends PathWithTypePropertyBaseType<TypeKey>
: P extends (Record<TypeKey, NumberSchemaDefinition | StringSchemaDefinition | BooleanSchemaDefinition | DateSchemaDefinition>)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I assumed not all of typeKey's types should be allowed to be remove the optional flag on that path.

So I set it to these 4. Because I think they're the only ones that should be able to provide defaults.

Copy link
Contributor Author

@iammola iammola Jul 1, 2022

Choose a reason for hiding this comment

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

So if you don't want the types to be limited, I'll revert the whole commit. @mohammad0-0ahmad

PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date :
PathValueType extends StringSchemaDefinition ? PathEnumOrString<Options['enum']> :
PathValueType extends NumberSchemaDefinition ? number :
PathValueType extends DateSchemaDefinition ? Date :
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These changes are just because I noticed they're defined in their respective SchemaDefinition types.

@mohammad0-0ahmad
Copy link
Contributor

mohammad0-0ahmad commented Jul 1, 2022

Sorry @iammola, I can't contributing for now, I will check changes when I can as soon as possible, please consider checking TS benchmark while refactoring types to avoid slower performance.

Copy link
Collaborator

@vkarpov15 vkarpov15 left a comment

Choose a reason for hiding this comment

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

This is great, thanks!

@vkarpov15 vkarpov15 added this to the 6.4.2 milestone Jul 1, 2022
@vkarpov15 vkarpov15 merged commit 87a6edc into Automattic:master Jul 1, 2022
@mohammad0-0ahmad
Copy link
Contributor

@iammola sorry for being late, But I would like to say well done : )

@iammola
Copy link
Contributor Author

iammola commented Jul 1, 2022

@iammola sorry for being late, But I would like to say well done : )

Thank you, man... I really appreciate it. I honestly didn't do much, but I'll accept it 🙏

@iammola iammola deleted the inferschematype branch July 2, 2022 04:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants