quonfig 0.0.2

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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/rules/constitution.md +81 -0
  3. data/.claude/rules/git-safety.md +11 -0
  4. data/.claude/rules/issue-tracking.md +13 -0
  5. data/.claude/rules/testing-workflow.md +28 -0
  6. data/.envrc.sample +3 -0
  7. data/.github/CODEOWNERS +2 -0
  8. data/.github/pull_request_template.md +8 -0
  9. data/.github/workflows/push_gem.yml +49 -0
  10. data/.github/workflows/ruby.yml +60 -0
  11. data/.github/workflows/test.yaml +40 -0
  12. data/.rubocop.yml +13 -0
  13. data/.tool-versions +1 -0
  14. data/CHANGELOG.md +301 -0
  15. data/CLAUDE.md +29 -0
  16. data/CODEOWNERS +1 -0
  17. data/Gemfile +26 -0
  18. data/Gemfile.lock +177 -0
  19. data/LICENSE.txt +20 -0
  20. data/README.md +213 -0
  21. data/Rakefile +64 -0
  22. data/VERSION +1 -0
  23. data/dev/allocation_stats +60 -0
  24. data/dev/benchmark +40 -0
  25. data/dev/console +12 -0
  26. data/dev/script_setup.rb +18 -0
  27. data/lib/quonfig/bound_client.rb +71 -0
  28. data/lib/quonfig/caching_http_connection.rb +95 -0
  29. data/lib/quonfig/client.rb +221 -0
  30. data/lib/quonfig/config_envelope.rb +5 -0
  31. data/lib/quonfig/config_loader.rb +103 -0
  32. data/lib/quonfig/config_store.rb +42 -0
  33. data/lib/quonfig/context.rb +101 -0
  34. data/lib/quonfig/datadir.rb +101 -0
  35. data/lib/quonfig/duration.rb +58 -0
  36. data/lib/quonfig/encryption.rb +74 -0
  37. data/lib/quonfig/error.rb +6 -0
  38. data/lib/quonfig/errors/env_var_parse_error.rb +11 -0
  39. data/lib/quonfig/errors/initialization_timeout_error.rb +12 -0
  40. data/lib/quonfig/errors/invalid_sdk_key_error.rb +19 -0
  41. data/lib/quonfig/errors/missing_default_error.rb +13 -0
  42. data/lib/quonfig/errors/missing_env_var_error.rb +11 -0
  43. data/lib/quonfig/errors/type_mismatch_error.rb +11 -0
  44. data/lib/quonfig/errors/uninitialized_error.rb +13 -0
  45. data/lib/quonfig/evaluation.rb +64 -0
  46. data/lib/quonfig/evaluator.rb +464 -0
  47. data/lib/quonfig/exponential_backoff.rb +21 -0
  48. data/lib/quonfig/fixed_size_hash.rb +14 -0
  49. data/lib/quonfig/http_connection.rb +46 -0
  50. data/lib/quonfig/internal_logger.rb +173 -0
  51. data/lib/quonfig/murmer3.rb +50 -0
  52. data/lib/quonfig/options.rb +194 -0
  53. data/lib/quonfig/periodic_sync.rb +74 -0
  54. data/lib/quonfig/quonfig.rb +58 -0
  55. data/lib/quonfig/rate_limit_cache.rb +41 -0
  56. data/lib/quonfig/reason.rb +39 -0
  57. data/lib/quonfig/resolver.rb +42 -0
  58. data/lib/quonfig/semantic_logger_filter.rb +90 -0
  59. data/lib/quonfig/semver.rb +132 -0
  60. data/lib/quonfig/sse_config_client.rb +135 -0
  61. data/lib/quonfig/time_helpers.rb +7 -0
  62. data/lib/quonfig/types.rb +56 -0
  63. data/lib/quonfig/weighted_value_resolver.rb +49 -0
  64. data/lib/quonfig.rb +57 -0
  65. data/quonfig.gemspec +149 -0
  66. data/scripts/generate_integration_tests.rb +362 -0
  67. data/test/fixtures/datafile.json +87 -0
  68. data/test/integration/test_context_precedence.rb +194 -0
  69. data/test/integration/test_datadir_environment.rb +76 -0
  70. data/test/integration/test_enabled.rb +784 -0
  71. data/test/integration/test_enabled_with_contexts.rb +94 -0
  72. data/test/integration/test_get.rb +224 -0
  73. data/test/integration/test_get_feature_flag.rb +34 -0
  74. data/test/integration/test_get_or_raise.rb +86 -0
  75. data/test/integration/test_get_weighted_values.rb +29 -0
  76. data/test/integration/test_helpers.rb +139 -0
  77. data/test/integration/test_helpers_test.rb +73 -0
  78. data/test/integration/test_post.rb +34 -0
  79. data/test/integration/test_telemetry.rb +114 -0
  80. data/test/support/common_helpers.rb +106 -0
  81. data/test/support/mock_base_client.rb +27 -0
  82. data/test/support/mock_config_loader.rb +1 -0
  83. data/test/test_bound_client.rb +109 -0
  84. data/test/test_caching_http_connection.rb +218 -0
  85. data/test/test_client.rb +255 -0
  86. data/test/test_config_loader.rb +70 -0
  87. data/test/test_context.rb +136 -0
  88. data/test/test_datadir.rb +199 -0
  89. data/test/test_duration.rb +37 -0
  90. data/test/test_encryption.rb +16 -0
  91. data/test/test_evaluator.rb +285 -0
  92. data/test/test_exponential_backoff.rb +44 -0
  93. data/test/test_fixed_size_hash.rb +119 -0
  94. data/test/test_helper.rb +17 -0
  95. data/test/test_http_connection.rb +79 -0
  96. data/test/test_internal_logger.rb +34 -0
  97. data/test/test_options.rb +167 -0
  98. data/test/test_rate_limit_cache.rb +44 -0
  99. data/test/test_reason.rb +79 -0
  100. data/test/test_rename.rb +65 -0
  101. data/test/test_resolver.rb +144 -0
  102. data/test/test_semantic_logger_filter.rb +123 -0
  103. data/test/test_semver.rb +108 -0
  104. data/test/test_sse_config_client.rb +297 -0
  105. data/test/test_typed_getters.rb +131 -0
  106. data/test/test_types.rb +141 -0
  107. data/test/test_weighted_value_resolver.rb +84 -0
  108. metadata +311 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestWeightedValueResolver < Minitest::Test
