Модельный загрузчик для Sequelize v5 & Typescript - PullRequest
1 голос
/ 16 октября 2019

Ранее использовал Sequelize для проектов (v4), но пытался начать новый проект с Sequelize v5 & Typescript

Я следовал документации Sequelize для определения моделей в: https://sequelize.org/master/manual/typescript.html#usage-of--code-sequelize-define--code-

У меня есть работающий ORM сейчас, но только при импорте фактической модели для использования , а не через импорт БД из загрузчика модели.
т.е. import { User } from "../db/models/user";

при импортеdb, просто возвращает undefined при попытке доступа к db.User.

Попытка выяснить, как заставить загрузчик модели размещаться красиво с Sequelize V5 и Typescript, но в настоящее время он готовитсяпусто.

Теперь я могу сказать, что он ищет .js файлов. Так что очевидно, что он не подхватит файл user.ts. Изменение этого значения на .ts приводит к ошибке ....

    at Sequelize.import (/node_modules/sequelize/lib/sequelize.js:486:38)
    at fs_1.default.readdirSync.filter.forEach.file (/src/db/models/index.ts:26:35)
    at Array.forEach (<anonymous>)
    at Object.<anonymous> (/src/db/models/index.ts:25:4)

Я пытался получить четкий ответ из поисковых запросов в Интернете, но, похоже, вылетел пустым. Было достаточно головной боли, когда я пытался заставить все играть хорошо ... и на этом этапе я запускаю переносы / просеиватели в виде файлов js, потому что я не хочу иметь дело с sequelize-typescript-cli или sequelize-typcript

src/db/models/user.ts Модель пользователя

import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
import { HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, Association, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize';
const db = require('./index')
import * as bcrypt from "bcryptjs";

export interface UserAttributes extends Model {
  id: string;
  email: string;
  username: string;
  password: string;
  createdAt: Date;
  updatedAt: Date;
  validatePassword(password: string): boolean;
  generateHash(password: string): string;
}

export type UserModel = typeof Model & {
  new (): UserAttributes;
};

export const User = <UserModel>db.sequelize.define("User", {
  id: {
    type: DataTypes.UUID,
    allowNull: false,
    primaryKey: true
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  username: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false,
  }
},
{
  tableName: "User",
  freezeTableName: true,
 });

 User.prototype.validatePassword = function (password: string) {

  return bcrypt.compareSync(password, this.password)
 }

 User.prototype.generateHash = function (password: string) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(10))
  }

src/db/models/index.ts Модель Loader

'use strict';

import fs from "fs";
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(module.filename);

const env = process.env.NODE_ENV || 'development';
const config = require(`${__dirname}/../config/config.json`)[env];

interface DB {
  [key: string]: any;
}

var db: DB = {};

const 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 = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });
// Important: creates associations based on associations defined in associate function in the model files
Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

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

module.exports = db;

Более того, чтение https://sequelize.org/master/manual/typescript.html#usage

Кажется, что есть немного более четкий (но несколько более избыточный) способ определения Моделей, но тогда как этот метод init называетсяпри инициализации Sequelize из index.js?

1 Ответ

1 голос
/ 17 октября 2019

Итак, у меня все работает, но в циклическом загрузчике моделей. Я проигнорировал определенную документацию, https://sequelize.org/master/manual/typescript.html#usage-of--code-sequelize-define--code-

для подхода с длинными классами здесь: https://sequelize.org/master/manual/typescript.html#usage

Я пройду процесс настройки 2 моделей и их ассоциаций длянадеюсь, помочь тем, кто пытается интегрировать Typescript с Sequelize v5.

ОПРЕДЕЛЕННО ЛЮБЛЮ ОБРАТНУЮ ОБРАТНУЮ СВЯЗЬ С ЭТИМ ПОДХОДОМ.

Начнем с классов для пользователя и связанных с ним удостоверений (для доступа к API)

/src/db/models/user.ts

