familia 2.0.0.pre19 → 2.0.0.pre22

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 (370) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/.talismanrc +5 -1
  8. data/CHANGELOG.rst +220 -112
  9. data/CLAUDE.md +28 -1
  10. data/Gemfile +1 -1
  11. data/Gemfile.lock +20 -17
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  15. data/docs/guides/encryption.md +486 -0
  16. data/docs/guides/feature-encrypted-fields.md +123 -7
  17. data/docs/guides/feature-expiration.md +161 -117
  18. data/docs/guides/feature-external-identifiers.md +415 -443
  19. data/docs/guides/feature-object-identifiers.md +400 -269
  20. data/docs/guides/feature-quantization.md +120 -6
  21. data/docs/guides/feature-relationships-indexing.md +318 -0
  22. data/docs/guides/feature-relationships-methods.md +146 -604
  23. data/docs/guides/feature-relationships-participation.md +263 -0
  24. data/docs/guides/feature-relationships.md +118 -136
  25. data/docs/guides/feature-system-devs.md +176 -693
  26. data/docs/guides/feature-system.md +119 -6
  27. data/docs/guides/feature-transient-fields.md +81 -0
  28. data/docs/guides/field-system.md +778 -0
  29. data/docs/guides/index.md +32 -15
  30. data/docs/guides/logging.md +187 -0
  31. data/docs/guides/optimized-loading.md +674 -0
  32. data/docs/guides/thread-safety-monitoring.md +61 -0
  33. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  34. data/docs/migrating/v2.0.0-pre22.md +241 -0
  35. data/docs/overview.md +7 -9
  36. data/docs/reference/api-technical.md +267 -320
  37. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  38. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  39. data/examples/autoloader/mega_customer.rb +2 -0
  40. data/examples/datatype_standalone.rb +4 -3
  41. data/examples/encrypted_fields.rb +2 -1
  42. data/examples/json_usage_patterns.rb +2 -0
  43. data/examples/relationships.rb +3 -0
  44. data/examples/safe_dump.rb +2 -1
  45. data/examples/sampling_demo.rb +53 -0
  46. data/examples/single_connection_transaction_confusions.rb +2 -1
  47. data/familia.gemspec +2 -1
  48. data/lib/familia/base.rb +2 -0
  49. data/lib/familia/connection/behavior.rb +2 -0
  50. data/lib/familia/connection/handlers.rb +2 -0
  51. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  52. data/lib/familia/connection/middleware.rb +34 -24
  53. data/lib/familia/connection/operation_core.rb +3 -2
  54. data/lib/familia/connection/operations.rb +2 -0
  55. data/lib/familia/connection/pipelined_core.rb +3 -3
  56. data/lib/familia/connection/transaction_core.rb +69 -2
  57. data/lib/familia/connection.rb +18 -3
  58. data/lib/familia/data_type/class_methods.rb +3 -1
  59. data/lib/familia/data_type/connection.rb +2 -0
  60. data/lib/familia/data_type/database_commands.rb +2 -0
  61. data/lib/familia/data_type/serialization.rb +79 -52
  62. data/lib/familia/data_type/settings.rb +2 -0
  63. data/lib/familia/data_type/types/counter.rb +2 -0
  64. data/lib/familia/data_type/types/hashkey.rb +7 -5
  65. data/lib/familia/data_type/types/listkey.rb +2 -0
  66. data/lib/familia/data_type/types/lock.rb +2 -0
  67. data/lib/familia/data_type/types/sorted_set.rb +7 -10
  68. data/lib/familia/data_type/types/stringkey.rb +24 -0
  69. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  70. data/lib/familia/data_type.rb +2 -0
  71. data/lib/familia/encryption/encrypted_data.rb +4 -2
  72. data/lib/familia/encryption/manager.rb +2 -0
  73. data/lib/familia/encryption/provider.rb +2 -0
  74. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  75. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  76. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  77. data/lib/familia/encryption/registry.rb +2 -0
  78. data/lib/familia/encryption/request_cache.rb +2 -0
  79. data/lib/familia/encryption.rb +9 -2
  80. data/lib/familia/errors.rb +2 -0
  81. data/lib/familia/features/autoloader.rb +2 -0
  82. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  83. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  84. data/lib/familia/features/encrypted_fields.rb +2 -2
  85. data/lib/familia/features/expiration/extensions.rb +3 -1
  86. data/lib/familia/features/expiration.rb +12 -4
  87. data/lib/familia/features/external_identifier.rb +62 -7
  88. data/lib/familia/features/object_identifier.rb +49 -0
  89. data/lib/familia/features/quantization.rb +3 -1
  90. data/lib/familia/features/relationships/README.md +3 -1
  91. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  92. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  93. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
  95. data/lib/familia/features/relationships/indexing.rb +3 -0
  96. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  98. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  100. data/lib/familia/features/relationships/participation.rb +155 -69
  101. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  102. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  103. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  104. data/lib/familia/features/relationships.rb +5 -3
  105. data/lib/familia/features/safe_dump.rb +2 -0
  106. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  107. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  108. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  109. data/lib/familia/features/transient_fields.rb +2 -0
  110. data/lib/familia/features.rb +2 -0
  111. data/lib/familia/field_type.rb +3 -1
  112. data/lib/familia/horreum/connection.rb +17 -1
  113. data/lib/familia/horreum/database_commands.rb +8 -1
  114. data/lib/familia/horreum/definition.rb +16 -6
  115. data/lib/familia/horreum/management.rb +353 -52
  116. data/lib/familia/horreum/persistence.rb +179 -108
  117. data/lib/familia/horreum/related_fields.rb +2 -0
  118. data/lib/familia/horreum/serialization.rb +23 -4
  119. data/lib/familia/horreum/settings.rb +2 -0
  120. data/lib/familia/horreum/utils.rb +2 -0
  121. data/lib/familia/horreum.rb +15 -1
  122. data/lib/familia/identifier_extractor.rb +3 -1
  123. data/lib/familia/instrumentation.rb +156 -0
  124. data/lib/familia/json_serializer.rb +2 -0
  125. data/lib/familia/logging.rb +92 -32
  126. data/lib/familia/refinements/dear_json.rb +2 -0
  127. data/lib/familia/refinements/stylize_words.rb +2 -14
  128. data/lib/familia/refinements/time_literals.rb +2 -0
  129. data/lib/familia/refinements.rb +2 -0
  130. data/lib/familia/secure_identifier.rb +10 -2
  131. data/lib/familia/settings.rb +2 -0
  132. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  133. data/lib/familia/thread_safety/monitor.rb +328 -0
  134. data/lib/familia/utils.rb +13 -0
  135. data/lib/familia/verifiable_identifier.rb +3 -1
  136. data/lib/familia/version.rb +3 -1
  137. data/lib/familia.rb +31 -4
  138. data/lib/middleware/database_command_counter.rb +152 -0
  139. data/lib/middleware/database_logger.rb +295 -170
  140. data/lib/multi_result.rb +61 -31
  141. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  142. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  143. data/try/edge_cases/json_serialization_try.rb +2 -0
  144. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  145. data/try/edge_cases/race_conditions_try.rb +4 -0
  146. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  147. data/try/edge_cases/string_coercion_try.rb +2 -0
  148. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  149. data/try/features/count_any_edge_cases_try.rb +486 -0
  150. data/try/features/count_any_methods_try.rb +197 -0
  151. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  152. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  153. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  154. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  155. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  156. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  158. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  159. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  160. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  161. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  162. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  164. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  165. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  166. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  167. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  168. data/try/features/encryption/config_persistence_try.rb +4 -0
  169. data/try/features/encryption/core_try.rb +4 -0
  170. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  171. data/try/features/encryption/module_loading_try.rb +4 -0
  172. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  173. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  174. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  175. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  176. data/try/features/expiration/expiration_try.rb +4 -0
  177. data/try/features/external_identifier/external_identifier_try.rb +305 -8
  178. data/try/features/feature_dependencies_try.rb +2 -0
  179. data/try/features/feature_improvements_try.rb +2 -0
  180. data/try/features/field_groups_try.rb +2 -0
  181. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  182. data/try/features/object_identifier/object_identifier_try.rb +140 -0
  183. data/try/features/quantization/quantization_try.rb +4 -0
  184. data/try/features/real_feature_integration_try.rb +2 -0
  185. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  186. data/try/features/relationships/indexing_rebuild_try.rb +606 -0
  187. data/try/features/relationships/indexing_try.rb +2 -0
  188. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  189. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  190. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  192. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  193. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  194. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  195. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  196. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  197. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  198. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  199. data/try/features/relationships/relationships_performance_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  201. data/try/features/relationships/relationships_try.rb +6 -4
  202. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  203. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  204. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  205. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  206. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  207. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  209. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  210. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  211. data/try/integration/connection/handler_constraints_try.rb +4 -0
  212. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  213. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  214. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  215. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  216. data/try/integration/connection/pools_try.rb +4 -0
  217. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  218. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  219. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  220. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  221. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  222. data/try/integration/connection/transaction_modes_try.rb +4 -0
  223. data/try/integration/conventional_inheritance_try.rb +4 -0
  224. data/try/integration/create_method_try.rb +4 -0
  225. data/try/integration/cross_component_try.rb +4 -0
  226. data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
  227. data/try/integration/data_types/datatype_transactions_try.rb +17 -7
  228. data/try/integration/database_consistency_try.rb +4 -0
  229. data/try/integration/familia_extended_try.rb +4 -0
  230. data/try/integration/familia_members_methods_try.rb +4 -0
  231. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  232. data/try/integration/models/customer_try.rb +7 -3
  233. data/try/integration/models/datatype_base_try.rb +4 -0
  234. data/try/integration/models/familia_object_try.rb +4 -0
  235. data/try/integration/persistence_operations_try.rb +4 -0
  236. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  237. data/try/integration/save_methods_consistency_try.rb +241 -0
  238. data/try/integration/scenarios_try.rb +4 -0
  239. data/try/integration/secure_identifier_try.rb +4 -0
  240. data/try/integration/transaction_safety_core_try.rb +176 -0
  241. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  242. data/try/integration/verifiable_identifier_try.rb +4 -0
  243. data/try/investigation/pipeline_routing/README.md +228 -0
  244. data/try/performance/benchmarks_try.rb +4 -0
  245. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  246. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  247. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  248. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  249. data/try/support/debugging/debug_aad_process.rb +3 -0
  250. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  251. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  252. data/try/support/debugging/debug_context_aad.rb +3 -0
  253. data/try/support/debugging/debug_context_simple.rb +3 -0
  254. data/try/support/debugging/debug_cross_context.rb +3 -0
  255. data/try/support/debugging/debug_database_load.rb +3 -0
  256. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  257. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  258. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  259. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  260. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  261. data/try/support/debugging/debug_load_path.rb +3 -0
  262. data/try/support/debugging/debug_method_definition.rb +3 -0
  263. data/try/support/debugging/debug_method_resolution.rb +3 -0
  264. data/try/support/debugging/debug_minimal.rb +3 -0
  265. data/try/support/debugging/debug_provider.rb +3 -0
  266. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  267. data/try/support/debugging/debug_string_class.rb +3 -0
  268. data/try/support/debugging/debug_test.rb +3 -0
  269. data/try/support/debugging/debug_test_design.rb +3 -0
  270. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  271. data/try/support/debugging/provider_diagnostics.rb +4 -0
  272. data/try/support/helpers/test_cleanup.rb +4 -0
  273. data/try/support/helpers/test_helpers.rb +5 -0
  274. data/try/support/memory/memory_basic_test.rb +4 -0
  275. data/try/support/memory/memory_detailed_test.rb +4 -0
  276. data/try/support/memory/memory_search_for_string.rb +4 -0
  277. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  278. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  279. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  280. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  282. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  283. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  284. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  285. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  286. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  287. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  290. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  291. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  292. data/try/thread_safety/README.md +496 -0
  293. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  294. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  295. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  296. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  297. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  298. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  299. data/try/thread_safety/field_registration_race_try.rb +222 -0
  300. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  301. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  302. data/try/thread_safety/module_config_race_try.rb +175 -0
  303. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  304. data/try/unit/core/autoloader_try.rb +4 -0
  305. data/try/unit/core/base_enhancements_try.rb +4 -0
  306. data/try/unit/core/connection_try.rb +4 -0
  307. data/try/unit/core/errors_try.rb +4 -0
  308. data/try/unit/core/extensions_try.rb +4 -0
  309. data/try/unit/core/familia_logger_try.rb +2 -0
  310. data/try/unit/core/familia_try.rb +4 -0
  311. data/try/unit/core/middleware_sampling_try.rb +335 -0
  312. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  313. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  314. data/try/unit/core/middleware_try.rb +4 -0
  315. data/try/unit/core/settings_try.rb +4 -0
  316. data/try/unit/core/time_utils_try.rb +4 -0
  317. data/try/unit/core/tools_try.rb +4 -0
  318. data/try/unit/core/utils_try.rb +37 -0
  319. data/try/unit/data_types/boolean_try.rb +39 -22
  320. data/try/unit/data_types/counter_try.rb +4 -0
  321. data/try/unit/data_types/datatype_base_try.rb +4 -0
  322. data/try/unit/data_types/hash_try.rb +6 -2
  323. data/try/unit/data_types/list_try.rb +4 -0
  324. data/try/unit/data_types/lock_try.rb +4 -0
  325. data/try/unit/data_types/serialization_try.rb +386 -0
  326. data/try/unit/data_types/sorted_set_try.rb +4 -0
  327. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  328. data/try/unit/data_types/string_try.rb +4 -0
  329. data/try/unit/data_types/unsortedset_try.rb +4 -0
  330. data/try/unit/familia_resolve_class_try.rb +116 -0
  331. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  332. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  333. data/try/unit/horreum/base_try.rb +4 -0
  334. data/try/unit/horreum/class_methods_try.rb +4 -0
  335. data/try/unit/horreum/commands_try.rb +4 -0
  336. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  337. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
  338. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  339. data/try/unit/horreum/field_categories_try.rb +4 -0
  340. data/try/unit/horreum/field_definition_try.rb +4 -0
  341. data/try/unit/horreum/initialization_try.rb +4 -0
  342. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  343. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  344. data/try/unit/horreum/relations_try.rb +4 -0
  345. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  346. data/try/unit/horreum/serialization_try.rb +4 -0
  347. data/try/unit/horreum/settings_try.rb +4 -0
  348. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  349. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  350. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  351. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  352. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  353. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  354. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  355. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  356. data/try/unit/thread_safety_monitor_try.rb +149 -0
  357. metadata +69 -17
  358. data/.github/workflows/code-quality.yml +0 -138
  359. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  360. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  361. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  362. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -1,4 +1,6 @@
