create-easy-stack 1.0.3

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 (327) hide show
  1. package/bin/create.mjs +244 -0
  2. package/infra/email/files/src/core/config/email.env.ts +73 -0
  3. package/infra/email/files/src/core/infrastructure/email/email-dispatcher.port.ts +13 -0
  4. package/infra/email/files/src/core/infrastructure/email/email-layout.spec.ts +83 -0
  5. package/infra/email/files/src/core/infrastructure/email/email-layout.ts +88 -0
  6. package/infra/email/files/src/core/infrastructure/email/email-sender.factory.spec.ts +87 -0
  7. package/infra/email/files/src/core/infrastructure/email/email-sender.factory.ts +35 -0
  8. package/infra/email/files/src/core/infrastructure/email/email-sender.port.ts +19 -0
  9. package/infra/email/files/src/core/infrastructure/email/email.job.spec.ts +101 -0
  10. package/infra/email/files/src/core/infrastructure/email/email.job.ts +64 -0
  11. package/infra/email/files/src/core/infrastructure/email/email.module.spec.ts +60 -0
  12. package/infra/email/files/src/core/infrastructure/email/email.module.ts +34 -0
  13. package/infra/email/files/src/core/infrastructure/email/log-email-sender.spec.ts +34 -0
  14. package/infra/email/files/src/core/infrastructure/email/log-email-sender.ts +17 -0
  15. package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.spec.ts +99 -0
  16. package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.ts +63 -0
  17. package/infra/email/infra.json +58 -0
  18. package/infra/prisma-postgres/docker.service.yml +12 -0
  19. package/infra/prisma-postgres/files/prisma/schema.prisma +10 -0
  20. package/infra/prisma-postgres/files/prisma.config.mjs +10 -0
  21. package/infra/prisma-postgres/files/src/core/config/database.env.ts +13 -0
  22. package/infra/prisma-postgres/files/src/core/infrastructure/database/database.module.ts +9 -0
  23. package/infra/prisma-postgres/files/src/core/infrastructure/database/prisma.service.ts +26 -0
  24. package/infra/prisma-postgres/infra.json +60 -0
  25. package/infra/rabbitmq/docker.service.yml +16 -0
  26. package/infra/rabbitmq/files/src/core/config/messaging.env.ts +29 -0
  27. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.spec.ts +60 -0
  28. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.ts +33 -0
  29. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.spec.ts +23 -0
  30. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.ts +19 -0
  31. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.spec.ts +80 -0
  32. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.ts +66 -0
  33. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.boundary.spec.ts +34 -0
  34. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.factory.ts +21 -0
  35. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.spec.ts +75 -0
  36. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.ts +30 -0
  37. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.port.ts +59 -0
  38. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.spec.ts +510 -0
  39. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.ts +353 -0
  40. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.module.ts +9 -0
  41. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.spec.ts +289 -0
  42. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.ts +207 -0
  43. package/infra/rabbitmq/infra.json +55 -0
  44. package/infra/redis/docker.service.yml +10 -0
  45. package/infra/redis/files/src/core/http/decorators/rate-limit.decorator.ts +26 -0
  46. package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.spec.ts +180 -0
  47. package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.ts +105 -0
  48. package/infra/redis/files/src/core/infrastructure/redis/cache.constants.ts +67 -0
  49. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.module.ts +9 -0
  50. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.spec.ts +146 -0
  51. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.ts +131 -0
  52. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.module.ts +9 -0
  53. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.integration-spec.ts +111 -0
  54. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.ts +82 -0
  55. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.module.ts +15 -0
  56. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.spec.ts +45 -0
  57. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.ts +38 -0
  58. package/infra/redis/files/src/core/infrastructure/redis/redis.module.ts +9 -0
  59. package/infra/redis/files/src/core/infrastructure/redis/redis.service.ts +196 -0
  60. package/infra/redis/infra.json +53 -0
  61. package/infra/storage-cloudinary/files/src/core/config/storage.env.ts +43 -0
  62. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.spec.ts +275 -0
  63. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.ts +158 -0
  64. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.factory.ts +30 -0
  65. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.module.ts +17 -0
  66. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.port.ts +38 -0
  67. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.spec.ts +48 -0
  68. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.ts +25 -0
  69. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/unconfigured-image-storage.ts +19 -0
  70. package/infra/storage-cloudinary/infra.json +73 -0
  71. package/lib/fs-utils.mjs +115 -0
  72. package/lib/registry.mjs +50 -0
  73. package/lib/scaffold.mjs +157 -0
  74. package/package.json +37 -0
  75. package/templates/nest-ddd/files/.env.example +5 -0
  76. package/templates/nest-ddd/files/.husky/commit-msg +1 -0
  77. package/templates/nest-ddd/files/.husky/pre-commit +1 -0
  78. package/templates/nest-ddd/files/.oxlintrc.json +10 -0
  79. package/templates/nest-ddd/files/.prettierrc +4 -0
  80. package/templates/nest-ddd/files/_gitignore +40 -0
  81. package/templates/nest-ddd/files/commitlint.config.js +3 -0
  82. package/templates/nest-ddd/files/nest-cli.json +8 -0
  83. package/templates/nest-ddd/files/package.json +66 -0
  84. package/templates/nest-ddd/files/plop-templates/command/command.ts.hbs +5 -0
  85. package/templates/nest-ddd/files/plop-templates/command/handler.ts.hbs +30 -0
  86. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.command.ts.hbs +5 -0
  87. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.spec.ts.hbs +35 -0
  88. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.ts.hbs +25 -0
  89. package/templates/nest-ddd/files/plop-templates/module/application/index.ts.hbs +5 -0
  90. package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.handler.ts.hbs +29 -0
  91. package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.query.ts.hbs +5 -0
  92. package/templates/nest-ddd/files/plop-templates/module/domain/entities/{{kebab}}.entity.ts.hbs +37 -0
  93. package/templates/nest-ddd/files/plop-templates/module/domain/events/{{kebab}}-created.event.ts.hbs +10 -0
  94. package/templates/nest-ddd/files/plop-templates/module/domain/exceptions/{{kebab}}.exception.ts.hbs +11 -0
  95. package/templates/nest-ddd/files/plop-templates/module/domain/index.ts.hbs +4 -0
  96. package/templates/nest-ddd/files/plop-templates/module/domain/repositories/{{kebab}}.repository.ts.hbs +7 -0
  97. package/templates/nest-ddd/files/plop-templates/module/presentation/controllers/{{kebab}}.controller.ts.hbs +53 -0
  98. package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/create-{{kebab}}.dto.ts.hbs +10 -0
  99. package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/{{kebab}}-response.dto.ts.hbs +15 -0
  100. package/templates/nest-ddd/files/plop-templates/module/presentation/index.ts.hbs +4 -0
  101. package/templates/nest-ddd/files/plop-templates/module/presentation/mappers/{{kebab}}.mapper.ts.hbs +13 -0
  102. package/templates/nest-ddd/files/plop-templates/module/{{kebab}}.module.ts.hbs +40 -0
  103. package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/database/repositories/in-memory-{{kebab}}.repository.ts.hbs +17 -0
  104. package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/index.ts.hbs +1 -0
  105. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/mappers/{{kebab}}.mapper.ts.hbs +29 -0
  106. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/repositories/prisma-{{kebab}}.repository.ts.hbs +28 -0
  107. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/index.ts.hbs +2 -0
  108. package/templates/nest-ddd/files/plop-templates/query/handler.ts.hbs +28 -0
  109. package/templates/nest-ddd/files/plop-templates/query/query.ts.hbs +5 -0
  110. package/templates/nest-ddd/files/plop-templates/snippets/error-codes.hbs +14 -0
  111. package/templates/nest-ddd/files/plop-templates/snippets/prisma-model.hbs +7 -0
  112. package/templates/nest-ddd/files/plopfile.mjs +186 -0
  113. package/templates/nest-ddd/files/src/app.controller.ts +13 -0
  114. package/templates/nest-ddd/files/src/app.module.ts +26 -0
  115. package/templates/nest-ddd/files/src/core/config/app.config.ts +9 -0
  116. package/templates/nest-ddd/files/src/core/config/env.validation.ts +30 -0
  117. package/templates/nest-ddd/files/src/core/domain/base/aggregate-root.base.ts +18 -0
  118. package/templates/nest-ddd/files/src/core/domain/base/domain-event.base.ts +7 -0
  119. package/templates/nest-ddd/files/src/core/domain/base/entity.base.ts +36 -0
  120. package/templates/nest-ddd/files/src/core/domain/base/value-object.base.ts +17 -0
  121. package/templates/nest-ddd/files/src/core/domain/exceptions/domain.exception.ts +16 -0
  122. package/templates/nest-ddd/files/src/core/domain/exceptions/error-codes.ts +28 -0
  123. package/templates/nest-ddd/files/src/core/domain/types/pagination.ts +9 -0
  124. package/templates/nest-ddd/files/src/core/http/decorators/client-info.decorator.ts +24 -0
  125. package/templates/nest-ddd/files/src/core/http/decorators/current-user.decorator.ts +19 -0
  126. package/templates/nest-ddd/files/src/core/http/dtos/paginated-response.dto.ts +29 -0
  127. package/templates/nest-ddd/files/src/core/http/dtos/pagination-query.dto.ts +17 -0
  128. package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.spec.ts +163 -0
  129. package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.ts +97 -0
  130. package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.spec.ts +47 -0
  131. package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.ts +31 -0
  132. package/templates/nest-ddd/files/src/core/http/interceptors/logging.interceptor.ts +37 -0
  133. package/templates/nest-ddd/files/src/core/http/interceptors/response.interceptor.ts +53 -0
  134. package/templates/nest-ddd/files/src/core/http/logging/app.logger.ts +34 -0
  135. package/templates/nest-ddd/files/src/core/index.ts +33 -0
  136. package/templates/nest-ddd/files/src/core/utils/correlation-id.util.spec.ts +29 -0
  137. package/templates/nest-ddd/files/src/core/utils/correlation-id.util.ts +15 -0
  138. package/templates/nest-ddd/files/src/core/utils/hash.util.spec.ts +61 -0
  139. package/templates/nest-ddd/files/src/core/utils/hash.util.ts +47 -0
  140. package/templates/nest-ddd/files/src/core/utils/snowflake.util.spec.ts +40 -0
  141. package/templates/nest-ddd/files/src/core/utils/snowflake.util.ts +99 -0
  142. package/templates/nest-ddd/files/src/core/utils/string.util.spec.ts +20 -0
  143. package/templates/nest-ddd/files/src/core/utils/string.util.ts +21 -0
  144. package/templates/nest-ddd/files/src/core/utils/time.util.spec.ts +25 -0
  145. package/templates/nest-ddd/files/src/core/utils/time.util.ts +25 -0
  146. package/templates/nest-ddd/files/src/core/utils/ttl.util.ts +22 -0
  147. package/templates/nest-ddd/files/src/main.ts +61 -0
  148. package/templates/nest-ddd/files/test/setup.ts +6 -0
  149. package/templates/nest-ddd/files/tsconfig.build.json +9 -0
  150. package/templates/nest-ddd/files/tsconfig.json +28 -0
  151. package/templates/nest-ddd/files/vitest.config.e2e.ts +15 -0
  152. package/templates/nest-ddd/files/vitest.config.ts +16 -0
  153. package/templates/nest-ddd/template.json +8 -0
  154. package/templates/node-express/files/.env.example +28 -0
  155. package/templates/node-express/files/docker-compose.yml +52 -0
  156. package/templates/node-express/files/package-lock.json +11880 -0
  157. package/templates/node-express/files/package.json +43 -0
  158. package/templates/node-express/files/server.js +13 -0
  159. package/templates/node-express/files/src/app.js +72 -0
  160. package/templates/node-express/files/src/auth/authUtils.js +116 -0
  161. package/templates/node-express/files/src/auth/checkAuth.js +61 -0
  162. package/templates/node-express/files/src/bucket_pattern/comment.model.js +27 -0
  163. package/templates/node-express/files/src/bucket_pattern/comment.service.js +50 -0
  164. package/templates/node-express/files/src/config/config.aws-s3.js +28 -0
  165. package/templates/node-express/files/src/config/config.cloudinary.js +13 -0
  166. package/templates/node-express/files/src/config/config.mongodb.js +25 -0
  167. package/templates/node-express/files/src/config/config.multer.js +25 -0
  168. package/templates/node-express/files/src/config/config.nodemailer.js +16 -0
  169. package/templates/node-express/files/src/controllers/access.controller.js +49 -0
  170. package/templates/node-express/files/src/controllers/cart.controller.js +38 -0
  171. package/templates/node-express/files/src/controllers/checkout.controller.js +15 -0
  172. package/templates/node-express/files/src/controllers/comment.controller.js +29 -0
  173. package/templates/node-express/files/src/controllers/discount.controller.js +68 -0
  174. package/templates/node-express/files/src/controllers/email.controller.js +16 -0
  175. package/templates/node-express/files/src/controllers/inventory.controller.js +15 -0
  176. package/templates/node-express/files/src/controllers/notification.controller.js +15 -0
  177. package/templates/node-express/files/src/controllers/product.controller.js +175 -0
  178. package/templates/node-express/files/src/controllers/profile.controller.js +65 -0
  179. package/templates/node-express/files/src/controllers/rbac.controller.js +41 -0
  180. package/templates/node-express/files/src/controllers/template.controller.js +20 -0
  181. package/templates/node-express/files/src/controllers/upload.controller.js +56 -0
  182. package/templates/node-express/files/src/controllers/user.controller.js +36 -0
  183. package/templates/node-express/files/src/core/error.response.js +84 -0
  184. package/templates/node-express/files/src/core/success.response.js +44 -0
  185. package/templates/node-express/files/src/database/init.mongodb.level0.js +16 -0
  186. package/templates/node-express/files/src/database/init.mongodb.level1.js +36 -0
  187. package/templates/node-express/files/src/database/init.redis.js +119 -0
  188. package/templates/node-express/files/src/helpers/asyncHandler.js +7 -0
  189. package/templates/node-express/files/src/helpers/check.connect.js +29 -0
  190. package/templates/node-express/files/src/keys/private_key.pem +28 -0
  191. package/templates/node-express/files/src/keys/public_key.pem +9 -0
  192. package/templates/node-express/files/src/logger/discord.log.js +26 -0
  193. package/templates/node-express/files/src/logger/discord.log.v2.js +56 -0
  194. package/templates/node-express/files/src/logger/mylogger.log.js +130 -0
  195. package/templates/node-express/files/src/logger/winston.log.js +27 -0
  196. package/templates/node-express/files/src/middlewares/index.js +21 -0
  197. package/templates/node-express/files/src/middlewares/rbac.js +37 -0
  198. package/templates/node-express/files/src/middlewares/role.middleware.js +12 -0
  199. package/templates/node-express/files/src/models/KeyToken.model.js +37 -0
  200. package/templates/node-express/files/src/models/apiKey.model.js +31 -0
  201. package/templates/node-express/files/src/models/cart.model.js +47 -0
  202. package/templates/node-express/files/src/models/comment.model.js +43 -0
  203. package/templates/node-express/files/src/models/discount.model.js +92 -0
  204. package/templates/node-express/files/src/models/inventory.model.js +42 -0
  205. package/templates/node-express/files/src/models/notification.model.js +52 -0
  206. package/templates/node-express/files/src/models/order.model.js +68 -0
  207. package/templates/node-express/files/src/models/otp.model.js +32 -0
  208. package/templates/node-express/files/src/models/product.model.js +144 -0
  209. package/templates/node-express/files/src/models/repository/cart.repo.js +71 -0
  210. package/templates/node-express/files/src/models/repository/discount.repo.js +63 -0
  211. package/templates/node-express/files/src/models/repository/inventory.repo.js +45 -0
  212. package/templates/node-express/files/src/models/repository/product.repo.js +145 -0
  213. package/templates/node-express/files/src/models/repository/shop.repo.js +18 -0
  214. package/templates/node-express/files/src/models/repository/spu.repo.js +18 -0
  215. package/templates/node-express/files/src/models/repository/user.repo.js +20 -0
  216. package/templates/node-express/files/src/models/resource.model.js +32 -0
  217. package/templates/node-express/files/src/models/role.model.js +57 -0
  218. package/templates/node-express/files/src/models/shop.model.js +43 -0
  219. package/templates/node-express/files/src/models/sku.model.js +81 -0
  220. package/templates/node-express/files/src/models/spu.model.js +101 -0
  221. package/templates/node-express/files/src/models/template.model.js +25 -0
  222. package/templates/node-express/files/src/models/user.model.js +82 -0
  223. package/templates/node-express/files/src/postman/access.post.http +22 -0
  224. package/templates/node-express/files/src/routes/access/index.route.js +21 -0
  225. package/templates/node-express/files/src/routes/cart/index.route.js +20 -0
  226. package/templates/node-express/files/src/routes/checkout/index.route.js +10 -0
  227. package/templates/node-express/files/src/routes/comment/index.route.js +16 -0
  228. package/templates/node-express/files/src/routes/discount/index.route.js +26 -0
  229. package/templates/node-express/files/src/routes/index.route.js +27 -0
  230. package/templates/node-express/files/src/routes/inventory/index.route.js +11 -0
  231. package/templates/node-express/files/src/routes/notification/index.route.js +14 -0
  232. package/templates/node-express/files/src/routes/product/index.route.js +38 -0
  233. package/templates/node-express/files/src/routes/profile/index.route.js +15 -0
  234. package/templates/node-express/files/src/routes/rbac/index.route.js +21 -0
  235. package/templates/node-express/files/src/routes/template/index.route.js +11 -0
  236. package/templates/node-express/files/src/routes/upload/index.route.js +28 -0
  237. package/templates/node-express/files/src/routes/user/index.route.js +13 -0
  238. package/templates/node-express/files/src/services/access.service.js +257 -0
  239. package/templates/node-express/files/src/services/apiKey.service.js +15 -0
  240. package/templates/node-express/files/src/services/cart.service.js +115 -0
  241. package/templates/node-express/files/src/services/checkout.service.js +218 -0
  242. package/templates/node-express/files/src/services/comment.service.js +185 -0
  243. package/templates/node-express/files/src/services/discount.service.js +282 -0
  244. package/templates/node-express/files/src/services/email.service.js +71 -0
  245. package/templates/node-express/files/src/services/inventory.service.js +35 -0
  246. package/templates/node-express/files/src/services/keyToken.service.js +77 -0
  247. package/templates/node-express/files/src/services/notification.service.js +58 -0
  248. package/templates/node-express/files/src/services/otp.service.js +36 -0
  249. package/templates/node-express/files/src/services/product.service.js +228 -0
  250. package/templates/node-express/files/src/services/rbac.service.js +143 -0
  251. package/templates/node-express/files/src/services/redis.service.js +57 -0
  252. package/templates/node-express/files/src/services/redisPubSub.service.js +72 -0
  253. package/templates/node-express/files/src/services/shop.service.js +21 -0
  254. package/templates/node-express/files/src/services/sku.service.js +52 -0
  255. package/templates/node-express/files/src/services/spu.service.js +89 -0
  256. package/templates/node-express/files/src/services/template.service.js +41 -0
  257. package/templates/node-express/files/src/services/upload.s3.service.js +55 -0
  258. package/templates/node-express/files/src/services/upload.service.js +74 -0
  259. package/templates/node-express/files/src/services/user.service.js +92 -0
  260. package/templates/node-express/files/src/temp/1734420604996-ci-cd.png +0 -0
  261. package/templates/node-express/files/src/temp/1734421222213-cover-fb.jpg +0 -0
  262. package/templates/node-express/files/src/temp/1734421222214-cover-fb-01.jpg +0 -0
  263. package/templates/node-express/files/src/temp/1734421293437-cover-fb-01.jpg +0 -0
  264. package/templates/node-express/files/src/temp/1734421293437-cover-fb.jpg +0 -0
  265. package/templates/node-express/files/src/tests/inventory.test.js +21 -0
  266. package/templates/node-express/files/src/tests/message_queue/kafka/consumer.js +23 -0
  267. package/templates/node-express/files/src/tests/message_queue/kafka/producer.js +21 -0
  268. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/consumer.js +28 -0
  269. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.consumer.js +26 -0
  270. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.producer.js +31 -0
  271. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producer.js +26 -0
  272. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producerDLX.js +44 -0
  273. package/templates/node-express/files/src/tests/mysql/connect.js +59 -0
  274. package/templates/node-express/files/src/tests/product.test.js +14 -0
  275. package/templates/node-express/files/src/utils/index.js +78 -0
  276. package/templates/node-express/files/src/utils/reasonPhrases.js +344 -0
  277. package/templates/node-express/files/src/utils/statusCodes.js +344 -0
  278. package/templates/node-express/files/src/utils/template.html.js +38 -0
  279. package/templates/node-express/template.json +11 -0
  280. package/templates/react-fsd/files/index.html +13 -0
  281. package/templates/react-fsd/files/package.json +15 -0
  282. package/templates/react-fsd/files/public/favicon.svg +1 -0
  283. package/templates/react-fsd/files/public/icons.svg +24 -0
  284. package/templates/react-fsd/files/src/assets/hero.png +0 -0
  285. package/templates/react-fsd/files/src/assets/typescript.svg +1 -0
  286. package/templates/react-fsd/files/src/assets/vite.svg +1 -0
  287. package/templates/react-fsd/files/src/counter.ts +9 -0
  288. package/templates/react-fsd/files/src/main.ts +60 -0
  289. package/templates/react-fsd/files/src/style.css +296 -0
  290. package/templates/react-fsd/files/tsconfig.json +24 -0
  291. package/templates/react-fsd/template.json +11 -0
  292. package/templates/react-monolithic/files/index.html +13 -0
  293. package/templates/react-monolithic/files/package.json +15 -0
  294. package/templates/react-monolithic/files/public/favicon.svg +1 -0
  295. package/templates/react-monolithic/files/public/icons.svg +24 -0
  296. package/templates/react-monolithic/files/src/assets/hero.png +0 -0
  297. package/templates/react-monolithic/files/src/assets/typescript.svg +1 -0
  298. package/templates/react-monolithic/files/src/assets/vite.svg +1 -0
  299. package/templates/react-monolithic/files/src/counter.ts +9 -0
  300. package/templates/react-monolithic/files/src/main.ts +60 -0
  301. package/templates/react-monolithic/files/src/style.css +296 -0
  302. package/templates/react-monolithic/files/tsconfig.json +24 -0
  303. package/templates/react-monolithic/template.json +11 -0
  304. package/templates/vue-fsd/files/index.html +13 -0
  305. package/templates/vue-fsd/files/package.json +15 -0
  306. package/templates/vue-fsd/files/public/favicon.svg +1 -0
  307. package/templates/vue-fsd/files/public/icons.svg +24 -0
  308. package/templates/vue-fsd/files/src/assets/hero.png +0 -0
  309. package/templates/vue-fsd/files/src/assets/typescript.svg +1 -0
  310. package/templates/vue-fsd/files/src/assets/vite.svg +1 -0
  311. package/templates/vue-fsd/files/src/counter.ts +9 -0
  312. package/templates/vue-fsd/files/src/main.ts +60 -0
  313. package/templates/vue-fsd/files/src/style.css +296 -0
  314. package/templates/vue-fsd/files/tsconfig.json +24 -0
  315. package/templates/vue-fsd/template.json +11 -0
  316. package/templates/vue-monolithic/files/index.html +13 -0
  317. package/templates/vue-monolithic/files/package.json +15 -0
  318. package/templates/vue-monolithic/files/public/favicon.svg +1 -0
  319. package/templates/vue-monolithic/files/public/icons.svg +24 -0
  320. package/templates/vue-monolithic/files/src/assets/hero.png +0 -0
  321. package/templates/vue-monolithic/files/src/assets/typescript.svg +1 -0
  322. package/templates/vue-monolithic/files/src/assets/vite.svg +1 -0
  323. package/templates/vue-monolithic/files/src/counter.ts +9 -0
  324. package/templates/vue-monolithic/files/src/main.ts +60 -0
  325. package/templates/vue-monolithic/files/src/style.css +296 -0
  326. package/templates/vue-monolithic/files/tsconfig.json +24 -0
  327. package/templates/vue-monolithic/template.json +11 -0
