Skip to content

Commit

Permalink
fix(types): avoid inferring timestamps if methods, virtuals, or `…
Browse files Browse the repository at this point in the history
…statics` set

Re: #12807
  • Loading branch information
vkarpov15 committed Jan 4, 2023
1 parent d10ad8c commit 0ed84bc
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/typescript/schemas.md
Expand Up @@ -64,6 +64,7 @@ There are a few caveats for using automatic type inference:

1. You need to set `strictNullChecks: true` or `strict: true` in your `tsconfig.json`. Or, if you're setting flags at the command line, `--strictNullChecks` or `--strict`. There are [known issues](https://github.com/Automattic/mongoose/issues/12420) with automatic type inference with strict mode disabled.
2. You need to define your schema in the `new Schema()` call. Don't assign your schema definition to a temporary variable. Doing something like `const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition);` will not work.
3. Mongoose adds `createdAt` and `updatedAt` to your schema if you specify the `timestamps` option in your schema, _except_ if you also specify `methods`, `virtuals`, or `statics`. There is a [known issue](https://github.com/Automattic/mongoose/issues/12807) with type inference with timestamps and methods/virtuals/statics options. If you use methods, virtuals, and statics, you're responsible for adding `createdAt` and `updatedAt` to your schema definition.

If automatic type inference doesn't work for you, you can always fall back to document interface definitions.

Expand Down
16 changes: 16 additions & 0 deletions test/types/schema.test.ts
Expand Up @@ -873,6 +873,22 @@ function testInferTimestamps() {
// is not identical to argument type { createdAt: NativeDate; updatedAt: NativeDate; } &
// { name?: string | undefined; }"
expectType<{ createdAt: Date, updatedAt: Date } & { name?: string }>({} as WithTimestamps);

const schema2 = new Schema({
name: String
}, {
timestamps: true,
methods: { myName(): string | undefined {
return this.name;
} }
});

type WithTimestamps2 = InferSchemaType<typeof schema2>;
// For some reason, expectType<{ createdAt: Date, updatedAt: Date, name?: string }> throws
// an error "Parameter type { createdAt: Date; updatedAt: Date; name?: string | undefined; }
// is not identical to argument type { createdAt: NativeDate; updatedAt: NativeDate; } &
// { name?: string | undefined; }"
expectType<{ name?: string }>({} as WithTimestamps2);
}

function gh12431() {
Expand Down
10 changes: 8 additions & 2 deletions types/inferschematype.d.ts
Expand Up @@ -60,12 +60,18 @@ declare module 'mongoose' {
}[alias]
: unknown;

type ResolveSchemaOptions<T> = Omit<MergeType<DefaultSchemaOptions, T>, 'statics' | 'methods' | 'query' | 'virtuals'>;
// Without Omit, this gives us a "Type parameter 'TSchemaOptions' has a circular constraint."
type ResolveSchemaOptions<T> = Omit<MergeType<DefaultSchemaOptions, T>, 'fakepropertyname'>;

type ApplySchemaOptions<T, O = DefaultSchemaOptions> = ResolveTimestamps<T, O>;

type ResolveTimestamps<T, O> = O extends { timestamps: true }
? { createdAt: NativeDate; updatedAt: NativeDate; } & T
// For some reason, TypeScript sets all the document properties to unknown
// if we use methods, statics, or virtuals. So avoid inferring timestamps
// if any of these are set for now. See gh-12807
? O extends { methods: any } | { statics: any } | { virtuals: any }
? T
: { createdAt: NativeDate; updatedAt: NativeDate; } & T
: T;
}

Expand Down

0 comments on commit 0ed84bc

Please sign in to comment.