launchdarkly-server-sdk 8.13.0-java → 8.15.0-java

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/ldclient-rb/config.rb +24 -3
  4. data/lib/ldclient-rb/data_system/polling_data_source_builder.rb +7 -5
  5. data/lib/ldclient-rb/impl/context_filter.rb +7 -7
  6. data/lib/ldclient-rb/impl/data_source/polling.rb +14 -1
  7. data/lib/ldclient-rb/impl/data_source/requestor.rb +13 -3
  8. data/lib/ldclient-rb/impl/data_source/stream.rb +2 -0
  9. data/lib/ldclient-rb/impl/data_source.rb +37 -0
  10. data/lib/ldclient-rb/impl/data_store/feature_store_client_wrapper.rb +12 -0
  11. data/lib/ldclient-rb/impl/data_store/store.rb +24 -0
  12. data/lib/ldclient-rb/impl/data_system/fdv1.rb +5 -0
  13. data/lib/ldclient-rb/impl/data_system/fdv2.rb +92 -13
  14. data/lib/ldclient-rb/impl/data_system/polling.rb +100 -33
  15. data/lib/ldclient-rb/impl/data_system/protocolv2.rb +3 -29
  16. data/lib/ldclient-rb/impl/data_system/streaming.rb +54 -31
  17. data/lib/ldclient-rb/impl/data_system.rb +15 -5
  18. data/lib/ldclient-rb/impl/integrations/file_data_source_v2.rb +29 -22
  19. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +4 -0
  20. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +53 -48
  21. data/lib/ldclient-rb/integrations/consul.rb +6 -2
  22. data/lib/ldclient-rb/integrations/dynamodb.rb +6 -2
  23. data/lib/ldclient-rb/integrations/file_data.rb +1 -4
  24. data/lib/ldclient-rb/integrations/redis.rb +6 -2
  25. data/lib/ldclient-rb/integrations/test_data_v2.rb +0 -3
  26. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +39 -19
  27. data/lib/ldclient-rb/interfaces/data_system.rb +60 -57
  28. data/lib/ldclient-rb/interfaces/hooks.rb +11 -1
  29. data/lib/ldclient-rb/ldclient.rb +1 -1
  30. data/lib/ldclient-rb/util.rb +3 -2
  31. data/lib/ldclient-rb/version.rb +1 -1
  32. metadata +2 -2
@@ -46,56 +46,61 @@ module LaunchDarkly
46
46
  # Implementation of the Initializer.fetch method.
47
47
  #
48
48
  # Returns the current test data as a Basis for initial data loading.
49
+ # Test data sources never request the FDv1 Fallback Directive, so the
50
+ # returned {FetchResult} always reports `fallback_to_fdv1: false`.
49
51
  #
50
52
  # @param selector_store [LaunchDarkly::Interfaces::DataSystem::SelectorStore] Provides the Selector (unused for test data)
51
- # @return [LaunchDarkly::Result] A Result containing either a Basis or an error message
53
+ # @return [LaunchDarkly::Interfaces::DataSystem::FetchResult]
52
54
  #
53
55
  def fetch(selector_store)
