Optify

Optify is an Elixir client for Optify, powered by the upstream Rust crate via Rustler NIFs.

Usage

Configure the default provider in your app config. Optify starts its own default provider process, so you do not need to add it to your app's supervisor tree manually. The default provider auto-loads automatically once :optify, :provider is configured.

# config/dev.exs
import Config

config :optify,
  auto_reload_default_provider: true
# config/runtime.exs
import Config

config :optify, :provider,
  directory:
    System.get_env("OPTIFY_CONFIG_DIR") ||
      Application.app_dir(:my_app, "priv/optify")

# Optional: validate feature files against a custom schema that extends
# Optify's upstream feature-file schema.
config :optify, :provider,
  directory:
    System.get_env("OPTIFY_CONFIG_DIR") ||
      Application.app_dir(:my_app, "priv/optify"),
  schema_path:
    System.get_env("OPTIFY_SCHEMA_PATH") ||
      Application.app_dir(:my_app, "priv/optify_schema.json")

Recommended behavior:

Feature files

Point :provider at a directory of feature files. Feature names come from the relative file path without the extension.

Example files:

// priv/optify/feature_a.json
{
  "metadata": {
    "aliases": ["A"]
  },
  "options": {
    "flow": {
      "handler": "a",
      "timeout_ms": 100
    }
  }
}
# priv/optify/feature_b.yaml
metadata:
  aliases:
    - "B"
options:
  flow:
    handler: "b"
    timeout_ms: 200

That gives you:

When you request both features, later features override earlier ones.

Loading from features

Fetch merged options by feature name using the default provider:

options = Optify.get_options!(["feature_a", "feature_b"])

# atom keys by default for dot access
options.flow
options.flow.handler

flow is just a normal top-level key under options in your feature files. No special key name is required.

The high-level Optify.get_options!/1 and Optify.get_options!/2 APIs also hydrate known nested option paths with nil defaults. If another feature defines options.flow.handler, then options.flow.handler stays safe even when the selected feature set does not define flow at all.

Aliases work too:

options = Optify.get_options!(["A", "B"])
options.flow.handler

You can also fetch a specific top-level key with the explicit provider API:

provider = Optify.build!(Application.app_dir(:my_app, "priv/optify"))

flow = Optify.get_options!(provider, "flow", ["feature_a", "feature_b"])
flow["handler"]

The same default-provider shortcut pattern is available for provider introspection, including feature listing, alias lookup, and feature metadata access.

Dumping a resolved feature

When a feature is spread across many imported files, you can dump the resolved merged output for review:

mix optify.dump feature_a
mix optify.dump A --output tmp/optify/feature_a.json
mix optify.dump feature_a --key flow

The task:

Advanced: typed options

You can cast the merged options into a struct/module:

defmodule MyApp.FlowOptions do
  defstruct [:handler, :timeout_ms]

  def from_optify(%{flow: flow}) do
    struct(__MODULE__, flow)
  end
end

flow = Optify.get_options!(["feature_a"], as: MyApp.FlowOptions)

This is optional. The default get_options! API already returns dot-friendly maps, so you only need as: when you specifically want a typed struct/module.

If as: points to a struct module without from_optify/1, Optify will attempt direct key-based casting.

API shape

Development

Rust and Cargo are required because the NIF is built from source. If you use mise, install Rust before running the project:

mise use -g rust@stable

Then:

mix deps.get
mix format
mix test