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 nameDetected asForm inputList display
first_commit_onDate<input type="date">Locale-formatted date
payment_due_byDate<input type="date">Locale-formatted date
incorporated_onDate<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 nameDetected asForm inputList display
created_atDatetimeRead-only (maintenance)Locale-formatted datetime
updated_atDatetimeRead-only (maintenance)Locale-formatted datetime
published_atDatetime<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 nameDetected asForm inputList display
is_javascriptBooleanCheckboxYes/No pill
has_premiumBooleanCheckboxYes/No pill
can_editBooleanCheckboxYes/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.

ConstantDefaultApplies 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_CH80Max 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:

  1. Add your suffix to the appropriate array in config/db_structure.ts
  2. Handle the new suffix in generator/schema/ if the generator needs to produce a different form input or grid renderer
  3. Re-run bun reemanRefresh 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 ProvidesDomain Types Provide
Suffix/prefix → form-control mappingColumn name → SQL type mapping
Table-level inclusion/exclusion rulesCanonical vocabulary of column types
Grid column width defaultsDialect-specific SQL for each type
Localization eligibility rulesType-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.