import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
import { Association, HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize';
import { Identity } from './identity';
export class User extends Model {
  public id!: string; // Note that the `null assertion` `!` is required in strict mode.
  public active!: boolean;

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

  public getIdentities!: HasManyGetAssociationsMixin<Identity>; // Note the null assertions!
  public addIdentity!: HasManyAddAssociationMixin<Identity, number>;
  public hasIdentity!: HasManyHasAssociationMixin<Identity, number>;
  public countIdentities!: HasManyCountAssociationsMixin;
  public createIdentity!: HasManyCreateAssociationMixin<Identity>;

  // You can also pre-declare possible inclusions, these will only be populated if you
  // actively include a relation.
  public readonly identities?: Identity[]; // Note this is optional since it's only populated when explicitly requested in code

  public static associations: {
    identities: Association<User, Identity>;
  };

}

export function initUser(sequelize: Sequelize): void {
  User.init({
    id: {
      type: DataTypes.UUID,
      primaryKey: true,
    },
    active: {
      type:DataTypes.BOOLEAN,
      defaultValue: true,
      allowNull: false
    }
  }, {
    tableName: 'User', 
    sequelize: sequelize, // this bit is important
  });


}

export function associateUser(): void {
  // Here we associate which actually populates out pre-declared `association` static and other methods.
  User.hasMany(Identity, {
    sourceKey: 'id',
    foreignKey: 'UserId',
    as: 'identities' // this determines the name in `associations`!
  });
}

/src/db/models/identity.ts

import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
import { Association, HasOneGetAssociationMixin, HasOneCreateAssociationMixin } from 'sequelize';
import { User } from './user'

import * as bcrypt from "bcryptjs";

export class Identity extends Model {
  public id!: string; // Note that the `null assertion` `!` is required in strict mode.
  public username!: string;
  public password!: string;
  public UserId: string;
  public active!: boolean;

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

  public getUser!: HasOneGetAssociationMixin<User>; // Note the null assertions!

  // You can also pre-declare possible inclusions, these will only be populated if you
  // actively include a relation.
  public readonly user?: User; // Note this is optional since it's only populated when explicitly requested in code

  public static associations: {
    user: Association<Identity, User>;
  };

  public validatePassword(password: string) : boolean {
    return bcrypt.compareSync(password, this.password)
  }
}

export function initIdentity(sequelize: Sequelize): void {
  Identity.init({
    id: {
      type: DataTypes.UUID,
      primaryKey: true,
    },
    username: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
    UserId: {
      type: DataTypes.UUID,
      allowNull: true
    },
    active: {
      type:DataTypes.BOOLEAN,
      defaultValue: true,
      allowNull: false
    }
  }, {
    tableName: 'Identity', 
    sequelize: sequelize, // this bit is important
  });

}

export function associateIdentity(): void {
  // Here we associate which actually populates out pre-declared `association` static and other methods.
  Identity.belongsTo(User, {targetKey: 'id'});
}


Итак, после этого мы объявили все «виртуальные» члены и функции, которые будут относиться к Sequelize и базе данных. Кроме того, есть функции init<model> && associate<model>, которые будут использоваться для связывания всего воедино.

Примечание Вы можете заметить, что в identity.ts вместо userId используется UserIdв ассоциациях. По некоторым причинам это продолжало предполагать, что ассоциация собиралась быть через UserId, даже при том, что я использовал userId. При выполнении запроса он жаловался на отсутствие столбца UserId (но userId). Так что обновление до заглавной буквы «U» решило это. Я не уверен, почему он делает это в данный момент.

Теперь, чтобы связать все это вместе

/src/db/index.ts

import { initUser, associateUser } from "./user";
import { initIdentity, associateIdentity } from "./identity";

const Sequelize = require('sequelize');

const env = process.env.NODE_ENV || 'development';
const config = require(`${__dirname}/../config/config.json`)[env];


interface DB {
  [key: string]: any;
}

const sequelize = new Sequelize(config.database, config.username, config.password, config);

initUser(sequelize);
initIdentity(sequelize)

associateUser();
associateIdentity();

const db = {
  sequelize,
  Sequelize,
  User: sequelize.models.User,
  Identity: sequelize.models.Identity
}

module.exports = db;

Обычная загрузка моделей, которую нужно выполнить, зашел в каталог, нашел все модели и затем импортировал их в секвелиз. Теперь, как я уже говорил ранее, попытка использовать define в классе модели вызвала проблемы при попытке пройти через этот загрузчик модели, потому что не версии Typescript всегда искали * .js, а не * .ts. Изменение на * .ts привело к сбою при вызове define. (Не говоря уже о том, что весь этот код будет перенесен в js-файлы, разве это не вызовет проблему наоборот?)

Но, как вы можете видеть, я делаю все вручную, а не в цикле,Возможно, есть лучший способ сделать это, но на данный момент этого будет достаточно.

Модели инициализируются последовательно с вызовами их функций init<model>. После инициализации их ассоциации создаются с помощью вызова функции associate<model>

Перед запуском моего экспресс-сервера мне требуется файл индекса, и все это запускается. Boom.

Другие замечания, касающиеся моего подхода Я не хотел устанавливать больше пакетов, чем мне было нужно. Так что я избежал от sequelize-typcript и sequelize-typescript-cli. Это означает, что все мои файлы seeder и файлы миграции должны быть сделаны вручную без использования cli (это действительно не так уж плохо) и не являются * .ts, а * .js.

пример: 20191017135846-create-identity.js

'use strict'
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable({tableName:'Identity'}, {
      id: {
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4,
        allowNull: false,
        autoIncrement: false,
        primaryKey: true,
      },
      username: {
        type: Sequelize.STRING,
        allowNull: false,
        unique: true,
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      UserId: {
        type: Sequelize.UUID,
        references: {
          model: 'User', // name of Target model
          key: 'id', // key in Target model that we're referencing
        },
        onUpdate: 'CASCADE',
        onDelete: 'SET NULL',
      },
      active: {
        type: Sequelize.BOOLEAN,
        defaultValue: true,
        allowNull: false,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.NOW
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.NOW
      },
    })
  },
  down: (queryInterface) => {
    return queryInterface.dropTable({tableName:'Identity', schema:'public'})
  }
}

