@currentjs/gen 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +10 -611
  2. package/README.md +623 -427
  3. package/dist/cli.js +2 -1
  4. package/dist/commands/commit.js +25 -42
  5. package/dist/commands/createApp.js +1 -0
  6. package/dist/commands/createModule.js +151 -45
  7. package/dist/commands/diff.js +27 -40
  8. package/dist/commands/generateAll.js +141 -291
  9. package/dist/commands/migrateCommit.js +6 -18
  10. package/dist/commands/migratePush.d.ts +1 -0
  11. package/dist/commands/migratePush.js +135 -0
  12. package/dist/commands/migrateUpdate.d.ts +1 -0
  13. package/dist/commands/migrateUpdate.js +147 -0
  14. package/dist/commands/newGenerateAll.d.ts +4 -0
  15. package/dist/commands/newGenerateAll.js +336 -0
  16. package/dist/generators/controllerGenerator.d.ts +43 -19
  17. package/dist/generators/controllerGenerator.js +547 -329
  18. package/dist/generators/domainLayerGenerator.d.ts +21 -0
  19. package/dist/generators/domainLayerGenerator.js +276 -0
  20. package/dist/generators/dtoGenerator.d.ts +21 -0
  21. package/dist/generators/dtoGenerator.js +518 -0
  22. package/dist/generators/newControllerGenerator.d.ts +55 -0
  23. package/dist/generators/newControllerGenerator.js +644 -0
  24. package/dist/generators/newServiceGenerator.d.ts +19 -0
  25. package/dist/generators/newServiceGenerator.js +266 -0
  26. package/dist/generators/newStoreGenerator.d.ts +39 -0
  27. package/dist/generators/newStoreGenerator.js +408 -0
  28. package/dist/generators/newTemplateGenerator.d.ts +29 -0
  29. package/dist/generators/newTemplateGenerator.js +510 -0
  30. package/dist/generators/serviceGenerator.d.ts +16 -51
  31. package/dist/generators/serviceGenerator.js +167 -586
  32. package/dist/generators/storeGenerator.d.ts +35 -32
  33. package/dist/generators/storeGenerator.js +291 -238
  34. package/dist/generators/storeGeneratorV2.d.ts +31 -0
  35. package/dist/generators/storeGeneratorV2.js +190 -0
  36. package/dist/generators/templateGenerator.d.ts +21 -21
  37. package/dist/generators/templateGenerator.js +393 -268
  38. package/dist/generators/templates/appTemplates.d.ts +3 -1
  39. package/dist/generators/templates/appTemplates.js +15 -10
  40. package/dist/generators/templates/data/appYamlTemplate +5 -2
  41. package/dist/generators/templates/data/cursorRulesTemplate +315 -221
  42. package/dist/generators/templates/data/frontendScriptTemplate +45 -11
  43. package/dist/generators/templates/data/mainViewTemplate +1 -1
  44. package/dist/generators/templates/data/systemTsTemplate +5 -0
  45. package/dist/generators/templates/index.d.ts +0 -3
  46. package/dist/generators/templates/index.js +0 -3
  47. package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
  48. package/dist/generators/templates/newStoreTemplates.js +141 -0
  49. package/dist/generators/templates/storeTemplates.d.ts +1 -5
  50. package/dist/generators/templates/storeTemplates.js +102 -219
  51. package/dist/generators/templates/viewTemplates.js +1 -1
  52. package/dist/generators/useCaseGenerator.d.ts +13 -0
  53. package/dist/generators/useCaseGenerator.js +188 -0
  54. package/dist/types/configTypes.d.ts +148 -0
  55. package/dist/types/configTypes.js +10 -0
  56. package/dist/utils/childEntityUtils.d.ts +18 -0
  57. package/dist/utils/childEntityUtils.js +78 -0
  58. package/dist/utils/commandUtils.d.ts +43 -0
  59. package/dist/utils/commandUtils.js +124 -0
  60. package/dist/utils/commitUtils.d.ts +4 -1
  61. package/dist/utils/constants.d.ts +10 -0
  62. package/dist/utils/constants.js +13 -1
  63. package/dist/utils/diResolver.d.ts +32 -0
  64. package/dist/utils/diResolver.js +204 -0
  65. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  66. package/dist/utils/new_parts_of_migrationUtils.js +164 -0
  67. package/dist/utils/typeUtils.d.ts +19 -0
  68. package/dist/utils/typeUtils.js +70 -0
  69. package/package.json +7 -3
package/README.md CHANGED
@@ -51,9 +51,9 @@ currentjs generate Blog --yaml app.yaml
51
51
  ```
52
52
 
53
53
  3. **Define your module's configuration** in `src/modules/Blog/blog.yaml`:
54
- - Define your data models
55
- - Configure API routes & actions (CRUD is already configured)
56
- - Set up permissions
54
+ - Define your domain (aggregates and value objects)
55
+ - Configure use cases (CRUD is already configured)
56
+ - Set up API and web endpoints with auth
57
57
 
58
58
  4. **Generate TypeScript files**
59
59
  ```bash
@@ -237,11 +237,12 @@ For the cleanest repository experience, add this to your `.gitignore`:
237
237
  ```gitignore
238
238
  # Generated source code (will be recreated from YAML + registry)
239
239
  src/modules/*/domain/entities/*.ts
240
+ src/modules/*/domain/valueObjects/*.ts
241
+ src/modules/*/application/useCases/*.ts
240
242
  src/modules/*/application/services/*.ts
241
- src/modules/*/application/validation/*.ts
243
+ src/modules/*/application/dto/*.ts
242
244
  src/modules/*/infrastructure/controllers/*.ts
243
245
  src/modules/*/infrastructure/stores/*.ts
244
- src/modules/*/infrastructure/interfaces/*.ts
245
246
 
246
247
  # Keep these in version control
247
248
  !*.yaml
@@ -259,91 +260,81 @@ With this setup, your repository stays focused on what matters: your specificati
259
260
 
260
261
  ## Multi-Model Endpoint Support 🔀
261
262
 
262
- Working with multiple related models in a single module? CurrentJS now supports flexible endpoint configurations for multi-model scenarios.
263
-
264
- ### Per-Endpoint Model Override
265
-
266
- Override the model for specific endpoints when you need to mix models within the same API or routes section:
263
+ Working with multiple related models in a single module? In the current YAML format, each model gets its own section in `api` and `web`, keyed by model name:
267
264
 
268
265
  ```yaml
