featurehub-sdk 1.3.0 → 2.0.0

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/CLAUDE.md +85 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +13 -1
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +20 -8
  7. data/README.md +306 -119
  8. data/examples/rails_example/.ruby-version +1 -1
  9. data/examples/rails_example/Dockerfile +1 -1
  10. data/examples/sinatra/.dockerignore +7 -0
  11. data/examples/sinatra/.ruby-version +1 -1
  12. data/examples/sinatra/Dockerfile +14 -25
  13. data/examples/sinatra/Gemfile +5 -4
  14. data/examples/sinatra/Gemfile.lock +40 -32
  15. data/examples/sinatra/app/application.rb +21 -9
  16. data/examples/sinatra/docker-compose.yaml +24 -0
  17. data/examples/sinatra/feature-flags.yaml +6 -0
  18. data/examples/sinatra/sinatra.iml +35 -14
  19. data/examples/sinatra/start.sh +2 -0
  20. data/featurehub-sdk.gemspec +4 -1
  21. data/lib/feature_hub/sdk/context.rb +28 -7
  22. data/lib/feature_hub/sdk/feature_hub_config.rb +68 -12
  23. data/lib/feature_hub/sdk/feature_repository.rb +52 -13
  24. data/lib/feature_hub/sdk/{feature_state.rb → feature_state_holder.rb} +13 -9
  25. data/lib/feature_hub/sdk/interceptors.rb +10 -6
  26. data/lib/feature_hub/sdk/internal_feature_repository.rb +7 -3
  27. data/lib/feature_hub/sdk/local_yaml_interceptor.rb +99 -0
  28. data/lib/feature_hub/sdk/local_yaml_store.rb +71 -0
  29. data/lib/feature_hub/sdk/poll_edge_service.rb +6 -11
  30. data/lib/feature_hub/sdk/raw_update_feature_listener.rb +19 -0
  31. data/lib/feature_hub/sdk/redis_session_store.rb +130 -0
  32. data/lib/feature_hub/sdk/strategy_attributes.rb +7 -0
  33. data/lib/feature_hub/sdk/streaming_edge_service.rb +4 -6
  34. data/lib/feature_hub/sdk/version.rb +2 -2
  35. data/lib/featurehub-sdk.rb +5 -1
  36. data/sig/feature_hub/featurehub.rbs +127 -28
  37. metadata +27 -5
@@ -2,32 +2,79 @@ module FeatureHub
2
2
  module Sdk
3
3
  VERSION: String
4
4
 
5
+ type RolloutStrategyAttributeConditional = "EQUALS" | "ENDS_WITH" | "STARTS_WITH" | "GREATER" |
6
+ "GREATER_EQUALS" | "LESS" | "LESS_EQUALS" | "NOT_EQUALS" | "INCLUDES" | "EXCLUDES" | "REGEX"
7
+
8
+ type RolloutStrategyFieldType = "STRING" | "SEMANTIC_VERSION" | "NUMBER" | "DATE" | "DATETIME" |
9
+ "BOOLEAN" | "IP_ADDRESS"
10
+
11
+ type FeatureRolloutStrategyAttributeHash = {
12
+ "id" => String?,
13
+ "conditional" => RolloutStrategyAttributeConditional,
14
+ "fieldName" => String,
15
+ "values" => Array[(bool | String | Float)?],
16
+ "type" => RolloutStrategyFieldType
17
+ }
18
+
19
+ type FeatureRolloutStrategyHash = {
20
+ "id" => String,
21
+ "percentage" => Integer?,
22
+ "percentageAttributes" => Array[String]?,
23
+ "attributes" => Array[FeatureRolloutStrategyAttributeHash]?,
24
+ "value" => (bool | String | Float)?
25
+ }
26
+
27
+ type FeatureState = {
28
+ "id" => String,
29
+ "key" => String,
30
+ "l" => bool?,
31
+ "version" => Integer,
32
+ "type" => "STRING" | "BOOLEAN" | "NUMBER" | "JSON",
33
+ "value" => (bool | String | Float)?,
34
+ "v" => String?,
35
+ "fp" => Hash[String, String]?,
36
+ "environmentId" => String?,
37
+ "strategies" => Array[FeatureRolloutStrategyHash]?
38
+ }
39
+
40
+ type ContextAttributeScalar = String | bool | Float
41
+ type ContextAttributeValue = ContextAttributeScalar | Array[String] | Array[bool] | Array[Float]
42
+ type ContextAttributes = Hash[Symbol, Array[ContextAttributeScalar]]
43
+
5
44
  # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
45
  #
7
46
 