1
1
  # examples/autoloader/mega_customer/features/deprecated_fields.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # Extend the MegaCustomer class to organize all of the deprecated fields into one place
4
6
  class MegaCustomer < Familia::Horreum
@@ -1,4 +1,6 @@
1
1
  # examples/autoloader/mega_customer/safe_dump_fields.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # Extend the MegaCustomer class to add safe dump fields
4
6
  class MegaCustomer < Familia::Horreum
@@ -1,4 +1,6 @@
1
1
  # examples/autoloader/mega_customer.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative '../../lib/familia'
4
6
 
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # examples/datatype_standalone.rb
3
+ #
4
+ # frozen_string_literal: true
3
5
 
4
6
  # Demonstration: Familia::StringKey for Session Storage with Atomic Transactions
5
- #
6
7
  # This example shows how to use Familia's DataType classes independently
7
8
  # without inheriting from Familia::Horreum. It implements a Rack-compatible
8
9
  # session store using Familia::StringKey for secure, TTL-managed storage.
@@ -146,7 +147,7 @@ class SecureSessionStore < Rack::Session::Abstract::PersistedSecure
146
147
  [sid, session_data]
147
148
  rescue Familia::PersistenceError => e
148
149
  # Log error in development/debugging
149
- Familia.ld "[Session] Error reading session #{sid_string}: #{e.message}"
150
+ Familia.debug "[Session] Error reading session #{sid_string}: #{e.message}"
150
151
 