269
- models:
270
- - name: Cat
271
- fields:
272
- - name: name
273
- type: string
274
- - name: Person
275
- fields:
276
- - name: name
277
- type: string
278
- - name: email
279
- type: string
280
-
281
- routes:
282
- prefix: /cat
283
- model: Cat # Default model for this section
284
- endpoints:
285
- - path: /create
286
- action: empty
287
- view: catCreate
288
- # Uses Cat model (from default)
289
-
290
- - path: /createOwner
291
- action: empty
292
- view: ownerCreate
293
- model: Person # Override to use Person model for this endpoint
294
- ```
295
-
296
- **Result**: The `/cat/createOwner` page will generate a form with Person fields (name, email), not Cat fields.
297
-
298
- ### Multiple API/Routes Sections
299
-
300
- Organize endpoints by model or functionality using arrays:
266
+ domain:
267
+ aggregates:
268
+ Cat:
269
+ root: true
270
+ fields:
271
+ name: { type: string, required: true }
272
+ Person:
273
+ root: true
274
+ fields:
275
+ name: { type: string, required: true }
276
+ email: { type: string, required: true }
277
+
278
+ useCases:
279
+ Cat:
280
+ list:
281
+ handlers: [default:list]
282
+ create:
283
+ input: { from: Cat }
284
+ output: { from: Cat }
285
+ handlers: [default:create]
286
+ Person:
287
+ list:
288
+ handlers: [default:list]
289
+ create:
290
+ input: { from: Person }
291
+ output: { from: Person }
292
+ handlers: [default:create]
301
293
 
302
- ```yaml
303
- # Multiple routes sections for different models
304
- routes:
305
- - prefix: /cat
306
- model: Cat
294
+ api:
295
+ Cat:
296
+ prefix: /api/cat
297
+ endpoints:
298
+ - method: GET
299
+ path: /
300
+ useCase: Cat:list
301
+ auth: all
302
+ Person:
303
+ prefix: /api/person
307
304
  endpoints:
305
+ - method: GET
306
+ path: /
307
+ useCase: Person:list
308
+ auth: all
309
+
310
+ web:
311
+ Cat:
312
+ prefix: /cat
313
+ layout: main_view
314
+ pages:
308
315
  - path: /
309
- action: list
316
+ useCase: Cat:list
310
317
  view: catList
318
+ auth: all
311
319
  - path: /create
312
- action: empty
320
+ method: GET
313
321
  view: catCreate
314
-
315
- - prefix: /person
316
- model: Person
317
- endpoints:
322
+ auth: authenticated
323
+ Person:
324
+ prefix: /person
325
+ layout: main_view
326
+ pages:
318
327
  - path: /
319
- action: list
328
+ useCase: Person:list
320
329
  view: personList
330
+ auth: all
321
331
  - path: /create
322
- action: empty
332
+ method: GET
323
333
  view: personCreate
334
+ auth: authenticated
324
335
  ```
325
336
 
326
- **Result**: Generates separate controllers with their own base paths and endpoints.
327
-
328
- ### Automatic Model Inference
329
-
330
- If you don't specify a `model`, the system will infer it from the action handler:
331
-
332
- ```yaml
333
- routes:
334
- prefix: /cat
335
- endpoints:
336
- - path: /createOwner
337
- action: createOwner
338
- view: ownerCreate
339
- # No model specified - inferred from action below
340
-
341
- actions:
342
- createOwner:
343
- handlers: [Person:default:create] # Infers Person model
344
- ```
345
-
346
- **Priority**: `endpoint.model` → inferred from handler → section `model` → first model in array
337
+ **Result**: Generates separate controllers, services, use cases, and stores for each model, each with their own base paths and endpoints.
347
338
 
348
339
  ## Example: Building a Blog System 📝
349
340
 
@@ -358,76 +349,109 @@ currentjs create module Blog
358
349
 
359
350
  ### 2. Define your data model
360
351
  ```yaml
361
- # src/modules/Blog/Blog.yaml
362
- models:
363
- - name: Post
364
- fields:
365
- - name: title
366
- type: string
367
- required: true
368
- - name: content
369
- type: string
370
- required: true
371
- - name: authorEmail
372
- type: string
373
- required: true
374
- - name: publishedAt
375
- type: datetime
376
- required: false
377
- # this part is already generated for you!
352
+ # src/modules/Blog/blog.yaml
353
+ # Only the domain fields need to be customized - the rest is auto-generated!
354
+ domain:
355
+ aggregates:
356
+ Post:
357
+ root: true
358
+ fields:
359
+ title: { type: string, required: true }
360
+ content: { type: string, required: true }
361
+ authorEmail: { type: string, required: true }
362
+ publishedAt: { type: datetime }
363
+
364
+ # Everything below is already generated for you by `currentjs create module`!
365
+ useCases:
366
+ Post:
367
+ list:
368
+ input:
369
+ pagination: { type: offset, defaults: { limit: 20, maxLimit: 100 } }
370
+ output: { from: Post, pagination: true }
371
+ handlers: [default:list]
372
+ get:
373
+ input: { identifier: id }
374
+ output: { from: Post }
375
+ handlers: [default:get]
376
+ create:
377
+ input: { from: Post }
378
+ output: { from: Post }
379
+ handlers: [default:create]
380
+ update:
381
+ input: { identifier: id, from: Post, partial: true }
382
+ output: { from: Post }
383
+ handlers: [default:update]
384
+ delete:
385
+ input: { identifier: id }
386
+ output: void
387
+ handlers: [default:delete]
388
+
378
389
  api:
