familia 2.0.0.pre4 → 2.0.0.pre6

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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop_todo.yml +17 -17
  4. data/CLAUDE.md +11 -8
  5. data/Gemfile +5 -1
  6. data/Gemfile.lock +19 -3
  7. data/README.md +36 -157
  8. data/docs/overview.md +359 -0
  9. data/docs/wiki/API-Reference.md +347 -0
  10. data/docs/wiki/Connection-Pooling-Guide.md +437 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +101 -0
  12. data/docs/wiki/Expiration-Feature-Guide.md +596 -0
  13. data/docs/wiki/Feature-System-Guide.md +600 -0
  14. data/docs/wiki/Features-System-Developer-Guide.md +892 -0
  15. data/docs/wiki/Field-System-Guide.md +784 -0
  16. data/docs/wiki/Home.md +106 -0
  17. data/docs/wiki/Implementation-Guide.md +276 -0
  18. data/docs/wiki/Quantization-Feature-Guide.md +721 -0
  19. data/docs/wiki/RelatableObjects-Guide.md +563 -0
  20. data/docs/wiki/Security-Model.md +183 -0
  21. data/docs/wiki/Transient-Fields-Guide.md +280 -0
  22. data/lib/familia/base.rb +18 -27
  23. data/lib/familia/connection.rb +6 -5
  24. data/lib/familia/{datatype → data_type}/commands.rb +2 -5
  25. data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
  26. data/lib/familia/data_type/types/counter.rb +38 -0
  27. data/lib/familia/{datatype → data_type}/types/hashkey.rb +20 -2
  28. data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
  29. data/lib/familia/data_type/types/lock.rb +43 -0
  30. data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
  31. data/lib/familia/{datatype → data_type}/types/string.rb +11 -3
  32. data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
  33. data/lib/familia/{datatype.rb → data_type.rb} +12 -14
  34. data/lib/familia/encryption/encrypted_data.rb +137 -0
  35. data/lib/familia/encryption/manager.rb +119 -0
  36. data/lib/familia/encryption/provider.rb +49 -0
  37. data/lib/familia/encryption/providers/aes_gcm_provider.rb +123 -0
  38. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
  39. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +138 -0
  40. data/lib/familia/encryption/registry.rb +50 -0
  41. data/lib/familia/encryption.rb +178 -0
  42. data/lib/familia/encryption_request_cache.rb +68 -0
  43. data/lib/familia/errors.rb +17 -3
  44. data/lib/familia/features/encrypted_fields/concealed_string.rb +295 -0
  45. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +221 -0
  46. data/lib/familia/features/encrypted_fields.rb +28 -0
  47. data/lib/familia/features/expiration.rb +107 -77
  48. data/lib/familia/features/quantization.rb +5 -9
  49. data/lib/familia/features/relatable_objects.rb +2 -4
  50. data/lib/familia/features/safe_dump.rb +14 -17
  51. data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
  52. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
  53. data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
  54. data/lib/familia/features/transient_fields.rb +47 -0
  55. data/lib/familia/features.rb +40 -24
  56. data/lib/familia/field_type.rb +273 -0
  57. data/lib/familia/horreum/{connection.rb → core/connection.rb} +6 -15
  58. data/lib/familia/horreum/{commands.rb → core/database_commands.rb} +20 -21
  59. data/lib/familia/horreum/core/serialization.rb +535 -0
  60. data/lib/familia/horreum/{utils.rb → core/utils.rb} +9 -12
  61. data/lib/familia/horreum/core.rb +21 -0
  62. data/lib/familia/horreum/{settings.rb → shared/settings.rb} +10 -4
  63. data/lib/familia/horreum/subclass/definition.rb +469 -0
  64. data/lib/familia/horreum/{class_methods.rb → subclass/management.rb} +27 -250
  65. data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
  66. data/lib/familia/horreum.rb +30 -22
  67. data/lib/familia/logging.rb +14 -14
  68. data/lib/familia/settings.rb +39 -3
  69. data/lib/familia/utils.rb +45 -0
  70. data/lib/familia/version.rb +1 -1
  71. data/lib/familia.rb +3 -2
  72. data/try/core/base_enhancements_try.rb +115 -0
  73. data/try/core/connection_try.rb +0 -1
  74. data/try/core/create_method_try.rb +240 -0
  75. data/try/core/database_consistency_try.rb +299 -0
  76. data/try/core/errors_try.rb +25 -5
  77. data/try/core/familia_extended_try.rb +3 -4
  78. data/try/core/familia_try.rb +1 -2
  79. data/try/core/persistence_operations_try.rb +297 -0
  80. data/try/core/pools_try.rb +2 -2
  81. data/try/core/secure_identifier_try.rb +0 -1
  82. data/try/core/settings_try.rb +0 -1
  83. data/try/core/utils_try.rb +0 -1
  84. data/try/{datatypes → data_types}/boolean_try.rb +1 -2
  85. data/try/data_types/counter_try.rb +93 -0
  86. data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
  87. data/try/{datatypes → data_types}/hash_try.rb +1 -2
  88. data/try/{datatypes → data_types}/list_try.rb +1 -2
  89. data/try/data_types/lock_try.rb +133 -0
  90. data/try/{datatypes → data_types}/set_try.rb +1 -2
  91. data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
  92. data/try/{datatypes → data_types}/string_try.rb +1 -2
  93. data/try/debugging/README.md +32 -0
  94. data/try/debugging/cache_behavior_tracer.rb +91 -0
  95. data/try/debugging/debug_aad_process.rb +82 -0
  96. data/try/debugging/debug_concealed_internal.rb +59 -0
  97. data/try/debugging/debug_concealed_reveal.rb +61 -0
  98. data/try/debugging/debug_context_aad.rb +68 -0
  99. data/try/debugging/debug_context_simple.rb +80 -0
  100. data/try/debugging/debug_cross_context.rb +62 -0
  101. data/try/debugging/debug_database_load.rb +64 -0
  102. data/try/debugging/debug_encrypted_json_check.rb +53 -0
  103. data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
  104. data/try/debugging/debug_exists_lifecycle.rb +54 -0
  105. data/try/debugging/debug_field_decrypt.rb +74 -0
  106. data/try/debugging/debug_fresh_cross_context.rb +73 -0
  107. data/try/debugging/debug_load_path.rb +66 -0
  108. data/try/debugging/debug_method_definition.rb +46 -0
  109. data/try/debugging/debug_method_resolution.rb +41 -0
  110. data/try/debugging/debug_minimal.rb +24 -0
  111. data/try/debugging/debug_provider.rb +68 -0
  112. data/try/debugging/debug_secure_behavior.rb +73 -0
  113. data/try/debugging/debug_string_class.rb +46 -0
  114. data/try/debugging/debug_test.rb +46 -0
  115. data/try/debugging/debug_test_design.rb +80 -0
  116. data/try/debugging/encryption_method_tracer.rb +138 -0
  117. data/try/debugging/provider_diagnostics.rb +110 -0
  118. data/try/edge_cases/hash_symbolization_try.rb +0 -1
  119. data/try/edge_cases/json_serialization_try.rb +0 -1
  120. data/try/edge_cases/reserved_keywords_try.rb +42 -11
  121. data/try/encryption/config_persistence_try.rb +192 -0
  122. data/try/encryption/encryption_core_try.rb +328 -0
  123. data/try/encryption/instance_variable_scope_try.rb +31 -0
  124. data/try/encryption/module_loading_try.rb +28 -0
  125. data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
  126. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
  127. data/try/encryption/roundtrip_validation_try.rb +28 -0
  128. data/try/encryption/secure_memory_handling_try.rb +125 -0
  129. data/try/features/encrypted_fields_core_try.rb +125 -0
  130. data/try/features/encrypted_fields_integration_try.rb +216 -0
  131. data/try/features/encrypted_fields_no_cache_security_try.rb +219 -0
  132. data/try/features/encrypted_fields_security_try.rb +377 -0
  133. data/try/features/encryption_fields/aad_protection_try.rb +138 -0
  134. data/try/features/encryption_fields/concealed_string_core_try.rb +250 -0
  135. data/try/features/encryption_fields/context_isolation_try.rb +141 -0
  136. data/try/features/encryption_fields/error_conditions_try.rb +116 -0
  137. data/try/features/encryption_fields/fresh_key_derivation_try.rb +128 -0
  138. data/try/features/encryption_fields/fresh_key_try.rb +168 -0
  139. data/try/features/encryption_fields/key_rotation_try.rb +123 -0
  140. data/try/features/encryption_fields/memory_security_try.rb +37 -0
  141. data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
  142. data/try/features/encryption_fields/nonce_uniqueness_try.rb +56 -0
  143. data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
  144. data/try/features/encryption_fields/thread_safety_try.rb +199 -0
  145. data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
  146. data/try/features/expiration_try.rb +0 -1
  147. data/try/features/feature_dependencies_try.rb +159 -0
  148. data/try/features/quantization_try.rb +0 -1
  149. data/try/features/real_feature_integration_try.rb +148 -0
  150. data/try/features/relatable_objects_try.rb +0 -1
  151. data/try/features/safe_dump_advanced_try.rb +0 -1
  152. data/try/features/safe_dump_try.rb +0 -1
  153. data/try/features/transient_fields/redacted_string_try.rb +248 -0
  154. data/try/features/transient_fields/refresh_reset_try.rb +164 -0
  155. data/try/features/transient_fields/simple_refresh_test.rb +50 -0
  156. data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
  157. data/try/features/transient_fields_core_try.rb +181 -0
  158. data/try/features/transient_fields_integration_try.rb +260 -0
  159. data/try/helpers/test_helpers.rb +67 -0
  160. data/try/horreum/base_try.rb +157 -3
  161. data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
  162. data/try/horreum/field_categories_try.rb +118 -0
  163. data/try/horreum/field_definition_try.rb +96 -0
  164. data/try/horreum/initialization_try.rb +1 -2
  165. data/try/horreum/relations_try.rb +1 -2
  166. data/try/horreum/serialization_persistent_fields_try.rb +165 -0
  167. data/try/horreum/serialization_try.rb +41 -7
  168. data/try/memory/memory_basic_test.rb +73 -0
  169. data/try/memory/memory_detailed_test.rb +121 -0
  170. data/try/memory/memory_docker_ruby_dump.sh +80 -0
  171. data/try/memory/memory_search_for_string.rb +83 -0
  172. data/try/memory/test_actual_redactedstring_protection.rb +38 -0
  173. data/try/models/customer_safe_dump_try.rb +1 -2
  174. data/try/models/customer_try.rb +1 -2
  175. data/try/models/datatype_base_try.rb +1 -2
  176. data/try/models/familia_object_try.rb +0 -1
  177. metadata +131 -23
  178. data/lib/familia/horreum/serialization.rb +0 -445
