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 代替


歡迎關注個人公衆號山月行,在這裏記錄着個人技術成長,歡迎交流

歡迎關注公衆號山月行,在這裏記錄個人技術成長,歡迎交流
相關文章
相關標籤/搜索