Entity lifecycle

Table of Contents

A full-stack ORE Studio entity spans seven layers: a domain type, a SQL schema, and five exposure layers (HTTP REST, CLI, shell REPL, Wt web UI, Qt desktop UI). All layers are mandatory for a regular entity — do not skip one without explicit user approval. Use the entity-creator orchestrator skill which creates a TaskCreate task for each layer and drives the sub-skills in order.

Codegen is the primary path for every layer that has a profile; the mustache templates in projects/ores.codegen/library/templates/ are the ground truth for what each layer generates. Four layers (HTTP, CLI, Shell, Wt) have no profile yet — see §Codegen gaps below.

Return to Knowledge.

Layer ordering

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

[domain type\n(ores.*api)] as D
[SQL schema\n(ores.sql)] as S
[HTTP endpoints\n(ores.http.server)] as H
[CLI commands\n(ores.cli)] as C
[shell commands\n(ores.shell)] as SH
[Wt UI\n(ores.wt)] as W
[Qt UI\n(ores.qt.*)] as Q

D --> S  : persists via repository
D --> H  : service → REST routes
D --> C  : service → CLI parser
D --> SH : service → REPL command
D --> W  : service → Wt widget
D --> Q  : service → Qt model
@enduml

Build all layers in order. An exposure layer must not be created before its domain type and SQL schema are merged.

Layer Skill Pattern doc Recipe
Domain type domain-type-creator (this doc §Type mappings)
SQL schema sql-schema-creator SQL entity schema patterns How do I create a new entity SQL schema?
HTTP endpoints http-entity-creator HTTP entity patterns How do I create HTTP endpoints for a new entity?
CLI commands cli-entity-creator CLI entity patterns How do I create CLI commands for a new entity?
Shell commands shell-entity-creator Shell entity patterns How do I create shell commands for a new entity?
Wt UI wt-entity-creator Wt entity patterns How do I create Wt widgets for a new entity?
Qt UI qt-entity-creator Qt entity patterns How do I create Qt UI for a new entity?

Type mappings

Canonical DB → C++ type mapping used by all layers. Codegen reads these from the JSON model and enforces them. Apply the same mapping when writing or reviewing hand-crafted code.

Database type C++ type Include
uuid boost::uuids::uuid <boost/uuid/uuid.hpp>
text std::string <string>
integer int (built-in)
bigint std::int64_t <cstdint>
boolean bool (built-in)
timestamp std::chrono::system_clock::time_point <chrono>
real double (built-in)
bytea std::vector<std::byte> <vector>, <cstddef>
uuid (optional FK) std::optional<boost::uuids::uuid> <optional>, <boost/uuid/uuid.hpp>
uuid (tenant) utility::uuid::tenant_id ores.utility/uuid/tenant_id.hpp

Domain entities use UUID primary keys (id column). Lookup/reference entities use text primary keys with a domain-specific column name (iso_code, code, type, etc.).

Service method naming

The service layer wraps repository operations and is the contract all exposure layers consume. Names must follow this table — do not invent synonyms.

Operation Method pattern Returns
Create or update (upsert) save_<entity> void
Delete remove_<entity> void
Find by primary key find_<entity> std::optional<domain::<entity>>
List all list_<entities> std::vector<domain::<entity>>
List with filter list_<entities>_by_<key> std::vector<domain::<entity>>
List since timestamp list_<entities>_since std::vector<domain::<entity>>
Get full history get_<entity>_history std::vector<domain::<entity>>

save_* exposes the repository's upsert semantics directly. Never split it into separate create_* / update_* methods at the service layer — doing so duplicates repository semantics without adding value.

Complete entity file checklist

The canonical reference is the currency entity. A complete regular entity must have all of the following files (paths are relative to the repo root, * = component-specific prefix):

Domain + infrastructure (ores.*api, generated by --profile all):

