最近把项目中的 sequelize 由 4.38.0
升级到了 5.8.7
,以下是升级记录
本文地址: shanyue.tech/post/sequel…
01 删包
从 package.json 中删掉 sequelize 以及 @types/sequelize
02 过文档
大致过一遍官方升级文档 docs.sequelizejs.com/manual/upgr…
03 npm install
由于官方提供了 typescript
的支持,不需要在安装 @types/sequelize
。
npm install sequelize
复制代码
04 tsc
由于使用了 typescript
编译,解决问题。
$ tsc
...
Found 1361 errors.
复制代码
05 new Sequelize
从数据库初始化入手,解决一些 Sequelize 实例化时的类型问题
06 AnyModel & AnyPropModel & Sequelize.define
由于 sequelize
的 type
此时由官方维护,重新定义了 Model
等类型。
虽然目前官方已经支持了对 Model
的 typescript 支持,但是为了更小幅度的升级,仍然使用 Sequelize.define
。
以后将 AnyPropModel
逐渐替换为 UserModel
等真实的 Model。
根据文档,对 Model
以及 Sequelize.define
做以下更改。
class AnyPropModel extends Model {
[key: string]: any;
}
export type AnyModel = typeof Model & {
new (values?: any, options?: BuildOptions): AnyPropModel;
}
export type Models = Record<string, AnyModel>;
const UserModel = <AnyModel>sequelize.define('MyDefineModel', {
id: {
primaryKey: true,
type: DataTypes.INTEGER.UNSIGNED,
}
});
复制代码
更改之后再次编译.
$ tsc
Found 898 errors.
复制代码
07 归并与分类,逐个击破
对 typescript 编译出来的错误信息进行格式化并做统计,以下为格式化数据,抽出 { file, code, message }
本来也想抽出 lines,没有成功...
$ tsc | grep 'error TS' | jq -R -c -s 'split("\n") | map(capture("(?<file>.+): error (?<code>.+): (?<message>.+)")) | .[]' > build.jsonl
$ head -3 build.jsonl
{"file":"bin/demo.ts(278,23)","code":"TS2351","message":"Cannot use 'new' with an expression whose type lacks a call or construct signature."}
{"file":"src/helpers/one.ts(36,23)","code":"TS2576","message":"Property 'literal' is a static member of type 'Sequelize'"}
{"file":"src/helpers/two.ts(188,24)","code":"TS2576","message":"Property 'fn' is a static member of type 'Sequelize'"}
复制代码
根据格式化信息,对相同 code 的错误进行分类,先解决错误率最高的五类
$ cat build.jsonl | jq -s 'group_by(.code) | map({count: length, code: .[0].code, message: .[0].message}) | sort_by(.count) | .[]'
{
"count": 41,
"code": "TS6133",
"message": "'DataTypes' is declared but its value is never read."
}
{
"count": 53,
"code": "TS2339",
"message": "Property 'id' does not exist on type 'object'."
}
{
"count": 82,
"code": "TS2709",
"message": "Cannot use namespace 'DataTypes' as a type."
}
{
"count": 94,
"code": "TS7006",
"message": "Parameter 'e' implicitly has an 'any' type."
}
{
"count": 142,
"code": "TS2576",
"message": "Property 'col' is a static member of type 'Sequelize'"
}
{
"count": 335,
"code": "TS2531",
"message": "Object is possibly 'null'."
}
复制代码
08 TS2576: Sequelize.prototype -> Sequelize
"Property 'col' is a static member of type 'Sequelize'"
"Property 'literal' is a static member of type 'Sequelize'"
"Property 'or' is a static member of type 'Sequelize'"
复制代码
根据文档 docs.sequelizejs.com/manual/upgr…
Sequelize 示例上的很多方法变成了 static method.
借助 VS Code
与其内置的命令行工具,输入命令 tsc | grep 2576
可以更快解决问题:
tsc | grep 2576
提供所有的此类问题与行号VS Code
可以根据行号快速定位
但不管怎么说,这还是一个体力活......
09 TS2339 Model 的废弃方法
Property 'findById' does not exist on type 'AnyModel'.
Property 'find' does not exist on type 'AnyModel'.
Property 'id' does not exist on type 'AnyPropModel | null'.
Property 'LOCK' does not exist on type 'Transaction'.
复制代码
这个在升级文档中也提到过 docs.sequelizejs.com/manual/upgr…
至于替换也是一个体力活,再次编译
Found 753 errors.
10 TS2531 Object is possibly 'null'
$ tsc | grep 2531 | wc
406
复制代码
再次统计下此问题的个数,比刚才统计时多了一百多
我们总是在不停地解决 Bug 的过程中引入新的 Bug。在解决旧 Bug 的过程中总有产生新 Bug 的风险
findById
更改之后有更多的问题显现出来,是因为没有对返回的数据做不存在断言处理,如下例所示
const user = await models.user.findOne()
// 此时 user 可能不存在,可能报错
const id = user.id
复制代码
在解决问题之前,我先分析下原因
使用 rejectOnEmpty
来修正它,他能保证数据一定存在
const user = await models.User.findOne({
rejectOnEmpty: true
})
复制代码
接下来就是体力活了:
tsc | grep 2531
提供所有的此类问题与行号VS Code
根据行号快速定位gd
vim 的Go to Def
可以快速定位到出问题的变量定义处"0p
使用 vim 把rejectOnEmpty
至于0号寄存器,快速粘贴==
vim 进行格式化
再次编译:
Found 335 errors.
11 migration
TS2709: Cannot use namespace 'DataTypes' as a type
复制代码
这都是在 migration 文件中的内容,既然数据库迁移脚本已经执行过了,它其实也没多大用处了,我觉得不改也可以了...
另外,migration 这种数据库迁移脚本是不是可以单独从项目中抽出来,有两个原因
- 它是一次性脚本
- 一个数据库有可能对应多个后端应用
import { QueryInterface, DataTypes } from 'sequelize'
export const up = async function (queryInterface: QueryInterface, Sequelize: DataTypes) {
}
复制代码
全局替换
Sequelize: DataTypes -> sequelize: typeof Sequelize
Found 216 errors.
12 implicitly any
TS7006 Parameter 'item' implicitly has an 'any' type.
复制代码
解决后再次编译
Found 185 errors.
13 使用 sed 批量替换 Op
// 替换前
where.count = { $lte: 10 }
where.count['$lte'] = 10
where.count.$lte = 10
// 替换后
where.count = { [Op.lte]: 10 }
where.count[Op.lte] = 10
where.count[Op.lte] = 10
复制代码
写一段 sed
脚本来批量替换
再次编译
Found 412 errors.
14 补充 Op
以上错误的原因过多是因为批量替换成 Op
后提示 Op
不存在
import { Op } from 'sequelize'
复制代码
15 再次统计
$ tsc | grep 'error TS' | jq -R -s 'split("\n") | map(capture("(?<file>.+): error (?<code>.+): (?<message>.+)")) | group_by(.code) | map({count: length, code: .[0].code, message: .[0].message}) | sort_by(.count) | .[]'
{
"count": 12,
"code": "TS2684",
"message": "The 'this' context of type 'typeof Model' is not assignable to method's 'this' of type '(new () => Model<{}, {}>) & typeof Model'."
}
{
"count": 14,
"code": "TS2322",
"message": "Type 'true' is not assignable to type 'false'."
}
{
"count": 18,
"code": "TS2694",
"message": "Namespace '\"/Users/shanyue/backend/node_modules/sequelize/types/index\"' has no exported member 'AnyFindOptions'."
}
{
"count": 20,
"code": "TS2532",
"message": "Object is possibly 'undefined'."
}
{
"count": 118,
"code": "TS2304",
"message": "Cannot find name 'Op'."
}
复制代码
都是一些小问题了,逐个解决
16 git diff
统计下修改了多少内容
$ git diff master --shortstat
199 files changed, 1784 insertions(+), 1411 deletions(-)
复制代码
17 运行时问题: postgres range
以上编译时的问题解决了,最令人头疼的还是运行时问题了
这次碰到的是 postgres 的 range
这个数据类型
// 更改前
[0, 100]
// 更改后
[{
value: 0,
inclusive: false
}, {
value: 100,
inclusive: true
}]
复制代码
但是如果对数据库的每个 Model 都加上 type 的话,这个问题就可以在编译时解决
18 运行时问题: undefined in where
在 where 中遇到 undefined
会抛出异常。github.com/sequelize/s…
使用 _.pickBy
过滤掉 undefined
const where = _.pickBy(data, x => x !== undefined)
复制代码
当然,如果 typescript 做的比较严格的话,这个问题也可以避免
19 运行时问题: _.assign({ [Op.ne]: 3 })
_.assign
会丢失 Symbol 属性,使用 Object.assign
代替
欢迎关注我的公众号山月行,在这里记录着我的技术成长,欢迎交流