Open-source compliance tools for EHS professionals. Local-first, single-binary Go applications that work standalone or as an integrated ecosystem.
The Schema Builder lets users design their own database tables, fields, and relationships from scratch — runtime DDL on SQLite with safety, auditability, and interoperability with pre-built modules.
Three metadata tables describe user-defined schemas:
_custom_tables — table definitions (name, display name, icon,
establishment scope)_custom_fields — field definitions (name, type, required, default,
type-specific config JSON)_custom_relations — relationships between tables (belongs_to, has_many,
many_to_many; can target custom or pre-built tables)_custom_table_versions — DDL change history for each table| Schema Builder Type | SQLite Type | Config Examples |
|---|---|---|
text |
TEXT | {"multiline": true, "max_length": 500} |
number |
INTEGER | {"min": 0, "max": 100} |
decimal |
REAL | {"precision": 2} |
date |
TEXT | stored as YYYY-MM-DD |
datetime |
TEXT | stored as ISO 8601 |
boolean |
INTEGER | 0/1 |
select |
TEXT | {"options": ["Low", "Medium", "High"]} |
relation |
INTEGER | {"target_table": "cx_projects", "display_field": "name"} |
All user-created tables get the cx_ prefix. The validator enforces:
^[a-z][a-z0-9_]{1,58}$ (after prefix, max 63 chars)^[a-z][a-z0-9_]{1,62}$id, establishment_id, created_at, updated_at
(auto-added to every custom table)The executor translates metadata into DDL:
Table creation adds id, establishment_id, created_at, updated_at
automatically, plus an index on establishment_id.
Field addition uses ALTER TABLE ADD COLUMN.
Field deactivation sets is_active = 0 in metadata — hides from UI and
query builder. SQLite column remains. Explicit compaction can drop it later
(SQLite 3.35.0+).
Safe parameterized queries for custom tables. Column names validated against metadata are interpolated; all data values are parameterized:
func (qb *QueryBuilder) Select(tableID int64, opts SelectOpts) (string, []any, error)
func (qb *QueryBuilder) Insert(tableID int64, values map[string]any) (string, []any, error)
func (qb *QueryBuilder) Update(tableID int64, id int64, values map[string]any) (string, []any, error)
func (qb *QueryBuilder) Delete(tableID int64, id int64) (string, []any, error)
Custom tables can relate to whitelisted pre-built tables:
establishments, employeesincidents, chemicalstraining_courses, training_completionsstorage_locations, work_areasThe query builder generates appropriate JOINs. RecordForm.svelte renders
searchable dropdowns for relation fields.
Every DDL change is recorded in _custom_table_versions — enables future undo,
export, and schema sharing between establishments.