sequelize V5 升级记录

最近把项目中的 sequelize 由 4.38.0 升级到了 5.8.7,如下是升级记录html

本文地址: shanyue.tech/post/sequel…node

01 删包

从 package.json 中删掉 sequelize 以及 @types/sequelizegit

02 过文档

大体过一遍官方升级文档 docs.sequelizejs.com/manual/upgr…github

03 npm install

因为官方提供了 typescript 的支持,不须要在安装 @types/sequelizetypescript

npm install sequelize
复制代码

04 tsc

因为使用了 typescript 编译,解决问题。shell

$ tsc
...
Found 1361 errors.
复制代码

05 new Sequelize

从数据库初始化入手,解决一些 Sequelize 实例化时的类型问题数据库

06 AnyModel & AnyPropModel & Sequelize.define

因为 sequelizetype 此时由官方维护,从新定义了 Model 等类型。express

虽然目前官方已经支持了对 Model 的 typescript 支持,可是为了更小幅度的升级,仍然使用 Sequelize.definenpm

之后将 AnyPropModel 逐渐替换为 UserModel 等真实的 Model。json

根据文档,对 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 能够更快解决问题:

  1. tsc | grep 2576 提供全部的此类问题与行号
  2. 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
})
复制代码

接下来就是体力活了:

  1. tsc | grep 2531 提供全部的此类问题与行号
  2. VS Code 根据行号快速定位
  3. gd vim 的 Go to Def 能够快速定位到出问题的变量定义处
  4. "0p 使用 vim 把 rejectOnEmpty 至于0号寄存器,快速粘贴
  5. == vim 进行格式化

再次编译:

Found 335 errors.

11 migration

TS2709: Cannot use namespace 'DataTypes' as a type
复制代码

这都是在 migration 文件中的内容,既然数据库迁移脚本已经执行过了,它其实也没多大用处了,我以为不改也能够了...

另外,migration 这种数据库迁移脚本是否是能够单独从项目中抽出来,有两个缘由

  1. 它是一次性脚本
  2. 一个数据库有可能对应多个后端应用
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 代替


欢迎关注个人公众号山月行,在这里记录着个人技术成长,欢迎交流

欢迎关注公众号山月行,在这里记录个人技术成长,欢迎交流
相关文章
相关标签/搜索