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