379
- prefix: /api/posts
380
- model: Post # Optional: specify which model this API serves
381
- endpoints:
382
- - method: GET
383
- path: /
384
- action: list
385
- - method: POST
386
- path: /
387
- action: create
388
- - method: GET
389
- path: /:id
390
- action: get
391
- - method: PUT
392
- path: /:id
393
- action: update
394
- - method: DELETE
395
- path: /:id
396
- action: delete
397
-
398
- routes:
399
- prefix: /posts
400
- model: Post # Optional: specify which model this route serves
401
- strategy: [back, toast]
402
- endpoints:
403
- - path: /
404
- action: list
405
- view: postList
406
- - path: /:id
407
- action: get
408
- view: postDetail
409
- - path: /create
410
- action: empty
411
- view: postCreate
412
- - path: /:id/edit
413
- action: get
414
- view: postUpdate
415
-
416
- actions:
417
- list:
418
- handlers: [Post:default:list]
419
- get:
420
- handlers: [Post:default:get]
421
- create:
422
- handlers: [Post:default:create]
423
- update:
424
- handlers: [Post:default:update]
425
- delete:
426
- handlers: [Post:default:delete]
427
-
428
- permissions: []
429
- ```
430
- > **Note**: All CRUD routes and configurations are automatically generated when you run `currentjs create module Blog`. The only thing is left for you is your data model.
390
+ Post:
391
+ prefix: /api/posts
392
+ endpoints:
393
+ - method: GET
394
+ path: /
395
+ useCase: Post:list
396
+ auth: all
397
+ - method: GET
398
+ path: /:id
399
+ useCase: Post:get
400
+ auth: all
401
+ - method: POST
402
+ path: /
403
+ useCase: Post:create
404
+ auth: authenticated
405
+ - method: PUT
406
+ path: /:id
407
+ useCase: Post:update
408
+ auth: [owner, admin]
409
+ - method: DELETE
410
+ path: /:id
411
+ useCase: Post:delete
412
+ auth: [owner, admin]
413
+
414
+ web:
415
+ Post:
416
+ prefix: /posts
417
+ layout: main_view
418
+ pages:
419
+ - path: /
420
+ useCase: Post:list
421
+ view: postList
422
+ auth: all
423
+ - path: /:id
424
+ useCase: Post:get
425
+ view: postDetail
426
+ auth: all
427
+ - path: /create
428
+ method: GET
429
+ view: postCreate
430
+ auth: authenticated
431
+ - path: /create
432
+ method: POST
433
+ useCase: Post:create
434
+ auth: authenticated
435
+ onSuccess:
436
+ redirect: /posts/:id
437
+ toast: "Post created successfully"
438
+ onError:
439
+ stay: true
440
+ toast: error
441
+ - path: /:id/edit
442
+ method: GET
443
+ useCase: Post:get
444
+ view: postEdit
445
+ auth: [owner, admin]
446
+ - path: /:id/edit
447
+ method: POST
448
+ useCase: Post:update
449
+ auth: [owner, admin]
450
+ onSuccess:
451
+ back: true
452
+ toast: "Post updated successfully"
453
+ ```
454
+ > **Note**: All CRUD use cases, API endpoints, and web pages are automatically generated when you run `currentjs create module Blog`. The only thing left for you is your domain fields.
431
455
 
432
456
  ### 3. Generate everything
433
457
  ```bash
@@ -450,7 +474,8 @@ my-app/
450
474
  ├── tsconfig.json # TypeScript configuration
451
475
  ├── app.yaml # Main application config
452
476
  ├── src/
453
- │ ├── app.ts # Main application entry point
477
+ │ ├── app.ts # Main application entry point (with DI wiring)
478
+ │ ├── system.ts # @Injectable decorator for DI
454
479
  │ ├── common/ # Shared utilities and templates
455
480
  │ │ ├── services/ # Common services
456
481
  │ │ └── ui/
@@ -459,14 +484,16 @@ my-app/
459
484
  │ │ └── error.html # Error page template
460
485
  │ └── modules/ # Your business modules
461
486
  │ └── YourModule/
462
- │ ├── YourModule.yaml # Module specification
487
+ │ ├── yourmodule.yaml # Module specification
463
488
  │ ├── domain/
464
- │ │ └── entities/ # Domain models
489
+ │ │ ├── entities/ # Domain models (aggregates)
490
+ │ │ └── valueObjects/ # Value objects
465
491
  │ ├── application/
466
- │ │ ├── services/ # Business logic
467
- │ │ └── validation/ # Input validation
492
+ │ │ ├── useCases/ # Use case orchestrators
493
+ │ │ ├── services/ # Business logic handlers
494
+ │ │ └── dto/ # Input/Output DTOs
468
495
  │ ├── infrastructure/
469
- │ │ ├── controllers/ # HTTP endpoints
496
+ │ │ ├── controllers/ # HTTP endpoints (API + Web)
470
497
  │ │ └── stores/ # Data access
471
498
  │ └── views/ # HTML templates
472
499
  ├── build/ # Compiled JavaScript
@@ -476,22 +503,117 @@ my-app/
476
503
  ```
477
504
 
478
505
 
