@currentjs/gen
v0.5.2
Published
CLI code generator
Maintainers
Readme
@currentjs/gen
A CLI code generator that transforms YAML specifications into fully functional TypeScript applications following clean architecture principles.
Table of Contents
- Installation
- Quick Start
- Development Flow
- Reference
- Module Configuration Overview
- Generated Source Code
- Change Tracking: diff and commit
- Database Migrations
- Template System
- Authorship & Contribution
- License
Installation
npm install -g @currentjs/gen
# or use without installing
npx @currentjs/genQuick Start
Building an application from scratch:
- Initialize a new project:
currentjs init myapp
cd myapp- Create a module:
currentjs create module Blog- Run an interactive command:
currentjs create model Blog:PostIt will:
- ask everything it needs,
- generate yaml config,
- generate a TypeScript source code,
- and build it.
Alternatively, you can:
- edit the generated module YAML at
src/modules/Blog/blog.yaml. Define the domain model fields, use cases, API endpoints, and web routes. - Generate TypeScript files from the YAML configuration:
currentjs generate Blog - If needed, make manual changes to generated files (domain entities, views, services).
- Commit those manual changes so they survive regeneration:
currentjs commit
To add custom (non-CRUD) behavior: define a method in the service, reference it in the module YAML as a handler, regenerate, and commit.
currentjs generate Blog
currentjs commitDevelopment Flow
┌──────────────────┐
│ currentjs init │
└────────┬─────────┘
│
▼
┌────────────────────────┐
│ currentjs create module│
└────────────┬───────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────────┐
│ Edit module YAML │ │ currentjs create model │
│ (define structure) │ │ (interactive wizard) │
└─────────┬──────────┘ └────────────┬───────────┘
│ │
▼ │
┌───────────────────┐ │
│ currentjs generate│ │
└─────────┬─────────┘ │
│ │
└────────────┬──────────────┘
│
▼
┌───────────────────────┐
│ Modify generated files│
│ (entities, views, etc)│
│ (optional step) │
└───────────┬───────────┘
│
▼
┌───────────────────┐
│ currentjs commit │
└───────────────────┘There are two paths after creating a module:
- Interactive wizard (
currentjs create model Blog:Post) — prompts for fields, use cases, routes, and permissions, then generates everything automatically. - Manual editing — edit the module YAML by hand, then run
currentjs generate. This gives full control over every configuration option.
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.
When you need behavior beyond standard CRUD:
- Implement the custom method in the generated service class.
- Reference it in the module YAML as a handler (e.g.,
service:myMethod). - Optionally add API/web endpoints for the new action.
- Regenerate and commit.
TLDR
- module's YAML configurations (plus "commits") are the source of truth.
currentjs create modulecreates module's structure (empty folders, YAML configuration with one model without any fields)currentjs create model: You > YAML >generatecurrentjs generate: YAML + commits > TypeScript > JS- since YAML is the main source of truth, it's also the best place to make changes
- if changes are beyond configuration (require some coding), the best place is (in descending order): model, service
- in order to preserve changes in TypeScript files, use
currentjs commit - templates can be changed freely, they are not regenerated by default.
Reference
For detailed documentation of all CLI commands, YAML configuration options, and field types, see the Reference.
Module Configuration Overview
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.
Layers at a Glance
| YAML Section | Purpose | Generated Files |
|---|---|---|
| domain | Define your data models (aggregates, value objects) | Entity classes, value object classes |
| useCases | Define business operations, input/output shapes, handler chains | Use case orchestrators, services, DTOs |
| api | Define REST API endpoints | API controller |
| web | Define server-rendered pages and forms | Web controller, HTML templates |
A minimal module YAML needs at least domain and useCases. The api and web sections are optional.
→ Reference: Module Configuration
Domain Layer (domain)
The domain layer defines your data models. There are two kinds: aggregates (entities) and value objects.
Aggregates
Aggregates are the main entities. One aggregate should be marked as the root (root: true), which enables ownership tracking (auto-generated ownerId field).
domain:
aggregates:
Post:
root: true
fields:
title: { type: string, required: true }
content: { type: string, required: true }
status: { type: enum, values: [draft, published, archived] }
publishedAt: { type: datetime }Fields like id, ownerId, created_at, updated_at, and deleted_at are added automatically — do not include them.
Available field types: string, number, integer, decimal, boolean, datetime, date, id, json, array, object, enum.
For enum fields, provide the allowed values with values: [...].
Model Relationships
Set the type to another aggregate's name to create a foreign key relationship:
domain:
aggregates:
Author:
root: true
fields:
name: { type: string, required: true }
Post:
root: true
fields:
title: { type: string, required: true }
author: { type: Author, required: true }The generator automatically:
- Creates a foreign key column
authorIdin the database. - Uses the full
Authorobject in the domain model (not just the ID). - Uses
authorId: numberin DTOs for API transmission. - Generates a
<select>dropdown with a "Create New" button in HTML forms. - Wires the related store as a dependency for loading relationships.
Foreign key naming follows the pattern fieldName + 'Id' (e.g., author → authorId).
Child Entities
An aggregate can have child entities listed in the entities field:
domain:
aggregates:
Invoice:
root: true
fields:
number: { type: string, required: true }
entities: [InvoiceItem]
InvoiceItem:
fields:
productName: { type: string, required: true }
quantity: { type: integer, required: true }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.
Value Objects
Value objects are reusable types embedded in aggregates, stored as JSON in the database:
domain:
valueObjects:
Money:
fields:
amount: { type: decimal, constraints: { min: 0 } }
currency: { type: enum, values: [USD, EUR, PLN] }→ Reference: aggregates · valueObjects · Field Types · Child Entities
Use Cases Layer (useCases)
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.
useCases:
Post:
list:
input:
pagination: { type: offset, defaults: { limit: 20, maxLimit: 100 } }
output: { from: Post, pagination: true }
handlers: [default:list]
get:
input: { identifier: id }
output: { from: Post }
handlers: [default:get]
create:
input: { from: Post }
output: { from: Post }
handlers: [default:create]Handlers
Handlers are listed in execution order. Each handler becomes a method on the service class.
Built-in handlers:
| Handler | Description |
|---|---|
| default:list | Paginated list of entities |
| default:get | Fetch by ID |
| default:create | Create with validation |
| default:update | Update by ID |
| default:delete | Soft-delete by ID |
Custom handlers — use methodName (or service:methodName). The generator creates a stub method that receives (result, input):
useCases:
Post:
publish:
input: { identifier: id }
output: { from: Post }
handlers:
- default:get
- validateForPublish
- updatePublishStatusThis generates three service methods called in sequence. Custom methods get a TODO comment for you to fill in.
Input Configuration
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 for the full input specification.
Displaying Child Entities (withChild)
When an aggregate root has child entities, you can show them on the root's pages:
useCases:
Invoice:
list:
withChild: true # Adds a link column to child entities on the list page
# ...
get:
withChild: true # Shows a child entities table on the detail page
# ...→ Reference: useCases · handlers · input · output
API Layer (api)
Defines REST API endpoints. Each model gets its own section keyed by name:
api:
Post:
prefix: /api/posts
endpoints:
- method: GET
path: /
useCase: Post:list
auth: all
- method: POST
path: /
useCase: Post:create
auth: authenticated
- method: PUT
path: /:id
useCase: Post:update
auth: [owner, admin]Each endpoint references a use case with the format ModelName:actionName.
Auth / Roles
The auth field controls access:
| Value | Meaning |
|---|---|
| all | Public, no authentication required |
| authenticated | Any logged-in user (valid JWT) |
| owner | User must own the resource (matched via ownerId) |
| admin, editor, etc. | User must have this role (from JWT) |
| [owner, admin] | OR logic — user matches any of the listed roles |
When owner is combined with privileged roles (e.g., [owner, admin]), privileged roles bypass the ownership check.
Web Layer (web)
Defines server-rendered pages and forms:
web:
Post:
prefix: /posts
layout: main_view
pages:
- path: /
useCase: Post:list
view: postList
auth: all
- path: /create
method: GET
view: postCreate
auth: authenticated
- path: /create
method: POST
useCase: Post:create
auth: authenticated
onSuccess:
redirect: /posts/:id
toast: "Post created"
onError:
stay: true
toast: errorForm submission results are handled with onSuccess / onError:
toast: "message"— show a toast notificationback: true— navigate back in browser historyredirect: /path— redirect to a URL (supports:idplaceholder)stay: true— stay on the current page
Generated Source Code
When you run currentjs generate, the following files are produced for each model defined in a module:
src/modules/<ModuleName>/
domain/
entities/<EntityName>.ts — Domain entity class with typed constructor and setters
valueObjects/<ValueObject>.ts — Value object class (if defined)
application/
dto/<Action>InputDto.ts — Input DTO with parse() and validation
dto/<Action>OutputDto.ts — Output DTO with from() mapper
useCases/<EntityName>UseCase.ts — Use case orchestrator (calls service methods in sequence)
services/<EntityName>Service.ts — Service with handler implementations (CRUD + custom stubs)
infrastructure/
controllers/<EntityName>ApiController.ts — REST endpoints with auth checks
controllers/<EntityName>WebController.ts — Page rendering with form handling
stores/<EntityName>Store.ts — Database access (CRUD, row-to-model conversion, relationships)
views/
<viewName>.html — HTML templates (list, detail, create, edit forms)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.
Each generated class is decorated with @Injectable() or @Controller(), so the DI system discovers and wires them automatically.
→ Reference: Generated File Structure · generate
Change Tracking: diff and commit
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.
How it works
Registry — when files are generated, their content hashes are stored in
registry.json.currentjs diff [module]— compares each generated file's current content against what the generator would produce. Reports files as[clean],[modified], or[missing].currentjs commit [files...]— records the differences between your current files and the generated baseline. Diffs are saved as JSON files in thecommits/directory.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--forceor--skipis set).
Practical workflow
# Generate code
currentjs generate
# Make your changes (service logic, templates, etc.)
# ...
# See what you changed
currentjs diff Blog
# Save your changes
currentjs commit
# Later, after modifying the YAML and regenerating:
currentjs generate
# Your committed changes are reapplied automaticallyRepository strategy
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.
→ Reference: diff · commit · Notes — File Change Tracking · Notes — Commit Mechanism
Database Migrations
The generator can produce SQL migration files based on changes to your domain models.
migrate commit
currentjs migrate commitCollects 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.
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.
After generating the file, the schema state is updated so the next migrate commit only produces a diff of subsequent changes.
migrate push (not yet implemented)
Will apply pending migration files to the database.
migrate update (not yet implemented)
Will compare the live database schema against the current model definitions and generate a migration to bring them in sync.
→ Reference: migrate commit
Template System
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.
Template header
Each template starts with a comment that declares its name:
<!-- @template name="postList" -->Variables
{{ title }}
{{ post.authorName }}
{{ formData.email || '' }}Loops
<tbody x-for="items" x-row="item">
<tr>
<td>{{ item.name }}</td>
<td>{{ $index }}</td>
</tr>
</tbody>x-for specifies the data key to iterate over, x-row names the loop variable. $index gives the current iteration index.
Conditionals
<div x-if="user.isAdmin">Admin-only content</div>
<span x-if="errors.name">{{ errors.name }}</span>Layouts
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 }}.
Forms
Generated forms include data-strategy attributes for the frontend JavaScript to handle submission via AJAX:
<form data-strategy='["toast", "back"]'
data-entity-name="Post"
data-field-types='{"age": "number", "active": "boolean"}'>
<input name="title" type="text" required>
<button type="submit">Save</button>
</form>The data-field-types attribute tells the frontend how to convert form values before sending (e.g., string to number, checkbox to boolean).
Template regeneration behavior
By default, currentjs generate does not overwrite existing HTML templates. Only missing templates are created. Use --with-templates to force regeneration of all templates.
→ Reference: Notes — Template Regeneration · web
Authorship & Contribution
Vibecoded mostly with claude models by Konstantin Zavalny. Yes, it is a vibecoded solution, really.
Any contributions such as bugfixes, improvements, etc are very welcome.
License
GNU Lesser General Public License (LGPL)
It simply means, that you:
- 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).
- can make any modifications, but must distribute those modifications under the LGPL (or a compatible license) and include the original copyright and license notice.
