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:

  1. Create mustache templates that encode the layer's conventions.
  2. Add the new archetype's row to its facet's Archetypes table in the ores.* physical-space graph.
  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.

6. See also

Emacs 29.3 (Org mode 9.6.15)