506
+ ## Automatic Dependency Injection 🔌
507
+
508
+ The generator includes a **decorator-driven DI system** that automatically wires all your module classes together in `src/app.ts`. No manual instantiation or import management required.
509
+
510
+ ### How It Works
511
+
512
+ 1. **Generated classes are decorated**: Stores, Services, and UseCases get the `@Injectable()` decorator. Controllers use the existing `@Controller()` decorator.
513
+ 2. **Constructor-based discovery**: The generator scans constructors to determine what each class needs (e.g., `InvoiceService` needs `InvoiceStore`).
514
+ 3. **Automatic ordering**: Dependencies are topologically sorted — stores first, then services, then use cases, then controllers.
515
+ 4. **Wiring in `app.ts`**: All imports, instantiations, and the `controllers` array are auto-generated between marker comments.
516
+
517
+ ### The `@Injectable` Decorator
518
+
519
+ Lives in `src/system.ts` (generated with your app, no external dependencies):
520
+
521
+ ```typescript
522
+ export function Injectable() {
523
+ return function (target: any) {
524
+ target.__injectable = true;
525
+ };
526
+ }
527
+ ```
528
+
529
+ Any class decorated with `@Injectable()` will be automatically discovered, instantiated, and injected where needed.
530
+
531
+ ### Adding Custom Injectable Classes
532
+
533
+ If you create a custom class that should participate in DI wiring, just add the `@Injectable()` decorator:
534
+
535
+ ```typescript
536
+ import { Injectable } from '../../../../system';
537
+
538
+ @Injectable()
539
+ export class EmailNotificationService {
540
+ constructor(private invoiceService: InvoiceService) {}
541
+
542
+ async sendInvoiceEmail(invoiceId: number): Promise<void> {
543
+ // ...
544
+ }
545
+ }
546
+ ```
547
+
548
+ On the next `currentjs generate`, this class will be automatically imported and instantiated in `app.ts` with its dependencies resolved.
549
+
550
+ ### Database Providers
551
+
552
+ Database providers are configured in `app.yaml`:
553
+
554
+ ```yaml
555
+ database:
556
+ provider: "@currentjs/provider-mysql" # npm package
557
+ # or:
558
+ provider: "./src/common/SomeProvider" # local file path
559
+ ```
560
+
561
+ Modules can override the global provider with their own:
562
+
563
+ ```yaml
564
+ # In module's yaml section of app.yaml
565
+ modules:
566
+ - name: Analytics
567
+ database:
568
+ provider: "@currentjs/provider-postgres"
569
+ ```
570
+
571
+ Both npm packages and local paths are supported. Stores automatically receive the correct provider instance based on their module's configuration.
572
+
573
+ ### Generated Wiring Example
574
+
575
+ After generation, `src/app.ts` contains auto-generated wiring between markers:
576
+
577
+ ```typescript
578
+ // currentjs:controllers:start
579
+ import { InvoiceStore } from './modules/Invoice/infrastructure/stores/InvoiceStore';
580
+ import { InvoiceService } from './modules/Invoice/application/services/InvoiceService';
581
+ import { InvoiceUseCase } from './modules/Invoice/application/useCases/InvoiceUseCase';
582
+ import { InvoiceApiController } from './modules/Invoice/infrastructure/controllers/InvoiceApiController';
583
+ import { InvoiceWebController } from './modules/Invoice/infrastructure/controllers/InvoiceWebController';
584
+
585
+ const db = new MySQLProvider(config.database);
586
+ const invoiceStore = new InvoiceStore(db);
587
+ const invoiceService = new InvoiceService(invoiceStore);
588
+ const invoiceUseCase = new InvoiceUseCase(invoiceService);
589
+
590
+ const controllers = [
591
+ new InvoiceApiController(invoiceUseCase),
592
+ new InvoiceWebController(invoiceUseCase),
593
+ ];
594
+ // currentjs:controllers:end
595
+ ```
596
+
597
+ This block is fully regenerated on each `currentjs generate` run. You never need to edit it manually.
598
+
479
599
  ## Complete YAML Configuration Guide 📋
480
600
 
481
601
  ### Module Structure Overview
482
602
 
483
- When you create a module, you'll work primarily with the `ModuleName.yaml` file. This file defines everything about your module:
603
+ When you create a module, you'll work primarily with the `modulename.yaml` file. This file defines everything about your module:
484
604
 
485
605
  ```
486
606
  src/modules/YourModule/
487
- ├── YourModule.yaml # ← This is where you define everything
607
+ ├── yourmodule.yaml # ← This is where you define everything
488
608
  ├── domain/
489
- └── entities/ # Generated domain models
609
+ ├── entities/ # Generated domain models (aggregates)
610
+ │ └── valueObjects/ # Generated value objects
490
611
  ├── application/
491
- │ ├── services/ # Generated business logic
492
- └── validation/ # Generated input validation
612
+ │ ├── useCases/ # Generated use case orchestrators
613
+ ├── services/ # Generated business logic handlers
614
+ │ └── dto/ # Generated Input/Output DTOs
493
615
  ├── infrastructure/
494
- │ ├── controllers/ # Generated HTTP endpoints
616
+ │ ├── controllers/ # Generated HTTP endpoints (API + Web)
495
617
  │ └── stores/ # Generated data access
496
618
  └── views/ # Generated HTML templates
497
619
  ```
@@ -501,127 +623,178 @@ src/modules/YourModule/
501
623
  Here's a comprehensive example showing all available configuration options:
502
624
 
