archforge-x 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,920 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExpressGenerator = void 0;
4
+ const base_1 = require("../base");
5
+ class ExpressGenerator extends base_1.BaseGenerator {
6
+ getGeneratorName() {
7
+ return "Express.js";
8
+ }
9
+ async generateProjectStructure(root, arch, options) {
10
+ const archStyle = options.architecture || "clean";
11
+ // 1. Common Configuration Files
12
+ this.generateCommonFiles(root, options);
13
+ // 2. Source Code Structure based on Architecture
14
+ if (archStyle === "clean") {
15
+ this.generateCleanArchitecture(root, options);
16
+ }
17
+ else if (archStyle === "mvc") {
18
+ this.generateMVCArchitecture(root, options);
19
+ }
20
+ else {
21
+ this.generateLayeredArchitecture(root, options);
22
+ }
23
+ }
24
+ generateCommonFiles(root, options) {
25
+ const projectName = options.projectName || "express-app";
26
+ // package.json
27
+ this.writeFile(root, "package.json", JSON.stringify({
28
+ name: projectName,
29
+ version: "1.0.0",
30
+ description: "Generated by ArchForge X",
31
+ main: "dist/main.js",
32
+ scripts: {
33
+ "start": "node dist/main.js",
34
+ "start:dev": "ts-node-dev --respawn --transpile-only src/main.ts",
35
+ "build": "tsc",
36
+ "lint": "eslint . --ext .ts",
37
+ "test": "jest"
38
+ },
39
+ dependencies: {
40
+ "express": "^4.18.2",
41
+ "express-async-errors": "^3.1.1",
42
+ "cors": "^2.8.5",
43
+ "dotenv": "^16.3.1",
44
+ "helmet": "^7.0.0",
45
+ "morgan": "^1.10.0",
46
+ "winston": "^3.10.0",
47
+ "uuid": "^9.0.0",
48
+ "zod": "^3.22.2",
49
+ "ioredis": "^5.3.2",
50
+ "http-status-codes": "^2.2.0",
51
+ ...(options.orm === "prisma" ? { "@prisma/client": "^5.0.0" } : {}),
52
+ ...(options.orm === "typeorm" ? { "typeorm": "^0.3.17", "reflect-metadata": "^0.1.13" } : {})
53
+ },
54
+ devDependencies: {
55
+ "@types/express": "^4.17.17",
56
+ "@types/cors": "^2.8.13",
57
+ "@types/node": "^20.4.5",
58
+ "@types/morgan": "^1.10.0",
59
+ "@types/uuid": "^9.0.2",
60
+ "typescript": "^5.1.6",
61
+ "ts-node-dev": "^2.0.0",
62
+ "eslint": "^8.45.0",
63
+ "eslint-plugin-import": "^2.27.5",
64
+ "jest": "^29.6.1",
65
+ "ts-jest": "^29.1.1",
66
+ "@types/jest": "^29.5.3",
67
+ "pino-pretty": "^10.2.0",
68
+ ...(options.orm === "prisma" ? { "prisma": "^5.0.0" } : {})
69
+ }
70
+ }, null, 2));
71
+ // Prisma Schema
72
+ if (options.orm === "prisma") {
73
+ this.generatePrismaSchema(root, options);
74
+ }
75
+ // ... (tsconfig.json and .eslintrc.json remain same)
76
+ // Logger Configuration
77
+ this.writeFile(root, "src/infrastructure/logger.ts", `
78
+ import winston from "winston";
79
+
80
+ const logger = winston.createLogger({
81
+ level: process.env.LOG_LEVEL || "info",
82
+ format: winston.format.combine(
83
+ winston.format.timestamp(),
84
+ winston.format.json()
85
+ ),
86
+ transports: [
87
+ new winston.transports.Console({
88
+ format: winston.format.combine(
89
+ winston.format.colorize(),
90
+ winston.format.simple()
91
+ )
92
+ })
93
+ ]
94
+ });
95
+
96
+ export default logger;
97
+ `);
98
+ // tsconfig.json
99
+ this.writeFile(root, "tsconfig.json", JSON.stringify({
100
+ compilerOptions: {
101
+ target: "ES2020",
102
+ module: "commonjs",
103
+ outDir: "./dist",
104
+ rootDir: "./src",
105
+ strict: true,
106
+ esModuleInterop: true,
107
+ skipLibCheck: true,
108
+ forceConsistentCasingInFileNames: true
109
+ },
110
+ exclude: ["node_modules", "dist", "**/*.test.ts"]
111
+ }, null, 2));
112
+ // .eslintrc.json (Architecture Guardrails)
113
+ const archStyle = options.architecture || "clean";
114
+ let restrictedPaths = [];
115
+ if (archStyle === "clean") {
116
+ restrictedPaths = [
117
+ {
118
+ target: "./src/domain",
119
+ from: "./src/application",
120
+ message: "Domain layer cannot import from Application layer"
121
+ },
122
+ {
123
+ target: "./src/domain",
124
+ from: "./src/infrastructure",
125
+ message: "Domain layer cannot import from Infrastructure layer"
126
+ },
127
+ {
128
+ target: "./src/domain",
129
+ from: "./src/interface-adapters",
130
+ message: "Domain layer cannot import from Interface Adapters layer"
131
+ },
132
+ {
133
+ target: "./src/application",
134
+ from: "./src/infrastructure",
135
+ message: "Application layer cannot import from Infrastructure layer"
136
+ },
137
+ {
138
+ target: "./src/application",
139
+ from: "./src/interface-adapters",
140
+ message: "Application layer cannot import from Interface Adapters layer"
141
+ },
142
+ {
143
+ target: "./src/infrastructure",
144
+ from: "./src/interface-adapters",
145
+ message: "Infrastructure layer cannot import from Interface Adapters layer"
146
+ }
147
+ ];
148
+ }
149
+ else if (archStyle === "mvc") {
150
+ restrictedPaths = [
151
+ {
152
+ target: "./src/models",
153
+ from: "./src/controllers",
154
+ message: "Models cannot import from Controllers"
155
+ },
156
+ {
157
+ target: "./src/models",
158
+ from: "./src/routes",
159
+ message: "Models cannot import from Routes"
160
+ },
161
+ {
162
+ target: "./src/services",
163
+ from: "./src/controllers",
164
+ message: "Services cannot import from Controllers"
165
+ },
166
+ {
167
+ target: "./src/services",
168
+ from: "./src/routes",
169
+ message: "Services cannot import from Routes"
170
+ }
171
+ ];
172
+ }
173
+ else {
174
+ restrictedPaths = [
175
+ {
176
+ target: "./src/core",
177
+ from: "./src/services",
178
+ message: "Core cannot import from Services"
179
+ },
180
+ {
181
+ target: "./src/core",
182
+ from: "./src/api",
183
+ message: "Core cannot import from API"
184
+ },
185
+ {
186
+ target: "./src/services",
187
+ from: "./src/api",
188
+ message: "Services cannot import from API"
189
+ }
190
+ ];
191
+ }
192
+ this.writeFile(root, ".eslintrc.json", JSON.stringify({
193
+ "env": {
194
+ "es2021": true,
195
+ "node": true
196
+ },
197
+ "extends": [
198
+ "eslint:recommended",
199
+ "plugin:@typescript-eslint/recommended"
200
+ ],
201
+ "parser": "@typescript-eslint/parser",
202
+ "parserOptions": {
203
+ "ecmaVersion": "latest",
204
+ "sourceType": "module"
205
+ },
206
+ "plugins": [
207
+ "@typescript-eslint",
208
+ "import"
209
+ ],
210
+ "rules": {
211
+ "import/no-restricted-paths": ["error", {
212
+ "zones": restrictedPaths
213
+ }]
214
+ }
215
+ }, null, 2));
216
+ // .env
217
+ this.writeFile(root, ".env", `PORT=3000\nNODE_ENV=development\nDATABASE_URL="postgresql://user:password@localhost:5432/appdb?schema=public"\nREDIS_URL="redis://localhost:6379"\n`);
218
+ this.writeFile(root, ".env.example", `PORT=3000\nNODE_ENV=development\nDATABASE_URL="postgresql://user:password@localhost:5432/appdb?schema=public"\nREDIS_URL="redis://localhost:6379"\n`);
219
+ this.writeFile(root, ".gitignore", `node_modules/\ndist/\n.env\ncoverage/\n`);
220
+ this.generateConfig(root, options);
221
+ this.generateCacheLayer(root, options);
222
+ this.generateErrorHandling(root, options);
223
+ this.generateHealthChecks(root, options);
224
+ }
225
+ generateConfig(root, options) {
226
+ this.writeFile(root, "src/config/env.schema.ts", `
227
+ import { z } from "zod";
228
+
229
+ export const envSchema = z.object({
230
+ PORT: z.string().default("3000"),
231
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
232
+ DATABASE_URL: z.string(),
233
+ REDIS_URL: z.string().default("redis://localhost:6379"),
234
+ LOG_LEVEL: z.string().default("info"),
235
+ });
236
+
237
+ export type Env = z.infer<typeof envSchema>;
238
+ `);
239
+ this.writeFile(root, "src/config/index.ts", `
240
+ import dotenv from "dotenv";
241
+ import { envSchema } from "./env.schema";
242
+
243
+ dotenv.config();
244
+
245
+ const parsed = envSchema.safeParse(process.env);
246
+
247
+ if (!parsed.success) {
248
+ console.error("❌ Invalid environment variables:", parsed.error.format());
249
+ process.exit(1);
250
+ }
251
+
252
+ export const config = {
253
+ app: {
254
+ port: parseInt(parsed.data.PORT, 10),
255
+ env: parsed.data.NODE_ENV,
256
+ logLevel: parsed.data.LOG_LEVEL,
257
+ },
258
+ database: {
259
+ url: parsed.data.DATABASE_URL,
260
+ },
261
+ cache: {
262
+ url: parsed.data.REDIS_URL,
263
+ },
264
+ };
265
+ `);
266
+ }
267
+ generateCacheLayer(root, options) {
268
+ this.writeFile(root, "src/infrastructure/cache/cache.interface.ts", `
269
+ export interface ICache {
270
+ get<T>(key: string): Promise<T | null>;
271
+ set(key: string, value: any, ttlSeconds?: number): Promise<void>;
272
+ delete(key: string): Promise<void>;
273
+ }
274
+ `);
275
+ this.writeFile(root, "src/infrastructure/cache/redis.cache.ts", `
276
+ import Redis from "ioredis";
277
+ import { ICache } from "./cache.interface";
278
+ import { config } from "../../config";
279
+ import logger from "../logger";
280
+
281
+ export class RedisCache implements ICache {
282
+ private redis: Redis;
283
+
284
+ constructor() {
285
+ this.redis = new Redis(config.cache.url);
286
+ this.redis.on("error", (err) => logger.error("Redis Error", err));
287
+ }
288
+
289
+ async get<T>(key: string): Promise<T | null> {
290
+ const data = await this.redis.get(key);
291
+ return data ? JSON.parse(data) : null;
292
+ }
293
+
294
+ async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
295
+ const data = JSON.stringify(value);
296
+ if (ttlSeconds) {
297
+ await this.redis.setex(key, ttlSeconds, data);
298
+ } else {
299
+ await this.redis.set(key, data);
300
+ }
301
+ }
302
+
303
+ async delete(key: string): Promise<void> {
304
+ await this.redis.del(key);
305
+ }
306
+ }
307
+ `);
308
+ this.writeFile(root, "src/infrastructure/cache/memory.cache.ts", `
309
+ import { ICache } from "./cache.interface";
310
+
311
+ export class MemoryCache implements ICache {
312
+ private cache = new Map<string, { value: any; expiresAt?: number }>();
313
+
314
+ async get<T>(key: string): Promise<T | null> {
315
+ const item = this.cache.get(key);
316
+ if (!item) return null;
317
+ if (item.expiresAt && item.expiresAt < Date.now()) {
318
+ this.cache.delete(key);
319
+ return null;
320
+ }
321
+ return item.value;
322
+ }
323
+
324
+ async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
325
+ const expiresAt = ttlSeconds ? Date.now() + ttlSeconds * 1000 : undefined;
326
+ this.cache.set(key, { value, expiresAt });
327
+ }
328
+
329
+ async delete(key: string): Promise<void> {
330
+ this.cache.delete(key);
331
+ }
332
+ }
333
+ `);
334
+ }
335
+ generateErrorHandling(root, options) {
336
+ this.writeFile(root, "src/infrastructure/errors/app.error.ts", `
337
+ import { StatusCodes } from "http-status-codes";
338
+
339
+ export class AppError extends Error {
340
+ constructor(
341
+ public message: string,
342
+ public statusCode: number = StatusCodes.INTERNAL_SERVER_ERROR,
343
+ public isOperational: boolean = true
344
+ ) {
345
+ super(message);
346
+ Object.setPrototypeOf(this, AppError.prototype);
347
+ }
348
+ }
349
+
350
+ export class NotFoundError extends AppError {
351
+ constructor(message: string = "Resource not found") {
352
+ super(message, StatusCodes.NOT_FOUND);
353
+ }
354
+ }
355
+
356
+ export class BadRequestError extends AppError {
357
+ constructor(message: string = "Bad request") {
358
+ super(message, StatusCodes.BAD_REQUEST);
359
+ }
360
+ }
361
+ `);
362
+ this.writeFile(root, "src/infrastructure/middleware/error.middleware.ts", `
363
+ import { Request, Response, NextFunction } from "express";
364
+ import { AppError } from "../errors/app.error";
365
+ import logger from "../logger";
366
+ import { StatusCodes } from "http-status-codes";
367
+
368
+ export const errorMiddleware = (
369
+ err: Error,
370
+ req: Request,
371
+ res: Response,
372
+ next: NextFunction
373
+ ) => {
374
+ if (err instanceof AppError) {
375
+ return res.status(err.statusCode).json({
376
+ status: "error",
377
+ message: err.message,
378
+ });
379
+ }
380
+
381
+ logger.error("Unhandled Error", err);
382
+
383
+ return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
384
+ status: "error",
385
+ message: "Something went wrong",
386
+ });
387
+ };
388
+ `);
389
+ }
390
+ generateHealthChecks(root, options) {
391
+ this.writeFile(root, "src/interface-adapters/controllers/health.controller.ts", `
392
+ import { Request, Response } from "express";
393
+ import { StatusCodes } from "http-status-codes";
394
+ import prisma from "../../infrastructure/database/prisma";
395
+
396
+ export class HealthController {
397
+ async health(req: Request, res: Response) {
398
+ res.status(StatusCodes.OK).json({ status: "ok", timestamp: new Date().toISOString() });
399
+ }
400
+
401
+ async ready(req: Request, res: Response) {
402
+ const checks: any = {
403
+ uptime: process.uptime(),
404
+ timestamp: new Date().toISOString(),
405
+ };
406
+
407
+ try {
408
+ ${options.orm === 'prisma' ? 'await prisma.$queryRaw`SELECT 1`' : ''}
409
+ checks.database = "connected";
410
+ } catch (e) {
411
+ checks.database = "disconnected";
412
+ return res.status(StatusCodes.SERVICE_UNAVAILABLE).json({ status: "error", checks });
413
+ }
414
+
415
+ res.status(StatusCodes.OK).json({ status: "ok", checks });
416
+ }
417
+ }
418
+ `);
419
+ }
420
+ generateCleanArchitecture(root, options) {
421
+ // Domain Layer
422
+ this.writeFile(root, "src/domain/entities/user.entity.ts", `
423
+ export class User {
424
+ constructor(
425
+ public readonly id: string,
426
+ public readonly name: string,
427
+ public readonly email: string
428
+ ) {}
429
+ }
430
+ `);
431
+ this.writeFile(root, "src/domain/repositories/user.repository.ts", `
432
+ import { User } from "../entities/user.entity";
433
+
434
+ export interface UserRepository {
435
+ save(user: User): Promise<User>;
436
+ findByEmail(email: string): Promise<User | null>;
437
+ findById(id: string): Promise<User | null>;
438
+ }
439
+ `);
440
+ // Application Layer
441
+ this.writeFile(root, "src/application/use-cases/create-user.use-case.ts", `
442
+ import { User } from "../../domain/entities/user.entity";
443
+ import { UserRepository } from "../../domain/repositories/user.repository";
444
+ import { BadRequestError } from "../../infrastructure/errors/app.error";
445
+
446
+ export class CreateUserUseCase {
447
+ constructor(private userRepository: UserRepository) {}
448
+
449
+ async execute(name: string, email: string): Promise<User> {
450
+ const existing = await this.userRepository.findByEmail(email);
451
+ if (existing) throw new BadRequestError("User already exists");
452
+
453
+ const user = new User(Date.now().toString(), name, email);
454
+ return this.userRepository.save(user);
455
+ }
456
+ }
457
+ `);
458
+ this.writeFile(root, "src/application/use-cases/get-user.use-case.ts", `
459
+ import { User } from "../../domain/entities/user.entity";
460
+ import { UserRepository } from "../../domain/repositories/user.repository";
461
+ import { NotFoundError } from "../../infrastructure/errors/app.error";
462
+ import { ICache } from "../../infrastructure/cache/cache.interface";
463
+
464
+ export class GetUserUseCase {
465
+ constructor(
466
+ private userRepository: UserRepository,
467
+ private cache: ICache
468
+ ) {}
469
+
470
+ async execute(id: string): Promise<User> {
471
+ const cacheKey = \`user:\${id}\`;
472
+ const cached = await this.cache.get<User>(cacheKey);
473
+ if (cached) return cached;
474
+
475
+ const user = await this.userRepository.findById(id);
476
+ if (!user) throw new NotFoundError("User not found");
477
+
478
+ await this.cache.set(cacheKey, user, 3600);
479
+ return user;
480
+ }
481
+ }
482
+ `);
483
+ // Infrastructure Layer
484
+ this.writeFile(root, "src/infrastructure/persistence/in-memory-user.repository.ts", `
485
+ import { User } from "../../domain/entities/user.entity";
486
+ import { UserRepository } from "../../domain/repositories/user.repository";
487
+
488
+ export class InMemoryUserRepository implements UserRepository {
489
+ private users: User[] = [];
490
+
491
+ async save(user: User): Promise<User> {
492
+ this.users.push(user);
493
+ return user;
494
+ }
495
+
496
+ async findByEmail(email: string): Promise<User | null> {
497
+ return this.users.find(u => u.email === email) || null;
498
+ }
499
+
500
+ async findById(id: string): Promise<User | null> {
501
+ return this.users.find(u => u.id === id) || null;
502
+ }
503
+ }
504
+ `);
505
+ // Interface Adapters (Controllers)
506
+ this.writeFile(root, "src/interface-adapters/controllers/user.controller.ts", `
507
+ import { Request, Response } from "express";
508
+ import { StatusCodes } from "http-status-codes";
509
+ import { CreateUserUseCase } from "../../application/use-cases/create-user.use-case";
510
+ import { GetUserUseCase } from "../../application/use-cases/get-user.use-case";
511
+
512
+ export class UserController {
513
+ constructor(
514
+ private createUserUseCase: CreateUserUseCase,
515
+ private getUserUseCase: GetUserUseCase
516
+ ) {}
517
+
518
+ async create(req: Request, res: Response) {
519
+ const { name, email } = req.body;
520
+ const user = await this.createUserUseCase.execute(name, email);
521
+ res.status(StatusCodes.CREATED).json(user);
522
+ }
523
+
524
+ async get(req: Request, res: Response) {
525
+ const { id } = req.params;
526
+ const user = await this.getUserUseCase.execute(id);
527
+ res.status(StatusCodes.OK).json(user);
528
+ }
529
+ }
530
+ `);
531
+ // Main Entry Point
532
+ this.writeFile(root, "src/main.ts", `
533
+ import "express-async-errors";
534
+ import express from "express";
535
+ import cors from "cors";
536
+ import helmet from "helmet";
537
+ import morgan from "morgan";
538
+ import { v4 as uuidv4 } from "uuid";
539
+ import { config } from "./config";
540
+ import logger from "./infrastructure/logger";
541
+ import { errorMiddleware } from "./infrastructure/middleware/error.middleware";
542
+ import { InMemoryUserRepository } from "./infrastructure/persistence/in-memory-user.repository";
543
+ import { MemoryCache } from "./infrastructure/cache/memory.cache";
544
+ import { CreateUserUseCase } from "./application/use-cases/create-user.use-case";
545
+ import { GetUserUseCase } from "./application/use-cases/get-user.use-case";
546
+ import { UserController } from "./interface-adapters/controllers/user.controller";
547
+ import { HealthController } from "./interface-adapters/controllers/health.controller";
548
+
549
+ const app = express();
550
+
551
+ // Security & Middleware
552
+ app.use(helmet());
553
+ app.use(express.json());
554
+ app.use(cors());
555
+ app.use(morgan("combined"));
556
+
557
+ // Correlation ID & Logging
558
+ app.use((req, res, next) => {
559
+ const correlationId = (req.headers["x-correlation-id"] as string) || uuidv4();
560
+ req.headers["x-correlation-id"] = correlationId;
561
+ res.setHeader("X-Correlation-ID", correlationId);
562
+ next();
563
+ });
564
+
565
+ // Dependency Injection
566
+ const userRepository = new InMemoryUserRepository();
567
+ const cache = new MemoryCache(); // Use RedisCache in production
568
+ const createUserUseCase = new CreateUserUseCase(userRepository);
569
+ const getUserUseCase = new GetUserUseCase(userRepository, cache);
570
+ const userController = new UserController(createUserUseCase, getUserUseCase);
571
+ const healthController = new HealthController();
572
+
573
+ // Routes
574
+ app.get("/health", (req, res) => healthController.health(req, res));
575
+ app.get("/ready", (req, res) => healthController.ready(req, res));
576
+
577
+ app.post("/users", (req, res) => userController.create(req, res));
578
+ app.get("/users/:id", (req, res) => userController.get(req, res));
579
+
580
+ // Error Handling
581
+ app.use(errorMiddleware);
582
+
583
+ const PORT = config.app.port;
584
+ app.listen(PORT, () => {
585
+ logger.info(\`🚀 Server running in \${config.app.env} mode on port \${PORT}\`);
586
+ });
587
+ `);
588
+ }
589
+ generateMVCArchitecture(root, options) {
590
+ // Model
591
+ this.writeFile(root, "src/models/user.model.ts", `
592
+ export interface User {
593
+ id: string;
594
+ name: string;
595
+ email: string;
596
+ }
597
+ `);
598
+ // Service (Business Logic)
599
+ this.writeFile(root, "src/services/user.service.ts", `
600
+ import { User } from "../models/user.model";
601
+ import { ICache } from "../infrastructure/cache/cache.interface";
602
+ import { BadRequestError, NotFoundError } from "../infrastructure/errors/app.error";
603
+
604
+ const users: User[] = [];
605
+
606
+ export class UserService {
607
+ constructor(private cache: ICache) {}
608
+
609
+ async create(name: string, email: string): Promise<User> {
610
+ if (users.find(u => u.email === email)) {
611
+ throw new BadRequestError("User already exists");
612
+ }
613
+ const user = { id: Date.now().toString(), name, email };
614
+ users.push(user);
615
+ return user;
616
+ }
617
+
618
+ async getById(id: string): Promise<User> {
619
+ const cacheKey = \`user:\${id}\`;
620
+ const cached = await this.cache.get<User>(cacheKey);
621
+ if (cached) return cached;
622
+
623
+ const user = users.find(u => u.id === id);
624
+ if (!user) throw new NotFoundError("User not found");
625
+
626
+ await this.cache.set(cacheKey, user, 3600);
627
+ return user;
628
+ }
629
+ }
630
+ `);
631
+ // Controller
632
+ this.writeFile(root, "src/controllers/user.controller.ts", `
633
+ import { Request, Response } from "express";
634
+ import { StatusCodes } from "http-status-codes";
635
+ import { UserService } from "../services/user.service";
636
+
637
+ export class UserController {
638
+ constructor(private userService: UserService) {}
639
+
640
+ async create(req: Request, res: Response) {
641
+ const { name, email } = req.body;
642
+ const user = await this.userService.create(name, email);
643
+ res.status(StatusCodes.CREATED).json(user);
644
+ }
645
+
646
+ async get(req: Request, res: Response) {
647
+ const { id } = req.params;
648
+ const user = await this.userService.getById(id);
649
+ res.status(StatusCodes.OK).json(user);
650
+ }
651
+ }
652
+ `);
653
+ // Routes
654
+ this.writeFile(root, "src/routes/user.routes.ts", `
655
+ import { Router } from "express";
656
+ import { UserController } from "../controllers/user.controller";
657
+ import { UserService } from "../services/user.service";
658
+ import { MemoryCache } from "../infrastructure/cache/memory.cache";
659
+
660
+ const router = Router();
661
+ const cache = new MemoryCache();
662
+ const userService = new UserService(cache);
663
+ const userController = new UserController(userService);
664
+
665
+ router.post("/", (req, res) => userController.create(req, res));
666
+ router.get("/:id", (req, res) => userController.get(req, res));
667
+
668
+ export default router;
669
+ `);
670
+ // Main
671
+ this.writeFile(root, "src/main.ts", `
672
+ import "express-async-errors";
673
+ import express from "express";
674
+ import cors from "cors";
675
+ import helmet from "helmet";
676
+ import morgan from "morgan";
677
+ import { config } from "./config";
678
+ import logger from "./infrastructure/logger";
679
+ import { errorMiddleware } from "./infrastructure/middleware/error.middleware";
680
+ import userRoutes from "./routes/user.routes";
681
+ import { HealthController } from "./interface-adapters/controllers/health.controller";
682
+
683
+ const app = express();
684
+
685
+ app.use(helmet());
686
+ app.use(express.json());
687
+ app.use(cors());
688
+ app.use(morgan("combined"));
689
+
690
+ const healthController = new HealthController();
691
+ app.get("/health", (req, res) => healthController.health(req, res));
692
+ app.get("/ready", (req, res) => healthController.ready(req, res));
693
+
694
+ app.use("/users", userRoutes);
695
+
696
+ app.use(errorMiddleware);
697
+
698
+ const PORT = config.app.port;
699
+ app.listen(PORT, () => {
700
+ logger.info(\`🚀 Server running in \${config.app.env} mode on port \${PORT}\`);
701
+ });
702
+ `);
703
+ }
704
+ generateLayeredArchitecture(root, options) {
705
+ // Core Layer
706
+ this.writeFile(root, "src/core/types/user.ts", `
707
+ export interface User {
708
+ id: string;
709
+ name: string;
710
+ email: string;
711
+ }
712
+ `);
713
+ // Services Layer
714
+ this.writeFile(root, "src/services/user.service.ts", `
715
+ import { User } from "../core/types/user";
716
+ import { ICache } from "../infrastructure/cache/cache.interface";
717
+ import { BadRequestError, NotFoundError } from "../infrastructure/errors/app.error";
718
+
719
+ const users: User[] = [];
720
+
721
+ export class UserService {
722
+ constructor(private cache: ICache) {}
723
+
724
+ async create(name: string, email: string): Promise<User> {
725
+ const user = { id: Date.now().toString(), name, email };
726
+ users.push(user);
727
+ return user;
728
+ }
729
+
730
+ async getById(id: string): Promise<User> {
731
+ const cacheKey = \`user:\${id}\`;
732
+ const cached = await this.cache.get<User>(cacheKey);
733
+ if (cached) return cached;
734
+
735
+ const user = users.find(u => u.id === id);
736
+ if (!user) throw new NotFoundError("User not found");
737
+
738
+ await this.cache.set(cacheKey, user, 3600);
739
+ return user;
740
+ }
741
+ }
742
+ `);
743
+ // API Layer
744
+ this.writeFile(root, "src/api/routes/user.routes.ts", `
745
+ import { Router } from "express";
746
+ import { UserService } from "../../services/user.service";
747
+ import { MemoryCache } from "../../infrastructure/cache/memory.cache";
748
+ import { StatusCodes } from "http-status-codes";
749
+
750
+ const router = Router();
751
+ const cache = new MemoryCache();
752
+ const userService = new UserService(cache);
753
+
754
+ router.post("/", async (req, res) => {
755
+ const user = await userService.create(req.body.name, req.body.email);
756
+ res.status(StatusCodes.CREATED).json(user);
757
+ });
758
+
759
+ router.get("/:id", async (req, res) => {
760
+ const user = await userService.getById(req.params.id);
761
+ res.status(StatusCodes.OK).json(user);
762
+ });
763
+
764
+ export default router;
765
+ `);
766
+ // Main
767
+ this.writeFile(root, "src/main.ts", `
768
+ import "express-async-errors";
769
+ import express from "express";
770
+ import helmet from "helmet";
771
+ import cors from "cors";
772
+ import { config } from "./config";
773
+ import logger from "./infrastructure/logger";
774
+ import { errorMiddleware } from "./infrastructure/middleware/error.middleware";
775
+ import userRoutes from "./api/routes/user.routes";
776
+ import { HealthController } from "./interface-adapters/controllers/health.controller";
777
+
778
+ const app = express();
779
+ app.use(helmet());
780
+ app.use(express.json());
781
+ app.use(cors());
782
+
783
+ const healthController = new HealthController();
784
+ app.get("/health", (req, res) => healthController.health(req, res));
785
+ app.get("/ready", (req, res) => healthController.ready(req, res));
786
+
787
+ app.use("/api/users", userRoutes);
788
+
789
+ app.use(errorMiddleware);
790
+
791
+ const PORT = config.app.port;
792
+ app.listen(PORT, () => {
793
+ logger.info(\`🚀 Server running in \${config.app.env} mode on port \${PORT}\`);
794
+ });
795
+ `);
796
+ }
797
+ generateDocker(root, options) {
798
+ const dbService = options.database === "postgresql" ? `
799
+ db:
800
+ image: postgres:15-alpine
801
+ environment:
802
+ - POSTGRES_USER=user
803
+ - POSTGRES_PASSWORD=password
804
+ - POSTGRES_DB=appdb
805
+ ports:
806
+ - "5432:5432"
807
+ ` : options.database === "mysql" ? `
808
+ db:
809
+ image: mysql:8
810
+ environment:
811
+ - MYSQL_ROOT_PASSWORD=password
812
+ - MYSQL_DATABASE=appdb
813
+ ports:
814
+ - "3306:3306"
815
+ ` : "";
816
+ const dbUrl = options.database === "postgresql" ? "postgresql://user:password@db:5432/appdb?schema=public" :
817
+ options.database === "mysql" ? "mysql://root:password@db:3306/appdb" : "";
818
+ this.writeFile(root, "Dockerfile", `
819
+ FROM node:18-alpine AS builder
820
+ WORKDIR /app
821
+ COPY package*.json ./
822
+ RUN npm ci
823
+ COPY . .
824
+ ${options.orm === 'prisma' ? 'RUN npx prisma generate' : ''}
825
+ RUN npm run build
826
+
827
+ FROM node:18-alpine
828
+ WORKDIR /app
829
+ COPY --from=builder /app/dist ./dist
830
+ COPY --from=builder /app/package*.json ./
831
+ ${options.orm === 'prisma' ? 'COPY --from=builder /app/prisma ./prisma' : ''}
832
+ RUN npm ci --production
833
+ ${options.orm === 'prisma' ? 'RUN npx prisma generate' : ''}
834
+ EXPOSE 3000
835
+ CMD ["npm", "start"]
836
+ `);
837
+ this.writeFile(root, "docker-compose.yml", `
838
+ version: '3.8'
839
+ services:
840
+ app:
841
+ build: .
842
+ ports:
843
+ - "3000:3000"
844
+ environment:
845
+ - PORT=3000
846
+ - NODE_ENV=production
847
+ ${dbUrl ? `- DATABASE_URL=\${DATABASE_URL:-${dbUrl}}` : ""}
848
+ - REDIS_URL=redis://cache:6379
849
+ depends_on:
850
+ ${dbService ? "- db" : ""}
851
+ - cache
852
+ restart: always
853
+ ${dbService}
854
+ cache:
855
+ image: redis:7-alpine
856
+ ports:
857
+ - "6379:6379"
858
+ `);
859
+ }
860
+ generateCI(root, options) {
861
+ this.writeFile(root, ".github/workflows/ci.yml", `
862
+ name: CI
863
+
864
+ on:
865
+ push:
866
+ branches: [ main ]
867
+ pull_request:
868
+ branches: [ main ]
869
+
870
+ jobs:
871
+ build:
872
+ runs-on: ubuntu-latest
873
+
874
+ steps:
875
+ - uses: actions/checkout@v3
876
+ - name: Use Node.js 18.x
877
+ uses: actions/setup-node@v3
878
+ with:
879
+ node-version: 18.x
880
+ cache: 'npm'
881
+ - run: npm ci
882
+ ${options.orm === 'prisma' ? '- run: npx prisma generate' : ''}
883
+ - name: Architecture Sync & Validation
884
+ run: npx archforge sync
885
+ - run: npm run lint
886
+ - run: npm test
887
+ - run: npm run build
888
+ `);
889
+ }
890
+ generatePrismaSchema(root, options) {
891
+ const provider = options.database === "mongodb" ? "mongodb" :
892
+ options.database === "mysql" ? "mysql" :
893
+ options.database === "sqlite" ? "sqlite" : "postgresql";
894
+ const url = options.database === "sqlite" ? "file:./dev.db" : "env(\"DATABASE_URL\")";
895
+ this.writeFile(root, "prisma/schema.prisma", `
896
+ generator client {
897
+ provider = "prisma-client-js"
898
+ }
899
+
900
+ datasource db {
901
+ provider = "${provider}"
902
+ url = ${url}
903
+ }
904
+
905
+ model User {
906
+ id String @id @default(uuid())
907
+ email String @unique
908
+ name String?
909
+ }
910
+ `);
911
+ this.writeFile(root, "src/infrastructure/database/prisma.ts", `
912
+ import { PrismaClient } from "@prisma/client";
913
+
914
+ const prisma = new PrismaClient();
915
+
916
+ export default prisma;
917
+ `);
918
+ }
919
+ }
920
+ exports.ExpressGenerator = ExpressGenerator;