20191015141822-seed-users.js

'use strict'
var moment = require('moment');
var uuidv4 = require('uuid/v4');
const bcrypt = require('bcryptjs');

module.exports = {
  up: async (queryInterface) => {   
      // User
      const user1Id = uuidv4();
      await queryInterface.bulkInsert('User', 
        [
          {
            id:user1Id,
            createdAt: new Date( moment.utc().format() ), 
            updatedAt: new Date( moment.utc().format() )
          }
        ], 
      )
      await queryInterface.bulkInsert('Identity', 
        [
          {
            id:uuidv4(),
            username: "user1",
            password: bcrypt.hashSync('password', bcrypt.genSaltSync(10)),
            UserId: user1Id,
            createdAt: new Date( moment.utc().format() ), 
            updatedAt: new Date( moment.utc().format() )
          }
        ], 
      )

      const user2Id = uuidv4();
      await queryInterface.bulkInsert('User', 
        [
          {
            id:user2Id,
            createdAt: new Date( moment.utc().format() ), 
            updatedAt: new Date( moment.utc().format() )
          }
        ], 
      )
      await queryInterface.bulkInsert('Identity', 
        [
          {
            id:uuidv4(),
            username: "user2",
            password: bcrypt.hashSync('password', bcrypt.genSaltSync(10)),
            UserId: user2Id,
            createdAt: new Date( moment.utc().format() ), 
            updatedAt: new Date( moment.utc().format() )
          }
        ], 
      )

      const user3Id = uuidv4();
      await queryInterface.bulkInsert('User', 
        [
          {
            id:user3Id,
            createdAt: new Date( moment.utc().format() ), 
            updatedAt: new Date( moment.utc().format() )
          }
        ], 
      )
      await queryInterface.bulkInsert('Identity', 
        [
          {
            id:uuidv4(),
            username: "user3",
            password: bcrypt.hashSync('password', bcrypt.genSaltSync(10)),
            UserId: user3Id,
            createdAt: new Date( moment.utc().format() ), 
            updatedAt: new Date( moment.utc().format() )
          }
        ], 
      )


  },
  down: async (queryInterface) => {
    await queryInterface.bulkDelete({ tableName: 'User'}, null, {})
  }
}

, которые в этот момент вы можете запустить

sequelize db:migrate
sequelize db:seed:all

, и все работает и может получить доступ к БД.

Теперь, используя классы / машинопись, я заметил, что добавление моделей в и экспортированный объект БД является излишним ....

Я могу получить доступ к нужным моделям через импорт

import {User} из '../db/models/user' или require ('./ db / models / index')

, что я могу затем сделать User.findAll () или с другим импортом db.User. FindAll ()

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...