151
152
  # Return new session on any error
152
153
  [generate_sid, {}]
@@ -196,7 +197,7 @@ class SecureSessionStore < Rack::Session::Abstract::PersistedSecure
196
197
  sid
197
198
  rescue Familia::PersistenceError => e
198
199
  # Log error in development/debugging
199
- Familia.ld "[Session] Error writing session #{sid_string}: #{e.message}"
200
+ Familia.debug "[Session] Error writing session #{sid_string}: #{e.message}"
200
201
 
201
202
  # Return false to indicate failure
202
203
  false
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  # examples/encrypted_fields.rb
4
3
  #
4
+ # frozen_string_literal: true
5
+
5
6
  # Demonstrates the EncryptedFields feature for protecting sensitive data.
6
7
  # This feature provides transparent encryption/decryption of sensitive fields
7
8
  # using strong cryptographic algorithms with field-specific key derivation.
@@ -1,5 +1,7 @@
1
1
  # examples/json_usage_patterns.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  # This file demonstrates the JSON serialization patterns available in Familia,
4
6
  # showing both the secure defaults and optional developer convenience features.
5
7
 
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
+ # examples/relationships.rb
3
+ #
4
+ # frozen_string_literal: true
2
5
 
3
6
  # Basic Relationships Example
4
7
  # This example demonstrates the core features of Familia's relationships system
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  # examples/safe_dump.rb
4
3
  #
