@diagramers/cli 1.0.17 → 1.0.19
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/README.md +393 -98
- package/dist/commands/api.d.ts.map +1 -1
- package/dist/commands/api.js +18 -0
- package/dist/commands/api.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/services/relation-generator.d.ts +3 -0
- package/dist/services/relation-generator.d.ts.map +1 -0
- package/dist/services/relation-generator.js +212 -0
- package/dist/services/relation-generator.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/api.ts +18 -0
- package/src/index.ts +1 -0
- package/src/services/relation-generator.ts +203 -0
package/README.md
CHANGED
@@ -1,195 +1,490 @@
|
|
1
1
|
# @diagramers/cli
|
2
2
|
|
3
|
-
|
3
|
+
A powerful command-line interface for rapid API development with TypeScript, Node.js, and MongoDB. Streamline your development workflow with automated code generation, project scaffolding, and database management.
|
4
4
|
|
5
|
-
## 🚀
|
5
|
+
## 🚀 Features
|
6
|
+
|
7
|
+
- **Project Initialization**: Create new API and admin projects from templates
|
8
|
+
- **Code Generation**: Generate complete CRUD modules, database tables, and relationships
|
9
|
+
- **Project Management**: Update and extend existing projects with new features
|
10
|
+
- **Database Management**: Create tables, relationships, and manage database context
|
11
|
+
- **Template Processing**: Customize project templates for different environments
|
12
|
+
|
13
|
+
## 📦 Installation
|
6
14
|
|
7
15
|
```bash
|
8
16
|
npm install -g @diagramers/cli
|
9
17
|
```
|
10
18
|
|
11
|
-
##
|
19
|
+
## 🎯 Quick Start
|
20
|
+
|
21
|
+
```bash
|
22
|
+
# Create a new API project
|
23
|
+
diagramers init api my-ecommerce-api
|
24
|
+
|
25
|
+
# Navigate to your project
|
26
|
+
cd my-ecommerce-api
|
27
|
+
|
28
|
+
# Generate a complete product module
|
29
|
+
diagramers api generate:module product
|
30
|
+
|
31
|
+
# Generate database tables
|
32
|
+
diagramers api generate:table category
|
33
|
+
diagramers api generate:table brand
|
34
|
+
|
35
|
+
# Create relationships between tables
|
36
|
+
diagramers api generate:relation product category
|
37
|
+
diagramers api generate:relation product brand
|
38
|
+
```
|
39
|
+
|
40
|
+
## 📋 Command Reference
|
12
41
|
|
13
|
-
###
|
42
|
+
### Project Initialization
|
14
43
|
|
15
44
|
```bash
|
16
|
-
# Create new API project
|
17
|
-
diagramers init api
|
45
|
+
# Create new API project with TypeScript, MongoDB, and Express
|
46
|
+
diagramers init api <project-name>
|
18
47
|
|
19
|
-
# Create new admin project
|
20
|
-
diagramers init admin
|
48
|
+
# Create new admin dashboard project
|
49
|
+
diagramers init admin <project-name>
|
21
50
|
```
|
22
51
|
|
23
|
-
|
52
|
+
**What's included:**
|
53
|
+
- TypeScript configuration
|
54
|
+
- Express.js server setup
|
55
|
+
- MongoDB with Mongoose
|
56
|
+
- Firebase Functions integration
|
57
|
+
- Socket.io for real-time features
|
58
|
+
- Authentication system
|
59
|
+
- Email and SMS services
|
60
|
+
- Audit logging
|
61
|
+
- Environment configurations
|
62
|
+
|
63
|
+
### Project Updates
|
24
64
|
|
25
65
|
```bash
|
26
|
-
# Update
|
66
|
+
# Update with latest features
|
27
67
|
diagramers update
|
28
68
|
|
29
|
-
# Update with backup
|
69
|
+
# Update with backup (recommended)
|
30
70
|
diagramers update --backup
|
31
71
|
|
32
|
-
# Force update (
|
72
|
+
# Force update (overwrites conflicts)
|
33
73
|
diagramers update --force
|
34
74
|
```
|
35
75
|
|
36
|
-
###
|
76
|
+
### Project Extensions
|
37
77
|
|
38
78
|
```bash
|
39
79
|
# List available features
|
40
80
|
diagramers extend --list
|
41
81
|
|
42
|
-
# Add authentication
|
82
|
+
# Add authentication system
|
43
83
|
diagramers extend --feature auth
|
44
84
|
|
45
|
-
# Add email
|
85
|
+
# Add email service
|
46
86
|
diagramers extend --feature email
|
47
87
|
|
48
88
|
# Add WebSocket support
|
49
89
|
diagramers extend --feature socket
|
50
90
|
|
51
|
-
# Add cron
|
91
|
+
# Add cron job system
|
52
92
|
diagramers extend --feature cron
|
53
93
|
|
54
94
|
# Add audit logging
|
55
95
|
diagramers extend --feature audit
|
56
96
|
```
|
57
97
|
|
58
|
-
|
98
|
+
## 🔧 API Development Commands
|
59
99
|
|
60
|
-
|
100
|
+
### Generate Complete Module
|
61
101
|
|
62
|
-
|
63
|
-
Creates a complete CRUD module with entity, schema, service, controller, and routes:
|
102
|
+
Creates a full CRUD module with all layers:
|
64
103
|
|
65
104
|
```bash
|
66
|
-
|
105
|
+
diagramers api generate:module <name>
|
106
|
+
```
|
107
|
+
|
108
|
+
**Example:**
|
109
|
+
```bash
|
67
110
|
diagramers api generate:module product
|
111
|
+
```
|
68
112
|
|
69
|
-
|
70
|
-
|
113
|
+
**Generated Files:**
|
114
|
+
```
|
115
|
+
src/
|
116
|
+
├── entities/product.ts # TypeScript interface
|
117
|
+
├── schemas/product.ts # Mongoose schema
|
118
|
+
├── services/product-service.ts # Business logic
|
119
|
+
├── controllers/product-controller.ts # Request handlers
|
120
|
+
└── routes/product-routes.ts # API endpoints
|
71
121
|
```
|
72
122
|
|
73
|
-
**
|
74
|
-
-
|
75
|
-
-
|
76
|
-
-
|
77
|
-
-
|
78
|
-
-
|
79
|
-
|
123
|
+
**API Endpoints Created:**
|
124
|
+
- `GET /api/products` - List all products
|
125
|
+
- `GET /api/products/:id` - Get product by ID
|
126
|
+
- `POST /api/products` - Create new product
|
127
|
+
- `PUT /api/products/:id` - Update product
|
128
|
+
- `DELETE /api/products/:id` - Delete product
|
129
|
+
|
130
|
+
### Generate Database Table
|
80
131
|
|
81
|
-
|
82
|
-
|
132
|
+
Creates only the database layer (entity and schema):
|
133
|
+
|
134
|
+
```bash
|
135
|
+
diagramers api generate:table <name>
|
136
|
+
```
|
83
137
|
|
138
|
+
**Example:**
|
84
139
|
```bash
|
85
|
-
# Generate a category table
|
86
140
|
diagramers api generate:table category
|
141
|
+
diagramers api generate:table brand
|
142
|
+
diagramers api generate:table inventory
|
143
|
+
```
|
87
144
|
|
88
|
-
|
89
|
-
|
145
|
+
**Generated Files:**
|
146
|
+
```
|
147
|
+
src/
|
148
|
+
├── entities/category.ts # TypeScript interface
|
149
|
+
└── schemas/category.ts # Mongoose schema
|
90
150
|
```
|
91
151
|
|
92
|
-
**
|
93
|
-
-
|
94
|
-
-
|
95
|
-
-
|
152
|
+
**Use Cases:**
|
153
|
+
- Lookup tables (categories, brands, statuses)
|
154
|
+
- Configuration tables (settings, preferences)
|
155
|
+
- Reference data (countries, currencies)
|
156
|
+
- Internal data models
|
96
157
|
|
97
|
-
|
98
|
-
|
158
|
+
### Generate Table Relationships
|
159
|
+
|
160
|
+
Creates relationships between existing tables:
|
99
161
|
|
100
162
|
```bash
|
101
|
-
|
102
|
-
diagramers api process:template my-custom-api
|
163
|
+
diagramers api generate:relation <table1> <table2> [type]
|
103
164
|
```
|
104
165
|
|
105
|
-
**
|
106
|
-
-
|
107
|
-
-
|
108
|
-
-
|
109
|
-
|
110
|
-
|
166
|
+
**Relationship Types:**
|
167
|
+
- `one-to-one` (default)
|
168
|
+
- `one-to-many`
|
169
|
+
- `many-to-many`
|
170
|
+
|
171
|
+
**Examples:**
|
172
|
+
```bash
|
173
|
+
# One-to-many: Product belongs to Category
|
174
|
+
diagramers api generate:relation product category one-to-many
|
111
175
|
|
112
|
-
|
176
|
+
# Many-to-many: Product has many Tags
|
177
|
+
diagramers api generate:relation product tag many-to-many
|
113
178
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
- When you need both database and API endpoints
|
179
|
+
# One-to-one: User has one Profile
|
180
|
+
diagramers api generate:relation user profile one-to-one
|
181
|
+
```
|
118
182
|
|
119
|
-
|
120
|
-
-
|
121
|
-
-
|
122
|
-
-
|
123
|
-
-
|
183
|
+
**What it does:**
|
184
|
+
- Updates entity interfaces with relationship fields
|
185
|
+
- Updates schemas with references and indexes
|
186
|
+
- Adds population methods to services
|
187
|
+
- Updates database context
|
124
188
|
|
125
|
-
###
|
126
|
-
- Customizing a new API project
|
127
|
-
- Setting up project-specific configurations
|
128
|
-
- Preparing for deployment with custom names
|
189
|
+
### Process Template
|
129
190
|
|
130
|
-
|
191
|
+
Customize your project for different environments:
|
131
192
|
|
132
193
|
```bash
|
133
|
-
|
134
|
-
|
135
|
-
cd diagramers-cli
|
194
|
+
diagramers api process:template <project-name>
|
195
|
+
```
|
136
196
|
|
137
|
-
|
138
|
-
|
197
|
+
**Example:**
|
198
|
+
```bash
|
199
|
+
diagramers api process:template ecommerce-production
|
200
|
+
```
|
139
201
|
|
140
|
-
|
141
|
-
|
202
|
+
**What it updates:**
|
203
|
+
- Package.json name and description
|
204
|
+
- README.md title and description
|
205
|
+
- Database names in configuration files
|
206
|
+
- Environment-specific settings
|
207
|
+
- Creates .env.example template
|
142
208
|
|
143
|
-
|
144
|
-
npm run dev
|
209
|
+
## 🏗️ Project Structure
|
145
210
|
|
146
|
-
|
147
|
-
npm link
|
211
|
+
After using the CLI, your project will have this structure:
|
148
212
|
|
149
|
-
|
150
|
-
|
213
|
+
```
|
214
|
+
src/
|
215
|
+
├── entities/ # TypeScript interfaces
|
216
|
+
│ ├── user.ts
|
217
|
+
│ ├── product.ts
|
218
|
+
│ └── category.ts
|
219
|
+
├── schemas/ # Mongoose schemas
|
220
|
+
│ ├── user.ts
|
221
|
+
│ ├── product.ts
|
222
|
+
│ └── category.ts
|
223
|
+
├── services/ # Business logic layer
|
224
|
+
│ ├── user-service.ts
|
225
|
+
│ ├── product-service.ts
|
226
|
+
│ └── category-service.ts
|
227
|
+
├── controllers/ # Request handlers
|
228
|
+
│ ├── user-controller.ts
|
229
|
+
│ ├── product-controller.ts
|
230
|
+
│ └── category-controller.ts
|
231
|
+
├── routes/ # API endpoints
|
232
|
+
│ ├── user-routes.ts
|
233
|
+
│ ├── product-routes.ts
|
234
|
+
│ ├── category-routes.ts
|
235
|
+
│ └── index.ts
|
236
|
+
├── helpers/ # Utilities and helpers
|
237
|
+
│ ├── dbcontext.ts # Database context
|
238
|
+
│ ├── auth.ts
|
239
|
+
│ ├── mailer.ts
|
240
|
+
│ └── result.ts
|
241
|
+
└── config/ # Environment configurations
|
242
|
+
├── development.ts
|
243
|
+
├── staging.ts
|
244
|
+
└── production.ts
|
151
245
|
```
|
152
246
|
|
153
|
-
##
|
247
|
+
## 🎯 Real-World Examples
|
154
248
|
|
155
|
-
###
|
249
|
+
### E-commerce API Development
|
156
250
|
|
157
251
|
```bash
|
158
|
-
# 1. Initialize
|
159
|
-
diagramers init api
|
160
|
-
|
161
|
-
# 2. Navigate to the project
|
162
|
-
cd my-ecommerce-api
|
252
|
+
# 1. Initialize project
|
253
|
+
diagramers init api ecommerce-api
|
254
|
+
cd ecommerce-api
|
163
255
|
|
164
|
-
#
|
256
|
+
# 2. Generate main entities with full CRUD
|
165
257
|
diagramers api generate:module product
|
166
258
|
diagramers api generate:module user
|
167
259
|
diagramers api generate:module order
|
168
260
|
|
169
|
-
#
|
261
|
+
# 3. Generate supporting tables
|
170
262
|
diagramers api generate:table category
|
171
263
|
diagramers api generate:table brand
|
264
|
+
diagramers api generate:table tag
|
172
265
|
diagramers api generate:table shipping_method
|
266
|
+
diagramers api generate:table payment_method
|
173
267
|
|
174
|
-
#
|
268
|
+
# 4. Create relationships
|
269
|
+
diagramers api generate:relation product category one-to-many
|
270
|
+
diagramers api generate:relation product brand one-to-many
|
271
|
+
diagramers api generate:relation product tag many-to-many
|
272
|
+
diagramers api generate:relation order user one-to-many
|
273
|
+
diagramers api generate:relation order shipping_method one-to-one
|
274
|
+
|
275
|
+
# 5. Customize for production
|
175
276
|
diagramers api process:template ecommerce-production
|
176
277
|
```
|
177
278
|
|
178
|
-
|
279
|
+
### Blog API Development
|
280
|
+
|
281
|
+
```bash
|
282
|
+
# 1. Initialize project
|
283
|
+
diagramers init api blog-api
|
284
|
+
cd blog-api
|
179
285
|
|
180
|
-
|
286
|
+
# 2. Generate main entities
|
287
|
+
diagramers api generate:module post
|
288
|
+
diagramers api generate:module user
|
289
|
+
diagramers api generate:module comment
|
181
290
|
|
291
|
+
# 3. Generate supporting tables
|
292
|
+
diagramers api generate:table category
|
293
|
+
diagramers api generate:table tag
|
294
|
+
|
295
|
+
# 4. Create relationships
|
296
|
+
diagramers api generate:relation post user one-to-many
|
297
|
+
diagramers api generate:relation post category one-to-many
|
298
|
+
diagramers api generate:relation post tag many-to-many
|
299
|
+
diagramers api generate:relation comment post one-to-many
|
300
|
+
diagramers api generate:relation comment user one-to-many
|
182
301
|
```
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
302
|
+
|
303
|
+
## 🔄 Database Relationships
|
304
|
+
|
305
|
+
### One-to-Many Relationship
|
306
|
+
|
307
|
+
```typescript
|
308
|
+
// Product belongs to Category
|
309
|
+
interface IProduct {
|
310
|
+
_id: ObjectId;
|
311
|
+
name: string;
|
312
|
+
categoryId: ObjectId; // Reference to category
|
313
|
+
category?: ICategory; // Populated field
|
314
|
+
}
|
315
|
+
|
316
|
+
interface ICategory {
|
317
|
+
_id: ObjectId;
|
318
|
+
name: string;
|
319
|
+
products?: IProduct[]; // Virtual field
|
320
|
+
}
|
321
|
+
```
|
322
|
+
|
323
|
+
### Many-to-Many Relationship
|
324
|
+
|
325
|
+
```typescript
|
326
|
+
// Product has many Tags
|
327
|
+
interface IProduct {
|
328
|
+
_id: ObjectId;
|
329
|
+
name: string;
|
330
|
+
tagIds: ObjectId[]; // Array of references
|
331
|
+
tags?: ITag[]; // Populated field
|
332
|
+
}
|
333
|
+
|
334
|
+
interface ITag {
|
335
|
+
_id: ObjectId;
|
336
|
+
name: string;
|
337
|
+
productIds: ObjectId[]; // Array of references
|
338
|
+
products?: IProduct[]; // Virtual field
|
339
|
+
}
|
340
|
+
```
|
341
|
+
|
342
|
+
### One-to-One Relationship
|
343
|
+
|
344
|
+
```typescript
|
345
|
+
// User has one Profile
|
346
|
+
interface IUser {
|
347
|
+
_id: ObjectId;
|
348
|
+
email: string;
|
349
|
+
profileId: ObjectId; // Reference to profile
|
350
|
+
profile?: IProfile; // Populated field
|
351
|
+
}
|
352
|
+
|
353
|
+
interface IProfile {
|
354
|
+
_id: ObjectId;
|
355
|
+
firstName: string;
|
356
|
+
lastName: string;
|
357
|
+
userId: ObjectId; // Reference back to user
|
358
|
+
user?: IUser; // Populated field
|
359
|
+
}
|
360
|
+
```
|
361
|
+
|
362
|
+
## 🛠️ Development Workflow
|
363
|
+
|
364
|
+
### 1. Project Setup
|
365
|
+
```bash
|
366
|
+
# Create new project
|
367
|
+
diagramers init api my-api
|
368
|
+
cd my-api
|
369
|
+
|
370
|
+
# Install dependencies
|
371
|
+
npm install
|
372
|
+
|
373
|
+
# Start development server
|
374
|
+
npm run serve
|
375
|
+
```
|
376
|
+
|
377
|
+
### 2. Database Design
|
378
|
+
```bash
|
379
|
+
# Generate main entities
|
380
|
+
diagramers api generate:module user
|
381
|
+
diagramers api generate:module product
|
382
|
+
|
383
|
+
# Generate supporting tables
|
384
|
+
diagramers api generate:table category
|
385
|
+
diagramers api generate:table brand
|
386
|
+
|
387
|
+
# Create relationships
|
388
|
+
diagramers api generate:relation product category one-to-many
|
389
|
+
diagramers api generate:relation product brand one-to-many
|
390
|
+
```
|
391
|
+
|
392
|
+
### 3. API Development
|
393
|
+
```bash
|
394
|
+
# Test your endpoints
|
395
|
+
curl http://localhost:4000/api/products
|
396
|
+
curl http://localhost:4000/api/categories
|
191
397
|
```
|
192
398
|
|
399
|
+
### 4. Production Deployment
|
400
|
+
```bash
|
401
|
+
# Process template for production
|
402
|
+
diagramers api process:template my-api-production
|
403
|
+
|
404
|
+
# Build and deploy
|
405
|
+
npm run build:prod
|
406
|
+
npm run deploy
|
407
|
+
```
|
408
|
+
|
409
|
+
## 🔧 Configuration
|
410
|
+
|
411
|
+
### Environment Variables
|
412
|
+
|
413
|
+
The CLI creates a comprehensive `.env.example` file with:
|
414
|
+
|
415
|
+
```env
|
416
|
+
# Server Configuration
|
417
|
+
PORT=4000
|
418
|
+
HOST=localhost
|
419
|
+
|
420
|
+
# Database Configuration
|
421
|
+
MONGODB_URI=mongodb://127.0.0.1:27017/my-api-development
|
422
|
+
|
423
|
+
# Firebase Configuration
|
424
|
+
FIREBASE_API_KEY=your-api-key
|
425
|
+
FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
|
426
|
+
|
427
|
+
# JWT Configuration
|
428
|
+
JWT_SECRET=your-jwt-secret-key
|
429
|
+
|
430
|
+
# Email Configuration
|
431
|
+
SMTP_HOST=smtp.gmail.com
|
432
|
+
SMTP_PORT=587
|
433
|
+
SMTP_USER=your-email@gmail.com
|
434
|
+
SMTP_PASS=your-app-password
|
435
|
+
```
|
436
|
+
|
437
|
+
### Database Configuration
|
438
|
+
|
439
|
+
The CLI automatically configures different database names for each environment:
|
440
|
+
|
441
|
+
- **Development**: `my-api-development`
|
442
|
+
- **Staging**: `my-api-staging`
|
443
|
+
- **Production**: `my-api-production`
|
444
|
+
|
445
|
+
## 🚀 Best Practices
|
446
|
+
|
447
|
+
### 1. Naming Conventions
|
448
|
+
- Use singular names for entities: `product`, `user`, `category`
|
449
|
+
- Use descriptive names: `user_profile`, `product_inventory`
|
450
|
+
- Follow camelCase for multi-word names
|
451
|
+
|
452
|
+
### 2. Database Design
|
453
|
+
- Generate tables first, then relationships
|
454
|
+
- Use `generate:table` for lookup data
|
455
|
+
- Use `generate:module` for main business entities
|
456
|
+
- Always create relationships after both tables exist
|
457
|
+
|
458
|
+
### 3. API Development
|
459
|
+
- Test endpoints after generation
|
460
|
+
- Customize generated code for your business logic
|
461
|
+
- Add validation and error handling
|
462
|
+
- Implement proper authentication and authorization
|
463
|
+
|
464
|
+
### 4. Project Organization
|
465
|
+
- Keep related files together
|
466
|
+
- Use consistent naming patterns
|
467
|
+
- Document your API endpoints
|
468
|
+
- Version your API properly
|
469
|
+
|
470
|
+
## 🤝 Contributing
|
471
|
+
|
472
|
+
1. Fork the repository
|
473
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
474
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
475
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
476
|
+
5. Open a Pull Request
|
477
|
+
|
193
478
|
## 📄 License
|
194
479
|
|
195
|
-
MIT
|
480
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
481
|
+
|
482
|
+
## 🆘 Support
|
483
|
+
|
484
|
+
- **Documentation**: [GitHub Wiki](https://github.com/diagramers/diagramers-cli/wiki)
|
485
|
+
- **Issues**: [GitHub Issues](https://github.com/diagramers/diagramers-cli/issues)
|
486
|
+
- **Discussions**: [GitHub Discussions](https://github.com/diagramers/diagramers-cli/discussions)
|
487
|
+
|
488
|
+
---
|
489
|
+
|
490
|
+
**Built with ❤️ by the Diagramers Team**
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/commands/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/commands/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,WAoE1C"}
|
package/dist/commands/api.js
CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.apiCommand = apiCommand;
|
7
7
|
const api_generator_1 = require("../services/api-generator");
|
8
8
|
const table_generator_1 = require("../services/table-generator");
|
9
|
+
const relation_generator_1 = require("../services/relation-generator");
|
9
10
|
const template_processor_1 = require("../services/template-processor");
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
11
12
|
function apiCommand(program) {
|
@@ -57,6 +58,23 @@ function apiCommand(program) {
|
|
57
58
|
process.exit(1);
|
58
59
|
}
|
59
60
|
});
|
61
|
+
// Generate relation command
|
62
|
+
api
|
63
|
+
.command('generate:relation <table1> <table2> [type]')
|
64
|
+
.description('Generate a relationship between two existing tables')
|
65
|
+
.option('-t, --type <type>', 'Relationship type: one-to-one, one-to-many, many-to-many', 'one-to-one')
|
66
|
+
.action(async (table1, table2, options) => {
|
67
|
+
try {
|
68
|
+
const relationType = options.type || 'one-to-one';
|
69
|
+
console.log(chalk_1.default.blue(`🔗 Creating ${relationType} relationship between ${table1} and ${table2}...`));
|
70
|
+
await (0, relation_generator_1.generateRelation)(table1, table2, relationType);
|
71
|
+
console.log(chalk_1.default.green(`✅ Relationship created successfully!`));
|
72
|
+
}
|
73
|
+
catch (error) {
|
74
|
+
console.error(chalk_1.default.red(`❌ Error creating relationship: ${error.message}`));
|
75
|
+
process.exit(1);
|
76
|
+
}
|
77
|
+
});
|
60
78
|
return api;
|
61
79
|
}
|
62
80
|
//# sourceMappingURL=api.js.map
|
package/dist/commands/api.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/commands/api.ts"],"names":[],"mappings":";;;;;
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/commands/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
@@ -25,6 +25,7 @@ Examples:
|
|
25
25
|
$ diagramers extend --feature auth
|
26
26
|
$ diagramers api generate:module product
|
27
27
|
$ diagramers api generate:table category
|
28
|
+
$ diagramers api generate:relation product category one-to-many
|
28
29
|
$ diagramers api process:template my-api-project
|
29
30
|
`);
|
30
31
|
program.parse();
|
package/dist/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,0CAA8C;AAC9C,8CAAkD;AAClD,8CAAkD;AAClD,wCAA4C;AAG5C,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,eAAe;AACf,IAAA,kBAAW,EAAC,OAAO,CAAC,CAAC;AACrB,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;AACvB,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;AACvB,IAAA,gBAAU,EAAC,OAAO,CAAC,CAAC;AAEpB,gBAAgB;AAChB,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,0CAA8C;AAC9C,8CAAkD;AAClD,8CAAkD;AAClD,wCAA4C;AAG5C,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,eAAe;AACf,IAAA,kBAAW,EAAC,OAAO,CAAC,CAAC;AACrB,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;AACvB,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;AACvB,IAAA,gBAAU,EAAC,OAAO,CAAC,CAAC;AAEpB,gBAAgB;AAChB,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE;;;;;;;;;;CAU5B,CAAC,CAAC;AAEH,OAAO,CAAC,KAAK,EAAE,CAAC;AAEhB,uCAAuC;AACvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAClC,OAAO,CAAC,UAAU,EAAE,CAAC;AACvB,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"relation-generator.d.ts","sourceRoot":"","sources":["../../src/services/relation-generator.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,cAAc,CAAC;AAEzE,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAE,YAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyC/H"}
|
@@ -0,0 +1,212 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
19
|
+
var ownKeys = function(o) {
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
21
|
+
var ar = [];
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
23
|
+
return ar;
|
24
|
+
};
|
25
|
+
return ownKeys(o);
|
26
|
+
};
|
27
|
+
return function (mod) {
|
28
|
+
if (mod && mod.__esModule) return mod;
|
29
|
+
var result = {};
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
31
|
+
__setModuleDefault(result, mod);
|
32
|
+
return result;
|
33
|
+
};
|
34
|
+
})();
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
36
|
+
exports.generateRelation = generateRelation;
|
37
|
+
const fs = __importStar(require("fs"));
|
38
|
+
const path = __importStar(require("path"));
|
39
|
+
async function generateRelation(table1, table2, relationType = 'one-to-one') {
|
40
|
+
// Validate table names
|
41
|
+
if (!table1 || !table2 || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(table1) || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(table2)) {
|
42
|
+
throw new Error('Invalid table names. Use only letters, numbers, hyphens, and underscores. Must start with a letter.');
|
43
|
+
}
|
44
|
+
const table1Capitalized = table1.charAt(0).toUpperCase() + table1.slice(1);
|
45
|
+
const table2Capitalized = table2.charAt(0).toUpperCase() + table2.slice(1);
|
46
|
+
const currentDir = process.cwd();
|
47
|
+
// Check for required API project structure
|
48
|
+
const requiredDirs = ['src/entities', 'src/schemas'];
|
49
|
+
const missingDirs = requiredDirs.filter(dir => !fs.existsSync(path.join(currentDir, dir)));
|
50
|
+
if (missingDirs.length > 0) {
|
51
|
+
throw new Error(`This command should be run from a diagramers API project. Missing directories: ${missingDirs.join(', ')}`);
|
52
|
+
}
|
53
|
+
// Check if both tables exist
|
54
|
+
const table1EntityPath = path.join(currentDir, 'src/entities', `${table1}.ts`);
|
55
|
+
const table2EntityPath = path.join(currentDir, 'src/entities', `${table2}.ts`);
|
56
|
+
const table1SchemaPath = path.join(currentDir, 'src/schemas', `${table1}.ts`);
|
57
|
+
const table2SchemaPath = path.join(currentDir, 'src/schemas', `${table2}.ts`);
|
58
|
+
if (!fs.existsSync(table1EntityPath) || !fs.existsSync(table2EntityPath)) {
|
59
|
+
throw new Error(`Both tables must exist before creating relationships. Missing: ${!fs.existsSync(table1EntityPath) ? table1 : ''} ${!fs.existsSync(table2EntityPath) ? table2 : ''}`);
|
60
|
+
}
|
61
|
+
console.log(`🔗 Creating ${relationType} relationship between ${table1} and ${table2}...`);
|
62
|
+
// Update entities
|
63
|
+
console.log('📝 Updating entities...');
|
64
|
+
updateEntity(table1EntityPath, table1Capitalized, table2Capitalized, relationType, 'forward');
|
65
|
+
updateEntity(table2EntityPath, table2Capitalized, table1Capitalized, relationType, 'reverse');
|
66
|
+
// Update schemas
|
67
|
+
console.log('📋 Updating schemas...');
|
68
|
+
updateSchema(table1SchemaPath, table1Capitalized, table2Capitalized, relationType, 'forward');
|
69
|
+
updateSchema(table2SchemaPath, table2Capitalized, table1Capitalized, relationType, 'reverse');
|
70
|
+
console.log('✅ Relationship created successfully!');
|
71
|
+
console.log(`🔗 ${table1} ↔ ${table2} (${relationType})`);
|
72
|
+
}
|
73
|
+
function updateEntity(entityPath, tableCapitalized, relatedTableCapitalized, relationType, direction) {
|
74
|
+
let entityContent = fs.readFileSync(entityPath, 'utf8');
|
75
|
+
// Add import for the related interface
|
76
|
+
const importStatement = `import { I${relatedTableCapitalized} } from './${direction === 'forward' ? relatedTableCapitalized.toLowerCase() : relatedTableCapitalized.toLowerCase()}';`;
|
77
|
+
if (!entityContent.includes(importStatement)) {
|
78
|
+
// Find the last import statement and add after it
|
79
|
+
const importRegex = /import.*from.*['"];?\s*$/gm;
|
80
|
+
const matches = [...entityContent.matchAll(importRegex)];
|
81
|
+
if (matches.length > 0) {
|
82
|
+
const lastImport = matches[matches.length - 1];
|
83
|
+
const insertIndex = lastImport.index + lastImport[0].length;
|
84
|
+
entityContent = entityContent.slice(0, insertIndex) + '\n' + importStatement + entityContent.slice(insertIndex);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
// Add relationship fields based on type
|
88
|
+
const interfaceRegex = /export interface I\w+ extends mongoose\.Document \{([^}]*)\}/s;
|
89
|
+
const match = entityContent.match(interfaceRegex);
|
90
|
+
if (match) {
|
91
|
+
const interfaceContent = match[1];
|
92
|
+
const newFields = generateEntityFields(tableCapitalized, relatedTableCapitalized, relationType, direction);
|
93
|
+
// Add new fields before the closing brace
|
94
|
+
const updatedInterfaceContent = interfaceContent.trim() + '\n ' + newFields;
|
95
|
+
entityContent = entityContent.replace(interfaceRegex, `export interface I${tableCapitalized} extends mongoose.Document {$1${updatedInterfaceContent}\n}`);
|
96
|
+
}
|
97
|
+
fs.writeFileSync(entityPath, entityContent);
|
98
|
+
}
|
99
|
+
function updateSchema(schemaPath, tableCapitalized, relatedTableCapitalized, relationType, direction) {
|
100
|
+
let schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
101
|
+
// Add import for the related schema
|
102
|
+
const importStatement = `import { ${relatedTableCapitalized}Entity } from './${direction === 'forward' ? relatedTableCapitalized.toLowerCase() : relatedTableCapitalized.toLowerCase()}';`;
|
103
|
+
if (!schemaContent.includes(importStatement)) {
|
104
|
+
// Find the last import statement and add after it
|
105
|
+
const importRegex = /import.*from.*['"];?\s*$/gm;
|
106
|
+
const matches = [...schemaContent.matchAll(importRegex)];
|
107
|
+
if (matches.length > 0) {
|
108
|
+
const lastImport = matches[matches.length - 1];
|
109
|
+
const insertIndex = lastImport.index + lastImport[0].length;
|
110
|
+
schemaContent = schemaContent.slice(0, insertIndex) + '\n' + importStatement + schemaContent.slice(insertIndex);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
// Add schema fields based on relationship type
|
114
|
+
const schemaRegex = /export const \w+Schema = new mongoose\.Schema\(([^)]*),/s;
|
115
|
+
const match = schemaContent.match(schemaRegex);
|
116
|
+
if (match) {
|
117
|
+
const schemaFields = match[1];
|
118
|
+
const newFields = generateSchemaFields(tableCapitalized, relatedTableCapitalized, relationType, direction);
|
119
|
+
// Add new fields before the closing brace
|
120
|
+
const updatedSchemaFields = schemaFields.trim() + '\n ' + newFields;
|
121
|
+
schemaContent = schemaContent.replace(schemaRegex, `export const ${tableCapitalized.toLowerCase()}Schema = new mongoose.Schema(${updatedSchemaFields},`);
|
122
|
+
}
|
123
|
+
// Add virtual fields and population methods
|
124
|
+
const modelRegex = /export const \w+Entity = mongoose\.model<.*>\(.*\);?\s*$/;
|
125
|
+
const virtualFields = generateVirtualFields(tableCapitalized, relatedTableCapitalized, relationType, direction);
|
126
|
+
if (virtualFields) {
|
127
|
+
schemaContent = schemaContent.replace(modelRegex, `${virtualFields}\n\n$&`);
|
128
|
+
}
|
129
|
+
fs.writeFileSync(schemaPath, schemaContent);
|
130
|
+
}
|
131
|
+
function generateEntityFields(tableCapitalized, relatedTableCapitalized, relationType, direction) {
|
132
|
+
switch (relationType) {
|
133
|
+
case 'one-to-one':
|
134
|
+
if (direction === 'forward') {
|
135
|
+
return `_${relatedTableCapitalized.toLowerCase()}Id: ObjectId,\n ${relatedTableCapitalized.toLowerCase()}?: I${relatedTableCapitalized}`;
|
136
|
+
}
|
137
|
+
else {
|
138
|
+
return `_${tableCapitalized.toLowerCase()}Id: ObjectId,\n ${tableCapitalized.toLowerCase()}?: I${tableCapitalized}`;
|
139
|
+
}
|
140
|
+
case 'one-to-many':
|
141
|
+
if (direction === 'forward') {
|
142
|
+
return `${relatedTableCapitalized.toLowerCase()}Id: ObjectId,\n ${relatedTableCapitalized.toLowerCase()}?: I${relatedTableCapitalized}`;
|
143
|
+
}
|
144
|
+
else {
|
145
|
+
return `${tableCapitalized.toLowerCase()}s?: I${tableCapitalized}[]`;
|
146
|
+
}
|
147
|
+
case 'many-to-many':
|
148
|
+
if (direction === 'forward') {
|
149
|
+
return `${relatedTableCapitalized.toLowerCase()}Ids: ObjectId[],\n ${relatedTableCapitalized.toLowerCase()}s?: I${relatedTableCapitalized}[]`;
|
150
|
+
}
|
151
|
+
else {
|
152
|
+
return `${tableCapitalized.toLowerCase()}Ids: ObjectId[],\n ${tableCapitalized.toLowerCase()}s?: I${tableCapitalized}[]`;
|
153
|
+
}
|
154
|
+
default:
|
155
|
+
return '';
|
156
|
+
}
|
157
|
+
}
|
158
|
+
function generateSchemaFields(tableCapitalized, relatedTableCapitalized, relationType, direction) {
|
159
|
+
switch (relationType) {
|
160
|
+
case 'one-to-one':
|
161
|
+
if (direction === 'forward') {
|
162
|
+
return `${relatedTableCapitalized.toLowerCase()}Id: {\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${relatedTableCapitalized.toLowerCase()}',\n required: false\n }`;
|
163
|
+
}
|
164
|
+
else {
|
165
|
+
return `${tableCapitalized.toLowerCase()}Id: {\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${tableCapitalized.toLowerCase()}',\n required: false\n }`;
|
166
|
+
}
|
167
|
+
case 'one-to-many':
|
168
|
+
if (direction === 'forward') {
|
169
|
+
return `${relatedTableCapitalized.toLowerCase()}Id: {\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${relatedTableCapitalized.toLowerCase()}',\n required: false\n }`;
|
170
|
+
}
|
171
|
+
else {
|
172
|
+
return ''; // No field needed for reverse one-to-many
|
173
|
+
}
|
174
|
+
case 'many-to-many':
|
175
|
+
if (direction === 'forward') {
|
176
|
+
return `${relatedTableCapitalized.toLowerCase()}Ids: [{\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${relatedTableCapitalized.toLowerCase()}'\n }]`;
|
177
|
+
}
|
178
|
+
else {
|
179
|
+
return `${tableCapitalized.toLowerCase()}Ids: [{\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${tableCapitalized.toLowerCase()}'\n }]`;
|
180
|
+
}
|
181
|
+
default:
|
182
|
+
return '';
|
183
|
+
}
|
184
|
+
}
|
185
|
+
function generateVirtualFields(tableCapitalized, relatedTableCapitalized, relationType, direction) {
|
186
|
+
switch (relationType) {
|
187
|
+
case 'one-to-one':
|
188
|
+
if (direction === 'forward') {
|
189
|
+
return `// Virtual populate for ${relatedTableCapitalized.toLowerCase()}\n${tableCapitalized.toLowerCase()}Schema.virtual('${relatedTableCapitalized.toLowerCase()}', {\n ref: '${relatedTableCapitalized.toLowerCase()}',\n localField: '${relatedTableCapitalized.toLowerCase()}Id',\n foreignField: '_id',\n justOne: true\n});`;
|
190
|
+
}
|
191
|
+
else {
|
192
|
+
return `// Virtual populate for ${tableCapitalized.toLowerCase()}\n${tableCapitalized.toLowerCase()}Schema.virtual('${tableCapitalized.toLowerCase()}', {\n ref: '${tableCapitalized.toLowerCase()}',\n localField: '${tableCapitalized.toLowerCase()}Id',\n foreignField: '_id',\n justOne: true\n});`;
|
193
|
+
}
|
194
|
+
case 'one-to-many':
|
195
|
+
if (direction === 'forward') {
|
196
|
+
return `// Virtual populate for ${relatedTableCapitalized.toLowerCase()}\n${tableCapitalized.toLowerCase()}Schema.virtual('${relatedTableCapitalized.toLowerCase()}', {\n ref: '${relatedTableCapitalized.toLowerCase()}',\n localField: '${relatedTableCapitalized.toLowerCase()}Id',\n foreignField: '_id',\n justOne: true\n});`;
|
197
|
+
}
|
198
|
+
else {
|
199
|
+
return `// Virtual populate for ${tableCapitalized.toLowerCase()}s\n${tableCapitalized.toLowerCase()}Schema.virtual('${tableCapitalized.toLowerCase()}s', {\n ref: '${tableCapitalized.toLowerCase()}',\n localField: '_id',\n foreignField: '${tableCapitalized.toLowerCase()}Id'\n});`;
|
200
|
+
}
|
201
|
+
case 'many-to-many':
|
202
|
+
if (direction === 'forward') {
|
203
|
+
return `// Virtual populate for ${relatedTableCapitalized.toLowerCase()}s\n${tableCapitalized.toLowerCase()}Schema.virtual('${relatedTableCapitalized.toLowerCase()}s', {\n ref: '${relatedTableCapitalized.toLowerCase()}',\n localField: '${relatedTableCapitalized.toLowerCase()}Ids',\n foreignField: '_id'\n});`;
|
204
|
+
}
|
205
|
+
else {
|
206
|
+
return `// Virtual populate for ${tableCapitalized.toLowerCase()}s\n${tableCapitalized.toLowerCase()}Schema.virtual('${tableCapitalized.toLowerCase()}s', {\n ref: '${tableCapitalized.toLowerCase()}',\n localField: '${tableCapitalized.toLowerCase()}Ids',\n foreignField: '_id'\n});`;
|
207
|
+
}
|
208
|
+
default:
|
209
|
+
return '';
|
210
|
+
}
|
211
|
+
}
|
212
|
+
//# sourceMappingURL=relation-generator.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"relation-generator.js","sourceRoot":"","sources":["../../src/services/relation-generator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/commands/api.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Command } from 'commander';
|
2
2
|
import { generateModule } from '../services/api-generator';
|
3
3
|
import { generateTable } from '../services/table-generator';
|
4
|
+
import { generateRelation } from '../services/relation-generator';
|
4
5
|
import { processTemplate } from '../services/template-processor';
|
5
6
|
import chalk from 'chalk';
|
6
7
|
|
@@ -54,5 +55,22 @@ export function apiCommand(program: Command) {
|
|
54
55
|
}
|
55
56
|
});
|
56
57
|
|
58
|
+
// Generate relation command
|
59
|
+
api
|
60
|
+
.command('generate:relation <table1> <table2> [type]')
|
61
|
+
.description('Generate a relationship between two existing tables')
|
62
|
+
.option('-t, --type <type>', 'Relationship type: one-to-one, one-to-many, many-to-many', 'one-to-one')
|
63
|
+
.action(async (table1: string, table2: string, options: any) => {
|
64
|
+
try {
|
65
|
+
const relationType = options.type || 'one-to-one';
|
66
|
+
console.log(chalk.blue(`🔗 Creating ${relationType} relationship between ${table1} and ${table2}...`));
|
67
|
+
await generateRelation(table1, table2, relationType);
|
68
|
+
console.log(chalk.green(`✅ Relationship created successfully!`));
|
69
|
+
} catch (error: any) {
|
70
|
+
console.error(chalk.red(`❌ Error creating relationship: ${error.message}`));
|
71
|
+
process.exit(1);
|
72
|
+
}
|
73
|
+
});
|
74
|
+
|
57
75
|
return api;
|
58
76
|
}
|
package/src/index.ts
CHANGED
@@ -29,6 +29,7 @@ Examples:
|
|
29
29
|
$ diagramers extend --feature auth
|
30
30
|
$ diagramers api generate:module product
|
31
31
|
$ diagramers api generate:table category
|
32
|
+
$ diagramers api generate:relation product category one-to-many
|
32
33
|
$ diagramers api process:template my-api-project
|
33
34
|
`);
|
34
35
|
|
@@ -0,0 +1,203 @@
|
|
1
|
+
import * as fs from 'fs';
|
2
|
+
import * as path from 'path';
|
3
|
+
|
4
|
+
export type RelationType = 'one-to-one' | 'one-to-many' | 'many-to-many';
|
5
|
+
|
6
|
+
export async function generateRelation(table1: string, table2: string, relationType: RelationType = 'one-to-one'): Promise<void> {
|
7
|
+
// Validate table names
|
8
|
+
if (!table1 || !table2 || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(table1) || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(table2)) {
|
9
|
+
throw new Error('Invalid table names. Use only letters, numbers, hyphens, and underscores. Must start with a letter.');
|
10
|
+
}
|
11
|
+
|
12
|
+
const table1Capitalized = table1.charAt(0).toUpperCase() + table1.slice(1);
|
13
|
+
const table2Capitalized = table2.charAt(0).toUpperCase() + table2.slice(1);
|
14
|
+
const currentDir = process.cwd();
|
15
|
+
|
16
|
+
// Check for required API project structure
|
17
|
+
const requiredDirs = ['src/entities', 'src/schemas'];
|
18
|
+
const missingDirs = requiredDirs.filter(dir => !fs.existsSync(path.join(currentDir, dir)));
|
19
|
+
if (missingDirs.length > 0) {
|
20
|
+
throw new Error(`This command should be run from a diagramers API project. Missing directories: ${missingDirs.join(', ')}`);
|
21
|
+
}
|
22
|
+
|
23
|
+
// Check if both tables exist
|
24
|
+
const table1EntityPath = path.join(currentDir, 'src/entities', `${table1}.ts`);
|
25
|
+
const table2EntityPath = path.join(currentDir, 'src/entities', `${table2}.ts`);
|
26
|
+
const table1SchemaPath = path.join(currentDir, 'src/schemas', `${table1}.ts`);
|
27
|
+
const table2SchemaPath = path.join(currentDir, 'src/schemas', `${table2}.ts`);
|
28
|
+
|
29
|
+
if (!fs.existsSync(table1EntityPath) || !fs.existsSync(table2EntityPath)) {
|
30
|
+
throw new Error(`Both tables must exist before creating relationships. Missing: ${!fs.existsSync(table1EntityPath) ? table1 : ''} ${!fs.existsSync(table2EntityPath) ? table2 : ''}`);
|
31
|
+
}
|
32
|
+
|
33
|
+
console.log(`🔗 Creating ${relationType} relationship between ${table1} and ${table2}...`);
|
34
|
+
|
35
|
+
// Update entities
|
36
|
+
console.log('📝 Updating entities...');
|
37
|
+
updateEntity(table1EntityPath, table1Capitalized, table2Capitalized, relationType, 'forward');
|
38
|
+
updateEntity(table2EntityPath, table2Capitalized, table1Capitalized, relationType, 'reverse');
|
39
|
+
|
40
|
+
// Update schemas
|
41
|
+
console.log('📋 Updating schemas...');
|
42
|
+
updateSchema(table1SchemaPath, table1Capitalized, table2Capitalized, relationType, 'forward');
|
43
|
+
updateSchema(table2SchemaPath, table2Capitalized, table1Capitalized, relationType, 'reverse');
|
44
|
+
|
45
|
+
console.log('✅ Relationship created successfully!');
|
46
|
+
console.log(`🔗 ${table1} ↔ ${table2} (${relationType})`);
|
47
|
+
}
|
48
|
+
|
49
|
+
function updateEntity(entityPath: string, tableCapitalized: string, relatedTableCapitalized: string, relationType: RelationType, direction: 'forward' | 'reverse'): void {
|
50
|
+
let entityContent = fs.readFileSync(entityPath, 'utf8');
|
51
|
+
|
52
|
+
// Add import for the related interface
|
53
|
+
const importStatement = `import { I${relatedTableCapitalized} } from './${direction === 'forward' ? relatedTableCapitalized.toLowerCase() : relatedTableCapitalized.toLowerCase()}';`;
|
54
|
+
if (!entityContent.includes(importStatement)) {
|
55
|
+
// Find the last import statement and add after it
|
56
|
+
const importRegex = /import.*from.*['"];?\s*$/gm;
|
57
|
+
const matches = [...entityContent.matchAll(importRegex)];
|
58
|
+
if (matches.length > 0) {
|
59
|
+
const lastImport = matches[matches.length - 1];
|
60
|
+
const insertIndex = lastImport.index! + lastImport[0].length;
|
61
|
+
entityContent = entityContent.slice(0, insertIndex) + '\n' + importStatement + entityContent.slice(insertIndex);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
// Add relationship fields based on type
|
66
|
+
const interfaceRegex = /export interface I\w+ extends mongoose\.Document \{([^}]*)\}/s;
|
67
|
+
const match = entityContent.match(interfaceRegex);
|
68
|
+
|
69
|
+
if (match) {
|
70
|
+
const interfaceContent = match[1];
|
71
|
+
const newFields = generateEntityFields(tableCapitalized, relatedTableCapitalized, relationType, direction);
|
72
|
+
|
73
|
+
// Add new fields before the closing brace
|
74
|
+
const updatedInterfaceContent = interfaceContent.trim() + '\n ' + newFields;
|
75
|
+
entityContent = entityContent.replace(interfaceRegex, `export interface I${tableCapitalized} extends mongoose.Document {$1${updatedInterfaceContent}\n}`);
|
76
|
+
}
|
77
|
+
|
78
|
+
fs.writeFileSync(entityPath, entityContent);
|
79
|
+
}
|
80
|
+
|
81
|
+
function updateSchema(schemaPath: string, tableCapitalized: string, relatedTableCapitalized: string, relationType: RelationType, direction: 'forward' | 'reverse'): void {
|
82
|
+
let schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
83
|
+
|
84
|
+
// Add import for the related schema
|
85
|
+
const importStatement = `import { ${relatedTableCapitalized}Entity } from './${direction === 'forward' ? relatedTableCapitalized.toLowerCase() : relatedTableCapitalized.toLowerCase()}';`;
|
86
|
+
if (!schemaContent.includes(importStatement)) {
|
87
|
+
// Find the last import statement and add after it
|
88
|
+
const importRegex = /import.*from.*['"];?\s*$/gm;
|
89
|
+
const matches = [...schemaContent.matchAll(importRegex)];
|
90
|
+
if (matches.length > 0) {
|
91
|
+
const lastImport = matches[matches.length - 1];
|
92
|
+
const insertIndex = lastImport.index! + lastImport[0].length;
|
93
|
+
schemaContent = schemaContent.slice(0, insertIndex) + '\n' + importStatement + schemaContent.slice(insertIndex);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
// Add schema fields based on relationship type
|
98
|
+
const schemaRegex = /export const \w+Schema = new mongoose\.Schema\(([^)]*),/s;
|
99
|
+
const match = schemaContent.match(schemaRegex);
|
100
|
+
|
101
|
+
if (match) {
|
102
|
+
const schemaFields = match[1];
|
103
|
+
const newFields = generateSchemaFields(tableCapitalized, relatedTableCapitalized, relationType, direction);
|
104
|
+
|
105
|
+
// Add new fields before the closing brace
|
106
|
+
const updatedSchemaFields = schemaFields.trim() + '\n ' + newFields;
|
107
|
+
schemaContent = schemaContent.replace(schemaRegex, `export const ${tableCapitalized.toLowerCase()}Schema = new mongoose.Schema(${updatedSchemaFields},`);
|
108
|
+
}
|
109
|
+
|
110
|
+
// Add virtual fields and population methods
|
111
|
+
const modelRegex = /export const \w+Entity = mongoose\.model<.*>\(.*\);?\s*$/;
|
112
|
+
const virtualFields = generateVirtualFields(tableCapitalized, relatedTableCapitalized, relationType, direction);
|
113
|
+
|
114
|
+
if (virtualFields) {
|
115
|
+
schemaContent = schemaContent.replace(modelRegex, `${virtualFields}\n\n$&`);
|
116
|
+
}
|
117
|
+
|
118
|
+
fs.writeFileSync(schemaPath, schemaContent);
|
119
|
+
}
|
120
|
+
|
121
|
+
function generateEntityFields(tableCapitalized: string, relatedTableCapitalized: string, relationType: RelationType, direction: 'forward' | 'reverse'): string {
|
122
|
+
switch (relationType) {
|
123
|
+
case 'one-to-one':
|
124
|
+
if (direction === 'forward') {
|
125
|
+
return `_${relatedTableCapitalized.toLowerCase()}Id: ObjectId,\n ${relatedTableCapitalized.toLowerCase()}?: I${relatedTableCapitalized}`;
|
126
|
+
} else {
|
127
|
+
return `_${tableCapitalized.toLowerCase()}Id: ObjectId,\n ${tableCapitalized.toLowerCase()}?: I${tableCapitalized}`;
|
128
|
+
}
|
129
|
+
|
130
|
+
case 'one-to-many':
|
131
|
+
if (direction === 'forward') {
|
132
|
+
return `${relatedTableCapitalized.toLowerCase()}Id: ObjectId,\n ${relatedTableCapitalized.toLowerCase()}?: I${relatedTableCapitalized}`;
|
133
|
+
} else {
|
134
|
+
return `${tableCapitalized.toLowerCase()}s?: I${tableCapitalized}[]`;
|
135
|
+
}
|
136
|
+
|
137
|
+
case 'many-to-many':
|
138
|
+
if (direction === 'forward') {
|
139
|
+
return `${relatedTableCapitalized.toLowerCase()}Ids: ObjectId[],\n ${relatedTableCapitalized.toLowerCase()}s?: I${relatedTableCapitalized}[]`;
|
140
|
+
} else {
|
141
|
+
return `${tableCapitalized.toLowerCase()}Ids: ObjectId[],\n ${tableCapitalized.toLowerCase()}s?: I${tableCapitalized}[]`;
|
142
|
+
}
|
143
|
+
|
144
|
+
default:
|
145
|
+
return '';
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
function generateSchemaFields(tableCapitalized: string, relatedTableCapitalized: string, relationType: RelationType, direction: 'forward' | 'reverse'): string {
|
150
|
+
switch (relationType) {
|
151
|
+
case 'one-to-one':
|
152
|
+
if (direction === 'forward') {
|
153
|
+
return `${relatedTableCapitalized.toLowerCase()}Id: {\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${relatedTableCapitalized.toLowerCase()}',\n required: false\n }`;
|
154
|
+
} else {
|
155
|
+
return `${tableCapitalized.toLowerCase()}Id: {\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${tableCapitalized.toLowerCase()}',\n required: false\n }`;
|
156
|
+
}
|
157
|
+
|
158
|
+
case 'one-to-many':
|
159
|
+
if (direction === 'forward') {
|
160
|
+
return `${relatedTableCapitalized.toLowerCase()}Id: {\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${relatedTableCapitalized.toLowerCase()}',\n required: false\n }`;
|
161
|
+
} else {
|
162
|
+
return ''; // No field needed for reverse one-to-many
|
163
|
+
}
|
164
|
+
|
165
|
+
case 'many-to-many':
|
166
|
+
if (direction === 'forward') {
|
167
|
+
return `${relatedTableCapitalized.toLowerCase()}Ids: [{\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${relatedTableCapitalized.toLowerCase()}'\n }]`;
|
168
|
+
} else {
|
169
|
+
return `${tableCapitalized.toLowerCase()}Ids: [{\n type: mongoose.SchemaTypes.ObjectId,\n ref: '${tableCapitalized.toLowerCase()}'\n }]`;
|
170
|
+
}
|
171
|
+
|
172
|
+
default:
|
173
|
+
return '';
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
function generateVirtualFields(tableCapitalized: string, relatedTableCapitalized: string, relationType: RelationType, direction: 'forward' | 'reverse'): string {
|
178
|
+
switch (relationType) {
|
179
|
+
case 'one-to-one':
|
180
|
+
if (direction === 'forward') {
|
181
|
+
return `// Virtual populate for ${relatedTableCapitalized.toLowerCase()}\n${tableCapitalized.toLowerCase()}Schema.virtual('${relatedTableCapitalized.toLowerCase()}', {\n ref: '${relatedTableCapitalized.toLowerCase()}',\n localField: '${relatedTableCapitalized.toLowerCase()}Id',\n foreignField: '_id',\n justOne: true\n});`;
|
182
|
+
} else {
|
183
|
+
return `// Virtual populate for ${tableCapitalized.toLowerCase()}\n${tableCapitalized.toLowerCase()}Schema.virtual('${tableCapitalized.toLowerCase()}', {\n ref: '${tableCapitalized.toLowerCase()}',\n localField: '${tableCapitalized.toLowerCase()}Id',\n foreignField: '_id',\n justOne: true\n});`;
|
184
|
+
}
|
185
|
+
|
186
|
+
case 'one-to-many':
|
187
|
+
if (direction === 'forward') {
|
188
|
+
return `// Virtual populate for ${relatedTableCapitalized.toLowerCase()}\n${tableCapitalized.toLowerCase()}Schema.virtual('${relatedTableCapitalized.toLowerCase()}', {\n ref: '${relatedTableCapitalized.toLowerCase()}',\n localField: '${relatedTableCapitalized.toLowerCase()}Id',\n foreignField: '_id',\n justOne: true\n});`;
|
189
|
+
} else {
|
190
|
+
return `// Virtual populate for ${tableCapitalized.toLowerCase()}s\n${tableCapitalized.toLowerCase()}Schema.virtual('${tableCapitalized.toLowerCase()}s', {\n ref: '${tableCapitalized.toLowerCase()}',\n localField: '_id',\n foreignField: '${tableCapitalized.toLowerCase()}Id'\n});`;
|
191
|
+
}
|
192
|
+
|
193
|
+
case 'many-to-many':
|
194
|
+
if (direction === 'forward') {
|
195
|
+
return `// Virtual populate for ${relatedTableCapitalized.toLowerCase()}s\n${tableCapitalized.toLowerCase()}Schema.virtual('${relatedTableCapitalized.toLowerCase()}s', {\n ref: '${relatedTableCapitalized.toLowerCase()}',\n localField: '${relatedTableCapitalized.toLowerCase()}Ids',\n foreignField: '_id'\n});`;
|
196
|
+
} else {
|
197
|
+
return `// Virtual populate for ${tableCapitalized.toLowerCase()}s\n${tableCapitalized.toLowerCase()}Schema.virtual('${tableCapitalized.toLowerCase()}s', {\n ref: '${tableCapitalized.toLowerCase()}',\n localField: '${tableCapitalized.toLowerCase()}Ids',\n foreignField: '_id'\n});`;
|
198
|
+
}
|
199
|
+
|
200
|
+
default:
|
201
|
+
return '';
|
202
|
+
}
|
203
|
+
}
|