@@ -0,0 +1,437 @@
1
+ # Connection Pooling Guide
2
+
3
+ ## Overview
4
+
5
+ Familia provides robust connection pooling through a provider pattern that enables efficient Redis/Valkey connection management with support for multiple logical databases, thread safety, and optimal performance.
6
+
7
+ ## Core Concepts
8
+
9
+ ### Connection Provider Contract
10
+
11
+ Your connection provider **MUST** follow these rules:
12
+
13
+ 1. **Database Selection**: Return connections already on the correct logical database
14
+ 2. **No SELECT Commands**: Familia will NOT issue `SELECT` commands
15
+ 3. **URI-based Selection**: Accept normalized URIs (e.g., `redis://localhost:6379/2`)
16
+ 4. **Thread Safety**: Handle concurrent access safely
17
+
18
+ ### Connection Priority System
19
+
20
+ Familia uses a three-tier connection resolution system:
21
+
22
+ 1. **Thread-local connections** (middleware pattern)
23
+ 2. **Connection provider** (if configured)
24
+ 3. **Fallback behavior** (legacy direct connections, if allowed)
25
+
26
+ ```ruby
27
+ # Priority 1: Thread-local (set by middleware)
28
+ Thread.current[:familia_connection] = redis_client
29
+
30
+ # Priority 2: Connection provider
31
+ Familia.connection_provider = ->(uri) { pool.checkout(uri) }
32
+
33
+ # Priority 3: Fallback (can be disabled)
34
+ Familia.connection_required = true # Disable fallback
35
+ ```
36
+
37
+ ## Basic Setup
38
+
39
+ ### Simple Connection Pool
40
+
41
+ ```ruby
42
+ require 'connection_pool'
43
+
44
+ class ConnectionManager
45
+ @pools = {}
46
+
47
+ # Configure provider at application startup
48
+ def self.setup!
49
+ Familia.connection_provider = lambda do |uri|
50
+ parsed = URI.parse(uri)
51
+ pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
52
+
53
+ @pools[pool_key] ||= ConnectionPool.new(size: 10, timeout: 5) do
54
+ Redis.new(
55
+ host: parsed.host,
56
+ port: parsed.port,
57
+ db: parsed.db || 0 # CRITICAL: Set DB on connection creation
58
+ )
59
+ end
60
+
61
+ @pools[pool_key].with { |conn| conn }
62
+ end
63
+ end
64
+ end
65
+
66
+ # Initialize at app startup
67
+ ConnectionManager.setup!
68
+ ```
69
+
70
+ ### Multi-Database Configuration
71
+
72
+ ```ruby
73
+ class DatabasePoolManager
74
+ POOL_CONFIGS = {
75
+ 0 => { size: 20, timeout: 5 }, # Main application data
76
+ 1 => { size: 5, timeout: 3 }, # Analytics/reporting
77
+ 2 => { size: 10, timeout: 2 }, # Session/cache data
78
+ 3 => { size: 15, timeout: 5 } # Background jobs
79
+ }.freeze
80
+
81
+ @pools = {}
82
+
83
+ def self.setup!
84
+ Familia.connection_provider = lambda do |uri|
85
+ parsed = URI.parse(uri)
86
+ db = parsed.db || 0
87
+ server = "#{parsed.host}:#{parsed.port}"
88
+ pool_key = "#{server}/#{db}"
89
+
90
+ @pools[pool_key] ||= begin
91
+ config = POOL_CONFIGS[db] || { size: 5, timeout: 5 }
92
+
93
+ ConnectionPool.new(**config) do
94
+ Redis.new(
95
+ host: parsed.host,
96
+ port: parsed.port,
97
+ db: db,
98
+ timeout: 1,
99
+ reconnect_attempts: 3,
100
+ inherit_socket: false
101
+ )
102
+ end
103
+ end
104
+
105
+ @pools[pool_key].with { |conn| conn }
106
+ end
107
+ end
108
+ end
109
+ ```
110
+
111
+ ## Advanced Patterns
112
+
113
+ ### Rails/Sidekiq Integration
114
+
115
+ ```ruby
116
+ # config/initializers/familia_pools.rb
117
+ class FamiliaPoolManager
118
+ include Singleton
119
+
120
+ def initialize
121
+ @pools = {}
122
+ setup_connection_provider
123
+ end
124
+
125
+ private
126
+
127
+ def setup_connection_provider
128
+ Familia.connection_provider = lambda do |uri|
129
+ get_connection(uri)
130
+ end
131
+ end
132
+
133
+ def get_connection(uri)
134
+ parsed = URI.parse(uri)
135
+ pool_key = connection_key(parsed)
136
+
137
+ @pools[pool_key] ||= create_pool(parsed)
138
+ @pools[pool_key].with { |conn| conn }
139
+ end
140
+
141
+ def connection_key(parsed_uri)
142
+ "#{parsed_uri.host}:#{parsed_uri.port}/#{parsed_uri.db || 0}"
143
+ end
144
+
145
+ def create_pool(parsed_uri)
146
+ db = parsed_uri.db || 0
147
+
148
+ ConnectionPool.new(
149
+ size: pool_size_for_database(db),
150
+ timeout: 5
151
+ ) do
152
+ Redis.new(
153
+ host: parsed_uri.host,
154
+ port: parsed_uri.port,
155
+ db: db,
156
+ timeout: redis_timeout,
157
+ reconnect_attempts: 3
158
+ )
159
+ end
160
+ end
161
+
162
+ def pool_size_for_database(db)
163
+ case db
164
+ when 0 then sidekiq_concurrency + web_concurrency + 2 # Main DB
165
+ when 1 then 5 # Analytics
166
+ when 2 then web_concurrency + 2 # Sessions
167
+ else 5 # Default
168
+ end
169
+ end
170
+
171
+ def sidekiq_concurrency
172
+ defined?(Sidekiq) ? Sidekiq.options[:concurrency] : 0
173
+ end
174
+
175
+ def web_concurrency
176
+ ENV.fetch('WEB_CONCURRENCY', 5).to_i
177
+ end
178
+
179
+ def redis_timeout
180
+ Rails.env.production? ? 1 : 5
181
+ end
182
+ end
183
+
184
+ # Initialize the pool manager
185
+ FamiliaPoolManager.instance
186
+ ```
187
+
188
+ ### Request-Scoped Connections (Middleware)
189
+
190
+ ```ruby
191
+ # Middleware for per-request connection management
192
+ class FamiliaConnectionMiddleware
193
+ def initialize(app)
194
+ @app = app
195
+ end
196
+
197
+ def call(env)
198
+ # Provide a single connection for the entire request
199
+ ConnectionPool.with do |conn|
200
+ Thread.current[:familia_connection] = conn
201
+ @app.call(env)
202
+ end
203
+ ensure
204
+ Thread.current[:familia_connection] = nil
205
+ end
206
+ end
207
+
208
+ # In your Rack/Rails app
209
+ use FamiliaConnectionMiddleware
210
+ ```
211
+
212
+ ## Model Database Configuration
213
+
214
+ Configure models to use specific logical databases:
215
+
216
+ ```ruby
217
+ class Customer < Familia::Horreum
218
+ self.logical_database = 0 # Primary application data
219
+ field :name, :email, :status
220
+ end
221
+
222
+ class AnalyticsEvent < Familia::Horreum
223
+ self.logical_database = 1 # Separate analytics database
224
+ field :event_type, :user_id, :timestamp, :properties
225
+ end
226
+
227
+ class SessionData < Familia::Horreum
228
+ self.logical_database = 2 # Fast cache database
229
+ feature :expiration
230
+ default_expiration 1.hour
231
+ field :user_id, :data, :csrf_token
232
+ end
233
+
234
+ class BackgroundJob < Familia::Horreum
235
+ self.logical_database = 3 # Job queue database
236
+ field :job_type, :payload, :status, :retry_count
237
+ end
238
+ ```
239
+
240
+ ## Performance Benefits
241
+
242
+ ### Without Connection Pooling
243
+
244
+ Each operation may trigger database switches:
245
+
246
+ ```ruby
247
+ # These operations might use different connections, causing SELECT commands:
248
+ customer = Customer.find(123) # SELECT 0, then query
249
+ session = SessionData.find(456) # SELECT 2, then query
250
+ analytics = AnalyticsEvent.find(789) # SELECT 1, then query
251
+ ```
252
+
253
+ ### With Connection Pooling
254
+
255
+ Connections stay on the correct database:
256
+
257
+ ```ruby
258
+ # Each model uses its dedicated connection pool:
259
+ customer = Customer.find(123) # Connection already on DB 0
260
+ session = SessionData.find(456) # Different connection, already on DB 2
261
+ analytics = AnalyticsEvent.find(789) # Different connection, already on DB 1
262
+ ```
263
+
264
+ ## Pool Sizing Guidelines
265
+
266
+ ### Web Applications
267
+ - **Formula**: `(threads_per_process * processes) + buffer`
268
+ - **Puma**: `(threads * workers) + 2`
269
+ - **Unicorn**: `processes + 2`
270
+
271
+ ### Background Jobs
272
+ - **Sidekiq**: `concurrency + 2`
273
+ - **DelayedJob**: `worker_processes + 2`
274
+
275
+ ### Database-Specific Sizing
276
+ ```ruby
277
+ def pool_size_for_database(db)
278
+ base_size = web_concurrency + sidekiq_concurrency
279
+
280
+ case db
281
+ when 0 then base_size + 5 # Main DB: highest usage
282
+ when 1 then 3 # Analytics: batch operations
283
+ when 2 then base_size + 2 # Sessions: per-request access
284
+ when 3 then sidekiq_concurrency # Jobs: worker access only
285
+ else 5 # Default for new DBs
286
+ end
287
+ end
288
+ ```
289
+
290
+ ## Monitoring and Debugging
291
+
292
+ ### Enable Debug Mode
293
+
294
+ ```ruby
295
+ Familia.debug = true
296
+ # Shows database selection and connection provider usage
297
+ ```
298
+
299
+ ### Pool Usage Monitoring
300
+
301
+ ```ruby
302
+ class PoolMonitor
303
+ def self.stats
304
+ FamiliaPoolManager.instance.instance_variable_get(:@pools).map do |key, pool|
305
+ {
306
+ database: key,
307
+ size: pool.size,
308
+ available: pool.available,
309
+ checked_out: pool.size - pool.available
310
+ }
311
+ end
312
+ end
313
+
314
+ def self.health_check
315
+ stats.each do |stat|
316
+ utilization = (stat[:checked_out] / stat[:size].to_f) * 100
317
+ puts "DB #{stat[:database]}: #{utilization.round(1)}% utilized"
318
+ warn "High utilization!" if utilization > 80
319
+ end
320
+ end
321
+ end
322
+ ```
323
+
324
+ ### Connection Testing
325
+
326
+ ```ruby
327
+ # Test concurrent access patterns
328
+ def test_concurrent_access
329
+ threads = 20.times.map do |i|
330
+ Thread.new do
331
+ 50.times do |j|
332
+ Customer.create(name: "test-#{i}-#{j}")
333
+ SessionData.create(user_id: i, data: "session-#{j}")
334
+ end
335
+ end
336
+ end
337
+
338
+ threads.each(&:join)
339
+ puts "Concurrent test completed"
340
+ end
341
+ ```
342
+
343
+ ## Troubleshooting
344
+
345
+ ### Common Issues
346
+
347
+ **1. Wrong Database Connections**
348
+ ```ruby
349
+ # Problem: Provider not setting DB correctly
350
+ Redis.new(host: 'localhost', port: 6379) # Missing db: parameter
351
+
352
+ # Solution: Always specify database
353
+ Redis.new(host: 'localhost', port: 6379, db: parsed_uri.db || 0)
354
+ ```
355
+
356
+ **2. Pool Exhaustion**
357
+ ```ruby
358
+ # Monitor pool usage
359
+ ConnectionPool.stats # If available
360
+ # Increase pool size or reduce hold time
361
+ ```
362
+
363
+ **3. Connection Leaks**
364
+ ```ruby
365
+ # Always use .with for pool connections
366
+ pool.with do |conn|
367
+ # Use connection
368
+ end
369
+
370
+ # Never checkout without returning
371
+ conn = pool.checkout # ❌ Can leak
372
+ ```
373
+
374
+ ### Error Handling
375
+
376
+ ```ruby
377
+ Familia.connection_provider = lambda do |uri|
378
+ begin
379
+ get_pooled_connection(uri)
380
+ rescue Redis::ConnectionError => e
381
+ # Log error, potentially retry or fall back
382
+ Familia.logger.error "Connection failed: #{e.message}"
383
+ raise Familia::ConnectionError, "Pool connection failed"
384
+ end
385
+ end
386
+ ```
387
+
388
+ ## Best Practices
389
+
390
+ 1. **Return Pre-Selected Connections**: Provider must return connections on the correct DB
391
+ 2. **One Pool Per Database**: Each logical DB needs its own pool
392
+ 3. **Thread Safety**: Use thread-safe pool creation and access
393
+ 4. **Monitor Usage**: Track pool utilization and adjust sizes
394
+ 5. **Proper Sizing**: Account for all concurrent access patterns
395
+ 6. **Error Handling**: Gracefully handle connection failures
396
+ 7. **Connection Validation**: Verify connections are healthy before use
397
+
398
+ ## Integration Examples
399
+
400
+ ### Roda Application
401
+
402
+ ```ruby
403
+ class App < Roda
404
+ plugin :hooks
405
+
406
+ before do
407
+ # Connection pooling handled automatically via provider
408
+ # Each request gets appropriate connections per database
409
+ end
410
+
411
+ route do |r|
412
+ r.get 'customers', Integer do |id|
413
+ customer = Customer.find(id) # Uses DB 0 pool
414
+ session = SessionData.find(r.env) # Uses DB 2 pool
415
+
416
+ render_json(customer: customer, session: session)
417
+ end
418
+ end
419
+ end
420
+ ```
421
+
422
+ ### Background Jobs
423
+
424
+ ```ruby
425
+ class ProcessCustomerJob
426
+ include Sidekiq::Worker
427
+
428
+ def perform(customer_id)
429
+ # Each of these uses appropriate database pool
430
+ customer = Customer.find(customer_id) # DB 0
431
+ SessionData.expire_for_user(customer_id) # DB 2
432
+ AnalyticsEvent.track('job.completed', user: customer_id) # DB 1
433
+ end
434
+ end
435
+ ```
436
+
437
+ This connection pooling system provides the foundation for scalable, performant Familia applications with proper resource management across multiple logical databases.
@@ -0,0 +1,101 @@
1
+ # Encrypted Fields Overview
2
+
3
+ ## Quick Start
4
+
5
+ Add encrypted field support to any Familia model in one line:
6
+
7
+ ```ruby
8
+ class User < Familia::Horreum
9
+ encrypted_field :diary_entry
10
+ end
11
+ ```
12
+
13
+ ## What It Does
14
+
15
+ - **Automatic Encryption**: Fields are encrypted before storing in Redis/Valkey
16
+ - **Transparent Decryption**: Access encrypted fields like normal attributes
17
+ - **Modular Providers**: Pluggable encryption algorithms (XChaCha20-Poly1305, AES-GCM)
18
+ - **Secure by Default**: Uses authenticated encryption with automatic algorithm selection
19
+ - **Zero Boilerplate**: No manual encrypt/decrypt calls needed
20
+
21
+ ## When to Use
22
+
23
+ Use encrypted fields for:
24
+ - Personal Identifiable Information (PII)
25
+ - API keys and secrets
26
+ - Medical records
27
+ - Financial data
28
+ - Any sensitive user data
29
+
30
+ ## Basic Example
31
+
32
+ ```ruby
33
+ class Customer < Familia::Horreum
34
+ field :email # Regular field
35
+ encrypted_field :secret_recipe # Encrypted field
36
+ encrypted_field :diary_entry # Another encrypted field
37
+ end
38
+
39
+ # Usage is identical to regular fields
40
+ customer = Customer.new(
41
+ email: 'user@example.com',
42
+ secret_recipe: 'Add extra vanilla',
43
+ diary_entry: 'Today I learned Redis is fast'
44
+ )
45
+
46
+ customer.save
47
+ customer.secret_recipe # => "Add extra vanilla" (decrypted automatically)
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ Set your encryption key in environment:
53
+
54
+ ```bash
55
+ export FAMILIA_ENCRYPTION_KEY=$(openssl rand -base64 32)
56
+ ```
57
+
58
+ Configure in your app:
59
+
60
+ ```ruby
61
+ Familia.configure do |config|
62
+ config.encryption_keys = {
63
+ v1: ENV['FAMILIA_ENCRYPTION_KEY']
64
+ }
65
+ config.current_key_version = :v1
66
+
67
+ # Optional: customize personalization for key derivation
68
+ config.encryption_personalization = 'MyApp-2024'
69
+ end
70
+ ```
71
+
72
+ ## Advanced Features
73
+
74
+ ### Per-Field Algorithm Selection
75
+
76
+ Choose specific encryption algorithms for different fields:
77
+
78
+ ```ruby
79
+ class SecretVault < Familia::Horreum
80
+ # Use default best-available algorithm
81
+ encrypted_field :user_secret
82
+
83
+ # Force specific algorithm (when implemented)
84
+ # encrypted_field :ultra_secure_data, algorithm: 'xchacha20poly1305'
85
+ end
86
+ ```
87
+
88
+ ### Provider Priority System
89
+
90
+ Familia automatically selects the best available encryption provider:
91
+
92
+ 1. **XChaCha20-Poly1305** (Priority: 100) - Requires `rbnacl` gem
93
+ 2. **AES-256-GCM** (Priority: 50) - Uses OpenSSL, always available
94
+
95
+ ```ruby
96
+ # Check current encryption status
97
+ puts Familia::Encryption.status
98
+ # => { default_algorithm: "xchacha20poly1305",
99
+ # available_algorithms: ["xchacha20poly1305", "aes-256-gcm"],
100
+ # using_hardware: false }
101
+ ```