quonfig 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +24 -20
- data/lib/quonfig/client.rb +96 -23
- data/lib/quonfig/config_loader.rb +1 -1
- data/lib/quonfig/options.rb +70 -7
- data/lib/quonfig/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a08513df922f41ef1dc16b37bb31b226e71aadaa38f324bdc223a90c4371b1a6
|
|
4
|
+
data.tar.gz: ae7f4292d6dba19047434eb39047f004269a50a59e39926ea02363a51b0a99f5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf23519f237e32d9c56453dcb4614a9540ce51094bb7e873c1787abf6ecf672fd0f78e9228fbc477d7f4732cff6d026f437ff4c91d08a47ebca6773b96d004ab
|
|
7
|
+
data.tar.gz: cd804bde09b2f3795a6bc42f2d73483153ec8a85b7e833bdd973080edd8b02d31ba5977d1fc954441e41ac7e860b28925ae24a5a40c509c355bcd999be56b71e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.20 - 2026-05-29
|
|
4
|
+
|
|
5
|
+
- **Fix (delivery): derive evaluator env from `meta.environment` in HTTP+SSE delivery mode (qfg-xpln.2).** Per-environment overrides were not being applied when the SDK ran in delivery mode (HTTP fetch + SSE stream). The evaluator now derives its `env_id` from the authoritative `meta.environment` carried on every delivery envelope, so environment-scoped rules and overrides resolve correctly against the environment api-delivery actually served — matching datadir-mode behavior.
|
|
6
|
+
- **Fix (options): an explicit environment pin is datadir-only; ignored in delivery mode with a WARN (qfg-pinh).** In delivery mode the served envelope's `meta.environment` is authoritative — the environment is decided server-side by api-delivery, not by the client. A client-supplied environment pin (`with_environment` / `QUONFIG_ENVIRONMENT`) is therefore only meaningful in datadir mode. In delivery mode the pin is now ignored and a one-time WARN is logged so the conflict is visible rather than silently misleading.
|
|
7
|
+
|
|
8
|
+
## 0.0.19 - 2026-05-28
|
|
9
|
+
|
|
10
|
+
- **Deprecation (context): `in_context` is now a deprecated alias of `with_context` (qfg-e0kk).** The two methods have always been runtime-identical — both accept a properties hash and either yield a `Quonfig::BoundClient` to a given block or return the BoundClient directly. As part of the sdk-1.0 unification, every SDK in the family is converging on the `with_context` family; sdk-ruby keeps `in_context` as a YARD-`@deprecated` alias for the 1.0.0 cycle so existing customer code (especially Prefab-fork lineage call sites) keeps working without a runtime warning. Implementation collapses to a one-line forward (`in_context` now calls `with_context`), and the README example is updated to use `with_context`. Slated for removal in 2.0.0.
|
|
11
|
+
- **Feat (options): rename `initialization_timeout_sec` → `init_timeout_ms` (qfg-39za).** Of the six SDKs, only sdk-ruby (and sdk-python, also being renamed) expressed this in seconds; sdk-node uses `initTimeout` (ms), sdk-javascript uses `timeout` (ms), sdk-java uses `Duration`. Picking `init_timeout_ms` as the canonical snake_case name lets sdk-ruby and sdk-python converge. The default is unchanged in wall-clock terms: `init_timeout_ms: 10_000` (previously expressed as `initialization_timeout_sec: 10`). The legacy `initialization_timeout_sec` kwarg and accessor are kept as deprecated aliases for one minor cycle; passing `initialization_timeout_sec:` (seconds) is forwarded transparently as `* 1000` into the ms-based storage, and reading `Options#initialization_timeout_sec` returns the configured timeout in seconds. Will be removed in a future minor release.
|
|
12
|
+
- **Feat (options): rename `enable_polling` → `fallback_poll_enabled`, `poll_interval` → `fallback_poll_interval_ms` (qfg-thsn).** The old names predated the SSE+fallback architecture and read as "always poll", but the actual behavior since 0.0.4 has been "poll only when SSE is unavailable for >= 2x the interval". The new names match sdk-node / sdk-python / sdk-java. Defaults are unchanged: `fallback_poll_enabled: true`, `fallback_poll_interval_ms: 60_000` (previously expressed as 60 seconds). The legacy `enable_polling` and `poll_interval` kwargs and accessors are kept as deprecated aliases for one minor cycle; passing `poll_interval:` (seconds) is forwarded transparently as `poll_interval * 1000` into the new ms-based storage, and reading `Options#poll_interval` returns the configured interval in seconds. Will be removed in a future minor release.
|
|
13
|
+
|
|
3
14
|
## 0.0.18 - 2026-05-21
|
|
4
15
|
|
|
5
16
|
- **Fix (SSE): give Net `read_timeout` headroom over the watchdog deadline (qfg-6y44).** `stream_once` armed two read deadlines at the identical `sse_read_timeout` value: `Net::HTTP#read_timeout` and the `ReadDeadlineWatchdog`. On the body read both were live, and the watchdog carries up to `POLL_INTERVAL` (0.25 s) of polling latency on top of its deadline — so when Net's (unreliable on the `read_body` path) stdlib timeout did fire, it could beat the watchdog and surface a `Net::ReadTimeout` instead of the `SSEReadDeadlineExceeded` the SDK is instrumented around. A new `READ_TIMEOUT_HEADROOM` (30 s) keeps Net's `read_timeout` as a redundant backstop while guaranteeing the watchdog fires first.
|
data/README.md
CHANGED
|
@@ -50,13 +50,13 @@ attach a context in three ways:
|
|
|
50
50
|
client.get_bool('beta-feature', user: { key: 'user-123', plan: 'pro' })
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
### 2. `
|
|
53
|
+
### 2. `with_context` block
|
|
54
54
|
|
|
55
55
|
Everything evaluated inside the block sees the supplied context. The block's
|
|
56
|
-
return value is returned from `
|
|
56
|
+
return value is returned from `with_context`.
|
|
57
57
|
|
|
58
58
|
```ruby
|
|
59
|
-
result = client.
|
|
59
|
+
result = client.with_context(user: { key: 'user-123', plan: 'pro' }) do |bound|
|
|
60
60
|
{
|
|
61
61
|
hero: bound.get_string('homepage-hero'),
|
|
62
62
|
limit: bound.get_int('rate-limit'),
|
|
@@ -67,8 +67,9 @@ end
|
|
|
67
67
|
|
|
68
68
|
### 3. `with_context` — BoundClient for repeated lookups
|
|
69
69
|
|
|
70
|
-
`with_context` returns an immutable `BoundClient` that
|
|
71
|
-
every call. Useful when you want to pass a
|
|
70
|
+
Called without a block, `with_context` returns an immutable `BoundClient` that
|
|
71
|
+
carries the context on every call. Useful when you want to pass a
|
|
72
|
+
context-bound handle down the stack.
|
|
72
73
|
|
|
73
74
|
```ruby
|
|
74
75
|
bound = client.with_context(user: { key: 'user-123', plan: 'pro' })
|
|
@@ -78,6 +79,9 @@ bound.enabled?('beta-feature')
|
|
|
78
79
|
bound.get_int('rate-limit')
|
|
79
80
|
```
|
|
80
81
|
|
|
82
|
+
> `in_context` is a deprecated alias of `with_context` kept for backward
|
|
83
|
+
> compatibility through 1.0.0. New code should use `with_context`.
|
|
84
|
+
|
|
81
85
|
## Datadir / offline mode
|
|
82
86
|
|
|
83
87
|
For tests, CI, or air-gapped environments, point the client at a local workspace
|
|
@@ -221,17 +225,17 @@ for the cross-SDK story (sdk-node, sdk-go, sdk-ruby, sdk-python, sdk-java).
|
|
|
221
225
|
|
|
222
226
|
```ruby
|
|
223
227
|
Quonfig::Client.new(
|
|
224
|
-
sdk_key:
|
|
225
|
-
api_urls:
|
|
226
|
-
telemetry_url:
|
|
227
|
-
enable_sse:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
on_no_default:
|
|
232
|
-
global_context:
|
|
233
|
-
datadir:
|
|
234
|
-
environment:
|
|
228
|
+
sdk_key: '...', # required unless QUONFIG_BACKEND_SDK_KEY is set
|
|
229
|
+
api_urls: ['https://primary.quonfig.com', 'https://secondary.quonfig.com'],
|
|
230
|
+
telemetry_url: 'https://telemetry.quonfig.com',
|
|
231
|
+
enable_sse: true,
|
|
232
|
+
fallback_poll_enabled: true,
|
|
233
|
+
fallback_poll_interval_ms: 60_000,
|
|
234
|
+
init_timeout_ms: 10_000,
|
|
235
|
+
on_no_default: :error,
|
|
236
|
+
global_context: {},
|
|
237
|
+
datadir: '/path/to/workspace',
|
|
238
|
+
environment: 'production',
|
|
235
239
|
data_dir_auto_reload: false,
|
|
236
240
|
data_dir_auto_reload_debounce_ms: 200
|
|
237
241
|
)
|
|
@@ -242,10 +246,10 @@ Quonfig::Client.new(
|
|
|
242
246
|
| `sdk_key` | `String` | `ENV['QUONFIG_BACKEND_SDK_KEY']` | SDK key for API authentication. |
|
|
243
247
|
| `api_urls` | `Array<String>` | `["https://primary.${QUONFIG_DOMAIN}", "https://secondary.${QUONFIG_DOMAIN}"]` | Ordered list of API base URLs to try. SSE stream URLs are derived by prepending `stream.` to each hostname. Defaults derive from `QUONFIG_DOMAIN` (default `quonfig.com`). |
|
|
244
248
|
| `telemetry_url` | `String` | `https://telemetry.${QUONFIG_DOMAIN}` | Base URL for the telemetry service. Default derives from `QUONFIG_DOMAIN`. |
|
|
245
|
-
| `enable_sse`
|
|
246
|
-
| `
|
|
247
|
-
| `
|
|
248
|
-
| `
|
|
249
|
+
| `enable_sse` | `Boolean` | `true` | Receive real-time updates over Server-Sent Events. |
|
|
250
|
+
| `fallback_poll_enabled` | `Boolean` | `true` | Engage HTTP polling as a fallback when SSE is unavailable for >= 2x `fallback_poll_interval_ms`. Deprecated alias: `enable_polling`. |
|
|
251
|
+
| `fallback_poll_interval_ms` | `Integer` (ms) | `60_000` | Interval between fallback HTTP polls, in milliseconds. Deprecated alias: `poll_interval` (seconds, multiplied by 1000 internally). |
|
|
252
|
+
| `init_timeout_ms` | `Integer` (ms) | `10_000` | Maximum time to wait for the initial config load, in milliseconds. Deprecated alias: `initialization_timeout_sec` (seconds, multiplied by 1000 internally). |
|
|
249
253
|
| `on_no_default` | `Symbol` | `:error` | Behavior when a key has no value and no default: `:error`, `:warn`, or `:ignore`. |
|
|
250
254
|
| `global_context` | `Hash` | `{}` | Context applied to every evaluation. |
|
|
251
255
|
| `datadir` | `String` | `ENV['QUONFIG_DIR']` | Path to a local workspace. When set, the SDK runs offline from disk. |
|
data/lib/quonfig/client.rb
CHANGED
|
@@ -185,17 +185,27 @@ module Quonfig
|
|
|
185
185
|
|
|
186
186
|
# ---- Context binding ----------------------------------------------
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
# Bind +properties+ as a context. With a block, yields a
|
|
189
|
+
# {Quonfig::BoundClient} and returns the block's value. Without a block,
|
|
190
|
+
# returns the BoundClient directly.
|
|
191
|
+
#
|
|
192
|
+
# qfg-e0kk: kept as a deprecated alias of {#with_context}. The two methods
|
|
193
|
+
# have always been runtime-identical; sdk-1.0 unifies on +with_context+
|
|
194
|
+
# across all SDKs. No runtime warning is emitted (Prefab-fork lineage,
|
|
195
|
+
# heavy customer usage). Slated for removal in 2.0.0.
|
|
196
|
+
#
|
|
197
|
+
# @deprecated Use {#with_context} instead.
|
|
198
|
+
def in_context(properties, &block)
|
|
199
|
+
with_context(properties, &block)
|
|
191
200
|
end
|
|
192
201
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
# Bind +properties+ as a context. With a block, yields a
|
|
203
|
+
# {Quonfig::BoundClient} and returns the block's value. Without a block,
|
|
204
|
+
# returns the BoundClient directly — useful for passing a context-bound
|
|
205
|
+
# handle down the stack.
|
|
206
|
+
def with_context(properties)
|
|
207
|
+
bound = Quonfig::BoundClient.new(self, properties)
|
|
208
|
+
block_given? ? yield(bound) : bound
|
|
199
209
|
end
|
|
200
210
|
|
|
201
211
|
# ---- Filters & helpers --------------------------------------------
|
|
@@ -334,7 +344,7 @@ module Quonfig
|
|
|
334
344
|
end
|
|
335
345
|
|
|
336
346
|
sse_started = @options.enable_sse && start_sse
|
|
337
|
-
start_polling if @options.
|
|
347
|
+
start_polling if @options.fallback_poll_enabled && !sse_started
|
|
338
348
|
|
|
339
349
|
restart_telemetry_in_child
|
|
340
350
|
end
|
|
@@ -515,7 +525,7 @@ module Quonfig
|
|
|
515
525
|
[@sse_ever_connected, @sse_terminal_failure]
|
|
516
526
|
end
|
|
517
527
|
|
|
518
|
-
return unless @options.respond_to?(:
|
|
528
|
+
return unless @options.respond_to?(:fallback_poll_enabled) && @options.fallback_poll_enabled
|
|
519
529
|
return if @stopped
|
|
520
530
|
# qfg-i5xv: a terminal SSE classification suppresses polling engage in
|
|
521
531
|
# every branch — the customer's key is bad and HTTP polling will fail
|
|
@@ -575,15 +585,19 @@ module Quonfig
|
|
|
575
585
|
end
|
|
576
586
|
end
|
|
577
587
|
|
|
578
|
-
# Schedule a 2*
|
|
579
|
-
# If SSE recovers before the timer fires,
|
|
580
|
-
# tears it down. Idempotent — does nothing
|
|
581
|
-
# or the supervisor is already alive.
|
|
588
|
+
# Schedule a 2*fallback_poll_interval grace timer after a connected->error
|
|
589
|
+
# edge. If SSE recovers before the timer fires,
|
|
590
|
+
# +cancel_fallback_engage_timer+ tears it down. Idempotent — does nothing
|
|
591
|
+
# if a timer is already pending or the supervisor is already alive.
|
|
582
592
|
def schedule_fallback_engage
|
|
583
|
-
|
|
584
|
-
|
|
593
|
+
poll_ms = if @options.respond_to?(:fallback_poll_interval_ms) && @options.fallback_poll_interval_ms
|
|
594
|
+
@options.fallback_poll_interval_ms
|
|
595
|
+
else
|
|
596
|
+
60_000
|
|
597
|
+
end
|
|
598
|
+
return if poll_ms <= 0
|
|
585
599
|
|
|
586
|
-
grace_seconds =
|
|
600
|
+
grace_seconds = (poll_ms / 1000.0) * 2.0
|
|
587
601
|
|
|
588
602
|
@state_mutex.synchronize do
|
|
589
603
|
return if @fallback_engage_timer&.alive?
|
|
@@ -700,6 +714,14 @@ module Quonfig
|
|
|
700
714
|
old_keys = @store.keys.to_set
|
|
701
715
|
(old_keys - new_keys).each { |k| @store.delete(k) }
|
|
702
716
|
envelope.configs.each { |cfg| @store.set(cfg['key'], cfg) }
|
|
717
|
+
# qfg-pinh: evaluate against the installed envelope's meta.environment,
|
|
718
|
+
# matching sdk-go. In datadir mode the loader stamps meta.environment =
|
|
719
|
+
# the resolved env (the `environment:` pin or QUONFIG_ENVIRONMENT), so
|
|
720
|
+
# this also covers the env-var-only case where @options.environment is
|
|
721
|
+
# nil at evaluator construction.
|
|
722
|
+
meta = envelope.respond_to?(:meta) ? envelope.meta : nil
|
|
723
|
+
env_id = meta && (meta['environment'] || meta[:environment])
|
|
724
|
+
@evaluator.env_id = env_id if env_id && !env_id.to_s.empty?
|
|
703
725
|
record_refresh!
|
|
704
726
|
end
|
|
705
727
|
|
|
@@ -743,10 +765,12 @@ module Quonfig
|
|
|
743
765
|
end
|
|
744
766
|
|
|
745
767
|
# Initialize network mode: sync HTTP fetch (bounded by
|
|
746
|
-
#
|
|
768
|
+
# init_timeout_ms) then start SSE + polling as requested.
|
|
747
769
|
def initialize_network_mode
|
|
748
770
|
raise Quonfig::Errors::InvalidSdkKeyError, @options.sdk_key if @options.sdk_key.nil? || @options.sdk_key.to_s.strip.empty?
|
|
749
771
|
|
|
772
|
+
warn_if_pin_ignored_in_delivery_mode
|
|
773
|
+
|
|
750
774
|
@config_loader = Quonfig::ConfigLoader.new(@store, @options)
|
|
751
775
|
|
|
752
776
|
perform_initial_fetch
|
|
@@ -760,7 +784,7 @@ module Quonfig
|
|
|
760
784
|
end
|
|
761
785
|
|
|
762
786
|
def perform_initial_fetch
|
|
763
|
-
timeout = @options.
|
|
787
|
+
timeout = (@options.init_timeout_ms || 10_000) / 1000.0
|
|
764
788
|
result = :failed
|
|
765
789
|
|
|
766
790
|
begin
|
|
@@ -777,10 +801,52 @@ module Quonfig
|
|
|
777
801
|
if result == :failed
|
|
778
802
|
handle_init_failure(RuntimeError.new('Config fetch failed against all api_urls'))
|
|
779
803
|
else
|
|
804
|
+
sync_evaluator_env_id!
|
|
780
805
|
record_refresh!
|
|
781
806
|
end
|
|
782
807
|
end
|
|
783
808
|
|
|
809
|
+
# qfg-pinh: In SDK-key DELIVERY mode the server's `meta.environment` is
|
|
810
|
+
# AUTHORITATIVE. The server scopes each config to a single environment and
|
|
811
|
+
# reports the active env id in `meta.environment` (the loader captures it
|
|
812
|
+
# as @config_loader.environment_id). The evaluator must always evaluate
|
|
813
|
+
# against that installed env id — matching sdk-go, where eval never
|
|
814
|
+
# branches on the pin (c.envID = envelope.Meta.Environment, quonfig.go:850).
|
|
815
|
+
#
|
|
816
|
+
# An explicit environment pin (`environment:` option / QUONFIG_ENVIRONMENT)
|
|
817
|
+
# is DATADIR-ONLY: in delivery mode it is IGNORED (it only feeds the datadir
|
|
818
|
+
# loader, which stamps meta.environment = pin). So we always adopt the
|
|
819
|
+
# server's env id here regardless of the pin. A WARN is emitted once at init
|
|
820
|
+
# (see #warn_if_pin_ignored_in_delivery_mode) when a pin is set in delivery
|
|
821
|
+
# mode so customers aren't surprised that it has no effect.
|
|
822
|
+
#
|
|
823
|
+
# qfg-xpln.2 originally only adopted the server env when NO pin was set,
|
|
824
|
+
# which let the pin win in delivery mode — qfg-pinh reverses that.
|
|
825
|
+
def sync_evaluator_env_id!
|
|
826
|
+
return unless @config_loader.respond_to?(:environment_id)
|
|
827
|
+
|
|
828
|
+
server_env = @config_loader.environment_id
|
|
829
|
+
@evaluator.env_id = server_env if server_env && !server_env.to_s.empty?
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
# qfg-pinh: an explicit environment pin (`environment:` option or
|
|
833
|
+
# QUONFIG_ENVIRONMENT) is DATADIR-ONLY. In delivery (SDK-key) mode the
|
|
834
|
+
# active environment is determined by the SDK key and reported via
|
|
835
|
+
# `meta.environment`, so the pin is ignored. Warn once at init so the
|
|
836
|
+
# customer isn't surprised the setting has no effect. Fired only on the
|
|
837
|
+
# delivery-mode init path (datadir mode honors the pin and never calls
|
|
838
|
+
# this).
|
|
839
|
+
def warn_if_pin_ignored_in_delivery_mode
|
|
840
|
+
env = @options.environment
|
|
841
|
+
return if env.nil? || env.to_s.empty?
|
|
842
|
+
|
|
843
|
+
LOG.warn(
|
|
844
|
+
"[quonfig] environment '#{env}' was set but the client is in delivery " \
|
|
845
|
+
'(SDK-key) mode; the active environment is determined by the SDK key, ' \
|
|
846
|
+
'so this setting is ignored (it applies only when loading from a local data dir)'
|
|
847
|
+
)
|
|
848
|
+
end
|
|
849
|
+
|
|
784
850
|
def handle_init_failure(err)
|
|
785
851
|
if @options.on_init_failure == Quonfig::Options::ON_INITIALIZATION_FAILURE::RETURN
|
|
786
852
|
LOG.warn "[quonfig] Initialization did not complete cleanly; continuing with empty store: #{err.message}"
|
|
@@ -807,6 +873,7 @@ module Quonfig
|
|
|
807
873
|
|
|
808
874
|
begin
|
|
809
875
|
@config_loader.apply_envelope(envelope)
|
|
876
|
+
sync_evaluator_env_id!
|
|
810
877
|
handle_sse_state_change(:connected)
|
|
811
878
|
record_refresh!
|
|
812
879
|
rescue StandardError => e
|
|
@@ -827,18 +894,24 @@ module Quonfig
|
|
|
827
894
|
return if @stopped
|
|
828
895
|
return if @poll_supervisor&.alive?
|
|
829
896
|
|
|
830
|
-
|
|
831
|
-
|
|
897
|
+
poll_ms = if @options.respond_to?(:fallback_poll_interval_ms) && @options.fallback_poll_interval_ms
|
|
898
|
+
@options.fallback_poll_interval_ms
|
|
899
|
+
else
|
|
900
|
+
60_000
|
|
901
|
+
end
|
|
902
|
+
return if poll_ms <= 0
|
|
832
903
|
|
|
904
|
+
poll_seconds = poll_ms / 1000.0
|
|
833
905
|
stopped_ref = -> { @stopped }
|
|
834
906
|
worker = lambda do |notify_delivered|
|
|
835
907
|
loop do
|
|
836
908
|
break if stopped_ref.call
|
|
837
909
|
|
|
838
|
-
sleep
|
|
910
|
+
sleep poll_seconds
|
|
839
911
|
break if stopped_ref.call
|
|
840
912
|
|
|
841
913
|
@config_loader.fetch!
|
|
914
|
+
sync_evaluator_env_id!
|
|
842
915
|
record_refresh!
|
|
843
916
|
notify_delivered.call
|
|
844
917
|
notify_on_update_callback
|
|
@@ -12,7 +12,7 @@ module Quonfig
|
|
|
12
12
|
# -> 304 Not Modified (ETag honored via If-None-Match)
|
|
13
13
|
#
|
|
14
14
|
# The fetch is synchronous; Client is responsible for timing out the initial
|
|
15
|
-
# fetch per `
|
|
15
|
+
# fetch per `init_timeout_ms`.
|
|
16
16
|
class ConfigLoader
|
|
17
17
|
LOG = Quonfig::InternalLogger.new(self)
|
|
18
18
|
|
data/lib/quonfig/options.rb
CHANGED
|
@@ -6,10 +6,41 @@ module Quonfig
|
|
|
6
6
|
# Options passed to Quonfig::Client at construction time.
|
|
7
7
|
class Options
|
|
8
8
|
attr_reader :sdk_key, :environment, :api_urls, :sse_api_urls, :telemetry_destination, :config_api_urls,
|
|
9
|
-
:on_no_default, :
|
|
9
|
+
:on_no_default, :init_timeout_ms, :on_init_failure, :collect_sync_interval, :datadir, :enable_sse, :fallback_poll_enabled, :fallback_poll_interval_ms, :global_context, :logger_key, :logger, :enable_quonfig_user_context,
|
|
10
10
|
:data_dir_auto_reload, :data_dir_auto_reload_debounce_ms
|
|
11
11
|
attr_accessor :is_fork
|
|
12
12
|
|
|
13
|
+
# Default fallback poll interval, in milliseconds. The SDK polls api-delivery
|
|
14
|
+
# at this cadence only when SSE is unavailable for >= 2x this value.
|
|
15
|
+
DEFAULT_FALLBACK_POLL_INTERVAL_MS = 60_000
|
|
16
|
+
|
|
17
|
+
# Default initialization timeout, in milliseconds. The SDK waits up to this
|
|
18
|
+
# long for the initial config fetch before failing per :on_init_failure.
|
|
19
|
+
DEFAULT_INIT_TIMEOUT_MS = 10_000
|
|
20
|
+
|
|
21
|
+
# Deprecated alias for #fallback_poll_enabled. Will be removed in a future
|
|
22
|
+
# minor release.
|
|
23
|
+
def enable_polling
|
|
24
|
+
@fallback_poll_enabled
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Deprecated alias for #fallback_poll_interval_ms, in seconds. Reads back the
|
|
28
|
+
# interval in the legacy unit so existing callers (e.g. internal code that
|
|
29
|
+
# `sleep`s on this value) keep working. Will be removed in a future minor
|
|
30
|
+
# release.
|
|
31
|
+
def poll_interval
|
|
32
|
+
@fallback_poll_interval_ms / 1000.0
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Deprecated alias for #init_timeout_ms, in seconds. Reads back the timeout
|
|
36
|
+
# in the legacy unit so existing callers (e.g. internal code that passes
|
|
37
|
+
# this to Timeout.timeout) keep working. Will be removed in a future minor
|
|
38
|
+
# release.
|
|
39
|
+
def initialization_timeout_sec
|
|
40
|
+
ms = @init_timeout_ms.to_f / 1000.0
|
|
41
|
+
ms == ms.to_i ? ms.to_i : ms
|
|
42
|
+
end
|
|
43
|
+
|
|
13
44
|
module ON_INITIALIZATION_FAILURE
|
|
14
45
|
RAISE = :raise
|
|
15
46
|
RETURN = :return
|
|
@@ -145,10 +176,13 @@ module Quonfig
|
|
|
145
176
|
environment: ENV.fetch('QUONFIG_ENVIRONMENT', nil),
|
|
146
177
|
datadir: ENV.fetch('QUONFIG_DIR', nil),
|
|
147
178
|
enable_sse: true,
|
|
148
|
-
|
|
149
|
-
|
|
179
|
+
fallback_poll_enabled: nil,
|
|
180
|
+
fallback_poll_interval_ms: nil,
|
|
181
|
+
enable_polling: nil,
|
|
182
|
+
poll_interval: nil,
|
|
150
183
|
on_no_default: ON_NO_DEFAULT::RAISE,
|
|
151
|
-
|
|
184
|
+
init_timeout_ms: nil,
|
|
185
|
+
initialization_timeout_sec: nil,
|
|
152
186
|
on_init_failure: ON_INITIALIZATION_FAILURE::RAISE,
|
|
153
187
|
collect_max_paths: DEFAULT_MAX_PATHS,
|
|
154
188
|
collect_sync_interval: nil,
|
|
@@ -168,10 +202,39 @@ module Quonfig
|
|
|
168
202
|
@environment = environment
|
|
169
203
|
@datadir = datadir
|
|
170
204
|
@enable_sse = enable_sse
|
|
171
|
-
|
|
172
|
-
|
|
205
|
+
# qfg-thsn: canonical names are fallback_poll_enabled and
|
|
206
|
+
# fallback_poll_interval_ms (matches sdk-node / sdk-python / sdk-java).
|
|
207
|
+
# The legacy enable_polling / poll_interval (seconds) kwargs are kept
|
|
208
|
+
# as deprecated aliases for one minor cycle. The canonical kwarg wins
|
|
209
|
+
# if both are passed; otherwise the legacy value is forwarded (and the
|
|
210
|
+
# seconds-based interval is multiplied *1000 transparently).
|
|
211
|
+
@fallback_poll_enabled = if !fallback_poll_enabled.nil?
|
|
212
|
+
fallback_poll_enabled
|
|
213
|
+
elsif !enable_polling.nil?
|
|
214
|
+
enable_polling
|
|
215
|
+
else
|
|
216
|
+
true
|
|
217
|
+
end
|
|
218
|
+
@fallback_poll_interval_ms = if !fallback_poll_interval_ms.nil?
|
|
219
|
+
fallback_poll_interval_ms
|
|
220
|
+
elsif !poll_interval.nil?
|
|
221
|
+
poll_interval * 1000
|
|
222
|
+
else
|
|
223
|
+
DEFAULT_FALLBACK_POLL_INTERVAL_MS
|
|
224
|
+
end
|
|
173
225
|
@on_no_default = on_no_default
|
|
174
|
-
|
|
226
|
+
# qfg-39za: canonical name is init_timeout_ms. The legacy
|
|
227
|
+
# initialization_timeout_sec (seconds) kwarg is kept as a deprecated
|
|
228
|
+
# alias for one minor cycle. The canonical kwarg wins if both are
|
|
229
|
+
# passed; otherwise the legacy value is forwarded (and the seconds-based
|
|
230
|
+
# timeout is multiplied *1000 transparently).
|
|
231
|
+
@init_timeout_ms = if !init_timeout_ms.nil?
|
|
232
|
+
init_timeout_ms
|
|
233
|
+
elsif !initialization_timeout_sec.nil?
|
|
234
|
+
(initialization_timeout_sec * 1000).to_i
|
|
235
|
+
else
|
|
236
|
+
DEFAULT_INIT_TIMEOUT_MS
|
|
237
|
+
end
|
|
175
238
|
@on_init_failure = on_init_failure
|
|
176
239
|
|
|
177
240
|
@collect_max_paths = collect_max_paths
|
data/lib/quonfig/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quonfig
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeff Dwyer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|