sequelize.js - Konfigurering av babel-typescript for Sequelize ORM forårsaker udefinerte egenskaper


Det oppstår et problem når man bruker sequelize-typescript sammen med babel-preset-typescript. Kolonner bør hentes gjennom Object.defineProperty(), men når en eksisterende egenskap allerede finnes, blir den hoppet over, se kilde her. Når koden kjøres med vanlig TypeScript-implementering med Sequelize†, fungerer alt, men når man implementerer babel-typescript, forblir egenskapene.

Spørsmål: Hvordan bør jeg definere min Babel-konfigurasjon slik at egenskapene blir korrekt ignorert?

Babel-definisjoner (babel.config.js)

Jeg har prøvd to Babel-definisjoner, en som har alle ønskede funksjoner og en som krever eksplisitte definisjoner.

Full definisjon

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"],
};

Liten eksplisitt definisjon

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"],
};

Bakgrunn og detaljer

Du kan finne et vanlig ikke-babel-eksempel for testing her, grenene babel_build og babel_explicit inneholder forskjellige babel-implementasjoner.

Hovedpoenget med å definere en tabell er et trivielt tilfelle av å legge til dekoratører i koden:

import { Table, Column, Model } from 'sequelize-typescript'

@Table
class Person extends Model {
  @Column
  name: string

  @Column
  birthday: Date
}

Problemet er at hvis vi deretter henter en sak gjennom:

const p = await Person.findOne({ name: "John Doe" })
console.log(p)

vi får:

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,
}

merk de to siste radene med undefined.

Oppdatering - bruker declare

Det ser ut til at de riktige definisjonene bør bruke declare-nøkkelordet. Selv om dette burde løse problemet, fungerer det ikke i praksis.

import { Table, Column, Model } from 'sequelize-typescript'

@Table
class Person extends Model {
  @Column
  declare name: string

  @Column
  declare birthday: Date
}

med konfigurasjonen:

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"],
};

Dessverre er problemet ikke løst, men det ser ut til at declare er mer i tråd med den tiltenkte bruken.

† dette er mest sannsynlig uavhengig av sequelize-typescript-tilleggene

Svar #1

Oppdatering 2022-01-21

Du bør bruke declare i stedet for public ....

For eksempel:

class User extends Model<UserAttributes, UserCreationAttributes>
  implements UserAttributes {
  //public id!: number; // Note that the `null assertion` `!` is required in strict mode.
  declare id: number;

  //public name!: string;
  declare name: string;

  //public preferredName!: string | null; // for nullable fields
  declare preferredName: string | null;

  // timestamps!
  //public readonly createdAt!: Date;
  declare readonly createdAt: Date;

Kilde :

  • https://github.com/sequelize/sequelize/pull/13877/files
  • https://github.com/sequelize/sequelize/issues/11675#issuecomment-1003765448

Originalt svar

Fra https://sequelize.org/master/manual/typescript.html, er TypeScript-definisjonen for modellinitiering korrekt, men dokumentasjonen er ikke korrekt!

Når du bruker null-assertion (public id!: number;/Merk at "null-assertion" "!" er nødvendig i strikt modus), gjør dette attributtet til undefined ved instansinitialisering.

Min idé er å tvinge modellinitialiseringen ved å bruke getDataValue() i Model Constructor og unngå å bruke den anbefalte null assertion.

Eksempel

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');
  }
}

Relaterte artikler