54
- begin
55
- @lock.synchronize do
56
- if @closed
57
- return LaunchDarkly::Result.fail('TestDataV2 source has been closed')
58
- end
59
-
60
- # Get all current flags and segments from test data
61
- init_data = @test_data.make_init_data
62
- version = @test_data.get_version
63
-
64
- # Build a full transfer changeset
65
- builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new
66
- builder.start(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL)
67
-
68
- # Add all flags to the changeset
69
- init_data[:flags].each do |key, flag_data|
70
- builder.add_put(
71
- LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG,
72
- key,
73
- flag_data[:version] || 1,
74
- flag_data
75
- )
76
- end
77
-
78
- # Add all segments to the changeset
79
- init_data[:segments].each do |key, segment_data|
80
- builder.add_put(
81
- LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT,
82
- key,
83
- segment_data[:version] || 1,
84
- segment_data
85
- )
56
+ result =
57
+ begin
58
+ @lock.synchronize do
59
+ if @closed
60
+ next LaunchDarkly::Result.fail('TestDataV2 source has been closed')
61
+ end
62
+
63
+ # Get all current flags and segments from test data
64
+ init_data = @test_data.make_init_data
65
+ version = @test_data.get_version
66
+
67
+ # Build a full transfer changeset
68
+ builder = LaunchDarkly::Interfaces::DataSystem::ChangeSetBuilder.new
69
+ builder.start(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL)
70
+
71
+ # Add all flags to the changeset
72
+ init_data[:flags].each do |key, flag_data|
73
+ builder.add_put(
74
+ LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG,
75
+ key,
76
+ flag_data[:version] || 1,
77
+ flag_data
78
+ )
79
+ end
80
+
81
+ # Add all segments to the changeset
82
+ init_data[:segments].each do |key, segment_data|
83
+ builder.add_put(
84
+ LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT,
85
+ key,
86
+ segment_data[:version] || 1,
87
+ segment_data
88
+ )
89
+ end
90
+
91
+ # Create selector for this version
92
+ selector = LaunchDarkly::Interfaces::DataSystem::Selector.new_selector(version.to_s, version)
93
+ change_set = builder.finish(selector)
94
+
95
+ basis = LaunchDarkly::Interfaces::DataSystem::Basis.new(change_set: change_set, persist: false, environment_id: nil)
96
+
97
+ LaunchDarkly::Result.success(basis)
86
98
  end
87
-
88
- # Create selector for this version
89
- selector = LaunchDarkly::Interfaces::DataSystem::Selector.new_selector(version.to_s, version)
90
- change_set = builder.finish(selector)
91
-
92
- basis = LaunchDarkly::Interfaces::DataSystem::Basis.new(change_set: change_set, persist: false, environment_id: nil)
93
-
94
- LaunchDarkly::Result.success(basis)
99
+ rescue => e
100
+ LaunchDarkly::Result.fail("Error fetching test data: #{e.message}", e)
95
101
  end
96
- rescue => e
97
- LaunchDarkly::Result.fail("Error fetching test data: #{e.message}", e)
98
- end
102
+
103
+ LaunchDarkly::Interfaces::DataSystem::FetchResult.new(result: result, fallback_to_fdv1: false)
99
104
  end
100
105
 
101
106
  #
@@ -109,14 +114,14 @@ module LaunchDarkly
109
114
  #
110
115
  def sync(selector_store)
111
116
  # First yield initial data
112
- initial_result = fetch(selector_store)
113
- unless initial_result.success?
117
+ initial_fetch = fetch(selector_store)
118
+ unless initial_fetch.success?
114
119
  yield LaunchDarkly::Interfaces::DataSystem::Update.new(
115
120
  state: LaunchDarkly::Interfaces::DataSource::Status::OFF,
116
121
  error: LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
117
122
  LaunchDarkly::Interfaces::DataSource::ErrorInfo::STORE_ERROR,
118
123
  0,
119
- initial_result.error,
124
+ initial_fetch.error,
120
125
  Time.now
121
126
  )
122
127
  )
@@ -126,7 +131,7 @@ module LaunchDarkly
126
131
  # Yield the initial successful state
127
132
  yield LaunchDarkly::Interfaces::DataSystem::Update.new(
128
133
  state: LaunchDarkly::Interfaces::DataSource::Status::VALID,
129
- change_set: initial_result.value.change_set
134
+ change_set: initial_fetch.value.change_set
130
135
  )
131
136
 
132
137
  # Continue yielding updates as they arrive
@@ -32,8 +32,12 @@ module LaunchDarkly
32
32
  # @option opts [String] :url shortcut for setting the `url` property of the Consul client configuration
33
33
  # @option opts [String] :prefix namespace prefix to add to all keys used by LaunchDarkly
34
34
  # @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