4
+ # frozen_string_literal: true
5
+
5
6
  # Demonstrates the SafeDump feature with the new DSL methods.
6
7
  # SafeDump allows you to control which fields are exposed when
7
8
  # serializing objects, preventing accidental exposure of sensitive data.
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/sampling_demo.rb
3
+ #
4
+ # frozen_string_literal: true
5
+
6
+ # Demonstrates DatabaseLogger sampling to reduce log volume in high-traffic scenarios.
7
+ # Run with: bundle exec ruby examples/sampling_demo.rb
8
+
9
+ require_relative '../lib/familia'
10
+ require 'logger'
11
+
12
+ # Enable database command logging (middleware registered automatically)
13
+ Familia.enable_database_logging = true
14
+ DatabaseLogger.logger = Familia::FamiliaLogger.new($stdout)
15
+ DatabaseLogger.logger.level = Familia::FamiliaLogger::TRACE
16
+
17
+ # Scenario 1: No sampling (default) - logs every command
18
+ puts "\n=== Scenario 1: No Sampling (logs all 100 commands) ==="
19
+ DatabaseLogger.sample_rate = nil
20
+ 100.times { |i| Familia.dbclient.set("key_#{i}", "value_#{i}") }
21
+ puts "Commands captured: #{DatabaseLogger.commands.size}"
22
+ puts "(Check output above - should see ~100 log lines)"
23
+
24
+ # Scenario 2: 10% sampling - logs ~10 commands
25
+ puts "\n=== Scenario 2: 10% Sampling (logs ~10 of 100 commands) ==="
26
+ DatabaseLogger.clear_commands
27
+ DatabaseLogger.sample_rate = 0.1
28
+ 100.times { |i| Familia.dbclient.set("sampled_10_#{i}", "value_#{i}") }
29
+ puts "Commands captured: #{DatabaseLogger.commands.size}"
30
+ puts "(Check output above - should see ~10 log lines)"
31
+
32
+ # Scenario 3: 1% sampling - logs ~1 command (production-friendly)
33
+ puts "\n=== Scenario 3: 1% Sampling (logs ~1 of 100 commands) ==="
34
+ DatabaseLogger.clear_commands
35
+ DatabaseLogger.sample_rate = 0.01
36
+ 100.times { |i| Familia.dbclient.set("sampled_1_#{i}", "value_#{i}") }
37
+ puts "Commands captured: #{DatabaseLogger.commands.size}"
38
+ puts "(Check output above - should see ~1 log line)"
39
+
40
+ # Scenario 4: Sampling with structured logging
41
+ puts "\n=== Scenario 4: 10% Sampling + Structured Logging ==="
42
+ DatabaseLogger.clear_commands
43
+ DatabaseLogger.sample_rate = 0.1
44
+ DatabaseLogger.structured_logging = true
45
+ 100.times { |i| Familia.dbclient.set("structured_#{i}", "value_#{i}") }
46
+ puts "Commands captured: #{DatabaseLogger.commands.size}"
47
+ puts "(Check structured output above)"
48
+
49
+ puts "\n=== Key Insights ==="
50
+ puts "✓ Command capture is unaffected (always 100 commands captured)"
51
+ puts "✓ Only logger output is sampled (reduces log volume)"
52
+ puts "✓ Tests can verify commands while production logs stay clean"
53
+ puts "✓ Deterministic sampling (every Nth command) ensures consistency"
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # examples/single_connection_transaction_confusions.rb
3
+ #
4
+ # frozen_string_literal: true
3
5
 
