143 lines
4.4 KiB
Plaintext
Raw Normal View History

# 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
# Mode config values override defaults (shallow merge at each level)
#
# 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.type.is_record base && std.type.is_record override then
let base_fields = std.record.fields base in
let override_fields = std.record.fields override in
base_fields
|> std.array.fold
(fun acc key =>
let base_value = base |> std.record.get key in
if std.record.has_field override key then
let override_value = override |> std.record.get key in
if std.type.is_record base_value && std.type.is_record override_value 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
)
(override_fields
|> std.array.fold
(fun acc key =>
if !std.record.has_field base key then
acc |> std.record.insert key (override |> std.record.get key)
else
acc
)
base
)
else
# If either is not a record, override takes precedence
if std.type.is_null override then base else override,
# 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.type.is_record config then
std.record.fields config
|> std.array.fold
(fun acc key =>
let value = config |> std.record.get key in
let full_key = if std.string.is_empty prefix then
key
else
prefix ++ "." ++ key
in
if std.type.is_record value 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,
# Deep merge with recursive descent
# Used for complex nested configs
deep_merge = fun a b =>
if std.type.is_record a && std.type.is_record b then
let a_fields = std.record.fields a in
a_fields
|> std.array.fold
(fun acc key =>
let a_val = a |> std.record.get key in
let has_b_key = std.record.has_field b key in
if has_b_key then
let b_val = b |> std.record.get key in
if std.type.is_record a_val && std.type.is_record b_val then
acc |> std.record.insert key (deep_merge a_val b_val)
else
acc |> std.record.insert key b_val
else
acc |> std.record.insert key a_val
)
{}
|> (fun merged =>
std.record.fields b
|> std.array.fold
(fun acc key =>
if !std.record.has_field a key then
acc |> std.record.insert key (b |> std.record.get key)
else
acc
)
merged
)
else
b,
}