Database Structure Config
config/db_structure.ts defines the naming conventions that let Reepolee's CRUD generator, reeman, and form builders auto-detect column types without per-column configuration. Name a column first_commit_on and the generator knows it's a date — it renders a date input, formats it in list views, and encodes it correctly for the database. Name it logo_image and an image upload field appears. These conventions are the wiring between your SQL schema and the generated UI.
They're consumed by the schema introspection layer (generator/schema/), which reads your DDL, matches column names against these patterns, and produces the typed column descriptors used by every CRUD route.
Configuration File
export const INTERNAL_TABLE_PREFIX = "_" as const;
export const IGNORE_TABLES = ["modules", "sessions", "email", "images", "files",
"users", "translations", "db_tables", "db_routes"] as const;
export const MAINTENANCE_FIELDS = ["created_at", "updated_at"] as const;
export const DATE_SUFFIXES = ["_on", "_by"] as const;
export const DATETIME_SUFFIXES = ["_at"] as const;
export const IMAGE_SUFFIXES = ["_image"] as const;
export const FILE_SUFFIXES = ["_file"] as const;
export const IGNORE_INDEX_FIELDS = ["display", "option_display", "option_text",
"search_text", "hashed_password", "previous_hashed_password"] as const;
export const IGNORE_ORDER_FIELDS = ["option_display", "search_text",
"hashed_password", "previous_hashed_password"] as const;
export const BOOLEAN_PREFIXES = ["is_", "has_", "can_"] as const;
export const LOCALIZATION_SYSTEM_FIELDS = ["id", "display", "search_text",
"created_at", "updated_at"] as const;
export const LOCALIZABLE_STRING_TYPES = ["text", "textarea", "markdown"] as const;
export const MIN_PASSWORD_LENGTH = Bun.argv.includes("--dev") ? 1 : 8;
export const CURRENCY_FIELD = "decimal(18,2)" as const;
export const PERCENT_FIELD = "decimal(12,4)" as const;
export const COL_WIDTH_DECIMAL = "20ch";
export const COL_WIDTH_INTEGER = "10ch";
export const COL_WIDTH_BOOLEAN = "15ch";
export const COL_WIDTH_TEMPORAL = "20ch";
export const COL_WIDTH_IMAGE = "120px";
export const COL_WIDTH_FILE = "20ch";
export const COL_WIDTH_AUTO = "auto";
export const COL_WIDTH_STRING_MAX_CH = 80;
Change a value here and every generated CRUD route reflects it — no per-table configuration needed.
Table Filters
INTERNAL_TABLE_PREFIX
export const INTERNAL_TABLE_PREFIX = "_" as const;
Tables whose names start with _ (e.g. _temp_migration, _backup_data) are excluded from all table-selection UIs, CRUD generation, and schema introspection. This is a glanceable convention for internal/working/temporary tables that should never get a user-facing CRUD route.
IGNORE_TABLES
export const IGNORE_TABLES = ["modules", "sessions", "email", "images", "files",
"users", "translations", "db_tables", "db_routes"] as const;
Tables whose CRUD is hand-written and should not be overwritten by the generator. These are the core system tables shipped with Reepolee. The generator skips them during CRUD scaffolding and table-discovery UIs.
Date & Datetime Detection
Reepolee detects temporal columns from column name suffixes — no explicit type annotation needed.
DATE_SUFFIXES
export const DATE_SUFFIXES = ["_on", "_by"] as const;
Columns whose names end in _on or _by are treated as date-only fields (no time component). Examples:
| Column name | Detected as | Form input | List display |
|---|---|---|---|
first_commit_on | Date | <input type="date"> | Locale-formatted date |
payment_due_by | Date | <input type="date"> | Locale-formatted date |
incorporated_on | Date | <input type="date"> | Locale-formatted date |
DATETIME_SUFFIXES
export const DATETIME_SUFFIXES = ["_at"] as const;
Columns whose names end in _at are treated as datetime/timestamp fields. Examples:
| Column name | Detected as | Form input | List display |
|---|---|---|---|
created_at | Datetime | Read-only (maintenance) | Locale-formatted datetime |
updated_at | Datetime | Read-only (maintenance) | Locale-formatted datetime |
published_at | Datetime | <input type="datetime-local"> | Locale-formatted datetime |
Columns matching DATETIME_SUFFIXES that also appear in MAINTENANCE_FIELDS (created_at, updated_at) are rendered as read-only in forms — they're managed by the database trigger, not user input.
MAINTENANCE_FIELDS
export const MAINTENANCE_FIELDS = ["created_at", "updated_at"] as const;
Columns managed entirely by the database — never written from user input. Generated forms render them as read-only. Generated sql.ts queries omit them from INSERT and UPDATE payloads because the DB trigger handles them.
File & Image Detection
IMAGE_SUFFIXES
export const IMAGE_SUFFIXES = ["_image"] as const;
Columns ending in _image store an uploaded image path. The generator produces an <image-upload> component in forms and a 100×100 thumbnail in grid views (via the image_thumbnail template helper). Examples: portrait_image, logo_image, hero_image.
FILE_SUFFIXES
export const FILE_SUFFIXES = ["_file"] as const;
Columns ending in _file store an uploaded document path (PDF, DOCX, etc.). The generator produces a <file-upload> component in forms and a filename/size link in grid views (via the file_link template helper). Examples: contract_file, invoice_file, report_file.
Boolean Detection
BOOLEAN_PREFIXES
export const BOOLEAN_PREFIXES = ["is_", "has_", "can_"] as const;
Columns starting with is_, has_, or can_ are treated as boolean/integer flags. Examples:
| Column name | Detected as | Form input | List display |
|---|---|---|---|
is_javascript | Boolean | Checkbox | Yes/No pill |
has_premium | Boolean | Checkbox | Yes/No pill |
can_edit | Boolean | Checkbox | Yes/No pill |
Booleans are stored as integers in the database and treated specially in forms — they'll always be present in form POSTs (HTML checkboxes submit nothing when unchecked, so Reepolee normalizes the absent value to 0).
Index/List Filters
IGNORE_INDEX_FIELDS
export const IGNORE_INDEX_FIELDS = ["display", "option_display", "option_text",
"search_text", "hashed_password", "previous_hashed_password"] as const;
Columns excluded from generated list/grid views. These fields can be selected in the SQL query but won't get a column header or data cell in the default index table. Mostly system fields that exist for internal use — search indexing, password storage, generated display values — where showing them in a list would be noise.
IGNORE_ORDER_FIELDS
export const IGNORE_ORDER_FIELDS = ["option_display", "search_text",
"hashed_password", "previous_hashed_password"] as const;
Columns excluded from sort dropdowns in generated list views. Ordering by these fields produces either meaningless or security-sensitive results, so they're omitted from the UI. The column is still sortable via URL query parameter — this only removes the dropdown option.
Overlaps with IGNORE_INDEX_FIELDS but is a separate list: display and option_text are excluded from the table but are valid sort columns.
Localization Rules
LOCALIZATION_SYSTEM_FIELDS
export const LOCALIZATION_SYSTEM_FIELDS = ["id", "display", "search_text",
"created_at", "updated_at"] as const;
Columns never eligible for per-locale content overrides, even when they would otherwise pass the localizable-type check. These are identifiers and metadata — not translatable copy.
LOCALIZABLE_STRING_TYPES
export const LOCALIZABLE_STRING_TYPES = ["text", "textarea", "markdown"] as const;
Form field types that reeman marks localized: true by default when LOCALIZE_CONTENT=true. Freeform text content like descriptions and long-form markdown are natural candidates for per-locale overrides; structured data like email, URL, telephone, number, and date fields are not.
SQL Type Constants
CURRENCY_FIELD
export const CURRENCY_FIELD = "decimal(18,2)" as const;
The canonical SQL type for monetary values. Used by the domain types system and schema validation. 18 digits total, 2 after the decimal — enough for most applications up to quadrillions.
PERCENT_FIELD
export const PERCENT_FIELD = "decimal(12,4)" as const;
The canonical SQL type for percentage values. 12 digits total, 4 after the decimal — supports precise fractional percentages (e.g. 99.9876%).
MIN_PASSWORD_LENGTH
export const MIN_PASSWORD_LENGTH = Bun.argv.includes("--dev") ? 1 : 8;
Minimum password length for user registration and password changes. Relaxed to 1 character in dev mode (--dev flag) so you can use simple passwords during development. Production mode enforces 8 characters minimum.
Grid Column Width Defaults
Generated CRUD index views assign initial column widths based on the detected column type. These defaults can be overridden per-column in the generated schema/table.ts file.
| Constant | Default | Applies to |
|---|---|---|
COL_WIDTH_DECIMAL | "20ch" | decimal/numeric columns |
COL_WIDTH_INTEGER | "10ch" | Integer columns |
COL_WIDTH_BOOLEAN | "15ch" | Boolean/checkbox columns (is_/has_/can_) |
COL_WIDTH_TEMPORAL | "20ch" | Date, datetime, timestamp, time columns |
COL_WIDTH_IMAGE | "120px" | Image upload columns (_image suffix) |
COL_WIDTH_FILE | "20ch" | File upload columns (_file suffix) |
COL_WIDTH_AUTO | "auto" | Fallback when no type-specific default applies |
COL_WIDTH_STRING_MAX_CH | 80 | Max ch-width cap for string columns |
All ch-based widths are capped at COL_WIDTH_STRING_MAX_CH to prevent absurdly wide columns for long text fields.
How It's Consumed
config/db_structure.ts ← naming conventions
│
├── generator/schema/loader.ts ← reads DDL, matches column names
│
├── generator/schema/write_table.ts ← produces typed column descriptors
│
├── generator/crud/forms.ts ← picks form inputs by suffix/prefix
│
├── generator/crud/list.ts ← builds grid columns with widths
│
├── lib/template_helpers.ts ← image_thumbnail(), file_link(), yes_no()
│
└── lib/localized_form.ts ← applies LOCALIZATION_SYSTEM_FIELDS filter
Extending the Conventions
Need a new suffix convention? Edit db_structure.ts and re-run reeman's schema refresh. For example, to add _url as a URL-detection suffix:
- Add your suffix to the appropriate array in
config/db_structure.ts - Handle the new suffix in
generator/schema/if the generator needs to produce a different form input or grid renderer - Re-run
bun reeman→ Refresh CRUD for the affected tables
The suffix/prefix arrays are plain TypeScript as const arrays — extend them directly. The generator reads them dynamically, so no regeneration step is needed beyond refreshing the CRUD routes themselves.
Relationship to Domain Types
This config and the Domain Types system work together:
db_structure.ts Provides | Domain Types Provide |
|---|---|
| Suffix/prefix → form-control mapping | Column name → SQL type mapping |
| Table-level inclusion/exclusion rules | Canonical vocabulary of column types |
| Grid column width defaults | Dialect-specific SQL for each type |
| Localization eligibility rules | Type-compliance auditing |
Together, they let you write plain SQL DDL and get a fully typed, generated CRUD application with correct form inputs, list views, and validation — no per-column configuration or annotation needed.