4
6
  # Redis Single Connection Mode Confusions
5
- #
6
7
  # This file demonstrates why mixing Redis operation modes on a single connection
7
8
  # causes subtle but critical failures in production applications.
8
9
  #
data/familia.gemspec CHANGED
@@ -17,9 +17,10 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.required_ruby_version = Gem::Requirement.new('>= 3.4')
20
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.3.6')
21
21
 
22
22
  spec.add_dependency 'benchmark', '~> 0.4'
23
+ spec.add_dependency 'concurrent-ruby', '~> 1.3'
23
24
  spec.add_dependency 'connection_pool', '~> 2.5'
24
25
  spec.add_dependency 'csv', '~> 3.3'
25
26
  spec.add_dependency 'logger', '~> 1.7'
data/lib/familia/base.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # lib/familia/base.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # A common module for Familia::DataType and Familia::Horreum to include.
@@ -1,3 +1,5 @@
1
+ # lib/familia/connection/behavior.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
5
  # lib/familia/connection/behavior.rb
@@ -1,4 +1,6 @@
1
1
  # lib/familia/connection/handlers.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # Familia
4
6
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/connection/individual_command_proxy.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Connection
@@ -1,6 +1,9 @@
1
1
  # lib/familia/connection/middleware.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative '../../middleware/database_logger'