503
625
  ```yaml
504
- models:
505
- - name: Post # Entity name (capitalized)
506
- fields:
507
- - name: title # Field name
508
- type: string # Field type: string, number, boolean, datetime
509
- required: true # Validation requirement
510
- - name: content
511
- type: string
512
- required: true
513
- - name: authorId
514
- type: number
515
- required: true
516
- - name: publishedAt
517
- type: datetime
518
- required: false
519
- - name: status
520
- type: string
521
- required: true
522
-
523
- api: # REST API configuration
524
- prefix: /api/posts # Base URL for API endpoints
525
- endpoints:
526
- - method: GET # HTTP method
527
- path: / # Relative path (becomes /api/posts/)
528
- action: list # Action name (references actions section)
529
- - method: GET
530
- path: /:id # Path parameter
531
- action: get
532
- - method: POST
533
- path: /
534
- action: create
535
- - method: PUT
536
- path: /:id
537
- action: update
538
- - method: DELETE
539
- path: /:id
540
- action: delete
541
- - method: POST # Custom endpoint
542
- path: /:id/publish
543
- action: publish
544
-
545
- routes: # Web interface configuration
546
- prefix: /posts # Base URL for web pages
547
- strategy: [toast, back] # Default success strategies for forms
548
- endpoints:
549
- - path: / # List page
550
- action: list
551
- view: postList # Template name
552
- - path: /:id # Detail page
553
- action: get
554
- view: postDetail
555
- - path: /create # Create form page
556
- action: empty # No data loading action
557
- view: postCreate
558
- - path: /:id/edit # Edit form page
559
- action: get # Load existing data
560
- view: postUpdate
561
-
562
- actions: # Business logic mapping
563
- list:
564
- handlers: [Post:default:list] # Use built-in list handler
565
- get:
566
- handlers: [Post:default:get] # Use built-in get handler
567
- create:
568
- handlers: [Post:default:create] # Built-in create
569
- update:
570
- handlers: [Post:default:update]
571
- delete:
572
- handlers: [ # Chain multiple handlers
573
- Post:checkCanDelete, # Custom business logic
574
- Post:default:delete
575
- ]
576
- publish: # Custom action
577
- handlers: [
578
- Post:default:get, # Fetch entity
579
- Post:validateForPublish, # Custom validation
580
- Post:updatePublishStatus # Custom update logic
581
- ]
582
-
583
- permissions: # Role-based access control
584
- - role: all
585
- actions: [list, get] # Anyone (including anonymous)
586
- - role: authenticated
587
- actions: [create] # Must be logged in
588
- - role: owner
589
- actions: [update, publish] # Entity owner permissions
590
- - role: admin
591
- actions: [update, delete, publish] # Admin role permissions
592
- - role: editor
593
- actions: [publish] # Editor role permissions
626
+ domain:
627
+ aggregates:
628
+ Post:
629
+ root: true # Marks as aggregate root
630
+ fields:
631
+ title: { type: string, required: true }
632
+ content: { type: string, required: true }
633
+ authorId: { type: id, required: true }
634
+ publishedAt: { type: datetime }
635
+ status: { type: string, required: true }
636
+
637
+ useCases:
638
+ Post:
639
+ list:
640
+ input:
641
+ pagination: { type: offset, defaults: { limit: 20, maxLimit: 100 } }
642
+ output: { from: Post, pagination: true }
643
+ handlers: [default:list] # Built-in list handler
644
+ get:
645
+ input: { identifier: id }
646
+ output: { from: Post }
647
+ handlers: [default:get] # Built-in get handler
648
+ create:
649
+ input: { from: Post }
650
+ output: { from: Post }
651
+ handlers: [default:create]
652
+ update:
653
+ input: { identifier: id, from: Post, partial: true }
654
+ output: { from: Post }
655
+ handlers: [default:update]
656
+ delete:
657
+ input: { identifier: id }
658
+ output: void
659
+ handlers: [ # Chain multiple handlers
660
+ checkCanDelete, # Custom → PostService.checkCanDelete(result, input)
661
+ default:delete # Built-in delete
662
+ ]
663
+ publish: # Custom action
664
+ input: { identifier: id }
665
+ output: { from: Post }
666
+ handlers: [
667
+ default:get, # Fetch entity
668
+ validateForPublish, # Custom PostService.validateForPublish(result, input)
669
+ updatePublishStatus # Custom PostService.updatePublishStatus(result, input)
670
+ ]
671
+
672
+ api: # REST API configuration
673
+ Post: # Keyed by model name
674
+ prefix: /api/posts
675
+ endpoints:
676
+ - method: GET
677
+ path: /
678
+ useCase: Post:list # References useCases.Post.list
679
+ auth: all # Public access
680
+ - method: GET
681
+ path: /:id
682
+ useCase: Post:get
683
+ auth: all
684
+ - method: POST
685
+ path: /
686
+ useCase: Post:create
687
+ auth: authenticated # Must be logged in
688
+ - method: PUT
689
+ path: /:id
690
+ useCase: Post:update
691
+ auth: [owner, admin] # Owner OR admin (OR logic)
692
+ - method: DELETE
693
+ path: /:id
694
+ useCase: Post:delete
695
+ auth: [owner, admin]
696
+ - method: POST # Custom endpoint
697
+ path: /:id/publish
698
+ useCase: Post:publish
699
+ auth: [owner, editor, admin]
700
+
701
+ web: # Web interface configuration
702
+ Post: # Keyed by model name
703
+ prefix: /posts
704
+ layout: main_view
705
+ pages:
706
+ - path: / # List page
707
+ useCase: Post:list
708
+ view: postList
709
+ auth: all
710
+ - path: /:id # Detail page
711
+ useCase: Post:get
712
+ view: postDetail
713
+ auth: all
714
+ - path: /create # Create form (GET = show form)
715
+ method: GET
716
+ view: postCreate
717
+ auth: authenticated
718
+ - path: /create # Create form (POST = submit)
719
+ method: POST
720
+ useCase: Post:create
721
+ auth: authenticated
722
+ onSuccess:
723
+ redirect: /posts/:id
724
+ toast: "Post created successfully"
725
+ onError:
726
+ stay: true
727
+ toast: error
728
+ - path: /:id/edit # Edit form (GET = show form)
729
+ method: GET
730
+ useCase: Post:get
731
+ view: postUpdate
732
+ auth: [owner, admin]
733
+ - path: /:id/edit # Edit form (POST = submit)
734
+ method: POST
735
+ useCase: Post:update
736
+ auth: [owner, admin]
737
+ onSuccess:
738
+ back: true
739
+ toast: "Post updated successfully"
740
+ ```
741
+
742
+ ### Displaying child entities on root pages (withChild)
743
+
744
+ When you have an aggregate root with child entities (e.g. `Invoice` with `InvoiceItem`), you can show child data on the root’s list or detail page by setting `withChild: true` on the corresponding use case.
745
+
746
+ - **`list` + `withChild: true`**: Adds a link column (e.g. “Items”) on the list page; each row links to that root’s child entity list. No extra data is loaded (good for performance).
747
+ - **`get` + `withChild: true`**: On the root’s detail page, shows a table of child entities below the main card, with links to view/edit each child and to add new ones.
748
+
749
+ If the entity has no child entities, `withChild` is ignored. The parameter defaults to `false` when you run `currentjs create module`.
750
+
751
+ **Example:**
752
+
753
+ ```yaml
754
+ useCases:
755
+ Invoice:
756
+ list:
757
+ withChild: true # Adds link column to child entities on list page
758
+ input:
759
+ pagination: { type: offset, defaults: { limit: 20, maxLimit: 100 } }
760
+ output: { from: Invoice, pagination: true }
761
+ handlers: [default:list]
762
+ get:
763
+ withChild: true # Shows child entities table on detail page
764
+ input: { identifier: id }
765
+ output: { from: Invoice }
766
+ handlers: [default:get]
594
767
  ```
595
768
 
596
769
  ### Field Types and Validation
597
770
 
598
771
  **Available Field Types:**
599
772
  - `string` - Text data (VARCHAR in database)
600
- - `number` - Numeric data (INT/DECIMAL in database)
773
+ - `number` - Numeric data (INT/DECIMAL in database)
774
+ - `integer` - Integer data (INT in database)
775
+ - `decimal` - Decimal data (DECIMAL in database)
601
776
  - `boolean` - True/false values (BOOLEAN in database)
602
777
  - `datetime` - Date and time values (DATETIME in database)
778
+ - `date` - Date values (DATE in database)
779
+ - `id` - Foreign key reference (INT in database)
780
+ - `json` - JSON data
781
+ - `enum` - Enumerated values (use with `values: [...]`)
603
782
 
604
783
  **Important Field Rules:**
