node.js - Sequelize CLI och DB-migreringar med Typescript


Sequelize-dokumentationen hävdar att det fungerar med Typescript, men för att det ska vara användbart i produktion måste man använda DB-migrationsskript. Dessa kan bara köras med Sequelize CLI, men denna CLI verkar inte bry sig om Typescript alls. Den genererar (och kör tydligen) bara JS-filer. Det finns ett projekt "sequelize-cli-typescript" på NPM, men det är 4 år gammalt! Och det enda svaret på Stack Overflow på en relaterad fråga är bokstavligen 6 ord långt, inga av dem användbara: Using sequelize cli with typescript

Min uppsättning är så här:

Jag skapade en grundläggande Node-app med Typescript och en enkel tsconfig.js så här:

{
  "compilerOptions": {
    "allowJs": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "noImplicitAny": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist",
    "baseUrl": ".",
    "paths": {
      "*": [
        "node_modules/*"
      ]
    }
  },
  "include": [
    "*"
  ]
}

Jag använder också ESLint med följande .eslintrc:

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "airbnb-base", "airbnb-typescript/base"
  ],
  "parserOptions": {
    "project": "./tsconfig.json"
  }
}

Jag försökte sedan köra Sequelize CLI init-kommandot, npx sequelize-cli init som beskrivs i dokumentationen för databas-migrationer. Detta resulterade i att en fil models/index.js skapades med följande innehåll:

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

Detta gör att min IDE (VS Code) visar ett fel:

Parsningsfel: "parserOptions.project" har ställts in för @typescript-eslint/parser. Filen matchar inte din projektkonfiguration: models/index.js. Filen måste ingå i minst ett av de angivna projekten.

Jag har ingen aning om vad detta är tänkt att betyda. Jag har läst flera förklaringar och ingen verkar rimlig för mig. Jag har allowJs satt till true i tsconfig och inkluderat allt. Vad missar jag? Viktigare, hur kan jag använda Typescript för databasmigrationer och CLI-genererad kod?

Svar #1

När det gäller genererad kod för att koppla till DB och registrera modeller bör du konvertera den manuellt till ts (vanligtvis en engångsgrej).
När det gäller migrationer använder jag följande lösning:

  1. konfigurera migrationssökvägen i .sequelizerc till en undermapp i mappen där den kompilerade appen kommer att placeras av TS (det är viktigt för att kunna tillämpa migrationer på test- och produktionsmiljöer), något som:
const { resolve } = require('path');

module.exports = {
    config: resolve('build/application/config.js'),
    'seeders-path': resolve('build/application/seeders'),
    'migrations-path': resolve('build/application/migrations'),
    'models-path': resolve('application/models')
};
  1. generera en migrationsfil med ett givet namn:
sequelize migration:create --name add-some-table
  1. flytta den genererade filen till application/migrations och ändra dess filändelse till .ts
  2. ersätt innehållet i filen med någon form av en migration Typescript-mall:
import { QueryInterface, DataTypes, QueryTypes } from 'sequelize';

/** @type {import("sequelize-cli").Migration} */
module.exports = {
    up: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
        async (transaction) => {
          // here go all migration changes
        }
    ),

    down: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
        async (transaction) => {
          // here go all migration undo changes
        }
    )
};
  1. lägg till nödvändiga ändringar i migrationsfilen

I princip är det möjligt att skriva någon skriptfil för att helt enkelt generera en .ts-migrationsfil genom att kopiera en fördefinierad mallfil i application/migrations-mappen direkt med ett givet namn för att kombinera steg 2-4 till ett enda steg.

Om du så småningom kommer på en bättre lösning är du välkommen att dela den i kommentarerna eller till och med som ett separat svar här.

UPD. Från och med v6.5.2 har Sequelize CLI typdeklarationer som kan hjälpa till att härleda egenskaper och använda IDE-förslag och så vidare (se den uppdaterade koden ovan)

Svar #2

@Anatolys svar ändrades eftersom jag fick detta felmeddelande "ERROR: Dialect behöver anges uttryckligen från och med v4.0.0".

Jag skulle kunna kommentera där men jag kan för närvarande inte och någon kanske behöver detta;

ORSAN TILL PROBLEMET Problemet är att det beror på TypeScripts transformation/kompilering.

LÖSNING Efter att ha undersökt den transpilerade versionen av config.ts-filen, märkte jag att konfigurationen exporterades som:

module.exports.default = config
But sequelize is expecting the config to be at module.exports.

Så, jag modifierade config.ts-filen genom att lägga till den här raden i slutet av filen, och det löste det för mig.

module.exports = config;

tsconfig.json-fil

 "exclude": ["node_modules", "__tests__"],
 "compilerOptions": {
   "target": "ES2021",
   "module": "commonjs",
   "lib": ["es6"],
   "moduleResolution": "Node",
   "types": ["node", "jest"],
   // "sourceMap": true,
   "outDir": "./dist",
   "rootDir": "./",
   "allowSyntheticDefaultImports": true,
   "resolveJsonModule": true,
   "strict": true,
   "typeRoots": ["./types", "./digitalniweb-types"],
   "strictNullChecks": true,
   "esModuleInterop": true,
   "skipLibCheck": true,
    // "noImplicitAny": false
   // "strictPropertyInitialization": false
   // "strictNullChecks": false,
 }
}

config.ts

import dotenv from "dotenv";
import { Options } from "sequelize";

dotenv.config();

interface ConfigTs {
    development: Options;
    test: Options;
    production: Options;
}

const configDB: ConfigTs = {
    development: {
        username: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        host: process.env.DB_HOST,
        dialect: "postgres",
        dialectOptions: {
            charset: "utf8",
        },
        define: {
            timestamps: false,
        },
    },
    test: {
        username: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        host: process.env.DB_HOST,
        dialect: "postgres",
        dialectOptions: {
            charset: "utf8",
        },
        define: {
            timestamps: false,
        },
    },
    production: {
        username: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        host: process.env.DB_HOST,
        dialect: "postgres",
        dialectOptions: {
            charset: "utf8",
            multipleStatements: true,
        },
        logging: false,
        define: {
            timestamps: false,
        },
    },
};
export default configDB;

module.exports = configDB;


  1. konfigurerade migrationsvägen i .sequelizerc till en undermapp i mappen där den kompilerade appen kommer att placeras av TS (det är viktigt för att kunna tillämpa migrationer på test- och produktionsmiljöer), något som:
    const path = require("path")
    
    
    module.exports ={
        env: process.env.NODE_ENV || 'development',
        config: path.resolve("dist/config", "config.js"),
        "models-path": path.resolve("dist", 'models'),
        "seeders-path": path.resolve("dist", 'seeders'),
        "migrations-path": path.resolve("dist", 'migrations'),
    
    }
  1. generera en migrationsfil med ett givet namn:

    sequelize migration:create --name add-some-table

  2. flytta den genererade filen till Typescript-migreringar och ändra dess filändelse till .ts

  3. ersätt innehållet i filen med någon form av en migration Typescript-mall:

      import { QueryInterface, DataTypes, QueryTypes } from 'sequelize';
    
    module.exports = {
        up: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
            async (transaction) => {
              // here go all migration changes
            }
        ),
    
        down: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
            async (transaction) => {
              // here go all migration undo changes
            }
        )
    };
  1. lägg till nödvändiga ändringar i migrationsfilen
  2. bygg din applikation igen

Relaterade artiklar