6
+ require_relative '../../middleware/database_command_counter'
4
7
 
5
8
  module Familia
6
9
  module Connection
@@ -13,19 +16,20 @@ module Familia
13
16
 
14
17
  # @return [Integer] Current middleware version for cache invalidation
15
18
  def middleware_version
16
- @middleware_version
19
+ @middleware_version.value
17
20
  end
18
21
 
19
22
  # Increments the middleware version, invalidating all cached connections
20
23
  def increment_middleware_version!
21
- @middleware_version += 1
22
- Familia.trace :MIDDLEWARE_VERSION, nil, "Incremented to #{@middleware_version}"
24
+ new_version = @middleware_version.increment
25
+ Familia.trace :MIDDLEWARE_VERSION, nil, "Incremented to #{new_version}"
23
26
  end
24
27
 
25
28
  # Sets a versioned fiber-local connection
26
29
  def fiber_connection=(connection)
27
- Fiber[:familia_connection] = [connection, middleware_version]
28
- Familia.trace :FIBER_CONNECTION, nil, "Set with version #{middleware_version}"
30
+ current_version = middleware_version
31
+ Fiber[:familia_connection] = [connection, current_version]
32
+ Familia.trace :FIBER_CONNECTION, nil, "Set with version #{current_version}"
29
33
  end
30
34
 
31
35
  # Clears the fiber-local connection
@@ -81,18 +85,23 @@ module Familia
81
85
  # Familia.reconnect! # Force new connections with middleware
82
86
  #
83
87
  def reconnect!
84
- # Allow middleware to be re-registered
85
- @middleware_registered = false
86
- register_middleware_once
87
-
88
- # Clear connection chain to force rebuild
89
- @connection_chain = nil
90
-
91
- # Increment version to invalidate all cached connections
92
- increment_middleware_version!
93
-
94
- # Clear fiber-local connections
95
- clear_fiber_connection!
88
+ # Thread-safe: Use same mutex as dbclient to protect @connection_chain
89
+ @connection_chain_mutex.synchronize do
90
+ # Allow middleware to be re-registered by resetting all flags
91
+ @middleware_registered = false
92
+ @logger_registered = false
93
+ @counter_registered = false
94
+ register_middleware_once
95
+
96
+ # Clear connection chain to force rebuild
97
+ @connection_chain = nil
98
+
99
+ # Increment version to invalidate all cached connections
100
+ increment_middleware_version!
101
+
102
+ # Clear fiber-local connections
103
+ clear_fiber_connection!
104
+ end
96
105
 
97
106
  Familia.trace :RECONNECT, nil, 'Connection chain cleared, will rebuild with current middleware on next use'
98
107
  end
@@ -102,27 +111,28 @@ module Familia
102
111
  # Registers middleware once globally, regardless of when clients are created.
103
112
  # This prevents duplicate middleware registration and ensures all clients get middleware.
104
113
  def register_middleware_once
105
- # Skip if already registered
106
- return if @middleware_registered
107
-
108
114
  # Check if any middleware is enabled
109
115
  return unless Familia.enable_database_logging || Familia.enable_database_counter
110
116
 
111
- if Familia.enable_database_logging
117
+ # Register each middleware independently to avoid early return bug
118
+ # where enabling one middleware prevents the other from being registered
119
+ if Familia.enable_database_logging && !@logger_registered
112
120
  DatabaseLogger.logger = Familia.logger
113
121
  RedisClient.register(DatabaseLogger)
114
122
  Familia.trace :MIDDLEWARE_REGISTERED, nil, 'Registered DatabaseLogger'
123
+ @logger_registered = true
115
124
  end
116
125
 
117
- if Familia.enable_database_counter
126
+ if Familia.enable_database_counter && !@counter_registered
118
127
  # NOTE: This middleware uses AtomicFixnum from concurrent-ruby which is
119
128
  # less contentious than Mutex-based counters. Safe for production.
