@currentjs/gen 0.5.1 โ†’ 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,27 @@
1
- # @currentjs/gen ๐Ÿš€
2
-
3
- > *"Because writing boilerplate code is like doing laundry - necessary, tedious, and something a machine should definitely handle for you."*
4
-
5
- A CLI code generator that transforms YAML specifications into fully functional TypeScript applications following clean architecture principles. Think of it as the overly enthusiastic intern who actually enjoys writing controllers, services, and domain models all day long.
6
-
7
- ## Installation ๐Ÿ“ฆ
1
+ # @currentjs/gen
2
+
3
+ A CLI code generator that transforms YAML specifications into fully functional TypeScript applications following clean architecture principles.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Development Flow](#development-flow)
10
+ - [TL;DR](#tldr)
11
+ - [Reference](#reference)
12
+ - [Module Configuration Overview](#module-configuration-overview)
13
+ - [Domain Layer](#domain-layer-domain)
14
+ - [Use Cases Layer](#use-cases-layer-usecases)
15
+ - [API Layer](#api-layer-api)
16
+ - [Web Layer](#web-layer-web)
17
+ - [Generated Source Code](#generated-source-code)
18
+ - [Change Tracking: diff and commit](#change-tracking-diff-and-commit)
19
+ - [Database Migrations](#database-migrations)
20
+ - [Template System](#template-system)
21
+ - [Authorship & Contribution](#authorship--contribution)
22
+ - [License](#license)
23
+
24
+ ## Installation
8
25
 
9
26
  ```bash
10
27
  npm install -g @currentjs/gen
@@ -12,356 +29,233 @@ npm install -g @currentjs/gen
12
29
  npx @currentjs/gen
13
30
  ```
14
31
 
15
- ## Quick Start ๐Ÿƒโ€โ™‚๏ธ
32
+ ## Quick Start
16
33
 
17
- ```bash
18
- # Show help
19
- currentjs --help
34
+ Building an application from scratch:
20
35
 
21
- # Create a new app in the current directory
22
- currentjs create app
23
-
24
- # Create a new app inside a folder
25
- currentjs create app my-app
26
-
27
- # Create a module folder under src/modules
28
- currentjs create module Blog
36
+ 1. Initialize a new project:
29
37
 
30
- # Generate everything from app.yaml
31
- currentjs generate
32
-
33
- # Generate specific module
34
- currentjs generate Blog --yaml app.yaml
38
+ ```
39
+ currentjs init myapp
40
+ cd myapp
35
41
  ```
36
42
 
37
- ## Step-by-Step Development Workflow ๐Ÿ”„
38
-
39
- ### Basic Development Flow
40
-
41
- 1. **Create an empty app**
42
- ```bash
43
- currentjs create app # will create an app inside the current directory
44
- # or:
45
- currentjs create app my-project # will create a directory "my-project" and create an app there
46
- ```
47
-
48
- 2. **Create a new module**
49
- ```bash
50
- currentjs create module Blog
51
- ```
52
-
53
- 3. **Define your module's configuration** in `src/modules/Blog/blog.yaml`:
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
-
58
- 4. **Generate TypeScript files**
59
- ```bash
60
- currentjs generate Blog
61
- ```
62
-
63
- 5. **Make custom changes** (if needed) to:
64
- - Business logic in models
65
- - Custom actions in services
66
- - HTML templates
43
+ 2. Create a module:
67
44
 
68
- 6. **Commit your changes** to preserve them
69
- ```bash
70
- currentjs commit
71
- ```
45
+ ```
46
+ currentjs create module Blog
47
+ ```
72
48
 
73
- ## What Does This Actually Do? ๐Ÿค”
49
+ 3. Run an interactive command:
74
50
 
75
- This generator takes your YAML specifications and creates:
51
+ ```
52
+ currentjs create model Blog:Post
53
+ ```
76
54
 
77
- - **๐Ÿ—๏ธ Complete app structure** with TypeScript, configs, and dependencies
78
- - **๐Ÿ“‹ Domain entities** from your model definitions
79
- - **๐Ÿ”„ Service layer** with business logic and validation
80
- - **๐ŸŽญ Controllers** for both API endpoints and web pages
81
- - **๐Ÿ’พ Data stores** with database provider integration
82
- - **๐ŸŽจ HTML templates** using @currentjs/templating
83
- - **๐Ÿ“Š Change tracking** so you can modify generated code safely
55
+ It will:
56
+ - ask everything it needs,
57
+ - generate yaml config,
58
+ - generate a TypeScript source code,
59
+ - and build it.
84
60
 
85
- ## Commands Reference ๐Ÿ› ๏ธ
61
+ Alternatively, you can:
62
+ - edit the generated module YAML at `src/modules/Blog/blog.yaml`. Define the domain model fields, use cases, API endpoints, and web routes.
63
+ - Generate TypeScript files from the YAML configuration: `currentjs generate Blog`
64
+ - If needed, make manual changes to generated files (domain entities, views, services).
65
+ - Commit those manual changes so they survive regeneration: `currentjs commit`
86
66
 
87
- > See the [HOW TO](howto.md) reference
67
+ To add custom (non-CRUD) behavior: define a method in the service, reference it in the module YAML as a handler, regenerate, and commit.
88
68
 
89
- ### App & Module Creation
90
- ```bash
91
- currentjs create app [name] # Create new application
92
- currentjs create module <name> # Create new module in existing app
93
69
  ```
94
-
95
- ### Code Generation
96
- ```bash
97
- currentjs generate [module] # Generate code from YAML specs
98
- --yaml app.yaml # Specify config file (default: ./app.yaml)
99
- --force # Overwrite files without prompting
100
- --skip # Skip conflicts, never overwrite
70
+ currentjs generate Blog
71
+ currentjs commit
101
72
  ```
102
73
 
103
- ### Advanced Change Management ๐Ÿ”„
104
-
105
- The generator includes a sophisticated change tracking system that **revolutionizes how you work with generated code**:
74
+ ## Development Flow
106
75
 
107
- ```bash
108
- currentjs diff [module] # Show differences between generated and current code
109
- currentjs commit [files...] # Commit your changes to version tracking
110
- currentjs infer --file Entity.ts # Generate YAML model from existing TypeScript
111
- --write # Write back to module YAML file
76
+ ```
77
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
78
+ โ”‚ currentjs init โ”‚
79
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
80
+ โ”‚
81
+ โ–ผ
82
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
83
+ โ”‚ currentjs create moduleโ”‚
84
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
85
+ โ”‚
86
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
87
+ โ”‚ โ”‚
88
+ โ–ผ โ–ผ
89
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
90
+ โ”‚ Edit module YAML โ”‚ โ”‚ currentjs create model โ”‚
91
+ โ”‚ (define structure) โ”‚ โ”‚ (interactive wizard) โ”‚
92
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
93
+ โ”‚ โ”‚
94
+ โ–ผ โ”‚
95
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
96
+ โ”‚ currentjs generateโ”‚ โ”‚
97
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
98
+ โ”‚ โ”‚
99
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
100
+ โ”‚
101
+ โ–ผ
102
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
103
+ โ”‚ Modify generated filesโ”‚
104
+ โ”‚ (entities, views, etc)โ”‚
105
+ โ”‚ (optional step) โ”‚
106
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
107
+ โ”‚
108
+ โ–ผ
109
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
110
+ โ”‚ currentjs commit โ”‚
111
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
112
112
  ```
113
113
 
114
- #### ๐Ÿš€ **The Revolutionary Approach: Version Control Without Generated Code**
114
+ There are two paths after creating a module:
115
115
 
116
- Here's the game-changer: **You don't need to commit generated source code to your repository at all!**
116
+ - **Interactive wizard** (`currentjs create model Blog:Post`) โ€” prompts for fields, use cases, routes, and permissions, then generates everything automatically.
117
+ - **Manual editing** โ€” edit the module YAML by hand, then run `currentjs generate`. This gives full control over every configuration option.
117
118
 
118
- Instead, you only need to track:
119
- - **Your YAML files** (the source of truth)
120
- - **`registry.json`** (change tracking metadata)
121
- - **Your custom modifications** (stored as reusable "patches")
119
+ Both paths converge at the same point: once files are generated, you can optionally customize the generated code (business logic, templates, etc.) and run `currentjs commit` to preserve those changes across future regenerations.
122
120
 
123
- **Traditional Approach** โŒ
124
- ```
125
- git/
126
- โ”œโ”€โ”€ src/modules/Blog/Blog.yaml # Source specification
127
- โ”œโ”€โ”€ src/modules/Blog/domain/entities/Post.ts # Generated + modified
128
- โ”œโ”€โ”€ src/modules/Blog/services/PostService.ts # Generated + modified
129
- โ””โ”€โ”€ ... (hundreds of generated files with custom changes)
130
- ```
121
+ When you need behavior beyond standard CRUD:
131
122
 
132
- **CurrentJS Approach** โœ…
133
- ```
134
- git/
135
- โ”œโ”€โ”€ src/modules/Blog/Blog.yaml # Source specification
136
- โ”œโ”€โ”€ registry.json # Change tracking metadata
137
- โ””โ”€โ”€ .currentjs/commits/ # Your custom modifications as patches
138
- โ”œโ”€โ”€ commit-2024-01-15.json
139
- โ””โ”€โ”€ commit-2024-01-20.json
140
- ```
123
+ 1. Implement the custom method in the generated service class.
124
+ 2. Reference it in the module YAML as a handler (e.g., `service:myMethod`).
125
+ 3. Optionally add API/web endpoints for the new action.
126
+ 4. Regenerate and commit.
141
127
 
142
- #### ๐Ÿ”ง **How It Works (Like Git for Generated Code)**
128
+ #### TLDR
143
129
 
144
- **1. Initial Generation**
145
- ```bash
146
- currentjs generate
147
- # Creates all files and tracks their hashes in registry.json
148
- ```
130
+ - module's YAML configurations (plus "commits") are the source of truth.
131
+ - `currentjs create module` creates module's structure (empty folders, YAML configuration with one model without any fields)
132
+ - `currentjs create model`: You > YAML > `generate`
133
+ - `currentjs generate`: YAML + commits > TypeScript > JS
134
+ - since YAML is the main source of truth, it's also the best place to make changes
135
+ - if changes are beyond configuration (require some coding), the best place is (in descending order): model, service
136
+ - in order to preserve changes in TypeScript files, use `currentjs commit`
137
+ - templates can be changed freely, they are not regenerated by default.
149
138
 
150
- **2. Make Your Custom Changes**
151
- ```typescript
152
- // Edit generated service to add custom logic
153
- export class PostService extends GeneratedPostService {
154
- async publishPost(id: number): Promise<void> {
155
- // Your custom business logic here
156
- const post = await this.getById(id);
157
- post.publishedAt = new Date();
158
- await this.update(id, post);
159
- await this.sendNotificationEmail(post);
160
- }
161
- }
162
- ```
163
139
 
164
- **3. Commit Your Changes**
165
- ```bash
166
- currentjs commit src/modules/Blog/services/PostService.ts
167
- # Saves your modifications as reusable "hunks" (like git patches)
168
- ```
140
+ ## Reference
169
141
 
170
- **4. Regenerate Safely**
171
- ```bash
172
- # Change your YAML specification
173
- currentjs generate --force
174
- # Your custom changes are automatically reapplied to the new generated code!
175
- ```
142
+ For detailed documentation of all CLI commands, YAML configuration options, and field types, see the [Reference](REFERENCE.md).
176
143
 
177
- #### ๐Ÿ“Š **Change Tracking in Action**
144
+ ## Module Configuration Overview
178
145
 
179
- ```bash
180
- # See what's different from generated baseline
181
- currentjs diff Blog
182
- ```
146
+ Each module is configured through a single YAML file located at `src/modules/<Name>/<name>.yaml`. The configuration follows a layered structure inspired by Clean Architecture. Each layer in the YAML maps to a set of generated TypeScript files.
183
147
 
184
- **Sample Output:**
185
- ```diff
186
- [modified] src/modules/Blog/services/PostService.ts
187
-
188
- @@ -15,0 +16,8 @@
189
- +
190
- + async publishPost(id: number): Promise<void> {
191
- + const post = await this.getById(id);
192
- + post.publishedAt = new Date();
193
- + await this.update(id, post);
194
- + await this.sendNotificationEmail(post);
195
- + }
196
- ```
148
+ ### Layers at a Glance
197
149
 
198
- #### ๐ŸŒŸ **Workflow Benefits**
150
+ | YAML Section | Purpose | Generated Files |
151
+ |---|---|---|
152
+ | `domain` | Define your data models (aggregates, value objects) | Entity classes, value object classes |
153
+ | `useCases` | Define business operations, input/output shapes, handler chains | Use case orchestrators, services, DTOs |
154
+ | `api` | Define REST API endpoints | API controller |
155
+ | `web` | Define server-rendered pages and forms | Web controller, HTML templates |
199
156
 
200
- **For Solo Development:**
201
- - Cleaner repositories (no generated code noise)
202
- - Fearless regeneration (your changes are always preserved)
203
- - Clear separation between specifications and implementations
157
+ A minimal module YAML needs at least `domain` and `useCases`. The `api` and `web` sections are optional.
204
158
 
205
- **For Team Development:**
206
- - Merge conflicts only happen in YAML files (much simpler)
207
- - Team members can have different generated code locally
208
- - Changes to business logic are tracked separately from schema changes
209
- - New team members just run `currentjs generate` to get up and running
159
+ โ†’ Reference: [Module Configuration](REFERENCE.md#module-configuration-module-yaml)
210
160
 
211
- **For CI/CD:**
212
- ```bash
213
- # In your deployment pipeline
214
- git clone your-repo
215
- currentjs generate # Recreates all source code from YAML + patches
216
- npm run deploy
217
- ```
161
+ ---
218
162
 
219
- **Sharing Customizations:**
220
- ```bash
221
- # Export your modifications
222
- git add registry.json .currentjs/
223
- git commit -m "Add custom publish functionality"
224
- git push
225
-
226
- # Teammates get your changes
227
- git pull
228
- currentjs generate # Their code automatically includes your customizations
229
- ```
163
+ ### Domain Layer (`domain`)
230
164
 
231
- This change management system solves the age-old problem of "generated code vs. version control" by treating your customizations as first-class citizens while keeping your repository clean and merge-friendly!
165
+ The domain layer defines your data models. There are two kinds: **aggregates** (entities) and **value objects**.
232
166
 
233
- #### ๐Ÿ“ **Recommended .gitignore Setup**
167
+ #### Aggregates
234
168
 
235
- For the cleanest repository experience, add this to your `.gitignore`:
169
+ Aggregates are the main entities. One aggregate should be marked as the root (`root: true`), which enables ownership tracking (auto-generated `ownerId` field).
236
170
 
237
- ```gitignore
238
- # Generated source code (will be recreated from YAML + registry)
239
- src/modules/*/domain/entities/*.ts
240
- src/modules/*/domain/valueObjects/*.ts
241
- src/modules/*/application/useCases/*.ts
242
- src/modules/*/application/services/*.ts
243
- src/modules/*/application/dto/*.ts
244
- src/modules/*/infrastructure/controllers/*.ts
245
- src/modules/*/infrastructure/stores/*.ts
171
+ ```yaml
172
+ domain:
173
+ aggregates:
174
+ Post:
175
+ root: true
176
+ fields:
177
+ title: { type: string, required: true }
178
+ content: { type: string, required: true }
179
+ status: { type: enum, values: [draft, published, archived] }
180
+ publishedAt: { type: datetime }
181
+ ```
246
182
 
247
- # Keep these in version control
248
- !*.yaml
249
- !registry.json
250
- !.currentjs/
183
+ Fields like `id`, `ownerId`, `created_at`, `updated_at`, and `deleted_at` are added automatically โ€” do not include them.
251
184
 
252
- # Standard Node.js ignores
253
- node_modules/
254
- build/
255
- dist/
256
- *.log
257
- ```
185
+ **Available field types:** `string`, `number`, `integer`, `decimal`, `boolean`, `datetime`, `date`, `id`, `json`, `array`, `object`, `enum`.
258
186
 
259
- With this setup, your repository stays focused on what matters: your specifications and customizations, not generated boilerplate!
187
+ For `enum` fields, provide the allowed values with `values: [...]`.
260
188
 
261
- ## Multi-Model Endpoint Support ๐Ÿ”€
189
+ #### Model Relationships
262
190
 
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:
191
+ Set the `type` to another aggregate's name to create a foreign key relationship:
264
192
 
265
193
  ```yaml
266
194
  domain:
267
195
  aggregates:
268
- Cat:
196
+ Author:
269
197
  root: true
270
198
  fields:
271
199
  name: { type: string, required: true }
272
- Person:
200
+ Post:
273
201
  root: true
274
202
  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]
293
-
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
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:
315
- - path: /
316
- useCase: Cat:list
317
- view: catList
318
- auth: all
319
- - path: /create
320
- method: GET
321
- view: catCreate
322
- auth: authenticated
323
- Person:
324
- prefix: /person
325
- layout: main_view
326
- pages:
327
- - path: /
328
- useCase: Person:list
329
- view: personList
330
- auth: all
331
- - path: /create
332
- method: GET
333
- view: personCreate
334
- auth: authenticated
203
+ title: { type: string, required: true }
204
+ author: { type: Author, required: true }
335
205
  ```
336
206
 
337
- **Result**: Generates separate controllers, services, use cases, and stores for each model, each with their own base paths and endpoints.
207
+ The generator automatically:
208
+ - Creates a foreign key column `authorId` in the database.
209
+ - Uses the full `Author` object in the domain model (not just the ID).
210
+ - Uses `authorId: number` in DTOs for API transmission.
211
+ - Generates a `<select>` dropdown with a "Create New" button in HTML forms.
212
+ - Wires the related store as a dependency for loading relationships.
338
213
 
339
- ## Example: Building a Blog System ๐Ÿ“
214
+ Foreign key naming follows the pattern `fieldName + 'Id'` (e.g., `author` โ†’ `authorId`).
340
215
 
341
- Here's how you'd create a complete very simple blog system:
216
+ #### Child Entities
342
217
 
343
- ### 1. Create the app and module
344
- ```bash
345
- currentjs create app my-blog
346
- cd my-blog
347
- currentjs create module Blog
348
- ```
218
+ An aggregate can have child entities listed in the `entities` field:
349
219
 
350
- ### 2. Define your data model
351
220
  ```yaml
352
- # src/modules/Blog/blog.yaml
353
- # Only the domain fields need to be customized - the rest is auto-generated!
354
221
  domain:
355
222
  aggregates:
356
- Post:
223
+ Invoice:
357
224
  root: true
358
225
  fields:
359
- title: { type: string, required: true }
360
- content: { type: string, required: true }
361
- authorEmail: { type: string, required: true }
362
- publishedAt: { type: datetime }
226
+ number: { type: string, required: true }
227
+ entities: [InvoiceItem]
228
+
229
+ InvoiceItem:
230
+ fields:
231
+ productName: { type: string, required: true }
232
+ quantity: { type: integer, required: true }
233
+ ```
234
+
235
+ Child entities get a `getByParentId()` method in their store and `listByParent()` in their service. Use `input.parentId` in child use cases to link them to the parent.
236
+
237
+ #### Value Objects
363
238
 
364
- # Everything below is already generated for you by `currentjs create module`!
239
+ Value objects are reusable types embedded in aggregates, stored as JSON in the database:
240
+
241
+ ```yaml
242
+ domain:
243
+ valueObjects:
244
+ Money:
245
+ fields:
246
+ amount: { type: decimal, constraints: { min: 0 } }
247
+ currency: { type: enum, values: [USD, EUR, PLN] }
248
+ ```
249
+
250
+ โ†’ Reference: [aggregates](REFERENCE.md#aggregates) ยท [valueObjects](REFERENCE.md#valueobjects) ยท [Field Types](REFERENCE.md#field-types) ยท [Child Entities](REFERENCE.md#child-entities)
251
+
252
+ ---
253
+
254
+ ### Use Cases Layer (`useCases`)
255
+
256
+ Use cases define the operations available for each model. Each use case specifies its input shape, output shape, and a chain of handlers to execute.
257
+
258
+ ```yaml
365
259
  useCases:
366
260
  Post:
367
261
  list:
@@ -377,15 +271,66 @@ useCases:
377
271
  input: { from: Post }
378
272
  output: { from: Post }
379
273
  handlers: [default:create]
380
- update:
381
- input: { identifier: id, from: Post, partial: true }
382
- output: { from: Post }
383
- handlers: [default:update]
384
- delete:
274
+ ```
275
+
276
+ #### Handlers
277
+
278
+ Handlers are listed in execution order. Each handler becomes a method on the service class.
279
+
280
+ **Built-in handlers:**
281
+
282
+ | Handler | Description |
283
+ |---|---|
284
+ | `default:list` | Paginated list of entities |
285
+ | `default:get` | Fetch by ID |
286
+ | `default:create` | Create with validation |
287
+ | `default:update` | Update by ID |
288
+ | `default:delete` | Soft-delete by ID |
289
+
290
+ **Custom handlers** โ€” use `methodName` (or `service:methodName`). The generator creates a stub method that receives `(result, input)`:
291
+
292
+ ```yaml
293
+ useCases:
294
+ Post:
295
+ publish:
385
296
  input: { identifier: id }
386
- output: void
387
- handlers: [default:delete]
297
+ output: { from: Post }
298
+ handlers:
299
+ - default:get
300
+ - validateForPublish
301
+ - updatePublishStatus
302
+ ```
303
+
304
+ This generates three service methods called in sequence. Custom methods get a TODO comment for you to fill in.
305
+
306
+ #### Input Configuration
307
+
308
+ Inputs can derive fields from a model (`from`), pick/omit specific fields, add extra fields, define validation rules, enable pagination, filtering, and sorting. See the [Reference](REFERENCE.md) for the full input specification.
309
+
310
+ #### Displaying Child Entities (`withChild`)
311
+
312
+ When an aggregate root has child entities, you can show them on the root's pages:
313
+
314
+ ```yaml
315
+ useCases:
316
+ Invoice:
317
+ list:
318
+ withChild: true # Adds a link column to child entities on the list page
319
+ # ...
320
+ get:
321
+ withChild: true # Shows a child entities table on the detail page
322
+ # ...
323
+ ```
388
324
 
325
+ โ†’ Reference: [useCases](REFERENCE.md#usecases) ยท [handlers](REFERENCE.md#handlers) ยท [input](REFERENCE.md#input) ยท [output](REFERENCE.md#output)
326
+
327
+ ---
328
+
329
+ ### API Layer (`api`)
330
+
331
+ Defines REST API endpoints. Each model gets its own section keyed by name:
332
+
333
+ ```yaml
389
334
  api:
390
335
  Post:
391
336
  prefix: /api/posts
@@ -394,10 +339,6 @@ api:
394
339
  path: /
395
340
  useCase: Post:list
396
341
  auth: all
397
- - method: GET
398
- path: /:id
399
- useCase: Post:get
400
- auth: all
401
342
  - method: POST
402
343
  path: /
403
344
  useCase: Post:create
@@ -406,11 +347,33 @@ api:
406
347
  path: /:id
407
348
  useCase: Post:update
408
349
  auth: [owner, admin]
409
- - method: DELETE
410
- path: /:id
411
- useCase: Post:delete
412
- auth: [owner, admin]
350
+ ```
351
+
352
+ Each endpoint references a use case with the format `ModelName:actionName`.
413
353
 
354
+ #### Auth / Roles
355
+
356
+ The `auth` field controls access:
357
+
358
+ | Value | Meaning |
359
+ |---|---|
360
+ | `all` | Public, no authentication required |
361
+ | `authenticated` | Any logged-in user (valid JWT) |
362
+ | `owner` | User must own the resource (matched via `ownerId`) |
363
+ | `admin`, `editor`, etc. | User must have this role (from JWT) |
364
+ | `[owner, admin]` | OR logic โ€” user matches any of the listed roles |
365
+
366
+ When `owner` is combined with privileged roles (e.g., `[owner, admin]`), privileged roles bypass the ownership check.
367
+
368
+ โ†’ Reference: [api](REFERENCE.md#api) ยท [auth](REFERENCE.md#auth)
369
+
370
+ ---
371
+
372
+ ### Web Layer (`web`)
373
+
374
+ Defines server-rendered pages and forms:
375
+
376
+ ```yaml
414
377
  web:
415
378
  Post:
416
379
  prefix: /posts
@@ -420,10 +383,6 @@ web:
420
383
  useCase: Post:list
421
384
  view: postList
422
385
  auth: all
423
- - path: /:id
424
- useCase: Post:get
425
- view: postDetail
426
- auth: all
427
386
  - path: /create
428
387
  method: GET
429
388
  view: postCreate
@@ -434,761 +393,181 @@ web:
434
393
  auth: authenticated
435
394
  onSuccess:
436
395
  redirect: /posts/:id
437
- toast: "Post created successfully"
396
+ toast: "Post created"
438
397
  onError:
439
398
  stay: true
440
399
  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.
455
-
456
- ### 3. Generate everything
457
- ```bash
458
- currentjs generate
459
- npm start
460
- ```
461
-
462
- **Boom!** ๐Ÿ’ฅ You now have a complete blog system with:
463
- - REST API endpoints at `/api/posts/*`
464
- - Web interface at `/posts/*`
465
- - Full CRUD operations
466
- - HTML templates for all views
467
- - Database integration ready to go
468
-
469
- ## Generated Project Structure ๐Ÿ—๏ธ
470
-
471
400
  ```
472
- my-app/
473
- โ”œโ”€โ”€ package.json # Dependencies (router, templating, providers)
474
- โ”œโ”€โ”€ tsconfig.json # TypeScript configuration
475
- โ”œโ”€โ”€ app.yaml # Main application config
476
- โ”œโ”€โ”€ src/
477
- โ”‚ โ”œโ”€โ”€ app.ts # Main application entry point (with DI wiring)
478
- โ”‚ โ”œโ”€โ”€ system.ts # @Injectable decorator for DI
479
- โ”‚ โ”œโ”€โ”€ common/ # Shared utilities and templates
480
- โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Common services
481
- โ”‚ โ”‚ โ””โ”€โ”€ ui/
482
- โ”‚ โ”‚ โ””โ”€โ”€ templates/
483
- โ”‚ โ”‚ โ”œโ”€โ”€ main_view.html # Main layout template
484
- โ”‚ โ”‚ โ””โ”€โ”€ error.html # Error page template
485
- โ”‚ โ””โ”€โ”€ modules/ # Your business modules
486
- โ”‚ โ””โ”€โ”€ YourModule/
487
- โ”‚ โ”œโ”€โ”€ yourmodule.yaml # Module specification
488
- โ”‚ โ”œโ”€โ”€ domain/
489
- โ”‚ โ”‚ โ”œโ”€โ”€ entities/ # Domain models (aggregates)
490
- โ”‚ โ”‚ โ””โ”€โ”€ valueObjects/ # Value objects
491
- โ”‚ โ”œโ”€โ”€ application/
492
- โ”‚ โ”‚ โ”œโ”€โ”€ useCases/ # Use case orchestrators
493
- โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Business logic handlers
494
- โ”‚ โ”‚ โ””โ”€โ”€ dto/ # Input/Output DTOs
495
- โ”‚ โ”œโ”€โ”€ infrastructure/
496
- โ”‚ โ”‚ โ”œโ”€โ”€ controllers/ # HTTP endpoints (API + Web)
497
- โ”‚ โ”‚ โ””โ”€โ”€ stores/ # Data access
498
- โ”‚ โ””โ”€โ”€ views/ # HTML templates
499
- โ”œโ”€โ”€ build/ # Compiled JavaScript
500
- โ””โ”€โ”€ web/ # Static assets, served as is
501
- โ”œโ”€โ”€ app.js # Frontend JavaScript
502
- โ””โ”€โ”€ translations.json # i18n support
503
- ```
504
-
505
401
 
506
- ## Automatic Dependency Injection ๐Ÿ”Œ
402
+ Form submission results are handled with `onSuccess` / `onError`:
507
403
 
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.
404
+ - `toast: "message"` โ€” show a toast notification
405
+ - `back: true` โ€” navigate back in browser history
406
+ - `redirect: /path` โ€” redirect to a URL (supports `:id` placeholder)
407
+ - `stay: true` โ€” stay on the current page
509
408
 
510
- ### How It Works
409
+ โ†’ Reference: [web](REFERENCE.md#web) ยท [auth](REFERENCE.md#auth)
511
410
 
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.
411
+ ---
516
412
 
517
- ### The `@Injectable` Decorator
413
+ ## Generated Source Code
518
414
 
519
- Lives in `src/system.ts` (generated with your app, no external dependencies):
415
+ When you run `currentjs generate`, the following files are produced for each model defined in a module:
520
416
 
521
- ```typescript
522
- export function Injectable() {
523
- return function (target: any) {
524
- target.__injectable = true;
525
- };
526
- }
527
417
  ```
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
- }
418
+ src/modules/<ModuleName>/
419
+ domain/
420
+ entities/<EntityName>.ts โ€” Domain entity class with typed constructor and setters
421
+ valueObjects/<ValueObject>.ts โ€” Value object class (if defined)
422
+ application/
423
+ dto/<Action>InputDto.ts โ€” Input DTO with parse() and validation
424
+ dto/<Action>OutputDto.ts โ€” Output DTO with from() mapper
425
+ useCases/<EntityName>UseCase.ts โ€” Use case orchestrator (calls service methods in sequence)
426
+ services/<EntityName>Service.ts โ€” Service with handler implementations (CRUD + custom stubs)
427
+ infrastructure/
428
+ controllers/<EntityName>ApiController.ts โ€” REST endpoints with auth checks
429
+ controllers/<EntityName>WebController.ts โ€” Page rendering with form handling
430
+ stores/<EntityName>Store.ts โ€” Database access (CRUD, row-to-model conversion, relationships)
431
+ views/
432
+ <viewName>.html โ€” HTML templates (list, detail, create, edit forms)
546
433
  ```
547
434
 
548
- On the next `currentjs generate`, this class will be automatically imported and instantiated in `app.ts` with its dependencies resolved.
435
+ The generator also updates `src/app.ts` with dependency injection wiring between marker comments (`// currentjs:controllers:start` ... `// currentjs:controllers:end`). This section is fully regenerated each time โ€” imports, instantiation order (topologically sorted), and the controllers array.
549
436
 
550
- ### Database Providers
437
+ Each generated class is decorated with `@Injectable()` or `@Controller()`, so the DI system discovers and wires them automatically.
551
438
 
552
- Database providers are configured in `app.yaml`:
439
+ โ†’ Reference: [Generated File Structure](REFERENCE.md#generated-file-structure) ยท [generate](REFERENCE.md#generate)
553
440
 
554
- ```yaml
555
- database:
556
- provider: "@currentjs/provider-mysql" # npm package
557
- # or:
558
- provider: "./src/common/SomeProvider" # local file path
559
- ```
441
+ ## Change Tracking: `diff` and `commit`
560
442
 
561
- Modules can override the global provider with their own:
443
+ Generated code often needs small adjustments โ€” custom business logic, template tweaks, validation rules. The generator includes a change tracking system so these adjustments survive regeneration.
562
444
 
563
- ```yaml
564
- # In module's yaml section of app.yaml
565
- modules:
566
- - name: Analytics
567
- database:
568
- provider: "@currentjs/provider-postgres"
569
- ```
445
+ ### How it works
570
446
 
571
- Both npm packages and local paths are supported. Stores automatically receive the correct provider instance based on their module's configuration.
447
+ 1. **Registry** โ€” when files are generated, their content hashes are stored in `registry.json`.
572
448
 
573
- ### Generated Wiring Example
449
+ 2. **`currentjs diff [module]`** โ€” compares each generated file's current content against what the generator would produce. Reports files as `[clean]`, `[modified]`, or `[missing]`.
574
450
 
575
- After generation, `src/app.ts` contains auto-generated wiring between markers:
451
+ 3. **`currentjs commit [files...]`** โ€” records the differences between your current files and the generated baseline. Diffs are saved as JSON files in the `commits/` directory.
576
452
 
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';
453
+ 4. **Regeneration** โ€” on the next `currentjs generate`, committed changes are reapplied to the freshly generated code. If a change cannot be applied cleanly (e.g., the generated code changed in the same area), you are prompted to resolve it (unless `--force` or `--skip` is set).
584
454
 
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);
455
+ ### Practical workflow
589
456
 
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.
457
+ ```bash
458
+ # Generate code
459
+ currentjs generate
598
460
 
599
- ## Complete YAML Configuration Guide ๐Ÿ“‹
461
+ # Make your changes (service logic, templates, etc.)
462
+ # ...
600
463
 
601
- ### Module Structure Overview
464
+ # See what you changed
465
+ currentjs diff Blog
602
466
 
603
- When you create a module, you'll work primarily with the `modulename.yaml` file. This file defines everything about your module:
467
+ # Save your changes
468
+ currentjs commit
604
469
 
605
- ```
606
- src/modules/YourModule/
607
- โ”œโ”€โ”€ yourmodule.yaml # โ† This is where you define everything
608
- โ”œโ”€โ”€ domain/
609
- โ”‚ โ”œโ”€โ”€ entities/ # Generated domain models (aggregates)
610
- โ”‚ โ””โ”€โ”€ valueObjects/ # Generated value objects
611
- โ”œโ”€โ”€ application/
612
- โ”‚ โ”œโ”€โ”€ useCases/ # Generated use case orchestrators
613
- โ”‚ โ”œโ”€โ”€ services/ # Generated business logic handlers
614
- โ”‚ โ””โ”€โ”€ dto/ # Generated Input/Output DTOs
615
- โ”œโ”€โ”€ infrastructure/
616
- โ”‚ โ”œโ”€โ”€ controllers/ # Generated HTTP endpoints (API + Web)
617
- โ”‚ โ””โ”€โ”€ stores/ # Generated data access
618
- โ””โ”€โ”€ views/ # Generated HTML templates
470
+ # Later, after modifying the YAML and regenerating:
471
+ currentjs generate
472
+ # Your committed changes are reapplied automatically
619
473
  ```
620
474
 
621
- ### Complete Module Configuration Example
475
+ ### Repository strategy
622
476
 
623
- Here's a comprehensive example showing all available configuration options:
624
-
625
- ```yaml
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 }
477
+ You can choose to either commit generated source code to git normally, or keep your repository lean by only tracking YAML files, `registry.json`, and the `commits/` directory. In the latter case, anyone can recreate the full source by running `currentjs generate`.
636
478
 
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]
479
+ โ†’ Reference: [diff](REFERENCE.md#diff) ยท [commit](REFERENCE.md#commit) ยท [Notes โ€” File Change Tracking](REFERENCE.md#file-change-tracking) ยท [Notes โ€” Commit Mechanism](REFERENCE.md#commit-mechanism)
700
480
 
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
- ```
481
+ ## Database Migrations
741
482
 
742
- ### Displaying child entities on root pages (withChild)
483
+ The generator can produce SQL migration files based on changes to your domain models.
743
484
 
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.
485
+ ### `migrate commit`
745
486
 
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.
487
+ ```bash
488
+ currentjs migrate commit
489
+ ```
748
490
 
749
- If the entity has no child entities, `withChild` is ignored. The parameter defaults to `false` when you run `currentjs create module`.
491
+ Collects all aggregate definitions from module YAMLs, compares them against the stored schema state (`migrations/schema_state.yaml`), and generates a `.sql` migration file in the `migrations/` directory.
750
492
 
751
- **Example:**
493
+ The migration file contains `CREATE TABLE`, `ALTER TABLE ADD/MODIFY/DROP COLUMN`, and `DROP TABLE` statements as needed. Foreign keys, indexes, and standard timestamp columns (`created_at`, `updated_at`, `deleted_at`) are handled automatically.
752
494
 
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]
767
- ```
495
+ After generating the file, the schema state is updated so the next `migrate commit` only produces a diff of subsequent changes.
768
496
 
769
- ### Field Types and Validation
770
-
771
- **Available Field Types:**
772
- - `string` - Text data (VARCHAR in database)
773
- - `number` - Numeric data (INT/DECIMAL in database)
774
- - `integer` - Integer data (INT in database)
775
- - `decimal` - Decimal data (DECIMAL in database)
776
- - `boolean` - True/false values (BOOLEAN in database)
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: [...]`)
782
-
783
- **Important Field Rules:**
784
- - **Never include `id`/`owner_id`/`created_at`/`updated_at`/`deleted_at` fields** - these are added automatically
785
- - Use `required: true` for mandatory fields
786
- - Fields without `required` are optional
497
+ ### `migrate push` *(not yet implemented)*
787
498
 
788
- ```yaml
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 }
798
- ```
499
+ Will apply pending migration files to the database.
799
500
 
800
- ### ๐Ÿ”— Model Relationships
501
+ ### `migrate update` *(not yet implemented)*
801
502
 
802
- You can define relationships between models by specifying another model name as the field type. The generator will automatically handle foreign keys, type checking, and UI components.
503
+ Will compare the live database schema against the current model definitions and generate a migration to bring them in sync.
803
504
 
804
- **Basic Relationship Example:**
805
- ```yaml
806
- domain:
807
- aggregates:
808
- Owner:
809
- root: true
810
- fields:
811
- name: { type: string, required: true }
812
- email: { type: string, required: true }
505
+ โ†’ Reference: [migrate commit](REFERENCE.md#migrate-commit)
813
506
 
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
820
- ```
507
+ ## Template System
821
508
 
822
- **Architecture: Rich Domain Models**
823
-
824
- The generator uses **Infrastructure-Level Relationship Assembly**:
825
-
826
- - **Domain Layer**: Works with full objects (no FKs)
827
- - **Infrastructure (Store)**: Handles FK โ†” Object conversion
828
- - **DTOs**: Use FKs for API transmission
829
-
830
- **What Gets Generated:**
831
-
832
- 1. **Domain Model**: Pure business objects
833
- ```typescript
834
- import { Owner } from './Owner';
835
-
836
- export class Cat {
837
- constructor(
838
- public id: number,
839
- public name: string,
840
- public breed?: string,
841
- public owner: Owner // โœจ Full object, no FK!
842
- ) {}
843
- }
844
- ```
845
-
846
- 2. **DTOs**: Use foreign keys for API
847
- ```typescript
848
- export interface CatDTO {
849
- name: string;
850
- breed?: string;
851
- ownerId: number; // โœจ FK for over-the-wire
852
- }
853
- ```
854
-
855
- 3. **Store**: Converts FK โ†” Object
856
- ```typescript
857
- export class CatStore {
858
- constructor(
859
- private db: ISqlProvider,
860
- private ownerStore: OwnerStore // โœจ Foreign store dependency
861
- ) {}
862
-
863
- async loadRelationships(entity: Cat, row: CatRow): Promise<Cat> {
864
- const owner = await this.ownerStore.getById(row.ownerId);
865
- if (owner) entity.setOwner(owner);
866
- return entity;
867
- }
868
-
869
- async insert(cat: Cat): Promise<Cat> {
870
- const row = {
871
- name: cat.name,
872
- ownerId: cat.owner?.id // โœจ Extract FK to save
873
- };
874
- // ...
875
- }
876
- }
877
- ```
878
-
879
- 4. **Service**: Loads objects from FKs
880
- ```typescript
881
- export class CatService {
882
- constructor(
883
- private catStore: CatStore,
884
- private ownerStore: OwnerStore // โœจ To load relationships
885
- ) {}
886
-
887
- async create(catData: CatDTO): Promise<Cat> {
888
- // โœจ Load full owner object from FK
889
- const owner = await this.ownerStore.getById(catData.ownerId);
890
- const cat = new Cat(0, catData.name, catData.breed, owner);
891
- return await this.catStore.insert(cat);
892
- }
893
- }
894
- ```
895
-
896
- 5. **HTML Forms**: Select dropdown with "Create New" button
897
- ```html
898
- <select id="ownerId" name="ownerId" required>
899
- <option value="">-- Select Owner --</option>
900
- <!-- Options loaded from /api/owner -->
901
- </select>
902
- <button onclick="window.open('/owner/create')">+ New</button>
903
- ```
904
-
905
- **Relationship Naming Convention:**
906
-
907
- The generator automatically creates foreign key fields following this convention:
908
- - **Field name**: `owner` โ†’ **Foreign key**: `ownerId`
909
- - **Field name**: `author` โ†’ **Foreign key**: `authorId`
910
- - **Field name**: `parentComment` โ†’ **Foreign key**: `parentCommentId`
911
-
912
- The foreign key always references the `id` field of the related model.
913
-
914
- **Multiple Relationships:**
915
- ```yaml
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
925
- ```
509
+ Generated HTML templates use the `@currentjs/templating` engine. Templates are placed in the module's `views/` directory and referenced by name in the `web` section of the YAML.
926
510
 
927
- **Relationship Best Practices:**
928
- - โœ… Always define the foreign model first in the same module
929
- - โœ… Use descriptive field names for relationships (e.g., `author` instead of `user`)
930
- - โœ… Set appropriate `displayFields` to show meaningful data in dropdowns
931
- - โœ… Use `required: false` for optional relationships
932
- - โœ… Foreign keys are auto-generated following the pattern `fieldName + 'Id'`
933
- - โŒ Don't manually add foreign key fields (they're auto-generated)
934
- - โŒ Don't create circular dependencies between modules
935
-
936
- ### Use Case Handlers Explained
937
-
938
- **๐Ÿ”„ Handler vs Use Case Distinction:**
939
- - **Handler**: Creates a separate service method (one handler = one method)
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
949
-
950
- Note: Handlers within `useCases` do NOT need a model prefix because the model is already the key.
951
-
952
- **Custom Handlers:**
953
- - `customMethodName` - Creates service method that accepts `(result, input)` parameters
954
- - `result`: Result from previous handler (or `null` if it's the first handler)
955
- - `input`: The parsed input DTO
956
- - User can customize the implementation after generation
957
- - Each handler generates a separate method in the service
958
-
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:
511
+ ### Template header
961
512
 
962
- ```yaml
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
971
- ```
513
+ Each template starts with a comment that declares its name:
972
514
 
973
- **Generated Code Example:**
974
- ```typescript
975
- // InvoiceService.ts
976
- async get(id: number): Promise<Invoice> {
977
- // Standard get implementation
978
- }
979
- async enrichData(result: any, input: any): Promise<any> {
980
- // TODO: Implement custom enrichData method
981
- // result = result from previous handler (Invoice object in this case)
982
- // input = parsed input DTO
983
- }
984
-
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);
989
- return result; // Returns result from last handler
990
- }
515
+ ```html
516
+ <!-- @template name="postList" -->
991
517
  ```
992
518
 
993
- **Parameter Passing Rules:**
994
- - **Default handlers** (`default:*`): Receive standard parameters (id, pagination, DTO, etc.)
995
- - **Custom handlers**: Receive `(result, input)` where:
996
- - `result`: Result from previous handler, or `null` if it's the first handler
997
- - `input`: Parsed input DTO
519
+ ### Variables
998
520
 
999
- **Handler Format Examples:**
1000
- ```yaml
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
- ]
521
+ ```html
522
+ {{ title }}
523
+ {{ post.authorName }}
524
+ {{ formData.email || '' }}
1017
525
  ```
1018
526
 
1019
- ### Multi-Model Support ๐Ÿ”„
1020
-
1021
- When you have multiple models in a single module, the system generates individual services, use cases, controllers, and stores for each model:
527
+ ### Loops
1022
528
 
1023
- ```yaml
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]
1049
-
1050
- api:
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
529
+ ```html
530
+ <tbody x-for="items" x-row="item">
531
+ <tr>
532
+ <td>{{ item.name }}</td>
533
+ <td>{{ $index }}</td>
534
+ </tr>
535
+ </tbody>
1069
536
  ```
1070
537
 
1071
- **Key Points:**
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)
1076
-
1077
- ### Form Success/Error Handling
538
+ `x-for` specifies the data key to iterate over, `x-row` names the loop variable. `$index` gives the current iteration index.
1078
539
 
1079
- Configure what happens after successful form submissions using `onSuccess` and `onError` on web page endpoints:
540
+ ### Conditionals
1080
541
 
1081
- ```yaml
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
542
+ ```html
543
+ <div x-if="user.isAdmin">Admin-only content</div>
544
+ <span x-if="errors.name">{{ errors.name }}</span>
1096
545
  ```
1097
546
 
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
547
+ ### Layouts
1103
548
 
1104
- **Available `onError` Options:**
1105
- - `stay: true` - Stay on current page (re-render form with errors)
1106
- - `toast: error` - Show error toast notification
549
+ Templates are rendered inside a layout (specified per resource or per page in the `web` config). The layout receives the rendered template content as `{{ content }}`.
1107
550
 
1108
- **Common Combinations:**
1109
- ```yaml
1110
- # Show message and go back
1111
- onSuccess: { toast: "Saved!", back: true }
551
+ ### Forms
1112
552
 
1113
- # Redirect after creation
1114
- onSuccess: { redirect: /posts/:id, toast: "Created!" }
553
+ Generated forms include `data-strategy` attributes for the frontend JavaScript to handle submission via AJAX:
1115
554
 
1116
- # Stay on page with toast
1117
- onSuccess: { stay: true, toast: "Updated!" }
555
+ ```html
556
+ <form data-strategy='["toast", "back"]'
557
+ data-entity-name="Post"
558
+ data-field-types='{"age": "number", "active": "boolean"}'>
559
+ <input name="title" type="text" required>
560
+ <button type="submit">Save</button>
561
+ </form>
1118
562
  ```
1119
563
 
1120
- The template generator converts `onSuccess` options into `data-strategy` attributes on HTML forms for the frontend JavaScript to handle.
564
+ The `data-field-types` attribute tells the frontend how to convert form values before sending (e.g., string to number, checkbox to boolean).
1121
565
 
1122
- ### Auth System
566
+ ### Template regeneration behavior
1123
567
 
1124
- Control who can access what using the `auth` property on each endpoint in `api` and `web`:
1125
-
1126
- ```yaml
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
- ```
568
+ By default, `currentjs generate` does not overwrite existing HTML templates. Only missing templates are created. Use `--with-templates` to force regeneration of all templates.
1148
569
 
1149
- **Auth Options:**
1150
- - `all` - Everyone (including anonymous users)
1151
- - `authenticated` - Any logged-in user
1152
- - `owner` - User who created the entity
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.
1155
-
1156
- **How Ownership Works:**
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
1160
-
1161
- ### Code vs Configuration Guidelines
1162
-
1163
- **โœ… Use YAML Configuration For:**
1164
- - Basic CRUD operations
1165
- - Standard REST endpoints
1166
- - Simple permission rules
1167
- - Form success strategies
1168
- - Standard data field types
1169
-
1170
- **โœ… Write Custom Code For:**
1171
- - Complex business logic
1172
- - Custom validation rules
1173
- - Data transformations
1174
- - Integration with external services
1175
- - Complex database queries
1176
-
1177
- ## Part of the Framework Ecosystem ๐ŸŒ
1178
-
1179
- This generator is the foundation of the `currentjs` framework:
1180
- - Works seamlessly with `@currentjs/router` for HTTP handling
1181
- - Integrates with `@currentjs/templating` for server-side rendering
1182
- - Uses `@currentjs/provider-*` packages for database access
1183
- - Follows clean architecture principles for maintainable code
1184
-
1185
- ## Notes
1186
-
1187
- - `currentjs create app` scaffolds complete app structure with TypeScript configs and dependencies
1188
- - `currentjs generate` creates domain entities, value objects, use cases, services, DTOs, controllers, stores, and templates
1189
- - Generated code follows clean architecture: domain/application/infrastructure layers
1190
- - Supports both API endpoints and web page routes in the same module
1191
- - Includes change tracking system for safely modifying generated code
570
+ โ†’ Reference: [Notes โ€” Template Regeneration](REFERENCE.md#template-regeneration) ยท [web](REFERENCE.md#web)
1192
571
 
1193
572
  ## Authorship & Contribution
1194
573
 
@@ -1203,4 +582,3 @@ GNU Lesser General Public License (LGPL)
1203
582
  It simply means, that you:
1204
583
  - can create a proprietary application that uses this library without having to open source their entire application code (this is the "lesser" aspect of LGPL compared to GPL).
1205
584
  - can make any modifications, but must distribute those modifications under the LGPL (or a compatible license) and include the original copyright and license notice.
1206
-