typeorm
Version:
Data-Mapper ORM for TypeScript and ES2023+. Supports MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB databases.
662 lines (607 loc) • 21.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InitCommand = void 0;
const tslib_1 = require("tslib");
const ansis_1 = tslib_1.__importDefault(require("ansis"));
const node_path_1 = tslib_1.__importDefault(require("node:path"));
const error_1 = require("../error");
const PlatformTools_1 = require("../platform/PlatformTools");
const CommandUtils_1 = require("./CommandUtils");
/**
* Generates a new project with TypeORM.
*/
class InitCommand {
constructor() {
this.command = "init";
this.describe = "Generates initial TypeORM project structure. " +
"If name specified then creates files inside directory called as name. " +
"If its not specified then creates files inside current directory.";
}
builder(args) {
return args
.option("n", {
alias: "name",
describe: "Name of the project directory.",
})
.option("db", {
alias: "database",
describe: "Database type you'll use in your project.",
choices: [
"postgres",
"mysql",
"mariadb",
"better-sqlite3",
"mssql",
"oracle",
"mongodb",
"cockroachdb",
"spanner",
],
})
.option("express", {
describe: "Indicates if express server sample code should be included in the project. False by default.",
})
.option("docker", {
describe: "Set to true if docker-compose must be generated as well. False by default.",
})
.option("ms", {
alias: "module",
choices: ["commonjs", "esm"],
default: "commonjs",
describe: "Module system to use for project, expected values are commonjs or esm.",
});
}
async handler(args) {
try {
const database = args.database ?? "postgres";
const isExpress = args.express !== undefined ? true : false;
const isDocker = args.docker !== undefined ? true : false;
const basePath = process.cwd() + (args.name ? "/" + args.name : "");
const projectName = args.name
? node_path_1.default.basename(args.name)
: undefined;
const projectIsEsm = args.ms === "esm";
await CommandUtils_1.CommandUtils.createFile(basePath + "/package.json", InitCommand.getPackageJsonTemplate(projectName, projectIsEsm), false);
if (isDocker)
await CommandUtils_1.CommandUtils.createFile(basePath + "/docker-compose.yml", InitCommand.getDockerComposeTemplate(database), false);
await CommandUtils_1.CommandUtils.createFile(basePath + "/.gitignore", InitCommand.getGitIgnoreFile());
await CommandUtils_1.CommandUtils.createFile(basePath + "/README.md", InitCommand.getReadmeTemplate({ docker: isDocker }), false);
await CommandUtils_1.CommandUtils.createFile(basePath + "/tsconfig.json", InitCommand.getTsConfigTemplate(projectIsEsm));
await CommandUtils_1.CommandUtils.createFile(basePath + "/src/entities/User.ts", InitCommand.getUserEntityTemplate(database));
await CommandUtils_1.CommandUtils.createFile(basePath + "/src/data-source.ts", InitCommand.getAppDataSourceTemplate(projectIsEsm, database));
await CommandUtils_1.CommandUtils.createFile(basePath + "/src/index.ts", InitCommand.getAppIndexTemplate(isExpress, projectIsEsm));
await CommandUtils_1.CommandUtils.createDirectories(basePath + "/src/migrations");
// generate extra files for express application
if (isExpress) {
await CommandUtils_1.CommandUtils.createFile(basePath + "/src/routes.ts", InitCommand.getRoutesTemplate(projectIsEsm));
await CommandUtils_1.CommandUtils.createFile(basePath + "/src/controllers/UserController.ts", InitCommand.getControllerTemplate(projectIsEsm));
}
const packageJsonContents = await CommandUtils_1.CommandUtils.readFile(basePath + "/package.json");
await CommandUtils_1.CommandUtils.createFile(basePath + "/package.json", await InitCommand.appendPackageJson(packageJsonContents, database, isExpress, projectIsEsm));
if (args.name) {
console.log(ansis_1.default.green `Project created inside ${ansis_1.default.blue(basePath)} directory.`);
}
else {
console.log(ansis_1.default.green `Project created inside current directory.`);
}
console.log(ansis_1.default.green `Please verify the package.json file and install dependencies using your preferred package manager.`);
console.log(ansis_1.default.green `Done! Start playing with a new project!`);
}
catch (err) {
PlatformTools_1.PlatformTools.logCmdErr("Error during project initialization:", err);
process.exit(1);
}
}
// -------------------------------------------------------------------------
// Protected Static Methods
// -------------------------------------------------------------------------
/**
* Gets contents of the ormconfig file.
*
* @param isEsm
* @param database
*/
static getAppDataSourceTemplate(isEsm, database) {
let dbSettings;
switch (database) {
case "mysql":
dbSettings = [
'type: "mysql"',
'host: "localhost"',
"port: 3306",
'username: "root"',
'password: "password"',
'database: "typeorm"',
];
break;
case "mariadb":
dbSettings = [
'type: "mariadb"',
'host: "localhost"',
"port: 3306",
'username: "root"',
'password: "password"',
'database: "typeorm"',
];
break;
case "better-sqlite3":
dbSettings = [
'type: "better-sqlite3"',
'database: "database.sqlite"',
];
break;
case "postgres":
dbSettings = [
'type: "postgres"',
'host: "localhost"',
"port: 5432",
'username: "root"',
'password: "password"',
'database: "typeorm"',
];
break;
case "cockroachdb":
dbSettings = [
'type: "cockroachdb"',
'host: "localhost"',
"port: 26257",
'username: "root"',
'password: ""',
'database: "defaultdb"',
];
break;
case "mssql":
dbSettings = [
'type: "mssql"',
'host: "localhost"',
'username: "sa"',
'password: "Admin12345"',
'database: "tempdb"',
];
break;
case "oracle":
dbSettings = [
'type: "oracle"',
'host: "localhost"',
'username: "system"',
'password: "oracle"',
"port: 1521",
'sid: "xe.oracle.docker"',
];
break;
case "mongodb":
dbSettings = ['type: "mongodb"', 'database: "test"'];
break;
case "spanner":
dbSettings = [
'type: "spanner"',
'projectId: "test"',
'instanceId: "test"',
'databaseId: "test"',
];
break;
default:
throw new Error(`Unknown database "${database}"`);
}
return `import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entities/User${isEsm ? ".js" : ""}"
export const AppDataSource = new DataSource({
${dbSettings.map((s) => ` ${s},`).join("\n")}
synchronize: true,
logging: false,
entities: [User],
migrations: [],
subscribers: [],
})
`;
}
/**
* Gets contents of the ormconfig file.
*
* @param esmModule
*/
static getTsConfigTemplate(esmModule) {
if (esmModule)
return JSON.stringify({
compilerOptions: {
lib: ["es2023"],
target: "es2022",
module: "es2022",
moduleResolution: "node",
allowSyntheticDefaultImports: true,
outDir: "./build",
emitDecoratorMetadata: true,
experimentalDecorators: true,
sourceMap: true,
},
}, undefined, 3);
else
return JSON.stringify({
compilerOptions: {
lib: ["es2023"],
target: "es2022",
module: "commonjs",
moduleResolution: "node",
outDir: "./build",
emitDecoratorMetadata: true,
experimentalDecorators: true,
sourceMap: true,
},
}, undefined, 3);
}
/**
* Gets contents of the .gitignore file.
*/
static getGitIgnoreFile() {
return `.idea/
.vscode/
node_modules/
build/
tmp/
temp/`;
}
/**
* Gets contents of the user entity.
*
* @param database
*/
static getUserEntityTemplate(database) {
return `${database === "mongodb"
? `import { Entity, ObjectIdColumn, Column } from "typeorm"\nimport { ObjectId } from "mongodb"`
: `import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"`}
@Entity()
export class User {
${database === "mongodb"
? "@ObjectIdColumn()"
: "@PrimaryGeneratedColumn()"}
id: ${database === "mongodb" ? "ObjectId" : "number"}
@Column()
firstName: string
@Column()
lastName: string
@Column()
age: number
}
`;
}
/**
* Gets contents of the route file (used when express is enabled).
*
* @param isEsm
*/
static getRoutesTemplate(isEsm) {
return `import { UserController } from "./controllers/UserController${isEsm ? ".js" : ""}"
export const Routes = [{
method: "get",
route: "/users",
controller: UserController,
action: "all"
}, {
method: "get",
route: "/users/:id",
controller: UserController,
action: "one"
}, {
method: "post",
route: "/users",
controller: UserController,
action: "save"
}, {
method: "delete",
route: "/users/:id",
controller: UserController,
action: "remove"
}]`;
}
/**
* Gets contents of the user controller file (used when express is enabled).
*
* @param isEsm
*/
static getControllerTemplate(isEsm) {
return `import { AppDataSource } from "../data-source${isEsm ? ".js" : ""}"
import { NextFunction, Request, Response } from "express"
import { User } from "../entities/User${isEsm ? ".js" : ""}"
export class UserController {
private userRepository = AppDataSource.getRepository(User)
async all(request: Request, response: Response, next: NextFunction) {
return this.userRepository.find()
}
async one(request: Request, response: Response, next: NextFunction) {
const id = parseInt(request.params.id)
const user = await this.userRepository.findOne({
where: { id }
})
if (!user) {
return "unregistered user"
}
return user
}
async save(request: Request, response: Response, next: NextFunction) {
const { firstName, lastName, age } = request.body;
const user = Object.assign(new User(), {
firstName,
lastName,
age
})
return this.userRepository.save(user)
}
async remove(request: Request, response: Response, next: NextFunction) {
const id = parseInt(request.params.id)
let userToRemove = await this.userRepository.findOneBy({ id })
if (!userToRemove) {
return "this user not exist"
}
await this.userRepository.remove(userToRemove)
return "user has been removed"
}
}`;
}
/**
* Gets contents of the main (index) application file.
*
* @param express
* @param isEsm
*/
static getAppIndexTemplate(express, isEsm) {
if (express) {
return `import ${!isEsm ? "* as " : ""}express from "express"
import ${!isEsm ? "* as " : ""}bodyParser from "body-parser"
import { Request, Response } from "express"
import { AppDataSource } from "./data-source${isEsm ? ".js" : ""}"
import { Routes } from "./routes${isEsm ? ".js" : ""}"
import { User } from "./entities/User${isEsm ? ".js" : ""}"
AppDataSource.initialize().then(async () => {
// create express app
const app = express()
app.use(bodyParser.json())
// register express routes from defined application routes
Routes.forEach(route => {
(app as any)[route.method](route.route, (req: Request, res: Response, next: Function) => {
const result = (new (route.controller as any))[route.action](req, res, next)
if (result instanceof Promise) {
result.then(result => result !== null && result !== undefined ? res.send(result) : undefined)
} else if (result !== null && result !== undefined) {
res.json(result)
}
})
})
// setup express app here
// ...
// start express server
app.listen(3000)
// insert new users for test
await AppDataSource.manager.save(
AppDataSource.manager.create(User, {
firstName: "Timber",
lastName: "Saw",
age: 27
})
)
await AppDataSource.manager.save(
AppDataSource.manager.create(User, {
firstName: "Phantom",
lastName: "Assassin",
age: 24
})
)
console.log("Express server has started on port 3000. Open http://localhost:3000/users to see results")
}).catch(error => console.log(error))
`;
}
else {
return `import { AppDataSource } from "./data-source${isEsm ? ".js" : ""}"
import { User } from "./entities/User${isEsm ? ".js" : ""}"
AppDataSource.initialize().then(async () => {
console.log("Inserting a new user into the database...")
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.age = 25
await AppDataSource.manager.save(user)
console.log("Saved a new user with id: " + user.id)
console.log("Loading users from the database...")
const users = await AppDataSource.manager.find(User)
console.log("Loaded users: ", users)
console.log("Here you can setup and run express / fastify / any other framework.")
}).catch(error => console.log(error))
`;
}
}
/**
* Gets contents of the new package.json file.
*
* @param projectName
* @param projectIsEsm
*/
static getPackageJsonTemplate(projectName, projectIsEsm) {
return JSON.stringify({
name: projectName ?? "typeorm-sample",
version: "0.0.1",
description: "Awesome project developed with TypeORM.",
type: projectIsEsm ? "module" : "commonjs",
devDependencies: {},
dependencies: {},
scripts: {},
}, undefined, 3);
}
/**
* Gets contents of the new docker-compose.yml file.
*
* @param database
*/
static getDockerComposeTemplate(database) {
switch (database) {
case "mysql":
return `services:
mysql:
image: "mysql:9.2.0"
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "password"
MYSQL_DATABASE: "typeorm"
`;
case "mariadb":
return `services:
mariadb:
image: "mariadb:11.7.2"
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "password"
MYSQL_DATABASE: "typeorm"
`;
case "postgres":
return `services:
postgres:
image: "postgres:17.2"
ports:
- "5432:5432"
environment:
POSTGRES_USER: "root"
POSTGRES_PASSWORD: "password"
POSTGRES_DB: "typeorm"
`;
case "cockroachdb":
return `services:
cockroachdb:
image: "cockroachdb/cockroach:v25.1.2"
command: start --insecure
ports:
- "26257:26257"
`;
case "better-sqlite3":
throw new error_1.TypeORMError(`SQLite does not require docker`);
case "oracle":
throw new error_1.TypeORMError(`You cannot initialize a project with docker for Oracle driver yet.`); // todo: implement for oracle as well
case "mssql":
return `services:
mssql:
image: "mcr.microsoft.com/mssql/server:2022-latest"
ports:
- "1433:1433"
environment:
SA_PASSWORD: "Admin12345"
ACCEPT_EULA: "Y"
MSSQL_PID: "Express"
`;
case "mongodb":
return `services:
mongodb:
image: "mongo:8"
container_name: "typeorm-mongodb"
ports:
- "27017:27017"
`;
case "spanner":
return `services:
spanner:
image: gcr.io/cloud-spanner-emulator/emulator:1.5.30
ports:
- "9010:9010"
- "9020:9020"
`;
}
return "";
}
/**
* Gets contents of the new readme.md file.
*
* @param options
* @param options.docker
*/
static getReadmeTemplate(options) {
let template = `# Awesome Project Build with TypeORM
Steps to run this project:
1. Run \`npm i\` command
`;
if (options.docker) {
template += `2. Run \`docker-compose up\` command
`;
}
else {
template += `2. Setup database settings inside \`data-source.ts\` file
`;
}
template += `3. Run \`npm start\` command
`;
return template;
}
/**
* Appends to a given package.json template everything needed.
*
* @param packageJsonContents
* @param database
* @param express
* @param projectIsEsm
*/
static async appendPackageJson(packageJsonContents, database, express, projectIsEsm /*, docker: boolean*/) {
const packageJson = JSON.parse(packageJsonContents);
const ourPackageJson = JSON.parse(await CommandUtils_1.CommandUtils.readFile(node_path_1.default.resolve(__dirname, "..", "package.json")));
packageJson.devDependencies ??= {};
packageJson.devDependencies = {
"@types/node": ourPackageJson.devDependencies["@types/node"],
"ts-node": ourPackageJson.devDependencies["ts-node"],
typescript: ourPackageJson.devDependencies.typescript,
...packageJson.devDependencies,
};
packageJson.dependencies ??= {};
packageJson.dependencies = {
...packageJson.dependencies,
"reflect-metadata": ourPackageJson.dependencies["reflect-metadata"],
typeorm: ourPackageJson.version,
};
switch (database) {
case "mysql":
case "mariadb":
packageJson.dependencies["mysql2"] =
ourPackageJson.devDependencies.mysql2;
break;
case "postgres":
case "cockroachdb":
packageJson.dependencies["pg"] =
ourPackageJson.devDependencies.pg;
break;
case "better-sqlite3":
packageJson.dependencies["better-sqlite3"] =
ourPackageJson.devDependencies["better-sqlite3"];
break;
case "oracle":
packageJson.dependencies["oracledb"] =
ourPackageJson.devDependencies.oracledb;
break;
case "mssql":
packageJson.dependencies["mssql"] =
ourPackageJson.devDependencies.mssql;
break;
case "mongodb":
packageJson.dependencies["mongodb"] =
ourPackageJson.devDependencies.mongodb;
break;
case "spanner":
packageJson.dependencies["@google-cloud/spanner"] =
ourPackageJson.devDependencies["@google-cloud/spanner"];
break;
}
if (express) {
packageJson.dependencies["express"] = "^4.21.2";
packageJson.dependencies["body-parser"] = "^1.20.3";
}
packageJson.scripts ??= {};
if (projectIsEsm)
Object.assign(packageJson.scripts, {
start: /*(docker ? "docker-compose up && " : "") + */ "node --loader ts-node/esm src/index.ts",
typeorm: "typeorm-ts-node-esm",
});
else
Object.assign(packageJson.scripts, {
start: /*(docker ? "docker-compose up && " : "") + */ "ts-node src/index.ts",
typeorm: "typeorm-ts-node-commonjs",
});
return JSON.stringify(packageJson, undefined, 3);
}
}
exports.InitCommand = InitCommand;
//# sourceMappingURL=InitCommand.js.map