@currentjs/gen 0.3.1 → 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.
- package/CHANGELOG.md +8 -289
- package/README.md +623 -427
- package/dist/cli.js +2 -1
- package/dist/commands/commit.js +25 -42
- package/dist/commands/createApp.js +1 -0
- package/dist/commands/createModule.js +151 -45
- package/dist/commands/diff.js +27 -40
- package/dist/commands/generateAll.js +141 -291
- package/dist/commands/migrateCommit.js +6 -18
- package/dist/commands/migratePush.d.ts +1 -0
- package/dist/commands/migratePush.js +135 -0
- package/dist/commands/migrateUpdate.d.ts +1 -0
- package/dist/commands/migrateUpdate.js +147 -0
- package/dist/commands/newGenerateAll.d.ts +4 -0
- package/dist/commands/newGenerateAll.js +336 -0
- package/dist/generators/controllerGenerator.d.ts +43 -19
- package/dist/generators/controllerGenerator.js +547 -329
- package/dist/generators/domainLayerGenerator.d.ts +21 -0
- package/dist/generators/domainLayerGenerator.js +276 -0
- package/dist/generators/dtoGenerator.d.ts +21 -0
- package/dist/generators/dtoGenerator.js +518 -0
- package/dist/generators/newControllerGenerator.d.ts +55 -0
- package/dist/generators/newControllerGenerator.js +644 -0
- package/dist/generators/newServiceGenerator.d.ts +19 -0
- package/dist/generators/newServiceGenerator.js +266 -0
- package/dist/generators/newStoreGenerator.d.ts +39 -0
- package/dist/generators/newStoreGenerator.js +408 -0
- package/dist/generators/newTemplateGenerator.d.ts +29 -0
- package/dist/generators/newTemplateGenerator.js +510 -0
- package/dist/generators/serviceGenerator.d.ts +16 -51
- package/dist/generators/serviceGenerator.js +167 -586
- package/dist/generators/storeGenerator.d.ts +35 -32
- package/dist/generators/storeGenerator.js +291 -238
- package/dist/generators/storeGeneratorV2.d.ts +31 -0
- package/dist/generators/storeGeneratorV2.js +190 -0
- package/dist/generators/templateGenerator.d.ts +21 -21
- package/dist/generators/templateGenerator.js +393 -268
- package/dist/generators/templates/appTemplates.d.ts +3 -1
- package/dist/generators/templates/appTemplates.js +15 -10
- package/dist/generators/templates/data/appYamlTemplate +5 -2
- package/dist/generators/templates/data/cursorRulesTemplate +315 -221
- package/dist/generators/templates/data/frontendScriptTemplate +76 -47
- package/dist/generators/templates/data/mainViewTemplate +1 -1
- package/dist/generators/templates/data/systemTsTemplate +5 -0
- package/dist/generators/templates/index.d.ts +0 -3
- package/dist/generators/templates/index.js +0 -3
- package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
- package/dist/generators/templates/newStoreTemplates.js +141 -0
- package/dist/generators/templates/storeTemplates.d.ts +1 -5
- package/dist/generators/templates/storeTemplates.js +102 -219
- package/dist/generators/templates/viewTemplates.js +1 -1
- package/dist/generators/useCaseGenerator.d.ts +13 -0
- package/dist/generators/useCaseGenerator.js +188 -0
- package/dist/types/configTypes.d.ts +148 -0
- package/dist/types/configTypes.js +10 -0
- package/dist/utils/childEntityUtils.d.ts +18 -0
- package/dist/utils/childEntityUtils.js +78 -0
- package/dist/utils/commandUtils.d.ts +43 -0
- package/dist/utils/commandUtils.js +124 -0
- package/dist/utils/commitUtils.d.ts +4 -1
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +13 -1
- package/dist/utils/diResolver.d.ts +32 -0
- package/dist/utils/diResolver.js +204 -0
- package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
- package/dist/utils/new_parts_of_migrationUtils.js +164 -0
- package/dist/utils/typeUtils.d.ts +19 -0
- package/dist/utils/typeUtils.js +70 -0
- 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
|
|
55
|
-
- Configure
|
|
56
|
-
- Set up
|
|
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/
|
|
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?
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
type: string
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
316
|
+
useCase: Cat:list
|
|
310
317
|
view: catList
|
|
318
|
+
auth: all
|
|
311
319
|
- path: /create
|
|
312
|
-
|
|
320
|
+
method: GET
|
|
313
321
|
view: catCreate
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
322
|
+
auth: authenticated
|
|
323
|
+
Person:
|
|
324
|
+
prefix: /person
|
|
325
|
+
layout: main_view
|
|
326
|
+
pages:
|
|
318
327
|
- path: /
|
|
319
|
-
|
|
328
|
+
useCase: Person:list
|
|
320
329
|
view: personList
|
|
330
|
+
auth: all
|
|
321
331
|
- path: /create
|
|
322
|
-
|
|
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/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
type: string
|
|
370
|
-
required: true
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
│ ├──
|
|
487
|
+
│ ├── yourmodule.yaml # Module specification
|
|
463
488
|
│ ├── domain/
|
|
464
|
-
│ │
|
|
489
|
+
│ │ ├── entities/ # Domain models (aggregates)
|
|
490
|
+
│ │ └── valueObjects/ # Value objects
|
|
465
491
|
│ ├── application/
|
|
466
|
-
│ │ ├──
|
|
467
|
-
│ │
|
|
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 `
|
|
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
|
-
├──
|
|
607
|
+
├── yourmodule.yaml # ← This is where you define everything
|
|
488
608
|
├── domain/
|
|
489
|
-
│
|
|
609
|
+
│ ├── entities/ # Generated domain models (aggregates)
|
|
610
|
+
│ └── valueObjects/ # Generated value objects
|
|
490
611
|
├── application/
|
|
491
|
-
│ ├──
|
|
492
|
-
│
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
required: true
|
|
510
|
-
|
|
511
|
-
type:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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`/`
|
|
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
|
-
-
|
|
786
|
+
- Fields without `required` are optional
|
|
608
787
|
|
|
609
788
|
```yaml
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
required: true
|
|
616
|
-
|
|
617
|
-
type:
|
|
618
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
required: true
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
type:
|
|
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
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
required: true
|
|
774
|
-
|
|
775
|
-
type:
|
|
776
|
-
|
|
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
|
-
###
|
|
936
|
+
### Use Case Handlers Explained
|
|
796
937
|
|
|
797
|
-
**🔄 Handler vs
|
|
938
|
+
**🔄 Handler vs Use Case Distinction:**
|
|
798
939
|
- **Handler**: Creates a separate service method (one handler = one method)
|
|
799
|
-
- **
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
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
|
|
816
|
-
When
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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,
|
|
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
|
-
//
|
|
982
|
+
// input = parsed input DTO
|
|
836
983
|
}
|
|
837
984
|
|
|
838
|
-
//
|
|
839
|
-
async get(
|
|
840
|
-
const
|
|
841
|
-
const
|
|
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** (
|
|
849
|
-
- **Custom handlers**: Receive `(result,
|
|
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
|
-
- `
|
|
997
|
+
- `input`: Parsed input DTO
|
|
852
998
|
|
|
853
999
|
**Handler Format Examples:**
|
|
854
1000
|
```yaml
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
-
|
|
904
|
-
-
|
|
905
|
-
-
|
|
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
|
-
###
|
|
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
|
-
|
|
914
|
-
|
|
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
|
|
918
|
-
- `toast` -
|
|
919
|
-
- `back` - Navigate back in browser history
|
|
920
|
-
- `
|
|
921
|
-
- `
|
|
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
|
-
**
|
|
926
|
-
|
|
927
|
-
|
|
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
|
-
|
|
931
|
-
|
|
1108
|
+
**Common Combinations:**
|
|
1109
|
+
```yaml
|
|
1110
|
+
# Show message and go back
|
|
1111
|
+
onSuccess: { toast: "Saved!", back: true }
|
|
932
1112
|
|
|
933
|
-
#
|
|
934
|
-
|
|
1113
|
+
# Redirect after creation
|
|
1114
|
+
onSuccess: { redirect: /posts/:id, toast: "Created!" }
|
|
935
1115
|
|
|
936
|
-
#
|
|
937
|
-
|
|
1116
|
+
# Stay on page with toast
|
|
1117
|
+
onSuccess: { stay: true, toast: "Updated!" }
|
|
938
1118
|
```
|
|
939
1119
|
|
|
940
|
-
|
|
1120
|
+
The template generator converts `onSuccess` options into `data-strategy` attributes on HTML forms for the frontend JavaScript to handle.
|
|
941
1121
|
|
|
942
|
-
|
|
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
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
|
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
|
|
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
|
|