我正在尝试在后端节点项目中将FlowJS与Sequelize一起使用。但是,对于如何注释所涉及的数据类型,没有特定的FlowJS文档,并且我得到的代码可以工作,但还会引发很多类型错误。
我有这个演示项目on github的完整工作版本。
我们从基本模型定义开始。我最近玩过很多Stellaris,所以我在这里考虑大银河策略。
// @flow
import Sequelize, { type Model } from "sequelize";
export type Empire = {
name: string,
species: string,
homeworld: string
};
const EmpireModel = (sequelize: Sequelize): Class<Model<Empire>> =>
sequelize.define("empires", {
name: { type: Sequelize.STRING, allowNull: false },
species: { type: Sequelize.STRING, allowNull: false },
homeworld: { type: Sequelize.STRING, allowNull: false }
});
module.exports = EmpireModel;
我认为,我想要一种包含所有Sequelize函数表操作函数(创建,销毁,findAll等)的数据类型,而我认为我希望另一种数据类型仅代表涉及的实际对象(数据库中存储了一个特定的Empire)。我可能完全错了,并且乐于接受有关如何使上述内容更好的建议。
上面的代码类型检查没问题,但是我不确定Class<Model<Empire>>
是否是sequelize.define
返回值的正确注释。
现在我们进入应用程序本身:
// @flow
import Sequelize, { type Model } from "sequelize";
import { type Empire } from "./empire";
const sequelize = new Sequelize(
"postgres://postgres:sequelize@localhost:5432/sqldemo"
);
const main = async () => {
const EmpireModel: Class<Model<Empire>> = await require("./empire")(
sequelize
);
const empire: Model<Empire> = await EmpireModel.create({
name: "Sildaran Republic",
species: "Sildar",
homeworld: "Sakatarola"
});
const empires = await EmpireModel.findAll();
console.log(empires);
empires.map(empire => {
console.log("[Empire] ", empire.name);
console.log("[Founding Species] ", empire.species);
console.log("[Homeworld] ", empire.homeworld);
});
};
此代码实际上可以正确运行,但是类型检查器在行await Empires.findAll()
上标记错误。错误消息是
Cannot call await with `EmpireModel.findAll()` bound to `p` because:
- Either cannot get `empire.name` because property `name` is missing in `Model` [1].
- Or cannot call `empires.map` because property `map` is missing in `Promise` [2].
src/index.js:20:25
20| const empires = await EmpireModel.findAll();
^^^^^^^^^^^^^^^^^^^^^
References:
flow-typed/npm/sequelize_v4.x.x.js:3264:72
3264| options?: FindOptions<TAttributes & TCustomAttributes>): Promise<this[]>,
^^^^ [1]
flow-typed/npm/sequelize_v4.x.x.js:3264:64
3264| options?: FindOptions<TAttributes & TCustomAttributes>): Promise<this[]>,
^^^^^^^^^^^^^^^ [2]
所以,我实际上有几个具体问题:
EmpireModel
中分配给empires.js
的实际正确类型是什么?await require("./empire")(sequelize);
的返回值的实际类型是什么,当我弄错时如何使Flow中断(Flow对在此指定的任何类型感到满意,并且仅在以后使用时才标记错误)值与类型不一致)我也欢迎所有其他建议,以使此代码更正确。
答案 0 :(得分:2)
免责声明:我真的不了解任何Flow,并且以TypeScript和Haskell为背景来到这里,不幸的是TypeScript对此没有任何启示(.define()
的输出是只是一个Model<Empire, Empire>
,并具有适当的.findAll()
方法)-但请不要认为我比这更专家。
如果我用test suite来查看流类型,他们并不总是使用Class<>
utility type来键入s.define()
的输出,而是偶尔使用typeof
,并且似乎符合使用子类的愿望:
type WarehouseAttributes = {
id?: number;
address?: string;
capacity?: number;
};
declare class WarehouseInstance extends Model<WarehouseAttributes> {
id?: number;
address?: string;
capacity?: number;
// ... mixins ...
};
// ... then this gets used to define ...
let Warehouse: typeof WarehouseInstance = s.define('warehouse', {});
// ... then this gets used to query ...
Warehouse
.findAll({include: [{association: WarehouseProducts}]})]
.then((warehouses: Array<WarehouseInstance>) => {})
这种方法是否有可能使您返回声明的类而不是像现在那样返回Class<Model<EmpireAttributes>>
?也许有必要使Promise<this[]>
具有一个this[]
并包含该实例上的Empire
属性?
答案 1 :(得分:1)
简短答案:
对于某些模型x
,您必须调用x.get()
或x.toJSON()
1 来获得模型基础的普通对象。如果您将查询后的代码更改为
empires.map(empire => empire.get())
.map(empire => {
console.log("[Empire] ", empire.name);
console.log("[Founding Species] ", empire.species);
console.log("[Homeworld] ", empire.homeworld);
});
然后它将正常工作。
长答案:
该解决方案似乎有些武断,所以这是我用来查找它的过程。不幸的是,Flow的好处是以学习其局限性以及如何调试其错误消息为代价的,因此希望这能为我做些什么拉开帷幕。
我经常检查的第一件事是所有类型都正确导入。 flow-typed
目录和Empire
类型都在错误消息中,因此看起来不错。
接下来,错误消息给我们两种可能性,我想弄清楚我们正在处理哪一种。 empires.map
无效,或者empire.name
无效。因此,我注释掉了map
中的现有代码,并将其替换为身份函数empire => empire
。当我这样做时,错误消失了,因此很明显问题出在访问empire.name
。
要弄清楚为什么这是个问题,我为Sequelize挖掘了libdef。它位于流类型的存储库here中。 Libdefs看起来令人生畏,但它们只是添加了declare
关键字的普通流定义。在普通代码中,您可能会说let i : number = 1
,而在使用declare
时您会说
let i = 1;
declare i : number
这两者之间的区别在于,流类型会检查常规注释以确保它们有意义,但会将declare
语句视为福音 2 。考虑到这一点,我们可以阅读libdef本身,以了解我们正在处理的类型和接口。因为libdefs使用declare
,所以Sequelize包中的实际代码与类型检查无关;流仅查看libdef,而不查看实现。
这里的注释中有非常详尽的文档,但是不幸的是,该文件的长度超过7k行,并且没有将最重要的定义放在首位。
Model
类的声明以here表示,而findAll
的定义以here表示。我们可以看到findAll
返回类型为this
的数组,这意味着类型为Model
的数组具有与当前Model
相同的通用类型参数。返回Model
的顶部,我们看到以下参数:
Model<TAttributes, TInitAttributes = TAttributes, TPlainAttributes = TAttributes>
虽然Model
类型有3个参数,但其中两个是可选参数,默认情况下设置为第一个参数。
所以我们的问题是,我们得到了一组类型为Model<Empire, Empire, Empire>
的对象,我们正在其上进行映射,然后尝试访问{{1}中定义的字段}类型。在无类型的JavaScript中,这很好,因为库可以动态地向模型添加字段,但是在Flow中,像这样将一种对象类型与另一种对象“合并”需要使用交集类型,并且可能会有一些粗糙的情况。幸运的是,查看文件后面的Empire
的定义表明,这里没有做任何花哨的事情。如果要访问包含的sequelize.define
,则需要找到一种返回值是Empire
的泛型类型参数之一的方法。
我们可以看到那些here,最后一个定义具有所有可选参数,从而为我们提供了简单答案中的解决方案。搜索课程的其余部分还会显示toJSON function.
1 请注意,Model
返回一个对象,而不是JSON字符串。当将对象传递到toJSON
中时,JSON.stringify
将对该对象调用stringify
函数(如果存在),并将对toJSON
返回的对象进行字符串化。这就是为什么您可以在toJSON
对象上调用console.log
并查看您在此模型上定义的属性,而不是Sequelize附加到模型的所有其他字段的原因。名称令人误解,但这是JavaScript的错误,而不是Sequelize的错误。
2 从技术上讲,有一些检查。例如,如果您Model
的变量同时具有类型declare
和string
,则会出现错误。仅检查number
注释与其他declare
注释的一致性,然后针对这些注释检查非declare
注释,以确保如果有错误,则将其报告为正常问题注释,而不是带有declare
注释。