Class: Smplkit::Client
- Inherits:
-
Object
- Object
- Smplkit::Client
- Defined in:
- lib/smplkit/client.rb
Overview
Synchronous entry point for the smplkit SDK.
client = Smplkit::Client.new(environment: "production", service: "my-svc")
checkout_v2 = client.flags.boolean_flag("checkout-v2", default: false)
if checkout_v2.get
# ...
end
client.close
Block form:
Smplkit::Client.open(environment: "production", service: "my-svc") do |client|
# ...
end
All parameters are optional. When omitted, the SDK resolves them from environment variables (SMPLKIT_*) or the ~/.smplkit configuration file. See ADR-021 for the full resolution algorithm.
Smplkit::Client is thread-safe by construction. Background work runs on internal SDK-owned threads; public methods block the calling thread and return values directly.
Constant Summary collapse
- PERIODIC_FLUSH_INTERVAL =
Periodic flush of all sub-client registration buffers (contexts, flags, loggers). Threshold flushes still fire immediately when buffers fill up; this timer is the liveness guarantee for the tail.
60.0
Instance Attribute Summary collapse
-
#account ⇒ Object
readonly
Returns the value of attribute account.
-
#audit ⇒ Object
readonly
Returns the value of attribute audit.
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#flags ⇒ Object
readonly
Returns the value of attribute flags.
-
#jobs ⇒ Object
readonly
Returns the value of attribute jobs.
-
#logging ⇒ Object
readonly
Returns the value of attribute logging.
-
#platform ⇒ Object
readonly
Returns the value of attribute platform.
Class Method Summary collapse
-
.open(**kwargs) ⇒ Object
Construct, yield to the block, and close on exit.
Instance Method Summary collapse
- #_api_key ⇒ Object
- #_app_base_url ⇒ Object
-
#_ensure_started ⇒ Object
Start the deferred background machinery exactly once.
- #_ensure_ws ⇒ Object
- #_environment ⇒ Object
- #_extra_headers ⇒ Object
- #_metrics ⇒ Object
-
#_service ⇒ Object
Internal accessors used by sub-clients ——————————–.
-
#close ⇒ Object
Release all resources held by this client.
-
#initialize(api_key: nil, environment: nil, service: nil, profile: nil, base_domain: nil, scheme: nil, debug: nil, telemetry: nil, extra_headers: nil) ⇒ Client
constructor
A new instance of Client.
-
#set_context(contexts, &block) ⇒ Object
Stash
contextsas the current request’s evaluation context. -
#wait_until_ready(timeout: 10.0) ⇒ Object
Optionally pre-warm the SDK and block until the live socket is up.
Constructor Details
#initialize(api_key: nil, environment: nil, service: nil, profile: nil, base_domain: nil, scheme: nil, debug: nil, telemetry: nil, extra_headers: nil) ⇒ Client
Returns a new instance of Client.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/smplkit/client.rb', line 46 def initialize(api_key: nil, environment: nil, service: nil, profile: nil, base_domain: nil, scheme: nil, debug: nil, telemetry: nil, extra_headers: nil) cfg = ConfigResolution.resolve_config( profile: profile, api_key: api_key, base_domain: base_domain, scheme: scheme, environment: environment, service: service, debug: debug, telemetry: telemetry ) Smplkit.enable_debug if cfg.debug @api_key = cfg.api_key @environment = cfg.environment @service = cfg.service @base_domain = cfg.base_domain @scheme = cfg.scheme @extra_headers = extra_headers masked_key = cfg.api_key.length > 10 ? "#{cfg.api_key[0, 10]}..." : cfg.api_key Smplkit.debug( "lifecycle", "Client init: api_key=#{masked_key} env=#{cfg.environment.inspect} service=#{cfg.service.inspect} " \ "base_domain=#{cfg.base_domain.inspect} scheme=#{cfg.scheme.inspect} " \ "debug=#{cfg.debug} telemetry=#{cfg.telemetry}" ) # Build the per-service HTTP transports + the context-registration buffer. # Side-effect-free: each transport connects lazily on first call. # client.platform owns the buffer; client.config/flags/logging/jobs borrow # their transports from here. @transports = Transport.build_service_transports(Transport.to_transport_config(cfg, extra_headers)) app_url = @transports.app_url audit_url = ConfigResolution.service_url(cfg.scheme, "audit", cfg.base_domain) # Alias the shared HTTP transports — single connection pool per service. @http_client = @transports.config_http @app_http = @transports.app_http @app_base_url = app_url # Metrics reporter @metrics = if cfg.telemetry MetricsReporter.new(http_client: @app_http, environment: cfg.environment, service: cfg.service) end @ws_manager = nil # Platform's cross-cutting CRUD on one client; wired into this parent so # it borrows the shared app transport, and owns the context-registration # buffer. Built BEFORE flags so the contexts seam below is available. @platform = Platform::PlatformClient.new(app_transport: @app_http) # Account-level settings on one client; built from the app url + api key # (the settings sub-client uses Faraday directly). @account = Account::AccountClient.new(api_key: cfg.api_key, base_url: app_url, extra_headers: extra_headers) # Config's full surface on one client; wired into this parent so it # borrows the shared config transport and WebSocket. @config = Config::ConfigClient.new(parent: self, transport: @transports.config_http, metrics: @metrics) # Flags' full surface on one client; wired into this parent so it borrows # the shared flags transport and WebSocket. ``contexts`` is the injection # seam for evaluation-context registration, wired to # ``client.platform.contexts``. @flags = Flags::FlagsClient.new( parent: self, transport: @transports.flags_http, contexts: @platform.contexts, metrics: @metrics ) # Logging's full surface on one client; wired into this parent so it # borrows the shared logging transport and WebSocket. The two management # sub-clients live at client.logging.loggers / client.logging.log_groups. @logging = Logging::LoggingClient.new(parent: self, transport: @transports.logging_http, metrics: @metrics) # Audit's full surface on one client; this runtime instance carries the # configured environment as ``X-Smplkit-Environment`` and owns its own # transport (closed in ``close``). @audit = Audit::AuditClient.new( api_key: cfg.api_key, base_url: audit_url, environment: cfg.environment, extra_headers: extra_headers ) # Jobs has no runtime/management split — reuse the shared jobs transport # (single connection pool) so ``client.jobs`` is one-stop. @jobs = Jobs::JobsClient.new(auth_client: @transports.jobs_http) # Construction is side-effect-free: no background threads, no phone-home. # The periodic registration-buffer flush and the service-context # registration are deferred until the first config/flags/logging operation # or set_context via ``_ensure_started`` — so an audit-only or jobs-only # customer pays zero threads and zero network at construction. @closed = false @started = false @start_lock = Mutex.new @flush_timer = nil @init_thread = nil end |
Instance Attribute Details
#account ⇒ Object (readonly)
Returns the value of attribute account.
34 35 36 |
# File 'lib/smplkit/client.rb', line 34 def account @account end |
#audit ⇒ Object (readonly)
Returns the value of attribute audit.
34 35 36 |
# File 'lib/smplkit/client.rb', line 34 def audit @audit end |
#config ⇒ Object (readonly)
Returns the value of attribute config.
34 35 36 |
# File 'lib/smplkit/client.rb', line 34 def config @config end |
#flags ⇒ Object (readonly)
Returns the value of attribute flags.
34 35 36 |
# File 'lib/smplkit/client.rb', line 34 def flags @flags end |
#jobs ⇒ Object (readonly)
Returns the value of attribute jobs.
34 35 36 |
# File 'lib/smplkit/client.rb', line 34 def jobs @jobs end |
#logging ⇒ Object (readonly)
Returns the value of attribute logging.
34 35 36 |
# File 'lib/smplkit/client.rb', line 34 def logging @logging end |
#platform ⇒ Object (readonly)
Returns the value of attribute platform.
34 35 36 |
# File 'lib/smplkit/client.rb', line 34 def platform @platform end |
Class Method Details
.open(**kwargs) ⇒ Object
Construct, yield to the block, and close on exit.
37 38 39 40 41 42 43 44 |
# File 'lib/smplkit/client.rb', line 37 def self.open(**kwargs) client = new(**kwargs) begin yield client ensure client.close end end |
Instance Method Details
#_api_key ⇒ Object
216 |
# File 'lib/smplkit/client.rb', line 216 def _api_key = @api_key |
#_app_base_url ⇒ Object
217 |
# File 'lib/smplkit/client.rb', line 217 def _app_base_url = @app_base_url |
#_ensure_started ⇒ Object
Start the deferred background machinery exactly once.
Idempotent and thread-safe (lock + flag); a no-op after close. Triggered by the first config/flags/logging operation, set_context, wait_until_ready, or WebSocket open — never at construction.
226 227 228 229 230 231 232 233 234 |
# File 'lib/smplkit/client.rb', line 226 def _ensure_started @start_lock.synchronize do return if @started || @closed @started = true end schedule_periodic_flush @init_thread = Thread.new { register_service_context } end |
#_ensure_ws ⇒ Object
236 237 238 239 240 241 242 243 |
# File 'lib/smplkit/client.rb', line 236 def _ensure_ws _ensure_started if @ws_manager.nil? @ws_manager = SharedWebSocket.new(app_base_url: @app_base_url, api_key: @api_key, metrics: @metrics) @ws_manager.start end @ws_manager end |
#_environment ⇒ Object
215 |
# File 'lib/smplkit/client.rb', line 215 def _environment = @environment |
#_extra_headers ⇒ Object
219 |
# File 'lib/smplkit/client.rb', line 219 def _extra_headers = @extra_headers |
#_metrics ⇒ Object
218 |
# File 'lib/smplkit/client.rb', line 218 def _metrics = @metrics |
#_service ⇒ Object
Internal accessors used by sub-clients ——————————–
214 |
# File 'lib/smplkit/client.rb', line 214 def _service = @service |
#close ⇒ Object
Release all resources held by this client.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/smplkit/client.rb', line 194 def close Smplkit.debug("lifecycle", "Client.close called") @closed = true @flush_timer&.shutdown @flush_timer = nil final_flush @metrics&.close @logging._close @flags._close @audit._close @ws_manager&.stop @ws_manager = nil # Close the shared per-service HTTP transports (app/config/flags/logging/ # jobs). client.platform/account borrow the app transport and close # nothing; client.audit owns and closed its own transport above. @transports.close end |
#set_context(contexts, &block) ⇒ Object
Stash contexts as the current request’s evaluation context.
Typical use is from middleware — set the context once at request entry and every flag.get (and other context-sensitive evaluations) inside that request automatically picks it up.
Each unique (type, key) is also queued for bulk registration on the management API (deduplicated; flushed in the background).
Two usage shapes:
# Fire-and-forget (typical middleware)
client.set_context([Smplkit::Context.new("user", "u-123")])
# Scoped block (e.g. impersonation or one-off override)
client.set_context([Smplkit::Context.new("user", "impersonated")]) do
# ...
end
# original context restored here
181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/smplkit/client.rb', line 181 def set_context(contexts, &block) _ensure_started @platform.contexts.register(contexts) if contexts && !contexts.empty? scope = Smplkit.set_request_context(contexts || []) if block scope.call(&block) else scope end end |
#wait_until_ready(timeout: 10.0) ⇒ Object
Optionally pre-warm the SDK and block until the live socket is up.
Eagerly connects config and flags — flushing discovery, pre-fetching all flags and configs into the local cache, opening the live-updates WebSocket — and waits for the handshake to complete. After this returns, flag.get / client.config.subscribe hit cache (no first-request connect tax) and any on_change listeners receive every server event from this point forward.
Optional: config and flags connect lazily on first live use, so this is purely a pre-warm / WebSocket-ready barrier. Logging integration is not connected here — call client.logging.install separately if you want it (it installs adapters and hooks into your application’s logger, which should be opt-in).
147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/smplkit/client.rb', line 147 def wait_until_ready(timeout: 10.0) @flags._ensure_connected @config._ensure_connected ws = _ensure_ws deadline = monotonic_now + timeout while ws.connection_status != "connected" if monotonic_now >= deadline raise TimeoutError, "Live-updates websocket did not connect within #{timeout}s " \ "(status: #{ws.connection_status.inspect})" end sleep(0.05) end end |