35
- # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching
36
- # @option opts [Integer] :capacity (1000) maximum number of items in the cache
35
+ # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching.
36
+ # When the SDK is configured to use FDv2, the persistent-store cache is automatically
37
+ # dropped once the in-memory store has been initialized, so this setting only affects
38
+ # the brief bootstrap window before the first set of flag data has been received.
39
+ # @option opts [Integer] :capacity (1000) maximum number of items in the cache.
40
+ # Same FDv2 caveat as `:expiration` applies.
37
41
  # @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
38
42
  #
39
43
  def self.new_feature_store(opts = {})
@@ -42,8 +42,12 @@ module LaunchDarkly
42
42
  # @option opts [Object] :existing_client an already-constructed DynamoDB client for the feature store to use
43
43
  # @option opts [String] :prefix namespace prefix to add to all keys used by LaunchDarkly
44
44
  # @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
45
- # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching
46
- # @option opts [Integer] :capacity (1000) maximum number of items in the cache
45
+ # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching.
46
+ # When the SDK is configured to use FDv2, the persistent-store cache is automatically
47
+ # dropped once the in-memory store has been initialized, so this setting only affects
48
+ # the brief bootstrap window before the first set of flag data has been received.
49
+ # @option opts [Integer] :capacity (1000) maximum number of items in the cache.
50
+ # Same FDv2 caveat as `:expiration` applies.
47
51
  # @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
48
52
  #
49
53
  def self.new_feature_store(table_name, opts = {})
@@ -50,7 +50,7 @@ module LaunchDarkly
50
50
  # "segments": {
51
51
  # "segment-key-1": {
52
52
  # "key": "segment-key-1",
53
- # "includes": [ "user-key-1" ]
53
+ # "included": [ "user-key-1" ]
54
54
  # }
55
55
  # }
56
56
  # }
@@ -108,9 +108,6 @@ module LaunchDarkly
108
108
  #
109
109
  # Returns a builder for the FDv2-compatible file data source.
110
110
  #
111
- # This method is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
112
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
113
- #
114
111
  # This method returns a builder proc that can be used with the FDv2 data system
115
112
  # configuration as both an Initializer and a Synchronizer. When used as an Initializer
116
113
  # (via `fetch`), it reads files once. When used as a Synchronizer (via `sync`), it
@@ -50,8 +50,12 @@ module LaunchDarkly
50
50
  # @option opts [String] :prefix (default_prefix) namespace prefix to add to all hash keys used by LaunchDarkly
51
51
  # @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
52
52
  # @option opts [Integer] :max_connections size of the Redis connection pool
53
- # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching
54
- # @option opts [Integer] :capacity (1000) maximum number of items in the cache
53
+ # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching.
54
+ # When the SDK is configured to use FDv2, the persistent-store cache is automatically
55
+ # dropped once the in-memory store has been initialized, so this setting only affects
56
+ # the brief bootstrap window before the first set of flag data has been received.
57
+ # @option opts [Integer] :capacity (1000) maximum number of items in the cache.
58
+ # Same FDv2 caveat as `:expiration` applies.
55
59
  # @option opts [Object] :pool custom connection pool, if desired
56
60
  # @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool;
57
61
  # this is true by default, and should be set to false only if you are managing the pool yourself and want its
@@ -9,9 +9,6 @@ module LaunchDarkly
9
9
  # A mechanism for providing dynamically updatable feature flag state in a
10
10
  # simplified form to an SDK client in test scenarios using the FDv2 protocol.
11
11
  #
12
- # This class is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
13
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
14
- #
15
12
  # Unlike {LaunchDarkly::Integrations::FileData}, this mechanism does not use any external resources. It
16
13
  # provides only the data that the application has put into it using the {#update} method.
17
14
  #
@@ -61,50 +61,52 @@ module LaunchDarkly
61
61
  @core.init_internal(all_data)
62
62
  @inited.make_true
63
63
 
64
- unless @cache.nil?
65
- @cache.clear
64
+ cache = @cache
65
+ unless cache.nil?
66
+ cache.clear
66
67
  all_data.each do |kind, items|
