網站搜尋

node.js - TypeScript 無法編譯 Mongoose 查詢輔助函數


我正在開發一個使用 Mongoose 的 TypeScript 專案,並且使用 tsc 編譯。當我嘗試在 .find() 中使用查詢助手時:

import { InferRawDocType, Schema, model } from "mongoose";

export const mySchemaDefinition = {
  key: "string",
  value: "string"
} as const;
export type RawSchema = InferRawDocType<typeof mySchemaDefinition>;
export const mySchema = new Schema(mySchemaDefinition, {
  query: {
    byKey: function (key: string) {
      return this.where({ key: key });
    }
  }
});
export const MyModel = model("MyModel", mySchema);

MyModel.find({ key: "123" }).exec(); // OK
MyModel.findOne().byKey("123").exec(); // OK
MyModel.find().byKey("123").exec(); // Error

TypeScript 在轉譯過程中拋出錯誤:

The 'this' context of type 'QueryWithHelpers<(Document<unknown, { byKey: <T extends Query<unknown, Document<unknown, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; }>> & FlatRecord<{ readonly key: "string"; readonly value: "string"; }> & { _id: ObjectId; } & { __v: number; }, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; }>, "find", Record<string, never>>>(this: T, key: string) => T; }, { readonly key: "string"; readonly value: "string"; }> & { readonly key: "string"; readonly value: "string"; 
} & { _id: ObjectId; } & { __v: number; })[], Document<unknown, { byKey: <T extends Query<unknown, Document<unknown, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; }>> & FlatRecord<{ readonly key: "string"; readonly value: "string"; }> & { _id: ObjectId; } & { __v: number; }, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; }>, "find", Record<string, never>>>(this: T, key: string) => T; }, { readonly key: "string"; readonly value: "string"; }> & { readonly key: "string"; readonly value: "string"; } & { _id: ObjectId; } & { __v: number; }, { byKey: <T extends Query<unknown, Document<unknown, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; }>> & FlatRecord<{ readonly key: "string"; readonly value: "string"; }> & { _id: ObjectId; } & { __v: number; }, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; }>, "find", Record<string, never>>>(this: T, key: string) => T; }, { readonly key: "string"; readonly value: "string"; }, "find", {}>' is not assignable to method's 'this' of type 'Query<unknown, Document<unknown, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; 
}>> & FlatRecord<{ readonly key: "string"; readonly value: "string"; }> & { _id: ObjectId; } & { __v: number; }, {}, FlatRecord<{ readonly key: "string"; readonly value: "string"; }>, "find", Record<string, never>>'.
  The types returned by 'lean(...).exec()' are incompatible between these types.
    Type 'Promise<({ readonly key: "string"; readonly value: "string"; } & { _id: ObjectId; } & { __v: number; })[]>' is not assignable to type 'Promise<{ readonly key: "string"; readonly value: "string"; } & { _id: ObjectId; } & { __v: number; }>'.
      Type '({ readonly key: "string"; readonly value: "string"; } & { _id: ObjectId; } & { __v: number; })[]' is not assignable to type '{ readonly key: "string"; readonly value: "string"; } & 
{ _id: ObjectId; } & { __v: number; }'.
        Type '({ readonly key: "string"; readonly value: "string"; } & { _id: ObjectId; } & { __v: number; })[]' is missing the following properties from type '{ readonly key: "string"; readonly value: "string"; }': key, value

但這只會發生在 .find 上,.findOne 正常運作。
this 的上下文似乎是正確的類型。
這是 tsc 配置的問題,還是我可以修正的 Mongoose 類型錯誤?

建置指令:

tsc index.ts --esModuleInterop --outDir ./build --noErrorTruncation

tsconfig.json(主要針對同一專案中的 React 應用程式):

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}

答案#1

此錯誤發生是因為 byKey 查詢輔助未正確設置類型以處理 find() 和 findOne() 兩種操作。當前的實現假設返回單一文件類型,但 find() 返回一個陣列。我希望這能解決問題。

export const mySchemaDefinition = {
  key: "string",
  value: "string"
} as const;
export const mySchema = new Schema(mySchemaDefinition, {
query: {
byKey: function<T extends Query<any, any>>(this: T, key: string) {
 return this.where({ key: key });
    }
  }
});

此更改使 byKey 查詢輔助函數通用到足以同時適用於 find() 和 findOne() 操作。

為了在 Typescript 代碼中避免使用 any,你可以為你的 schema 定義一個特定的類型,並在 Query 類型中使用它。以下是你可以這樣做的方法:

type MySchemaType = {
  key: string;
   value: string;
};

export const mySchemaDefinition = {
 key: "string",
value: "string"
} as const;
export const mySchema = new Schema(mySchemaDefinition, {
 query: {
 byKey: function<T extends Query<MySchemaType, typeof 
 mySchemaDefinition>>(this: T, key: string) {
 return this.where({ key: key });
  }
}
});

答案#2

這可能不是最方便或最佳的解決方案,但它說明了這一點。

import { InferRawDocType, Schema, model, Query } from "mongoose";

export const mySchemaDefinition = {
  key: "string",
  value: "string"
} as const;
export type RawSchema = InferRawDocType<typeof mySchemaDefinition>;

export const mySchema = new Schema<RawSchema>(mySchemaDefinition, {
  query: {
    byKey: function<T extends Query<any, RawSchema>>(this: T, key: string) {
      return this.where({ key: key });
    }
  }
});

export const MyModel = model("MyModel", mySchema);

MyModel.find({ key: "123" }).exec(); // OK
MyModel.findOne().byKey("123").exec(); // OK
MyModel.find().byKey("123").exec(); // OK

或者如果你想避免使用 Query<any, RawSchema>

import { Schema, model, Document, Model } from "mongoose";

interface MyDocument extends Document {
  key: string;
  value: string;
}

const mySchema = new Schema<MyDocument>({
  key: { type: String, required: true },
  value: { type: String, required: true }
});

mySchema.query.byKey = function (key: string) {
  return this.where({ key });
};

const MyModel: Model<MyDocument> = model("MyModel", mySchema);

MyModel.find({ key: "123" }).exec(); // OK
MyModel.findOne().byKey("123").exec(); // OK
MyModel.find().byKey("123").exec(); // OK

甚至

import { InferRawDocType, Schema, model, Query } from "mongoose";

export const mySchemaDefinition = {
  key: "string",
  value: "string"
} as const;
export type RawSchema = InferRawDocType<typeof mySchemaDefinition>;

export const mySchema = new Schema<RawSchema>(mySchemaDefinition, {
  query: {
    byKey: function(this: Query<RawSchema[], RawSchema>, key: string) {
      return this.where({ key: key });
    }
  }
});

export const MyModel = model("MyModel", mySchema);

MyModel.find({ key: "123" }).exec(); // OK
MyModel.findOne().byKey("123").exec(); // OK
MyModel.find().byKey("123").exec(); // OK