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.
- checksums.yaml +4 -4
- data/.claude/CLAUDE.md +85 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +13 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -8
- data/README.md +306 -119
- data/examples/rails_example/.ruby-version +1 -1
- data/examples/rails_example/Dockerfile +1 -1
- data/examples/sinatra/.dockerignore +7 -0
- data/examples/sinatra/.ruby-version +1 -1
- data/examples/sinatra/Dockerfile +14 -25
- data/examples/sinatra/Gemfile +5 -4
- data/examples/sinatra/Gemfile.lock +40 -32
- data/examples/sinatra/app/application.rb +21 -9
- data/examples/sinatra/docker-compose.yaml +24 -0
- data/examples/sinatra/feature-flags.yaml +6 -0
- data/examples/sinatra/sinatra.iml +35 -14
- data/examples/sinatra/start.sh +2 -0
- data/featurehub-sdk.gemspec +4 -1
- data/lib/feature_hub/sdk/context.rb +28 -7
- data/lib/feature_hub/sdk/feature_hub_config.rb +68 -12
- data/lib/feature_hub/sdk/feature_repository.rb +52 -13
- data/lib/feature_hub/sdk/{feature_state.rb → feature_state_holder.rb} +13 -9
- data/lib/feature_hub/sdk/interceptors.rb +10 -6
- data/lib/feature_hub/sdk/internal_feature_repository.rb +7 -3
- data/lib/feature_hub/sdk/local_yaml_interceptor.rb +99 -0
- data/lib/feature_hub/sdk/local_yaml_store.rb +71 -0
- data/lib/feature_hub/sdk/poll_edge_service.rb +6 -11
- data/lib/feature_hub/sdk/raw_update_feature_listener.rb +19 -0
- data/lib/feature_hub/sdk/redis_session_store.rb +130 -0
- data/lib/feature_hub/sdk/strategy_attributes.rb +7 -0
- data/lib/feature_hub/sdk/streaming_edge_service.rb +4 -6
- data/lib/feature_hub/sdk/version.rb +2 -2
- data/lib/featurehub-sdk.rb +5 -1
- data/sig/feature_hub/featurehub.rbs +127 -28
- 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
|
-
|
|
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:
|
|
58
|
+
feature_state: FeatureState?, parent_state: FeatureStateHolder?, ctx: ClientContext?) -> void
|
|
13
59
|
|
|
14
60
|
def locked?: -> bool
|
|
15
61
|
|
|
16
|
-
def exists?: (top_feature:
|
|
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) ->
|
|
69
|
+
def with_context: (ctx: ClientContext) -> FeatureStateHolder
|
|
23
70
|
|
|
24
|
-
def update_feature_state: (feature_state:
|
|
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: () ->
|
|
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: () ->
|
|
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: ->
|
|
99
|
+
def top_feature_state: -> FeatureStateHolder
|
|
53
100
|
|
|
54
|
-
def _feature_state: ->
|
|
101
|
+
def _feature_state: -> FeatureState
|
|
55
102
|
|
|
56
|
-
def _set_feature_state: (feature_state:
|
|
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) ->
|
|
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) ->
|
|
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: (
|
|
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:
|
|
184
|
+
def notify: (status: String, data: FeatureState, ?source: String) -> void
|
|
107
185
|
end
|
|
108
186
|
|
|
109
187
|
class FeatureHubRepository < InternalFeatureRepository
|
|
110
|
-
@features: Hash[String,
|
|
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:
|
|
195
|
+
def notify: (status: String, data: FeatureState, ?source: String) -> void
|
|
118
196
|
|
|
119
|
-
def feature: (key: String) ->
|
|
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
|
|
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[
|
|
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
|
|
230
|
+
def device: (value: String | Symbol) -> ClientContext
|
|
146
231
|
|
|
147
|
-
def
|
|
232
|
+
def attribute_value: (key: String | Symbol, values: ContextAttributeValue) -> ClientContext
|
|
148
233
|
|
|
149
|
-
def
|
|
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:
|
|
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) ->
|
|
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) ->
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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/
|
|
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.
|
|
266
|
+
rubygems_version: 4.0.6
|
|
245
267
|
specification_version: 4
|
|
246
268
|
summary: FeatureHub Ruby SDK
|
|
247
269
|
test_files: []
|