67
- @cache[kind] = items_if_not_deleted(items)
68
+ cache[kind] = items_if_not_deleted(items)
68
69
  items.each do |key, item|
69
- @cache[item_cache_key(kind, key)] = [item]
70
+ cache[item_cache_key(kind, key)] = [item]
70
71
  end
71
72
  end
72
73
  end
73
74
  end
74
75
 
75
76
  def get(kind, key)
76
- unless @cache.nil?
77
- cache_key = item_cache_key(kind, key)
78
- cached = @cache[cache_key] # note, item entries in the cache are wrapped in an array so we can cache nil values
77
+ cache = @cache
78
+ cache_key = item_cache_key(kind, key)
79
+ unless cache.nil?
80
+ cached = cache[cache_key] # note, item entries in the cache are wrapped in an array so we can cache nil values
79
81
  return item_if_not_deleted(cached[0]) unless cached.nil?
80
82
  end
81
83
 
82
84
  item = @core.get_internal(kind, key)
83
85
 
84
- unless @cache.nil?
85
- @cache[cache_key] = [item]
86
- end
86
+ cache[cache_key] = [item] unless cache.nil?
87
87
 
88
88
  item_if_not_deleted(item)
89
89
  end
90
90
 
91
91
  def all(kind)
92
- unless @cache.nil?
93
- items = @cache[all_cache_key(kind)]
92
+ cache = @cache
93
+ unless cache.nil?
94
+ items = cache[all_cache_key(kind)]
94
95
  return items unless items.nil?
95
96
  end
96
97
 
97
98
  items = items_if_not_deleted(@core.get_all_internal(kind))
98
- @cache[all_cache_key(kind)] = items unless @cache.nil?
99
+ cache[all_cache_key(kind)] = items unless cache.nil?
99
100
  items
100
101
  end
101
102
 
102
103
  def upsert(kind, item)
103
104
  new_state = @core.upsert_internal(kind, item)
104
105
 
105
- unless @cache.nil?
106
- @cache[item_cache_key(kind, item[:key])] = [new_state]
107
- @cache.delete(all_cache_key(kind))
106
+ cache = @cache
107
+ unless cache.nil?
108
+ cache[item_cache_key(kind, item[:key])] = [new_state]
109
+ cache.delete(all_cache_key(kind))
108
110
  end
109
111
  end
110
112
 
@@ -115,13 +117,14 @@ module LaunchDarkly
115
117
  def initialized?
116
118
  return true if @inited.value
117
119
 
118
- if @cache.nil?
120
+ cache = @cache
121
+ if cache.nil?
119
122
  result = @core.initialized_internal?
120
123
  else
121
- result = @cache[inited_cache_key]
124
+ result = cache[inited_cache_key]
122
125
  if result.nil?
123
126
  result = @core.initialized_internal?
124
- @cache[inited_cache_key] = result
127
+ cache[inited_cache_key] = result
125
128
  end
126
129
  end
127
130
 
@@ -129,6 +132,23 @@ module LaunchDarkly
129
132
  result
130
133
  end
131
134
 
135
+ #
136
+ # Disable the in-memory cache. Releases the cache reference so subsequent operations
137
+ # bypass it and go directly to the underlying core. Safe to call multiple times.
138
+ #
139
+ # Called by the FDv2 store coordinator once the in-memory store has become the
140
+ # source of truth and the persistent-store cache is no longer useful. Internal --
141
+ # not part of the public API.
142
+ #
143
+ # @return [void]
144
+ #
145
+ def disable_cache
146
+ cache = @cache
147
+ return if cache.nil?
148
+ @cache = nil
149
+ cache.clear
150
+ end
151
+
132
152
  def stop
133
153
  @core.stop
134
154
  end
@@ -6,9 +6,6 @@ module LaunchDarkly
6
6
  #
7
7
  # EventName represents the name of an event that can be sent by the server for FDv2.
8
8
  #
