@forinda/kickjs-cli 1.2.10 → 1.2.12
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.js +695 -2912
- package/dist/index.js +470 -1410
- package/package.json +1 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,88 +1,5 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// src/generators/module.ts
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
import { createInterface } from "readline";
|
|
7
|
-
|
|
8
|
-
// src/utils/fs.ts
|
|
9
|
-
import { writeFile, mkdir, access, readFile } from "fs/promises";
|
|
10
|
-
import { dirname } from "path";
|
|
11
|
-
var _dryRun = false;
|
|
12
|
-
async function writeFileSafe(filePath, content) {
|
|
13
|
-
if (_dryRun) return;
|
|
14
|
-
await mkdir(dirname(filePath), {
|
|
15
|
-
recursive: true
|
|
16
|
-
});
|
|
17
|
-
await writeFile(filePath, content, "utf-8");
|
|
18
|
-
}
|
|
19
|
-
__name(writeFileSafe, "writeFileSafe");
|
|
20
|
-
async function fileExists(filePath) {
|
|
21
|
-
try {
|
|
22
|
-
await access(filePath);
|
|
23
|
-
return true;
|
|
24
|
-
} catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
__name(fileExists, "fileExists");
|
|
29
|
-
|
|
30
|
-
// src/utils/naming.ts
|
|
31
|
-
function toPascalCase(name) {
|
|
32
|
-
return name.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
|
|
33
|
-
}
|
|
34
|
-
__name(toPascalCase, "toPascalCase");
|
|
35
|
-
function toCamelCase(name) {
|
|
36
|
-
const pascal = toPascalCase(name);
|
|
37
|
-
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
38
|
-
}
|
|
39
|
-
__name(toCamelCase, "toCamelCase");
|
|
40
|
-
function toKebabCase(name) {
|
|
41
|
-
return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
42
|
-
}
|
|
43
|
-
__name(toKebabCase, "toKebabCase");
|
|
44
|
-
function pluralize(name) {
|
|
45
|
-
if (name.endsWith("s")) return name;
|
|
46
|
-
if (name.endsWith("x") || name.endsWith("z")) return name + "es";
|
|
47
|
-
if (name.endsWith("sh") || name.endsWith("ch")) return name + "es";
|
|
48
|
-
if (name.endsWith("y") && !/[aeiou]y$/.test(name)) return name.slice(0, -1) + "ies";
|
|
49
|
-
return name + "s";
|
|
50
|
-
}
|
|
51
|
-
__name(pluralize, "pluralize");
|
|
52
|
-
function pluralizePascal(name) {
|
|
53
|
-
if (name.endsWith("s")) return name;
|
|
54
|
-
if (name.endsWith("x") || name.endsWith("z")) return name + "es";
|
|
55
|
-
if (name.endsWith("sh") || name.endsWith("ch")) return name + "es";
|
|
56
|
-
if (name.endsWith("y") && !/[aeiou]y$/i.test(name)) return name.slice(0, -1) + "ies";
|
|
57
|
-
return name + "s";
|
|
58
|
-
}
|
|
59
|
-
__name(pluralizePascal, "pluralizePascal");
|
|
60
|
-
|
|
61
|
-
// src/generators/module.ts
|
|
62
|
-
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
63
|
-
|
|
64
|
-
// src/generators/templates/module-index.ts
|
|
65
|
-
function repoMaps(pascal, kebab, repo) {
|
|
66
|
-
const repoClassMap = {
|
|
67
|
-
inmemory: `InMemory${pascal}Repository`,
|
|
68
|
-
drizzle: `Drizzle${pascal}Repository`,
|
|
69
|
-
prisma: `Prisma${pascal}Repository`
|
|
70
|
-
};
|
|
71
|
-
const repoFileMap = {
|
|
72
|
-
inmemory: `in-memory-${kebab}`,
|
|
73
|
-
drizzle: `drizzle-${kebab}`,
|
|
74
|
-
prisma: `prisma-${kebab}`
|
|
75
|
-
};
|
|
76
|
-
return {
|
|
77
|
-
repoClass: repoClassMap[repo] ?? repoClassMap.inmemory,
|
|
78
|
-
repoFile: repoFileMap[repo] ?? repoFileMap.inmemory
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
__name(repoMaps, "repoMaps");
|
|
82
|
-
function generateModuleIndex(pascal, kebab, plural, repo) {
|
|
83
|
-
const { repoClass, repoFile } = repoMaps(pascal, kebab, repo);
|
|
84
|
-
return `/**
|
|
85
|
-
* ${pascal} Module
|
|
1
|
+
var me=Object.defineProperty;var i=(e,t)=>me(e,"name",{value:t,configurable:!0});import{join as oe}from"path";import{createInterface as ge}from"readline";import{writeFile as ue,mkdir as le,access as fe,readFile as it}from"fs/promises";import{dirname as $e}from"path";var ye=!1;async function c(e,t){ye||(await le($e(e),{recursive:!0}),await ue(e,t,"utf-8"))}i(c,"writeFileSafe");async function F(e){try{return await fe(e),!0}catch{return!1}}i(F,"fileExists");function l(e){return e.replace(/[-_\s]+(.)?/g,(t,r)=>r?r.toUpperCase():"").replace(/^(.)/,t=>t.toUpperCase())}i(l,"toPascalCase");function C(e){let t=l(e);return t.charAt(0).toLowerCase()+t.slice(1)}i(C,"toCamelCase");function f(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}i(f,"toKebabCase");function x(e){return e.endsWith("s")?e:e.endsWith("x")||e.endsWith("z")||e.endsWith("sh")||e.endsWith("ch")?e+"es":e.endsWith("y")&&!/[aeiou]y$/.test(e)?e.slice(0,-1)+"ies":e+"s"}i(x,"pluralize");function ae(e){return e.endsWith("s")?e:e.endsWith("x")||e.endsWith("z")||e.endsWith("sh")||e.endsWith("ch")?e+"es":e.endsWith("y")&&!/[aeiou]y$/i.test(e)?e.slice(0,-1)+"ies":e+"s"}i(ae,"pluralizePascal");import{readFile as he,writeFile as we}from"fs/promises";function de(e,t,r){let o={inmemory:`InMemory${e}Repository`,drizzle:`Drizzle${e}Repository`,prisma:`Prisma${e}Repository`},n={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`};return{repoClass:o[r]??o.inmemory,repoFile:n[r]??n.inmemory}}i(de,"repoMaps");function Q(e,t,r,o){let{repoClass:n,repoFile:s}=de(e,t,o);return`/**
|
|
2
|
+
* ${e} Module
|
|
86
3
|
*
|
|
87
4
|
* Self-contained feature module following Domain-Driven Design (DDD).
|
|
88
5
|
* Registers dependencies in the DI container and declares HTTP routes.
|
|
@@ -95,9 +12,9 @@ function generateModuleIndex(pascal, kebab, plural, repo) {
|
|
|
95
12
|
*/
|
|
96
13
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
97
14
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
98
|
-
import { ${
|
|
99
|
-
import { ${
|
|
100
|
-
import { ${
|
|
15
|
+
import { ${e.toUpperCase()}_REPOSITORY } from './domain/repositories/${t}.repository'
|
|
16
|
+
import { ${n} } from './infrastructure/repositories/${s}.repository'
|
|
17
|
+
import { ${e}Controller } from './presentation/${t}.controller'
|
|
101
18
|
|
|
102
19
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
103
20
|
import.meta.glob(
|
|
@@ -105,402 +22,325 @@ import.meta.glob(
|
|
|
105
22
|
{ eager: true },
|
|
106
23
|
)
|
|
107
24
|
|
|
108
|
-
export class ${
|
|
25
|
+
export class ${e}Module implements AppModule {
|
|
109
26
|
/**
|
|
110
27
|
* Register module dependencies in the DI container.
|
|
111
28
|
* Bind repository interface tokens to their implementations here.
|
|
112
29
|
* To swap implementations (e.g. in-memory -> Drizzle), change the factory target.
|
|
113
30
|
*/
|
|
114
31
|
register(container: Container): void {
|
|
115
|
-
container.registerFactory(${
|
|
116
|
-
container.resolve(${
|
|
32
|
+
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
33
|
+
container.resolve(${n}),
|
|
117
34
|
)
|
|
118
35
|
}
|
|
119
36
|
|
|
120
37
|
/**
|
|
121
38
|
* Declare HTTP routes for this module.
|
|
122
|
-
* The path is prefixed with the global apiPrefix and version (e.g. /api/v1/${
|
|
39
|
+
* The path is prefixed with the global apiPrefix and version (e.g. /api/v1/${r}).
|
|
123
40
|
* Passing 'controller' enables automatic OpenAPI spec generation via SwaggerAdapter.
|
|
124
41
|
*/
|
|
125
42
|
routes(): ModuleRoutes {
|
|
126
43
|
return {
|
|
127
|
-
path: '/${
|
|
128
|
-
router: buildRoutes(${
|
|
129
|
-
controller: ${
|
|
44
|
+
path: '/${r}',
|
|
45
|
+
router: buildRoutes(${e}Controller),
|
|
46
|
+
controller: ${e}Controller,
|
|
130
47
|
}
|
|
131
48
|
}
|
|
132
49
|
}
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
__name(generateModuleIndex, "generateModuleIndex");
|
|
136
|
-
function generateRestModuleIndex(pascal, kebab, plural, repo) {
|
|
137
|
-
const { repoClass, repoFile } = repoMaps(pascal, kebab, repo);
|
|
138
|
-
return `/**
|
|
139
|
-
* ${pascal} Module
|
|
50
|
+
`}i(Q,"generateModuleIndex");function G(e,t,r,o){let{repoClass:n,repoFile:s}=de(e,t,o);return`/**
|
|
51
|
+
* ${e} Module
|
|
140
52
|
*
|
|
141
53
|
* REST module with a flat folder structure.
|
|
142
54
|
* Controller delegates to service, service wraps the repository.
|
|
143
55
|
*
|
|
144
56
|
* Structure:
|
|
145
|
-
* ${
|
|
146
|
-
* ${
|
|
147
|
-
* ${
|
|
148
|
-
* ${
|
|
57
|
+
* ${t}.controller.ts \u2014 HTTP routes (CRUD)
|
|
58
|
+
* ${t}.service.ts \u2014 Business logic
|
|
59
|
+
* ${t}.repository.ts \u2014 Repository interface
|
|
60
|
+
* ${s}.repository.ts \u2014 Repository implementation
|
|
149
61
|
* dtos/ \u2014 Request/response schemas
|
|
150
62
|
*/
|
|
151
63
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
152
64
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
153
|
-
import { ${
|
|
154
|
-
import { ${
|
|
155
|
-
import { ${
|
|
65
|
+
import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
|
|
66
|
+
import { ${n} } from './${s}.repository'
|
|
67
|
+
import { ${e}Controller } from './${t}.controller'
|
|
156
68
|
|
|
157
69
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
158
70
|
import.meta.glob(['./**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'], { eager: true })
|
|
159
71
|
|
|
160
|
-
export class ${
|
|
72
|
+
export class ${e}Module implements AppModule {
|
|
161
73
|
register(container: Container): void {
|
|
162
|
-
container.registerFactory(${
|
|
163
|
-
container.resolve(${
|
|
74
|
+
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
75
|
+
container.resolve(${n}),
|
|
164
76
|
)
|
|
165
77
|
}
|
|
166
78
|
|
|
167
79
|
routes(): ModuleRoutes {
|
|
168
80
|
return {
|
|
169
|
-
path: '/${
|
|
170
|
-
router: buildRoutes(${
|
|
171
|
-
controller: ${
|
|
81
|
+
path: '/${r}',
|
|
82
|
+
router: buildRoutes(${e}Controller),
|
|
83
|
+
controller: ${e}Controller,
|
|
172
84
|
}
|
|
173
85
|
}
|
|
174
86
|
}
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
__name(generateRestModuleIndex, "generateRestModuleIndex");
|
|
178
|
-
function generateMinimalModuleIndex(pascal, kebab, plural) {
|
|
179
|
-
return `import { type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
87
|
+
`}i(G,"generateRestModuleIndex");function N(e,t,r){return`import { type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
180
88
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
181
|
-
import { ${
|
|
89
|
+
import { ${e}Controller } from './${t}.controller'
|
|
182
90
|
|
|
183
|
-
export class ${
|
|
91
|
+
export class ${e}Module implements AppModule {
|
|
184
92
|
routes(): ModuleRoutes {
|
|
185
93
|
return {
|
|
186
|
-
path: '/${
|
|
187
|
-
router: buildRoutes(${
|
|
188
|
-
controller: ${
|
|
94
|
+
path: '/${r}',
|
|
95
|
+
router: buildRoutes(${e}Controller),
|
|
96
|
+
controller: ${e}Controller,
|
|
189
97
|
}
|
|
190
98
|
}
|
|
191
99
|
}
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
__name(generateMinimalModuleIndex, "generateMinimalModuleIndex");
|
|
195
|
-
|
|
196
|
-
// src/generators/templates/controller.ts
|
|
197
|
-
function generateController(pascal, kebab, plural, pluralPascal) {
|
|
198
|
-
return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
100
|
+
`}i(N,"generateMinimalModuleIndex");function Y(e,t,r,o){return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
199
101
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
200
102
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
201
|
-
import { Create${
|
|
202
|
-
import { Get${
|
|
203
|
-
import { List${
|
|
204
|
-
import { Update${
|
|
205
|
-
import { Delete${
|
|
206
|
-
import { create${
|
|
207
|
-
import { update${
|
|
208
|
-
import { ${
|
|
103
|
+
import { Create${e}UseCase } from '../application/use-cases/create-${t}.use-case'
|
|
104
|
+
import { Get${e}UseCase } from '../application/use-cases/get-${t}.use-case'
|
|
105
|
+
import { List${o}UseCase } from '../application/use-cases/list-${r}.use-case'
|
|
106
|
+
import { Update${e}UseCase } from '../application/use-cases/update-${t}.use-case'
|
|
107
|
+
import { Delete${e}UseCase } from '../application/use-cases/delete-${t}.use-case'
|
|
108
|
+
import { create${e}Schema } from '../application/dtos/create-${t}.dto'
|
|
109
|
+
import { update${e}Schema } from '../application/dtos/update-${t}.dto'
|
|
110
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from '../constants'
|
|
209
111
|
|
|
210
112
|
@Controller()
|
|
211
|
-
export class ${
|
|
212
|
-
@Autowired() private create${
|
|
213
|
-
@Autowired() private get${
|
|
214
|
-
@Autowired() private list${
|
|
215
|
-
@Autowired() private update${
|
|
216
|
-
@Autowired() private delete${
|
|
113
|
+
export class ${e}Controller {
|
|
114
|
+
@Autowired() private create${e}UseCase!: Create${e}UseCase
|
|
115
|
+
@Autowired() private get${e}UseCase!: Get${e}UseCase
|
|
116
|
+
@Autowired() private list${o}UseCase!: List${o}UseCase
|
|
117
|
+
@Autowired() private update${e}UseCase!: Update${e}UseCase
|
|
118
|
+
@Autowired() private delete${e}UseCase!: Delete${e}UseCase
|
|
217
119
|
|
|
218
120
|
@Get('/')
|
|
219
|
-
@ApiTags('${
|
|
220
|
-
@ApiQueryParams(${
|
|
121
|
+
@ApiTags('${e}')
|
|
122
|
+
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
221
123
|
async list(ctx: RequestContext) {
|
|
222
124
|
return ctx.paginate(
|
|
223
|
-
(parsed) => this.list${
|
|
224
|
-
${
|
|
125
|
+
(parsed) => this.list${o}UseCase.execute(parsed),
|
|
126
|
+
${e.toUpperCase()}_QUERY_CONFIG,
|
|
225
127
|
)
|
|
226
128
|
}
|
|
227
129
|
|
|
228
130
|
@Get('/:id')
|
|
229
|
-
@ApiTags('${
|
|
131
|
+
@ApiTags('${e}')
|
|
230
132
|
async getById(ctx: RequestContext) {
|
|
231
|
-
const result = await this.get${
|
|
232
|
-
if (!result) return ctx.notFound('${
|
|
133
|
+
const result = await this.get${e}UseCase.execute(ctx.params.id)
|
|
134
|
+
if (!result) return ctx.notFound('${e} not found')
|
|
233
135
|
ctx.json(result)
|
|
234
136
|
}
|
|
235
137
|
|
|
236
|
-
@Post('/', { body: create${
|
|
237
|
-
@ApiTags('${
|
|
138
|
+
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
139
|
+
@ApiTags('${e}')
|
|
238
140
|
async create(ctx: RequestContext) {
|
|
239
|
-
const result = await this.create${
|
|
141
|
+
const result = await this.create${e}UseCase.execute(ctx.body)
|
|
240
142
|
ctx.created(result)
|
|
241
143
|
}
|
|
242
144
|
|
|
243
|
-
@Put('/:id', { body: update${
|
|
244
|
-
@ApiTags('${
|
|
145
|
+
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
146
|
+
@ApiTags('${e}')
|
|
245
147
|
async update(ctx: RequestContext) {
|
|
246
|
-
const result = await this.update${
|
|
148
|
+
const result = await this.update${e}UseCase.execute(ctx.params.id, ctx.body)
|
|
247
149
|
ctx.json(result)
|
|
248
150
|
}
|
|
249
151
|
|
|
250
152
|
@Delete('/:id')
|
|
251
|
-
@ApiTags('${
|
|
153
|
+
@ApiTags('${e}')
|
|
252
154
|
async remove(ctx: RequestContext) {
|
|
253
|
-
await this.delete${
|
|
155
|
+
await this.delete${e}UseCase.execute(ctx.params.id)
|
|
254
156
|
ctx.noContent()
|
|
255
157
|
}
|
|
256
158
|
}
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
__name(generateController, "generateController");
|
|
260
|
-
function generateRestController(pascal, kebab, plural, pluralPascal) {
|
|
261
|
-
const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
262
|
-
return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
159
|
+
`}i(Y,"generateController");function B(e,t,r,o){let n=e.charAt(0).toLowerCase()+e.slice(1);return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
263
160
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
264
161
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
265
|
-
import { ${
|
|
266
|
-
import { create${
|
|
267
|
-
import { update${
|
|
268
|
-
import { ${
|
|
162
|
+
import { ${e}Service } from './${t}.service'
|
|
163
|
+
import { create${e}Schema } from './dtos/create-${t}.dto'
|
|
164
|
+
import { update${e}Schema } from './dtos/update-${t}.dto'
|
|
165
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
|
|
269
166
|
|
|
270
167
|
@Controller()
|
|
271
|
-
export class ${
|
|
272
|
-
@Autowired() private ${
|
|
168
|
+
export class ${e}Controller {
|
|
169
|
+
@Autowired() private ${n}Service!: ${e}Service
|
|
273
170
|
|
|
274
171
|
@Get('/')
|
|
275
|
-
@ApiTags('${
|
|
276
|
-
@ApiQueryParams(${
|
|
172
|
+
@ApiTags('${e}')
|
|
173
|
+
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
277
174
|
async list(ctx: RequestContext) {
|
|
278
175
|
return ctx.paginate(
|
|
279
|
-
(parsed) => this.${
|
|
280
|
-
${
|
|
176
|
+
(parsed) => this.${n}Service.findPaginated(parsed),
|
|
177
|
+
${e.toUpperCase()}_QUERY_CONFIG,
|
|
281
178
|
)
|
|
282
179
|
}
|
|
283
180
|
|
|
284
181
|
@Get('/:id')
|
|
285
|
-
@ApiTags('${
|
|
182
|
+
@ApiTags('${e}')
|
|
286
183
|
async getById(ctx: RequestContext) {
|
|
287
|
-
const result = await this.${
|
|
288
|
-
if (!result) return ctx.notFound('${
|
|
184
|
+
const result = await this.${n}Service.findById(ctx.params.id)
|
|
185
|
+
if (!result) return ctx.notFound('${e} not found')
|
|
289
186
|
ctx.json(result)
|
|
290
187
|
}
|
|
291
188
|
|
|
292
|
-
@Post('/', { body: create${
|
|
293
|
-
@ApiTags('${
|
|
189
|
+
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
190
|
+
@ApiTags('${e}')
|
|
294
191
|
async create(ctx: RequestContext) {
|
|
295
|
-
const result = await this.${
|
|
192
|
+
const result = await this.${n}Service.create(ctx.body)
|
|
296
193
|
ctx.created(result)
|
|
297
194
|
}
|
|
298
195
|
|
|
299
|
-
@Put('/:id', { body: update${
|
|
300
|
-
@ApiTags('${
|
|
196
|
+
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
197
|
+
@ApiTags('${e}')
|
|
301
198
|
async update(ctx: RequestContext) {
|
|
302
|
-
const result = await this.${
|
|
199
|
+
const result = await this.${n}Service.update(ctx.params.id, ctx.body)
|
|
303
200
|
ctx.json(result)
|
|
304
201
|
}
|
|
305
202
|
|
|
306
203
|
@Delete('/:id')
|
|
307
|
-
@ApiTags('${
|
|
204
|
+
@ApiTags('${e}')
|
|
308
205
|
async remove(ctx: RequestContext) {
|
|
309
|
-
await this.${
|
|
206
|
+
await this.${n}Service.delete(ctx.params.id)
|
|
310
207
|
ctx.noContent()
|
|
311
208
|
}
|
|
312
209
|
}
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
__name(generateRestController, "generateRestController");
|
|
316
|
-
|
|
317
|
-
// src/generators/templates/constants.ts
|
|
318
|
-
function generateConstants(pascal) {
|
|
319
|
-
return `import type { QueryParamsConfig } from '@forinda/kickjs-core'
|
|
210
|
+
`}i(B,"generateRestController");function L(e){return`import type { QueryParamsConfig } from '@forinda/kickjs-core'
|
|
320
211
|
|
|
321
|
-
export const ${
|
|
212
|
+
export const ${e.toUpperCase()}_QUERY_CONFIG: QueryParamsConfig = {
|
|
322
213
|
filterable: ['name'],
|
|
323
214
|
sortable: ['name', 'createdAt'],
|
|
324
215
|
searchable: ['name'],
|
|
325
216
|
}
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
__name(generateConstants, "generateConstants");
|
|
329
|
-
function generateDrizzleConstants(pascal, kebab) {
|
|
330
|
-
return `import type { DrizzleQueryParamsConfig } from '@forinda/kickjs-drizzle'
|
|
217
|
+
`}i(L,"generateConstants");function W(e,t){return`import type { DrizzleQueryParamsConfig } from '@forinda/kickjs-drizzle'
|
|
331
218
|
// TODO: Import your schema table and reference actual columns for type safety
|
|
332
|
-
// import { ${
|
|
219
|
+
// import { ${t}s } from '@/db/schema'
|
|
333
220
|
|
|
334
|
-
export const ${
|
|
221
|
+
export const ${e.toUpperCase()}_QUERY_CONFIG: DrizzleQueryParamsConfig = {
|
|
335
222
|
columns: {
|
|
336
223
|
// Replace with actual Drizzle Column references for type-safe filtering:
|
|
337
|
-
// name: ${
|
|
338
|
-
// status: ${
|
|
224
|
+
// name: ${t}s.name,
|
|
225
|
+
// status: ${t}s.status,
|
|
339
226
|
},
|
|
340
227
|
sortable: {
|
|
341
|
-
// name: ${
|
|
342
|
-
// createdAt: ${
|
|
228
|
+
// name: ${t}s.name,
|
|
229
|
+
// createdAt: ${t}s.createdAt,
|
|
343
230
|
},
|
|
344
231
|
searchColumns: [
|
|
345
|
-
// ${
|
|
232
|
+
// ${t}s.name,
|
|
346
233
|
],
|
|
347
234
|
}
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
__name(generateDrizzleConstants, "generateDrizzleConstants");
|
|
351
|
-
|
|
352
|
-
// src/generators/templates/dtos.ts
|
|
353
|
-
function generateCreateDTO(pascal, kebab) {
|
|
354
|
-
return `import { z } from 'zod'
|
|
235
|
+
`}i(W,"generateDrizzleConstants");function R(e,t){return`import { z } from 'zod'
|
|
355
236
|
|
|
356
237
|
/**
|
|
357
|
-
* Create ${
|
|
358
|
-
* This schema is passed to @Post('/', { body: create${
|
|
238
|
+
* Create ${e} DTO \u2014 Zod schema for validating POST request bodies.
|
|
239
|
+
* This schema is passed to @Post('/', { body: create${e}Schema }) for automatic validation.
|
|
359
240
|
* It also generates OpenAPI request body docs when SwaggerAdapter is used.
|
|
360
241
|
*
|
|
361
242
|
* Add more fields as needed. Supported Zod types:
|
|
362
243
|
* z.string(), z.number(), z.boolean(), z.enum([...]),
|
|
363
244
|
* z.array(), z.object(), .optional(), .default(), .transform()
|
|
364
245
|
*/
|
|
365
|
-
export const create${
|
|
246
|
+
export const create${e}Schema = z.object({
|
|
366
247
|
name: z.string().min(1, 'Name is required').max(200),
|
|
367
248
|
})
|
|
368
249
|
|
|
369
|
-
export type Create${
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
__name(generateCreateDTO, "generateCreateDTO");
|
|
373
|
-
function generateUpdateDTO(pascal, kebab) {
|
|
374
|
-
return `import { z } from 'zod'
|
|
250
|
+
export type Create${e}DTO = z.infer<typeof create${e}Schema>
|
|
251
|
+
`}i(R,"generateCreateDTO");function D(e,t){return`import { z } from 'zod'
|
|
375
252
|
|
|
376
|
-
export const update${
|
|
253
|
+
export const update${e}Schema = z.object({
|
|
377
254
|
name: z.string().min(1).max(200).optional(),
|
|
378
255
|
})
|
|
379
256
|
|
|
380
|
-
export type Update${
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
__name(generateUpdateDTO, "generateUpdateDTO");
|
|
384
|
-
function generateResponseDTO(pascal, kebab) {
|
|
385
|
-
return `export interface ${pascal}ResponseDTO {
|
|
257
|
+
export type Update${e}DTO = z.infer<typeof update${e}Schema>
|
|
258
|
+
`}i(D,"generateUpdateDTO");function O(e,t){return`export interface ${e}ResponseDTO {
|
|
386
259
|
id: string
|
|
387
260
|
name: string
|
|
388
261
|
createdAt: string
|
|
389
262
|
updatedAt: string
|
|
390
263
|
}
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
__name(generateResponseDTO, "generateResponseDTO");
|
|
394
|
-
|
|
395
|
-
// src/generators/templates/use-cases.ts
|
|
396
|
-
function generateUseCases(pascal, kebab, plural, pluralPascal) {
|
|
397
|
-
return [
|
|
398
|
-
{
|
|
399
|
-
file: `create-${kebab}.use-case.ts`,
|
|
400
|
-
content: `/**
|
|
401
|
-
* Create ${pascal} Use Case
|
|
264
|
+
`}i(O,"generateResponseDTO");function b(e,t,r,o){return[{file:`create-${t}.use-case.ts`,content:`/**
|
|
265
|
+
* Create ${e} Use Case
|
|
402
266
|
*
|
|
403
267
|
* Application layer \u2014 orchestrates a single business operation.
|
|
404
268
|
* Use cases are thin: validate input (via DTO), call domain/repo, return response.
|
|
405
269
|
* Keep business rules in the domain service, not here.
|
|
406
270
|
*/
|
|
407
271
|
import { Service, Inject } from '@forinda/kickjs-core'
|
|
408
|
-
import { ${
|
|
409
|
-
import type { Create${
|
|
410
|
-
import type { ${
|
|
272
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
273
|
+
import type { Create${e}DTO } from '../dtos/create-${t}.dto'
|
|
274
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
411
275
|
|
|
412
276
|
@Service()
|
|
413
|
-
export class Create${
|
|
277
|
+
export class Create${e}UseCase {
|
|
414
278
|
constructor(
|
|
415
|
-
@Inject(${
|
|
279
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
416
280
|
) {}
|
|
417
281
|
|
|
418
|
-
async execute(dto: Create${
|
|
282
|
+
async execute(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
419
283
|
return this.repo.create(dto)
|
|
420
284
|
}
|
|
421
285
|
}
|
|
422
|
-
`
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
file: `get-${kebab}.use-case.ts`,
|
|
426
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
427
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
428
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
286
|
+
`},{file:`get-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
287
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
288
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
429
289
|
|
|
430
290
|
@Service()
|
|
431
|
-
export class Get${
|
|
291
|
+
export class Get${e}UseCase {
|
|
432
292
|
constructor(
|
|
433
|
-
@Inject(${
|
|
293
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
434
294
|
) {}
|
|
435
295
|
|
|
436
|
-
async execute(id: string): Promise<${
|
|
296
|
+
async execute(id: string): Promise<${e}ResponseDTO | null> {
|
|
437
297
|
return this.repo.findById(id)
|
|
438
298
|
}
|
|
439
299
|
}
|
|
440
|
-
`
|
|
441
|
-
|
|
442
|
-
{
|
|
443
|
-
file: `list-${plural}.use-case.ts`,
|
|
444
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
445
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
300
|
+
`},{file:`list-${r}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
301
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
446
302
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
447
303
|
|
|
448
304
|
@Service()
|
|
449
|
-
export class List${
|
|
305
|
+
export class List${o}UseCase {
|
|
450
306
|
constructor(
|
|
451
|
-
@Inject(${
|
|
307
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
452
308
|
) {}
|
|
453
309
|
|
|
454
310
|
async execute(parsed: ParsedQuery) {
|
|
455
311
|
return this.repo.findPaginated(parsed)
|
|
456
312
|
}
|
|
457
313
|
}
|
|
458
|
-
`
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
463
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
464
|
-
import type { Update${pascal}DTO } from '../dtos/update-${kebab}.dto'
|
|
465
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
314
|
+
`},{file:`update-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
315
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
316
|
+
import type { Update${e}DTO } from '../dtos/update-${t}.dto'
|
|
317
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
466
318
|
|
|
467
319
|
@Service()
|
|
468
|
-
export class Update${
|
|
320
|
+
export class Update${e}UseCase {
|
|
469
321
|
constructor(
|
|
470
|
-
@Inject(${
|
|
322
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
471
323
|
) {}
|
|
472
324
|
|
|
473
|
-
async execute(id: string, dto: Update${
|
|
325
|
+
async execute(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
474
326
|
return this.repo.update(id, dto)
|
|
475
327
|
}
|
|
476
328
|
}
|
|
477
|
-
`
|
|
478
|
-
|
|
479
|
-
{
|
|
480
|
-
file: `delete-${kebab}.use-case.ts`,
|
|
481
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
482
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
329
|
+
`},{file:`delete-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
330
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
483
331
|
|
|
484
332
|
@Service()
|
|
485
|
-
export class Delete${
|
|
333
|
+
export class Delete${e}UseCase {
|
|
486
334
|
constructor(
|
|
487
|
-
@Inject(${
|
|
335
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
488
336
|
) {}
|
|
489
337
|
|
|
490
338
|
async execute(id: string): Promise<void> {
|
|
491
339
|
await this.repo.delete(id)
|
|
492
340
|
}
|
|
493
341
|
}
|
|
494
|
-
`
|
|
495
|
-
|
|
496
|
-
];
|
|
497
|
-
}
|
|
498
|
-
__name(generateUseCases, "generateUseCases");
|
|
499
|
-
|
|
500
|
-
// src/generators/templates/repository.ts
|
|
501
|
-
function generateRepositoryInterface(pascal, kebab, dtoPrefix = "../../application/dtos") {
|
|
502
|
-
return `/**
|
|
503
|
-
* ${pascal} Repository Interface
|
|
342
|
+
`}]}i(b,"generateUseCases");function k(e,t,r="../../application/dtos"){return`/**
|
|
343
|
+
* ${e} Repository Interface
|
|
504
344
|
*
|
|
505
345
|
* Defines the contract for data access.
|
|
506
346
|
* The interface declares what operations are available;
|
|
@@ -508,27 +348,23 @@ function generateRepositoryInterface(pascal, kebab, dtoPrefix = "../../applicati
|
|
|
508
348
|
*
|
|
509
349
|
* To swap implementations, change the factory in the module's register() method.
|
|
510
350
|
*/
|
|
511
|
-
import type { ${
|
|
512
|
-
import type { Create${
|
|
513
|
-
import type { Update${
|
|
351
|
+
import type { ${e}ResponseDTO } from '${r}/${t}-response.dto'
|
|
352
|
+
import type { Create${e}DTO } from '${r}/create-${t}.dto'
|
|
353
|
+
import type { Update${e}DTO } from '${r}/update-${t}.dto'
|
|
514
354
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
515
355
|
|
|
516
|
-
export interface I${
|
|
517
|
-
findById(id: string): Promise<${
|
|
518
|
-
findAll(): Promise<${
|
|
519
|
-
findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
520
|
-
create(dto: Create${
|
|
521
|
-
update(id: string, dto: Update${
|
|
356
|
+
export interface I${e}Repository {
|
|
357
|
+
findById(id: string): Promise<${e}ResponseDTO | null>
|
|
358
|
+
findAll(): Promise<${e}ResponseDTO[]>
|
|
359
|
+
findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }>
|
|
360
|
+
create(dto: Create${e}DTO): Promise<${e}ResponseDTO>
|
|
361
|
+
update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO>
|
|
522
362
|
delete(id: string): Promise<void>
|
|
523
363
|
}
|
|
524
364
|
|
|
525
|
-
export const ${
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
__name(generateRepositoryInterface, "generateRepositoryInterface");
|
|
529
|
-
function generateInMemoryRepository(pascal, kebab, repoPrefix = "../../domain/repositories", dtoPrefix = "../../application/dtos") {
|
|
530
|
-
return `/**
|
|
531
|
-
* In-Memory ${pascal} Repository
|
|
365
|
+
export const ${e.toUpperCase()}_REPOSITORY = Symbol('I${e}Repository')
|
|
366
|
+
`}i(k,"generateRepositoryInterface");function I(e,t,r="../../domain/repositories",o="../../application/dtos"){return`/**
|
|
367
|
+
* In-Memory ${e} Repository
|
|
532
368
|
*
|
|
533
369
|
* Implements the repository interface using a Map.
|
|
534
370
|
* Useful for prototyping and testing. Replace with a database implementation
|
|
@@ -539,32 +375,32 @@ function generateInMemoryRepository(pascal, kebab, repoPrefix = "../../domain/re
|
|
|
539
375
|
import { randomUUID } from 'node:crypto'
|
|
540
376
|
import { Repository, HttpException } from '@forinda/kickjs-core'
|
|
541
377
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
542
|
-
import type { I${
|
|
543
|
-
import type { ${
|
|
544
|
-
import type { Create${
|
|
545
|
-
import type { Update${
|
|
378
|
+
import type { I${e}Repository } from '${r}/${t}.repository'
|
|
379
|
+
import type { ${e}ResponseDTO } from '${o}/${t}-response.dto'
|
|
380
|
+
import type { Create${e}DTO } from '${o}/create-${t}.dto'
|
|
381
|
+
import type { Update${e}DTO } from '${o}/update-${t}.dto'
|
|
546
382
|
|
|
547
383
|
@Repository()
|
|
548
|
-
export class InMemory${
|
|
549
|
-
private store = new Map<string, ${
|
|
384
|
+
export class InMemory${e}Repository implements I${e}Repository {
|
|
385
|
+
private store = new Map<string, ${e}ResponseDTO>()
|
|
550
386
|
|
|
551
|
-
async findById(id: string): Promise<${
|
|
387
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
552
388
|
return this.store.get(id) ?? null
|
|
553
389
|
}
|
|
554
390
|
|
|
555
|
-
async findAll(): Promise<${
|
|
391
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
556
392
|
return Array.from(this.store.values())
|
|
557
393
|
}
|
|
558
394
|
|
|
559
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
395
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
560
396
|
const all = Array.from(this.store.values())
|
|
561
397
|
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
562
398
|
return { data, total: all.length }
|
|
563
399
|
}
|
|
564
400
|
|
|
565
|
-
async create(dto: Create${
|
|
401
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
566
402
|
const now = new Date().toISOString()
|
|
567
|
-
const entity: ${
|
|
403
|
+
const entity: ${e}ResponseDTO = {
|
|
568
404
|
id: randomUUID(),
|
|
569
405
|
name: dto.name,
|
|
570
406
|
createdAt: now,
|
|
@@ -574,25 +410,21 @@ export class InMemory${pascal}Repository implements I${pascal}Repository {
|
|
|
574
410
|
return entity
|
|
575
411
|
}
|
|
576
412
|
|
|
577
|
-
async update(id: string, dto: Update${
|
|
413
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
578
414
|
const existing = this.store.get(id)
|
|
579
|
-
if (!existing) throw HttpException.notFound('${
|
|
415
|
+
if (!existing) throw HttpException.notFound('${e} not found')
|
|
580
416
|
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
581
417
|
this.store.set(id, updated)
|
|
582
418
|
return updated
|
|
583
419
|
}
|
|
584
420
|
|
|
585
421
|
async delete(id: string): Promise<void> {
|
|
586
|
-
if (!this.store.has(id)) throw HttpException.notFound('${
|
|
422
|
+
if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
|
|
587
423
|
this.store.delete(id)
|
|
588
424
|
}
|
|
589
425
|
}
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
__name(generateInMemoryRepository, "generateInMemoryRepository");
|
|
593
|
-
function generateDrizzleRepository(pascal, kebab, repoPrefix = "../../domain/repositories", dtoPrefix = "../../application/dtos") {
|
|
594
|
-
return `/**
|
|
595
|
-
* Drizzle ${pascal} Repository
|
|
426
|
+
`}i(I,"generateInMemoryRepository");function T(e,t,r="../../domain/repositories",o="../../application/dtos"){return`/**
|
|
427
|
+
* Drizzle ${e} Repository
|
|
596
428
|
*
|
|
597
429
|
* Implements the repository interface using Drizzle ORM.
|
|
598
430
|
* Uses buildFromColumns() with Column objects for type-safe query building.
|
|
@@ -606,185 +438,170 @@ import { eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc,
|
|
|
606
438
|
import { Repository, HttpException, Inject } from '@forinda/kickjs-core'
|
|
607
439
|
import { DRIZZLE_DB, DrizzleQueryAdapter } from '@forinda/kickjs-drizzle'
|
|
608
440
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
609
|
-
import type { I${
|
|
610
|
-
import type { ${
|
|
611
|
-
import type { Create${
|
|
612
|
-
import type { Update${
|
|
613
|
-
import { ${
|
|
441
|
+
import type { I${e}Repository } from '${r}/${t}.repository'
|
|
442
|
+
import type { ${e}ResponseDTO } from '${o}/${t}-response.dto'
|
|
443
|
+
import type { Create${e}DTO } from '${o}/create-${t}.dto'
|
|
444
|
+
import type { Update${e}DTO } from '${o}/update-${t}.dto'
|
|
445
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from '../../constants'
|
|
614
446
|
|
|
615
447
|
// TODO: Import your Drizzle schema table \u2014 e.g.:
|
|
616
|
-
// import { ${
|
|
448
|
+
// import { ${t}s } from '@/db/schema'
|
|
617
449
|
|
|
618
450
|
const queryAdapter = new DrizzleQueryAdapter({
|
|
619
451
|
eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc,
|
|
620
452
|
})
|
|
621
453
|
|
|
622
454
|
@Repository()
|
|
623
|
-
export class Drizzle${
|
|
455
|
+
export class Drizzle${e}Repository implements I${e}Repository {
|
|
624
456
|
constructor(@Inject(DRIZZLE_DB) private db: any) {}
|
|
625
457
|
|
|
626
|
-
async findById(id: string): Promise<${
|
|
458
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
627
459
|
// TODO: Implement with Drizzle
|
|
628
|
-
// const row = this.db.select().from(${
|
|
460
|
+
// const row = this.db.select().from(${t}s).where(eq(${t}s.id, id)).get()
|
|
629
461
|
// return row ?? null
|
|
630
|
-
throw new Error('Drizzle ${
|
|
462
|
+
throw new Error('Drizzle ${e} repository not yet implemented \u2014 update schema imports and queries')
|
|
631
463
|
}
|
|
632
464
|
|
|
633
|
-
async findAll(): Promise<${
|
|
465
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
634
466
|
// TODO: Implement with Drizzle
|
|
635
|
-
// return this.db.select().from(${
|
|
636
|
-
throw new Error('Drizzle ${
|
|
467
|
+
// return this.db.select().from(${t}s).all()
|
|
468
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
637
469
|
}
|
|
638
470
|
|
|
639
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
471
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
640
472
|
// TODO: Use buildFromColumns() with your query config for type-safe filtering
|
|
641
|
-
// const query = queryAdapter.buildFromColumns(parsed, ${
|
|
473
|
+
// const query = queryAdapter.buildFromColumns(parsed, ${e.toUpperCase()}_QUERY_CONFIG)
|
|
642
474
|
//
|
|
643
475
|
// const data = this.db
|
|
644
|
-
// .select().from(${
|
|
476
|
+
// .select().from(${t}s).$dynamic()
|
|
645
477
|
// .where(query.where).orderBy(...query.orderBy)
|
|
646
478
|
// .limit(query.limit).offset(query.offset).all()
|
|
647
479
|
//
|
|
648
480
|
// const totalResult = this.db
|
|
649
|
-
// .select({ count: count() }).from(${
|
|
481
|
+
// .select({ count: count() }).from(${t}s)
|
|
650
482
|
// .$dynamic().where(query.where).get()
|
|
651
483
|
//
|
|
652
484
|
// return { data, total: totalResult?.count ?? 0 }
|
|
653
|
-
throw new Error('Drizzle ${
|
|
485
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
654
486
|
}
|
|
655
487
|
|
|
656
|
-
async create(dto: Create${
|
|
488
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
657
489
|
// TODO: Implement with Drizzle
|
|
658
|
-
// return this.db.insert(${
|
|
659
|
-
throw new Error('Drizzle ${
|
|
490
|
+
// return this.db.insert(${t}s).values(dto).returning().get()
|
|
491
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
660
492
|
}
|
|
661
493
|
|
|
662
|
-
async update(id: string, dto: Update${
|
|
494
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
663
495
|
// TODO: Implement with Drizzle
|
|
664
|
-
// const row = this.db.update(${
|
|
665
|
-
// if (!row) throw HttpException.notFound('${
|
|
496
|
+
// const row = this.db.update(${t}s).set(dto).where(eq(${t}s.id, id)).returning().get()
|
|
497
|
+
// if (!row) throw HttpException.notFound('${e} not found')
|
|
666
498
|
// return row
|
|
667
|
-
throw new Error('Drizzle ${
|
|
499
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
668
500
|
}
|
|
669
501
|
|
|
670
502
|
async delete(id: string): Promise<void> {
|
|
671
503
|
// TODO: Implement with Drizzle
|
|
672
|
-
// this.db.delete(${
|
|
673
|
-
throw new Error('Drizzle ${
|
|
504
|
+
// this.db.delete(${t}s).where(eq(${t}s.id, id)).run()
|
|
505
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
674
506
|
}
|
|
675
507
|
}
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
__name(generateDrizzleRepository, "generateDrizzleRepository");
|
|
679
|
-
function generatePrismaRepository(pascal, kebab, repoPrefix = "../../domain/repositories", dtoPrefix = "../../application/dtos") {
|
|
680
|
-
const camel = kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
681
|
-
return `/**
|
|
682
|
-
* Prisma ${pascal} Repository
|
|
508
|
+
`}i(T,"generateDrizzleRepository");function P(e,t,r="../../domain/repositories",o="../../application/dtos"){let n=t.replace(/-([a-z])/g,(s,p)=>p.toUpperCase());return`/**
|
|
509
|
+
* Prisma ${e} Repository
|
|
683
510
|
*
|
|
684
511
|
* Implements the repository interface using Prisma Client.
|
|
685
512
|
* Requires a PrismaClient instance injected via the DI container.
|
|
686
513
|
*
|
|
687
|
-
* TODO: Ensure your Prisma schema has a '${
|
|
514
|
+
* TODO: Ensure your Prisma schema has a '${e}' model defined.
|
|
688
515
|
* TODO: Replace 'PRISMA_CLIENT' with your actual Prisma injection token.
|
|
689
516
|
*
|
|
690
517
|
* @Repository() registers this class in the DI container as a singleton.
|
|
691
518
|
*/
|
|
692
519
|
import { Repository, HttpException, Autowired } from '@forinda/kickjs-core'
|
|
693
520
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
694
|
-
import type { I${
|
|
695
|
-
import type { ${
|
|
696
|
-
import type { Create${
|
|
697
|
-
import type { Update${
|
|
521
|
+
import type { I${e}Repository } from '${r}/${t}.repository'
|
|
522
|
+
import type { ${e}ResponseDTO } from '${o}/${t}-response.dto'
|
|
523
|
+
import type { Create${e}DTO } from '${o}/create-${t}.dto'
|
|
524
|
+
import type { Update${e}DTO } from '${o}/update-${t}.dto'
|
|
698
525
|
|
|
699
526
|
// TODO: Import your Prisma injection token \u2014 e.g.:
|
|
700
527
|
// import { PRISMA_CLIENT } from '@/db/prisma.provider'
|
|
701
528
|
// import type { PrismaClient } from '@prisma/client'
|
|
702
529
|
|
|
703
530
|
@Repository()
|
|
704
|
-
export class Prisma${
|
|
531
|
+
export class Prisma${e}Repository implements I${e}Repository {
|
|
705
532
|
// TODO: Uncomment and configure your Prisma injection:
|
|
706
533
|
// @Autowired(PRISMA_CLIENT) private prisma!: PrismaClient
|
|
707
534
|
|
|
708
|
-
async findById(id: string): Promise<${
|
|
535
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
709
536
|
// TODO: Implement with Prisma
|
|
710
|
-
// return this.prisma.${
|
|
711
|
-
throw new Error('Prisma ${
|
|
537
|
+
// return this.prisma.${n}.findUnique({ where: { id } })
|
|
538
|
+
throw new Error('Prisma ${e} repository not yet implemented \u2014 update Prisma imports and queries')
|
|
712
539
|
}
|
|
713
540
|
|
|
714
|
-
async findAll(): Promise<${
|
|
541
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
715
542
|
// TODO: Implement with Prisma
|
|
716
|
-
// return this.prisma.${
|
|
717
|
-
throw new Error('Prisma ${
|
|
543
|
+
// return this.prisma.${n}.findMany()
|
|
544
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
718
545
|
}
|
|
719
546
|
|
|
720
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
547
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
721
548
|
// TODO: Implement with Prisma
|
|
722
549
|
// const [data, total] = await Promise.all([
|
|
723
|
-
// this.prisma.${
|
|
550
|
+
// this.prisma.${n}.findMany({
|
|
724
551
|
// skip: parsed.pagination.offset,
|
|
725
552
|
// take: parsed.pagination.limit,
|
|
726
553
|
// }),
|
|
727
|
-
// this.prisma.${
|
|
554
|
+
// this.prisma.${n}.count(),
|
|
728
555
|
// ])
|
|
729
556
|
// return { data, total }
|
|
730
|
-
throw new Error('Prisma ${
|
|
557
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
731
558
|
}
|
|
732
559
|
|
|
733
|
-
async create(dto: Create${
|
|
560
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
734
561
|
// TODO: Implement with Prisma
|
|
735
|
-
// return this.prisma.${
|
|
736
|
-
throw new Error('Prisma ${
|
|
562
|
+
// return this.prisma.${n}.create({ data: dto })
|
|
563
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
737
564
|
}
|
|
738
565
|
|
|
739
|
-
async update(id: string, dto: Update${
|
|
566
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
740
567
|
// TODO: Implement with Prisma
|
|
741
|
-
// const row = await this.prisma.${
|
|
742
|
-
// if (!row) throw HttpException.notFound('${
|
|
568
|
+
// const row = await this.prisma.${n}.update({ where: { id }, data: dto })
|
|
569
|
+
// if (!row) throw HttpException.notFound('${e} not found')
|
|
743
570
|
// return row
|
|
744
|
-
throw new Error('Prisma ${
|
|
571
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
745
572
|
}
|
|
746
573
|
|
|
747
574
|
async delete(id: string): Promise<void> {
|
|
748
575
|
// TODO: Implement with Prisma
|
|
749
|
-
// await this.prisma.${
|
|
750
|
-
throw new Error('Prisma ${
|
|
576
|
+
// await this.prisma.${n}.delete({ where: { id } })
|
|
577
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
751
578
|
}
|
|
752
579
|
}
|
|
753
|
-
|
|
754
|
-
}
|
|
755
|
-
__name(generatePrismaRepository, "generatePrismaRepository");
|
|
756
|
-
|
|
757
|
-
// src/generators/templates/domain.ts
|
|
758
|
-
function generateDomainService(pascal, kebab) {
|
|
759
|
-
return `/**
|
|
760
|
-
* ${pascal} Domain Service
|
|
580
|
+
`}i(P,"generatePrismaRepository");function K(e,t){return`/**
|
|
581
|
+
* ${e} Domain Service
|
|
761
582
|
*
|
|
762
583
|
* Domain layer \u2014 contains business rules that don't belong to a single entity.
|
|
763
584
|
* Use this for cross-entity logic, validation rules, and domain invariants.
|
|
764
585
|
* Keep it free of HTTP/framework concerns.
|
|
765
586
|
*/
|
|
766
587
|
import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
767
|
-
import { ${
|
|
588
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../repositories/${t}.repository'
|
|
768
589
|
|
|
769
590
|
@Service()
|
|
770
|
-
export class ${
|
|
591
|
+
export class ${e}DomainService {
|
|
771
592
|
constructor(
|
|
772
|
-
@Inject(${
|
|
593
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
773
594
|
) {}
|
|
774
595
|
|
|
775
596
|
async ensureExists(id: string): Promise<void> {
|
|
776
597
|
const entity = await this.repo.findById(id)
|
|
777
598
|
if (!entity) {
|
|
778
|
-
throw HttpException.notFound('${
|
|
599
|
+
throw HttpException.notFound('${e} not found')
|
|
779
600
|
}
|
|
780
601
|
}
|
|
781
602
|
}
|
|
782
|
-
|
|
783
|
-
}
|
|
784
|
-
__name(generateDomainService, "generateDomainService");
|
|
785
|
-
function generateEntity(pascal, kebab) {
|
|
786
|
-
return `/**
|
|
787
|
-
* ${pascal} Entity
|
|
603
|
+
`}i(K,"generateDomainService");function H(e,t){return`/**
|
|
604
|
+
* ${e} Entity
|
|
788
605
|
*
|
|
789
606
|
* Domain layer \u2014 the core business object.
|
|
790
607
|
* Uses a private constructor with static factory methods (create, reconstitute)
|
|
@@ -796,33 +613,33 @@ function generateEntity(pascal, kebab) {
|
|
|
796
613
|
* - reconstitute(): factory for rebuilding from persistence (no side effects)
|
|
797
614
|
* - changeName(): mutation method that enforces business rules
|
|
798
615
|
*/
|
|
799
|
-
import { ${
|
|
616
|
+
import { ${e}Id } from '../value-objects/${t}-id.vo'
|
|
800
617
|
|
|
801
|
-
interface ${
|
|
802
|
-
id: ${
|
|
618
|
+
interface ${e}Props {
|
|
619
|
+
id: ${e}Id
|
|
803
620
|
name: string
|
|
804
621
|
createdAt: Date
|
|
805
622
|
updatedAt: Date
|
|
806
623
|
}
|
|
807
624
|
|
|
808
|
-
export class ${
|
|
809
|
-
private constructor(private props: ${
|
|
625
|
+
export class ${e} {
|
|
626
|
+
private constructor(private props: ${e}Props) {}
|
|
810
627
|
|
|
811
|
-
static create(params: { name: string }): ${
|
|
628
|
+
static create(params: { name: string }): ${e} {
|
|
812
629
|
const now = new Date()
|
|
813
|
-
return new ${
|
|
814
|
-
id: ${
|
|
630
|
+
return new ${e}({
|
|
631
|
+
id: ${e}Id.create(),
|
|
815
632
|
name: params.name,
|
|
816
633
|
createdAt: now,
|
|
817
634
|
updatedAt: now,
|
|
818
635
|
})
|
|
819
636
|
}
|
|
820
637
|
|
|
821
|
-
static reconstitute(props: ${
|
|
822
|
-
return new ${
|
|
638
|
+
static reconstitute(props: ${e}Props): ${e} {
|
|
639
|
+
return new ${e}(props)
|
|
823
640
|
}
|
|
824
641
|
|
|
825
|
-
get id(): ${
|
|
642
|
+
get id(): ${e}Id {
|
|
826
643
|
return this.props.id
|
|
827
644
|
}
|
|
828
645
|
get name(): string {
|
|
@@ -852,54 +669,44 @@ export class ${pascal} {
|
|
|
852
669
|
}
|
|
853
670
|
}
|
|
854
671
|
}
|
|
855
|
-
|
|
856
|
-
}
|
|
857
|
-
__name(generateEntity, "generateEntity");
|
|
858
|
-
function generateValueObject(pascal, kebab) {
|
|
859
|
-
return `/**
|
|
860
|
-
* ${pascal} ID Value Object
|
|
672
|
+
`}i(H,"generateEntity");function V(e,t){return`/**
|
|
673
|
+
* ${e} ID Value Object
|
|
861
674
|
*
|
|
862
675
|
* Domain layer \u2014 wraps a primitive ID with type safety and validation.
|
|
863
676
|
* Value objects are immutable and compared by value, not reference.
|
|
864
677
|
*
|
|
865
|
-
* ${
|
|
866
|
-
* ${
|
|
678
|
+
* ${e}Id.create() \u2014 generate a new UUID
|
|
679
|
+
* ${e}Id.from(id) \u2014 wrap an existing ID string (validates non-empty)
|
|
867
680
|
* id.equals(other) \u2014 compare two IDs by value
|
|
868
681
|
*/
|
|
869
682
|
import { randomUUID } from 'node:crypto'
|
|
870
683
|
|
|
871
|
-
export class ${
|
|
684
|
+
export class ${e}Id {
|
|
872
685
|
private constructor(private readonly value: string) {}
|
|
873
686
|
|
|
874
|
-
static create(): ${
|
|
875
|
-
return new ${
|
|
687
|
+
static create(): ${e}Id {
|
|
688
|
+
return new ${e}Id(randomUUID())
|
|
876
689
|
}
|
|
877
690
|
|
|
878
|
-
static from(id: string): ${
|
|
691
|
+
static from(id: string): ${e}Id {
|
|
879
692
|
if (!id || id.trim().length === 0) {
|
|
880
|
-
throw new Error('${
|
|
693
|
+
throw new Error('${e}Id cannot be empty')
|
|
881
694
|
}
|
|
882
|
-
return new ${
|
|
695
|
+
return new ${e}Id(id)
|
|
883
696
|
}
|
|
884
697
|
|
|
885
698
|
toString(): string {
|
|
886
699
|
return this.value
|
|
887
700
|
}
|
|
888
701
|
|
|
889
|
-
equals(other: ${
|
|
702
|
+
equals(other: ${e}Id): boolean {
|
|
890
703
|
return this.value === other.value
|
|
891
704
|
}
|
|
892
705
|
}
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
__name(generateValueObject, "generateValueObject");
|
|
896
|
-
|
|
897
|
-
// src/generators/templates/tests.ts
|
|
898
|
-
function generateControllerTest(pascal, kebab, plural) {
|
|
899
|
-
return `import { describe, it, expect, beforeEach } from 'vitest'
|
|
706
|
+
`}i(V,"generateValueObject");function S(e,t,r){return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
900
707
|
import { Container } from '@forinda/kickjs-core'
|
|
901
708
|
|
|
902
|
-
describe('${
|
|
709
|
+
describe('${e}Controller', () => {
|
|
903
710
|
beforeEach(() => {
|
|
904
711
|
Container.reset()
|
|
905
712
|
})
|
|
@@ -908,64 +715,60 @@ describe('${pascal}Controller', () => {
|
|
|
908
715
|
expect(true).toBe(true)
|
|
909
716
|
})
|
|
910
717
|
|
|
911
|
-
describe('POST /${
|
|
912
|
-
it('should create a new ${
|
|
718
|
+
describe('POST /${r}', () => {
|
|
719
|
+
it('should create a new ${t}', async () => {
|
|
913
720
|
// TODO: Set up test module, call create endpoint, assert 201
|
|
914
721
|
expect(true).toBe(true)
|
|
915
722
|
})
|
|
916
723
|
})
|
|
917
724
|
|
|
918
|
-
describe('GET /${
|
|
919
|
-
it('should return paginated ${
|
|
725
|
+
describe('GET /${r}', () => {
|
|
726
|
+
it('should return paginated ${r}', async () => {
|
|
920
727
|
// TODO: Set up test module, call list endpoint, assert { data, meta }
|
|
921
728
|
expect(true).toBe(true)
|
|
922
729
|
})
|
|
923
730
|
})
|
|
924
731
|
|
|
925
|
-
describe('GET /${
|
|
926
|
-
it('should return a ${
|
|
927
|
-
// TODO: Create a ${
|
|
732
|
+
describe('GET /${r}/:id', () => {
|
|
733
|
+
it('should return a ${t} by id', async () => {
|
|
734
|
+
// TODO: Create a ${t}, then fetch by id, assert match
|
|
928
735
|
expect(true).toBe(true)
|
|
929
736
|
})
|
|
930
737
|
|
|
931
|
-
it('should return 404 for non-existent ${
|
|
738
|
+
it('should return 404 for non-existent ${t}', async () => {
|
|
932
739
|
// TODO: Fetch non-existent id, assert 404
|
|
933
740
|
expect(true).toBe(true)
|
|
934
741
|
})
|
|
935
742
|
})
|
|
936
743
|
|
|
937
|
-
describe('PUT /${
|
|
938
|
-
it('should update an existing ${
|
|
744
|
+
describe('PUT /${r}/:id', () => {
|
|
745
|
+
it('should update an existing ${t}', async () => {
|
|
939
746
|
// TODO: Create, update, assert changes
|
|
940
747
|
expect(true).toBe(true)
|
|
941
748
|
})
|
|
942
749
|
})
|
|
943
750
|
|
|
944
|
-
describe('DELETE /${
|
|
945
|
-
it('should delete a ${
|
|
751
|
+
describe('DELETE /${r}/:id', () => {
|
|
752
|
+
it('should delete a ${t}', async () => {
|
|
946
753
|
// TODO: Create, delete, assert gone
|
|
947
754
|
expect(true).toBe(true)
|
|
948
755
|
})
|
|
949
756
|
})
|
|
950
757
|
})
|
|
951
|
-
|
|
952
|
-
}
|
|
953
|
-
__name(generateControllerTest, "generateControllerTest");
|
|
954
|
-
function generateRepositoryTest(pascal, kebab, plural, repoImport = `../infrastructure/repositories/in-memory-${kebab}.repository`) {
|
|
955
|
-
return `import { describe, it, expect, beforeEach } from 'vitest'
|
|
956
|
-
import { InMemory${pascal}Repository } from '${repoImport}'
|
|
758
|
+
`}i(S,"generateControllerTest");function j(e,t,r,o=`../infrastructure/repositories/in-memory-${t}.repository`){return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
759
|
+
import { InMemory${e}Repository } from '${o}'
|
|
957
760
|
|
|
958
|
-
describe('InMemory${
|
|
959
|
-
let repo: InMemory${
|
|
761
|
+
describe('InMemory${e}Repository', () => {
|
|
762
|
+
let repo: InMemory${e}Repository
|
|
960
763
|
|
|
961
764
|
beforeEach(() => {
|
|
962
|
-
repo = new InMemory${
|
|
765
|
+
repo = new InMemory${e}Repository()
|
|
963
766
|
})
|
|
964
767
|
|
|
965
|
-
it('should create and retrieve a ${
|
|
966
|
-
const created = await repo.create({ name: 'Test ${
|
|
768
|
+
it('should create and retrieve a ${t}', async () => {
|
|
769
|
+
const created = await repo.create({ name: 'Test ${e}' })
|
|
967
770
|
expect(created).toBeDefined()
|
|
968
|
-
expect(created.name).toBe('Test ${
|
|
771
|
+
expect(created.name).toBe('Test ${e}')
|
|
969
772
|
expect(created.id).toBeDefined()
|
|
970
773
|
|
|
971
774
|
const found = await repo.findById(created.id)
|
|
@@ -977,18 +780,18 @@ describe('InMemory${pascal}Repository', () => {
|
|
|
977
780
|
expect(found).toBeNull()
|
|
978
781
|
})
|
|
979
782
|
|
|
980
|
-
it('should list all ${
|
|
981
|
-
await repo.create({ name: '${
|
|
982
|
-
await repo.create({ name: '${
|
|
783
|
+
it('should list all ${r}', async () => {
|
|
784
|
+
await repo.create({ name: '${e} 1' })
|
|
785
|
+
await repo.create({ name: '${e} 2' })
|
|
983
786
|
|
|
984
787
|
const all = await repo.findAll()
|
|
985
788
|
expect(all).toHaveLength(2)
|
|
986
789
|
})
|
|
987
790
|
|
|
988
791
|
it('should return paginated results', async () => {
|
|
989
|
-
await repo.create({ name: '${
|
|
990
|
-
await repo.create({ name: '${
|
|
991
|
-
await repo.create({ name: '${
|
|
792
|
+
await repo.create({ name: '${e} 1' })
|
|
793
|
+
await repo.create({ name: '${e} 2' })
|
|
794
|
+
await repo.create({ name: '${e} 3' })
|
|
992
795
|
|
|
993
796
|
const result = await repo.findPaginated({
|
|
994
797
|
filters: [],
|
|
@@ -1001,43 +804,37 @@ describe('InMemory${pascal}Repository', () => {
|
|
|
1001
804
|
expect(result.total).toBe(3)
|
|
1002
805
|
})
|
|
1003
806
|
|
|
1004
|
-
it('should update a ${
|
|
807
|
+
it('should update a ${t}', async () => {
|
|
1005
808
|
const created = await repo.create({ name: 'Original' })
|
|
1006
809
|
const updated = await repo.update(created.id, { name: 'Updated' })
|
|
1007
810
|
expect(updated.name).toBe('Updated')
|
|
1008
811
|
})
|
|
1009
812
|
|
|
1010
|
-
it('should delete a ${
|
|
813
|
+
it('should delete a ${t}', async () => {
|
|
1011
814
|
const created = await repo.create({ name: 'To Delete' })
|
|
1012
815
|
await repo.delete(created.id)
|
|
1013
816
|
const found = await repo.findById(created.id)
|
|
1014
817
|
expect(found).toBeNull()
|
|
1015
818
|
})
|
|
1016
819
|
})
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1019
|
-
__name(generateRepositoryTest, "generateRepositoryTest");
|
|
1020
|
-
|
|
1021
|
-
// src/generators/templates/rest-service.ts
|
|
1022
|
-
function generateRestService(pascal, kebab) {
|
|
1023
|
-
return `import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
820
|
+
`}i(j,"generateRepositoryTest");function J(e,t){return`import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
1024
821
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1025
|
-
import { ${
|
|
1026
|
-
import type { ${
|
|
1027
|
-
import type { Create${
|
|
1028
|
-
import type { Update${
|
|
822
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from './${t}.repository'
|
|
823
|
+
import type { ${e}ResponseDTO } from './dtos/${t}-response.dto'
|
|
824
|
+
import type { Create${e}DTO } from './dtos/create-${t}.dto'
|
|
825
|
+
import type { Update${e}DTO } from './dtos/update-${t}.dto'
|
|
1029
826
|
|
|
1030
827
|
@Service()
|
|
1031
|
-
export class ${
|
|
828
|
+
export class ${e}Service {
|
|
1032
829
|
constructor(
|
|
1033
|
-
@Inject(${
|
|
830
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1034
831
|
) {}
|
|
1035
832
|
|
|
1036
|
-
async findById(id: string): Promise<${
|
|
833
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
1037
834
|
return this.repo.findById(id)
|
|
1038
835
|
}
|
|
1039
836
|
|
|
1040
|
-
async findAll(): Promise<${
|
|
837
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
1041
838
|
return this.repo.findAll()
|
|
1042
839
|
}
|
|
1043
840
|
|
|
@@ -1045,11 +842,11 @@ export class ${pascal}Service {
|
|
|
1045
842
|
return this.repo.findPaginated(parsed)
|
|
1046
843
|
}
|
|
1047
844
|
|
|
1048
|
-
async create(dto: Create${
|
|
845
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1049
846
|
return this.repo.create(dto)
|
|
1050
847
|
}
|
|
1051
848
|
|
|
1052
|
-
async update(id: string, dto: Update${
|
|
849
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1053
850
|
return this.repo.update(id, dto)
|
|
1054
851
|
}
|
|
1055
852
|
|
|
@@ -1057,37 +854,15 @@ export class ${pascal}Service {
|
|
|
1057
854
|
await this.repo.delete(id)
|
|
1058
855
|
}
|
|
1059
856
|
}
|
|
1060
|
-
|
|
1061
|
-
}
|
|
1062
|
-
__name(generateRestService, "generateRestService");
|
|
1063
|
-
function generateRestConstants(pascal) {
|
|
1064
|
-
return `import type { QueryFieldConfig } from '@forinda/kickjs-http'
|
|
857
|
+
`}i(J,"generateRestService");function z(e){return`import type { QueryFieldConfig } from '@forinda/kickjs-http'
|
|
1065
858
|
|
|
1066
|
-
export const ${
|
|
859
|
+
export const ${e.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
1067
860
|
filterable: ['name'],
|
|
1068
861
|
sortable: ['name', 'createdAt'],
|
|
1069
862
|
searchable: ['name'],
|
|
1070
863
|
}
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
__name(generateRestConstants, "generateRestConstants");
|
|
1074
|
-
|
|
1075
|
-
// src/generators/templates/cqrs.ts
|
|
1076
|
-
function generateCqrsModuleIndex(pascal, kebab, plural, repo) {
|
|
1077
|
-
const repoClassMap = {
|
|
1078
|
-
inmemory: `InMemory${pascal}Repository`,
|
|
1079
|
-
drizzle: `Drizzle${pascal}Repository`,
|
|
1080
|
-
prisma: `Prisma${pascal}Repository`
|
|
1081
|
-
};
|
|
1082
|
-
const repoFileMap = {
|
|
1083
|
-
inmemory: `in-memory-${kebab}`,
|
|
1084
|
-
drizzle: `drizzle-${kebab}`,
|
|
1085
|
-
prisma: `prisma-${kebab}`
|
|
1086
|
-
};
|
|
1087
|
-
const repoClass = repoClassMap[repo] ?? repoClassMap.inmemory;
|
|
1088
|
-
const repoFile = repoFileMap[repo] ?? repoFileMap.inmemory;
|
|
1089
|
-
return `/**
|
|
1090
|
-
* ${pascal} Module \u2014 CQRS Pattern
|
|
864
|
+
`}i(z,"generateRestConstants");function Z(e,t,r,o){let n={inmemory:`InMemory${e}Repository`,drizzle:`Drizzle${e}Repository`,prisma:`Prisma${e}Repository`},s={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`},p=n[o]??n.inmemory,a=s[o]??s.inmemory;return`/**
|
|
865
|
+
* ${e} Module \u2014 CQRS Pattern
|
|
1091
866
|
*
|
|
1092
867
|
* Separates read (queries) and write (commands) operations.
|
|
1093
868
|
* Events are emitted after state changes and can be handled via
|
|
@@ -1101,9 +876,9 @@ function generateCqrsModuleIndex(pascal, kebab, plural, repo) {
|
|
|
1101
876
|
*/
|
|
1102
877
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
1103
878
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
1104
|
-
import { ${
|
|
1105
|
-
import { ${
|
|
1106
|
-
import { ${
|
|
879
|
+
import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
|
|
880
|
+
import { ${p} } from './${a}.repository'
|
|
881
|
+
import { ${e}Controller } from './${t}.controller'
|
|
1107
882
|
|
|
1108
883
|
// Eagerly load decorated classes
|
|
1109
884
|
import.meta.glob(
|
|
@@ -1116,210 +891,168 @@ import.meta.glob(
|
|
|
1116
891
|
{ eager: true },
|
|
1117
892
|
)
|
|
1118
893
|
|
|
1119
|
-
export class ${
|
|
894
|
+
export class ${e}Module implements AppModule {
|
|
1120
895
|
register(container: Container): void {
|
|
1121
|
-
container.registerFactory(${
|
|
1122
|
-
container.resolve(${
|
|
896
|
+
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
897
|
+
container.resolve(${p}),
|
|
1123
898
|
)
|
|
1124
899
|
}
|
|
1125
900
|
|
|
1126
901
|
routes(): ModuleRoutes {
|
|
1127
902
|
return {
|
|
1128
|
-
path: '/${
|
|
1129
|
-
router: buildRoutes(${
|
|
1130
|
-
controller: ${
|
|
903
|
+
path: '/${r}',
|
|
904
|
+
router: buildRoutes(${e}Controller),
|
|
905
|
+
controller: ${e}Controller,
|
|
1131
906
|
}
|
|
1132
907
|
}
|
|
1133
908
|
}
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
__name(generateCqrsModuleIndex, "generateCqrsModuleIndex");
|
|
1137
|
-
function generateCqrsController(pascal, kebab, plural, pluralPascal) {
|
|
1138
|
-
const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
1139
|
-
return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
909
|
+
`}i(Z,"generateCqrsModuleIndex");function X(e,t,r,o){let n=e.charAt(0).toLowerCase()+e.slice(1);return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
1140
910
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1141
911
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
1142
|
-
import { Create${
|
|
1143
|
-
import { Update${
|
|
1144
|
-
import { Delete${
|
|
1145
|
-
import { Get${
|
|
1146
|
-
import { List${
|
|
1147
|
-
import { create${
|
|
1148
|
-
import { update${
|
|
1149
|
-
import { ${
|
|
912
|
+
import { Create${e}Command } from './commands/create-${t}.command'
|
|
913
|
+
import { Update${e}Command } from './commands/update-${t}.command'
|
|
914
|
+
import { Delete${e}Command } from './commands/delete-${t}.command'
|
|
915
|
+
import { Get${e}Query } from './queries/get-${t}.query'
|
|
916
|
+
import { List${o}Query } from './queries/list-${r}.query'
|
|
917
|
+
import { create${e}Schema } from './dtos/create-${t}.dto'
|
|
918
|
+
import { update${e}Schema } from './dtos/update-${t}.dto'
|
|
919
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
|
|
1150
920
|
|
|
1151
921
|
@Controller()
|
|
1152
|
-
export class ${
|
|
1153
|
-
@Autowired() private create${
|
|
1154
|
-
@Autowired() private update${
|
|
1155
|
-
@Autowired() private delete${
|
|
1156
|
-
@Autowired() private get${
|
|
1157
|
-
@Autowired() private list${
|
|
922
|
+
export class ${e}Controller {
|
|
923
|
+
@Autowired() private create${e}Command!: Create${e}Command
|
|
924
|
+
@Autowired() private update${e}Command!: Update${e}Command
|
|
925
|
+
@Autowired() private delete${e}Command!: Delete${e}Command
|
|
926
|
+
@Autowired() private get${e}Query!: Get${e}Query
|
|
927
|
+
@Autowired() private list${o}Query!: List${o}Query
|
|
1158
928
|
|
|
1159
929
|
@Get('/')
|
|
1160
|
-
@ApiTags('${
|
|
1161
|
-
@ApiQueryParams(${
|
|
930
|
+
@ApiTags('${e}')
|
|
931
|
+
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
1162
932
|
async list(ctx: RequestContext) {
|
|
1163
933
|
return ctx.paginate(
|
|
1164
|
-
(parsed) => this.list${
|
|
1165
|
-
${
|
|
934
|
+
(parsed) => this.list${o}Query.execute(parsed),
|
|
935
|
+
${e.toUpperCase()}_QUERY_CONFIG,
|
|
1166
936
|
)
|
|
1167
937
|
}
|
|
1168
938
|
|
|
1169
939
|
@Get('/:id')
|
|
1170
|
-
@ApiTags('${
|
|
940
|
+
@ApiTags('${e}')
|
|
1171
941
|
async getById(ctx: RequestContext) {
|
|
1172
|
-
const result = await this.get${
|
|
1173
|
-
if (!result) return ctx.notFound('${
|
|
942
|
+
const result = await this.get${e}Query.execute(ctx.params.id)
|
|
943
|
+
if (!result) return ctx.notFound('${e} not found')
|
|
1174
944
|
ctx.json(result)
|
|
1175
945
|
}
|
|
1176
946
|
|
|
1177
|
-
@Post('/', { body: create${
|
|
1178
|
-
@ApiTags('${
|
|
947
|
+
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
948
|
+
@ApiTags('${e}')
|
|
1179
949
|
async create(ctx: RequestContext) {
|
|
1180
|
-
const result = await this.create${
|
|
950
|
+
const result = await this.create${e}Command.execute(ctx.body)
|
|
1181
951
|
ctx.created(result)
|
|
1182
952
|
}
|
|
1183
953
|
|
|
1184
|
-
@Put('/:id', { body: update${
|
|
1185
|
-
@ApiTags('${
|
|
954
|
+
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
955
|
+
@ApiTags('${e}')
|
|
1186
956
|
async update(ctx: RequestContext) {
|
|
1187
|
-
const result = await this.update${
|
|
957
|
+
const result = await this.update${e}Command.execute(ctx.params.id, ctx.body)
|
|
1188
958
|
ctx.json(result)
|
|
1189
959
|
}
|
|
1190
960
|
|
|
1191
961
|
@Delete('/:id')
|
|
1192
|
-
@ApiTags('${
|
|
962
|
+
@ApiTags('${e}')
|
|
1193
963
|
async remove(ctx: RequestContext) {
|
|
1194
|
-
await this.delete${
|
|
964
|
+
await this.delete${e}Command.execute(ctx.params.id)
|
|
1195
965
|
ctx.noContent()
|
|
1196
966
|
}
|
|
1197
967
|
}
|
|
1198
|
-
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
{
|
|
1204
|
-
file: `create-${kebab}.command.ts`,
|
|
1205
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1206
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1207
|
-
import type { Create${pascal}DTO } from '../dtos/create-${kebab}.dto'
|
|
1208
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
1209
|
-
import { ${pascal}Events } from '../events/${kebab}.events'
|
|
968
|
+
`}i(X,"generateCqrsController");function ee(e,t){return[{file:`create-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
969
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
970
|
+
import type { Create${e}DTO } from '../dtos/create-${t}.dto'
|
|
971
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
972
|
+
import { ${e}Events } from '../events/${t}.events'
|
|
1210
973
|
|
|
1211
974
|
@Service()
|
|
1212
|
-
export class Create${
|
|
975
|
+
export class Create${e}Command {
|
|
1213
976
|
constructor(
|
|
1214
|
-
@Inject(${
|
|
1215
|
-
@Inject(${
|
|
977
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
978
|
+
@Inject(${e}Events) private readonly events: ${e}Events,
|
|
1216
979
|
) {}
|
|
1217
980
|
|
|
1218
|
-
async execute(dto: Create${
|
|
981
|
+
async execute(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1219
982
|
const result = await this.repo.create(dto)
|
|
1220
|
-
this.events.emit('${
|
|
983
|
+
this.events.emit('${t}.created', result)
|
|
1221
984
|
return result
|
|
1222
985
|
}
|
|
1223
986
|
}
|
|
1224
|
-
`
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1230
|
-
import type { Update${pascal}DTO } from '../dtos/update-${kebab}.dto'
|
|
1231
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
1232
|
-
import { ${pascal}Events } from '../events/${kebab}.events'
|
|
987
|
+
`},{file:`update-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
988
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
989
|
+
import type { Update${e}DTO } from '../dtos/update-${t}.dto'
|
|
990
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
991
|
+
import { ${e}Events } from '../events/${t}.events'
|
|
1233
992
|
|
|
1234
993
|
@Service()
|
|
1235
|
-
export class Update${
|
|
994
|
+
export class Update${e}Command {
|
|
1236
995
|
constructor(
|
|
1237
|
-
@Inject(${
|
|
1238
|
-
@Inject(${
|
|
996
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
997
|
+
@Inject(${e}Events) private readonly events: ${e}Events,
|
|
1239
998
|
) {}
|
|
1240
999
|
|
|
1241
|
-
async execute(id: string, dto: Update${
|
|
1000
|
+
async execute(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1242
1001
|
const result = await this.repo.update(id, dto)
|
|
1243
|
-
this.events.emit('${
|
|
1002
|
+
this.events.emit('${t}.updated', result)
|
|
1244
1003
|
return result
|
|
1245
1004
|
}
|
|
1246
1005
|
}
|
|
1247
|
-
`
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
file: `delete-${kebab}.command.ts`,
|
|
1251
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1252
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1253
|
-
import { ${pascal}Events } from '../events/${kebab}.events'
|
|
1006
|
+
`},{file:`delete-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1007
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1008
|
+
import { ${e}Events } from '../events/${t}.events'
|
|
1254
1009
|
|
|
1255
1010
|
@Service()
|
|
1256
|
-
export class Delete${
|
|
1011
|
+
export class Delete${e}Command {
|
|
1257
1012
|
constructor(
|
|
1258
|
-
@Inject(${
|
|
1259
|
-
@Inject(${
|
|
1013
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1014
|
+
@Inject(${e}Events) private readonly events: ${e}Events,
|
|
1260
1015
|
) {}
|
|
1261
1016
|
|
|
1262
1017
|
async execute(id: string): Promise<void> {
|
|
1263
1018
|
await this.repo.delete(id)
|
|
1264
|
-
this.events.emit('${
|
|
1019
|
+
this.events.emit('${t}.deleted', { id })
|
|
1265
1020
|
}
|
|
1266
1021
|
}
|
|
1267
|
-
`
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1271
|
-
__name(generateCqrsCommands, "generateCqrsCommands");
|
|
1272
|
-
function generateCqrsQueries(pascal, kebab, plural, pluralPascal) {
|
|
1273
|
-
return [
|
|
1274
|
-
{
|
|
1275
|
-
file: `get-${kebab}.query.ts`,
|
|
1276
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1277
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1278
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
1022
|
+
`}]}i(ee,"generateCqrsCommands");function te(e,t,r,o){return[{file:`get-${t}.query.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1023
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1024
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1279
1025
|
|
|
1280
1026
|
@Service()
|
|
1281
|
-
export class Get${
|
|
1027
|
+
export class Get${e}Query {
|
|
1282
1028
|
constructor(
|
|
1283
|
-
@Inject(${
|
|
1029
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1284
1030
|
) {}
|
|
1285
1031
|
|
|
1286
|
-
async execute(id: string): Promise<${
|
|
1032
|
+
async execute(id: string): Promise<${e}ResponseDTO | null> {
|
|
1287
1033
|
return this.repo.findById(id)
|
|
1288
1034
|
}
|
|
1289
1035
|
}
|
|
1290
|
-
`
|
|
1291
|
-
|
|
1292
|
-
{
|
|
1293
|
-
file: `list-${plural}.query.ts`,
|
|
1294
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1295
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1036
|
+
`},{file:`list-${r}.query.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1037
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1296
1038
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1297
1039
|
|
|
1298
1040
|
@Service()
|
|
1299
|
-
export class List${
|
|
1041
|
+
export class List${o}Query {
|
|
1300
1042
|
constructor(
|
|
1301
|
-
@Inject(${
|
|
1043
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1302
1044
|
) {}
|
|
1303
1045
|
|
|
1304
1046
|
async execute(parsed: ParsedQuery) {
|
|
1305
1047
|
return this.repo.findPaginated(parsed)
|
|
1306
1048
|
}
|
|
1307
1049
|
}
|
|
1308
|
-
`
|
|
1309
|
-
}
|
|
1310
|
-
];
|
|
1311
|
-
}
|
|
1312
|
-
__name(generateCqrsQueries, "generateCqrsQueries");
|
|
1313
|
-
function generateCqrsEvents(pascal, kebab) {
|
|
1314
|
-
return [
|
|
1315
|
-
{
|
|
1316
|
-
file: `${kebab}.events.ts`,
|
|
1317
|
-
content: `import { Service } from '@forinda/kickjs-core'
|
|
1050
|
+
`}]}i(te,"generateCqrsQueries");function re(e,t){return[{file:`${t}.events.ts`,content:`import { Service } from '@forinda/kickjs-core'
|
|
1318
1051
|
import { EventEmitter } from 'node:events'
|
|
1319
|
-
import type { ${
|
|
1052
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1320
1053
|
|
|
1321
1054
|
/**
|
|
1322
|
-
* ${
|
|
1055
|
+
* ${e} domain event types.
|
|
1323
1056
|
*
|
|
1324
1057
|
* These events are emitted by commands after state changes.
|
|
1325
1058
|
* Subscribe to them in event handlers for side effects:
|
|
@@ -1328,346 +1061,118 @@ import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
|
1328
1061
|
* - Audit logging
|
|
1329
1062
|
* - Cache invalidation
|
|
1330
1063
|
*/
|
|
1331
|
-
export interface ${
|
|
1332
|
-
'${
|
|
1333
|
-
'${
|
|
1334
|
-
'${
|
|
1064
|
+
export interface ${e}EventMap {
|
|
1065
|
+
'${t}.created': ${e}ResponseDTO
|
|
1066
|
+
'${t}.updated': ${e}ResponseDTO
|
|
1067
|
+
'${t}.deleted': { id: string }
|
|
1335
1068
|
}
|
|
1336
1069
|
|
|
1337
1070
|
@Service()
|
|
1338
|
-
export class ${
|
|
1071
|
+
export class ${e}Events {
|
|
1339
1072
|
private emitter = new EventEmitter()
|
|
1340
1073
|
|
|
1341
|
-
emit<K extends keyof ${
|
|
1074
|
+
emit<K extends keyof ${e}EventMap>(event: K, data: ${e}EventMap[K]): void {
|
|
1342
1075
|
this.emitter.emit(event, data)
|
|
1343
1076
|
}
|
|
1344
1077
|
|
|
1345
|
-
on<K extends keyof ${
|
|
1078
|
+
on<K extends keyof ${e}EventMap>(event: K, handler: (data: ${e}EventMap[K]) => void): void {
|
|
1346
1079
|
this.emitter.on(event, handler)
|
|
1347
1080
|
}
|
|
1348
1081
|
|
|
1349
|
-
off<K extends keyof ${
|
|
1082
|
+
off<K extends keyof ${e}EventMap>(event: K, handler: (data: ${e}EventMap[K]) => void): void {
|
|
1350
1083
|
this.emitter.off(event, handler)
|
|
1351
1084
|
}
|
|
1352
1085
|
}
|
|
1353
|
-
`
|
|
1354
|
-
|
|
1355
|
-
{
|
|
1356
|
-
file: `on-${kebab}-change.handler.ts`,
|
|
1357
|
-
content: `import { Service, Autowired } from '@forinda/kickjs-core'
|
|
1358
|
-
import { ${pascal}Events } from './${kebab}.events'
|
|
1086
|
+
`},{file:`on-${t}-change.handler.ts`,content:`import { Service, Autowired } from '@forinda/kickjs-core'
|
|
1087
|
+
import { ${e}Events } from './${t}.events'
|
|
1359
1088
|
|
|
1360
1089
|
/**
|
|
1361
|
-
* ${
|
|
1090
|
+
* ${e} Change Event Handler
|
|
1362
1091
|
*
|
|
1363
1092
|
* Reacts to domain events emitted by commands.
|
|
1364
1093
|
* Wire up side effects here:
|
|
1365
1094
|
*
|
|
1366
1095
|
* 1. WebSocket broadcast \u2014 notify connected clients in real-time
|
|
1367
1096
|
* import { WsGateway } from '@forinda/kickjs-ws'
|
|
1368
|
-
* this.ws.broadcast('${
|
|
1097
|
+
* this.ws.broadcast('${t}-channel', { event, data })
|
|
1369
1098
|
*
|
|
1370
1099
|
* 2. Queue dispatch \u2014 offload heavy processing to background workers
|
|
1371
1100
|
* import { QueueService } from '@forinda/kickjs-queue'
|
|
1372
|
-
* this.queue.add('${
|
|
1101
|
+
* this.queue.add('${t}-etl', { action: event, payload: data })
|
|
1373
1102
|
*
|
|
1374
1103
|
* 3. ETL pipeline \u2014 transform and load data to external systems
|
|
1375
1104
|
* await this.etlPipeline.process(data)
|
|
1376
1105
|
*/
|
|
1377
1106
|
@Service()
|
|
1378
|
-
export class On${
|
|
1379
|
-
@Autowired() private events!: ${
|
|
1107
|
+
export class On${e}ChangeHandler {
|
|
1108
|
+
@Autowired() private events!: ${e}Events
|
|
1380
1109
|
|
|
1381
1110
|
// Uncomment to inject WebSocket and Queue services:
|
|
1382
1111
|
// @Autowired() private ws!: WsGateway
|
|
1383
1112
|
// @Autowired() private queue!: QueueService
|
|
1384
1113
|
|
|
1385
1114
|
onInit(): void {
|
|
1386
|
-
this.events.on('${
|
|
1387
|
-
console.log('[${
|
|
1115
|
+
this.events.on('${t}.created', (data) => {
|
|
1116
|
+
console.log('[${e}] Created:', data.id)
|
|
1388
1117
|
// TODO: Broadcast via WebSocket
|
|
1389
|
-
// this.ws.broadcast('${
|
|
1118
|
+
// this.ws.broadcast('${t}-channel', { event: '${t}.created', data })
|
|
1390
1119
|
// TODO: Dispatch to queue for async processing / ETL
|
|
1391
|
-
// this.queue.add('${
|
|
1120
|
+
// this.queue.add('${t}-etl', { action: 'create', payload: data })
|
|
1392
1121
|
})
|
|
1393
1122
|
|
|
1394
|
-
this.events.on('${
|
|
1395
|
-
console.log('[${
|
|
1123
|
+
this.events.on('${t}.updated', (data) => {
|
|
1124
|
+
console.log('[${e}] Updated:', data.id)
|
|
1396
1125
|
// TODO: Broadcast via WebSocket
|
|
1397
|
-
// this.ws.broadcast('${
|
|
1126
|
+
// this.ws.broadcast('${t}-channel', { event: '${t}.updated', data })
|
|
1398
1127
|
})
|
|
1399
1128
|
|
|
1400
|
-
this.events.on('${
|
|
1401
|
-
console.log('[${
|
|
1129
|
+
this.events.on('${t}.deleted', (data) => {
|
|
1130
|
+
console.log('[${e}] Deleted:', data.id)
|
|
1402
1131
|
// TODO: Broadcast via WebSocket
|
|
1403
|
-
// this.ws.broadcast('${
|
|
1132
|
+
// this.ws.broadcast('${t}-channel', { event: '${t}.deleted', data })
|
|
1404
1133
|
})
|
|
1405
1134
|
}
|
|
1406
1135
|
}
|
|
1407
|
-
`
|
|
1408
|
-
|
|
1409
|
-
];
|
|
1410
|
-
}
|
|
1411
|
-
__name(generateCqrsEvents, "generateCqrsEvents");
|
|
1412
|
-
|
|
1413
|
-
// src/generators/module.ts
|
|
1414
|
-
function promptUser(question) {
|
|
1415
|
-
const rl = createInterface({
|
|
1416
|
-
input: process.stdin,
|
|
1417
|
-
output: process.stdout
|
|
1418
|
-
});
|
|
1419
|
-
return new Promise((resolve2) => {
|
|
1420
|
-
rl.question(question, (answer) => {
|
|
1421
|
-
rl.close();
|
|
1422
|
-
resolve2(answer.trim().toLowerCase());
|
|
1423
|
-
});
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
__name(promptUser, "promptUser");
|
|
1427
|
-
async function generateModule(options) {
|
|
1428
|
-
const { name, modulesDir, noEntity, noTests, repo = "inmemory", force, dryRun } = options;
|
|
1429
|
-
let pattern = options.pattern ?? "ddd";
|
|
1430
|
-
if (options.minimal) pattern = "minimal";
|
|
1431
|
-
const kebab = toKebabCase(name);
|
|
1432
|
-
const pascal = toPascalCase(name);
|
|
1433
|
-
const plural = pluralize(kebab);
|
|
1434
|
-
const pluralPascal = pluralizePascal(pascal);
|
|
1435
|
-
const moduleDir = join(modulesDir, plural);
|
|
1436
|
-
const files = [];
|
|
1437
|
-
let overwriteAll = force ?? false;
|
|
1438
|
-
const write = /* @__PURE__ */ __name(async (relativePath, content) => {
|
|
1439
|
-
const fullPath = join(moduleDir, relativePath);
|
|
1440
|
-
if (dryRun) {
|
|
1441
|
-
files.push(fullPath);
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
if (!overwriteAll && await fileExists(fullPath)) {
|
|
1445
|
-
const answer = await promptUser(` File already exists: ${relativePath}
|
|
1446
|
-
Overwrite? (y/n/a = yes/no/all) `);
|
|
1447
|
-
if (answer === "a") {
|
|
1448
|
-
overwriteAll = true;
|
|
1449
|
-
} else if (answer !== "y") {
|
|
1450
|
-
console.log(` Skipped: ${relativePath}`);
|
|
1451
|
-
return;
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
await writeFileSafe(fullPath, content);
|
|
1455
|
-
files.push(fullPath);
|
|
1456
|
-
}, "write");
|
|
1457
|
-
const ctx = {
|
|
1458
|
-
kebab,
|
|
1459
|
-
pascal,
|
|
1460
|
-
plural,
|
|
1461
|
-
pluralPascal,
|
|
1462
|
-
moduleDir,
|
|
1463
|
-
repo,
|
|
1464
|
-
noEntity: noEntity ?? false,
|
|
1465
|
-
noTests: noTests ?? false,
|
|
1466
|
-
write,
|
|
1467
|
-
files
|
|
1468
|
-
};
|
|
1469
|
-
switch (pattern) {
|
|
1470
|
-
case "minimal":
|
|
1471
|
-
await generateMinimalFiles(ctx);
|
|
1472
|
-
break;
|
|
1473
|
-
case "rest":
|
|
1474
|
-
await generateRestFiles(ctx);
|
|
1475
|
-
break;
|
|
1476
|
-
case "cqrs":
|
|
1477
|
-
await generateCqrsFiles(ctx);
|
|
1478
|
-
break;
|
|
1479
|
-
case "graphql":
|
|
1480
|
-
case "ddd":
|
|
1481
|
-
default:
|
|
1482
|
-
await generateDddFiles(ctx);
|
|
1483
|
-
break;
|
|
1484
|
-
}
|
|
1485
|
-
if (!dryRun) {
|
|
1486
|
-
await autoRegisterModule(modulesDir, pascal, plural);
|
|
1487
|
-
}
|
|
1488
|
-
return files;
|
|
1489
|
-
}
|
|
1490
|
-
__name(generateModule, "generateModule");
|
|
1491
|
-
async function generateMinimalFiles(ctx) {
|
|
1492
|
-
const { pascal, kebab, plural, write } = ctx;
|
|
1493
|
-
await write("index.ts", generateMinimalModuleIndex(pascal, kebab, plural));
|
|
1494
|
-
await write(`${kebab}.controller.ts`, `import { Controller, Get } from '@forinda/kickjs-core'
|
|
1136
|
+
`}]}i(re,"generateCqrsEvents");function ve(e){let t=ge({input:process.stdin,output:process.stdout});return new Promise(r=>{t.question(e,o=>{t.close(),r(o.trim().toLowerCase())})})}i(ve,"promptUser");async function Ce(e){let{name:t,modulesDir:r,noEntity:o,noTests:n,repo:s="inmemory",force:p,dryRun:a}=e,d=e.pattern??"ddd";e.minimal&&(d="minimal");let m=f(t),u=l(t),y=x(m),v=ae(u),g=oe(r,y),E=[],ne=p??!1,A={kebab:m,pascal:u,plural:y,pluralPascal:v,moduleDir:g,repo:s,noEntity:o??!1,noTests:n??!1,write:i(async(M,ce)=>{let U=oe(g,M);if(a){E.push(U);return}if(!ne&&await F(U)){let se=await ve(` File already exists: ${M}
|
|
1137
|
+
Overwrite? (y/n/a = yes/no/all) `);if(se==="a")ne=!0;else if(se!=="y"){console.log(` Skipped: ${M}`);return}}await c(U,ce),E.push(U)},"write"),files:E};switch(d){case"minimal":await xe(A);break;case"rest":await Re(A);break;case"cqrs":await De(A);break;default:await Oe(A);break}return a||await ke(r,u,y),E}i(Ce,"generateModule");async function xe(e){let{pascal:t,kebab:r,plural:o,write:n}=e;await n("index.ts",N(t,r,o)),await n(`${r}.controller.ts`,`import { Controller, Get } from '@forinda/kickjs-core'
|
|
1495
1138
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1496
1139
|
|
|
1497
1140
|
@Controller()
|
|
1498
|
-
export class ${
|
|
1141
|
+
export class ${t}Controller {
|
|
1499
1142
|
@Get('/')
|
|
1500
1143
|
async list(ctx: RequestContext) {
|
|
1501
|
-
ctx.json({ message: '${
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
`);
|
|
1505
|
-
}
|
|
1506
|
-
__name(generateMinimalFiles, "generateMinimalFiles");
|
|
1507
|
-
async function generateRestFiles(ctx) {
|
|
1508
|
-
const { pascal, kebab, plural, pluralPascal, repo, noTests, write } = ctx;
|
|
1509
|
-
await write("index.ts", generateRestModuleIndex(pascal, kebab, plural, repo));
|
|
1510
|
-
await write(`${kebab}.constants.ts`, generateRestConstants(pascal));
|
|
1511
|
-
await write(`${kebab}.controller.ts`, generateRestController(pascal, kebab, plural, pluralPascal));
|
|
1512
|
-
await write(`${kebab}.service.ts`, generateRestService(pascal, kebab));
|
|
1513
|
-
await write(`dtos/create-${kebab}.dto.ts`, generateCreateDTO(pascal, kebab));
|
|
1514
|
-
await write(`dtos/update-${kebab}.dto.ts`, generateUpdateDTO(pascal, kebab));
|
|
1515
|
-
await write(`dtos/${kebab}-response.dto.ts`, generateResponseDTO(pascal, kebab));
|
|
1516
|
-
await write(`${kebab}.repository.ts`, generateRepositoryInterface(pascal, kebab, "./dtos"));
|
|
1517
|
-
const repoFileMap = {
|
|
1518
|
-
inmemory: `in-memory-${kebab}`,
|
|
1519
|
-
drizzle: `drizzle-${kebab}`,
|
|
1520
|
-
prisma: `prisma-${kebab}`
|
|
1521
|
-
};
|
|
1522
|
-
const repoGeneratorMap = {
|
|
1523
|
-
inmemory: /* @__PURE__ */ __name(() => generateInMemoryRepository(pascal, kebab, ".", "./dtos"), "inmemory"),
|
|
1524
|
-
drizzle: /* @__PURE__ */ __name(() => generateDrizzleRepository(pascal, kebab, ".", "./dtos"), "drizzle"),
|
|
1525
|
-
prisma: /* @__PURE__ */ __name(() => generatePrismaRepository(pascal, kebab, ".", "./dtos"), "prisma")
|
|
1526
|
-
};
|
|
1527
|
-
await write(`${repoFileMap[repo]}.repository.ts`, repoGeneratorMap[repo]());
|
|
1528
|
-
if (!noTests) {
|
|
1529
|
-
await write(`__tests__/${kebab}.controller.test.ts`, generateControllerTest(pascal, kebab, plural));
|
|
1530
|
-
await write(`__tests__/${kebab}.repository.test.ts`, generateRepositoryTest(pascal, kebab, plural, `../${repoFileMap.inmemory}.repository`));
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
__name(generateRestFiles, "generateRestFiles");
|
|
1534
|
-
async function generateCqrsFiles(ctx) {
|
|
1535
|
-
const { pascal, kebab, plural, pluralPascal, repo, noTests, write } = ctx;
|
|
1536
|
-
await write("index.ts", generateCqrsModuleIndex(pascal, kebab, plural, repo));
|
|
1537
|
-
await write(`${kebab}.constants.ts`, generateRestConstants(pascal));
|
|
1538
|
-
await write(`${kebab}.controller.ts`, generateCqrsController(pascal, kebab, plural, pluralPascal));
|
|
1539
|
-
await write(`dtos/create-${kebab}.dto.ts`, generateCreateDTO(pascal, kebab));
|
|
1540
|
-
await write(`dtos/update-${kebab}.dto.ts`, generateUpdateDTO(pascal, kebab));
|
|
1541
|
-
await write(`dtos/${kebab}-response.dto.ts`, generateResponseDTO(pascal, kebab));
|
|
1542
|
-
const commands = generateCqrsCommands(pascal, kebab);
|
|
1543
|
-
for (const cmd of commands) {
|
|
1544
|
-
await write(`commands/${cmd.file}`, cmd.content);
|
|
1545
|
-
}
|
|
1546
|
-
const queries = generateCqrsQueries(pascal, kebab, plural, pluralPascal);
|
|
1547
|
-
for (const q of queries) {
|
|
1548
|
-
await write(`queries/${q.file}`, q.content);
|
|
1549
|
-
}
|
|
1550
|
-
const events = generateCqrsEvents(pascal, kebab);
|
|
1551
|
-
for (const e of events) {
|
|
1552
|
-
await write(`events/${e.file}`, e.content);
|
|
1553
|
-
}
|
|
1554
|
-
await write(`${kebab}.repository.ts`, generateRepositoryInterface(pascal, kebab, "./dtos"));
|
|
1555
|
-
const repoFileMap = {
|
|
1556
|
-
inmemory: `in-memory-${kebab}`,
|
|
1557
|
-
drizzle: `drizzle-${kebab}`,
|
|
1558
|
-
prisma: `prisma-${kebab}`
|
|
1559
|
-
};
|
|
1560
|
-
const repoGeneratorMap = {
|
|
1561
|
-
inmemory: /* @__PURE__ */ __name(() => generateInMemoryRepository(pascal, kebab, ".", "./dtos"), "inmemory"),
|
|
1562
|
-
drizzle: /* @__PURE__ */ __name(() => generateDrizzleRepository(pascal, kebab, ".", "./dtos"), "drizzle"),
|
|
1563
|
-
prisma: /* @__PURE__ */ __name(() => generatePrismaRepository(pascal, kebab, ".", "./dtos"), "prisma")
|
|
1564
|
-
};
|
|
1565
|
-
await write(`${repoFileMap[repo]}.repository.ts`, repoGeneratorMap[repo]());
|
|
1566
|
-
if (!noTests) {
|
|
1567
|
-
await write(`__tests__/${kebab}.controller.test.ts`, generateControllerTest(pascal, kebab, plural));
|
|
1568
|
-
await write(`__tests__/${kebab}.repository.test.ts`, generateRepositoryTest(pascal, kebab, plural, `../${repoFileMap.inmemory}.repository`));
|
|
1144
|
+
ctx.json({ message: '${t} list' })
|
|
1569
1145
|
}
|
|
1570
1146
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
await write(`application/dtos/${kebab}-response.dto.ts`, generateResponseDTO(pascal, kebab));
|
|
1580
|
-
const useCases = generateUseCases(pascal, kebab, plural, pluralPascal);
|
|
1581
|
-
for (const uc of useCases) {
|
|
1582
|
-
await write(`application/use-cases/${uc.file}`, uc.content);
|
|
1583
|
-
}
|
|
1584
|
-
await write(`domain/repositories/${kebab}.repository.ts`, generateRepositoryInterface(pascal, kebab));
|
|
1585
|
-
await write(`domain/services/${kebab}-domain.service.ts`, generateDomainService(pascal, kebab));
|
|
1586
|
-
const repoFileMap = {
|
|
1587
|
-
inmemory: `in-memory-${kebab}`,
|
|
1588
|
-
drizzle: `drizzle-${kebab}`,
|
|
1589
|
-
prisma: `prisma-${kebab}`
|
|
1590
|
-
};
|
|
1591
|
-
const repoGeneratorMap = {
|
|
1592
|
-
inmemory: /* @__PURE__ */ __name(() => generateInMemoryRepository(pascal, kebab), "inmemory"),
|
|
1593
|
-
drizzle: /* @__PURE__ */ __name(() => generateDrizzleRepository(pascal, kebab), "drizzle"),
|
|
1594
|
-
prisma: /* @__PURE__ */ __name(() => generatePrismaRepository(pascal, kebab), "prisma")
|
|
1595
|
-
};
|
|
1596
|
-
await write(`infrastructure/repositories/${repoFileMap[repo]}.repository.ts`, repoGeneratorMap[repo]());
|
|
1597
|
-
if (!noEntity) {
|
|
1598
|
-
await write(`domain/entities/${kebab}.entity.ts`, generateEntity(pascal, kebab));
|
|
1599
|
-
await write(`domain/value-objects/${kebab}-id.vo.ts`, generateValueObject(pascal, kebab));
|
|
1600
|
-
}
|
|
1601
|
-
if (!noTests) {
|
|
1602
|
-
await write(`__tests__/${kebab}.controller.test.ts`, generateControllerTest(pascal, kebab, plural));
|
|
1603
|
-
await write(`__tests__/${kebab}.repository.test.ts`, generateRepositoryTest(pascal, kebab, plural));
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
__name(generateDddFiles, "generateDddFiles");
|
|
1607
|
-
async function autoRegisterModule(modulesDir, pascal, plural) {
|
|
1608
|
-
const indexPath = join(modulesDir, "index.ts");
|
|
1609
|
-
const exists = await fileExists(indexPath);
|
|
1610
|
-
if (!exists) {
|
|
1611
|
-
await writeFileSafe(indexPath, `import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
1612
|
-
import { ${pascal}Module } from './${plural}'
|
|
1613
|
-
|
|
1614
|
-
export const modules: AppModuleClass[] = [${pascal}Module]
|
|
1615
|
-
`);
|
|
1616
|
-
return;
|
|
1617
|
-
}
|
|
1618
|
-
let content = await readFile2(indexPath, "utf-8");
|
|
1619
|
-
const importLine = `import { ${pascal}Module } from './${plural}'`;
|
|
1620
|
-
if (!content.includes(`${pascal}Module`)) {
|
|
1621
|
-
const lastImportIdx = content.lastIndexOf("import ");
|
|
1622
|
-
if (lastImportIdx !== -1) {
|
|
1623
|
-
const lineEnd = content.indexOf("\n", lastImportIdx);
|
|
1624
|
-
content = content.slice(0, lineEnd + 1) + importLine + "\n" + content.slice(lineEnd + 1);
|
|
1625
|
-
} else {
|
|
1626
|
-
content = importLine + "\n" + content;
|
|
1627
|
-
}
|
|
1628
|
-
content = content.replace(/(=\s*\[)([\s\S]*?)(])/, (_match, open, existing, close) => {
|
|
1629
|
-
const trimmed = existing.trim();
|
|
1630
|
-
if (!trimmed) {
|
|
1631
|
-
return `${open}${pascal}Module${close}`;
|
|
1632
|
-
}
|
|
1633
|
-
const needsComma = trimmed.endsWith(",") ? "" : ",";
|
|
1634
|
-
return `${open}${existing.trimEnd()}${needsComma} ${pascal}Module${close}`;
|
|
1635
|
-
});
|
|
1636
|
-
}
|
|
1637
|
-
await writeFile2(indexPath, content, "utf-8");
|
|
1638
|
-
}
|
|
1639
|
-
__name(autoRegisterModule, "autoRegisterModule");
|
|
1640
|
-
|
|
1641
|
-
// src/generators/adapter.ts
|
|
1642
|
-
import { join as join2 } from "path";
|
|
1643
|
-
async function generateAdapter(options) {
|
|
1644
|
-
const { name, outDir } = options;
|
|
1645
|
-
const kebab = toKebabCase(name);
|
|
1646
|
-
const pascal = toPascalCase(name);
|
|
1647
|
-
const files = [];
|
|
1648
|
-
const filePath = join2(outDir, `${kebab}.adapter.ts`);
|
|
1649
|
-
await writeFileSafe(filePath, `import type { Express } from 'express'
|
|
1147
|
+
`)}i(xe,"generateMinimalFiles");async function Re(e){let{pascal:t,kebab:r,plural:o,pluralPascal:n,repo:s,noTests:p,write:a}=e;await a("index.ts",G(t,r,o,s)),await a(`${r}.constants.ts`,z(t)),await a(`${r}.controller.ts`,B(t,r,o,n)),await a(`${r}.service.ts`,J(t,r)),await a(`dtos/create-${r}.dto.ts`,R(t,r)),await a(`dtos/update-${r}.dto.ts`,D(t,r)),await a(`dtos/${r}-response.dto.ts`,O(t,r)),await a(`${r}.repository.ts`,k(t,r,"./dtos"));let d={inmemory:`in-memory-${r}`,drizzle:`drizzle-${r}`,prisma:`prisma-${r}`},m={inmemory:i(()=>I(t,r,".","./dtos"),"inmemory"),drizzle:i(()=>T(t,r,".","./dtos"),"drizzle"),prisma:i(()=>P(t,r,".","./dtos"),"prisma")};await a(`${d[s]}.repository.ts`,m[s]()),p||(await a(`__tests__/${r}.controller.test.ts`,S(t,r,o)),await a(`__tests__/${r}.repository.test.ts`,j(t,r,o,`../${d.inmemory}.repository`)))}i(Re,"generateRestFiles");async function De(e){let{pascal:t,kebab:r,plural:o,pluralPascal:n,repo:s,noTests:p,write:a}=e;await a("index.ts",Z(t,r,o,s)),await a(`${r}.constants.ts`,z(t)),await a(`${r}.controller.ts`,X(t,r,o,n)),await a(`dtos/create-${r}.dto.ts`,R(t,r)),await a(`dtos/update-${r}.dto.ts`,D(t,r)),await a(`dtos/${r}-response.dto.ts`,O(t,r));let d=ee(t,r);for(let g of d)await a(`commands/${g.file}`,g.content);let m=te(t,r,o,n);for(let g of m)await a(`queries/${g.file}`,g.content);let u=re(t,r);for(let g of u)await a(`events/${g.file}`,g.content);await a(`${r}.repository.ts`,k(t,r,"./dtos"));let y={inmemory:`in-memory-${r}`,drizzle:`drizzle-${r}`,prisma:`prisma-${r}`},v={inmemory:i(()=>I(t,r,".","./dtos"),"inmemory"),drizzle:i(()=>T(t,r,".","./dtos"),"drizzle"),prisma:i(()=>P(t,r,".","./dtos"),"prisma")};await a(`${y[s]}.repository.ts`,v[s]()),p||(await a(`__tests__/${r}.controller.test.ts`,S(t,r,o)),await a(`__tests__/${r}.repository.test.ts`,j(t,r,o,`../${y.inmemory}.repository`)))}i(De,"generateCqrsFiles");async function Oe(e){let{pascal:t,kebab:r,plural:o,pluralPascal:n,repo:s,noEntity:p,noTests:a,write:d}=e;await d("index.ts",Q(t,r,o,s)),await d("constants.ts",s==="drizzle"?W(t,r):L(t)),await d(`presentation/${r}.controller.ts`,Y(t,r,o,n)),await d(`application/dtos/create-${r}.dto.ts`,R(t,r)),await d(`application/dtos/update-${r}.dto.ts`,D(t,r)),await d(`application/dtos/${r}-response.dto.ts`,O(t,r));let m=b(t,r,o,n);for(let v of m)await d(`application/use-cases/${v.file}`,v.content);await d(`domain/repositories/${r}.repository.ts`,k(t,r)),await d(`domain/services/${r}-domain.service.ts`,K(t,r));let u={inmemory:`in-memory-${r}`,drizzle:`drizzle-${r}`,prisma:`prisma-${r}`},y={inmemory:i(()=>I(t,r),"inmemory"),drizzle:i(()=>T(t,r),"drizzle"),prisma:i(()=>P(t,r),"prisma")};await d(`infrastructure/repositories/${u[s]}.repository.ts`,y[s]()),p||(await d(`domain/entities/${r}.entity.ts`,H(t,r)),await d(`domain/value-objects/${r}-id.vo.ts`,V(t,r))),a||(await d(`__tests__/${r}.controller.test.ts`,S(t,r,o)),await d(`__tests__/${r}.repository.test.ts`,j(t,r,o)))}i(Oe,"generateDddFiles");async function ke(e,t,r){let o=oe(e,"index.ts");if(!await F(o)){await c(o,`import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
1148
|
+
import { ${t}Module } from './${r}'
|
|
1149
|
+
|
|
1150
|
+
export const modules: AppModuleClass[] = [${t}Module]
|
|
1151
|
+
`);return}let s=await he(o,"utf-8"),p=`import { ${t}Module } from './${r}'`;if(!s.includes(`${t}Module`)){let a=s.lastIndexOf("import ");if(a!==-1){let d=s.indexOf(`
|
|
1152
|
+
`,a);s=s.slice(0,d+1)+p+`
|
|
1153
|
+
`+s.slice(d+1)}else s=p+`
|
|
1154
|
+
`+s;s=s.replace(/(=\s*\[)([\s\S]*?)(])/,(d,m,u,y)=>{let v=u.trim();if(!v)return`${m}${t}Module${y}`;let g=v.endsWith(",")?"":",";return`${m}${u.trimEnd()}${g} ${t}Module${y}`})}await we(o,s,"utf-8")}i(ke,"autoRegisterModule");import{join as Ie}from"path";async function Te(e){let{name:t,outDir:r}=e,o=f(t),n=l(t),s=[],p=Ie(r,`${o}.adapter.ts`);return await c(p,`import type { Express } from 'express'
|
|
1650
1155
|
import type { AppAdapter, AdapterMiddleware, Container } from '@forinda/kickjs-core'
|
|
1651
1156
|
|
|
1652
|
-
export interface ${
|
|
1157
|
+
export interface ${n}AdapterOptions {
|
|
1653
1158
|
// Add your adapter configuration here
|
|
1654
1159
|
}
|
|
1655
1160
|
|
|
1656
1161
|
/**
|
|
1657
|
-
* ${
|
|
1162
|
+
* ${n} adapter.
|
|
1658
1163
|
*
|
|
1659
1164
|
* Hooks into the Application lifecycle to add middleware, routes,
|
|
1660
1165
|
* or external service connections.
|
|
1661
1166
|
*
|
|
1662
1167
|
* Usage:
|
|
1663
1168
|
* bootstrap({
|
|
1664
|
-
* adapters: [new ${
|
|
1169
|
+
* adapters: [new ${n}Adapter({ ... })],
|
|
1665
1170
|
* })
|
|
1666
1171
|
*/
|
|
1667
|
-
export class ${
|
|
1668
|
-
name = '${
|
|
1172
|
+
export class ${n}Adapter implements AppAdapter {
|
|
1173
|
+
name = '${n}Adapter'
|
|
1669
1174
|
|
|
1670
|
-
constructor(private options: ${
|
|
1175
|
+
constructor(private options: ${n}AdapterOptions = {}) {}
|
|
1671
1176
|
|
|
1672
1177
|
/**
|
|
1673
1178
|
* Return middleware entries that the Application will mount.
|
|
@@ -1680,7 +1185,7 @@ export class ${pascal}Adapter implements AppAdapter {
|
|
|
1680
1185
|
// {
|
|
1681
1186
|
// phase: 'beforeGlobal',
|
|
1682
1187
|
// handler: (_req: any, res: any, next: any) => {
|
|
1683
|
-
// res.setHeader('X-${
|
|
1188
|
+
// res.setHeader('X-${n}', 'true')
|
|
1684
1189
|
// next()
|
|
1685
1190
|
// },
|
|
1686
1191
|
// },
|
|
@@ -1700,7 +1205,7 @@ export class ${pascal}Adapter implements AppAdapter {
|
|
|
1700
1205
|
*/
|
|
1701
1206
|
beforeMount(app: Express, container: Container): void {
|
|
1702
1207
|
// Example: mount a status route
|
|
1703
|
-
// app.get('/${
|
|
1208
|
+
// app.get('/${o}/status', (_req, res) => {
|
|
1704
1209
|
// res.json({ status: 'ok' })
|
|
1705
1210
|
// })
|
|
1706
1211
|
}
|
|
@@ -1732,133 +1237,45 @@ export class ${pascal}Adapter implements AppAdapter {
|
|
|
1732
1237
|
// await this.pool.end()
|
|
1733
1238
|
}
|
|
1734
1239
|
}
|
|
1735
|
-
`);
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}
|
|
1739
|
-
__name(generateAdapter, "generateAdapter");
|
|
1740
|
-
|
|
1741
|
-
// src/generators/middleware.ts
|
|
1742
|
-
import { join as join4 } from "path";
|
|
1743
|
-
|
|
1744
|
-
// src/utils/resolve-out-dir.ts
|
|
1745
|
-
import { resolve, join as join3 } from "path";
|
|
1746
|
-
var DDD_FOLDER_MAP = {
|
|
1747
|
-
controller: "presentation",
|
|
1748
|
-
service: "domain/services",
|
|
1749
|
-
dto: "application/dtos",
|
|
1750
|
-
guard: "presentation/guards",
|
|
1751
|
-
middleware: "middleware"
|
|
1752
|
-
};
|
|
1753
|
-
var FLAT_FOLDER_MAP = {
|
|
1754
|
-
controller: "",
|
|
1755
|
-
service: "",
|
|
1756
|
-
dto: "dtos",
|
|
1757
|
-
guard: "guards",
|
|
1758
|
-
middleware: "middleware"
|
|
1759
|
-
};
|
|
1760
|
-
var CQRS_FOLDER_MAP = {
|
|
1761
|
-
controller: "",
|
|
1762
|
-
service: "",
|
|
1763
|
-
dto: "dtos",
|
|
1764
|
-
guard: "guards",
|
|
1765
|
-
middleware: "middleware",
|
|
1766
|
-
command: "commands",
|
|
1767
|
-
query: "queries",
|
|
1768
|
-
event: "events"
|
|
1769
|
-
};
|
|
1770
|
-
function resolveOutDir(options) {
|
|
1771
|
-
const { type, outDir, moduleName, modulesDir = "src/modules", defaultDir, pattern = "ddd" } = options;
|
|
1772
|
-
if (outDir) return resolve(outDir);
|
|
1773
|
-
if (moduleName) {
|
|
1774
|
-
const folderMap = pattern === "ddd" ? DDD_FOLDER_MAP : pattern === "cqrs" ? CQRS_FOLDER_MAP : FLAT_FOLDER_MAP;
|
|
1775
|
-
const kebab = toKebabCase(moduleName);
|
|
1776
|
-
const plural = pluralize(kebab);
|
|
1777
|
-
const subfolder = folderMap[type] ?? "";
|
|
1778
|
-
const base = join3(modulesDir, plural);
|
|
1779
|
-
return resolve(subfolder ? join3(base, subfolder) : base);
|
|
1780
|
-
}
|
|
1781
|
-
return resolve(defaultDir);
|
|
1782
|
-
}
|
|
1783
|
-
__name(resolveOutDir, "resolveOutDir");
|
|
1784
|
-
|
|
1785
|
-
// src/generators/middleware.ts
|
|
1786
|
-
async function generateMiddleware(options) {
|
|
1787
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
1788
|
-
const outDir = resolveOutDir({
|
|
1789
|
-
type: "middleware",
|
|
1790
|
-
outDir: options.outDir,
|
|
1791
|
-
moduleName,
|
|
1792
|
-
modulesDir,
|
|
1793
|
-
defaultDir: "src/middleware",
|
|
1794
|
-
pattern
|
|
1795
|
-
});
|
|
1796
|
-
const kebab = toKebabCase(name);
|
|
1797
|
-
const camel = toCamelCase(name);
|
|
1798
|
-
const files = [];
|
|
1799
|
-
const filePath = join4(outDir, `${kebab}.middleware.ts`);
|
|
1800
|
-
await writeFileSafe(filePath, `import type { Request, Response, NextFunction } from 'express'
|
|
1801
|
-
|
|
1802
|
-
export interface ${toPascalCase(name)}Options {
|
|
1240
|
+
`),s.push(p),s}i(Te,"generateAdapter");import{join as Ee}from"path";import{resolve as ie,join as pe}from"path";var Pe={controller:"presentation",service:"domain/services",dto:"application/dtos",guard:"presentation/guards",middleware:"middleware"},Se={controller:"",service:"",dto:"dtos",guard:"guards",middleware:"middleware"},je={controller:"",service:"",dto:"dtos",guard:"guards",middleware:"middleware",command:"commands",query:"queries",event:"events"};function w(e){let{type:t,outDir:r,moduleName:o,modulesDir:n="src/modules",defaultDir:s,pattern:p="ddd"}=e;if(r)return ie(r);if(o){let a=p==="ddd"?Pe:p==="cqrs"?je:Se,d=f(o),m=x(d),u=a[t]??"",y=pe(n,m);return ie(u?pe(y,u):y)}return ie(s)}i(w,"resolveOutDir");async function Ae(e){let{name:t,moduleName:r,modulesDir:o,pattern:n}=e,s=w({type:"middleware",outDir:e.outDir,moduleName:r,modulesDir:o,defaultDir:"src/middleware",pattern:n}),p=f(t),a=C(t),d=[],m=Ee(s,`${p}.middleware.ts`);return await c(m,`import type { Request, Response, NextFunction } from 'express'
|
|
1241
|
+
|
|
1242
|
+
export interface ${l(t)}Options {
|
|
1803
1243
|
// Add configuration options here
|
|
1804
1244
|
}
|
|
1805
1245
|
|
|
1806
1246
|
/**
|
|
1807
|
-
* ${
|
|
1247
|
+
* ${l(t)} middleware.
|
|
1808
1248
|
*
|
|
1809
1249
|
* Usage in bootstrap:
|
|
1810
|
-
* middleware: [${
|
|
1250
|
+
* middleware: [${a}()]
|
|
1811
1251
|
*
|
|
1812
1252
|
* Usage with adapter:
|
|
1813
|
-
* middleware() { return [{ handler: ${
|
|
1253
|
+
* middleware() { return [{ handler: ${a}(), phase: 'afterGlobal' }] }
|
|
1814
1254
|
*
|
|
1815
1255
|
* Usage with @Middleware decorator:
|
|
1816
|
-
* @Middleware(${
|
|
1256
|
+
* @Middleware(${a}())
|
|
1817
1257
|
*/
|
|
1818
|
-
export function ${
|
|
1258
|
+
export function ${a}(options: ${l(t)}Options = {}) {
|
|
1819
1259
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
1820
1260
|
// Implement your middleware logic here
|
|
1821
1261
|
next()
|
|
1822
1262
|
}
|
|
1823
1263
|
}
|
|
1824
|
-
`);
|
|
1825
|
-
files.push(filePath);
|
|
1826
|
-
return files;
|
|
1827
|
-
}
|
|
1828
|
-
__name(generateMiddleware, "generateMiddleware");
|
|
1829
|
-
|
|
1830
|
-
// src/generators/guard.ts
|
|
1831
|
-
import { join as join5 } from "path";
|
|
1832
|
-
async function generateGuard(options) {
|
|
1833
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
1834
|
-
const outDir = resolveOutDir({
|
|
1835
|
-
type: "guard",
|
|
1836
|
-
outDir: options.outDir,
|
|
1837
|
-
moduleName,
|
|
1838
|
-
modulesDir,
|
|
1839
|
-
defaultDir: "src/guards",
|
|
1840
|
-
pattern
|
|
1841
|
-
});
|
|
1842
|
-
const kebab = toKebabCase(name);
|
|
1843
|
-
const camel = toCamelCase(name);
|
|
1844
|
-
const pascal = toPascalCase(name);
|
|
1845
|
-
const files = [];
|
|
1846
|
-
const filePath = join5(outDir, `${kebab}.guard.ts`);
|
|
1847
|
-
await writeFileSafe(filePath, `import { Container, HttpException } from '@forinda/kickjs-core'
|
|
1264
|
+
`),d.push(m),d}i(Ae,"generateMiddleware");import{join as Ue}from"path";async function ze(e){let{name:t,moduleName:r,modulesDir:o,pattern:n}=e,s=w({type:"guard",outDir:e.outDir,moduleName:r,modulesDir:o,defaultDir:"src/guards",pattern:n}),p=f(t),a=C(t),d=l(t),m=[],u=Ue(s,`${p}.guard.ts`);return await c(u,`import { Container, HttpException } from '@forinda/kickjs-core'
|
|
1848
1265
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1849
1266
|
|
|
1850
1267
|
/**
|
|
1851
|
-
* ${
|
|
1268
|
+
* ${d} guard.
|
|
1852
1269
|
*
|
|
1853
1270
|
* Guards protect routes by checking conditions before the handler runs.
|
|
1854
1271
|
* Return early with an error response to block access.
|
|
1855
1272
|
*
|
|
1856
1273
|
* Usage:
|
|
1857
|
-
* @Middleware(${
|
|
1274
|
+
* @Middleware(${a}Guard)
|
|
1858
1275
|
* @Get('/protected')
|
|
1859
1276
|
* async handler(ctx: RequestContext) { ... }
|
|
1860
1277
|
*/
|
|
1861
|
-
export async function ${
|
|
1278
|
+
export async function ${a}Guard(ctx: RequestContext, next: () => void): Promise<void> {
|
|
1862
1279
|
// Example: check for an authorization header
|
|
1863
1280
|
const header = ctx.headers.authorization
|
|
1864
1281
|
if (!header?.startsWith('Bearer ')) {
|
|
@@ -1880,183 +1297,43 @@ export async function ${camel}Guard(ctx: RequestContext, next: () => void): Prom
|
|
|
1880
1297
|
ctx.res.status(401).json({ message: 'Invalid or expired token' })
|
|
1881
1298
|
}
|
|
1882
1299
|
}
|
|
1883
|
-
`);
|
|
1884
|
-
files.push(filePath);
|
|
1885
|
-
return files;
|
|
1886
|
-
}
|
|
1887
|
-
__name(generateGuard, "generateGuard");
|
|
1888
|
-
|
|
1889
|
-
// src/generators/service.ts
|
|
1890
|
-
import { join as join6 } from "path";
|
|
1891
|
-
async function generateService(options) {
|
|
1892
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
1893
|
-
const outDir = resolveOutDir({
|
|
1894
|
-
type: "service",
|
|
1895
|
-
outDir: options.outDir,
|
|
1896
|
-
moduleName,
|
|
1897
|
-
modulesDir,
|
|
1898
|
-
defaultDir: "src/services",
|
|
1899
|
-
pattern
|
|
1900
|
-
});
|
|
1901
|
-
const kebab = toKebabCase(name);
|
|
1902
|
-
const pascal = toPascalCase(name);
|
|
1903
|
-
const files = [];
|
|
1904
|
-
const filePath = join6(outDir, `${kebab}.service.ts`);
|
|
1905
|
-
await writeFileSafe(filePath, `import { Service } from '@forinda/kickjs-core'
|
|
1300
|
+
`),m.push(u),m}i(ze,"generateGuard");import{join as _e}from"path";async function qe(e){let{name:t,moduleName:r,modulesDir:o,pattern:n}=e,s=w({type:"service",outDir:e.outDir,moduleName:r,modulesDir:o,defaultDir:"src/services",pattern:n}),p=f(t),a=l(t),d=[],m=_e(s,`${p}.service.ts`);return await c(m,`import { Service } from '@forinda/kickjs-core'
|
|
1906
1301
|
|
|
1907
1302
|
@Service()
|
|
1908
|
-
export class ${
|
|
1303
|
+
export class ${a}Service {
|
|
1909
1304
|
// Inject dependencies via constructor
|
|
1910
1305
|
// constructor(
|
|
1911
1306
|
// @Inject(MY_REPO) private readonly repo: IMyRepository,
|
|
1912
1307
|
// ) {}
|
|
1913
1308
|
}
|
|
1914
|
-
`);
|
|
1915
|
-
files.push(filePath);
|
|
1916
|
-
return files;
|
|
1917
|
-
}
|
|
1918
|
-
__name(generateService, "generateService");
|
|
1919
|
-
|
|
1920
|
-
// src/generators/controller.ts
|
|
1921
|
-
import { join as join7 } from "path";
|
|
1922
|
-
async function generateController2(options) {
|
|
1923
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
1924
|
-
const outDir = resolveOutDir({
|
|
1925
|
-
type: "controller",
|
|
1926
|
-
outDir: options.outDir,
|
|
1927
|
-
moduleName,
|
|
1928
|
-
modulesDir,
|
|
1929
|
-
defaultDir: "src/controllers",
|
|
1930
|
-
pattern
|
|
1931
|
-
});
|
|
1932
|
-
const kebab = toKebabCase(name);
|
|
1933
|
-
const pascal = toPascalCase(name);
|
|
1934
|
-
const files = [];
|
|
1935
|
-
const filePath = join7(outDir, `${kebab}.controller.ts`);
|
|
1936
|
-
await writeFileSafe(filePath, `import { Controller, Get, Post, Autowired } from '@forinda/kickjs-core'
|
|
1309
|
+
`),d.push(m),d}i(qe,"generateService");import{join as Me}from"path";async function Fe(e){let{name:t,moduleName:r,modulesDir:o,pattern:n}=e,s=w({type:"controller",outDir:e.outDir,moduleName:r,modulesDir:o,defaultDir:"src/controllers",pattern:n}),p=f(t),a=l(t),d=[],m=Me(s,`${p}.controller.ts`);return await c(m,`import { Controller, Get, Post, Autowired } from '@forinda/kickjs-core'
|
|
1937
1310
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1938
1311
|
|
|
1939
1312
|
@Controller()
|
|
1940
|
-
export class ${
|
|
1313
|
+
export class ${a}Controller {
|
|
1941
1314
|
// @Autowired() private myService!: MyService
|
|
1942
1315
|
|
|
1943
1316
|
@Get('/')
|
|
1944
1317
|
async list(ctx: RequestContext) {
|
|
1945
|
-
ctx.json({ message: '${
|
|
1318
|
+
ctx.json({ message: '${a} list' })
|
|
1946
1319
|
}
|
|
1947
1320
|
|
|
1948
1321
|
@Post('/')
|
|
1949
1322
|
async create(ctx: RequestContext) {
|
|
1950
|
-
ctx.created({ message: '${
|
|
1323
|
+
ctx.created({ message: '${a} created', data: ctx.body })
|
|
1951
1324
|
}
|
|
1952
1325
|
}
|
|
1953
|
-
`);
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
}
|
|
1957
|
-
__name(generateController2, "generateController");
|
|
1958
|
-
|
|
1959
|
-
// src/generators/dto.ts
|
|
1960
|
-
import { join as join8 } from "path";
|
|
1961
|
-
async function generateDto(options) {
|
|
1962
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
1963
|
-
const outDir = resolveOutDir({
|
|
1964
|
-
type: "dto",
|
|
1965
|
-
outDir: options.outDir,
|
|
1966
|
-
moduleName,
|
|
1967
|
-
modulesDir,
|
|
1968
|
-
defaultDir: "src/dtos",
|
|
1969
|
-
pattern
|
|
1970
|
-
});
|
|
1971
|
-
const kebab = toKebabCase(name);
|
|
1972
|
-
const pascal = toPascalCase(name);
|
|
1973
|
-
const camel = toCamelCase(name);
|
|
1974
|
-
const files = [];
|
|
1975
|
-
const filePath = join8(outDir, `${kebab}.dto.ts`);
|
|
1976
|
-
await writeFileSafe(filePath, `import { z } from 'zod'
|
|
1977
|
-
|
|
1978
|
-
export const ${camel}Schema = z.object({
|
|
1326
|
+
`),d.push(m),d}i(Fe,"generateController");import{join as Qe}from"path";async function Ge(e){let{name:t,moduleName:r,modulesDir:o,pattern:n}=e,s=w({type:"dto",outDir:e.outDir,moduleName:r,modulesDir:o,defaultDir:"src/dtos",pattern:n}),p=f(t),a=l(t),d=C(t),m=[],u=Qe(s,`${p}.dto.ts`);return await c(u,`import { z } from 'zod'
|
|
1327
|
+
|
|
1328
|
+
export const ${d}Schema = z.object({
|
|
1979
1329
|
// Define your schema fields here
|
|
1980
1330
|
name: z.string().min(1).max(200),
|
|
1981
1331
|
})
|
|
1982
1332
|
|
|
1983
|
-
export type ${
|
|
1984
|
-
`);
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
}
|
|
1988
|
-
__name(generateDto, "generateDto");
|
|
1989
|
-
|
|
1990
|
-
// src/generators/project.ts
|
|
1991
|
-
import { join as join9, dirname as dirname2 } from "path";
|
|
1992
|
-
import { execSync } from "child_process";
|
|
1993
|
-
import { readFileSync } from "fs";
|
|
1994
|
-
import { fileURLToPath } from "url";
|
|
1995
|
-
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
1996
|
-
var cliPkg = JSON.parse(readFileSync(join9(__dirname, "..", "package.json"), "utf-8"));
|
|
1997
|
-
var KICKJS_VERSION = `^${cliPkg.version}`;
|
|
1998
|
-
async function initProject(options) {
|
|
1999
|
-
const { name, directory, packageManager = "pnpm", template = "rest" } = options;
|
|
2000
|
-
const dir = directory;
|
|
2001
|
-
console.log(`
|
|
2002
|
-
Creating KickJS project: ${name}
|
|
2003
|
-
`);
|
|
2004
|
-
const baseDeps = {
|
|
2005
|
-
"@forinda/kickjs-core": KICKJS_VERSION,
|
|
2006
|
-
"@forinda/kickjs-http": KICKJS_VERSION,
|
|
2007
|
-
"@forinda/kickjs-config": KICKJS_VERSION,
|
|
2008
|
-
express: "^5.1.0",
|
|
2009
|
-
"reflect-metadata": "^0.2.2",
|
|
2010
|
-
zod: "^4.3.6",
|
|
2011
|
-
pino: "^10.3.1",
|
|
2012
|
-
"pino-pretty": "^13.1.3"
|
|
2013
|
-
};
|
|
2014
|
-
if (template !== "minimal") {
|
|
2015
|
-
baseDeps["@forinda/kickjs-swagger"] = KICKJS_VERSION;
|
|
2016
|
-
baseDeps["@forinda/kickjs-devtools"] = KICKJS_VERSION;
|
|
2017
|
-
}
|
|
2018
|
-
if (template === "graphql") {
|
|
2019
|
-
baseDeps["@forinda/kickjs-graphql"] = KICKJS_VERSION;
|
|
2020
|
-
baseDeps["graphql"] = "^16.11.0";
|
|
2021
|
-
}
|
|
2022
|
-
if (template === "cqrs") {
|
|
2023
|
-
baseDeps["@forinda/kickjs-queue"] = KICKJS_VERSION;
|
|
2024
|
-
baseDeps["@forinda/kickjs-ws"] = KICKJS_VERSION;
|
|
2025
|
-
baseDeps["@forinda/kickjs-otel"] = KICKJS_VERSION;
|
|
2026
|
-
}
|
|
2027
|
-
if (template === "ddd") {
|
|
2028
|
-
baseDeps["@forinda/kickjs-swagger"] = KICKJS_VERSION;
|
|
2029
|
-
}
|
|
2030
|
-
await writeFileSafe(join9(dir, "package.json"), JSON.stringify({
|
|
2031
|
-
name,
|
|
2032
|
-
version: cliPkg.version,
|
|
2033
|
-
type: "module",
|
|
2034
|
-
scripts: {
|
|
2035
|
-
dev: "kick dev",
|
|
2036
|
-
"dev:debug": "kick dev:debug",
|
|
2037
|
-
build: "kick build",
|
|
2038
|
-
start: "kick start",
|
|
2039
|
-
test: "vitest run",
|
|
2040
|
-
"test:watch": "vitest",
|
|
2041
|
-
typecheck: "tsc --noEmit",
|
|
2042
|
-
lint: "eslint src/",
|
|
2043
|
-
format: "prettier --write src/"
|
|
2044
|
-
},
|
|
2045
|
-
dependencies: baseDeps,
|
|
2046
|
-
devDependencies: {
|
|
2047
|
-
"@forinda/kickjs-cli": KICKJS_VERSION,
|
|
2048
|
-
"@swc/core": "^1.7.28",
|
|
2049
|
-
"@types/express": "^5.0.6",
|
|
2050
|
-
"@types/node": "^24.5.2",
|
|
2051
|
-
"unplugin-swc": "^1.5.9",
|
|
2052
|
-
vite: "^7.3.1",
|
|
2053
|
-
"vite-node": "^5.3.0",
|
|
2054
|
-
vitest: "^3.2.4",
|
|
2055
|
-
typescript: "^5.9.2",
|
|
2056
|
-
prettier: "^3.8.1"
|
|
2057
|
-
}
|
|
2058
|
-
}, null, 2));
|
|
2059
|
-
await writeFileSafe(join9(dir, "vite.config.ts"), `import { defineConfig } from 'vite'
|
|
1333
|
+
export type ${a}DTO = z.infer<typeof ${d}Schema>
|
|
1334
|
+
`),m.push(u),m}i(Ge,"generateDto");import{join as $,dirname as Ne}from"path";import{execSync as _}from"child_process";import{readFileSync as Ye}from"fs";import{fileURLToPath as Be}from"url";var Le=Ne(Be(import.meta.url)),q=JSON.parse(Ye($(Le,"..","package.json"),"utf-8")),h=`^${q.version}`;async function We(e){let{name:t,directory:r,packageManager:o="pnpm",template:n="rest"}=e,s=r;console.log(`
|
|
1335
|
+
Creating KickJS project: ${t}
|
|
1336
|
+
`);let p={"@forinda/kickjs-core":h,"@forinda/kickjs-http":h,"@forinda/kickjs-config":h,express:"^5.1.0","reflect-metadata":"^0.2.2",zod:"^4.3.6",pino:"^10.3.1","pino-pretty":"^13.1.3"};if(n!=="minimal"&&(p["@forinda/kickjs-swagger"]=h,p["@forinda/kickjs-devtools"]=h),n==="graphql"&&(p["@forinda/kickjs-graphql"]=h,p.graphql="^16.11.0"),n==="cqrs"&&(p["@forinda/kickjs-queue"]=h,p["@forinda/kickjs-ws"]=h,p["@forinda/kickjs-otel"]=h),n==="ddd"&&(p["@forinda/kickjs-swagger"]=h),await c($(s,"package.json"),JSON.stringify({name:t,version:q.version,type:"module",scripts:{dev:"kick dev","dev:debug":"kick dev:debug",build:"kick build",start:"kick start",test:"vitest run","test:watch":"vitest",typecheck:"tsc --noEmit",lint:"eslint src/",format:"prettier --write src/"},dependencies:p,devDependencies:{"@forinda/kickjs-cli":h,"@swc/core":"^1.7.28","@types/express":"^5.0.6","@types/node":"^24.5.2","unplugin-swc":"^1.5.9",vite:"^7.3.1","vite-node":"^5.3.0",vitest:"^3.2.4",typescript:"^5.9.2",prettier:"^3.8.1"}},null,2)),await c($(s,"vite.config.ts"),`import { defineConfig } from 'vite'
|
|
2060
1337
|
import { resolve } from 'path'
|
|
2061
1338
|
import swc from 'unplugin-swc'
|
|
2062
1339
|
|
|
@@ -2082,46 +1359,7 @@ export default defineConfig({
|
|
|
2082
1359
|
},
|
|
2083
1360
|
},
|
|
2084
1361
|
})
|
|
2085
|
-
`)
|
|
2086
|
-
await writeFileSafe(join9(dir, "tsconfig.json"), JSON.stringify({
|
|
2087
|
-
compilerOptions: {
|
|
2088
|
-
target: "ES2022",
|
|
2089
|
-
module: "ESNext",
|
|
2090
|
-
moduleResolution: "bundler",
|
|
2091
|
-
lib: [
|
|
2092
|
-
"ES2022"
|
|
2093
|
-
],
|
|
2094
|
-
types: [
|
|
2095
|
-
"node",
|
|
2096
|
-
"vite/client"
|
|
2097
|
-
],
|
|
2098
|
-
strict: true,
|
|
2099
|
-
esModuleInterop: true,
|
|
2100
|
-
skipLibCheck: true,
|
|
2101
|
-
sourceMap: true,
|
|
2102
|
-
declaration: true,
|
|
2103
|
-
experimentalDecorators: true,
|
|
2104
|
-
emitDecoratorMetadata: true,
|
|
2105
|
-
outDir: "dist",
|
|
2106
|
-
rootDir: "src",
|
|
2107
|
-
paths: {
|
|
2108
|
-
"@/*": [
|
|
2109
|
-
"./src/*"
|
|
2110
|
-
]
|
|
2111
|
-
}
|
|
2112
|
-
},
|
|
2113
|
-
include: [
|
|
2114
|
-
"src"
|
|
2115
|
-
]
|
|
2116
|
-
}, null, 2));
|
|
2117
|
-
await writeFileSafe(join9(dir, ".prettierrc"), JSON.stringify({
|
|
2118
|
-
semi: false,
|
|
2119
|
-
singleQuote: true,
|
|
2120
|
-
trailingComma: "all",
|
|
2121
|
-
printWidth: 100,
|
|
2122
|
-
tabWidth: 2
|
|
2123
|
-
}, null, 2));
|
|
2124
|
-
await writeFileSafe(join9(dir, ".editorconfig"), `# https://editorconfig.org
|
|
1362
|
+
`),await c($(s,"tsconfig.json"),JSON.stringify({compilerOptions:{target:"ES2022",module:"ESNext",moduleResolution:"bundler",lib:["ES2022"],types:["node","vite/client"],strict:!0,esModuleInterop:!0,skipLibCheck:!0,sourceMap:!0,declaration:!0,experimentalDecorators:!0,emitDecoratorMetadata:!0,outDir:"dist",rootDir:"src",paths:{"@/*":["./src/*"]}},include:["src"]},null,2)),await c($(s,".prettierrc"),JSON.stringify({semi:!1,singleQuote:!0,trailingComma:"all",printWidth:100,tabWidth:2},null,2)),await c($(s,".editorconfig"),`# https://editorconfig.org
|
|
2125
1363
|
root = true
|
|
2126
1364
|
|
|
2127
1365
|
[*]
|
|
@@ -2134,15 +1372,13 @@ insert_final_newline = true
|
|
|
2134
1372
|
|
|
2135
1373
|
[*.md]
|
|
2136
1374
|
trim_trailing_whitespace = false
|
|
2137
|
-
`)
|
|
2138
|
-
await writeFileSafe(join9(dir, ".gitignore"), `node_modules/
|
|
1375
|
+
`),await c($(s,".gitignore"),`node_modules/
|
|
2139
1376
|
dist/
|
|
2140
1377
|
.env
|
|
2141
1378
|
coverage/
|
|
2142
1379
|
.DS_Store
|
|
2143
1380
|
*.tsbuildinfo
|
|
2144
|
-
`)
|
|
2145
|
-
await writeFileSafe(join9(dir, ".gitattributes"), `# Auto-detect text files and normalise line endings to LF
|
|
1381
|
+
`),await c($(s,".gitattributes"),`# Auto-detect text files and normalise line endings to LF
|
|
2146
1382
|
* text=auto eol=lf
|
|
2147
1383
|
|
|
2148
1384
|
# Explicitly mark generated / binary files
|
|
@@ -2160,25 +1396,17 @@ coverage/
|
|
|
2160
1396
|
pnpm-lock.yaml -diff linguist-generated
|
|
2161
1397
|
yarn.lock -diff linguist-generated
|
|
2162
1398
|
package-lock.json -diff linguist-generated
|
|
2163
|
-
`)
|
|
2164
|
-
await writeFileSafe(join9(dir, ".env"), `PORT=3000
|
|
1399
|
+
`),await c($(s,".env"),`PORT=3000
|
|
2165
1400
|
NODE_ENV=development
|
|
2166
|
-
`)
|
|
2167
|
-
await writeFileSafe(join9(dir, ".env.example"), `PORT=3000
|
|
1401
|
+
`),await c($(s,".env.example"),`PORT=3000
|
|
2168
1402
|
NODE_ENV=development
|
|
2169
|
-
`)
|
|
2170
|
-
await writeFileSafe(join9(dir, "src/index.ts"), getEntryFile(name, template));
|
|
2171
|
-
await writeFileSafe(join9(dir, "src/modules/index.ts"), `import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
1403
|
+
`),await c($(s,"src/index.ts"),be(t,n)),await c($(s,"src/modules/index.ts"),`import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
2172
1404
|
|
|
2173
1405
|
export const modules: AppModuleClass[] = []
|
|
2174
|
-
`)
|
|
2175
|
-
if (template === "graphql") {
|
|
2176
|
-
await writeFileSafe(join9(dir, "src/resolvers/.gitkeep"), "");
|
|
2177
|
-
}
|
|
2178
|
-
await writeFileSafe(join9(dir, "kick.config.ts"), `import { defineConfig } from '@forinda/kickjs-cli'
|
|
1406
|
+
`),n==="graphql"&&await c($(s,"src/resolvers/.gitkeep"),""),await c($(s,"kick.config.ts"),`import { defineConfig } from '@forinda/kickjs-cli'
|
|
2179
1407
|
|
|
2180
1408
|
export default defineConfig({
|
|
2181
|
-
pattern: '${
|
|
1409
|
+
pattern: '${n}',
|
|
2182
1410
|
modulesDir: 'src/modules',
|
|
2183
1411
|
defaultRepo: 'inmemory',
|
|
2184
1412
|
|
|
@@ -2206,8 +1434,7 @@ export default defineConfig({
|
|
|
2206
1434
|
},
|
|
2207
1435
|
],
|
|
2208
1436
|
})
|
|
2209
|
-
`)
|
|
2210
|
-
await writeFileSafe(join9(dir, "vitest.config.ts"), `import { defineConfig } from 'vitest/config'
|
|
1437
|
+
`),await c($(s,"vitest.config.ts"),`import { defineConfig } from 'vitest/config'
|
|
2211
1438
|
import swc from 'unplugin-swc'
|
|
2212
1439
|
|
|
2213
1440
|
export default defineConfig({
|
|
@@ -2218,89 +1445,12 @@ export default defineConfig({
|
|
|
2218
1445
|
include: ['src/**/*.test.ts'],
|
|
2219
1446
|
},
|
|
2220
1447
|
})
|
|
2221
|
-
`)
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
stdio: "pipe"
|
|
2228
|
-
});
|
|
2229
|
-
execSync("git add -A", {
|
|
2230
|
-
cwd: dir,
|
|
2231
|
-
stdio: "pipe"
|
|
2232
|
-
});
|
|
2233
|
-
execSync('git commit -m "chore: initial commit from kick new"', {
|
|
2234
|
-
cwd: dir,
|
|
2235
|
-
stdio: "pipe"
|
|
2236
|
-
});
|
|
2237
|
-
console.log(" Git repository initialized");
|
|
2238
|
-
} catch {
|
|
2239
|
-
console.log(" Warning: git init failed (git may not be installed)");
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
if (options.installDeps) {
|
|
2243
|
-
console.log(`
|
|
2244
|
-
Installing dependencies with ${packageManager}...
|
|
2245
|
-
`);
|
|
2246
|
-
try {
|
|
2247
|
-
execSync(`${packageManager} install`, {
|
|
2248
|
-
cwd: dir,
|
|
2249
|
-
stdio: "inherit"
|
|
2250
|
-
});
|
|
2251
|
-
console.log("\n Dependencies installed successfully!");
|
|
2252
|
-
} catch {
|
|
2253
|
-
console.log(`
|
|
2254
|
-
Warning: ${packageManager} install failed. Run it manually.`);
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
console.log("\n Project scaffolded successfully!");
|
|
2258
|
-
console.log();
|
|
2259
|
-
const needsCd = dir !== process.cwd();
|
|
2260
|
-
console.log(" Next steps:");
|
|
2261
|
-
if (needsCd) console.log(` cd ${name}`);
|
|
2262
|
-
if (!options.installDeps) console.log(` ${packageManager} install`);
|
|
2263
|
-
const genHint = {
|
|
2264
|
-
rest: "kick g module user",
|
|
2265
|
-
graphql: "kick g resolver user",
|
|
2266
|
-
ddd: "kick g module user --repo drizzle",
|
|
2267
|
-
cqrs: "kick g module user --pattern cqrs",
|
|
2268
|
-
minimal: "# add your routes to src/index.ts"
|
|
2269
|
-
};
|
|
2270
|
-
console.log(` ${genHint[template] ?? genHint.rest}`);
|
|
2271
|
-
console.log(" kick dev");
|
|
2272
|
-
console.log();
|
|
2273
|
-
console.log(" Commands:");
|
|
2274
|
-
console.log(" kick dev Start dev server with Vite HMR");
|
|
2275
|
-
console.log(" kick build Production build via Vite");
|
|
2276
|
-
console.log(" kick start Run production build");
|
|
2277
|
-
console.log();
|
|
2278
|
-
console.log(" Generators:");
|
|
2279
|
-
console.log(" kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)");
|
|
2280
|
-
console.log(" kick g scaffold <n> <f..> CRUD module from field definitions");
|
|
2281
|
-
console.log(" kick g controller <name> Standalone controller");
|
|
2282
|
-
console.log(" kick g service <name> @Service() class");
|
|
2283
|
-
console.log(" kick g middleware <name> Express middleware");
|
|
2284
|
-
console.log(" kick g guard <name> Route guard (auth, roles, etc.)");
|
|
2285
|
-
console.log(" kick g adapter <name> AppAdapter with lifecycle hooks");
|
|
2286
|
-
console.log(" kick g dto <name> Zod DTO schema");
|
|
2287
|
-
if (template === "graphql") console.log(" kick g resolver <name> GraphQL resolver");
|
|
2288
|
-
if (template === "cqrs") console.log(" kick g job <name> Queue job processor");
|
|
2289
|
-
console.log(" kick g config Generate kick.config.ts");
|
|
2290
|
-
console.log();
|
|
2291
|
-
console.log(" Add packages:");
|
|
2292
|
-
console.log(" kick add <pkg> Install a KickJS package + peers");
|
|
2293
|
-
console.log(" kick add --list Show all available packages");
|
|
2294
|
-
console.log();
|
|
2295
|
-
console.log(" Available: auth, swagger, graphql, drizzle, prisma, ws,");
|
|
2296
|
-
console.log(" cron, queue, mailer, otel, multi-tenant, notifications, testing");
|
|
2297
|
-
console.log();
|
|
2298
|
-
}
|
|
2299
|
-
__name(initProject, "initProject");
|
|
2300
|
-
function getEntryFile(name, template) {
|
|
2301
|
-
switch (template) {
|
|
2302
|
-
case "graphql":
|
|
2303
|
-
return `import 'reflect-metadata'
|
|
1448
|
+
`),await c($(s,"README.md"),Ke(t,n,o)),e.initGit)try{_("git init",{cwd:s,stdio:"pipe"}),_("git add -A",{cwd:s,stdio:"pipe"}),_('git commit -m "chore: initial commit from kick new"',{cwd:s,stdio:"pipe"}),console.log(" Git repository initialized")}catch{console.log(" Warning: git init failed (git may not be installed)")}if(e.installDeps){console.log(`
|
|
1449
|
+
Installing dependencies with ${o}...
|
|
1450
|
+
`);try{_(`${o} install`,{cwd:s,stdio:"inherit"}),console.log(`
|
|
1451
|
+
Dependencies installed successfully!`)}catch{console.log(`
|
|
1452
|
+
Warning: ${o} install failed. Run it manually.`)}}console.log(`
|
|
1453
|
+
Project scaffolded successfully!`),console.log();let a=s!==process.cwd();console.log(" Next steps:"),a&&console.log(` cd ${t}`),e.installDeps||console.log(` ${o} install`);let d={rest:"kick g module user",graphql:"kick g resolver user",ddd:"kick g module user --repo drizzle",cqrs:"kick g module user --pattern cqrs",minimal:"# add your routes to src/index.ts"};console.log(` ${d[n]??d.rest}`),console.log(" kick dev"),console.log(),console.log(" Commands:"),console.log(" kick dev Start dev server with Vite HMR"),console.log(" kick build Production build via Vite"),console.log(" kick start Run production build"),console.log(),console.log(" Generators:"),console.log(" kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)"),console.log(" kick g scaffold <n> <f..> CRUD module from field definitions"),console.log(" kick g controller <name> Standalone controller"),console.log(" kick g service <name> @Service() class"),console.log(" kick g middleware <name> Express middleware"),console.log(" kick g guard <name> Route guard (auth, roles, etc.)"),console.log(" kick g adapter <name> AppAdapter with lifecycle hooks"),console.log(" kick g dto <name> Zod DTO schema"),n==="graphql"&&console.log(" kick g resolver <name> GraphQL resolver"),n==="cqrs"&&console.log(" kick g job <name> Queue job processor"),console.log(" kick g config Generate kick.config.ts"),console.log(),console.log(" Add packages:"),console.log(" kick add <pkg> Install a KickJS package + peers"),console.log(" kick add --list Show all available packages"),console.log(),console.log(" Available: auth, swagger, graphql, drizzle, prisma, ws,"),console.log(" cron, queue, mailer, otel, multi-tenant, notifications, testing"),console.log()}i(We,"initProject");function be(e,t){switch(t){case"graphql":return`import 'reflect-metadata'
|
|
2304
1454
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
2305
1455
|
import { DevToolsAdapter } from '@forinda/kickjs-devtools'
|
|
2306
1456
|
import { GraphQLAdapter } from '@forinda/kickjs-graphql'
|
|
@@ -2320,9 +1470,7 @@ bootstrap({
|
|
|
2320
1470
|
}),
|
|
2321
1471
|
],
|
|
2322
1472
|
})
|
|
2323
|
-
`;
|
|
2324
|
-
case "cqrs":
|
|
2325
|
-
return `import 'reflect-metadata'
|
|
1473
|
+
`;case"cqrs":return`import 'reflect-metadata'
|
|
2326
1474
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
2327
1475
|
import { DevToolsAdapter } from '@forinda/kickjs-devtools'
|
|
2328
1476
|
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
@@ -2334,10 +1482,10 @@ import { modules } from './modules'
|
|
|
2334
1482
|
bootstrap({
|
|
2335
1483
|
modules,
|
|
2336
1484
|
adapters: [
|
|
2337
|
-
new OtelAdapter({ serviceName: '${
|
|
1485
|
+
new OtelAdapter({ serviceName: '${e}' }),
|
|
2338
1486
|
new DevToolsAdapter(),
|
|
2339
1487
|
new SwaggerAdapter({
|
|
2340
|
-
info: { title: '${
|
|
1488
|
+
info: { title: '${e}', version: '${q.version}' },
|
|
2341
1489
|
}),
|
|
2342
1490
|
// Uncomment for WebSocket support:
|
|
2343
1491
|
// new WsAdapter(),
|
|
@@ -2347,18 +1495,12 @@ bootstrap({
|
|
|
2347
1495
|
// }),
|
|
2348
1496
|
],
|
|
2349
1497
|
})
|
|
2350
|
-
`;
|
|
2351
|
-
case "minimal":
|
|
2352
|
-
return `import 'reflect-metadata'
|
|
1498
|
+
`;case"minimal":return`import 'reflect-metadata'
|
|
2353
1499
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
2354
1500
|
import { modules } from './modules'
|
|
2355
1501
|
|
|
2356
1502
|
bootstrap({ modules })
|
|
2357
|
-
`;
|
|
2358
|
-
case "ddd":
|
|
2359
|
-
case "rest":
|
|
2360
|
-
default:
|
|
2361
|
-
return `import 'reflect-metadata'
|
|
1503
|
+
`;default:return`import 'reflect-metadata'
|
|
2362
1504
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
2363
1505
|
import { DevToolsAdapter } from '@forinda/kickjs-devtools'
|
|
2364
1506
|
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
@@ -2369,42 +1511,18 @@ bootstrap({
|
|
|
2369
1511
|
adapters: [
|
|
2370
1512
|
new DevToolsAdapter(),
|
|
2371
1513
|
new SwaggerAdapter({
|
|
2372
|
-
info: { title: '${
|
|
1514
|
+
info: { title: '${e}', version: '${q.version}' },
|
|
2373
1515
|
}),
|
|
2374
1516
|
],
|
|
2375
1517
|
})
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
}
|
|
2379
|
-
__name(getEntryFile, "getEntryFile");
|
|
2380
|
-
function generateReadme(name, template, pm) {
|
|
2381
|
-
const templateLabels = {
|
|
2382
|
-
rest: "REST API",
|
|
2383
|
-
graphql: "GraphQL API",
|
|
2384
|
-
ddd: "Domain-Driven Design",
|
|
2385
|
-
cqrs: "CQRS + Event-Driven",
|
|
2386
|
-
minimal: "Minimal"
|
|
2387
|
-
};
|
|
2388
|
-
const packages = [
|
|
2389
|
-
"@forinda/kickjs-core",
|
|
2390
|
-
"@forinda/kickjs-http",
|
|
2391
|
-
"@forinda/kickjs-config"
|
|
2392
|
-
];
|
|
2393
|
-
if (template !== "minimal") {
|
|
2394
|
-
packages.push("@forinda/kickjs-swagger", "@forinda/kickjs-devtools");
|
|
2395
|
-
}
|
|
2396
|
-
if (template === "graphql") packages.push("@forinda/kickjs-graphql");
|
|
2397
|
-
if (template === "cqrs") {
|
|
2398
|
-
packages.push("@forinda/kickjs-queue", "@forinda/kickjs-ws", "@forinda/kickjs-otel");
|
|
2399
|
-
}
|
|
2400
|
-
return `# ${name}
|
|
2401
|
-
|
|
2402
|
-
A **${templateLabels[template] ?? "REST API"}** built with [KickJS](https://forinda.github.io/kick-js/) \u2014 a decorator-driven Node.js framework on Express 5 and TypeScript.
|
|
1518
|
+
`}}i(be,"getEntryFile");function Ke(e,t,r){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 t!=="minimal"&&n.push("@forinda/kickjs-swagger","@forinda/kickjs-devtools"),t==="graphql"&&n.push("@forinda/kickjs-graphql"),t==="cqrs"&&n.push("@forinda/kickjs-queue","@forinda/kickjs-ws","@forinda/kickjs-otel"),`# ${e}
|
|
1519
|
+
|
|
1520
|
+
A **${o[t]??"REST API"}** built with [KickJS](https://forinda.github.io/kick-js/) \u2014 a decorator-driven Node.js framework on Express 5 and TypeScript.
|
|
2403
1521
|
|
|
2404
1522
|
## Getting Started
|
|
2405
1523
|
|
|
2406
1524
|
\`\`\`bash
|
|
2407
|
-
${
|
|
1525
|
+
${r} install
|
|
2408
1526
|
kick dev
|
|
2409
1527
|
\`\`\`
|
|
2410
1528
|
|
|
@@ -2415,7 +1533,7 @@ kick dev
|
|
|
2415
1533
|
| \`kick dev\` | Start dev server with Vite HMR |
|
|
2416
1534
|
| \`kick build\` | Production build |
|
|
2417
1535
|
| \`kick start\` | Run production build |
|
|
2418
|
-
| \`${
|
|
1536
|
+
| \`${r} run test\` | Run tests with Vitest |
|
|
2419
1537
|
| \`kick g module <name>\` | Generate a DDD module |
|
|
2420
1538
|
| \`kick g scaffold <name> <fields...>\` | Generate CRUD from field definitions |
|
|
2421
1539
|
| \`kick add <package>\` | Add a KickJS package |
|
|
@@ -2432,7 +1550,8 @@ src/
|
|
|
2432
1550
|
|
|
2433
1551
|
## Packages
|
|
2434
1552
|
|
|
2435
|
-
${
|
|
1553
|
+
${n.map(s=>`- \`${s}\``).join(`
|
|
1554
|
+
`)}
|
|
2436
1555
|
|
|
2437
1556
|
## Adding Features
|
|
2438
1557
|
|
|
@@ -2459,63 +1578,4 @@ Copy \`.env.example\` to \`.env\` and configure:
|
|
|
2459
1578
|
|
|
2460
1579
|
- [KickJS Documentation](https://forinda.github.io/kick-js/)
|
|
2461
1580
|
- [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
|
|
2462
|
-
|
|
2463
|
-
}
|
|
2464
|
-
__name(generateReadme, "generateReadme");
|
|
2465
|
-
|
|
2466
|
-
// src/config.ts
|
|
2467
|
-
import { readFile as readFile3, access as access2 } from "fs/promises";
|
|
2468
|
-
import { join as join10 } from "path";
|
|
2469
|
-
function defineConfig(config) {
|
|
2470
|
-
return config;
|
|
2471
|
-
}
|
|
2472
|
-
__name(defineConfig, "defineConfig");
|
|
2473
|
-
var CONFIG_FILES = [
|
|
2474
|
-
"kick.config.ts",
|
|
2475
|
-
"kick.config.js",
|
|
2476
|
-
"kick.config.mjs",
|
|
2477
|
-
"kick.config.json"
|
|
2478
|
-
];
|
|
2479
|
-
async function loadKickConfig(cwd) {
|
|
2480
|
-
for (const filename of CONFIG_FILES) {
|
|
2481
|
-
const filepath = join10(cwd, filename);
|
|
2482
|
-
try {
|
|
2483
|
-
await access2(filepath);
|
|
2484
|
-
} catch {
|
|
2485
|
-
continue;
|
|
2486
|
-
}
|
|
2487
|
-
if (filename.endsWith(".json")) {
|
|
2488
|
-
const content = await readFile3(filepath, "utf-8");
|
|
2489
|
-
return JSON.parse(content);
|
|
2490
|
-
}
|
|
2491
|
-
try {
|
|
2492
|
-
const { pathToFileURL } = await import("url");
|
|
2493
|
-
const mod = await import(pathToFileURL(filepath).href);
|
|
2494
|
-
return mod.default ?? mod;
|
|
2495
|
-
} catch (err) {
|
|
2496
|
-
if (filename.endsWith(".ts")) {
|
|
2497
|
-
console.warn(`Warning: Failed to load ${filename}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);
|
|
2498
|
-
}
|
|
2499
|
-
continue;
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
2502
|
-
return null;
|
|
2503
|
-
}
|
|
2504
|
-
__name(loadKickConfig, "loadKickConfig");
|
|
2505
|
-
export {
|
|
2506
|
-
defineConfig,
|
|
2507
|
-
generateAdapter,
|
|
2508
|
-
generateController2 as generateController,
|
|
2509
|
-
generateDto,
|
|
2510
|
-
generateGuard,
|
|
2511
|
-
generateMiddleware,
|
|
2512
|
-
generateModule,
|
|
2513
|
-
generateService,
|
|
2514
|
-
initProject,
|
|
2515
|
-
loadKickConfig,
|
|
2516
|
-
pluralize,
|
|
2517
|
-
toCamelCase,
|
|
2518
|
-
toKebabCase,
|
|
2519
|
-
toPascalCase
|
|
2520
|
-
};
|
|
2521
|
-
//# sourceMappingURL=index.js.map
|
|
1581
|
+
`}i(Ke,"generateReadme");import{readFile as He,access as Ve}from"fs/promises";import{join as Je}from"path";function Ze(e){return e}i(Ze,"defineConfig");var Xe=["kick.config.ts","kick.config.js","kick.config.mjs","kick.config.json"];async function et(e){for(let t of Xe){let r=Je(e,t);try{await Ve(r)}catch{continue}if(t.endsWith(".json")){let o=await He(r,"utf-8");return JSON.parse(o)}try{let{pathToFileURL:o}=await import("url"),n=await import(o(r).href);return n.default??n}catch{t.endsWith(".ts")&&console.warn(`Warning: Failed to load ${t}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);continue}}return null}i(et,"loadKickConfig");export{Ze as defineConfig,Te as generateAdapter,Fe as generateController,Ge as generateDto,ze as generateGuard,Ae as generateMiddleware,Ce as generateModule,qe as generateService,We as initProject,et as loadKickConfig,x as pluralize,C as toCamelCase,f as toKebabCase,l as toPascalCase};
|