Every layer of Hive documented in technical detail — from the ModelDefinition fluent API and ColumnDefinitionFlag bitmask through the trigger pipeline, LRU cache, cron scheduler, and frontend ESM module architecture.
struct ModelDefinition is the single source of truth that drives REST route generation, SQLite persistence, validator dispatch, trigger pipeline, and frontend schema rendering. Defined per model inside a plugin's registration code.
ModelDefinition md("note", "slip_box");
md.set_group("Slip Box", 100)
.set_all_rest_operations() // C+R+U+D+L
.set_title_column("title")
.set_columns({
{"title", MANDATORY | TEXT},
{"content", TEXTAREA},
{"map_id", FOREIGN_KEY}, // auto → Integer
{"parent_note_id", FOREIGN_KEY},
{"is_public", BOOL | MUTABLE},
{"importance", INTEGER}
});
Every call returns ModelDefinition& for chaining. set_columns() automatically prepends id, created_at, updated_at as the first three columns.
| Method | Effect |
|---|---|
set_group(name, order) | Frontend navigation grouping and ordering |
set_all_rest_operations() | Enables C+R+U+D+L for this model |
set_rest_operations("crl") | Enable only Create, Read, List |
set_title_column("field") | Primary display field for FK label resolution |
allow_reader_write() | Lets Reader role write (overrides default) |
set_readonly() | No mutations allowed via API |
set_virtual_table(true) | Model has no real DB table (trigger-driven) |
set_no_table(true) | Model without any table at all |
set_cache_enabled(false) | Disable LRU cache for this model |
set_cached_after_create(false) | Skip caching new records |
add_custom_list_action(...) | Adds a frontend action button on list view |
add_custom_read_action(...) | Adds a frontend action button on read view |
add_custom_create_action(...) | Adds a frontend action button on create view |
Each column is defined by a name and an integer bitmask of ColumnDefinitionFlag values. The constructor automatically infers types from naming conventions.
| Column name | Auto behaviour |
|---|---|
id | Integer, primary key, mandatory, unique, auto, readonly |
created_at | DateTime, mandatory, auto, readonly |
updated_at | DateTime, auto (updated on every write) |
*_id suffix | Automatically Integer type + foreign key set to prefix |
| Flag | SQLite type / UI widget |
|---|---|
TEXT | TEXT column, single-line input |
TEXTAREA | TEXT column, multi-line textarea |
INTEGER | INTEGER column, number input |
BOOL | INTEGER(0/1), checkbox in UI |
DATETIME | DATETIME column, formatted display |
REAL | REAL column, decimal number |
BLOB | BLOB column (binary data, roadmap) |
| Flag | Bit | Meaning |
|---|---|---|
MANDATORY | 1<<0 | Field required in create/update |
UNIQUE | 1<<1 | Must be unique across all records |
FOREIGN_KEY | 1<<2 | Auto-derives FK from _id suffix |
AUTO | 1<<3 | Server-managed, not sent by client |
HIDDEN | 1<<4 | Hidden in frontend list/form views |
READONLY | 1<<5 | Cannot be mutated via API |
MUTABLE | 1<<6 | Explicitly mutable (overrides defaults) |
INTERNAL | 1<<7 | Internal field, stripped from responses |
// Combine any flags with bitwise OR:
{"title", MANDATORY | TEXT},
{"content", TEXTAREA | MUTABLE},
{"parent_id", FOREIGN_KEY | HIDDEN},
{"token_hash", MANDATORY | UNIQUE | INTERNAL},
{"enabled", BOOL | MUTABLE}
.set_default_value(true)
.set_description("Whether the job is active.")
The hive::essential::Crudl enum is used everywhere to name operations: in ModelDefinition::allowed_rest_operations, trigger phase selectors, validator method names, history records, and CustomAction buttons.
POST /api/v1/<model>
Runs validator can_create(), then Before triggers, repository create(), After triggers. Returns new record ID.
GET /api/v1/<model>/:id
Runs can_read(). Checks ModelCache before hitting IRepository::read(). Cache TTL default 1 week.
PUT /api/v1/<model>/:id
Runs can_update(db, token, new_ef, old_ef). Both new and old entity fields are passed for diff-based validation.
DELETE /api/v1/<model>/:id
Runs can_delete(). After trigger writes to history table via HistoryCommonTrigger.
GET /api/v1/<model>
Runs can_list(db, token, filter). Supports page_number, page_size, sort, order, and field-level filter params.
set_rest_operations("crl") — each char maps: 'c'→Create, 'r'→Read, 'u'→Update, 'd'→Delete, 'l'→List. set_all_rest_operations() is equivalent to "crudl".
Every model registered via Plugin::register_model() carries a shared pointer to an IValidator instance. Validators run before any trigger or persistence — they are the last line of defence.
class IValidator {
public:
// Is the user allowed to create this record?
virtual OperationResult can_create(
DbPtr& db,
AccessTokenContext& token,
entity_fields& ef) const = 0;
virtual OperationResult can_read(
DbPtr& db,
AccessTokenContext& token,
identification id) const = 0;
virtual OperationResult can_update(
DbPtr& db,
AccessTokenContext& token,
entity_fields& ef,
entity_fields& old_fields) const = 0;
virtual OperationResult can_delete(
DbPtr& db,
AccessTokenContext& token,
identification id) const = 0;
virtual OperationResult can_list(
DbPtr& db,
AccessTokenContext& token,
string_map& filter) const = 0;
};
struct OperationResult {
int status; // 0 = OK, 403/405/... = error
std::string error; // human-readable reason
bool ok() const { return status == 0; }
bool ko() const { return !ok(); }
explicit operator bool() const noexcept;
};
// Convenience macros:
ok_result {}
status_403_forbidden {403, "You are not authorized..."}
status_405_unsupported_operation {405, "..."}
struct AccessTokenContext {
identification user_id;
std::string msg;
int status; // HTTP status: 200=ok, 401=...
bool system; // true = system token
bool ok() const;
bool is_system() const;
};
Validators use token.ok() to check authentication and token.user_id for ownership checks. Cross-model queries via IValidator::get_validator(model_name).
UserValidator, TeamValidator, TeamMemberValidator, HistoryValidator, ApiLogValidator, AccessTokenValidator, RefreshTokenValidator, LoginSessionValidator, AuthLogValidator, SuperAdminLogValidator, JobEntryValidator, JobRunValidator, ErrorValidator
Triggers extend behaviour around every CRUD operation without touching the core orchestration layer. They carry a priority, a set of target operations (Crudl bitmask), and a phase.
Runs before the repository call. Can inspect and modify incoming entity_fields. Can abort the operation by returning a non-OK result. Used for: computed defaults, soft locks, pre-normalisation.
run_before_or_after(
operation, // Crudl
stack_depth, // recursion guard
validation_result,
action_result, // out param
def, // ModelDefinition
user_id, id,
fields, // mutable
old_fields, // for update
query_params)
Completely replaces the default CRUD behaviour. Returns std::optional — if non-empty, the result is used instead of the repository call. Used for: fulltext search, virtual entities, derived reads.
// For Create:
std::optional<pair<int, OperationResult>>
run_instead_of_create(...);
// For Read:
std::optional<pair<entity_fields, OperationResult>>
run_instead_of_read(...);
// For List:
std::optional<pair<vector<entity_fields>,
OperationResult>>
run_instead_of_list(...);
Runs after successful persistence. Cannot abort the operation. Used for: history records, derived data maintenance, fulltext index updates, cross-model synchronisation, analytics.
Example — HistoryCommonTrigger:
// After ANY CRUDL on ANY table ("*")
// Priority: 1000 (runs last)
// Serializes operation data as JSON
// Skips: history, api_log, auth_log,
// super_admin_log
// Skips if action_result.ko()
class Trigger : public AbstractTriggerJob {
Trigger(
const std::string& name,
const std::string& description,
int priority, // lower = runs first
std::set<hive::essential::Crudl> operations,
TriggerPhase phase,
const std::string& table // "*" = all tables
);
};
// Fixed thread pool of 4 worker threads
class CronScheduler {
struct ScheduledJobEntry {
JobPtr job;
std::string cron; // expression
time_t next_run;
i64 job_id; // job_entry.id
std::string job_name;
bool enabled;
bool running;
time_t last_started_at;
time_t last_enabled_check;
cronq::JobConfig job_config;
};
// Methods:
void scheduler_loop();
void load_jobs_from_db();
void compute_initial_next_runs();
void run_job(ScheduledJobEntry&);
void insert_job_run();
void update_job_run();
void update_next_run_in_db();
};
class Job : public AbstractTriggerJob {
Job(
const std::string& job_name,
const std::string& job_description,
const std::string& cron_expression,
bool enabled_by_default = true,
bool run_once_when_missed = true
);
// Override to implement job logic:
virtual std::string run(
cronq::JobConfig& job_config) = 0;
// Query access (same as Trigger):
nlohmann::json call_query(
const std::string& query_name,
nlohmann::json& request);
};
// Access per-job configuration:
auto [val, err] = job_config.get_string("key");
auto val2 = job_config.get_int_or_default(
"days", 30);
auto hash = job_config.get_sha256();
// (SHA-256 of entire config content)
| Job class | Plugin | Cron | Default enabled | Description |
|---|---|---|---|---|
CleanupJob | core | @daily | Yes | Deletes old api_log, history (R/L ops), login_session, access_token (default: 30 days threshold per type) |
CleanupHistoryOrphansJob | core | @daily | Yes | Removes history entries whose referenced record_id no longer exists |
VacuumJob | core | @monthly | No | Runs SQLite VACUUM; run_once_when_missed=false (skips if missed) |
TestJob | core | configurable | Yes | Debug/test job for scheduler verification |
HtmlExportJob | slip_box | configurable | Yes | Exports entire Slipbox as navigable static HTML files |
DictionaryHtmlExportJob | dictionary | configurable | Yes | Exports Dictionary as static HTML |
set_ttl_ms())read_cache_capacity_size)read_cache_capacity_bytes)std::shared_mutex (shared reads, exclusive writes)CacheKey { table_name, record_id }struct CacheStats {
size_t hits;
size_t misses;
size_t evicted_lru; // evicted by LRU policy
size_t expired; // evicted by TTL
size_t shrinks; // forced shrinks
};
class ModelCache {
// Read from cache (shared lock):
bool get(table, id, out_row);
// Write to cache (exclusive lock):
void put(table, id, row);
// Invalidate on update/delete:
void invalidate(table, id);
// Full flush:
void clear();
// Force-shrink to target size:
void shrink_to(size_t target, ByteUnit);
// Configuration:
void set_ttl_ms(int64_t ms);
void set_capacity_size(size_t n);
void set_capacity_bytes(size_t bytes);
// Diagnostics:
void print_info();
CacheStats information_no_lock() const;
};
Cache is disabled per-model via ModelDefinition::set_cache_enabled(false). Disabled automatically for frequently-written models like api_log, history.
| Parameter | Default | Config key |
|---|---|---|
| Access token lifetime | 15 min | access_token_expires_in |
| Refresh token lifetime | 30 days (43200 min) | refresh_token_expires_in |
| Rotation threshold | 7 days (10080 min) | refresh_token_rotation_threshold_in |
is_revoked flag + revoked_at timestamp per tokenrefresh_token.replaced_by_id and rotated_from_id maintain rotation chainlogin_session tableSession / Api / Service// Login — returns access + refresh tokens
POST /api/v1/auth/login
{
"username": "admin",
"password": "secret"
}
// Response:
{
"user_id": 1,
"access_token": "...",
"access_token_expires_at": 1234567890,
"refresh_token": "..."
}
// Refresh (proactive in frontend: 60s before expiry)
POST /api/v1/auth/refresh_token
{ "refresh_token": "..." }
// Logout
POST /api/v1/auth/logout
Authorization: Bearer <access_token>
{ "refresh_token": "..." }
// Change password
POST /api/v1/auth/change_password
{ "old_password": "...",
"new_password": "..." }
// Register
POST /api/v1/auth/register
{ "username": "...", "password": "...",
"email": "..." }
enum UserRole {
Guest = 0, // unauthenticated
Reader = 1, // read-only
Editor = 2, // can create/update
Reviewer = 3, // can review
Admin = 4, // full control
SuperAdmin= 5, // platform admin
System = 100 // internal system user
};
enum UserStatus {
Pending = 0, // awaiting approval
Active = 1, // normal access
Deactivated = 2, // soft-disabled
Banned = 3, // policy violation
Suspended = 4, // temporary
Deleted = 5 // logical delete
};
enum RegistrationMode {
Free = 0,
RequiresAdminApproval = 1,
AdminAddsUsers = 2
};
The frontend is pure ES Modules — no bundler, no transpilation, no npm. Load in a browser and it works. Served directly by Hive's WebEndpointsGenerator under /web/.
Core functions: apiFetch(url, options), list_entities(entity, params, page, size), list_all_entities(entity) (auto-pages up to 1000), read_entity(entity, id), put_entity(model, id, json), post_entity(model, json), delete_entity(entity, id).
Model schema cached in localStorage for 3 hours (CACHE_TTL_MS). FK title labels cached 24 h via getTitleCache / setTitleCache.
Functions: login(username, password), logout(), register(data), refreshToken(), changePassword(old, new), configure_get(). Tokens stored in localStorage under access_token, refresh_token, access_token_expires_at.
apiFetch() calls ensureFreshAccessToken() before every request — refreshes if expiry < 60 s away.
renderEntityList(entity) — sortable table with column selector, per-field filter inputs, page-size selector (5/10/20/50/100), and ±2 page navigation (First/Prev/[pages]/Next/Last + "Go to" input).
renderEntityForm(entity, data) — auto-generates form: enums → <select>, booleans → checkbox, datetime → text with parseDateTimeToUnix(), FK → number input.
renderEntityRead(entity, id) — detail table with FK label resolution via resolveForeignKeyValue() and clickable FK links. Shows CustomAction buttons grouped by model.
filterColumnsForForm(fields) — filters columns for form display (excludes auto fields). toLabel(name) — converts snake_case to Title Case for UI labels.
state.js tracks: currentPage, pageSize, totalPages, entitySchemas (the full model_definition response). Column visibility stored in localStorage via getHiddenColumns / setHiddenColumns.
Slipbox-specific graph exploration. Uses InsteadOfReadNoteNavigationTrigger as the data source for the note navigation virtual model. Renders interactive graph of notes, links, and relationships.
app_slip_box.js — full note editor with Markdown rendering via markdown-it.min.js + syntax highlighting (highlight.min.js) + emoji support (markdown-it-emoji.min.js) registered by the Slip Box plugin.
app_dictionary.js — term browser with three-level autocomplete: exact title > exact alias > prefix title > prefix alias > substring.
app_repetition.js — spaced-repetition review session UI with card rating.
// Regex: V(\d+)__([a-zA-Z0-9_]+)\.sql
// Example names:
V1__create_user.sql
V2__create_team.sql
V13__add_job_entry_configuration.sql
// Migrations stored as C++ raw string
// literals inline in MigrationScripts.cpp
migration tableThe Core plugin already ships two migration script implementations:
CoreSQLiteMigrationScripts — V1–V14 in SQLite dialectCorePostgreSQLMigrationScripts — V1–V14 in PostgreSQL dialectThis signals active work toward multi-DB support via the planned IDatabase / IStatement / IMigration interface layer (see Roadmap).
| Plugin | Migrations |
|---|---|
| Core | V1–V14 (14) |
| Slip Box | V1–V33 (33) |
| Dictionary | V1–V29 (29) |
| Repetition | V1–V23 (23) |
Define IDatabase, IStatement, IMigration interfaces. Implement PostgresDatabase, PostgresStatement. RepositoryFactory selects DB type at runtime from database_type config. Core plugin PostgreSQL migrations already exist.
Migrate from #include headers to C++20 module units (.ixx files). Expected: faster compilation, better encapsulation, cleaner dependency graph. Incremental per-module approach planned.
JSON filter expressions passed as query param: {"and":[{"title":{"like":"%go%"}},{"deleted":{"eq":false}}]}. Operators: eq, neq, lt, gt, lte, gte, in, like, is_null. Logical: and, or, not.
Integrate AI capabilities directly into the Hive platform — likely as a plugin-level extension point compatible with the existing trigger/query mechanism.
New file table with content-addressable storage (files/ab/cdef1234... path structure). Dual BLOB/path storage. SHA-256 integrity, MIME type, encryption, compression metadata.
Auto-generate OpenAPI 3.x spec from model metadata at runtime. Enables standard client SDK generation, Swagger UI, and API testing tooling without manual spec maintenance.
Replace current hash_sha_256(password) with Argon2id (preferred) or bcrypt/scrypt/PBKDF2 for proper password key derivation with salt and work factor.
Containerised deployment with official Dockerfile and docker-compose.yml. Planned alongside PostgreSQL support for cloud-native deployments.