120
129
  RedisClient.register(DatabaseCommandCounter)
121
130
  Familia.trace :MIDDLEWARE_REGISTERED, nil, 'Registered DatabaseCommandCounter'
131
+ @counter_registered = true
122
132
  end
123
133
 
124
- # Set flag after successful registration
125
- @middleware_registered = true
134
+ # Set global flag when any middleware is registered
135
+ @middleware_registered = @logger_registered || @counter_registered
126
136
  end
127
137
  end
128
138
  end
@@ -1,3 +1,5 @@
1
+ # lib/familia/connection/operation_core.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
5
  module Familia
@@ -85,8 +87,7 @@ module Familia
85
87
 
86
88
  # Return MultiResult format for consistency
87
89
  results = proxy.collected_results
88
- summary_boolean = results.all? { |ret| !ret.is_a?(Exception) }
89
- MultiResult.new(summary_boolean, results)
90
+ MultiResult.new(results)
90
91
  end
91
92
  end
92
93
  end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/connection/operations.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # Familia
4
6
  #
@@ -1,3 +1,5 @@
1
+ # lib/familia/connection/pipelined_core.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
5
  module Familia
@@ -78,9 +80,7 @@ module Familia
78
80
  end
79
81
 
80
82
  # Return same MultiResult format as other methods
81
- # Pipeline success is true if no exceptions occurred (all commands executed)
82
- summary_boolean = command_return_values.none? { |ret| ret.is_a?(Exception) }
83
- MultiResult.new(summary_boolean, command_return_values)
83
+ MultiResult.new(command_return_values)
84
84
  end
85
85
  end
86
86
  end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/connection/transaction_core.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Connection
@@ -8,11 +10,62 @@ module Familia
8
10
  # behavior when transactions are unavailable due to connection handler constraints.
9
11
  # Eliminates code duplication between Operations and Horreum Connection modules.
10
12
  #
13
+ # ## Transaction Safety Rules
14
+ #
15
+ # ### Rule 1: No Save Operations Inside Transactions
16
+ # The following methods CANNOT be called within a transaction context:
17
+ # - `save`, `save!`, `save_if_not_exists!`, `create!`
18
+ # These methods require reading current state for validation, which would
19
+ # return uninspectable Redis::Future objects inside transactions.
20
+ #
21
+ # ### Rule 2: Reentrant Transaction Behavior
22
+ # Nested transaction calls reuse the same connection and do not create new
23
+ # MULTI/EXEC blocks. This ensures atomicity across nested operations.
24
+ #
25
+ # ### Rule 3: Read Operations Return Futures
26
+ # Inside transactions, read operations return Redis::Future objects that
27
+ # cannot be inspected until the transaction completes. Always check conditions
28
+ # before entering the transaction.
29
+ #
30
+ # ### Rule 4: Connection Handler Compatibility
31
+ # - **FiberTransactionHandler**: Supports reentrant transactions
32
+ # - **ProviderConnectionHandler**: Full transaction support
33
+ # - **CreateConnectionHandler**: Full transaction support
34
+ # - **FiberConnectionHandler**: Blocked (raises OperationModeError)
35
+ # - **DefaultConnectionHandler**: Blocked (raises OperationModeError)
36
+ #
37
+ # @example Correct Pattern: Save Before Transaction
38
+ # customer = Customer.new(email: 'test@example.com')
39
+ # customer.save # Validates unique constraints here
40
+ #
41
+ # customer.transaction do
42
+ # customer.increment(:login_count)
43
+ # customer.hset(:last_login, Time.now.to_i)
44
+ # end
45
+ #
46
+ # @example Incorrect Pattern: Save Inside Transaction
47
+ # Customer.transaction do
48
+ # customer = Customer.new(email: 'test@example.com')
49
+ # customer.save # Raises Familia::OperationModeError
50
+ # end
51
+ #
52
+ # @example Reentrant Transactions
53
+ # Customer.transaction do |outer_conn|
54
+ # outer_conn.set('key1', 'value1')
55
+ #
56
+ # # Nested call reuses same connection - no new MULTI/EXEC
57
+ # Customer.transaction do |inner_conn|
58
+ # inner_conn.set('key2', 'value2') # Same connection as outer
59
+ # end
60
+ # end
61
+ #
11
62
  # @example Usage in transaction methods
12
63
  # def transaction(&block)
13
64
  # TransactionCore.execute_transaction(-> { dbclient }, &block)
14
65
  # end
15
66
  #