9
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
10
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
11
- #
12
9
  module EventName
13
10
  # Specifies that an object should be added to the data set with upsert semantics.
14
11
  PUT_OBJECT = :"put-object"
@@ -35,9 +32,6 @@ module LaunchDarkly
35
32
  #
36
33
  # ObjectKind represents the kind of object.
37
34
  #
38
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
39
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
40
- #
41
35
  module ObjectKind
42
36
  # Represents a feature flag.
43
37
  FLAG = "flag"
@@ -49,9 +43,6 @@ module LaunchDarkly
49
43
  #
50
44
  # ChangeType specifies if an object is being upserted or deleted.
51
45
  #
52
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
53
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
54
- #
55
46
  module ChangeType
56
47
  # Represents an object being upserted.
57
48
  PUT = "put"
@@ -63,9 +54,6 @@ module LaunchDarkly
63
54
  #
64
55
  # IntentCode represents the various intents that can be sent by the server.
65
56
  #
66
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
67
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
68
- #
69
57
  module IntentCode
70
58
  # The server intends to send a full data set.
71
59
  TRANSFER_FULL = "xfer-full"
@@ -80,9 +68,6 @@ module LaunchDarkly
80
68
  #
81
69
  # DataStoreMode represents the mode of operation of a Data Store in FDV2 mode.
82
70
  #
83
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
84
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
85
- #
86
71
  module DataStoreMode
87
72
  # Indicates that the data store is read-only. Data will never be written back to the store by the SDK.
88
73
  READ_ONLY = :read_only
@@ -95,9 +80,6 @@ module LaunchDarkly
95
80
  #
96
81
  # Selector represents a particular snapshot of data.
97
82
  #
98
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
99
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
100
- #
101
83
  class Selector
102
84
  # @return [String] The state
103
85
  attr_reader :state
@@ -196,9 +178,6 @@ module LaunchDarkly
196
178
  #
197
179
  # Change represents a change to a piece of data, such as an update or deletion.
198
180
  #
199
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
200
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
201
- #
202
181
  class Change
203
182
  # @return [String] The action ({ChangeType})
204
183
  attr_reader :action
@@ -234,9 +213,6 @@ module LaunchDarkly
234
213
  #
235
214
  # ChangeSet represents a list of changes to be applied.
236
215
  #
237
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
238
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
239
- #
240
216
  class ChangeSet
241
217
  # @return [String] The intent code ({IntentCode})
242
218
  attr_reader :intent_code
@@ -262,9 +238,6 @@ module LaunchDarkly
262
238
  #
263
239
  # Basis represents the initial payload of data that a data source can provide.
264
240
  #
265
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
266
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
267
- #
268
241
  class Basis
269
242
  # @return [ChangeSet] The change set
270
243
  attr_reader :change_set
@@ -290,9 +263,6 @@ module LaunchDarkly
290
263
  #
291
264
  # Payload represents a payload delivered in a streaming response.
292
265
  #
293
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
294
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
295
- #
296
266
  class Payload
297
267
  # @return [String] The payload ID
298
268
  attr_reader :id
@@ -357,9 +327,6 @@ module LaunchDarkly
357
327
  #
358
328
  # ServerIntent represents the type of change associated with the payload.
359
329
  #
360
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
361
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
362
- #
363
330
  class ServerIntent
364
331
  # @return [Payload] The payload
365
332
  attr_reader :payload
@@ -405,9 +372,6 @@ module LaunchDarkly
405
372
  #
406
373
  # ChangeSetBuilder is a helper for constructing a ChangeSet.
407
374
  #
408
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
409
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
410
- #
411
375
  class ChangeSetBuilder
412
376
  # @return [String, nil] The current intent ({IntentCode})
413
377
  attr_accessor :intent
@@ -546,9 +510,6 @@ module LaunchDarkly
546
510
  #
547
511
  # Update represents the results of a synchronizer's ongoing sync method.
548
512
  #
549
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
550
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
551
- #
552
513
  class Update
