statsig 1.25.1 → 1.26.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/lib/client_initialize_helpers.rb +29 -3
- data/lib/diagnostics.rb +82 -15
- data/lib/error_boundary.rb +41 -34
- data/lib/evaluator.rb +6 -5
- data/lib/hash_utils.rb +17 -0
- data/lib/network.rb +57 -46
- data/lib/spec_store.rb +109 -64
- data/lib/statsig.rb +6 -4
- data/lib/statsig_driver.rb +106 -84
- data/lib/statsig_errors.rb +1 -0
- data/lib/statsig_logger.rb +25 -15
- data/lib/statsig_options.rb +8 -0
- data/lib/ua_parser.rb +1 -0
- data/lib/uri_helper.rb +37 -0
- metadata +52 -2
data/lib/statsig_driver.rb
CHANGED
@@ -30,19 +30,19 @@ class StatsigDriver
|
|
30
30
|
end
|
31
31
|
|
32
32
|
@err_boundary = Statsig::ErrorBoundary.new(secret_key)
|
33
|
-
@err_boundary.capture(
|
34
|
-
@
|
35
|
-
@
|
33
|
+
@err_boundary.capture(task: lambda {
|
34
|
+
@diagnostics = Statsig::Diagnostics.new('initialize')
|
35
|
+
tracker = @diagnostics.track('overall')
|
36
36
|
@options = options || StatsigOptions.new
|
37
37
|
@shutdown = false
|
38
38
|
@secret_key = secret_key
|
39
39
|
@net = Statsig::Network.new(secret_key, @options)
|
40
|
-
@logger = Statsig::StatsigLogger.new(@net, @options)
|
41
|
-
@evaluator = Statsig::Evaluator.new(@net, @options, error_callback, @
|
42
|
-
|
40
|
+
@logger = Statsig::StatsigLogger.new(@net, @options, @err_boundary)
|
41
|
+
@evaluator = Statsig::Evaluator.new(@net, @options, error_callback, @diagnostics, @err_boundary, @logger)
|
42
|
+
tracker.end(success: true)
|
43
43
|
|
44
|
-
|
45
|
-
})
|
44
|
+
@logger.log_diagnostics_event(@diagnostics)
|
45
|
+
}, caller: __method__.to_s)
|
46
46
|
end
|
47
47
|
|
48
48
|
class CheckGateOptions < T::Struct
|
@@ -52,34 +52,37 @@ class StatsigDriver
|
|
52
52
|
sig { params(user: StatsigUser, gate_name: String, options: CheckGateOptions).returns(T::Boolean) }
|
53
53
|
|
54
54
|
def check_gate(user, gate_name, options = CheckGateOptions.new)
|
55
|
-
@err_boundary.capture(
|
56
|
-
|
57
|
-
|
58
|
-
res = @evaluator.check_gate(user, gate_name)
|
59
|
-
if res.nil?
|
60
|
-
res = Statsig::ConfigResult.new(gate_name)
|
61
|
-
end
|
55
|
+
@err_boundary.capture(task: lambda {
|
56
|
+
run_with_diagnostics(task: lambda {
|
57
|
+
user = verify_inputs(user, gate_name, "gate_name")
|
62
58
|
|
63
|
-
|
64
|
-
res
|
65
|
-
|
66
|
-
else
|
67
|
-
if options.log_exposure
|
68
|
-
@logger.log_gate_exposure(user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details)
|
59
|
+
res = @evaluator.check_gate(user, gate_name)
|
60
|
+
if res.nil?
|
61
|
+
res = Statsig::ConfigResult.new(gate_name)
|
69
62
|
end
|
70
|
-
end
|
71
63
|
|
72
|
-
|
73
|
-
|
64
|
+
if res == $fetch_from_server
|
65
|
+
res = check_gate_fallback(user, gate_name)
|
66
|
+
# exposure logged by the server
|
67
|
+
else
|
68
|
+
if options.log_exposure
|
69
|
+
@logger.log_gate_exposure(user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details)
|
70
|
+
end
|
71
|
+
end
|
74
72
|
|
73
|
+
res.gate_value
|
74
|
+
}, caller: __method__.to_s)
|
75
|
+
}, recover: -> { false }, caller: __method__.to_s)
|
75
76
|
end
|
76
77
|
|
77
78
|
sig { params(user: StatsigUser, gate_name: String).void }
|
78
79
|
|
79
80
|
def manually_log_gate_exposure(user, gate_name)
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
@err_boundary.capture(task: lambda {
|
82
|
+
res = @evaluator.check_gate(user, gate_name)
|
83
|
+
context = { 'is_manual_exposure' => true }
|
84
|
+
@logger.log_gate_exposure(user, gate_name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
85
|
+
})
|
83
86
|
end
|
84
87
|
|
85
88
|
class GetConfigOptions < T::Struct
|
@@ -89,10 +92,12 @@ class StatsigDriver
|
|
89
92
|
sig { params(user: StatsigUser, dynamic_config_name: String, options: GetConfigOptions).returns(DynamicConfig) }
|
90
93
|
|
91
94
|
def get_config(user, dynamic_config_name, options = GetConfigOptions.new)
|
92
|
-
@err_boundary.capture(
|
93
|
-
|
94
|
-
|
95
|
-
|
95
|
+
@err_boundary.capture(task: lambda {
|
96
|
+
run_with_diagnostics(task: lambda {
|
97
|
+
user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
|
98
|
+
get_config_impl(user, dynamic_config_name, options)
|
99
|
+
}, caller: __method__.to_s)
|
100
|
+
}, recover: -> { DynamicConfig.new(dynamic_config_name) }, caller: __method__.to_s)
|
96
101
|
end
|
97
102
|
|
98
103
|
class GetExperimentOptions < T::Struct
|
@@ -102,18 +107,22 @@ class StatsigDriver
|
|
102
107
|
sig { params(user: StatsigUser, experiment_name: String, options: GetExperimentOptions).returns(DynamicConfig) }
|
103
108
|
|
104
109
|
def get_experiment(user, experiment_name, options = GetExperimentOptions.new)
|
105
|
-
@err_boundary.capture(
|
106
|
-
|
107
|
-
|
108
|
-
|
110
|
+
@err_boundary.capture(task: lambda {
|
111
|
+
run_with_diagnostics(task: lambda {
|
112
|
+
user = verify_inputs(user, experiment_name, "experiment_name")
|
113
|
+
get_config_impl(user, experiment_name, options)
|
114
|
+
}, caller: __method__.to_s)
|
115
|
+
}, recover: -> { DynamicConfig.new(experiment_name) }, caller: __method__.to_s)
|
109
116
|
end
|
110
117
|
|
111
118
|
sig { params(user: StatsigUser, config_name: String).void }
|
112
119
|
|
113
120
|
def manually_log_config_exposure(user, config_name)
|
114
|
-
|
115
|
-
|
116
|
-
|
121
|
+
@err_boundary.capture(task: lambda {
|
122
|
+
res = @evaluator.get_config(user, config_name)
|
123
|
+
context = { 'is_manual_exposure' => true }
|
124
|
+
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
125
|
+
}, caller: __method__.to_s)
|
117
126
|
end
|
118
127
|
|
119
128
|
class GetLayerOptions < T::Struct
|
@@ -123,42 +132,44 @@ class StatsigDriver
|
|
123
132
|
sig { params(user: StatsigUser, layer_name: String, options: GetLayerOptions).returns(Layer) }
|
124
133
|
|
125
134
|
def get_layer(user, layer_name, options = GetLayerOptions.new)
|
126
|
-
@err_boundary.capture(
|
127
|
-
|
135
|
+
@err_boundary.capture(task: lambda {
|
136
|
+
run_with_diagnostics(task: lambda {
|
137
|
+
user = verify_inputs(user, layer_name, "layer_name")
|
128
138
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
139
|
+
res = @evaluator.get_layer(user, layer_name)
|
140
|
+
if res.nil?
|
141
|
+
res = Statsig::ConfigResult.new(layer_name)
|
142
|
+
end
|
133
143
|
|
134
|
-
|
135
|
-
|
136
|
-
|
144
|
+
if res == $fetch_from_server
|
145
|
+
if res.config_delegate.empty?
|
146
|
+
return Layer.new(layer_name)
|
147
|
+
end
|
148
|
+
res = get_config_fallback(user, res.config_delegate)
|
149
|
+
# exposure logged by the server
|
137
150
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
Layer.new(res.name, res.json_value, res.rule_id, exposure_log_func)
|
146
|
-
}, -> {
|
147
|
-
Layer.new(layer_name)
|
148
|
-
})
|
151
|
+
|
152
|
+
exposure_log_func = options.log_exposure ? lambda { |layer, parameter_name|
|
153
|
+
@logger.log_layer_exposure(user, layer, parameter_name, res)
|
154
|
+
} : nil
|
155
|
+
Layer.new(res.name, res.json_value, res.rule_id, exposure_log_func)
|
156
|
+
}, caller: __method__.to_s)
|
157
|
+
}, recover: lambda { Layer.new(layer_name) }, caller: __method__.to_s)
|
149
158
|
end
|
150
159
|
|
151
160
|
sig { params(user: StatsigUser, layer_name: String, parameter_name: String).void }
|
152
161
|
|
153
162
|
def manually_log_layer_parameter_exposure(user, layer_name, parameter_name)
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
163
|
+
@err_boundary.capture(task: lambda {
|
164
|
+
res = @evaluator.get_layer(user, layer_name)
|
165
|
+
layer = Layer.new(layer_name, res.json_value, res.rule_id)
|
166
|
+
context = { 'is_manual_exposure' => true }
|
167
|
+
@logger.log_layer_exposure(user, layer, parameter_name, res, context)
|
168
|
+
}, caller: __method__.to_s)
|
158
169
|
end
|
159
170
|
|
160
171
|
def log_event(user, event_name, value = nil, metadata = nil)
|
161
|
-
@err_boundary.capture(
|
172
|
+
@err_boundary.capture(task: lambda {
|
162
173
|
if !user.nil? && !user.instance_of?(StatsigUser)
|
163
174
|
raise Statsig::ValueError.new('Must provide a valid StatsigUser or nil')
|
164
175
|
end
|
@@ -171,37 +182,38 @@ class StatsigDriver
|
|
171
182
|
event.value = value
|
172
183
|
event.metadata = metadata
|
173
184
|
@logger.log_event(event)
|
174
|
-
})
|
185
|
+
}, caller: __method__.to_s)
|
175
186
|
end
|
176
187
|
|
177
188
|
def shutdown
|
178
|
-
@err_boundary.capture(
|
189
|
+
@err_boundary.capture(task: lambda {
|
179
190
|
@shutdown = true
|
180
191
|
@logger.shutdown
|
181
192
|
@evaluator.shutdown
|
182
|
-
})
|
193
|
+
}, caller: __method__.to_s)
|
183
194
|
end
|
184
195
|
|
185
196
|
def override_gate(gate_name, gate_value)
|
186
|
-
@err_boundary.capture(
|
197
|
+
@err_boundary.capture(task: lambda {
|
187
198
|
@evaluator.override_gate(gate_name, gate_value)
|
188
|
-
})
|
199
|
+
}, caller: __method__.to_s)
|
189
200
|
end
|
190
201
|
|
191
202
|
def override_config(config_name, config_value)
|
192
|
-
@err_boundary.capture(
|
203
|
+
@err_boundary.capture(task: lambda {
|
193
204
|
@evaluator.override_config(config_name, config_value)
|
194
|
-
})
|
205
|
+
}, caller: __method__.to_s)
|
195
206
|
end
|
196
207
|
|
197
208
|
# @param [StatsigUser] user
|
209
|
+
# @param [String | nil] client_sdk_key
|
198
210
|
# @return [Hash]
|
199
|
-
def get_client_initialize_response(user)
|
200
|
-
@err_boundary.capture(
|
211
|
+
def get_client_initialize_response(user, hash, client_sdk_key)
|
212
|
+
@err_boundary.capture(task: lambda {
|
201
213
|
validate_user(user)
|
202
214
|
normalize_user(user)
|
203
|
-
@evaluator.get_client_initialize_response(user)
|
204
|
-
}, -> { nil })
|
215
|
+
@evaluator.get_client_initialize_response(user, hash, client_sdk_key)
|
216
|
+
}, recover: -> { nil }, caller: __method__.to_s)
|
205
217
|
end
|
206
218
|
|
207
219
|
def maybe_restart_background_threads
|
@@ -209,14 +221,32 @@ class StatsigDriver
|
|
209
221
|
return
|
210
222
|
end
|
211
223
|
|
212
|
-
@err_boundary.capture(
|
224
|
+
@err_boundary.capture(task: lambda {
|
213
225
|
@evaluator.maybe_restart_background_threads
|
214
226
|
@logger.maybe_restart_background_threads
|
215
|
-
})
|
227
|
+
}, caller: __method__.to_s)
|
216
228
|
end
|
217
229
|
|
218
230
|
private
|
219
231
|
|
232
|
+
def run_with_diagnostics(task:, caller:)
|
233
|
+
diagnostics = nil
|
234
|
+
if Statsig::Diagnostics::API_CALL_KEYS.include?(caller) && Statsig::Diagnostics.sample(10_000)
|
235
|
+
diagnostics = Statsig::Diagnostics.new('api_call')
|
236
|
+
tracker = diagnostics.track(caller)
|
237
|
+
end
|
238
|
+
begin
|
239
|
+
res = task.call
|
240
|
+
tracker&.end(success: true)
|
241
|
+
rescue StandardError => e
|
242
|
+
tracker&.end(success: false)
|
243
|
+
raise e
|
244
|
+
ensure
|
245
|
+
@logger.log_diagnostics_event(diagnostics)
|
246
|
+
end
|
247
|
+
return res
|
248
|
+
end
|
249
|
+
|
220
250
|
sig { params(user: StatsigUser, config_name: String, variable_name: String).returns(StatsigUser) }
|
221
251
|
|
222
252
|
def verify_inputs(user, config_name, variable_name)
|
@@ -302,12 +332,4 @@ class StatsigDriver
|
|
302
332
|
network_result['rule_id'],
|
303
333
|
)
|
304
334
|
end
|
305
|
-
|
306
|
-
def log_init_diagnostics
|
307
|
-
if @options.disable_diagnostics_logging
|
308
|
-
return
|
309
|
-
end
|
310
|
-
|
311
|
-
@logger.log_diagnostics_event(@init_diagnostics)
|
312
|
-
end
|
313
335
|
end
|
data/lib/statsig_errors.rb
CHANGED
data/lib/statsig_logger.rb
CHANGED
@@ -9,7 +9,7 @@ $diagnostics_event = 'statsig::diagnostics'
|
|
9
9
|
$ignored_metadata_keys = ['serverTime', 'configSyncTime', 'initTime', 'reason']
|
10
10
|
module Statsig
|
11
11
|
class StatsigLogger
|
12
|
-
def initialize(network, options)
|
12
|
+
def initialize(network, options, error_boundary)
|
13
13
|
@network = network
|
14
14
|
@events = []
|
15
15
|
@options = options
|
@@ -23,9 +23,11 @@ module Statsig
|
|
23
23
|
fallback_policy: :discard
|
24
24
|
)
|
25
25
|
|
26
|
+
@error_boundary = error_boundary
|
26
27
|
@background_flush = periodic_flush
|
27
28
|
@deduper = Concurrent::Set.new()
|
28
29
|
@interval = 0
|
30
|
+
@flush_mutex = Mutex.new
|
29
31
|
end
|
30
32
|
|
31
33
|
def log_event(event)
|
@@ -97,20 +99,26 @@ module Statsig
|
|
97
99
|
end
|
98
100
|
|
99
101
|
def log_diagnostics_event(diagnostics, user = nil)
|
102
|
+
return if @options.disable_diagnostics_logging
|
103
|
+
return if diagnostics.nil? || diagnostics.markers.empty?
|
104
|
+
|
100
105
|
event = StatsigEvent.new($diagnostics_event)
|
101
106
|
event.user = user
|
102
107
|
event.metadata = diagnostics.serialize
|
103
108
|
log_event(event)
|
109
|
+
diagnostics.clear_markers
|
104
110
|
end
|
105
111
|
|
106
112
|
def periodic_flush
|
107
113
|
Thread.new do
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
+
@error_boundary.capture(task: lambda {
|
115
|
+
loop do
|
116
|
+
sleep @options.logging_interval_seconds
|
117
|
+
flush_async
|
118
|
+
@interval += 1
|
119
|
+
@deduper.clear if @interval % 2 == 0
|
120
|
+
end
|
121
|
+
})
|
114
122
|
end
|
115
123
|
end
|
116
124
|
|
@@ -128,18 +136,20 @@ module Statsig
|
|
128
136
|
end
|
129
137
|
|
130
138
|
def flush
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@events = []
|
136
|
-
flush_events = events_clone.map { |e| e.serialize }
|
139
|
+
@flush_mutex.synchronize do
|
140
|
+
if @events.length.zero?
|
141
|
+
return
|
142
|
+
end
|
137
143
|
|
138
|
-
|
144
|
+
events_clone = @events
|
145
|
+
@events = []
|
146
|
+
flush_events = events_clone.map { |e| e.serialize }
|
147
|
+
@network.post_logs(flush_events)
|
148
|
+
end
|
139
149
|
end
|
140
150
|
|
141
151
|
def maybe_restart_background_threads
|
142
|
-
if @background_flush.nil?
|
152
|
+
if @background_flush.nil? || !@background_flush.alive?
|
143
153
|
@background_flush = periodic_flush
|
144
154
|
end
|
145
155
|
end
|
data/lib/statsig_options.rb
CHANGED
@@ -19,6 +19,11 @@ class StatsigOptions
|
|
19
19
|
# default: https://statsigapi.net/v1
|
20
20
|
attr_accessor :api_url_base
|
21
21
|
|
22
|
+
# The base url used specifically to call download_config_specs.
|
23
|
+
# Takes precedence over api_url_base
|
24
|
+
sig { returns(T.any(String, NilClass)) }
|
25
|
+
attr_accessor :api_url_download_config_specs
|
26
|
+
|
22
27
|
sig { returns(T.any(Float, Integer)) }
|
23
28
|
# The interval (in seconds) to poll for changes to your Statsig configuration
|
24
29
|
# default: 10s
|
@@ -97,6 +102,7 @@ class StatsigOptions
|
|
97
102
|
params(
|
98
103
|
environment: T.any(T::Hash[String, String], NilClass),
|
99
104
|
api_url_base: String,
|
105
|
+
api_url_download_config_specs: T.any(String, NilClass),
|
100
106
|
rulesets_sync_interval: T.any(Float, Integer),
|
101
107
|
idlists_sync_interval: T.any(Float, Integer),
|
102
108
|
logging_interval_seconds: T.any(Float, Integer),
|
@@ -118,6 +124,7 @@ class StatsigOptions
|
|
118
124
|
def initialize(
|
119
125
|
environment = nil,
|
120
126
|
api_url_base = 'https://statsigapi.net/v1',
|
127
|
+
api_url_download_config_specs: nil,
|
121
128
|
rulesets_sync_interval: 10,
|
122
129
|
idlists_sync_interval: 60,
|
123
130
|
logging_interval_seconds: 60,
|
@@ -135,6 +142,7 @@ class StatsigOptions
|
|
135
142
|
post_logs_retry_backoff: nil)
|
136
143
|
@environment = environment.is_a?(Hash) ? environment : nil
|
137
144
|
@api_url_base = api_url_base
|
145
|
+
@api_url_download_config_specs = api_url_download_config_specs
|
138
146
|
@rulesets_sync_interval = rulesets_sync_interval
|
139
147
|
@idlists_sync_interval = idlists_sync_interval
|
140
148
|
@logging_interval_seconds = logging_interval_seconds
|
data/lib/ua_parser.rb
CHANGED
data/lib/uri_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
class URIHelper
|
6
|
+
class URIBuilder
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(StatsigOptions) }
|
10
|
+
attr_accessor :options
|
11
|
+
|
12
|
+
sig { params(options: StatsigOptions).void }
|
13
|
+
def initialize(options)
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(endpoint: String).returns(String) }
|
18
|
+
def build_url(endpoint)
|
19
|
+
api = @options.api_url_base
|
20
|
+
if endpoint == 'download_config_specs' && !@options.api_url_download_config_specs.nil?
|
21
|
+
api = T.must(@options.api_url_download_config_specs)
|
22
|
+
end
|
23
|
+
unless api.end_with?('/')
|
24
|
+
api += '/'
|
25
|
+
end
|
26
|
+
"#{api}#{endpoint}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.initialize(options)
|
31
|
+
@uri_builder = URIBuilder.new(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.build_url(endpoint)
|
35
|
+
@uri_builder.build_url(endpoint)
|
36
|
+
end
|
37
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: statsig
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.26.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Statsig, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 5.14.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-suite
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.0.3
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: spy
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -226,6 +254,26 @@ dependencies:
|
|
226
254
|
- - "<"
|
227
255
|
- !ruby/object:Gem::Version
|
228
256
|
version: '6.0'
|
257
|
+
- !ruby/object:Gem::Dependency
|
258
|
+
name: connection_pool
|
259
|
+
requirement: !ruby/object:Gem::Requirement
|
260
|
+
requirements:
|
261
|
+
- - "~>"
|
262
|
+
- !ruby/object:Gem::Version
|
263
|
+
version: '2.4'
|
264
|
+
- - ">="
|
265
|
+
- !ruby/object:Gem::Version
|
266
|
+
version: 2.4.1
|
267
|
+
type: :runtime
|
268
|
+
prerelease: false
|
269
|
+
version_requirements: !ruby/object:Gem::Requirement
|
270
|
+
requirements:
|
271
|
+
- - "~>"
|
272
|
+
- !ruby/object:Gem::Version
|
273
|
+
version: '2.4'
|
274
|
+
- - ">="
|
275
|
+
- !ruby/object:Gem::Version
|
276
|
+
version: 2.4.1
|
229
277
|
- !ruby/object:Gem::Dependency
|
230
278
|
name: ip3country
|
231
279
|
requirement: !ruby/object:Gem::Requirement
|
@@ -282,6 +330,7 @@ files:
|
|
282
330
|
- lib/evaluation_details.rb
|
283
331
|
- lib/evaluation_helpers.rb
|
284
332
|
- lib/evaluator.rb
|
333
|
+
- lib/hash_utils.rb
|
285
334
|
- lib/id_list.rb
|
286
335
|
- lib/interfaces/data_store.rb
|
287
336
|
- lib/layer.rb
|
@@ -295,6 +344,7 @@ files:
|
|
295
344
|
- lib/statsig_options.rb
|
296
345
|
- lib/statsig_user.rb
|
297
346
|
- lib/ua_parser.rb
|
347
|
+
- lib/uri_helper.rb
|
298
348
|
homepage: https://rubygems.org/gems/statsig
|
299
349
|
licenses:
|
300
350
|
- ISC
|