Entity lifecycle
Table of Contents
A full-stack ORE Studio entity spans four layers: a domain type, a
SQL schema, and two surface layers (HTTP REST, shell REPL).
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. Two layers (HTTP,
Shell) have no profile yet — see §Codegen gaps below.
Return to Knowledge.
1. 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 [shell commands\n(ores.shell)] as SH D --> S : persists via repository D --> H : service → REST routes D --> SH : service → REPL command @enduml
Build all layers in order, which is the argued reading order for this cluster as well as the build order. An exposure layer must not be created before its domain type and SQL schema are merged: every exposure layer projects the domain type, so building one first means guessing at a shape that is still moving, and the SQL schema is what fixes that shape. The two surface layers are independent of each other and may be built in any order among themselves, or in parallel.
Each row's pattern document is the page to read before writing that layer; each row's recipe carries the commands.
| Layer | Skill | Pattern doc | Recipe |
|---|---|---|---|
| Domain type | compass-code-add-domain-type | (this doc §Type mappings) | — |
| SQL schema | compass-codegen-add-sql-schema | SQL entity schema patterns | How do I create a new entity SQL schema? |
| HTTP endpoints | compass-codegen-add-surface-entity | HTTP entity patterns | How do I create HTTP endpoints for a new entity? |
| Shell commands | compass-codegen-add-surface-entity | Shell entity patterns | How do I create shell commands for a new entity? |
2. 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.).
3. 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.
4. 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 --address ores.cpp):
| 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 --address ores.cpp.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 --address ores.sql.schema):
| 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 |
TypeScript wire protocol (ores.ts.protocol, generated by --address ores.ts.protocol):
| File | Template |
|---|---|
src/{component}/protocol/{entity}_protocol.ts |
ts_protocol.ts.mustache |
TypeScript domain shape (ores.ts.domain, generated by --address ores.ts.domain):
| File | Template |
|---|---|
src/{component}/domain/{entity}.ts |
domain_types.ts.mustache |
Layers with codegen gaps (manual until profiles are implemented):
| Layer | Component | Files needed |
|---|---|---|
| HTTP endpoints | ores.http.server |
routes header + implementation |
| Shell commands | ores.shell |
commands header + implementation |
5. Codegen gaps
Two 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:
- Create mustache templates that encode the layer's conventions.
- Add the new archetype's row to its facet's Archetypes table in the
ores.*physical-space graph. - Validate the profile against an existing entity (
currencyfor refdata,accountfor IAM). - 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.
6. See also
- entity-creator — orchestrator skill; use this when adding a new entity.
- compass-code-add-domain-type — first layer skill.
- ORE Studio Codegen — codegen component model, profile catalogue, model schema.
- ORE Studio SQL Schema — schema mental model and PostgreSQL setup.
- ores.cpp.repository, ores.cpp.service-app — the literate templates for the repository and service facets.