clearschema
v1.4.0
Published
Language-agnostic Clean Architecture code generator. One schema, 13 languages, 6 framework presets.
Maintainers
Readme
ClearSchema
Read one schema, generate Clean Architecture layers in the language or framework you want.
ClearSchema is a zero-config CLI that parses a Prisma-style schema and emits strict, SOLID-compliant entity, service, repository, and controller code across 13 target languages and 6 framework presets — all from a single source of truth. Hand-written business logic is preserved across regenerations through a marker-aware merge.
npm install -g clearschemaclearschema ./schema.prisma --lang typescript
clearschema ./schema.prisma --framework nestjs
clearschema ./schema.prisma --framework nestjs,spring-boot,fastapi --out ./servicesWhy
You have one canonical data model. You want to ship a TypeScript SDK, a Go microservice, a Spring Boot backend, and an iOS Swift client. Today you either hand-write the same entities four times or stitch together four single-purpose generators. ClearSchema is one tool, one schema, one command per output.
It also preserves your custom code: regenerating after a schema change does not clobber the method bodies you wrote inside the marked blocks.
Install
# Global
npm install -g clearschema
# Per-project (recommended for CI reproducibility)
npm install --save-dev clearschema
npx clearschema ./schema.prisma --lang typescriptNode 18.17 or newer.
Quickstart
1. Write a schema
// schema.prisma
model User {
id String @id
email String @unique
name String
age Int?
createdAt DateTime
active Boolean
}
model Post {
id String @id
title String
body String
authorId String
}2. Generate
clearschema ./schema.prisma --lang typescriptOutput lands in ./ts/:
ts/
├── User.gen.ts # plain interfaces (overwritten on regen)
├── UserRepository.ts # interface + in-memory impl (preserved on regen)
├── Post.gen.ts
└── PostRepository.ts3. Add custom logic
Open UserRepository.ts, find the marker block, drop in your code:
async create(input: UserCreateInput): Promise<User> {
// //ClearSchema:CustomLogic:Start:create
const row: User = { ...input, id: crypto.randomUUID() };
await db.users.insert(row);
return row;
// //ClearSchema:CustomLogic:End:create
}Regenerate after a schema change — your create() body survives.
Three ways to invoke
Zero-config one-shot
clearschema ./schema.prisma --lang ts # TypeScript only
clearschema ./schema.prisma --lang ts,dart,go,python # multi-language
clearschema ./schema.prisma --framework nestjs # framework preset
clearschema ./schema.prisma --framework spring-boot --out backend/srcInteractive
clearschema
# Auto-detects schema.prisma, prompts for language(s) and output dir.Committed config (teams)
clearschema init # writes clearschema.json with your choices
clearschema generate # uses clearschema.json
clearschema watch # live dashboard with debounced regenerationSupported targets
Languages (--lang <name>)
| Language | Aliases | Native type strategy |
|---|---|---|
| TypeScript | ts, typescript | string, number, boolean, Date |
| Dart / Flutter | dart, flutter | String, int, double, DateTime |
| Go | go, golang | string, int64, float64, time.Time |
| Python | py, python | str, int, float, datetime |
| Java | java | String, long, double, Instant |
| Kotlin | kt, kotlin | String, Long, Double, Instant |
| C# / .NET | cs, csharp, dotnet | string, long, double, DateTime |
| Rust | rs, rust | String, i64, f64, chrono::DateTime<Utc> |
| Ruby | rb, ruby | String, Integer, Float, Time |
| PHP | php | string, int, float, \DateTimeImmutable |
| Swift | swift, ios | String, Int64, Double, Date |
| Scala | scala | String, Long, Double, java.time.Instant |
| Elixir | ex, elixir | String.t(), integer(), float(), DateTime.t() |
Frameworks (--framework <slug>)
Run clearschema frameworks to list everything available.
| Framework | Language | Generates per entity |
|---|---|---|
| nestjs | TypeScript | entity, dto (class-validator), service, controller, module |
| fastapi | Python | Pydantic schema (Create/Update/Read), service, router |
| spring-boot | Java | JPA entity, Spring Data repo, @Service, @RestController |
| gin-gorm | Go | GORM-tagged struct, service, Gin handler |
| aspnet | C# | EF Core entity, service, [ApiController] |
| phoenix | Elixir | Ecto schema (changeset), context, controller |
Frameworks auto-derive their language — --framework nestjs already implies
TypeScript; you do not need --lang ts.
How it works
ClearSchema is split into four small, modular stages.
Stage 1 — Parser engine
A focused Prisma-flavoured parser deconstructs the schema into a language-neutral Intermediate Representation (IR):
{
"entities": [
{
"entityName": "User",
"fields": [
{ "name": "id", "type": "String", "isPrimary": true, "isUnique": true, "isNullable": false }
]
}
]
}Errors surface as line-precise diagnostics (line, column, snippet); a single broken row never aborts the generator.
Stage 2 — Polymorphic translation
Each target language ships with a total type-mapping dictionary registered as
Handlebars helpers: {{tsType type}}, {{javaType type}}, {{rustType type}},
{{nullableKotlin type isNullable}}, etc. Helpers throw on unknown types
rather than silently emitting undefined.
Stage 3 — Custom logic preservation
Every mutable file write goes through a marker-aware merge:
- Read existing file (if any).
- Extract every block between
CustomLogic:Start[:name]andCustomLogic:End[:name]. - Render the fresh template.
- Splice preserved blocks back into matching markers.
- Atomic write via
*.tmp+rename.
Marker syntax adapts to the language:
| File extension | Marker style |
|---|---|
| .ts .dart .go .java .kt .cs .rs .swift .php .scala | // //ClearSchema:CustomLogic:Start[:name] |
| .py .rb .ex .exs .sh | # #ClearSchema:CustomLogic:Start[:name] |
Note: the bundled templates currently emit the marker token as
SchemaSync:CustomLogic:Startfor historical compatibility — both forms are recognised by the merge engine.
Templates expose multiple named blocks (create, update, findById, …) so
your edits to one method never collide with another.
Stage 4 — Watcher + dashboard
clearschema watch uses chokidar with a 300 ms debounce to bundle rapid IDE
saves, then renders a locked terminal dashboard showing:
- Compilation time (
Took 142ms) - A tree of generated files grouped by target
- Count of preserved custom-logic blocks per file
- Diagnostics with
line:columnand snippet
Configuration file
For team workflows, commit a clearschema.json at the project root:
{
"schema": "schema.prisma",
"templatesRoot": "templates",
"targets": [
{
"name": "typescript",
"language": "typescript",
"outputDir": "src/generated/ts",
"templates": [
{ "template": "typescript/entity.hbs", "outputPattern": "{{entity}}.gen.ts", "overwrite": true },
{ "template": "typescript/repository.hbs", "outputPattern": "{{entity}}Repository.ts" }
]
},
{
"name": "spring-backend",
"language": "java",
"outputDir": "backend/src/main/java",
"templates": [
{ "template": "java/spring-boot/entity.hbs", "outputPattern": "domain/{{entity}}.java", "overwrite": true },
{ "template": "java/spring-boot/repository.hbs", "outputPattern": "repository/{{entity}}Repository.java", "overwrite": true },
{ "template": "java/spring-boot/service.hbs", "outputPattern": "service/{{entity}}Service.java" },
{ "template": "java/spring-boot/controller.hbs", "outputPattern": "controller/{{entity}}Controller.java" }
]
}
]
}The templatesRoot field is optional — omit it to use ClearSchema's bundled
templates. Set overwrite: true for boilerplate files that should be rewritten
on every run; omit it (default) to enable preservation.
Writing custom templates
Templates are Handlebars with these helpers registered globally:
Type helpers — one per language:
{{tsType}} {{dartType}} {{goType}} {{pyType}} {{javaType}}
{{kotlinType}} {{csType}} {{rustType}} {{rubyType}} {{phpType}}
{{swiftType}} {{scalaType}} {{elixirType}}
Nullable wrappers — wrap the native type in the idiomatic optional shape:
{{nullableTs type isNullable}} → T | null ·
{{nullableDart type isNullable}} → T? ·
{{nullableJava type isNullable}} → Optional<T> ·
{{nullableRust type isNullable}} → Option<T> ·
{{nullableScala type isNullable}} → Option[T] ·
{{nullableElixir type isNullable}} → T | nil
String cases: {{pascalCase x}} {{camelCase x}} {{snakeCase x}}
Subexpression conditionals: {{#if (eq type "String")}}…{{/if}}
Context available in every template: entityName, entityLower,
entitySnake, entityKebab, fields[] (each with name, type,
nativeType, isPrimary, isUnique, isNullable), entity, target.
CLI reference
| Command | Description |
|---|---|
| clearschema | Interactive flow — auto-detects schema, prompts for languages and output dir |
| clearschema <schema> --lang <list> | Zero-config language generation |
| clearschema <schema> --framework <list> | Zero-config framework generation |
| clearschema init | Scaffold a clearschema.json |
| clearschema generate | Render once using clearschema.json |
| clearschema watch | Live dashboard with debounced regeneration |
| clearschema frameworks | List every available framework preset |
| clearschema --help | Top-level help |
| clearschema <command> --help | Per-command help |
| Flag | Description |
|---|---|
| -l, --lang <list> | Comma-separated language slugs |
| -f, --framework <list> | Comma-separated framework slugs (implies --lang) |
| -o, --out <dir> | Output base directory (default: current directory) |
| -c, --config <path> | Path to a clearschema.json |
License
MIT © Osama Riyad
