Schema-Driven Auto Columns
Relevant source files
The following files were used as context for generating this wiki page:
Schema-driven auto columns is a core architectural feature of Better-Tables that allows the system to automatically generate column definitions by introspecting the underlying database schema. This functionality eliminates the need for manual column boilerplate while maintaining a high degree of type safety and developer control. By leveraging adapter-level introspection, the system can infer data types, nullability, primary keys, and even enum options directly from the database schema.
The feature is implemented through a two-fold approach: a no-factory defineTable method for full inference and the t.auto() builder for partial inference with explicit overrides. This ensures that while columns can be generated automatically, sensitive fields like password hashes remain protected unless explicitly included.
Sources: plans/054-schema-driven-auto-columns.md:14-16, plans/054-schema-driven-auto-columns.md:31-36
Architecture and Data Flow
The architecture relies on the TableAdapter's ability to provide metadata about the database through the describeColumns method. This data is then processed by a lazy resolver in the UI or core data layer to generate the final column definitions used for rendering and filtering.
Data Flow Overview
The following diagram illustrates how schema information flows from the database adapter to the final table UI components.
The data flow ensures that schema introspection happens lazily at mount time to remain RSC-safe.
Sources: plans/054-schema-driven-auto-columns.md:88-100, plans/054-schema-driven-auto-columns.md:147-160
Core Components and Contracts
The InferredColumnSpec
The InferredColumnSpec is the standard interface that adapters must satisfy to enable auto-columns. It contains the raw material required to build a ColumnDefinition.
| Field | Type | Description |
|---|---|---|
field | string | Storage field name on the primary table. |
columnType | ColumnType | Mapped Better-Tables type (e.g., text, number). |
label | string | Humanized display label derived from the field name. |
options | Array | Enum choices (value/label) derived from the schema. |
primaryKey | boolean | Indicates if the field is a primary key. |
writable | boolean | Indicates if the field can be updated via the adapter. |
Sources: plans/054-schema-driven-auto-columns.md:120-138
Adapter Introspection
The describeColumns method is an optional capability in the TableAdapter contract. The Drizzle adapter implementation uses getTableColumns to map database-specific data types to Better-Tables types.
- Type Mapping: Maps
dataType 'string'totext,numbertonumber, and uses Drizzle'senumValuesto populateoptioncolumns. - Memoization: Results are memoized per table using a
WeakMapto optimize performance across renders.
Sources: plans/054-schema-driven-auto-columns.md:140-145, plans/054-schema-driven-auto-columns.md:65-71
Column Resolution Logic
Column resolution occurs via the resolveTableColumns helper. This process handles two distinct tasks: Enrichment and Inference.
Enrichment and Precedence
Enrichment allows manually defined columns to automatically inherit schema properties. For example, a manually defined t.option('status') without explicit options will fetch its choices from the database enum metadata.
- Explicit Wins: Any property explicitly defined in the
defineTablefactory overrides schema-inferred values. - Order Preservation: Explicitly defined columns appear first, followed by inferred columns in stable schema order.
- Validation: The system warns in development if a declared column type (e.g.,
t.text) contradicts the actual schema type (e.g.,boolean).
Sources: plans/054-schema-driven-auto-columns.md:162-181
The t.auto() Marker
The t.auto() builder acts as a sentinel in the column array. During the build phase, it sets an autoColumns flag on the table definition, signaling the resolver to include all other eligible columns from the schema.
// Example of partial inference with overrides
export const usersTable = tables.define('users', (t) => [
...t.auto(),
t.text('email').editable(), // Override inferred column to make it editable
t.text('internal_notes').hidden() // Hide a specific schema field
]);
Sources: plans/054-schema-driven-auto-columns.md:46-49, plans/054-schema-driven-auto-columns.md:164-168
Interaction with UI and State
Auto-columns integrate deeply with the UI components to provide a "mount and work" experience.
- Lazy Resolution: Since table definitions are often server-side or type-only, the UI layer resolves the full column set at mount time.
- Option Fallbacks: If a column is identified as an
optiontype but lacks enum metadata in the schema, the UI will lazily fetch choices usingadapter.getFilterOptionswhen the dropdown is opened. - Editable Gating: By default, auto-inferred columns are read-only. Editability must be enabled explicitly per-column using the
.editable()builder.
Sources: plans/054-schema-driven-auto-columns.md:79-86, plans/054-schema-driven-auto-columns.md:183-189, plans/053-editable-cells.md:23-28
Resolution Sequence Diagram
Limitations
- Own-Table Only: Auto-columns currently only support own-table fields. Relationship-path columns (e.g.,
profile.bio) must still be declared explicitly. - RSC Boundary: Resolution must be performed in a client-side context where the runtime adapter instance is available, as table definitions are designed to be serializable and database-agnostic.
- Adapter Support: Requires an adapter that implements
describeColumns. Adapters lacking this capability will fall back to displaying only manually defined columns.
Sources: plans/054-schema-driven-auto-columns.md:104-110, plans/054-schema-driven-auto-columns.md:179-181