unleash 6.3.1 → 6.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9c5d4638648ffd4a6891f6b6f851437028af770b417d020b6ae852f24d5ead7
4
- data.tar.gz: 9933cfff16b53947a37385f0193dd61b6955d9010865cb9ad02e193d3363afdf
3
+ metadata.gz: 6c28fb398c92bd470282a94e7fe07b910fa4afed65381da67231c60c2b2960c2
4
+ data.tar.gz: 3a5b78a63ccfcec23ed8eba1e2168a5be7f183ca0c3beac0eb6497d1d4d6678c
5
5
  SHA512:
6
- metadata.gz: 4f0ab91b15331353b5d3024f102a9915c68c62471da230057c363dfbd0098b14619ec0df7cc63a5aec906760d25f5dcd70af7f6490c57e7f1ada3a1878eaac9c
7
- data.tar.gz: 3bf0dd46e6f23715e5495fd3fcf09940cd39722d32d938730102a45661813efefc164bb3e84b55410db753e5531b4e57bdea3476ad263500d8a077f832e968ca
6
+ metadata.gz: 3d66480a2211bc60a0ddb2899d189a7966be1659f8618873233c8e419f2261dc5cf13cf79f747d36039af5f9ce1ead45fb4e3bc4140d420d4d59369a9a995bc4
7
+ data.tar.gz: ba06afc53ff6d2806d6bbf18cc93e2287ae327f42cb6699f663a3f2f5bb9abc6e3f71ce02ba7fe18f48bdcb9fb1913c2d9cc4a6be2d05bae6f740def1a4281d5
data/CHANGELOG.md CHANGED
@@ -13,7 +13,20 @@ Note: These changes are not considered notable:
13
13
 
14
14
  ## [Unreleased]
15
15
 
16
- ## [6.3.0] - 2025-05-27
16
+ ## [6.4.1] - 2025-12-04
17
+
18
+ ### Changed
19
+ - Bump yggdrasil-engine
20
+
21
+ ## [6.4.0] - 2025-08-12
22
+ ### Added
23
+ - Experimental streaming support
24
+
25
+ #### Changed
26
+ - SDK registration name
27
+ - base64 dependency version
28
+
29
+ ## [6.3.1] - 2025-05-27
17
30
  ### Fixed
18
31
  - Upgraded Yggdrasil engine to fix a memory leak in metrics.
19
32
 
data/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # Unleash::Client
2
2
 
