@forinda/kickjs-cli 1.3.2 → 1.4.1-alpha.0

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 (110) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +1195 -2392
  4. package/dist/commands/add.d.ts +5 -0
  5. package/dist/commands/add.d.ts.map +1 -0
  6. package/dist/commands/custom.d.ts +56 -0
  7. package/dist/commands/custom.d.ts.map +1 -0
  8. package/dist/commands/generate.d.ts +3 -0
  9. package/dist/commands/generate.d.ts.map +1 -0
  10. package/dist/commands/info.d.ts +3 -0
  11. package/dist/commands/info.d.ts.map +1 -0
  12. package/dist/commands/init.d.ts +3 -0
  13. package/dist/commands/init.d.ts.map +1 -0
  14. package/dist/commands/inspect.d.ts +3 -0
  15. package/dist/commands/inspect.d.ts.map +1 -0
  16. package/dist/commands/remove.d.ts +3 -0
  17. package/dist/commands/remove.d.ts.map +1 -0
  18. package/dist/commands/run.d.ts +3 -0
  19. package/dist/commands/run.d.ts.map +1 -0
  20. package/dist/commands/tinker.d.ts +3 -0
  21. package/dist/commands/tinker.d.ts.map +1 -0
  22. package/dist/config-C4XJLiRC.js +3110 -0
  23. package/dist/config.d.ts +131 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/generators/adapter.d.ts +7 -0
  26. package/dist/generators/adapter.d.ts.map +1 -0
  27. package/dist/generators/config.d.ts +9 -0
  28. package/dist/generators/config.d.ts.map +1 -0
  29. package/dist/generators/controller.d.ts +11 -0
  30. package/dist/generators/controller.d.ts.map +1 -0
  31. package/dist/generators/dto.d.ts +11 -0
  32. package/dist/generators/dto.d.ts.map +1 -0
  33. package/dist/generators/guard.d.ts +11 -0
  34. package/dist/generators/guard.d.ts.map +1 -0
  35. package/dist/generators/job.d.ts +8 -0
  36. package/dist/generators/job.d.ts.map +1 -0
  37. package/dist/generators/middleware.d.ts +11 -0
  38. package/dist/generators/middleware.d.ts.map +1 -0
  39. package/dist/generators/module.d.ts +33 -0
  40. package/dist/generators/module.d.ts.map +1 -0
  41. package/dist/generators/patterns/cqrs.d.ts +3 -0
  42. package/dist/generators/patterns/cqrs.d.ts.map +1 -0
  43. package/dist/generators/patterns/ddd.d.ts +3 -0
  44. package/dist/generators/patterns/ddd.d.ts.map +1 -0
  45. package/dist/generators/patterns/index.d.ts +6 -0
  46. package/dist/generators/patterns/index.d.ts.map +1 -0
  47. package/dist/generators/patterns/minimal.d.ts +3 -0
  48. package/dist/generators/patterns/minimal.d.ts.map +1 -0
  49. package/dist/generators/patterns/rest.d.ts +3 -0
  50. package/dist/generators/patterns/rest.d.ts.map +1 -0
  51. package/dist/generators/patterns/types.d.ts +15 -0
  52. package/dist/generators/patterns/types.d.ts.map +1 -0
  53. package/dist/generators/project.d.ts +14 -0
  54. package/dist/generators/project.d.ts.map +1 -0
  55. package/dist/generators/remove-module.d.ts +12 -0
  56. package/dist/generators/remove-module.d.ts.map +1 -0
  57. package/dist/generators/resolver.d.ts +7 -0
  58. package/dist/generators/resolver.d.ts.map +1 -0
  59. package/dist/generators/scaffold.d.ts +20 -0
  60. package/dist/generators/scaffold.d.ts.map +1 -0
  61. package/dist/generators/service.d.ts +11 -0
  62. package/dist/generators/service.d.ts.map +1 -0
  63. package/dist/generators/templates/constants.d.ts +3 -0
  64. package/dist/generators/templates/constants.d.ts.map +1 -0
  65. package/dist/generators/templates/controller.d.ts +6 -0
  66. package/dist/generators/templates/controller.d.ts.map +1 -0
  67. package/dist/generators/templates/cqrs.d.ts +23 -0
  68. package/dist/generators/templates/cqrs.d.ts.map +1 -0
  69. package/dist/generators/templates/domain.d.ts +5 -0
  70. package/dist/generators/templates/domain.d.ts.map +1 -0
  71. package/dist/generators/templates/drizzle/index.d.ts +4 -0
  72. package/dist/generators/templates/drizzle/index.d.ts.map +1 -0
  73. package/dist/generators/templates/dtos.d.ts +5 -0
  74. package/dist/generators/templates/dtos.d.ts.map +1 -0
  75. package/dist/generators/templates/index.d.ts +14 -0
  76. package/dist/generators/templates/index.d.ts.map +1 -0
  77. package/dist/generators/templates/module-index.d.ts +13 -0
  78. package/dist/generators/templates/module-index.d.ts.map +1 -0
  79. package/dist/generators/templates/prisma/index.d.ts +3 -0
  80. package/dist/generators/templates/prisma/index.d.ts.map +1 -0
  81. package/dist/generators/templates/project-app.d.ts +9 -0
  82. package/dist/generators/templates/project-app.d.ts.map +1 -0
  83. package/dist/generators/templates/project-config.d.ts +23 -0
  84. package/dist/generators/templates/project-config.d.ts.map +1 -0
  85. package/dist/generators/templates/project-docs.d.ts +9 -0
  86. package/dist/generators/templates/project-docs.d.ts.map +1 -0
  87. package/dist/generators/templates/repository.d.ts +5 -0
  88. package/dist/generators/templates/repository.d.ts.map +1 -0
  89. package/dist/generators/templates/rest-service.d.ts +6 -0
  90. package/dist/generators/templates/rest-service.d.ts.map +1 -0
  91. package/dist/generators/templates/tests.d.ts +4 -0
  92. package/dist/generators/templates/tests.d.ts.map +1 -0
  93. package/dist/generators/templates/types.d.ts +20 -0
  94. package/dist/generators/templates/types.d.ts.map +1 -0
  95. package/dist/generators/templates/use-cases.d.ts +6 -0
  96. package/dist/generators/templates/use-cases.d.ts.map +1 -0
  97. package/dist/generators/test.d.ts +9 -0
  98. package/dist/generators/test.d.ts.map +1 -0
  99. package/dist/index.d.ts +12 -234
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +17 -2184
  102. package/dist/utils/fs.d.ts +11 -0
  103. package/dist/utils/fs.d.ts.map +1 -0
  104. package/dist/utils/naming.d.ts +18 -0
  105. package/dist/utils/naming.d.ts.map +1 -0
  106. package/dist/utils/resolve-out-dir.d.ts +25 -0
  107. package/dist/utils/resolve-out-dir.d.ts.map +1 -0
  108. package/dist/utils/shell.d.ts +3 -0
  109. package/dist/utils/shell.d.ts.map +1 -0
  110. package/package.json +8 -7
package/dist/cli.js CHANGED
@@ -1,2194 +1,126 @@
1
1
  #!/usr/bin/env node
