Pure functions for database schema introspection and entity configuration.
This namespace contains pure business logic for transforming raw database metadata into UI-friendly entity configurations. All functions are pure (no side effects) and testable without database connections.
Key responsibilities:
Pure functions for database schema introspection and entity configuration. This namespace contains pure business logic for transforming raw database metadata into UI-friendly entity configurations. All functions are pure (no side effects) and testable without database connections. Key responsibilities: - Parse database column metadata into field configurations - Infer appropriate UI widgets from database types - Detect relationships and foreign keys - Merge auto-detected configuration with manual overrides - Generate sensible defaults for labels and field ordering
(apply-field-order fields field-order)Apply preferred field ordering to a vector of fields.
Uses stable sorting: fields in :field-order come first (in that order), remaining fields are appended in their original order.
Args: fields: Vector of field keywords to reorder field-order: Optional vector of preferred field order
Returns: Reordered vector of field keywords
Example: (apply-field-order [:c :a :b :d] [:a :b]) ;=> [:a :b :c :d]
(apply-field-order [:email :name :role :active] [:role :email]) ;=> [:role :email :name :active]
Apply preferred field ordering to a vector of fields. Uses stable sorting: fields in :field-order come first (in that order), remaining fields are appended in their original order. Args: fields: Vector of field keywords to reorder field-order: Optional vector of preferred field order Returns: Reordered vector of field keywords Example: (apply-field-order [:c :a :b :d] [:a :b]) ;=> [:a :b :c :d] (apply-field-order [:email :name :role :active] [:role :email]) ;=> [:role :email :name :active]
(apply-field-order-to-config entity-config)Apply :field-order to :editable-fields and :detail-fields in entity config.
If :field-order is present in the config, reorders :editable-fields and :detail-fields accordingly. Does not modify :list-fields (those have their own explicit ordering).
Args: entity-config: Entity configuration map
Returns: Entity configuration with reordered field vectors
Example: (apply-field-order-to-config {:editable-fields [:c :a :b] :detail-fields [:c :a :b :d] :field-order [:a :b :c]})
Apply :field-order to :editable-fields and :detail-fields in entity config.
If :field-order is present in the config, reorders :editable-fields and
:detail-fields accordingly. Does not modify :list-fields (those have their
own explicit ordering).
Args:
entity-config: Entity configuration map
Returns:
Entity configuration with reordered field vectors
Example:
(apply-field-order-to-config
{:editable-fields [:c :a :b]
:detail-fields [:c :a :b :d]
:field-order [:a :b :c]})(build-entity-config auto-config manual-config)Build complete entity configuration by merging auto-detected with manual.
This is the main function that combines schema introspection results with user-provided configuration overrides.
Args: auto-config: Entity configuration from parse-table-metadata manual-config: Manual configuration overrides (can be nil or partial)
Returns: Complete merged entity configuration
Example: (build-entity-config {:label "Users" :fields {...} :list-fields [...]} {:label "System Users" :list-fields [:email :name :role]})
Build complete entity configuration by merging auto-detected with manual.
This is the main function that combines schema introspection results
with user-provided configuration overrides.
Args:
auto-config: Entity configuration from parse-table-metadata
manual-config: Manual configuration overrides (can be nil or partial)
Returns:
Complete merged entity configuration
Example:
(build-entity-config
{:label "Users" :fields {...} :list-fields [...]}
{:label "System Users" :list-fields [:email :name :role]})Set of field names that are typically hidden in admin UI (in kebab-case).
Set of field names that are typically hidden in admin UI (in kebab-case).
Set of field names that are typically readonly (in kebab-case).
Set of field names that are typically readonly (in kebab-case).
(detect-foreign-keys fields-by-name)Detect foreign key relationships from field names.
Week 2: Heuristic based on field naming conventions. Week 3+: Could enhance with actual database foreign key constraints.
Args: fields-by-name: Map of field-name -> field-config
Returns: Vector of relationship maps: [{:field :user-id :references-entity :users :references-field :id :display-field :name}]
Example: (detect-foreign-keys {:user-id {...} :category-id {...}})
Detect foreign key relationships from field names.
Week 2: Heuristic based on field naming conventions.
Week 3+: Could enhance with actual database foreign key constraints.
Args:
fields-by-name: Map of field-name -> field-config
Returns:
Vector of relationship maps:
[{:field :user-id
:references-entity :users
:references-field :id
:display-field :name}]
Example:
(detect-foreign-keys {:user-id {...} :category-id {...}})(detect-relationships entity-config)Detect all relationships for an entity configuration.
Week 2: Detects belongs-to relationships from foreign key fields. Week 3+: Could add has-many and has-one detection.
Args: entity-config: Entity configuration map
Returns: Entity configuration with :relationships key added
Example: (detect-relationships {:label "Orders" :fields {...}})
Detect all relationships for an entity configuration.
Week 2: Detects belongs-to relationships from foreign key fields.
Week 3+: Could add has-many and has-one detection.
Args:
entity-config: Entity configuration map
Returns:
Entity configuration with :relationships key added
Example:
(detect-relationships {:label "Orders" :fields {...}})(extract-enum-fields-from-malli-schema schema)Extract enum field configurations from a raw Malli :map schema.
Walks the map children and returns a partial field config for every field whose schema is [:enum v1 v2 ...]. Works on raw Malli schema data (no compilation / malli.core dependency needed).
Args: schema: Raw Malli schema data, expected to be a :map vector such as [:map {} [:role [:enum :admin :user]] [:theme {:optional true} [:enum :light :dark]]]
Returns: Map of field-name keyword → {:type :enum :widget :select :options [...]} Options are [value label] pairs where label is a humanised string. Returns {} when schema is nil or not a :map schema.
Example: (extract-enum-fields-from-malli-schema [:map {} [:role [:enum :admin :user :viewer]]]) ;=> {:role {:type :enum :widget :select ; :options [[:admin "Admin"] [:user "User"] [:viewer "Viewer"]]}}
Extract enum field configurations from a raw Malli :map schema.
Walks the map children and returns a partial field config for every field
whose schema is [:enum v1 v2 ...]. Works on raw Malli schema data (no
compilation / malli.core dependency needed).
Args:
schema: Raw Malli schema data, expected to be a :map vector such as
[:map {} [:role [:enum :admin :user]] [:theme {:optional true}
[:enum :light :dark]]]
Returns:
Map of field-name keyword → {:type :enum :widget :select :options [...]}
Options are [value label] pairs where label is a humanised string.
Returns {} when schema is nil or not a :map schema.
Example:
(extract-enum-fields-from-malli-schema
[:map {} [:role [:enum :admin :user :viewer]]])
;=> {:role {:type :enum :widget :select
; :options [[:admin "Admin"] [:user "User"] [:viewer "Viewer"]]}}(humanize-entity-name entity-name)Convert entity name to human-readable label (pluralized).
Args: entity-name: Keyword entity name
Returns: String display label
Examples: (humanize-entity-name :user) ;=> "Users" (humanize-entity-name :item) ;=> "Items" (humanize-entity-name :category) ;=> "Categories"
Convert entity name to human-readable label (pluralized). Args: entity-name: Keyword entity name Returns: String display label Examples: (humanize-entity-name :user) ;=> "Users" (humanize-entity-name :item) ;=> "Items" (humanize-entity-name :category) ;=> "Categories"
(humanize-field-name field-name)Convert field name to human-readable label.
Converts kebab-case or snake_case to Title Case.
Args: field-name: Keyword field name
Returns: String display label
Examples: (humanize-field-name :email) ;=> "Email" (humanize-field-name :first-name) ;=> "First Name" (humanize-field-name :created_at) ;=> "Created At" (humanize-field-name :mfa-enabled) ;=> "Mfa Enabled"
Convert field name to human-readable label. Converts kebab-case or snake_case to Title Case. Args: field-name: Keyword field name Returns: String display label Examples: (humanize-field-name :email) ;=> "Email" (humanize-field-name :first-name) ;=> "First Name" (humanize-field-name :created_at) ;=> "Created At" (humanize-field-name :mfa-enabled) ;=> "Mfa Enabled"
(infer-field-type sql-type)(infer-field-type sql-type field-name)Infer logical field type from SQL/database type.
Args: sql-type: String SQL type from database metadata field-name: (Optional) Keyword field name for heuristics
Returns: Keyword field type (:uuid, :string, :int, etc.) Defaults to :string if type is not recognized
Examples: (infer-field-type "VARCHAR(255)") ;=> :string (infer-field-type "INTEGER") ;=> :int (infer-field-type "BOOLEAN") ;=> :boolean (infer-field-type "TIMESTAMP") ;=> :instant (infer-field-type "TEXT" :created-at) ;=> :instant (heuristic) (infer-field-type "TEXT" :email) ;=> :string (heuristic) (infer-field-type "UNKNOWN_TYPE") ;=> :string
Infer logical field type from SQL/database type. Args: sql-type: String SQL type from database metadata field-name: (Optional) Keyword field name for heuristics Returns: Keyword field type (:uuid, :string, :int, etc.) Defaults to :string if type is not recognized Examples: (infer-field-type "VARCHAR(255)") ;=> :string (infer-field-type "INTEGER") ;=> :int (infer-field-type "BOOLEAN") ;=> :boolean (infer-field-type "TIMESTAMP") ;=> :instant (infer-field-type "TEXT" :created-at) ;=> :instant (heuristic) (infer-field-type "TEXT" :email) ;=> :string (heuristic) (infer-field-type "UNKNOWN_TYPE") ;=> :string
(infer-widget-for-field field-name field-type _sql-type)Infer appropriate UI widget for a field based on its characteristics.
Args: field-name: Keyword field name (used for heuristics) field-type: Keyword field type (:uuid, :string, :int, etc.) sql-type: Original SQL type string (for additional context)
Returns: Keyword widget type (:text-input, :email-input, :checkbox, etc.)
Examples: (infer-widget-for-field :email :string "VARCHAR") ;=> :email-input (infer-widget-for-field :password :string "VARCHAR") ;=> :password-input (infer-widget-for-field :active :boolean "BOOLEAN") ;=> :checkbox (infer-widget-for-field :created-at :instant "TIMESTAMP") ;=> :datetime-input
Infer appropriate UI widget for a field based on its characteristics. Args: field-name: Keyword field name (used for heuristics) field-type: Keyword field type (:uuid, :string, :int, etc.) sql-type: Original SQL type string (for additional context) Returns: Keyword widget type (:text-input, :email-input, :checkbox, etc.) Examples: (infer-widget-for-field :email :string "VARCHAR") ;=> :email-input (infer-widget-for-field :password :string "VARCHAR") ;=> :password-input (infer-widget-for-field :active :boolean "BOOLEAN") ;=> :checkbox (infer-widget-for-field :created-at :instant "TIMESTAMP") ;=> :datetime-input
(merge-field-config auto-config manual-config)Merge auto-detected field config with manual overrides.
Args: auto-config: Field configuration from schema introspection manual-config: Manual overrides map (can be nil)
Returns: Merged field configuration with manual overrides applied
Example: (merge-field-config {:name :email :widget :text-input} {:widget :email-input :required true})
Merge auto-detected field config with manual overrides.
Args:
auto-config: Field configuration from schema introspection
manual-config: Manual overrides map (can be nil)
Returns:
Merged field configuration with manual overrides applied
Example:
(merge-field-config {:name :email :widget :text-input}
{:widget :email-input :required true})(merge-fields-config auto-fields manual-fields)Merge all field configurations with manual overrides.
Args: auto-fields: Map of field-name -> auto-detected field config manual-fields: Map of field-name -> manual field config (can be nil)
Returns: Merged fields map
Example: (merge-fields-config {:email {...} :name {...}} {:email {:widget :email-input}})
Merge all field configurations with manual overrides.
Args:
auto-fields: Map of field-name -> auto-detected field config
manual-fields: Map of field-name -> manual field config (can be nil)
Returns:
Merged fields map
Example:
(merge-fields-config {:email {...} :name {...}}
{:email {:widget :email-input}})(normalize-sql-type sql-type)Normalize SQL type string to lowercase without size/precision.
Args: sql-type: String SQL type (e.g., 'VARCHAR(255)', 'DECIMAL(10,2)')
Returns: Normalized lowercase type string without size
Examples: (normalize-sql-type "VARCHAR(255)") ;=> "varchar" (normalize-sql-type "DECIMAL(10,2)") ;=> "decimal" (normalize-sql-type "INTEGER") ;=> "integer" (normalize-sql-type "TIMESTAMP") ;=> "timestamp"
Normalize SQL type string to lowercase without size/precision. Args: sql-type: String SQL type (e.g., 'VARCHAR(255)', 'DECIMAL(10,2)') Returns: Normalized lowercase type string without size Examples: (normalize-sql-type "VARCHAR(255)") ;=> "varchar" (normalize-sql-type "DECIMAL(10,2)") ;=> "decimal" (normalize-sql-type "INTEGER") ;=> "integer" (normalize-sql-type "TIMESTAMP") ;=> "timestamp"
(parse-column-metadata column-meta)Parse single database column into field configuration.
Args: column-meta: Map with column metadata from database: {:name "email" :type "varchar" :not-null true :default nil :primary-key false}
Returns: Field configuration map: {:name :email :label "Email" :type :string :widget :email-input :required true :readonly false :hidden false :searchable true :sortable true :filterable true}
Example: (parse-column-metadata {:name "email" :type "VARCHAR(255)" :not-null true :primary-key false})
Parse single database column into field configuration.
Args:
column-meta: Map with column metadata from database:
{:name "email"
:type "varchar"
:not-null true
:default nil
:primary-key false}
Returns:
Field configuration map:
{:name :email
:label "Email"
:type :string
:widget :email-input
:required true
:readonly false
:hidden false
:searchable true
:sortable true
:filterable true}
Example:
(parse-column-metadata {:name "email"
:type "VARCHAR(255)"
:not-null true
:primary-key false})(parse-table-metadata table-name columns-meta)Parse database table metadata into entity configuration.
Takes raw database column metadata and produces a complete entity configuration with sensible defaults for all fields.
Args: table-name: Keyword table name columns-meta: Vector of column metadata maps from database
Returns: Entity configuration map with:
Example: (parse-table-metadata :users [{:name "id" :type "UUID" :primary-key true} {:name "email" :type "VARCHAR(255)" :not-null true} {:name "created_at" :type "TIMESTAMP" :not-null true}])
Parse database table metadata into entity configuration.
Takes raw database column metadata and produces a complete entity
configuration with sensible defaults for all fields.
Args:
table-name: Keyword table name
columns-meta: Vector of column metadata maps from database
Returns:
Entity configuration map with:
- Field configurations for all columns
- Default field lists (list, detail, editable, etc.)
- Primary key identification
- Auto-generated entity label
Example:
(parse-table-metadata :users
[{:name "id" :type "UUID" :primary-key true}
{:name "email" :type "VARCHAR(255)" :not-null true}
{:name "created_at" :type "TIMESTAMP" :not-null true}])(should-be-hidden? field-name)Determine if field should be hidden in admin UI.
Args: field-name: Keyword field name
Returns: Boolean true if field should be hidden
Examples: (should-be-hidden? :password-hash) ;=> true (should-be-hidden? :email) ;=> false
Determine if field should be hidden in admin UI. Args: field-name: Keyword field name Returns: Boolean true if field should be hidden Examples: (should-be-hidden? :password-hash) ;=> true (should-be-hidden? :email) ;=> false
(should-be-in-list-view? field-name field-type)Determine if field should be shown in table list view.
Some fields are better shown only in detail/edit views, not in the compact table list view.
Args: field-name: Keyword field name field-type: Keyword field type
Returns: Boolean true if field should be in list view
Examples: (should-be-in-list-view? :email :string) ;=> true (should-be-in-list-view? :active :boolean) ;=> false (should-be-in-list-view? :notes :text) ;=> false
Determine if field should be shown in table list view. Some fields are better shown only in detail/edit views, not in the compact table list view. Args: field-name: Keyword field name field-type: Keyword field type Returns: Boolean true if field should be in list view Examples: (should-be-in-list-view? :email :string) ;=> true (should-be-in-list-view? :active :boolean) ;=> false (should-be-in-list-view? :notes :text) ;=> false
(should-be-readonly? field-name is-primary-key?)Determine if field should be readonly based on characteristics.
Args: field-name: Keyword field name is-primary-key?: Boolean indicating if field is primary key
Returns: Boolean true if field should be readonly
Examples: (should-be-readonly? :id true) ;=> true (should-be-readonly? :created-at false) ;=> true (should-be-readonly? :name false) ;=> false
Determine if field should be readonly based on characteristics. Args: field-name: Keyword field name is-primary-key?: Boolean indicating if field is primary key Returns: Boolean true if field should be readonly Examples: (should-be-readonly? :id true) ;=> true (should-be-readonly? :created-at false) ;=> true (should-be-readonly? :name false) ;=> false
(should-be-searchable? field-type field-name)Determine if field should be searchable.
Args: field-type: Keyword field type field-name: Keyword field name
Returns: Boolean true if field should be searchable
Examples: (should-be-searchable? :string :email) ;=> true (should-be-searchable? :text :description) ;=> true (should-be-searchable? :binary :avatar) ;=> false
Determine if field should be searchable. Args: field-type: Keyword field type field-name: Keyword field name Returns: Boolean true if field should be searchable Examples: (should-be-searchable? :string :email) ;=> true (should-be-searchable? :text :description) ;=> true (should-be-searchable? :binary :avatar) ;=> false
(should-be-sortable? field-type)Determine if field should be sortable.
Args: field-type: Keyword field type
Returns: Boolean true if field should be sortable
Examples: (should-be-sortable? :string) ;=> true (should-be-sortable? :int) ;=> true (should-be-sortable? :json) ;=> false
Determine if field should be sortable. Args: field-type: Keyword field type Returns: Boolean true if field should be sortable Examples: (should-be-sortable? :string) ;=> true (should-be-sortable? :int) ;=> true (should-be-sortable? :json) ;=> false
Mapping from SQL/database types to logical field types.
Keys are lowercase type names as strings (normalized from database). Values are field type keywords used in entity configuration.
Mapping from SQL/database types to logical field types. Keys are lowercase type names as strings (normalized from database). Values are field type keywords used in entity configuration.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |