nestjs-ddd-cli 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +118 -9
  2. package/ddd.schema.json +11 -1
  3. package/dist/commands/doctor-enhanced.d.ts +1 -0
  4. package/dist/commands/doctor-enhanced.js +94 -79
  5. package/dist/commands/doctor-enhanced.js.map +1 -1
  6. package/dist/commands/doctor.d.ts +9 -0
  7. package/dist/commands/doctor.js +68 -21
  8. package/dist/commands/doctor.js.map +1 -1
  9. package/dist/commands/generate-all.js +238 -176
  10. package/dist/commands/generate-all.js.map +1 -1
  11. package/dist/commands/generate-controller.d.ts +1 -0
  12. package/dist/commands/generate-controller.js +71 -0
  13. package/dist/commands/generate-controller.js.map +1 -0
  14. package/dist/commands/generate-domain-service.d.ts +1 -1
  15. package/dist/commands/generate-domain-service.js +6 -4
  16. package/dist/commands/generate-domain-service.js.map +1 -1
  17. package/dist/commands/generate-dto.d.ts +4 -0
  18. package/dist/commands/generate-dto.js +162 -0
  19. package/dist/commands/generate-dto.js.map +1 -0
  20. package/dist/commands/generate-entity.js +44 -35
  21. package/dist/commands/generate-entity.js.map +1 -1
  22. package/dist/commands/generate-event.js +10 -4
  23. package/dist/commands/generate-event.js.map +1 -1
  24. package/dist/commands/generate-migration-deployment.d.ts +15 -0
  25. package/dist/commands/generate-migration-deployment.js +326 -0
  26. package/dist/commands/generate-migration-deployment.js.map +1 -0
  27. package/dist/commands/generate-migration-deployment.spec.d.ts +1 -0
  28. package/dist/commands/generate-migration-deployment.spec.js +337 -0
  29. package/dist/commands/generate-migration-deployment.spec.js.map +1 -0
  30. package/dist/commands/generate-module.js +41 -31
  31. package/dist/commands/generate-module.js.map +1 -1
  32. package/dist/commands/generate-query.js +23 -4
  33. package/dist/commands/generate-query.js.map +1 -1
  34. package/dist/commands/generate-repository.d.ts +1 -1
  35. package/dist/commands/generate-repository.js +17 -34
  36. package/dist/commands/generate-repository.js.map +1 -1
  37. package/dist/commands/generate-service.js +19 -4
  38. package/dist/commands/generate-service.js.map +1 -1
  39. package/dist/commands/generate-usecase.js +12 -31
  40. package/dist/commands/generate-usecase.js.map +1 -1
  41. package/dist/commands/generate-value-object.js +3 -14
  42. package/dist/commands/generate-value-object.js.map +1 -1
  43. package/dist/commands/init-project.d.ts +3 -0
  44. package/dist/commands/init-project.js +19 -9
  45. package/dist/commands/init-project.js.map +1 -1
  46. package/dist/commands/interactive-scaffold.js +10 -0
  47. package/dist/commands/interactive-scaffold.js.map +1 -1
  48. package/dist/commands/migration.d.ts +3 -0
  49. package/dist/commands/migration.js +83 -48
  50. package/dist/commands/migration.js.map +1 -1
  51. package/dist/commands/recipe.d.ts +2 -0
  52. package/dist/commands/recipe.js +282 -91
  53. package/dist/commands/recipe.js.map +1 -1
  54. package/dist/commands/recipes/banklink-connector-contract.recipe.d.ts +1 -0
  55. package/dist/commands/recipes/banklink-connector-contract.recipe.js +161 -0
  56. package/dist/commands/recipes/banklink-connector-contract.recipe.js.map +1 -0
  57. package/dist/commands/recipes/bounded-json-http.recipe.d.ts +2 -0
  58. package/dist/commands/recipes/bounded-json-http.recipe.js +53 -0
  59. package/dist/commands/recipes/bounded-json-http.recipe.js.map +1 -0
  60. package/dist/commands/recipes/business-reference-identifiers.recipe.d.ts +1 -0
  61. package/dist/commands/recipes/business-reference-identifiers.recipe.js +223 -0
  62. package/dist/commands/recipes/business-reference-identifiers.recipe.js.map +1 -0
  63. package/dist/commands/recipes/event-backbone.recipe.d.ts +1 -0
  64. package/dist/commands/recipes/event-backbone.recipe.js +1118 -0
  65. package/dist/commands/recipes/event-backbone.recipe.js.map +1 -0
  66. package/dist/commands/recipes/external-projection-worker.recipe.d.ts +6 -0
  67. package/dist/commands/recipes/external-projection-worker.recipe.js +211 -0
  68. package/dist/commands/recipes/external-projection-worker.recipe.js.map +1 -0
  69. package/dist/commands/recipes/index.d.ts +12 -0
  70. package/dist/commands/recipes/index.js +12 -0
  71. package/dist/commands/recipes/index.js.map +1 -1
  72. package/dist/commands/recipes/jest-cjs-compat.recipe.d.ts +3 -0
  73. package/dist/commands/recipes/jest-cjs-compat.recipe.js +130 -0
  74. package/dist/commands/recipes/jest-cjs-compat.recipe.js.map +1 -0
  75. package/dist/commands/recipes/joi-env.recipe.d.ts +1 -0
  76. package/dist/commands/recipes/joi-env.recipe.js +301 -0
  77. package/dist/commands/recipes/joi-env.recipe.js.map +1 -0
  78. package/dist/commands/recipes/kafka-consumer.recipe.d.ts +1 -0
  79. package/dist/commands/recipes/kafka-consumer.recipe.js +65 -0
  80. package/dist/commands/recipes/kafka-consumer.recipe.js.map +1 -0
  81. package/dist/commands/recipes/oauth2-client-credentials.recipe.d.ts +1 -0
  82. package/dist/commands/recipes/oauth2-client-credentials.recipe.js +60 -0
  83. package/dist/commands/recipes/oauth2-client-credentials.recipe.js.map +1 -0
  84. package/dist/commands/recipes/oauth2-token-introspection.recipe.d.ts +1 -0
  85. package/dist/commands/recipes/oauth2-token-introspection.recipe.js +67 -0
  86. package/dist/commands/recipes/oauth2-token-introspection.recipe.js.map +1 -0
  87. package/dist/commands/recipes/oidc-bff-session.recipe.d.ts +1 -0
  88. package/dist/commands/recipes/oidc-bff-session.recipe.js +57 -0
  89. package/dist/commands/recipes/oidc-bff-session.recipe.js.map +1 -0
  90. package/dist/commands/recipes/oidc-dashboard.recipe.d.ts +1 -0
  91. package/dist/commands/recipes/oidc-dashboard.recipe.js +463 -0
  92. package/dist/commands/recipes/oidc-dashboard.recipe.js.map +1 -0
  93. package/dist/commands/recipes/oidc-resource-server.recipe.d.ts +1 -0
  94. package/dist/commands/recipes/oidc-resource-server.recipe.js +85 -0
  95. package/dist/commands/recipes/oidc-resource-server.recipe.js.map +1 -0
  96. package/dist/commands/recipes/platform-context.recipe.d.ts +1 -0
  97. package/dist/commands/recipes/platform-context.recipe.js +229 -0
  98. package/dist/commands/recipes/platform-context.recipe.js.map +1 -0
  99. package/dist/commands/recipes/platform-parc-authorization.recipe.d.ts +1 -0
  100. package/dist/commands/recipes/platform-parc-authorization.recipe.js +339 -0
  101. package/dist/commands/recipes/platform-parc-authorization.recipe.js.map +1 -0
  102. package/dist/commands/recipes/platform-parc-bound-authorization.recipe.d.ts +1 -0
  103. package/dist/commands/recipes/platform-parc-bound-authorization.recipe.js +56 -0
  104. package/dist/commands/recipes/platform-parc-bound-authorization.recipe.js.map +1 -0
  105. package/dist/commands/recipes/platform-service-access-request-context.recipe.d.ts +1 -0
  106. package/dist/commands/recipes/platform-service-access-request-context.recipe.js +103 -0
  107. package/dist/commands/recipes/platform-service-access-request-context.recipe.js.map +1 -0
  108. package/dist/commands/recipes/platform-service-runtime.recipe.d.ts +1 -0
  109. package/dist/commands/recipes/platform-service-runtime.recipe.js +415 -0
  110. package/dist/commands/recipes/platform-service-runtime.recipe.js.map +1 -0
  111. package/dist/index.js +82 -11
  112. package/dist/index.js.map +1 -1
  113. package/dist/templates/ai-context/CLAUDE.md.hbs +54 -3
  114. package/dist/templates/ai-context/conventions.md.hbs +20 -8
  115. package/dist/templates/command/create-command.hbs +8 -3
  116. package/dist/templates/command/delete-command.hbs +1 -1
  117. package/dist/templates/command/update-command.hbs +1 -1
  118. package/dist/templates/controller/application-controller.hbs +7 -0
  119. package/dist/templates/controller/controller.hbs +6 -0
  120. package/dist/templates/dto/create-dto.hbs +17 -5
  121. package/dist/templates/dto/filter-dto.hbs +1 -1
  122. package/dist/templates/dto/response-dto.hbs +1 -1
  123. package/dist/templates/entity/entity.hbs +4 -0
  124. package/dist/templates/mapper/mapper.hbs +6 -2
  125. package/dist/templates/orm-entity/orm-entity.hbs +9 -1
  126. package/dist/templates/prisma/prisma-mapper.hbs +6 -0
  127. package/dist/templates/prisma/prisma-repository.hbs +57 -5
  128. package/dist/templates/prisma/prisma-schema.hbs +2 -0
  129. package/dist/templates/query/get-all.query.hbs +10 -2
  130. package/dist/templates/recipes/bounded-json-http/README.md.hbs +36 -0
  131. package/dist/templates/recipes/external-projection-worker/README.md.hbs +157 -0
  132. package/dist/templates/recipes/external-projection-worker/canonical-json.ts.hbs +123 -0
  133. package/dist/templates/recipes/external-projection-worker/enqueuer.ts.hbs +167 -0
  134. package/dist/templates/recipes/external-projection-worker/index.ts.hbs +6 -0
  135. package/dist/templates/recipes/external-projection-worker/migration.ts.hbs +88 -0
  136. package/dist/templates/recipes/external-projection-worker/module.ts.hbs +36 -0
  137. package/dist/templates/recipes/external-projection-worker/store.ts.hbs +354 -0
  138. package/dist/templates/recipes/external-projection-worker/types.ts.hbs +134 -0
  139. package/dist/templates/recipes/external-projection-worker/worker.ts.hbs +296 -0
  140. package/dist/templates/recipes/kafka-consumer/README.md.hbs +50 -0
  141. package/dist/templates/recipes/kafka-consumer/kafka-consumer.ts.hbs +135 -0
  142. package/dist/templates/recipes/kafka-consumer/migration.ts.hbs +17 -0
  143. package/dist/templates/recipes/oauth2-client-credentials/README.md.hbs +40 -0
  144. package/dist/templates/recipes/oauth2-client-credentials/client.spec.ts.hbs +234 -0
  145. package/dist/templates/recipes/oauth2-client-credentials/client.ts.hbs +210 -0
  146. package/dist/templates/recipes/oauth2-client-credentials/index.ts.hbs +2 -0
  147. package/dist/templates/recipes/oauth2-client-credentials/module.ts.hbs +58 -0
  148. package/dist/templates/recipes/oauth2-token-introspection/README.md.hbs +158 -0
  149. package/dist/templates/recipes/oauth2-token-introspection/client.spec.ts.hbs +350 -0
  150. package/dist/templates/recipes/oauth2-token-introspection/client.ts.hbs +241 -0
  151. package/dist/templates/recipes/oauth2-token-introspection/http-client.spec.ts.hbs +411 -0
  152. package/dist/templates/recipes/oauth2-token-introspection/http-client.ts.hbs +343 -0
  153. package/dist/templates/recipes/oauth2-token-introspection/index.ts.hbs +3 -0
  154. package/dist/templates/recipes/oauth2-token-introspection/module.ts.hbs +58 -0
  155. package/dist/templates/recipes/oidc-bff-session/README.md.hbs +96 -0
  156. package/dist/templates/recipes/oidc-bff-session/client.test.ts.hbs +663 -0
  157. package/dist/templates/recipes/oidc-bff-session/client.ts.hbs +635 -0
  158. package/dist/templates/recipes/oidc-bff-session/index.ts.hbs +3 -0
  159. package/dist/templates/recipes/oidc-bff-session/store.integration.test.ts.hbs +480 -0
  160. package/dist/templates/recipes/oidc-bff-session/store.test.ts.hbs +87 -0
  161. package/dist/templates/recipes/oidc-bff-session/store.ts.hbs +549 -0
  162. package/dist/templates/recipes/oidc-bff-session/valkey.test.ts.hbs +158 -0
  163. package/dist/templates/recipes/oidc-bff-session/valkey.ts.hbs +148 -0
  164. package/dist/templates/recipes/oidc-resource-server/README.md.hbs +74 -0
  165. package/dist/templates/recipes/oidc-resource-server/index.ts.hbs +2 -0
  166. package/dist/templates/recipes/oidc-resource-server/module.ts.hbs +51 -0
  167. package/dist/templates/recipes/oidc-resource-server/verifier.spec.ts.hbs +186 -0
  168. package/dist/templates/recipes/oidc-resource-server/verifier.ts.hbs +440 -0
  169. package/dist/templates/recipes/platform-parc-bound-authorization/README.md.hbs +92 -0
  170. package/dist/templates/recipes/platform-parc-bound-authorization/client.ts.hbs +142 -0
  171. package/dist/templates/recipes/platform-service-access-request-context/docs.md.hbs +61 -0
  172. package/dist/templates/recipes/platform-service-access-request-context/env.example.hbs +7 -0
  173. package/dist/templates/recipes/platform-service-access-request-context/index.ts.hbs +7 -0
  174. package/dist/templates/recipes/platform-service-access-request-context/request-context-headers.ts.hbs +61 -0
  175. package/dist/templates/recipes/platform-service-access-request-context/request-context-introspection.parser.ts.hbs +132 -0
  176. package/dist/templates/recipes/platform-service-access-request-context/request-context-introspection.types.ts.hbs +40 -0
  177. package/dist/templates/recipes/platform-service-access-request-context/service-access-request-context.client.ts.hbs +45 -0
  178. package/dist/templates/recipes/platform-service-access-request-context/service-access-request-context.config.ts.hbs +142 -0
  179. package/dist/templates/recipes/platform-service-access-request-context/service-access-request-context.guard.ts.hbs +77 -0
  180. package/dist/templates/recipes/platform-service-access-request-context/service-access-request-context.module.ts.hbs +17 -0
  181. package/dist/templates/repository/repository.hbs +43 -5
  182. package/dist/templates/resolver/graphql-input.hbs +23 -6
  183. package/dist/templates/resolver/resolver.hbs +4 -0
  184. package/dist/templates/service/domain-service.hbs +2 -2
  185. package/dist/templates/test/controller.spec.hbs +5 -1
  186. package/dist/templates/test/prisma-repository.spec.hbs +192 -0
  187. package/dist/templates/test/repository.spec.hbs +41 -2
  188. package/dist/templates/test/usecase.spec.hbs +24 -5
  189. package/dist/templates/usecase/application-usecase.hbs +17 -0
  190. package/dist/templates/usecase/create-usecase.hbs +34 -3
  191. package/dist/templates/usecase/update-usecase.hbs +15 -1
  192. package/dist/utils/config.utils.d.ts +2 -0
  193. package/dist/utils/config.utils.js +14 -2
  194. package/dist/utils/config.utils.js.map +1 -1
  195. package/dist/utils/dependency.utils.d.ts +18 -2
  196. package/dist/utils/dependency.utils.js +144 -15
  197. package/dist/utils/dependency.utils.js.map +1 -1
  198. package/dist/utils/field.utils.d.ts +6 -0
  199. package/dist/utils/field.utils.js +78 -24
  200. package/dist/utils/field.utils.js.map +1 -1
  201. package/dist/utils/field.utils.spec.d.ts +1 -0
  202. package/dist/utils/field.utils.spec.js +38 -0
  203. package/dist/utils/field.utils.spec.js.map +1 -0
  204. package/dist/utils/file.utils.d.ts +39 -3
  205. package/dist/utils/file.utils.js +150 -13
  206. package/dist/utils/file.utils.js.map +1 -1
  207. package/dist/utils/naming.utils.js +5 -4
  208. package/dist/utils/naming.utils.js.map +1 -1
  209. package/dist/utils/static-recipe.utils.d.ts +4 -0
  210. package/dist/utils/static-recipe.utils.js +148 -0
  211. package/dist/utils/static-recipe.utils.js.map +1 -0
  212. package/dist/utils/template-engine.utils.js +83 -68
  213. package/dist/utils/template-engine.utils.js.map +1 -1
  214. package/package.json +20 -11