553
514
  # @return [Symbol] The data source state ({LaunchDarkly::Interfaces::DataSource::Status})
554
515
  attr_reader :state
@@ -559,8 +520,9 @@ module LaunchDarkly
559
520
  # @return [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] Error information
560
521
  attr_reader :error
561
522
 
562
- # @return [Boolean] Whether to revert to FDv1
563
- attr_reader :revert_to_fdv1
523
+ # @return [Boolean] Whether the LaunchDarkly server has instructed the SDK to fall
524
+ # back to the FDv1 protocol (signalled via the `X-LD-FD-Fallback` response header).
525
+ attr_reader :fallback_to_fdv1
564
526
 
565
527
  # @return [String, nil] The environment ID
566
528
  attr_reader :environment_id
@@ -569,23 +531,67 @@ module LaunchDarkly
569
531
  # @param state [Symbol] The data source state ({LaunchDarkly::Interfaces::DataSource::Status})
570
532
  # @param change_set [ChangeSet, nil] The change set
571
533
  # @param error [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] Error information
572
- # @param revert_to_fdv1 [Boolean] Whether to revert to FDv1
534
+ # @param fallback_to_fdv1 [Boolean] Whether to fall back to FDv1
573
535
  # @param environment_id [String, nil] The environment ID
574
536
  #
575
- def initialize(state:, change_set: nil, error: nil, revert_to_fdv1: false, environment_id: nil)
537
+ def initialize(state:, change_set: nil, error: nil, fallback_to_fdv1: false, environment_id: nil)
576
538
  @state = state
577
539
  @change_set = change_set
578
540
  @error = error
579
- @revert_to_fdv1 = revert_to_fdv1
541
+ @fallback_to_fdv1 = fallback_to_fdv1
580
542
  @environment_id = environment_id
581
543
  end
582
544
  end
583
545
 
584
546
  #
585
- # SelectorStore represents a component capable of providing Selectors for data retrieval.
547
+ # FetchResult pairs the result of an {Initializer#fetch} call with the
548
+ # server-directed FDv1 Fallback Directive signal.
549
+ #
550
+ # When the LaunchDarkly server returns the `X-LD-FD-Fallback: true`
551
+ # response header on an initializer response, the SDK must apply any
552
+ # accompanying payload and then switch to the FDv1 Fallback Synchronizer.
553
+ # Surfacing this signal alongside the {LaunchDarkly::Result} ensures
554
+ # callers cannot silently drop it.
555
+ #
556
+ class FetchResult
557
+ # @return [LaunchDarkly::Result] A Result containing either a {Basis} or an error.
558
+ attr_reader :result
559
+
560
+ # @return [Boolean] Whether the server has instructed the SDK to fall back to the FDv1 protocol.
561
+ attr_reader :fallback_to_fdv1
562
+
563
+ #
564
+ # @param result [LaunchDarkly::Result] A Result containing either a Basis or an error.
565
+ # @param fallback_to_fdv1 [Boolean] Whether to fall back to FDv1.
566
+ #
567
+ def initialize(result:, fallback_to_fdv1: false)
568
+ @result = result
569
+ @fallback_to_fdv1 = fallback_to_fdv1
570
+ end
571
+
572
+ # @return [Boolean] true when the underlying Result was successful.
573
+ def success?
574
+ @result.success?
575
+ end
576
+
577
+ # @return [Object, nil] The {Basis} returned from a successful fetch, or nil.
578
+ def value
579
+ @result.value
580
+ end
581
+
582
+ # @return [String, nil] An error description, or nil on success.
583
+ def error
584
+ @result.error
585
+ end
586
+
587
+ # @return [Exception, nil] An optional exception describing the failure.
588
+ def exception
589
+ @result.exception
590
+ end
591
+ end
592
+
586
593
  #
587
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
588
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
594
+ # SelectorStore represents a component capable of providing Selectors for data retrieval.
589
595
  #
590
596
  module SelectorStore
591
597
  #
@@ -601,9 +607,6 @@ module LaunchDarkly
601
607
  #
