@currentjs/gen 0.5.1 → 0.5.2
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 +9 -0
- package/README.md +374 -996
- package/dist/cli.js +28 -10
- package/dist/commands/createModel.d.ts +1 -0
- package/dist/commands/createModel.js +764 -0
- package/dist/commands/createModule.js +13 -0
- package/dist/commands/generateAll.d.ts +1 -0
- package/dist/commands/generateAll.js +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/{createApp.js → init.js} +2 -2
- package/dist/commands/migrateCommit.js +33 -68
- package/dist/generators/domainLayerGenerator.js +34 -1
- package/dist/generators/templateGenerator.d.ts +1 -0
- package/dist/generators/templateGenerator.js +8 -2
- package/dist/generators/templates/data/cursorRulesTemplate +11 -755
- package/dist/types/configTypes.d.ts +5 -0
- package/dist/utils/migrationUtils.d.ts +9 -19
- package/dist/utils/migrationUtils.js +80 -110
- package/dist/utils/promptUtils.d.ts +37 -0
- package/dist/utils/promptUtils.js +149 -0
- package/package.json +1 -1
- package/dist/commands/createApp.d.ts +0 -1
- package/howto.md +0 -667
|
@@ -1,765 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
0. This is fullstack application, generated by CurrentJS code generator. Instructions are in REFERENCE.md.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
This is a CurrentJS framework application using clean architecture principles with the following layers:
|
|
5
|
-
- **Controllers**: Handle HTTP requests/responses and route handling
|
|
6
|
-
- **UseCases**: Orchestrate handler calls in sequence
|
|
7
|
-
- **Services**: Contain business logic and orchestrate operations
|
|
8
|
-
- **Stores**: Provide data access layer and database operations
|
|
9
|
-
- **Domain Entities**: Core business models
|
|
10
|
-
- **Views**: HTML templates for server-side rendering
|
|
3
|
+
1. Yaml-first: edit yaml, `current generate`, `current commit` — in that order. `current generate` overwrites TypeScript files. Any hand-edits to generated files must be preserved with current commit before the next generation, or they are lost. This is the single most critical process rule.
|
|
11
4
|
|
|
12
|
-
|
|
5
|
+
2. Never edit the DI wiring block in src/app.ts. The block between // currentjs:controllers:start and // currentjs:controllers:end is fully auto-regenerated. Changes there will silently vanish. Don't touch app.yaml either.
|
|
13
6
|
|
|
14
|
-
|
|
7
|
+
3. Custom logic belongs exclusively in Models and Service methods. Controllers, UseCases, and DTOs are fully generated. When CRUD isn't enough, add a method to the *Service.ts class, then reference it in the yaml as a handler — never implement business logic in controllers.
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
current create module Modulename # Creates "Modulename" with default structure and yaml file
|
|
18
|
-
```
|
|
19
|
-
```bash
|
|
20
|
-
current generate Modulename # Generates all TypeScript files based on the module's yaml, and runs "npm run build"
|
|
21
|
-
current generate # Does the same as above, but for all modules
|
|
22
|
-
```
|
|
23
|
-
```bash
|
|
24
|
-
current commit [files...] # Commits all changes in the code, so they won't be overwritten after regeneration
|
|
25
|
-
current diff [module] # Show differences between generated and current code
|
|
26
|
-
```
|
|
9
|
+
4. Custom handlers must be declared in the yaml before (re)generating. After writing a service method, add it to useCases.Model.action.handlers in the yaml. If you skip this step, the use case won't call your method and the wiring won't be regenerated correctly.
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
1. Create an empty app (`current create app`) – this step is already done.
|
|
30
|
-
2. Create a new module: `current create module Name`
|
|
31
|
-
3. In the module's yaml file, define module's:
|
|
32
|
-
- domain (aggregates and value objects)
|
|
33
|
-
- useCases (with input/output and handlers)
|
|
34
|
-
- api endpoints (with auth)
|
|
35
|
-
- web pages (with auth and onSuccess/onError)
|
|
36
|
-
4. Generate TypeScript files: `current generate Name`
|
|
37
|
-
5. If required, make changes in the:
|
|
38
|
-
- model (i.e. some specific business rules or validations)
|
|
39
|
-
- views
|
|
40
|
-
6. Commit those changes: `current commit`
|
|
11
|
+
5. Never include auto-managed fields in the domain definition. id, owner_id, created_at, updated_at, deleted_at are added automatically. Defining them manually causes schema conflicts and duplicate columns.
|
|
41
12
|
|
|
42
|
-
|
|
13
|
+
6. Model relationships use the model name as the field type, not a raw foreign key. owner: { type: Owner, required: true } — not ownerId: { type: id }. The generator derives the FK column, DTO field, and store JOIN automatically from the model-name type.
|
|
43
14
|
|
|
44
|
-
7.
|
|
45
|
-
8. Describe this action in the module's yaml under useCases. Additionally, you may define api/web endpoints.
|
|
46
|
-
9. `current generate Modulename`
|
|
47
|
-
10. commit changes: `current commit`
|
|
15
|
+
7. Business rule validation belongs in domain entities, not in services. Services orchestrate; entities own domain invariants. A rule like "a published post cannot be archived" is an entity concern. Type/presence validations are handled by DTOs.
|
|
48
16
|
|
|
49
|
-
|
|
17
|
+
8. Auth and permissions are yaml configuration, not code. auth: [owner, admin] on an endpoint is enough. Do not write auth guards or role checks manually in controllers — it's redundant and won't survive regeneration.
|
|
50
18
|
|
|
51
|
-
|
|
19
|
+
9. The template engine is custom: x-for/x-if attributes and {{ }} syntax. It is not Alpine, Vue, or Jinja. Loops use <tbody x-for="items" x-row="item">, conditionals use x-if="value", interpolation uses {{ item.name }}. Don't import or use other templating conventions.
|
|
52
20
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
### Module Configuration (modulename.yaml)
|
|
56
|
-
|
|
57
|
-
**The most work must be done in these files**
|
|
58
|
-
|
|
59
|
-
**Complete Module Example:**
|
|
60
|
-
```yaml
|
|
61
|
-
domain:
|
|
62
|
-
aggregates:
|
|
63
|
-
Post:
|
|
64
|
-
root: true # Marks as aggregate root
|
|
65
|
-
fields:
|
|
66
|
-
title: { type: string, required: true }
|
|
67
|
-
content: { type: string, required: true }
|
|
68
|
-
authorId: { type: id, required: true }
|
|
69
|
-
publishedAt: { type: datetime }
|
|
70
|
-
status: { type: string, required: true }
|
|
71
|
-
|
|
72
|
-
useCases:
|
|
73
|
-
Post:
|
|
74
|
-
list:
|
|
75
|
-
input:
|
|
76
|
-
pagination: { type: offset, defaults: { limit: 20, maxLimit: 100 } }
|
|
77
|
-
output: { from: Post, pagination: true }
|
|
78
|
-
handlers: [default:list] # Built-in list handler
|
|
79
|
-
get:
|
|
80
|
-
input: { identifier: id }
|
|
81
|
-
output: { from: Post }
|
|
82
|
-
handlers: [default:get] # Built-in get handler
|
|
83
|
-
create:
|
|
84
|
-
input: { from: Post }
|
|
85
|
-
output: { from: Post }
|
|
86
|
-
handlers: [default:create] # Built-in create
|
|
87
|
-
update:
|
|
88
|
-
input: { identifier: id, from: Post, partial: true }
|
|
89
|
-
output: { from: Post }
|
|
90
|
-
handlers: [default:update]
|
|
91
|
-
delete:
|
|
92
|
-
input: { identifier: id }
|
|
93
|
-
output: void
|
|
94
|
-
handlers: [ # Chain multiple handlers
|
|
95
|
-
checkCanDelete, # Custom → PostService.checkCanDelete(result, input)
|
|
96
|
-
default:delete # Built-in delete
|
|
97
|
-
]
|
|
98
|
-
publish: # Custom action
|
|
99
|
-
input: { identifier: id }
|
|
100
|
-
output: { from: Post }
|
|
101
|
-
handlers: [
|
|
102
|
-
default:get, # Fetch entity
|
|
103
|
-
validateForPublish, # Custom → PostService.validateForPublish(result, input)
|
|
104
|
-
updatePublishStatus # Custom → PostService.updatePublishStatus(result, input)
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
api: # REST API configuration
|
|
108
|
-
Post: # Keyed by model name
|
|
109
|
-
prefix: /api/posts # Base URL for API endpoints
|
|
110
|
-
endpoints:
|
|
111
|
-
- method: GET # HTTP method
|
|
112
|
-
path: / # Relative path (becomes /api/posts/)
|
|
113
|
-
useCase: Post:list # References useCases.Post.list
|
|
114
|
-
auth: all # Public access
|
|
115
|
-
- method: GET
|
|
116
|
-
path: /:id # Path parameter
|
|
117
|
-
useCase: Post:get
|
|
118
|
-
auth: all
|
|
119
|
-
- method: POST
|
|
120
|
-
path: /
|
|
121
|
-
useCase: Post:create
|
|
122
|
-
auth: authenticated # Must be logged in
|
|
123
|
-
- method: PUT
|
|
124
|
-
path: /:id
|
|
125
|
-
useCase: Post:update
|
|
126
|
-
auth: [owner, admin] # Owner OR admin (OR logic)
|
|
127
|
-
- method: DELETE
|
|
128
|
-
path: /:id
|
|
129
|
-
useCase: Post:delete
|
|
130
|
-
auth: [owner, admin]
|
|
131
|
-
- method: POST # Custom endpoint
|
|
132
|
-
path: /:id/publish
|
|
133
|
-
useCase: Post:publish
|
|
134
|
-
auth: [owner, editor, admin]
|
|
135
|
-
|
|
136
|
-
web: # Web interface configuration
|
|
137
|
-
Post: # Keyed by model name
|
|
138
|
-
prefix: /posts # Base URL for web pages
|
|
139
|
-
layout: main_view # Layout template name
|
|
140
|
-
pages:
|
|
141
|
-
- path: / # List page
|
|
142
|
-
useCase: Post:list
|
|
143
|
-
view: postList # Template name
|
|
144
|
-
auth: all
|
|
145
|
-
- path: /:id # Detail page
|
|
146
|
-
useCase: Post:get
|
|
147
|
-
view: postDetail
|
|
148
|
-
auth: all
|
|
149
|
-
- path: /create # Create form (GET = show form)
|
|
150
|
-
method: GET
|
|
151
|
-
view: postCreate
|
|
152
|
-
auth: authenticated
|
|
153
|
-
- path: /create # Create form (POST = submit)
|
|
154
|
-
method: POST
|
|
155
|
-
useCase: Post:create
|
|
156
|
-
auth: authenticated
|
|
157
|
-
onSuccess: # What happens after successful submission
|
|
158
|
-
redirect: /posts/:id
|
|
159
|
-
toast: "Post created successfully"
|
|
160
|
-
onError:
|
|
161
|
-
stay: true
|
|
162
|
-
toast: error
|
|
163
|
-
- path: /:id/edit # Edit form (GET = show form)
|
|
164
|
-
method: GET
|
|
165
|
-
useCase: Post:get # Load existing data
|
|
166
|
-
view: postUpdate
|
|
167
|
-
auth: [owner, admin]
|
|
168
|
-
- path: /:id/edit # Edit form (POST = submit)
|
|
169
|
-
method: POST
|
|
170
|
-
useCase: Post:update
|
|
171
|
-
auth: [owner, admin]
|
|
172
|
-
onSuccess:
|
|
173
|
-
back: true
|
|
174
|
-
toast: "Post updated successfully"
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**Make sure no `id`/`owner_id`/`created_at`/`updated_at`/`deleted_at` fields are present in the domain definition, since they are added automatically**
|
|
178
|
-
|
|
179
|
-
**Use case option: withChild (aggregate root with child entities)**
|
|
180
|
-
|
|
181
|
-
Per use case you can set `withChild: true` (default `false`) to show child entities on the root's pages. Only applies when the entity has child entities (e.g. Invoice with InvoiceItem). Ignored if there are no child entities.
|
|
182
|
-
- **list** + `withChild: true`: adds a link column on the list page to the child entity list (no extra loading).
|
|
183
|
-
- **get** + `withChild: true`: on the detail page, renders a table of child entities below the main card, with View/Edit/Add links.
|
|
184
|
-
|
|
185
|
-
Example:
|
|
186
|
-
```yaml
|
|
187
|
-
useCases:
|
|
188
|
-
Invoice:
|
|
189
|
-
get:
|
|
190
|
-
withChild: true
|
|
191
|
-
input: { identifier: id }
|
|
192
|
-
output: { from: Invoice }
|
|
193
|
-
handlers: [default:get]
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
**Field Types:**
|
|
197
|
-
- `string` - Text data (VARCHAR in database)
|
|
198
|
-
- `number` - Numeric data (INT/DECIMAL in database)
|
|
199
|
-
- `integer` - Integer data (INT in database)
|
|
200
|
-
- `decimal` - Decimal data (DECIMAL in database)
|
|
201
|
-
- `boolean` - True/false values (BOOLEAN in database)
|
|
202
|
-
- `datetime` - Date and time values (DATETIME in database)
|
|
203
|
-
- `date` - Date values (DATE in database)
|
|
204
|
-
- `id` - Foreign key reference (INT in database)
|
|
205
|
-
- `json` - JSON data
|
|
206
|
-
- `enum` - Enumerated values (use with `values: [...]`)
|
|
207
|
-
- `ModelName` - Relationship to another model (e.g., `Owner`, `User`, `Post`)
|
|
208
|
-
|
|
209
|
-
**Multi-Model Configuration:**
|
|
210
|
-
|
|
211
|
-
When working with multiple models in a single module, each model is a separate key in `api` and `web`:
|
|
212
|
-
|
|
213
|
-
```yaml
|
|
214
|
-
domain:
|
|
215
|
-
aggregates:
|
|
216
|
-
Cat:
|
|
217
|
-
root: true
|
|
218
|
-
fields:
|
|
219
|
-
name: { type: string, required: true }
|
|
220
|
-
Person:
|
|
221
|
-
root: true
|
|
222
|
-
fields:
|
|
223
|
-
name: { type: string, required: true }
|
|
224
|
-
email: { type: string, required: true }
|
|
225
|
-
|
|
226
|
-
api:
|
|
227
|
-
Cat:
|
|
228
|
-
prefix: /api/cat
|
|
229
|
-
endpoints:
|
|
230
|
-
- method: GET
|
|
231
|
-
path: /
|
|
232
|
-
useCase: Cat:list
|
|
233
|
-
auth: all
|
|
234
|
-
Person:
|
|
235
|
-
prefix: /api/person
|
|
236
|
-
endpoints:
|
|
237
|
-
- method: GET
|
|
238
|
-
path: /
|
|
239
|
-
useCase: Person:list
|
|
240
|
-
auth: all
|
|
241
|
-
|
|
242
|
-
web:
|
|
243
|
-
Cat:
|
|
244
|
-
prefix: /cat
|
|
245
|
-
layout: main_view
|
|
246
|
-
pages:
|
|
247
|
-
- path: /
|
|
248
|
-
useCase: Cat:list
|
|
249
|
-
view: catList
|
|
250
|
-
auth: all
|
|
251
|
-
Person:
|
|
252
|
-
prefix: /person
|
|
253
|
-
layout: main_view
|
|
254
|
-
pages:
|
|
255
|
-
- path: /
|
|
256
|
-
useCase: Person:list
|
|
257
|
-
view: personList
|
|
258
|
-
auth: all
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
**Model Relationships:**
|
|
262
|
-
|
|
263
|
-
Define relationships by using another model's name as the field type in `domain.aggregates`:
|
|
264
|
-
|
|
265
|
-
```yaml
|
|
266
|
-
domain:
|
|
267
|
-
aggregates:
|
|
268
|
-
Owner:
|
|
269
|
-
root: true
|
|
270
|
-
fields:
|
|
271
|
-
name: { type: string, required: true }
|
|
272
|
-
|
|
273
|
-
Cat:
|
|
274
|
-
root: true
|
|
275
|
-
fields:
|
|
276
|
-
name: { type: string, required: true }
|
|
277
|
-
owner: { type: Owner, required: true } # Creates relationship with Owner model
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**Generated Behavior:**
|
|
281
|
-
- **Domain Model**: Rich object with `owner: Owner` (full object, not just ID)
|
|
282
|
-
- **DTOs**: Use `ownerId: number` for API requests
|
|
283
|
-
- **Database**: Stores `ownerId` foreign key column (references `Owner.id`)
|
|
284
|
-
- **Store**: Automatically loads the related Owner object when fetching a Cat
|
|
285
|
-
- **HTML Forms**: Auto-generates select dropdown with "Create New" button
|
|
286
|
-
- **TypeScript**: Full type safety with proper imports
|
|
287
|
-
|
|
288
|
-
**Naming Convention:**
|
|
289
|
-
Foreign keys are auto-generated following the pattern `fieldName + 'Id'`:
|
|
290
|
-
- `owner` → `ownerId`
|
|
291
|
-
- `author` → `authorId`
|
|
292
|
-
- `parentComment` → `parentCommentId`
|
|
293
|
-
|
|
294
|
-
The foreign key always references the `id` field of the related model.
|
|
295
|
-
|
|
296
|
-
**🔄 Handler vs Use Case Architecture:**
|
|
297
|
-
- **Handler**: Creates a separate service method (one handler = one service method)
|
|
298
|
-
- **Use Case**: Defined under `useCases.ModelName.actionName`, calls handler methods step-by-step
|
|
299
|
-
- **UseCase reference**: Used in `api`/`web` endpoints as `ModelName:actionName` (e.g., `Post:list`)
|
|
300
|
-
|
|
301
|
-
**Built-in Handlers (used in `useCases.*.*.handlers`):**
|
|
302
|
-
- `default:list` - Creates service method with pagination parameters
|
|
303
|
-
- `default:get` - Creates service method named `get` with ID parameter
|
|
304
|
-
- `default:create` - Creates service method with DTO parameter
|
|
305
|
-
- `default:update` - Creates service method with ID and DTO parameters
|
|
306
|
-
- `default:delete` - Creates service method with ID parameter
|
|
307
|
-
|
|
308
|
-
Note: Handlers within `useCases` do NOT need a model prefix because the model is already the key.
|
|
309
|
-
|
|
310
|
-
**Custom Handlers:**
|
|
311
|
-
- `customMethodName` - Creates service method that accepts `(result, input)` parameters
|
|
312
|
-
- `result`: Result from previous handler (or `null` if it's the first handler)
|
|
313
|
-
- `input`: The parsed input DTO
|
|
314
|
-
- Each handler generates a separate method in the service
|
|
315
|
-
- User can customize the implementation after generation
|
|
316
|
-
|
|
317
|
-
**🔗 Multiple Handlers per Use Case:**
|
|
318
|
-
When a use case has multiple handlers, each handler generates a separate service method, and the use case orchestrator calls them sequentially. The use case returns the result from the last handler.
|
|
319
|
-
|
|
320
|
-
**Parameter Passing Rules:**
|
|
321
|
-
- **Default handlers** (`default:*`): Receive standard parameters (id, pagination, DTO, etc.)
|
|
322
|
-
- **Custom handlers**: Receive `(result, input)` where:
|
|
323
|
-
- `result`: Result from previous handler, or `null` if it's the first handler
|
|
324
|
-
- `input`: Parsed input DTO
|
|
325
|
-
|
|
326
|
-
**Handler Format Examples (inside `useCases`):**
|
|
327
|
-
- `default:list` - Creates service method `list(page, limit)`
|
|
328
|
-
- `default:get` - Creates service method `get(id)`
|
|
329
|
-
- `validateContent` - Creates service method `validateContent(result, input)`
|
|
330
|
-
- `notifySubscribers` - Creates service method `notifySubscribers(result, input)`
|
|
331
|
-
|
|
332
|
-
**Form Success/Error Handling (in `web` pages):**
|
|
333
|
-
|
|
334
|
-
Instead of a separate `strategy` field, use `onSuccess` and `onError` on web page endpoints:
|
|
335
|
-
```yaml
|
|
336
|
-
onSuccess:
|
|
337
|
-
redirect: /posts/:id # Redirect to URL
|
|
338
|
-
toast: "Saved!" # Show toast notification
|
|
339
|
-
back: true # Navigate back in browser history
|
|
340
|
-
stay: true # Stay on current page
|
|
341
|
-
onError:
|
|
342
|
-
stay: true # Stay on current page
|
|
343
|
-
toast: error # Show error toast
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
The template generator converts `onSuccess` options into `data-strategy` attributes on HTML forms.
|
|
347
|
-
|
|
348
|
-
**Auth Configuration (per endpoint in `api` and `web`):**
|
|
349
|
-
- `all` - Anyone (including anonymous users)
|
|
350
|
-
- `authenticated` - Any logged-in user
|
|
351
|
-
- `owner` - User who created the entity (checks `ownerId` field)
|
|
352
|
-
- `admin`, `editor`, `user` - Custom roles from JWT token
|
|
353
|
-
- `[owner, admin]` - Array syntax: user must match ANY (OR logic). Privileged roles bypass ownership check.
|
|
354
|
-
|
|
355
|
-
**Generated Files from Configuration:**
|
|
356
|
-
- Domain entity class (one per model)
|
|
357
|
-
- Use case orchestrator (one per model)
|
|
358
|
-
- Service class with handler methods (one per model)
|
|
359
|
-
- Input/Output DTOs (one per use case)
|
|
360
|
-
- API controller with REST endpoints (one per model)
|
|
361
|
-
- Web controller with page rendering (one per model)
|
|
362
|
-
- Store class with database operations (one per model)
|
|
363
|
-
- HTML templates for all views
|
|
364
|
-
|
|
365
|
-
**Multi-Model Support:**
|
|
366
|
-
- Each model gets its own Service, UseCase, Controller, and Store classes
|
|
367
|
-
- In `api`/`web`, each model is a separate key (e.g., `api.Cat`, `api.Person`)
|
|
368
|
-
- UseCase references use `ModelName:actionName` format (e.g., `Post:list`, `Person:create`)
|
|
369
|
-
- Handlers within `useCases` do not need model prefix (model is already the key)
|
|
370
|
-
- Controllers and services are generated per model, not per module
|
|
371
|
-
|
|
372
|
-
## Dependency Injection & Wiring
|
|
373
|
-
|
|
374
|
-
The application uses a decorator-driven DI system. All wiring is auto-generated in `src/app.ts` between `// currentjs:controllers:start` and `// currentjs:controllers:end` markers. **Never edit this block manually** — it is regenerated on each `current generate`.
|
|
375
|
-
|
|
376
|
-
### `@Injectable` Decorator
|
|
377
|
-
|
|
378
|
-
Lives in `src/system.ts`. Marks a class for automatic DI discovery:
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
import { Injectable } from '../../../../system';
|
|
382
|
-
|
|
383
|
-
@Injectable()
|
|
384
|
-
export class MyService {
|
|
385
|
-
constructor(private myStore: MyStore) {}
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
Generated Stores, Services, and UseCases already have `@Injectable()`. Controllers use `@Controller()` from `@currentjs/router` instead.
|
|
390
|
-
|
|
391
|
-
### Adding Custom Classes to DI
|
|
392
|
-
|
|
393
|
-
If you create a new class that should be auto-wired:
|
|
394
|
-
1. Add `@Injectable()` decorator (import from `src/system.ts`)
|
|
395
|
-
2. Declare dependencies as constructor parameters with their types
|
|
396
|
-
3. Run `current generate` — the class will be auto-imported and instantiated in `app.ts`
|
|
397
|
-
4. Run `current commit` to preserve your changes
|
|
398
|
-
|
|
399
|
-
### How Wiring Works
|
|
400
|
-
|
|
401
|
-
The generator:
|
|
402
|
-
1. Scans all module `.ts` files for `@Injectable` and `@Controller` decorators
|
|
403
|
-
2. Parses constructor parameters to build a dependency graph
|
|
404
|
-
3. Topologically sorts classes (stores → services → use cases → controllers)
|
|
405
|
-
4. Generates imports, instantiations, and the `controllers` array in `app.ts`
|
|
406
|
-
|
|
407
|
-
Database provider instances are injected into stores based on `app.yaml` configuration (global, with optional per-module overrides). Both npm packages and local file paths are supported as providers.
|
|
408
|
-
|
|
409
|
-
## Module Structure
|
|
410
|
-
```
|
|
411
|
-
src/modules/ModuleName/
|
|
412
|
-
├── domain/
|
|
413
|
-
│ ├── entities/
|
|
414
|
-
│ │ └── Entity.ts # Domain model (aggregate root)
|
|
415
|
-
│ └── valueObjects/
|
|
416
|
-
│ └── ValueObject.ts # Value objects (if any)
|
|
417
|
-
├── application/
|
|
418
|
-
│ ├── useCases/
|
|
419
|
-
│ │ └── EntityUseCase.ts # Use case orchestrator
|
|
420
|
-
│ ├── services/
|
|
421
|
-
│ │ └── EntityService.ts # Business logic handlers
|
|
422
|
-
│ └── dto/
|
|
423
|
-
│ └── EntityAction.ts # Input/Output DTOs per action
|
|
424
|
-
├── infrastructure/
|
|
425
|
-
│ ├── controllers/
|
|
426
|
-
│ │ ├── EntityApiController.ts # REST API endpoints
|
|
427
|
-
│ │ └── EntityWebController.ts # Web page controllers
|
|
428
|
-
│ └── stores/
|
|
429
|
-
│ └── EntityStore.ts # Data access implementation
|
|
430
|
-
├── views/
|
|
431
|
-
│ ├── entityList.html # List view template
|
|
432
|
-
│ ├── entityDetail.html # Detail view template
|
|
433
|
-
│ ├── entityCreate.html # Create form template
|
|
434
|
-
│ └── entityEdit.html # Edit form template
|
|
435
|
-
└── modulename.yaml # Module configuration
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
## Best Practices
|
|
439
|
-
|
|
440
|
-
- Use Domain Driven Design and Clean Architecture (kind of).
|
|
441
|
-
- Prefer declarative configuration over imperative programming: when possible, change yamls instead of writing the code. Write the code only when it's really neccessary.
|
|
442
|
-
- CRUD operation are autogenerated (first in module's yaml, then in the generated code).
|
|
443
|
-
- If some custom action is needed, then it has to be defined in the **Service** then just put this action to the module's yaml. You may also define new methods in the *Store* and its interface (if needed).
|
|
444
|
-
- Business rules must be defined only in models. That also applies to business rules validations (in contrast to *just* validations: e.g. if field exists and is of needed type – then validators are in use)
|
|
445
|
-
|
|
446
|
-
## Core Package APIs (For Generated Code)
|
|
447
|
-
|
|
448
|
-
### @currentjs/router - Controller Decorators & Context
|
|
449
|
-
|
|
450
|
-
**Decorators (in controllers):**
|
|
451
|
-
```typescript
|
|
452
|
-
@Controller('/api/posts') // Base path for controller
|
|
453
|
-
@Get('/') // GET endpoint
|
|
454
|
-
@Get('/:id') // GET with path parameter
|
|
455
|
-
@Post('/') // POST endpoint
|
|
456
|
-
@Put('/:id') // PUT endpoint
|
|
457
|
-
@Delete('/:id') // DELETE endpoint
|
|
458
|
-
@Render('template', 'layout') // For web controllers
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
**Context Object (in route handlers):**
|
|
462
|
-
```typescript
|
|
463
|
-
interface IContext {
|
|
464
|
-
request: {
|
|
465
|
-
url: string;
|
|
466
|
-
path: string;
|
|
467
|
-
method: string;
|
|
468
|
-
parameters: Record<string, string | number>; // Path params + query params
|
|
469
|
-
body: any; // Parsed JSON or raw string
|
|
470
|
-
headers: Record<string, string | string[]>;
|
|
471
|
-
user?: AuthenticatedUser; // Parsed JWT user if authenticated
|
|
472
|
-
};
|
|
473
|
-
response: Record<string, any>;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Usage examples
|
|
477
|
-
const id = parseInt(ctx.request.parameters.id as string);
|
|
478
|
-
const page = parseInt(ctx.request.parameters.page as string) || 1;
|
|
479
|
-
const payload = ctx.request.body;
|
|
480
|
-
const user = ctx.request.user; // If authenticated
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
**Authentication Support:**
|
|
484
|
-
- JWT tokens parsed automatically from `Authorization: Bearer <token>` header
|
|
485
|
-
- User object available at `ctx.request.user` with `id`, `email`, `role` fields
|
|
486
|
-
- No manual setup required in generated code
|
|
487
|
-
|
|
488
|
-
### @currentjs/templating - Template Syntax
|
|
489
|
-
|
|
490
|
-
**Variables & Data Access:**
|
|
491
|
-
```html
|
|
492
|
-
{{ variableName }}
|
|
493
|
-
{{ object.property }}
|
|
494
|
-
{{ $root.arrayData }} <!-- Access root data -->
|
|
495
|
-
{{ $index }} <!-- Loop index -->
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
**Control Structures:**
|
|
499
|
-
```html
|
|
500
|
-
<!-- Loops -->
|
|
501
|
-
<tbody x-for="$root" x-row="item">
|
|
502
|
-
<tr>
|
|
503
|
-
<td>{{ item.name }}</td>
|
|
504
|
-
<td>{{ $index }}</td>
|
|
505
|
-
</tr>
|
|
506
|
-
</tbody>
|
|
507
|
-
|
|
508
|
-
<!-- Conditionals -->
|
|
509
|
-
<div x-if="user.isAdmin">Admin content</div>
|
|
510
|
-
<span x-if="errors.name">{{ errors.name }}</span>
|
|
511
|
-
|
|
512
|
-
<!-- Template includes -->
|
|
513
|
-
<userCard name="{{ user.name }}" role="admin" />
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
**Layout Integration:**
|
|
517
|
-
- Templates use `<!-- @template name="templateName" -->` header
|
|
518
|
-
- Main layout gets `{{ content }}` variable
|
|
519
|
-
- Forms use `{{ formData.field || '' }}` for default values
|
|
520
|
-
|
|
521
|
-
### @currentjs/provider-mysql - Database Operations
|
|
522
|
-
|
|
523
|
-
**Query Execution (in stores):**
|
|
524
|
-
```typescript
|
|
525
|
-
// Named parameters (preferred)
|
|
526
|
-
const result = await this.db.query(
|
|
527
|
-
'SELECT * FROM users WHERE status = :status AND age > :minAge',
|
|
528
|
-
{ status: 'active', minAge: 18 }
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
// Result handling
|
|
532
|
-
if (result.success && result.data.length > 0) {
|
|
533
|
-
return result.data.map(row => this.rowToModel(row));
|
|
534
|
-
}
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
**Common Query Patterns:**
|
|
538
|
-
```typescript
|
|
539
|
-
// Insert with auto-generated fields
|
|
540
|
-
const row = { ...data, created_at: new Date(), updated_at: new Date() };
|
|
541
|
-
const result = await this.db.query(
|
|
542
|
-
`INSERT INTO table_name (\${fields.join(', ')}) VALUES (\${placeholders})`,
|
|
543
|
-
row
|
|
544
|
-
);
|
|
545
|
-
const newId = result.insertId;
|
|
546
|
-
|
|
547
|
-
// Update with validation
|
|
548
|
-
const query = `UPDATE table_name SET \${updateFields.join(', ')}, updated_at = :updated_at WHERE id = :id`;
|
|
549
|
-
await this.db.query(query, { ...updateData, updated_at: new Date(), id });
|
|
550
|
-
|
|
551
|
-
// Soft delete
|
|
552
|
-
await this.db.query(
|
|
553
|
-
'UPDATE table_name SET deleted_at = :deleted_at WHERE id = :id',
|
|
554
|
-
{ deleted_at: new Date(), id }
|
|
555
|
-
);
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
**Error Handling:**
|
|
559
|
-
```typescript
|
|
560
|
-
try {
|
|
561
|
-
const result = await this.db.query(query, params);
|
|
562
|
-
} catch (error) {
|
|
563
|
-
if (error instanceof MySQLConnectionError) {
|
|
564
|
-
throw new Error(`Database connection error: \${error.message}`);
|
|
565
|
-
} else if (error instanceof MySQLQueryError) {
|
|
566
|
-
throw new Error(`Query error: \${error.message}`);
|
|
567
|
-
}
|
|
568
|
-
throw error;
|
|
569
|
-
}
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
## Frontend System (web/app.js)
|
|
573
|
-
|
|
574
|
-
### Translation System
|
|
575
|
-
|
|
576
|
-
**Basic Usage:**
|
|
577
|
-
```javascript
|
|
578
|
-
// Translate strings
|
|
579
|
-
App.lang.t('Hello World') // Returns translated version or original
|
|
580
|
-
App.lang.t('Save changes')
|
|
581
|
-
|
|
582
|
-
// Set language
|
|
583
|
-
App.lang.set('pl') // Switch to Polish
|
|
584
|
-
App.lang.set('en') // Switch to English
|
|
585
|
-
App.lang.get() // Get current language code
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
**Translation File (web/translations.json):**
|
|
589
|
-
```json
|
|
590
|
-
{
|
|
591
|
-
"pl": {
|
|
592
|
-
"Hello World": "Witaj Świecie",
|
|
593
|
-
"Save changes": "Zapisz zmiany",
|
|
594
|
-
"Delete": "Usuń"
|
|
595
|
-
},
|
|
596
|
-
"ru": {
|
|
597
|
-
"Hello World": "Привет мир",
|
|
598
|
-
"Save changes": "Сохранить изменения"
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
```
|
|
602
|
-
|
|
603
|
-
### UI Feedback & Notifications
|
|
604
|
-
|
|
605
|
-
**Toast Notifications:**
|
|
606
|
-
```javascript
|
|
607
|
-
App.ui.showToast('Success message', 'success') // Green toast
|
|
608
|
-
App.ui.showToast('Error occurred', 'error') // Red toast
|
|
609
|
-
App.ui.showToast('Information', 'info') // Blue toast
|
|
610
|
-
App.ui.showToast('Warning', 'warning') // Yellow toast
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
**Inline Messages:**
|
|
614
|
-
```javascript
|
|
615
|
-
App.ui.showMessage('messageId', 'Success!', 'success')
|
|
616
|
-
App.ui.showMessage('errorContainer', 'Validation failed', 'error')
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
**Modal Dialogs:**
|
|
620
|
-
```javascript
|
|
621
|
-
App.ui.showModal('confirmModal', 'Item saved successfully', 'success')
|
|
622
|
-
App.ui.showModal('errorModal', 'Operation failed', 'error')
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
### Navigation & Page Actions
|
|
626
|
-
|
|
627
|
-
**Navigation Functions:**
|
|
628
|
-
```javascript
|
|
629
|
-
// SPA-style navigation
|
|
630
|
-
App.nav.go('/posts/123') // Loads via AJAX, updates #main
|
|
631
|
-
|
|
632
|
-
// Or use native browser APIs directly
|
|
633
|
-
window.history.back() // Go back in history
|
|
634
|
-
window.location.href = '/posts' // Full page redirect
|
|
635
|
-
window.location.reload() // Reload page
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Form Handling & Strategy System
|
|
639
|
-
|
|
640
|
-
**Form Strategy Configuration:**
|
|
641
|
-
```html
|
|
642
|
-
<!-- Form with strategy attributes -->
|
|
643
|
-
<form data-strategy='["toast", "back"]'
|
|
644
|
-
data-entity-name="Post"
|
|
645
|
-
data-field-types='{"age": "number", "active": "boolean"}'>
|
|
646
|
-
<input name="title" type="text" required>
|
|
647
|
-
<input name="age" type="number">
|
|
648
|
-
<input name="active" type="checkbox">
|
|
649
|
-
<button type="submit">Save</button>
|
|
650
|
-
</form>
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
**Available Strategies:**
|
|
654
|
-
- `toast` - Show success toast notification
|
|
655
|
-
- `back` - Navigate back using browser history
|
|
656
|
-
- `message` - Show inline message in specific element
|
|
657
|
-
- `modal` - Show modal dialog
|
|
658
|
-
- `redirect` - Redirect to specific URL
|
|
659
|
-
- `refresh` - Reload the page
|
|
660
|
-
- `remove` - Remove form element
|
|
661
|
-
|
|
662
|
-
**Manual Form Submission:**
|
|
663
|
-
```javascript
|
|
664
|
-
const form = document.querySelector('#myForm');
|
|
665
|
-
App.nav.submit(form, ['toast', 'back'], {
|
|
666
|
-
entityName: 'Post',
|
|
667
|
-
basePath: '/posts',
|
|
668
|
-
messageId: 'form-message'
|
|
669
|
-
});
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
### Loading States & Utilities
|
|
673
|
-
|
|
674
|
-
**Loading Indicators:**
|
|
675
|
-
```javascript
|
|
676
|
-
App.ui.showLoading('#form') // Show spinner on form
|
|
677
|
-
App.ui.hideLoading('#form') // Hide spinner
|
|
678
|
-
App.ui.showLoading('#main') // Show spinner on main content
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
**Utility Functions:**
|
|
682
|
-
```javascript
|
|
683
|
-
App.utils.debounce(searchFunction, 300) // Debounce for search inputs
|
|
684
|
-
App.utils.$('#selector') // Safe element selection
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
### Event Handling & SPA Integration
|
|
688
|
-
|
|
689
|
-
**Automatic Link Handling:**
|
|
690
|
-
- Internal links automatically use AJAX navigation
|
|
691
|
-
- External links work normally
|
|
692
|
-
- Links with `data-external` skip AJAX handling
|
|
693
|
-
|
|
694
|
-
**Automatic Form Handling:**
|
|
695
|
-
- Forms with `data-strategy` use AJAX submission
|
|
696
|
-
- Regular forms work normally
|
|
697
|
-
- Automatic JSON conversion from FormData
|
|
698
|
-
|
|
699
|
-
**Custom Event Listeners:**
|
|
700
|
-
```javascript
|
|
701
|
-
// Re-initialize after dynamic content loading
|
|
702
|
-
App.utils.initializeEventListeners();
|
|
703
|
-
|
|
704
|
-
// Handle specific link navigation
|
|
705
|
-
document.querySelector('#myLink').addEventListener('click', (e) => {
|
|
706
|
-
e.preventDefault();
|
|
707
|
-
App.nav.go('/custom/path');
|
|
708
|
-
});
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
### Global App Object
|
|
712
|
-
|
|
713
|
-
**Accessing Functions:**
|
|
714
|
-
```javascript
|
|
715
|
-
// All functions available under window.App
|
|
716
|
-
// Organized by category for better discoverability
|
|
717
|
-
|
|
718
|
-
// UI Functions
|
|
719
|
-
App.ui.showToast('Message', 'success');
|
|
720
|
-
App.ui.showMessage('elementId', 'Success!', 'success');
|
|
721
|
-
App.ui.showModal('modalId', 'Done!', 'success');
|
|
722
|
-
App.ui.showLoading('#form');
|
|
723
|
-
App.ui.hideLoading('#form');
|
|
724
|
-
|
|
725
|
-
// Navigation Functions
|
|
726
|
-
App.nav.go('/posts/123'); // SPA-style navigation with AJAX
|
|
727
|
-
App.nav.submit(formElement, ['toast', 'back'], options); // Submit form via AJAX
|
|
728
|
-
|
|
729
|
-
// Translation Functions
|
|
730
|
-
App.lang.t('Translate this'); // Translate string
|
|
731
|
-
App.lang.set('pl'); // Set language
|
|
732
|
-
App.lang.get(); // Get current language
|
|
733
|
-
|
|
734
|
-
// Utility Functions
|
|
735
|
-
App.utils.$('#selector'); // Safe element selection
|
|
736
|
-
App.utils.debounce(fn, 300); // Debounce function
|
|
737
|
-
App.utils.initializeEventListeners(); // Re-initialize after dynamic content
|
|
738
|
-
|
|
739
|
-
// Authentication Functions (JWT)
|
|
740
|
-
App.auth.setAuthToken(token); // Store JWT token
|
|
741
|
-
App.auth.clearAuthToken(); // Remove JWT token
|
|
742
|
-
App.auth.buildAuthHeaders(additionalHeaders); // Build headers with auth token
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
### Template Data Binding
|
|
746
|
-
```html
|
|
747
|
-
<!-- List with pagination -->
|
|
748
|
-
<tbody x-for="modules" x-row="module">
|
|
749
|
-
<tr>
|
|
750
|
-
<td>{{ module.name }}</td>
|
|
751
|
-
<td><a href="/module/{{ module.id }}">View</a></td>
|
|
752
|
-
</tr>
|
|
753
|
-
</tbody>
|
|
754
|
-
|
|
755
|
-
<!-- Form with validation errors -->
|
|
756
|
-
<div x-if="errors.name" class="text-danger">{{ errors.name }}</div>
|
|
757
|
-
<input type="text" name="name" value="{{ formData.name || '' }}" class="form-control">
|
|
758
|
-
|
|
759
|
-
<!-- Form with strategy attributes -->
|
|
760
|
-
<form data-strategy='["toast", "back"]'
|
|
761
|
-
data-entity-name="Module"
|
|
762
|
-
data-field-types='{"count": "number", "active": "boolean"}'>
|
|
763
|
-
<!-- form fields -->
|
|
764
|
-
</form>
|
|
765
|
-
```
|
|
21
|
+
10. Run current diff before modifying any generated file. It shows exactly what's been committed (safe to keep) vs. what's purely generated (will be overwritten). Use it as orientation before every non-yaml change.
|