@forinda/kickjs-cli 1.2.12 → 1.3.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.
- package/README.md +51 -9
- package/dist/cli.js +1095 -484
- package/dist/index.d.ts +70 -5
- package/dist/index.js +980 -377
- package/package.json +15 -4
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
Creating KickJS project: ${t}
|
|
4
|
-
`);let d={"@forinda/kickjs-core":D,"@forinda/kickjs-http":D,"@forinda/kickjs-config":D,express:"^5.1.0","reflect-metadata":"^0.2.2",zod:"^4.3.6",pino:"^10.3.1","pino-pretty":"^13.1.3"};if(n!=="minimal"&&(d["@forinda/kickjs-swagger"]=D,d["@forinda/kickjs-devtools"]=D),n==="graphql"&&(d["@forinda/kickjs-graphql"]=D,d.graphql="^16.11.0"),n==="cqrs"&&(d["@forinda/kickjs-queue"]=D,d["@forinda/kickjs-ws"]=D,d["@forinda/kickjs-otel"]=D),n==="ddd"&&(d["@forinda/kickjs-swagger"]=D),await l(h(i,"package.json"),JSON.stringify({name:t,version:Z.version,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:d,devDependencies:{"@forinda/kickjs-cli":D,"@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)),await l(h(i,"vite.config.ts"),`import { defineConfig } from 'vite'
|
|
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'
|
|
5
3
|
import { resolve } from 'path'
|
|
6
4
|
import swc from 'unplugin-swc'
|
|
7
5
|
|
|
@@ -27,7 +25,7 @@ export default defineConfig({
|
|
|
27
25
|
},
|
|
28
26
|
},
|
|
29
27
|
})
|
|
30
|
-
`
|
|
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
|
|
31
29
|
root = true
|
|
32
30
|
|
|
33
31
|
[*]
|
|
@@ -40,13 +38,13 @@ insert_final_newline = true
|
|
|
40
38
|
|
|
41
39
|
[*.md]
|
|
42
40
|
trim_trailing_whitespace = false
|
|
43
|
-
`
|
|
41
|
+
`}s(_e,"generateEditorConfig");function Ge(){return`node_modules/
|
|
44
42
|
dist/
|
|
45
43
|
.env
|
|
46
44
|
coverage/
|
|
47
45
|
.DS_Store
|
|
48
46
|
*.tsbuildinfo
|
|
49
|
-
`
|
|
47
|
+
`}s(Ge,"generateGitIgnore");function Qe(){return`# Auto-detect text files and normalise line endings to LF
|
|
50
48
|
* text=auto eol=lf
|
|
51
49
|
|
|
52
50
|
# Explicitly mark generated / binary files
|
|
@@ -64,45 +62,11 @@ coverage/
|
|
|
64
62
|
pnpm-lock.yaml -diff linguist-generated
|
|
65
63
|
yarn.lock -diff linguist-generated
|
|
66
64
|
package-lock.json -diff linguist-generated
|
|
67
|
-
`
|
|
65
|
+
`}s(Qe,"generateGitAttributes");function Fe(){return`PORT=3000
|
|
68
66
|
NODE_ENV=development
|
|
69
|
-
`
|
|
67
|
+
`}s(Fe,"generateEnv");function Le(){return`PORT=3000
|
|
70
68
|
NODE_ENV=development
|
|
71
|
-
`
|
|
72
|
-
|
|
73
|
-
export const modules: AppModuleClass[] = []
|
|
74
|
-
`),n==="graphql"&&await l(h(i,"src/resolvers/.gitkeep"),""),await l(h(i,"kick.config.ts"),`import { defineConfig } from '@forinda/kickjs-cli'
|
|
75
|
-
|
|
76
|
-
export default defineConfig({
|
|
77
|
-
pattern: '${n}',
|
|
78
|
-
modulesDir: 'src/modules',
|
|
79
|
-
defaultRepo: 'inmemory',
|
|
80
|
-
|
|
81
|
-
commands: [
|
|
82
|
-
{
|
|
83
|
-
name: 'test',
|
|
84
|
-
description: 'Run tests with Vitest',
|
|
85
|
-
steps: 'npx vitest run',
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: 'format',
|
|
89
|
-
description: 'Format code with Prettier',
|
|
90
|
-
steps: 'npx prettier --write src/',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
name: 'format:check',
|
|
94
|
-
description: 'Check formatting without writing',
|
|
95
|
-
steps: 'npx prettier --check src/',
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
name: 'check',
|
|
99
|
-
description: 'Run typecheck + format check',
|
|
100
|
-
steps: ['npx tsc --noEmit', 'npx prettier --check src/'],
|
|
101
|
-
aliases: ['verify', 'ci'],
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
})
|
|
105
|
-
`),await l(h(i,"vitest.config.ts"),`import { defineConfig } from 'vitest/config'
|
|
69
|
+
`}s(Le,"generateEnvExample");function Ne(){return`import { defineConfig } from 'vitest/config'
|
|
106
70
|
import swc from 'unplugin-swc'
|
|
107
71
|
|
|
108
72
|
export default defineConfig({
|
|
@@ -113,12 +77,7 @@ export default defineConfig({
|
|
|
113
77
|
include: ['src/**/*.test.ts'],
|
|
114
78
|
},
|
|
115
79
|
})
|
|
116
|
-
`
|
|
117
|
-
Installing dependencies with ${r}...
|
|
118
|
-
`);try{V(`${r} install`,{cwd:i,stdio:"inherit"}),console.log(`
|
|
119
|
-
Dependencies installed successfully!`)}catch{console.log(`
|
|
120
|
-
Warning: ${r} install failed. Run it manually.`)}}console.log(`
|
|
121
|
-
Project scaffolded successfully!`),console.log();let c=i!==process.cwd();console.log(" Next steps:"),c&&console.log(` cd ${t}`),e.installDeps||console.log(` ${r} install`);let a={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"};console.log(` ${a[n]??a.rest}`),console.log(" kick dev"),console.log(),console.log(" Commands:"),console.log(" kick dev Start dev server with Vite HMR"),console.log(" kick build Production build via Vite"),console.log(" kick start Run production build"),console.log(),console.log(" Generators:"),console.log(" kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)"),console.log(" kick g scaffold <n> <f..> CRUD module from field definitions"),console.log(" kick g controller <name> Standalone controller"),console.log(" kick g service <name> @Service() class"),console.log(" kick g middleware <name> Express middleware"),console.log(" kick g guard <name> Route guard (auth, roles, etc.)"),console.log(" kick g adapter <name> AppAdapter with lifecycle hooks"),console.log(" kick g dto <name> Zod DTO schema"),n==="graphql"&&console.log(" kick g resolver <name> GraphQL resolver"),n==="cqrs"&&console.log(" kick g job <name> Queue job processor"),console.log(" kick g config Generate kick.config.ts"),console.log(),console.log(" Add packages:"),console.log(" kick add <pkg> Install a KickJS package + peers"),console.log(" kick add --list Show all available packages"),console.log(),console.log(" Available: auth, swagger, graphql, drizzle, prisma, ws,"),console.log(" cron, queue, mailer, otel, multi-tenant, notifications, testing"),console.log()}s(Te,"initProject");function Rt(e,t){switch(t){case"graphql":return`import 'reflect-metadata'
|
|
80
|
+
`}s(Ne,"generateVitestConfig");function We(r,e,t){switch(e){case"graphql":return`import 'reflect-metadata'
|
|
122
81
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
123
82
|
import { DevToolsAdapter } from '@forinda/kickjs-devtools'
|
|
124
83
|
import { GraphQLAdapter } from '@forinda/kickjs-graphql'
|
|
@@ -150,10 +109,10 @@ import { modules } from './modules'
|
|
|
150
109
|
bootstrap({
|
|
151
110
|
modules,
|
|
152
111
|
adapters: [
|
|
153
|
-
new OtelAdapter({ serviceName: '${
|
|
112
|
+
new OtelAdapter({ serviceName: '${r}' }),
|
|
154
113
|
new DevToolsAdapter(),
|
|
155
114
|
new SwaggerAdapter({
|
|
156
|
-
info: { title: '${
|
|
115
|
+
info: { title: '${r}', version: '${t}' },
|
|
157
116
|
}),
|
|
158
117
|
// Uncomment for WebSocket support:
|
|
159
118
|
// new WsAdapter(),
|
|
@@ -179,18 +138,55 @@ bootstrap({
|
|
|
179
138
|
adapters: [
|
|
180
139
|
new DevToolsAdapter(),
|
|
181
140
|
new SwaggerAdapter({
|
|
182
|
-
info: { title: '${
|
|
141
|
+
info: { title: '${r}', version: '${t}' },
|
|
183
142
|
}),
|
|
184
143
|
],
|
|
185
144
|
})
|
|
186
|
-
`}}s(
|
|
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}
|
|
187
183
|
|
|
188
|
-
A **${
|
|
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.
|
|
189
185
|
|
|
190
186
|
## Getting Started
|
|
191
187
|
|
|
192
188
|
\`\`\`bash
|
|
193
|
-
${
|
|
189
|
+
${t} install
|
|
194
190
|
kick dev
|
|
195
191
|
\`\`\`
|
|
196
192
|
|
|
@@ -201,7 +197,7 @@ kick dev
|
|
|
201
197
|
| \`kick dev\` | Start dev server with Vite HMR |
|
|
202
198
|
| \`kick build\` | Production build |
|
|
203
199
|
| \`kick start\` | Run production build |
|
|
204
|
-
| \`${
|
|
200
|
+
| \`${t} run test\` | Run tests with Vitest |
|
|
205
201
|
| \`kick g module <name>\` | Generate a DDD module |
|
|
206
202
|
| \`kick g scaffold <name> <fields...>\` | Generate CRUD from field definitions |
|
|
207
203
|
| \`kick add <package>\` | Add a KickJS package |
|
|
@@ -246,9 +242,561 @@ Copy \`.env.example\` to \`.env\` and configure:
|
|
|
246
242
|
|
|
247
243
|
- [KickJS Documentation](https://forinda.github.io/kick-js/)
|
|
248
244
|
- [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
|
|
249
|
-
`}s(
|
|
250
|
-
|
|
251
|
-
|
|
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`/**
|
|
252
800
|
* ${e} Module
|
|
253
801
|
*
|
|
254
802
|
* Self-contained feature module following Domain-Driven Design (DDD).
|
|
@@ -258,12 +806,12 @@ Copy \`.env.example\` to \`.env\` and configure:
|
|
|
258
806
|
* presentation/ \u2014 HTTP controllers (entry points)
|
|
259
807
|
* application/ \u2014 Use cases (orchestration) and DTOs (validation)
|
|
260
808
|
* domain/ \u2014 Entities, value objects, repository interfaces, domain services
|
|
261
|
-
* infrastructure/ \u2014 Repository implementations (
|
|
809
|
+
* infrastructure/ \u2014 Repository implementations (currently ${et(n)})
|
|
262
810
|
*/
|
|
263
811
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
264
812
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
265
813
|
import { ${e.toUpperCase()}_REPOSITORY } from './domain/repositories/${t}.repository'
|
|
266
|
-
import { ${
|
|
814
|
+
import { ${i} } from './infrastructure/repositories/${c}.repository'
|
|
267
815
|
import { ${e}Controller } from './presentation/${t}.controller'
|
|
268
816
|
|
|
269
817
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
@@ -276,11 +824,11 @@ export class ${e}Module implements AppModule {
|
|
|
276
824
|
/**
|
|
277
825
|
* Register module dependencies in the DI container.
|
|
278
826
|
* Bind repository interface tokens to their implementations here.
|
|
279
|
-
*
|
|
827
|
+
* Currently wired to ${et(n)}. To swap implementations, change the factory target.
|
|
280
828
|
*/
|
|
281
829
|
register(container: Container): void {
|
|
282
830
|
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
283
|
-
container.resolve(${
|
|
831
|
+
container.resolve(${i}),
|
|
284
832
|
)
|
|
285
833
|
}
|
|
286
834
|
|
|
@@ -297,7 +845,7 @@ export class ${e}Module implements AppModule {
|
|
|
297
845
|
}
|
|
298
846
|
}
|
|
299
847
|
}
|
|
300
|
-
`}s(
|
|
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`/**
|
|
301
849
|
* ${e} Module
|
|
302
850
|
*
|
|
303
851
|
* REST module with a flat folder structure.
|
|
@@ -307,13 +855,13 @@ export class ${e}Module implements AppModule {
|
|
|
307
855
|
* ${t}.controller.ts \u2014 HTTP routes (CRUD)
|
|
308
856
|
* ${t}.service.ts \u2014 Business logic
|
|
309
857
|
* ${t}.repository.ts \u2014 Repository interface
|
|
310
|
-
* ${
|
|
858
|
+
* ${c}.repository.ts \u2014 Repository implementation
|
|
311
859
|
* dtos/ \u2014 Request/response schemas
|
|
312
860
|
*/
|
|
313
861
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
314
862
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
315
863
|
import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
|
|
316
|
-
import { ${
|
|
864
|
+
import { ${i} } from './${c}.repository'
|
|
317
865
|
import { ${e}Controller } from './${t}.controller'
|
|
318
866
|
|
|
319
867
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
@@ -322,7 +870,7 @@ import.meta.glob(['./**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'
|
|
|
322
870
|
export class ${e}Module implements AppModule {
|
|
323
871
|
register(container: Container): void {
|
|
324
872
|
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
325
|
-
container.resolve(${
|
|
873
|
+
container.resolve(${i}),
|
|
326
874
|
)
|
|
327
875
|
}
|
|
328
876
|
|
|
@@ -334,7 +882,7 @@ export class ${e}Module implements AppModule {
|
|
|
334
882
|
}
|
|
335
883
|
}
|
|
336
884
|
}
|
|
337
|
-
`}s(
|
|
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'
|
|
338
886
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
339
887
|
import { ${e}Controller } from './${t}.controller'
|
|
340
888
|
|
|
@@ -347,12 +895,12 @@ export class ${e}Module implements AppModule {
|
|
|
347
895
|
}
|
|
348
896
|
}
|
|
349
897
|
}
|
|
350
|
-
`}s(
|
|
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'
|
|
351
899
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
352
900
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
353
901
|
import { Create${e}UseCase } from '../application/use-cases/create-${t}.use-case'
|
|
354
902
|
import { Get${e}UseCase } from '../application/use-cases/get-${t}.use-case'
|
|
355
|
-
import { List${
|
|
903
|
+
import { List${n}UseCase } from '../application/use-cases/list-${o}.use-case'
|
|
356
904
|
import { Update${e}UseCase } from '../application/use-cases/update-${t}.use-case'
|
|
357
905
|
import { Delete${e}UseCase } from '../application/use-cases/delete-${t}.use-case'
|
|
358
906
|
import { create${e}Schema } from '../application/dtos/create-${t}.dto'
|
|
@@ -363,7 +911,7 @@ import { ${e.toUpperCase()}_QUERY_CONFIG } from '../constants'
|
|
|
363
911
|
export class ${e}Controller {
|
|
364
912
|
@Autowired() private create${e}UseCase!: Create${e}UseCase
|
|
365
913
|
@Autowired() private get${e}UseCase!: Get${e}UseCase
|
|
366
|
-
@Autowired() private list${
|
|
914
|
+
@Autowired() private list${n}UseCase!: List${n}UseCase
|
|
367
915
|
@Autowired() private update${e}UseCase!: Update${e}UseCase
|
|
368
916
|
@Autowired() private delete${e}UseCase!: Delete${e}UseCase
|
|
369
917
|
|
|
@@ -372,7 +920,7 @@ export class ${e}Controller {
|
|
|
372
920
|
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
373
921
|
async list(ctx: RequestContext) {
|
|
374
922
|
return ctx.paginate(
|
|
375
|
-
(parsed) => this.list${
|
|
923
|
+
(parsed) => this.list${n}UseCase.execute(parsed),
|
|
376
924
|
${e.toUpperCase()}_QUERY_CONFIG,
|
|
377
925
|
)
|
|
378
926
|
}
|
|
@@ -406,7 +954,7 @@ export class ${e}Controller {
|
|
|
406
954
|
ctx.noContent()
|
|
407
955
|
}
|
|
408
956
|
}
|
|
409
|
-
`}s(
|
|
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'
|
|
410
958
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
411
959
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
412
960
|
import { ${e}Service } from './${t}.service'
|
|
@@ -416,14 +964,14 @@ import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
|
|
|
416
964
|
|
|
417
965
|
@Controller()
|
|
418
966
|
export class ${e}Controller {
|
|
419
|
-
@Autowired() private ${
|
|
967
|
+
@Autowired() private ${i}Service!: ${e}Service
|
|
420
968
|
|
|
421
969
|
@Get('/')
|
|
422
970
|
@ApiTags('${e}')
|
|
423
971
|
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
424
972
|
async list(ctx: RequestContext) {
|
|
425
973
|
return ctx.paginate(
|
|
426
|
-
(parsed) => this.${
|
|
974
|
+
(parsed) => this.${i}Service.findPaginated(parsed),
|
|
427
975
|
${e.toUpperCase()}_QUERY_CONFIG,
|
|
428
976
|
)
|
|
429
977
|
}
|
|
@@ -431,7 +979,7 @@ export class ${e}Controller {
|
|
|
431
979
|
@Get('/:id')
|
|
432
980
|
@ApiTags('${e}')
|
|
433
981
|
async getById(ctx: RequestContext) {
|
|
434
|
-
const result = await this.${
|
|
982
|
+
const result = await this.${i}Service.findById(ctx.params.id)
|
|
435
983
|
if (!result) return ctx.notFound('${e} not found')
|
|
436
984
|
ctx.json(result)
|
|
437
985
|
}
|
|
@@ -439,50 +987,32 @@ export class ${e}Controller {
|
|
|
439
987
|
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
440
988
|
@ApiTags('${e}')
|
|
441
989
|
async create(ctx: RequestContext) {
|
|
442
|
-
const result = await this.${
|
|
990
|
+
const result = await this.${i}Service.create(ctx.body)
|
|
443
991
|
ctx.created(result)
|
|
444
992
|
}
|
|
445
993
|
|
|
446
994
|
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
447
995
|
@ApiTags('${e}')
|
|
448
996
|
async update(ctx: RequestContext) {
|
|
449
|
-
const result = await this.${
|
|
997
|
+
const result = await this.${i}Service.update(ctx.params.id, ctx.body)
|
|
450
998
|
ctx.json(result)
|
|
451
999
|
}
|
|
452
1000
|
|
|
453
1001
|
@Delete('/:id')
|
|
454
1002
|
@ApiTags('${e}')
|
|
455
1003
|
async remove(ctx: RequestContext) {
|
|
456
|
-
await this.${
|
|
1004
|
+
await this.${i}Service.delete(ctx.params.id)
|
|
457
1005
|
ctx.noContent()
|
|
458
1006
|
}
|
|
459
1007
|
}
|
|
460
|
-
`}s(
|
|
1008
|
+
`}s(pe,"generateRestController");function le(r){let{pascal:e}=r;return`import type { QueryParamsConfig } from '@forinda/kickjs-core'
|
|
461
1009
|
|
|
462
1010
|
export const ${e.toUpperCase()}_QUERY_CONFIG: QueryParamsConfig = {
|
|
463
1011
|
filterable: ['name'],
|
|
464
1012
|
sortable: ['name', 'createdAt'],
|
|
465
1013
|
searchable: ['name'],
|
|
466
1014
|
}
|
|
467
|
-
`}s(
|
|
468
|
-
// TODO: Import your schema table and reference actual columns for type safety
|
|
469
|
-
// import { ${t}s } from '@/db/schema'
|
|
470
|
-
|
|
471
|
-
export const ${e.toUpperCase()}_QUERY_CONFIG: DrizzleQueryParamsConfig = {
|
|
472
|
-
columns: {
|
|
473
|
-
// Replace with actual Drizzle Column references for type-safe filtering:
|
|
474
|
-
// name: ${t}s.name,
|
|
475
|
-
// status: ${t}s.status,
|
|
476
|
-
},
|
|
477
|
-
sortable: {
|
|
478
|
-
// name: ${t}s.name,
|
|
479
|
-
// createdAt: ${t}s.createdAt,
|
|
480
|
-
},
|
|
481
|
-
searchColumns: [
|
|
482
|
-
// ${t}s.name,
|
|
483
|
-
],
|
|
484
|
-
}
|
|
485
|
-
`}s(pe,"generateDrizzleConstants");function M(e,t){return`import { z } from 'zod'
|
|
1015
|
+
`}s(le,"generateConstants");function U(r){let{pascal:e,kebab:t}=r;return`import { z } from 'zod'
|
|
486
1016
|
|
|
487
1017
|
/**
|
|
488
1018
|
* Create ${e} DTO \u2014 Zod schema for validating POST request bodies.
|
|
@@ -498,20 +1028,20 @@ export const create${e}Schema = z.object({
|
|
|
498
1028
|
})
|
|
499
1029
|
|
|
500
1030
|
export type Create${e}DTO = z.infer<typeof create${e}Schema>
|
|
501
|
-
`}s(
|
|
1031
|
+
`}s(U,"generateCreateDTO");function z(r){let{pascal:e,kebab:t}=r;return`import { z } from 'zod'
|
|
502
1032
|
|
|
503
1033
|
export const update${e}Schema = z.object({
|
|
504
1034
|
name: z.string().min(1).max(200).optional(),
|
|
505
1035
|
})
|
|
506
1036
|
|
|
507
1037
|
export type Update${e}DTO = z.infer<typeof update${e}Schema>
|
|
508
|
-
`}s(
|
|
1038
|
+
`}s(z,"generateUpdateDTO");function M(r){let{pascal:e,kebab:t}=r;return`export interface ${e}ResponseDTO {
|
|
509
1039
|
id: string
|
|
510
1040
|
name: string
|
|
511
1041
|
createdAt: string
|
|
512
1042
|
updatedAt: string
|
|
513
1043
|
}
|
|
514
|
-
`}s(
|
|
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:`/**
|
|
515
1045
|
* Create ${e} Use Case
|
|
516
1046
|
*
|
|
517
1047
|
* Application layer \u2014 orchestrates a single business operation.
|
|
@@ -552,7 +1082,7 @@ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domai
|
|
|
552
1082
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
553
1083
|
|
|
554
1084
|
@Service()
|
|
555
|
-
export class List${
|
|
1085
|
+
export class List${n}UseCase {
|
|
556
1086
|
constructor(
|
|
557
1087
|
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
558
1088
|
) {}
|
|
@@ -589,7 +1119,7 @@ export class Delete${e}UseCase {
|
|
|
589
1119
|
await this.repo.delete(id)
|
|
590
1120
|
}
|
|
591
1121
|
}
|
|
592
|
-
`}]}s(me,"generateUseCases");function
|
|
1122
|
+
`}]}s(me,"generateUseCases");function q(r){let{pascal:e,kebab:t,dtoPrefix:o="../../application/dtos"}=r;return`/**
|
|
593
1123
|
* ${e} Repository Interface
|
|
594
1124
|
*
|
|
595
1125
|
* Defines the contract for data access.
|
|
@@ -613,7 +1143,7 @@ export interface I${e}Repository {
|
|
|
613
1143
|
}
|
|
614
1144
|
|
|
615
1145
|
export const ${e.toUpperCase()}_REPOSITORY = Symbol('I${e}Repository')
|
|
616
|
-
`}s(
|
|
1146
|
+
`}s(q,"generateRepositoryInterface");function _(r){let{pascal:e,kebab:t,repoPrefix:o="../../domain/repositories",dtoPrefix:n="../../application/dtos"}=r;return`/**
|
|
617
1147
|
* In-Memory ${e} Repository
|
|
618
1148
|
*
|
|
619
1149
|
* Implements the repository interface using a Map.
|
|
@@ -626,9 +1156,9 @@ import { randomUUID } from 'node:crypto'
|
|
|
626
1156
|
import { Repository, HttpException } from '@forinda/kickjs-core'
|
|
627
1157
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
628
1158
|
import type { I${e}Repository } from '${o}/${t}.repository'
|
|
629
|
-
import type { ${e}ResponseDTO } from '${
|
|
630
|
-
import type { Create${e}DTO } from '${
|
|
631
|
-
import type { Update${e}DTO } from '${
|
|
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'
|
|
632
1162
|
|
|
633
1163
|
@Repository()
|
|
634
1164
|
export class InMemory${e}Repository implements I${e}Repository {
|
|
@@ -668,166 +1198,81 @@ export class InMemory${e}Repository implements I${e}Repository {
|
|
|
668
1198
|
return updated
|
|
669
1199
|
}
|
|
670
1200
|
|
|
671
|
-
async delete(id: string): Promise<void> {
|
|
672
|
-
if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
|
|
673
|
-
this.store.delete(id)
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
`}s(F,"generateInMemoryRepository");function L(e,t,o="../../domain/repositories",r="../../application/dtos"){return`/**
|
|
677
|
-
* Drizzle ${e} Repository
|
|
678
|
-
*
|
|
679
|
-
* Implements the repository interface using Drizzle ORM.
|
|
680
|
-
* Uses buildFromColumns() with Column objects for type-safe query building.
|
|
681
|
-
*
|
|
682
|
-
* TODO: Update the schema import to match your Drizzle schema file.
|
|
683
|
-
* TODO: Replace DRIZZLE_DB injection token with your actual database token.
|
|
684
|
-
*
|
|
685
|
-
* @Repository() registers this class in the DI container as a singleton.
|
|
686
|
-
*/
|
|
687
|
-
import { eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc, count, sql } from 'drizzle-orm'
|
|
688
|
-
import { Repository, HttpException, Inject } from '@forinda/kickjs-core'
|
|
689
|
-
import { DRIZZLE_DB, DrizzleQueryAdapter } from '@forinda/kickjs-drizzle'
|
|
690
|
-
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
691
|
-
import type { I${e}Repository } from '${o}/${t}.repository'
|
|
692
|
-
import type { ${e}ResponseDTO } from '${r}/${t}-response.dto'
|
|
693
|
-
import type { Create${e}DTO } from '${r}/create-${t}.dto'
|
|
694
|
-
import type { Update${e}DTO } from '${r}/update-${t}.dto'
|
|
695
|
-
import { ${e.toUpperCase()}_QUERY_CONFIG } from '../../constants'
|
|
696
|
-
|
|
697
|
-
// TODO: Import your Drizzle schema table \u2014 e.g.:
|
|
698
|
-
// import { ${t}s } from '@/db/schema'
|
|
699
|
-
|
|
700
|
-
const queryAdapter = new DrizzleQueryAdapter({
|
|
701
|
-
eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc,
|
|
702
|
-
})
|
|
703
|
-
|
|
704
|
-
@Repository()
|
|
705
|
-
export class Drizzle${e}Repository implements I${e}Repository {
|
|
706
|
-
constructor(@Inject(DRIZZLE_DB) private db: any) {}
|
|
707
|
-
|
|
708
|
-
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
709
|
-
// TODO: Implement with Drizzle
|
|
710
|
-
// const row = this.db.select().from(${t}s).where(eq(${t}s.id, id)).get()
|
|
711
|
-
// return row ?? null
|
|
712
|
-
throw new Error('Drizzle ${e} repository not yet implemented \u2014 update schema imports and queries')
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
716
|
-
// TODO: Implement with Drizzle
|
|
717
|
-
// return this.db.select().from(${t}s).all()
|
|
718
|
-
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
722
|
-
// TODO: Use buildFromColumns() with your query config for type-safe filtering
|
|
723
|
-
// const query = queryAdapter.buildFromColumns(parsed, ${e.toUpperCase()}_QUERY_CONFIG)
|
|
724
|
-
//
|
|
725
|
-
// const data = this.db
|
|
726
|
-
// .select().from(${t}s).$dynamic()
|
|
727
|
-
// .where(query.where).orderBy(...query.orderBy)
|
|
728
|
-
// .limit(query.limit).offset(query.offset).all()
|
|
729
|
-
//
|
|
730
|
-
// const totalResult = this.db
|
|
731
|
-
// .select({ count: count() }).from(${t}s)
|
|
732
|
-
// .$dynamic().where(query.where).get()
|
|
733
|
-
//
|
|
734
|
-
// return { data, total: totalResult?.count ?? 0 }
|
|
735
|
-
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
739
|
-
// TODO: Implement with Drizzle
|
|
740
|
-
// return this.db.insert(${t}s).values(dto).returning().get()
|
|
741
|
-
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
745
|
-
// TODO: Implement with Drizzle
|
|
746
|
-
// const row = this.db.update(${t}s).set(dto).where(eq(${t}s.id, id)).returning().get()
|
|
747
|
-
// if (!row) throw HttpException.notFound('${e} not found')
|
|
748
|
-
// return row
|
|
749
|
-
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
async delete(id: string): Promise<void> {
|
|
753
|
-
// TODO: Implement with Drizzle
|
|
754
|
-
// this.db.delete(${t}s).where(eq(${t}s.id, id)).run()
|
|
755
|
-
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
1201
|
+
async delete(id: string): Promise<void> {
|
|
1202
|
+
if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
|
|
1203
|
+
this.store.delete(id)
|
|
756
1204
|
}
|
|
757
1205
|
}
|
|
758
|
-
`}s(
|
|
759
|
-
*
|
|
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
|
|
760
1208
|
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
1209
|
+
* Stub implementation for a custom '${o}' repository.
|
|
1210
|
+
* Implements the repository interface using an in-memory Map as a placeholder.
|
|
763
1211
|
*
|
|
764
|
-
* TODO:
|
|
765
|
-
*
|
|
1212
|
+
* TODO: Replace the in-memory Map with your ${o} data-access logic.
|
|
1213
|
+
* See I${e}Repository for the interface contract.
|
|
766
1214
|
*
|
|
767
1215
|
* @Repository() registers this class in the DI container as a singleton.
|
|
768
1216
|
*/
|
|
769
|
-
import {
|
|
1217
|
+
import { randomUUID } from 'node:crypto'
|
|
1218
|
+
import { Repository, HttpException } from '@forinda/kickjs-core'
|
|
770
1219
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
771
|
-
import type { I${e}Repository } from '${
|
|
772
|
-
import type { ${e}ResponseDTO } from '${
|
|
773
|
-
import type { Create${e}DTO } from '${
|
|
774
|
-
import type { Update${e}DTO } from '${
|
|
775
|
-
|
|
776
|
-
// TODO: Import your Prisma injection token \u2014 e.g.:
|
|
777
|
-
// import { PRISMA_CLIENT } from '@/db/prisma.provider'
|
|
778
|
-
// import type { PrismaClient } from '@prisma/client'
|
|
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'
|
|
779
1224
|
|
|
780
1225
|
@Repository()
|
|
781
|
-
export class
|
|
782
|
-
// TODO:
|
|
783
|
-
|
|
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>()
|
|
784
1229
|
|
|
785
1230
|
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
786
|
-
// TODO: Implement with
|
|
787
|
-
|
|
788
|
-
throw new Error('Prisma ${e} repository not yet implemented \u2014 update Prisma imports and queries')
|
|
1231
|
+
// TODO: Implement with ${o}
|
|
1232
|
+
return this.store.get(id) ?? null
|
|
789
1233
|
}
|
|
790
1234
|
|
|
791
1235
|
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
792
|
-
// TODO: Implement with
|
|
793
|
-
|
|
794
|
-
throw new Error('Prisma ${e} repository not yet implemented')
|
|
1236
|
+
// TODO: Implement with ${o}
|
|
1237
|
+
return Array.from(this.store.values())
|
|
795
1238
|
}
|
|
796
1239
|
|
|
797
1240
|
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
798
|
-
// TODO: Implement with
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
// take: parsed.pagination.limit,
|
|
803
|
-
// }),
|
|
804
|
-
// this.prisma.${n}.count(),
|
|
805
|
-
// ])
|
|
806
|
-
// return { data, total }
|
|
807
|
-
throw new Error('Prisma ${e} repository not yet implemented')
|
|
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 }
|
|
808
1245
|
}
|
|
809
1246
|
|
|
810
1247
|
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
811
|
-
// TODO: Implement with
|
|
812
|
-
|
|
813
|
-
|
|
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
|
|
814
1258
|
}
|
|
815
1259
|
|
|
816
1260
|
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
817
|
-
// TODO: Implement with
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
|
822
1267
|
}
|
|
823
1268
|
|
|
824
1269
|
async delete(id: string): Promise<void> {
|
|
825
|
-
// TODO: Implement with
|
|
826
|
-
|
|
827
|
-
|
|
1270
|
+
// TODO: Implement with ${o}
|
|
1271
|
+
if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
|
|
1272
|
+
this.store.delete(id)
|
|
828
1273
|
}
|
|
829
1274
|
}
|
|
830
|
-
`}s(
|
|
1275
|
+
`}s(G,"generateCustomRepository");function ue(r){let{pascal:e,kebab:t}=r;return`/**
|
|
831
1276
|
* ${e} Domain Service
|
|
832
1277
|
*
|
|
833
1278
|
* Domain layer \u2014 contains business rules that don't belong to a single entity.
|
|
@@ -850,7 +1295,7 @@ export class ${e}DomainService {
|
|
|
850
1295
|
}
|
|
851
1296
|
}
|
|
852
1297
|
}
|
|
853
|
-
`}s(
|
|
1298
|
+
`}s(ue,"generateDomainService");function fe(r){let{pascal:e,kebab:t}=r;return`/**
|
|
854
1299
|
* ${e} Entity
|
|
855
1300
|
*
|
|
856
1301
|
* Domain layer \u2014 the core business object.
|
|
@@ -919,7 +1364,7 @@ export class ${e} {
|
|
|
919
1364
|
}
|
|
920
1365
|
}
|
|
921
1366
|
}
|
|
922
|
-
`}s(
|
|
1367
|
+
`}s(fe,"generateEntity");function ge(r){let{pascal:e,kebab:t}=r;return`/**
|
|
923
1368
|
* ${e} ID Value Object
|
|
924
1369
|
*
|
|
925
1370
|
* Domain layer \u2014 wraps a primitive ID with type safety and validation.
|
|
@@ -953,7 +1398,7 @@ export class ${e}Id {
|
|
|
953
1398
|
return this.value === other.value
|
|
954
1399
|
}
|
|
955
1400
|
}
|
|
956
|
-
`}s(
|
|
1401
|
+
`}s(ge,"generateValueObject");function Q(r){let{pascal:e,kebab:t,plural:o=""}=r;return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
957
1402
|
import { Container } from '@forinda/kickjs-core'
|
|
958
1403
|
|
|
959
1404
|
describe('${e}Controller', () => {
|
|
@@ -1005,8 +1450,8 @@ describe('${e}Controller', () => {
|
|
|
1005
1450
|
})
|
|
1006
1451
|
})
|
|
1007
1452
|
})
|
|
1008
|
-
`}s(
|
|
1009
|
-
import { InMemory${e}Repository } from '${
|
|
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}'
|
|
1010
1455
|
|
|
1011
1456
|
describe('InMemory${e}Repository', () => {
|
|
1012
1457
|
let repo: InMemory${e}Repository
|
|
@@ -1067,7 +1512,7 @@ describe('InMemory${e}Repository', () => {
|
|
|
1067
1512
|
expect(found).toBeNull()
|
|
1068
1513
|
})
|
|
1069
1514
|
})
|
|
1070
|
-
`}s(
|
|
1515
|
+
`}s(F,"generateRepositoryTest");function $e(r){let{pascal:e,kebab:t}=r;return`import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
1071
1516
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1072
1517
|
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from './${t}.repository'
|
|
1073
1518
|
import type { ${e}ResponseDTO } from './dtos/${t}-response.dto'
|
|
@@ -1104,14 +1549,14 @@ export class ${e}Service {
|
|
|
1104
1549
|
await this.repo.delete(id)
|
|
1105
1550
|
}
|
|
1106
1551
|
}
|
|
1107
|
-
`}s($e,"generateRestService");function
|
|
1552
|
+
`}s($e,"generateRestService");function V(r){let{pascal:e}=r;return`import type { QueryFieldConfig } from '@forinda/kickjs-http'
|
|
1108
1553
|
|
|
1109
1554
|
export const ${e.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
1110
1555
|
filterable: ['name'],
|
|
1111
1556
|
sortable: ['name', 'createdAt'],
|
|
1112
1557
|
searchable: ['name'],
|
|
1113
1558
|
}
|
|
1114
|
-
`}s(
|
|
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`/**
|
|
1115
1560
|
* ${e} Module \u2014 CQRS Pattern
|
|
1116
1561
|
*
|
|
1117
1562
|
* Separates read (queries) and write (commands) operations.
|
|
@@ -1127,7 +1572,7 @@ export const ${e.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
|
1127
1572
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
1128
1573
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
1129
1574
|
import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
|
|
1130
|
-
import { ${d} } from './${
|
|
1575
|
+
import { ${d} } from './${a}.repository'
|
|
1131
1576
|
import { ${e}Controller } from './${t}.controller'
|
|
1132
1577
|
|
|
1133
1578
|
// Eagerly load decorated classes
|
|
@@ -1156,14 +1601,14 @@ export class ${e}Module implements AppModule {
|
|
|
1156
1601
|
}
|
|
1157
1602
|
}
|
|
1158
1603
|
}
|
|
1159
|
-
`}s(
|
|
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'
|
|
1160
1605
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1161
1606
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
1162
1607
|
import { Create${e}Command } from './commands/create-${t}.command'
|
|
1163
1608
|
import { Update${e}Command } from './commands/update-${t}.command'
|
|
1164
1609
|
import { Delete${e}Command } from './commands/delete-${t}.command'
|
|
1165
1610
|
import { Get${e}Query } from './queries/get-${t}.query'
|
|
1166
|
-
import { List${
|
|
1611
|
+
import { List${n}Query } from './queries/list-${o}.query'
|
|
1167
1612
|
import { create${e}Schema } from './dtos/create-${t}.dto'
|
|
1168
1613
|
import { update${e}Schema } from './dtos/update-${t}.dto'
|
|
1169
1614
|
import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
|
|
@@ -1174,14 +1619,14 @@ export class ${e}Controller {
|
|
|
1174
1619
|
@Autowired() private update${e}Command!: Update${e}Command
|
|
1175
1620
|
@Autowired() private delete${e}Command!: Delete${e}Command
|
|
1176
1621
|
@Autowired() private get${e}Query!: Get${e}Query
|
|
1177
|
-
@Autowired() private list${
|
|
1622
|
+
@Autowired() private list${n}Query!: List${n}Query
|
|
1178
1623
|
|
|
1179
1624
|
@Get('/')
|
|
1180
1625
|
@ApiTags('${e}')
|
|
1181
1626
|
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
1182
1627
|
async list(ctx: RequestContext) {
|
|
1183
1628
|
return ctx.paginate(
|
|
1184
|
-
(parsed) => this.list${
|
|
1629
|
+
(parsed) => this.list${n}Query.execute(parsed),
|
|
1185
1630
|
${e.toUpperCase()}_QUERY_CONFIG,
|
|
1186
1631
|
)
|
|
1187
1632
|
}
|
|
@@ -1215,7 +1660,7 @@ export class ${e}Controller {
|
|
|
1215
1660
|
ctx.noContent()
|
|
1216
1661
|
}
|
|
1217
1662
|
}
|
|
1218
|
-
`}s(
|
|
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'
|
|
1219
1664
|
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1220
1665
|
import type { Create${e}DTO } from '../dtos/create-${t}.dto'
|
|
1221
1666
|
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
@@ -1269,7 +1714,7 @@ export class Delete${e}Command {
|
|
|
1269
1714
|
this.events.emit('${t}.deleted', { id })
|
|
1270
1715
|
}
|
|
1271
1716
|
}
|
|
1272
|
-
`}]}s(
|
|
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'
|
|
1273
1718
|
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1274
1719
|
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1275
1720
|
|
|
@@ -1288,7 +1733,7 @@ import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.rep
|
|
|
1288
1733
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1289
1734
|
|
|
1290
1735
|
@Service()
|
|
1291
|
-
export class List${
|
|
1736
|
+
export class List${n}Query {
|
|
1292
1737
|
constructor(
|
|
1293
1738
|
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1294
1739
|
) {}
|
|
@@ -1297,7 +1742,7 @@ export class List${r}Query {
|
|
|
1297
1742
|
return this.repo.findPaginated(parsed)
|
|
1298
1743
|
}
|
|
1299
1744
|
}
|
|
1300
|
-
`}]}s(we,"generateCqrsQueries");function ve(e,t
|
|
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'
|
|
1301
1746
|
import { EventEmitter } from 'node:events'
|
|
1302
1747
|
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1303
1748
|
|
|
@@ -1383,25 +1828,183 @@ export class On${e}ChangeHandler {
|
|
|
1383
1828
|
})
|
|
1384
1829
|
}
|
|
1385
1830
|
}
|
|
1386
|
-
`}]}s(ve,"generateCqrsEvents");function
|
|
1387
|
-
|
|
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'
|
|
1388
1990
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1389
1991
|
|
|
1390
1992
|
@Controller()
|
|
1391
|
-
export class ${
|
|
1993
|
+
export class ${e}Controller {
|
|
1392
1994
|
@Get('/')
|
|
1393
1995
|
async list(ctx: RequestContext) {
|
|
1394
|
-
ctx.json({ message: '${
|
|
1996
|
+
ctx.json({ message: '${e} list' })
|
|
1395
1997
|
}
|
|
1396
1998
|
}
|
|
1397
|
-
`)}s(
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
`+i
|
|
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'
|
|
1405
2008
|
import type { AppAdapter, AdapterMiddleware, Container } from '@forinda/kickjs-core'
|
|
1406
2009
|
|
|
1407
2010
|
export interface ${n}AdapterOptions {
|
|
@@ -1455,7 +2058,7 @@ export class ${n}Adapter implements AppAdapter {
|
|
|
1455
2058
|
*/
|
|
1456
2059
|
beforeMount(app: Express, container: Container): void {
|
|
1457
2060
|
// Example: mount a status route
|
|
1458
|
-
// app.get('/${
|
|
2061
|
+
// app.get('/${o}/status', (_req, res) => {
|
|
1459
2062
|
// res.json({ status: 'ok' })
|
|
1460
2063
|
// })
|
|
1461
2064
|
}
|
|
@@ -1487,31 +2090,31 @@ export class ${n}Adapter implements AppAdapter {
|
|
|
1487
2090
|
// await this.pool.end()
|
|
1488
2091
|
}
|
|
1489
2092
|
}
|
|
1490
|
-
`),i.push(
|
|
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'
|
|
1491
2094
|
|
|
1492
|
-
export interface ${
|
|
2095
|
+
export interface ${$(e)}Options {
|
|
1493
2096
|
// Add configuration options here
|
|
1494
2097
|
}
|
|
1495
2098
|
|
|
1496
2099
|
/**
|
|
1497
|
-
* ${
|
|
2100
|
+
* ${$(e)} middleware.
|
|
1498
2101
|
*
|
|
1499
2102
|
* Usage in bootstrap:
|
|
1500
|
-
* middleware: [${
|
|
2103
|
+
* middleware: [${d}()]
|
|
1501
2104
|
*
|
|
1502
2105
|
* Usage with adapter:
|
|
1503
|
-
* middleware() { return [{ handler: ${
|
|
2106
|
+
* middleware() { return [{ handler: ${d}(), phase: 'afterGlobal' }] }
|
|
1504
2107
|
*
|
|
1505
2108
|
* Usage with @Middleware decorator:
|
|
1506
|
-
* @Middleware(${
|
|
2109
|
+
* @Middleware(${d}())
|
|
1507
2110
|
*/
|
|
1508
|
-
export function ${
|
|
2111
|
+
export function ${d}(options: ${$(e)}Options = {}) {
|
|
1509
2112
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
1510
2113
|
// Implement your middleware logic here
|
|
1511
2114
|
next()
|
|
1512
2115
|
}
|
|
1513
2116
|
}
|
|
1514
|
-
`),a.push(p),a}s(
|
|
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'
|
|
1515
2118
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1516
2119
|
|
|
1517
2120
|
/**
|
|
@@ -1521,11 +2124,11 @@ import type { RequestContext } from '@forinda/kickjs-http'
|
|
|
1521
2124
|
* Return early with an error response to block access.
|
|
1522
2125
|
*
|
|
1523
2126
|
* Usage:
|
|
1524
|
-
* @Middleware(${
|
|
2127
|
+
* @Middleware(${d}Guard)
|
|
1525
2128
|
* @Get('/protected')
|
|
1526
2129
|
* async handler(ctx: RequestContext) { ... }
|
|
1527
2130
|
*/
|
|
1528
|
-
export async function ${
|
|
2131
|
+
export async function ${d}Guard(ctx: RequestContext, next: () => void): Promise<void> {
|
|
1529
2132
|
// Example: check for an authorization header
|
|
1530
2133
|
const header = ctx.headers.authorization
|
|
1531
2134
|
if (!header?.startsWith('Bearer ')) {
|
|
@@ -1547,46 +2150,46 @@ export async function ${c}Guard(ctx: RequestContext, next: () => void): Promise<
|
|
|
1547
2150
|
ctx.res.status(401).json({ message: 'Invalid or expired token' })
|
|
1548
2151
|
}
|
|
1549
2152
|
}
|
|
1550
|
-
`),p.push(
|
|
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'
|
|
1551
2154
|
|
|
1552
2155
|
@Service()
|
|
1553
|
-
export class ${
|
|
2156
|
+
export class ${d}Service {
|
|
1554
2157
|
// Inject dependencies via constructor
|
|
1555
2158
|
// constructor(
|
|
1556
2159
|
// @Inject(MY_REPO) private readonly repo: IMyRepository,
|
|
1557
2160
|
// ) {}
|
|
1558
2161
|
}
|
|
1559
|
-
`),a.push(p),a}s(
|
|
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'
|
|
1560
2163
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1561
2164
|
|
|
1562
2165
|
@Controller()
|
|
1563
|
-
export class ${
|
|
2166
|
+
export class ${d}Controller {
|
|
1564
2167
|
// @Autowired() private myService!: MyService
|
|
1565
2168
|
|
|
1566
2169
|
@Get('/')
|
|
1567
2170
|
async list(ctx: RequestContext) {
|
|
1568
|
-
ctx.json({ message: '${
|
|
2171
|
+
ctx.json({ message: '${d} list' })
|
|
1569
2172
|
}
|
|
1570
2173
|
|
|
1571
2174
|
@Post('/')
|
|
1572
2175
|
async create(ctx: RequestContext) {
|
|
1573
|
-
ctx.created({ message: '${
|
|
2176
|
+
ctx.created({ message: '${d} created', data: ctx.body })
|
|
1574
2177
|
}
|
|
1575
2178
|
}
|
|
1576
|
-
`),a.push(p),a}s(
|
|
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'
|
|
1577
2180
|
|
|
1578
2181
|
export const ${a}Schema = z.object({
|
|
1579
2182
|
// Define your schema fields here
|
|
1580
2183
|
name: z.string().min(1).max(200),
|
|
1581
2184
|
})
|
|
1582
2185
|
|
|
1583
|
-
export type ${
|
|
1584
|
-
`),p.push(
|
|
1585
|
-
Skipped \u2014 existing kick.config.ts preserved.`),[]):(await
|
|
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'
|
|
1586
2189
|
|
|
1587
2190
|
export default defineConfig({
|
|
1588
|
-
modulesDir: '${
|
|
1589
|
-
defaultRepo: '${
|
|
2191
|
+
modulesDir: '${t}',
|
|
2192
|
+
defaultRepo: '${o}',
|
|
1590
2193
|
|
|
1591
2194
|
commands: [
|
|
1592
2195
|
{
|
|
@@ -1612,11 +2215,11 @@ export default defineConfig({
|
|
|
1612
2215
|
},
|
|
1613
2216
|
],
|
|
1614
2217
|
})
|
|
1615
|
-
`),[
|
|
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'
|
|
1616
2219
|
import { Resolver, Query, Mutation, Arg } from '@forinda/kickjs-graphql'
|
|
1617
2220
|
|
|
1618
2221
|
/**
|
|
1619
|
-
* ${
|
|
2222
|
+
* ${o} GraphQL Resolver
|
|
1620
2223
|
*
|
|
1621
2224
|
* Decorators:
|
|
1622
2225
|
* @Resolver(typeName?) \u2014 marks this class as a GraphQL resolver
|
|
@@ -1625,35 +2228,35 @@ import { Resolver, Query, Mutation, Arg } from '@forinda/kickjs-graphql'
|
|
|
1625
2228
|
* @Arg(name, type?) \u2014 marks a method parameter as a GraphQL argument
|
|
1626
2229
|
*/
|
|
1627
2230
|
@Service()
|
|
1628
|
-
@Resolver('${
|
|
1629
|
-
export class ${
|
|
2231
|
+
@Resolver('${o}')
|
|
2232
|
+
export class ${o}Resolver {
|
|
1630
2233
|
private items: Array<{ id: string; name: string }> = []
|
|
1631
2234
|
|
|
1632
|
-
@Query('${i}s', { returnType: '[${
|
|
2235
|
+
@Query('${i}s', { returnType: '[${o}]', description: 'List all ${i}s' })
|
|
1633
2236
|
findAll() {
|
|
1634
2237
|
return this.items
|
|
1635
2238
|
}
|
|
1636
2239
|
|
|
1637
|
-
@Query('${i}', { returnType: '${
|
|
2240
|
+
@Query('${i}', { returnType: '${o}', description: 'Get a ${i} by ID' })
|
|
1638
2241
|
findById(@Arg('id', 'ID!') id: string) {
|
|
1639
2242
|
return this.items.find((item) => item.id === id) ?? null
|
|
1640
2243
|
}
|
|
1641
2244
|
|
|
1642
|
-
@Mutation('create${
|
|
2245
|
+
@Mutation('create${o}', { returnType: '${o}', description: 'Create a new ${i}' })
|
|
1643
2246
|
create(@Arg('name', 'String!') name: string) {
|
|
1644
2247
|
const item = { id: String(this.items.length + 1), name }
|
|
1645
2248
|
this.items.push(item)
|
|
1646
2249
|
return item
|
|
1647
2250
|
}
|
|
1648
2251
|
|
|
1649
|
-
@Mutation('update${
|
|
2252
|
+
@Mutation('update${o}', { returnType: '${o}', description: 'Update a ${i}' })
|
|
1650
2253
|
update(@Arg('id', 'ID!') id: string, @Arg('name', 'String!') name: string) {
|
|
1651
2254
|
const item = this.items.find((i) => i.id === id)
|
|
1652
2255
|
if (item) item.name = name
|
|
1653
2256
|
return item
|
|
1654
2257
|
}
|
|
1655
2258
|
|
|
1656
|
-
@Mutation('delete${
|
|
2259
|
+
@Mutation('delete${o}', { returnType: 'Boolean', description: 'Delete a ${i}' })
|
|
1657
2260
|
remove(@Arg('id', 'ID!') id: string) {
|
|
1658
2261
|
const idx = this.items.findIndex((i) => i.id === id)
|
|
1659
2262
|
if (idx === -1) return false
|
|
@@ -1661,21 +2264,21 @@ export class ${r}Resolver {
|
|
|
1661
2264
|
return true
|
|
1662
2265
|
}
|
|
1663
2266
|
}
|
|
1664
|
-
`),await
|
|
1665
|
-
* ${
|
|
2267
|
+
`),await d(`${n}.typedefs.ts`,`/**
|
|
2268
|
+
* ${o} GraphQL type definitions.
|
|
1666
2269
|
* Pass to GraphQLAdapter's typeDefs option to register custom types.
|
|
1667
2270
|
*/
|
|
1668
2271
|
export const ${i}TypeDefs = \`
|
|
1669
|
-
type ${
|
|
2272
|
+
type ${o} {
|
|
1670
2273
|
id: ID!
|
|
1671
2274
|
name: String!
|
|
1672
2275
|
}
|
|
1673
2276
|
\`
|
|
1674
|
-
`),
|
|
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'
|
|
1675
2278
|
import { Job, Process, QUEUE_MANAGER, type QueueService } from '@forinda/kickjs-queue'
|
|
1676
2279
|
|
|
1677
2280
|
/**
|
|
1678
|
-
* ${
|
|
2281
|
+
* ${o} Job Processor
|
|
1679
2282
|
*
|
|
1680
2283
|
* Decorators:
|
|
1681
2284
|
* @Job(queueName) \u2014 marks this class as a job processor for a queue
|
|
@@ -1685,10 +2288,10 @@ import { Job, Process, QUEUE_MANAGER, type QueueService } from '@forinda/kickjs-
|
|
|
1685
2288
|
*
|
|
1686
2289
|
* To add jobs to this queue from a service or controller:
|
|
1687
2290
|
* @Inject(QUEUE_MANAGER) private queue: QueueService
|
|
1688
|
-
* await this.queue.add('${
|
|
2291
|
+
* await this.queue.add('${c}', '${i}', { ... })
|
|
1689
2292
|
*/
|
|
1690
|
-
@Job('${
|
|
1691
|
-
export class ${
|
|
2293
|
+
@Job('${c}')
|
|
2294
|
+
export class ${o}Job {
|
|
1692
2295
|
@Process()
|
|
1693
2296
|
async handle(job: { name: string; data: any; id?: string }) {
|
|
1694
2297
|
console.log(\`Processing \${job.name} (id: \${job.id})\`, job.data)
|
|
@@ -1704,68 +2307,68 @@ export class ${r}Job {
|
|
|
1704
2307
|
// Handle high-priority variant of this job
|
|
1705
2308
|
}
|
|
1706
2309
|
}
|
|
1707
|
-
`),
|
|
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(`
|
|
1708
2311
|
`);return`import { z } from 'zod'
|
|
1709
2312
|
|
|
1710
|
-
export const create${
|
|
1711
|
-
${
|
|
2313
|
+
export const create${r}Schema = z.object({
|
|
2314
|
+
${t}
|
|
1712
2315
|
})
|
|
1713
2316
|
|
|
1714
|
-
export type Create${
|
|
1715
|
-
`}s(
|
|
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(`
|
|
1716
2319
|
`);return`import { z } from 'zod'
|
|
1717
2320
|
|
|
1718
|
-
export const update${
|
|
1719
|
-
${
|
|
2321
|
+
export const update${r}Schema = z.object({
|
|
2322
|
+
${t}
|
|
1720
2323
|
})
|
|
1721
2324
|
|
|
1722
|
-
export type Update${
|
|
1723
|
-
`}s(
|
|
1724
|
-
`);return`export interface ${
|
|
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 {
|
|
1725
2328
|
id: string
|
|
1726
|
-
${
|
|
2329
|
+
${t}
|
|
1727
2330
|
createdAt: string
|
|
1728
2331
|
updatedAt: string
|
|
1729
2332
|
}
|
|
1730
|
-
`}s(
|
|
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'
|
|
1731
2334
|
|
|
1732
|
-
export const ${
|
|
2335
|
+
export const ${r.toUpperCase()}_QUERY_CONFIG: ApiQueryParamsConfig = {
|
|
1733
2336
|
filterable: [${i}],
|
|
1734
|
-
sortable: [${
|
|
1735
|
-
searchable: [${
|
|
2337
|
+
sortable: [${c}],
|
|
2338
|
+
searchable: [${d}],
|
|
1736
2339
|
}
|
|
1737
|
-
`}s(
|
|
2340
|
+
`}s(jr,"genConstants");function Ar(r,e,t){let o=t.map(i=>` ${i.name}: dto.${i.name},`).join(`
|
|
1738
2341
|
`);return`import { randomUUID } from 'node:crypto'
|
|
1739
2342
|
import { Repository, HttpException } from '@forinda/kickjs-core'
|
|
1740
2343
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1741
|
-
import type { I${
|
|
1742
|
-
import type { ${
|
|
1743
|
-
import type { Create${
|
|
1744
|
-
import type { Update${
|
|
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'
|
|
1745
2348
|
|
|
1746
2349
|
@Repository()
|
|
1747
|
-
export class InMemory${
|
|
1748
|
-
private store = new Map<string, ${
|
|
2350
|
+
export class InMemory${r}Repository implements I${r}Repository {
|
|
2351
|
+
private store = new Map<string, ${r}ResponseDTO>()
|
|
1749
2352
|
|
|
1750
|
-
async findById(id: string): Promise<${
|
|
2353
|
+
async findById(id: string): Promise<${r}ResponseDTO | null> {
|
|
1751
2354
|
return this.store.get(id) ?? null
|
|
1752
2355
|
}
|
|
1753
2356
|
|
|
1754
|
-
async findAll(): Promise<${
|
|
2357
|
+
async findAll(): Promise<${r}ResponseDTO[]> {
|
|
1755
2358
|
return Array.from(this.store.values())
|
|
1756
2359
|
}
|
|
1757
2360
|
|
|
1758
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
2361
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${r}ResponseDTO[]; total: number }> {
|
|
1759
2362
|
const all = Array.from(this.store.values())
|
|
1760
2363
|
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
1761
2364
|
return { data, total: all.length }
|
|
1762
2365
|
}
|
|
1763
2366
|
|
|
1764
|
-
async create(dto: Create${
|
|
2367
|
+
async create(dto: Create${r}DTO): Promise<${r}ResponseDTO> {
|
|
1765
2368
|
const now = new Date().toISOString()
|
|
1766
|
-
const entity: ${
|
|
2369
|
+
const entity: ${r}ResponseDTO = {
|
|
1767
2370
|
id: randomUUID(),
|
|
1768
|
-
${
|
|
2371
|
+
${o}
|
|
1769
2372
|
createdAt: now,
|
|
1770
2373
|
updatedAt: now,
|
|
1771
2374
|
}
|
|
@@ -1773,237 +2376,237 @@ ${r}
|
|
|
1773
2376
|
return entity
|
|
1774
2377
|
}
|
|
1775
2378
|
|
|
1776
|
-
async update(id: string, dto: Update${
|
|
2379
|
+
async update(id: string, dto: Update${r}DTO): Promise<${r}ResponseDTO> {
|
|
1777
2380
|
const existing = this.store.get(id)
|
|
1778
|
-
if (!existing) throw HttpException.notFound('${
|
|
2381
|
+
if (!existing) throw HttpException.notFound('${r} not found')
|
|
1779
2382
|
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
1780
2383
|
this.store.set(id, updated)
|
|
1781
2384
|
return updated
|
|
1782
2385
|
}
|
|
1783
2386
|
|
|
1784
2387
|
async delete(id: string): Promise<void> {
|
|
1785
|
-
if (!this.store.has(id)) throw HttpException.notFound('${
|
|
2388
|
+
if (!this.store.has(id)) throw HttpException.notFound('${r} not found')
|
|
1786
2389
|
this.store.delete(id)
|
|
1787
2390
|
}
|
|
1788
2391
|
}
|
|
1789
|
-
`}s(
|
|
1790
|
-
`),n=
|
|
1791
|
-
`),
|
|
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":""} {
|
|
1792
2395
|
return this.props.${a.name}
|
|
1793
2396
|
}`).join(`
|
|
1794
|
-
`),
|
|
1795
|
-
`);return`import { ${
|
|
2397
|
+
`),d=t.map(a=>` ${a.name}: this.props.${a.name},`).join(`
|
|
2398
|
+
`);return`import { ${r}Id } from '../value-objects/${e}-id.vo'
|
|
1796
2399
|
|
|
1797
|
-
interface ${
|
|
1798
|
-
id: ${
|
|
1799
|
-
${
|
|
2400
|
+
interface ${r}Props {
|
|
2401
|
+
id: ${r}Id
|
|
2402
|
+
${o}
|
|
1800
2403
|
createdAt: Date
|
|
1801
2404
|
updatedAt: Date
|
|
1802
2405
|
}
|
|
1803
2406
|
|
|
1804
|
-
export class ${
|
|
1805
|
-
private constructor(private props: ${
|
|
2407
|
+
export class ${r} {
|
|
2408
|
+
private constructor(private props: ${r}Props) {}
|
|
1806
2409
|
|
|
1807
|
-
static create(params: { ${n} }): ${
|
|
2410
|
+
static create(params: { ${n} }): ${r} {
|
|
1808
2411
|
const now = new Date()
|
|
1809
|
-
return new ${
|
|
1810
|
-
id: ${
|
|
2412
|
+
return new ${r}({
|
|
2413
|
+
id: ${r}Id.create(),
|
|
1811
2414
|
${i}
|
|
1812
2415
|
createdAt: now,
|
|
1813
2416
|
updatedAt: now,
|
|
1814
2417
|
})
|
|
1815
2418
|
}
|
|
1816
2419
|
|
|
1817
|
-
static reconstitute(props: ${
|
|
1818
|
-
return new ${
|
|
2420
|
+
static reconstitute(props: ${r}Props): ${r} {
|
|
2421
|
+
return new ${r}(props)
|
|
1819
2422
|
}
|
|
1820
2423
|
|
|
1821
|
-
get id(): ${
|
|
1822
|
-
${
|
|
2424
|
+
get id(): ${r}Id { return this.props.id }
|
|
2425
|
+
${c}
|
|
1823
2426
|
get createdAt(): Date { return this.props.createdAt }
|
|
1824
2427
|
get updatedAt(): Date { return this.props.updatedAt }
|
|
1825
2428
|
|
|
1826
2429
|
toJSON() {
|
|
1827
2430
|
return {
|
|
1828
2431
|
id: this.props.id.toString(),
|
|
1829
|
-
${
|
|
2432
|
+
${d}
|
|
1830
2433
|
createdAt: this.props.createdAt.toISOString(),
|
|
1831
2434
|
updatedAt: this.props.updatedAt.toISOString(),
|
|
1832
2435
|
}
|
|
1833
2436
|
}
|
|
1834
2437
|
}
|
|
1835
|
-
`}s(
|
|
2438
|
+
`}s(Er,"genEntity");function Ur(r){return`import { randomUUID } from 'node:crypto'
|
|
1836
2439
|
|
|
1837
|
-
export class ${
|
|
2440
|
+
export class ${r}Id {
|
|
1838
2441
|
private constructor(private readonly value: string) {}
|
|
1839
2442
|
|
|
1840
|
-
static create(): ${
|
|
2443
|
+
static create(): ${r}Id { return new ${r}Id(randomUUID()) }
|
|
1841
2444
|
|
|
1842
|
-
static from(id: string): ${
|
|
1843
|
-
if (!id || id.trim().length === 0) throw new Error('${
|
|
1844
|
-
return new ${
|
|
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)
|
|
1845
2448
|
}
|
|
1846
2449
|
|
|
1847
2450
|
toString(): string { return this.value }
|
|
1848
|
-
equals(other: ${
|
|
2451
|
+
equals(other: ${r}Id): boolean { return this.value === other.value }
|
|
1849
2452
|
}
|
|
1850
|
-
`}s(
|
|
1851
|
-
import { ${
|
|
1852
|
-
import { ${
|
|
1853
|
-
import { ${
|
|
1854
|
-
import { InMemory${
|
|
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'
|
|
1855
2458
|
|
|
1856
|
-
export class ${
|
|
2459
|
+
export class ${r}Module implements AppModule {
|
|
1857
2460
|
register(container: any): void {
|
|
1858
2461
|
container.registerFactory(
|
|
1859
|
-
${
|
|
1860
|
-
() => container.resolve(InMemory${
|
|
2462
|
+
${r.toUpperCase()}_REPOSITORY,
|
|
2463
|
+
() => container.resolve(InMemory${r}Repository),
|
|
1861
2464
|
)
|
|
1862
2465
|
}
|
|
1863
2466
|
|
|
1864
2467
|
routes() {
|
|
1865
|
-
return { prefix: '/${
|
|
2468
|
+
return { prefix: '/${t}', controllers: [${r}Controller] }
|
|
1866
2469
|
}
|
|
1867
2470
|
}
|
|
1868
|
-
`}s(
|
|
2471
|
+
`}s(zr,"genModuleIndex");function Mr(r,e,t,o){return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
1869
2472
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1870
2473
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
1871
|
-
import { Create${
|
|
1872
|
-
import { Get${
|
|
1873
|
-
import { List${
|
|
1874
|
-
import { Update${
|
|
1875
|
-
import { Delete${
|
|
1876
|
-
import { create${
|
|
1877
|
-
import { update${
|
|
1878
|
-
import { ${
|
|
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'
|
|
1879
2482
|
|
|
1880
2483
|
@Controller()
|
|
1881
|
-
export class ${
|
|
1882
|
-
@Autowired() private create${
|
|
1883
|
-
@Autowired() private get${
|
|
1884
|
-
@Autowired() private list${
|
|
1885
|
-
@Autowired() private update${
|
|
1886
|
-
@Autowired() private delete${
|
|
2484
|
+
export class ${r}Controller {
|
|
2485
|
+
@Autowired() private create${r}UseCase!: Create${r}UseCase
|
|
2486
|
+
@Autowired() private get${r}UseCase!: Get${r}UseCase
|
|
2487
|
+
@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
|
|
1887
2490
|
|
|
1888
2491
|
@Get('/')
|
|
1889
|
-
@ApiTags('${
|
|
1890
|
-
@ApiQueryParams(${
|
|
2492
|
+
@ApiTags('${r}')
|
|
2493
|
+
@ApiQueryParams(${r.toUpperCase()}_QUERY_CONFIG)
|
|
1891
2494
|
async list(ctx: RequestContext) {
|
|
1892
2495
|
return ctx.paginate(
|
|
1893
|
-
(parsed) => this.list${
|
|
1894
|
-
${
|
|
2496
|
+
(parsed) => this.list${o}UseCase.execute(parsed),
|
|
2497
|
+
${r.toUpperCase()}_QUERY_CONFIG,
|
|
1895
2498
|
)
|
|
1896
2499
|
}
|
|
1897
2500
|
|
|
1898
2501
|
@Get('/:id')
|
|
1899
|
-
@ApiTags('${
|
|
2502
|
+
@ApiTags('${r}')
|
|
1900
2503
|
async getById(ctx: RequestContext) {
|
|
1901
|
-
const result = await this.get${
|
|
1902
|
-
if (!result) return ctx.notFound('${
|
|
2504
|
+
const result = await this.get${r}UseCase.execute(ctx.params.id)
|
|
2505
|
+
if (!result) return ctx.notFound('${r} not found')
|
|
1903
2506
|
ctx.json(result)
|
|
1904
2507
|
}
|
|
1905
2508
|
|
|
1906
|
-
@Post('/', { body: create${
|
|
1907
|
-
@ApiTags('${
|
|
2509
|
+
@Post('/', { body: create${r}Schema, name: 'Create${r}' })
|
|
2510
|
+
@ApiTags('${r}')
|
|
1908
2511
|
async create(ctx: RequestContext) {
|
|
1909
|
-
const result = await this.create${
|
|
2512
|
+
const result = await this.create${r}UseCase.execute(ctx.body)
|
|
1910
2513
|
ctx.created(result)
|
|
1911
2514
|
}
|
|
1912
2515
|
|
|
1913
|
-
@Put('/:id', { body: update${
|
|
1914
|
-
@ApiTags('${
|
|
2516
|
+
@Put('/:id', { body: update${r}Schema, name: 'Update${r}' })
|
|
2517
|
+
@ApiTags('${r}')
|
|
1915
2518
|
async update(ctx: RequestContext) {
|
|
1916
|
-
const result = await this.update${
|
|
2519
|
+
const result = await this.update${r}UseCase.execute(ctx.params.id, ctx.body)
|
|
1917
2520
|
ctx.json(result)
|
|
1918
2521
|
}
|
|
1919
2522
|
|
|
1920
2523
|
@Delete('/:id')
|
|
1921
|
-
@ApiTags('${
|
|
2524
|
+
@ApiTags('${r}')
|
|
1922
2525
|
async remove(ctx: RequestContext) {
|
|
1923
|
-
await this.delete${
|
|
2526
|
+
await this.delete${r}UseCase.execute(ctx.params.id)
|
|
1924
2527
|
ctx.noContent()
|
|
1925
2528
|
}
|
|
1926
2529
|
}
|
|
1927
|
-
`}s(
|
|
1928
|
-
import type { Create${
|
|
1929
|
-
import type { Update${
|
|
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'
|
|
1930
2533
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1931
2534
|
|
|
1932
|
-
export interface I${
|
|
1933
|
-
findById(id: string): Promise<${
|
|
1934
|
-
findAll(): Promise<${
|
|
1935
|
-
findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
1936
|
-
create(dto: Create${
|
|
1937
|
-
update(id: string, dto: Update${
|
|
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>
|
|
1938
2541
|
delete(id: string): Promise<void>
|
|
1939
2542
|
}
|
|
1940
2543
|
|
|
1941
|
-
export const ${
|
|
1942
|
-
`}s(
|
|
1943
|
-
import { ${
|
|
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'
|
|
1944
2547
|
|
|
1945
2548
|
@Service()
|
|
1946
|
-
export class ${
|
|
2549
|
+
export class ${r}DomainService {
|
|
1947
2550
|
constructor(
|
|
1948
|
-
@Inject(${
|
|
2551
|
+
@Inject(${r.toUpperCase()}_REPOSITORY) private readonly repo: I${r}Repository,
|
|
1949
2552
|
) {}
|
|
1950
2553
|
|
|
1951
2554
|
async ensureExists(id: string): Promise<void> {
|
|
1952
2555
|
const entity = await this.repo.findById(id)
|
|
1953
|
-
if (!entity) throw HttpException.notFound('${
|
|
2556
|
+
if (!entity) throw HttpException.notFound('${r} not found')
|
|
1954
2557
|
}
|
|
1955
2558
|
}
|
|
1956
|
-
`}s(
|
|
1957
|
-
import { ${
|
|
1958
|
-
import type { Create${
|
|
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'
|
|
1959
2562
|
|
|
1960
2563
|
@Service()
|
|
1961
|
-
export class Create${
|
|
1962
|
-
constructor(@Inject(${
|
|
1963
|
-
async execute(dto: Create${
|
|
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) }
|
|
1964
2567
|
}
|
|
1965
|
-
`},{file:`get-${
|
|
1966
|
-
import { ${
|
|
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'
|
|
1967
2570
|
|
|
1968
2571
|
@Service()
|
|
1969
|
-
export class Get${
|
|
1970
|
-
constructor(@Inject(${
|
|
2572
|
+
export class Get${r}UseCase {
|
|
2573
|
+
constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
|
|
1971
2574
|
async execute(id: string) { return this.repo.findById(id) }
|
|
1972
2575
|
}
|
|
1973
|
-
`},{file:`list-${
|
|
2576
|
+
`},{file:`list-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1974
2577
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1975
|
-
import { ${
|
|
2578
|
+
import { ${r.toUpperCase()}_REPOSITORY, type I${r}Repository } from '../../domain/repositories/${e}.repository'
|
|
1976
2579
|
|
|
1977
2580
|
@Service()
|
|
1978
|
-
export class List${
|
|
1979
|
-
constructor(@Inject(${
|
|
2581
|
+
export class List${o}UseCase {
|
|
2582
|
+
constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
|
|
1980
2583
|
async execute(parsed: ParsedQuery) { return this.repo.findPaginated(parsed) }
|
|
1981
2584
|
}
|
|
1982
|
-
`},{file:`update-${
|
|
1983
|
-
import { ${
|
|
1984
|
-
import type { Update${
|
|
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'
|
|
1985
2588
|
|
|
1986
2589
|
@Service()
|
|
1987
|
-
export class Update${
|
|
1988
|
-
constructor(@Inject(${
|
|
1989
|
-
async execute(id: string, dto: Update${
|
|
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) }
|
|
1990
2593
|
}
|
|
1991
|
-
`},{file:`delete-${
|
|
1992
|
-
import { ${
|
|
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'
|
|
1993
2596
|
|
|
1994
2597
|
@Service()
|
|
1995
|
-
export class Delete${
|
|
1996
|
-
constructor(@Inject(${
|
|
2598
|
+
export class Delete${r}UseCase {
|
|
2599
|
+
constructor(@Inject(${r.toUpperCase()}_REPOSITORY) private repo: I${r}Repository) {}
|
|
1997
2600
|
async execute(id: string) { return this.repo.delete(id) }
|
|
1998
2601
|
}
|
|
1999
|
-
`}]}s(
|
|
2000
|
-
import { ${
|
|
2001
|
-
|
|
2002
|
-
export const modules: AppModuleClass[] = [${
|
|
2003
|
-
`);return}let i=await
|
|
2004
|
-
`,
|
|
2005
|
-
`+i.slice(a+1)}else i=
|
|
2006
|
-
`+i;i=i.replace(/(=\s*\[)([\s\S]*?)(])/,(a,p,
|
|
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'
|
|
2007
2610
|
import { Container } from '@forinda/kickjs-core'
|
|
2008
2611
|
|
|
2009
2612
|
describe('${i}', () => {
|
|
@@ -2026,36 +2629,36 @@ describe('${i}', () => {
|
|
|
2026
2629
|
expect(true).toBe(true)
|
|
2027
2630
|
})
|
|
2028
2631
|
})
|
|
2029
|
-
`),
|
|
2030
|
-
${
|
|
2031
|
-
(dry run \u2014 no files were written)`),console.log()}s(
|
|
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(`
|
|
2032
2635
|
Available generators:
|
|
2033
|
-
`);let
|
|
2034
|
-
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(o,
|
|
2035
|
-
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(o,
|
|
2036
|
-
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(o,
|
|
2037
|
-
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(o,
|
|
2038
|
-
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(o,
|
|
2039
|
-
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(o,
|
|
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
|
|
2040
2643
|
Example: kick g scaffold Post title:string body:text published:boolean?
|
|
2041
2644
|
Types: string, text, number, int, float, boolean, date, email, url, uuid, json, enum:a,b,c
|
|
2042
|
-
Append ? for optional fields: description:text?`).option("--no-entity","Skip entity and value object generation").option("--no-tests","Skip test file generation").option("--modules-dir <dir>","Modules directory").action(async(o,
|
|
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(`
|
|
2043
2646
|
Error: At least one field is required.
|
|
2044
2647
|
Usage: kick g scaffold <name> <field:type> [field:type...]
|
|
2045
2648
|
Example: kick g scaffold Post title:string body:text published:boolean
|
|
2046
|
-
`),process.exit(1));let
|
|
2047
|
-
Scaffolded ${
|
|
2048
|
-
KickJS dev server starting...`),console.log(` Entry: ${
|
|
2049
|
-
`);try{
|
|
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(`
|
|
2050
2653
|
Building for production...
|
|
2051
|
-
`),
|
|
2052
|
-
Copying directories to dist...`);for(let
|
|
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(`
|
|
2053
2656
|
Build complete.
|
|
2054
|
-
`)}),
|
|
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(`
|
|
2055
2658
|
KickJS CLI
|
|
2056
2659
|
|
|
2057
2660
|
System:
|
|
2058
|
-
OS: ${
|
|
2661
|
+
OS: ${Vr()} ${Zr()} (${Xr()})
|
|
2059
2662
|
Node: ${process.version}
|
|
2060
2663
|
|
|
2061
2664
|
Packages:
|
|
@@ -2063,32 +2666,32 @@ describe('${i}', () => {
|
|
|
2063
2666
|
@forinda/kickjs-http workspace
|
|
2064
2667
|
@forinda/kickjs-config workspace
|
|
2065
2668
|
@forinda/kickjs-cli workspace
|
|
2066
|
-
`)})}s(
|
|
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(`
|
|
2067
2670
|
Available KickJS packages:
|
|
2068
|
-
`);let
|
|
2069
|
-
Usage: kick add graphql drizzle otel`),console.log(" kick add queue:bullmq"),console.log()}s(
|
|
2070
|
-
Unknown packages: ${
|
|
2071
|
-
`),i.size===0&&
|
|
2072
|
-
Installing ${a.length} dependency(ies):`);for(let
|
|
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(`
|
|
2073
2676
|
Installation failed. Run manually:
|
|
2074
2677
|
${p}
|
|
2075
|
-
`)}}if(
|
|
2076
|
-
Installing ${a.length} dev dependency(ies):`);for(let
|
|
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(`
|
|
2077
2680
|
Installation failed. Run manually:
|
|
2078
2681
|
${p}
|
|
2079
2682
|
`)}}console.log(` Done!
|
|
2080
|
-
`)}})}s(
|
|
2081
|
-
Error: ${
|
|
2082
|
-
`),process.exit(1));let n=
|
|
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(`
|
|
2083
2686
|
Error: tsx not found. Install it: pnpm add -D tsx
|
|
2084
|
-
`),process.exit(1));let i=
|
|
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`
|
|
2085
2688
|
import 'reflect-metadata'
|
|
2086
2689
|
|
|
2087
2690
|
// Prevent bootstrap() from starting the HTTP server
|
|
2088
2691
|
process.env.KICK_TINKER = '1'
|
|
2089
2692
|
|
|
2090
2693
|
console.log('\\n \u{1F527} KickJS Tinker')
|
|
2091
|
-
console.log(' Loading: ${
|
|
2694
|
+
console.log(' Loading: ${e}\\n')
|
|
2092
2695
|
|
|
2093
2696
|
// Load core
|
|
2094
2697
|
let Container, Logger, HttpException, HttpStatus
|
|
@@ -2106,7 +2709,7 @@ try {
|
|
|
2106
2709
|
|
|
2107
2710
|
// Load entry to trigger decorator registration
|
|
2108
2711
|
try {
|
|
2109
|
-
await import('${
|
|
2712
|
+
await import('${t}')
|
|
2110
2713
|
} catch (err) {
|
|
2111
2714
|
console.warn(' Warning: ' + err.message)
|
|
2112
2715
|
console.warn(' Container may be partially initialized.\\n')
|
|
@@ -2135,4 +2738,12 @@ server.on('exit', () => {
|
|
|
2135
2738
|
console.log('\\n Goodbye!\\n')
|
|
2136
2739
|
process.exit(0)
|
|
2137
2740
|
})
|
|
2138
|
-
`}s(
|
|
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(`
|
|
2744
|
+
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});
|