605
- - **Never include `id`/`owner_id`/`is_deleted` fields** - these are added automatically
784
+ - **Never include `id`/`owner_id`/`created_at`/`updated_at`/`deleted_at` fields** - these are added automatically
606
785
  - Use `required: true` for mandatory fields
607
- - Use `required: false` for optional fields
786
+ - Fields without `required` are optional
608
787
 
609
788
  ```yaml
610
- models:
611
- - name: User
612
- fields:
613
- - name: email
614
- type: string
615
- required: true
616
- - name: age
617
- type: number
618
- required: false
619
- - name: isActive
620
- type: boolean
621
- required: true
622
- - name: lastLoginAt
623
- type: datetime
624
- required: false
789
+ domain:
790
+ aggregates:
791
+ User:
792
+ root: true
793
+ fields:
794
+ email: { type: string, required: true }
795
+ age: { type: number }
796
+ isActive: { type: boolean, required: true }
797
+ lastLoginAt: { type: datetime }
625
798
  ```
626
799
 
627
800
  ### 🔗 Model Relationships
@@ -630,27 +803,20 @@ You can define relationships between models by specifying another model name as
630
803
 
631
804
  **Basic Relationship Example:**
632
805
  ```yaml
633
- models:
634
- - name: Owner
635
- fields:
636
- - name: name
637
- type: string
638
- required: true
639
- - name: email
640
- type: string
641
- required: true
642
-
643
- - name: Cat
644
- fields:
645
- - name: name
646
- type: string
647
- required: true
648
- - name: breed
649
- type: string
650
- required: false
651
- - name: owner
652
- type: Owner # Relationship to Owner model
653
- required: true
806
+ domain:
807
+ aggregates:
808
+ Owner:
809
+ root: true
810
+ fields:
811
+ name: { type: string, required: true }
812
+ email: { type: string, required: true }
813
+
814
+ Cat:
815
+ root: true
816
+ fields:
817
+ name: { type: string, required: true }
818
+ breed: { type: string }
819
+ owner: { type: Owner, required: true } # Relationship to Owner model
654
820
  ```
655
821
 
656
822
  **Architecture: Rich Domain Models**
@@ -745,42 +911,17 @@ The generator automatically creates foreign key fields following this convention
745
911
 
746
912
  The foreign key always references the `id` field of the related model.
747
913
 
748
- **Optional Configuration:**
749
-
750
- ```yaml
751
- models:
752
- - name: Post
753
- fields:
754
- - name: title
755
- type: string
756
- required: true
757
- - name: author
758
- type: User
759
- required: true
760
- displayFields: [name, email] # Fields to show in dropdowns (optional)
761
- ```
762
-
763
- **Configuration Options:**
764
- - `displayFields`: Array of fields from the related model to display in UI dropdowns (optional, defaults to showing the ID)
765
-
766
914
  **Multiple Relationships:**
767
915
  ```yaml
768
- models:
769
- - name: Comment
770
- fields:
771
- - name: content
772
- type: string
773
- required: true
774
- - name: post
775
- type: Post # Creates foreign key: postId
776
- required: true
777
- - name: author
778
- type: User # Creates foreign key: authorId
779
- required: true
780
- displayFields: [username, email]
781
- - name: parentComment
782
- type: Comment # Self-referential relationship
783
- required: false # Creates foreign key: parentCommentId
916
+ domain:
917
+ aggregates:
918
+ Comment:
919
+ root: true
920
+ fields:
921
+ content: { type: string, required: true }
922
+ post: { type: Post, required: true } # Creates foreign key: postId
923
+ author: { type: User, required: true } # Creates foreign key: authorId
924
+ parentComment: { type: Comment } # Self-referential, optional
784
925
  ```
785
926
 
786
927
  **Relationship Best Practices:**
@@ -792,35 +933,41 @@ models:
792
933
  - ❌ Don't manually add foreign key fields (they're auto-generated)
793
934
  - ❌ Don't create circular dependencies between modules
794
935
 
795
- ### Action Handlers Explained
936
+ ### Use Case Handlers Explained
796
937
 
797
- **🔄 Handler vs Action Distinction:**
938
+ **🔄 Handler vs Use Case Distinction:**
798
939
  - **Handler**: Creates a separate service method (one handler = one method)
799
- - **Action**: Virtual controller concept that calls handler methods step-by-step
940
+ - **Use Case**: Defined under `useCases.ModelName.actionName`, orchestrates handler calls step-by-step
941
+ - **UseCase reference**: Used in `api`/`web` endpoints as `ModelName:actionName` (e.g., `Post:list`)
942
+
943
+ **Built-in Handlers (inside `useCases.*.*.handlers`):**
944
+ - `default:list` - Creates service method with pagination parameters
945
+ - `default:get` - Creates service method named `get` with ID parameter
946
+ - `default:create` - Creates service method with DTO parameter
947
+ - `default:update` - Creates service method with ID and DTO parameters
948
+ - `default:delete` - Creates service method with ID parameter
800
949
 
801
- **Built-in Handlers:**
802
- - `ModelName:default:list` - Creates service method with pagination parameters
803
- - `ModelName:default:get` - Creates service method named `get` with ID parameter
804
- - `ModelName:default:create` - Creates service method with DTO parameter
805
- - `ModelName:default:update` - Creates service method with ID and DTO parameters
806
- - `ModelName:default:delete` - Creates service method with ID parameter
950
+ Note: Handlers within `useCases` do NOT need a model prefix because the model is already the key.
807
951
 
808
952
  **Custom Handlers:**