2
- var Nt=Object.defineProperty;var s=(r,e)=>Nt(r,"name",{value:e,configurable:!0});import{Command as Co}from"commander";import{readFileSync as xo}from"fs";import{dirname as Ro,join as Do}from"path";import{fileURLToPath as bo}from"url";import{resolve as oe,basename as er}from"path";import{createInterface as tr}from"readline";import{existsSync as rr,readdirSync as or,rmSync as ir}from"fs";import{join as h,dirname as Yt}from"path";import{execSync as J}from"child_process";import{readFileSync as Jt}from"fs";import{fileURLToPath as Vt}from"url";import{writeFile as Wt,mkdir as Bt,access as Kt,readFile as jo}from"fs/promises";import{dirname as Ht}from"path";var Ee=!1;function x(r){Ee=r}s(x,"setDryRun");async function m(r,e){Ee||(await Bt(Ht(r),{recursive:!0}),await Wt(r,e,"utf-8"))}s(m,"writeFileSafe");async function A(r){try{return await Kt(r),!0}catch{return!1}}s(A,"fileExists");function Ue(r,e,t){let o={"@forinda/kickjs-core":t,"@forinda/kickjs-http":t,"@forinda/kickjs-config":t,express:"^5.1.0","reflect-metadata":"^0.2.2",zod:"^4.3.6",pino:"^10.3.1","pino-pretty":"^13.1.3"};return e!=="minimal"&&(o["@forinda/kickjs-swagger"]=t,o["@forinda/kickjs-devtools"]=t),e==="graphql"&&(o["@forinda/kickjs-graphql"]=t,o.graphql="^16.11.0"),e==="cqrs"&&(o["@forinda/kickjs-queue"]=t,o["@forinda/kickjs-ws"]=t,o["@forinda/kickjs-otel"]=t),e==="ddd"&&(o["@forinda/kickjs-swagger"]=t),JSON.stringify({name:r,version:t.replace("^",""),type:"module",scripts:{dev:"kick dev","dev:debug":"kick dev:debug",build:"kick build",start:"kick start",test:"vitest run","test:watch":"vitest",typecheck:"tsc --noEmit",lint:"eslint src/",format:"prettier --write src/"},dependencies:o,devDependencies:{"@forinda/kickjs-cli":t,"@swc/core":"^1.7.28","@types/express":"^5.0.6","@types/node":"^24.5.2","unplugin-swc":"^1.5.9",vite:"^7.3.1","vite-node":"^5.3.0",vitest:"^3.2.4",typescript:"^5.9.2",prettier:"^3.8.1"}},null,2)}s(Ue,"generatePackageJson");function ze(){return`import { defineConfig } from 'vite'
3
- import { resolve } from 'path'
4
- import swc from 'unplugin-swc'
5
-
6
- export default defineConfig({
7
- plugins: [swc.vite()],
8
- resolve: {
9
- alias: {
10
- '@': resolve(__dirname, 'src'),
11
- },
12
- },
13
- server: {
14
- watch: { usePolling: false },
15
- hmr: true,
16
- },
17
- build: {
18
- target: 'node20',
19
- ssr: true,
20
- outDir: 'dist',
21
- sourcemap: true,
22
- rollupOptions: {
23
- input: resolve(__dirname, 'src/index.ts'),
24
- output: { format: 'esm' },
25
- },
26
- },
27
- })
28
- `}s(ze,"generateViteConfig");function Me(){return JSON.stringify({compilerOptions:{target:"ES2022",module:"ESNext",moduleResolution:"bundler",lib:["ES2022"],types:["node","vite/client"],strict:!0,esModuleInterop:!0,skipLibCheck:!0,sourceMap:!0,declaration:!0,experimentalDecorators:!0,emitDecoratorMetadata:!0,outDir:"dist",rootDir:"src",paths:{"@/*":["./src/*"]}},include:["src"]},null,2)}s(Me,"generateTsConfig");function qe(){return JSON.stringify({semi:!1,singleQuote:!0,trailingComma:"all",printWidth:100,tabWidth:2},null,2)}s(qe,"generatePrettierConfig");function _e(){return`# https://editorconfig.org
29
- root = true
30
-
31
- [*]
32
- indent_style = space
33
- indent_size = 2
34
- end_of_line = lf
35
- charset = utf-8
36
- trim_trailing_whitespace = true
37
- insert_final_newline = true
38
-
39
- [*.md]
40
- trim_trailing_whitespace = false
41
- `}s(_e,"generateEditorConfig");function Ge(){return`node_modules/
42
- dist/
43
- .env
44
- coverage/
45
- .DS_Store
46
- *.tsbuildinfo
47
- `}s(Ge,"generateGitIgnore");function Qe(){return`# Auto-detect text files and normalise line endings to LF
48
- * text=auto eol=lf
49
-
50
- # Explicitly mark generated / binary files
51
- *.png binary
52
- *.jpg binary
53
- *.jpeg binary
54
- *.gif binary
55
- *.ico binary
56
- *.woff binary
57
- *.woff2 binary
58
- *.ttf binary
59
- *.eot binary
60
-
61
- # Lock files \u2014 treat as generated
62
- pnpm-lock.yaml -diff linguist-generated
63
- yarn.lock -diff linguist-generated
64
- package-lock.json -diff linguist-generated
65
- `}s(Qe,"generateGitAttributes");function Fe(){return`PORT=3000
66
- NODE_ENV=development
67
- `}s(Fe,"generateEnv");function Le(){return`PORT=3000
68
- NODE_ENV=development
69
- `}s(Le,"generateEnvExample");function Ne(){return`import { defineConfig } from 'vitest/config'
70
- import swc from 'unplugin-swc'
71
-
72
- export default defineConfig({
73
- plugins: [swc.vite()],
74
- test: {
75
- globals: true,
76
- environment: 'node',
77
- include: ['src/**/*.test.ts'],
78
- },
79
- })
80
- `}s(Ne,"generateVitestConfig");function We(r,e,t){switch(e){case"graphql":return`import 'reflect-metadata'
81
- import { bootstrap } from '@forinda/kickjs-http'
82
- import { DevToolsAdapter } from '@forinda/kickjs-devtools'
83
- import { GraphQLAdapter } from '@forinda/kickjs-graphql'
84
- import { modules } from './modules'
85
-
86
- // Import your resolvers here
87
- // import { UserResolver } from './resolvers/user.resolver'
88
-
89
- bootstrap({
90
- modules,
91
- adapters: [
92
- new DevToolsAdapter(),
93
- new GraphQLAdapter({
94
- resolvers: [/* UserResolver */],
95
- // Add custom type definitions here:
96
- // typeDefs: userTypeDefs,
97
- }),
98
- ],
99
- })
100
- `;case"cqrs":return`import 'reflect-metadata'
101
- import { bootstrap } from '@forinda/kickjs-http'
102
- import { DevToolsAdapter } from '@forinda/kickjs-devtools'
103
- import { SwaggerAdapter } from '@forinda/kickjs-swagger'
104
- import { OtelAdapter } from '@forinda/kickjs-otel'
105
- // import { WsAdapter } from '@forinda/kickjs-ws'
106
- // import { QueueAdapter, BullMQProvider } from '@forinda/kickjs-queue'
107
- import { modules } from './modules'
108
-
109
- bootstrap({
110
- modules,
111
- adapters: [
112
- new OtelAdapter({ serviceName: '${r}' }),
113
- new DevToolsAdapter(),
114
- new SwaggerAdapter({
115
- info: { title: '${r}', version: '${t}' },
116
- }),
117
- // Uncomment for WebSocket support:
118
- // new WsAdapter(),
119
- // Uncomment when Redis is available:
120
- // new QueueAdapter({
121
- // provider: new BullMQProvider({ host: 'localhost', port: 6379 }),
122
- // }),
123
- ],
124
- })
125
- `;case"minimal":return`import 'reflect-metadata'
126
- import { bootstrap } from '@forinda/kickjs-http'
127
- import { modules } from './modules'
128
-
129
- bootstrap({ modules })
130
- `;default:return`import 'reflect-metadata'
131
- import { bootstrap } from '@forinda/kickjs-http'
132
- import { DevToolsAdapter } from '@forinda/kickjs-devtools'
133
- import { SwaggerAdapter } from '@forinda/kickjs-swagger'
134
- import { modules } from './modules'
135
-
136
- bootstrap({
137
- modules,
138
- adapters: [
139
- new DevToolsAdapter(),
140
- new SwaggerAdapter({
141
- info: { title: '${r}', version: '${t}' },
142
- }),
143
- ],
144
- })
145
- `}}s(We,"generateEntryFile");function Be(){return`import type { AppModuleClass } from '@forinda/kickjs-core'
146
-
147
- export const modules: AppModuleClass[] = []
148
- `}s(Be,"generateModulesIndex");function Ke(r,e="inmemory"){return`import { defineConfig } from '@forinda/kickjs-cli'
149
-
150
- export default defineConfig({
151
- pattern: '${r}',
152
- modules: {
153
- dir: 'src/modules',
154
- repo: '${e}',
155
- pluralize: true,
156
- },
157
-
158
- commands: [
159
- {
160
- name: 'test',
161
- description: 'Run tests with Vitest',
162
- steps: 'npx vitest run',
163
- },
164
- {
165
- name: 'format',
166
- description: 'Format code with Prettier',
167
- steps: 'npx prettier --write src/',
168
- },
169
- {
170
- name: 'format:check',
171
- description: 'Check formatting without writing',
172
- steps: 'npx prettier --check src/',
173
- },
174
- {
175
- name: 'check',
176
- description: 'Run typecheck + format check',
177
- steps: ['npx tsc --noEmit', 'npx prettier --check src/'],
178
- aliases: ['verify', 'ci'],
179
- },
180
- ],
181
- })
182
- `}s(Ke,"generateKickConfig");function He(r,e,t){let o={rest:"REST API",graphql:"GraphQL API",ddd:"Domain-Driven Design",cqrs:"CQRS + Event-Driven",minimal:"Minimal"},n=["@forinda/kickjs-core","@forinda/kickjs-http","@forinda/kickjs-config"];return e!=="minimal"&&n.push("@forinda/kickjs-swagger","@forinda/kickjs-devtools"),e==="graphql"&&n.push("@forinda/kickjs-graphql"),e==="cqrs"&&n.push("@forinda/kickjs-queue","@forinda/kickjs-ws","@forinda/kickjs-otel"),`# ${r}
183
-
184
- A **${o[e]??"REST API"}** built with [KickJS](https://forinda.github.io/kick-js/) \u2014 a decorator-driven Node.js framework on Express 5 and TypeScript.
185
-
186
- ## Getting Started
187
-
188
- \`\`\`bash
189
- ${t} install
190
- kick dev
191
- \`\`\`
192
-
193
- ## Scripts
194
-
195
- | Command | Description |
196
- |---|---|
197
- | \`kick dev\` | Start dev server with Vite HMR |
198
- | \`kick build\` | Production build |
199
- | \`kick start\` | Run production build |
200
- | \`${t} run test\` | Run tests with Vitest |
201
- | \`kick g module <name>\` | Generate a DDD module |
202
- | \`kick g scaffold <name> <fields...>\` | Generate CRUD from field definitions |
203
- | \`kick add <package>\` | Add a KickJS package |
204
-
205
- ## Project Structure
206
-
207
- \`\`\`
208
- src/
209
- \u251C\u2500\u2500 index.ts # Application entry point
210
- \u251C\u2500\u2500 modules/ # Feature modules (controllers, services, repos)
211
- \u2502 \u2514\u2500\u2500 index.ts # Module registry
212
- \u2514\u2500\u2500 ...
213
- \`\`\`
214
-
215
- ## Packages
216
-
217
- ${n.map(i=>`- \`${i}\``).join(`
218
- `)}
219
-
220
- ## Adding Features
221
-
222
- \`\`\`bash
223
- kick add auth # Authentication (JWT, API key, OAuth)
224
- kick add swagger # OpenAPI documentation
225
- kick add ws # WebSocket support
226
- kick add queue # Background job processing
227
- kick add mailer # Email sending
228
- kick add cron # Scheduled tasks
229
- kick add --list # Show all available packages
230
- \`\`\`
231
-
232
- ## Environment Variables
233
-
234
- Copy \`.env.example\` to \`.env\` and configure:
235
-
236
- | Variable | Default | Description |
237
- |---|---|---|
238
- | \`PORT\` | \`3000\` | Server port |
239
- | \`NODE_ENV\` | \`development\` | Environment |
240
-
241
- ## Learn More
242
-
243
- - [KickJS Documentation](https://forinda.github.io/kick-js/)
244
- - [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
245
- `}s(He,"generateReadme");function Ye(r,e,t){return`# CLAUDE.md \u2014 ${r} Development Guide
246
-
247
- ## Project Overview
248
-
249
- This is a **${{rest:"REST API",graphql:"GraphQL API",ddd:"Domain-Driven Design",cqrs:"CQRS + Event-Driven",minimal:"Minimal Express"}[e]??"REST API"}** application built with [KickJS](https://forinda.github.io/kick-js/) \u2014 a decorator-driven Node.js framework on Express 5 and TypeScript.
250
-
251
- ## Quick Commands
252
-
253
- \`\`\`bash
254
- ${t} install # Install dependencies
255
- kick dev # Start dev server with HMR
256
- kick build # Production build via Vite
257
- kick start # Run production build
258
- ${t} run test # Run tests with Vitest
259
- ${t} run typecheck # TypeScript type checking
260
- ${t} run format # Format code with Prettier
261
- \`\`\`
262
-
263
- ## Project Structure
264
-
265
- \`\`\`
266
- src/
267
- \u251C\u2500\u2500 index.ts # Application bootstrap
268
- \u251C\u2500\u2500 modules/ # Feature modules (DDD/CQRS pattern)
269
- \u2502 \u2514\u2500\u2500 index.ts # Module registry
270
- ${e==="graphql"?`\u251C\u2500\u2500 resolvers/ # GraphQL resolvers
271
- `:""}\u2514\u2500\u2500 ...
272
- \`\`\`
273
-
274
- ## Package Manager
275
-
276
- - Always use **${t}** for this project
277
- - Run \`${t} install\` to sync dependencies
278
- - Never mix package managers (npm/yarn/pnpm)
279
-
280
- ## Code Style
281
-
282
- - **Prettier** \u2014 no semicolons, single quotes, trailing commas, 100 char width
283
- - **TypeScript strict mode** \u2014 all types required
284
- - Format before committing: \`${t} run format\`
285
- - Type check with: \`${t} run typecheck\`
286
-
287
- ## Key Patterns
288
-
289
- ### Controllers
290
-
291
- Use decorators to define routes:
292
-
293
- \`\`\`ts
294
- import { Controller, Get, Post, RequestContext } from '@forinda/kickjs-http'
295
-
296
- @Controller('/users')
297
- export class UserController {
298
- @Get('/')
299
- async findAll(ctx: RequestContext) {
300
- return ctx.json({ users: [] })
301
- }
302
-
303
- @Post('/')
304
- async create(ctx: RequestContext) {
305
- const data = ctx.body
306
- return ctx.created({ user: data })
307
- }
308
- }
309
- \`\`\`
310
-
311
- ### Services
312
-
313
- Inject dependencies with \`@Service()\` and \`@Autowired()\`:
314
-
315
- \`\`\`ts
316
- import { Service, Autowired } from '@forinda/kickjs-core'
317
-
318
- @Service()
319
- export class UserService {
320
- @Autowired()
321
- private userRepository!: UserRepository
322
-
323
- async findAll() {
324
- return this.userRepository.findAll()
325
- }
326
- }
327
- \`\`\`
328
-
329
- ### Modules
330
-
331
- Register controllers and providers in modules:
332
-
333
- \`\`\`ts
334
- import { Module } from '@forinda/kickjs-core'
335
- import { UserController } from './user.controller'
336
- import { UserService } from './user.service'
337
-
338
- @Module({
339
- controllers: [UserController],
340
- providers: [UserService],
341
- })
342
- export class UserModule {}
343
- \`\`\`
344
-
345
- ### RequestContext
346
-
347
- Every controller method receives \`ctx: RequestContext\`:
348
-
349
- \`\`\`ts
350
- ctx.body // Request body (parsed JSON)
351
- ctx.params // Route params
352
- ctx.query // Query string
353
- ctx.headers // Request headers
354
- ctx.requestId // Auto-generated request ID
355
- ctx.session // Session data (if session middleware enabled)
356
- ctx.file // Uploaded file (single)
357
- ctx.files // Uploaded files (multiple)
358
-
359
- // Pagination helpers
360
- ctx.qs(config) // Parse query with filters/sort/pagination
361
- ctx.paginate(handler) // Auto-paginated response
362
-
363
- // Response helpers
364
- ctx.json(data) // 200 OK with JSON
365
- ctx.created(data) // 201 Created
366
- ctx.noContent() // 204 No Content
367
- ctx.notFound() // 404 Not Found
368
- ctx.badRequest(msg) // 400 Bad Request
369
- \`\`\`
370
-
371
- ## CLI Generators
372
-
373
- Generate code with the \`kick\` CLI:
374
-
375
- \`\`\`bash
376
- kick g module <name> # Full module (controller, service, DTOs, repo)
377
- kick g scaffold <name> <fields> # CRUD module from field definitions
378
- kick g controller <name> # Standalone controller
379
- kick g service <name> # Service class
380
- kick g middleware <name> # Express middleware
381
- kick g guard <name> # Route guard (auth, roles)
382
- kick g adapter <name> # AppAdapter with lifecycle hooks
383
- kick g dto <name> # Zod DTO schema
384
- ${e==="graphql"?`kick g resolver <name> # GraphQL resolver
385
- `:""}${e==="cqrs"?`kick g job <name> # Queue job processor
386
- `:""}\`\`\`
387
-
388
- ## Adding Packages
389
-
390
- \`\`\`bash
391
- kick add auth # JWT, API key, OAuth strategies
392
- kick add swagger # OpenAPI docs from decorators
393
- kick add ws # WebSocket support
394
- kick add queue # Background jobs (BullMQ/RabbitMQ/Kafka)
395
- kick add mailer # Email (SMTP, Resend, SES)
396
- kick add cron # Scheduled tasks
397
- kick add prisma # Prisma ORM adapter
398
- kick add drizzle # Drizzle ORM adapter
399
- kick add otel # OpenTelemetry tracing
400
- kick add --list # Show all available packages
401
- \`\`\`
402
-
403
- ## Environment Configuration
404
-
405
- Edit \`.env\` for environment variables. Access them with \`@Value()\` decorator:
406
-
407
- \`\`\`ts
408
- import { Value } from '@forinda/kickjs-config'
409
-
410
- @Service()
411
- export class ApiService {
412
- @Value('API_KEY')
413
- private apiKey!: string
414
-
415
- @Value('PORT', 3000) // With default
416
- private port!: number
417
- }
418
- \`\`\`
419
-
420
- Or use \`ConfigService\`:
421
-
422
- \`\`\`ts
423
- import { ConfigService } from '@forinda/kickjs-config'
424
-
425
- @Service()
426
- export class AppService {
427
- @Autowired()
428
- private config!: ConfigService
429
-
430
- getPort() {
431
- return this.config.get('PORT', 3000)
432
- }
433
- }
434
- \`\`\`
435
-
436
- ## Testing
437
-
438
- Tests live in \`src/**/*.test.ts\`:
439
-
440
- \`\`\`ts
441
- import { describe, it, expect, beforeEach } from 'vitest'
442
- import { Container } from '@forinda/kickjs-core'
443
- import { createTestApp } from '@forinda/kickjs-testing'
444
-
445
- describe('UserController', () => {
446
- beforeEach(() => Container.reset())
447
-
448
- it('should return users', async () => {
449
- const app = await createTestApp([UserModule])
450
- const res = await app.get('/users')
451
- expect(res.status).toBe(200)
452
- })
453
- })
454
- \`\`\`
455
-
456
- Run tests:
457
- - \`${t} run test\` \u2014 run all tests
458
- - \`${t} run test:watch\` \u2014 watch mode
459
-
460
- ## Decorators Reference
461
-
462
- ### Route Decorators
463
- - \`@Controller('/path')\` \u2014 define controller prefix
464
- - \`@Get('/'), @Post('/'), @Put('/'), @Delete('/'), @Patch('/')\` \u2014 HTTP methods
465
- - \`@Middleware(fn)\` \u2014 attach middleware
466
- - \`@Public()\` \u2014 skip authentication (requires @forinda/kickjs-auth)
467
- - \`@Roles('admin', 'user')\` \u2014 role-based access control
468
-
469
- ### DI Decorators
470
- - \`@Module({ controllers, providers, imports })\` \u2014 define module
471
- - \`@Service()\` \u2014 singleton service (DI-registered)
472
- - \`@Repository()\` \u2014 repository (semantic alias for @Service)
473
- - \`@Autowired()\` \u2014 property injection
474
- - \`@Inject('token')\` \u2014 token-based injection
475
- - \`@Value('ENV_VAR')\` \u2014 inject config value
476
-
477
- ${e==="cqrs"?"### CQRS/Event Decorators\n- `@Job('job-name')` \u2014 queue job handler\n- `@Process('queue-name')` \u2014 queue processor\n- `@Cron('0 * * * *')` \u2014 cron schedule\n- `@WsController('/path')` \u2014 WebSocket controller\n- `@Subscribe('event')` \u2014 WebSocket event handler\n\n":""}${e==="graphql"?"### GraphQL Decorators\n- `@Resolver()` \u2014 GraphQL resolver\n- `@Query()` \u2014 GraphQL query\n- `@Mutation()` \u2014 GraphQL mutation\n- `@Arg('name')` \u2014 resolver argument\n\n":""}## Common Pitfalls
478
-
479
- 1. **Decorators fire at import time** \u2014 make sure to import module classes in \`src/modules/index.ts\`
480
- 2. **Tests need \`Container.reset()\`** \u2014 call in \`beforeEach\` to isolate DI state
481
- 3. **Always use \`ctx.body\`** \u2014 never \`req.body\` directly
482
- 4. **DI requires \`reflect-metadata\`** \u2014 already imported in \`src/index.ts\`
483
- 5. **Vite HMR requires proper cleanup** \u2014 adapters should implement \`shutdown()\`
484
-
485
- ## Learn More
486
-
487
- - [KickJS Documentation](https://forinda.github.io/kick-js/)
488
- - [API Reference](https://forinda.github.io/kick-js/api/)
489
- - [CLI Commands](https://forinda.github.io/kick-js/guide/cli-commands.html)
490
- - [Decorators Guide](https://forinda.github.io/kick-js/guide/decorators.html)
491
- `}s(Ye,"generateClaude");function Je(r,e,t){return`# AGENTS.md \u2014 AI Agent Guide for ${r}
492
-
493
- This guide helps AI agents (Claude, Copilot, etc.) work effectively on this KickJS application.
494
-
495
- ## Before You Start
496
-
497
- 1. Read \`CLAUDE.md\` for project conventions and commands
498
- 2. Run \`${t} install\` to install dependencies
499
- 3. Run \`kick dev\` to verify the app starts
500
- 4. Read the [KickJS documentation](https://forinda.github.io/kick-js/) for framework details
501
-
502
- ## Where to Find Things
503
-
504
- ### Application Structure
505
-
506
- | What | Where |
507
- |------|-------|
508
- | Entry point | \`src/index.ts\` |
509
- | Module registry | \`src/modules/index.ts\` |
510
- | Feature modules | \`src/modules/<module-name>/\` |
511
- ${e==="graphql"?"| GraphQL resolvers | `src/resolvers/` |\n":""}| Environment config | \`.env\` |
512
- | TypeScript config | \`tsconfig.json\` |
513
- | Vite config (HMR) | \`vite.config.ts\` |
514
- | Vitest config | \`vitest.config.ts\` |
515
- | Prettier config | \`.prettierrc\` |
516
- | CLI config | \`kick.config.ts\` |
517
-
518
- ### Module Pattern (${e.toUpperCase()})
519
-
520
- Each module in \`src/modules/<name>/\` typically contains:
521
-
522
- ${e==="ddd"?`\`\`\`
523
- <name>/
524
- \u251C\u2500\u2500 <name>.controller.ts # HTTP routes (@Controller)
525
- \u251C\u2500\u2500 <name>.service.ts # Business logic (@Service)
526
- \u251C\u2500\u2500 <name>.repository.ts # Data access (@Repository)
527
- \u251C\u2500\u2500 <name>.dto.ts # Request/response schemas (Zod)
528
- \u251C\u2500\u2500 <name>.entity.ts # Domain entity (optional)
529
- \u2514\u2500\u2500 <name>.module.ts # Module definition (@Module)
530
- \`\`\`
531
- `:e==="cqrs"?`\`\`\`
532
- <name>/
533
- \u251C\u2500\u2500 commands/ # Write operations
534
- \u2502 \u251C\u2500\u2500 create-<name>.command.ts
535
- \u2502 \u2514\u2500\u2500 create-<name>.handler.ts
536
- \u251C\u2500\u2500 queries/ # Read operations
537
- \u2502 \u251C\u2500\u2500 get-<name>.query.ts
538
- \u2502 \u2514\u2500\u2500 get-<name>.handler.ts
539
- \u251C\u2500\u2500 events/ # Domain events
540
- \u2502 \u2514\u2500\u2500 <name>-created.event.ts
541
- \u251C\u2500\u2500 <name>.controller.ts # HTTP routes
542
- \u251C\u2500\u2500 <name>.repository.ts # Data access
543
- \u2514\u2500\u2500 <name>.module.ts # Module definition
544
- \`\`\`
545
- `:e==="graphql"?"```\nresolvers/\n\u251C\u2500\u2500 <name>.resolver.ts # @Resolver, @Query, @Mutation\n\u251C\u2500\u2500 <name>.types.ts # GraphQL type definitions\n\u2514\u2500\u2500 <name>.service.ts # Business logic\n```\n":e==="rest"?`\`\`\`
546
- <name>/
547
- \u251C\u2500\u2500 <name>.controller.ts # HTTP routes (@Controller)
548
- \u251C\u2500\u2500 <name>.service.ts # Business logic (@Service)
549
- \u251C\u2500\u2500 <name>.dto.ts # Request/response schemas (Zod)
550
- \u2514\u2500\u2500 <name>.module.ts # Module definition (@Module)
551
- \`\`\`
552
- `:"```\nsrc/\n\u251C\u2500\u2500 index.ts # Add routes here\n\u2514\u2500\u2500 ... # Custom structure\n```\n"}
553
-
554
- ## Checklist: Adding a Feature
555
-
556
- ### New Module (Recommended)
557
-
558
- Use the CLI generator for consistency:
559
-
560
- \`\`\`bash
561
- kick g module <name> # Generate full module
562
- # or
563
- kick g scaffold <name> <fields> # Generate CRUD from fields
564
- \`\`\`
565
-
566
- Then:
567
- - [ ] Review generated files in \`src/modules/<name>/\`
568
- - [ ] Verify module is registered in \`src/modules/index.ts\`
569
- - [ ] Update DTOs in \`<name>.dto.ts\` if needed
570
- - [ ] Implement business logic in \`<name>.service.ts\`
571
- - [ ] Run \`kick dev\` to test with HMR
572
- - [ ] Write tests in \`<name>.test.ts\`
573
-
574
- ### Manual Controller
575
-
576
- If not using generators:
577
-
578
- - [ ] Create \`src/modules/<name>/<name>.controller.ts\`
579
- - [ ] Add \`@Controller('/path')\` decorator
580
- - [ ] Add route handlers with \`@Get()\`, \`@Post()\`, etc.
581
- - [ ] Create module file with \`@Module({ controllers: [NameController] })\`
582
- - [ ] Register module in \`src/modules/index.ts\`
583
- - [ ] Test with \`kick dev\`
584
-
585
- ### Manual Service
586
-
587
- - [ ] Create \`src/modules/<name>/<name>.service.ts\`
588
- - [ ] Add \`@Service()\` decorator
589
- - [ ] Inject dependencies with \`@Autowired()\`
590
- - [ ] Register in module \`providers\` array
591
- - [ ] Write unit tests
592
-
593
- ### New Middleware
594
-
595
- - [ ] Create \`src/middleware/<name>.middleware.ts\`
596
- - [ ] Export middleware function (Express format)
597
- - [ ] Register in \`src/index.ts\` or attach to routes with \`@Middleware()\`
598
- - [ ] Test with sample requests
599
-
600
- ### Adding a Package
601
-
602
- Use \`kick add\` to install KickJS packages with correct peer dependencies:
603
-
604
- - [ ] Run \`kick add <package>\` (e.g., \`kick add auth\`)
605
- - [ ] Follow package-specific setup in terminal output
606
- - [ ] Update \`src/index.ts\` to register adapter (if needed)
607
- - [ ] Configure environment variables in \`.env\`
608
- - [ ] Test integration with \`kick dev\`
609
-
610
- ## Common Tasks
611
-
612
- ### Generate CRUD Module
613
-
614
- \`\`\`bash
615
- kick g scaffold user name:string email:string age:number
616
- \`\`\`
617
-
618
- This creates a full CRUD module with:
619
- - Controller with GET, POST, PUT, DELETE routes
620
- - Service with business logic
621
- - Repository with data access
622
- - DTOs with Zod validation
623
-
624
- ### Add Authentication
625
-
626
- \`\`\`bash
627
- kick add auth
628
- \`\`\`
629
-
630
- Then configure in \`src/index.ts\`:
631
-
632
- \`\`\`ts
633
- import { AuthAdapter, JwtStrategy } from '@forinda/kickjs-auth'
634
-
635
- bootstrap({
636
- modules,
637
- adapters: [
638
- new AuthAdapter({
639
- strategies: [new JwtStrategy({ secret: process.env.JWT_SECRET! })],
640
- }),
641
- ],
642
- })
643
- \`\`\`
644
-
645
- ### Add Database (Prisma)
646
-
647
- \`\`\`bash
648
- kick add prisma
649
- ${t} install prisma @prisma/client
650
- npx prisma init
651
- # Edit prisma/schema.prisma
652
- npx prisma migrate dev --name init
653
- kick g module user --repo prisma
654
- \`\`\`
655
-
656
- ### Add WebSocket Support
657
-
658
- \`\`\`bash
659
- kick add ws
660
- \`\`\`
661
-
662
- Then add adapter in \`src/index.ts\`:
663
-
664
- \`\`\`ts
665
- import { WsAdapter } from '@forinda/kickjs-ws'
666
-
667
- bootstrap({
668
- modules,
669
- adapters: [new WsAdapter()],
670
- })
671
- \`\`\`
672
-
673
- Create WebSocket controller:
674
-
675
- \`\`\`bash
676
- kick g controller chat --ws
677
- \`\`\`
678
-
679
- ## Testing Guidelines
680
-
681
- All tests use Vitest:
682
-
683
- \`\`\`ts
684
- import { describe, it, expect, beforeEach } from 'vitest'
685
- import { Container } from '@forinda/kickjs-core'
686
- import { createTestApp } from '@forinda/kickjs-testing'
687
-
688
- describe('UserController', () => {
689
- beforeEach(() => {
690
- Container.reset() // Important: isolate DI state
691
- })
692
-
693
- it('should return users', async () => {
694
- const app = await createTestApp([UserModule])
695
- const res = await app.get('/users')
696
-
697
- expect(res.status).toBe(200)
698
- expect(res.body).toHaveProperty('users')
699
- })
700
- })
701
- \`\`\`
702
-
703
- Run tests:
704
- - \`${t} run test\` \u2014 run all tests once
705
- - \`${t} run test:watch\` \u2014 watch mode
706
- - Individual file: \`${t} run test src/modules/user/user.test.ts\`
707
-
708
- ## Environment Variables
709
-
710
- Managed via \`.env\` file. Access with:
711
-
712
- 1. **@Value() decorator** (recommended):
713
- \`\`\`ts
714
- @Value('DATABASE_URL')
715
- private dbUrl!: string
716
- \`\`\`
717
-
718
- 2. **ConfigService** (for dynamic access):
719
- \`\`\`ts
720
- @Autowired()
721
- private config!: ConfigService
722
-
723
- const port = this.config.get('PORT', 3000)
724
- \`\`\`
725
-
726
- 3. **Direct access** (avoid in app code):
727
- \`\`\`ts
728
- process.env.PORT
729
- \`\`\`
730
-
731
- ## Key Decorators
732
-
733
- ### HTTP Routes
734
- | Decorator | Purpose |
735
- |-----------|---------|
736
- | \`@Controller('/path')\` | Define route prefix |
737
- | \`@Get('/'), @Post('/')\` | HTTP method handlers |
738
- | \`@Middleware(fn)\` | Attach middleware |
739
- | \`@Public()\` | Skip auth (requires auth adapter) |
740
- | \`@Roles('admin')\` | Role-based access |
741
-
742
- ### Dependency Injection
743
- | Decorator | Purpose |
744
- |-----------|---------|
745
- | \`@Module({})\` | Define feature module |
746
- | \`@Service()\` | Register singleton service |
747
- | \`@Repository()\` | Register repository |
748
- | \`@Autowired()\` | Property injection |
749
- | \`@Inject('token')\` | Token-based injection |
750
- | \`@Value('VAR')\` | Inject env variable |
751
-
752
- ${e==="graphql"?"### GraphQL\n| Decorator | Purpose |\n|-----------|---------|\n| `@Resolver()` | GraphQL resolver class |\n| `@Query()` | Query handler |\n| `@Mutation()` | Mutation handler |\n| `@Arg('name')` | Resolver argument |\n\n":""}${e==="cqrs"?"### Background Jobs\n| Decorator | Purpose |\n|-----------|---------|\n| `@Job('name')` | Queue job handler |\n| `@Process('queue')` | Queue processor |\n| `@Cron('0 * * * *')` | Cron schedule |\n| `@WsController()` | WebSocket controller |\n\n":""}## Common Pitfalls
753
-
754
- 1. **Forgot to register module** \u2014 Add to \`src/modules/index.ts\` exports array
755
- 2. **DI not working** \u2014 Ensure \`reflect-metadata\` is imported in \`src/index.ts\`
756
- 3. **Tests failing randomly** \u2014 Missing \`Container.reset()\` in \`beforeEach\`
757
- 4. **Routes not found** \u2014 Check controller path and module registration
758
- 5. **HMR not working** \u2014 Verify \`vite.config.ts\` has \`hmr: true\`
759
- 6. **Decorators not working** \u2014 Check \`tsconfig.json\` has \`experimentalDecorators: true\`
760
-
761
- ## CLI Commands Reference
762
-
763
- | Command | Description |
764
- |---------|-------------|
765
- | \`kick dev\` | Dev server with HMR |
766
- | \`kick dev:debug\` | Dev server with debugger |
767
- | \`kick build\` | Production build |
768
- | \`kick start\` | Run production build |
769
- | \`kick g module <names...>\` | Generate one or more modules |
770
- | \`kick g scaffold <name> <fields>\` | Generate CRUD |
771
- | \`kick g controller <name>\` | Generate controller |
772
- | \`kick g service <name>\` | Generate service |
773
- | \`kick g middleware <name>\` | Generate middleware |
774
- | \`kick add <package>\` | Add KickJS package |
775
- | \`kick add --list\` | List available packages |
776
- | \`kick rm module <names...>\` | Remove one or more modules |
777
-
778
- > **Note:** When using \`kick new\` in scripts or CI, pass \`-t\` (or \`--template\`) and \`-r\` (or \`--repo\`) flags to bypass interactive prompts:
779
- > \`\`\`bash
780
- > kick new my-api -t ddd -r prisma --pm ${t} --no-git --no-install -f
781
- > \`\`\`
782
-
783
- ## Learn More
784
-
785
- - [KickJS Docs](https://forinda.github.io/kick-js/)
786
- - [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
787
- - [Decorators Guide](https://forinda.github.io/kick-js/guide/decorators.html)
788
- - [DI System](https://forinda.github.io/kick-js/guide/dependency-injection.html)
789
- - [Testing](https://forinda.github.io/kick-js/api/testing.html)
790
- `}s(Je,"generateAgents");var Zt=Yt(Vt(import.meta.url)),Ve=JSON.parse(Jt(h(Zt,"..","package.json"),"utf-8")),Xt=`^${Ve.version}`;async function Ze(r){let{name:e,directory:t,packageManager:o="pnpm",template:n="rest",defaultRepo:i="inmemory"}=r,c=t,d=s(l=>console.log(` ${l}`),"log");if(console.log(`
791
- Creating KickJS project: ${e}
792
- `),await m(h(c,"package.json"),Ue(e,n,Xt)),await m(h(c,"vite.config.ts"),ze()),await m(h(c,"tsconfig.json"),Me()),await m(h(c,".prettierrc"),qe()),await m(h(c,".editorconfig"),_e()),await m(h(c,".gitignore"),Ge()),await m(h(c,".gitattributes"),Qe()),await m(h(c,".env"),Fe()),await m(h(c,".env.example"),Le()),await m(h(c,"src/index.ts"),We(e,n,Ve.version)),await m(h(c,"src/modules/index.ts"),Be()),n==="graphql"&&await m(h(c,"src/resolvers/.gitkeep"),""),await m(h(c,"kick.config.ts"),Ke(n,i)),await m(h(c,"vitest.config.ts"),Ne()),await m(h(c,"README.md"),He(e,n,o)),await m(h(c,"CLAUDE.md"),Ye(e,n,o)),await m(h(c,"AGENTS.md"),Je(e,n,o)),r.initGit)try{J("git init",{cwd:c,stdio:"pipe"}),J("git branch -M main",{cwd:c,stdio:"pipe"}),J("git add -A",{cwd:c,stdio:"pipe"}),J('git commit -m "chore: initial commit from kick new"',{cwd:c,stdio:"pipe"}),d("Git repository initialized")}catch{d("Warning: git init failed (git may not be installed)")}if(r.installDeps){console.log(`
793
- Installing dependencies with ${o}...
794
- `);try{J(`${o} install`,{cwd:c,stdio:"inherit"}),console.log(`
795
- Dependencies installed successfully!`)}catch{console.log(`
796
- Warning: ${o} install failed. Run it manually.`)}}console.log(`
797
- Project scaffolded successfully!`),console.log();let a=c!==process.cwd();d("Next steps:"),a&&d(` cd ${e}`),r.installDeps||d(` ${o} install`);let p={rest:"kick g module user",graphql:"kick g resolver user",ddd:"kick g module user --repo drizzle",cqrs:"kick g module user --pattern cqrs",minimal:"# add your routes to src/index.ts"};d(` ${p[n]??p.rest}`),d(" kick dev"),d(""),d("Commands:"),d(" kick dev Start dev server with Vite HMR"),d(" kick build Production build via Vite"),d(" kick start Run production build"),d(""),d("Generators:"),d(" kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)"),d(" kick g scaffold <n> <f..> CRUD module from field definitions"),d(" kick g controller <name> Standalone controller"),d(" kick g service <name> @Service() class"),d(" kick g middleware <name> Express middleware"),d(" kick g guard <name> Route guard (auth, roles, etc.)"),d(" kick g adapter <name> AppAdapter with lifecycle hooks"),d(" kick g dto <name> Zod DTO schema"),n==="graphql"&&d(" kick g resolver <name> GraphQL resolver"),n==="cqrs"&&d(" kick g job <name> Queue job processor"),d(" kick g config Generate kick.config.ts"),d(""),d("Add packages:"),d(" kick add <pkg> Install a KickJS package + peers"),d(" kick add --list Show all available packages"),d(""),d("Available: auth, swagger, graphql, drizzle, prisma, ws,"),d(" cron, queue, mailer, otel, multi-tenant, notifications, testing"),d("")}s(Ze,"initProject");function te(r,e){let t=tr({input:process.stdin,output:process.stdout}),o=e?` (${e})`:"";return new Promise(n=>{t.question(` ${r}${o}: `,i=>{t.close(),n(i.trim()||e||"")})})}s(te,"ask");async function ie(r,e,t=0){console.log(` ${r}`);for(let i=0;i<e.length;i++)console.log(` ${i===t?">":" "} ${i+1}. ${e[i]}`);let o=await te("Choose",String(t+1)),n=parseInt(o,10)-1;return e[n]??e[t]}s(ie,"choose");async function se(r,e=!0){let o=await te(`${r} (${e?"Y/n":"y/N"})`);return o?o.toLowerCase().startsWith("y"):e}s(se,"confirm");function Xe(r){r.command("new [name]").alias("init").description('Create a new KickJS project (use "." for current directory)').option("-d, --directory <dir>","Target directory (defaults to project name)").option("--pm <manager>","Package manager: pnpm | npm | yarn").option("--git","Initialize git repository").option("--no-git","Skip git initialization").option("--install","Install dependencies after scaffolding").option("--no-install","Skip dependency installation").option("-f, --force","Remove existing files without prompting").option("-t, --template <type>","Project template: rest | graphql | ddd | cqrs | minimal").option("-r, --repo <type>","Default repository: prisma | drizzle | inmemory | custom").action(async(e,t)=>{console.log(),e||(e=await te("Project name","my-api"));let o;if(e==="."?(o=oe("."),e=er(o)):o=oe(t.directory||e),rr(o)){let p=or(o);if(p.length>0){if(t.force)console.log(` Clearing existing files in ${o}...
798
- `);else{console.log(` Directory "${e}" is not empty:`);let l=p.slice(0,5);for(let g of l)console.log(` - ${g}`);if(p.length>5&&console.log(` ... and ${p.length-5} more`),console.log(),!await se("Remove all existing files and proceed?",!1)){console.log(` Aborted.
799
- `);return}}for(let l of p)ir(oe(o,l),{recursive:!0,force:!0})}}let n=t.template;n||(n=await ie("Project template:",["REST API (Express + Swagger)","GraphQL API (GraphQL + GraphiQL)","DDD (Domain-Driven Design modules)","CQRS (Commands, Queries, Events + WS/Queue)","Minimal (bare Express)"],0),n={"REST API (Express + Swagger)":"rest","GraphQL API (GraphQL + GraphiQL)":"graphql","DDD (Domain-Driven Design modules)":"ddd","CQRS (Commands, Queries, Events + WS/Queue)":"cqrs","Minimal (bare Express)":"minimal"}[n]??"rest");let i=t.pm;i||(i=await ie("Package manager:",["pnpm","npm","yarn"],0));let c=t.repo;if(!c){let p=await ie("Default repository/ORM:",["Prisma","Drizzle","In-Memory","Custom (specify later)"],0);c={Prisma:"prisma",Drizzle:"drizzle","In-Memory":"inmemory","Custom (specify later)":"custom"}[p]??"inmemory",c==="custom"&&(c=await te("Custom repository name","custom"))}let d;t.git===void 0?d=await se("Initialize git repository?",!0):d=t.git;let a;t.install===void 0?a=await se("Install dependencies?",!0):a=t.install,await Ze({name:e,directory:o,packageManager:i,initGit:d,installDeps:a,template:n,defaultRepo:c})})}s(Xe,"registerInitCommand");import{resolve as Y}from"path";import{join as Pe}from"path";import{createInterface as ar}from"readline";function $(r){return r.replace(/[-_\s]+(.)?/g,(e,t)=>t?t.toUpperCase():"").replace(/^(.)/,e=>e.toUpperCase())}s($,"toPascalCase");function R(r){let e=$(r);return e.charAt(0).toLowerCase()+e.slice(1)}s(R,"toCamelCase");function f(r){return r.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}s(f,"toKebabCase");function T(r){return r.endsWith("s")?r:r.endsWith("x")||r.endsWith("z")||r.endsWith("sh")||r.endsWith("ch")?r+"es":r.endsWith("y")&&!/[aeiou]y$/.test(r)?r.slice(0,-1)+"ies":r+"s"}s(T,"pluralize");function re(r){return r.endsWith("s")?r:r.endsWith("x")||r.endsWith("z")||r.endsWith("sh")||r.endsWith("ch")?r+"es":r.endsWith("y")&&!/[aeiou]y$/i.test(r)?r.slice(0,-1)+"ies":r+"s"}s(re,"pluralizePascal");import{readFile as cr,writeFile as dr}from"fs/promises";var sr={inmemory:"in-memory",drizzle:"Drizzle",prisma:"Prisma"};function tt(r){return r.charAt(0).toUpperCase()+r.slice(1).replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}s(tt,"toPascalRepoType");function nr(r){return r.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}s(nr,"toKebabRepoType");function et(r){return sr[r]??tt(r)}s(et,"repoLabel");function rt(r,e,t){let o={inmemory:`InMemory${r}Repository`,drizzle:`Drizzle${r}Repository`,prisma:`Prisma${r}Repository`},n={inmemory:`in-memory-${e}`,drizzle:`drizzle-${e}`,prisma:`prisma-${e}`};return{repoClass:o[t]??`${tt(t)}${r}Repository`,repoFile:n[t]??`${nr(t)}-${e}`}}s(rt,"repoMaps");function ne(r){let{pascal:e,kebab:t,plural:o="",repo:n}=r,{repoClass:i,repoFile:c}=rt(e,t,n);return`/**
800
- * ${e} Module
801
- *
802
- * Self-contained feature module following Domain-Driven Design (DDD).
803
- * Registers dependencies in the DI container and declares HTTP routes.
804
- *
805
- * Structure:
806
- * presentation/ \u2014 HTTP controllers (entry points)
807
- * application/ \u2014 Use cases (orchestration) and DTOs (validation)
808
- * domain/ \u2014 Entities, value objects, repository interfaces, domain services
809
- * infrastructure/ \u2014 Repository implementations (currently ${et(n)})
810
- */
811
- import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
812
- import { buildRoutes } from '@forinda/kickjs-http'
813
- import { ${e.toUpperCase()}_REPOSITORY } from './domain/repositories/${t}.repository'
814
- import { ${i} } from './infrastructure/repositories/${c}.repository'
815
- import { ${e}Controller } from './presentation/${t}.controller'
816
-
817
- // Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
818
- import.meta.glob(
819
- ['./domain/services/**/*.ts', './application/use-cases/**/*.ts', '!./**/*.test.ts'],
820
- { eager: true },
821
- )
822
-
823
- export class ${e}Module implements AppModule {
824
- /**
825
- * Register module dependencies in the DI container.
826
- * Bind repository interface tokens to their implementations here.
827
- * Currently wired to ${et(n)}. To swap implementations, change the factory target.
828
- */
829
- register(container: Container): void {
830
- container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
831
- container.resolve(${i}),
832
- )
833
- }
834
-
835
- /**
836
- * Declare HTTP routes for this module.
837
- * The path is prefixed with the global apiPrefix and version (e.g. /api/v1/${o}).
838
- * Passing 'controller' enables automatic OpenAPI spec generation via SwaggerAdapter.
839
- */
840
- routes(): ModuleRoutes {
841
- return {
842
- path: '/${o}',
843
- router: buildRoutes(${e}Controller),
844
- controller: ${e}Controller,
845
- }
846
- }
847
- }
848
- `}s(ne,"generateModuleIndex");function ae(r){let{pascal:e,kebab:t,plural:o="",repo:n}=r,{repoClass:i,repoFile:c}=rt(e,t,n);return`/**
849
- * ${e} Module
850
- *
851
- * REST module with a flat folder structure.
852
- * Controller delegates to service, service wraps the repository.
853
- *
854
- * Structure:
855
- * ${t}.controller.ts \u2014 HTTP routes (CRUD)
856
- * ${t}.service.ts \u2014 Business logic
857
- * ${t}.repository.ts \u2014 Repository interface
858
- * ${c}.repository.ts \u2014 Repository implementation
859
- * dtos/ \u2014 Request/response schemas
860
- */
861
- import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
862
- import { buildRoutes } from '@forinda/kickjs-http'
863
- import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
864
- import { ${i} } from './${c}.repository'
865
- import { ${e}Controller } from './${t}.controller'
866
-
867
- // Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
868
- import.meta.glob(['./**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'], { eager: true })
869
-
870
- export class ${e}Module implements AppModule {
871
- register(container: Container): void {
872
- container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
873
- container.resolve(${i}),
874
- )
875
- }
876
-
877
- routes(): ModuleRoutes {
878
- return {
879
- path: '/${o}',
880
- router: buildRoutes(${e}Controller),
881
- controller: ${e}Controller,
882
- }
883
- }
884
- }
885
- `}s(ae,"generateRestModuleIndex");function ce(r){let{pascal:e,kebab:t,plural:o=""}=r;return`import { type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
886
- import { buildRoutes } from '@forinda/kickjs-http'
887
- import { ${e}Controller } from './${t}.controller'
888
-
889
- export class ${e}Module implements AppModule {
890
- routes(): ModuleRoutes {
891
- return {
892
- path: '/${o}',
893
- router: buildRoutes(${e}Controller),
894
- controller: ${e}Controller,
895
- }
896
- }
897
- }
898
- `}s(ce,"generateMinimalModuleIndex");function de(r){let{pascal:e,kebab:t,plural:o="",pluralPascal:n=""}=r;return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
899
- import type { RequestContext } from '@forinda/kickjs-http'
900
- import { ApiTags } from '@forinda/kickjs-swagger'
901
- import { Create${e}UseCase } from '../application/use-cases/create-${t}.use-case'
902
- import { Get${e}UseCase } from '../application/use-cases/get-${t}.use-case'
903
- import { List${n}UseCase } from '../application/use-cases/list-${o}.use-case'
904
- import { Update${e}UseCase } from '../application/use-cases/update-${t}.use-case'
905
- import { Delete${e}UseCase } from '../application/use-cases/delete-${t}.use-case'
906
- import { create${e}Schema } from '../application/dtos/create-${t}.dto'
907
- import { update${e}Schema } from '../application/dtos/update-${t}.dto'
908
- import { ${e.toUpperCase()}_QUERY_CONFIG } from '../constants'
909
-
910
- @Controller()
911
- export class ${e}Controller {
912
- @Autowired() private create${e}UseCase!: Create${e}UseCase
913
- @Autowired() private get${e}UseCase!: Get${e}UseCase
914
- @Autowired() private list${n}UseCase!: List${n}UseCase
915
- @Autowired() private update${e}UseCase!: Update${e}UseCase
916
- @Autowired() private delete${e}UseCase!: Delete${e}UseCase
917
-
918
- @Get('/')
919
- @ApiTags('${e}')
920
- @ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
921
- async list(ctx: RequestContext) {
922
- return ctx.paginate(
923
- (parsed) => this.list${n}UseCase.execute(parsed),
924
- ${e.toUpperCase()}_QUERY_CONFIG,
925
- )
926
- }
927
-
928
- @Get('/:id')
929
- @ApiTags('${e}')
930
- async getById(ctx: RequestContext) {
931
- const result = await this.get${e}UseCase.execute(ctx.params.id)
932
- if (!result) return ctx.notFound('${e} not found')
933
- ctx.json(result)
934
- }
935
-
936
- @Post('/', { body: create${e}Schema, name: 'Create${e}' })
937
- @ApiTags('${e}')
938
- async create(ctx: RequestContext) {
939
- const result = await this.create${e}UseCase.execute(ctx.body)
940
- ctx.created(result)
941
- }
942
-
943
- @Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
944
- @ApiTags('${e}')
945
- async update(ctx: RequestContext) {
946
- const result = await this.update${e}UseCase.execute(ctx.params.id, ctx.body)
947
- ctx.json(result)
948
- }
949
-
950
- @Delete('/:id')
951
- @ApiTags('${e}')
952
- async remove(ctx: RequestContext) {
953
- await this.delete${e}UseCase.execute(ctx.params.id)
954
- ctx.noContent()
955
- }
956
- }
957
- `}s(de,"generateController");function pe(r){let{pascal:e,kebab:t,plural:o="",pluralPascal:n=""}=r,i=e.charAt(0).toLowerCase()+e.slice(1);return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
958
- import type { RequestContext } from '@forinda/kickjs-http'
959
- import { ApiTags } from '@forinda/kickjs-swagger'
960
- import { ${e}Service } from './${t}.service'
961
- import { create${e}Schema } from './dtos/create-${t}.dto'
962
- import { update${e}Schema } from './dtos/update-${t}.dto'
963
- import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
964
-
965
- @Controller()
966
- export class ${e}Controller {
967
- @Autowired() private ${i}Service!: ${e}Service
968
-
969
- @Get('/')
970
- @ApiTags('${e}')
971
- @ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
972
- async list(ctx: RequestContext) {
973
- return ctx.paginate(
974
- (parsed) => this.${i}Service.findPaginated(parsed),
975
- ${e.toUpperCase()}_QUERY_CONFIG,
976
- )
977
- }
978
-
979
- @Get('/:id')
980
- @ApiTags('${e}')
981
- async getById(ctx: RequestContext) {
982
- const result = await this.${i}Service.findById(ctx.params.id)
983
- if (!result) return ctx.notFound('${e} not found')
984
- ctx.json(result)
985
- }
986
-
987
- @Post('/', { body: create${e}Schema, name: 'Create${e}' })
988
- @ApiTags('${e}')
989
- async create(ctx: RequestContext) {
990
- const result = await this.${i}Service.create(ctx.body)
991
- ctx.created(result)
992
- }
993
-
994
- @Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
995
- @ApiTags('${e}')
996
- async update(ctx: RequestContext) {
997
- const result = await this.${i}Service.update(ctx.params.id, ctx.body)
998
- ctx.json(result)
999
- }
1000
-
1001
- @Delete('/:id')
1002
- @ApiTags('${e}')
1003
- async remove(ctx: RequestContext) {
1004
- await this.${i}Service.delete(ctx.params.id)
1005
- ctx.noContent()
1006
- }
1007
- }
1008
- `}s(pe,"generateRestController");function le(r){let{pascal:e}=r;return`import type { QueryParamsConfig } from '@forinda/kickjs-core'
1009
-
1010
- export const ${e.toUpperCase()}_QUERY_CONFIG: QueryParamsConfig = {
1011
- filterable: ['name'],
1012
- sortable: ['name', 'createdAt'],
1013
- searchable: ['name'],
1014
- }
1015
- `}s(le,"generateConstants");function U(r){let{pascal:e,kebab:t}=r;return`import { z } from 'zod'
1016
-
1017
- /**
1018
- * Create ${e} DTO \u2014 Zod schema for validating POST request bodies.
1019
- * This schema is passed to @Post('/', { body: create${e}Schema }) for automatic validation.
1020
- * It also generates OpenAPI request body docs when SwaggerAdapter is used.
1021
- *
1022
- * Add more fields as needed. Supported Zod types:
1023
- * z.string(), z.number(), z.boolean(), z.enum([...]),
1024
- * z.array(), z.object(), .optional(), .default(), .transform()
1025
- */
1026
- export const create${e}Schema = z.object({
1027
- name: z.string().min(1, 'Name is required').max(200),
1028
- })
1029
-
1030
- export type Create${e}DTO = z.infer<typeof create${e}Schema>
1031
- `}s(U,"generateCreateDTO");function z(r){let{pascal:e,kebab:t}=r;return`import { z } from 'zod'
1032
-
1033
- export const update${e}Schema = z.object({
1034
- name: z.string().min(1).max(200).optional(),
1035
- })
1036
-
1037
- export type Update${e}DTO = z.infer<typeof update${e}Schema>
1038
- `}s(z,"generateUpdateDTO");function M(r){let{pascal:e,kebab:t}=r;return`export interface ${e}ResponseDTO {
1039
- id: string
1040
- name: string
1041
- createdAt: string
1042
- updatedAt: string
1043
- }
1044
- `}s(M,"generateResponseDTO");function me(r){let{pascal:e,kebab:t,plural:o="",pluralPascal:n=""}=r;return[{file:`create-${t}.use-case.ts`,content:`/**
1045
- * Create ${e} Use Case
1046
- *
1047
- * Application layer \u2014 orchestrates a single business operation.
1048
- * Use cases are thin: validate input (via DTO), call domain/repo, return response.
1049
- * Keep business rules in the domain service, not here.
1050
- */
1051
- import { Service, Inject } from '@forinda/kickjs-core'
1052
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
1053
- import type { Create${e}DTO } from '../dtos/create-${t}.dto'
1054
- import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
1055
-
1056
- @Service()
1057
- export class Create${e}UseCase {
1058
- constructor(
1059
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1060
- ) {}
1061
-
1062
- async execute(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
1063
- return this.repo.create(dto)
1064
- }
1065
- }
1066
- `},{file:`get-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1067
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
1068
- import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
1069
-
1070
- @Service()
1071
- export class Get${e}UseCase {
1072
- constructor(
1073
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1074
- ) {}
1075
-
1076
- async execute(id: string): Promise<${e}ResponseDTO | null> {
1077
- return this.repo.findById(id)
1078
- }
1079
- }
1080
- `},{file:`list-${o}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1081
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
1082
- import type { ParsedQuery } from '@forinda/kickjs-http'
1083
-
1084
- @Service()
1085
- export class List${n}UseCase {
1086
- constructor(
1087
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1088
- ) {}
1089
-
1090
- async execute(parsed: ParsedQuery) {
1091
- return this.repo.findPaginated(parsed)
1092
- }
1093
- }
1094
- `},{file:`update-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1095
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
1096
- import type { Update${e}DTO } from '../dtos/update-${t}.dto'
1097
- import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
1098
-
1099
- @Service()
1100
- export class Update${e}UseCase {
1101
- constructor(
1102
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1103
- ) {}
1104
-
1105
- async execute(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
1106
- return this.repo.update(id, dto)
1107
- }
1108
- }
1109
- `},{file:`delete-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1110
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
1111
-
1112
- @Service()
1113
- export class Delete${e}UseCase {
1114
- constructor(
1115
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1116
- ) {}
1117
-
1118
- async execute(id: string): Promise<void> {
1119
- await this.repo.delete(id)
1120
- }
1121
- }
1122
- `}]}s(me,"generateUseCases");function q(r){let{pascal:e,kebab:t,dtoPrefix:o="../../application/dtos"}=r;return`/**
1123
- * ${e} Repository Interface
1124
- *
1125
- * Defines the contract for data access.
1126
- * The interface declares what operations are available;
1127
- * implementations (in-memory, Drizzle, Prisma) fulfill the contract.
1128
- *
1129
- * To swap implementations, change the factory in the module's register() method.
1130
- */
1131
- import type { ${e}ResponseDTO } from '${o}/${t}-response.dto'
1132
- import type { Create${e}DTO } from '${o}/create-${t}.dto'
1133
- import type { Update${e}DTO } from '${o}/update-${t}.dto'
1134
- import type { ParsedQuery } from '@forinda/kickjs-http'
1135
-
1136
- export interface I${e}Repository {
1137
- findById(id: string): Promise<${e}ResponseDTO | null>
1138
- findAll(): Promise<${e}ResponseDTO[]>
1139
- findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }>
1140
- create(dto: Create${e}DTO): Promise<${e}ResponseDTO>
1141
- update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO>
1142
- delete(id: string): Promise<void>
1143
- }
1144
-
1145
- export const ${e.toUpperCase()}_REPOSITORY = Symbol('I${e}Repository')
1146
- `}s(q,"generateRepositoryInterface");function _(r){let{pascal:e,kebab:t,repoPrefix:o="../../domain/repositories",dtoPrefix:n="../../application/dtos"}=r;return`/**
1147
- * In-Memory ${e} Repository
1148
- *
1149
- * Implements the repository interface using a Map.
1150
- * Useful for prototyping and testing. Replace with a database implementation
1151
- * (Drizzle, Prisma, etc.) for production use.
1152
- *
1153
- * @Repository() registers this class in the DI container as a singleton.
1154
- */
1155
- import { randomUUID } from 'node:crypto'
1156
- import { Repository, HttpException } from '@forinda/kickjs-core'
1157
- import type { ParsedQuery } from '@forinda/kickjs-http'
1158
- import type { I${e}Repository } from '${o}/${t}.repository'
1159
- import type { ${e}ResponseDTO } from '${n}/${t}-response.dto'
1160
- import type { Create${e}DTO } from '${n}/create-${t}.dto'
1161
- import type { Update${e}DTO } from '${n}/update-${t}.dto'
1162
-
1163
- @Repository()
1164
- export class InMemory${e}Repository implements I${e}Repository {
1165
- private store = new Map<string, ${e}ResponseDTO>()
1166
-
1167
- async findById(id: string): Promise<${e}ResponseDTO | null> {
1168
- return this.store.get(id) ?? null
1169
- }
1170
-
1171
- async findAll(): Promise<${e}ResponseDTO[]> {
1172
- return Array.from(this.store.values())
1173
- }
1174
-
1175
- async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
1176
- const all = Array.from(this.store.values())
1177
- const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
1178
- return { data, total: all.length }
1179
- }
1180
-
1181
- async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
1182
- const now = new Date().toISOString()
1183
- const entity: ${e}ResponseDTO = {
1184
- id: randomUUID(),
1185
- name: dto.name,
1186
- createdAt: now,
1187
- updatedAt: now,
1188
- }
1189
- this.store.set(entity.id, entity)
1190
- return entity
1191
- }
1192
-
1193
- async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
1194
- const existing = this.store.get(id)
1195
- if (!existing) throw HttpException.notFound('${e} not found')
1196
- const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
1197
- this.store.set(id, updated)
1198
- return updated
1199
- }
1200
-
1201
- async delete(id: string): Promise<void> {
1202
- if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
1203
- this.store.delete(id)
1204
- }
1205
- }
1206
- `}s(_,"generateInMemoryRepository");function G(r){let{pascal:e,kebab:t,repoType:o="",repoPrefix:n="../../domain/repositories",dtoPrefix:i="../../application/dtos"}=r,c=o.charAt(0).toUpperCase()+o.slice(1).replace(/-([a-z])/g,(d,a)=>a.toUpperCase());return`/**
1207
- * ${c} ${e} Repository
1208
- *
1209
- * Stub implementation for a custom '${o}' repository.
1210
- * Implements the repository interface using an in-memory Map as a placeholder.
1211
- *
1212
- * TODO: Replace the in-memory Map with your ${o} data-access logic.
1213
- * See I${e}Repository for the interface contract.
1214
- *
1215
- * @Repository() registers this class in the DI container as a singleton.
1216
- */
1217
- import { randomUUID } from 'node:crypto'
1218
- import { Repository, HttpException } from '@forinda/kickjs-core'
1219
- import type { ParsedQuery } from '@forinda/kickjs-http'
1220
- import type { I${e}Repository } from '${n}/${t}.repository'
1221
- import type { ${e}ResponseDTO } from '${i}/${t}-response.dto'
1222
- import type { Create${e}DTO } from '${i}/create-${t}.dto'
1223
- import type { Update${e}DTO } from '${i}/update-${t}.dto'
1224
-
1225
- @Repository()
1226
- export class ${c}${e}Repository implements I${e}Repository {
1227
- // TODO: Replace with your ${o} client/connection
1228
- private store = new Map<string, ${e}ResponseDTO>()
1229
-
1230
- async findById(id: string): Promise<${e}ResponseDTO | null> {
1231
- // TODO: Implement with ${o}
1232
- return this.store.get(id) ?? null
1233
- }
1234
-
1235
- async findAll(): Promise<${e}ResponseDTO[]> {
1236
- // TODO: Implement with ${o}
1237
- return Array.from(this.store.values())
1238
- }
1239
-
1240
- async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
1241
- // TODO: Implement with ${o}
1242
- const all = Array.from(this.store.values())
1243
- const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
1244
- return { data, total: all.length }
1245
- }
1246
-
1247
- async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
1248
- // TODO: Implement with ${o}
1249
- const now = new Date().toISOString()
1250
- const entity: ${e}ResponseDTO = {
1251
- id: randomUUID(),
1252
- name: dto.name,
1253
- createdAt: now,
1254
- updatedAt: now,
1255
- }
1256
- this.store.set(entity.id, entity)
1257
- return entity
1258
- }
1259
-
1260
- async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
1261
- // TODO: Implement with ${o}
1262
- const existing = this.store.get(id)
1263
- if (!existing) throw HttpException.notFound('${e} not found')
1264
- const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
1265
- this.store.set(id, updated)
1266
- return updated
1267
- }
1268
-
1269
- async delete(id: string): Promise<void> {
1270
- // TODO: Implement with ${o}
1271
- if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
1272
- this.store.delete(id)
1273
- }
1274
- }
1275
- `}s(G,"generateCustomRepository");function ue(r){let{pascal:e,kebab:t}=r;return`/**
1276
- * ${e} Domain Service
1277
- *
1278
- * Domain layer \u2014 contains business rules that don't belong to a single entity.
1279
- * Use this for cross-entity logic, validation rules, and domain invariants.
1280
- * Keep it free of HTTP/framework concerns.
1281
- */
1282
- import { Service, Inject, HttpException } from '@forinda/kickjs-core'
1283
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../repositories/${t}.repository'
1284
-
1285
- @Service()
1286
- export class ${e}DomainService {
1287
- constructor(
1288
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1289
- ) {}
1290
-
1291
- async ensureExists(id: string): Promise<void> {
1292
- const entity = await this.repo.findById(id)
1293
- if (!entity) {
1294
- throw HttpException.notFound('${e} not found')
1295
- }
1296
- }
1297
- }
1298
- `}s(ue,"generateDomainService");function fe(r){let{pascal:e,kebab:t}=r;return`/**
1299
- * ${e} Entity
1300
- *
1301
- * Domain layer \u2014 the core business object.
1302
- * Uses a private constructor with static factory methods (create, reconstitute)
1303
- * to enforce invariants. Properties are accessed via getters to maintain encapsulation.
1304
- *
1305
- * Patterns used:
1306
- * - Private constructor: prevents direct instantiation
1307
- * - create(): factory for new entities (generates ID, sets timestamps)
1308
- * - reconstitute(): factory for rebuilding from persistence (no side effects)
1309
- * - changeName(): mutation method that enforces business rules
1310
- */
1311
- import { ${e}Id } from '../value-objects/${t}-id.vo'
1312
-
1313
- interface ${e}Props {
1314
- id: ${e}Id
1315
- name: string
1316
- createdAt: Date
1317
- updatedAt: Date
1318
- }
1319
-
1320
- export class ${e} {
1321
- private constructor(private props: ${e}Props) {}
1322
-
1323
- static create(params: { name: string }): ${e} {
1324
- const now = new Date()
1325
- return new ${e}({
1326
- id: ${e}Id.create(),
1327
- name: params.name,
1328
- createdAt: now,
1329
- updatedAt: now,
1330
- })
1331
- }
1332
-
1333
- static reconstitute(props: ${e}Props): ${e} {
1334
- return new ${e}(props)
1335
- }
1336
-
1337
- get id(): ${e}Id {
1338
- return this.props.id
1339
- }
1340
- get name(): string {
1341
- return this.props.name
1342
- }
1343
- get createdAt(): Date {
1344
- return this.props.createdAt
1345
- }
1346
- get updatedAt(): Date {
1347
- return this.props.updatedAt
1348
- }
1349
-
1350
- changeName(name: string): void {
1351
- if (!name || name.trim().length === 0) {
1352
- throw new Error('Name cannot be empty')
1353
- }
1354
- this.props.name = name.trim()
1355
- this.props.updatedAt = new Date()
1356
- }
1357
-
1358
- toJSON() {
1359
- return {
1360
- id: this.props.id.toString(),
1361
- name: this.props.name,
1362
- createdAt: this.props.createdAt.toISOString(),
1363
- updatedAt: this.props.updatedAt.toISOString(),
1364
- }
1365
- }
1366
- }
1367
- `}s(fe,"generateEntity");function ge(r){let{pascal:e,kebab:t}=r;return`/**
1368
- * ${e} ID Value Object
1369
- *
1370
- * Domain layer \u2014 wraps a primitive ID with type safety and validation.
1371
- * Value objects are immutable and compared by value, not reference.
1372
- *
1373
- * ${e}Id.create() \u2014 generate a new UUID
1374
- * ${e}Id.from(id) \u2014 wrap an existing ID string (validates non-empty)
1375
- * id.equals(other) \u2014 compare two IDs by value
1376
- */
1377
- import { randomUUID } from 'node:crypto'
1378
-
1379
- export class ${e}Id {
1380
- private constructor(private readonly value: string) {}
1381
-
1382
- static create(): ${e}Id {
1383
- return new ${e}Id(randomUUID())
1384
- }
1385
-
1386
- static from(id: string): ${e}Id {
1387
- if (!id || id.trim().length === 0) {
1388
- throw new Error('${e}Id cannot be empty')
1389
- }
1390
- return new ${e}Id(id)
1391
- }
1392
-
1393
- toString(): string {
1394
- return this.value
1395
- }
1396
-
1397
- equals(other: ${e}Id): boolean {
1398
- return this.value === other.value
1399
- }
1400
- }
1401
- `}s(ge,"generateValueObject");function Q(r){let{pascal:e,kebab:t,plural:o=""}=r;return`import { describe, it, expect, beforeEach } from 'vitest'
1402
- import { Container } from '@forinda/kickjs-core'
1403
-
1404
- describe('${e}Controller', () => {
1405
- beforeEach(() => {
1406
- Container.reset()
1407
- })
1408
-
1409
- it('should be defined', () => {
1410
- expect(true).toBe(true)
1411
- })
1412
-
1413
- describe('POST /${o}', () => {
1414
- it('should create a new ${t}', async () => {
1415
- // TODO: Set up test module, call create endpoint, assert 201
1416
- expect(true).toBe(true)
1417
- })
1418
- })
1419
-
1420
- describe('GET /${o}', () => {
1421
- it('should return paginated ${o}', async () => {
1422
- // TODO: Set up test module, call list endpoint, assert { data, meta }
1423
- expect(true).toBe(true)
1424
- })
1425
- })
1426
-
1427
- describe('GET /${o}/:id', () => {
1428
- it('should return a ${t} by id', async () => {
1429
- // TODO: Create a ${t}, then fetch by id, assert match
1430
- expect(true).toBe(true)
1431
- })
1432
-
1433
- it('should return 404 for non-existent ${t}', async () => {
1434
- // TODO: Fetch non-existent id, assert 404
1435
- expect(true).toBe(true)
1436
- })
1437
- })
1438
-
1439
- describe('PUT /${o}/:id', () => {
1440
- it('should update an existing ${t}', async () => {
1441
- // TODO: Create, update, assert changes
1442
- expect(true).toBe(true)
1443
- })
1444
- })
1445
-
1446
- describe('DELETE /${o}/:id', () => {
1447
- it('should delete a ${t}', async () => {
1448
- // TODO: Create, delete, assert gone
1449
- expect(true).toBe(true)
1450
- })
1451
- })
1452
- })
1453
- `}s(Q,"generateControllerTest");function F(r){let{pascal:e,kebab:t,plural:o="",repoPrefix:n=`../infrastructure/repositories/in-memory-${t}.repository`}=r;return`import { describe, it, expect, beforeEach } from 'vitest'
1454
- import { InMemory${e}Repository } from '${n}'
1455
-
1456
- describe('InMemory${e}Repository', () => {
1457
- let repo: InMemory${e}Repository
1458
-
1459
- beforeEach(() => {
1460
- repo = new InMemory${e}Repository()
1461
- })
1462
-
1463
- it('should create and retrieve a ${t}', async () => {
1464
- const created = await repo.create({ name: 'Test ${e}' })
1465
- expect(created).toBeDefined()
1466
- expect(created.name).toBe('Test ${e}')
1467
- expect(created.id).toBeDefined()
1468
-
1469
- const found = await repo.findById(created.id)
1470
- expect(found).toEqual(created)
1471
- })
1472
-
1473
- it('should return null for non-existent id', async () => {
1474
- const found = await repo.findById('non-existent')
1475
- expect(found).toBeNull()
1476
- })
1477
-
1478
- it('should list all ${o}', async () => {
1479
- await repo.create({ name: '${e} 1' })
1480
- await repo.create({ name: '${e} 2' })
1481
-
1482
- const all = await repo.findAll()
1483
- expect(all).toHaveLength(2)
1484
- })
1485
-
1486
- it('should return paginated results', async () => {
1487
- await repo.create({ name: '${e} 1' })
1488
- await repo.create({ name: '${e} 2' })
1489
- await repo.create({ name: '${e} 3' })
1490
-
1491
- const result = await repo.findPaginated({
1492
- filters: [],
1493
- sort: [],
1494
- search: '',
1495
- pagination: { page: 1, limit: 2, offset: 0 },
1496
- })
1497
-
1498
- expect(result.data).toHaveLength(2)
1499
- expect(result.total).toBe(3)
1500
- })
1501
-
1502
- it('should update a ${t}', async () => {
1503
- const created = await repo.create({ name: 'Original' })
1504
- const updated = await repo.update(created.id, { name: 'Updated' })
1505
- expect(updated.name).toBe('Updated')
1506
- })
1507
-
1508
- it('should delete a ${t}', async () => {
1509
- const created = await repo.create({ name: 'To Delete' })
1510
- await repo.delete(created.id)
1511
- const found = await repo.findById(created.id)
1512
- expect(found).toBeNull()
1513
- })
1514
- })
1515
- `}s(F,"generateRepositoryTest");function $e(r){let{pascal:e,kebab:t}=r;return`import { Service, Inject, HttpException } from '@forinda/kickjs-core'
1516
- import type { ParsedQuery } from '@forinda/kickjs-http'
1517
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from './${t}.repository'
1518
- import type { ${e}ResponseDTO } from './dtos/${t}-response.dto'
1519
- import type { Create${e}DTO } from './dtos/create-${t}.dto'
1520
- import type { Update${e}DTO } from './dtos/update-${t}.dto'
1521
-
1522
- @Service()
1523
- export class ${e}Service {
1524
- constructor(
1525
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1526
- ) {}
1527
-
1528
- async findById(id: string): Promise<${e}ResponseDTO | null> {
1529
- return this.repo.findById(id)
1530
- }
1531
-
1532
- async findAll(): Promise<${e}ResponseDTO[]> {
1533
- return this.repo.findAll()
1534
- }
1535
-
1536
- async findPaginated(parsed: ParsedQuery) {
1537
- return this.repo.findPaginated(parsed)
1538
- }
1539
-
1540
- async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
1541
- return this.repo.create(dto)
1542
- }
1543
-
1544
- async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
1545
- return this.repo.update(id, dto)
1546
- }
1547
-
1548
- async delete(id: string): Promise<void> {
1549
- await this.repo.delete(id)
1550
- }
1551
- }
1552
- `}s($e,"generateRestService");function V(r){let{pascal:e}=r;return`import type { QueryFieldConfig } from '@forinda/kickjs-http'
1553
-
1554
- export const ${e.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
1555
- filterable: ['name'],
1556
- sortable: ['name', 'createdAt'],
1557
- searchable: ['name'],
1558
- }
1559
- `}s(V,"generateRestConstants");function ye(r){let{pascal:e,kebab:t,plural:o="",repo:n}=r,i={inmemory:`InMemory${e}Repository`,drizzle:`Drizzle${e}Repository`,prisma:`Prisma${e}Repository`},c={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`},d=i[n]??i.inmemory,a=c[n]??c.inmemory;return`/**
1560
- * ${e} Module \u2014 CQRS Pattern
1561
- *
1562
- * Separates read (queries) and write (commands) operations.
1563
- * Events are emitted after state changes and can be handled via
1564
- * WebSocket broadcasts, queue jobs, or ETL pipelines.
1565
- *
1566
- * Structure:
1567
- * commands/ \u2014 Write operations (create, update, delete)
1568
- * queries/ \u2014 Read operations (get, list)
1569
- * events/ \u2014 Domain events + handlers (WS broadcast, queue dispatch)
1570
- * dtos/ \u2014 Request/response schemas
1571
- */
1572
- import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
1573
- import { buildRoutes } from '@forinda/kickjs-http'
1574
- import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
1575
- import { ${d} } from './${a}.repository'
1576
- import { ${e}Controller } from './${t}.controller'
1577
-
1578
- // Eagerly load decorated classes
1579
- import.meta.glob(
1580
- [
1581
- './commands/**/*.ts',
1582
- './queries/**/*.ts',
1583
- './events/**/*.ts',
1584
- '!./**/*.test.ts',
1585
- ],
1586
- { eager: true },
1587
- )
1588
-
1589
- export class ${e}Module implements AppModule {
1590
- register(container: Container): void {
1591
- container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
1592
- container.resolve(${d}),
1593
- )
1594
- }
1595
-
1596
- routes(): ModuleRoutes {
1597
- return {
1598
- path: '/${o}',
1599
- router: buildRoutes(${e}Controller),
1600
- controller: ${e}Controller,
1601
- }
1602
- }
1603
- }
1604
- `}s(ye,"generateCqrsModuleIndex");function he(r){let{pascal:e,kebab:t,plural:o="",pluralPascal:n=""}=r;return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
1605
- import type { RequestContext } from '@forinda/kickjs-http'
1606
- import { ApiTags } from '@forinda/kickjs-swagger'
1607
- import { Create${e}Command } from './commands/create-${t}.command'
1608
- import { Update${e}Command } from './commands/update-${t}.command'
1609
- import { Delete${e}Command } from './commands/delete-${t}.command'
1610
- import { Get${e}Query } from './queries/get-${t}.query'
1611
- import { List${n}Query } from './queries/list-${o}.query'
1612
- import { create${e}Schema } from './dtos/create-${t}.dto'
1613
- import { update${e}Schema } from './dtos/update-${t}.dto'
1614
- import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
1615
-
1616
- @Controller()
1617
- export class ${e}Controller {
1618
- @Autowired() private create${e}Command!: Create${e}Command
1619
- @Autowired() private update${e}Command!: Update${e}Command
1620
- @Autowired() private delete${e}Command!: Delete${e}Command
1621
- @Autowired() private get${e}Query!: Get${e}Query
1622
- @Autowired() private list${n}Query!: List${n}Query
1623
-
1624
- @Get('/')
1625
- @ApiTags('${e}')
1626
- @ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
1627
- async list(ctx: RequestContext) {
1628
- return ctx.paginate(
1629
- (parsed) => this.list${n}Query.execute(parsed),
1630
- ${e.toUpperCase()}_QUERY_CONFIG,
1631
- )
1632
- }
1633
-
1634
- @Get('/:id')
1635
- @ApiTags('${e}')
1636
- async getById(ctx: RequestContext) {
1637
- const result = await this.get${e}Query.execute(ctx.params.id)
1638
- if (!result) return ctx.notFound('${e} not found')
1639
- ctx.json(result)
1640
- }
1641
-
1642
- @Post('/', { body: create${e}Schema, name: 'Create${e}' })
1643
- @ApiTags('${e}')
1644
- async create(ctx: RequestContext) {
1645
- const result = await this.create${e}Command.execute(ctx.body)
1646
- ctx.created(result)
1647
- }
1648
-
1649
- @Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
1650
- @ApiTags('${e}')
1651
- async update(ctx: RequestContext) {
1652
- const result = await this.update${e}Command.execute(ctx.params.id, ctx.body)
1653
- ctx.json(result)
1654
- }
1655
-
1656
- @Delete('/:id')
1657
- @ApiTags('${e}')
1658
- async remove(ctx: RequestContext) {
1659
- await this.delete${e}Command.execute(ctx.params.id)
1660
- ctx.noContent()
1661
- }
1662
- }
1663
- `}s(he,"generateCqrsController");function ke(r){let{pascal:e,kebab:t}=r;return[{file:`create-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1664
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
1665
- import type { Create${e}DTO } from '../dtos/create-${t}.dto'
1666
- import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
1667
- import { ${e}Events } from '../events/${t}.events'
1668
-
1669
- @Service()
1670
- export class Create${e}Command {
1671
- constructor(
1672
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1673
- @Inject(${e}Events) private readonly events: ${e}Events,
1674
- ) {}
1675
-
1676
- async execute(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
1677
- const result = await this.repo.create(dto)
1678
- this.events.emit('${t}.created', result)
1679
- return result
1680
- }
1681
- }
1682
- `},{file:`update-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1683
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
1684
- import type { Update${e}DTO } from '../dtos/update-${t}.dto'
1685
- import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
1686
- import { ${e}Events } from '../events/${t}.events'
1687
-
1688
- @Service()
1689
- export class Update${e}Command {
1690
- constructor(
1691
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1692
- @Inject(${e}Events) private readonly events: ${e}Events,
1693
- ) {}
1694
-
1695
- async execute(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
1696
- const result = await this.repo.update(id, dto)
1697
- this.events.emit('${t}.updated', result)
1698
- return result
1699
- }
1700
- }
1701
- `},{file:`delete-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1702
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
1703
- import { ${e}Events } from '../events/${t}.events'
1704
-
1705
- @Service()
1706
- export class Delete${e}Command {
1707
- constructor(
1708
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1709
- @Inject(${e}Events) private readonly events: ${e}Events,
1710
- ) {}
1711
-
1712
- async execute(id: string): Promise<void> {
1713
- await this.repo.delete(id)
1714
- this.events.emit('${t}.deleted', { id })
1715
- }
1716
- }
1717
- `}]}s(ke,"generateCqrsCommands");function we(r){let{pascal:e,kebab:t,plural:o="",pluralPascal:n=""}=r;return[{file:`get-${t}.query.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1718
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
1719
- import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
1720
-
1721
- @Service()
1722
- export class Get${e}Query {
1723
- constructor(
1724
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1725
- ) {}
1726
-
1727
- async execute(id: string): Promise<${e}ResponseDTO | null> {
1728
- return this.repo.findById(id)
1729
- }
1730
- }
1731
- `},{file:`list-${o}.query.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
1732
- import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
1733
- import type { ParsedQuery } from '@forinda/kickjs-http'
1734
-
1735
- @Service()
1736
- export class List${n}Query {
1737
- constructor(
1738
- @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
1739
- ) {}
1740
-
1741
- async execute(parsed: ParsedQuery) {
1742
- return this.repo.findPaginated(parsed)
1743
- }
1744
- }
1745
- `}]}s(we,"generateCqrsQueries");function ve(r){let{pascal:e,kebab:t}=r;return[{file:`${t}.events.ts`,content:`import { Service } from '@forinda/kickjs-core'
1746
- import { EventEmitter } from 'node:events'
1747
- import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
1748
-
1749
- /**
1750
- * ${e} domain event types.
1751
- *
1752
- * These events are emitted by commands after state changes.
1753
- * Subscribe to them in event handlers for side effects:
1754
- * - WebSocket broadcasts (real-time UI updates)
1755
- * - Queue jobs (async processing, ETL pipelines)
1756
- * - Audit logging
1757
- * - Cache invalidation
1758
- */
1759
- export interface ${e}EventMap {
1760
- '${t}.created': ${e}ResponseDTO
1761
- '${t}.updated': ${e}ResponseDTO
1762
- '${t}.deleted': { id: string }
1763
- }
1764
-
1765
- @Service()
1766
- export class ${e}Events {
1767
- private emitter = new EventEmitter()
1768
-
1769
- emit<K extends keyof ${e}EventMap>(event: K, data: ${e}EventMap[K]): void {
1770
- this.emitter.emit(event, data)
1771
- }
1772
-
1773
- on<K extends keyof ${e}EventMap>(event: K, handler: (data: ${e}EventMap[K]) => void): void {
1774
- this.emitter.on(event, handler)
1775
- }
1776
-
1777
- off<K extends keyof ${e}EventMap>(event: K, handler: (data: ${e}EventMap[K]) => void): void {
1778
- this.emitter.off(event, handler)
1779
- }
1780
- }
1781
- `},{file:`on-${t}-change.handler.ts`,content:`import { Service, Autowired } from '@forinda/kickjs-core'
1782
- import { ${e}Events } from './${t}.events'
1783
-
1784
- /**
1785
- * ${e} Change Event Handler
1786
- *
1787
- * Reacts to domain events emitted by commands.
1788
- * Wire up side effects here:
1789
- *
1790
- * 1. WebSocket broadcast \u2014 notify connected clients in real-time
1791
- * import { WsGateway } from '@forinda/kickjs-ws'
1792
- * this.ws.broadcast('${t}-channel', { event, data })
1793
- *
1794
- * 2. Queue dispatch \u2014 offload heavy processing to background workers
1795
- * import { QueueService } from '@forinda/kickjs-queue'
1796
- * this.queue.add('${t}-etl', { action: event, payload: data })
1797
- *
1798
- * 3. ETL pipeline \u2014 transform and load data to external systems
1799
- * await this.etlPipeline.process(data)
1800
- */
1801
- @Service()
1802
- export class On${e}ChangeHandler {
1803
- @Autowired() private events!: ${e}Events
1804
-
1805
- // Uncomment to inject WebSocket and Queue services:
1806
- // @Autowired() private ws!: WsGateway
1807
- // @Autowired() private queue!: QueueService
1808
-
1809
- onInit(): void {
1810
- this.events.on('${t}.created', (data) => {
1811
- console.log('[${e}] Created:', data.id)
1812
- // TODO: Broadcast via WebSocket
1813
- // this.ws.broadcast('${t}-channel', { event: '${t}.created', data })
1814
- // TODO: Dispatch to queue for async processing / ETL
1815
- // this.queue.add('${t}-etl', { action: 'create', payload: data })
1816
- })
1817
-
1818
- this.events.on('${t}.updated', (data) => {
1819
- console.log('[${e}] Updated:', data.id)
1820
- // TODO: Broadcast via WebSocket
1821
- // this.ws.broadcast('${t}-channel', { event: '${t}.updated', data })
1822
- })
1823
-
1824
- this.events.on('${t}.deleted', (data) => {
1825
- console.log('[${e}] Deleted:', data.id)
1826
- // TODO: Broadcast via WebSocket
1827
- // this.ws.broadcast('${t}-channel', { event: '${t}.deleted', data })
1828
- })
1829
- }
1830
- }
1831
- `}]}s(ve,"generateCqrsEvents");function L(r){let{pascal:e,kebab:t,repoPrefix:o="../../domain/repositories",dtoPrefix:n="../../application/dtos"}=r;return`/**
1832
- * Drizzle ${e} Repository
1833
- *
1834
- * Implements the repository interface using Drizzle ORM.
1835
- * Uses buildFromColumns() with Column objects for type-safe query building.
1836
- *
1837
- * TODO: Update the schema import to match your Drizzle schema file.
1838
- * TODO: Replace DRIZZLE_DB injection token with your actual database token.
1839
- *
1840
- * @Repository() registers this class in the DI container as a singleton.
1841
- */
1842
- import { eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc, count, sql } from 'drizzle-orm'
1843
- import { Repository, HttpException, Inject } from '@forinda/kickjs-core'
1844
- import { DRIZZLE_DB, DrizzleQueryAdapter } from '@forinda/kickjs-drizzle'
1845
- import type { ParsedQuery } from '@forinda/kickjs-http'
1846
- import type { I${e}Repository } from '${o}/${t}.repository'
1847
- import type { ${e}ResponseDTO } from '${n}/${t}-response.dto'
1848
- import type { Create${e}DTO } from '${n}/create-${t}.dto'
1849
- import type { Update${e}DTO } from '${n}/update-${t}.dto'
1850
- import { ${e.toUpperCase()}_QUERY_CONFIG } from '../../constants'
1851
-
1852
- // TODO: Import your Drizzle schema table \u2014 e.g.:
1853
- // import { ${t}s } from '@/db/schema'
1854
-
1855
- const queryAdapter = new DrizzleQueryAdapter({
1856
- eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc,
1857
- })
1858
-
1859
- @Repository()
1860
- export class Drizzle${e}Repository implements I${e}Repository {
1861
- constructor(@Inject(DRIZZLE_DB) private db: any) {}
1862
-
1863
- async findById(id: string): Promise<${e}ResponseDTO | null> {
1864
- // TODO: Implement with Drizzle
1865
- // const row = this.db.select().from(${t}s).where(eq(${t}s.id, id)).get()
1866
- // return row ?? null
1867
- throw new Error('Drizzle ${e} repository not yet implemented \u2014 update schema imports and queries')
1868
- }
1869
-
1870
- async findAll(): Promise<${e}ResponseDTO[]> {
1871
- // TODO: Implement with Drizzle
1872
- // return this.db.select().from(${t}s).all()
1873
- throw new Error('Drizzle ${e} repository not yet implemented')
1874
- }
1875
-
1876
- async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
1877
- // TODO: Use buildFromColumns() with your query config for type-safe filtering
1878
- // const query = queryAdapter.buildFromColumns(parsed, ${e.toUpperCase()}_QUERY_CONFIG)
1879
- //
1880
- // const data = this.db
1881
- // .select().from(${t}s).$dynamic()
1882
- // .where(query.where).orderBy(...query.orderBy)
1883
- // .limit(query.limit).offset(query.offset).all()
1884
- //
1885
- // const totalResult = this.db
1886
- // .select({ count: count() }).from(${t}s)
1887
- // .$dynamic().where(query.where).get()
1888
- //
1889
- // return { data, total: totalResult?.count ?? 0 }
1890
- throw new Error('Drizzle ${e} repository not yet implemented')
1891
- }
1892
-
1893
- async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
1894
- // TODO: Implement with Drizzle
1895
- // return this.db.insert(${t}s).values(dto).returning().get()
1896
- throw new Error('Drizzle ${e} repository not yet implemented')
1897
- }
1898
-
1899
- async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
1900
- // TODO: Implement with Drizzle
1901
- // const row = this.db.update(${t}s).set(dto).where(eq(${t}s.id, id)).returning().get()
1902
- // if (!row) throw HttpException.notFound('${e} not found')
1903
- // return row
1904
- throw new Error('Drizzle ${e} repository not yet implemented')
1905
- }
1906
-
1907
- async delete(id: string): Promise<void> {
1908
- // TODO: Implement with Drizzle
1909
- // this.db.delete(${t}s).where(eq(${t}s.id, id)).run()
1910
- throw new Error('Drizzle ${e} repository not yet implemented')
1911
- }
1912
- }
1913
- `}s(L,"generateDrizzleRepository");function Ce(r){let{pascal:e,kebab:t}=r;return`import type { DrizzleQueryParamsConfig } from '@forinda/kickjs-drizzle'
1914
- // TODO: Import your schema table and reference actual columns for type safety
1915
- // import { ${t}s } from '@/db/schema'
1916
-
1917
- export const ${e.toUpperCase()}_QUERY_CONFIG: DrizzleQueryParamsConfig = {
1918
- columns: {
1919
- // Replace with actual Drizzle Column references for type-safe filtering:
1920
- // name: ${t}s.name,
1921
- // status: ${t}s.status,
1922
- },
1923
- sortable: {
1924
- // name: ${t}s.name,
1925
- // createdAt: ${t}s.createdAt,
1926
- },
1927
- searchColumns: [
1928
- // ${t}s.name,
1929
- ],
1930
- }
1931
- `}s(Ce,"generateDrizzleConstants");function N(r){let{pascal:e,kebab:t,repoPrefix:o="../../domain/repositories",dtoPrefix:n="../../application/dtos"}=r,i=t.replace(/-([a-z])/g,(c,d)=>d.toUpperCase());return`/**
1932
- * Prisma ${e} Repository
1933
- *
1934
- * Implements the repository interface using Prisma Client.
1935
- * Requires a PrismaClient instance injected via the DI container.
1936
- *
1937
- * Ensure your Prisma schema has a '${e}' model defined.
1938
- *
1939
- * For full Prisma field-level type safety, replace PrismaModelDelegate with your PrismaClient:
1940
- * @Inject(PRISMA_CLIENT) private prisma!: PrismaClient
1941
- *
1942
- * @Repository() registers this class in the DI container as a singleton.
1943
- */
1944
- import { Repository, HttpException, Inject } from '@forinda/kickjs-core'
1945
- import { PRISMA_CLIENT, type PrismaModelDelegate } from '@forinda/kickjs-prisma'
1946
- import type { ParsedQuery } from '@forinda/kickjs-http'
1947
- import type { I${e}Repository } from '${o}/${t}.repository'
1948
- import type { ${e}ResponseDTO } from '${n}/${t}-response.dto'
1949
- import type { Create${e}DTO } from '${n}/create-${t}.dto'
1950
- import type { Update${e}DTO } from '${n}/update-${t}.dto'
1951
-
1952
- @Repository()
1953
- export class Prisma${e}Repository implements I${e}Repository {
1954
- @Inject(PRISMA_CLIENT) private prisma!: { ${i}: PrismaModelDelegate }
1955
-
1956
- async findById(id: string): Promise<${e}ResponseDTO | null> {
1957
- return this.prisma.${i}.findUnique({ where: { id } }) as Promise<${e}ResponseDTO | null>
1958
- }
1959
-
1960
- async findAll(): Promise<${e}ResponseDTO[]> {
1961
- return this.prisma.${i}.findMany() as Promise<${e}ResponseDTO[]>
1962
- }
1963
-
1964
- async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
1965
- const [data, total] = await Promise.all([
1966
- this.prisma.${i}.findMany({
1967
- skip: parsed.pagination.offset,
1968
- take: parsed.pagination.limit,
1969
- }) as Promise<${e}ResponseDTO[]>,
1970
- this.prisma.${i}.count(),
1971
- ])
1972
- return { data, total }
1973
- }
1974
-
1975
- async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
1976
- return this.prisma.${i}.create({ data: dto as Record<string, unknown> }) as Promise<${e}ResponseDTO>
1977
- }
1978
-
1979
- async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
1980
- const existing = await this.prisma.${i}.findUnique({ where: { id } })
1981
- if (!existing) throw HttpException.notFound('${e} not found')
1982
- return this.prisma.${i}.update({ where: { id }, data: dto as Record<string, unknown> }) as Promise<${e}ResponseDTO>
1983
- }
1984
-
1985
- async delete(id: string): Promise<void> {
1986
- await this.prisma.${i}.deleteMany({ where: { id } })
1987
- }
1988
- }
1989
- `}s(N,"generatePrismaRepository");async function xe(r){let{pascal:e,kebab:t,plural:o,write:n}=r;await n("index.ts",ce({pascal:e,kebab:t,plural:o})),await n(`${t}.controller.ts`,`import { Controller, Get } from '@forinda/kickjs-core'
1990
- import type { RequestContext } from '@forinda/kickjs-http'
1991
-
1992
- @Controller()
1993
- export class ${e}Controller {
1994
- @Get('/')
1995
- async list(ctx: RequestContext) {
1996
- ctx.json({ message: '${e} list' })
1997
- }
1998
- }
1999
- `)}s(xe,"generateMinimalFiles");async function Re(r){let{pascal:e,kebab:t,plural:o,pluralPascal:n,repo:i,noTests:c,prismaClientPath:d,write:a}=r;await a("index.ts",ae({pascal:e,kebab:t,plural:o,repo:i})),await a(`${t}.constants.ts`,V({pascal:e,kebab:t})),await a(`${t}.controller.ts`,pe({pascal:e,kebab:t,plural:o,pluralPascal:n})),await a(`${t}.service.ts`,$e({pascal:e,kebab:t})),await a(`dtos/create-${t}.dto.ts`,U({pascal:e,kebab:t})),await a(`dtos/update-${t}.dto.ts`,z({pascal:e,kebab:t})),await a(`dtos/${t}-response.dto.ts`,M({pascal:e,kebab:t})),await a(`${t}.repository.ts`,q({pascal:e,kebab:t,dtoPrefix:"./dtos"}));let p={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`},l={inmemory:s(()=>_({pascal:e,kebab:t,repoPrefix:".",dtoPrefix:"./dtos"}),"inmemory"),drizzle:s(()=>L({pascal:e,kebab:t,repoPrefix:".",dtoPrefix:"./dtos"}),"drizzle"),prisma:s(()=>N({pascal:e,kebab:t,repoPrefix:".",dtoPrefix:"./dtos",prismaClientPath:d}),"prisma")},u=p[i]??`${f(i)}-${t}`,g=l[i]??(()=>G({pascal:e,kebab:t,repoType:i,repoPrefix:".",dtoPrefix:"./dtos"}));await a(`${u}.repository.ts`,g()),c||(await a(`__tests__/${t}.controller.test.ts`,Q({pascal:e,kebab:t,plural:o})),await a(`__tests__/${t}.repository.test.ts`,F({pascal:e,kebab:t,plural:o,repoPrefix:`../${p.inmemory??`in-memory-${t}`}.repository`})))}s(Re,"generateRestFiles");async function De(r){let{pascal:e,kebab:t,plural:o,pluralPascal:n,repo:i,noTests:c,prismaClientPath:d,write:a}=r;await a("index.ts",ye({pascal:e,kebab:t,plural:o,repo:i})),await a(`${t}.constants.ts`,V({pascal:e,kebab:t})),await a(`${t}.controller.ts`,he({pascal:e,kebab:t,plural:o,pluralPascal:n})),await a(`dtos/create-${t}.dto.ts`,U({pascal:e,kebab:t})),await a(`dtos/update-${t}.dto.ts`,z({pascal:e,kebab:t})),await a(`dtos/${t}-response.dto.ts`,M({pascal:e,kebab:t}));let p=ke({pascal:e,kebab:t});for(let C of p)await a(`commands/${C.file}`,C.content);let l=we({pascal:e,kebab:t,plural:o,pluralPascal:n});for(let C of l)await a(`queries/${C.file}`,C.content);let u=ve({pascal:e,kebab:t});for(let C of u)await a(`events/${C.file}`,C.content);await a(`${t}.repository.ts`,q({pascal:e,kebab:t,dtoPrefix:"./dtos"}));let g={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`},k={inmemory:s(()=>_({pascal:e,kebab:t,repoPrefix:".",dtoPrefix:"./dtos"}),"inmemory"),drizzle:s(()=>L({pascal:e,kebab:t,repoPrefix:".",dtoPrefix:"./dtos"}),"drizzle"),prisma:s(()=>N({pascal:e,kebab:t,repoPrefix:".",dtoPrefix:"./dtos",prismaClientPath:d}),"prisma")},v=g[i]??`${f(i)}-${t}`,y=k[i]??(()=>G({pascal:e,kebab:t,repoType:i,repoPrefix:".",dtoPrefix:"./dtos"}));await a(`${v}.repository.ts`,y()),c||(await a(`__tests__/${t}.controller.test.ts`,Q({pascal:e,kebab:t,plural:o})),await a(`__tests__/${t}.repository.test.ts`,F({pascal:e,kebab:t,plural:o,repoPrefix:`../${g.inmemory??`in-memory-${t}`}.repository`})))}s(De,"generateCqrsFiles");async function be(r){let{pascal:e,kebab:t,plural:o,pluralPascal:n,repo:i,noEntity:c,noTests:d,prismaClientPath:a,write:p}=r;await p("index.ts",ne({pascal:e,kebab:t,plural:o,repo:i})),await p("constants.ts",i==="drizzle"?Ce({pascal:e,kebab:t}):le({pascal:e,kebab:t})),await p(`presentation/${t}.controller.ts`,de({pascal:e,kebab:t,plural:o,pluralPascal:n})),await p(`application/dtos/create-${t}.dto.ts`,U({pascal:e,kebab:t})),await p(`application/dtos/update-${t}.dto.ts`,z({pascal:e,kebab:t})),await p(`application/dtos/${t}-response.dto.ts`,M({pascal:e,kebab:t}));let l=me({pascal:e,kebab:t,plural:o,pluralPascal:n});for(let y of l)await p(`application/use-cases/${y.file}`,y.content);await p(`domain/repositories/${t}.repository.ts`,q({pascal:e,kebab:t})),await p(`domain/services/${t}-domain.service.ts`,ue({pascal:e,kebab:t}));let u={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`},g={inmemory:s(()=>_({pascal:e,kebab:t}),"inmemory"),drizzle:s(()=>L({pascal:e,kebab:t}),"drizzle"),prisma:s(()=>N({pascal:e,kebab:t,prismaClientPath:a}),"prisma")},k=u[i]??`${f(i)}-${t}`,v=g[i]??(()=>G({pascal:e,kebab:t,repoType:i}));await p(`infrastructure/repositories/${k}.repository.ts`,v()),c||(await p(`domain/entities/${t}.entity.ts`,fe({pascal:e,kebab:t})),await p(`domain/value-objects/${t}-id.vo.ts`,ge({pascal:e,kebab:t}))),d||(await p(`__tests__/${t}.controller.test.ts`,Q({pascal:e,kebab:t,plural:o})),await p(`__tests__/${t}.repository.test.ts`,F({pascal:e,kebab:t,plural:o})))}s(be,"generateDddFiles");function ot(r){return r?typeof r=="string"?r:r.name:"inmemory"}s(ot,"resolveRepoType");function pr(r){let e=ar({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question(r,o=>{e.close(),t(o.trim().toLowerCase())})})}s(pr,"promptUser");async function it(r){let{name:e,modulesDir:t,noEntity:o,noTests:n,repo:i="inmemory",force:c,dryRun:d}=r,a=r.pluralize!==!1,p=r.pattern??"ddd";r.minimal&&(p="minimal");let l=f(e),u=$(e),g=a?T(l):l,k=a?re(u):u,v=Pe(t,g),y=[],C=c??!1,B=s(async(H,Lt)=>{let ee=Pe(v,H);if(d){y.push(ee);return}if(!C&&await A(ee)){let Ae=await pr(` File already exists: ${H}
2000
- Overwrite? (y/n/a = yes/no/all) `);if(Ae==="a")C=!0;else if(Ae!=="y"){console.log(` Skipped: ${H}`);return}}await m(ee,Lt),y.push(ee)},"write"),K={kebab:l,pascal:u,plural:g,pluralPascal:k,moduleDir:v,repo:i,noEntity:o??!1,noTests:n??!1,prismaClientPath:r.prismaClientPath??"@prisma/client",write:B,files:y};switch(p){case"minimal":await xe(K);break;case"rest":await Re(K);break;case"cqrs":await De(K);break;default:await be(K);break}return d||await lr(t,u,g),y}s(it,"generateModule");async function lr(r,e,t){let o=Pe(r,"index.ts");if(!await A(o)){await m(o,`import type { AppModuleClass } from '@forinda/kickjs-core'
2001
- import { ${e}Module } from './${t}'
2002
-
2003
- export const modules: AppModuleClass[] = [${e}Module]
2004
- `);return}let i=await cr(o,"utf-8"),c=`import { ${e}Module } from './${t}'`;if(!i.includes(`${e}Module`)){let d=i.lastIndexOf("import ");if(d!==-1){let a=i.indexOf(`
2005
- `,d);i=i.slice(0,a+1)+c+`
2006
- `+i.slice(a+1)}else i=c+`
2007
- `+i;i=i.replace(/(=\s*\[)([\s\S]*?)(])/,(a,p,l,u)=>{let g=l.trim();if(!g)return`${p}${e}Module${u}`;let k=g.endsWith(",")?"":",";return`${p}${l.trimEnd()}${k} ${e}Module${u}`})}await dr(o,i,"utf-8")}s(lr,"autoRegisterModule");import{join as mr}from"path";async function st(r){let{name:e,outDir:t}=r,o=f(e),n=$(e),i=[],c=mr(t,`${o}.adapter.ts`);return await m(c,`import type { Express } from 'express'
2008
- import type { AppAdapter, AdapterMiddleware, Container } from '@forinda/kickjs-core'
2009
-
2010
- export interface ${n}AdapterOptions {
2011
- // Add your adapter configuration here
2012
- }
2013
-
2014
- /**
2015
- * ${n} adapter.
2016
- *
2017
- * Hooks into the Application lifecycle to add middleware, routes,
2018
- * or external service connections.
2019
- *
2020
- * Usage:
2021
- * bootstrap({
2022
- * adapters: [new ${n}Adapter({ ... })],
2023
- * })
2024
- */
2025
- export class ${n}Adapter implements AppAdapter {
2026
- name = '${n}Adapter'
2027
-
2028
- constructor(private options: ${n}AdapterOptions = {}) {}
2029
-
2030
- /**
2031
- * Return middleware entries that the Application will mount.
2032
- * Use \`phase\` to control where in the pipeline they run:
2033
- * 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'
2034
- */
2035
- middleware(): AdapterMiddleware[] {
2036
- return [
2037
- // Example: add a custom header to all responses
2038
- // {
2039
- // phase: 'beforeGlobal',
2040
- // handler: (_req: any, res: any, next: any) => {
2041
- // res.setHeader('X-${n}', 'true')
2042
- // next()
2043
- // },
2044
- // },
2045
- // Example: scope middleware to a specific path
2046
- // {
2047
- // phase: 'beforeRoutes',
2048
- // path: '/api/v1/admin',
2049
- // handler: myAdminMiddleware(),
2050
- // },
2051
- ]
2052
- }
2053
-
2054
- /**
2055
- * Called before global middleware.
2056
- * Use this to mount routes that bypass the middleware stack
2057
- * (health checks, docs UI, static assets).
2058
- */
2059
- beforeMount(app: Express, container: Container): void {
2060
- // Example: mount a status route
2061
- // app.get('/${o}/status', (_req, res) => {
2062
- // res.json({ status: 'ok' })
2063
- // })
2064
- }
2065
-
2066
- /**
2067
- * Called after modules and routes are registered, before the server starts.
2068
- * Use this for late-stage DI registrations or config validation.
2069
- */
2070
- beforeStart(app: Express, container: Container): void {
2071
- // Example: register a service in the DI container
2072
- // container.registerInstance(MY_TOKEN, new MyService(this.options))
2073
- }
2074
-
2075
- /**
2076
- * Called after the HTTP server is listening.
2077
- * Use this to attach to the raw http.Server (Socket.IO, gRPC, etc).
2078
- */
2079
- afterStart(server: any, container: Container): void {
2080
- // Example: attach Socket.IO
2081
- // const io = new Server(server)
2082
- // container.registerInstance(SOCKET_IO, io)
2083
- }
2084
-
2085
- /**
2086
- * Called on graceful shutdown. Clean up connections.
2087
- */
2088
- async shutdown(): Promise<void> {
2089
- // Example: close a connection pool
2090
- // await this.pool.end()
2091
- }
2092
- }
2093
- `),i.push(c),i}s(st,"generateAdapter");import{join as $r}from"path";import{resolve as Te,join as nt}from"path";var ur={controller:"presentation",service:"domain/services",dto:"application/dtos",guard:"presentation/guards",middleware:"middleware"},fr={controller:"",service:"",dto:"dtos",guard:"guards",middleware:"middleware"},gr={controller:"",service:"",dto:"dtos",guard:"guards",middleware:"middleware",command:"commands",query:"queries",event:"events"};function O(r){let{type:e,outDir:t,moduleName:o,modulesDir:n="src/modules",defaultDir:i,pattern:c="ddd"}=r;if(t)return Te(t);if(o){let d=c==="ddd"?ur:c==="cqrs"?gr:fr,a=f(o),p=T(a),l=d[e]??"",u=nt(n,p);return Te(l?nt(u,l):u)}return Te(i)}s(O,"resolveOutDir");async function at(r){let{name:e,moduleName:t,modulesDir:o,pattern:n}=r,i=O({type:"middleware",outDir:r.outDir,moduleName:t,modulesDir:o,defaultDir:"src/middleware",pattern:n}),c=f(e),d=R(e),a=[],p=$r(i,`${c}.middleware.ts`);return await m(p,`import type { Request, Response, NextFunction } from 'express'
2094
-
2095
- export interface ${$(e)}Options {
2096
- // Add configuration options here
2097
- }
2098
-
2099
- /**
2100
- * ${$(e)} middleware.
2101
- *
2102
- * Usage in bootstrap:
2103
- * middleware: [${d}()]
2104
- *
2105
- * Usage with adapter:
2106
- * middleware() { return [{ handler: ${d}(), phase: 'afterGlobal' }] }
2107
- *
2108
- * Usage with @Middleware decorator:
2109
- * @Middleware(${d}())
2110
- */
2111
- export function ${d}(options: ${$(e)}Options = {}) {
2112
- return (req: Request, res: Response, next: NextFunction) => {
2113
- // Implement your middleware logic here
2114
- next()
2115
- }
2116
- }
2117
- `),a.push(p),a}s(at,"generateMiddleware");import{join as yr}from"path";async function ct(r){let{name:e,moduleName:t,modulesDir:o,pattern:n}=r,i=O({type:"guard",outDir:r.outDir,moduleName:t,modulesDir:o,defaultDir:"src/guards",pattern:n}),c=f(e),d=R(e),a=$(e),p=[],l=yr(i,`${c}.guard.ts`);return await m(l,`import { Container, HttpException } from '@forinda/kickjs-core'
2118
- import type { RequestContext } from '@forinda/kickjs-http'
2119
-
2120
- /**
2121
- * ${a} guard.
2122
- *
2123
- * Guards protect routes by checking conditions before the handler runs.
2124
- * Return early with an error response to block access.
2125
- *
2126
- * Usage:
2127
- * @Middleware(${d}Guard)
2128
- * @Get('/protected')
2129
- * async handler(ctx: RequestContext) { ... }
2130
- */
2131
- export async function ${d}Guard(ctx: RequestContext, next: () => void): Promise<void> {
2132
- // Example: check for an authorization header
2133
- const header = ctx.headers.authorization
2134
- if (!header?.startsWith('Bearer ')) {
2135
- ctx.res.status(401).json({ message: 'Missing or invalid authorization header' })
2136
- return
2137
- }
2138
-
2139
- const token = header.slice(7)
2140
-
2141
- try {
2142
- // Verify the token using a service from the DI container
2143
- // const container = Container.getInstance()
2144
- // const authService = container.resolve(AuthService)
2145
- // const payload = authService.verifyToken(token)
2146
- // ctx.set('auth', payload)
2147
-
2148
- next()
2149
- } catch {
2150
- ctx.res.status(401).json({ message: 'Invalid or expired token' })
2151
- }
2152
- }
2153
- `),p.push(l),p}s(ct,"generateGuard");import{join as hr}from"path";async function dt(r){let{name:e,moduleName:t,modulesDir:o,pattern:n}=r,i=O({type:"service",outDir:r.outDir,moduleName:t,modulesDir:o,defaultDir:"src/services",pattern:n}),c=f(e),d=$(e),a=[],p=hr(i,`${c}.service.ts`);return await m(p,`import { Service } from '@forinda/kickjs-core'
2154
-
2155
- @Service()
2156
- export class ${d}Service {
2157
- // Inject dependencies via constructor
2158
- // constructor(
2159
- // @Inject(MY_REPO) private readonly repo: IMyRepository,
2160
- // ) {}
2161
- }
2162
- `),a.push(p),a}s(dt,"generateService");import{join as kr}from"path";async function pt(r){let{name:e,moduleName:t,modulesDir:o,pattern:n}=r,i=O({type:"controller",outDir:r.outDir,moduleName:t,modulesDir:o,defaultDir:"src/controllers",pattern:n}),c=f(e),d=$(e),a=[],p=kr(i,`${c}.controller.ts`);return await m(p,`import { Controller, Get, Post, Autowired } from '@forinda/kickjs-core'
2163
- import type { RequestContext } from '@forinda/kickjs-http'
2164
-
2165
- @Controller()
2166
- export class ${d}Controller {
2167
- // @Autowired() private myService!: MyService
2168
-
2169
- @Get('/')
2170
- async list(ctx: RequestContext) {
2171
- ctx.json({ message: '${d} list' })
2172
- }
2173
-
2174
- @Post('/')
2175
- async create(ctx: RequestContext) {
2176
- ctx.created({ message: '${d} created', data: ctx.body })
2177
- }
2178
- }
2179
- `),a.push(p),a}s(pt,"generateController");import{join as wr}from"path";async function lt(r){let{name:e,moduleName:t,modulesDir:o,pattern:n}=r,i=O({type:"dto",outDir:r.outDir,moduleName:t,modulesDir:o,defaultDir:"src/dtos",pattern:n}),c=f(e),d=$(e),a=R(e),p=[],l=wr(i,`${c}.dto.ts`);return await m(l,`import { z } from 'zod'
2180
-
2181
- export const ${a}Schema = z.object({
2182
- // Define your schema fields here
2183
- name: z.string().min(1).max(200),
2184
- })
2185
-
2186
- export type ${d}DTO = z.infer<typeof ${a}Schema>
2187
- `),p.push(l),p}s(lt,"generateDto");import{join as vr}from"path";import{existsSync as Cr}from"fs";import{createInterface as xr}from"readline";async function Rr(r){let e=xr({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question(` ${r} (y/N) `,o=>{e.close(),t(o.trim().toLowerCase()==="y")})})}s(Rr,"confirm");async function mt(r){let e=vr(r.outDir,"kick.config.ts"),t=r.modulesDir??"src/modules",o=r.defaultRepo??"inmemory";return Cr(e)&&!r.force&&!await Rr("kick.config.ts already exists. Overwrite?")?(console.log(`
2188
- Skipped \u2014 existing kick.config.ts preserved.`),[]):(await m(e,`import { defineConfig } from '@forinda/kickjs-cli'
2
+ import { _ as E, a as ee, b as P, c as te, d as oe, f as re, g as S, h as L, i as ne, l as ie, m as se, n as w, o as ae, p as _, r as R, s as ce, u as de, v as M, y as $ } from "./config-C4XJLiRC.js";
3
+ import { basename as pe, dirname as le, join as g, resolve as m } from "node:path";
4
+ import { createInterface as N } from "node:readline";
5
+ import { readFile as B, rm as me, writeFile as W } from "node:fs/promises";
6
+ import { execSync as q, fork as ue } from "node:child_process";
7
+ import { cpSync as fe, existsSync as j, mkdirSync as ge, readFileSync as $e, readdirSync as ye, rmSync as he } from "node:fs";
8
+ import { fileURLToPath as we, pathToFileURL as ke } from "node:url";
9
+ import { Command as ve } from "commander";
10
+ import { arch as De, platform as Re, release as xe } from "node:os";
11
+ function U(e, t) {
12
+ const r = N({
13
+ input: process.stdin,
14
+ output: process.stdout
15
+ }), o = t ? ` (${t})` : "";
16
+ return new Promise((n) => {
17
+ r.question(` ${e}${o}: `, (i) => {
18
+ r.close(), n(i.trim() || t || "");
19
+ });
20
+ });
21
+ }
22
+ async function A(e, t, r = 0) {
23
+ console.log(` ${e}`);
24
+ for (let n = 0; n < t.length; n++) console.log(` ${n === r ? ">" : " "} ${n + 1}. ${t[n]}`);
25
+ const o = await U("Choose", String(r + 1));
26
+ return t[parseInt(o, 10) - 1] ?? t[r];
27
+ }
28
+ async function z(e, t = !0) {
29
+ const r = await U(`${e} (${t ? "Y/n" : "y/N"})`);
30
+ return r ? r.toLowerCase().startsWith("y") : t;
31
+ }
32
+ function Ce(e) {
33
+ e.command("new [name]").alias("init").description('Create a new KickJS project (use "." for current directory)').option("-d, --directory <dir>", "Target directory (defaults to project name)").option("--pm <manager>", "Package manager: pnpm | npm | yarn").option("--git", "Initialize git repository").option("--no-git", "Skip git initialization").option("--install", "Install dependencies after scaffolding").option("--no-install", "Skip dependency installation").option("-f, --force", "Remove existing files without prompting").option("-t, --template <type>", "Project template: rest | graphql | ddd | cqrs | minimal").option("-r, --repo <type>", "Default repository: prisma | drizzle | inmemory | custom").action(async (t, r) => {
34
+ console.log(), t || (t = await U("Project name", "my-api"));
35
+ let o;
36
+ if (t === "." ? (o = m("."), t = pe(o)) : o = m(r.directory || t), j(o)) {
37
+ const d = ye(o);
38
+ if (d.length > 0) {
39
+ if (r.force) console.log(` Clearing existing files in ${o}...
40
+ `);
41
+ else {
42
+ console.log(` Directory "${t}" is not empty:`);
43
+ const p = d.slice(0, 5);
44
+ for (const l of p) console.log(` - ${l}`);
45
+ if (d.length > 5 && console.log(` ... and ${d.length - 5} more`), console.log(), !await z("Remove all existing files and proceed?", !1)) {
46
+ console.log(` Aborted.
47
+ `);
48
+ return;
49
+ }
50
+ }
51
+ for (const p of d) he(m(o, p), {
52
+ recursive: !0,
53
+ force: !0
54
+ });
55
+ }
56
+ }
57
+ let n = r.template;
58
+ n || (n = await A("Project template:", [
59
+ "REST API (Express + Swagger)",
60
+ "GraphQL API (GraphQL + GraphiQL)",
61
+ "DDD (Domain-Driven Design modules)",
62
+ "CQRS (Commands, Queries, Events + WS/Queue)",
63
+ "Minimal (bare Express)"
64
+ ], 0), n = {
65
+ "REST API (Express + Swagger)": "rest",
66
+ "GraphQL API (GraphQL + GraphiQL)": "graphql",
67
+ "DDD (Domain-Driven Design modules)": "ddd",
68
+ "CQRS (Commands, Queries, Events + WS/Queue)": "cqrs",
69
+ "Minimal (bare Express)": "minimal"
70
+ }[n] ?? "rest");
71
+ let i = r.pm;
72
+ i || (i = await A("Package manager:", [
73
+ "pnpm",
74
+ "npm",
75
+ "yarn"
76
+ ], 0));
77
+ let s = r.repo;
78
+ if (!s) {
79
+ const d = await A("Default repository/ORM:", [
80
+ "Prisma",
81
+ "Drizzle",
82
+ "In-Memory",
83
+ "Custom (specify later)"
84
+ ], 0);
85
+ s = {
86
+ Prisma: "prisma",
87
+ Drizzle: "drizzle",
88
+ "In-Memory": "inmemory",
89
+ "Custom (specify later)": "custom"
90
+ }[d] ?? "inmemory", s === "custom" && (s = await U("Custom repository name", "custom"));
91
+ }
92
+ let a;
93
+ r.git === void 0 ? a = await z("Initialize git repository?", !0) : a = r.git;
94
+ let c;
95
+ r.install === void 0 ? c = await z("Install dependencies?", !0) : c = r.install, await ne({
96
+ name: t,
97
+ directory: o,
98
+ packageManager: i,
99
+ initGit: a,
100
+ installDeps: c,
101
+ template: n,
102
+ defaultRepo: s
103
+ });
104
+ });
105
+ }
106
+ async function je(e) {
107
+ const t = N({
108
+ input: process.stdin,
109
+ output: process.stdout
110
+ });
111
+ return new Promise((r) => {
112
+ t.question(` ${e} (y/N) `, (o) => {
113
+ t.close(), r(o.trim().toLowerCase() === "y");
114
+ });
115
+ });
116
+ }
117
+ async function Se(e) {
118
+ const t = g(e.outDir, "kick.config.ts"), r = e.modulesDir ?? "src/modules", o = e.defaultRepo ?? "inmemory";
119
+ return j(t) && !e.force && !await je("kick.config.ts already exists. Overwrite?") ? (console.log(`
120
+ Skipped existing kick.config.ts preserved.`), []) : (await P(t, `import { defineConfig } from '@forinda/kickjs-cli'
2189
121
 
2190
122
  export default defineConfig({
2191
- modulesDir: '${t}',
123
+ modulesDir: '${r}',
2192
124
  defaultRepo: '${o}',
2193
125
 
2194
126
  commands: [
@@ -2215,17 +147,24 @@ export default defineConfig({
2215
147
  },
2216
148
  ],
2217
149
  })
2218
- `),[e])}s(mt,"generateConfig");import{join as Dr}from"path";async function ut(r){let{name:e,outDir:t}=r,o=$(e),n=f(e),i=R(e),c=[],d=s(async(a,p)=>{let l=Dr(t,a);await m(l,p),c.push(l)},"write");return await d(`${n}.resolver.ts`,`import { Service } from '@forinda/kickjs-core'
150
+ `), [t]);
151
+ }
152
+ async function Pe(e) {
153
+ const { name: t, outDir: r } = e, o = E(t), n = S(t), i = L(t), s = [], a = async (c, d) => {
154
+ const p = g(r, c);
155
+ await P(p, d), s.push(p);
156
+ };
157
+ return await a(`${n}.resolver.ts`, `import { Service } from '@forinda/kickjs-core'
2219
158
  import { Resolver, Query, Mutation, Arg } from '@forinda/kickjs-graphql'
2220
159
 
2221
160
  /**
2222
161
  * ${o} GraphQL Resolver
2223
162
  *
2224
163
  * Decorators:
2225
- * @Resolver(typeName?) \u2014 marks this class as a GraphQL resolver
2226
- * @Query(name?, { returnType?, description? }) \u2014 defines a query field
2227
- * @Mutation(name?, { returnType?, description? }) \u2014 defines a mutation field
2228
- * @Arg(name, type?) \u2014 marks a method parameter as a GraphQL argument
164
+ * @Resolver(typeName?) marks this class as a GraphQL resolver
165
+ * @Query(name?, { returnType?, description? }) defines a query field
166
+ * @Mutation(name?, { returnType?, description? }) defines a mutation field
167
+ * @Arg(name, type?) marks a method parameter as a GraphQL argument
2229
168
  */
2230
169
  @Service()
2231
170
  @Resolver('${o}')
@@ -2264,7 +203,7 @@ export class ${o}Resolver {
2264
203
  return true
2265
204
  }
2266
205
  }
2267
- `),await d(`${n}.typedefs.ts`,`/**
206
+ `), await a(`${n}.typedefs.ts`, `/**
2268
207
  * ${o} GraphQL type definitions.
2269
208
  * Pass to GraphQLAdapter's typeDefs option to register custom types.
2270
209
  */
@@ -2274,23 +213,30 @@ export const ${i}TypeDefs = \`
2274
213
  name: String!
2275
214
  }
2276
215
  \`
2277
- `),c}s(ut,"generateResolver");import{join as br}from"path";async function ft(r){let{name:e,outDir:t}=r,o=$(e),n=f(e),i=R(e),c=r.queue??`${n}-queue`,d=[];return await s(async(p,l)=>{let u=br(t,p);await m(u,l),d.push(u)},"write")(`${n}.job.ts`,`import { Inject } from '@forinda/kickjs-core'
216
+ `), s;
217
+ }
218
+ async function Ie(e) {
219
+ const { name: t, outDir: r } = e, o = E(t), n = S(t), i = L(t), s = e.queue ?? `${n}-queue`, a = [];
220
+ return await (async (d, p) => {
221
+ const l = g(r, d);
222
+ await P(l, p), a.push(l);
223
+ })(`${n}.job.ts`, `import { Inject } from '@forinda/kickjs-core'
2278
224
  import { Job, Process, QUEUE_MANAGER, type QueueService } from '@forinda/kickjs-queue'
2279
225
 
2280
226
  /**
2281
227
  * ${o} Job Processor
2282
228
  *
2283
229
  * Decorators:
2284
- * @Job(queueName) \u2014 marks this class as a job processor for a queue
2285
- * @Process(jobName?) \u2014 marks a method as the handler for a specific job type
230
+ * @Job(queueName) marks this class as a job processor for a queue
231
+ * @Process(jobName?) marks a method as the handler for a specific job type
2286
232
  * - Without a name: handles all jobs in the queue
2287
233
  * - With a name: handles only jobs matching that name
2288
234
  *
2289
235
  * To add jobs to this queue from a service or controller:
2290
236
  * @Inject(QUEUE_MANAGER) private queue: QueueService
2291
- * await this.queue.add('${c}', '${i}', { ... })
237
+ * await this.queue.add('${s}', '${i}', { ... })
2292
238
  */
2293
- @Job('${c}')
239
+ @Job('${s}')
2294
240
  export class ${o}Job {
2295
241
  @Process()
2296
242
  async handle(job: { name: string; data: any; id?: string }) {
@@ -2307,68 +253,182 @@ export class ${o}Job {
2307
253
  // Handle high-priority variant of this job
2308
254
  }
2309
255
  }
2310
- `),d}s(ft,"generateJob");import{join as Oe}from"path";import{readFile as Pr,writeFile as Tr}from"fs/promises";var gt={string:{ts:"string",zod:"z.string()"},text:{ts:"string",zod:"z.string()"},number:{ts:"number",zod:"z.number()"},int:{ts:"number",zod:"z.number().int()"},float:{ts:"number",zod:"z.number()"},boolean:{ts:"boolean",zod:"z.boolean()"},date:{ts:"string",zod:"z.string().datetime()"},email:{ts:"string",zod:"z.string().email()"},url:{ts:"string",zod:"z.string().url()"},uuid:{ts:"string",zod:"z.string().uuid()"},json:{ts:"any",zod:"z.any()"}};function $t(r){return r.map(e=>{let t=e.indexOf(":");if(t===-1)throw new Error(`Invalid field: "${e}". Use format: name:type (e.g. title:string)`);let o=e.slice(0,t),n=e.slice(t+1);if(!o||!n)throw new Error(`Invalid field: "${e}". Use format: name:type (e.g. title:string)`);let i=n.endsWith("?"),c=i?n.slice(0,-1):n;if(c.startsWith("enum:")){let a=c.slice(5).split(",");return{name:o,type:"enum",tsType:a.map(p=>`'${p}'`).join(" | "),zodType:`z.enum([${a.map(p=>`'${p}'`).join(", ")}])`,optional:i}}let d=gt[c];if(!d){let a=[...Object.keys(gt),"enum:a,b,c"].join(", ");throw new Error(`Unknown field type: "${c}". Valid types: ${a}`)}return{name:o,type:c,tsType:d.ts,zodType:d.zod,optional:i}})}s($t,"parseFields");async function yt(r){let{name:e,fields:t,modulesDir:o,noEntity:n,noTests:i,repo:c="inmemory"}=r,d=r.pluralize!==!1,a=f(e),p=$(e),l=R(e),u=d?T(a):a,g=d?re(p):p,k=Oe(o,u),v=[],y=s(async(B,K)=>{let H=Oe(k,B);await m(H,K),v.push(H)},"write");await y("index.ts",zr(p,a,u,c)),await y("constants.ts",jr(p,t)),await y(`presentation/${a}.controller.ts`,Mr(p,a,u,g)),await y(`application/dtos/create-${a}.dto.ts`,Or(p,t)),await y(`application/dtos/update-${a}.dto.ts`,Sr(p,t)),await y(`application/dtos/${a}-response.dto.ts`,Ir(p,t));let C=Gr(p,a,u,g);for(let B of C)await y(`application/use-cases/${B.file}`,B.content);return await y(`domain/repositories/${a}.repository.ts`,qr(p,a)),await y(`domain/services/${a}-domain.service.ts`,_r(p,a)),c==="inmemory"&&await y(`infrastructure/repositories/in-memory-${a}.repository.ts`,Ar(p,a,t)),n||(await y(`domain/entities/${a}.entity.ts`,Er(p,a,t)),await y(`domain/value-objects/${a}-id.vo.ts`,Ur(p))),await Qr(o,p,u),v}s(yt,"generateScaffold");function Or(r,e){let t=e.map(o=>{let n=o.zodType;return` ${o.name}: ${n}${o.optional?".optional()":""},`}).join(`
2311
- `);return`import { z } from 'zod'
256
+ `), a;
257
+ }
258
+ var F = {
259
+ string: {
260
+ ts: "string",
261
+ zod: "z.string()"
262
+ },
263
+ text: {
264
+ ts: "string",
265
+ zod: "z.string()"
266
+ },
267
+ number: {
268
+ ts: "number",
269
+ zod: "z.number()"
270
+ },
271
+ int: {
272
+ ts: "number",
273
+ zod: "z.number().int()"
274
+ },
275
+ float: {
276
+ ts: "number",
277
+ zod: "z.number()"
278
+ },
279
+ boolean: {
280
+ ts: "boolean",
281
+ zod: "z.boolean()"
282
+ },
283
+ date: {
284
+ ts: "string",
285
+ zod: "z.string().datetime()"
286
+ },
287
+ email: {
288
+ ts: "string",
289
+ zod: "z.string().email()"
290
+ },
291
+ url: {
292
+ ts: "string",
293
+ zod: "z.string().url()"
294
+ },
295
+ uuid: {
296
+ ts: "string",
297
+ zod: "z.string().uuid()"
298
+ },
299
+ json: {
300
+ ts: "any",
301
+ zod: "z.any()"
302
+ }
303
+ };
304
+ function Oe(e) {
305
+ return e.map((t) => {
306
+ const r = t.indexOf(":");
307
+ if (r === -1) throw new Error(`Invalid field: "${t}". Use format: name:type (e.g. title:string)`);
308
+ const o = t.slice(0, r), n = t.slice(r + 1);
309
+ if (!o || !n) throw new Error(`Invalid field: "${t}". Use format: name:type (e.g. title:string)`);
310
+ const i = n.endsWith("?"), s = i ? n.slice(0, -1) : n;
311
+ if (s.startsWith("enum:")) {
312
+ const c = s.slice(5).split(",");
313
+ return {
314
+ name: o,
315
+ type: "enum",
316
+ tsType: c.map((d) => `'${d}'`).join(" | "),
317
+ zodType: `z.enum([${c.map((d) => `'${d}'`).join(", ")}])`,
318
+ optional: i
319
+ };
320
+ }
321
+ const a = F[s];
322
+ if (!a) {
323
+ const c = [...Object.keys(F), "enum:a,b,c"].join(", ");
324
+ throw new Error(`Unknown field type: "${s}". Valid types: ${c}`);
325
+ }
326
+ return {
327
+ name: o,
328
+ type: s,
329
+ tsType: a.ts,
330
+ zodType: a.zod,
331
+ optional: i
332
+ };
333
+ });
334
+ }
335
+ async function Te(e) {
336
+ const { name: t, fields: r, modulesDir: o, noEntity: n, noTests: i, repo: s = "inmemory" } = e, a = e.pluralize !== !1, c = S(t), d = E(t);
337
+ L(t);
338
+ const p = a ? _(c) : c, l = a ? se(d) : d, f = g(o, p), v = [], u = async (b, X) => {
339
+ const H = g(f, b);
340
+ await P(H, X), v.push(H);
341
+ };
342
+ await u("index.ts", Ge(d, c, p, s)), await u("constants.ts", Ae(d, r)), await u(`presentation/${c}.controller.ts`, Qe(d, c, p, l)), await u(`application/dtos/create-${c}.dto.ts`, Ee(d, r)), await u(`application/dtos/update-${c}.dto.ts`, be(d, r)), await u(`application/dtos/${c}-response.dto.ts`, Ue(d, r));
343
+ const I = Ne(d, c, p, l);
344
+ for (const b of I) await u(`application/use-cases/${b.file}`, b.content);
345
+ return await u(`domain/repositories/${c}.repository.ts`, Le(d, c)), await u(`domain/services/${c}-domain.service.ts`, _e(d, c)), s === "inmemory" && await u(`infrastructure/repositories/in-memory-${c}.repository.ts`, ze(d, c, r)), n || (await u(`domain/entities/${c}.entity.ts`, Me(d, c, r)), await u(`domain/value-objects/${c}-id.vo.ts`, qe(d))), await He(o, d, p), v;
346
+ }
347
+ function Ee(e, t) {
348
+ return `import { z } from 'zod'
2312
349
 
2313
- export const create${r}Schema = z.object({
2314
- ${t}
350
+ export const create${e}Schema = z.object({
351
+ ${t.map((r) => {
352
+ const o = r.zodType;
353
+ return ` ${r.name}: ${o}${r.optional ? ".optional()" : ""},`;
354
+ }).join(`
355
+ `)}
2315
356
  })
2316
357
 
2317
- export type Create${r}DTO = z.infer<typeof create${r}Schema>
2318
- `}s(Or,"genCreateDTO");function Sr(r,e){let t=e.map(o=>` ${o.name}: ${o.zodType}.optional(),`).join(`
2319
- `);return`import { z } from 'zod'
358
+ export type Create${e}DTO = z.infer<typeof create${e}Schema>
359
+ `;
360
+ }
361
+ function be(e, t) {
362
+ return `import { z } from 'zod'
2320
363
 
2321
- export const update${r}Schema = z.object({
2322
- ${t}
364
+ export const update${e}Schema = z.object({
365
+ ${t.map((r) => ` ${r.name}: ${r.zodType}.optional(),`).join(`
366
+ `)}
2323
367
  })
2324
368
 
2325
- export type Update${r}DTO = z.infer<typeof update${r}Schema>
2326
- `}s(Sr,"genUpdateDTO");function Ir(r,e){let t=e.map(o=>` ${o.name}${o.optional?"?":""}: ${o.tsType}`).join(`
2327
- `);return`export interface ${r}ResponseDTO {
369
+ export type Update${e}DTO = z.infer<typeof update${e}Schema>
370
+ `;
371
+ }
372
+ function Ue(e, t) {
373
+ return `export interface ${e}ResponseDTO {
2328
374
  id: string
2329
- ${t}
375
+ ${t.map((r) => ` ${r.name}${r.optional ? "?" : ""}: ${r.tsType}`).join(`
376
+ `)}
2330
377
  createdAt: string
2331
378
  updatedAt: string
2332
379
  }
2333
- `}s(Ir,"genResponseDTO");function jr(r,e){let t=e.filter(a=>a.tsType==="string").map(a=>`'${a.name}'`),o=e.filter(a=>a.tsType==="number").map(a=>`'${a.name}'`),n=e.map(a=>`'${a.name}'`),i=[...n].join(", "),c=[...n,"'createdAt'","'updatedAt'"].join(", "),d=t.length>0?t.join(", "):"'name'";return`import type { ApiQueryParamsConfig } from '@forinda/kickjs-core'
380
+ `;
381
+ }
382
+ function Ae(e, t) {
383
+ const r = t.filter((a) => a.tsType === "string").map((a) => `'${a.name}'`);
384
+ t.filter((a) => a.tsType === "number").map((a) => `'${a.name}'`);
385
+ const o = t.map((a) => `'${a.name}'`), n = [...o].join(", "), i = [
386
+ ...o,
387
+ "'createdAt'",
388
+ "'updatedAt'"
389
+ ].join(", "), s = r.length > 0 ? r.join(", ") : "'name'";
390
+ return `import type { ApiQueryParamsConfig } from '@forinda/kickjs-core'
2334
391
 
2335
- export const ${r.toUpperCase()}_QUERY_CONFIG: ApiQueryParamsConfig = {
2336
- filterable: [${i}],
2337
- sortable: [${c}],
2338
- searchable: [${d}],
392
+ export const ${e.toUpperCase()}_QUERY_CONFIG: ApiQueryParamsConfig = {
393
+ filterable: [${n}],
394
+ sortable: [${i}],
395
+ searchable: [${s}],
2339
396
  }
2340
- `}s(jr,"genConstants");function Ar(r,e,t){let o=t.map(i=>` ${i.name}: dto.${i.name},`).join(`
2341
- `);return`import { randomUUID } from 'node:crypto'
397
+ `;
398
+ }
399
+ function ze(e, t, r) {
400
+ return `import { randomUUID } from 'node:crypto'
2342
401
  import { Repository, HttpException } from '@forinda/kickjs-core'
2343
402
  import type { ParsedQuery } from '@forinda/kickjs-http'
2344
- import type { I${r}Repository } from '../../domain/repositories/${e}.repository'
2345
- import type { ${r}ResponseDTO } from '../../application/dtos/${e}-response.dto'
2346
- import type { Create${r}DTO } from '../../application/dtos/create-${e}.dto'
2347
- import type { Update${r}DTO } from '../../application/dtos/update-${e}.dto'
403
+ import type { I${e}Repository } from '../../domain/repositories/${t}.repository'
404
+ import type { ${e}ResponseDTO } from '../../application/dtos/${t}-response.dto'
405
+ import type { Create${e}DTO } from '../../application/dtos/create-${t}.dto'
406
+ import type { Update${e}DTO } from '../../application/dtos/update-${t}.dto'
2348
407
 
2349
408
  @Repository()
2350
- export class InMemory${r}Repository implements I${r}Repository {
2351
- private store = new Map<string, ${r}ResponseDTO>()
409
+ export class InMemory${e}Repository implements I${e}Repository {
410
+ private store = new Map<string, ${e}ResponseDTO>()
2352
411
 
2353
- async findById(id: string): Promise<${r}ResponseDTO | null> {
412
+ async findById(id: string): Promise<${e}ResponseDTO | null> {
2354
413
  return this.store.get(id) ?? null
2355
414
  }
2356
415
 
2357
- async findAll(): Promise<${r}ResponseDTO[]> {
416
+ async findAll(): Promise<${e}ResponseDTO[]> {
2358
417
  return Array.from(this.store.values())
2359
418
  }
2360
419
 
2361
- async findPaginated(parsed: ParsedQuery): Promise<{ data: ${r}ResponseDTO[]; total: number }> {
420
+ async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
2362
421
  const all = Array.from(this.store.values())
2363
422
  const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
2364
423
  return { data, total: all.length }
2365
424
  }
2366
425
 
2367
- async create(dto: Create${r}DTO): Promise<${r}ResponseDTO> {
426
+ async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
2368
427
  const now = new Date().toISOString()
2369
- const entity: ${r}ResponseDTO = {
428
+ const entity: ${e}ResponseDTO = {
2370
429
  id: randomUUID(),
2371
- ${o}
430
+ ${r.map((o) => ` ${o.name}: dto.${o.name},`).join(`
431
+ `)}
2372
432
  createdAt: now,
2373
433
  updatedAt: now,
2374
434
  }
@@ -2376,237 +436,313 @@ ${o}
2376
436
  return entity
2377
437
  }
2378
438
 
2379
- async update(id: string, dto: Update${r}DTO): Promise<${r}ResponseDTO> {
439
+ async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
2380
440
  const existing = this.store.get(id)
2381
- if (!existing) throw HttpException.notFound('${r} not found')
441
+ if (!existing) throw HttpException.notFound('${e} not found')
2382
442
  const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
2383
443
  this.store.set(id, updated)
2384
444
  return updated
2385
445
  }
2386
446
 
2387
447
  async delete(id: string): Promise<void> {
2388
- if (!this.store.has(id)) throw HttpException.notFound('${r} not found')
448
+ if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
2389
449
  this.store.delete(id)
2390
450
  }
2391
451
  }
2392
- `}s(Ar,"genInMemoryRepository");function Er(r,e,t){let o=t.map(a=>` ${a.name}${a.optional?"?":""}: ${a.tsType}`).join(`
2393
- `),n=t.filter(a=>!a.optional).map(a=>`${a.name}: ${a.tsType}`).join("; "),i=t.filter(a=>!a.optional).map(a=>` ${a.name}: params.${a.name},`).join(`
2394
- `),c=t.map(a=>` get ${a.name}(): ${a.tsType}${a.optional?" | undefined":""} {
2395
- return this.props.${a.name}
2396
- }`).join(`
2397
- `),d=t.map(a=>` ${a.name}: this.props.${a.name},`).join(`
2398
- `);return`import { ${r}Id } from '../value-objects/${e}-id.vo'
452
+ `;
453
+ }
454
+ function Me(e, t, r) {
455
+ return `import { ${e}Id } from '../value-objects/${t}-id.vo'
2399
456
 
2400
- interface ${r}Props {
2401
- id: ${r}Id
2402
- ${o}
457
+ interface ${e}Props {
458
+ id: ${e}Id
459
+ ${r.map((o) => ` ${o.name}${o.optional ? "?" : ""}: ${o.tsType}`).join(`
460
+ `)}
2403
461
  createdAt: Date
2404
462
  updatedAt: Date
2405
463
  }
2406
464
 
2407
- export class ${r} {
2408
- private constructor(private props: ${r}Props) {}
465
+ export class ${e} {
466
+ private constructor(private props: ${e}Props) {}
2409
467
 
2410
- static create(params: { ${n} }): ${r} {
468
+ static create(params: { ${r.filter((o) => !o.optional).map((o) => `${o.name}: ${o.tsType}`).join("; ")} }): ${e} {
2411
469
  const now = new Date()
2412
- return new ${r}({
2413
- id: ${r}Id.create(),
2414
- ${i}
470
+ return new ${e}({
471
+ id: ${e}Id.create(),
472
+ ${r.filter((o) => !o.optional).map((o) => ` ${o.name}: params.${o.name},`).join(`
473
+ `)}
2415
474
  createdAt: now,
2416
475
  updatedAt: now,
2417
476
  })
2418
477
  }
2419
478
 
2420
- static reconstitute(props: ${r}Props): ${r} {
2421
- return new ${r}(props)
479
+ static reconstitute(props: ${e}Props): ${e} {
480
+ return new ${e}(props)
2422
481
  }
2423
482
 
2424
- get id(): ${r}Id { return this.props.id }
2425
- ${c}
483
+ get id(): ${e}Id { return this.props.id }
484
+ ${r.map((o) => ` get ${o.name}(): ${o.tsType}${o.optional ? " | undefined" : ""} {
485
+ return this.props.${o.name}
486
+ }`).join(`
487
+ `)}
2426
488
  get createdAt(): Date { return this.props.createdAt }
2427
489
  get updatedAt(): Date { return this.props.updatedAt }
2428
490
 
2429
491
  toJSON() {
2430
492
  return {
2431
493
  id: this.props.id.toString(),
2432
- ${d}
494
+ ${r.map((o) => ` ${o.name}: this.props.${o.name},`).join(`
495
+ `)}
2433
496
  createdAt: this.props.createdAt.toISOString(),
2434
497
  updatedAt: this.props.updatedAt.toISOString(),
2435
498
  }
2436
499
  }
2437
500
  }
2438
- `}s(Er,"genEntity");function Ur(r){return`import { randomUUID } from 'node:crypto'
501
+ `;
502
+ }
503
+ function qe(e) {
504
+ return `import { randomUUID } from 'node:crypto'
2439
505
 
2440
- export class ${r}Id {
506
+ export class ${e}Id {
2441
507
  private constructor(private readonly value: string) {}
2442
508
 
2443
- static create(): ${r}Id { return new ${r}Id(randomUUID()) }
509
+ static create(): ${e}Id { return new ${e}Id(randomUUID()) }
2444
510
 
2445
- static from(id: string): ${r}Id {
2446
- if (!id || id.trim().length === 0) throw new Error('${r}Id cannot be empty')
2447
- return new ${r}Id(id)
511
+ static from(id: string): ${e}Id {
512
+ if (!id || id.trim().length === 0) throw new Error('${e}Id cannot be empty')
513
+ return new ${e}Id(id)
2448
514
  }
2449
515
 
2450
516
  toString(): string { return this.value }
2451
- equals(other: ${r}Id): boolean { return this.value === other.value }
517
+ equals(other: ${e}Id): boolean { return this.value === other.value }
518
+ }
519
+ `;
2452
520
  }
