Class: Smplkit::Flags::FlagsClient

Inherits:
Object
  • Object
show all
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().

Instance Method Summary collapse

Constructor Details

#initialize(parent, manage:, metrics:, flags_base_url:, app_base_url:) ⇒ FlagsClient

Returns a new instance of FlagsClient.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/smplkit/flags/client.rb', line 82

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
  @cache = ResolutionCache.new
  @handles = {}
  @global_listeners = []
  @key_listeners = Hash.new { |h, k| h[k] = [] }
  @ws_manager = nil
  @lock = Mutex.new
end

Instance Method Details

#_closeObject



164
165
166
# File 'lib/smplkit/flags/client.rb', line 164

def _close
  # No durable resources here — kept for symmetry with Python SDK.
end

#_evaluate_handle(flag_id, default, context) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/smplkit/flags/client.rb', line 168

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



101
102
103
# File 'lib/smplkit/flags/client.rb', line 101

def boolean_flag(id, default:)
  register_handle(BooleanFlag, id, "BOOLEAN", default)
end

#json_flag(id, default:) ⇒ Object



113
114
115
# File 'lib/smplkit/flags/client.rb', line 113

def json_flag(id, default:)
  register_handle(JsonFlag, id, "JSON", default)
end

#number_flag(id, default:) ⇒ Object



109
110
111
# File 'lib/smplkit/flags/client.rb', line 109

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

Raises:

  • (ArgumentError)


153
154
155
156
157
158
159
160
161
162
# File 'lib/smplkit/flags/client.rb', line 153

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

#refreshObject



139
140
141
142
143
# File 'lib/smplkit/flags/client.rb', line 139

def refresh
  fetch_all_flags
  @cache.clear
  fire_change_listeners_all("manual")
end

#startObject

Eagerly initialize the flags subclient.

Drains 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.



125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/smplkit/flags/client.rb', line 125

def start
  return if @connected

  @environment = @parent._environment
  flush_flags_safely
  refresh
  @connected = true

  @ws_manager = @parent._ensure_ws
  @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) }
end

#statsObject



145
146
147
# File 'lib/smplkit/flags/client.rb', line 145

def stats
  FlagStats.new(cache_hits: @cache.cache_hits, cache_misses: @cache.cache_misses)
end

#string_flag(id, default:) ⇒ Object



105
106
107
# File 'lib/smplkit/flags/client.rb', line 105

def string_flag(id, default:)
  register_handle(StringFlag, id, "STRING", default)
end