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