2453
- `}s(Ur,"genValueObject");function zr(r,e,t,o){return`import type { AppModule, AppModuleClass } from '@forinda/kickjs-core'
2454
- import { ${r}Controller } from './presentation/${e}.controller'
2455
- import { ${r}DomainService } from './domain/services/${e}-domain.service'
2456
- import { ${r.toUpperCase()}_REPOSITORY } from './domain/repositories/${e}.repository'
2457
- import { InMemory${r}Repository } from './infrastructure/repositories/in-memory-${e}.repository'
521
+ function Ge(e, t, r, o) {
522
+ return `import type { AppModule, AppModuleClass } from '@forinda/kickjs-core'
523
+ import { ${e}Controller } from './presentation/${t}.controller'
524
+ import { ${e}DomainService } from './domain/services/${t}-domain.service'
525
+ import { ${e.toUpperCase()}_REPOSITORY } from './domain/repositories/${t}.repository'
526
+ import { InMemory${e}Repository } from './infrastructure/repositories/in-memory-${t}.repository'
2458
527
 
2459
- export class ${r}Module implements AppModule {
528
+ export class ${e}Module implements AppModule {
2460
529
  register(container: any): void {
2461
530
  container.registerFactory(
2462
- ${r.toUpperCase()}_REPOSITORY,
2463
- () => container.resolve(InMemory${r}Repository),
531
+ ${e.toUpperCase()}_REPOSITORY,
532
+ () => container.resolve(InMemory${e}Repository),
2464
533
  )
2465
534
  }
2466
535
 
2467
536
  routes() {
2468
- return { prefix: '/${t}', controllers: [${r}Controller] }
537
+ return { prefix: '/${r}', controllers: [${e}Controller] }
2469
538
  }
2470
539
  }
2471
- `}s(zr,"genModuleIndex");function Mr(r,e,t,o){return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
540
+ `;
541
+ }
542
+ function Qe(e, t, r, o) {
543
+ return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
2472
544
  import type { RequestContext } from '@forinda/kickjs-http'
2473
545
  import { ApiTags } from '@forinda/kickjs-swagger'
2474
- import { Create${r}UseCase } from '../application/use-cases/create-${e}.use-case'
2475
- import { Get${r}UseCase } from '../application/use-cases/get-${e}.use-case'
2476
- import { List${o}UseCase } from '../application/use-cases/list-${t}.use-case'
2477
- import { Update${r}UseCase } from '../application/use-cases/update-${e}.use-case'
2478
- import { Delete${r}UseCase } from '../application/use-cases/delete-${e}.use-case'
2479
- import { create${r}Schema } from '../application/dtos/create-${e}.dto'
2480
- import { update${r}Schema } from '../application/dtos/update-${e}.dto'
2481
- import { ${r.toUpperCase()}_QUERY_CONFIG } from '../constants'
546
+ import { Create${e}UseCase } from '../application/use-cases/create-${t}.use-case'
547
+ import { Get${e}UseCase } from '../application/use-cases/get-${t}.use-case'
548
+ import { List${o}UseCase } from '../application/use-cases/list-${r}.use-case'
549
+ import { Update${e}UseCase } from '../application/use-cases/update-${t}.use-case'
550
+ import { Delete${e}UseCase } from '../application/use-cases/delete-${t}.use-case'
551
+ import { create${e}Schema } from '../application/dtos/create-${t}.dto'
552
+ import { update${e}Schema } from '../application/dtos/update-${t}.dto'
553
+ import { ${e.toUpperCase()}_QUERY_CONFIG } from '../constants'
2482
554
 
2483
555
  @Controller()
2484
- export class ${r}Controller {
2485
- @Autowired() private create${r}UseCase!: Create${r}UseCase
2486
- @Autowired() private get${r}UseCase!: Get${r}UseCase
556
+ export class ${e}Controller {
557
+ @Autowired() private create${e}UseCase!: Create${e}UseCase
558
+ @Autowired() private get${e}UseCase!: Get${e}UseCase
2487
559
  @Autowired() private list${o}UseCase!: List${o}UseCase
2488
- @Autowired() private update${r}UseCase!: Update${r}UseCase
2489
- @Autowired() private delete${r}UseCase!: Delete${r}UseCase
560
+ @Autowired() private update${e}UseCase!: Update${e}UseCase
561
+ @Autowired() private delete${e}UseCase!: Delete${e}UseCase
2490
562
 
2491
563
  @Get('/')
2492
- @ApiTags('${r}')
2493
- @ApiQueryParams(${r.toUpperCase()}_QUERY_CONFIG)
564
+ @ApiTags('${e}')
565
+ @ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
2494
566
  async list(ctx: RequestContext) {
2495
567
  return ctx.paginate(
2496
568
  (parsed) => this.list${o}UseCase.execute(parsed),
2497
- ${r.toUpperCase()}_QUERY_CONFIG,
569
+ ${e.toUpperCase()}_QUERY_CONFIG,
2498
570
  )
2499
571
  }
2500
572
 
2501
573
  @Get('/:id')
2502
- @ApiTags('${r}')
574
+ @ApiTags('${e}')
2503
575
  async getById(ctx: RequestContext) {
2504
- const result = await this.get${r}UseCase.execute(ctx.params.id)
2505
- if (!result) return ctx.notFound('${r} not found')
576
+ const result = await this.get${e}UseCase.execute(ctx.params.id)
577
+ if (!result) return ctx.notFound('${e} not found')
2506
578
  ctx.json(result)
2507
579
  }
2508
580
 
2509
- @Post('/', { body: create${r}Schema, name: 'Create${r}' })
2510
- @ApiTags('${r}')
581
+ @Post('/', { body: create${e}Schema, name: 'Create${e}' })
582
+ @ApiTags('${e}')
2511
583
  async create(ctx: RequestContext) {
2512
- const result = await this.create${r}UseCase.execute(ctx.body)
584
+ const result = await this.create${e}UseCase.execute(ctx.body)
2513
585
  ctx.created(result)
2514
586
  }
2515
587
 
2516
- @Put('/:id', { body: update${r}Schema, name: 'Update${r}' })
2517
- @ApiTags('${r}')
588
+ @Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
589
+ @ApiTags('${e}')
2518
590
  async update(ctx: RequestContext) {
2519
- const result = await this.update${r}UseCase.execute(ctx.params.id, ctx.body)
591
+ const result = await this.update${e}UseCase.execute(ctx.params.id, ctx.body)
2520
592
  ctx.json(result)
2521
593
  }
2522
594
 
2523
595
  @Delete('/:id')
2524
- @ApiTags('${r}')
596
+ @ApiTags('${e}')
2525
597
  async remove(ctx: RequestContext) {
2526
- await this.delete${r}UseCase.execute(ctx.params.id)
598
+ await this.delete${e}UseCase.execute(ctx.params.id)
2527
599
  ctx.noContent()
2528
600
  }
2529
601
  }
2530
- `}s(Mr,"genController");function qr(r,e){return`import type { ${r}ResponseDTO } from '../../application/dtos/${e}-response.dto'
2531
- import type { Create${r}DTO } from '../../application/dtos/create-${e}.dto'
2532
- import type { Update${r}DTO } from '../../application/dtos/update-${e}.dto'
602
+ `;
603
+ }
604
+ function Le(e, t) {
605
+ return `import type { ${e}ResponseDTO } from '../../application/dtos/${t}-response.dto'
606
+ import type { Create${e}DTO } from '../../application/dtos/create-${t}.dto'
607
+ import type { Update${e}DTO } from '../../application/dtos/update-${t}.dto'
2533
608
  import type { ParsedQuery } from '@forinda/kickjs-http'
2534
609
 
2535
- export interface I${r}Repository {
2536
- findById(id: string): Promise<${r}ResponseDTO | null>
2537
- findAll(): Promise<${r}ResponseDTO[]>
2538
- findPaginated(parsed: ParsedQuery): Promise<{ data: ${r}ResponseDTO[]; total: number }>
2539
- create(dto: Create${r}DTO): Promise<${r}ResponseDTO>
2540
- update(id: string, dto: Update${r}DTO): Promise<${r}ResponseDTO>
610
+ export interface I${e}Repository {
611
+ findById(id: string): Promise<${e}ResponseDTO | null>
612
+ findAll(): Promise<${e}ResponseDTO[]>
613
+ findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }>
614
+ create(dto: Create${e}DTO): Promise<${e}ResponseDTO>
615
+ update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO>
2541
616
  delete(id: string): Promise<void>
2542
617
  }
2543
618
 
2544
- export const ${r.toUpperCase()}_REPOSITORY = Symbol('I${r}Repository')
2545
- `}s(qr,"genRepositoryInterface");function _r(r,e){return`import { Service, Inject, HttpException } from '@forinda/kickjs-core'
2546
- import { ${r.toUpperCase()}_REPOSITORY, type I${r}Repository } from '../repositories/${e}.repository'
619
+ export const ${e.toUpperCase()}_REPOSITORY = Symbol('I${e}Repository')
620
+ `;
621
+ }
622
+ function _e(e, t) {
623
+ return `import { Service, Inject, HttpException } from '@forinda/kickjs-core'
624
+ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../repositories/${t}.repository'
2547
625
 
2548
626
  @Service()
2549
- export class ${r}DomainService {
627
+ export class ${e}DomainService {
2550
628
  constructor(
2551
- @Inject(${r.toUpperCase()}_REPOSITORY) private readonly repo: I${r}Repository,
629
+ @Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
2552
630
  ) {}
2553
631
 
2554
632
  async ensureExists(id: string): Promise<void> {
2555
633
  const entity = await this.repo.findById(id)
2556
- if (!entity) throw HttpException.notFound('${r} not found')
634
+ if (!entity) throw HttpException.notFound('${e} not found')
2557
635
  }
2558
636
  }
2559
- `}s(_r,"genDomainService");function Gr(r,e,t,o){return[{file:`create-${e}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
2560
- import { ${r.toUpperCase()}_REPOSITORY, type I${r}Repository } from '../../domain/repositories/${e}.repository'
2561
- import type { Create${r}DTO } from '../dtos/create-${e}.dto'
637
+ `;
638
+ }
639
+ function Ne(e, t, r, o) {
640
+ return [
641
+ {
642
+ file: `create-${t}.use-case.ts`,
643
+ content: `import { Service, Inject } from '@forinda/kickjs-core'
644
+ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
645
+ import type { Create${e}DTO } from '../dtos/create-${t}.dto'
2562
646
 
2563
647
  @Service()
2564
- export class Create${r}UseCase {
2565
- constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
2566
- async execute(dto: Create${r}DTO) { return this.repo.create(dto) }
648
+ export class Create${e}UseCase {
649
+ constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
650
+ async execute(dto: Create${e}DTO) { return this.repo.create(dto) }
2567
651
  }
2568
- `},{file:`get-${e}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
2569
- import { ${r.toUpperCase()}_REPOSITORY, type I${r}Repository } from '../../domain/repositories/${e}.repository'
652
+ `
653
+ },
654
+ {
655
+ file: `get-${t}.use-case.ts`,
656
+ content: `import { Service, Inject } from '@forinda/kickjs-core'
657
+ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
2570
658
 
2571
659
  @Service()
2572
- export class Get${r}UseCase {
2573
- constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
660
+ export class Get${e}UseCase {
661
+ constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
2574
662
  async execute(id: string) { return this.repo.findById(id) }
2575
663
  }
2576
- `},{file:`list-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
664
+ `
665
+ },
666
+ {
667
+ file: `list-${r}.use-case.ts`,
668
+ content: `import { Service, Inject } from '@forinda/kickjs-core'
2577
669
  import type { ParsedQuery } from '@forinda/kickjs-http'
2578
- import { ${r.toUpperCase()}_REPOSITORY, type I${r}Repository } from '../../domain/repositories/${e}.repository'
670
+ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
2579
671
 
2580
672
  @Service()
2581
673
  export class List${o}UseCase {
2582
- constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
674
+ constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
2583
675
  async execute(parsed: ParsedQuery) { return this.repo.findPaginated(parsed) }
2584
676
  }
2585
- `},{file:`update-${e}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
2586
- import { ${r.toUpperCase()}_REPOSITORY, type I${r}Repository } from '../../domain/repositories/${e}.repository'
2587
- import type { Update${r}DTO } from '../dtos/update-${e}.dto'
677
+ `
678
+ },
679
+ {
680
+ file: `update-${t}.use-case.ts`,
681
+ content: `import { Service, Inject } from '@forinda/kickjs-core'
682
+ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
683
+ import type { Update${e}DTO } from '../dtos/update-${t}.dto'
2588
684
 
2589
685
  @Service()
2590
- export class Update${r}UseCase {
2591
- constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
2592
- async execute(id: string, dto: Update${r}DTO) { return this.repo.update(id, dto) }
686
+ export class Update${e}UseCase {
687
+ constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
688
+ async execute(id: string, dto: Update${e}DTO) { return this.repo.update(id, dto) }
2593
689
  }
2594
- `},{file:`delete-${e}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
2595
- import { ${r.toUpperCase()}_REPOSITORY, type I${r}Repository } from '../../domain/repositories/${e}.repository'
690
+ `
691
+ },
692
+ {
693
+ file: `delete-${t}.use-case.ts`,
694
+ content: `import { Service, Inject } from '@forinda/kickjs-core'
695
+ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
2596
696
 
2597
697
  @Service()
2598
- export class Delete${r}UseCase {
2599
- constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
698
+ export class Delete${e}UseCase {
699
+ constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
2600
700
  async execute(id: string) { return this.repo.delete(id) }
2601
701
  }
2602
- `}]}s(Gr,"genUseCases");async function Qr(r,e,t){let o=Oe(r,"index.ts");if(!await A(o)){await m(o,`import type { AppModuleClass } from '@forinda/kickjs-core'
2603
- import { ${e}Module } from './${t}'
2604
-
2605
- export const modules: AppModuleClass[] = [${e}Module]
2606
- `);return}let i=await Pr(o,"utf-8"),c=`import { ${e}Module } from './${t}'`;if(!i.includes(`${e}Module`)){let d=i.lastIndexOf("import ");if(d!==-1){let a=i.indexOf(`
2607
- `,d);i=i.slice(0,a+1)+c+`
2608
- `+i.slice(a+1)}else i=c+`
2609
- `+i;i=i.replace(/(=\s*\[)([\s\S]*?)(])/,(a,p,l,u)=>{let g=l.trim();if(!g)return`${p}${e}Module${u}`;let k=g.endsWith(",")?"":",";return`${p}${l.trimEnd()}${k} ${e}Module${u}`})}await Tr(o,i,"utf-8")}s(Qr,"autoRegisterModule");import{join as ht,resolve as Se}from"path";async function kt(r){let{name:e,moduleName:t,modulesDir:o}=r,n=f(e),i=$(e),c=[],d;if(r.outDir)d=Se(r.outDir);else if(t){let p=f(t),l=T(p);d=Se(ht(o??"src/modules",l,"__tests__"))}else d=Se("src/__tests__");let a=ht(d,`${n}.test.ts`);return await m(a,`import { describe, it, expect, beforeEach } from 'vitest'
702
+ `
703
+ }
704
+ ];
705
+ }
706
+ async function He(e, t, r) {
707
+ const o = g(e, "index.ts");
708
+ if (!await M(o)) {
709
+ await P(o, `import type { AppModuleClass } from '@forinda/kickjs-core'
710
+ import { ${t}Module } from './${r}'
711
+
712
+ export const modules: AppModuleClass[] = [${t}Module]
713
+ `);
714
+ return;
715
+ }
716
+ let n = await B(o, "utf-8");
717
+ const i = `import { ${t}Module } from './${r}'`;
718
+ if (!n.includes(`${t}Module`)) {
719
+ const s = n.lastIndexOf("import ");
720
+ if (s !== -1) {
721
+ const a = n.indexOf(`
722
+ `, s);
723
+ n = n.slice(0, a + 1) + i + `
724
+ ` + n.slice(a + 1);
725
+ } else n = i + `
726
+ ` + n;
727
+ n = n.replace(/(=\s*\[)([\s\S]*?)(])/, (a, c, d, p) => {
728
+ const l = d.trim();
729
+ if (!l) return `${c}${t}Module${p}`;
730
+ const f = l.endsWith(",") ? "" : ",";
731
+ return `${c}${d.trimEnd()}${f} ${t}Module${p}`;
732
+ });
733
+ }
734
+ await W(o, n, "utf-8");
735
+ }
736
+ async function Fe(e) {
737
+ const { name: t, moduleName: r, modulesDir: o } = e, n = S(t), i = E(t), s = [];
738
+ let a;
739
+ if (e.outDir) a = m(e.outDir);
740
+ else if (r) {
741
+ const d = _(S(r));
742
+ a = m(g(o ?? "src/modules", d, "__tests__"));
743
+ } else a = m("src/__tests__");
744
+ const c = g(a, `${n}.test.ts`);
745
+ return await P(c, `import { describe, it, expect, beforeEach } from 'vitest'
2610
746
  import { Container } from '@forinda/kickjs-core'
2611
747
 
2612
748
  describe('${i}', () => {
@@ -2629,36 +765,327 @@ describe('${i}', () => {
2629
765
  expect(true).toBe(true)
2630
766
  })
2631
767
  })
2632
- `),c.push(a),c}s(kt,"generateTest");import{readFile as Fr,access as Lr}from"fs/promises";import{join as Nr}from"path";var wt=["drizzle","inmemory","prisma"];function P(r){if(!r)return{};let e={dir:r.modules?.dir??r.modulesDir,repo:r.modules?.repo??r.defaultRepo,schemaDir:r.modules?.schemaDir??r.schemaDir,pluralize:r.modules?.pluralize??r.pluralize,prismaClientPath:r.modules?.prismaClientPath};return e.repo&&typeof e.repo=="string"&&!wt.includes(e.repo)&&console.warn(` Warning: modules.repo '${e.repo}' is not a built-in type (${wt.join(", ")}). It will generate a stub repository. Use { name: '${e.repo}' } to silence this warning.`),e}s(P,"resolveModuleConfig");var Wr=["kick.config.ts","kick.config.js","kick.config.mjs","kick.config.json"];async function w(r){for(let e of Wr){let t=Nr(r,e);try{await Lr(t)}catch{continue}if(e.endsWith(".json")){let o=await Fr(t,"utf-8");return JSON.parse(o)}try{let{pathToFileURL:o}=await import("url"),n=await import(o(t).href);return n.default??n}catch{e.endsWith(".ts")&&console.warn(`Warning: Failed to load ${e}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);continue}}return null}s(w,"loadKickConfig");function D(r){return r.parent?.opts()?.dryRun??!1}s(D,"isDryRun");function b(r,e=!1){let t=process.cwd();console.log(`
2633
- ${e?"Would generate":"Generated"} ${r.length} file${r.length===1?"":"s"}:`);for(let n of r)console.log(` ${n.replace(t+"/","")}`);e&&console.log(`
2634
- (dry run \u2014 no files were written)`),console.log()}s(b,"printGenerated");var vt=[{name:"module <name>",description:"Full DDD module (controller, DTOs, use-cases, repo)"},{name:"scaffold <name> <fields...>",description:"CRUD module from field definitions"},{name:"controller <name>",description:"@Controller() class [-m module]"},{name:"service <name>",description:"@Service() singleton [-m module]"},{name:"middleware <name>",description:"Express middleware function [-m module]"},{name:"guard <name>",description:"Route guard (auth, roles, etc.) [-m module]"},{name:"dto <name>",description:"Zod DTO schema [-m module]"},{name:"adapter <name>",description:"AppAdapter with lifecycle hooks (app-level only)"},{name:"test <name>",description:"Vitest test scaffold [-m module]"},{name:"resolver <name>",description:"GraphQL @Resolver class"},{name:"job <name>",description:"Queue @Job processor"},{name:"config",description:"Generate kick.config.ts"}];function Br(){console.log(`
768
+ `), s.push(c), s;
769
+ }
770
+ function y(e) {
771
+ return e.parent?.opts()?.dryRun ?? !1;
772
+ }
773
+ function h(e, t = !1) {
774
+ const r = process.cwd();
775
+ console.log(`
776
+ ${t ? "Would generate" : "Generated"} ${e.length} file${e.length === 1 ? "" : "s"}:`);
777
+ for (const o of e) console.log(` ${o.replace(r + "/", "")}`);
778
+ t && console.log(`
779
+ (dry run — no files were written)`), console.log();
780
+ }
781
+ var J = [
782
+ {
783
+ name: "module <name>",
784
+ description: "Full DDD module (controller, DTOs, use-cases, repo)"
785
+ },
786
+ {
787
+ name: "scaffold <name> <fields...>",
788
+ description: "CRUD module from field definitions"
789
+ },
790
+ {
791
+ name: "controller <name>",
792
+ description: "@Controller() class [-m module]"
793
+ },
794
+ {
795
+ name: "service <name>",
796
+ description: "@Service() singleton [-m module]"
797
+ },
798
+ {
799
+ name: "middleware <name>",
800
+ description: "Express middleware function [-m module]"
801
+ },
802
+ {
803
+ name: "guard <name>",
804
+ description: "Route guard (auth, roles, etc.) [-m module]"
805
+ },
806
+ {
807
+ name: "dto <name>",
808
+ description: "Zod DTO schema [-m module]"
809
+ },
810
+ {
811
+ name: "adapter <name>",
812
+ description: "AppAdapter with lifecycle hooks (app-level only)"
813
+ },
814
+ {
815
+ name: "test <name>",
816
+ description: "Vitest test scaffold [-m module]"
817
+ },
818
+ {
819
+ name: "resolver <name>",
820
+ description: "GraphQL @Resolver class"
821
+ },
822
+ {
823
+ name: "job <name>",
824
+ description: "Queue @Job processor"
825
+ },
826
+ {
827
+ name: "config",
828
+ description: "Generate kick.config.ts"
829
+ }
830
+ ];
831
+ function Je() {
832
+ console.log(`
2635
833
  Available generators:
2636
- `);let r=Math.max(...vt.map(e=>e.name.length));for(let e of vt)console.log(` kick g ${e.name.padEnd(r+2)} ${e.description}`);console.log()}s(Br,"printGeneratorList");function Ct(r){let e=r.command("generate").alias("g").description("Generate code scaffolds").option("--list","List all available generators").option("--dry-run","Preview files that would be generated without writing them").action(t=>{t.list?Br():e.help()});e.command("module <names...>").description("Generate one or more modules (e.g. kick g module user task project)").option("--no-entity","Skip entity and value object generation").option("--no-tests","Skip test file generation").option("--repo <type>","Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>","Override project pattern: rest | ddd | cqrs | minimal").option("--minimal","Shorthand for --pattern minimal").option("--modules-dir <dir>","Modules directory").option("--no-pluralize","Use singular names (skip auto-pluralization)").option("-f, --force","Overwrite existing files without prompting").action(async(t,o,n)=>{let i=D(n);x(i);let c=await w(process.cwd()),d=P(c),a=o.modulesDir??d.dir??"src/modules",p=o.repo??ot(d.repo),l=o.pattern??c?.pattern??"ddd",u=o.pluralize===!1?!1:d.pluralize??!0,g=[];for(let k of t){let v=await it({name:k,modulesDir:Y(a),noEntity:o.entity===!1,noTests:o.tests===!1,repo:p,minimal:o.minimal,force:o.force,pattern:l,dryRun:i,pluralize:u,prismaClientPath:d.prismaClientPath});g.push(...v)}b(g,i)}),e.command("adapter <name>").description("Generate an AppAdapter with lifecycle hooks and middleware support").option("-o, --out <dir>","Output directory","src/adapters").action(async(t,o,n)=>{let i=D(n);x(i);let c=await st({name:t,outDir:Y(o.out)});b(c,i)}),e.command("middleware <name>").description(`Generate an Express middleware function
2637
- Use -m to scope it to a module: kick g middleware auth -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(t,o,n)=>{let i=D(n);x(i);let c=await w(process.cwd()),d=P(c).dir??"src/modules",a=await at({name:t,outDir:o.out,moduleName:o.module,modulesDir:d,pattern:c?.pattern});b(a,i)}),e.command("guard <name>").description(`Generate a route guard (auth, roles, etc.)
2638
- Use -m to scope it to a module: kick g guard admin -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(t,o,n)=>{let i=D(n);x(i);let c=await w(process.cwd()),d=P(c).dir??"src/modules",a=await ct({name:t,outDir:o.out,moduleName:o.module,modulesDir:d,pattern:c?.pattern});b(a,i)}),e.command("service <name>").description(`Generate a @Service() class
2639
- Use -m to scope it to a module: kick g service payment -m orders`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(t,o,n)=>{let i=D(n);x(i);let c=await w(process.cwd()),d=P(c).dir??"src/modules",a=await dt({name:t,outDir:o.out,moduleName:o.module,modulesDir:d,pattern:c?.pattern});b(a,i)}),e.command("controller <name>").description(`Generate a @Controller() class with basic routes
2640
- Use -m to scope it to a module: kick g controller auth -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(t,o,n)=>{let i=D(n);x(i);let c=await w(process.cwd()),d=P(c).dir??"src/modules",a=await pt({name:t,outDir:o.out,moduleName:o.module,modulesDir:d,pattern:c?.pattern});b(a,i)}),e.command("dto <name>").description(`Generate a Zod DTO schema
2641
- Use -m to scope it to a module: kick g dto create-user -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(t,o,n)=>{let i=D(n);x(i);let c=await w(process.cwd()),d=P(c).dir??"src/modules",a=await lt({name:t,outDir:o.out,moduleName:o.module,modulesDir:d,pattern:c?.pattern});b(a,i)}),e.command("test <name>").description(`Generate a Vitest test scaffold
2642
- Use -m to scope it to a module: kick g test user-service -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module's __tests__/ folder").action(async(t,o,n)=>{let i=D(n);x(i);let c=await w(process.cwd()),d=P(c).dir??"src/modules",a=await kt({name:t,outDir:o.out,moduleName:o.module,modulesDir:d});b(a,i)}),e.command("resolver <name>").description("Generate a GraphQL @Resolver class with @Query and @Mutation methods").option("-o, --out <dir>","Output directory","src/resolvers").action(async(t,o,n)=>{let i=D(n);x(i);let c=await ut({name:t,outDir:Y(o.out)});b(c,i)}),e.command("job <name>").description("Generate a @Job queue processor with @Process handlers").option("-o, --out <dir>","Output directory","src/jobs").option("-q, --queue <name>","Queue name (default: <name>-queue)").action(async(t,o,n)=>{let i=D(n);x(i);let c=await ft({name:t,outDir:Y(o.out),queue:o.queue});b(c,i)}),e.command("scaffold <name> [fields...]").description(`Generate a full CRUD module from field definitions
834
+ `);
835
+ const e = Math.max(...J.map((t) => t.name.length));
836
+ for (const t of J) console.log(` kick g ${t.name.padEnd(e + 2)} ${t.description}`);
837
+ console.log();
838
+ }
839
+ function Ye(e) {
840
+ const t = e.command("generate").alias("g").description("Generate code scaffolds").option("--list", "List all available generators").option("--dry-run", "Preview files that would be generated without writing them").action((r) => {
841
+ r.list ? Je() : t.help();
842
+ });
843
+ t.command("module <names...>").description("Generate one or more modules (e.g. kick g module user task project)").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--repo <type>", "Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>", "Override project pattern: rest | ddd | cqrs | minimal").option("--minimal", "Shorthand for --pattern minimal").option("--modules-dir <dir>", "Modules directory").option("--no-pluralize", "Use singular names (skip auto-pluralization)").option("-f, --force", "Overwrite existing files without prompting").action(async (r, o, n) => {
844
+ const i = y(n);
845
+ $(i);
846
+ const s = await w(process.cwd()), a = R(s), c = o.modulesDir ?? a.dir ?? "src/modules", d = o.repo ?? re(a.repo), p = o.pattern ?? s?.pattern ?? "ddd", l = o.pluralize === !1 ? !1 : a.pluralize ?? !0, f = [];
847
+ for (const v of r) {
848
+ const u = await oe({
849
+ name: v,
850
+ modulesDir: m(c),
851
+ noEntity: o.entity === !1,
852
+ noTests: o.tests === !1,
853
+ repo: d,
854
+ minimal: o.minimal,
855
+ force: o.force,
856
+ pattern: p,
857
+ dryRun: i,
858
+ pluralize: l,
859
+ prismaClientPath: a.prismaClientPath
860
+ });
861
+ f.push(...u);
862
+ }
863
+ h(f, i);
864
+ }), t.command("adapter <name>").description("Generate an AppAdapter with lifecycle hooks and middleware support").option("-o, --out <dir>", "Output directory", "src/adapters").action(async (r, o, n) => {
865
+ const i = y(n);
866
+ $(i), h(await de({
867
+ name: r,
868
+ outDir: m(o.out)
869
+ }), i);
870
+ }), t.command("middleware <name>").description(`Generate an Express middleware function
871
+ Use -m to scope it to a module: kick g middleware auth -m users`).option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (r, o, n) => {
872
+ const i = y(n);
873
+ $(i);
874
+ const s = await w(process.cwd()), a = R(s).dir ?? "src/modules";
875
+ h(await ie({
876
+ name: r,
877
+ outDir: o.out,
878
+ moduleName: o.module,
879
+ modulesDir: a,
880
+ pattern: s?.pattern
881
+ }), i);
882
+ }), t.command("guard <name>").description(`Generate a route guard (auth, roles, etc.)
883
+ Use -m to scope it to a module: kick g guard admin -m users`).option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (r, o, n) => {
884
+ const i = y(n);
885
+ $(i);
886
+ const s = await w(process.cwd()), a = R(s).dir ?? "src/modules";
887
+ h(await te({
888
+ name: r,
889
+ outDir: o.out,
890
+ moduleName: o.module,
891
+ modulesDir: a,
892
+ pattern: s?.pattern
893
+ }), i);
894
+ }), t.command("service <name>").description(`Generate a @Service() class
895
+ Use -m to scope it to a module: kick g service payment -m orders`).option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (r, o, n) => {
896
+ const i = y(n);
897
+ $(i);
898
+ const s = await w(process.cwd()), a = R(s).dir ?? "src/modules";
899
+ h(await ce({
900
+ name: r,
901
+ outDir: o.out,
902
+ moduleName: o.module,
903
+ modulesDir: a,
904
+ pattern: s?.pattern
905
+ }), i);
906
+ }), t.command("controller <name>").description(`Generate a @Controller() class with basic routes
907
+ Use -m to scope it to a module: kick g controller auth -m users`).option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (r, o, n) => {
908
+ const i = y(n);
909
+ $(i);
910
+ const s = await w(process.cwd()), a = R(s).dir ?? "src/modules";
911
+ h(await ae({
912
+ name: r,
913
+ outDir: o.out,
914
+ moduleName: o.module,
915
+ modulesDir: a,
916
+ pattern: s?.pattern
917
+ }), i);
918
+ }), t.command("dto <name>").description(`Generate a Zod DTO schema
919
+ Use -m to scope it to a module: kick g dto create-user -m users`).option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (r, o, n) => {
920
+ const i = y(n);
921
+ $(i);
922
+ const s = await w(process.cwd()), a = R(s).dir ?? "src/modules";
923
+ h(await ee({
924
+ name: r,
925
+ outDir: o.out,
926
+ moduleName: o.module,
927
+ modulesDir: a,
928
+ pattern: s?.pattern
929
+ }), i);
930
+ }), t.command("test <name>").description(`Generate a Vitest test scaffold
931
+ Use -m to scope it to a module: kick g test user-service -m users`).option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module's __tests__/ folder").action(async (r, o, n) => {
932
+ const i = y(n);
933
+ $(i);
934
+ const s = R(await w(process.cwd())).dir ?? "src/modules";
935
+ h(await Fe({
936
+ name: r,
937
+ outDir: o.out,
938
+ moduleName: o.module,
939
+ modulesDir: s
940
+ }), i);
941
+ }), t.command("resolver <name>").description("Generate a GraphQL @Resolver class with @Query and @Mutation methods").option("-o, --out <dir>", "Output directory", "src/resolvers").action(async (r, o, n) => {
942
+ const i = y(n);
943
+ $(i), h(await Pe({
944
+ name: r,
945
+ outDir: m(o.out)
946
+ }), i);
947
+ }), t.command("job <name>").description("Generate a @Job queue processor with @Process handlers").option("-o, --out <dir>", "Output directory", "src/jobs").option("-q, --queue <name>", "Queue name (default: <name>-queue)").action(async (r, o, n) => {
948
+ const i = y(n);
949
+ $(i), h(await Ie({
950
+ name: r,
951
+ outDir: m(o.out),
952
+ queue: o.queue
953
+ }), i);
954
+ }), t.command("scaffold <name> [fields...]").description(`Generate a full CRUD module from field definitions
2643
955
  Example: kick g scaffold Post title:string body:text published:boolean?
2644
956
  Types: string, text, number, int, float, boolean, date, email, url, uuid, json, enum:a,b,c
2645
- Append ? for optional fields: description:text?`).option("--no-entity","Skip entity and value object generation").option("--no-tests","Skip test file generation").option("--no-pluralize","Use singular names (skip auto-pluralization)").option("--modules-dir <dir>","Modules directory").action(async(t,o,n,i)=>{let c=D(i);x(c),o.length===0&&(console.error(`
957
+ Append ? for optional fields: description:text?`).option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--no-pluralize", "Use singular names (skip auto-pluralization)").option("--modules-dir <dir>", "Modules directory").action(async (r, o, n, i) => {
958
+ const s = y(i);
959
+ $(s), o.length === 0 && (console.error(`
2646
960
  Error: At least one field is required.
2647
961
  Usage: kick g scaffold <name> <field:type> [field:type...]
2648
962
  Example: kick g scaffold Post title:string body:text published:boolean
2649
- `),process.exit(1));let d=await w(process.cwd()),a=P(d),p=n.modulesDir??a.dir??"src/modules",l=$t(o),u=await yt({name:t,fields:l,modulesDir:Y(p),noEntity:n.entity===!1,noTests:n.tests===!1,pluralize:n.pluralize===!1?!1:a.pluralize??!0});console.log(`
2650
- Scaffolded ${t} with ${l.length} field(s):`);for(let g of l)console.log(` ${g.name}: ${g.type}${g.optional?" (optional)":""}`);b(u,c)}),e.command("config").description("Generate a kick.config.ts at the project root").option("--modules-dir <dir>","Modules directory path","src/modules").option("--repo <type>","Default repository type: inmemory | drizzle | prisma","inmemory").option("-f, --force","Overwrite existing kick.config.ts without prompting").action(async(t,o)=>{let n=D(o);x(n);let i=await mt({outDir:Y("."),modulesDir:t.modulesDir,defaultRepo:t.repo,force:t.force});b(i,n)})}s(Ct,"registerGenerateCommand");import{cpSync as Hr,existsSync as Yr,mkdirSync as Jr}from"fs";import{resolve as xt,join as Rt}from"path";import{execSync as Kr}from"child_process";function W(r,e){Kr(r,{cwd:e,stdio:"inherit"})}s(W,"runShellCommand");function Dt(r){r.command("dev").description("Start development server with Vite HMR (zero-downtime reload)").option("-e, --entry <file>","Entry file","src/index.ts").option("-p, --port <port>","Port number").action(e=>{let t=[];e.port&&t.push(`PORT=${e.port}`);let o=`npx vite-node --watch ${e.entry}`,n=t.length?`${t.join(" ")} ${o}`:o;console.log(`
2651
- KickJS dev server starting...`),console.log(` Entry: ${e.entry}`),console.log(` HMR: enabled (vite-node)
2652
- `);try{W(n)}catch{}}),r.command("build").description("Build for production via Vite").action(async()=>{console.log(`
963
+ `), process.exit(1));
964
+ const a = R(await w(process.cwd())), c = n.modulesDir ?? a.dir ?? "src/modules", d = Oe(o), p = await Te({
965
+ name: r,
966
+ fields: d,
967
+ modulesDir: m(c),
968
+ noEntity: n.entity === !1,
969
+ noTests: n.tests === !1,
970
+ pluralize: n.pluralize === !1 ? !1 : a.pluralize ?? !0
971
+ });
972
+ console.log(`
973
+ Scaffolded ${r} with ${d.length} field(s):`);
974
+ for (const l of d) console.log(` ${l.name}: ${l.type}${l.optional ? " (optional)" : ""}`);
975
+ h(p, s);
976
+ }), t.command("config").description("Generate a kick.config.ts at the project root").option("--modules-dir <dir>", "Modules directory path", "src/modules").option("--repo <type>", "Default repository type: inmemory | drizzle | prisma", "inmemory").option("-f, --force", "Overwrite existing kick.config.ts without prompting").action(async (r, o) => {
977
+ const n = y(o);
978
+ $(n), h(await Se({
979
+ outDir: m("."),
980
+ modulesDir: r.modulesDir,
981
+ defaultRepo: r.repo,
982
+ force: r.force
983
+ }), n);
984
+ });
985
+ }
986
+ function K(e, t) {
987
+ q(e, {
988
+ cwd: t,
989
+ stdio: "inherit"
990
+ });
991
+ }
992
+ async function Y(e, t) {
993
+ t && (process.env.PORT = t);
994
+ const { createRequire: r } = await import("node:module"), o = r(m("package.json")), n = o.resolve("vite"), { createServer: i, isRunnableDevEnvironment: s } = await import(n), a = await i({
995
+ configFile: m("vite.config.ts"),
996
+ appType: "custom",
997
+ server: {
998
+ middlewareMode: !0,
999
+ hmr: !0
1000
+ },
1001
+ environments: { ssr: {} },
1002
+ customLogger: (() => {
1003
+ const { createLogger: l } = o(n), f = l(), v = f.warn.bind(f);
1004
+ return f.warn = (u, I) => {
1005
+ u.includes("(client)") && u.includes("externalized") || v(u, I);
1006
+ }, f;
1007
+ })()
1008
+ }), c = a.environments.ssr;
1009
+ s(c) || (console.error(`
1010
+ Error: Vite environment is not runnable.
1011
+ Ensure vite.config.ts uses the default SSR environment.
1012
+ `), process.exit(1)), console.log(`
1013
+ KickJS dev server starting...`), console.log(` Entry: ${e}`), console.log(` HMR: enabled (Vite Environment Runner)
1014
+ `), await c.runner.import(`/${e}`);
1015
+ const d = [
1016
+ "kick.config.ts",
1017
+ "kick.config.js",
1018
+ "kick.config.mjs"
1019
+ ];
1020
+ for (const l of d) {
1021
+ const f = m(l);
1022
+ a.watcher.add(f);
1023
+ }
1024
+ a.watcher.on("change", (l) => {
1025
+ const f = l.split("/").pop() ?? "";
1026
+ d.includes(f) && (console.log(`
1027
+ kick.config changed, restarting...
1028
+ `), a.restart());
1029
+ });
1030
+ const p = async () => {
1031
+ await a.close(), process.exit(0);
1032
+ };
1033
+ process.on("SIGINT", p), process.on("SIGTERM", p);
1034
+ }
1035
+ function Be(e) {
1036
+ e.command("dev").description("Start development server with Vite HMR (zero-downtime reload)").option("-e, --entry <file>", "Entry file", "src/index.ts").option("-p, --port <port>", "Port number").action(async (t) => {
1037
+ try {
1038
+ await Y(t.entry, t.port);
1039
+ } catch (r) {
1040
+ r.code === "ERR_MODULE_NOT_FOUND" && r.message?.includes("vite") ? console.error(`
1041
+ Error: vite is not installed.
1042
+ Run: pnpm add -D vite unplugin-swc
1043
+ `) : console.error(`
1044
+ Dev server failed:`, r.message ?? r), process.exit(1);
1045
+ }
1046
+ }), e.command("build").description("Build for production via Vite").action(async () => {
1047
+ console.log(`
2653
1048
  Building for production...
2654
- `),W("npx vite build");let t=(await w(process.cwd()))?.copyDirs??[];if(t.length>0){console.log(`
2655
- Copying directories to dist...`);for(let o of t){let n=typeof o=="string"?o:o.src,i=typeof o=="string"?Rt("dist",o):o.dest??Rt("dist",n),c=xt(n),d=xt(i);if(!Yr(c)){console.log(` \u26A0 Skipped ${n} (not found)`);continue}Jr(d,{recursive:!0}),Hr(c,d,{recursive:!0}),console.log(` \u2713 ${n} \u2192 ${i}`)}}console.log(`
1049
+ `);
1050
+ const { createRequire: t } = await import("node:module"), { build: r } = await import(t(m("package.json")).resolve("vite"));
1051
+ await r({ configFile: m("vite.config.ts") });
1052
+ const o = (await w(process.cwd()))?.copyDirs ?? [];
1053
+ if (o.length > 0) {
1054
+ console.log(`
1055
+ Copying directories to dist...`);
1056
+ for (const n of o) {
1057
+ const i = typeof n == "string" ? n : n.src, s = typeof n == "string" ? g("dist", n) : n.dest ?? g("dist", i), a = m(i), c = m(s);
1058
+ if (!j(a)) {
1059
+ console.log(` ⚠ Skipped ${i} (not found)`);
1060
+ continue;
1061
+ }
1062
+ ge(c, { recursive: !0 }), fe(a, c, { recursive: !0 }), console.log(` ✓ ${i} → ${s}`);
1063
+ }
1064
+ }
1065
+ console.log(`
2656
1066
  Build complete.
2657
- `)}),r.command("start").description("Start production server").option("-e, --entry <file>","Entry file","dist/index.js").option("-p, --port <port>","Port number").action(e=>{let t=["NODE_ENV=production"];e.port&&t.push(`PORT=${e.port}`),W(`${t.join(" ")} node ${e.entry}`)}),r.command("dev:debug").description("Start dev server with Node.js inspector").option("-e, --entry <file>","Entry file","src/index.ts").option("-p, --port <port>","Port number").action(e=>{let t=e.port?`PORT=${e.port} `:"";try{W(`${t}npx vite-node --inspect --watch ${e.entry}`)}catch{}})}s(Dt,"registerRunCommands");import{platform as Vr,release as Zr,arch as Xr}from"os";function bt(r){r.command("info").description("Print system and framework info").action(()=>{console.log(`
1067
+ `);
1068
+ }), e.command("start").description("Start production server").option("-e, --entry <file>", "Entry file", "dist/index.js").option("-p, --port <port>", "Port number").action((t) => {
1069
+ const r = ["NODE_ENV=production"];
1070
+ t.port && r.push(`PORT=${t.port}`), K(`${r.join(" ")} node ${t.entry}`);
1071
+ }), e.command("dev:debug").description("Start dev server with Node.js inspector attached").option("-e, --entry <file>", "Entry file", "src/index.ts").option("-p, --port <port>", "Port number").option("--inspect-port <port>", "Inspector port", "9229").action(async (t) => {
1072
+ const r = t.inspectPort ?? "9229";
1073
+ process.env.NODE_OPTIONS = `--inspect=0.0.0.0:${r}`, console.log(` Debugger: ws://0.0.0.0:${r}`);
1074
+ try {
1075
+ await Y(t.entry, t.port);
1076
+ } catch (o) {
1077
+ console.error(`
1078
+ Dev server (debug) failed:`, o.message ?? o), process.exit(1);
1079
+ }
1080
+ });
1081
+ }
1082
+ function We(e) {
1083
+ e.command("info").description("Print system and framework info").action(() => {
1084
+ console.log(`
2658
1085
  KickJS CLI
2659
1086
 
2660
1087
  System:
2661
- OS: ${Vr()} ${Zr()} (${Xr()})
1088
+ OS: ${Re()} ${xe()} (${De()})
2662
1089
  Node: ${process.version}
2663
1090
 
2664
1091
  Packages:
@@ -2666,32 +1093,339 @@ describe('${i}', () => {
2666
1093
  @forinda/kickjs-http workspace
2667
1094
  @forinda/kickjs-config workspace
2668
1095
  @forinda/kickjs-cli workspace
2669
- `)})}s(bt,"registerInfoCommand");function Pt(r,e){if(e?.commands?.length)for(let t of e.commands)eo(r,t)}s(Pt,"registerCustomCommands");function eo(r,e){let t=r.command(e.name).description(e.description);if(e.aliases)for(let o of e.aliases)t.alias(o);t.allowUnknownOption(!0),t.argument("[args...]","Additional arguments passed to the command"),t.action(o=>{let n=o.join(" "),i=Array.isArray(e.steps)?e.steps:[e.steps];for(let c of i){let d=n?`${c} ${n}`:c;console.log(` $ ${d}`);try{W(d)}catch{console.error(` Command failed: ${e.name}`),process.exitCode=1;return}}})}s(eo,"registerSingleCommand");var j=s(r=>`\x1B[${r}m`,"esc"),E=j("0"),I=s(r=>`${j("1")}${r}${E}`,"bold"),S=s(r=>`${j("2")}${r}${E}`,"dim"),Ie=s(r=>`${j("32")}${r}${E}`,"green"),X=s(r=>`${j("31")}${r}${E}`,"red"),Tt=s(r=>`${j("33")}${r}${E}`,"yellow"),to=s(r=>`${j("36")}${r}${E}`,"cyan"),ro=s(r=>`${j("35")}${r}${E}`,"magenta"),oo=s(r=>`${j("34")}${r}${E}`,"blue"),io={GET:Ie,POST:to,PUT:Tt,PATCH:ro,DELETE:X};function so(r){return(io[r]??S)(r.padEnd(7))}s(so,"colorMethod");function no(r){let e=Math.floor(r/86400),t=Math.floor(r%86400/3600),o=Math.floor(r%3600/60),n=r%60,i=[];return e&&i.push(`${e}d`),t&&i.push(`${t}h`),o&&i.push(`${o}m`),i.push(`${n}s`),i.join(" ")}s(no,"formatUptime");async function ao(r){let e=await fetch(r,{signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`${e.status} ${e.statusText}`);return e.json()}s(ao,"fetchJson");async function Z(r,e){try{return await ao(`${r}${e}`)}catch{return null}}s(Z,"fetchEndpoint");async function co(r){let[e,t,o,n,i]=await Promise.all([Z(r,"/health"),Z(r,"/metrics"),Z(r,"/routes"),Z(r,"/container"),Z(r,"/ws")]);return{health:e,metrics:t,routes:o,container:n,ws:i}}s(co,"fetchAll");function po(r,e){let{health:t,metrics:o,routes:n,container:i,ws:c}=e,d=S("\u2500".repeat(60));if(console.log(),console.log(I(" KickJS Inspector")+S(` \u2192 ${r}`)),console.log(d),t){let a=t.status==="healthy"?Ie("\u25CF healthy"):X("\u25CF "+t.status);console.log(` ${I("Health:")} ${a}`)}else console.log(` ${I("Health:")} ${X("\u25CF unreachable")}`);if(o){let a=((o.errorRate??0)*100).toFixed(1),p=o.errorRate>.1?X:o.errorRate>0?Tt:Ie;console.log(` ${I("Uptime:")} ${no(o.uptimeSeconds)}`),console.log(` ${I("Requests:")} ${o.requests}`),console.log(` ${I("Errors:")} ${o.serverErrors} server, ${o.clientErrors??0} client ${S("(")}${p(a+"%")}${S(")")}`)}if(i&&console.log(` ${I("DI:")} ${i.count} bindings`),c&&c.enabled&&console.log(` ${I("WS:")} ${c.connections??0} connections, ${c.namespaces??0} namespaces`),n?.routes?.length){console.log(),console.log(I(" Routes")),console.log(d),console.log(` ${S("METHOD")} ${S("PATH".padEnd(36))} ${S("CONTROLLER")}`);for(let a of n.routes){let p=a.path.length>36?a.path.slice(0,33)+"...":a.path.padEnd(36);console.log(` ${so(a.method)} ${p} ${oo(a.controller)}.${S(a.handler)}`)}}console.log(d),console.log()}s(po,"printSummary");function Ot(r){r.command("inspect [url]").description("Connect to a running KickJS app and display debug info").option("-p, --port <port>","Override port").option("-w, --watch","Poll every 5 seconds").option("-j, --json","Output raw JSON").action(async(e,t)=>{let o=e??"http://localhost:3000";if(t.port)try{let c=new URL(o);c.port=t.port,o=c.origin}catch{o=`http://localhost:${t.port}`}let n=`${o.replace(/\/$/,"")}/_debug`,i=s(async()=>{try{let c=await co(n);t.json?console.log(JSON.stringify(c,null,2)):po(o,c)}catch(c){t.json?console.log(JSON.stringify({error:String(c)})):(console.error(X(` \u2716 Could not connect to ${o}`)),console.error(S(` ${c instanceof Error?c.message:String(c)}`))),t.watch||(process.exitCode=1)}},"run");if(t.watch){let c=s(async()=>{process.stdout.write("\x1B[2J\x1B[H"),await i()},"poll");await c(),setInterval(c,5e3)}else await i()})}s(Ot,"registerInspectCommand");import{execSync as St}from"child_process";import{existsSync as It}from"fs";import{resolve as jt}from"path";var je={core:{pkg:"@forinda/kickjs-core",peers:[],description:"DI container, decorators, reactivity"},http:{pkg:"@forinda/kickjs-http",peers:["express"],description:"Express 5, routing, middleware"},config:{pkg:"@forinda/kickjs-config",peers:[],description:"Zod-based env validation"},cli:{pkg:"@forinda/kickjs-cli",peers:[],description:"CLI tool and code generators",dev:!0},swagger:{pkg:"@forinda/kickjs-swagger",peers:[],description:"OpenAPI spec + Swagger UI + ReDoc"},graphql:{pkg:"@forinda/kickjs-graphql",peers:["graphql"],description:"GraphQL resolvers + GraphiQL"},drizzle:{pkg:"@forinda/kickjs-drizzle",peers:["drizzle-orm"],description:"Drizzle ORM adapter + query builder"},prisma:{pkg:"@forinda/kickjs-prisma",peers:["@prisma/client"],description:"Prisma adapter + query builder"},ws:{pkg:"@forinda/kickjs-ws",peers:["socket.io"],description:"WebSocket with @WsController decorators"},otel:{pkg:"@forinda/kickjs-otel",peers:["@opentelemetry/api"],description:"OpenTelemetry tracing + metrics"},devtools:{pkg:"@forinda/kickjs-devtools",peers:[],description:"Development dashboard \u2014 routes, DI, metrics, health",dev:!0},auth:{pkg:"@forinda/kickjs-auth",peers:["jsonwebtoken"],description:"Authentication \u2014 JWT, API key, and custom strategies"},mailer:{pkg:"@forinda/kickjs-mailer",peers:["nodemailer"],description:"Email sending \u2014 SMTP, Resend, SES, or custom provider"},cron:{pkg:"@forinda/kickjs-cron",peers:["croner"],description:"Cron job scheduling (production-grade with croner)"},queue:{pkg:"@forinda/kickjs-queue",peers:[],description:"Queue adapter (BullMQ/RabbitMQ/Kafka)"},"queue:bullmq":{pkg:"@forinda/kickjs-queue",peers:["bullmq","ioredis"],description:"Queue with BullMQ + Redis"},"queue:rabbitmq":{pkg:"@forinda/kickjs-queue",peers:["amqplib"],description:"Queue with RabbitMQ"},"queue:kafka":{pkg:"@forinda/kickjs-queue",peers:["kafkajs"],description:"Queue with Kafka"},"multi-tenant":{pkg:"@forinda/kickjs-multi-tenant",peers:[],description:"Tenant resolution middleware"},notifications:{pkg:"@forinda/kickjs-notifications",peers:[],description:"Multi-channel notifications \u2014 email, Slack, Discord, webhook"},testing:{pkg:"@forinda/kickjs-testing",peers:[],description:"Test utilities and TestModule builder",dev:!0}};function lo(){return It(jt("pnpm-lock.yaml"))?"pnpm":It(jt("yarn.lock"))?"yarn":"npm"}s(lo,"detectPackageManager");function At(){console.log(`
1096
+ `);
1097
+ });
1098
+ }
1099
+ function Ke(e, t) {
1100
+ if (t?.commands?.length)
1101
+ for (const r of t.commands) Ve(e, r);
1102
+ }
1103
+ function Ve(e, t) {
1104
+ const r = e.command(t.name).description(t.description);
1105
+ if (t.aliases) for (const o of t.aliases) r.alias(o);
1106
+ r.allowUnknownOption(!0), r.argument("[args...]", "Additional arguments passed to the command"), r.action((o) => {
1107
+ const n = o.join(" "), i = Array.isArray(t.steps) ? t.steps : [t.steps];
1108
+ for (const s of i) {
1109
+ const a = n ? `${s} ${n}` : s;
1110
+ console.log(` $ ${a}`);
1111
+ try {
1112
+ K(a);
1113
+ } catch {
1114
+ console.error(` Command failed: ${t.name}`), process.exitCode = 1;
1115
+ return;
1116
+ }
1117
+ }
1118
+ });
1119
+ }
1120
+ var x = (e) => `\x1B[${e}m`, C = x("0"), D = (e) => `${x("1")}${e}${C}`, k = (e) => `${x("2")}${e}${C}`, G = (e) => `${x("32")}${e}${C}`, T = (e) => `${x("31")}${e}${C}`, V = (e) => `${x("33")}${e}${C}`, Ze = (e) => `${x("36")}${e}${C}`, Xe = (e) => `${x("35")}${e}${C}`, et = (e) => `${x("34")}${e}${C}`, tt = {
1121
+ GET: G,
1122
+ POST: Ze,
1123
+ PUT: V,
1124
+ PATCH: Xe,
1125
+ DELETE: T
1126
+ };
1127
+ function ot(e) {
1128
+ return (tt[e] ?? k)(e.padEnd(7));
1129
+ }
1130
+ function rt(e) {
1131
+ const t = Math.floor(e / 86400), r = Math.floor(e % 86400 / 3600), o = Math.floor(e % 3600 / 60), n = e % 60, i = [];
1132
+ return t && i.push(`${t}d`), r && i.push(`${r}h`), o && i.push(`${o}m`), i.push(`${n}s`), i.join(" ");
1133
+ }
1134
+ async function nt(e) {
1135
+ const t = await fetch(e, { signal: AbortSignal.timeout(5e3) });
1136
+ if (!t.ok) throw new Error(`${t.status} ${t.statusText}`);
1137
+ return t.json();
1138
+ }
1139
+ async function O(e, t) {
1140
+ try {
1141
+ return await nt(`${e}${t}`);
1142
+ } catch {
1143
+ return null;
1144
+ }
1145
+ }
1146
+ async function it(e) {
1147
+ const [t, r, o, n, i] = await Promise.all([
1148
+ O(e, "/health"),
1149
+ O(e, "/metrics"),
1150
+ O(e, "/routes"),
1151
+ O(e, "/container"),
1152
+ O(e, "/ws")
1153
+ ]);
1154
+ return {
1155
+ health: t,
1156
+ metrics: r,
1157
+ routes: o,
1158
+ container: n,
1159
+ ws: i
1160
+ };
1161
+ }
1162
+ function st(e, t) {
1163
+ const { health: r, metrics: o, routes: n, container: i, ws: s } = t, a = k("─".repeat(60));
1164
+ if (console.log(), console.log(D(" KickJS Inspector") + k(` → ${e}`)), console.log(a), r) {
1165
+ const c = r.status === "healthy" ? G("● healthy") : T("● " + r.status);
1166
+ console.log(` ${D("Health:")} ${c}`);
1167
+ } else console.log(` ${D("Health:")} ${T("● unreachable")}`);
1168
+ if (o) {
1169
+ const c = ((o.errorRate ?? 0) * 100).toFixed(1), d = o.errorRate > 0.1 ? T : o.errorRate > 0 ? V : G;
1170
+ console.log(` ${D("Uptime:")} ${rt(o.uptimeSeconds)}`), console.log(` ${D("Requests:")} ${o.requests}`), console.log(` ${D("Errors:")} ${o.serverErrors} server, ${o.clientErrors ?? 0} client ${k("(")}${d(c + "%")}${k(")")}`);
1171
+ }
1172
+ if (i && console.log(` ${D("DI:")} ${i.count} bindings`), s && s.enabled && console.log(` ${D("WS:")} ${s.connections ?? 0} connections, ${s.namespaces ?? 0} namespaces`), n?.routes?.length) {
1173
+ console.log(), console.log(D(" Routes")), console.log(a), console.log(` ${k("METHOD")} ${k("PATH".padEnd(36))} ${k("CONTROLLER")}`);
1174
+ for (const c of n.routes) {
1175
+ const d = c.path.length > 36 ? c.path.slice(0, 33) + "..." : c.path.padEnd(36);
1176
+ console.log(` ${ot(c.method)} ${d} ${et(c.controller)}.${k(c.handler)}`);
1177
+ }
1178
+ }
1179
+ console.log(a), console.log();
1180
+ }
1181
+ function at(e) {
1182
+ e.command("inspect [url]").description("Connect to a running KickJS app and display debug info").option("-p, --port <port>", "Override port").option("-w, --watch", "Poll every 5 seconds").option("-j, --json", "Output raw JSON").action(async (t, r) => {
1183
+ let o = t ?? "http://localhost:3000";
1184
+ if (r.port) try {
1185
+ const s = new URL(o);
1186
+ s.port = r.port, o = s.origin;
1187
+ } catch {
1188
+ o = `http://localhost:${r.port}`;
1189
+ }
1190
+ const n = `${o.replace(/\/$/, "")}/_debug`, i = async () => {
1191
+ try {
1192
+ const s = await it(n);
1193
+ r.json ? console.log(JSON.stringify(s, null, 2)) : st(o, s);
1194
+ } catch (s) {
1195
+ r.json ? console.log(JSON.stringify({ error: String(s) })) : (console.error(T(` ✖ Could not connect to ${o}`)), console.error(k(` ${s instanceof Error ? s.message : String(s)}`))), r.watch || (process.exitCode = 1);
1196
+ }
1197
+ };
1198
+ if (r.watch) {
1199
+ const s = async () => {
1200
+ process.stdout.write("\x1B[2J\x1B[H"), await i();
1201
+ };
1202
+ await s(), setInterval(s, 5e3);
1203
+ } else await i();
1204
+ });
1205
+ }
1206
+ var Q = {
1207
+ core: {
1208
+ pkg: "@forinda/kickjs-core",
1209
+ peers: [],
1210
+ description: "DI container, decorators, reactivity"
1211
+ },
1212
+ http: {
1213
+ pkg: "@forinda/kickjs-http",
1214
+ peers: ["express"],
1215
+ description: "Express 5, routing, middleware"
1216
+ },
1217
+ config: {
1218
+ pkg: "@forinda/kickjs-config",
1219
+ peers: [],
1220
+ description: "Zod-based env validation"
1221
+ },
1222
+ cli: {
1223
+ pkg: "@forinda/kickjs-cli",
1224
+ peers: [],
1225
+ description: "CLI tool and code generators",
1226
+ dev: !0
1227
+ },
1228
+ swagger: {
1229
+ pkg: "@forinda/kickjs-swagger",
1230
+ peers: [],
1231
+ description: "OpenAPI spec + Swagger UI + ReDoc"
1232
+ },
1233
+ graphql: {
1234
+ pkg: "@forinda/kickjs-graphql",
1235
+ peers: ["graphql"],
1236
+ description: "GraphQL resolvers + GraphiQL"
1237
+ },
1238
+ drizzle: {
1239
+ pkg: "@forinda/kickjs-drizzle",
1240
+ peers: ["drizzle-orm"],
1241
+ description: "Drizzle ORM adapter + query builder"
1242
+ },
1243
+ prisma: {
1244
+ pkg: "@forinda/kickjs-prisma",
1245
+ peers: ["@prisma/client"],
1246
+ description: "Prisma adapter + query builder"
1247
+ },
1248
+ ws: {
1249
+ pkg: "@forinda/kickjs-ws",
1250
+ peers: ["socket.io"],
1251
+ description: "WebSocket with @WsController decorators"
1252
+ },
1253
+ otel: {
1254
+ pkg: "@forinda/kickjs-otel",
1255
+ peers: ["@opentelemetry/api"],
1256
+ description: "OpenTelemetry tracing + metrics"
1257
+ },
1258
+ devtools: {
1259
+ pkg: "@forinda/kickjs-devtools",
1260
+ peers: [],
1261
+ description: "Development dashboard — routes, DI, metrics, health",
1262
+ dev: !0
1263
+ },
1264
+ auth: {
1265
+ pkg: "@forinda/kickjs-auth",
1266
+ peers: ["jsonwebtoken"],
1267
+ description: "Authentication — JWT, API key, and custom strategies"
1268
+ },
1269
+ mailer: {
1270
+ pkg: "@forinda/kickjs-mailer",
1271
+ peers: ["nodemailer"],
1272
+ description: "Email sending — SMTP, Resend, SES, or custom provider"
1273
+ },
1274
+ cron: {
1275
+ pkg: "@forinda/kickjs-cron",
1276
+ peers: ["croner"],
1277
+ description: "Cron job scheduling (production-grade with croner)"
1278
+ },
1279
+ queue: {
1280
+ pkg: "@forinda/kickjs-queue",
1281
+ peers: [],
1282
+ description: "Queue adapter (BullMQ/RabbitMQ/Kafka)"
1283
+ },
1284
+ "queue:bullmq": {
1285
+ pkg: "@forinda/kickjs-queue",
1286
+ peers: ["bullmq", "ioredis"],
1287
+ description: "Queue with BullMQ + Redis"
1288
+ },
1289
+ "queue:rabbitmq": {
1290
+ pkg: "@forinda/kickjs-queue",
1291
+ peers: ["amqplib"],
1292
+ description: "Queue with RabbitMQ"
1293
+ },
1294
+ "queue:kafka": {
1295
+ pkg: "@forinda/kickjs-queue",
1296
+ peers: ["kafkajs"],
1297
+ description: "Queue with Kafka"
1298
+ },
1299
+ "multi-tenant": {
1300
+ pkg: "@forinda/kickjs-multi-tenant",
1301
+ peers: [],
1302
+ description: "Tenant resolution middleware"
1303
+ },
1304
+ notifications: {
1305
+ pkg: "@forinda/kickjs-notifications",
1306
+ peers: [],
1307
+ description: "Multi-channel notifications — email, Slack, Discord, webhook"
1308
+ },
1309
+ testing: {
1310
+ pkg: "@forinda/kickjs-testing",
1311
+ peers: [],
1312
+ description: "Test utilities and TestModule builder",
1313
+ dev: !0
1314
+ }
1315
+ };
1316
+ function ct() {
1317
+ return j(m("pnpm-lock.yaml")) ? "pnpm" : j(m("yarn.lock")) ? "yarn" : "npm";
1318
+ }
1319
+ function Z() {
1320
+ console.log(`
2670
1321
  Available KickJS packages:
2671
- `);let r=Math.max(...Object.keys(je).map(e=>e.length));for(let[e,t]of Object.entries(je)){let o=e.padEnd(r+2),n=t.peers.length?` (+ ${t.peers.join(", ")})`:"";console.log(` ${o} ${t.description}${n}`)}console.log(`
2672
- Usage: kick add graphql drizzle otel`),console.log(" kick add queue:bullmq"),console.log()}s(At,"printPackageList");function Et(r){r.command("list").alias("ls").description("List all available KickJS packages").action(()=>{At()})}s(Et,"registerListCommand");function Ut(r){r.command("add [packages...]").description("Add KickJS packages with their required dependencies").option("--pm <manager>","Package manager override").option("-D, --dev","Install as dev dependency").option("--list","List all available packages").action(async(e,t)=>{if(t.list||e.length===0){At();return}let o=t.pm??lo(),n=t.dev,i=new Set,c=new Set,d=[];for(let a of e){let p=je[a];if(!p){d.push(a);continue}let l=n||p.dev?c:i;l.add(p.pkg);for(let u of p.peers)l.add(u)}if(!(d.length>0&&(console.log(`
2673
- Unknown packages: ${d.join(", ")}`),console.log(` Run "kick add --list" to see available packages.
2674
- `),i.size===0&&c.size===0))){if(i.size>0){let a=Array.from(i),p=`${o} add ${a.join(" ")}`;console.log(`
2675
- Installing ${a.length} dependency(ies):`);for(let l of a)console.log(` + ${l}`);console.log();try{St(p,{stdio:"inherit"})}catch{console.log(`
1322
+ `);
1323
+ const e = Math.max(...Object.keys(Q).map((t) => t.length));
1324
+ for (const [t, r] of Object.entries(Q)) {
1325
+ const o = t.padEnd(e + 2), n = r.peers.length ? ` (+ ${r.peers.join(", ")})` : "";
1326
+ console.log(` ${o} ${r.description}${n}`);
1327
+ }
1328
+ console.log(`
1329
+ Usage: kick add graphql drizzle otel`), console.log(" kick add queue:bullmq"), console.log();
1330
+ }
1331
+ function dt(e) {
1332
+ e.command("list").alias("ls").description("List all available KickJS packages").action(() => {
1333
+ Z();
1334
+ });
1335
+ }
1336
+ function pt(e) {
1337
+ e.command("add [packages...]").description("Add KickJS packages with their required dependencies").option("--pm <manager>", "Package manager override").option("-D, --dev", "Install as dev dependency").option("--list", "List all available packages").action(async (t, r) => {
1338
+ if (r.list || t.length === 0) {
1339
+ Z();
1340
+ return;
1341
+ }
1342
+ const o = r.pm ?? ct(), n = r.dev, i = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Set(), a = [];
1343
+ for (const c of t) {
1344
+ const d = Q[c];
1345
+ if (!d) {
1346
+ a.push(c);
1347
+ continue;
1348
+ }
1349
+ const p = n || d.dev ? s : i;
1350
+ p.add(d.pkg);
1351
+ for (const l of d.peers) p.add(l);
1352
+ }
1353
+ if (!(a.length > 0 && (console.log(`
1354
+ Unknown packages: ${a.join(", ")}`), console.log(` Run "kick add --list" to see available packages.
1355
+ `), i.size === 0 && s.size === 0))) {
1356
+ if (i.size > 0) {
1357
+ const c = Array.from(i), d = `${o} add ${c.join(" ")}`;
1358
+ console.log(`
1359
+ Installing ${c.length} dependency(ies):`);
1360
+ for (const p of c) console.log(` + ${p}`);
1361
+ console.log();
1362
+ try {
1363
+ q(d, { stdio: "inherit" });
1364
+ } catch {
1365
+ console.log(`
2676
1366
  Installation failed. Run manually:
2677
- ${p}
2678
- `)}}if(c.size>0){let a=Array.from(c),p=`${o} add -D ${a.join(" ")}`;console.log(`
2679
- Installing ${a.length} dev dependency(ies):`);for(let l of a)console.log(` + ${l} (dev)`);console.log();try{St(p,{stdio:"inherit"})}catch{console.log(`
1367
+ ${d}
1368
+ `);
1369
+ }
1370
+ }
1371
+ if (s.size > 0) {
1372
+ const c = Array.from(s), d = `${o} add -D ${c.join(" ")}`;
1373
+ console.log(`
1374
+ Installing ${c.length} dev dependency(ies):`);
1375
+ for (const p of c) console.log(` + ${p} (dev)`);
1376
+ console.log();
1377
+ try {
1378
+ q(d, { stdio: "inherit" });
1379
+ } catch {
1380
+ console.log(`
2680
1381
  Installation failed. Run manually:
2681
- ${p}
2682
- `)}}console.log(` Done!
2683
- `)}})}s(Ut,"registerAddCommand");import{resolve as zt,join as Mt}from"path";import{existsSync as qt}from"fs";import{pathToFileURL as mo}from"url";import{fork as uo}from"child_process";function _t(r){r.command("tinker").description("Interactive REPL with DI container and services loaded").option("-e, --entry <file>","Entry file to load","src/index.ts").action(async e=>{let t=process.cwd(),o=zt(t,e.entry);qt(o)||(console.error(`
2684
- Error: ${e.entry} not found.
2685
- `),process.exit(1));let n=go(t,"tsx");n||(console.error(`
1382
+ ${d}
1383
+ `);
1384
+ }
1385
+ }
1386
+ console.log(` Done!
1387
+ `);
1388
+ }
1389
+ });
1390
+ }
1391
+ function lt(e) {
1392
+ e.command("tinker").description("Interactive REPL with DI container and services loaded").option("-e, --entry <file>", "Entry file to load", "src/index.ts").action(async (t) => {
1393
+ const r = process.cwd(), o = m(r, t.entry);
1394
+ j(o) || (console.error(`
1395
+ Error: ${t.entry} not found.
1396
+ `), process.exit(1));
1397
+ const n = ut(r, "tsx");
1398
+ n || (console.error(`
2686
1399
  Error: tsx not found. Install it: pnpm add -D tsx
2687
- `),process.exit(1));let i=fo(o,e.entry),c=Mt(t,".kick-tinker.mjs"),{writeFileSync:d,unlinkSync:a}=await import("fs");d(c,i,"utf-8");try{let p=uo(c,[],{cwd:t,execPath:n,stdio:"inherit"});await new Promise(l=>{p.on("exit",()=>l())})}finally{try{a(c)}catch{}}})}s(_t,"registerTinkerCommand");function fo(r,e){let t=mo(r).href;return`
1400
+ `), process.exit(1));
1401
+ const i = mt(o, t.entry), s = g(r, ".kick-tinker.mjs"), { writeFileSync: a, unlinkSync: c } = await import("node:fs");
1402
+ a(s, i, "utf-8");
1403
+ try {
1404
+ const d = ue(s, [], {
1405
+ cwd: r,
1406
+ execPath: n,
1407
+ stdio: "inherit"
1408
+ });
1409
+ await new Promise((p) => {
1410
+ d.on("exit", () => p());
1411
+ });
1412
+ } finally {
1413
+ try {
1414
+ c(s);
1415
+ } catch {
1416
+ }
1417
+ }
1418
+ });
1419
+ }
1420
+ function mt(e, t) {
1421
+ return `
2688
1422
  import 'reflect-metadata'
2689
1423
 
2690
1424
  // Prevent bootstrap() from starting the HTTP server
2691
1425
  process.env.KICK_TINKER = '1'
2692
1426
 
2693
- console.log('\\n \u{1F527} KickJS Tinker')
2694
- console.log(' Loading: ${e}\\n')
1427
+ console.log('\\n 🔧 KickJS Tinker')
1428
+ console.log(' Loading: ${t}\\n')
2695
1429
 
2696
1430
  // Load core
2697
1431
  let Container, Logger, HttpException, HttpStatus
@@ -2709,7 +1443,7 @@ try {
2709
1443
 
2710
1444
  // Load entry to trigger decorator registration
2711
1445
  try {
2712
- await import('${t}')
1446
+ await import('${ke(e).href}')
2713
1447
  } catch (err) {
2714
1448
  console.warn(' Warning: ' + err.message)
2715
1449
  console.warn(' Container may be partially initialized.\\n')
@@ -2729,8 +1463,8 @@ server.context.HttpException = HttpException
2729
1463
  server.context.HttpStatus = HttpStatus
2730
1464
 
2731
1465
  console.log(' Available globals:')
2732
- console.log(' container \u2014 DI container instance')
2733
- console.log(' resolve(T) \u2014 shorthand for container.resolve(T)')
1466
+ console.log(' container DI container instance')
1467
+ console.log(' resolve(T) shorthand for container.resolve(T)')
2734
1468
  console.log(' Container, Logger, HttpException, HttpStatus')
2735
1469
  console.log()
2736
1470
 
@@ -2738,12 +1472,81 @@ server.on('exit', () => {
2738
1472
  console.log('\\n Goodbye!\\n')
2739
1473
  process.exit(0)
2740
1474
  })
2741
- `}s(fo,"generateTinkerScript");function go(r,e){let t=r;for(;;){let o=Mt(t,"node_modules",".bin",e);if(qt(o))return o;let n=zt(t,"..");if(n===t)break;t=n}return null}s(go,"findBin");import{resolve as vo}from"path";import{join as Gt}from"path";import{readFile as $o,writeFile as yo,rm as ho}from"fs/promises";import{createInterface as ko}from"readline";function wo(r){let e=ko({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question(` ${r} (y/N) `,o=>{e.close(),t(o.trim().toLowerCase()==="y")})})}s(wo,"promptConfirm");async function Qt(r){let{name:e,modulesDir:t,force:o}=r,n=r.pluralize!==!1,i=f(e),c=$(e),d=n?T(i):i,a=Gt(t,d);if(!await A(a)){console.log(`
2742
- Module not found: ${a}
2743
- `);return}if(!o&&!await wo(`Delete module '${d}' at ${a}? This cannot be undone.`)){console.log(`
1475
+ `;
1476
+ }
1477
+ function ut(e, t) {
1478
+ let r = e;
1479
+ for (; ; ) {
1480
+ const o = g(r, "node_modules", ".bin", t);
1481
+ if (j(o)) return o;
1482
+ const n = m(r, "..");
1483
+ if (n === r) break;
1484
+ r = n;
1485
+ }
1486
+ return null;
1487
+ }
1488
+ function ft(e) {
1489
+ const t = N({
1490
+ input: process.stdin,
1491
+ output: process.stdout
1492
+ });
1493
+ return new Promise((r) => {
1494
+ t.question(` ${e} (y/N) `, (o) => {
1495
+ t.close(), r(o.trim().toLowerCase() === "y");
1496
+ });
1497
+ });
1498
+ }
1499
+ async function gt(e) {
1500
+ const { name: t, modulesDir: r, force: o } = e, n = e.pluralize !== !1, i = S(t), s = E(t), a = n ? _(i) : i, c = g(r, a);
1501
+ if (!await M(c)) {
1502
+ console.log(`
1503
+ Module not found: ${c}
1504
+ `);
1505
+ return;
1506
+ }
1507
+ if (!o && !await ft(`Delete module '${a}' at ${c}? This cannot be undone.`)) {
1508
+ console.log(`
2744
1509
  Cancelled.
2745
- `);return}await ho(a,{recursive:!0,force:!0}),console.log(` Deleted: ${a}`);let p=Gt(t,"index.ts");if(await A(p)){let l=await $o(p,"utf-8"),u=l,g=new RegExp(`^import\\s*\\{\\s*${c}Module\\s*\\}\\s*from\\s*['\\./${d}']+.*\\n?`,"gm");l=l.replace(g,""),l=l.replace(new RegExp(`\\s*,?\\s*${c}Module\\s*,?`,"g"),k=>{let v=k.trimStart().startsWith(","),y=k.trimEnd().endsWith(",");return v&&y?",":""}),l=l.replace(/,(\s*])/,"$1"),l=l.replace(/\n{3,}/g,`
2746
-
2747
- `),l!==u&&(await yo(p,l,"utf-8"),console.log(` Unregistered: ${c}Module from ${p}`))}console.log(`
2748
- Module '${d}' removed.
2749
- `)}s(Qt,"removeModule");function Ft(r){r.command("remove").alias("rm").description("Remove generated code").command("module <names...>").description("Remove one or more modules (e.g. kick rm module user task)").option("--modules-dir <dir>","Modules directory").option("--no-pluralize","Use singular module name").option("-f, --force","Skip confirmation prompt").action(async(t,o)=>{let n=await w(process.cwd()),i=P(n),c=o.modulesDir??i.dir??"src/modules",d=o.pluralize===!1?!1:i.pluralize??!0;for(let a of t)await Qt({name:a,modulesDir:vo(c),force:o.force,pluralize:d})})}s(Ft,"registerRemoveCommand");var Po=Ro(bo(import.meta.url)),To=JSON.parse(xo(Do(Po,"..","package.json"),"utf-8"));async function Oo(){let r=new Co;r.name("kick").description("KickJS \u2014 A production-grade, decorator-driven Node.js framework").version(To.version);let e=await w(process.cwd());Xe(r),Ct(r),Dt(r),bt(r),Ot(r),Ut(r),Et(r),_t(r),Ft(r),Pt(r,e),r.showHelpAfterError(),await r.parseAsync(process.argv)}s(Oo,"main");Oo().catch(r=>{console.error(r instanceof Error?r.message:r),process.exitCode=1});
1510
+ `);
1511
+ return;
1512
+ }
1513
+ await me(c, {
1514
+ recursive: !0,
1515
+ force: !0
1516
+ }), console.log(` Deleted: ${c}`);
1517
+ const d = g(r, "index.ts");
1518
+ if (await M(d)) {
1519
+ let p = await B(d, "utf-8");
1520
+ const l = p, f = new RegExp(`^import\\s*\\{\\s*${s}Module\\s*\\}\\s*from\\s*['\\./${a}']+.*\\n?`, "gm");
1521
+ p = p.replace(f, ""), p = p.replace(new RegExp(`\\s*,?\\s*${s}Module\\s*,?`, "g"), (v) => {
1522
+ const u = v.trimStart().startsWith(","), I = v.trimEnd().endsWith(",");
1523
+ return u && I ? "," : "";
1524
+ }), p = p.replace(/,(\s*])/, "$1"), p = p.replace(/\n{3,}/g, `
1525
+
1526
+ `), p !== l && (await W(d, p, "utf-8"), console.log(` Unregistered: ${s}Module from ${d}`));
1527
+ }
1528
+ console.log(`
1529
+ Module '${a}' removed.
1530
+ `);
1531
+ }
1532
+ function $t(e) {
1533
+ e.command("remove").alias("rm").description("Remove generated code").command("module <names...>").description("Remove one or more modules (e.g. kick rm module user task)").option("--modules-dir <dir>", "Modules directory").option("--no-pluralize", "Use singular module name").option("-f, --force", "Skip confirmation prompt").action(async (t, r) => {
1534
+ const o = R(await w(process.cwd())), n = r.modulesDir ?? o.dir ?? "src/modules", i = r.pluralize === !1 ? !1 : o.pluralize ?? !0;
1535
+ for (const s of t) await gt({
1536
+ name: s,
1537
+ modulesDir: m(n),
1538
+ force: r.force,
1539
+ pluralize: i
1540
+ });
1541
+ });
1542
+ }
1543
+ var yt = le(we(import.meta.url)), ht = JSON.parse($e(g(yt, "..", "package.json"), "utf-8"));
1544
+ async function wt() {
1545
+ const e = new ve();
1546
+ e.name("kick").description("KickJS — A production-grade, decorator-driven Node.js framework").version(ht.version);
1547
+ const t = await w(process.cwd());
1548
+ Ce(e), Ye(e), Be(e), We(e), at(e), pt(e), dt(e), lt(e), $t(e), Ke(e, t), e.showHelpAfterError(), await e.parseAsync(process.argv);
1549
+ }
1550
+ wt().catch((e) => {
1551
+ console.error(e instanceof Error ? e.message : e), process.exitCode = 1;
1552
+ });