File Template
include/ores.*/domain/{entity}.hpp cpp_domain_type_class.hpp.mustache
include/ores.*/io/{entity}_json_io.hpp cpp_domain_type_json_io.hpp.mustache
src/io/{entity}_json_io.cpp cpp_domain_type_json_io.cpp.mustache
include/ores.*/io/{entity}_table.hpp cpp_domain_type_table.hpp.mustache
src/io/{entity}_table.cpp cpp_domain_type_table.cpp.mustache
include/ores.*/io/{entity}_table_io.hpp cpp_domain_type_table_io.hpp.mustache
src/io/{entity}_table_io.cpp cpp_domain_type_table_io.cpp.mustache
include/ores.*/generators/{entity}_generator.hpp cpp_domain_type_generator.hpp.mustache
src/generators/{entity}_generator.cpp cpp_domain_type_generator.cpp.mustache
include/ores.*/messaging/{entity}_protocol.hpp cpp_protocol.hpp.mustache
include/ores.*/service/{entity}_service.hpp cpp_service.hpp.mustache
src/service/{entity}_service.cpp cpp_service.cpp.mustache

Repository (ores.*core, generated by --profile repository):

File Template
include/ores.*.core/repository/{entity}_entity.hpp cpp_domain_type_entity.hpp.mustache
src/repository/{entity}_entity.cpp cpp_domain_type_entity.cpp.mustache
include/ores.*.core/repository/{entity}_mapper.hpp cpp_domain_type_mapper.hpp.mustache
src/repository/{entity}_mapper.cpp cpp_domain_type_mapper.cpp.mustache
include/ores.*.core/repository/{entity}_repository.hpp cpp_domain_type_repository.hpp.mustache
src/repository/{entity}_repository.cpp cpp_domain_type_repository.cpp.mustache

SQL schema (ores.sql, generated by --profile sql):

File Template
create/{component}/{component}_{entity}_create.sql sql_schema_domain_entity_create.mustache
create/{component}/{component}_{entity}_notify_trigger_create.sql sql_schema_notify_trigger.mustache
drop/{component}/{component}_{entity}_drop.sql sql_schema_domain_entity_drop.mustache
drop/{component}/{component}_{entity}_notify_trigger_drop.sql sql_schema_notify_trigger_drop.mustache

Qt UI (ores.qt.{component}, generated by --profile qt):

File Template
include/ores.qt/Client{Entity}Model.hpp cpp_qt_client_model.hpp.mustache
src/Client{Entity}Model.cpp cpp_qt_client_model.cpp.mustache
include/ores.qt/{Entity}MdiWindow.hpp cpp_qt_mdi_window.hpp.mustache
src/{Entity}MdiWindow.cpp cpp_qt_mdi_window.cpp.mustache
include/ores.qt/{Entity}DetailDialog.hpp cpp_qt_detail_dialog.hpp.mustache
src/{Entity}DetailDialog.cpp cpp_qt_detail_dialog.cpp.mustache
ui/{Entity}DetailDialog.ui qt_detail_dialog_ui.mustache
include/ores.qt/{Entity}HistoryDialog.hpp cpp_qt_history_dialog.hpp.mustache
src/{Entity}HistoryDialog.cpp cpp_qt_history_dialog.cpp.mustache
ui/{Entity}HistoryDialog.ui qt_history_dialog_ui.mustache
include/ores.qt/{Entity}Controller.hpp cpp_qt_controller.hpp.mustache
src/{Entity}Controller.cpp cpp_qt_controller.cpp.mustache

Layers with codegen gaps (manual until profiles are implemented):

Layer Component Files needed
HTTP endpoints ores.http.server routes header + implementation
CLI commands ores.cli options struct + parser header + implementation
Shell commands ores.shell commands header + implementation
Wt UI ores.wt.service list widget header + implementation + dialog header + implementation

Codegen gaps

Four layers have no codegen profile. When the entity-creator reaches one of these layers it must stop and raise a sprint story before proceeding.

The story for each gap must:

  1. Create mustache templates that encode the layer's conventions.
  2. Add a profile entry to facet_catalogue.org.
  3. Validate the profile against an existing entity (currency for refdata, account for IAM).
  4. Update this document's file checklist with the new profile's output paths.

Only after the profile is merged can the layer be generated for the new entity. Any hand-written files created as an interim workaround must be replaced with generated output once the profile exists.

See also

Emacs 29.1 (Org mode 9.6.6)