问题描述
sequelize-typescript与babel-preset-typescript.列应通过 对象检索.defineProperty() 但是当现有属性已经存在时,它会被跳过 在这里查看源代码.当使用 Sequelize† 运行常规 typescript 实现的代码时,一切正常,但在实现 babel-typescript 时,属性仍然存在.
There is a problem when using sequelize-typescript together with babel-preset-typescript. Columns should be retrieved through the Object.defineProperty() but when an existing property already exists it is skipped see source here. When running the code with regular typescript implementation with Sequelize† everything works but when implementing babel-typescript the properties remain.
问题:我应该如何定义我的 babel 配置,以便正确忽略这些属性?
Question: How should I define my babel configuration so that the properties are properly ignored?
我尝试了两种 Babel 定义,一种具有所有所需的功能和一个需要明确定义的.
I've tried two Babel definitions, one that has all desired features & one that requires explicit definitions.
module.exports = {
plugins: [
[
"@babel/plugin-transform-runtime",
{
regenerator: true,
},
],
"babel-plugin-inline-import",
"babel-plugin-transform-typescript-metadata",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/proposal-class-properties", { loose: true }],
"@babel/proposal-object-rest-spread",
"@babel/plugin-proposal-optional-chaining",
[
"module-resolver",
{
alias: {
"~": "./src",
},
},
],
],
presets: ["@babel/preset-env", "@babel/typescript"],
};
微小的显式定义
module.exports = {
plugins: [
["@babel/proposal-decorators", { legacy: true }],
["@babel/proposal-class-properties", { loose: true }],
"@babel/proposal-async-generator-functions",
"@babel/proposal-object-rest-spread",
"@babel/plugin-transform-runtime",
],
presets: ["@babel/preset-env", "@babel/typescript"],
};
背景与详情
您可以在这里找到一个用于测试的常规非 babel 示例,分支 babel_build 和 babel_explicit 包含不同的 babel 实现.
Background & details
You can find a regular non-babel example for testing here, the branch babel_build and babel_explicit contain different babel-implementations.
定义表的要点是在代码中添加装饰器的一个小例子:
The gist of defining a table is a trivial case of adding decorators to the code:
import { Table, Column, Model } from 'sequelize-typescript'
@Table
class Person extends Model {
@Column
name: string
@Column
birthday: Date
}
问题是,如果我们通过以下方式检索一个案例:
The problem is that if we then retrieve a case through:
const p = await Person.findOne({ name: "John Doe" })
console.log(p)
我们得到:
Person {
dataValues: {
id: 1,
name: "John Doe",
birthday: null
},
_previousDataValues: {
id: 1,
name: "John Doe",
birthday: null
},
_changed: Set(0) {},
_options: {
isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true,
attributes: [
'id',
'name',
'birthday'
]
},
isNewRecord: false,
name: undefined,
birthday: undefined,
}
用 undefined
记下最后两行.
似乎正确的定义应该使用 declare
关键字.虽然这应该可以解决问题,但它在实践中不起作用.
It seems that the correct definitions should be using the declare
keyword. While this should solve the issue it doesn't work in practice.
import { Table, Column, Model } from 'sequelize-typescript'
@Table
class Person extends Model {
@Column
declare name: string
@Column
declare birthday: Date
}
配置:
module.exports = {
plugins: [
["@babel/plugin-transform-typescript", { allowDeclareFields: true }],
[
"@babel/plugin-transform-runtime",
{
regenerator: true,
},
],
"babel-plugin-transform-typescript-metadata",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/proposal-class-properties", { loose: true }],
[
"module-resolver",
{
alias: {
"~": "./src",
},
},
],
],
presets: ["@babel/preset-env"],
};
很遗憾,问题没有解决,但似乎 declare
更符合预期用途.
Unfortunately, the issue is not resolved but seems that declare
is more in line with the intended use.
† 这很可能独立于 sequelize-typescript
附加组件
† this is most likely independent of the sequelize-typescript
add-ons
推荐答案
来自 https://sequelize.org/master/manual/typescript.html,模型初始化的TypeScript定义是正确的,但是文档不正确!
From https://sequelize.org/master/manual/typescript.html, the TypeScript definition for model initialization is correct, but the documentation is not correct!
当你使用 null 断言时 (public id!: number;//注意null assertion"; !"在严格模式下是必需的
),这会使属性在实例初始化时为 undefined
.
When you use the null assertion (public id!: number; // Note that the "null assertion" "!" is required in strict mode
), this make the attribute as undefined
at instance initialization.
我的想法是在 Model Constructor 使用 getDataValue()
强制模型初始化,并避免使用推荐的 null 断言.p>
示例
My idea is to force the model initialization using getDataValue()
at Model Constructor and avoid to use the recommended null assertion.
import {
BuildOptions,
Sequelize,
Model,
Optional,
} from "sequelize";
const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb");
// These are all the attributes in the User model
interface UserAttributes {
id: number;
name: string;
//preferredName!: string | null;
preferredName: string | null;
}
// Some attributes are optional in `User.build` and `User.create` calls
interface UserCreationAttributes extends Optional<UserAttributes, "id"> {}
class User extends Model<UserAttributes, UserCreationAttributes>
implements UserAttributes {
//public id!: number; // Note that the `null assertion` `!` is required in strict mode.
public id: number;
//public name!: string;
public name: string;
//public preferredName!: string | null; // for nullable fields
public preferredName: string | null;
// timestamps!
//public readonly createdAt!: Date;
public readonly createdAt: Date;
//public readonly updatedAt!: Date;
public readonly updatedAt: Date;
/**
* Initialization to fix Sequelize Issue #11675.
*
* @see https://stackoverflow.com/questions/66515762/configuring-babel-typescript-for-sequelize-orm-causes-undefined-properties
* @see https://github.com/sequelize/sequelize/issues/11675
* @ref #SEQUELIZE-11675
*/
constructor(values?: TCreationAttributes, options?: BuildOptions) {
super(values, options);
// All fields should be here!
this.id = this.getDataValue('id');
this.name = this.getDataValue('name');
this.preferredName = this.getDataValue('preferredName');
this.createdAt = this.getDataValue('createdAt');
this.updatedAt = this.getDataValue('updatedAt');
}
}
这篇关于为 Sequelize ORM 配置 babel-typescript 会导致未定义的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!