809
- - `ModelName:customMethodName` - Creates service method that accepts `result, context` parameters
953
+ - `customMethodName` - Creates service method that accepts `(result, input)` parameters
810
954
  - `result`: Result from previous handler (or `null` if it's the first handler)
811
- - `context`: The request context object
955
+ - `input`: The parsed input DTO
812
956
  - User can customize the implementation after generation
813
957
  - Each handler generates a separate method in the service
814
958
 
815
- **🔗 Multiple Handlers per Action:**
816
- When an action has multiple handlers, each handler generates a separate service method, and the controller action calls them sequentially:
959
+ **🔗 Multiple Handlers per Use Case:**
960
+ When a use case has multiple handlers, each handler generates a separate service method, and the use case orchestrator calls them sequentially:
817
961
 
818
962
  ```yaml
819
- actions:
820
- get:
821
- handlers:
822
- - Invoice:default:get # Creates Invoice.get() method
823
- - Invoice:enrichData # Creates Invoice.enrichData() method
963
+ useCases:
964
+ Invoice:
965
+ get:
966
+ input: { identifier: id }
967
+ output: { from: Invoice }
968
+ handlers:
969
+ - default:get # Creates InvoiceService.get() method
970
+ - enrichData # Creates InvoiceService.enrichData() method
824
971
  ```
825
972
 
826
973
  **Generated Code Example:**
@@ -829,138 +976,187 @@ actions:
829
976
  async get(id: number): Promise<Invoice> {
830
977
  // Standard get implementation
831
978
  }
832
- async enrichData(result: any, context: any): Promise<any> {
979
+ async enrichData(result: any, input: any): Promise<any> {
833
980
  // TODO: Implement custom enrichData method
834
981
  // result = result from previous handler (Invoice object in this case)
835
- // context = request context
982
+ // input = parsed input DTO
836
983
  }
837
984
 
838
- // InvoiceController.ts
839
- async get(context: IContext): Promise<Invoice> {
840
- const id = parseInt(context.request.parameters.id as string);
841
- const result1 = await this.invoiceService.get(id);
842
- const result = await this.invoiceService.enrichData(result1, context);
985
+ // InvoiceUseCase.ts
986
+ async get(input: InvoiceGetInput): Promise<Invoice> {
987
+ const result0 = await this.invoiceService.get(input.id);
988
+ const result = await this.invoiceService.enrichData(result0, input);
843
989
  return result; // Returns result from last handler
844
990
  }
845
991
  ```
846
992
 
847
993
  **Parameter Passing Rules:**
848
- - **Default handlers** (`:default:`): Receive standard parameters (id, pagination, DTO, etc.)
849
- - **Custom handlers**: Receive `(result, context)` where:
994
+ - **Default handlers** (`default:*`): Receive standard parameters (id, pagination, DTO, etc.)
995
+ - **Custom handlers**: Receive `(result, input)` where:
850
996
  - `result`: Result from previous handler, or `null` if it's the first handler
851
- - `context`: Request context object
997
+ - `input`: Parsed input DTO
852
998
 
853
999
  **Handler Format Examples:**
854
1000
  ```yaml
855
- actions:
856
- list:
857
- handlers: [Post:default:list] # Single handler: list(page, limit)
858
- get:
859
- handlers: [Post:default:get] # Single handler: get(id)
860
- complexFlow:
861
- handlers: [
862
- Post:default:create, # create(userData) - standard parameters
863
- Post:sendNotification, # sendNotification(result, context) - result from create
864
- Comment:default:create # create(userData) - standard parameters
865
- ]
866
- customFirst:
867
- handlers: [
868
- Post:validateInput, # validateInput(null, context) - first handler
869
- Post:default:create # create(userData) - standard parameters
870
- ]
1001
+ useCases:
1002
+ Post:
1003
+ list:
1004
+ handlers: [default:list] # Single handler: list(page, limit)
1005
+ get:
1006
+ handlers: [default:get] # Single handler: get(id)
1007
+ complexFlow:
1008
+ handlers: [
1009
+ default:create, # create(input) - standard parameters
1010
+ sendNotification # sendNotification(result, input) - result from create
1011
+ ]
1012
+ customFirst:
1013
+ handlers: [
1014
+ validateInput, # validateInput(null, input) - first handler
1015
+ default:create # create(input) - standard parameters
1016
+ ]
871
1017
  ```
872
1018
 
873
1019
  ### Multi-Model Support 🔄
874
1020
 
875
- When you have multiple models in a single module, the system generates individual services, controllers, and stores for each model:
1021
+ When you have multiple models in a single module, the system generates individual services, use cases, controllers, and stores for each model:
876
1022
 
877
1023
  ```yaml
878
- models:
879
- - name: Post
880
- fields: [...]
881
- - name: Comment
882
- fields: [...]
1024
+ domain:
1025
+ aggregates:
1026
+ Post:
1027
+ root: true
1028
+ fields:
1029
+ title: { type: string, required: true }
1030
+ Comment:
1031
+ root: true
1032
+ fields:
1033
+ content: { type: string, required: true }
1034
+ post: { type: Post, required: true }
1035
+
1036
+ useCases:
1037
+ Post:
1038
+ list:
1039
+ handlers: [default:list]
1040
+ create:
1041
+ input: { from: Post }
1042
+ output: { from: Post }
1043
+ handlers: [default:create]
1044
+ Comment:
1045
+ create:
1046
+ input: { from: Comment }
1047
+ output: { from: Comment }
1048
+ handlers: [default:create]
883
1049
 
884
1050
  api:
885
- model: Post # This API serves the Post model
886
- prefix: /api/posts
887
- endpoints: [...]
888
-
889
- actions:
890
- createPost:
891
- handlers: [Post:default:create] # Calls PostService.create()
892
- addComment:
893
- handlers: [Comment:default:create] # Calls CommentService.create()
894
- publishPost:
895
- handlers: [
896
- Post:validateContent, # Calls PostService.validateContent()
897
- Comment:notifySubscribers # Calls CommentService.notifySubscribers()
898
- ]
1051
+ Post:
1052
+ prefix: /api/posts
1053
+ endpoints:
1054
+ - method: GET
1055
+ path: /
1056
+ useCase: Post:list
1057
+ auth: all
1058
+ - method: POST
1059
+ path: /
1060
+ useCase: Post:create
1061
+ auth: authenticated
1062
+ Comment:
1063
+ prefix: /api/comments
1064
+ endpoints:
1065
+ - method: POST
1066
+ path: /
1067
+ useCase: Comment:create
1068
+ auth: authenticated
899
1069
  ```
900
1070
 
901
1071
  **Key Points:**
902
- - Each model gets its own Service, Controller, and Store classes
903
- - Use `model` parameter to specify which model an API/route serves (defaults to first model)
904
- - Use `ModelName:default:action` for built-in operations (list, create, etc.)
905
- - Use `ModelName:customMethod` for custom service methods
906
- - You can mix actions across different models in a single handler chain
1072
+ - Each model gets its own Service, UseCase, Controller, and Store classes
1073
+ - In `api`/`web`, each model is a separate key (e.g., `api.Post`, `api.Comment`)
1074
+ - UseCase references use `ModelName:actionName` format (e.g., `Post:list`, `Comment:create`)
1075
+ - Handlers within `useCases` do not need a model prefix (model is already the key)
907
1076
 
908
- ### Strategy Options for Forms
1077
+ ### Form Success/Error Handling
909
1078
 
910
- Configure what happens after successful form submissions:
1079
+ Configure what happens after successful form submissions using `onSuccess` and `onError` on web page endpoints:
911
1080
 
912
1081
  ```yaml
913
- routes:
914
- strategy: [toast, back] # Show success message and go back
1082
+ web:
1083
+ Post:
1084
+ prefix: /posts
1085
+ pages:
1086
+ - path: /create
1087
+ method: POST
1088
+ useCase: Post:create
1089
+ auth: authenticated
1090
+ onSuccess:
1091
+ redirect: /posts/:id
1092
+ toast: "Post created successfully"
1093
+ onError:
1094
+ stay: true
1095
+ toast: error
915
1096
  ```
916
1097
 
917
- **Available Strategies:**
918
- - `toast` - Success toast notification (most common)
919
- - `back` - Navigate back in browser history
920
- - `message` - Inline success message
921
- - `modal` - Modal success dialog
922
- - `redirect` - Redirect to specific URL
923
- - `refresh` - Reload current page
1098
+ **Available `onSuccess` Options:**
1099
+ - `toast: "message"` - Show toast notification with custom message
1100
+ - `back: true` - Navigate back in browser history
1101
+ - `redirect: /path` - Redirect to specific URL
1102
+ - `stay: true` - Stay on current page
924
1103
 
925
- **Common Strategy Combinations:**
926
- ```yaml
927
- # Most common: Show message and go back
928
- strategy: [toast, back]
1104
+ **Available `onError` Options:**
1105
+ - `stay: true` - Stay on current page (re-render form with errors)
1106
+ - `toast: error` - Show error toast notification
929
1107
 
930
- # Show message and stay on page
931
- strategy: [toast]
1108
+ **Common Combinations:**
1109
+ ```yaml
1110
+ # Show message and go back
1111
+ onSuccess: { toast: "Saved!", back: true }
932
1112
 
933
- # Show modal dialog
934
- strategy: [modal]
1113
+ # Redirect after creation
1114
+ onSuccess: { redirect: /posts/:id, toast: "Created!" }
935
1115
 
936
- # Multiple feedback
937
- strategy: [toast, modal, back]
1116
+ # Stay on page with toast
1117
+ onSuccess: { stay: true, toast: "Updated!" }
938
1118
  ```
939
1119
 
940
- ### Permission System
1120
+ The template generator converts `onSuccess` options into `data-strategy` attributes on HTML forms for the frontend JavaScript to handle.
941
1121
 
942
- Control who can access what actions:
1122
+ ### Auth System
1123
+
1124
+ Control who can access what using the `auth` property on each endpoint in `api` and `web`:
943
1125
 
944
1126
  ```yaml
945
- permissions:
946
- - role: all
947
- actions: [list] # Anyone (including anonymous)
948
- - role: authenticated
949
- actions: [create] # Any logged-in user
950
- - role: owner
951
- actions: [update] # Owner of entity
952
- - role: admin
953
- actions: [update, delete] # Admin permissions
954
- ```
955
-
956
- **Special Roles:**
1127
+ api:
1128
+ Post:
1129
+ prefix: /api/posts
1130
+ endpoints:
1131
+ - method: GET
1132
+ path: /
1133
+ useCase: Post:list
1134
+ auth: all # Anyone (including anonymous)
1135
+ - method: POST
1136
+ path: /
1137
+ useCase: Post:create
1138
+ auth: authenticated # Any logged-in user
1139
+ - method: PUT
1140
+ path: /:id
1141
+ useCase: Post:update
1142
+ auth: [owner, admin] # Owner OR admin (OR logic)
1143
+ - method: DELETE
1144
+ path: /:id
1145
+ useCase: Post:delete
1146
+ auth: admin # Only admin role
1147
+ ```
1148
+
1149
+ **Auth Options:**
957
1150
  - `all` - Everyone (including anonymous users)
958
1151
  - `authenticated` - Any logged-in user
959
1152
  - `owner` - User who created the entity
960
1153
  - `admin`, `editor`, `user` - Custom roles from JWT token
1154
+ - `[owner, admin]` - Array syntax: user must match ANY (OR logic). Privileged roles bypass ownership check.
961
1155
 
962
1156
  **How Ownership Works:**
963
- The system automatically adds an `owner_id` field to track who created each entity. When you use `owner` role, it checks if the current user's ID matches the entity's `owner_id`.
1157
+ The system automatically adds an `owner_id` field to aggregate roots to track who created each entity. When `owner` auth is specified:
1158
+ - **For reads (get)**: Post-fetch check compares `result.ownerId` with `context.request.user.id`
1159
+ - **For mutations (update/delete)**: Pre-mutation check calls `getResourceOwner(id)` before the operation to prevent unauthorized changes
964
1160
 
965
1161
  ### Code vs Configuration Guidelines
966
1162
 
@@ -989,14 +1185,14 @@ This generator is the foundation of the `currentjs` framework:
989
1185
  ## Notes
990
1186
 
991
1187
  - `currentjs create app` scaffolds complete app structure with TypeScript configs and dependencies
992
- - `currentjs generate` creates domain entities, services, controllers, stores, and templates
1188
+ - `currentjs generate` creates domain entities, value objects, use cases, services, DTOs, controllers, stores, and templates
993
1189
  - Generated code follows clean architecture: domain/application/infrastructure layers
994
1190
  - Supports both API endpoints and web page routes in the same module
995
1191
  - Includes change tracking system for safely modifying generated code
996
1192
 
997
1193
  ## Authorship & Contribution
998
1194
 
999
- Vibecoded with `claude-4-sonnet` (mostly) by Konstantin Zavalny. Yes, it is a vibecoded solution, really.
1195
+ Vibecoded mostly with `claude` models by Konstantin Zavalny. Yes, it is a vibecoded solution, really.
1000
1196
 
1001
1197
  Any contributions such as bugfixes, improvements, etc are very welcome.
1002
1198