6
+ KEY = 'config_key'
7
+
8
+ def test_resolving_single_value
9
+ values = weighted_values([['abc', 1]])
10
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, nil)
11
+ assert_equal 'abc', resolver.resolve[0][:value]
12
+ assert_equal 0, resolver.resolve[1]
13
+ end
14
+
15
+ def test_resolving_multiple_values_evenly_distributed
16
+ values = weighted_values([['abc', 1], ['def', 1]])
17
+
18
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, 'user:001')
19
+ assert_equal 'abc', resolver.resolve[0][:value]
20
+ assert_equal 0, resolver.resolve[1]
21
+
22
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, 'user:456')
23
+ assert_equal 'def', resolver.resolve[0][:value]
24
+ assert_equal 1, resolver.resolve[1]
25
+ end
26
+
27
+ def test_resolving_multiple_values_unevenly_distributed
28
+ values = weighted_values([['abc', 1], ['def', 98], ['ghi', 1]])
29
+
30
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, 'user:456')
31
+ assert_equal 'def', resolver.resolve[0][:value]
32
+ assert_equal 1, resolver.resolve[1]
33
+
34
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, 'user:103')
35
+ assert_equal 'ghi', resolver.resolve[0][:value]
36
+ assert_equal 2, resolver.resolve[1]
37
+
38
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, 'user:119')
39
+ assert_equal 'abc', resolver.resolve[0][:value]
40
+ assert_equal 0, resolver.resolve[1]
41
+ end
42
+
43
+ def test_resolving_multiple_values_with_simulation
44
+ values = weighted_values([['abc', 1], ['def', 98], ['ghi', 1]])
45
+ results = {}
46
+
47
+ 10_000.times do |i|
48
+ result = Quonfig::WeightedValueResolver.new(values, KEY, "user:#{i}").resolve[0][:value]
49
+ results[result] ||= 0
50
+ results[result] += 1
51
+ end
52
+
53
+ assert_in_delta 100, results['abc'], 20
54
+ assert_in_delta 9800, results['def'], 50
55
+ assert_in_delta 100, results['ghi'], 20
56
+ end
57
+
58
+ def test_string_keyed_weighted_value_hashes
59
+ # JSON.parse without symbolize_names yields string keys; the resolver must
60
+ # accept that shape too.
61
+ values = [
62
+ { 'value' => 'abc', 'weight' => 1 },
63
+ { 'value' => 'def', 'weight' => 1 }
64
+ ]
65
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, 'user:456')
66
+ assert_equal 'def', resolver.resolve[0]['value']
67
+ assert_equal 1, resolver.resolve[1]
68
+ end
69
+
70
+ def test_all_zero_weights_returns_last_variant
71
+ values = weighted_values([['abc', 0], ['def', 0], ['ghi', 0]])
72
+ resolver = Quonfig::WeightedValueResolver.new(values, KEY, 'user:001')
73
+ assert_equal 'ghi', resolver.resolve[0][:value]
74
+ assert_equal 2, resolver.resolve[1]
75
+ end
76
+
77
+ private
78
+
79
+ def weighted_values(values_and_weights)
80
+ values_and_weights.map do |value, weight|
81
+ { value: value, weight: weight }
82
+ end
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,311 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quonfig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Dwyer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.5
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.5
33
+ - !ruby/object:Gem::Dependency
34
+ name: faraday
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: ld-eventsource
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: uuid
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: activesupport
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '4'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '4'
89
+ - !ruby/object:Gem::Dependency
90
+ name: allocation_stats
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: benchmark-ips
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: bundler
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: juwelier
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: 2.4.9
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: 2.4.9
145
+ - !ruby/object:Gem::Dependency
146
+ name: rdoc
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: simplecov
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ description: Quonfig — feature flags and live config, stored as files in git.
174
+ email: jeff@quonfig.com
175
+ executables: []
176
+ extensions: []
177
+ extra_rdoc_files:
178
+ - CHANGELOG.md
179
+ - LICENSE.txt
180
+ - README.md
181
+ files:
182
+ - ".claude/rules/constitution.md"
183
+ - ".claude/rules/git-safety.md"
184
+ - ".claude/rules/issue-tracking.md"
185
+ - ".claude/rules/testing-workflow.md"
186
+ - ".envrc.sample"
187
+ - ".github/CODEOWNERS"
188
+ - ".github/pull_request_template.md"
189
+ - ".github/workflows/push_gem.yml"
190
+ - ".github/workflows/ruby.yml"
191
+ - ".github/workflows/test.yaml"
192
+ - ".rubocop.yml"
193
+ - ".tool-versions"
194
+ - CHANGELOG.md
195
+ - CLAUDE.md
196
+ - CODEOWNERS
197
+ - Gemfile
198
+ - Gemfile.lock
199
+ - LICENSE.txt
200
+ - README.md
201
+ - Rakefile
202
+ - VERSION
203
+ - dev/allocation_stats
204
+ - dev/benchmark
205
+ - dev/console
206
+ - dev/script_setup.rb
207
+ - lib/quonfig.rb
208
+ - lib/quonfig/bound_client.rb
209
+ - lib/quonfig/caching_http_connection.rb
210
+ - lib/quonfig/client.rb
211
+ - lib/quonfig/config_envelope.rb
212
+ - lib/quonfig/config_loader.rb
213
+ - lib/quonfig/config_store.rb
214
+ - lib/quonfig/context.rb
215
+ - lib/quonfig/datadir.rb
216
+ - lib/quonfig/duration.rb
217
+ - lib/quonfig/encryption.rb
218
+ - lib/quonfig/error.rb
219
+ - lib/quonfig/errors/env_var_parse_error.rb
220
+ - lib/quonfig/errors/initialization_timeout_error.rb
221
+ - lib/quonfig/errors/invalid_sdk_key_error.rb
222
+ - lib/quonfig/errors/missing_default_error.rb
223
+ - lib/quonfig/errors/missing_env_var_error.rb
224
+ - lib/quonfig/errors/type_mismatch_error.rb
225
+ - lib/quonfig/errors/uninitialized_error.rb
226
+ - lib/quonfig/evaluation.rb
227
+ - lib/quonfig/evaluator.rb
228
+ - lib/quonfig/exponential_backoff.rb
229
+ - lib/quonfig/fixed_size_hash.rb
230
+ - lib/quonfig/http_connection.rb
231
+ - lib/quonfig/internal_logger.rb
232
+ - lib/quonfig/murmer3.rb
233
+ - lib/quonfig/options.rb
234
+ - lib/quonfig/periodic_sync.rb
235
+ - lib/quonfig/quonfig.rb
236
+ - lib/quonfig/rate_limit_cache.rb
237
+ - lib/quonfig/reason.rb
238
+ - lib/quonfig/resolver.rb
239
+ - lib/quonfig/semantic_logger_filter.rb
240
+ - lib/quonfig/semver.rb
241
+ - lib/quonfig/sse_config_client.rb
242
+ - lib/quonfig/time_helpers.rb
243
+ - lib/quonfig/types.rb
244
+ - lib/quonfig/weighted_value_resolver.rb
245
+ - quonfig.gemspec
246
+ - scripts/generate_integration_tests.rb
247
+ - test/fixtures/datafile.json
248
+ - test/integration/test_context_precedence.rb
249
+ - test/integration/test_datadir_environment.rb
250
+ - test/integration/test_enabled.rb
251
+ - test/integration/test_enabled_with_contexts.rb
252
+ - test/integration/test_get.rb
253
+ - test/integration/test_get_feature_flag.rb
254
+ - test/integration/test_get_or_raise.rb
255
+ - test/integration/test_get_weighted_values.rb
256
+ - test/integration/test_helpers.rb
257
+ - test/integration/test_helpers_test.rb
258
+ - test/integration/test_post.rb
259
+ - test/integration/test_telemetry.rb
260
+ - test/support/common_helpers.rb
261
+ - test/support/mock_base_client.rb
262
+ - test/support/mock_config_loader.rb
263
+ - test/test_bound_client.rb
264
+ - test/test_caching_http_connection.rb
265
+ - test/test_client.rb
266
+ - test/test_config_loader.rb
267
+ - test/test_context.rb
268
+ - test/test_datadir.rb
269
+ - test/test_duration.rb
270
+ - test/test_encryption.rb
271
+ - test/test_evaluator.rb
272
+ - test/test_exponential_backoff.rb
273
+ - test/test_fixed_size_hash.rb
274
+ - test/test_helper.rb
275
+ - test/test_http_connection.rb
276
+ - test/test_internal_logger.rb
277
+ - test/test_options.rb
278
+ - test/test_rate_limit_cache.rb
279
+ - test/test_reason.rb
280
+ - test/test_rename.rb
281
+ - test/test_resolver.rb
282
+ - test/test_semantic_logger_filter.rb
283
+ - test/test_semver.rb
284
+ - test/test_sse_config_client.rb
285
+ - test/test_typed_getters.rb
286
+ - test/test_types.rb
287
+ - test/test_weighted_value_resolver.rb
288
+ homepage: https://github.com/quonfig/sdk-ruby
289
+ licenses:
290
+ - MIT
291
+ metadata: {}
292
+ post_install_message:
293
+ rdoc_options: []
294
+ require_paths:
295
+ - lib
296
+ required_ruby_version: !ruby/object:Gem::Requirement
297
+ requirements:
298
+ - - ">="
299
+ - !ruby/object:Gem::Version
300
+ version: '0'
301
+ required_rubygems_version: !ruby/object:Gem::Requirement
302
+ requirements:
303
+ - - ">="
304
+ - !ruby/object:Gem::Version
305
+ version: '0'
306
+ requirements: []
307
+ rubygems_version: 3.5.22
308
+ signing_key:
309
+ specification_version: 4
310
+ summary: Quonfig Ruby SDK
311
+ test_files: []