@@ -0,0 +1,157 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import {
5
+ appendLines,
6
+ copyTemplateDir,
7
+ injectAtMarker,
8
+ mergePackageJson,
9
+ readJson,
10
+ writeJson,
11
+ } from './fs-utils.mjs';
12
+ import { resolveInfra } from './registry.mjs';
13
+
14
+ export const LOCK_FILE = '.my-stack.json';
15
+
16
+ const COMPOSE_SKELETON = `services:
17
+ # @infra:services
18
+
19
+ volumes:
20
+ # @infra:volumes
21
+ `;
22
+
23
+ /** Names usable inside templates: {{PROJECT_NAME}}, {{PROJECT_NAME_PASCAL}}, {{PROJECT_NAME_SNAKE}}. */
24
+ export function buildVars(projectName) {
25
+ const words = projectName.split(/[^a-zA-Z0-9]+/).filter(Boolean);
26
+ const lower = words.map((word) => word.toLowerCase());
27
+ return {
28
+ PROJECT_NAME: projectName,
29
+ PROJECT_NAME_PASCAL: lower.map((w) => w[0].toUpperCase() + w.slice(1)).join(''),
30
+ PROJECT_NAME_SNAKE: lower.join('_'),
31
+ };
32
+ }
33
+
34
+ export function isValidProjectName(name) {
35
+ return /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
36
+ }
37
+
38
+ function run(command, args, cwd) {
39
+ const result = spawnSync(command, args, {
40
+ cwd,
41
+ stdio: 'inherit',
42
+ shell: process.platform === 'win32',
43
+ });
44
+ return result.status === 0;
45
+ }
46
+
47
+ /** Applies one infra module (files, deps, env, docker service, injections) onto a project. */
48
+ function applyInfra(projectDir, infra, vars, log) {
49
+ const filesDir = path.join(infra.dir, 'files');
50
+ if (fs.existsSync(filesDir)) {
51
+ const { skipped } = copyTemplateDir(filesDir, projectDir, vars, { overwrite: false });
52
+ for (const file of skipped) log(` skipped existing file: ${file}`);
53
+ }
54
+
55
+ mergePackageJson(projectDir, infra);
56
+
57
+ if (infra.env?.length) {
58
+ appendLines(path.join(projectDir, '.env.example'), ['', ...infra.env]);
59
+ }
60
+
61
+ if (infra.docker) {
62
+ const composeFile = path.join(projectDir, 'docker-compose.yml');
63
+ if (!fs.existsSync(composeFile)) fs.writeFileSync(composeFile, COMPOSE_SKELETON);
64
+ const service = fs
65
+ .readFileSync(path.join(infra.dir, infra.docker.file), 'utf8')
66
+ .replace(/\{\{([A-Z_]+)\}\}/g, (m, key) => vars[key] ?? m)
67
+ .trimEnd()
68
+ .split(/\r?\n/);
69
+ injectAtMarker(composeFile, '# @infra:services', [...service, '']);
70
+ const volumes = (infra.docker.volumes ?? []).map((name) => `${name}:`);
71
+ if (volumes.length) injectAtMarker(composeFile, '# @infra:volumes', volumes);
72
+ }
73
+
74
+ for (const step of infra.inject ?? []) {
75
+ const file = path.join(projectDir, step.file);
76
+ if (!injectAtMarker(file, step.marker, step.lines)) {
77
+ log(` ! marker "${step.marker}" not found in ${step.file} (add it by hand)`);
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Creates `projectDir` from `template`, then layers the requested infra on top.
84
+ * Returns what happened so the CLI can print an accurate summary.
85
+ */
86
+ export function createProject({ template, infra: requested = [], projectDir, availableInfra, log = () => {} }) {
87
+ const projectName = path.basename(projectDir);
88
+ const vars = buildVars(projectName);
89
+ const { ordered, added } = resolveInfra(requested, availableInfra);
90
+
91
+ const unsupported = ordered.filter(
92
+ (infra) => infra.templates && !infra.templates.includes(template.id),
93
+ );
94
+ if (unsupported.length) {
95
+ throw new Error(
96
+ `Infra not available for ${template.id}: ${unsupported.map((i) => i.id).join(', ')}`,
97
+ );
98
+ }
99
+
100
+ fs.mkdirSync(projectDir, { recursive: true });
101
+ copyTemplateDir(path.join(template.dir, 'files'), projectDir, vars);
102
+
103
+ for (const infra of ordered) {
104
+ log(`+ infra: ${infra.id}`);
105
+ applyInfra(projectDir, infra, vars, log);
106
+ }
107
+
108
+ if (fs.existsSync(path.join(projectDir, '.env.example'))) {
109
+ const envFile = path.join(projectDir, '.env');
110
+ if (!fs.existsSync(envFile)) {
111
+ fs.copyFileSync(path.join(projectDir, '.env.example'), envFile);
112
+ }
113
+ }
114
+
115
+ writeJson(path.join(projectDir, LOCK_FILE), {
116
+ template: template.id,
117
+ infra: ordered.map((infra) => infra.id),
118
+ });
119
+
120
+ return { projectName, vars, infra: ordered, autoAdded: added };
121
+ }
122
+
123
+ /** `create-my-stack add <infra>` — layers more infra onto a project created earlier. */
124
+ export function addInfraToProject({ projectDir, infra: requested, availableInfra, log = () => {} }) {
125
+ const lockPath = path.join(projectDir, LOCK_FILE);
126
+ if (!fs.existsSync(lockPath)) {
127
+ throw new Error(`${LOCK_FILE} not found in ${projectDir}; was it created by create-my-stack?`);
128
+ }
129
+ const lock = readJson(lockPath);
130
+ const installed = new Set(lock.infra ?? []);
131
+ const vars = buildVars(path.basename(projectDir));
132
+
133
+ const { ordered, added } = resolveInfra(requested, availableInfra);
134
+ const pending = ordered.filter((infra) => !installed.has(infra.id));
135
+ for (const infra of pending) {
136
+ if (infra.templates && !infra.templates.includes(lock.template)) {
137
+ throw new Error(`Infra "${infra.id}" does not support template ${lock.template}`);
138
+ }
139
+ }
140
+
141
+ for (const infra of pending) {
142
+ log(`+ infra: ${infra.id}`);
143
+ applyInfra(projectDir, infra, vars, log);
144
+ }
145
+
146
+ writeJson(lockPath, { ...lock, infra: [...installed, ...pending.map((infra) => infra.id)] });
147
+ return { added: pending, autoAdded: added.filter((a) => !installed.has(a.id)) };
148
+ }
149
+
150
+ export function initGit(projectDir) {
151
+ if (run('git', ['--version'], projectDir) === false) return false;
152
+ return run('git', ['init', '-q'], projectDir);
153
+ }
154
+
155
+ export function installDependencies(projectDir, packageManager) {
156
+ return run(packageManager, ['install'], projectDir);
157
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "create-easy-stack",
3
+ "version": "1.0.3",
4
+ "description": "Personal project generator: NestJS DDD, React/Vue FSD & monolith templates + pluggable infra (Prisma, Redis, RabbitMQ...).",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-easy-stack": "./bin/create.mjs"
8
+ },
9
+ "scripts": {
10
+ "create": "node bin/create.mjs",
11
+ "list": "node bin/create.mjs list",
12
+ "test": "echo \"No tests specified\" && exit 0",
13
+ "smoke": "node scripts/smoke.mjs"
14
+ },
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "dependencies": {
19
+ "prompts": "^2.4.2"
20
+ },
21
+ "files": [
22
+ "bin/",
23
+ "lib/",
24
+ "templates/",
25
+ "infra/"
26
+ ],
27
+ "author": "Thuy Phuoc Thinh <thuyphuocthinh@npm>",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/thuyphuocthinh/create-easy-stack.git"
32
+ },
33
+ "homepage": "https://github.com/thuyphuocthinh/create-easy-stack#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/thuyphuocthinh/create-easy-stack/issues"
36
+ }
37
+ }
@@ -0,0 +1,5 @@
1
+ NODE_ENV=development
2
+ PORT=3000
3
+ # Comma separated list; '*' is allowed in development only
4
+ CORS_ORIGINS=
5
+ APP_NAME="{{PROJECT_NAME}}"
@@ -0,0 +1 @@
1
+ npx --no-install commitlint --edit "$1"
@@ -0,0 +1 @@
1
+ npx lint-staged
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "rules": {
4
+ "typescript/no-explicit-any": "off",
5
+ "typescript/no-floating-promises": "error"
6
+ },
7
+ "env": {
8
+ "node": true
9
+ }
10
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "all"
4
+ }
@@ -0,0 +1,40 @@
1
+ # compiled output
2
+ /dist
3
+ /node_modules
4
+ *.tsbuildinfo
5
+
6
+ # Logs
7
+ logs
8
+ *.log
9
+ npm-debug.log*
10
+ pnpm-debug.log*
11
+ yarn-debug.log*
12
+ yarn-error.log*
13
+
14
+ # OS
15
+ .DS_Store
16
+
17
+ # Tests
18
+ /coverage
19
+ /.nyc_output
20
+
21
+ # IDEs and editors
22
+ /.idea
23
+ .project
24
+ .classpath
25
+ .c9/
26
+ *.launch
27
+ .settings/
28
+ *.sublime-workspace
29
+ .vscode/*
30
+ !.vscode/settings.json
31
+ !.vscode/tasks.json
32
+ !.vscode/launch.json
33
+ !.vscode/extensions.json
34
+
35
+ # dotenv
36
+ .env
37
+ .env.test
38
+ .env.production
39
+
40
+ /generated/prisma
@@ -0,0 +1,3 @@
1
+ export default {
2
+ extends: ['@commitlint/config-conventional'],
3
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true
7
+ }
8
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "private": true,
6
+ "license": "UNLICENSED",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "nest build",
10
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11
+ "start": "nest start",
12
+ "start:dev": "nest start --watch",
13
+ "start:debug": "nest start --debug --watch",
14
+ "start:prod": "node dist/main",
15
+ "lint": "oxlint --type-aware src/ test/",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest",
18
+ "test:cov": "vitest run --coverage",
19
+ "test:e2e": "vitest run --config ./vitest.config.e2e.ts",
20
+ "gen": "plop",
21
+ "prepare": "husky"
22
+ },
23
+ "dependencies": {
24
+ "@nestjs/common": "^12.0.1",
25
+ "@nestjs/config": "^12.0.0",
26
+ "@nestjs/core": "^12.0.1",
27
+ "@nestjs/cqrs": "^12.0.0",
28
+ "@nestjs/platform-express": "^12.0.1",
29
+ "@nestjs/swagger": "^12.0.1",
30
+ "class-transformer": "^0.5.1",
31
+ "class-validator": "^0.15.1",
32
+ "express": "^5.2.1",
33
+ "helmet": "^8.3.0",
34
+ "nestjs-cls": "^6.3.0",
35
+ "reflect-metadata": "^0.2.2",
36
+ "rxjs": "^7.8.1"
37
+ },
38
+ "devDependencies": {
39
+ "@commitlint/cli": "^21.2.2",
40
+ "@commitlint/config-conventional": "^21.2.2",
41
+ "@nestjs/cli": "^12.0.0",
42
+ "@nestjs/schematics": "^12.0.0",
43
+ "@nestjs/testing": "^12.0.1",
44
+ "@types/express": "^5.0.0",
45
+ "@types/node": "^24.0.0",
46
+ "@types/supertest": "^7.0.0",
47
+ "@vitest/coverage-v8": "^4.1.2",
48
+ "dotenv": "^18.0.1",
49
+ "husky": "^9.1.7",
50
+ "lint-staged": "^17.5.1",
51
+ "oxlint": "^1.58.0",
52
+ "oxlint-tsgolint": "^7.0.2001",
53
+ "plop": "^4.0.5",
54
+ "prettier": "^3.4.2",
55
+ "source-map-support": "^0.5.21",
56
+ "supertest": "^7.0.0",
57
+ "typescript": "^6.0.2",
58
+ "vitest": "^4.1.2"
59
+ },
60
+ "lint-staged": {
61
+ "*.ts": [
62
+ "oxlint --fix",
63
+ "prettier --write"
64
+ ]
65
+ }
66
+ }
@@ -0,0 +1,5 @@
1
+ import { ICommand } from '@nestjs/cqrs';
2
+
3
+ export class {{Pascal}}Command implements ICommand {
4
+ constructor(public readonly id: string) {}
5
+ }
@@ -0,0 +1,30 @@
1
+ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
2
+ import { Inject, Logger } from '@nestjs/common';
3
+ import { {{Pascal}}Command } from './{{kebab}}.command.js';
4
+ import { {{ModulePascal}}DomainException } from '../../domain/index.js';
5
+ import type { I{{ModulePascal}}Repository } from '../../domain/index.js';
6
+ import { {{ModulePascal}}ErrorCodes } from '@/core/index.js';
7
+
8
+ @CommandHandler({{Pascal}}Command)
9
+ export class {{Pascal}}Handler implements ICommandHandler<{{Pascal}}Command> {
10
+ private readonly logger = new Logger({{Pascal}}Handler.name);
11
+
12
+ constructor(
13
+ @Inject('I{{ModulePascal}}Repository')
14
+ private readonly repo: I{{ModulePascal}}Repository,
15
+ ) {}
16
+
17
+ async execute(command: {{Pascal}}Command): Promise<void> {
18
+ this.logger.log(`Executing {{Pascal}}Command for ${command.id}`);
19
+
20
+ const entity = await this.repo.findById(command.id);
21
+ if (!entity) {
22
+ throw new {{ModulePascal}}DomainException(
23
+ {{ModulePascal}}ErrorCodes.{{ModuleConstant}}_NOT_FOUND,
24
+ );
25
+ }
26
+
27
+ // TODO: call a domain method on `entity` (business rules live there), then:
28
+ await this.repo.save(entity);
29
+ }
30
+ }
@@ -0,0 +1,5 @@
1
+ import { ICommand } from '@nestjs/cqrs';
2
+
3
+ export class Create{{Pascal}}Command implements ICommand {
4
+ constructor(public readonly name: string) {}
5
+ }
@@ -0,0 +1,35 @@
1
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
2
+ import { Create{{Pascal}}Handler } from './create-{{kebab}}.handler.js';
3
+ import { Create{{Pascal}}Command } from './create-{{kebab}}.command.js';
4
+ import { {{Pascal}}, {{Pascal}}DomainException } from '../../domain/index.js';
5
+ import { {{Pascal}}ErrorCodes } from '@/core/index.js';
6
+
7
+ describe('Create{{Pascal}}Handler', () => {
8
+ let handler: Create{{Pascal}}Handler;
9
+ let {{camel}}Repo: any;
10
+
11
+ beforeEach(() => {
12
+ {{camel}}Repo = { findById: vi.fn(), save: vi.fn() };
13
+ handler = new Create{{Pascal}}Handler({{camel}}Repo);
14
+ });
15
+
16
+ it('creates and saves a {{kebab}}', async () => {
17
+ {{camel}}Repo.save.mockResolvedValueOnce(undefined);
18
+
19
+ const {{camel}} = await handler.execute(new Create{{Pascal}}Command('Sample'));
20
+
21
+ expect({{camel}}).toBeInstanceOf({{Pascal}});
22
+ expect({{camel}}.name).toBe('Sample');
23
+ expect({{camel}}Repo.save).toHaveBeenCalledWith({{camel}});
24
+ });
25
+
26
+ it('rejects an empty name', async () => {
27
+ await expect(handler.execute(new Create{{Pascal}}Command(' '))).rejects.toThrow(
28
+ {{Pascal}}DomainException,
29
+ );
30
+ await expect(handler.execute(new Create{{Pascal}}Command(' '))).rejects.toMatchObject({
31
+ code: {{Pascal}}ErrorCodes.{{CONSTANT}}_INVALID_NAME.code,
32
+ });
33
+ expect({{camel}}Repo.save).not.toHaveBeenCalled();
34
+ });
35
+ });
@@ -0,0 +1,25 @@
1
+ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
2
+ import { Inject, Logger } from '@nestjs/common';
3
+ import { Create{{Pascal}}Command } from './create-{{kebab}}.command.js';
4
+ import { {{Pascal}} } from '../../domain/index.js';
5
+ import type { I{{Pascal}}Repository } from '../../domain/index.js';
6
+
7
+ @CommandHandler(Create{{Pascal}}Command)
8
+ export class Create{{Pascal}}Handler implements ICommandHandler<Create{{Pascal}}Command> {
9
+ private readonly logger = new Logger(Create{{Pascal}}Handler.name);
10
+
11
+ constructor(
12
+ @Inject('I{{Pascal}}Repository')
13
+ private readonly {{camel}}Repo: I{{Pascal}}Repository,
14
+ ) {}
15
+
16
+ async execute(command: Create{{Pascal}}Command): Promise<{{Pascal}}> {
17
+ this.logger.log(`Executing Create{{Pascal}}Command: ${command.name}`);
18
+
19
+ const {{camel}} = {{Pascal}}.create(command.name);
20
+ await this.{{camel}}Repo.save({{camel}});
21
+
22
+ this.logger.log(`Created {{kebab}} with ID: ${ {{camel}}.id }`);
23
+ return {{camel}};
24
+ }
25
+ }
@@ -0,0 +1,5 @@
1
+ export * from './commands/create-{{kebab}}.command.js';
2
+ export * from './commands/create-{{kebab}}.handler.js';
3
+ export * from './queries/get-{{kebab}}-by-id.query.js';
4
+ export * from './queries/get-{{kebab}}-by-id.handler.js';
5
+ // @plop:exports
@@ -0,0 +1,29 @@
1
+ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
2
+ import { Inject, Logger } from '@nestjs/common';
3
+ import { Get{{Pascal}}ByIdQuery } from './get-{{kebab}}-by-id.query.js';
4
+ import { {{Pascal}}, {{Pascal}}DomainException } from '../../domain/index.js';
5
+ import type { I{{Pascal}}Repository } from '../../domain/index.js';
6
+ import { {{Pascal}}ErrorCodes } from '@/core/index.js';
7
+
8
+ @QueryHandler(Get{{Pascal}}ByIdQuery)
9
+ export class Get{{Pascal}}ByIdHandler implements IQueryHandler<Get{{Pascal}}ByIdQuery> {
10
+ private readonly logger = new Logger(Get{{Pascal}}ByIdHandler.name);
11
+
12
+ constructor(
13
+ @Inject('I{{Pascal}}Repository')
14
+ private readonly {{camel}}Repo: I{{Pascal}}Repository,
15
+ ) {}
16
+
17
+ async execute(query: Get{{Pascal}}ByIdQuery): Promise<{{Pascal}}> {
18
+ this.logger.debug(`Executing Get{{Pascal}}ByIdQuery: ${query.id}`);
19
+
20
+ const {{camel}} = await this.{{camel}}Repo.findById(query.id);
21
+ if (!{{camel}}) {
22
+ throw new {{Pascal}}DomainException(
23
+ {{Pascal}}ErrorCodes.{{CONSTANT}}_NOT_FOUND,
24
+ `{{Pascal}} with ID ${query.id} not found`,
25
+ );
26
+ }
27
+ return {{camel}};
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ import { IQuery } from '@nestjs/cqrs';
2
+
3
+ export class Get{{Pascal}}ByIdQuery implements IQuery {
4
+ constructor(public readonly id: string) {}
5
+ }
@@ -0,0 +1,37 @@
1
+ import { AggregateRoot } from '@/core/index.js';
2
+ import { {{Pascal}}CreatedEvent } from '../events/{{kebab}}-created.event.js';
3
+ import { {{Pascal}}DomainException } from '../exceptions/{{kebab}}.exception.js';
4
+ import { {{Pascal}}ErrorCodes } from '@/core/index.js';
5
+
6
+ export interface {{Pascal}}Props {
7
+ name: string;
8
+ }
9
+
10
+ /**
11
+ * Aggregate root of the {{kebab}} bounded context. Only the aggregate root gets a repository;
12
+ * child entities are reachable through it.
13
+ */
14
+ export class {{Pascal}} extends AggregateRoot<{{Pascal}}Props> {
15
+ static create(name: string, id?: string): {{Pascal}} {
16
+ {{Pascal}}.assertValidName(name);
17
+ const entity = new {{Pascal}}({ name: name.trim() }, id);
18
+ entity.addDomainEvent(new {{Pascal}}CreatedEvent(entity.id, entity.name));
19
+ return entity;
20
+ }
21
+
22
+ get name(): string {
23
+ return this.props.name;
24
+ }
25
+
26
+ rename(name: string): void {
27
+ {{Pascal}}.assertValidName(name);
28
+ this.props.name = name.trim();
29
+ this.updatedAt = new Date();
30
+ }
31
+
32
+ private static assertValidName(name: string): void {
33
+ if (!name || name.trim().length === 0) {
34
+ throw new {{Pascal}}DomainException({{Pascal}}ErrorCodes.{{CONSTANT}}_INVALID_NAME);
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,10 @@
1
+ import { DomainEvent } from '@/core/index.js';
2
+
3
+ export class {{Pascal}}CreatedEvent extends DomainEvent {
4
+ constructor(
5
+ public readonly aggregateId: string,
6
+ public readonly name: string,
7
+ ) {
8
+ super(aggregateId);
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ import { DomainException, ErrorCode } from '@/core/index.js';
2
+
3
+ export class {{Pascal}}DomainException extends DomainException {
4
+ constructor(error: ErrorCode, customMessage?: string) {
5
+ super({
6
+ code: error.code,
7
+ message: customMessage || error.message,
8
+ status: error.status,
9
+ });
10
+ }
11
+ }
@@ -0,0 +1,4 @@
1
+ export * from './entities/{{kebab}}.entity.js';
2
+ export * from './events/{{kebab}}-created.event.js';
3
+ export * from './exceptions/{{kebab}}.exception.js';
4
+ export * from './repositories/{{kebab}}.repository.js';
@@ -0,0 +1,7 @@
1
+ import { {{Pascal}} } from '../entities/{{kebab}}.entity.js';
2
+
3
+ /** Port: the domain/application layers depend on this, never on Prisma or any other adapter. */
4
+ export interface I{{Pascal}}Repository {
5
+ findById(id: string): Promise<{{Pascal}} | null>;
6
+ save(entity: {{Pascal}}): Promise<void>;
7
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ Controller,
3
+ Post,
4
+ Get,
5
+ Body,
6
+ Param,
7
+ HttpCode,
8
+ HttpStatus,
9
+ } from '@nestjs/common';
10
+ import { CommandBus, QueryBus } from '@nestjs/cqrs';
11
+ import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
12
+ import { Create{{Pascal}}Dto } from '../dtos/create-{{kebab}}.dto.js';
13
+ import { {{Pascal}}Mapper } from '../mappers/{{kebab}}.mapper.js';
14
+ import {
15
+ Create{{Pascal}}Command,
16
+ Get{{Pascal}}ByIdQuery,
17
+ } from '../../application/index.js';
18
+ import { {{Pascal}} } from '../../domain/index.js';
19
+
20
+ // Add guards (e.g. @UseGuards(JwtAuthGuard)) once an auth module exists.
21
+ @ApiTags('{{Pascal}}')
22
+ @Controller('{{plural}}')
23
+ export class {{Pascal}}Controller {
24
+ constructor(
25
+ private readonly commandBus: CommandBus,
26
+ private readonly queryBus: QueryBus,
27
+ ) {}
28
+
29
+ @Post()
30
+ @HttpCode(HttpStatus.CREATED)
31
+ @ApiOperation({ summary: 'Create a {{kebab}}' })
32
+ @ApiResponse({ status: 201, description: '{{Pascal}} created' })
33
+ @ApiResponse({ status: 400, description: 'Bad Request' })
34
+ async create(@Body() dto: Create{{Pascal}}Dto) {
35
+ const {{camel}} = await this.commandBus.execute<Create{{Pascal}}Command, {{Pascal}}>(
36
+ new Create{{Pascal}}Command(dto.name),
37
+ );
38
+ return {{Pascal}}Mapper.toResponse({{camel}});
39
+ }
40
+
41
+ @Get(':id')
42
+ @HttpCode(HttpStatus.OK)
43
+ @ApiOperation({ summary: 'Get a {{kebab}} by ID' })
44
+ @ApiParam({ name: 'id', description: '{{Pascal}} ID' })
45
+ @ApiResponse({ status: 200, description: '{{Pascal}} retrieved' })
46
+ @ApiResponse({ status: 404, description: '{{Pascal}} not found' })
47
+ async getById(@Param('id') id: string) {
48
+ const {{camel}} = await this.queryBus.execute<Get{{Pascal}}ByIdQuery, {{Pascal}}>(
49
+ new Get{{Pascal}}ByIdQuery(id),
50
+ );
51
+ return {{Pascal}}Mapper.toResponse({{camel}});
52
+ }
53
+ }
@@ -0,0 +1,10 @@
1
+ import { IsString, IsNotEmpty, MaxLength } from 'class-validator';
2
+ import { ApiProperty } from '@nestjs/swagger';
3
+
4
+ export class Create{{Pascal}}Dto {
5
+ @ApiProperty({ example: 'Sample', maxLength: 100 })
6
+ @IsString()
7
+ @IsNotEmpty()
8
+ @MaxLength(100)
9
+ name: string;
10
+ }
@@ -0,0 +1,15 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+
3
+ export class {{Pascal}}ResponseDto {
4
+ @ApiProperty()
5
+ id: string;
6
+
7
+ @ApiProperty()
8
+ name: string;
9
+
10
+ @ApiProperty()
11
+ createdAt: Date;
12
+
13
+ @ApiProperty()
14
+ updatedAt: Date;
15
+ }
@@ -0,0 +1,4 @@
1
+ export * from './controllers/{{kebab}}.controller.js';
2
+ export * from './dtos/create-{{kebab}}.dto.js';
3
+ export * from './dtos/{{kebab}}-response.dto.js';
4
+ export * from './mappers/{{kebab}}.mapper.js';