Class: Smplkit::Config::ConfigClient
- Inherits:
-
Object
- Object
- Smplkit::Config::ConfigClient
- Defined in:
- lib/smplkit/config/client.rb
Overview
Synchronous runtime client for Smpl Config.
Obtained via Smplkit::Client#config. Exposes #bind (the recommended declarative API), #get (lookup-only escape hatch), #refresh, and #on_change. Management/CRUD lives on Smplkit::Client#manage.config.
Instance Method Summary collapse
-
#_cached_values(config_id) ⇒ Object
Internal: return (a copy of) the resolved values for a config id.
- #_close ⇒ Object
-
#_observe_config_declaration(config_id, parent:, name:, description:) ⇒ Object
Internal: queue a config declaration with the management buffer.
-
#_observe_item_declaration(config_id, item_key, item_type, default, description) ⇒ Object
Internal: queue a config item declaration with the management buffer.
-
#bind(id, target, parent: nil) ⇒ Object
Bind a Hash or Struct to a config id; return the same object back, live.
-
#get(id, key = MISSING, default = MISSING) ⇒ Object
Read a config (full) or a single value within a config.
-
#initialize(parent, manage:, metrics:) ⇒ ConfigClient
constructor
A new instance of ConfigClient.
-
#on_change(config_id = nil, item_key: nil, &block) ⇒ Object
Register a change listener.
-
#refresh ⇒ Object
Re-fetch all configs and update resolved values, firing change listeners for anything that differs from the previous state.
-
#start ⇒ Object
Eagerly initialize the runtime.
Constructor Details
#initialize(parent, manage:, metrics:) ⇒ ConfigClient
Returns a new instance of ConfigClient.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/smplkit/config/client.rb', line 224 def initialize(parent, manage:, metrics:) @parent = parent @manage = manage @metrics = metrics @environment = parent._environment @service = parent._service @config_cache = {} # config_key -> { item_key => resolved_value } @raw_config_store = {} # config_key -> Smplkit::Config::Config @proxies = {} # config_key -> LiveConfigProxy @bindings = {} # config_key -> Hash | Struct (bound target) @listeners = [] # [callback, config_id_or_nil, item_key_or_nil] @connected = false @lock = Mutex.new @ws_manager = nil end |
Instance Method Details
#_cached_values(config_id) ⇒ Object
Internal: return (a copy of) the resolved values for a config id. Used by LiveConfigProxy.
365 366 367 368 369 |
# File 'lib/smplkit/config/client.rb', line 365 def _cached_values(config_id) @lock.synchronize do (@config_cache[config_id] || {}).dup end end |
#_close ⇒ Object
358 359 360 361 |
# File 'lib/smplkit/config/client.rb', line 358 def _close # No durable resources owned by this sub-client; the parent client # tears down the WebSocket and management transports. end |
#_observe_config_declaration(config_id, parent:, name:, description:) ⇒ Object
Internal: queue a config declaration with the management buffer.
372 373 374 375 376 377 378 379 380 381 |
# File 'lib/smplkit/config/client.rb', line 372 def _observe_config_declaration(config_id, parent:, name:, description:) @manage&.config&.register_config( config_id, service: @service, environment: @environment, parent: parent, name: name, description: description ) end |
#_observe_item_declaration(config_id, item_key, item_type, default, description) ⇒ Object
Internal: queue a config item declaration with the management buffer.
384 385 386 |
# File 'lib/smplkit/config/client.rb', line 384 def _observe_item_declaration(config_id, item_key, item_type, default, description) @manage&.config&.register_config_item(config_id, item_key, item_type, default, description) end |
#bind(id, target, parent: nil) ⇒ Object
Bind a Hash or Struct to a config id; return the same object back, live.
Declarative, code-first API. Two flavors:
-
Hash: keys present are leaves to register, with their values as the in-code defaults. Nested Hashes flatten to dot-notation. Keys the caller wants to inherit fromparent:are simply omitted. -
Struct: every member is registered as an explicit override. Ruby Structs do not track which members were “explicitly set” vs defaulted, so there is no Hash-style omit-to-inherit. For omit-to-inherit, use a Hash target.
On first call the schema and values are registered with the server. After the local cache is populated, any server-side overrides for this config are applied to the bound object in place. WebSocket events thereafter mutate the bound object in place — readers always see the current resolved value with no indirection.
Idempotent. Repeat calls with the same id return the originally-bound object; the new config argument is ignored.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/smplkit/config/client.rb', line 291 def bind(id, target, parent: nil) unless target.is_a?(Hash) || target.is_a?(Struct) raise TypeError, "bind() requires a Hash or Struct; got #{target.class.name}" end return @bindings[id] if @bindings.key?(id) parent_id = resolve_parent_id(parent) if target.is_a?(Struct) class_name = target.class.name config_name = class_name&.split("::")&.last else config_name = nil end _observe_config_declaration(id, parent: parent_id, name: config_name, description: nil) Discovery.iter_items(target).each do |item_key, item_type, value, description| _observe_item_declaration(id, item_key, item_type, value, description) end # Register the binding BEFORE start() so any WS dispatch that fires # during the initial fetch finds it. @bindings[id] = target start unless @connected sync_target_from_cache(target, id) target end |
#get(id, key = MISSING, default = MISSING) ⇒ Object
Read a config (full) or a single value within a config.
Three forms dispatched by argument count:
get("id") # LiveConfigProxy (raises NotFoundError)
get("id", "key") # value (raises NotFoundError / KeyError)
get("id", "key", default) # value or default; auto-registers (never raises)
329 330 331 332 333 334 335 |
# File 'lib/smplkit/config/client.rb', line 329 def get(id, key = MISSING, default = MISSING) start unless @connected return get_full_config(id) if key.equal?(MISSING) get_single_value(id, key.to_s, default) end |
#on_change(config_id = nil, item_key: nil, &block) ⇒ Object
Register a change listener.
Three forms:
client.config.on_change { |event| ... } # global
client.config.on_change("id") { |event| ... } # config-scoped
client.config.on_change("id", item_key: "key") { |event| ... } # item-scoped
344 345 346 347 348 349 |
# File 'lib/smplkit/config/client.rb', line 344 def on_change(config_id = nil, item_key: nil, &block) raise ArgumentError, "on_change requires a block" unless block @listeners << [block, config_id, item_key&.to_s] block end |
#refresh ⇒ Object
Re-fetch all configs and update resolved values, firing change listeners for anything that differs from the previous state.
353 354 355 356 |
# File 'lib/smplkit/config/client.rb', line 353 def refresh start unless @connected do_refresh("manual") end |
#start ⇒ Object
Eagerly initialize the runtime. Flushes any buffered discovery declarations, fetches the full config list, resolves values for the SDK’s current environment into the local cache, and subscribes to config_changed / config_deleted / configs_changed events on the shared WebSocket.
Idempotent — safe to call multiple times. Invoked automatically on the first #get or #bind call.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/smplkit/config/client.rb', line 249 def start return if @connected @environment = @parent._environment # Per ADR-037 §2.14: flush pending discovery declarations BEFORE # the initial fetch so newly-declared configs show up in the cache. begin @manage&.config&.flush rescue StandardError => e Smplkit.debug("config", "pre-start discovery flush failed: #{e.class}: #{e.}") end do_refresh("initial") @connected = true @ws_manager = @parent._ensure_ws @ws_manager.on("config_changed") { |data| handle_config_changed(data) } @ws_manager.on("config_deleted") { |data| handle_config_deleted(data) } @ws_manager.on("configs_changed") { |data| handle_configs_changed(data) } end |