Manifest Reference
Everything in Qleany is defined in qleany.yaml. This document covers all manifest options. The UI is still the primary
way to create and edit manifests, but this reference helps when you need to edit the file directly. Or if you are curious.
Example Manifests
Real-world manifests you can reference:
| Project | Language | Frontend | Link |
|---|---|---|---|
| Skribisto | C++20 / Qt6 | QtQuick | qleany.yaml |
| Qleany | Rust 2024 | Slint + CLI | qleany.yaml |
Basic Structure
schema:
version: 2
global:
language: cpp-qt # rust, cpp-qt
application_name: MyApp
organisation:
name: myorg
domain: myorg.com
prefix_path: src
entities:
- name: EntityBase
# ...
features:
- name: my_feature
# ...
Required Base Entity
All entities must have id, created_at, and updated_at fields. These are essential for identity, caching, and change tracking.
To simplify this, Qleany provides EntityBase — a heritable entity with these three fields pre-defined. When you create a new manifest, Qleany automatically generates:
EntityBasewithid,created_at,updated_at- An empty
Rootentity inheriting fromEntityBase
All your entities should inherit from EntityBase using the inherits_from field.
entities:
# EntityBase provides the necessary id, created_at, updated_at
- name: EntityBase
only_for_heritage: true
allow_direct_access: false
fields:
- name: id
type: uinteger
- name: created_at
type: datetime
- name: updated_at
type: datetime
- name: Root
inherits_from: EntityBase
undoable: false
fields:
# Your root-level relationships here
Complete Example
schema:
version: 2
global:
language: cpp-qt
application_name: MyApp
organisation:
name: myorg
domain: myorg.com
prefix_path: src
entities:
- name: EntityBase
only_for_heritage: true
allow_direct_access: false
fields:
- name: id
type: uinteger
- name: created_at
type: datetime
- name: updated_at
type: datetime
- name: Root
inherits_from: EntityBase
undoable: false
fields:
- name: works
type: entity
entity: Work
relationship: ordered_one_to_many
strong: true
list_model: true
list_model_displayed_field: title
- name: Work
inherits_from: EntityBase
undoable: true
single_model: true
fields:
- name: title
type: string
- name: binders
type: entity
entity: Binder
relationship: ordered_one_to_many
strong: true
list_model: true
list_model_displayed_field: name
- name: tags
type: entity
entity: BinderTag
relationship: one_to_many
strong: true
- name: Binder
inherits_from: EntityBase
undoable: true
fields:
- name: name
type: string
- name: items
type: entity
entity: BinderItem
relationship: ordered_one_to_many
strong: true
list_model: true
list_model_displayed_field: title
- name: BinderItem
inherits_from: EntityBase
undoable: true
fields:
- name: title
type: string
- name: parentItem
type: entity
entity: BinderItem
relationship: many_to_one
- name: tags
type: entity
entity: BinderTag
relationship: many_to_many
features:
- name: work_management
use_cases:
- name: load_work
undoable: false
entities: [Root, Work, Binder, BinderItem]
dto_in:
name: LoadWorkDto
fields:
- name: file_path
type: string
dto_out:
name: LoadWorkResultDto
fields:
- name: work_id
type: integer
Entity Options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Entity name (PascalCase) |
inherits_from | string | none | Parent entity for inheritance |
only_for_heritage | bool | false | Entity used only as base class |
undoable | bool | false | Enable undo/redo for this entity’s controller |
allow_direct_access | bool | true | Generate files in direct_access/ for UI access |
single_model | bool | false | Generate Single{Entity} QML wrapper (C++/Qt only) |
Field options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Field name (snake_case) |
type | string | required | Field type (see below) |
entity | string | none | For entity type, name of the entity |
relationship | string | none | For entity type, relationship type (see below) |
optional | bool | false | For one_to_one and many_to_one |
strong | bool | false | For one_to_one, one_to_many, and ordered_one_to_many, enable cascade deletion |
list_model | bool | false | For C++/Qt only, generate a C++ QAbstractListModel and its QML wrapper for this relationship field |
list_model_displayed_field | string | none | For C++/Qt only, default display role for the generated ListModel |
enum_name | string | none | For enum type, name of the enum (PascalCase) |
enum_values | array | none | For enum type, list of enum values (PascalCase) |
Field Types
| Type | Description | Example |
|---|---|---|
boolean | True/false value | is_active: true |
integer | Whole number | count: 42 |
float | Decimal number | price: 19.99 |
string | Text | name: "Alice" |
uuid | Unique identifier | id: "550e8400-..." |
datetime | Date and time | created_at: "2024-01-15T10:30:00" |
entity | Relationship to another entity | See relationship section |
enum | Enumerated value | See enum section |
Enum Fields
- name: status
type: enum
enum_name: CarStatus
enum_values:
- Available
- Reserved
- Sold
Like entities, the enum name should be PascalCase. Enum values should also be PascalCase. And the name must be unique.
Relationship Fields
This section will seem to be a bit repetitive, but for those not familiar with database relationships, it’s important to get all the details right. And some people have different ways of understanding relationships, so I want to be as clear as possible. Bear with me.
When type: entity, additional options define the relationship:
Relationship Types
| Relationship | Junction Type | Return Type (C++ / Rust) |
|---|---|---|
one_to_one | OneToOne | std::optional<int> / Option<i64> |
many_to_one | ManyToOne | std::optional<int> / Option<i64> |
one_to_many | UnorderedOneToMany | QList<int> / Vec<i64> |
ordered_one_to_many | OrderedOneToMany | QList<int> / Vec<i64> |
many_to_many | UnorderedManyToMany | QList<int> / Vec<i64> |
Relationship Flags
| Flag | Valid for | Effect |
|---|---|---|
optional | one_to_one, many_to_one | Validated on create/update (0..1 instead of 1..1) |
strong | one_to_one, one_to_many, ordered_one_to_many | Cascade deletion — removing parent removes children |
QML Generation Flags (C++/Qt only)
| Flag | Effect |
|---|---|
list_model | Generate {Entity}ListModelFrom{Parent}{Relationship} |
list_model_displayed_field | Default display role for the list model |
Validation Rules
| Flag | one_to_one | many_to_one | one_to_many | ordered_one_to_many | many_to_many |
|---|---|---|---|---|---|
optional | ✓/✗ | ✓/✗ | N.A. | N.A. | N.A. |
strong | ✓/✗ (see note) | N.A. | ✓/✗ | ✓/✗ | N.A. |
N.A.: Not applicable for this relationship type. There will be no change in generated code, or don’t use them. When you write code, an empty list expresses the intent to hold no relationship.
Note: If one_to_one holds a weak relationship (strong: false), it couldn’t be required (so, it must be optional: true). There is the risk of a dangling reference if the entity targeted by the reference is deleted.
Some invalid combinations are rejected at manifest parsing. This validation step is still in the works.
Understanding Relationships
Database relationships describe how entities connect. Two concepts matter:
Cardinality — How many entities can participate on each side?
- One — At most one entity (0..1 or exactly 1)
- Many — Zero or more entities (0..*)
Direction — Which side “owns” the relationship?
- The parent side holds the list of children
- The child side holds a reference back to its parent (in Qleany case, this reference is hidden from the user)
The reality in Qleany is a bit more nuanced, but this mental model helps understand how to model your data.
┌─────────────────────────────────────────────────────────────┐
│ RELATIONSHIP TYPES │
├─────────────────────────────────────────────────────────────┤
│ │
│ ONE-TO-ONE (1:1) │
│ ┌───────┐ ┌───────┐ │
│ │ User │─────────│Profile│ Each user has one profile │
│ └───────┘ └───────┘ Each profile belongs to │
│ one user │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ONE-TO-MANY (1:N) │
│ ┌───────┐ ┌───────┐ │
│ │Group │────────<│ Item │ One group has many items │
│ └───────┘ └───────┘ Binder.items: [1, 2, 3] │
│ │
│ MANY-TO-ONE (N:1) │
│ ┌───────┐ ┌───────┐ │
│ │ Car │>────────│Brand │ Many items belong to one │
│ └───────┘ └───────┘ brand. Car.brand: 5 │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ MANY-TO-MANY (N:M) │
│ ┌───────┐ ┌───────┐ │
│ │ Item │>───────<│ Tag │ Items have many tags │
│ └───────┘ └───────┘ Tags apply to many items │
│ │
└─────────────────────────────────────────────────────────────┘
Reality in Qleany
Like said earlier, the reality is a bit more nuanced:
- Special junction tables are used for all relationships (even the simpler ones) and “sit” between parent and child tables
- These junction tables can be accessed by parent and child tables equally.
- This means that for every relationship, both sides can see each other (no true “back-reference” concept)
- The relationship type defines how the junction table behaves, and how the parent and child entities see each other.
- In the deeper code, there is always the mentions of a left entity and a right entity (parent and child respectively in the mental model).
This may be easier to understand: all relationships are defined from the perspective of the entity holding the field. This means that:
- For
one_to_one, the entity with the field is one side, the referenced entity is the other side.Car.brand: EntityId - For
many_to_one, the entity with the field is the “many” side (several entities of the same type), the referenced entity is the “one” side.Car.brand: EntityId - For
one_to_manyandordered_one_to_many, the entity with the field is the “one” side, the referenced entity is the “many” side.Car.brand: Vec<EntityId> - For
many_to_many, both sides are “many”.Car.brand: Vec<EntityId>
Why this design? The manifest describes meaning: ownership, cardinality, ordering, whether deletion cascades. The generated code handles implementation: junction tables, column schemas, query patterns, cache invalidation. You think about your domain; the generator thinks about the database. These are deliberately separated.
The cost is storage overhead. A one_to_one that could be a single foreign key column gets its own junction table instead. For a desktop or mobile application with hundreds or thousands of entities, you will never notice. For a web backend serving millions of rows, you would care, but that’s not what Qleany targets.
The payoff is uniformity. Every relationship, regardless of type, goes through the same junction table infrastructure. This means one code path for snapshot/restore (which is what makes undo/redo work on entity trees), one code path for bidirectional navigation (both sides can always see each other), and one code path for the generator to produce. No special cases, no “this relationship is simple enough for a foreign key but that one needs a junction table.” The complexity stays in the infrastructure, not in your head.
Yes, database engineers might cringe at this, but this greatly simplifies the code generation and the overall mental model when designing your entities. They can cringe more when I say there is no notion of foreign keys in Qleany internal database.
When to use each:
| Relationship | Use when… | Example |
|---|---|---|
one_to_one | Exactly one related entity, exclusive | User → Profile |
many_to_one | Many entities reference one child | Car → Brand, Comment → Post |
one_to_many | Parent owns a collection of children | Binder → Items, Post → Comments |
ordered_one_to_many | Same as above, but order matters | Book → Chapters, Playlist → Songs |
many_to_many | Entities share references both ways | Items ↔ Tags, Students ↔ Courses |
There is no ordered_many_to_many because I’m not mad enough to handle that complexity.
Relationship Examples
# Exclusive single reference (0..1) — each side has at most one. UserProfile is owned.
- name: profile
type: entity
entity: UserProfile
relationship: one_to_one
strong: true
optional: true
# Back-reference to parent (N:1) — many children point to one parent
- name: parentItem
type: entity
entity: BinderItem
relationship: many_to_one
# Required back-reference
- name: binder
type: entity
entity: Binder
relationship: many_to_one
# implied:
optional: false
# Unordered children with cascade delete (1:N)
- name: tags
type: entity
entity: BinderTag
relationship: one_to_many
strong: true
# Ordered children (1:N with order)
- name: chapters
type: entity
entity: BinderItem
relationship: ordered_one_to_many
strong: true
# Shared references (N:M)
- name: tags
type: entity
entity: BinderTag
relationship: many_to_many
Weak Relationships
Both many_to_one and many_to_many are always weak — they reference entities owned elsewhere. They cannot have strong: true. In Qleany, the owning side (with a strong relationship) controls cascade deletion. For many_to_one and many_to_many, theorically, it would mean that a child would be deleted only if there was no parent left to “own” it. Too difficult to implement, so no.
Dev note: theoretically, you can play with the junction table code base to support many_to_one with strong ownership, but that would be a nightmare to maintain and reason about.
entities:
- name: Work
fields:
- name: tags # Owns the tags (strong one-to-many)
type: entity
entity: BinderTag
relationship: one_to_many
strong: true
- name: Binder
fields:
- name: items # Owns the items (strong ordered)
type: entity
entity: BinderItem
relationship: ordered_one_to_many
strong: true
- name: BinderItem
fields:
- name: binder # Back-reference (weak many-to-one)
type: entity
entity: Binder
relationship: many_to_one
- name: tags # Shared reference (weak many-to-many)
type: entity
entity: BinderTag
relationship: many_to_many
Rule of thumb: Every entity referenced by a weak relationship (
many_to_oneormany_to_many) must be strongly owned somewhere else in your entity graph. Without strong ownership, entities become orphans with no lifecycle management.
Features and Use Cases
Features group related use cases together.
features:
- name: file_management
use_cases:
- name: load_file
# ...
- name: save_file
# ...
Use Case Options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Use case name (snake_case) |
undoable | bool | false | Generate undo/redo command scaffolding |
read_only | bool | false | No data modification (affects generated code) |
long_operation | bool | false | Async execution with progress (Rust only) |
entities | list | [] | Entities this use case works with |
dto_in | object | none | Input DTO for this use case |
dto_out | object | none | Output DTO for this use case |
In Rust, entities are doing a bit of the legwork for you to define which repositories are injected into the use case struct and prepare the use of a special macro macros::uow_action to simplify unit of work handling. These macro lines must be adapted in your use cases files, and the exact same macros must be repeated in these use cases’ unit of work files. Commentary lines will be generated to help you find and adapt these lines.
Similar macros are offered on the C++/Qt side.
DTOs
Each use case can have input and output DTOs, or only one, or none at all.
use_cases:
- name: import_inventory
dto_in:
name: ImportInventoryDto
fields:
- name: file_path
type: string
- name: skip_header
type: boolean
- name: inventory_type
type: enum
enum_name: InventoryType
enum_values:
- Full
- Incremental
dto_out:
name: ImportReturnDto
fields:
- name: imported_count
type: integer
- name: error_messages
type: string
is_list: true
You can’t put entities in DTOs. Only primitive types are allowed because entities are tied to the database and business logic, while DTOs are simple data carriers. Think of it as a way to control the data flow between the UI and the business logic. You don’t want to expose a password to the user UI just because it’s in an entity.
DTO Field Options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Field name (snake_case) |
type | string | required | Field type (boolean, integer, float, string, uuid, datetime) |
is_list | bool | false | Field is a list/array |
optional | bool | false | Field can be Option<>/std::optional |
enum_name | string | none | For enum type, name of the enum |
enum_values | list | none | For enum type, list of possible values |
User Interface Options
ui:
rust_cli: true
rust_slint: true
cpp_qt_qtwidgets: false
cpp_qt_qtquick: false
These options allow the generation of scaffolding for the UI. They will each live in their own separate folders, also separate from the common backend code.
One backend, many frontends.