# `Lotus.Config`
[🔗](https://github.com/elixir-lotus/lotus/blob/v1.0.0/lib/lotus/config.ex#L1)

Configuration management for Lotus.

Handles loading and validating configuration from the application environment
using `NimbleOptions`.

## Required Configuration

Lotus requires a storage repository where it will store query definitions:

    config :lotus,
      storage_repo: MyApp.Repo  # Where Lotus stores its query definitions

## Data Sources Configuration

Configure named data sources that Lotus can execute queries against:

    config :lotus,
      data_sources: %{
        "primary" => MyApp.Repo,
        "analytics" => MyApp.AnalyticsRepo,
        "warehouse" => MyApp.WarehouseRepo
      }

## Visibility Configuration

Control which schemas and tables are accessible through Lotus with visibility rules:

    config :lotus,
      # Schema-level rules (higher precedence)
      schema_visibility: %{
        postgres: [
          allow: ["public", ~r/^tenant_/],  # Only public + tenant schemas
          deny: ["legacy"]                  # Block legacy schema
        ],
        mysql: [
          # In MySQL, schemas = databases
          allow: ["app_db", "analytics_db"],
          deny: ["staging_db"]
        ]
      },

      # Table-level rules (lower precedence)
      table_visibility: %{
        default: [
          deny: ["user_passwords", "api_keys", ~r/^audit_/]
        ],
        postgres: [
          allow: [
            {"public", ~r/^dim_/},      # Dimension tables only
            {"analytics", ~r/.*/}       # All analytics tables
          ]
        ]
      }

**Key Principle**: Schema visibility gates table visibility. If a schema is denied,
all tables within it are blocked regardless of table-level rules.

**Database-Specific Schema Behavior**:

- **PostgreSQL**: True namespaced schemas within a database (`public`, `reporting`, etc.)
- **MySQL**: Schemas = Databases (when you connect to MySQL, you can access multiple databases)
- **SQLite**: Schema-less (visibility rules don't apply)

See the [Visibility Guide](guides/visibility.html) for detailed configuration examples.

## Optional Configuration

    config :lotus,
      default_source: "primary",     # Default data source for queries
      unique_names: false,           # Defaults to true
      read_only: false               # Defaults to true; set to false to allow writes

# `cache_config`

```elixir
@type cache_config() :: %{
  optional(:cachex_opts) =&gt; keyword(),
  adapter: module() | nil,
  namespace: String.t(),
  profiles: %{required(atom()) =&gt; keyword()},
  compress: boolean(),
  max_bytes: non_neg_integer(),
  lock_timeout: non_neg_integer(),
  default_ttl_ms: non_neg_integer(),
  default_profile: atom(),
  key_builder: module()
}
```

# `t`

```elixir
@type t() :: %{
  storage_repo: module(),
  read_only: boolean(),
  unique_names: boolean(),
  data_sources: %{required(String.t()) =&gt; module() | map()},
  default_source: String.t() | nil,
  default_page_size: pos_integer() | nil,
  table_visibility: map(),
  column_visibility: map(),
  schema_visibility: map(),
  allow_unrestricted_resources: boolean(),
  cache: cache_config(),
  ai: keyword() | map() | nil,
  source_adapters: [module()],
  trusted_source_adapters: [module()],
  source_resolver: module(),
  visibility_resolver: module(),
  middleware: %{required(atom()) =&gt; [{module(), term()}]}
}
```

# `ai`

```elixir
@spec ai() :: keyword()
```

Returns AI configuration keyword list.

# `ai_enabled?`

```elixir
@spec ai_enabled?() :: boolean()
```

Returns whether AI features are enabled.

# `all`

```elixir
@spec all() :: t() | keyword()
```

Returns the entire validated configuration as a map.

Useful for debugging or inspection.

# `allow_unrestricted_resources?`

```elixir
@spec allow_unrestricted_resources?(String.t()) :: boolean()
```

Returns whether the given source is allowed to return
`{:unrestricted, _}` from `extract_accessed_resources/2` without being
blocked by preflight.

Resolution order:

  1. If the source's `data_sources` entry is a config map with an explicit
     boolean `allow_unrestricted_resources`, that value wins for the source —
     `true` opts it in, `false` tightens it even when the global flag is on.
  2. Otherwise falls back to the top-level `:allow_unrestricted_resources`
     flag.

The per-source override is symmetric on purpose: an operator who sets
`false` on a single source while running a permissive global default
should be able to trust that the source stays locked down.

Used by `Lotus.Preflight.authorize/4` to gate non-SQL adapters whose
engines enforce visibility at a layer Lotus can't introspect.

# `cache_adapter`

```elixir
@spec cache_adapter() :: {:ok, module()} | :error
```

Returns cache adapter module if configured.

# `cache_config`

```elixir
@spec cache_config() :: cache_config() | nil
```

Returns the cache configuration.

# `cache_entry_options`

```elixir
@spec cache_entry_options() :: keyword()
```

Returns the cache entry options the operator set in `:cache` config.

These apply to every cached entry unless a caller overrides them in a
per-call `:cache` option:

  * `:max_bytes` - skip writing an entry whose encoded size exceeds this
  * `:compress` - store the entry compressed
  * `:lock_timeout` - how long a caller waits for whichever process is
    already computing the same key, before computing it itself

Only keys that are actually configured are returned, so the cache adapter
keeps deciding the default for anything left unset.

# `cache_key_builder`

```elixir
@spec cache_key_builder() :: module()
```

Returns the configured cache key builder module.

Falls back to `Lotus.Cache.KeyBuilder.Default` when not configured.

# `cache_namespace`

```elixir
@spec cache_namespace() :: String.t()
```

Returns the cache namespace.

# `cache_profile_settings`

```elixir
@spec cache_profile_settings(atom()) :: keyword()
```

Returns cache settings for a specific profile.

Falls back to built-in defaults for :results, :schema, and :options profiles.
Users can override these defaults in their configuration.

# `column_rules_for_source_name`

```elixir
@spec column_rules_for_source_name(String.t()) :: list()
```

Returns column visibility rules for a specific source.

Falls back to default rules if source-specific rules are not configured.

# `data_sources`

```elixir
@spec data_sources() :: %{required(String.t()) =&gt; module() | map()}
```

Returns the configured data sources.

# `default_cache_profile`

```elixir
@spec default_cache_profile() :: atom()
```

Returns the globally configured default cache profile.

Falls back to :results if none configured.

# `default_data_source`

```elixir
@spec default_data_source() :: {String.t(), module()}
```

Returns the default data source as a {name, module} tuple.

- If default_source is configured, returns that source
- If default_source is not configured, returns the first available source
- If no data sources are configured, raises an error

# `default_page_size`

```elixir
@spec default_page_size() :: pos_integer() | nil
```

Returns the globally configured default page size for windowed pagination, if any.

When nil, Lotus uses its built-in default page size.

# `get`

```elixir
@spec get(atom()) :: any()
```

Gets a configuration value by key.

Returns the configuration for the given key from the application environment.

# `get_data_source!`

```elixir
@spec get_data_source!(String.t()) :: module() | map()
```

Gets a data source by name.

Returns the source module or raises if not found.

# `list_data_source_names`

```elixir
@spec list_data_source_names() :: [String.t()]
```

Lists the names of all configured data sources.

# `load!`

```elixir
@spec load!() :: t() | keyword()
```

Loads and validates the Lotus configuration.

When called with no arguments, returns a cached, pre-validated configuration
from `:persistent_term`, validating and caching it on first access. When
called with an explicit keyword list, always validates the supplied options
without touching the cache.

Raises `ArgumentError` if the configuration is invalid.

# `load!`

```elixir
@spec load!(keyword()) :: t() | keyword()
```

# `middleware`

```elixir
@spec middleware() :: map()
```

Returns middleware configuration map, or empty map if not configured.

# `read_only?`

```elixir
@spec read_only?() :: boolean()
```

Returns whether queries are restricted to read-only operations.

# `reload!`

```elixir
@spec reload!() :: t() | keyword()
```

Re-reads configuration from the application environment, validates it, and
refreshes the cached value in `:persistent_term`.

Call this whenever `:lotus` application environment changes after boot
(e.g. in tests that use `Application.put_env/3`).

# `repo!`

```elixir
@spec repo!() :: module()
```

Returns the configured Ecto repository.

# `rules_for_source_name`

```elixir
@spec rules_for_source_name(String.t()) :: keyword()
```

Returns table visibility rules for a specific source.

Falls back to default rules if source-specific rules are not configured.

# `schema_rules_for_source_name`

```elixir
@spec schema_rules_for_source_name(String.t()) :: keyword()
```

Returns schema visibility rules for a specific source.

Falls back to default rules if source-specific rules are not configured.

# `source_adapters`

```elixir
@spec source_adapters() :: [module()]
```

Returns the list of external source adapter modules.

# `source_resolver`

```elixir
@spec source_resolver() :: module()
```

Returns the configured source resolver module.

# `trusted_source_adapter?`

```elixir
@spec trusted_source_adapter?(module()) :: boolean()
```

Return whether the given adapter module is trusted to contribute
free-form text (`:syntax_notes`, `:error_patterns`) to AI prompts.

Always-trusted: the built-in `Lotus.Source.Adapters.Ecto` and the
first-party per-dialect adapters it exposes via `builtin_adapters/0`
(Postgres, MySQL, SQLite3). Additional modules can be trusted via
`config :lotus, :trusted_source_adapters, [MyAdapter]`.

Untrusted adapters still supply a `:language` identifier to the
prompt — just not the free-form fields.

# `unique_names?`

```elixir
@spec unique_names?() :: boolean()
```

Returns whether unique query names are enforced.

# `visibility_resolver`

```elixir
@spec visibility_resolver() :: module()
```

Returns the configured visibility resolver module.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