8
- class FeatureState
47
+ module FeatureValueType
48
+ BOOLEAN: String
49
+ JSON: String
50
+ STRING: String
51
+ NUMBER: String
52
+ end
53
+
54
+ class FeatureStateHolder
9
55
  attr_reader key: Symbol
10
56
 
11
57
  def initialize: (key: String, repo: InternalFeatureRepository,
12
- feature_state: Hash[untyped, untyped]?, parent_state: FeatureState?, ctx: ClientContext?) -> void
58
+ feature_state: FeatureState?, parent_state: FeatureStateHolder?, ctx: ClientContext?) -> void
13
59
 
14
60
  def locked?: -> bool
15
61
 
16
- def exists?: (top_feature: FeatureState?) -> bool
62
+ def exists?: (top_feature: FeatureStateHolder?) -> bool
63
+ def present?: () -> bool
17
64
 
18
65
  def id: -> String?
19
66
 
20
67
  def feature_type: -> String?
21
68
 
22
- def with_context: (ctx: ClientContext) -> FeatureState
69
+ def with_context: (ctx: ClientContext) -> FeatureStateHolder
23
70
 
24
- def update_feature_state: (feature_state: Hash[untyped, untyped]) -> void
71
+ def update_feature_state: (feature_state: FeatureState) -> void
25
72
 
26
73
  # this is the feature state of the top level, it always walks up
27
- def feature_state: () -> Hash[untyped, untyped]
74
+ def feature_state: () -> FeatureState
28
75
 
29
76
  # this is strictly the internal feature state, further down from the top level it will be nil
30
- def internal_feature_state: () -> Hash[untyped, untyped]
77
+ def internal_feature_state: () -> FeatureState
31
78
 
32
79
  def value: () -> [bool? | String? | Float?]
33
80
 
@@ -49,11 +96,11 @@ module FeatureHub
49
96
 
50
97
  private
51
98
 
52
- def top_feature_state: -> FeatureState
99
+ def top_feature_state: -> FeatureStateHolder
53
100
 
54
- def _feature_state: -> Hash[untyped, untyped]
101
+ def _feature_state: -> FeatureState
55
102
 
56
- def _set_feature_state: (feature_state: Hash[untyped, untyped]) -> void
103
+ def _set_feature_state: (feature_state: FeatureState) -> void
57
104
 
58
105
  def get_value: (feature_type: String?) -> [bool? | String? | Float?]
59
106
  end
@@ -72,12 +119,42 @@ module FeatureHub
72
119
  end
73
120
 
74
121
  class ValueInterceptor
75
- def intercepted_value: (feature_key: Symbol) -> InterceptorValue?
122
+ def intercepted_value: (feature_key: Symbol, repository: FeatureHubRepository, feature_state: FeatureStateHolder?) -> [bool, (bool? | String? | Float?)]
123
+
124
+ def close: () -> void
125
+ end
126
+
127
+ class LocalYamlValueInterceptor < ValueInterceptor
128
+ def initialize: (?{ filename: String?, watch: bool?, watch_interval: Integer?, logger: untyped }? opts) -> void
76
129
  end
77
130
 
78
131
  class EnvironmentInterceptor < ValueInterceptor
79
132
  end
80
133
 
134
+ class RawUpdateFeatureListener
135
+ def delete_feature: (feature: FeatureState, source: String) -> void
136
+
137
+ def process_updates: (features: Array[FeatureState], source: String) -> void
138
+
139
+ def process_update: (feature: FeatureState, source: String) -> void
140
+
141
+ def close: () -> void
142
+
143
+ def config_changed: () -> void
144
+ end
145
+
146
+ class LocalYamlStore < RawUpdateFeatureListener
147
+ SOURCE: String
148
+
149
+ def initialize: (repository: FeatureHubRepository, ?filename: String?) -> void
150
+ end
151
+
152
+ class RedisSessionStore < RawUpdateFeatureListener
153
+ SOURCE: String
154
+
155
+ def initialize: (connection_string: String, repository: FeatureHubRepository, ?opts: { namespace: Integer?, prefix: String?, timeout: Integer?, password: String?, logger: untyped }?) -> void
156
+ end
157
+
81
158
  class PercentageCalculator
82
159
  def determine_client_percentage: (percentage_text: String, feature_id: String) -> int
83
160
  end
@@ -93,9 +170,10 @@ module FeatureHub
93
170
  end
94
171
 
95
172
  class InternalFeatureRepository
96
- def feature: (key: String) -> FeatureState?
173
+ def feature: (key: String, ?attrs: Hash[untyped, ContextAttributeValue]?) -> FeatureStateHolder?
174
+ def value: (key: String, ?default_value: (bool? | String? | Float?), ?attrs: Hash[untyped, ContextAttributeValue]?) -> (bool? | String? | Float?)
97
175
 
