113 lines
3.7 KiB
Text
113 lines
3.7 KiB
Text
# Configuration Composition Helpers
|
|
#
|
|
# Provides utilities for merging configurations from multiple layers:
|
|
# 1. Schema (type contracts)
|
|
# 2. Service Defaults (base values)
|
|
# 3. Mode Overlay (mode-specific tuning)
|
|
# 4. User Customization (overrides)
|
|
|
|
{
|
|
# Recursively merge two record configurations
|
|
# Override values take precedence over base values
|
|
# When both values are records, merge recursively (deep merge)
|
|
#
|
|
# Example:
|
|
# let base = { server = { port = 9000, workers = 4 } }
|
|
# let mode = { server = { workers = 16 } }
|
|
# merge_with_override base mode
|
|
# # Result: { server = { port = 9000, workers = 16 } }
|
|
merge_with_override = fun base override =>
|
|
if (std.typeof base) == "record" && (std.typeof override) == "record" then
|
|
let base_fields = std.record.fields base in
|
|
let override_fields = std.record.fields override in
|
|
|
|
# Pass 1: iterate base fields, merging with override where present
|
|
base_fields
|
|
|> std.array.fold_left
|
|
(fun acc key =>
|
|
let base_value = std.record.get key base in
|
|
|
|
if std.record.has_field key override then
|
|
let override_value = std.record.get key override in
|
|
|
|
if (std.typeof base_value) == "record" && (std.typeof override_value) == "record" then
|
|
acc
|
|
|> std.record.insert key (
|
|
merge_with_override base_value override_value
|
|
)
|
|
else
|
|
# Override value takes precedence
|
|
acc |> std.record.insert key override_value
|
|
else
|
|
# Keep base value
|
|
acc |> std.record.insert key base_value
|
|
)
|
|
{}
|
|
# Pass 2: add fields exclusive to override
|
|
|> (fun merged =>
|
|
override_fields
|
|
|> std.array.fold_left
|
|
(fun acc key =>
|
|
if !(std.record.has_field key base) then
|
|
acc |> std.record.insert key (std.record.get key override)
|
|
else
|
|
acc
|
|
)
|
|
merged
|
|
)
|
|
else
|
|
# If either is not a record, override takes precedence
|
|
if (std.typeof override) == "null" then base else override,
|
|
|
|
# Alias for backwards compatibility
|
|
deep_merge = fun a b => merge_with_override a b,
|
|
|
|
# Compose configuration from multiple layers with proper merging
|
|
#
|
|
# Layer 1: defaults (base values)
|
|
# Layer 2: mode_config (mode-specific overrides)
|
|
# Layer 3: user_overrides (user customization)
|
|
#
|
|
# Example:
|
|
# compose_config
|
|
# orchestrator_defaults
|
|
# solo_mode_config
|
|
# user_customization
|
|
compose_config = fun defaults mode_config user_overrides =>
|
|
let base_merged = merge_with_override defaults mode_config in
|
|
merge_with_override base_merged user_overrides,
|
|
|
|
# Transform configuration for runtime (identity for now, extensible)
|
|
# This is a hook for service-specific transformations
|
|
transform_for_runtime = fun config service_name =>
|
|
config,
|
|
|
|
# Flatten nested record into dot-notation for TOML export
|
|
# Example:
|
|
# flatten_config { server = { port = 9000 } }
|
|
# # Result: { "server.port" = 9000 }
|
|
flatten_config = fun config =>
|
|
let rec flatten_with_prefix = fun prefix config =>
|
|
if (std.typeof config) == "record" then
|
|
std.record.fields config
|
|
|> std.array.fold_left
|
|
(fun acc key =>
|
|
let value = std.record.get key config in
|
|
let full_key = if std.string.is_empty prefix then
|
|
key
|
|
else
|
|
prefix ++ "." ++ key
|
|
in
|
|
|
|
if (std.typeof value) == "record" then
|
|
acc
|
|
|> std.record.merge (flatten_with_prefix full_key value)
|
|
else
|
|
acc |> std.record.insert full_key value
|
|
)
|
|
{}
|
|
else
|
|
config
|
|
in
|
|
flatten_with_prefix "" config,
|
|
}
|