3
- ![Build Status](https://github.com/Unleash/unleash-client-ruby/actions/workflows/pull_request.yml/badge.svg?branch=main)
4
- [![Coverage Status](https://coveralls.io/repos/github/Unleash/unleash-client-ruby/badge.svg?branch=main)](https://coveralls.io/github/Unleash/unleash-client-ruby?branch=main)
3
+ ![Build Status](https://github.com/Unleash/unleash-ruby-sdk/actions/workflows/pull_request.yml/badge.svg?branch=main)
4
+ [![Coverage Status](https://coveralls.io/repos/github/Unleash/unleash-ruby-sdk/badge.svg?branch=main)](https://coveralls.io/github/Unleash/unleash-ruby-sdk?branch=main)
5
5
  [![Gem Version](https://badge.fury.io/rb/unleash.svg)](https://badge.fury.io/rb/unleash)
6
6
 
7
- Ruby client for the [Unleash](https://github.com/Unleash/unleash) feature management service.
7
+ Unleash is a private, secure, and scalable [feature management platform](https://www.getunleash.io/) built to reduce the risk of releasing new features and accelerate software development. This Ruby SDK is designed to help you integrate with Unleash and evaluate feature flags inside your application.
8
+
9
+ You can use this client with [Unleash Enterprise](https://www.getunleash.io/pricing?utm_source=readme&utm_medium=ruby) or [Unleash Open Source](https://github.com/Unleash/unleash).
8
10
 
9
11
  > **Migrating to v6**
10
12
  >
@@ -26,7 +28,7 @@ Ruby client for the [Unleash](https://github.com/Unleash/unleash) feature manage
26
28
  Add this line to your application's Gemfile:
27
29
 
28
30
  ```ruby
29
- gem 'unleash', '~> 6.3.0'
31
+ gem 'unleash', '~> 6.4.1'
30
32
  ```
31
33
 
32
34
  And then execute:
@@ -563,7 +565,7 @@ To release a new version, follow these steps:
563
565
 
564
566
  ## Contributing
565
567
 
566
- Bug reports and pull requests are welcome on GitHub at https://github.com/unleash/unleash-client-ruby.
568
+ Bug reports and pull requests are welcome on GitHub at https://github.com/unleash/unleash-ruby-sdk.
567
569
 
568
570
  Be sure to run both `bundle exec rspec` and `bundle exec rubocop` in your branch before creating a pull request.
569
571
 
data/bin/unleash-client CHANGED
@@ -77,7 +77,7 @@ log_level = \
77
77
 
78
78
  @unleash = Unleash::Client.new(
79
79
  url: options[:url],
80
- app_name: 'unleash-client-ruby-cli',
80
+ app_name: 'unleash-ruby-sdk-cli',
81
81
  disable_metrics: options[:metrics],
82
82
  custom_http_headers: options[:custom_http_headers],
83
83
  log_level: log_level
data/examples/simple.rb CHANGED
@@ -18,8 +18,8 @@ puts ">> START simple.rb"
18
18
  # or:
19
19
 
20
20
  @unleash = Unleash::Client.new(
21
- url: 'https://unleash.herokuapp.com/api',
22
- custom_http_headers: { 'Authorization': '943ca9171e2c884c545c5d82417a655fb77cec970cc3b78a8ff87f4406b495d0' },
21
+ url: 'https://app.unleash-hosted.com/demo/api',
22
+ custom_http_headers: { 'Authorization': 'demo-app:dev.9fc74dd72d2b88bea5253c04240b21a54841f08d9918046ed55a06b5' },
23
23
  app_name: 'simple-test',
24
24
  instance_id: 'local-test-cli',
25
25
  refresh_interval: 2,
@@ -27,8 +27,7 @@ puts ">> START simple.rb"
27
27
  retry_limit: 2
28
28
  )
29
29
 
30
- # feature_name = "AwesomeFeature"
31
- feature_name = "4343443"
30
+ feature_name = "example-flag"
32
31
  unleash_context = Unleash::Context.new
33
32
  unleash_context.user_id = 123
34
33
 
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'unleash'
4
+ require 'unleash/context'
5
+
6
+ puts ">> START streaming.rb"
7
+
8
+ @unleash = Unleash::Client.new(
9
+ url: 'https://app.unleash-hosted.com/demo/api',
10
+ custom_http_headers: { 'Authorization': 'demo-app:dev.9fc74dd72d2b88bea5253c04240b21a54841f08d9918046ed55a06b5' },
11
+ app_name: 'streaming-test',
12
+ instance_id: 'local-streaming-cli',
13
+ refresh_interval: 2,
14
+ metrics_interval: 2,
15
+ retry_limit: 2,
16
+ experimental_mode: { type: 'streaming' },
17
+ timeout: 5,
18
+ log_level: Logger::DEBUG
19
+ )
20
+
21
+ feature_name = "example-flag"
22
+ unleash_context = Unleash::Context.new
23
+ unleash_context.user_id = 123
24
+
25
+ puts "Waiting for client to initialize..."
26
+ sleep 2
27
+
28
+ 100.times do
29
+ if @unleash.is_enabled?(feature_name, unleash_context)
30
+ puts "> #{feature_name} is enabled"
31
+ else
32
+ puts "> #{feature_name} is not enabled"
33
+ end
34
+ sleep 1
35
+ puts "---"
36
+ puts ""
37
+ puts ""
38
+ end
39
+ feature_name = "foobar"
40
+ if @unleash.is_enabled?(feature_name, unleash_context, true)
41
+ puts "> #{feature_name} is enabled"
42
+ else
43
+ puts "> #{feature_name} is not enabled"
44
+ end
45
+
46
+ puts "> shutting down client..."
47
+
48
+ @unleash.shutdown
49
+
50
+ puts ">> END streaming.rb"
@@ -0,0 +1,24 @@
1
+ require 'unleash/configuration'
2
+
3
+ module Unleash
4
+ class BackupFileReader
5
+ def self.read!
6
+ Unleash.logger.debug "read!()"
7
+
8
+ backup_file = Unleash.configuration.backup_file
9
+ return nil unless File.exist?(backup_file)
10
+
11
+ File.read(backup_file)
12
+ rescue IOError => e
13
+ # :nocov:
14
+ Unleash.logger.error "Unable to read the backup_file: #{e}"
15
+ # :nocov:
16
+ nil
17
+ rescue StandardError => e
18
+ # :nocov:
19
+ Unleash.logger.error "Unable to extract valid data from backup_file. Exception thrown: #{e}"
20
+ # :nocov:
21
+ nil
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ require 'unleash/configuration'
2
+
3
+ module Unleash
4
+ class BackupFileWriter
5
+ def self.save!(toggle_data)
6
+ Unleash.logger.debug "Will save toggles to disk now"
7
+
8
+ backup_file = Unleash.configuration.backup_file
9
+ backup_file_tmp = "#{backup_file}.tmp-#{Process.pid}"
10
+
11
+ File.open(backup_file_tmp, "w") do |file|
12
+ file.write(toggle_data)
13
+ end
14
+ File.rename(backup_file_tmp, backup_file)
15
+ rescue StandardError => e
16
+ # This is not really the end of the world. Swallowing the exception.
17
+ Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
18
+ Unleash.logger.error "stacktrace: #{e.backtrace}"
19
+ end
20
+ end
21
+ end
@@ -2,8 +2,10 @@ require 'unleash/configuration'
2
2
  require 'unleash/toggle_fetcher'
3
3
  require 'unleash/metrics_reporter'
4
4
  require 'unleash/scheduled_executor'
5
+ require 'unleash/streaming_client_executor'
5
6
  require 'unleash/variant'
6
7
  require 'unleash/util/http'
8
+ require 'unleash/util/event_source_wrapper'
7
9
  require 'logger'
8
10
  require 'time'
9
11
 
@@ -21,7 +23,8 @@ module Unleash
21
23
  Unleash.engine = YggdrasilEngine.new
22
24
  Unleash.engine.register_custom_strategies(Unleash.configuration.strategies.custom_strategies)
23
25
 
24
- Unleash.toggle_fetcher = Unleash::ToggleFetcher.new Unleash.engine
26
+ Unleash.toggle_fetcher = Unleash::ToggleFetcher.new Unleash.engine unless Unleash.configuration.streaming_mode?
27
+
25
28
  if Unleash.configuration.disable_client
26
29
  Unleash.logger.warn "Unleash::Client is disabled! Will only return default (or bootstrapped if available) results!"
27
30
  Unleash.logger.warn "Unleash::Client is disabled! Metrics and MetricsReporter are also disabled!"
@@ -30,7 +33,9 @@ module Unleash
30
33
  end
31
34
 
32
35
  register
33
- start_toggle_fetcher
36
+
37
+ initialize_client_mode
38
+
34
39
  start_metrics unless Unleash.configuration.disable_metrics
35
40
  end
36
41
  # rubocop:enable Metrics/AbcSize
@@ -105,7 +110,7 @@ module Unleash
105
110
  # quick shutdown: just kill running threads
106
111
  def shutdown!
107
112
  unless Unleash.configuration.disable_client
108
- self.fetcher_scheduled_executor.exit
113
+ self.fetcher_scheduled_executor&.exit
109
114
  self.metrics_scheduled_executor.exit unless Unleash.configuration.disable_metrics
110
115
  end
111
116
  end
@@ -117,7 +122,7 @@ module Unleash
117
122
  'appName': Unleash.configuration.app_name,
118
123
  'instanceId': Unleash.configuration.instance_id,
119
124
  'connectionId': Unleash.configuration.connection_id,
120
- 'sdkVersion': "unleash-client-ruby:" + Unleash::VERSION,
125
+ 'sdkVersion': "unleash-ruby-sdk:" + Unleash::VERSION,
121
126
  'strategies': Unleash.strategies.known_strategies,
122
127
  'started': Time.now.iso8601(Unleash::TIME_RESOLUTION),
123
128
  'interval': Unleash.configuration.metrics_interval_in_millis,
@@ -140,6 +145,11 @@ module Unleash
140
145
  end
141
146
  end
142
147
 
148
+ def start_streaming_client
149
+ self.fetcher_scheduled_executor = Unleash::StreamingClientExecutor.new('StreamingExecutor', Unleash.engine)
150
+ self.fetcher_scheduled_executor.run
151
+ end
152
+
143
153
  def start_metrics
144
154
  Unleash.reporter = Unleash::MetricsReporter.new
145
155
  self.metrics_scheduled_executor = Unleash::ScheduledExecutor.new(
@@ -172,5 +182,13 @@ module Unleash
172
182
  def first_fetch_is_eager
173
183
  Unleash.configuration.use_bootstrap?
174
184
  end
185
+
186
+ def initialize_client_mode
187
+ if Unleash.configuration.streaming_mode?
188
+ start_streaming_client
189
+ else
190
+ start_toggle_fetcher
191
+ end
192
+ end
175
193
  end
176
194
  end
@@ -22,7 +22,8 @@ module Unleash
22
22
  :log_level,
23
23
  :bootstrap_config,
24
24
  :strategies,
25
- :use_delta_api
25
+ :use_delta_api,
26
+ :experimental_mode
26
27
  attr_reader :connection_id
27
28
 
28
29
  def initialize(opts = {})
@@ -57,7 +58,7 @@ module Unleash
57
58
  'UNLEASH-INSTANCEID' => self.instance_id,
58
59
  'UNLEASH-APPNAME' => self.app_name,
59
60
  'Unleash-Client-Spec' => CLIENT_SPECIFICATION_VERSION,
60
- 'UNLEASH-SDK' => "unleash-client-ruby:#{Unleash::VERSION}"
61
+ 'UNLEASH-SDK' => "unleash-ruby-sdk:#{Unleash::VERSION}"
61
62
  }.merge!(generate_custom_http_headers)
62
63
  headers['UNLEASH-CONNECTION-ID'] = @connection_id
63
64
  headers
@@ -67,7 +68,9 @@ module Unleash
67
68
  uri = nil
68
69
  ## Personal feeling but Rubocop's suggestion here is too dense to be properly readable
69
70
  # rubocop:disable Style/ConditionalAssignment
70
- if self.use_delta_api
71
+ if streaming_mode?
72
+ uri = URI("#{self.url_stripped_of_slash}/client/streaming")
73
+ elsif self.use_delta_api || polling_with_delta?
71
74
  uri = URI("#{self.url_stripped_of_slash}/client/delta")
72
75
  else
73
76
  uri = URI("#{self.url_stripped_of_slash}/client/features")
@@ -93,6 +96,17 @@ module Unleash
93
96
  self.bootstrap_config&.valid?
94
97
  end
95
98
 
99
+ def streaming_mode?
100
+ validate_streaming_support! if streaming_configured?
101
+ streaming_configured?
102
+ end
103
+
104
+ def polling_with_delta?
105
+ self.experimental_mode.is_a?(Hash) &&
106
+ self.experimental_mode[:type] == 'polling' &&
107
+ self.experimental_mode[:format] == 'delta'
108
+ end
109
+
96
110
  private
97
111
 
98
112
  def set_defaults
@@ -112,6 +126,7 @@ module Unleash
112
126
  self.bootstrap_config = nil
113
127
  self.strategies = Unleash::Strategies.new
114
128
  self.use_delta_api = false
129
+ self.experimental_mode = nil
115
130
 
116
131
  self.custom_http_headers = {}
117
132
  @connection_id = SecureRandom.uuid
@@ -149,5 +164,15 @@ module Unleash
149
164
  rescue NoMethodError
150
165
  raise ArgumentError, "unknown configuration parameter '#{val}'"
151
166
  end
167
+
168
+ def streaming_configured?
169
+ self.experimental_mode.is_a?(Hash) && self.experimental_mode[:type] == 'streaming'
170
+ end
171
+
172
+ def validate_streaming_support!
173
+ return unless RUBY_ENGINE == 'jruby'
174
+
175
+ raise "Streaming mode is not supported on JRuby. Please use polling mode instead or switch to MRI/CRuby."
176
+ end
152
177
  end
153
178
  end
@@ -1,3 +1,3 @@
1
1
  module Unleash
2
- CLIENT_SPECIFICATION_VERSION = "5.2.0".freeze
2
+ CLIENT_SPECIFICATION_VERSION = "5.2.2".freeze
3
3
  end
@@ -0,0 +1,114 @@
1
+ require 'unleash/streaming_event_processor'
2
+ require 'unleash/bootstrap/handler'
3
+ require 'unleash/backup_file_reader'
4
+ require 'unleash/util/event_source_wrapper'
5
+
6
+ module Unleash
7
+ class StreamingClientExecutor
8
+ attr_accessor :name, :event_source, :event_processor, :running
9
+
10
+ def initialize(name, engine)
11
+ self.name = name || 'StreamingClientExecutor'
12
+ self.event_source = nil
13
+ self.event_processor = Unleash::StreamingEventProcessor.new(engine)
14
+ self.running = false
15
+
16
+ begin
17
+ # if bootstrap configuration is available, initialize. Otherwise read backup file
18
+ if Unleash.configuration.use_bootstrap?
19
+ bootstrap(engine)
20
+ else
21
+ read_backup_file!(engine)
22
+ end
23
+ rescue StandardError => e
24
+ # fall back to reading the backup file
25
+ Unleash.logger.warn "StreamingClientExecutor was unable to initialize, attempting to read from backup file."
26
+ Unleash.logger.debug "Exception Caught: #{e}"
27
+ read_backup_file!(engine)
28
+ end
29
+ end
30
+
31
+ def run(&_block)
32
+ start
33
+ end
34
+
35
+ def start
36
+ return if self.running || Unleash.configuration.disable_client
37
+
38
+ Unleash.logger.debug "Streaming client #{self.name} starting connection to: #{Unleash.configuration.fetch_toggles_uri}"
39
+
40
+ self.event_source = create_event_source
41
+ setup_event_handlers
42
+
43
+ self.running = true
44
+ Unleash.logger.debug "Streaming client #{self.name} connection established"
45
+ end
46
+
47
+ def stop
48
+ return unless self.running
49
+
50
+ Unleash.logger.debug "Streaming client #{self.name} stopping connection"
51
+ self.running = false
52
+ self.event_source&.close
53
+ self.event_source = nil
54
+ Unleash.logger.debug "Streaming client #{self.name} connection closed"
55
+ end
56
+
57
+ alias exit stop
58
+
59
+ def running?
60
+ self.running
61
+ end
62
+
63
+ private
64
+
65
+ def create_event_source
66
+ sse_client = Unleash::Util::EventSourceWrapper.client
67
+ if sse_client.nil?
68
+ raise "Streaming mode is configured but EventSource client is not available. " \
69
+ "Please install the 'ld-eventsource' gem or switch to polling mode."
70
+ end
71
+
72
+ headers = (Unleash.configuration.http_headers || {}).dup
73
+
74
+ sse_client.new(
75
+ Unleash.configuration.fetch_toggles_uri.to_s,
76
+ headers: headers,
77
+ read_timeout: 60,
78
+ reconnect_time: 2,
79
+ connect_timeout: 10,
80
+ logger: Unleash.logger
81
+ )
82
+ end
83
+
84
+ def setup_event_handlers
85
+ self.event_source.on_event do |event|
86
+ handle_event(event)
87
+ end
88
+
89
+ self.event_source.on_error do |error|
90
+ Unleash.logger.warn "Streaming client #{self.name} error: #{error}"
91
+ end
92
+ end
93
+
94
+ def handle_event(event)
95
+ self.event_processor.process_event(event)
96
+ rescue StandardError => e
97
+ Unleash.logger.error "Streaming client #{self.name} threw exception #{e.class}: '#{e}'"
98
+ Unleash.logger.debug "stacktrace: #{e.backtrace}"
99
+ end
100
+
101
+ def read_backup_file!(engine)
102
+ backup_data = Unleash::BackupFileReader.read!
103
+ engine.take_state(backup_data) if backup_data
104
+ end
105
+
106
+ def bootstrap(engine)
107
+ bootstrap_payload = Unleash::Bootstrap::Handler.new(Unleash.configuration.bootstrap_config).retrieve_toggles
108
+ engine.take_state(bootstrap_payload)
109
+
110
+ # reset Unleash.configuration.bootstrap_data to free up memory, as we will never use it again
111
+ Unleash.configuration.bootstrap_config = nil
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,55 @@
1
+ require 'json'
2
+ require 'unleash/backup_file_writer'
3
+
4
+ module Unleash
5
+ class StreamingEventProcessor
6
+ attr_accessor :toggle_engine, :mutex
7
+
8
+ def initialize(toggle_engine)
9
+ self.toggle_engine = toggle_engine
10
+ self.mutex = Mutex.new
11
+ end
12
+
13
+ def process_event(event)
14
+ case event.type.to_s
15
+ when 'unleash-connected'
16
+ Unleash.logger.debug "Streaming client connected"
17
+ handle_connected_event(event)
18
+ when 'unleash-updated'
19
+ Unleash.logger.debug "Received streaming update"
20
+ handle_updated_event(event)
21
+ else
22
+ Unleash.logger.debug "Received unknown event type: #{event.type}"
23
+ end
24
+ rescue StandardError => e
25
+ Unleash.logger.error "Error handling streaming event threw exception #{e.class}: '#{e}'"
26
+ Unleash.logger.debug "stacktrace: #{e.backtrace}"
27
+ end
28
+
29
+ def handle_delta_event(event_data)
30
+ self.mutex.synchronize do
31
+ self.toggle_engine.take_state(event_data)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def handle_connected_event(event)
38
+ Unleash.logger.debug "Processing initial hydration data"
39
+ handle_updated_event(event)
40
+ end
41
+
42
+ def handle_updated_event(event)
43
+ handle_delta_event(event.data)
44
+
45
+ full_state = @toggle_engine.get_state
46
+ Unleash::BackupFileWriter.save!(full_state)
47
+ rescue JSON::ParserError => e
48
+ Unleash.logger.error "Unable to parse JSON from streaming event data. Exception thrown #{e.class}: '#{e}'"
49
+ Unleash.logger.debug "stacktrace: #{e.backtrace}"
50
+ rescue StandardError => e
51
+ Unleash.logger.error "Error processing delta update threw exception #{e.class}: '#{e}'"
52
+ Unleash.logger.debug "stacktrace: #{e.backtrace}"
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,7 @@
1
1
  require 'unleash/configuration'
2
2
  require 'unleash/bootstrap/handler'
3
+ require 'unleash/backup_file_writer'
4
+ require 'unleash/backup_file_reader'
3
5
  require 'net/http'
4
6
  require 'json'
5
7
  require 'yggdrasil_engine'
@@ -23,10 +25,10 @@ module Unleash
23
25
  fetch
24
26
  end
25
27
  rescue StandardError => e
26
- # fail back to reading the backup file
28
+ # fall back to reading the backup file
27
29
  Unleash.logger.warn "ToggleFetcher was unable to fetch from the network, attempting to read from backup file."
28
30
  Unleash.logger.debug "Exception Caught: #{e}"
29
- read!
31
+ read_backup_file!
30
32
  end
31
33
 
32
34
  # once initialized, somewhere else you will want to start a loop with fetch()
@@ -54,25 +56,7 @@ module Unleash
54
56
  # always synchronize with the local cache when fetching:
55
57
  update_engine_state!(response.body)
56
58
 
57
- save! response.body
58
- end
59
-
60
- def save!(toggle_data)
61
- Unleash.logger.debug "Will save toggles to disk now"
62
-
63
- backup_file = Unleash.configuration.backup_file
64
- backup_file_tmp = "#{backup_file}.tmp"
65
-
66
- self.toggle_lock.synchronize do
67
- File.open(backup_file_tmp, "w") do |file|
68
- file.write(toggle_data)
69
- end
70
- File.rename(backup_file_tmp, backup_file)
71
- end
72
- rescue StandardError => e
73
- # This is not really the end of the world. Swallowing the exception.
74
- Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
75
- Unleash.logger.error "stacktrace: #{e.backtrace}"
59
+ Unleash::BackupFileWriter.save! response.body
76
60
  end
77
61
 
78
62
  private
@@ -88,25 +72,9 @@ module Unleash
88
72
  Unleash.logger.error "Failed to hydrate state: #{e.backtrace}"
89
73
  end
90
74
 
91
- def read!
92
- Unleash.logger.debug "read!()"
93
- backup_file = Unleash.configuration.backup_file
94
- return nil unless File.exist?(backup_file)
95
-
96
- backup_data = File.read(backup_file)
97
- update_engine_state!(backup_data)
98
- rescue IOError => e
99
- # :nocov:
100
- Unleash.logger.error "Unable to read the backup_file: #{e}"
101
- # :nocov:
102
- rescue JSON::ParserError => e
103
- # :nocov:
104
- Unleash.logger.error "Unable to parse JSON from existing backup_file: #{e}"
105
- # :nocov:
106
- rescue StandardError => e
107
- # :nocov:
108
- Unleash.logger.error "Unable to extract valid data from backup_file. Exception thrown: #{e}"
109
- # :nocov:
75
+ def read_backup_file!
76
+ backup_data = Unleash::BackupFileReader.read!
77
+ update_engine_state!(backup_data) if backup_data
110
78
  end
111
79
 
112
80
  def bootstrap
@@ -0,0 +1,17 @@
1
+ module Unleash
2
+ module Util
3
+ module EventSourceWrapper
4
+ def self.client
5
+ return nil if RUBY_ENGINE == 'jruby'
6
+
7
+ begin
8
+ require 'ld-eventsource'
9
+ SSE::Client
10
+ rescue LoadError => e
11
+ Unleash.logger.error "Failed to load ld-eventsource: #{e.message}"
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module Unleash
2
- VERSION = "6.3.1".freeze
2
+ VERSION = "6.4.1".freeze
3
3
  end
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.description = "This is the ruby client for Unleash, a powerful feature toggle system
14
14
  that gives you a great overview over all feature toggles across all your applications and services."
15
15
 
16
- spec.homepage = "https://github.com/unleash/unleash-client-ruby"
16
+ spec.homepage = "https://github.com/unleash/unleash-ruby-sdk"
17
17
 
18
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
19
  f.match(%r{^(test|spec|features)/})
@@ -23,9 +23,10 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
  spec.required_ruby_version = ">= 2.7"
25
25
 
26
- spec.add_dependency "yggdrasil-engine", "~> 1.0.4"
26
+ spec.add_dependency "ld-eventsource", "2.2.4" unless RUBY_ENGINE == 'jruby'
27
+ spec.add_dependency "yggdrasil-engine", "~> 1.1.1"
27
28
 
28
- spec.add_dependency "base64", "~> 0.2.0"
29
+ spec.add_dependency "base64", "~> 0.3.0"
29
30
  spec.add_dependency "logger", "~> 1.6"
30
31
 
31
32
  spec.add_development_dependency "bundler", "~> 2.1"
metadata CHANGED
@@ -1,43 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unleash
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.1
4
+ version: 6.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Renato Arruda
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-27 00:00:00.000000000 Z
11
+ date: 2025-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ld-eventsource
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 2.2.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.2.4
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: yggdrasil-engine
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: 1.0.4
33
+ version: 1.1.1
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: 1.0.4
40
+ version: 1.1.1
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: base64
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: 0.2.0
47
+ version: 0.3.0
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: 0.2.0
54
+ version: 0.3.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: logger
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -193,7 +207,10 @@ files:
193
207
  - examples/default-toggles.json
194
208
  - examples/extending_unleash_with_opentelemetry.rb
195
209
  - examples/simple.rb
210
+ - examples/streaming.rb
196
211
  - lib/unleash.rb
212
+ - lib/unleash/backup_file_reader.rb
213
+ - lib/unleash/backup_file_writer.rb
197
214
  - lib/unleash/bootstrap/configuration.rb
198
215
  - lib/unleash/bootstrap/handler.rb
199
216
  - lib/unleash/bootstrap/provider/base.rb
@@ -206,17 +223,20 @@ files:
206
223
  - lib/unleash/scheduled_executor.rb
207
224
  - lib/unleash/spec_version.rb
208
225
  - lib/unleash/strategies.rb
226
+ - lib/unleash/streaming_client_executor.rb
227
+ - lib/unleash/streaming_event_processor.rb
209
228
  - lib/unleash/toggle_fetcher.rb
229
+ - lib/unleash/util/event_source_wrapper.rb
210
230
  - lib/unleash/util/http.rb
211
231
  - lib/unleash/variant.rb
212
232
  - lib/unleash/version.rb
213
233
  - unleash-client.gemspec
214
234
  - v6_MIGRATION_GUIDE.md
215
- homepage: https://github.com/unleash/unleash-client-ruby
235
+ homepage: https://github.com/unleash/unleash-ruby-sdk
216
236
  licenses:
217
237
  - Apache-2.0
218
238
  metadata: {}
219
- post_install_message:
239
+ post_install_message:
220
240
  rdoc_options: []
221
241
  require_paths:
222
242
  - lib
@@ -231,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
251
  - !ruby/object:Gem::Version
232
252
  version: '0'
233
253
  requirements: []
234
- rubygems_version: 3.5.6
235
- signing_key:
254
+ rubygems_version: 3.4.20
255
+ signing_key:
236
256
  specification_version: 4
237
257
  summary: Unleash feature toggle client.
238
258
  test_files: []