602
608
  # ReadOnlyStore represents a read-only store interface for retrieving data.
603
609
  #
604
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
605
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
606
- #
607
610
  module ReadOnlyStore
608
611
  #
609
612
  # Retrieves an item by kind and key.
@@ -639,9 +642,6 @@ module LaunchDarkly
639
642
  #
640
643
  # Initializer represents a component capable of retrieving a single data result.
641
644
  #
642
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
643
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
644
- #
645
645
  module Initializer
646
646
  #
647
647
  # Returns the name of the initializer.
@@ -655,8 +655,14 @@ module LaunchDarkly
655
655
  #
656
656
  # Retrieves the initial data set for the data source.
657
657
  #
658
+ # If the LaunchDarkly server has instructed the SDK to fall back to
659
+ # the FDv1 protocol, the returned {FetchResult#fallback_to_fdv1} is
660
+ # true. The wrapped result may still carry a successful {Basis} when
661
+ # the directive accompanied a valid payload, in which case callers
662
+ # should apply the payload before switching protocols.
663
+ #
658
664
  # @param selector_store [SelectorStore] Provides the Selector
659
- # @return [LaunchDarkly::Result<Basis, String>]
665
+ # @return [FetchResult]
660
666
  #
661
667
  def fetch(selector_store)
662
668
  raise NotImplementedError, "#{self.class} must implement #fetch"
@@ -666,9 +672,6 @@ module LaunchDarkly
666
672
  #
667
673
  # Synchronizer represents a component capable of synchronizing data from an external source.
668
674
  #
669
- # This type is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
670
- # It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
671
- #
672
675
  module Synchronizer
673
676
  #
674
677
  # Returns the name of the synchronizer.
@@ -70,17 +70,27 @@ module LaunchDarkly
70
70
  attr_reader :default_value
71
71
  attr_reader :method
72
72
 
73
+ #
74
+ # The ID of the LaunchDarkly environment the SDK is connected to, if known. This is only available once the
75
+ # SDK has received data from LaunchDarkly.
76
+ #
77
+ # @return [String, nil]
78
+ #
79
+ attr_reader :environment_id
80
+
73
81
  #
74
82
  # @param key [String]
75
83
  # @param context [LaunchDarkly::LDContext]
76
84
  # @param default_value [any]
77
85
  # @param method [Symbol]
86
+ # @param environment_id [String, nil]
78
87
  #
79
- def initialize(key, context, default_value, method)
88
+ def initialize(key, context, default_value, method, environment_id = nil)
80
89
  @key = key
81
90
  @context = context
82
91
  @default_value = default_value
83
92
  @method = method
93
+ @environment_id = environment_id
84
94
  end
85
95
  end
86
96
  end
@@ -421,7 +421,7 @@ module LaunchDarkly
421
421
  # Hooks can be added and we want to ensure all correct stages for a given hook execute. For example, we do not
422
422
  # want to trigger the after_evaluation method without also triggering the before_evaluation method.
423
423
  hooks = @hooks.dup
424
- evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method)
424
+ evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method, @data_system.environment_id)
425
425
 
426
426
  [hooks, evaluation_series_context]
427
427
  end
@@ -16,10 +16,11 @@ module LaunchDarkly
16
16
  # Create a successful result with the provided value.
17
17
  #
18
18
  # @param value [Object, nil]
19
+ # @param headers [Hash, nil] Optional headers associated with the result
19
20
  # @return [Result]
20
21
  #
21
- def self.success(value)
22
- Result.new(value)
22
+ def self.success(value, headers = nil)
23
+ Result.new(value, nil, nil, headers)
23
24
  end
24
25
 
25
26
  #
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "8.13.0" # x-release-please-version
2
+ VERSION = "8.15.0" # x-release-please-version
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.13.0
4
+ version: 8.15.0
5
5
  platform: java
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-27 00:00:00.000000000 Z
10
+ date: 2026-08-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: aws-sdk-dynamodb