@@ -3,12 +3,24 @@ import { InjectRepository } from "@nestjs/typeorm";
3
3
  import { Repository, FindOptionsOrder } from "typeorm";
4
4
  import { {{entityNamePascal}}Mapper } from "@modules/{{moduleNameKebab}}/infrastructure/mappers/{{entityNameKebab}}.mapper";
5
5
  import { {{entityNamePascal}}OrmEntity } from "@modules/{{moduleNameKebab}}/infrastructure/orm-entities/{{entityNameKebab}}.orm-entity";
6
- import { {{entityNamePascal}}Entity } from "@modules/{{moduleNameKebab}}/domain/entities/{{entityNameKebab}}.entity";
6
+ import { {{entityNamePascal}}Entity } from "@modules/{{moduleNameKebab}}/application/domain/entities/{{entityNameKebab}}.entity";
7
7
 
8
- export interface PaginationOptions {
8
+ const {{entityNameCamel}}OrderFields = [
9
+ "id",
10
+ {{#each fields}}
11
+ "{{#if this.isRelation}}{{this.camelCase}}{{else}}{{this.snakeCase}}{{/if}}",
12
+ {{/each}}
13
+ "isActive",
14
+ "createdAt",
15
+ "updatedAt",
16
+ ] as const satisfies readonly (keyof {{entityNamePascal}}OrmEntity)[];
17
+
18
+ export type {{entityNamePascal}}OrderField = (typeof {{entityNameCamel}}OrderFields)[number];
19
+
20
+ export interface {{entityNamePascal}}PaginationOptions {
9
21
  skip: number;
10
22
  take: number;
11
- orderBy: string;
23
+ orderBy: {{entityNamePascal}}OrderField;
12
24
  orderDirection: "ASC" | "DESC";
13
25
  }
14
26
 
@@ -20,6 +32,10 @@ export class {{entityNamePascal}}Repository {
20
32
  private readonly mapper: {{entityNamePascal}}Mapper,
21
33
  ) {}
22
34
 
35
+ static isOrderByField(value: string): value is {{entityNamePascal}}OrderField {
36
+ return {{entityNameCamel}}OrderFields.some((field) => field === value);
37
+ }
38
+
23
39
  async create(entity: {{entityNamePascal}}Entity): Promise<{{entityNamePascal}}Entity> {
24
40
  const ormEntity = this.mapper.toOrmEntity(entity);
25
41
  const savedOrmEntity = await this.repository.save(ormEntity);
@@ -28,7 +44,11 @@ export class {{entityNamePascal}}Repository {
28
44
 
29
45
  async findById(id: string): Promise<{{entityNamePascal}}Entity | null> {
30
46
  const ormEntity = await this.repository.findOne({
47
+ {{#if softDelete}}
31
48
  where: { id, deletedAt: null } as any,
49
+ {{else}}
50
+ where: { id },
51
+ {{/if}}
32
52
  });
33
53
 
34
54
  if (!ormEntity) {
@@ -39,15 +59,19 @@ export class {{entityNamePascal}}Repository {
39
59
  }
40
60
 
41
61
  async findAll(): Promise<{{entityNamePascal}}Entity[]> {
62
+ {{#if softDelete}}
42
63
  const ormEntities = await this.repository.find({
43
64
  where: { deletedAt: null } as any,
44
65
  });
66
+ {{else}}
67
+ const ormEntities = await this.repository.find();
68
+ {{/if}}
45
69
 
46
70
  return ormEntities.map((ormEntity) => this.mapper.toDomainEntity(ormEntity));
47
71
  }
48
72
 
49
73
  async findAllPaginated(
50
- options: PaginationOptions,
74
+ options: {{entityNamePascal}}PaginationOptions,
51
75
  ): Promise<[{{entityNamePascal}}Entity[], number]> {
52
76
  const { skip, take, orderBy, orderDirection } = options;
53
77
 
@@ -56,7 +80,9 @@ export class {{entityNamePascal}}Repository {
56
80
  } as FindOptionsOrder<{{entityNamePascal}}OrmEntity>;
57
81
 
58
82
  const [ormEntities, total] = await this.repository.findAndCount({
83
+ {{#if softDelete}}
59
84
  where: { deletedAt: null } as any,
85
+ {{/if}}
60
86
  skip,
61
87
  take,
62
88
  order,
@@ -75,21 +101,33 @@ export class {{entityNamePascal}}Repository {
75
101
  return entity;
76
102
  }
77
103
 
104
+ {{#if deleteEnabled}}
78
105
  async delete(id: string): Promise<void> {
79
- // Soft delete
106
+ {{#if softDelete}}
80
107
  await this.repository.update(id, {
81
108
  deletedAt: new Date(),
82
109
  isActive: false,
83
110
  } as any);
111
+ {{else}}
112
+ await this.repository.delete(id);
113
+ {{/if}}
84
114
  }
85
115
 
116
+ {{#if hardDelete}}
86
117
  async hardDelete(id: string): Promise<void> {
87
118
  await this.repository.delete(id);
88
119
  }
89
120
 
121
+ {{/if}}
122
+
123
+ {{/if}}
90
124
  async exists(id: string): Promise<boolean> {
91
125
  const count = await this.repository.count({
126
+ {{#if softDelete}}
92
127
  where: { id, deletedAt: null } as any,
128
+ {{else}}
129
+ where: { id },
130
+ {{/if}}
93
131
  });
94
132
  return count > 0;
95
133
  }
@@ -1,25 +1,34 @@
1
1
  import { Field, InputType } from "@nestjs/graphql";
2
- import { IsNotEmpty, IsOptional, IsString, IsBoolean } from "class-validator";
2
+ import { IsDecimal, IsNotEmpty, IsOptional, IsString, IsBoolean } from "class-validator";
3
3
 
4
4
  @InputType()
5
5
  export class Create{{entityNamePascal}}Input {
6
- {{#if hasFields}}
7
- {{#each fields}}
6
+ {{#if hasRequestFields}}
7
+ {{#each requestFields}}
8
8
  {{#unless this.isOptional}}
9
9
  @Field()
10
10
  @IsNotEmpty()
11
+ {{#if this.isMoney}}
12
+ @IsDecimal({ decimal_digits: "0,18", force_decimal: false })
13
+ {{/if}}
11
14
  {{this.camelCase}}: {{this.tsType}};
12
15
 
13
16
  {{/unless}}
14
17
  {{/each}}
15
- {{#each fields}}
18
+ {{#each requestFields}}
16
19
  {{#if this.isOptional}}
17
20
  @Field({ nullable: true })
18
21
  @IsOptional()
22
+ {{#if this.isMoney}}
23
+ @IsDecimal({ decimal_digits: "0,18", force_decimal: false })
24
+ {{/if}}
19
25
  {{this.camelCase}}?: {{this.tsType}};
20
26
 
21
27
  {{/if}}
22
28
  {{/each}}
29
+ {{else}}
30
+ {{#if hasFields}}
31
+ // This aggregate has no client-writable scaffold fields.
23
32
  {{else}}
24
33
  // Add your input fields here
25
34
  // Example:
@@ -28,17 +37,24 @@ export class Create{{entityNamePascal}}Input {
28
37
  // @IsString()
29
38
  // name: string;
30
39
  {{/if}}
40
+ {{/if}}
31
41
  }
32
42
 
33
43
  @InputType()
34
44
  export class Update{{entityNamePascal}}Input {
35
- {{#if hasFields}}
36
- {{#each fields}}
45
+ {{#if hasRequestFields}}
46
+ {{#each requestFields}}
37
47
  @Field({ nullable: true })
38
48
  @IsOptional()
49
+ {{#if this.isMoney}}
50
+ @IsDecimal({ decimal_digits: "0,18", force_decimal: false })
51
+ {{/if}}
39
52
  {{this.camelCase}}?: {{this.tsType}};
40
53
 
41
54
  {{/each}}
55
+ {{else}}
56
+ {{#if hasFields}}
57
+ // This aggregate has no client-writable scaffold fields.
42
58
  {{else}}
43
59
  // Add your input fields here
44
60
  // Example:
@@ -46,6 +62,7 @@ export class Update{{entityNamePascal}}Input {
46
62
  // @IsOptional()
47
63
  // @IsString()
48
64
  // name?: string;
65
+ {{/if}}
49
66
  {{/if}}
50
67
  @Field({ nullable: true })
51
68
  @IsOptional()
@@ -2,7 +2,9 @@ import { Args, ID, Mutation, Query, Resolver } from "@nestjs/graphql";
2
2
  import { CommandBus, QueryBus } from "@nestjs/cqrs";
3
3
  import { Create{{entityNamePascal}}Command } from "@modules/{{moduleNameKebab}}/application/commands/create-{{entityNameKebab}}.command";
4
4
  import { Update{{entityNamePascal}}Command } from "@modules/{{moduleNameKebab}}/application/commands/update-{{entityNameKebab}}.command";
5
+ {{#if deleteEnabled}}
5
6
  import { Delete{{entityNamePascal}}Command } from "@modules/{{moduleNameKebab}}/application/commands/delete-{{entityNameKebab}}.command";
7
+ {{/if}}
6
8
  import { Get{{entityNamePascal}}ByIdQuery } from "@modules/{{moduleNameKebab}}/application/queries/get-{{entityNameKebab}}-by-id.query";
7
9
  import { GetAll{{entityNamePluralPascal}}Query } from "@modules/{{moduleNameKebab}}/application/queries/get-all-{{entityNamePluralKebab}}.query";
8
10
  import { Create{{entityNamePascal}}Input } from "./inputs/create-{{entityNameKebab}}.input";
@@ -51,6 +53,7 @@ export class {{entityNamePascal}}Resolver {
51
53
  return this.commandBus.execute(command);
52
54
  }
53
55
 
56
+ {{#if deleteEnabled}}
54
57
  @Mutation(() => Boolean)
55
58
  async delete{{entityNamePascal}}(
56
59
  @Args("id", { type: () => ID }) id: string,
@@ -59,4 +62,5 @@ export class {{entityNamePascal}}Resolver {
59
62
  await this.commandBus.execute(command);
60
63
  return true;
61
64
  }
65
+ {{/if}}
62
66
  }
@@ -1,7 +1,7 @@
1
1
  import { Injectable } from '@nestjs/common';
2
2
 
3
3
  @Injectable()
4
- export class {{entityName}}Service {
4
+ export class {{entityNamePascal}}Service {
5
5
  constructor() {}
6
6
 
7
7
  // Add your domain service methods here
@@ -10,4 +10,4 @@ export class {{entityName}}Service {
10
10
  // // Domain validation logic
11
11
  // return true;
12
12
  // }
13
- }
13
+ }
@@ -1,9 +1,11 @@
1
1
  import { Test, TestingModule } from "@nestjs/testing";
2
2
  import { CommandBus, QueryBus } from "@nestjs/cqrs";
3
- import { {{entityNamePascal}}Controller } from "../{{entityNameKebab}}.controller";
3
+ import { {{entityNamePascal}}Controller } from "./{{entityNameKebab}}.controller";
4
4
  import { Create{{entityNamePascal}}Command } from "@modules/{{moduleNameKebab}}/application/commands/create-{{entityNameKebab}}.command";
5
5
  import { Update{{entityNamePascal}}Command } from "@modules/{{moduleNameKebab}}/application/commands/update-{{entityNameKebab}}.command";
6
+ {{#if deleteEnabled}}
6
7
  import { Delete{{entityNamePascal}}Command } from "@modules/{{moduleNameKebab}}/application/commands/delete-{{entityNameKebab}}.command";
8
+ {{/if}}
7
9
  import { Get{{entityNamePascal}}ByIdQuery } from "@modules/{{moduleNameKebab}}/application/queries/get-{{entityNameKebab}}-by-id.query";
8
10
  import { GetAll{{entityNamePluralPascal}}Query } from "@modules/{{moduleNameKebab}}/application/queries/get-all-{{entityNamePluralKebab}}.query";
9
11
 
@@ -110,6 +112,7 @@ describe("{{entityNamePascal}}Controller", () => {
110
112
  });
111
113
  });
112
114
 
115
+ {{#if deleteEnabled}}
113
116
  describe("delete", () => {
114
117
  it("should delete the {{entityNameCamel}}", async () => {
115
118
  commandBus.execute.mockResolvedValue(undefined);
@@ -121,4 +124,5 @@ describe("{{entityNamePascal}}Controller", () => {
121
124
  );
122
125
  });
123
126
  });
127
+ {{/if}}
124
128
  });
@@ -0,0 +1,192 @@
1
+ import { Test, TestingModule } from "@nestjs/testing";
2
+ import { {{entityNamePascal}} as Prisma{{entityNamePascal}} } from "@prisma/client";
3
+ import { PrismaService } from "@prisma/prisma.service";
4
+ import { {{entityNamePascal}}Repository } from "./{{entityNameKebab}}.repository";
5
+ import { {{entityNamePascal}}Mapper } from "../mappers/{{entityNameKebab}}.mapper";
6
+ import { {{entityNamePascal}}Entity } from "@modules/{{moduleNameKebab}}/application/domain/entities/{{entityNameKebab}}.entity";
7
+
8
+ describe("{{entityNamePascal}}Repository", () => {
9
+ let repository: {{entityNamePascal}}Repository;
10
+ let mapper: jest.Mocked<{{entityNamePascal}}Mapper>;
11
+
12
+ const prismaDelegate = {
13
+ create: jest.fn(),
14
+ findFirst: jest.fn(),
15
+ findMany: jest.fn(),
16
+ count: jest.fn(),
17
+ update: jest.fn(),
18
+ {{#if deleteEnabled}}
19
+ delete: jest.fn(),
20
+ {{/if}}
21
+ };
22
+
23
+ const mockPrismaEntity = {
24
+ id: "test-uuid",
25
+ {{#each fields}}
26
+ {{this.snakeCase}}: undefined as never,
27
+ {{/each}}
28
+ is_active: true,
29
+ created_at: new Date(),
30
+ updated_at: new Date(),
31
+ {{#if softDelete}}
32
+ deleted_at: null,
33
+ {{/if}}
34
+ } as Prisma{{entityNamePascal}};
35
+
36
+ const mockDomainEntity = new {{entityNamePascal}}Entity({
37
+ id: "test-uuid",
38
+ {{#each fields}}
39
+ {{this.camelCase}}: undefined as never,
40
+ {{/each}}
41
+ isActive: true,
42
+ createdAt: new Date(),
43
+ updatedAt: new Date(),
44
+ });
45
+
46
+ beforeEach(async () => {
47
+ const module: TestingModule = await Test.createTestingModule({
48
+ providers: [
49
+ {{entityNamePascal}}Repository,
50
+ {
51
+ provide: PrismaService,
52
+ useValue: {
53
+ {{entityNameCamel}}: prismaDelegate,
54
+ },
55
+ },
56
+ {
57
+ provide: {{entityNamePascal}}Mapper,
58
+ useValue: {
59
+ toPrismaCreate: jest.fn(),
60
+ toPrismaUpdate: jest.fn(),
61
+ toDomainEntity: jest.fn(),
62
+ toResponseDto: jest.fn(),
63
+ },
64
+ },
65
+ ],
66
+ }).compile();
67
+
68
+ repository = module.get<{{entityNamePascal}}Repository>({{entityNamePascal}}Repository);
69
+ mapper = module.get({{entityNamePascal}}Mapper);
70
+ jest.clearAllMocks();
71
+ });
72
+
73
+ describe("create", () => {
74
+ it("should create and return a domain entity", async () => {
75
+ mapper.toPrismaCreate.mockReturnValue({});
76
+ prismaDelegate.create.mockResolvedValue(mockPrismaEntity);
77
+ mapper.toDomainEntity.mockReturnValue(mockDomainEntity);
78
+
79
+ const result = await repository.create(mockDomainEntity);
80
+
81
+ expect(prismaDelegate.create).toHaveBeenCalledWith({ data: {} });
82
+ expect(result).toEqual(mockDomainEntity);
83
+ });
84
+ });
85
+
86
+ describe("findById", () => {
87
+ it("should return a domain entity when found", async () => {
88
+ prismaDelegate.findFirst.mockResolvedValue(mockPrismaEntity);
89
+ mapper.toDomainEntity.mockReturnValue(mockDomainEntity);
90
+
91
+ const result = await repository.findById("test-uuid");
92
+
93
+ expect(prismaDelegate.findFirst).toHaveBeenCalledWith({
94
+ {{#if softDelete}}
95
+ where: { id: "test-uuid", deleted_at: null },
96
+ {{else}}
97
+ where: { id: "test-uuid" },
98
+ {{/if}}
99
+ });
100
+ expect(result).toEqual(mockDomainEntity);
101
+ });
102
+
103
+ it("should return null when the entity is not found", async () => {
104
+ prismaDelegate.findFirst.mockResolvedValue(null);
105
+
106
+ await expect(repository.findById("missing-uuid")).resolves.toBeNull();
107
+ });
108
+ });
109
+
110
+ describe("findAllPaginated", () => {
111
+ it("should return paginated entities with a total count", async () => {
112
+ prismaDelegate.findMany.mockResolvedValue([mockPrismaEntity]);
113
+ prismaDelegate.count.mockResolvedValue(1);
114
+ mapper.toDomainEntity.mockReturnValue(mockDomainEntity);
115
+
116
+ const result = await repository.findAllPaginated({
117
+ skip: 0,
118
+ take: 10,
119
+ orderBy: "created_at",
120
+ orderDirection: "DESC",
121
+ });
122
+
123
+ expect(result[0]).toHaveLength(1);
124
+ expect(result[1]).toBe(1);
125
+ });
126
+ });
127
+
128
+ describe("update", () => {
129
+ it("should update and return a domain entity", async () => {
130
+ mapper.toPrismaUpdate.mockReturnValue({});
131
+ prismaDelegate.update.mockResolvedValue(mockPrismaEntity);
132
+ mapper.toDomainEntity.mockReturnValue(mockDomainEntity);
133
+
134
+ const result = await repository.update("test-uuid", mockDomainEntity);
135
+
136
+ expect(prismaDelegate.update).toHaveBeenCalledWith({
137
+ where: { id: "test-uuid" },
138
+ data: {},
139
+ });
140
+ expect(result).toEqual(mockDomainEntity);
141
+ });
142
+ });
143
+
144
+ {{#if deleteEnabled}}
145
+ describe("delete", () => {
146
+ {{#if softDelete}}
147
+ it("should soft delete the entity", async () => {
148
+ prismaDelegate.update.mockResolvedValue(mockPrismaEntity);
149
+
150
+ await repository.delete("test-uuid");
151
+
152
+ expect(prismaDelegate.update).toHaveBeenCalledWith({
153
+ where: { id: "test-uuid" },
154
+ data: expect.objectContaining({ deleted_at: expect.any(Date) }),
155
+ });
156
+ });
157
+ {{else}}
158
+ it("should delete the entity", async () => {
159
+ prismaDelegate.delete.mockResolvedValue(mockPrismaEntity);
160
+
161
+ await repository.delete("test-uuid");
162
+
163
+ expect(prismaDelegate.delete).toHaveBeenCalledWith({
164
+ where: { id: "test-uuid" },
165
+ });
166
+ });
167
+ {{/if}}
168
+ });
169
+
170
+ {{#if hardDelete}}
171
+ describe("hardDelete", () => {
172
+ it("should permanently delete the entity after explicit opt-in", async () => {
173
+ prismaDelegate.delete.mockResolvedValue(mockPrismaEntity);
174
+
175
+ await repository.hardDelete("test-uuid");
176
+
177
+ expect(prismaDelegate.delete).toHaveBeenCalledWith({
178
+ where: { id: "test-uuid" },
179
+ });
180
+ });
181
+ });
182
+
183
+ {{/if}}
184
+ {{/if}}
185
+ describe("exists", () => {
186
+ it("should return whether the entity exists", async () => {
187
+ prismaDelegate.count.mockResolvedValue(1);
188
+
189
+ await expect(repository.exists("test-uuid")).resolves.toBe(true);
190
+ });
191
+ });
192
+ });
@@ -1,10 +1,10 @@
1
1
  import { Test, TestingModule } from "@nestjs/testing";
2
2
  import { getRepositoryToken } from "@nestjs/typeorm";
3
3
  import { Repository } from "typeorm";
4
- import { {{entityNamePascal}}Repository } from "../{{entityNameKebab}}.repository";
4
+ import { {{entityNamePascal}}Repository } from "./{{entityNameKebab}}.repository";
5
5
  import { {{entityNamePascal}}OrmEntity } from "../orm-entities/{{entityNameKebab}}.orm-entity";
6
6
  import { {{entityNamePascal}}Mapper } from "../mappers/{{entityNameKebab}}.mapper";
7
- import { {{entityNamePascal}}Entity } from "@modules/{{moduleNameKebab}}/domain/entities/{{entityNameKebab}}.entity";
7
+ import { {{entityNamePascal}}Entity } from "@modules/{{moduleNameKebab}}/application/domain/entities/{{entityNameKebab}}.entity";
8
8
 
9
9
  describe("{{entityNamePascal}}Repository", () => {
10
10
  let repository: {{entityNamePascal}}Repository;
@@ -13,14 +13,22 @@ describe("{{entityNamePascal}}Repository", () => {
13
13
 
14
14
  const mockOrmEntity: {{entityNamePascal}}OrmEntity = {
15
15
  id: "test-uuid",
16
+ {{#each fields}}
17
+ {{#if this.isRelation}}{{this.camelCase}}{{else}}{{this.snakeCase}}{{/if}}: undefined as never,
18
+ {{/each}}
16
19
  isActive: true,
17
20
  createdAt: new Date(),
18
21
  updatedAt: new Date(),
22
+ {{#if softDelete}}
19
23
  deletedAt: null,
24
+ {{/if}}
20
25
  };
21
26
 
22
27
  const mockDomainEntity = new {{entityNamePascal}}Entity({
23
28
  id: "test-uuid",
29
+ {{#each fields}}
30
+ {{this.camelCase}}: undefined as never,
31
+ {{/each}}
24
32
  isActive: true,
25
33
  createdAt: new Date(),
26
34
  updatedAt: new Date(),
@@ -38,7 +46,9 @@ describe("{{entityNamePascal}}Repository", () => {
38
46
  find: jest.fn(),
39
47
  findAndCount: jest.fn(),
40
48
  update: jest.fn(),
49
+ {{#if deleteEnabled}}
41
50
  delete: jest.fn(),
51
+ {{/if}}
42
52
  count: jest.fn(),
43
53
  },
44
54
  },
@@ -81,7 +91,11 @@ describe("{{entityNamePascal}}Repository", () => {
81
91
  const result = await repository.findById("test-uuid");
82
92
 
83
93
  expect(typeOrmRepo.findOne).toHaveBeenCalledWith({
94
+ {{#if softDelete}}
84
95
  where: { id: "test-uuid", deletedAt: null },
96
+ {{else}}
97
+ where: { id: "test-uuid" },
98
+ {{/if}}
85
99
  });
86
100
  expect(result).toEqual(mockDomainEntity);
87
101
  });
@@ -125,7 +139,9 @@ describe("{{entityNamePascal}}Repository", () => {
125
139
  });
126
140
  });
127
141
 
142
+ {{#if deleteEnabled}}
128
143
  describe("delete", () => {
144
+ {{#if softDelete}}
129
145
  it("should soft delete the entity", async () => {
130
146
  typeOrmRepo.update.mockResolvedValue({ affected: 1 } as any);
131
147
 
@@ -136,8 +152,31 @@ describe("{{entityNamePascal}}Repository", () => {
136
152
  expect.objectContaining({ deletedAt: expect.any(Date) })
137
153
  );
138
154
  });
155
+ {{else}}
156
+ it("should delete the entity", async () => {
157
+ typeOrmRepo.delete.mockResolvedValue({ affected: 1 } as any);
158
+
159
+ await repository.delete("test-uuid");
160
+
161
+ expect(typeOrmRepo.delete).toHaveBeenCalledWith("test-uuid");
162
+ });
163
+ {{/if}}
164
+ });
165
+
166
+ {{#if hardDelete}}
167
+ describe("hardDelete", () => {
168
+ it("should permanently delete the entity after explicit opt-in", async () => {
169
+ typeOrmRepo.delete.mockResolvedValue({ affected: 1 } as any);
170
+
171
+ await repository.hardDelete("test-uuid");
172
+
173
+ expect(typeOrmRepo.delete).toHaveBeenCalledWith("test-uuid");
174
+ });
139
175
  });
140
176
 
177
+ {{/if}}
178
+
179
+ {{/if}}
141
180
  describe("exists", () => {
142
181
  it("should return true when entity exists", async () => {
143
182
  typeOrmRepo.count.mockResolvedValue(1);
@@ -1,16 +1,20 @@
1
1
  import { Test, TestingModule } from "@nestjs/testing";
2
2
  import { NotFoundException } from "@nestjs/common";
3
- import { Create{{entityNamePascal}}UseCase } from "../create-{{entityNameKebab}}.use-case";
4
- import { Update{{entityNamePascal}}UseCase } from "../update-{{entityNameKebab}}.use-case";
5
- import { Delete{{entityNamePascal}}UseCase } from "../delete-{{entityNameKebab}}.use-case";
3
+ import { Create{{entityNamePascal}}UseCase } from "./create-{{entityNameKebab}}.use-case";
4
+ import { Update{{entityNamePascal}}UseCase } from "./update-{{entityNameKebab}}.use-case";
5
+ {{#if deleteEnabled}}
6
+ import { Delete{{entityNamePascal}}UseCase } from "./delete-{{entityNameKebab}}.use-case";
7
+ {{/if}}
6
8
  import { {{entityNamePascal}}Repository } from "@modules/{{moduleNameKebab}}/infrastructure/repositories/{{entityNameKebab}}.repository";
7
9
  import { {{entityNamePascal}}Mapper } from "@modules/{{moduleNameKebab}}/infrastructure/mappers/{{entityNameKebab}}.mapper";
8
- import { {{entityNamePascal}}Entity } from "@modules/{{moduleNameKebab}}/domain/entities/{{entityNameKebab}}.entity";
10
+ import { {{entityNamePascal}}Entity } from "@modules/{{moduleNameKebab}}/application/domain/entities/{{entityNameKebab}}.entity";
9
11
 
10
12
  describe("{{entityNamePascal}} Use Cases", () => {
11
13
  let createUseCase: Create{{entityNamePascal}}UseCase;
12
14
  let updateUseCase: Update{{entityNamePascal}}UseCase;
15
+ {{#if deleteEnabled}}
13
16
  let deleteUseCase: Delete{{entityNamePascal}}UseCase;
17
+ {{/if}}
14
18
  let repository: jest.Mocked<{{entityNamePascal}}Repository>;
15
19
  let mapper: jest.Mocked<{{entityNamePascal}}Mapper>;
16
20
 
@@ -33,14 +37,18 @@ describe("{{entityNamePascal}} Use Cases", () => {
33
37
  providers: [
34
38
  Create{{entityNamePascal}}UseCase,
35
39
  Update{{entityNamePascal}}UseCase,
40
+ {{#if deleteEnabled}}
36
41
  Delete{{entityNamePascal}}UseCase,
42
+ {{/if}}
37
43
  {
38
44
  provide: {{entityNamePascal}}Repository,
39
45
  useValue: {
40
46
  create: jest.fn(),
41
47
  findById: jest.fn(),
42
48
  update: jest.fn(),
49
+ {{#if deleteEnabled}}
43
50
  delete: jest.fn(),
51
+ {{/if}}
44
52
  },
45
53
  },
46
54
  {
@@ -54,7 +62,9 @@ describe("{{entityNamePascal}} Use Cases", () => {
54
62
 
55
63
  createUseCase = module.get<Create{{entityNamePascal}}UseCase>(Create{{entityNamePascal}}UseCase);
56
64
  updateUseCase = module.get<Update{{entityNamePascal}}UseCase>(Update{{entityNamePascal}}UseCase);
65
+ {{#if deleteEnabled}}
57
66
  deleteUseCase = module.get<Delete{{entityNamePascal}}UseCase>(Delete{{entityNamePascal}}UseCase);
67
+ {{/if}}
58
68
  repository = module.get({{entityNamePascal}}Repository);
59
69
  mapper = module.get({{entityNamePascal}}Mapper);
60
70
  });
@@ -65,7 +75,14 @@ describe("{{entityNamePascal}} Use Cases", () => {
65
75
  repository.create.mockResolvedValue(mockEntity);
66
76
  mapper.toResponseDto.mockReturnValue(mockResponseDto as any);
67
77
 
68
- const result = await createUseCase.execute(createDto as any);
78
+ const result = await createUseCase.execute(
79
+ createDto as any{{#if hasServerOwnedFields}},
80
+ {
81
+ {{#each serverOwnedFields}}
82
+ {{this.camelCase}}: undefined as never,
83
+ {{/each}}
84
+ }{{/if}},
85
+ );
69
86
 
70
87
  expect(repository.create).toHaveBeenCalled();
71
88
  expect(mapper.toResponseDto).toHaveBeenCalledWith(mockEntity);
@@ -96,6 +113,7 @@ describe("{{entityNamePascal}} Use Cases", () => {
96
113
  });
97
114
  });
98
115
 
116
+ {{#if deleteEnabled}}
99
117
  describe("Delete{{entityNamePascal}}UseCase", () => {
100
118
  it("should delete an existing {{entityNameCamel}}", async () => {
101
119
  repository.findById.mockResolvedValue(mockEntity);
@@ -113,4 +131,5 @@ describe("{{entityNamePascal}} Use Cases", () => {
113
131
  await expect(deleteUseCase.execute("non-existent")).rejects.toThrow(NotFoundException);
114
132
  });
115
133
  });
134
+ {{/if}}
116
135
  });
@@ -0,0 +1,17 @@
1
+ import { Injectable } from "@nestjs/common";
2
+
3
+ export interface {{entityNamePascal}}Input {
4
+ // Define validated application input.
5
+ }
6
+
7
+ export interface {{entityNamePascal}}Result {
8
+ // Define the use-case result contract.
9
+ }
10
+
11
+ @Injectable()
12
+ export class {{entityNamePascal}}UseCase {
13
+ async execute(input: {{entityNamePascal}}Input): Promise<{{entityNamePascal}}Result> {
14
+ void input;
15
+ throw new Error("{{entityNamePascal}}UseCase is not implemented");
16
+ }
17
+ }