Typescript Migration Sequelize

V8 Migration Typescript Sequelize

We followed step by step all the migration guide for v8: https://docs.forestadmin.com/woodshop/how-tos/translate-your-project-into-typescript/v8/migrate-sequelize-files

But apparently ts is not entirely supported by forest cli. Everything seems to works fine so far (The project builds and run.). The issue we have is with forest-cli and with all editions of our entities.


Open issues


Issue 1:


databases.js is needed by our cli to update our schema programatically.

Since the migration to ts, we have an error after executing this command:

forest schema:update

Error:

An unexpected error occurred. Please reach out for help in our Developers community (https://community.forestadmin.com/) or create a Github issue with following error: databasesConfig.map is not a function

Could it be an error from Sequelize ?. Our current solution is just to put back database.js, and the cli works fine.

Issue 2:


After upgrading our models/index.js to index.ts, the project seems to be launching without errors. Nevertheless, we are unable to perform any edit operation. When we revert this file to index.js, seems to work again.

The error we have for any crud model edition:

GET /forest/user/6cb2ff86-46f3-4d43-97a8-28feedb0b572/relationships/greenProjectSubscriptions/count?fields%5BgreenProjectSubscription%5D=user%2Cquantity%2CtotalPriceInEur%2CpricePerUnitInEur%2CcreatedAt%2CendedAt%2CgreenProject%2CgreenProjectPaymentServiceProductPricing%2Cid%2CisActive%2ClastUpdatedAt%2CnextPaymentAt%2CpaymentServiceId%2CpaymentServiceSubscriptionId%2Cunit&fields%5Buser%5D=fullname&fields%5BgreenProject%5D=name&fields%5BgreenProjectPaymentServiceProductPricing%5D=id&timezone=Europe%2FParis 304 - - 41.431 ms
[forest] 🌳🌳🌳  Unexpected error: Cannot read properties of undefined (reading 'name')
{
  "stack": "TypeError: Cannot read properties of undefined (reading 'name')\n    at Object.getModelName (/Users/Amaury/Desktop/dev/back-office/node_modules/forest-express-sequelize/dist/index.js:127:18)\n    at new ResourceDeserializer (/Users/Amaury/Desktop/dev/back-office/node_modules/forest-express/dist/deserializers/resource.js:16:47)\n    at RecordUpdater.deserialize (/Users/Amaury/Desktop/dev/back-office/node_modules/forest-express/dist/services/exposed/record-updater.js:49:14)\n    at /Users/Amaury/Desktop/dev/back-office/dist/routes/user.js:24:10\n    at Layer.handle [as handle_request] (/Users/Amaury/Desktop/dev/back-office/node_modules/express/lib/router/layer.js:95:5)\n    at next (/Users/Amaury/Desktop/dev/back-office/node_modules/express/lib/router/route.js:137:13)\n    at _callee$ (/Users/Amaury/Desktop/dev/back-office/node_modules/forest-express/dist/middlewares/permissions.js:117:19)\n    at tryCatch (/Users/Amaury/Desktop/dev/back-office/node_modules/regenerator-runtime/runtime.js:63:40)\n    at Generator.invoke [as _invoke] (/Users/Amaury/Desktop/dev/back-office/node_modules/regenerator-runtime/runtime.js:294:22)\n    at Generator.next (/Users/Amaury/Desktop/dev/back-office/node_modules/regenerator-runtime/runtime.js:119:21)\n    at asyncGeneratorStep (/Users/Amaury/Desktop/dev/back-office/node_modules/forest-express/node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:24)\n    at _next (/Users/Amaury/Desktop/dev/back-office/node_modules/forest-express/node_modules/@babel/runtime/helpers/asyncToGenerator.js:25:9)"
}
PUT /forest/user/6cb2ff86-46f3-4d43-97a8-28feedb0b572?timezone=Europe%2FParis 500 110 - 2.211 ms

Project context


import * as path from 'path';
import { Options } from 'sequelize';
import { DatabaseConfiguration } from 'forest-express-sequelize';

const databaseOptions: Options = {
  logging:
    !process.env.NODE_ENV || process.env.NODE_ENV === 'development'
      ? console.log
      : false,
  pool: { max: 10, min: 1 },
  dialectOptions: {},
};

if (
  process.env.DATABASE_SSL &&
  JSON.parse(process.env.DATABASE_SSL.toLowerCase())
) {
  const rejectUnauthorized = process.env.DATABASE_REJECT_UNAUTHORIZED;
  if (
    rejectUnauthorized &&
    JSON.parse(rejectUnauthorized.toLowerCase()) === false
  ) {
    // @ts-ignore
    databaseOptions.dialectOptions.ssl = { rejectUnauthorized: false };
  } else {
    // @ts-ignore
    databaseOptions.dialectOptions.ssl = true;
  }
}

const databasesConfiguration: DatabaseConfiguration[] = [
  {
    name: 'default',
    modelsDir: path.resolve(__dirname, '../models'),
    connection: {
      url: process.env.DATABASE_URL,
      options: { ...databaseOptions },
    },
  },
];

export default databasesConfiguration;
Object.defineProperty(exports, '__esModule', { value: true });
const path = require('path');

const databaseOptions = {
  logging:
    !process.env.NODE_ENV || process.env.NODE_ENV === 'development'
      ? console.log
      : false,
  pool: { max: 10, min: 1 },
  dialectOptions: {},
};
if (
  process.env.DATABASE_SSL &&
  JSON.parse(process.env.DATABASE_SSL.toLowerCase())
) {
  const rejectUnauthorized = process.env.DATABASE_REJECT_UNAUTHORIZED;
  if (
    rejectUnauthorized &&
    JSON.parse(rejectUnauthorized.toLowerCase()) === false
  ) {
    // @ts-ignore
    databaseOptions.dialectOptions.ssl = { rejectUnauthorized: false };
  } else {
    // @ts-ignore
    databaseOptions.dialectOptions.ssl = true;
  }
}
const databasesConfiguration = [
  {
    name: 'default',
    modelsDir: path.resolve(__dirname, '../models'),
    connection: {
      url: process.env.DATABASE_URL,
      options: { ...databaseOptions },
    },
  },
];
exports.default = databasesConfiguration;
{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "pretty": true,
    "sourceMap": false,
    "target": "es2017",
    "outDir": "dist",
    "baseUrl": ".",
    "types": ["node", "express", "forest-express-sequelize", "sequelize"],
    "allowJs": true
  },
  "include": ["./**/*", ".env"],
  "exclude": ["node_modules", "dist"]
}
{
  "name": "greenly-corporate",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node ./dist/server.js",
    "build": "rm -rf ./dist && tsc -b && yarn cp-config",
    "start:dev": "tsc-watch --onSuccess \"node ./dist/server.js\"",
    "lint": "eslint --ext .ts,.js .",
    "cp-config": "cp .env ./dist/ && cp -R ./views/ ./dist/views/",
    "lint:fix": "prettier --write \"**/*.{js,ts}\""
  },
  "dependencies": {
    "axios": "^0.21.0",
    "body-parser": "^1.19.0",
    "chalk": "~1.1.3",
    "cookie-parser": "1.4.4",
    "cors": "2.8.5",
    "debug": "~4.0.1",
    "dotenv": "~6.1.0",
    "express": "~4.17",
    "express-jwt": "5.3.1",
    "forest-express-sequelize": "^8.0.7",
    "morgan": "1.9.1",
    "pg": "8.5.1",
    "require-all": "^3.0.0",
    "sequelize": "^5.15.0"
  },
  "devDependencies": {
    "@types/express": "^4.17.13",
    "@types/node": "^16.11.6",
    "@types/sequelize": "^4.28.10",
    "@typescript-eslint/eslint-plugin": "^5.2.0",
    "@typescript-eslint/parser": "^5.2.0",
    "eslint": "^8.1.0",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-import": "^2.25.2",
    "eslint-plugin-prettier": "^4.0.0",
    "prettier": "^2.4.1",
    "tsc": "^2.0.3",
    "tsc-watch": "^4.5.0",
    "typescript": "^4.4.4"
  }
}
import { readdirSync } from 'fs';
import { join } from 'path';
import * as Sequelize from 'sequelize';
import databasesConfiguration from '../config/databases';

