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