98
- def find_interceptor: (feature_value: String) -> InterceptorValue?
176
+ def find_interceptor: (feature_key: Symbol, feature_state: FeatureStateHolder?) -> [bool, (bool? | String? | Float?)]
99
177
 
100
178
  def ready?: -> bool
101
179
 
@@ -103,36 +181,41 @@ module FeatureHub
103
181
 
104
182
  def apply: (strategies: Array[RolloutStrategy], key: String, feature_id: String, context: ClientContext) -> Applied
105
183
 
106
- def notify: (status: String, data: Hash[untyped, untyped]) -> void
184
+ def notify: (status: String, data: FeatureState, ?source: String) -> void
107
185
  end
108
186
 
109
187
  class FeatureHubRepository < InternalFeatureRepository
110
- @features: Hash[String, FeatureState]
188
+ @features: Hash[String, FeatureStateHolder]
111
189
  @ready: bool
112
190
 
113
191
  def initialize: (apply_features: nil | ApplyFeatures) -> void
114
192
 
115
193
  def apply: (strategies: Array[RolloutStrategy], key: String, feature_id: String, context: ClientContext) -> Applied
116
194
 
117
- def notify: (status: String, data: Hash[untyped, untyped]) -> void
195
+ def notify: (status: String, data: FeatureState, ?source: String) -> void
118
196
 
119
- def feature: (key: String) -> FeatureState
197
+ def feature: (key: String, ?attrs: Hash[untyped, ContextAttributeValue]?) -> FeatureStateHolder
198
+ def value: (key: String, ?default_value: (bool? | String? | Float?), ?attrs: Hash[untyped, ContextAttributeValue]?) -> (bool? | String? | Float?)
120
199
 
121
200
  def register_interceptor: (interceptor: ValueInterceptor) -> void
122
201
 
123
- def find_interceptor: (feature_value: String) -> InterceptorValue?
202
+ def register_raw_update_listener: (listener: RawUpdateFeatureListener) -> void
203
+
204
+ def find_interceptor: (feature_key: Symbol, feature_state: FeatureStateHolder?) -> [bool, (bool? | String? | Float?)]
205
+
206
+ def close: () -> void
124
207
 
125
208
  def ready?: -> bool
126
209
 
127
210
  def not_ready!: -> void
128
211
 
129
- def extract_feature_state: -> Array[Hash[untyped, untyped]]
212
+ def extract_feature_state: -> Array[FeatureState]
130
213
  end
131
214
 
132
215
  class ClientContext
133
216
  attr_reader repo: InternalFeatureRepository
134
217
 
135
- def initialize: (repository: InternalFeatureRepository) -> void
218
+ def initialize: (repository: InternalFeatureRepository, ?attrs: Hash[untyped, ContextAttributeValue]?) -> void
136
219
 
137
220
  def user_key: (value: String) -> ClientContext
138
221
 
@@ -140,23 +223,25 @@ module FeatureHub
140
223
 
141
224
  def version: (value: String) -> ClientContext
142
225
 
143
- def country: (value: Symbol) -> ClientContext
226
+ def country: (value: String | Symbol) -> ClientContext
227
+
228
+ def platform: (value: String | Symbol) -> ClientContext
144
229
 
145
- def platform: (value: Symbol) -> ClientContext
230
+ def device: (value: String | Symbol) -> ClientContext
146
231
 
147
- def device: (value: Symbol) -> ClientContext
232
+ def attribute_value: (key: String | Symbol, values: ContextAttributeValue) -> ClientContext
148
233
 
149
- def attribute_value: (key: String, values: Array[String]) -> ClientContext
234
+ def assign: (attrs: Hash[untyped, ContextAttributeValue]) -> ClientContext
150
235
 
151
236
  def clear: -> ClientContext
152
237
 
153
- def get_attr: (key: String, default_val: String?) -> Array[String]
238
+ def get_attr: (key: String | Symbol, ?default_val: ContextAttributeScalar?) -> Array[ContextAttributeScalar]
154
239
 
155
240
  def default_percentage_key: -> String?
156
241
 
157
242
  def enabled: (key: String) -> bool?
158
243
 
159
- def feature: (key: String) -> FeatureState
244
+ def feature: (key: String) -> FeatureStateHolder
160
245
 
161
246
  def set?: (key: String) -> bool?
162
247
 
@@ -164,7 +249,7 @@ module FeatureHub
164
249
 
165
250
  def string: (key: String) -> String?
166
251
 
167
- def json: (key: String) -> Hash[untyped, untyped]?
252
+ def json: (key: String) -> FeatureState?
168
253
 
169
254
  def raw_json: (key: String) -> String?
170
255
 
@@ -182,7 +267,14 @@ module FeatureHub
182
267
  end