const connections: Record<string, Sequelize.Sequelize> = {};
const objectMapping = Sequelize;
const models: Record<string, typeof Sequelize.Model> = {};

databasesConfiguration.forEach(databaseInfo => {
  const connection = new Sequelize.Sequelize(
    databaseInfo.connection.url,
    databaseInfo.connection.options,
  );
  connections[databaseInfo.name] = connection;

  const modelsDir =
    databaseInfo.modelsDir || join(__dirname, databaseInfo.name);
  readdirSync(modelsDir)
    .filter(
      file =>
        file.indexOf('.') !== 0 &&
        file !== 'index.js' &&
        !file.includes('.map'),
    )
    .forEach(file => {
      try {
        const model = connection.import(join(modelsDir, file));
        models[model.name] = model;
      } catch (error) {
        console.error(`Model creation error: ${error}`);
      }
    });
});

Object.keys(models).forEach(modelName => {
  if ('associate' in models[modelName]) {
    // @ts-ignore
    models[modelName].associate(models);
  }
});

export { objectMapping, models, connections };
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.connections = exports.models = exports.objectMapping = void 0;
const fs_1 = require('fs');
const path_1 = require('path');
const Sequelize = require('sequelize');
const databasesConfiguration = require('../config/databases');
const connections = {};
exports.connections = connections;
const objectMapping = Sequelize;
exports.objectMapping = objectMapping;
const models = {};
exports.models = models;
databasesConfiguration.forEach(databaseInfo => {
  const connection = new Sequelize.Sequelize(
    databaseInfo.connection.url,
    databaseInfo.connection.options,
  );
  connections[databaseInfo.name] = connection;
  const modelsDir =
    databaseInfo.modelsDir || (0, path_1.join)(__dirname, databaseInfo.name);
  (0, fs_1.readdirSync)(modelsDir)
    .filter(
      file =>
        file.indexOf('.') !== 0 &&
        file !== 'index.js' &&
        !file.includes('.map'),
    )
    .forEach(file => {
      try {
        const model = connection.import((0, path_1.join)(modelsDir, file));
        models[model.name] = model;
      } catch (error) {
        console.error(`Model creation error: ${error}`);
      }
    });
});
Object.keys(models).forEach(modelName => {
  if ('associate' in models[modelName]) {
    // @ts-ignore
    models[modelName].associate(models);
  }
});

Hello @Amaury_Schillio :wave:

Thanks for opening an issue, and thanks for the complete threads with a lot of informations :pray:

Issue 1
In fact, I have checked your first issue and the way we change the files into typescript breaks the CLI.

To fix the CLI, can you try to make the following changes please ?

Change the way databases configuration is exported in config/databases.ts

...
export = databasesConfiguration;

change the way you import the database config in models/index.ts with the following:

...
import * as databasesConfiguration from "../config/databases";
...

By doing this two changes, your CLI should be able to correctly retrieve your database configuration, and then work properly on your schema.

Issue 2
Sorry but I will need some more time to investigate on this. I will ping you as soon as I have more information or questions :smiley:

Regards,

Steve.

Hello @Steve_Bunlon,

Thanks for the feedback. Do you have any news on the second topic ?