servcraft 0.1.5 → 0.1.7

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.
@@ -10,6 +10,7 @@ import { ensureDir, writeFile, error, warn } from '../utils/helpers.js';
10
10
  interface InitOptions {
11
11
  name: string;
12
12
  language: 'typescript' | 'javascript';
13
+ moduleSystem: 'esm' | 'commonjs';
13
14
  database: 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'none';
14
15
  orm: 'prisma' | 'mongoose' | 'none';
15
16
  validator: 'zod' | 'joi' | 'yup';
@@ -23,11 +24,20 @@ export const initCommand = new Command('init')
23
24
  .option('-y, --yes', 'Skip prompts and use defaults')
24
25
  .option('--ts, --typescript', 'Use TypeScript (default)')
25
26
  .option('--js, --javascript', 'Use JavaScript')
27
+ .option('--esm', 'Use ES Modules (import/export) - default')
28
+ .option('--cjs, --commonjs', 'Use CommonJS (require/module.exports)')
26
29
  .option('--db <database>', 'Database type (postgresql, mysql, sqlite, mongodb, none)')
27
30
  .action(
28
31
  async (
29
32
  name?: string,
30
- cmdOptions?: { yes?: boolean; typescript?: boolean; javascript?: boolean; db?: string }
33
+ cmdOptions?: {
34
+ yes?: boolean;
35
+ typescript?: boolean;
36
+ javascript?: boolean;
37
+ esm?: boolean;
38
+ commonjs?: boolean;
39
+ db?: string;
40
+ }
31
41
  ) => {
32
42
  console.log(
33
43
  chalk.blue(`
@@ -46,6 +56,7 @@ export const initCommand = new Command('init')
46
56
  options = {
47
57
  name: name || 'my-servcraft-app',
48
58
  language: cmdOptions.javascript ? 'javascript' : 'typescript',
59
+ moduleSystem: cmdOptions.commonjs ? 'commonjs' : 'esm',
49
60
  database: db,
50
61
  orm: db === 'mongodb' ? 'mongoose' : db === 'none' ? 'none' : 'prisma',
51
62
  validator: 'zod',
@@ -75,6 +86,16 @@ export const initCommand = new Command('init')
75
86
  ],
76
87
  default: 'typescript',
77
88
  },
89
+ {
90
+ type: 'list',
91
+ name: 'moduleSystem',
92
+ message: 'Select module system:',
93
+ choices: [
94
+ { name: 'ESM (import/export) - Recommended', value: 'esm' },
95
+ { name: 'CommonJS (require/module.exports)', value: 'commonjs' },
96
+ ],
97
+ default: 'esm',
98
+ },
78
99
  {
79
100
  type: 'list',
80
101
  name: 'database',
@@ -150,10 +171,10 @@ export const initCommand = new Command('init')
150
171
 
151
172
  // Generate tsconfig or jsconfig
152
173
  if (options.language === 'typescript') {
153
- await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig());
154
- await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig());
174
+ await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig(options));
175
+ await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig(options));
155
176
  } else {
156
- await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig());
177
+ await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig(options));
157
178
  }
158
179
 
159
180
  // Generate .env files
@@ -171,7 +192,10 @@ export const initCommand = new Command('init')
171
192
  );
172
193
 
173
194
  // Create directory structure
174
- const ext = options.language === 'typescript' ? 'ts' : 'js';
195
+ // For JS: .js for ESM, .cjs for CommonJS
196
+ // For TS: always .ts (output format handled by tsup)
197
+ const ext =
198
+ options.language === 'typescript' ? 'ts' : options.moduleSystem === 'esm' ? 'js' : 'cjs';
175
199
  const dirs = [
176
200
  'src/core',
177
201
  'src/config',
@@ -207,6 +231,30 @@ export const initCommand = new Command('init')
207
231
  generateLoggerFile(options)
208
232
  );
209
233
 
234
+ // Generate config file
235
+ await writeFile(
236
+ path.join(projectDir, `src/config/index.${ext}`),
237
+ generateConfigFile(options)
238
+ );
239
+
240
+ // Generate middleware file
241
+ await writeFile(
242
+ path.join(projectDir, `src/middleware/index.${ext}`),
243
+ generateMiddlewareFile(options)
244
+ );
245
+
246
+ // Generate utils file
247
+ await writeFile(
248
+ path.join(projectDir, `src/utils/index.${ext}`),
249
+ generateUtilsFile(options)
250
+ );
251
+
252
+ // Generate types file
253
+ await writeFile(
254
+ path.join(projectDir, `src/types/index.${ext}`),
255
+ generateTypesFile(options)
256
+ );
257
+
210
258
  // Generate database files based on ORM choice
211
259
  if (options.orm === 'prisma') {
212
260
  await writeFile(
@@ -278,19 +326,46 @@ export const initCommand = new Command('init')
278
326
 
279
327
  function generatePackageJson(options: InitOptions): Record<string, unknown> {
280
328
  const isTS = options.language === 'typescript';
329
+ const isESM = options.moduleSystem === 'esm';
330
+
331
+ // Determine dev command based on language and module system
332
+ let devCommand: string;
333
+ if (isTS) {
334
+ devCommand = 'tsx watch src/index.ts';
335
+ } else if (isESM) {
336
+ devCommand = 'node --watch src/index.js';
337
+ } else {
338
+ devCommand = 'node --watch src/index.cjs';
339
+ }
340
+
341
+ // Determine start command
342
+ let startCommand: string;
343
+ if (isTS) {
344
+ startCommand = isESM ? 'node dist/index.js' : 'node dist/index.cjs';
345
+ } else if (isESM) {
346
+ startCommand = 'node src/index.js';
347
+ } else {
348
+ startCommand = 'node src/index.cjs';
349
+ }
281
350
 
282
351
  const pkg: Record<string, unknown> = {
283
352
  name: options.name,
284
353
  version: '0.1.0',
285
354
  description: 'A Servcraft application',
286
- main: isTS ? 'dist/index.js' : 'src/index.js',
287
- type: 'module',
355
+ main: isTS
356
+ ? isESM
357
+ ? 'dist/index.js'
358
+ : 'dist/index.cjs'
359
+ : isESM
360
+ ? 'src/index.js'
361
+ : 'src/index.cjs',
362
+ ...(isESM && { type: 'module' }),
288
363
  scripts: {
289
- dev: isTS ? 'tsx watch src/index.ts' : 'node --watch src/index.js',
364
+ dev: devCommand,
290
365
  build: isTS ? 'tsup' : 'echo "No build needed for JS"',
291
- start: isTS ? 'node dist/index.js' : 'node src/index.js',
366
+ start: startCommand,
292
367
  test: 'vitest',
293
- lint: isTS ? 'eslint src --ext .ts' : 'eslint src --ext .js',
368
+ lint: isTS ? 'eslint src --ext .ts' : 'eslint src --ext .js,.cjs',
294
369
  },
295
370
  dependencies: {
296
371
  fastify: '^4.28.1',
@@ -361,13 +436,15 @@ function generatePackageJson(options: InitOptions): Record<string, unknown> {
361
436
  return pkg;
362
437
  }
363
438
 
364
- function generateTsConfig(): string {
439
+ function generateTsConfig(options: InitOptions): string {
440
+ const isESM = options.moduleSystem === 'esm';
441
+
365
442
  return JSON.stringify(
366
443
  {
367
444
  compilerOptions: {
368
445
  target: 'ES2022',
369
- module: 'NodeNext',
370
- moduleResolution: 'NodeNext',
446
+ module: isESM ? 'NodeNext' : 'CommonJS',
447
+ moduleResolution: isESM ? 'NodeNext' : 'Node',
371
448
  lib: ['ES2022'],
372
449
  outDir: './dist',
373
450
  rootDir: './src',
@@ -387,12 +464,14 @@ function generateTsConfig(): string {
387
464
  );
388
465
  }
389
466
 
390
- function generateJsConfig(): string {
467
+ function generateJsConfig(options: InitOptions): string {
468
+ const isESM = options.moduleSystem === 'esm';
469
+
391
470
  return JSON.stringify(
392
471
  {
393
472
  compilerOptions: {
394
- module: 'NodeNext',
395
- moduleResolution: 'NodeNext',
473
+ module: isESM ? 'NodeNext' : 'CommonJS',
474
+ moduleResolution: isESM ? 'NodeNext' : 'Node',
396
475
  target: 'ES2022',
397
476
  checkJs: true,
398
477
  },
@@ -404,12 +483,14 @@ function generateJsConfig(): string {
404
483
  );
405
484
  }
406
485
 
407
- function generateTsupConfig(): string {
486
+ function generateTsupConfig(options: InitOptions): string {
487
+ const isESM = options.moduleSystem === 'esm';
488
+
408
489
  return `import { defineConfig } from 'tsup';
409
490
 
410
491
  export default defineConfig({
411
492
  entry: ['src/index.ts'],
412
- format: ['esm'],
493
+ format: ['${isESM ? 'esm' : 'cjs'}'],
413
494
  dts: true,
414
495
  clean: true,
415
496
  sourcemap: true,
@@ -619,8 +700,13 @@ model User {
619
700
 
620
701
  function generateEntryFile(options: InitOptions): string {
621
702
  const isTS = options.language === 'typescript';
703
+ const isESM = options.moduleSystem === 'esm';
704
+ const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
622
705
 
623
- return `${isTS ? "import 'dotenv/config';\nimport { createServer } from './core/server.js';\nimport { logger } from './core/logger.js';" : "require('dotenv').config();\nconst { createServer } = require('./core/server.js');\nconst { logger } = require('./core/logger.js');"}
706
+ if (isESM || isTS) {
707
+ return `import 'dotenv/config';
708
+ import { createServer } from './core/server.${fileExt}';
709
+ import { logger } from './core/logger.${fileExt}';
624
710
 
625
711
  async function main()${isTS ? ': Promise<void>' : ''} {
626
712
  const server = createServer();
@@ -635,22 +721,34 @@ async function main()${isTS ? ': Promise<void>' : ''} {
635
721
 
636
722
  main();
637
723
  `;
724
+ } else {
725
+ // CommonJS
726
+ return `require('dotenv').config();
727
+ const { createServer } = require('./core/server.cjs');
728
+ const { logger } = require('./core/logger.cjs');
729
+
730
+ async function main() {
731
+ const server = createServer();
732
+
733
+ try {
734
+ await server.start();
735
+ } catch (error) {
736
+ logger.error({ err: error }, 'Failed to start server');
737
+ process.exit(1);
738
+ }
739
+ }
740
+
741
+ main();
742
+ `;
743
+ }
638
744
  }
639
745
 
640
746
  function generateServerFile(options: InitOptions): string {
641
747
  const isTS = options.language === 'typescript';
748
+ const isESM = options.moduleSystem === 'esm';
749
+ const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
642
750
 
643
- return `${
644
- isTS
645
- ? `import Fastify from 'fastify';
646
- import type { FastifyInstance } from 'fastify';
647
- import { logger } from './logger.js';`
648
- : `const Fastify = require('fastify');
649
- const { logger } = require('./logger.js');`
650
- }
651
-
652
- ${isTS ? 'export function createServer(): { instance: FastifyInstance; start: () => Promise<void> }' : 'function createServer()'} {
653
- const app = Fastify({ logger });
751
+ const serverBody = ` const app = Fastify({ logger });
654
752
 
655
753
  // Health check
656
754
  app.get('/health', async () => ({
@@ -677,37 +775,66 @@ ${isTS ? 'export function createServer(): { instance: FastifyInstance; start: ()
677
775
  logger.info(\`Server listening on \${host}:\${port}\`);
678
776
  },
679
777
  };
680
- }
778
+ }`;
779
+
780
+ if (isESM || isTS) {
781
+ return `import Fastify from 'fastify';
782
+ ${isTS ? "import type { FastifyInstance } from 'fastify';" : ''}
783
+ import { logger } from './logger.${fileExt}';
784
+
785
+ ${isTS ? 'export function createServer(): { instance: FastifyInstance; start: () => Promise<void> }' : 'export function createServer()'} {
786
+ ${serverBody}
787
+ `;
788
+ } else {
789
+ // CommonJS
790
+ return `const Fastify = require('fastify');
791
+ const { logger } = require('./logger.cjs');
792
+
793
+ function createServer() {
794
+ ${serverBody}
681
795
 
682
- ${isTS ? '' : 'module.exports = { createServer };'}
796
+ module.exports = { createServer };
683
797
  `;
798
+ }
684
799
  }
685
800
 
686
801
  function generateLoggerFile(options: InitOptions): string {
687
802
  const isTS = options.language === 'typescript';
803
+ const isESM = options.moduleSystem === 'esm';
688
804
 
689
- return `${isTS ? "import pino from 'pino';\nimport type { Logger } from 'pino';" : "const pino = require('pino');"}
690
-
691
- ${isTS ? 'export const logger: Logger' : 'const logger'} = pino({
805
+ const loggerBody = `pino({
692
806
  level: process.env.LOG_LEVEL || 'info',
693
807
  transport: process.env.NODE_ENV !== 'production' ? {
694
808
  target: 'pino-pretty',
695
809
  options: { colorize: true },
696
810
  } : undefined,
697
- });
811
+ })`;
698
812
 
699
- ${isTS ? '' : 'module.exports = { logger };'}
813
+ if (isESM || isTS) {
814
+ return `import pino from 'pino';
815
+ ${isTS ? "import type { Logger } from 'pino';" : ''}
816
+
817
+ export const logger${isTS ? ': Logger' : ''} = ${loggerBody};
700
818
  `;
819
+ } else {
820
+ // CommonJS
821
+ return `const pino = require('pino');
822
+
823
+ const logger = ${loggerBody};
824
+
825
+ module.exports = { logger };
826
+ `;
827
+ }
701
828
  }
702
829
 
703
830
  function generateMongooseConnection(options: InitOptions): string {
704
831
  const isTS = options.language === 'typescript';
832
+ const isESM = options.moduleSystem === 'esm';
833
+ const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
705
834
 
706
- return `${isTS ? "import mongoose from 'mongoose';\nimport { logger } from '../core/logger.js';" : "const mongoose = require('mongoose');\nconst { logger } = require('../core/logger.js');"}
835
+ const connectionBody = `const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/mydb';
707
836
 
708
- const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/mydb';
709
-
710
- ${isTS ? 'export async function connectDatabase(): Promise<typeof mongoose>' : 'async function connectDatabase()'} {
837
+ async function connectDatabase()${isTS ? ': Promise<typeof mongoose>' : ''} {
711
838
  try {
712
839
  const conn = await mongoose.connect(MONGODB_URI);
713
840
  logger.info(\`MongoDB connected: \${conn.connection.host}\`);
@@ -718,41 +845,40 @@ ${isTS ? 'export async function connectDatabase(): Promise<typeof mongoose>' : '
718
845
  }
719
846
  }
720
847
 
721
- ${isTS ? 'export async function disconnectDatabase(): Promise<void>' : 'async function disconnectDatabase()'} {
848
+ async function disconnectDatabase()${isTS ? ': Promise<void>' : ''} {
722
849
  try {
723
850
  await mongoose.disconnect();
724
851
  logger.info('MongoDB disconnected');
725
852
  } catch (error) {
726
853
  logger.error({ err: error }, 'MongoDB disconnect failed');
727
854
  }
728
- }
855
+ }`;
856
+
857
+ if (isESM || isTS) {
858
+ return `import mongoose from 'mongoose';
859
+ import { logger } from '../core/logger.${fileExt}';
860
+
861
+ ${connectionBody}
862
+
863
+ export { connectDatabase, disconnectDatabase, mongoose };
864
+ `;
865
+ } else {
866
+ // CommonJS
867
+ return `const mongoose = require('mongoose');
868
+ const { logger } = require('../core/logger.cjs');
869
+
870
+ ${connectionBody}
729
871
 
730
- ${isTS ? 'export { mongoose };' : 'module.exports = { connectDatabase, disconnectDatabase, mongoose };'}
872
+ module.exports = { connectDatabase, disconnectDatabase, mongoose };
731
873
  `;
874
+ }
732
875
  }
733
876
 
734
877
  function generateMongooseUserModel(options: InitOptions): string {
735
878
  const isTS = options.language === 'typescript';
879
+ const isESM = options.moduleSystem === 'esm';
736
880
 
737
- return `${isTS ? "import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';" : "const mongoose = require('mongoose');\nconst bcrypt = require('bcryptjs');\nconst { Schema } = mongoose;"}
738
-
739
- ${
740
- isTS
741
- ? `export interface IUser extends Document {
742
- email: string;
743
- password: string;
744
- name?: string;
745
- role: 'user' | 'admin';
746
- status: 'active' | 'inactive' | 'suspended';
747
- emailVerified: boolean;
748
- createdAt: Date;
749
- updatedAt: Date;
750
- comparePassword(candidatePassword: string): Promise<boolean>;
751
- }`
752
- : ''
753
- }
754
-
755
- const userSchema = new Schema${isTS ? '<IUser>' : ''}({
881
+ const schemaBody = `const userSchema = new Schema${isTS ? '<IUser>' : ''}({
756
882
  email: {
757
883
  type: String,
758
884
  required: true,
@@ -803,8 +929,258 @@ userSchema.pre('save', async function(next) {
803
929
  // Compare password method
804
930
  userSchema.methods.comparePassword = async function(candidatePassword${isTS ? ': string' : ''})${isTS ? ': Promise<boolean>' : ''} {
805
931
  return bcrypt.compare(candidatePassword, this.password);
806
- };
932
+ };`;
933
+
934
+ const tsInterface = isTS
935
+ ? `
936
+ export interface IUser extends Document {
937
+ email: string;
938
+ password: string;
939
+ name?: string;
940
+ role: 'user' | 'admin';
941
+ status: 'active' | 'inactive' | 'suspended';
942
+ emailVerified: boolean;
943
+ createdAt: Date;
944
+ updatedAt: Date;
945
+ comparePassword(candidatePassword: string): Promise<boolean>;
946
+ }
947
+ `
948
+ : '';
949
+
950
+ if (isESM || isTS) {
951
+ return `import mongoose${isTS ? ', { Schema, Document }' : ''} from 'mongoose';
952
+ import bcrypt from 'bcryptjs';
953
+ ${!isTS ? 'const { Schema } = mongoose;' : ''}
954
+ ${tsInterface}
955
+ ${schemaBody}
956
+
957
+ export const User = mongoose.model${isTS ? '<IUser>' : ''}('User', userSchema);
958
+ `;
959
+ } else {
960
+ // CommonJS
961
+ return `const mongoose = require('mongoose');
962
+ const bcrypt = require('bcryptjs');
963
+ const { Schema } = mongoose;
964
+
965
+ ${schemaBody}
966
+
967
+ const User = mongoose.model('User', userSchema);
968
+
969
+ module.exports = { User };
970
+ `;
971
+ }
972
+ }
973
+
974
+ function generateConfigFile(options: InitOptions): string {
975
+ const isTS = options.language === 'typescript';
976
+ const isESM = options.moduleSystem === 'esm';
977
+
978
+ const configBody = `{
979
+ env: process.env.NODE_ENV || 'development',
980
+ port: parseInt(process.env.PORT || '3000', 10),
981
+ host: process.env.HOST || '0.0.0.0',
982
+
983
+ jwt: {
984
+ secret: process.env.JWT_SECRET || 'your-secret-key',
985
+ accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || '15m',
986
+ refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d',
987
+ },
988
+
989
+ cors: {
990
+ origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
991
+ },
992
+
993
+ rateLimit: {
994
+ max: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
995
+ },
996
+
997
+ log: {
998
+ level: process.env.LOG_LEVEL || 'info',
999
+ },
1000
+ }${isTS ? ' as const' : ''}`;
1001
+
1002
+ if (isESM || isTS) {
1003
+ return `import 'dotenv/config';
1004
+
1005
+ export const config = ${configBody};
1006
+ `;
1007
+ } else {
1008
+ // CommonJS
1009
+ return `require('dotenv').config();
1010
+
1011
+ const config = ${configBody};
1012
+
1013
+ module.exports = { config };
1014
+ `;
1015
+ }
1016
+ }
1017
+
1018
+ function generateMiddlewareFile(options: InitOptions): string {
1019
+ const isTS = options.language === 'typescript';
1020
+ const isESM = options.moduleSystem === 'esm';
1021
+ const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
1022
+
1023
+ const middlewareBody = `/**
1024
+ * Error handler middleware
1025
+ */
1026
+ function errorHandler(error${isTS ? ': Error' : ''}, request${isTS ? ': FastifyRequest' : ''}, reply${isTS ? ': FastifyReply' : ''})${isTS ? ': void' : ''} {
1027
+ logger.error({ err: error, url: request.url, method: request.method }, 'Request error');
1028
+
1029
+ const statusCode = (error${isTS ? ' as any' : ''}).statusCode || 500;
1030
+ const message = statusCode === 500 ? 'Internal Server Error' : error.message;
1031
+
1032
+ reply.status(statusCode).send({
1033
+ success: false,
1034
+ error: message,
1035
+ ...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
1036
+ });
1037
+ }
1038
+
1039
+ /**
1040
+ * Request logging middleware
1041
+ */
1042
+ function requestLogger(request${isTS ? ': FastifyRequest' : ''}, reply${isTS ? ': FastifyReply' : ''}, done${isTS ? ': () => void' : ''})${isTS ? ': void' : ''} {
1043
+ logger.info({ url: request.url, method: request.method, ip: request.ip }, 'Incoming request');
1044
+ done();
1045
+ }`;
1046
+
1047
+ if (isESM || isTS) {
1048
+ return `${isTS ? "import type { FastifyRequest, FastifyReply } from 'fastify';" : ''}
1049
+ import { logger } from '../core/logger.${fileExt}';
1050
+
1051
+ ${middlewareBody}
1052
+
1053
+ export { errorHandler, requestLogger };
1054
+ `;
1055
+ } else {
1056
+ // CommonJS
1057
+ return `const { logger } = require('../core/logger.cjs');
1058
+
1059
+ ${middlewareBody}
807
1060
 
808
- ${isTS ? "export const User = mongoose.model<IUser>('User', userSchema);" : "const User = mongoose.model('User', userSchema);\nmodule.exports = { User };"}
1061
+ module.exports = { errorHandler, requestLogger };
1062
+ `;
1063
+ }
1064
+ }
1065
+
1066
+ function generateUtilsFile(options: InitOptions): string {
1067
+ const isTS = options.language === 'typescript';
1068
+ const isESM = options.moduleSystem === 'esm';
1069
+
1070
+ const utilsBody = `/**
1071
+ * Standard API response helper
1072
+ */
1073
+ function apiResponse${isTS ? '<T>' : ''}(data${isTS ? ': T' : ''}, message = "Success")${isTS ? ': { success: boolean; message: string; data: T }' : ''} {
1074
+ return {
1075
+ success: true,
1076
+ message,
1077
+ data,
1078
+ };
1079
+ }
1080
+
1081
+ /**
1082
+ * Error response helper
1083
+ */
1084
+ function errorResponse(message${isTS ? ': string' : ''}, code${isTS ? '?: string' : ''})${isTS ? ': { success: boolean; error: string; code?: string }' : ''} {
1085
+ return {
1086
+ success: false,
1087
+ error: message,
1088
+ ...(code && { code }),
1089
+ };
1090
+ }
1091
+
1092
+ /**
1093
+ * Pagination helper
1094
+ */
1095
+ function paginate${isTS ? '<T>' : ''}(data${isTS ? ': T[]' : ''}, page${isTS ? ': number' : ''}, limit${isTS ? ': number' : ''}, total${isTS ? ': number' : ''})${isTS ? ': PaginationResult<T>' : ''} {
1096
+ const totalPages = Math.ceil(total / limit);
1097
+
1098
+ return {
1099
+ data,
1100
+ pagination: {
1101
+ page,
1102
+ limit,
1103
+ total,
1104
+ totalPages,
1105
+ hasNextPage: page < totalPages,
1106
+ hasPrevPage: page > 1,
1107
+ },
1108
+ };
1109
+ }`;
1110
+
1111
+ const tsInterface = isTS
1112
+ ? `
1113
+ /**
1114
+ * Pagination result type
1115
+ */
1116
+ export interface PaginationResult<T> {
1117
+ data: T[];
1118
+ pagination: {
1119
+ page: number;
1120
+ limit: number;
1121
+ total: number;
1122
+ totalPages: number;
1123
+ hasNextPage: boolean;
1124
+ hasPrevPage: boolean;
1125
+ };
1126
+ }
1127
+ `
1128
+ : '';
1129
+
1130
+ if (isESM || isTS) {
1131
+ return `${tsInterface}
1132
+ ${utilsBody}
1133
+
1134
+ export { apiResponse, errorResponse, paginate };
1135
+ `;
1136
+ } else {
1137
+ // CommonJS
1138
+ return `${utilsBody}
1139
+
1140
+ module.exports = { apiResponse, errorResponse, paginate };
1141
+ `;
1142
+ }
1143
+ }
1144
+
1145
+ function generateTypesFile(options: InitOptions): string {
1146
+ if (options.language !== 'typescript') {
1147
+ return '// Types file - not needed for JavaScript\n';
1148
+ }
1149
+
1150
+ return `/**
1151
+ * Common type definitions
1152
+ */
1153
+
1154
+ export interface ApiResponse<T = unknown> {
1155
+ success: boolean;
1156
+ message?: string;
1157
+ data?: T;
1158
+ error?: string;
1159
+ code?: string;
1160
+ }
1161
+
1162
+ export interface PaginatedResponse<T> {
1163
+ data: T[];
1164
+ pagination: {
1165
+ page: number;
1166
+ limit: number;
1167
+ total: number;
1168
+ totalPages: number;
1169
+ hasNextPage: boolean;
1170
+ hasPrevPage: boolean;
1171
+ };
1172
+ }
1173
+
1174
+ export interface RequestUser {
1175
+ id: string;
1176
+ email: string;
1177
+ role: string;
1178
+ }
1179
+
1180
+ declare module 'fastify' {
1181
+ interface FastifyRequest {
1182
+ user?: RequestUser;
1183
+ }
1184
+ }
809
1185
  `;
810
1186
  }
package/src/cli/index.ts CHANGED
@@ -5,6 +5,7 @@ import { initCommand } from './commands/init.js';
5
5
  import { generateCommand } from './commands/generate.js';
6
6
  import { addModuleCommand } from './commands/add-module.js';
7
7
  import { dbCommand } from './commands/db.js';
8
+ import { docsCommand } from './commands/docs.js';
8
9
 
9
10
  const program = new Command();
10
11
 
@@ -25,4 +26,7 @@ program.addCommand(addModuleCommand);
25
26
  // Database commands
26
27
  program.addCommand(dbCommand);
27
28
 
29
+ // Documentation commands
30
+ program.addCommand(docsCommand);
31
+
28
32
  program.parse();