Description
Prerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure the feature has not already been requested
🚀 Feature Proposal
Only reference I can find to this is #11148, which is now several years old.
I initially suggested this be a feature in typegoose (see here), but was informed that mongoose now has the features necessary to infer enough type information to properly type Model.create
and Model.insertOne
.
I mention in the issue linked above that AnyKeys
destroys type information, and is equivalent to the following type, which only preserves keys and not any information about their values, ie the use of T[P] | any
is equivalent to any
.
// given type
type AnyKeys<T> = { [P in keyof T]?: T[P] | any };
// equivalent to
type AnyKeys<T> = Partial<Record<keyof T, any>>
Likewise, if you put this type in union as T | AnyKeys<T>
(as is default in Model.create
), it is equivalent to AnyKeys<T>
, as typescript favors the least restrictive type in the union if types overlap.
In other words, TRawDocType | AnyKeys<TRawDocType>
means Partial<Record<keyof TRawDocType, any>>
.
Motivation
Suppose you're storing something of the following shape;
{
foo: {
bar: string
baz: number
}
}
foo
is a required property. It must be an object. It must have the key bar
of type string
, and it must have the key baz
of type `number.
It is unhelpful for the type inference in the first argument to model.create
to propose the shape;
{
foo?: any
}
foo
is an optional property. It can be anything at all.
The only way to get around this is to explicitly specify the type on every call to model.create
or model.insertOne
. This should not be needed. The correct type is already stored in the model via TRawDocType
, specifying it again on every call is redundant.
Example
export interface Model/*...*/ {
//...
- create<DocContents = AnyKeys<TRawDocType>>(docs: Array<TRawDocType | DocContents>, options: CreateOptions & { aggregateErrors: true }): Promise<(THydratedDocumentType | Error)[]>;
- create<DocContents = AnyKeys<TRawDocType>>(docs: Array<TRawDocType | DocContents>, options?: CreateOptions): Promise<THydratedDocumentType[]>;
- create<DocContents = AnyKeys<TRawDocType>>(doc: DocContents | TRawDocType): Promise<THydratedDocumentType>;
- create<DocContents = AnyKeys<TRawDocType>>(...docs: Array<TRawDocType | DocContents>): Promise<THydratedDocumentType[]>;
+ create(docs: Array<TRawDocType>, options: CreateOptions & { aggregateErrors: true }): Promise<(THydratedDocumentType | Error)[]>;
+ create(docs: Array<TRawDocType>, options?: CreateOptions): Promise<THydratedDocumentType[]>;
+ create(doc: TRawDocType): Promise<THydratedDocumentType>;
+ create(...docs: Array<TRawDocType>): Promise<THydratedDocumentType[]>;
//...
- insertOne<DocContents = AnyKeys<TRawDocType>>(doc: DocContents | TRawDocType, options?: SaveOptions): Promise<THydratedDocumentType>;
+ insertOne(doc: TRawDocType, options?: SaveOptions): Promise<THydratedDocumentType>;
}
Or, if it is still the case that TRawDocType
is unreliable for whether a property is optional, Partial<TRawDocType>
could be used. If its unreliable for nested keys too, a DeepPartial
type (eg, ts-essentials DeepPartial) could be created and used.