featurehub-sdk 2.0.1 → 2.1.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 +4 -4
- data/.claude/CLAUDE.md +1 -0
- data/.dockerignore +12 -0
- data/.rubocop.yml +0 -2
- data/CHANGELOG.md +14 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +38 -55
- data/Makefile +12 -0
- data/README.md +66 -13
- data/examples/sinatra/Dockerfile +16 -16
- data/examples/sinatra/Gemfile +4 -0
- data/examples/sinatra/Gemfile.lock +33 -14
- data/examples/sinatra/README.adoc +18 -2
- data/examples/sinatra/app/application.rb +31 -19
- data/examples/sinatra/conf/nginx.conf +3 -11
- data/examples/sinatra/conf/webapp.conf +16 -0
- data/examples/sinatra/docker-compose.yaml +32 -4
- data/examples/sinatra/start.sh +19 -4
- data/featurehub-sdk.gemspec +3 -2
- data/lib/feature_hub/sdk/context.rb +3 -1
- data/lib/feature_hub/sdk/feature_hub_config.rb +32 -8
- data/lib/feature_hub/sdk/feature_state_holder.rb +11 -0
- data/lib/feature_hub/sdk/memcache_session_store.rb +221 -0
- data/lib/feature_hub/sdk/poll_edge_service.rb +21 -15
- data/lib/feature_hub/sdk/redis_session_store.rb +145 -49
- data/lib/feature_hub/sdk/session_store_helpers.rb +45 -0
- data/lib/feature_hub/sdk/version.rb +1 -1
- data/lib/featurehub-sdk.rb +2 -0
- data/sig/feature_hub/featurehub.rbs +4 -0
- metadata +32 -10
- data/examples/sinatra/sinatra.iml +0 -43
- data/featurehub-ruby-sdk.iml +0 -9
- data/featurehub-sdk.iml +0 -87
|
@@ -2,67 +2,110 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "concurrent-ruby"
|
|
5
|
+
require_relative "session_store_helpers"
|
|
5
6
|
|
|
6
7
|
module FeatureHub
|
|
7
8
|
module Sdk
|
|
9
|
+
# Optional configuration for RedisSessionStore.
|
|
10
|
+
class RedisSessionStoreOptions
|
|
11
|
+
attr_reader :prefix, :backoff_timeout, :retry_update_count, :refresh_timeout, :logger, :db
|
|
12
|
+
|
|
13
|
+
def initialize(opts = nil)
|
|
14
|
+
opts ||= {}
|
|
15
|
+
@prefix = opts[:prefix] || "featurehub"
|
|
16
|
+
@backoff_timeout = opts[:backoff_timeout] || 500
|
|
17
|
+
@retry_update_count = opts[:retry_update_count] || 10
|
|
18
|
+
@refresh_timeout = opts[:refresh_timeout] || 300
|
|
19
|
+
@logger = opts[:logger]
|
|
20
|
+
@db = opts[:db] || 0
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
8
24
|
# Persists feature values from a FeatureHubRepository in Redis so they survive
|
|
9
25
|
# process restarts and are shared across multiple processes.
|
|
10
26
|
#
|
|
11
|
-
#
|
|
12
|
-
# context sends different resolved values; storing them in a shared Redis key
|
|
13
|
-
# will cause processes to overwrite each other's feature states.
|
|
27
|
+
# Uses SHA256-based change detection and Redis WATCH/MULTI/EXEC for multi-process safety.
|
|
14
28
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# timer re-reads all features from Redis so that updates published by other
|
|
19
|
-
# processes are picked up automatically.
|
|
29
|
+
# WARNING: Do not use with server-evaluated features. Each server-evaluated context
|
|
30
|
+
# sends different resolved values; storing them in a shared Redis key will cause
|
|
31
|
+
# processes to overwrite each other's feature states.
|
|
20
32
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# :timeout - seconds between periodic reloads (default: 30)
|
|
33
|
+
# On initialization the store reads any previously saved features from Redis and
|
|
34
|
+
# replays them into the repository. A periodic timer re-reads the SHA key so that
|
|
35
|
+
# updates published by other processes are picked up automatically.
|
|
25
36
|
class RedisSessionStore < RawUpdateFeatureListener
|
|
37
|
+
include SessionStoreHelpers
|
|
38
|
+
|
|
26
39
|
SOURCE = "redis-store"
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
# @param connection_or_client [String, Array, Object] Redis URL, list of cluster URLs,
|
|
42
|
+
# or an existing Redis client
|
|
43
|
+
# @param config [FeatureHubConfig] SDK config (provides repository and environment_id)
|
|
44
|
+
# @param opts [RedisSessionStoreOptions, Hash, nil] optional configuration
|
|
45
|
+
def initialize(connection_or_client, config, opts = nil)
|
|
29
46
|
super()
|
|
30
47
|
|
|
31
|
-
opts
|
|
32
|
-
|
|
33
|
-
@
|
|
34
|
-
@
|
|
35
|
-
@
|
|
36
|
-
@
|
|
37
|
-
@
|
|
48
|
+
options = opts.is_a?(RedisSessionStoreOptions) ? opts : RedisSessionStoreOptions.new(opts)
|
|
49
|
+
|
|
50
|
+
@repository = config.repository
|
|
51
|
+
@environment_id = config.environment_id
|
|
52
|
+
@prefix = options.prefix
|
|
53
|
+
@backoff_timeout = options.backoff_timeout
|
|
54
|
+
@retry_update_count = options.retry_update_count
|
|
55
|
+
@refresh_timeout = options.refresh_timeout
|
|
56
|
+
@internal_sha = nil
|
|
57
|
+
@mutex = Mutex.new
|
|
38
58
|
@task = nil
|
|
59
|
+
@logger = options.logger
|
|
39
60
|
|
|
40
61
|
return unless redis_available?
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
@redis = if connection_or_client.is_a?(String)
|
|
64
|
+
Redis.new(url: connection_or_client, db: options.db)
|
|
65
|
+
else
|
|
66
|
+
connection_or_client
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
config.register_raw_update_listener(self)
|
|
70
|
+
|
|
71
|
+
@logger&.debug("[featurehubsdk] started redis store")
|
|
72
|
+
Concurrent::Future.execute { load_from_redis }
|
|
46
73
|
start_timer
|
|
47
74
|
end
|
|
48
75
|
|
|
49
76
|
def process_updates(features, source)
|
|
50
77
|
return if source == SOURCE || !redis_available?
|
|
51
78
|
|
|
52
|
-
|
|
79
|
+
incoming_sha = calculate_sha(features)
|
|
80
|
+
return if incoming_sha == @redis.get(sha_key)
|
|
81
|
+
|
|
82
|
+
perform_store_with_retry do |redis_features|
|
|
83
|
+
has_newer = features.any? do |f|
|
|
84
|
+
existing = redis_features.find { |rf| rf["id"] == f["id"] }
|
|
85
|
+
existing.nil? || version_of(f) > version_of(existing)
|
|
86
|
+
end
|
|
87
|
+
has_newer ? merge_features(redis_features, features) : nil
|
|
88
|
+
end
|
|
53
89
|
end
|
|
54
90
|
|
|
55
91
|
def process_update(feature, source)
|
|
56
92
|
return if source == SOURCE || !redis_available?
|
|
57
93
|
|
|
58
|
-
|
|
94
|
+
perform_store_with_retry do |redis_features|
|
|
95
|
+
existing = redis_features.find { |f| f["id"] == feature["id"] }
|
|
96
|
+
next nil if existing && version_of(existing) >= version_of(feature)
|
|
97
|
+
|
|
98
|
+
merge_features(redis_features, [feature])
|
|
99
|
+
end
|
|
59
100
|
end
|
|
60
101
|
|
|
61
102
|
def delete_feature(feature, source)
|
|
62
103
|
return if source == SOURCE || !redis_available? || !feature["id"]
|
|
63
104
|
|
|
64
|
-
|
|
65
|
-
|
|
105
|
+
perform_store_with_retry do |redis_features|
|
|
106
|
+
updated = redis_features.reject { |f| f["id"] == feature["id"] }
|
|
107
|
+
updated.length < redis_features.length ? updated : nil
|
|
108
|
+
end
|
|
66
109
|
end
|
|
67
110
|
|
|
68
111
|
def close
|
|
@@ -84,14 +127,10 @@ module FeatureHub
|
|
|
84
127
|
end
|
|
85
128
|
|
|
86
129
|
def load_from_redis
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
features = ids.filter_map do |id|
|
|
91
|
-
json = @redis.get(feature_key(id))
|
|
92
|
-
JSON.parse(json) if json
|
|
93
|
-
end
|
|
130
|
+
sha = @redis.get(sha_key)
|
|
131
|
+
@mutex.synchronize { @internal_sha = sha }
|
|
94
132
|
|
|
133
|
+
features = read_features_from_redis
|
|
95
134
|
return if features.empty?
|
|
96
135
|
|
|
97
136
|
@logger&.debug("[featurehubsdk] loading #{features.size} feature(s) from redis")
|
|
@@ -99,31 +138,88 @@ module FeatureHub
|
|
|
99
138
|
end
|
|
100
139
|
|
|
101
140
|
def start_timer
|
|
102
|
-
@task = Concurrent::TimerTask.new(execution_interval: @
|
|
103
|
-
|
|
141
|
+
@task = Concurrent::TimerTask.new(execution_interval: @refresh_timeout, run_now: false) do
|
|
142
|
+
check_for_updates
|
|
104
143
|
end
|
|
105
144
|
@task.execute
|
|
106
145
|
end
|
|
107
146
|
|
|
108
|
-
def
|
|
109
|
-
return unless
|
|
147
|
+
def check_for_updates
|
|
148
|
+
return unless redis_available?
|
|
149
|
+
|
|
150
|
+
current_sha = @redis.get(sha_key)
|
|
151
|
+
stored_sha = @mutex.synchronize { @internal_sha }
|
|
152
|
+
return if current_sha == stored_sha
|
|
153
|
+
|
|
154
|
+
features = read_features_from_redis
|
|
155
|
+
@logger&.debug("[featurehubsdk] detected redis change, reloading #{features.size} feature(s)")
|
|
156
|
+
@repository.notify("features", features, SOURCE)
|
|
157
|
+
@mutex.synchronize { @internal_sha = current_sha }
|
|
158
|
+
end
|
|
110
159
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
160
|
+
# Computes what to write by yielding the current Redis features to the block.
|
|
161
|
+
# The block returns the new features array to store, or nil to abort.
|
|
162
|
+
# Uses WATCH/MULTI/EXEC with retry to handle multi-process contention.
|
|
163
|
+
def perform_store_with_retry
|
|
164
|
+
attempt = 0
|
|
165
|
+
while attempt < @retry_update_count
|
|
166
|
+
redis_features = read_features_from_redis
|
|
167
|
+
new_features = yield(redis_features)
|
|
168
|
+
return if new_features.nil?
|
|
169
|
+
|
|
170
|
+
new_sha = calculate_sha(new_features)
|
|
171
|
+
current_internal = @mutex.synchronize { @internal_sha }
|
|
172
|
+
return if new_sha == current_internal
|
|
173
|
+
|
|
174
|
+
current_sha = @redis.get(sha_key)
|
|
175
|
+
|
|
176
|
+
if current_sha != current_internal
|
|
177
|
+
# Another process updated Redis — reload and recheck on next attempt
|
|
178
|
+
sleep(@backoff_timeout / 1000.0) unless attempt == @retry_update_count - 1
|
|
179
|
+
attempt += 1
|
|
180
|
+
next
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
stored = attempt_atomic_write(current_internal, new_sha, new_features)
|
|
184
|
+
|
|
185
|
+
if stored
|
|
186
|
+
@mutex.synchronize { @internal_sha = new_sha }
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
sleep(@backoff_timeout / 1000.0) unless attempt == @retry_update_count - 1
|
|
191
|
+
attempt += 1
|
|
115
192
|
end
|
|
116
193
|
|
|
117
|
-
@
|
|
118
|
-
@redis.set(feature_key(feature["id"]), feature.to_json)
|
|
194
|
+
@logger&.warn("[featurehubsdk] failed to update redis after #{@retry_update_count} retries")
|
|
119
195
|
end
|
|
120
196
|
|
|
121
|
-
|
|
122
|
-
|
|
197
|
+
# Uses WATCH + MULTI/EXEC to atomically update both keys only if sha_key has not
|
|
198
|
+
# been modified since we read it. Returns true if the transaction committed.
|
|
199
|
+
def attempt_atomic_write(current_internal, new_sha, new_features)
|
|
200
|
+
@redis.watch(sha_key)
|
|
201
|
+
current = @redis.get(sha_key)
|
|
202
|
+
|
|
203
|
+
unless current == current_internal
|
|
204
|
+
@redis.unwatch
|
|
205
|
+
return false
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
result = @redis.multi do |tx|
|
|
209
|
+
tx.set(features_key, new_features.to_json)
|
|
210
|
+
tx.set(sha_key, new_sha)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
result.is_a?(Array)
|
|
123
214
|
end
|
|
124
215
|
|
|
125
|
-
def
|
|
126
|
-
|
|
216
|
+
def read_features_from_redis
|
|
217
|
+
json = @redis.get(features_key)
|
|
218
|
+
return [] unless json
|
|
219
|
+
|
|
220
|
+
JSON.parse(json)
|
|
221
|
+
rescue JSON::ParserError
|
|
222
|
+
[]
|
|
127
223
|
end
|
|
128
224
|
end
|
|
129
225
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module FeatureHub
|
|
6
|
+
module Sdk
|
|
7
|
+
# Shared pure helpers for MemcacheSessionStore and RedisSessionStore.
|
|
8
|
+
# Both stores rely on SHA256-based change detection and a two-key layout:
|
|
9
|
+
# ${prefix}_${environment_id} → JSON array of FeatureState objects
|
|
10
|
+
# ${prefix}_${environment_id}_sha → SHA256 of "id:version|..." for change detection
|
|
11
|
+
module SessionStoreHelpers
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def calculate_sha(features)
|
|
15
|
+
parts = features.map { |f| "#{f["id"]}:#{version_of(f)}" }.join("|")
|
|
16
|
+
Digest::SHA256.hexdigest(parts)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def merge_features(base, updates)
|
|
20
|
+
result = base.dup
|
|
21
|
+
updates.each do |update|
|
|
22
|
+
idx = result.find_index { |f| f["id"] == update["id"] }
|
|
23
|
+
if idx
|
|
24
|
+
result[idx] = update if version_of(update) > version_of(result[idx])
|
|
25
|
+
else
|
|
26
|
+
result << update
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def version_of(feature)
|
|
33
|
+
(feature["version"] || 0).to_i
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def features_key
|
|
37
|
+
"#{@prefix}_#{@environment_id}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def sha_key
|
|
41
|
+
"#{@prefix}_#{@environment_id}_sha"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/featurehub-sdk.rb
CHANGED
|
@@ -15,7 +15,9 @@ require_relative "feature_hub/sdk/strategy_attributes"
|
|
|
15
15
|
require_relative "feature_hub/sdk/local_yaml_interceptor"
|
|
16
16
|
require_relative "feature_hub/sdk/raw_update_feature_listener"
|
|
17
17
|
require_relative "feature_hub/sdk/local_yaml_store"
|
|
18
|
+
require_relative "feature_hub/sdk/session_store_helpers"
|
|
18
19
|
require_relative "feature_hub/sdk/redis_session_store"
|
|
20
|
+
require_relative "feature_hub/sdk/memcache_session_store"
|
|
19
21
|
require_relative "feature_hub/sdk/impl/strategy_wrappers"
|
|
20
22
|
require_relative "feature_hub/sdk/poll_edge_service"
|
|
21
23
|
require_relative "feature_hub/sdk/streaming_edge_service"
|
|
@@ -66,6 +66,8 @@ module FeatureHub
|
|
|
66
66
|
|
|
67
67
|
def feature_type: -> String?
|
|
68
68
|
|
|
69
|
+
def feature_properties: -> Hash[String, String]
|
|
70
|
+
|
|
69
71
|
def with_context: (ctx: ClientContext) -> FeatureStateHolder
|
|
70
72
|
|
|
71
73
|
def update_feature_state: (feature_state: FeatureState) -> void
|
|
@@ -94,6 +96,8 @@ module FeatureHub
|
|
|
94
96
|
|
|
95
97
|
def set?: () -> bool?
|
|
96
98
|
|
|
99
|
+
def phantom?: () -> bool
|
|
100
|
+
|
|
97
101
|
private
|
|
98
102
|
|
|
99
103
|
def top_feature_state: -> FeatureStateHolder
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: featurehub-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Vowles
|
|
@@ -28,30 +28,36 @@ dependencies:
|
|
|
28
28
|
name: faraday
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '0'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: ld-eventsource
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: 2.5.1
|
|
48
|
+
- - "<"
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: 2.7.0
|
|
48
51
|
type: :runtime
|
|
49
52
|
prerelease: false
|
|
50
53
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
54
|
requirements:
|
|
52
|
-
- - "
|
|
55
|
+
- - ">="
|
|
53
56
|
- !ruby/object:Gem::Version
|
|
54
57
|
version: 2.5.1
|
|
58
|
+
- - "<"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 2.7.0
|
|
55
61
|
- !ruby/object:Gem::Dependency
|
|
56
62
|
name: murmurhash3
|
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,6 +86,20 @@ dependencies:
|
|
|
80
86
|
- - "~>"
|
|
81
87
|
- !ruby/object:Gem::Version
|
|
82
88
|
version: 2.0.0
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: dalli
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '4'
|
|
96
|
+
type: :development
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '4'
|
|
83
103
|
- !ruby/object:Gem::Dependency
|
|
84
104
|
name: redis
|
|
85
105
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -102,6 +122,7 @@ extensions: []
|
|
|
102
122
|
extra_rdoc_files: []
|
|
103
123
|
files:
|
|
104
124
|
- ".claude/CLAUDE.md"
|
|
125
|
+
- ".dockerignore"
|
|
105
126
|
- ".rspec"
|
|
106
127
|
- ".rubocop.yml"
|
|
107
128
|
- ".ruby-version"
|
|
@@ -111,6 +132,7 @@ files:
|
|
|
111
132
|
- Gemfile.lock
|
|
112
133
|
- LICENSE
|
|
113
134
|
- LICENSE.txt
|
|
135
|
+
- Makefile
|
|
114
136
|
- README.md
|
|
115
137
|
- Rakefile
|
|
116
138
|
- examples/rails_example/.env.example
|
|
@@ -209,16 +231,14 @@ files:
|
|
|
209
231
|
- examples/sinatra/build.sh
|
|
210
232
|
- examples/sinatra/conf/nginx.conf
|
|
211
233
|
- examples/sinatra/conf/nsswitch.conf
|
|
234
|
+
- examples/sinatra/conf/webapp.conf
|
|
212
235
|
- examples/sinatra/config.ru
|
|
213
236
|
- examples/sinatra/docker-compose.yaml
|
|
214
237
|
- examples/sinatra/docker_start.sh
|
|
215
238
|
- examples/sinatra/feature-flags.yaml
|
|
216
|
-
- examples/sinatra/sinatra.iml
|
|
217
239
|
- examples/sinatra/start.sh
|
|
218
240
|
- examples/sinatra/thin.ru
|
|
219
|
-
- featurehub-ruby-sdk.iml
|
|
220
241
|
- featurehub-sdk.gemspec
|
|
221
|
-
- featurehub-sdk.iml
|
|
222
242
|
- lib/feature_hub/sdk/context.rb
|
|
223
243
|
- lib/feature_hub/sdk/feature_hub_config.rb
|
|
224
244
|
- lib/feature_hub/sdk/feature_repository.rb
|
|
@@ -231,10 +251,12 @@ files:
|
|
|
231
251
|
- lib/feature_hub/sdk/internal_feature_repository.rb
|
|
232
252
|
- lib/feature_hub/sdk/local_yaml_interceptor.rb
|
|
233
253
|
- lib/feature_hub/sdk/local_yaml_store.rb
|
|
254
|
+
- lib/feature_hub/sdk/memcache_session_store.rb
|
|
234
255
|
- lib/feature_hub/sdk/percentage_calc.rb
|
|
235
256
|
- lib/feature_hub/sdk/poll_edge_service.rb
|
|
236
257
|
- lib/feature_hub/sdk/raw_update_feature_listener.rb
|
|
237
258
|
- lib/feature_hub/sdk/redis_session_store.rb
|
|
259
|
+
- lib/feature_hub/sdk/session_store_helpers.rb
|
|
238
260
|
- lib/feature_hub/sdk/strategy_attributes.rb
|
|
239
261
|
- lib/feature_hub/sdk/streaming_edge_service.rb
|
|
240
262
|
- lib/feature_hub/sdk/version.rb
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="RUBY_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
4
|
-
<exclude-output />
|
|
5
|
-
<content url="file://$MODULE_DIR$" />
|
|
6
|
-
<orderEntry type="jdk" jdkName="rbenv: 3.3.10" jdkType="RUBY_SDK" />
|
|
7
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
8
|
-
<orderEntry type="library" scope="PROVIDED" name="addressable (v2.8.9, rbenv: 3.3.10) [gem]" level="application" />
|
|
9
|
-
<orderEntry type="library" scope="PROVIDED" name="bundler (v2.3.15, rbenv: 3.3.10) [gem]" level="application" />
|
|
10
|
-
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.3.6, rbenv: 3.3.10) [gem]" level="application" />
|
|
11
|
-
<orderEntry type="library" scope="PROVIDED" name="connection_pool (v3.0.2, rbenv: 3.3.10) [gem]" level="application" />
|
|
12
|
-
<orderEntry type="library" scope="PROVIDED" name="daemons (v1.4.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
13
|
-
<orderEntry type="library" scope="PROVIDED" name="domain_name (v0.6.20240107, rbenv: 3.3.10) [gem]" level="application" />
|
|
14
|
-
<orderEntry type="library" scope="PROVIDED" name="eventmachine (v1.2.7, rbenv: 3.3.10) [gem]" level="application" />
|
|
15
|
-
<orderEntry type="library" scope="PROVIDED" name="faraday (v2.14.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
16
|
-
<orderEntry type="library" scope="PROVIDED" name="faraday-net_http (v3.4.2, rbenv: 3.3.10) [gem]" level="application" />
|
|
17
|
-
<orderEntry type="library" scope="PROVIDED" name="ffi (v1.17.3, rbenv: 3.3.10) [gem]" level="application" />
|
|
18
|
-
<orderEntry type="library" scope="PROVIDED" name="ffi-compiler (v1.3.2, rbenv: 3.3.10) [gem]" level="application" />
|
|
19
|
-
<orderEntry type="library" scope="PROVIDED" name="http (v5.3.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
20
|
-
<orderEntry type="library" scope="PROVIDED" name="http-cookie (v1.1.0, rbenv: 3.3.10) [gem]" level="application" />
|
|
21
|
-
<orderEntry type="library" scope="PROVIDED" name="http-form_data (v2.3.0, rbenv: 3.3.10) [gem]" level="application" />
|
|
22
|
-
<orderEntry type="library" scope="PROVIDED" name="json (v2.19.2, rbenv: 3.3.10) [gem]" level="application" />
|
|
23
|
-
<orderEntry type="library" scope="PROVIDED" name="ld-eventsource (v2.5.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
24
|
-
<orderEntry type="library" scope="PROVIDED" name="llhttp-ffi (v0.5.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
25
|
-
<orderEntry type="library" scope="PROVIDED" name="logger (v1.7.0, rbenv: 3.3.10) [gem]" level="application" />
|
|
26
|
-
<orderEntry type="library" scope="PROVIDED" name="murmurhash3 (v0.1.7, rbenv: 3.3.10) [gem]" level="application" />
|
|
27
|
-
<orderEntry type="library" scope="PROVIDED" name="mustermann (v2.0.2, rbenv: 3.3.10) [gem]" level="application" />
|
|
28
|
-
<orderEntry type="library" scope="PROVIDED" name="net-http (v0.9.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
29
|
-
<orderEntry type="library" scope="PROVIDED" name="public_suffix (v7.0.5, rbenv: 3.3.10) [gem]" level="application" />
|
|
30
|
-
<orderEntry type="library" scope="PROVIDED" name="rack (v2.2.6.4, rbenv: 3.3.10) [gem]" level="application" />
|
|
31
|
-
<orderEntry type="library" scope="PROVIDED" name="rack-protection (v2.2.3, rbenv: 3.3.10) [gem]" level="application" />
|
|
32
|
-
<orderEntry type="library" scope="PROVIDED" name="rake (v13.3.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
33
|
-
<orderEntry type="library" scope="PROVIDED" name="redis (v5.4.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
34
|
-
<orderEntry type="library" scope="PROVIDED" name="redis-client (v0.28.0, rbenv: 3.3.10) [gem]" level="application" />
|
|
35
|
-
<orderEntry type="library" scope="PROVIDED" name="ruby2_keywords (v0.0.5, rbenv: 3.3.10) [gem]" level="application" />
|
|
36
|
-
<orderEntry type="library" scope="PROVIDED" name="sem_version (v2.0.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
37
|
-
<orderEntry type="library" scope="PROVIDED" name="shotgun (v0.9.2, rbenv: 3.3.10) [gem]" level="application" />
|
|
38
|
-
<orderEntry type="library" scope="PROVIDED" name="sinatra (v2.2.3, rbenv: 3.3.10) [gem]" level="application" />
|
|
39
|
-
<orderEntry type="library" scope="PROVIDED" name="thin (v1.8.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
40
|
-
<orderEntry type="library" scope="PROVIDED" name="tilt (v2.1.0, rbenv: 3.3.10) [gem]" level="application" />
|
|
41
|
-
<orderEntry type="library" scope="PROVIDED" name="uri (v1.1.1, rbenv: 3.3.10) [gem]" level="application" />
|
|
42
|
-
</component>
|
|
43
|
-
</module>
|
data/featurehub-ruby-sdk.iml
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="JAVA_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
4
|
-
<exclude-output />
|
|
5
|
-
<content url="file://$MODULE_DIR$" />
|
|
6
|
-
<orderEntry type="inheritedJdk" />
|
|
7
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
8
|
-
</component>
|
|
9
|
-
</module>
|
data/featurehub-sdk.iml
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="RUBY_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
4
|
-
<exclude-output />
|
|
5
|
-
<content url="file://$MODULE_DIR$" />
|
|
6
|
-
<orderEntry type="jdk" jdkName="rbenv: 3.0.6" jdkType="RUBY_SDK" />
|
|
7
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
8
|
-
<orderEntry type="library" scope="PROVIDED" name="addressable (v2.8.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
9
|
-
<orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, rbenv: 3.0.6) [gem]" level="application" />
|
|
10
|
-
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.1.10, rbenv: 3.0.6) [gem]" level="application" />
|
|
11
|
-
<orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
12
|
-
<orderEntry type="library" scope="PROVIDED" name="docile (v1.4.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
13
|
-
<orderEntry type="library" scope="PROVIDED" name="domain_name (v0.5.20190701, rbenv: 3.0.6) [gem]" level="application" />
|
|
14
|
-
<orderEntry type="library" scope="PROVIDED" name="faraday (v2.3.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
15
|
-
<orderEntry type="library" scope="PROVIDED" name="faraday-net_http (v2.0.3, rbenv: 3.0.6) [gem]" level="application" />
|
|
16
|
-
<orderEntry type="library" scope="PROVIDED" name="ffi (v1.15.5, rbenv: 3.0.6) [gem]" level="application" />
|
|
17
|
-
<orderEntry type="library" scope="PROVIDED" name="ffi-compiler (v1.0.1, rbenv: 3.0.6) [gem]" level="application" />
|
|
18
|
-
<orderEntry type="library" scope="PROVIDED" name="http (v5.0.4, rbenv: 3.0.6) [gem]" level="application" />
|
|
19
|
-
<orderEntry type="library" scope="PROVIDED" name="http-cookie (v1.0.5, rbenv: 3.0.6) [gem]" level="application" />
|
|
20
|
-
<orderEntry type="library" scope="PROVIDED" name="http-form_data (v2.3.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
21
|
-
<orderEntry type="library" scope="PROVIDED" name="ld-eventsource (v2.2.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
22
|
-
<orderEntry type="library" scope="PROVIDED" name="llhttp-ffi (v0.4.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
23
|
-
<orderEntry type="library" scope="PROVIDED" name="murmurhash3 (v0.1.6, rbenv: 3.0.6) [gem]" level="application" />
|
|
24
|
-
<orderEntry type="library" scope="PROVIDED" name="parallel (v1.22.1, rbenv: 3.0.6) [gem]" level="application" />
|
|
25
|
-
<orderEntry type="library" scope="PROVIDED" name="parser (v3.1.2.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
26
|
-
<orderEntry type="library" scope="PROVIDED" name="public_suffix (v4.0.7, rbenv: 3.0.6) [gem]" level="application" />
|
|
27
|
-
<orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, rbenv: 3.0.6) [gem]" level="application" />
|
|
28
|
-
<orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, rbenv: 3.0.6) [gem]" level="application" />
|
|
29
|
-
<orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.5.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
30
|
-
<orderEntry type="library" scope="PROVIDED" name="rexml (v3.2.5, rbenv: 3.0.6) [gem]" level="application" />
|
|
31
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec (v3.11.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
32
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.11.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
33
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.11.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
34
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.11.1, rbenv: 3.0.6) [gem]" level="application" />
|
|
35
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.11.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
36
|
-
<orderEntry type="library" scope="PROVIDED" name="rubocop (v1.30.1, rbenv: 3.0.6) [gem]" level="application" />
|
|
37
|
-
<orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.18.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
38
|
-
<orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.11.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
39
|
-
<orderEntry type="library" scope="PROVIDED" name="ruby2_keywords (v0.0.5, rbenv: 3.0.6) [gem]" level="application" />
|
|
40
|
-
<orderEntry type="library" scope="PROVIDED" name="sem_version (v2.0.1, rbenv: 3.0.6) [gem]" level="application" />
|
|
41
|
-
<orderEntry type="library" scope="PROVIDED" name="simplecov (v0.21.2, rbenv: 3.0.6) [gem]" level="application" />
|
|
42
|
-
<orderEntry type="library" scope="PROVIDED" name="simplecov-html (v0.12.3, rbenv: 3.0.6) [gem]" level="application" />
|
|
43
|
-
<orderEntry type="library" scope="PROVIDED" name="simplecov_json_formatter (v0.1.4, rbenv: 3.0.6) [gem]" level="application" />
|
|
44
|
-
<orderEntry type="library" scope="PROVIDED" name="unf (v0.1.4, rbenv: 3.0.6) [gem]" level="application" />
|
|
45
|
-
<orderEntry type="library" scope="PROVIDED" name="unf_ext (v0.0.8.2, rbenv: 3.0.6) [gem]" level="application" />
|
|
46
|
-
<orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.1.0, rbenv: 3.0.6) [gem]" level="application" />
|
|
47
|
-
</component>
|
|
48
|
-
<component name="RakeTasksCache">
|
|
49
|
-
<option name="myRootTask">
|
|
50
|
-
<RakeTaskImpl id="rake">
|
|
51
|
-
<subtasks>
|
|
52
|
-
<RakeTaskImpl description="Build featurehub-sdk-1.2.3.gem into the pkg directory" fullCommand="build" id="build" />
|
|
53
|
-
<RakeTaskImpl id="build">
|
|
54
|
-
<subtasks>
|
|
55
|
-
<RakeTaskImpl description="Generate SHA512 checksum if featurehub-sdk-1.2.3.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
|
|
56
|
-
</subtasks>
|
|
57
|
-
</RakeTaskImpl>
|
|
58
|
-
<RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
|
|
59
|
-
<RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
|
|
60
|
-
<RakeTaskImpl description="Build and install featurehub-sdk-1.2.3.gem into system gems" fullCommand="install" id="install" />
|
|
61
|
-
<RakeTaskImpl id="install">
|
|
62
|
-
<subtasks>
|
|
63
|
-
<RakeTaskImpl description="Build and install featurehub-sdk-1.2.3.gem into system gems without network access" fullCommand="install:local" id="local" />
|
|
64
|
-
</subtasks>
|
|
65
|
-
</RakeTaskImpl>
|
|
66
|
-
<RakeTaskImpl description="Create tag v1.2.3 and build and push featurehub-sdk-1.2.3.gem to https://rubygems.org" fullCommand="release[remote]" id="release[remote]" />
|
|
67
|
-
<RakeTaskImpl description="Run RuboCop" fullCommand="rubocop" id="rubocop" />
|
|
68
|
-
<RakeTaskImpl id="rubocop">
|
|
69
|
-
<subtasks>
|
|
70
|
-
<RakeTaskImpl description="Autocorrect RuboCop offenses" fullCommand="rubocop:auto_correct" id="auto_correct" />
|
|
71
|
-
</subtasks>
|
|
72
|
-
</RakeTaskImpl>
|
|
73
|
-
<RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
|
|
74
|
-
<RakeTaskImpl description="" fullCommand="default" id="default" />
|
|
75
|
-
<RakeTaskImpl description="" fullCommand="release" id="release" />
|
|
76
|
-
<RakeTaskImpl id="release">
|
|
77
|
-
<subtasks>
|
|
78
|
-
<RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
|
|
79
|
-
<RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
|
|
80
|
-
<RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
|
|
81
|
-
</subtasks>
|
|
82
|
-
</RakeTaskImpl>
|
|
83
|
-
</subtasks>
|
|
84
|
-
</RakeTaskImpl>
|
|
85
|
-
</option>
|
|
86
|
-
</component>
|
|
87
|
-
</module>
|