featurehub-sdk 2.0.1 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc48d48647d7b4aee1a53a733506382f3e6cb021de19ae9c7192a38ff3af38a9
4
- data.tar.gz: 499b54873a2cbb87e72dcb815509a8260f12ec0ffa6a4b529ac779acad7c4729
3
+ metadata.gz: 334643cd1a7037d4a57dd889800927fbb3764840c7debbc789bf27f956af773d
4
+ data.tar.gz: c59e9eff232a2489079ed20c432a6f28af46e278c426049110aa44138ec74833
5
5
  SHA512:
6
- metadata.gz: 1f3620cf2da86a7a897efae7c766b00eaf98549c9c9b90dee97f14c67c9d32718cdf4046accfdd887622b80bb44943704fda18acfa179ed90b801c9938bd2130
7
- data.tar.gz: f31beae7e13e8261b541ffc211a3a8dad8c49563918d12e178f9390b4d079bcd303d8273f5741986bc1907d12d7e646bf0f62c553527ed87ee83ca77834c1f42
6
+ metadata.gz: e1fbf9e70a80ba754703f8744175a4e0794a7fd975ced9e28b19dd0520d21e93cf4f5f772a3dac6e00dcd88af2d6ee2880ab5b557cb5817b57d358d1da032b02
7
+ data.tar.gz: d1ea8f20f72894561fe16e975b73eb3445f8d87ac7817e30ccd732123a56b4a8cb9b057f682e9e80596d234db2c04ab0cf175c2e901f991ba3b6326bbf7afd4b
data/.claude/CLAUDE.md CHANGED
@@ -83,3 +83,4 @@ Type definitions live in [sig/feature_hub/featurehub.rbs](sig/feature_hub/featur
83
83
  - RuboCop: max line length 120, metrics cops disabled, documentation disabled
84
84
  - Tests mirror lib structure: `spec/feature_hub/sdk/**/*_spec.rb`
85
85
  - Use `instance_double` for mocking, `aggregate_failures` for multiple assertions
86
+ - after making changes run `bundle exec rubocop -a` to autocorrect minor offences and understand what major offences may have been introduced and fix them
data/.dockerignore ADDED
@@ -0,0 +1,12 @@
1
+ /bin
2
+ /sig
3
+ /spec
4
+ /.rpsec
5
+ /.rubocop.yml
6
+ /*.md
7
+ /.github
8
+ /examples/rails-example
9
+ /examples/sinatra/logs
10
+ /.git
11
+ /.idea
12
+ /.claude
data/.rubocop.yml CHANGED
@@ -35,8 +35,6 @@ Metrics/CyclomaticComplexity:
35
35
  Naming/FileName:
36
36
  Enabled: false
37
37
 
38
- Gemspec/DeprecatedAttributeAssignment:
39
- Enabled: true
40
38
  Gemspec/DeprecatedAttributeAssignment: # new in 1.30
41
39
  Enabled: true
42
40
  Gemspec/RequireMFA: # new in 1.23
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [2.1.1] - 2026-05-01
2
+
3
+ - expose Feature Properties capability
4
+ - wrap poll in case of transient timeout or similar error to allow it to continue
5
+
6
+ ## [2.1.0] - 2026-04-14
7
+
8
+ - Once the Config is closed it won't reopen
9
+ - Added Memcache cache that operates on the same general principles as Redis.
10
+ It requires Dalli to be available in your dependencies at least 4.x.
11
+ - The requirement for faraday 2+ has been relaxed, just faraday is now required in
12
+ the gemspec. It has been tested with 2 and 1.
13
+ - Redis session store has been updated so it only uses two keys
14
+
1
15
  ## [2.0.1] - 2026-03-27
2
16
 
3
17
  - Remove `FeatureHub::Sdk.default_logger`; logger now defaults to `nil` instead of a stdout DEBUG logger
data/Gemfile CHANGED
@@ -9,16 +9,16 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
- gem "rubocop", "~> 1.21"
12
+ gem "rubocop", "~> 1.86"
13
13
 
14
14
  gem "simplecov", "~> 0.21"
15
15
 
16
16
  gem "concurrent-ruby", "~> 1.3"
17
17
 
18
- gem "faraday", "~> 2"
18
+ gem "faraday"
19
19
 
20
20
  gem "murmurhash3", "~> 0.1.7"
21
21
 
22
22
  gem "sem_version", "~> 2.0.0"
23
23
 
24
- gem "ld-eventsource", "~> 2.5.1"
24
+ gem "ld-eventsource", "~> 2.6.0"
data/Gemfile.lock CHANGED
@@ -1,21 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- featurehub-sdk (2.0.1)
4
+ featurehub-sdk (2.1.1)
5
5
  concurrent-ruby (~> 1.3)
6
- faraday (~> 2)
7
- ld-eventsource (~> 2.5.1)
6
+ faraday
7
+ ld-eventsource (>= 2.5.1, < 2.7.0)
8
8
  murmurhash3 (~> 0.1.7)
9
9
  sem_version (~> 2.0.0)
10
10
 
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- addressable (2.8.8)
15
- public_suffix (>= 2.0.2, < 8.0)
16
14
  ast (2.4.3)
17
15
  concurrent-ruby (1.3.6)
18
16
  connection_pool (3.0.2)
17
+ dalli (4.3.3)
18
+ logger
19
19
  diff-lcs (1.6.2)
20
20
  docile (1.4.1)
21
21
  domain_name (0.6.20240107)
@@ -25,39 +25,27 @@ GEM
25
25
  logger
26
26
  faraday-net_http (3.4.2)
27
27
  net-http (~> 0.5)
28
- ffi (1.17.3-arm64-darwin)
29
- ffi (1.17.3-x86_64-darwin)
30
- ffi (1.17.3-x86_64-linux-gnu)
31
- ffi-compiler (1.3.2)
32
- ffi (>= 1.15.5)
33
- rake
34
- http (5.3.1)
35
- addressable (~> 2.8)
28
+ http (6.0.2)
36
29
  http-cookie (~> 1.0)
37
- http-form_data (~> 2.2)
38
- llhttp-ffi (~> 0.5.0)
39
- http-cookie (1.1.0)
30
+ llhttp (~> 0.6.1)
31
+ http-cookie (1.1.4)
40
32
  domain_name (~> 0.5)
41
- http-form_data (2.3.0)
42
- json (2.19.2)
33
+ json (2.19.3)
43
34
  language_server-protocol (3.17.0.5)
44
- ld-eventsource (2.5.1)
35
+ ld-eventsource (2.6.0)
45
36
  concurrent-ruby (~> 1.0)
46
- http (>= 4.4.1, < 6.0.0)
37
+ http (>= 4.4.1, < 7.0.0)
47
38
  lint_roller (1.1.0)
48
- llhttp-ffi (0.5.1)
49
- ffi-compiler (~> 1.0)
50
- rake (~> 13.0)
39
+ llhttp (0.6.1)
51
40
  logger (1.7.0)
52
41
  murmurhash3 (0.1.7)
53
42
  net-http (0.9.1)
54
43
  uri (>= 0.11.1)
55
- parallel (1.27.0)
56
- parser (3.3.10.0)
44
+ parallel (1.28.0)
45
+ parser (3.3.11.1)
57
46
  ast (~> 2.4.1)
58
47
  racc
59
- prism (1.6.0)
60
- public_suffix (6.0.2)
48
+ prism (1.9.0)
61
49
  racc (1.8.1)
62
50
  rainbow (3.1.1)
63
51
  rake (13.3.1)
@@ -65,7 +53,7 @@ GEM
65
53
  redis-client (>= 0.22.0)
66
54
  redis-client (0.28.0)
67
55
  connection_pool
68
- regexp_parser (2.11.3)
56
+ regexp_parser (2.12.0)
69
57
  rspec (3.13.2)
70
58
  rspec-core (~> 3.13.0)
71
59
  rspec-expectations (~> 3.13.0)
@@ -79,20 +67,20 @@ GEM
79
67
  diff-lcs (>= 1.2.0, < 2.0)
80
68
  rspec-support (~> 3.13.0)
81
69
  rspec-support (3.13.6)
82
- rubocop (1.82.0)
70
+ rubocop (1.86.1)
83
71
  json (~> 2.3)
84
72
  language_server-protocol (~> 3.17.0.2)
85
73
  lint_roller (~> 1.1.0)
86
- parallel (~> 1.10)
74
+ parallel (>= 1.10)
87
75
  parser (>= 3.3.0.2)
88
76
  rainbow (>= 2.2.2, < 4.0)
89
77
  regexp_parser (>= 2.9.3, < 3.0)
90
- rubocop-ast (>= 1.48.0, < 2.0)
78
+ rubocop-ast (>= 1.49.0, < 2.0)
91
79
  ruby-progressbar (~> 1.7)
92
80
  unicode-display_width (>= 2.4.0, < 4.0)
93
- rubocop-ast (1.48.0)
81
+ rubocop-ast (1.49.1)
94
82
  parser (>= 3.3.7.2)
95
- prism (~> 1.4)
83
+ prism (~> 1.7)
96
84
  ruby-progressbar (1.13.0)
97
85
  sem_version (2.0.1)
98
86
  simplecov (0.22.0)
@@ -113,60 +101,55 @@ PLATFORMS
113
101
 
114
102
  DEPENDENCIES
115
103
  concurrent-ruby (~> 1.3)
116
- faraday (~> 2)
104
+ dalli (~> 4)
105
+ faraday
117
106
  featurehub-sdk!
118
- ld-eventsource (~> 2.5.1)
107
+ ld-eventsource (~> 2.6.0)
119
108
  murmurhash3 (~> 0.1.7)
120
109
  rake (~> 13.0)
121
110
  redis (~> 5)
122
111
  rspec (~> 3.0)
123
- rubocop (~> 1.21)
112
+ rubocop (~> 1.86)
124
113
  sem_version (~> 2.0.0)
125
114
  simplecov (~> 0.21)
126
115
 
127
116
  CHECKSUMS
128
- addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057
129
117
  ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
130
118
  concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
131
119
  connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
120
+ dalli (4.3.3) sha256=ae58aa3442b0d9e129898f56bc6e3a0f8b6149523e723b3eb124a05ae9a2da0c
132
121
  diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
133
122
  docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
134
123
  domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933
135
124
  faraday (2.14.0) sha256=8699cfe5d97e55268f2596f9a9d5a43736808a943714e3d9a53e6110593941cd
136
125
  faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
137
- featurehub-sdk (2.0.1)
138
- ffi (1.17.3-arm64-darwin) sha256=0c690555d4cee17a7f07c04d59df39b2fba74ec440b19da1f685c6579bb0717f
139
- ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5
140
- ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f
141
- ffi-compiler (1.3.2) sha256=a94f3d81d12caf5c5d4ecf13980a70d0aeaa72268f3b9cc13358bcc6509184a0
142
- http (5.3.1) sha256=c50802d8e9be3926cb84ac3b36d1a31fbbac383bc4cbecdce9053cb604231d7d
143
- http-cookie (1.1.0) sha256=38a5e60d1527eebc396831b8c4b9455440509881219273a6c99943d29eadbb19
144
- http-form_data (2.3.0) sha256=cc4eeb1361d9876821e31d7b1cf0b68f1cf874b201d27903480479d86448a5f3
145
- json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
126
+ featurehub-sdk (2.1.1)
127
+ http (6.0.2) sha256=be337816fb45cee712eeb0829a16d9300c9d2b87b38b771d177d7a59f77e8b83
128
+ http-cookie (1.1.4) sha256=8dd8011dedcae5f91af2671b7ba878c4a9e89f0f6384790c1f4cdd176f5e3ada
129
+ json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
146
130
  language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
147
- ld-eventsource (2.5.1) sha256=19cb44b7d3f91f76b7401a1b74293b32c723389b2d6f9d81ecb803c628ac1b25
131
+ ld-eventsource (2.6.0) sha256=e70b34226972d3c729c92787de7f342de08340f7249393aa8b1d4b4e3f0b062d
148
132
  lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
149
- llhttp-ffi (0.5.1) sha256=9a25a7fc19311f691a78c9c0ac0fbf4675adbd0cca74310228fdf841018fa7bc
133
+ llhttp (0.6.1) sha256=9da187ecf6407265465919cc0d691210ef79e38fa6e86e5e45593bdf25b50146
150
134
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
151
135
  murmurhash3 (0.1.7) sha256=370a2ce2e9ab0711e51554e530b5f63956927a6554a296855f42a1a4a5ed0936
152
136
  net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
153
- parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
154
- parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6
155
- prism (1.6.0) sha256=bfc0281a81718c4872346bc858dc84abd3a60cae78336c65ad35c8fbff641c6b
156
- public_suffix (6.0.2) sha256=bfa7cd5108066f8c9602e0d6d4114999a5df5839a63149d3e8b0f9c1d3558394
137
+ parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970
138
+ parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
139
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
157
140
  racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
158
141
  rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
159
142
  rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
160
143
  redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae
161
144
  redis-client (0.28.0) sha256=888892f9cd8787a41c0ece00bdf5f556dfff7770326ce40bb2bc11f1bfec824b
162
- regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
145
+ regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
163
146
  rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
164
147
  rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
165
148
  rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
166
149
  rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c
167
150
  rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2
168
- rubocop (1.82.0) sha256=237b7dc24952d7ec469a9593c7a5283315515e2e7dc24ac91532819c254fc4ec
169
- rubocop-ast (1.48.0) sha256=22df9bbf3f7a6eccde0fad54e68547ae1e2a704bf8719e7c83813a99c05d2e76
151
+ rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
152
+ rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
170
153
  ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
171
154
  sem_version (2.0.1) sha256=6d97d4f67e28546ba90b3c290f901d6c8031ddb8e08bce962139739c4d40b183
172
155
  simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
data/Makefile ADDED
@@ -0,0 +1,12 @@
1
+ e2e:
2
+ docker build -t todo-server:e2e --progress=plain -f examples/sinatra/Dockerfile .
3
+
4
+ run-e2e: e2e
5
+
6
+ cop:
7
+ bundle exec rubocop -a
8
+
9
+ spec:
10
+ bundle exec rspec
11
+
12
+ ci: cop spec
data/README.md CHANGED
@@ -295,34 +295,87 @@ The file path defaults to `featurehub-overrides.yaml` or the `FEATUREHUB_LOCAL_Y
295
295
 
296
296
  ## Caching feature state in Redis
297
297
 
298
- `RedisSessionStore` persists feature values from a `FeatureHubRepository` to Redis. On startup it replays cached features into the repository, then listens for live updates and writes newer versions back. A background timer re-reads all features periodically so updates from other processes are picked up automatically.
298
+ `RedisSessionStore` persists feature values from a `FeatureHubRepository` to Redis. On startup it replays cached features into the repository, then listens for live updates and writes newer versions back. A background timer periodically re-reads a SHA key so that updates published by other processes are picked up automatically.
299
299
 
300
300
  > **Warning:** Do not use `RedisSessionStore` with server-evaluated features. Each server-evaluated context resolves to different values; sharing a single Redis key across processes will cause them to overwrite each other's state.
301
301
 
302
+ Multi-process writes are safe: the store uses SHA256-based change detection and Redis `WATCH`/`MULTI`/`EXEC` to atomically update both keys and prevent races between concurrent writers.
303
+
304
+ Pass a `FeatureHubConfig` as the second argument — the store reads `repository` and `environment_id` from it and registers itself as a raw update listener automatically.
305
+
302
306
  ```ruby
303
307
  # Requires the 'redis' gem: gem 'redis', '~> 5'
304
308
  store = FeatureHub::Sdk::RedisSessionStore.new(
305
309
  "redis://localhost:6379",
306
- config.repository,
310
+ config, # FeatureHubConfig — NOT config.repository
307
311
  {
308
- prefix: "myapp", # Redis key prefix (default: "featurehub")
309
- namespace: 0, # Redis DB index (default: 0)
310
- timeout: 60, # Seconds between periodic reloads (default: 30)
311
- password: "secret", # Optional Redis password
312
- logger: my_logger # Optional logger (default: SDK default logger)
312
+ prefix: "myapp", # Redis key prefix (default: "featurehub")
313
+ db: 0, # Redis DB index (default: 0)
314
+ refresh_timeout: 300, # Seconds between periodic SHA checks (default: 300)
315
+ backoff_timeout: 500, # Milliseconds to wait between WATCH retries (default: 500)
316
+ retry_update_count: 10, # Maximum WATCH retry attempts per write (default: 10)
317
+ logger: my_logger # Optional logger
313
318
  }
314
319
  )
315
320
 
316
- # Register it so it also receives live updates
317
- config.register_raw_update_listener(store)
318
-
319
321
  # Shut down cleanly
320
322
  store.close
321
323
  ```
322
324
 
325
+ You can also pass an existing Redis client instead of a connection string (e.g. a RedisCluster client or a pre-configured `Redis` instance):
326
+
327
+ ```ruby
328
+ redis = Redis.new(url: "redis://localhost:6379", db: 1)
329
+ store = FeatureHub::Sdk::RedisSessionStore.new(redis, config)
330
+ ```
331
+
332
+ You can also pass a `RedisSessionStoreOptions` object directly:
333
+
334
+ ```ruby
335
+ opts = FeatureHub::Sdk::RedisSessionStoreOptions.new(prefix: "myapp", db: 2)
336
+ store = FeatureHub::Sdk::RedisSessionStore.new("redis://localhost:6379", config, opts)
337
+ ```
338
+
323
339
  Redis keys used:
324
- - `{prefix}_ids` — a Redis SET of feature IDs
325
- - `{prefix}_{id}` — the JSON-encoded feature state for each feature
340
+ - `{prefix}_{environment_id}` — JSON-encoded array of all feature states
341
+ - `{prefix}_{environment_id}_sha` — SHA256 fingerprint used for cross-process change detection
342
+
343
+ ## Caching feature state in Memcache
344
+
345
+ `MemcacheSessionStore` persists feature values from a `FeatureHubRepository` to Memcache. On startup it reads any previously saved features from Memcache and replays them into the repository, then listens for live updates and writes newer versions back. A background timer periodically re-reads a SHA key so that updates published by other processes are picked up automatically.
346
+
347
+ > **Warning:** Do not use `MemcacheSessionStore` with server-evaluated features. Each server-evaluated context resolves to different values; sharing a single Memcache key across processes will cause them to overwrite each other's state.
348
+
349
+ Multi-process writes are safe: the store uses SHA256-based change detection and Dalli's compare-and-set (`cas`) to prevent races between concurrent writers.
350
+
351
+ ```ruby
352
+ # Requires the 'dalli' gem: gem 'dalli', '~> 5'
353
+ store = FeatureHub::Sdk::MemcacheSessionStore.new(
354
+ "localhost:11211",
355
+ config,
356
+ {
357
+ prefix: "myapp", # Key prefix (default: "featurehub")
358
+ refresh_timeout: 300, # Seconds between periodic SHA checks (default: 300)
359
+ backoff_timeout: 500, # Milliseconds to wait between CAS retries (default: 500)
360
+ retry_update_count: 10, # Maximum CAS retry attempts per write (default: 10)
361
+ logger: my_logger # Optional logger (default: SDK default logger)
362
+ }
363
+ )
364
+
365
+ # Shut down cleanly
366
+ store.close
367
+ ```
368
+
369
+ You can also pass an existing `Dalli::Client` instead of a connection string:
370
+
371
+ ```ruby
372
+ dalli = Dalli::Client.new("localhost:11211", serializer: JSON)
373
+ store = FeatureHub::Sdk::MemcacheSessionStore.new(dalli, config)
374
+ ```
375
+
376
+ Memcache keys used:
377
+ - `{prefix}_{environment_id}` — JSON-encoded array of all feature states
378
+ - `{prefix}_{environment_id}_sha` — SHA256 fingerprint used for cross-process change detection
326
379
 
327
380
  ## Custom raw update listeners
328
381
 
@@ -346,7 +399,7 @@ end
346
399
  config.register_raw_update_listener(MyAuditListener.new)
347
400
  ```
348
401
 
349
- Callbacks are dispatched asynchronously via `Concurrent::Future`. The `source` parameter will be `"streaming"`, `"polling"`, `"local-yaml"`, `"redis-store"`, or `"unknown"`.
402
+ Callbacks are dispatched asynchronously via `Concurrent::Future`. The `source` parameter will be `"streaming"`, `"polling"`, `"local-yaml"`, `"redis-store"`, `"memcache-store"`, or `"unknown"`.
350
403
 
351
404
  All listeners are closed automatically when `config.close` or `repository.close` is called.
352
405
 
@@ -1,31 +1,31 @@
1
- FROM ruby:3.3.10-bookworm
1
+ FROM phusion/passenger-ruby33:latest
2
2
 
3
3
  MAINTAINER info@featurehub.io
4
- ENV BUNDLER_VERSION 2.3.15
4
+ ENV BUNDLER_VERSION 4.0.3
5
5
  ARG DEBIAN_FRONTEND=noninteractive
6
6
 
7
- RUN apt update && \
8
- apt install -y gnupg wget tzdata apt-transport-https dirmngr gnupg curl && \
9
- curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key-2025.txt | gpg --dearmor | tee /etc/apt/trusted.gpg.d/phusion.gpg >/dev/null && \
10
- sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bookworm main > /etc/apt/sources.list.d/passenger.list' && \
11
- apt-get update && \
12
- apt-get install -y nginx passenger
13
-
14
- #ENV BUNDLE_PATH /bundle
15
- RUN passenger-config build-native-support
7
+ # The purpose of this Dockerfile is to build a test to-do server that can be used against the
8
+ # e2e tests. It *MUST* test the SDK on the current HEAD, so the file is complicated and awkward
9
+ # and not like a typical deploy.
16
10
 
17
11
  RUN echo 'gem: --no-document' >> ~/.gemrc && \
18
12
  gem update --system && \
19
13
  gem install bundler -v ${BUNDLER_VERSION} --force
20
14
 
15
+ RUN gem uninstall logger -v 1.6.0
16
+
21
17
  # set up nsswitch
22
18
  COPY examples/sinatra/conf/nsswitch.conf /etc/nsswitch.conf
23
19
 
24
- RUN mkdir -p /app/featurehub
25
20
  COPY examples/sinatra/conf/nginx.conf /etc/nginx/nginx.conf
26
- COPY . /app/
21
+ ADD . /app/featurehub
27
22
  WORKDIR /app/featurehub
28
- RUN cd /app/featurehub && bundle install
29
- ADD examples/sinatra/ /app/featurehub
23
+ RUN rm Gemfile Gemfile.lock && cd examples/sinatra && bundle config set --local deployment 'true' --without 'development test' path 'vendor/bundle'
24
+ RUN cd /app/featurehub/examples/sinatra && cat .bundle/config && mkdir logs
25
+ RUN cd /app/featurehub/examples/sinatra && bundle install
26
+ RUN chown -R nobody /app
27
+ ENV BUNDLE_PATH=/app/featurehub/examples/sinatra/vendor/bundle
30
28
 
31
- CMD /usr/sbin/nginx -g \'daemon off;\'
29
+ USER root
30
+ CMD /usr/sbin/nginx -g 'daemon off;'
31
+ #CMD /usr/bin/bash
@@ -4,7 +4,11 @@ source "https://rubygems.org"
4
4
 
5
5
  ruby "3.3.10"
6
6
 
7
+ # 5 prevents us using ruby 3.2 which while EOL is still in our build
8
+ gem "dalli", "~> 4"
7
9
  gem "featurehub-sdk", path: "../.."
10
+ # this is just to test 1.x works OK
11
+ gem "faraday", "~> 1"
8
12
  gem "rack"
9
13
  gem "redis"
10
14
  gem "sinatra"
@@ -1,30 +1,50 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- featurehub-sdk (2.0.0)
4
+ featurehub-sdk (2.1.1)
5
5
  concurrent-ruby (~> 1.3)
6
- faraday (~> 2)
7
- ld-eventsource (~> 2.5.1)
6
+ faraday
7
+ ld-eventsource (>= 2.5.1, < 2.7.0)
8
8
  murmurhash3 (~> 0.1.7)
9
9
  sem_version (~> 2.0.0)
10
10
 
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- addressable (2.8.9)
14
+ addressable (2.9.0)
15
15
  public_suffix (>= 2.0.2, < 8.0)
16
16
  concurrent-ruby (1.3.6)
17
17
  connection_pool (3.0.2)
18
18
  daemons (1.4.1)
19
+ dalli (4.3.3)
20
+ logger
19
21
  domain_name (0.6.20240107)
20
22
  eventmachine (1.2.7)
21
- faraday (2.14.1)
22
- faraday-net_http (>= 2.0, < 3.5)
23
- json
24
- logger
25
- faraday-net_http (3.4.2)
26
- net-http (~> 0.5)
23
+ faraday (1.10.5)
24
+ faraday-em_http (~> 1.0)
25
+ faraday-em_synchrony (~> 1.0)
26
+ faraday-excon (~> 1.1)
27
+ faraday-httpclient (~> 1.0)
28
+ faraday-multipart (~> 1.0)
29
+ faraday-net_http (~> 1.0)
30
+ faraday-net_http_persistent (~> 1.0)
31
+ faraday-patron (~> 1.0)
32
+ faraday-rack (~> 1.0)
33
+ faraday-retry (~> 1.0)
34
+ ruby2_keywords (>= 0.0.4)
35
+ faraday-em_http (1.0.0)
36
+ faraday-em_synchrony (1.0.1)
37
+ faraday-excon (1.1.0)
38
+ faraday-httpclient (1.0.1)
39
+ faraday-multipart (1.2.0)
40
+ multipart-post (~> 2.0)
41
+ faraday-net_http (1.0.2)
42
+ faraday-net_http_persistent (1.2.0)
43
+ faraday-patron (1.0.0)
44
+ faraday-rack (1.0.0)
45
+ faraday-retry (1.0.4)
27
46
  ffi (1.17.3-x86_64-darwin)
47
+ ffi (1.17.3-x86_64-linux-gnu)
28
48
  ffi-compiler (1.3.2)
29
49
  ffi (>= 1.15.5)
30
50
  rake
@@ -36,7 +56,6 @@ GEM
36
56
  http-cookie (1.1.0)
37
57
  domain_name (~> 0.5)
38
58
  http-form_data (2.3.0)
39
- json (2.19.2)
40
59
  ld-eventsource (2.5.1)
41
60
  concurrent-ruby (~> 1.0)
42
61
  http (>= 4.4.1, < 6.0.0)
@@ -44,11 +63,10 @@ GEM
44
63
  ffi-compiler (~> 1.0)
45
64
  rake (~> 13.0)
46
65
  logger (1.7.0)
66
+ multipart-post (2.4.1)
47
67
  murmurhash3 (0.1.7)
48
68
  mustermann (2.0.2)
49
69
  ruby2_keywords (~> 0.0.1)
50
- net-http (0.9.1)
51
- uri (>= 0.11.1)
52
70
  public_suffix (7.0.5)
53
71
  rack (2.2.6.4)
54
72
  rack-protection (2.2.3)
@@ -72,7 +90,6 @@ GEM
72
90
  eventmachine (~> 1.0, >= 1.0.4)
73
91
  rack (>= 1, < 3)
74
92
  tilt (2.1.0)
75
- uri (1.1.1)
76
93
 
77
94
  PLATFORMS
78
95
  x86_64-darwin-21
@@ -80,6 +97,8 @@ PLATFORMS
80
97
  x86_64-linux
81
98
 
82
99
  DEPENDENCIES
100
+ dalli (~> 4)
101
+ faraday (~> 1)
83
102
  featurehub-sdk!
84
103
  rack
85
104
  redis
@@ -4,7 +4,23 @@ This is a simple todo server example that we use to run our Cucumber tests again
4
4
  to ensure that the SDK is operating as expected. Please read the `application.rb`
5
5
  for details on how it works, it is fairly simple.
6
6
 
7
- It can be built into a Passenger dockerfile using the `build.sh` script and started
8
- using `docker_start.sh`
7
+ NOTE: The `Dockerfile` is intended for use in the e2e tests in the pipeline and is not a general use-case sample. It uses Passenger, Sinatra and the FeatureHub SDK on this specific branch. It is, as such, constructed from the root directory (using the `Makefile`).
9
8
 
9
+ We typically want to run one cache and then two copies of the server. We would normally disconnect the primary server (running on 8099) from edge, leaving the second server running (8100) and writing to the cache.
10
10
 
11
+ To disconnect from edge, call `curl localhost:8099/health/disconnect`
12
+
13
+ The tests should still pass as the primary server is picking up changes from the cache.
14
+
15
+ == Redis
16
+
17
+ ----
18
+ docker run -d -p 6379:6379 --name redis redis
19
+
20
+ ----
21
+
22
+ == Memcache
23
+
24
+ ----
25
+ docker run -p 11211:11211 --name memcache -d memcached
26
+ ----