67
+ # @see docs/transaction_safety.md for complete safety guidelines
68
+ #
16
69
  module TransactionCore
17
70
  # Executes a transaction with configurable fallback behavior
18
71
  #
@@ -21,6 +74,11 @@ module Familia
21
74
  # 2. Reentrant transaction when already within a transaction context
22
75
  # 3. Individual command execution with configurable error/warn/silent modes
23
76
  #
77
+ # ## Safety Mechanisms
78
+ # - Fiber-local storage tracks transaction state across nested calls
79
+ # - Connection handler validation prevents unsafe transaction usage
80
+ # - Automatic cleanup ensures proper state management even on exceptions
81
+ #
24
82
  # @param dbclient_proc [Proc] Lambda that returns the Redis connection
25
83
  # @param block [Proc] Block containing Redis commands to execute
26
84
  # @return [MultiResult] Result object with success status and command results
@@ -72,6 +130,16 @@ module Familia
72
130
  # Handles the standard transaction flow including nested transaction detection,
73
131
  # proper Fiber-local state management, and cleanup in ensure blocks.
74
132
  #
133
+ # ## Implementation Details
134
+ # - Uses Fiber[:familia_transaction] to track active transaction connection
135
+ # - Reentrant behavior: yields existing connection if already in transaction
136
+ # - All commands queued and executed atomically on EXEC
137
+ # - Returns MultiResult with success status and command results
138
+ #
139
+ # ## Thread Safety
140
+ # Each thread has its own root fiber with isolated fiber-local storage,
141
+ # ensuring transactions don't interfere across threads.
142
+ #
75
143
  # @param dbclient_proc [Proc] Lambda that returns the Redis connection
76
144
  # @param block [Proc] Block containing Redis commands to execute
77
145
  # @return [MultiResult] Result object with transaction command results
@@ -90,8 +158,7 @@ module Familia
90
158
  end
91
159
 
92
160
  # Return same MultiResult format as other methods
93
- summary_boolean = command_return_values.all? { |ret| %w[OK 0 1].include?(ret.to_s) }
94
- MultiResult.new(summary_boolean, command_return_values)
161
+ MultiResult.new(command_return_values)
95
162
  end
96
163
  end
97
164
  end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/connection.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative 'connection/behavior'
4
6
  require_relative 'connection/handlers'
@@ -16,7 +18,10 @@ require_relative 'connection/pipelined_core'
16
18
  module Familia
17
19
  @uri = URI.parse 'redis://127.0.0.1:6379'
18
20
  @middleware_registered = false
19
- @middleware_version = 0
21
+ @logger_registered = false
22
+ @counter_registered = false
23
+ @middleware_version = Concurrent::AtomicFixnum.new(0)
24
+ @connection_chain_mutex = Familia::ThreadSafety::InstrumentedMutex.new('connection_chain') # Thread-safe connection chain initialization
20
25
 
21
26
  # The Connection module provides Database connection management for Familia.
22
27
  # It allows easy setup and access to Database clients across different URIs
@@ -86,12 +91,22 @@ module Familia
86
91
  # Retrieves a Database connection using the Chain of Responsibility pattern.
87
92
  # Handles DB selection automatically based on the URI.
88
93
  #
94
+ # Thread-safe: Uses double-checked locking pattern to avoid mutex overhead
95
+ # on the hot path. Only acquires mutex during initial lazy initialization.
96
+ # MRI's GIL provides implicit memory barriers making this pattern safe.
97
+ #
89
98
  # @return [Redis] The Database client for the specified URI
90
99
  # @example Familia.dbclient('redis://localhost:6379/1')
91
100
  # Familia.dbclient(2) # Use DB 2 with default server
92
101
  def dbclient(uri = nil)
93
- @connection_chain ||= build_connection_chain
94
- @connection_chain.handle(uri)
102
+ # Fast path - read with local variable to ensure single read
103
+ chain = @connection_chain
104
+ return chain.handle(uri) if chain
105
+
106
+ # Slow path - initialization only
107
+ @connection_chain_mutex.synchronize do
108
+ @connection_chain ||= build_connection_chain
109
+ end.handle(uri)
95
110
  end
96
111
 
97
112
  # Builds the connection chain with handlers in priority order
@@ -1,4 +1,6 @@
1
- # lib/familia/data_type/definition.rb
1
+ # lib/familia/data_type/class_methods.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class DataType
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/connection.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class DataType
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/database_commands.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class DataType