katax-cli 1.4.2 → 1.4.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.
Files changed (36) hide show
  1. package/README.md +28 -1
  2. package/dist/commands/add-endpoint.d.ts +29 -0
  3. package/dist/commands/add-endpoint.d.ts.map +1 -1
  4. package/dist/commands/add-endpoint.js +154 -51
  5. package/dist/commands/add-endpoint.js.map +1 -1
  6. package/dist/commands/generate-crud.d.ts +29 -0
  7. package/dist/commands/generate-crud.d.ts.map +1 -1
  8. package/dist/commands/generate-crud.js +157 -50
  9. package/dist/commands/generate-crud.js.map +1 -1
  10. package/dist/commands/generate-repository.d.ts +42 -0
  11. package/dist/commands/generate-repository.d.ts.map +1 -0
  12. package/dist/commands/generate-repository.js +451 -0
  13. package/dist/commands/generate-repository.js.map +1 -0
  14. package/dist/commands/init.d.ts +17 -0
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +456 -114
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/generators/validator-generator.d.ts.map +1 -1
  19. package/dist/generators/validator-generator.improved.d.ts +1 -1
  20. package/dist/generators/validator-generator.improved.js +55 -55
  21. package/dist/generators/validator-generator.improved.js.map +1 -1
  22. package/dist/generators/validator-generator.js +8 -31
  23. package/dist/generators/validator-generator.js.map +1 -1
  24. package/dist/index.js +11 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/services/project-structure-generator.js +1 -1
  27. package/dist/services/project-structure-generator.js.map +1 -1
  28. package/dist/templates/generators/auth-utils-template.d.ts.map +1 -1
  29. package/dist/templates/generators/auth-utils-template.js +3 -10
  30. package/dist/templates/generators/auth-utils-template.js.map +1 -1
  31. package/dist/templates/generators/stream-utils-template.d.ts.map +1 -1
  32. package/dist/templates/generators/stream-utils-template.js +4 -5
  33. package/dist/templates/generators/stream-utils-template.js.map +1 -1
  34. package/dist/types/index.d.ts +6 -1
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/package.json +4 -2
@@ -8,6 +8,46 @@ import { directoryExists, ensureDir, writeFile, } from "../utils/file-utils.js";
8
8
  import { generateSwaggerSetup } from "../templates/generators/swagger-template.js";
9
9
  import { generateStreamUtils } from "../templates/generators/stream-utils-template.js";
10
10
  import { generateAuthUtils } from "../templates/generators/auth-utils-template.js";
11
+ const KATAX_SERVICE_MANAGER_PEERS = [
12
+ "dotenv",
13
+ "mongodb",
14
+ "mysql2",
15
+ "node-cron",
16
+ "pg",
17
+ "pino-pretty",
18
+ "redis",
19
+ "socket.io",
20
+ ];
21
+ function getPackageManager(pm) {
22
+ return pm === "npm" ? "npm" : "pnpm";
23
+ }
24
+ function getInstallCommand(packageManager, packages = [], ignoreScripts = false) {
25
+ const args = [];
26
+ if (packageManager === "pnpm") {
27
+ if (packages.length > 0) {
28
+ args.push("add", ...packages);
29
+ }
30
+ else {
31
+ args.push("install");
32
+ }
33
+ }
34
+ else {
35
+ args.push("install", ...packages);
36
+ }
37
+ if (ignoreScripts) {
38
+ args.push("--ignore-scripts");
39
+ }
40
+ return { cmd: packageManager, args };
41
+ }
42
+ async function writeIgnoreScriptsNpmrc(projectPath) {
43
+ const npmrcContent = [
44
+ "# Generated by katax-cli",
45
+ "# Prevent lifecycle scripts during install for safer bootstrapping",
46
+ "ignore-scripts=true",
47
+ "",
48
+ ].join("\n");
49
+ await writeFile(path.join(projectPath, ".npmrc"), npmrcContent);
50
+ }
11
51
  export async function initCommand(projectName, options = {}) {
12
52
  title("🚀 Katax CLI - Initialize API Project");
13
53
  let finalProjectName = projectName || "";
@@ -146,6 +186,21 @@ export async function initCommand(projectName, options = {}) {
146
186
  default: false,
147
187
  when: (answers) => answers.useKataxServiceManager,
148
188
  },
189
+ {
190
+ type: "list",
191
+ name: "peerDependenciesMode",
192
+ message: "Install katax-service-manager peer dependencies now?",
193
+ choices: [
194
+ { name: "No (install later as needed)", value: "none" },
195
+ {
196
+ name: "Yes, install peers for selected integrations",
197
+ value: "selected",
198
+ },
199
+ { name: "Yes, install all optional peers", value: "all" },
200
+ ],
201
+ default: "selected",
202
+ when: (answers) => answers.useKataxServiceManager,
203
+ },
149
204
  {
150
205
  type: "confirm",
151
206
  name: "useSeparateSocketPort",
@@ -186,6 +241,29 @@ export async function initCommand(projectName, options = {}) {
186
241
  message: "Initialize git repository?",
187
242
  default: true,
188
243
  },
244
+ {
245
+ type: "list",
246
+ name: "packageManager",
247
+ message: "Package manager for install commands:",
248
+ choices: [
249
+ { name: "npm", value: "npm" },
250
+ { name: "pnpm", value: "pnpm" },
251
+ ],
252
+ default: getPackageManager(options.pm),
253
+ },
254
+ {
255
+ type: "confirm",
256
+ name: "ignoreScripts",
257
+ message: "Install dependencies with --ignore-scripts?",
258
+ default: options.ignoreScripts ?? false,
259
+ },
260
+ {
261
+ type: "confirm",
262
+ name: "writeNpmrc",
263
+ message: "Create .npmrc with ignore-scripts=true?",
264
+ default: options.writeNpmrc ?? true,
265
+ when: (answers) => answers.ignoreScripts,
266
+ },
189
267
  ]);
