Class: Smplkit::Flags::FlagsClient
- Inherits:
-
Object
- Object
- Smplkit::Flags::FlagsClient
- Defined in:
- lib/smplkit/flags/client.rb
Overview
Synchronous flags runtime namespace.
Obtained via Smplkit::Client#flags. Exposes typed handles (boolean_flag/string_flag/number_flag/json_flag) and runtime control (refresh, stats, on_change). CRUD has moved to mgmt.flags.*. Per-request context is set via client.set_context().
Constant Summary collapse
- INITIAL_START_RETRY_DELAY =
1.0- MAX_START_RETRY_DELAY =
60.0
Instance Method Summary collapse
- #_close ⇒ Object
- #_evaluate_handle(flag_id, default, context) ⇒ Object
- #boolean_flag(id, default:) ⇒ Object
-
#initialize(parent, manage:, metrics:, flags_base_url:, app_base_url:) ⇒ FlagsClient
constructor
A new instance of FlagsClient.
- #json_flag(id, default:) ⇒ Object
- #number_flag(id, default:) ⇒ Object
-
#on_change(flag_id = nil, &block) ⇒ Object
Register a change listener.
- #refresh ⇒ Object
-
#start ⇒ Object
Eagerly initialize the flags subclient.
- #stats ⇒ Object
- #string_flag(id, default:) ⇒ Object
Constructor Details
#initialize(parent, manage:, metrics:, flags_base_url:, app_base_url:) ⇒ FlagsClient
Returns a new instance of FlagsClient.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/smplkit/flags/client.rb', line 85 def initialize(parent, manage:, metrics:, flags_base_url:, app_base_url:) @parent = parent @manage = manage @metrics = metrics @service = parent._service @environment = parent._environment @flags_base_url = flags_base_url @app_base_url = app_base_url @flag_store = {} @connected = false @ws_subscribed = false @next_start_attempt_at = 0.0 @start_retry_delay = INITIAL_START_RETRY_DELAY @cache = ResolutionCache.new @handles = {} @global_listeners = [] @key_listeners = Hash.new { |h, k| h[k] = [] } @ws_manager = nil @lock = Mutex.new end |
Instance Method Details
#_close ⇒ Object
190 191 192 |
# File 'lib/smplkit/flags/client.rb', line 190 def _close # No durable resources here — kept for symmetry with Python SDK. end |
#_evaluate_handle(flag_id, default, context) ⇒ Object
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/smplkit/flags/client.rb', line 194 def _evaluate_handle(flag_id, default, context) start unless @connected eval_dict = if context @manage.contexts.register(context) if @manage.respond_to?(:contexts) contexts_to_eval_dict(context) else current = Smplkit.request_context current.empty? ? {} : contexts_to_eval_dict(current) end eval_dict["service"] = { "key" => @service } if @service && !eval_dict.key?("service") ctx_hash = hash_context(eval_dict) cache_key = "#{flag_id}:#{ctx_hash}" hit, cached_value = @cache.get(cache_key) if hit @metrics&.record("flags.cache_hits", unit: "hits") @metrics&.record("flags.evaluations", unit: "evaluations", dimensions: { "flag" => flag_id }) return cached_value end flag_def = @flag_store[flag_id] if flag_def.nil? @cache.put(cache_key, default) return default end value = evaluate_flag(flag_def, @environment, eval_dict) value = default if value.nil? @cache.put(cache_key, value) @metrics&.record("flags.cache_misses", unit: "misses") @metrics&.record("flags.evaluations", unit: "evaluations", dimensions: { "flag" => flag_id }) value end |
#boolean_flag(id, default:) ⇒ Object
107 108 109 |
# File 'lib/smplkit/flags/client.rb', line 107 def boolean_flag(id, default:) register_handle(BooleanFlag, id, "BOOLEAN", default) end |
#json_flag(id, default:) ⇒ Object
119 120 121 |
# File 'lib/smplkit/flags/client.rb', line 119 def json_flag(id, default:) register_handle(JsonFlag, id, "JSON", default) end |
#number_flag(id, default:) ⇒ Object
115 116 117 |
# File 'lib/smplkit/flags/client.rb', line 115 def number_flag(id, default:) register_handle(NumberFlag, id, "NUMERIC", default) end |
#on_change(flag_id = nil, &block) ⇒ Object
Register a change listener.
client.flags.on_change { |event| ... } # global
client.flags.on_change("checkout-v2") { |e| ... } # flag-scoped
179 180 181 182 183 184 185 186 187 188 |
# File 'lib/smplkit/flags/client.rb', line 179 def on_change(flag_id = nil, &block) raise ArgumentError, "on_change requires a block" unless block if flag_id.nil? @global_listeners << block else @key_listeners[flag_id] << block end block end |
#refresh ⇒ Object
165 166 167 168 169 |
# File 'lib/smplkit/flags/client.rb', line 165 def refresh fetch_all_flags @cache.clear fire_change_listeners_all("manual") end |
#start ⇒ Object
Eagerly initialize the flags subclient.
Flushes any pending flag-declaration buffer, fetches all flag definitions, opens the shared WebSocket and subscribes to flag_changed / flag_deleted / flags_changed events.
Idempotent — safe to call multiple times. Called automatically on first flag.get evaluation if not invoked manually.
If the flags-service is unhealthy (e.g. a coordinated rebuild where the app pod starts before the schema is loaded), the flush or refresh will fail. Pending declarations stay queued, the client remains disconnected, and the next call retries after an exponentially backed-off delay (capped at MAX_START_RETRY_DELAY seconds). Evaluations during that window fall back to handle defaults.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/smplkit/flags/client.rb', line 138 def start return if @connected return if Process.clock_gettime(Process::CLOCK_MONOTONIC) < @next_start_attempt_at @environment = @parent._environment begin @manage.flags.flush refresh rescue StandardError => e schedule_start_retry(e) return end @connected = true @start_retry_delay = INITIAL_START_RETRY_DELAY @next_start_attempt_at = 0.0 @ws_manager = @parent._ensure_ws return if @ws_subscribed @ws_manager.on("flag_changed") { |data| handle_flag_changed(data) } @ws_manager.on("flag_deleted") { |data| handle_flag_deleted(data) } @ws_manager.on("flags_changed") { |data| handle_flags_changed(data) } @ws_subscribed = true end |
#stats ⇒ Object
171 172 173 |
# File 'lib/smplkit/flags/client.rb', line 171 def stats FlagStats.new(cache_hits: @cache.cache_hits, cache_misses: @cache.cache_misses) end |
#string_flag(id, default:) ⇒ Object
111 112 113 |
# File 'lib/smplkit/flags/client.rb', line 111 def string_flag(id, default:) register_handle(StringFlag, id, "STRING", default) end |