最近把項目中的 sequelize 由 4.38.0
升級到了 5.8.7
,如下是升級記錄html
本文地址: shanyue.tech/post/sequel…node
從 package.json 中刪掉 sequelize 以及 @types/sequelizegit
大體過一遍官方升級文檔 docs.sequelizejs.com/manual/upgr…github
因爲官方提供了 typescript
的支持,不須要在安裝 @types/sequelize
。typescript
npm install sequelize
複製代碼
因爲使用了 typescript
編譯,解決問題。shell
$ tsc
...
Found 1361 errors.
複製代碼
從數據庫初始化入手,解決一些 Sequelize 實例化時的類型問題數據庫
因爲 sequelize
的 type
此時由官方維護,從新定義了 Model
等類型。express
雖然目前官方已經支持了對 Model
的 typescript 支持,可是爲了更小幅度的升級,仍然使用 Sequelize.define
。npm
之後將 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.
複製代碼
對 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'."
}
複製代碼
"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
能夠根據行號快速定位但無論怎麼說,這仍是一個體力活......
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.
$ 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.
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.
TS7006 Parameter 'item' implicitly has an 'any' type.
複製代碼
解決後再次編譯
Found 185 errors.
// 替換前
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.
以上錯誤的緣由過可能是由於批量替換成 Op
後提示 Op
不存在
import { Op } from 'sequelize'
複製代碼
$ 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'."
}
複製代碼
都是一些小問題了,逐個解決
統計下修改了多少內容
$ git diff master --shortstat
199 files changed, 1784 insertions(+), 1411 deletions(-)
複製代碼
以上編譯時的問題解決了,最使人頭疼的仍是運行時問題了
此次碰到的是 postgres 的 range
這個數據類型
// 更改前
[0, 100]
// 更改後
[{
value: 0,
inclusive: false
}, {
value: 100,
inclusive: true
}]
複製代碼
可是若是對數據庫的每一個 Model 都加上 type 的話,這個問題就能夠在編譯時解決
在 where 中遇到 undefined
會拋出異常。github.com/sequelize/s…
使用 _.pickBy
過濾掉 undefined
const where = _.pickBy(data, x => x !== undefined)
複製代碼
固然,若是 typescript 作的比較嚴格的話,這個問題也能夠避免
_.assign
會丟失 Symbol 屬性,使用 Object.assign
代替
歡迎關注個人公衆號山月行,在這裏記錄着個人技術成長,歡迎交流