183
268
 
184
269
  class EdgeService
185
- def initialize: (repository: InternalFeatureRepository, api_keys: Array[String], edge_url: String) -> void
270
+ @repository: InternalFeatureRepository?
271
+ @api_keys: Array[String]
272
+ @edge_url: String?
273
+ @logger: untyped
274
+
275
+ attr_reader repository: InternalFeatureRepository?
276
+
277
+ def initialize: (InternalFeatureRepository?, Array[String], String?, ?untyped) -> void
186
278
 
187
279
  def poll: -> void
188
280
 
@@ -221,6 +313,13 @@ module FeatureHub
221
313
  #
222
314
  def use_polling_edge_service: (interval: int?) -> void
223
315
 
316
+ def register_interceptor: (interceptor: ValueInterceptor) -> void
317
+
318
+ def register_raw_update_listener: (listener: RawUpdateFeatureListener) -> void
319
+
320
+ def feature: (key: String, ?attrs: Hash[untyped, ContextAttributeValue]?) -> FeatureStateHolder
321
+ def value: (key: String, ?default_value: (bool? | String? | Float?), ?attrs: Hash[untyped, ContextAttributeValue]?) -> (bool? | String? | Float?)
322
+
224
323
  def new_context: -> ClientContext
225
324
 
226
325
  def close: -> void
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: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Vowles
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 2.3.0
47
+ version: 2.5.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 2.3.0
54
+ version: 2.5.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: murmurhash3
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 2.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: redis
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5'
83
97
  description: FeatureHub Ruby SDK
84
98
  email:
85
99
  - richard@bluetrainsoftware.com
@@ -87,6 +101,7 @@ executables: []
87
101
  extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
104
+ - ".claude/CLAUDE.md"
90
105
  - ".rspec"
91
106
  - ".rubocop.yml"
92
107
  - ".ruby-version"
@@ -183,6 +198,7 @@ files:
183
198
  - examples/rails_example/tmp/storage/.keep
184
199
  - examples/rails_example/vendor/.keep
185
200
  - examples/rails_example/vendor/javascript/.keep
201
+ - examples/sinatra/.dockerignore
186
202
  - examples/sinatra/.gitignore
187
203
  - examples/sinatra/.ruby-version
188
204
  - examples/sinatra/Dockerfile
@@ -194,7 +210,9 @@ files:
194
210
  - examples/sinatra/conf/nginx.conf
195
211
  - examples/sinatra/conf/nsswitch.conf
196
212
  - examples/sinatra/config.ru
213
+ - examples/sinatra/docker-compose.yaml
197
214
  - examples/sinatra/docker_start.sh
215
+ - examples/sinatra/feature-flags.yaml
198
216
  - examples/sinatra/sinatra.iml
199
217
  - examples/sinatra/start.sh
200
218
  - examples/sinatra/thin.ru
@@ -204,15 +222,19 @@ files:
204
222
  - lib/feature_hub/sdk/context.rb
205
223
  - lib/feature_hub/sdk/feature_hub_config.rb
206
224
  - lib/feature_hub/sdk/feature_repository.rb
207
- - lib/feature_hub/sdk/feature_state.rb
225
+ - lib/feature_hub/sdk/feature_state_holder.rb
208
226
  - lib/feature_hub/sdk/impl/apply_features.rb
209
227
  - lib/feature_hub/sdk/impl/murmur3_percentage.rb
210
228
  - lib/feature_hub/sdk/impl/rollout_holders.rb
211
229
  - lib/feature_hub/sdk/impl/strategy_wrappers.rb
212
230
  - lib/feature_hub/sdk/interceptors.rb
213
231
  - lib/feature_hub/sdk/internal_feature_repository.rb
232
+ - lib/feature_hub/sdk/local_yaml_interceptor.rb
233
+ - lib/feature_hub/sdk/local_yaml_store.rb
214
234
  - lib/feature_hub/sdk/percentage_calc.rb
215
235
  - lib/feature_hub/sdk/poll_edge_service.rb
236
+ - lib/feature_hub/sdk/raw_update_feature_listener.rb
237
+ - lib/feature_hub/sdk/redis_session_store.rb
216
238
  - lib/feature_hub/sdk/strategy_attributes.rb
217
239
  - lib/feature_hub/sdk/streaming_edge_service.rb
218
240
  - lib/feature_hub/sdk/version.rb
@@ -241,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
241
263
  - !ruby/object:Gem::Version
242
264
  version: '0'
243
265
  requirements: []
244
- rubygems_version: 4.0.3
266
+ rubygems_version: 4.0.6
245
267
  specification_version: 4
246
268
  summary: FeatureHub Ruby SDK
247
269
  test_files: []