togglecraft 1.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.
@@ -0,0 +1,506 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ToggleCraft::Utils do
4
+ describe '.hash_key' do
5
+ it 'returns a value between 0 and 100' do
6
+ result = described_class.hash_key('test-key')
7
+ expect(result).to be >= 0
8
+ expect(result).to be < 100
9
+ end
10
+
11
+ it 'returns consistent results for the same key' do
12
+ key = 'user-123:new-feature'
13
+ result1 = described_class.hash_key(key)
14
+ result2 = described_class.hash_key(key)
15
+ expect(result1).to eq(result2)
16
+ end
17
+
18
+ it 'returns different results for different keys' do
19
+ result1 = described_class.hash_key('key1')
20
+ result2 = described_class.hash_key('key2')
21
+ expect(result1).not_to eq(result2)
22
+ end
23
+
24
+ it 'handles empty strings' do
25
+ result = described_class.hash_key('')
26
+ expect(result).to be >= 0
27
+ expect(result).to be < 100
28
+ end
29
+
30
+ it 'handles special characters' do
31
+ result = described_class.hash_key('user@example.com:flag-123')
32
+ expect(result).to be >= 0
33
+ expect(result).to be < 100
34
+ end
35
+
36
+ # Test known values to ensure consistency with JS implementation
37
+ it 'matches JavaScript implementation for known values' do
38
+ # These values should match the JavaScript implementation
39
+ # Testing the hash algorithm produces consistent results
40
+ result = described_class.hash_key('test')
41
+ expect(result).to be_a(Integer)
42
+ expect(result).to be >= 0
43
+ expect(result).to be < 100
44
+ end
45
+ end
46
+
47
+ describe '.get_nested_property' do
48
+ let(:obj) do
49
+ {
50
+ user: {
51
+ id: '123',
52
+ email: 'test@example.com',
53
+ profile: {
54
+ name: 'Test User'
55
+ }
56
+ },
57
+ request: {
58
+ ip: '192.168.1.1'
59
+ }
60
+ }
61
+ end
62
+
63
+ it 'retrieves top-level properties with symbol keys' do
64
+ result = described_class.get_nested_property(obj, 'user')
65
+ expect(result).to eq(obj[:user])
66
+ end
67
+
68
+ it 'retrieves nested properties with dot notation' do
69
+ result = described_class.get_nested_property(obj, 'user.id')
70
+ expect(result).to eq('123')
71
+ end
72
+
73
+ it 'retrieves deeply nested properties' do
74
+ result = described_class.get_nested_property(obj, 'user.profile.name')
75
+ expect(result).to eq('Test User')
76
+ end
77
+
78
+ it 'works with string keys in the hash' do
79
+ string_obj = {
80
+ 'user' => {
81
+ 'id' => '456'
82
+ }
83
+ }
84
+ result = described_class.get_nested_property(string_obj, 'user.id')
85
+ expect(result).to eq('456')
86
+ end
87
+
88
+ it 'returns nil for missing properties' do
89
+ result = described_class.get_nested_property(obj, 'user.missing')
90
+ expect(result).to be_nil
91
+ end
92
+
93
+ it 'returns nil for missing nested properties' do
94
+ result = described_class.get_nested_property(obj, 'user.profile.missing.deeply')
95
+ expect(result).to be_nil
96
+ end
97
+
98
+ it 'returns nil when obj is nil' do
99
+ result = described_class.get_nested_property(nil, 'user.id')
100
+ expect(result).to be_nil
101
+ end
102
+
103
+ it 'returns nil when path is nil' do
104
+ result = described_class.get_nested_property(obj, nil)
105
+ expect(result).to be_nil
106
+ end
107
+
108
+ it 'returns nil when path is empty' do
109
+ result = described_class.get_nested_property(obj, '')
110
+ expect(result).to be_nil
111
+ end
112
+
113
+ it 'handles mixed symbol and string keys' do
114
+ mixed_obj = {
115
+ user: {
116
+ 'email' => 'test@example.com'
117
+ }
118
+ }
119
+ result = described_class.get_nested_property(mixed_obj, 'user.email')
120
+ expect(result).to eq('test@example.com')
121
+ end
122
+ end
123
+
124
+ describe '.compare_semver' do
125
+ it 'returns 0 for equal versions' do
126
+ expect(described_class.compare_semver('1.0.0', '1.0.0')).to eq(0)
127
+ expect(described_class.compare_semver('2.5.3', '2.5.3')).to eq(0)
128
+ end
129
+
130
+ it 'returns 1 when first version is greater' do
131
+ expect(described_class.compare_semver('2.0.0', '1.0.0')).to eq(1)
132
+ expect(described_class.compare_semver('1.2.0', '1.1.0')).to eq(1)
133
+ expect(described_class.compare_semver('1.0.2', '1.0.1')).to eq(1)
134
+ end
135
+
136
+ it 'returns -1 when first version is less' do
137
+ expect(described_class.compare_semver('1.0.0', '2.0.0')).to eq(-1)
138
+ expect(described_class.compare_semver('1.1.0', '1.2.0')).to eq(-1)
139
+ expect(described_class.compare_semver('1.0.1', '1.0.2')).to eq(-1)
140
+ end
141
+
142
+ it 'handles versions with different part counts' do
143
+ expect(described_class.compare_semver('1.0', '1.0.0')).to eq(0)
144
+ expect(described_class.compare_semver('1', '1.0.0')).to eq(0)
145
+ expect(described_class.compare_semver('2.0', '1.5.9')).to eq(1)
146
+ end
147
+
148
+ it 'handles missing version parts as zero' do
149
+ expect(described_class.compare_semver('1.2', '1.2.0')).to eq(0)
150
+ expect(described_class.compare_semver('1.2', '1.2.1')).to eq(-1)
151
+ expect(described_class.compare_semver('1.3', '1.2.9')).to eq(1)
152
+ end
153
+
154
+ it 'compares major versions correctly' do
155
+ expect(described_class.compare_semver('10.0.0', '9.0.0')).to eq(1)
156
+ expect(described_class.compare_semver('2.0.0', '10.0.0')).to eq(-1)
157
+ end
158
+
159
+ it 'compares minor versions correctly' do
160
+ expect(described_class.compare_semver('1.10.0', '1.9.0')).to eq(1)
161
+ expect(described_class.compare_semver('1.2.0', '1.10.0')).to eq(-1)
162
+ end
163
+
164
+ it 'compares patch versions correctly' do
165
+ expect(described_class.compare_semver('1.0.10', '1.0.9')).to eq(1)
166
+ expect(described_class.compare_semver('1.0.2', '1.0.10')).to eq(-1)
167
+ end
168
+ end
169
+
170
+ describe '.generate_jitter' do
171
+ it 'returns a value within the specified range' do
172
+ 100.times do
173
+ result = described_class.generate_jitter(1500)
174
+ expect(result).to be >= 0
175
+ expect(result).to be <= 1500
176
+ end
177
+ end
178
+
179
+ it 'returns different values on multiple calls' do
180
+ results = 100.times.map { described_class.generate_jitter(1500) }
181
+ unique_results = results.uniq
182
+ # Should have some variety (not all the same)
183
+ expect(unique_results.length).to be > 1
184
+ end
185
+
186
+ it 'works with different max values' do
187
+ result = described_class.generate_jitter(5000)
188
+ expect(result).to be >= 0
189
+ expect(result).to be <= 5000
190
+ end
191
+
192
+ it 'handles zero max jitter' do
193
+ result = described_class.generate_jitter(0)
194
+ expect(result).to eq(0)
195
+ end
196
+ end
197
+
198
+ describe '.create_cache_key' do
199
+ it 'creates a key from flag and user id' do
200
+ context = { user: { id: '123' } }
201
+ result = described_class.create_cache_key('my-flag', context)
202
+ expect(result).to eq('my-flag:123')
203
+ end
204
+
205
+ it 'uses email if id is missing' do
206
+ context = { user: { email: 'test@example.com' } }
207
+ result = described_class.create_cache_key('my-flag', context)
208
+ expect(result).to eq('my-flag:test@example.com')
209
+ end
210
+
211
+ it "uses 'anonymous' if both id and email are missing" do
212
+ context = { user: {} }
213
+ result = described_class.create_cache_key('my-flag', context)
214
+ expect(result).to eq('my-flag:anonymous')
215
+ end
216
+
217
+ it "uses 'anonymous' if user is missing" do
218
+ context = {}
219
+ result = described_class.create_cache_key('my-flag', context)
220
+ expect(result).to eq('my-flag:anonymous')
221
+ end
222
+
223
+ it 'prefers id over email' do
224
+ context = { user: { id: '123', email: 'test@example.com' } }
225
+ result = described_class.create_cache_key('my-flag', context)
226
+ expect(result).to eq('my-flag:123')
227
+ end
228
+ end
229
+
230
+ describe '.evaluate_operator' do
231
+ describe 'equals operator' do
232
+ it 'returns true when values are equal (case-insensitive)' do
233
+ expect(described_class.evaluate_operator('test', 'equals', ['test'])).to be true
234
+ expect(described_class.evaluate_operator('Test', 'equals', ['test'])).to be true
235
+ expect(described_class.evaluate_operator('TEST', 'equals', ['test'])).to be true
236
+ end
237
+
238
+ it 'returns false when values are not equal' do
239
+ expect(described_class.evaluate_operator('test', 'equals', ['other'])).to be false
240
+ end
241
+
242
+ it 'handles numeric values' do
243
+ expect(described_class.evaluate_operator(123, 'equals', [123])).to be true
244
+ expect(described_class.evaluate_operator(123, 'equals', ['123'])).to be true
245
+ end
246
+ end
247
+
248
+ describe 'not_equals operator' do
249
+ it 'returns false when values are equal' do
250
+ expect(described_class.evaluate_operator('test', 'not_equals', ['test'])).to be false
251
+ end
252
+
253
+ it 'returns true when values are not equal' do
254
+ expect(described_class.evaluate_operator('test', 'not_equals', ['other'])).to be true
255
+ end
256
+
257
+ it 'returns true for nil values' do
258
+ expect(described_class.evaluate_operator(nil, 'not_equals', ['test'])).to be true
259
+ end
260
+ end
261
+
262
+ describe 'contains operator' do
263
+ it 'returns true when attribute contains value' do
264
+ expect(described_class.evaluate_operator('hello world', 'contains', ['world'])).to be true
265
+ expect(described_class.evaluate_operator('HELLO WORLD', 'contains', ['world'])).to be true
266
+ end
267
+
268
+ it 'returns false when attribute does not contain value' do
269
+ expect(described_class.evaluate_operator('hello world', 'contains', ['xyz'])).to be false
270
+ end
271
+ end
272
+
273
+ describe 'not_contains operator' do
274
+ it 'returns false when attribute contains value' do
275
+ expect(described_class.evaluate_operator('hello world', 'not_contains', ['world'])).to be false
276
+ end
277
+
278
+ it 'returns true when attribute does not contain value' do
279
+ expect(described_class.evaluate_operator('hello world', 'not_contains', ['xyz'])).to be true
280
+ end
281
+ end
282
+
283
+ describe 'starts_with operator' do
284
+ it 'returns true when attribute starts with value' do
285
+ expect(described_class.evaluate_operator('hello world', 'starts_with', ['hello'])).to be true
286
+ expect(described_class.evaluate_operator('HELLO WORLD', 'starts_with', ['hello'])).to be true
287
+ end
288
+
289
+ it 'returns false when attribute does not start with value' do
290
+ expect(described_class.evaluate_operator('hello world', 'starts_with', ['world'])).to be false
291
+ end
292
+ end
293
+
294
+ describe 'ends_with operator' do
295
+ it 'returns true when attribute ends with value' do
296
+ expect(described_class.evaluate_operator('hello world', 'ends_with', ['world'])).to be true
297
+ expect(described_class.evaluate_operator('HELLO WORLD', 'ends_with', ['world'])).to be true
298
+ end
299
+
300
+ it 'returns false when attribute does not end with value' do
301
+ expect(described_class.evaluate_operator('hello world', 'ends_with', ['hello'])).to be false
302
+ end
303
+ end
304
+
305
+ describe 'in operator' do
306
+ it 'returns true when attribute is in the list' do
307
+ expect(described_class.evaluate_operator('admin', 'in', %w[admin user guest])).to be true
308
+ expect(described_class.evaluate_operator('ADMIN', 'in', %w[admin user guest])).to be true
309
+ end
310
+
311
+ it 'returns false when attribute is not in the list' do
312
+ expect(described_class.evaluate_operator('superuser', 'in', %w[admin user guest])).to be false
313
+ end
314
+
315
+ it 'handles numeric values' do
316
+ expect(described_class.evaluate_operator(1, 'in', [1, 2, 3])).to be true
317
+ expect(described_class.evaluate_operator(4, 'in', [1, 2, 3])).to be false
318
+ end
319
+ end
320
+
321
+ describe 'not_in operator' do
322
+ it 'returns false when attribute is in the list' do
323
+ expect(described_class.evaluate_operator('admin', 'not_in', %w[admin user guest])).to be false
324
+ end
325
+
326
+ it 'returns true when attribute is not in the list' do
327
+ expect(described_class.evaluate_operator('superuser', 'not_in', %w[admin user guest])).to be true
328
+ end
329
+
330
+ it 'returns true for nil values' do
331
+ expect(described_class.evaluate_operator(nil, 'not_in', %w[admin user guest])).to be true
332
+ end
333
+ end
334
+
335
+ describe 'gt operator' do
336
+ it 'returns true when attribute is greater than value' do
337
+ expect(described_class.evaluate_operator(10, 'gt', [5])).to be true
338
+ expect(described_class.evaluate_operator(5.5, 'gt', [5])).to be true
339
+ end
340
+
341
+ it 'returns false when attribute is less than or equal to value' do
342
+ expect(described_class.evaluate_operator(5, 'gt', [10])).to be false
343
+ expect(described_class.evaluate_operator(5, 'gt', [5])).to be false
344
+ end
345
+
346
+ it 'handles string numeric values' do
347
+ expect(described_class.evaluate_operator('10', 'gt', ['5'])).to be true
348
+ end
349
+ end
350
+
351
+ describe 'gte operator' do
352
+ it 'returns true when attribute is greater than or equal to value' do
353
+ expect(described_class.evaluate_operator(10, 'gte', [5])).to be true
354
+ expect(described_class.evaluate_operator(5, 'gte', [5])).to be true
355
+ end
356
+
357
+ it 'returns false when attribute is less than value' do
358
+ expect(described_class.evaluate_operator(5, 'gte', [10])).to be false
359
+ end
360
+ end
361
+
362
+ describe 'lt operator' do
363
+ it 'returns true when attribute is less than value' do
364
+ expect(described_class.evaluate_operator(5, 'lt', [10])).to be true
365
+ end
366
+
367
+ it 'returns false when attribute is greater than or equal to value' do
368
+ expect(described_class.evaluate_operator(10, 'lt', [5])).to be false
369
+ expect(described_class.evaluate_operator(5, 'lt', [5])).to be false
370
+ end
371
+ end
372
+
373
+ describe 'lte operator' do
374
+ it 'returns true when attribute is less than or equal to value' do
375
+ expect(described_class.evaluate_operator(5, 'lte', [10])).to be true
376
+ expect(described_class.evaluate_operator(5, 'lte', [5])).to be true
377
+ end
378
+
379
+ it 'returns false when attribute is greater than value' do
380
+ expect(described_class.evaluate_operator(10, 'lte', [5])).to be false
381
+ end
382
+ end
383
+
384
+ describe 'between operator' do
385
+ it 'returns true when attribute is between min and max (inclusive)' do
386
+ expect(described_class.evaluate_operator(5, 'between', [1, 10])).to be true
387
+ expect(described_class.evaluate_operator(1, 'between', [1, 10])).to be true
388
+ expect(described_class.evaluate_operator(10, 'between', [1, 10])).to be true
389
+ end
390
+
391
+ it 'returns false when attribute is outside range' do
392
+ expect(described_class.evaluate_operator(0, 'between', [1, 10])).to be false
393
+ expect(described_class.evaluate_operator(11, 'between', [1, 10])).to be false
394
+ end
395
+
396
+ it 'returns false when condition values are insufficient' do
397
+ expect(described_class.evaluate_operator(5, 'between', [1])).to be false
398
+ end
399
+
400
+ it 'handles decimal values' do
401
+ expect(described_class.evaluate_operator(5.5, 'between', [5, 6])).to be true
402
+ end
403
+ end
404
+
405
+ describe 'regex operator' do
406
+ it 'returns true when attribute matches regex' do
407
+ expect(described_class.evaluate_operator('test123', 'regex', ['^test\\d+$'])).to be true
408
+ expect(described_class.evaluate_operator('user@example.com', 'regex', ['^[^@]+@[^@]+$'])).to be true
409
+ end
410
+
411
+ it 'returns false when attribute does not match regex' do
412
+ expect(described_class.evaluate_operator('test', 'regex', ['^\\d+$'])).to be false
413
+ end
414
+
415
+ it 'returns false for invalid regex' do
416
+ expect(described_class.evaluate_operator('test', 'regex', ['[invalid'])).to be false
417
+ end
418
+
419
+ it 'handles complex patterns' do
420
+ expect(described_class.evaluate_operator('192.168.1.1', 'regex', ['^\\d+\\.\\d+\\.\\d+\\.\\d+$'])).to be true
421
+ end
422
+ end
423
+
424
+ describe 'semver operators' do
425
+ describe 'semver_eq' do
426
+ it 'returns true for equal versions' do
427
+ expect(described_class.evaluate_operator('1.2.3', 'semver_eq', ['1.2.3'])).to be true
428
+ end
429
+
430
+ it 'returns false for different versions' do
431
+ expect(described_class.evaluate_operator('1.2.3', 'semver_eq', ['1.2.4'])).to be false
432
+ end
433
+ end
434
+
435
+ describe 'semver_gt' do
436
+ it 'returns true when attribute version is greater' do
437
+ expect(described_class.evaluate_operator('2.0.0', 'semver_gt', ['1.0.0'])).to be true
438
+ expect(described_class.evaluate_operator('1.1.0', 'semver_gt', ['1.0.0'])).to be true
439
+ end
440
+
441
+ it 'returns false when attribute version is less or equal' do
442
+ expect(described_class.evaluate_operator('1.0.0', 'semver_gt', ['2.0.0'])).to be false
443
+ expect(described_class.evaluate_operator('1.0.0', 'semver_gt', ['1.0.0'])).to be false
444
+ end
445
+ end
446
+
447
+ describe 'semver_gte' do
448
+ it 'returns true when attribute version is greater or equal' do
449
+ expect(described_class.evaluate_operator('2.0.0', 'semver_gte', ['1.0.0'])).to be true
450
+ expect(described_class.evaluate_operator('1.0.0', 'semver_gte', ['1.0.0'])).to be true
451
+ end
452
+
453
+ it 'returns false when attribute version is less' do
454
+ expect(described_class.evaluate_operator('1.0.0', 'semver_gte', ['2.0.0'])).to be false
455
+ end
456
+ end
457
+
458
+ describe 'semver_lt' do
459
+ it 'returns true when attribute version is less' do
460
+ expect(described_class.evaluate_operator('1.0.0', 'semver_lt', ['2.0.0'])).to be true
461
+ end
462
+
463
+ it 'returns false when attribute version is greater or equal' do
464
+ expect(described_class.evaluate_operator('2.0.0', 'semver_lt', ['1.0.0'])).to be false
465
+ expect(described_class.evaluate_operator('1.0.0', 'semver_lt', ['1.0.0'])).to be false
466
+ end
467
+ end
468
+
469
+ describe 'semver_lte' do
470
+ it 'returns true when attribute version is less or equal' do
471
+ expect(described_class.evaluate_operator('1.0.0', 'semver_lte', ['2.0.0'])).to be true
472
+ expect(described_class.evaluate_operator('1.0.0', 'semver_lte', ['1.0.0'])).to be true
473
+ end
474
+
475
+ it 'returns false when attribute version is greater' do
476
+ expect(described_class.evaluate_operator('2.0.0', 'semver_lte', ['1.0.0'])).to be false
477
+ end
478
+ end
479
+ end
480
+
481
+ describe 'nil attribute handling' do
482
+ it 'returns false for most operators when attribute is nil' do
483
+ expect(described_class.evaluate_operator(nil, 'equals', ['test'])).to be false
484
+ expect(described_class.evaluate_operator(nil, 'contains', ['test'])).to be false
485
+ expect(described_class.evaluate_operator(nil, 'in', ['test'])).to be false
486
+ end
487
+
488
+ it 'returns true for not_equals when attribute is nil' do
489
+ expect(described_class.evaluate_operator(nil, 'not_equals', ['test'])).to be true
490
+ end
491
+
492
+ it 'returns true for not_in when attribute is nil' do
493
+ expect(described_class.evaluate_operator(nil, 'not_in', ['test'])).to be true
494
+ end
495
+ end
496
+
497
+ describe 'unknown operator' do
498
+ it 'returns false and warns for unknown operator' do
499
+ expect do
500
+ result = described_class.evaluate_operator('test', 'unknown_op', ['value'])
501
+ expect(result).to be false
502
+ end.to output(/Unknown operator: unknown_op/).to_stderr
503
+ end
504
+ end
505
+ end
506
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: togglecraft
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - ToggleCraft
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: http
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.12'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.12'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.59'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.59'
68
+ - !ruby/object:Gem::Dependency
69
+ name: simplecov
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.22'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.22'
82
+ - !ruby/object:Gem::Dependency
83
+ name: webmock
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.19'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.19'
96
+ description: Real-time feature flags with SSE streaming, local evaluation, and smart
97
+ caching
98
+ email:
99
+ - support@togglecraft.io
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - CHANGELOG.md
105
+ - LICENSE
106
+ - README.md
107
+ - lib/togglecraft.rb
108
+ - lib/togglecraft/cache.rb
109
+ - lib/togglecraft/cache_adapters/memory_adapter.rb
110
+ - lib/togglecraft/client.rb
111
+ - lib/togglecraft/connection_pool.rb
112
+ - lib/togglecraft/evaluator.rb
113
+ - lib/togglecraft/shared_sse_connection.rb
114
+ - lib/togglecraft/sse_connection.rb
115
+ - lib/togglecraft/utils.rb
116
+ - lib/togglecraft/version.rb
117
+ - spec/spec_helper.rb
118
+ - spec/togglecraft/cache/memory_adapter_spec.rb
119
+ - spec/togglecraft/cache_spec.rb
120
+ - spec/togglecraft/client_spec.rb
121
+ - spec/togglecraft/connection_pool_spec.rb
122
+ - spec/togglecraft/evaluator_spec.rb
123
+ - spec/togglecraft/shared_sse_connection_spec.rb
124
+ - spec/togglecraft/sse_connection_spec.rb
125
+ - spec/togglecraft/utils_spec.rb
126
+ homepage: https://github.com/togglecraft/ruby-sdk
127
+ licenses:
128
+ - MIT
129
+ metadata:
130
+ homepage_uri: https://github.com/togglecraft/ruby-sdk
131
+ source_code_uri: https://github.com/togglecraft/ruby-sdk
132
+ changelog_uri: https://github.com/togglecraft/ruby-sdk/blob/main/CHANGELOG.md
133
+ rubygems_mfa_required: 'true'
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 3.0.0
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.6.7
149
+ specification_version: 4
150
+ summary: Ruby SDK for ToggleCraft feature flag management
151
+ test_files: []