190
268
  let dbConfig = {};
191
269
  if (answers.database !== "none") {
@@ -310,7 +388,15 @@ export async function initCommand(projectName, options = {}) {
310
388
  useRegistry: answers.useRegistry || false,
311
389
  registryMode: answers.useRegistry ? answers.registryMode || "url" : "none",
312
390
  registryUrl: answers.registryUrl,
391
+ peerDependenciesMode: answers.useKataxServiceManager
392
+ ? answers.peerDependenciesMode || "selected"
393
+ : "none",
313
394
  useLifecycleHooks: answers.useLifecycleHooks || false,
395
+ packageManager: getPackageManager(answers.packageManager),
396
+ ignoreInstallScripts: answers.ignoreScripts || false,
397
+ createNpmrcForIgnoreScripts: answers.ignoreScripts
398
+ ? (answers.writeNpmrc ?? true)
399
+ : false,
314
400
  initGit: answers.initGit,
315
401
  redisConfig,
316
402
  dbConfig,
@@ -337,19 +423,30 @@ export async function initCommand(projectName, options = {}) {
337
423
  if (config.useKataxServiceManager) {
338
424
  gray(` Katax Mode: ${config.kataxMode === "instance" ? "Instance (new Katax())" : "Singleton"}`);
339
425
  gray(` Registry: ${config.useRegistry ? (config.registryMode === "url" ? `URL (${config.registryUrl})` : "Callback handler") : "No"}`);
426
+ gray(` Peer Deps: ${config.peerDependenciesMode || "none"}`);
340
427
  gray(` Lifecycle Hooks: ${config.useLifecycleHooks ? "Yes" : "No"}`);
341
428
  gray(` Redis Cache: ${config.useRedis ? "Yes" : "No"}`);
342
429
  gray(` WebSocket: ${config.useWebSocket ? (config.useSeparateSocketPort ? `Yes (port ${config.socketPort})` : "Yes (shared port)") : "No"}`);
343
430
  }
344
431
  gray(` Git: ${config.initGit ? "Yes" : "No"}`);
432
+ gray(` Package Manager: ${config.packageManager}`);
433
+ gray(` Ignore Install Scripts: ${config.ignoreInstallScripts ? "Yes" : "No"}`);
434
+ gray(` Write .npmrc: ${config.createNpmrcForIgnoreScripts ? "Yes" : "No"}`);
345
435
  gray(` Port: ${config.port}\n`);
346
436
  const spinner = ora("Creating project structure...").start();
347
437
  try {
348
438
  await createProjectStructure(projectPath, config, generateJwtSecrets);
349
439
  spinner.succeed("Project structure created");
440
+ if (config.createNpmrcForIgnoreScripts) {
441
+ spinner.start("Writing .npmrc...");
442
+ await writeIgnoreScriptsNpmrc(projectPath);
443
+ spinner.succeed(".npmrc created (ignore-scripts=true)");
444
+ }
350
445
  spinner.start("Installing dependencies...");
351
- await installDependencies(projectPath);
352
- spinner.succeed("Dependencies installed");
446
+ const installedPeerDeps = await installDependencies(projectPath, config);
447
+ spinner.succeed(installedPeerDeps.length > 0
448
+ ? `Dependencies installed (+ ${installedPeerDeps.length} peer deps)`
449
+ : "Dependencies installed");
353
450
  if (config.initGit) {
354
451
  spinner.start("Initializing git repository...");
355
452
  await initGitRepository(projectPath);
@@ -358,11 +455,16 @@ export async function initCommand(projectName, options = {}) {
358
455
  success(`\n✨ Project "${finalProjectName}" created successfully!\n`);
359
456
  info("Next steps:");
360
457
  gray(` cd ${finalProjectName}`);
361
- gray(` npm run dev\n`);
458
+ gray(` ${config.packageManager === "pnpm" ? "pnpm install" : "npm install"}`);
459
+ gray(` ${config.packageManager === "pnpm" ? "pnpm dev" : "npm run dev"}\n`);
362
460
  info("Available commands:");
363
461
  gray(` katax add endpoint <name> - Add a new endpoint`);
364
462
  gray(` katax generate crud <name> - Generate CRUD resource`);
365
463
  gray(` katax info - Show project structure\n`);
464
+ if (installedPeerDeps.length > 0) {
465
+ info("Installed peer dependencies:");
466
+ gray(` ${installedPeerDeps.join(", ")}\n`);
467
+ }
366
468
  if (config.swagger) {
367
469
  info("📖 API Documentation:");
368
470
  gray(` Open http://localhost:${config.port}/docs after starting the server`);
@@ -375,11 +477,44 @@ export async function initCommand(projectName, options = {}) {
375
477
  process.exit(1);
376
478
  }
377
479
  }
378
- async function installDependencies(projectPath) {
379
- await execa("npm", ["install"], {
480
+ async function installDependencies(projectPath, config) {
481
+ const packageManager = config.packageManager || "npm";
482
+ const ignoreScripts = config.ignoreInstallScripts || false;
483
+ const baseInstall = getInstallCommand(packageManager, [], ignoreScripts);
484
+ await execa(baseInstall.cmd, baseInstall.args, {
380
485
  cwd: projectPath,
381
486
  stdio: "ignore",
382
487
  });
488
+ const peers = resolvePeerDependencies(config);
489
+ if (peers.length === 0) {
490
+ return [];
491
+ }
492
+ const peerInstall = getInstallCommand(packageManager, peers, ignoreScripts);
493
+ await execa(peerInstall.cmd, peerInstall.args, {
494
+ cwd: projectPath,
495
+ stdio: "ignore",
496
+ });
497
+ return peers;
498
+ }
499
+ function resolvePeerDependencies(config) {
500
+ if (!config.useKataxServiceManager) {
501
+ return [];
502
+ }
503
+ const mode = config.peerDependenciesMode || "none";
504
+ if (mode === "none") {
505
+ return [];
506
+ }
507
+ if (mode === "all") {
508
+ return [...KATAX_SERVICE_MANAGER_PEERS];
509
+ }
510
+ const selected = new Set();
511
+ if (config.useRedis) {
512
+ selected.add("redis");
513
+ }
514
+ if (config.useWebSocket) {
515
+ selected.add("socket.io");
516
+ }
517
+ return [...selected];
383
518
  }
384
519
  async function initGitRepository(projectPath) {
385
520
  await execa("git", ["init"], {
@@ -567,9 +702,12 @@ async function createProjectStructure(projectPath, config, generateJwtSecrets) {
567
702
  dependencies: {
568
703
  express: "^4.18.2",
569
704
  cors: "^2.8.5",
705
+ helmet: "^8.1.0",
706
+ "cookie-parser": "^1.4.7",
707
+ "express-rate-limit": "^8.3.2",
570
708
  dotenv: "^16.3.1",
571
709
  ...(config.useKataxServiceManager
572
- ? { "katax-service-manager": "^0.3.3", "pino-pretty": "^10.3.1" }
710
+ ? { "katax-service-manager": "latest", "pino-pretty": "^10.3.1" }
573
711
  : { pino: "^8.17.2", "pino-pretty": "^10.3.1" }),
574
712
  ...(config.validation === "katax-core" && { "katax-core": "latest" }),
575
713
  ...(config.authentication === "jwt" && {
@@ -585,6 +723,7 @@ async function createProjectStructure(projectPath, config, generateJwtSecrets) {
585
723
  devDependencies: {
586
724
  "@types/express": "^4.17.21",
587
725
  "@types/cors": "^2.8.17",
726
+ "@types/cookie-parser": "^1.4.9",
588
727
  "@types/node": "^22.10.5",
589
728
  ...(config.authentication === "jwt" && {
590
729
  "@types/jsonwebtoken": "^9.0.5",
@@ -607,7 +746,7 @@ async function createProjectStructure(projectPath, config, generateJwtSecrets) {
607
746
  target: "ES2022",
608
747
  module: "ESNext",
609
748
  lib: ["ES2022"],
610
- moduleResolution: "node",
749
+ moduleResolution: "bundler",
611
750
  esModuleInterop: true,
612
751
  allowSyntheticDefaultImports: true,
613
752
  forceConsistentCasingInFileNames: true,
@@ -921,7 +1060,7 @@ ${listenCode}
921
1060
 
922
1061
  // Register custom shutdown hooks (optional)
923
1062
  katax.onShutdown(async () => {
924
- katax.logger.info('Running custom cleanup...');
1063
+ katax.logger.info({ message: 'Running custom cleanup...' });
925
1064
  // Add your custom cleanup logic here
926
1065
  });
927
1066
 
@@ -949,19 +1088,19 @@ validateEnvironment();
949
1088
  const PORT = process.env.PORT || ${config.port};
950
1089
 
951
1090
  app.listen(PORT, () => {
952
- logger.info(\`Server running on http://localhost:\${PORT}\`);
953
- logger.info(\`API endpoints available at http://localhost:\${PORT}/api\`);
954
- logger.info(\`Health check: http://localhost:\${PORT}/api/health\`);
1091
+ logger.info({ message: \`Server running on http://localhost:\${PORT}\` });
1092
+ logger.info({ message: \`API endpoints available at http://localhost:\${PORT}/api\` });
1093
+ logger.info({ message: \`Health check: http://localhost:\${PORT}/api/health\` });
955
1094
  });
956
1095
 
957
1096
  // Graceful shutdown handlers
958
1097
  process.on('SIGTERM', () => {
959
- logger.info('SIGTERM received, shutting down...');
1098
+ logger.info({ message: 'SIGTERM received, shutting down...' });
960
1099
  process.exit(0);
961
1100
  });
962
1101
 
963
1102
  process.on('SIGINT', () => {
964
- logger.info('SIGINT received, shutting down...');
1103
+ logger.info({ message: 'SIGINT received, shutting down...' });
965
1104
  process.exit(0);
966
1105
  });
967
1106
  `;
@@ -976,6 +1115,9 @@ export const katax = new Katax();
976
1115
  }
977
1116
  const appContent = `import express from 'express';
978
1117
  import cors from 'cors';
1118
+ import helmet from 'helmet';
1119
+ import cookieParser from 'cookie-parser';
1120
+ import { rateLimit } from 'express-rate-limit';
979
1121
  import router from './api/routes.js';
980
1122
  import { errorMiddleware } from './middleware/error.middleware.js';
981
1123
  import { requestLogger } from './middleware/logger.middleware.js';
@@ -983,10 +1125,24 @@ import { corsOptions } from './config/cors.config.js';${config.swagger ? "\nimpo
983
1125
 
984
1126
  const app = express();
985
1127
 
1128
+ const apiLimiter = rateLimit({
1129
+ windowMs: 15 * 60 * 1000,
1130
+ max: 200,
1131
+ standardHeaders: true,
1132
+ legacyHeaders: false,
1133
+ message: {
1134
+ success: false,
1135
+ message: 'Too many requests, please try again later.',
1136
+ error: 'RATE_LIMIT_EXCEEDED'
1137
+ }
1138
+ });
1139
+
986
1140
  // Middleware
1141
+ app.use(helmet());
987
1142
  app.use(cors(corsOptions));
988
- app.use(express.json());
989
- app.use(express.urlencoded({ extended: true }));
1143
+ app.use(cookieParser());
1144
+ app.use(express.json({ limit: '1mb' }));
1145
+ app.use(express.urlencoded({ extended: true, limit: '1mb' }));
990
1146
  app.use(requestLogger);
991
1147
 
992
1148
  ${config.swagger ? "// API Documentation\nsetupSwagger(app);\n\n" : ""}// Routes
@@ -999,6 +1155,7 @@ app.get('/', (req, res) => {
999
1155
  });
1000
1156
  });
1001
1157
 
1158
+ app.use('/api', apiLimiter);
1002
1159
  app.use('/api', router);
1003
1160
 
1004
1161
  // Error handling
@@ -1022,7 +1179,42 @@ router.use('/hello', helloRouter);
1022
1179
  export default router;
1023
1180
  `;
1024
1181
  await writeFile(path.join(projectPath, "src/api/routes.ts"), routesContent);
1025
- const errorMiddlewareContent = `import { Request, Response, NextFunction } from 'express';
1182
+ const errorMiddlewareContent = config.useKataxServiceManager
1183
+ ? `import { Request, Response, NextFunction } from 'express';
1184
+ import { katax } from '${kataxImportSourceShared}';
1185
+
1186
+ export interface ApiError extends Error {
1187
+ statusCode?: number;
1188
+ }
1189
+
1190
+ export function errorMiddleware(
1191
+ err: ApiError,
1192
+ req: Request,
1193
+ res: Response,
1194
+ next: NextFunction
1195
+ ): void {
1196
+ const statusCode = err.statusCode || 500;
1197
+ const message = err.message || 'Internal Server Error';
1198
+
1199
+ katax.logger.error({
1200
+ err,
1201
+ req: {
1202
+ method: req.method,
1203
+ url: req.url,
1204
+ headers: req.headers
1205
+ },
1206
+ statusCode,
1207
+ message
1208
+ });
1209
+
1210
+ res.status(statusCode).json({
1211
+ success: false,
1212
+ message,
1213
+ ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
1214
+ });
1215
+ }
1216
+ `
1217
+ : `import { Request, Response, NextFunction } from 'express';
1026
1218
  import { logger } from '../shared/logger.utils.js';
1027
1219
 
1028
1220
  export interface ApiError extends Error {
@@ -1061,14 +1253,24 @@ export function errorMiddleware(
1061
1253
  await createDatabaseConnection(projectPath, config);
1062
1254
  }
1063
1255
  if (config.validation === "katax-core") {
1256
+ const apiUtilsLoggerImport = config.useKataxServiceManager
1257
+ ? `import { katax } from '${kataxImportSourceShared}';`
1258
+ : "import { logger } from './logger.utils.js';";
1259
+ const apiUtilsWarnLogger = config.useKataxServiceManager
1260
+ ? "katax.logger.warn"
1261
+ : "logger.warn";
1262
+ const apiUtilsErrorLogger = config.useKataxServiceManager
1263
+ ? "katax.logger.error"
1264
+ : "logger.error";
1064
1265
  const apiUtilsContent = `import { Request, Response } from 'express';
1065
- import { logger } from './logger.utils.js';
1266
+ ${apiUtilsLoggerImport}
1066
1267
 
1067
1268
  export interface ControllerResult<T = any> {
1068
1269
  success: boolean;
1069
1270
  message: string;
1070
1271
  data?: T;
1071
1272
  error?: string;
1273
+ details?: unknown;
1072
1274
  statusCode?: number;
1073
1275
  currentPage?: number;
1074
1276
  totalPages?: number;
@@ -1077,6 +1279,93 @@ export interface ControllerResult<T = any> {
1077
1279
  hasMorePages?: boolean;
1078
1280
  }
1079
1281
 
1282
+ type PaginationMeta = Pick<
1283
+ ControllerResult,
1284
+ 'currentPage' | 'totalPages' | 'totalItems' | 'totalCount' | 'hasMorePages'
1285
+ >;
1286
+
1287
+ export const createResponse = {
1288
+ success<T>(
1289
+ message: string,
1290
+ data?: T,
1291
+ statusCode = 200,
1292
+ pagination?: PaginationMeta
1293
+ ): ControllerResult<T> {
1294
+ return {
1295
+ success: true,
1296
+ message,
1297
+ statusCode,
1298
+ ...(pagination ?? {}),
1299
+ data
1300
+ };
1301
+ },
1302
+
1303
+ error(
1304
+ message: string,
1305
+ error: string = 'Request error',
1306
+ statusCode = 400,
1307
+ details?: unknown
1308
+ ): ControllerResult {
1309
+ return {
1310
+ success: false,
1311
+ message,
1312
+ error,
1313
+ statusCode,
1314
+ details
1315
+ };
1316
+ },
1317
+
1318
+ unauthorized(
1319
+ message = 'Unauthorized',
1320
+ error = 'Authentication required'
1321
+ ): ControllerResult {
1322
+ return {
1323
+ success: false,
1324
+ message,
1325
+ error,
1326
+ statusCode: 401
1327
+ };
1328
+ },
1329
+
1330
+ forbidden(
1331
+ message = 'Forbidden',
1332
+ error = 'Insufficient permissions'
1333
+ ): ControllerResult {
1334
+ return {
1335
+ success: false,
1336
+ message,
1337
+ error,
1338
+ statusCode: 403
1339
+ };
1340
+ },
1341
+
1342
+ notFound(resource = 'Resource'): ControllerResult {
1343
+ return {
1344
+ success: false,
1345
+ message: resource + ' not found',
1346
+ error: 'NOT_FOUND',
1347
+ statusCode: 404
1348
+ };
1349
+ },
1350
+
1351
+ validation(
1352
+ errors: ValidationError[] = [],
1353
+ message = 'Validation failed'
1354
+ ): ControllerResult {
1355
+ return {
1356
+ success: false,
1357
+ message,
1358
+ error: 'VALIDATION_ERROR',
1359
+ statusCode: 422,
1360
+ details: errors
1361
+ };
1362
+ },
1363
+
1364
+ custom<T>(result: ControllerResult<T>): ControllerResult<T> {
1365
+ return result;
1366
+ }
1367
+ };
1368
+
1080
1369
  export function createSuccessResult<T>(
1081
1370
  message: string,
1082
1371
  data?: T,
@@ -1088,18 +1377,19 @@ export function createSuccessResult<T>(
1088
1377
  totalCount?: number,
1089
1378
  hasMorePages?: boolean
1090
1379
  ): ControllerResult<T> {
1091
- return {
1092
- success: true,
1093
- message,
1094
- error,
1095
- statusCode,
1380
+ const response = createResponse.success(message, data, statusCode, {
1096
1381
  currentPage,
1097
1382
  totalPages,
1098
1383
  totalItems,
1099
1384
  totalCount,
1100
- hasMorePages,
1101
- data
1102
- };
1385
+ hasMorePages
1386
+ });
1387
+
1388
+ if (error) {
1389
+ response.error = error;
1390
+ }
1391
+
1392
+ return response;
1103
1393
  }
1104
1394
 
1105
1395
  export function createErrorResult(
@@ -1107,13 +1397,34 @@ export function createErrorResult(
1107
1397
  error?: string,
1108
1398
  statusCode = 400
1109
1399
  ): ControllerResult {
1110
- return { success: false, message, error, statusCode };
1400
+ return createResponse.error(message, error, statusCode);
1401
+ }
1402
+
1403
+ export interface ValidationError {
1404
+ field: string;
1405
+ message: string;
1111
1406
  }
1112
1407
 
1113
- export interface ValidationResult<T = any> {
1408
+ export interface ValidationResult<T = unknown> {
1114
1409
  isValid: boolean;
1115
1410
  data?: T;
1116
- errors?: any[];
1411
+ errors?: ValidationError[];
1412
+ }
1413
+
1414
+ type KataxIssue = {
1415
+ path: (string | number)[];
1416
+ message: string;
1417
+ };
1418
+
1419
+ type KataxSafeParseResult<T> =
1420
+ | { success: true; data: T }
1421
+ | { success: false; issues: KataxIssue[] };
1422
+
1423
+ export interface KataxSchema<T = unknown> {
1424
+ safeParse(input: unknown): KataxSafeParseResult<T>;
1425
+ safeParseAsync(input: unknown): Promise<KataxSafeParseResult<T>>;
1426
+ hasAsyncValidation?: () => boolean;
1427
+ _asyncValidators?: unknown[];
1117
1428
  }
1118
1429
 
1119
1430
  /**
@@ -1125,13 +1436,16 @@ export interface ValidationResult<T = any> {
1125
1436
  * @returns ValidationResult con datos validados o errores
1126
1437
  */
1127
1438
  export async function validateSchema<T>(
1128
- schema: any,
1439
+ schema: KataxSchema<T>,
1129
1440
  data: unknown
1130
1441
  ): Promise<ValidationResult<T>> {
1131
1442
  // Detectar si el schema tiene validadores asíncronos
1132
- const hasAsyncValidators = schema._def?.async || false;
1443
+ const hasAsyncValidators =
1444
+ typeof schema.hasAsyncValidation === 'function'
1445
+ ? schema.hasAsyncValidation()
1446
+ : !!(schema._asyncValidators && schema._asyncValidators.length > 0);
1133
1447
 
1134
- let result: any;
1448
+ let result: KataxSafeParseResult<T>;
1135
1449
 
1136
1450
  try {
1137
1451
  if (hasAsyncValidators) {
@@ -1155,10 +1469,10 @@ export async function validateSchema<T>(
1155
1469
  }
1156
1470
 
1157
1471
  if (!result.success) {
1158
- const errors = result.issues?.map((issue: any) => ({
1472
+ const errors = result.issues.map((issue) => ({
1159
1473
  field: issue.path.join('.') || 'root',
1160
1474
  message: issue.message
1161
- })) || [];
1475
+ }));
1162
1476
 
1163
1477
  return {
1164
1478
  isValid: false,
@@ -1172,7 +1486,7 @@ export async function validateSchema<T>(
1172
1486
  };
1173
1487
  }
1174
1488
 
1175
- export async function sendResponse<TValidation = any, TResponse = any>(
1489
+ export async function sendResponse<TValidation = unknown, TResponse = unknown>(
1176
1490
  req: Request,
1177
1491
  res: Response,
1178
1492
  validator: () => Promise<ValidationResult<TValidation>>,
@@ -1184,19 +1498,18 @@ export async function sendResponse<TValidation = any, TResponse = any>(
1184
1498
 
1185
1499
  if (!validationResult.isValid) {
1186
1500
  // Validation error
1187
- logger.warn({
1501
+ ${apiUtilsWarnLogger}({
1188
1502
  method: req.method,
1189
1503
  path: req.path,
1190
1504
  errors: validationResult.errors,
1191
1505
  message:'Validation failed'}
1192
1506
  );
1193
-
1194
- res.status(400).json({
1195
- success: false,
1196
- message: 'Invalid data',
1197
- error: 'Validation failed',
1198
- details: validationResult.errors
1199
- });
1507
+
1508
+ const validationResponse = createResponse.validation(
1509
+ validationResult.errors || [],
1510
+ 'Invalid data'
1511
+ );
1512
+ res.status(validationResponse.statusCode || 422).json(validationResponse);
1200
1513
  return;
1201
1514
  }
1202
1515
 
@@ -1206,7 +1519,18 @@ export async function sendResponse<TValidation = any, TResponse = any>(
1206
1519
  // 3. Build HTTP response
1207
1520
  const statusCode = controllerResult.statusCode || (controllerResult.success ? 200 : 400);
1208
1521
 
1209
- const response: any = {
1522
+ interface ResponsePayload {
1523
+ success: boolean;
1524
+ message: string;
1525
+ error?: string;
1526
+ totalItems?: number;
1527
+ currentPage?: number;
1528
+ totalPages?: number;
1529
+ hasMorePages?: boolean;
1530
+ data?: TResponse;
1531
+ }
1532
+
1533
+ const response: ResponsePayload = {
1210
1534
  success: controllerResult.success,
1211
1535
  message: controllerResult.message
1212
1536
  };
@@ -1239,7 +1563,7 @@ export async function sendResponse<TValidation = any, TResponse = any>(
1239
1563
 
1240
1564
  } catch (error) {
1241
1565
  // Internal server error
1242
- logger.error({
1566
+ ${apiUtilsErrorLogger}({
1243
1567
  err: error,
1244
1568
  method: req.method,
1245
1569
  path: req.path,
@@ -1247,9 +1571,11 @@ export async function sendResponse<TValidation = any, TResponse = any>(
1247
1571
  });
1248
1572
 
1249
1573
  res.status(500).json({
1250
- success: false,
1251
- message: 'Internal server error',
1252
- error: error instanceof Error ? error.message : 'Unknown error'
1574
+ ...createResponse.error(
1575
+ 'Internal server error',
1576
+ error instanceof Error ? error.message : 'Unknown error',
1577
+ 500
1578
+ )
1253
1579
  });
1254
1580
  }
1255
1581
  }
@@ -1258,13 +1584,14 @@ export async function sendResponse<TValidation = any, TResponse = any>(
1258
1584
  }
1259
1585
  if (config.authentication === "jwt") {
1260
1586
  const jwtUtilsContent = `import jwt from 'jsonwebtoken';
1587
+ import type { SignOptions } from 'jsonwebtoken';
1261
1588
  import { Request, Response, NextFunction } from 'express';
1262
1589
 
1263
1590
  const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production';
1264
1591
  const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'your-refresh-secret-key';
1265
1592
 
1266
- const ACCESS_TOKEN_EXPIRY = '15m'; // 15 minutes
1267
- const REFRESH_TOKEN_EXPIRY = '7d'; // 7 days
1593
+ const ACCESS_TOKEN_EXPIRY = (process.env.JWT_EXPIRES_IN || '15m') as SignOptions['expiresIn'];
1594
+ const REFRESH_TOKEN_EXPIRY = (process.env.JWT_REFRESH_EXPIRES_IN || '7d') as SignOptions['expiresIn'];
1268
1595
 
1269
1596
  // ==================== INTERFACES ====================
1270
1597
 
@@ -1413,33 +1740,8 @@ export function requireRole(...roles: string[]) {
1413
1740
  await writeFile(path.join(projectPath, "src/shared/jwt.utils.ts"), jwtUtilsContent);
1414
1741
  await writeFile(path.join(projectPath, "src/shared/auth.utils.ts"), generateAuthUtils());
1415
1742
  }
1416
- let loggerUtilsContent;
1417
- if (config.useKataxServiceManager) {
1418
- loggerUtilsContent = `import { katax } from '${kataxImportSourceShared}';
1419
-
1420
- /**
1421
- * Re-export logger for convenience
1422
- * katax.logger is always available (creates a default logger if not initialized)
1423
- * Advanced features (broadcast, transports) require katax.init()
1424
- */
1425
- export const logger = katax.logger;
1426
-
1427
- /**
1428
- * Log HTTP request helper
1429
- */
1430
- export function logRequest(method: string, url: string, statusCode: number, duration: number): void {
1431
- logger.info({
1432
- message: \`\${method} \${url} - \${statusCode} (\${duration}ms)\`,
1433
- method,
1434
- url,
1435
- statusCode,
1436
- duration: \`\${duration}ms\`
1437
- });
1438
- }
1439
- `;
1440
- }
1441
- else {
1442
- loggerUtilsContent = `import pino from 'pino';
1743
+ if (!config.useKataxServiceManager) {
1744
+ const loggerUtilsContent = `import pino from 'pino';
1443
1745
 
1444
1746
  const isDevelopment = process.env.NODE_ENV !== 'production';
1445
1747
 
@@ -1482,38 +1784,68 @@ export function logRequest(method: string, url: string, statusCode: number, dura
1482
1784
  /**
1483
1785
  * Log error with context
1484
1786
  */
1485
- export function logError(error: Error, context?: Record<string, any>): void {
1787
+ export function logError(error: Error, context?: Record<string, unknown>): void {
1486
1788
  logger.error({
1789
+ message: error.message,
1487
1790
  err: error,
1488
1791
  ...context
1489
- }, error.message);
1792
+ });
1490
1793
  }
1491
1794
 
1492
1795
  /**
1493
1796
  * Log info message
1494
1797
  */
1495
- export function logInfo(message: string, data?: Record<string, any>): void {
1496
- logger.info(data, message);
1798
+ export function logInfo(message: string, data?: Record<string, unknown>): void {
1799
+ logger.info({ message, ...(data || {}) });
1497
1800
  }
1498
1801
 
1499
1802
  /**
1500
1803
  * Log warning message
1501
1804
  */
1502
- export function logWarning(message: string, data?: Record<string, any>): void {
1503
- logger.warn(data, message);
1805
+ export function logWarning(message: string, data?: Record<string, unknown>): void {
1806
+ logger.warn({ message, ...(data || {}) });
1504
1807
  }
1505
1808
 
1506
1809
  /**
1507
1810
  * Log debug message (only in development)
1508
1811
  */
1509
- export function logDebug(message: string, data?: Record<string, any>): void {
1510
- logger.debug(data, message);
1812
+ export function logDebug(message: string, data?: Record<string, unknown>): void {
1813
+ logger.debug({ message, ...(data || {}) });
1511
1814
  }
1512
1815
  `;
1816
+ await writeFile(path.join(projectPath, "src/shared/logger.utils.ts"), loggerUtilsContent);
1513
1817
  }
1514
- await writeFile(path.join(projectPath, "src/shared/logger.utils.ts"), loggerUtilsContent);
1515
1818
  await writeFile(path.join(projectPath, "src/shared/stream.utils.ts"), generateStreamUtils());
1516
- const loggerMiddlewareContent = `import { Request, Response, NextFunction } from 'express';
1819
+ const loggerMiddlewareContent = config.useKataxServiceManager
1820
+ ? `import { Request, Response, NextFunction } from 'express';
1821
+ import { katax } from '${kataxImportSourceShared}';
1822
+
1823
+ function logRequest(method: string, url: string, statusCode: number, duration: number): void {
1824
+ katax.logger.info({
1825
+ message: \`\${method} \${url} - \${statusCode} (\${duration}ms)\`,
1826
+ method,
1827
+ url,
1828
+ statusCode,
1829
+ duration
1830
+ });
1831
+ }
1832
+
1833
+ /**
1834
+ * Express middleware to log all HTTP requests
1835
+ */
1836
+ export function requestLogger(req: Request, res: Response, next: NextFunction): void {
1837
+ const startTime = Date.now();
1838
+
1839
+ // Log response when it finishes
1840
+ res.on('finish', () => {
1841
+ const duration = Date.now() - startTime;
1842
+ logRequest(req.method, req.url, res.statusCode, duration);
1843
+ });
1844
+
1845
+ next();
1846
+ }
1847
+ `
1848
+ : `import { Request, Response, NextFunction } from 'express';
1517
1849
  import { logRequest } from '../shared/logger.utils.js';
1518
1850
 
1519
1851
  /**
@@ -1557,7 +1889,16 @@ export const corsOptions: CorsOptions = {
1557
1889
  };
1558
1890
  `;
1559
1891
  await writeFile(path.join(projectPath, "src/config/cors.config.ts"), corsConfigContent);
1560
- const envValidatorContent = `import { logger } from '../shared/logger.utils.js';
1892
+ const envLoggerImport = config.useKataxServiceManager
1893
+ ? `import { katax } from '${kataxImportSourceShared}';`
1894
+ : "import { logger } from '../shared/logger.utils.js';";
1895
+ const envErrorLogger = config.useKataxServiceManager
1896
+ ? "katax.logger.error"
1897
+ : "logger.error";
1898
+ const envInfoLogger = config.useKataxServiceManager
1899
+ ? "katax.logger.info"
1900
+ : "logger.info";
1901
+ const envValidatorContent = `${envLoggerImport}
1561
1902
 
1562
1903
  interface RequiredEnvVars {
1563
1904
  [key: string]: string;
@@ -1582,11 +1923,11 @@ ${config.database !== "none" ? ` // Database variables\n required.DATABASE_URL
1582
1923
  }
1583
1924
 
1584
1925
  if (missing.length > 0) {
1585
- logger.error({message:\`Missing required environment variables: \${missing.join(', ')}\`});
1586
- logger.error({message:'Please check your .env file'});
1926
+ ${envErrorLogger}({message:\`Missing required environment variables: \${missing.join(', ')}\`});
1927
+ ${envErrorLogger}({message:'Please check your .env file'});
1587
1928
  }
1588
1929
 
1589
- logger.info({message:'Environment variables validated successfully'});
1930
+ ${envInfoLogger}({message:'Environment variables validated successfully'});
1590
1931
  }
1591
1932
  `;
1592
1933
  await writeFile(path.join(projectPath, "src/config/env.validator.ts"), envValidatorContent);
@@ -1839,10 +2180,21 @@ MIT
1839
2180
  }
1840
2181
  async function createHelloEndpoint(projectPath, config) {
1841
2182
  const helloPath = path.join(projectPath, "src/api/hello");
2183
+ const helloLoggerImport = config.useKataxServiceManager
2184
+ ? config.kataxMode === "instance"
2185
+ ? "import { katax } from '../../config/katax.instance.js';"
2186
+ : "import { katax } from 'katax-service-manager';"
2187
+ : "import { logger } from '../../shared/logger.utils.js';";
2188
+ const helloDebugLogger = config.useKataxServiceManager
2189
+ ? "katax.logger.debug"
2190
+ : "logger.debug";
2191
+ const helloErrorLogger = config.useKataxServiceManager
2192
+ ? "katax.logger.error"
2193
+ : "logger.error";
1842
2194
  const controllerContent = [
1843
- "import { ControllerResult, createSuccessResult, createErrorResult } from '../../shared/api.utils.js';",
2195
+ "import { ControllerResult, createResponse } from '../../shared/api.utils.js';",
1844
2196
  "import { HelloQuery } from './hello.validator.js';",
1845
- "import { logger } from '../../shared/logger.utils.js';",
2197
+ helloLoggerImport,
1846
2198
  "",
1847
2199
  "/**",
1848
2200
  " * Get hello message",
@@ -1850,9 +2202,9 @@ async function createHelloEndpoint(projectPath, config) {
1850
2202
  "export async function getHello(queryData: HelloQuery): Promise<ControllerResult<{ message: string; timestamp: string }>> {",
1851
2203
  " try {",
1852
2204
  " const name = queryData.name || 'World';",
1853
- " logger.debug({ name, message: 'Processing hello request' });",
2205
+ ` ${helloDebugLogger}({ name, message: 'Processing hello request' });`,
1854
2206
  " ",
1855
- " return createSuccessResult(",
2207
+ " return createResponse.success(",
1856
2208
  " 'Hello endpoint working!',",
1857
2209
  " {",
1858
2210
  " message: `Hello ${name}! Welcome to your API 🚀`,",
@@ -1860,8 +2212,8 @@ async function createHelloEndpoint(projectPath, config) {
1860
2212
  " }",
1861
2213
  " );",
1862
2214
  " } catch (error) {",
1863
- " logger.error({ err: error, message: 'Error in getHello controller' });",
1864
- " return createErrorResult(",
2215
+ ` ${helloErrorLogger}({ err: error, message: 'Error in getHello controller' });`,
2216
+ " return createResponse.error(",
1865
2217
  " 'Failed to get hello message',",
1866
2218
  " error instanceof Error ? error.message : 'Unknown error',",
1867
2219
  " 500",
@@ -1914,6 +2266,7 @@ async function createHelloEndpoint(projectPath, config) {
1914
2266
  if (config.validation === "katax-core") {
1915
2267
  const validatorContent = [
1916
2268
  "import { k, kataxInfer } from 'katax-core';",
2269
+ "import { validateSchema } from '../../shared/api.utils.js';",
1917
2270
  "import type { ValidationResult } from '../../shared/api.utils.js';",
1918
2271
  "",
1919
2272
  "// ==================== SCHEMAS ====================",
@@ -1934,27 +2287,16 @@ async function createHelloEndpoint(projectPath, config) {
1934
2287
  " * Validate hello query params",
1935
2288
  " */",
1936
2289
  "export async function validateHelloQuery(data: unknown): Promise<ValidationResult<HelloQuery>> {",
1937
- " const result = helloQuerySchema.safeParse(data);",
1938
- "",
1939
- " if (!result.success) {",
1940
- " const errors = result.issues.map(issue => ({",
1941
- " field: issue.path.join('.'),",
1942
- " message: issue.message",
1943
- " }));",
1944
- "",
1945
- " return {",
1946
- " isValid: false,",
1947
- " errors",
1948
- " };",
1949
- " }",
1950
- "",
1951
- " return {",
1952
- " isValid: true,",
1953
- " data: result.data",
1954
- " };",
2290
+ " return validateSchema(helloQuerySchema, data);",
1955
2291
  "}",
1956
2292
  ].join("\n");
1957
2293
  await writeFile(path.join(helloPath, "hello.validator.ts"), validatorContent);
1958
2294
  }
1959
2295
  }
2296
+ export const __initTestUtils = {
2297
+ getPackageManager,
2298
+ getInstallCommand,
2299
+ resolvePeerDependencies,
2300
+ KATAX_SERVICE_MANAGER_PEERS,
2301
+ };
1960
2302
  //# sourceMappingURL=init.js.map