最近把项目中的 sequelize 由 4.38.0 升级到了 5.8.7,以下是升级记录
本文地址: https://blog.zhequtao.com/post/sequelize-upgrade/
01 删包
从 package.json 中删掉 sequelize 以及 @types/sequelize
02 过文档
大致过一遍官方升级文档 http://docs.sequelizejs.com/manual/upgrade-to-v5.html
03 npm install
由于官方提供了 typescript 的支持,不需要在安装 @types/sequelize。
npm install sequelize04 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, }});更改之后再次编译.
$ tscFound 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'"根据文档 http://docs.sequelizejs.com/manual/upgrade-to-v5.html#sequelize
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'.这个在升级文档中也提到过 http://docs.sequelizejs.com/manual/upgrade-to-v5.html#model
至于替换也是一个体力活,再次编译
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根据行号快速定位gdvim 的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'] = 10where.count.$lte = 10
// 替换后where.count = { [Op.lte]: 10 }where.count[Op.lte] = 10where.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 会抛出异常。https://github.com/sequelize/sequelize/pull/9548/files
使用 _.pickBy 过滤掉 undefined
const where = _.pickBy(data, x => x !== undefined)当然,如果 typescript 做的比较严格的话,这个问题也可以避免
19 运行时问题: _.assign({ [Op.ne]: 3 })
_.assign 会丢失 Symbol 属性,使用 Object.assign 代替
欢迎关注我的公众号中关村程序员,在这里记录着我的技术成长,欢迎交流
