@saulpaulus17/node-module-generator 2.0.2 → 2.0.5
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/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/.github/ISSUE_TEMPLATE/custom.md +10 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/CONTRIBUTING.md +110 -0
- package/DOCS_STEPS.md +40 -0
- package/README.md +123 -60
- package/bin/cli.js +0 -0
- package/generator/dto.generator.js +4 -4
- package/generator/module.generator.js +34 -11
- package/generator/repository.generator.js +3 -3
- package/generator/resource.generator.js +14 -15
- package/generator/usecase.generator.js +2 -2
- package/package.json +2 -2
- package/src/modules/Auth/Auth.module.js +15 -0
- package/src/modules/Auth/application/dtos/auth.dto.js +10 -0
- package/src/modules/Auth/application/usecases/AuthUseCase.js +12 -0
- package/src/modules/Auth/application/usecases/AuthUseCase.test.js +30 -0
- package/src/modules/Auth/domain/entities/Auth.js +5 -0
- package/src/modules/Auth/domain/repositories/AuthRepository.js +9 -0
- package/src/modules/Auth/infrastructure/repositories/PrismaAuthRepository.js +15 -0
- package/src/modules/Auth/interfaces/controllers/AuthController.js +15 -0
- package/src/modules/Auth/interfaces/controllers/AuthController.test.js +49 -0
- package/src/modules/Auth/interfaces/routes/auth.routes.js +9 -0
- package/src/modules/Auth/package.json +3 -0
- package/templates/module/controller.ejs +6 -7
- package/templates/module/controller.test.ejs +12 -9
- package/templates/module/di.ejs +9 -10
- package/templates/module/dto.ejs +10 -7
- package/templates/module/entity.ejs +2 -5
- package/templates/module/repository.impl.ejs +9 -17
- package/templates/module/repository.interface.ejs +3 -5
- package/templates/module/route.ejs +7 -7
- package/templates/module/usecase.ejs +7 -10
- package/templates/module/usecase.test.ejs +13 -9
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**Describe the bug**
|
|
11
|
+
A clear and concise description of what the bug is.
|
|
12
|
+
|
|
13
|
+
**To Reproduce**
|
|
14
|
+
Steps to reproduce the behavior:
|
|
15
|
+
1. Go to '...'
|
|
16
|
+
2. Click on '....'
|
|
17
|
+
3. Scroll down to '....'
|
|
18
|
+
4. See error
|
|
19
|
+
|
|
20
|
+
**Expected behavior**
|
|
21
|
+
A clear and concise description of what you expected to happen.
|
|
22
|
+
|
|
23
|
+
**Screenshots**
|
|
24
|
+
If applicable, add screenshots to help explain your problem.
|
|
25
|
+
|
|
26
|
+
**Desktop (please complete the following information):**
|
|
27
|
+
- OS: [e.g. iOS]
|
|
28
|
+
- Browser [e.g. chrome, safari]
|
|
29
|
+
- Version [e.g. 22]
|
|
30
|
+
|
|
31
|
+
**Smartphone (please complete the following information):**
|
|
32
|
+
- Device: [e.g. iPhone6]
|
|
33
|
+
- OS: [e.g. iOS8.1]
|
|
34
|
+
- Browser [e.g. stock browser, safari]
|
|
35
|
+
- Version [e.g. 22]
|
|
36
|
+
|
|
37
|
+
**Additional context**
|
|
38
|
+
Add any other context about the problem here.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**Is your feature request related to a problem? Please describe.**
|
|
11
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
12
|
+
|
|
13
|
+
**Describe the solution you'd like**
|
|
14
|
+
A clear and concise description of what you want to happen.
|
|
15
|
+
|
|
16
|
+
**Describe alternatives you've considered**
|
|
17
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
18
|
+
|
|
19
|
+
**Additional context**
|
|
20
|
+
Add any other context or screenshots about the feature request here.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Contributing to Node Module Generator
|
|
2
|
+
|
|
3
|
+
First off, thank you for considering contributing to **Node Module Generator (NMG)**! It is people like you who make this tool a better resource for the entire Node.js community.
|
|
4
|
+
|
|
5
|
+
Please take a moment to review this document to understand the contribution process.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ⚖️ Code of Conduct
|
|
10
|
+
|
|
11
|
+
By participating in this project, you agree to abide by our Code of Conduct. We expect all contributors to maintain a respectful, inclusive, and professional environment.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🛠️ Getting Started
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
- **Node.js**: v18.0.0 or higher
|
|
19
|
+
- **NPM**, **Yarn**, or **PNPM**
|
|
20
|
+
|
|
21
|
+
### Development Setup
|
|
22
|
+
1. Fork the repository on GitHub.
|
|
23
|
+
2. Clone your fork locally:
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/YOUR_USERNAME/node-module-generator.git
|
|
26
|
+
cd node-module-generator
|
|
27
|
+
```
|
|
28
|
+
3. Install dependencies:
|
|
29
|
+
```bash
|
|
30
|
+
npm install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 🧪 Local Testing
|
|
34
|
+
To test your changes locally, you can run the CLI directly from the source:
|
|
35
|
+
```bash
|
|
36
|
+
node bin/cli.js <command> <name>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Alternatively, link it globally for local development:
|
|
40
|
+
```bash
|
|
41
|
+
npm link
|
|
42
|
+
# Now you can use 'nmg' command locally
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 📈 How to Contribute
|
|
48
|
+
|
|
49
|
+
### Reporting Bugs
|
|
50
|
+
- Check the **Issues** tab to see if the bug has already been reported.
|
|
51
|
+
- If not, open a new issue. Include a clear title, a description of the bug, steps to reproduce, and the expected vs. actual behavior.
|
|
52
|
+
|
|
53
|
+
### Proposing Features
|
|
54
|
+
- Open an issue titled `feat: <Description>` to discuss the proposal before starting any implementation.
|
|
55
|
+
|
|
56
|
+
### Pull Request Process
|
|
57
|
+
1. Create a new branch for your feature or fix:
|
|
58
|
+
```bash
|
|
59
|
+
git checkout -b feat/your-feature-name
|
|
60
|
+
# OR
|
|
61
|
+
git checkout -b fix/your-bug-name
|
|
62
|
+
```
|
|
63
|
+
2. Implement your changes. Ensure you adhere to **Clean Architecture** patterns.
|
|
64
|
+
3. **Write Tests**: If you add a new feature or fix a bug, please include a test case.
|
|
65
|
+
4. Run the full test suite:
|
|
66
|
+
```bash
|
|
67
|
+
npm test
|
|
68
|
+
```
|
|
69
|
+
5. Commit your changes using **Conventional Commits** (see below).
|
|
70
|
+
6. Push to your fork and submit a Pull Request to the `main` branch.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 📝 Coding Standards
|
|
75
|
+
|
|
76
|
+
- **Clean Architecture**: Maintain strict separation between Domain, Application, Infrastructure, and Interface layers.
|
|
77
|
+
- **ESM/CJS**: New modules should follow the latest ESM templates.
|
|
78
|
+
- **DRY & SOLID**: Write clean, modular, and reusable code.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 🏷️ Commit Message Guidelines
|
|
83
|
+
|
|
84
|
+
We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for our commit messages:
|
|
85
|
+
|
|
86
|
+
- `feat:`: A new feature for the user.
|
|
87
|
+
- `fix:`: A bug fix for the user.
|
|
88
|
+
- `docs:`: Documentation only changes.
|
|
89
|
+
- `refactor:`: A code change that neither fixes a bug nor adds a feature.
|
|
90
|
+
- `test:`: Adding missing tests or correcting existing tests.
|
|
91
|
+
- `chore:`: Changes to the build process or auxiliary tools.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
```text
|
|
95
|
+
feat(generator): add support for Zod schemas
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## ✅ Pull Request Checklist
|
|
101
|
+
|
|
102
|
+
Before submitting your PR, please ensure:
|
|
103
|
+
1. [ ] The code follows the project's architecture and style.
|
|
104
|
+
2. [ ] All tests pass locally (`npm test`).
|
|
105
|
+
3. [ ] All status checks on GitHub (CI) pass.
|
|
106
|
+
4. [ ] Documentation is updated if necessary.
|
|
107
|
+
5. [ ] Commit messages follow the Conventional Commits standard.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
Thank you for your contribution! 🚀
|
package/DOCS_STEPS.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Langkah-langkah Setelah Generate Modul Baru
|
|
2
|
+
|
|
3
|
+
Setelah menjalankan perintah `nmg module <name>`, ikuti langkah-langkah berikut untuk mengintegrasikan modul ke dalam proyek Node.js Anda:
|
|
4
|
+
|
|
5
|
+
## 1. Registrasi di Awilix Container (`src/container.js`)
|
|
6
|
+
|
|
7
|
+
Buka file `src/container.js` dan tambahkan registrasi untuk repository jika Anda ingin menggunakan alias spesifik:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { asFunction } from 'awilix';
|
|
11
|
+
|
|
12
|
+
// ... di dalam container.register({ ... })
|
|
13
|
+
container.register({
|
|
14
|
+
// Contoh alias: [module]Repository -> prisma[Module]Repository
|
|
15
|
+
authRepository: asFunction(({ prismaAuthRepository }) => prismaAuthRepository).scoped(),
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
*Catatan: Secara default, generator Awilix biasanya mengauto-load file. Pastikan loader Anda dikonfigurasi untuk memuat folder `usecases`, `repositories`, `controllers`, dan `routes`.*
|
|
20
|
+
|
|
21
|
+
## 2. Registrasi Route di Express (`src/app.js`)
|
|
22
|
+
|
|
23
|
+
Buka file `src/app.js` dan daftarkan router dari modul yang baru dibuat:
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
// ...
|
|
27
|
+
app.use('/api/v1/auth', container.resolve('authRoutes'));
|
|
28
|
+
// ...
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 3. Implementasi Detail Bisnis
|
|
32
|
+
* **Domain**: Tentukan skema data di `domain/entities`.
|
|
33
|
+
* **Repository Interface**: Tambahkan method yang dibutuhkan di `domain/repositories`.
|
|
34
|
+
* **Repository Implementation**: Implementasikan query database (Prisma) di `infrastructure/repositories`.
|
|
35
|
+
* **UseCase**: Tulis logika bisnis utama di `application/usecases`.
|
|
36
|
+
* **Controller**: Tangani input request dan panggil UseCase di `interfaces/controllers`.
|
|
37
|
+
* **Routes**: Definisikan endpoint HTTP di `interfaces/routes`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
*Generated by node-module-generator*
|
package/README.md
CHANGED
|
@@ -1,103 +1,166 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<
|
|
3
|
-
<
|
|
2
|
+
<img src="https://raw.githubusercontent.com/saul-paulus/node-module-generator/main/assets/banner.png" alt="Node Module Generator Banner" width="600" style="max-width: 100%;" />
|
|
3
|
+
<h1>🚀 Node Module Generator (NMG)</h1>
|
|
4
|
+
<p><strong>The ultimate CLI companion for rapid, enterprise-grade Node.js scaffolding.</strong></p>
|
|
4
5
|
<p>
|
|
5
6
|
<a href="https://github.com/saul-paulus/node-module-generator/actions/workflows/ci.yml">
|
|
6
7
|
<img src="https://github.com/saul-paulus/node-module-generator/actions/workflows/ci.yml/badge.svg" alt="CI Status">
|
|
7
8
|
</a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@saulpaulus17/node-module-generator">
|
|
10
|
+
<img src="https://img.shields.io/npm/v/@saulpaulus17/node-module-generator.svg?logo=npm&logoColor=white" alt="NPM Version" />
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@saulpaulus17/node-module-generator">
|
|
13
|
+
<img src="https://img.shields.io/npm/dt/@saulpaulus17/node-module-generator.svg?logo=npm&logoColor=white" alt="NPM Downloads" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://github.com/saul-paulus/node-module-generator/blob/main/LICENSE">
|
|
16
|
+
<img src="https://img.shields.io/npm/l/@saulpaulus17/node-module-generator.svg" alt="License" />
|
|
17
|
+
</a>
|
|
8
18
|
</p>
|
|
9
19
|
</div>
|
|
10
20
|
|
|
11
21
|
---
|
|
12
22
|
|
|
13
|
-
##
|
|
23
|
+
## 🏛️ Architecture Overview
|
|
24
|
+
|
|
25
|
+
Node Module Generator (NMG) enforces **Clean Architecture** principles to ensure your backend remains scalable, testable, and decoupled. It is purpose-built for high-performance **Express.js** environments using **Awilix** for Dependency Injection.
|
|
26
|
+
|
|
27
|
+
### Layered Structure
|
|
28
|
+
```mermaid
|
|
29
|
+
graph TD
|
|
30
|
+
UI[Interfaces Layer: Controllers & Routes] --> APP[Application Layer: Use Cases & DTOs]
|
|
31
|
+
APP --> DOM[Domain Layer: Entities & Repository Interfaces]
|
|
32
|
+
INF[Infrastructure Layer: Repository Impl & External Services] --> DOM
|
|
33
|
+
|
|
34
|
+
subgraph "Inner Layers"
|
|
35
|
+
DOM
|
|
36
|
+
APP
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
subgraph "Outer Layers"
|
|
40
|
+
UI
|
|
41
|
+
INF
|
|
42
|
+
end
|
|
43
|
+
```
|
|
14
44
|
|
|
15
|
-
|
|
45
|
+
---
|
|
16
46
|
|
|
17
|
-
|
|
47
|
+
## 🔥 Key Features
|
|
18
48
|
|
|
19
|
-
|
|
49
|
+
- 💎 **Clean Architecture by Design**: Strict separation into Domain, Application, Infrastructure, and Interface layers.
|
|
50
|
+
- 💉 **Native Dependency Injection**: Fully pre-configured for **Awilix**, providing seamless DI management.
|
|
51
|
+
- 🧪 **Test-Ready Scaffolding**: Automatically generates **Jest** test suites for Controllers and Use Cases.
|
|
52
|
+
- 🚀 **Modern Tooling**: Native support for **ES Modules (ESM)**, **Prisma ORM**, and **Joi/Zod** DTO patterns.
|
|
53
|
+
- 🤖 **Granular Control**: Generate full modules or individual components (UseCases, Repos, DTOs) without disrupting existing code.
|
|
20
54
|
|
|
21
|
-
|
|
22
|
-
- 💉 **Dependency Injection Ready**: Auto-generates Awilix configurations mapped correctly across use cases, controllers, and repositories.
|
|
23
|
-
- 🧪 **Test-Driven Design**: Scaffolds adjoining `*.test.js` files containing boilerplates for Jest to promote TDD.
|
|
24
|
-
- 🤖 **Continuous Integration**: Codebase natively incorporates GitHub Actions for automated unit testing checks.
|
|
25
|
-
- 🧩 **Granular Scaffolding**: Generate specific components (UseCases, Repositories, DTOs) dynamically on demand without overriding existing folders!
|
|
55
|
+
---
|
|
26
56
|
|
|
27
57
|
## 📦 Installation
|
|
28
58
|
|
|
29
|
-
|
|
59
|
+
### Prerequisites
|
|
60
|
+
- **Node.js**: v18.0.0 or higher (LTS recommended)
|
|
61
|
+
- **NPM**, **Yarn**, or **PNPM**
|
|
62
|
+
|
|
63
|
+
### Global Installation (Recommended)
|
|
64
|
+
Install NMG globally to access the command from any project.
|
|
30
65
|
|
|
31
66
|
```bash
|
|
32
|
-
# Install globally via NPM
|
|
33
67
|
npm install -g @saulpaulus17/node-module-generator
|
|
34
68
|
```
|
|
35
69
|
|
|
36
|
-
|
|
70
|
+
### Direct Execution
|
|
71
|
+
Run it on-the-fly without a permanent installation:
|
|
37
72
|
|
|
38
|
-
|
|
73
|
+
```bash
|
|
74
|
+
npx @saulpaulus17/node-module-generator <command> <name>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
39
78
|
|
|
40
|
-
|
|
41
|
-
|
|
79
|
+
## 🚀 Detailed Usage
|
|
80
|
+
|
|
81
|
+
### 1. Generating a Full Module
|
|
82
|
+
Scaffolds a complete standard architecture with all 4 layers and initial unit tests.
|
|
42
83
|
|
|
43
84
|
```bash
|
|
44
|
-
|
|
45
|
-
|
|
85
|
+
nmg module Auth
|
|
86
|
+
```
|
|
46
87
|
|
|
47
|
-
|
|
48
|
-
|
|
88
|
+
### 2. Generating Individual Components
|
|
89
|
+
Quickly add specific components to an existing module structure.
|
|
49
90
|
|
|
50
|
-
|
|
51
|
-
|
|
91
|
+
```bash
|
|
92
|
+
# Add a new UseCase (e.g., login) to the Auth module
|
|
93
|
+
nmg usecase login --module=Auth
|
|
52
94
|
|
|
53
|
-
#
|
|
54
|
-
nmg
|
|
95
|
+
# Add a Repository Interface and Implementation (Prisma)
|
|
96
|
+
nmg repository User
|
|
55
97
|
|
|
56
|
-
#
|
|
57
|
-
nmg
|
|
98
|
+
# Add a DTO validation schema
|
|
99
|
+
nmg dto userRegistration --module=Auth
|
|
58
100
|
```
|
|
59
101
|
|
|
60
|
-
|
|
102
|
+
---
|
|
61
103
|
|
|
62
|
-
## 📂
|
|
104
|
+
## 📂 Project Blueprint
|
|
63
105
|
|
|
64
|
-
|
|
106
|
+
Scaffolding a module (e.g., `nmg module Product`) produces the following industry-standard structure:
|
|
65
107
|
|
|
66
108
|
```text
|
|
67
|
-
src/modules/
|
|
68
|
-
├── application/
|
|
69
|
-
│ ├── dtos/ # (
|
|
70
|
-
│ └── usecases/
|
|
71
|
-
│ ├──
|
|
72
|
-
│ └──
|
|
73
|
-
├── domain/
|
|
74
|
-
│ ├── entities/
|
|
75
|
-
│ │ └──
|
|
76
|
-
│ └── repositories/ # Repository
|
|
77
|
-
│ └──
|
|
78
|
-
├── infrastructure/
|
|
79
|
-
│ ├── repositories/ #
|
|
80
|
-
│ │ └──
|
|
81
|
-
|
|
82
|
-
│
|
|
83
|
-
├──
|
|
84
|
-
│
|
|
85
|
-
│
|
|
86
|
-
│
|
|
87
|
-
|
|
88
|
-
│ └── user.routes.js # Express routers integrating the controller
|
|
89
|
-
└── user.module.js # Centralised Awilix Dependency Injection bindings
|
|
109
|
+
src/modules/Product/
|
|
110
|
+
├── application/
|
|
111
|
+
│ ├── dtos/ # DTO schemas (e.g., product.dto.js)
|
|
112
|
+
│ └── usecases/ # Business orchestration
|
|
113
|
+
│ ├── ProductUseCase.js # Logic implementation
|
|
114
|
+
│ └── ProductUseCase.test.js # Unit tests
|
|
115
|
+
├── domain/
|
|
116
|
+
│ ├── entities/ # Business entity definitions
|
|
117
|
+
│ │ └── Product.js
|
|
118
|
+
│ └── repositories/ # Repository Interface (Contracts)
|
|
119
|
+
│ └── ProductRepository.js
|
|
120
|
+
├── infrastructure/
|
|
121
|
+
│ ├── repositories/ # Implementation (default: Prisma)
|
|
122
|
+
│ │ └── PrismaProductRepository.js
|
|
123
|
+
├── interfaces/
|
|
124
|
+
│ ├── controllers/ # Express handlers
|
|
125
|
+
│ │ ├── ProductController.js
|
|
126
|
+
│ │ └── ProductController.test.js
|
|
127
|
+
│ └── routes/ # Express routes & method binding
|
|
128
|
+
│ └── product.routes.js
|
|
129
|
+
└── Product.module.js # Central Awilix Module Registration
|
|
90
130
|
```
|
|
91
131
|
|
|
92
|
-
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🛠️ Post-Scaffolding Integration
|
|
135
|
+
|
|
136
|
+
To finalize your new module integration, follow these standard steps:
|
|
137
|
+
|
|
138
|
+
1. **DI Registration**: Open `src/container.js` and register any specific repository aliases or scoped usecases.
|
|
139
|
+
2. **Route Mounting**: Mount the generated router in `src/app.js`:
|
|
140
|
+
```javascript
|
|
141
|
+
app.use('/api/v1/product', container.resolve('productRoutes'));
|
|
142
|
+
```
|
|
143
|
+
3. **Detailed Implementation**: Build out the specific logic in the generated templates (which are already integrated via Awilix).
|
|
144
|
+
|
|
145
|
+
---
|
|
93
146
|
|
|
94
|
-
|
|
147
|
+
## 🤝 Contributing & Support
|
|
95
148
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
149
|
+
We welcome contributions from the community!
|
|
150
|
+
1. Fork the project.
|
|
151
|
+
2. Create your Feature Branch (`git checkout -b feat/NewFeature`).
|
|
152
|
+
3. Commit your changes (`git commit -m 'feat: Add some NewFeature'`).
|
|
153
|
+
4. Push to the Branch (`git push origin feat/NewFeature`).
|
|
154
|
+
5. Open a Pull Request.
|
|
155
|
+
|
|
156
|
+
---
|
|
100
157
|
|
|
101
158
|
## 📝 License
|
|
102
159
|
|
|
103
|
-
|
|
160
|
+
Distributed under the **MIT License**. See `LICENSE` for more information.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
<div align="center">
|
|
164
|
+
<p>Built with ❤️ for modern Node.js developers</p>
|
|
165
|
+
<sub>Copyright © 2024 saul-paulus</sub>
|
|
166
|
+
</div>
|
package/bin/cli.js
CHANGED
|
File without changes
|
|
@@ -5,8 +5,8 @@ const { pascalCase, camelCase } = require("../utils/case.util");
|
|
|
5
5
|
|
|
6
6
|
module.exports = async function (schemaName, moduleName) {
|
|
7
7
|
const basePath = path.join(process.cwd(), "src/modules", moduleName);
|
|
8
|
-
const
|
|
9
|
-
fs.ensureDirSync(
|
|
8
|
+
const dtoDir = path.join(basePath, "application/dtos");
|
|
9
|
+
fs.ensureDirSync(dtoDir);
|
|
10
10
|
|
|
11
11
|
const templateData = {
|
|
12
12
|
name: moduleName,
|
|
@@ -15,7 +15,7 @@ module.exports = async function (schemaName, moduleName) {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const templateContent = await ejs.renderFile(path.join(__dirname, "../templates/module/dto.ejs"), templateData);
|
|
18
|
-
fs.writeFileSync(path.join(
|
|
18
|
+
fs.writeFileSync(path.join(dtoDir, `${schemaName.toLowerCase()}.dto.js`), templateContent);
|
|
19
19
|
|
|
20
|
-
console.log(`✔ DTO
|
|
20
|
+
console.log(`✔ DTO ${schemaName} generated inside module ${moduleName} at application/dtos.`);
|
|
21
21
|
};
|
|
@@ -9,13 +9,9 @@ module.exports = async function (name) {
|
|
|
9
9
|
const dirs = [
|
|
10
10
|
"domain/entities",
|
|
11
11
|
"domain/repositories",
|
|
12
|
-
"domain/services",
|
|
13
12
|
"application/usecases",
|
|
14
13
|
"application/dtos",
|
|
15
14
|
"infrastructure/repositories",
|
|
16
|
-
"infrastructure/validation",
|
|
17
|
-
"infrastructure/security",
|
|
18
|
-
"infrastructure/services",
|
|
19
15
|
"interfaces/controllers",
|
|
20
16
|
"interfaces/routes",
|
|
21
17
|
];
|
|
@@ -23,12 +19,39 @@ module.exports = async function (name) {
|
|
|
23
19
|
// Create directories
|
|
24
20
|
dirs.forEach((dir) => fs.ensureDirSync(path.join(basePath, dir)));
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
// Generate scoped package.json for ESM support
|
|
23
|
+
fs.writeJsonSync(path.join(basePath, "package.json"), { type: "module" }, { spaces: 2 });
|
|
24
|
+
|
|
25
|
+
const templateData = {
|
|
26
|
+
name,
|
|
27
|
+
className: pascalCase(name),
|
|
28
|
+
camelName: camelCase(name),
|
|
29
|
+
useCaseClassName: `${pascalCase(name)}UseCase`,
|
|
30
|
+
useCaseFileName: `${name.toLowerCase()}.usecase`,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const renderAndWrite = async (templateName, outputPath) => {
|
|
34
|
+
const templateContent = await ejs.renderFile(
|
|
35
|
+
path.join(__dirname, "../templates/module", templateName),
|
|
36
|
+
templateData
|
|
37
|
+
);
|
|
38
|
+
fs.writeFileSync(path.join(basePath, outputPath), templateContent);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
await renderAndWrite("controller.ejs", `interfaces/controllers/${pascalCase(name)}Controller.js`);
|
|
42
|
+
await renderAndWrite("controller.test.ejs", `interfaces/controllers/${pascalCase(name)}Controller.test.js`);
|
|
43
|
+
await renderAndWrite("route.ejs", `interfaces/routes/${name.toLowerCase()}.routes.js`);
|
|
44
|
+
|
|
45
|
+
await renderAndWrite("usecase.ejs", `application/usecases/${pascalCase(name)}UseCase.js`);
|
|
46
|
+
await renderAndWrite("usecase.test.ejs", `application/usecases/${pascalCase(name)}UseCase.test.js`);
|
|
27
47
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
await renderAndWrite("entity.ejs", `domain/entities/${pascalCase(name)}.js`);
|
|
49
|
+
await renderAndWrite("repository.interface.ejs", `domain/repositories/${pascalCase(name)}Repository.js`);
|
|
50
|
+
|
|
51
|
+
await renderAndWrite("repository.impl.ejs", `infrastructure/repositories/Prisma${pascalCase(name)}Repository.js`);
|
|
52
|
+
await renderAndWrite("dto.ejs", `application/dtos/${name.toLowerCase()}.dto.js`);
|
|
53
|
+
|
|
54
|
+
await renderAndWrite("di.ejs", `${name}.module.js`);
|
|
55
|
+
|
|
56
|
+
console.log(`✔ Module ${name} directory structure, standard files, and tests created.`);
|
|
34
57
|
};
|
|
@@ -28,9 +28,9 @@ module.exports = async function (moduleName) {
|
|
|
28
28
|
fs.writeFileSync(path.join(basePath, outputPath), templateContent);
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
await renderAndWrite("entity.ejs", `domain/entities/${moduleName}.
|
|
32
|
-
await renderAndWrite("repository.interface.ejs", `domain/repositories/${moduleName}.
|
|
33
|
-
await renderAndWrite("repository.impl.ejs", `infrastructure/repositories
|
|
31
|
+
await renderAndWrite("entity.ejs", `domain/entities/${pascalCase(moduleName)}.js`);
|
|
32
|
+
await renderAndWrite("repository.interface.ejs", `domain/repositories/${pascalCase(moduleName)}Repository.js`);
|
|
33
|
+
await renderAndWrite("repository.impl.ejs", `infrastructure/repositories/Prisma${pascalCase(moduleName)}Repository.js`);
|
|
34
34
|
|
|
35
35
|
console.log(`✔ Repository patterns for ${moduleName} generated successfully.`);
|
|
36
36
|
};
|
|
@@ -9,25 +9,24 @@ module.exports = async function (name) {
|
|
|
9
9
|
const dirs = [
|
|
10
10
|
"domain/entities",
|
|
11
11
|
"domain/repositories",
|
|
12
|
-
"domain/services",
|
|
13
12
|
"application/usecases",
|
|
14
13
|
"application/dtos",
|
|
15
14
|
"infrastructure/repositories",
|
|
16
|
-
"infrastructure/validation",
|
|
17
|
-
"infrastructure/security",
|
|
18
|
-
"infrastructure/services",
|
|
19
15
|
"interfaces/controllers",
|
|
20
16
|
"interfaces/routes",
|
|
21
17
|
];
|
|
22
18
|
|
|
23
19
|
dirs.forEach((dir) => fs.ensureDirSync(path.join(basePath, dir)));
|
|
24
20
|
|
|
21
|
+
// Generate scoped package.json for ESM support
|
|
22
|
+
fs.writeJsonSync(path.join(basePath, "package.json"), { type: "module" }, { spaces: 2 });
|
|
23
|
+
|
|
25
24
|
const templateData = {
|
|
26
25
|
name,
|
|
27
26
|
className: pascalCase(name),
|
|
28
27
|
camelName: camelCase(name),
|
|
29
|
-
useCaseClassName:
|
|
30
|
-
useCaseFileName:
|
|
28
|
+
useCaseClassName: `${pascalCase(name)}UseCase`,
|
|
29
|
+
useCaseFileName: `${name.toLowerCase()}.usecase`,
|
|
31
30
|
};
|
|
32
31
|
|
|
33
32
|
const renderAndWrite = async (templateName, outputPath) => {
|
|
@@ -38,18 +37,18 @@ module.exports = async function (name) {
|
|
|
38
37
|
fs.writeFileSync(path.join(basePath, outputPath), templateContent);
|
|
39
38
|
};
|
|
40
39
|
|
|
41
|
-
await renderAndWrite("controller.ejs", `interfaces/controllers/${name}.
|
|
42
|
-
await renderAndWrite("controller.test.ejs", `interfaces/controllers/${name}.
|
|
43
|
-
await renderAndWrite("route.ejs", `interfaces/routes/${name}.routes.js`);
|
|
40
|
+
await renderAndWrite("controller.ejs", `interfaces/controllers/${pascalCase(name)}Controller.js`);
|
|
41
|
+
await renderAndWrite("controller.test.ejs", `interfaces/controllers/${pascalCase(name)}Controller.test.js`);
|
|
42
|
+
await renderAndWrite("route.ejs", `interfaces/routes/${name.toLowerCase()}.routes.js`);
|
|
44
43
|
|
|
45
|
-
await renderAndWrite("usecase.ejs", `application/usecases
|
|
46
|
-
await renderAndWrite("usecase.test.ejs", `application/usecases
|
|
44
|
+
await renderAndWrite("usecase.ejs", `application/usecases/${pascalCase(name)}UseCase.js`);
|
|
45
|
+
await renderAndWrite("usecase.test.ejs", `application/usecases/${pascalCase(name)}UseCase.test.js`);
|
|
47
46
|
|
|
48
|
-
await renderAndWrite("entity.ejs", `domain/entities/${name}.
|
|
49
|
-
await renderAndWrite("repository.interface.ejs", `domain/repositories/${name}.
|
|
47
|
+
await renderAndWrite("entity.ejs", `domain/entities/${pascalCase(name)}.js`);
|
|
48
|
+
await renderAndWrite("repository.interface.ejs", `domain/repositories/${pascalCase(name)}Repository.js`);
|
|
50
49
|
|
|
51
|
-
await renderAndWrite("repository.impl.ejs", `infrastructure/repositories
|
|
52
|
-
await renderAndWrite("dto.ejs", `
|
|
50
|
+
await renderAndWrite("repository.impl.ejs", `infrastructure/repositories/Prisma${pascalCase(name)}Repository.js`);
|
|
51
|
+
await renderAndWrite("dto.ejs", `application/dtos/${name.toLowerCase()}.dto.js`);
|
|
53
52
|
|
|
54
53
|
await renderAndWrite("di.ejs", `${name}.module.js`);
|
|
55
54
|
|
|
@@ -17,10 +17,10 @@ module.exports = async function (useCaseName, moduleName) {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const templateContent = await ejs.renderFile(path.join(__dirname, "../templates/module/usecase.ejs"), templateData);
|
|
20
|
-
fs.writeFileSync(path.join(ucDir, `${useCaseName}.
|
|
20
|
+
fs.writeFileSync(path.join(ucDir, `${pascalCase(useCaseName)}UseCase.js`), templateContent);
|
|
21
21
|
|
|
22
22
|
const testContent = await ejs.renderFile(path.join(__dirname, "../templates/module/usecase.test.ejs"), templateData);
|
|
23
|
-
fs.writeFileSync(path.join(ucDir, `${useCaseName}.
|
|
23
|
+
fs.writeFileSync(path.join(ucDir, `${pascalCase(useCaseName)}UseCase.test.js`), testContent);
|
|
24
24
|
|
|
25
25
|
console.log(`✔ Usecase ${useCaseName} generated inside module ${moduleName}.`);
|
|
26
26
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saulpaulus17/node-module-generator",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "CLI tool to grenerate modular scaffolding for nodejs projects following clean arsitecture principles. ",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nodejs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"nmg": "./bin/cli.js"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "jest"
|
|
19
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"chalk": "^5.6.2",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { asClass, asFunction } from 'awilix';
|
|
2
|
+
|
|
3
|
+
import AuthController from './interfaces/controllers/AuthController';
|
|
4
|
+
import createAuthRoutes from './interfaces/routes/auth.routes';
|
|
5
|
+
import CreateAuthUseCase from './application/usecases/CreateAuthUseCase';
|
|
6
|
+
import PrismaAuthRepository from './infrastructure/repositories/PrismaAuthRepository';
|
|
7
|
+
|
|
8
|
+
export default function registerAuthModule(container) {
|
|
9
|
+
container.register({
|
|
10
|
+
authController: asClass(AuthController).singleton(),
|
|
11
|
+
authRoutes: asFunction(createAuthRoutes).singleton(),
|
|
12
|
+
CreateAuthUseCase: asClass(CreateAuthUseCase).singleton(),
|
|
13
|
+
authRepository: asFunction(({ prismaAuthRepository }) => prismaAuthRepository).scoped(),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default class AuthUseCase {
|
|
2
|
+
constructor({ authRepository, jwtService }) {
|
|
3
|
+
this.authRepository = authRepository;
|
|
4
|
+
this.jwtService = jwtService;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async execute(inputData) {
|
|
8
|
+
// Logic for AuthUseCase
|
|
9
|
+
const result = await this.authRepository.create(inputData);
|
|
10
|
+
return { success: true, data: result };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import AuthUseCase from './AuthUseCase.js';
|
|
3
|
+
|
|
4
|
+
describe('AuthUseCase', () => {
|
|
5
|
+
let useCase;
|
|
6
|
+
let mockRepository;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockRepository = {
|
|
10
|
+
create: jest.fn()
|
|
11
|
+
};
|
|
12
|
+
useCase = new AuthUseCase({
|
|
13
|
+
authRepository: mockRepository,
|
|
14
|
+
jwtService: {}
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('execute', () => {
|
|
19
|
+
it('should call repository create and return success', async () => {
|
|
20
|
+
const mockDto = { name: 'Test' };
|
|
21
|
+
const mockResult = { id: 1, ...mockDto };
|
|
22
|
+
mockRepository.create.mockResolvedValue(mockResult);
|
|
23
|
+
|
|
24
|
+
const result = await useCase.execute(mockDto);
|
|
25
|
+
|
|
26
|
+
expect(mockRepository.create).toHaveBeenCalledWith(mockDto);
|
|
27
|
+
expect(result).toEqual({ success: true, data: mockResult });
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default class PrismaAuthRepository {
|
|
2
|
+
constructor({ prisma }) {
|
|
3
|
+
this.prisma = prisma;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async findById(id) {
|
|
7
|
+
return this.prisma.auths.findUnique({
|
|
8
|
+
where: { id },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async create(data) {
|
|
13
|
+
return this.prisma.auths.create({ data });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default class AuthController {
|
|
2
|
+
constructor({ authRepository, authUseCase }) {
|
|
3
|
+
this.authRepository = authRepository;
|
|
4
|
+
this.authUseCase = authUseCase;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async index(req, res, next) {
|
|
8
|
+
try {
|
|
9
|
+
const result = await this.authUseCase.execute(req.body);
|
|
10
|
+
res.json(result);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
next(error);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import AuthController from './AuthController.js';
|
|
3
|
+
|
|
4
|
+
describe('AuthController', () => {
|
|
5
|
+
let controller;
|
|
6
|
+
let mockUseCase;
|
|
7
|
+
let mockReq;
|
|
8
|
+
let mockRes;
|
|
9
|
+
let mockNext;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockUseCase = {
|
|
13
|
+
execute: jest.fn()
|
|
14
|
+
};
|
|
15
|
+
controller = new AuthController({
|
|
16
|
+
authRepository: {},
|
|
17
|
+
authUseCase: mockUseCase
|
|
18
|
+
});
|
|
19
|
+
mockReq = {
|
|
20
|
+
body: { test: 'data' }
|
|
21
|
+
};
|
|
22
|
+
mockRes = {
|
|
23
|
+
status: jest.fn().mockReturnThis(),
|
|
24
|
+
json: jest.fn()
|
|
25
|
+
};
|
|
26
|
+
mockNext = jest.fn();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('index', () => {
|
|
30
|
+
it('should return result if use case succeeds', async () => {
|
|
31
|
+
const mockResult = { success: true, data: mockReq.body };
|
|
32
|
+
mockUseCase.execute.mockResolvedValue(mockResult);
|
|
33
|
+
|
|
34
|
+
await controller.index(mockReq, mockRes, mockNext);
|
|
35
|
+
|
|
36
|
+
expect(mockUseCase.execute).toHaveBeenCalledWith(mockReq.body);
|
|
37
|
+
expect(mockRes.json).toHaveBeenCalledWith(mockResult);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should call next with error if use case fails', async () => {
|
|
41
|
+
const error = new Error('Test error');
|
|
42
|
+
mockUseCase.execute.mockRejectedValue(error);
|
|
43
|
+
|
|
44
|
+
await controller.index(mockReq, mockRes, mockNext);
|
|
45
|
+
|
|
46
|
+
expect(mockNext).toHaveBeenCalledWith(error);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
class <%= className %>Controller {
|
|
2
|
-
constructor({ <%= camelName %>UseCase }) {
|
|
1
|
+
export default class <%= className %>Controller {
|
|
2
|
+
constructor({ <%= camelName %>Repository, <%= camelName %>UseCase }) {
|
|
3
|
+
this.<%= camelName %>Repository = <%= camelName %>Repository;
|
|
3
4
|
this.<%= camelName %>UseCase = <%= camelName %>UseCase;
|
|
4
5
|
}
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
async index(req, res, next) {
|
|
7
8
|
try {
|
|
8
9
|
const result = await this.<%= camelName %>UseCase.execute(req.body);
|
|
9
|
-
res.
|
|
10
|
+
res.json(result);
|
|
10
11
|
} catch (error) {
|
|
11
12
|
next(error);
|
|
12
13
|
}
|
|
13
|
-
}
|
|
14
|
+
}
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
module.exports = <%= className %>Controller;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import <%= className %>Controller from './<%= className %>Controller.js';
|
|
2
3
|
|
|
3
4
|
describe('<%= className %>Controller', () => {
|
|
4
5
|
let controller;
|
|
@@ -11,7 +12,10 @@ describe('<%= className %>Controller', () => {
|
|
|
11
12
|
mockUseCase = {
|
|
12
13
|
execute: jest.fn()
|
|
13
14
|
};
|
|
14
|
-
controller = new <%= className %>Controller({
|
|
15
|
+
controller = new <%= className %>Controller({
|
|
16
|
+
<%= camelName %>Repository: {},
|
|
17
|
+
<%= camelName %>UseCase: mockUseCase
|
|
18
|
+
});
|
|
15
19
|
mockReq = {
|
|
16
20
|
body: { test: 'data' }
|
|
17
21
|
};
|
|
@@ -22,23 +26,22 @@ describe('<%= className %>Controller', () => {
|
|
|
22
26
|
mockNext = jest.fn();
|
|
23
27
|
});
|
|
24
28
|
|
|
25
|
-
describe('
|
|
26
|
-
it('should return
|
|
27
|
-
const mockResult = {
|
|
29
|
+
describe('index', () => {
|
|
30
|
+
it('should return result if use case succeeds', async () => {
|
|
31
|
+
const mockResult = { success: true, data: mockReq.body };
|
|
28
32
|
mockUseCase.execute.mockResolvedValue(mockResult);
|
|
29
33
|
|
|
30
|
-
await controller.
|
|
34
|
+
await controller.index(mockReq, mockRes, mockNext);
|
|
31
35
|
|
|
32
36
|
expect(mockUseCase.execute).toHaveBeenCalledWith(mockReq.body);
|
|
33
|
-
expect(mockRes.
|
|
34
|
-
expect(mockRes.json).toHaveBeenCalledWith({ success: true, data: mockResult });
|
|
37
|
+
expect(mockRes.json).toHaveBeenCalledWith(mockResult);
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
it('should call next with error if use case fails', async () => {
|
|
38
41
|
const error = new Error('Test error');
|
|
39
42
|
mockUseCase.execute.mockRejectedValue(error);
|
|
40
43
|
|
|
41
|
-
await controller.
|
|
44
|
+
await controller.index(mockReq, mockRes, mockNext);
|
|
42
45
|
|
|
43
46
|
expect(mockNext).toHaveBeenCalledWith(error);
|
|
44
47
|
});
|
package/templates/module/di.ejs
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { asClass, asFunction } from 'awilix';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import <%= className %>Controller from './interfaces/controllers/<%= className %>Controller';
|
|
4
|
+
import create<%= className %>Routes from './interfaces/routes/<%= name.toLowerCase() %>.routes';
|
|
5
|
+
import Create<%= className %>UseCase from './application/usecases/Create<%= className %>UseCase';
|
|
6
|
+
import Prisma<%= className %>Repository from './infrastructure/repositories/Prisma<%= className %>Repository';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
export default function register<%= className %>Module(container) {
|
|
9
9
|
container.register({
|
|
10
10
|
<%= camelName %>Controller: asClass(<%= className %>Controller).singleton(),
|
|
11
11
|
<%= camelName %>Routes: asFunction(create<%= className %>Routes).singleton(),
|
|
12
|
-
<%=
|
|
13
|
-
|
|
14
|
-
<%= camelName %>Repository: asClass(<%= className %>Repository).singleton(),
|
|
12
|
+
Create<%= className %>UseCase: asClass(Create<%= className %>UseCase).singleton(),
|
|
13
|
+
<%= camelName %>Repository: asFunction(({ prisma<%= className %>Repository }) => prisma<%= className %>Repository).scoped(),
|
|
15
14
|
});
|
|
16
|
-
}
|
|
15
|
+
}
|
package/templates/module/dto.ejs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// DTO Template
|
|
2
|
+
export default {
|
|
3
|
+
// Define DTO validation schemas here (e.g. Joi, Zod)
|
|
4
|
+
createSchema: {
|
|
5
|
+
// rules
|
|
6
|
+
},
|
|
7
|
+
updateSchema: {
|
|
8
|
+
// rules
|
|
9
|
+
}
|
|
10
|
+
};
|
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class <%= className %>RepositoryImpl extends I<%= className %>Repository {
|
|
5
|
-
// If using Prisma, you would do: constructor({ prisma }) { super(); this.prisma = prisma; }
|
|
6
|
-
constructor() {
|
|
7
|
-
super();
|
|
8
|
-
this.db = new Map();
|
|
1
|
+
export default class Prisma<%= className %>Repository {
|
|
2
|
+
constructor({ prisma }) {
|
|
3
|
+
this.prisma = prisma;
|
|
9
4
|
}
|
|
10
5
|
|
|
11
|
-
async
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return entity;
|
|
6
|
+
async findById(id) {
|
|
7
|
+
return this.prisma.<%= camelName %>s.findUnique({
|
|
8
|
+
where: { id },
|
|
9
|
+
});
|
|
16
10
|
}
|
|
17
11
|
|
|
18
|
-
async
|
|
19
|
-
return this.
|
|
12
|
+
async create(data) {
|
|
13
|
+
return this.prisma.<%= camelName %>s.create({ data });
|
|
20
14
|
}
|
|
21
15
|
}
|
|
22
|
-
|
|
23
|
-
module.exports = <%= className %>RepositoryImpl;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
class
|
|
2
|
-
async
|
|
1
|
+
export default class <%= className %>Repository {
|
|
2
|
+
async findById(id) {
|
|
3
3
|
throw new Error('Method not implemented.');
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
async
|
|
6
|
+
async create(data) {
|
|
7
7
|
throw new Error('Method not implemented.');
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
module.exports = I<%= className %>Repository;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const router = Router();
|
|
5
|
-
|
|
6
|
-
router.
|
|
7
|
-
|
|
1
|
+
// routes
|
|
2
|
+
export default ({ <%= camelName %>Controller }) => {
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
router.get('/', <%= camelName %>Controller.index.bind(<%= camelName %>Controller));
|
|
7
|
+
|
|
8
8
|
return router;
|
|
9
9
|
};
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
class <%=
|
|
2
|
-
constructor({ <%= camelName %>Repository }) {
|
|
1
|
+
export default class <%= className %>UseCase {
|
|
2
|
+
constructor({ <%= camelName %>Repository, jwtService }) {
|
|
3
3
|
this.<%= camelName %>Repository = <%= camelName %>Repository;
|
|
4
|
+
this.jwtService = jwtService;
|
|
4
5
|
}
|
|
5
6
|
|
|
6
|
-
async execute(
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const entity = await this.<%= camelName %>Repository.save(dto);
|
|
11
|
-
return entity;
|
|
7
|
+
async execute(inputData) {
|
|
8
|
+
// Logic for <%= className %>UseCase
|
|
9
|
+
const result = await this.<%= camelName %>Repository.create(inputData);
|
|
10
|
+
return { success: true, data: result };
|
|
12
11
|
}
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
module.exports = <%= useCaseClassName %>;
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import <%= className %>UseCase from './<%= className %>UseCase.js';
|
|
2
3
|
|
|
3
|
-
describe('<%=
|
|
4
|
+
describe('<%= className %>UseCase', () => {
|
|
4
5
|
let useCase;
|
|
5
6
|
let mockRepository;
|
|
6
7
|
|
|
7
8
|
beforeEach(() => {
|
|
8
9
|
mockRepository = {
|
|
9
|
-
|
|
10
|
+
create: jest.fn()
|
|
10
11
|
};
|
|
11
|
-
useCase = new <%=
|
|
12
|
+
useCase = new <%= className %>UseCase({
|
|
13
|
+
<%= camelName %>Repository: mockRepository,
|
|
14
|
+
jwtService: {}
|
|
15
|
+
});
|
|
12
16
|
});
|
|
13
17
|
|
|
14
18
|
describe('execute', () => {
|
|
15
|
-
it('should
|
|
19
|
+
it('should call repository create and return success', async () => {
|
|
16
20
|
const mockDto = { name: 'Test' };
|
|
17
|
-
const
|
|
18
|
-
mockRepository.
|
|
21
|
+
const mockResult = { id: 1, ...mockDto };
|
|
22
|
+
mockRepository.create.mockResolvedValue(mockResult);
|
|
19
23
|
|
|
20
24
|
const result = await useCase.execute(mockDto);
|
|
21
25
|
|
|
22
|
-
expect(mockRepository.
|
|
23
|
-
expect(result).toEqual(
|
|
26
|
+
expect(mockRepository.create).toHaveBeenCalledWith(mockDto);
|
|
27
|
+
expect(result).toEqual({ success: true, data: mockResult });
|
|
24
28
|
});
|
|
25
29
|
});
|
|
26
30
|
});
|