familia 2.0.0.pre19 → 2.0.0.pre21

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 (372) 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/CHANGELOG.rst +177 -112
  8. data/CLAUDE.md +28 -1
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +20 -17
  11. data/bin/try +16 -0
  12. data/bin/tryouts +16 -0
  13. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  14. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  15. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  16. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  17. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  18. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  19. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  20. data/docs/guides/encryption.md +486 -0
  21. data/docs/guides/feature-encrypted-fields.md +123 -7
  22. data/docs/guides/feature-expiration.md +161 -117
  23. data/docs/guides/feature-external-identifiers.md +415 -443
  24. data/docs/guides/feature-object-identifiers.md +400 -269
  25. data/docs/guides/feature-quantization.md +120 -6
  26. data/docs/guides/feature-relationships-indexing.md +318 -0
  27. data/docs/guides/feature-relationships-methods.md +146 -604
  28. data/docs/guides/feature-relationships-participation.md +263 -0
  29. data/docs/guides/feature-relationships.md +118 -136
  30. data/docs/guides/feature-system-devs.md +176 -693
  31. data/docs/guides/feature-system.md +119 -6
  32. data/docs/guides/feature-transient-fields.md +81 -0
  33. data/docs/guides/field-system.md +778 -0
  34. data/docs/guides/index.md +32 -15
  35. data/docs/guides/logging.md +187 -0
  36. data/docs/guides/optimized-loading.md +674 -0
  37. data/docs/guides/thread-safety-monitoring.md +61 -0
  38. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  39. data/docs/migrating/v2.0.0-pre22.md +241 -0
  40. data/docs/overview.md +7 -9
  41. data/docs/reference/api-technical.md +267 -320
  42. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  43. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  44. data/examples/autoloader/mega_customer.rb +2 -0
  45. data/examples/datatype_standalone.rb +4 -3
  46. data/examples/encrypted_fields.rb +2 -1
  47. data/examples/json_usage_patterns.rb +2 -0
  48. data/examples/relationships.rb +3 -0
  49. data/examples/safe_dump.rb +2 -1
  50. data/examples/sampling_demo.rb +53 -0
  51. data/examples/single_connection_transaction_confusions.rb +2 -1
  52. data/familia.gemspec +2 -1
  53. data/lib/familia/base.rb +2 -0
  54. data/lib/familia/connection/behavior.rb +2 -0
  55. data/lib/familia/connection/handlers.rb +2 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  57. data/lib/familia/connection/middleware.rb +34 -24
  58. data/lib/familia/connection/operation_core.rb +2 -0
  59. data/lib/familia/connection/operations.rb +2 -0
  60. data/lib/familia/connection/pipelined_core.rb +2 -0
  61. data/lib/familia/connection/transaction_core.rb +68 -0
  62. data/lib/familia/connection.rb +18 -3
  63. data/lib/familia/data_type/class_methods.rb +3 -1
  64. data/lib/familia/data_type/connection.rb +2 -0
  65. data/lib/familia/data_type/database_commands.rb +2 -0
  66. data/lib/familia/data_type/serialization.rb +6 -4
  67. data/lib/familia/data_type/settings.rb +2 -0
  68. data/lib/familia/data_type/types/counter.rb +2 -0
  69. data/lib/familia/data_type/types/hashkey.rb +7 -5
  70. data/lib/familia/data_type/types/listkey.rb +2 -0
  71. data/lib/familia/data_type/types/lock.rb +2 -0
  72. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  73. data/lib/familia/data_type/types/stringkey.rb +2 -0
  74. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  75. data/lib/familia/data_type.rb +2 -0
  76. data/lib/familia/encryption/encrypted_data.rb +4 -2
  77. data/lib/familia/encryption/manager.rb +2 -0
  78. data/lib/familia/encryption/provider.rb +2 -0
  79. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  80. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  81. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  82. data/lib/familia/encryption/registry.rb +2 -0
  83. data/lib/familia/encryption/request_cache.rb +2 -0
  84. data/lib/familia/encryption.rb +9 -2
  85. data/lib/familia/errors.rb +2 -0
  86. data/lib/familia/features/autoloader.rb +2 -0
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  89. data/lib/familia/features/encrypted_fields.rb +2 -2
  90. data/lib/familia/features/expiration/extensions.rb +3 -1
  91. data/lib/familia/features/expiration.rb +12 -4
  92. data/lib/familia/features/external_identifier.rb +33 -7
  93. data/lib/familia/features/object_identifier.rb +2 -0
  94. data/lib/familia/features/quantization.rb +3 -1
  95. data/lib/familia/features/relationships/README.md +3 -1
  96. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  98. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  99. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
  100. data/lib/familia/features/relationships/indexing.rb +3 -0
  101. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  102. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  103. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  104. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  105. data/lib/familia/features/relationships/participation.rb +155 -69
  106. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  107. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  108. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  109. data/lib/familia/features/relationships.rb +5 -3
  110. data/lib/familia/features/safe_dump.rb +2 -0
  111. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  112. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  113. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  114. data/lib/familia/features/transient_fields.rb +2 -0
  115. data/lib/familia/features.rb +2 -0
  116. data/lib/familia/field_type.rb +3 -1
  117. data/lib/familia/horreum/connection.rb +17 -1
  118. data/lib/familia/horreum/database_commands.rb +2 -0
  119. data/lib/familia/horreum/definition.rb +16 -6
  120. data/lib/familia/horreum/management.rb +212 -42
  121. data/lib/familia/horreum/persistence.rb +176 -108
  122. data/lib/familia/horreum/related_fields.rb +2 -0
  123. data/lib/familia/horreum/serialization.rb +23 -4
  124. data/lib/familia/horreum/settings.rb +2 -0
  125. data/lib/familia/horreum/utils.rb +2 -0
  126. data/lib/familia/horreum.rb +15 -1
  127. data/lib/familia/identifier_extractor.rb +2 -0
  128. data/lib/familia/instrumentation.rb +156 -0
  129. data/lib/familia/json_serializer.rb +2 -0
  130. data/lib/familia/logging.rb +92 -32
  131. data/lib/familia/refinements/dear_json.rb +2 -0
  132. data/lib/familia/refinements/stylize_words.rb +2 -14
  133. data/lib/familia/refinements/time_literals.rb +2 -0
  134. data/lib/familia/refinements.rb +2 -0
  135. data/lib/familia/secure_identifier.rb +10 -2
  136. data/lib/familia/settings.rb +2 -0
  137. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  138. data/lib/familia/thread_safety/monitor.rb +328 -0
  139. data/lib/familia/utils.rb +13 -0
  140. data/lib/familia/verifiable_identifier.rb +3 -1
  141. data/lib/familia/version.rb +3 -1
  142. data/lib/familia.rb +31 -4
  143. data/lib/middleware/database_command_counter.rb +152 -0
  144. data/lib/middleware/database_logger.rb +295 -170
  145. data/lib/multi_result.rb +2 -0
  146. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  147. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  148. data/try/edge_cases/json_serialization_try.rb +2 -0
  149. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  150. data/try/edge_cases/race_conditions_try.rb +4 -0
  151. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  152. data/try/edge_cases/string_coercion_try.rb +2 -0
  153. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  154. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  155. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  156. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  158. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  160. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  161. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  162. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  163. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  164. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  165. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  166. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  167. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  168. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  169. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  170. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  171. data/try/features/encryption/config_persistence_try.rb +4 -0
  172. data/try/features/encryption/core_try.rb +4 -0
  173. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  174. data/try/features/encryption/module_loading_try.rb +4 -0
  175. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  176. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  177. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  178. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  179. data/try/features/expiration/expiration_try.rb +4 -0
  180. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  181. data/try/features/feature_dependencies_try.rb +2 -0
  182. data/try/features/feature_improvements_try.rb +2 -0
  183. data/try/features/field_groups_try.rb +2 -0
  184. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  185. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  186. data/try/features/quantization/quantization_try.rb +4 -0
  187. data/try/features/real_feature_integration_try.rb +2 -0
  188. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  189. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  190. data/try/features/relationships/indexing_try.rb +2 -0
  191. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  192. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  193. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  194. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  195. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  196. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  197. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  198. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  199. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  201. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  204. data/try/features/relationships/relationships_try.rb +6 -4
  205. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  206. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  207. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  209. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  210. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  211. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  212. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  213. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  214. data/try/integration/connection/handler_constraints_try.rb +4 -0
  215. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  216. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  217. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  218. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  219. data/try/integration/connection/pools_try.rb +4 -0
  220. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  221. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  222. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  223. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  225. data/try/integration/connection/transaction_modes_try.rb +4 -0
  226. data/try/integration/conventional_inheritance_try.rb +4 -0
  227. data/try/integration/create_method_try.rb +4 -0
  228. data/try/integration/cross_component_try.rb +4 -0
  229. data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
  230. data/try/integration/data_types/datatype_transactions_try.rb +4 -0
  231. data/try/integration/database_consistency_try.rb +4 -0
  232. data/try/integration/familia_extended_try.rb +4 -0
  233. data/try/integration/familia_members_methods_try.rb +4 -0
  234. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  235. data/try/integration/models/customer_try.rb +4 -0
  236. data/try/integration/models/datatype_base_try.rb +4 -0
  237. data/try/integration/models/familia_object_try.rb +4 -0
  238. data/try/integration/persistence_operations_try.rb +4 -0
  239. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  240. data/try/integration/save_methods_consistency_try.rb +241 -0
  241. data/try/integration/scenarios_try.rb +4 -0
  242. data/try/integration/secure_identifier_try.rb +4 -0
  243. data/try/integration/transaction_safety_core_try.rb +176 -0
  244. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  245. data/try/integration/verifiable_identifier_try.rb +4 -0
  246. data/try/investigation/pipeline_routing/README.md +228 -0
  247. data/try/performance/benchmarks_try.rb +4 -0
  248. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  249. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  250. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  251. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  252. data/try/support/debugging/debug_aad_process.rb +3 -0
  253. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  254. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  255. data/try/support/debugging/debug_context_aad.rb +3 -0
  256. data/try/support/debugging/debug_context_simple.rb +3 -0
  257. data/try/support/debugging/debug_cross_context.rb +3 -0
  258. data/try/support/debugging/debug_database_load.rb +3 -0
  259. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  260. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  261. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  262. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  263. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  264. data/try/support/debugging/debug_load_path.rb +3 -0
  265. data/try/support/debugging/debug_method_definition.rb +3 -0
  266. data/try/support/debugging/debug_method_resolution.rb +3 -0
  267. data/try/support/debugging/debug_minimal.rb +3 -0
  268. data/try/support/debugging/debug_provider.rb +3 -0
  269. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  270. data/try/support/debugging/debug_string_class.rb +3 -0
  271. data/try/support/debugging/debug_test.rb +3 -0
  272. data/try/support/debugging/debug_test_design.rb +3 -0
  273. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  274. data/try/support/debugging/provider_diagnostics.rb +4 -0
  275. data/try/support/helpers/test_cleanup.rb +4 -0
  276. data/try/support/helpers/test_helpers.rb +5 -0
  277. data/try/support/memory/memory_basic_test.rb +4 -0
  278. data/try/support/memory/memory_detailed_test.rb +4 -0
  279. data/try/support/memory/memory_search_for_string.rb +4 -0
  280. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  282. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  285. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  286. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  287. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  293. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  294. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  295. data/try/thread_safety/README.md +496 -0
  296. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  297. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  298. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  299. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  300. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  301. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  302. data/try/thread_safety/field_registration_race_try.rb +222 -0
  303. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  304. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  305. data/try/thread_safety/module_config_race_try.rb +175 -0
  306. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  307. data/try/unit/core/autoloader_try.rb +4 -0
  308. data/try/unit/core/base_enhancements_try.rb +4 -0
  309. data/try/unit/core/connection_try.rb +4 -0
  310. data/try/unit/core/errors_try.rb +4 -0
  311. data/try/unit/core/extensions_try.rb +4 -0
  312. data/try/unit/core/familia_logger_try.rb +2 -0
  313. data/try/unit/core/familia_try.rb +4 -0
  314. data/try/unit/core/middleware_sampling_try.rb +335 -0
  315. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  316. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  317. data/try/unit/core/middleware_try.rb +4 -0
  318. data/try/unit/core/settings_try.rb +4 -0
  319. data/try/unit/core/time_utils_try.rb +4 -0
  320. data/try/unit/core/tools_try.rb +4 -0
  321. data/try/unit/core/utils_try.rb +37 -0
  322. data/try/unit/data_types/boolean_try.rb +4 -0
  323. data/try/unit/data_types/counter_try.rb +4 -0
  324. data/try/unit/data_types/datatype_base_try.rb +4 -0
  325. data/try/unit/data_types/hash_try.rb +4 -0
  326. data/try/unit/data_types/list_try.rb +4 -0
  327. data/try/unit/data_types/lock_try.rb +4 -0
  328. data/try/unit/data_types/sorted_set_try.rb +4 -0
  329. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  330. data/try/unit/data_types/string_try.rb +4 -0
  331. data/try/unit/data_types/unsortedset_try.rb +4 -0
  332. data/try/unit/familia_resolve_class_try.rb +116 -0
  333. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  334. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  335. data/try/unit/horreum/base_try.rb +4 -0
  336. data/try/unit/horreum/class_methods_try.rb +4 -0
  337. data/try/unit/horreum/commands_try.rb +4 -0
  338. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  339. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  340. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  341. data/try/unit/horreum/field_categories_try.rb +4 -0
  342. data/try/unit/horreum/field_definition_try.rb +4 -0
  343. data/try/unit/horreum/initialization_try.rb +4 -0
  344. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  345. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  346. data/try/unit/horreum/relations_try.rb +4 -0
  347. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  348. data/try/unit/horreum/serialization_try.rb +4 -0
  349. data/try/unit/horreum/settings_try.rb +4 -0
  350. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  351. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  352. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  353. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  354. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  355. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  356. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  357. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  358. data/try/unit/thread_safety_monitor_try.rb +149 -0
  359. metadata +72 -17
  360. data/.github/workflows/code-quality.yml +0 -138
  361. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  362. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  363. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  364. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  365. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  366. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  367. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  368. data/docs/archive/README.md +0 -64
  369. data/docs/archive/api-reference.md +0 -333
  370. data/docs/guides/core-field-system.md +0 -806
  371. data/docs/guides/implementation.md +0 -276
  372. data/docs/guides/security-model.md +0 -183
@@ -0,0 +1,328 @@
1
+ # lib/familia/thread_safety/monitor.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'concurrent-ruby'
6
+
7
+ module Familia
8
+ module ThreadSafety
9
+ # Thread safety monitoring for production observability
10
+ #
11
+ # Tracks mutex contention, race conditions, and synchronization metrics
12
+ # to provide insights into thread safety behavior in production.
13
+ #
14
+ # @example Basic usage
15
+ # Familia::ThreadSafety::Monitor.start!
16
+ # # ... application runs ...
17
+ # report = Familia::ThreadSafety::Monitor.report
18
+ # puts report[:summary]
19
+ #
20
+ # @example Custom instrumentation
21
+ # Familia::ThreadSafety::Monitor.record_contention('connection_chain')
22
+ # Familia::ThreadSafety::Monitor.time_critical_section('field_registration') do
23
+ # # ... critical code ...
24
+ # end
25
+ class Monitor
26
+ class << self
27
+ def instance
28
+ @instance ||= new
29
+ end
30
+
31
+ # Delegate all methods to singleton instance
32
+ def method_missing(method, *args, &block)
33
+ if instance.respond_to?(method)
34
+ instance.send(method, *args, &block)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def respond_to_missing?(method, include_private = false)
41
+ instance.respond_to?(method) || super
42
+ end
43
+ end
44
+
45
+ attr_reader :enabled, :started_at
46
+
47
+ def initialize
48
+ @enabled = false
49
+ @started_at = nil
50
+ @mutex_contentions = Concurrent::AtomicFixnum.new(0)
51
+ @race_detections = Concurrent::AtomicFixnum.new(0)
52
+ @critical_sections = Concurrent::AtomicFixnum.new(0)
53
+ @deadlock_checks = Concurrent::AtomicFixnum.new(0)
54
+
55
+ # Track contention points with counts
56
+ @contention_points = Concurrent::Map.new
57
+
58
+ # Track wait time aggregates for critical sections (removed @wait_times to prevent memory leak)
59
+ @wait_time_totals = Concurrent::Map.new
60
+ @wait_time_counts = Concurrent::Map.new
61
+
62
+ # Track thread-local state for nested monitoring
63
+ @thread_state = Concurrent::Map.new
64
+
65
+ # Track concurrent operation counts
66
+ @concurrent_operations = Concurrent::Map.new
67
+
68
+ # Performance metrics
69
+ @section_timings = Concurrent::Map.new
70
+ @section_counts = Concurrent::Map.new
71
+ end
72
+
73
+ # Start monitoring
74
+ def start!
75
+ @enabled = true
76
+ @started_at = Time.now
77
+ reset_metrics
78
+ Familia.info("[ThreadSafety] Monitoring started")
79
+ true
80
+ end
81
+
82
+ # Stop monitoring
83
+ def stop!
84
+ @enabled = false
85
+ duration = @started_at ? Time.now - @started_at : 0
86
+ Familia.info("[ThreadSafety] Monitoring stopped after #{duration.round(2)}s")
87
+ @started_at = nil
88
+ true
89
+ end
90
+
91
+ # Reset all metrics
92
+ def reset_metrics
93
+ @mutex_contentions.value = 0
94
+ @race_detections.value = 0
95
+ @critical_sections.value = 0
96
+ @deadlock_checks.value = 0
97
+ @contention_points.clear
98
+ # @wait_times.clear - removed to prevent memory leak
99
+ @wait_time_totals.clear
100
+ @wait_time_counts.clear
101
+ @thread_state.clear
102
+ @concurrent_operations.clear
103
+ @section_timings.clear
104
+ @section_counts.clear
105
+ end
106
+
107
+ # Record a mutex contention event
108
+ def record_contention(location, wait_time = nil)
109
+ return unless @enabled
110
+
111
+ @mutex_contentions.increment
112
+ @contention_points[location] = @contention_points.fetch(location, 0) + 1
113
+
114
+ if wait_time
115
+ record_wait_time(location, wait_time)
116
+ end
117
+
118
+ Familia.trace(:THREAD_CONTENTION, nil, "Contention at #{location} (wait: #{wait_time&.round(4)}s)")
119
+ end
120
+
121
+ # Record wait time for a location
122
+ def record_wait_time(location, wait_time)
123
+ # Note: @wait_times was removed to prevent memory leak from unbounded array growth
124
+ # We only need the aggregated totals and counts for calculations
125
+ @wait_time_totals[location] = @wait_time_totals.fetch(location, 0.0) + wait_time
126
+ @wait_time_counts[location] = @wait_time_counts.fetch(location, 0) + 1
127
+ end
128
+
129
+ # Record a potential race condition detection
130
+ def record_race_condition(location, details = nil)
131
+ return unless @enabled
132
+
133
+ @race_detections.increment
134
+ msg = "Potential race condition at #{location}"
135
+ msg += ": #{details}" if details
136
+ Familia.warn("[ThreadSafety] #{msg}")
137
+ end
138
+
139
+ # Time a critical section with contention tracking
140
+ def time_critical_section(name)
141
+ return yield unless @enabled
142
+
143
+ thread_id = Thread.current.object_id
144
+ start_time = Familia.now_in_μs
145
+
146
+ # Check for concurrent execution
147
+ concurrent_count = @concurrent_operations[name] = @concurrent_operations.fetch(name, 0) + 1
148
+ if concurrent_count > 1
149
+ record_contention(name)
150
+ end
151
+
152
+ @critical_sections.increment
153
+
154
+ begin
155
+ result = yield
156
+ ensure
157
+ end_time = Familia.now_in_μs
158
+ duration_μs = end_time - start_time
159
+
160
+ # Record timing in microseconds
161
+ @section_timings[name] = @section_timings.fetch(name, 0) + duration_μs
162
+ @section_counts[name] = @section_counts.fetch(name, 0) + 1
163
+
164
+ # Decrement concurrent count
165
+ @concurrent_operations[name] = @concurrent_operations.fetch(name, 1) - 1
166
+
167
+ if duration_μs > 100_000 # Log slow critical sections (> 100ms = 100,000μs)
168
+ Familia.warn("[ThreadSafety] Slow critical section '#{name}': #{(duration_μs / 1000.0).round(2)}ms")
169
+ end
170
+ end
171
+
172
+ result
173
+ end
174
+
175
+ # NOTE: monitor_mutex method was removed as it was unused and had flawed
176
+ # exception handling that could lead to deadlocks. The InstrumentedMutex
177
+ # class should be used instead for mutex monitoring.
178
+
179
+ # Check for potential deadlocks
180
+ def check_deadlock
181
+ return unless @enabled
182
+
183
+ @deadlock_checks.increment
184
+
185
+ # This is a simple check - in production you might want more sophisticated detection
186
+ thread_count = Thread.list.count
187
+ if thread_count > 100
188
+ Familia.warn("[ThreadSafety] High thread count: #{thread_count}")
189
+ end
190
+
191
+ # Check for threads waiting on mutexes (simplified)
192
+ waiting_threads = Thread.list.select { |t| t.status == "sleep" }
193
+ if waiting_threads.size > thread_count * 0.8
194
+ Familia.warn("[ThreadSafety] Potential deadlock: #{waiting_threads.size}/#{thread_count} threads sleeping")
195
+ end
196
+ end
197
+
198
+ # Generate a comprehensive report
199
+ def report
200
+ return { enabled: false, message: "Monitoring not enabled" } unless @started_at
201
+
202
+ duration = Time.now - @started_at
203
+
204
+ # Calculate hot spots
205
+ hot_spots = []
206
+ @contention_points.each_pair do |location, count|
207
+ hot_spots << [location, count]
208
+ end
209
+ hot_spots = hot_spots
210
+ .sort_by { |_, count| -count }
211
+ .first(10)
212
+ .map { |location, count|
213
+ avg_wait_μs = if @wait_time_counts[location] && @wait_time_counts[location] > 0
214
+ (@wait_time_totals[location] / @wait_time_counts[location]).round(0)
215
+ else
216
+ 0
217
+ end
218
+ {
219
+ location: location,
220
+ contentions: count,
221
+ avg_wait_μs: avg_wait_μs
222
+ }
223
+ }
224
+
225
+ # Calculate critical section performance
226
+ section_performance = []
227
+ @section_counts.each_pair do |name, count|
228
+ avg_time_μs = (@section_timings[name] / count).round(0)
229
+ section_performance << {
230
+ section: name,
231
+ calls: count,
232
+ avg_time_μs: avg_time_μs,
233
+ total_time_μs: @section_timings[name]
234
+ }
235
+ end
236
+ section_performance.sort_by! { |s| -s[:total_time_μs] }
237
+
238
+ {
239
+ summary: {
240
+ monitoring_duration_s: duration.round(2),
241
+ mutex_contentions: @mutex_contentions.value,
242
+ race_detections: @race_detections.value,
243
+ critical_sections: @critical_sections.value,
244
+ deadlock_checks: @deadlock_checks.value
245
+ },
246
+ hot_spots: hot_spots,
247
+ section_performance: section_performance,
248
+ health: calculate_health_score,
249
+ recommendations: generate_recommendations(hot_spots)
250
+ }
251
+ end
252
+
253
+ # Calculate a health score (0-100)
254
+ def calculate_health_score
255
+ return 100 unless @started_at
256
+
257
+ duration = Time.now - @started_at
258
+ return 100 if duration < 60 # Need at least 1 minute of data
259
+
260
+ contentions_per_hour = (@mutex_contentions.value / duration) * 3600
261
+ races_per_hour = (@race_detections.value / duration) * 3600
262
+
263
+ score = 100
264
+ score -= [contentions_per_hour / 10.0, 30].min # -3 points per 100 contentions/hour, max -30
265
+ score -= [races_per_hour * 10, 50].min # -10 points per race/hour, max -50
266
+
267
+ [score, 0].max.round
268
+ end
269
+
270
+ # Generate recommendations based on metrics
271
+ def generate_recommendations(hot_spots)
272
+ recommendations = []
273
+
274
+ if @race_detections.value > 0
275
+ recommendations << {
276
+ severity: 'critical',
277
+ message: "#{@race_detections.value} potential race conditions detected - investigate immediately"
278
+ }
279
+ end
280
+
281
+ if hot_spots.any? { |h| h[:contentions] > 100 }
282
+ high_contention = hot_spots.select { |h| h[:contentions] > 100 }
283
+ locations = high_contention.map { |h| h[:location] }.join(', ')
284
+ recommendations << {
285
+ severity: 'warning',
286
+ message: "High contention detected at: #{locations}"
287
+ }
288
+ end
289
+
290
+ if hot_spots.any? { |h| h[:avg_wait_μs] > 100_000 } # > 100ms in microseconds
291
+ slow_spots = hot_spots.select { |h| h[:avg_wait_μs] > 100_000 }
292
+ recommendations << {
293
+ severity: 'warning',
294
+ message: "Long wait times at: #{slow_spots.map { |h| "#{h[:location]} (#{(h[:avg_wait_μs] / 1000.0).round(1)}ms)" }.join(', ')}"
295
+ }
296
+ end
297
+
298
+ if @deadlock_checks.value > 0 && Thread.list.count > 50
299
+ recommendations << {
300
+ severity: 'info',
301
+ message: "Consider connection pooling - high thread count detected"
302
+ }
303
+ end
304
+
305
+ recommendations
306
+ end
307
+
308
+ # Export metrics in a format suitable for APM tools
309
+ def export_metrics
310
+ {
311
+ 'familia.thread_safety.mutex_contentions' => @mutex_contentions.value,
312
+ 'familia.thread_safety.race_detections' => @race_detections.value,
313
+ 'familia.thread_safety.critical_sections' => @critical_sections.value,
314
+ 'familia.thread_safety.deadlock_checks' => @deadlock_checks.value,
315
+ 'familia.thread_safety.health_score' => calculate_health_score
316
+ }
317
+ end
318
+
319
+ # Hook for APM integration
320
+ def apm_transaction(name, &block)
321
+ return yield unless @enabled
322
+
323
+ # This is where you'd integrate with NewRelic, DataDog, etc.
324
+ time_critical_section(name, &block)
325
+ end
326
+ end
327
+ end
328
+ end
data/lib/familia/utils.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # lib/familia/utils.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # Family-related utility methods
@@ -42,6 +44,17 @@ module Familia
42
44
  current_time.utc.to_f
43
45
  end
44
46
 
47
+ # Returns the current time in microseconds.
48
+ # This is used to measure the duration of Database commands.
49
+ #
50
+ # Alias: now_in_microseconds
51
+ #
52
+ # @return [Integer] The current time in microseconds.
53
+ def now_in_μs
54
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
55
+ end
56
+ alias now_in_microseconds now_in_μs
57
+
45
58
  # A quantized timestamp
46
59
  #
47
60
  # @param quantum [Integer] The time quantum in seconds (default: 10 minutes).
@@ -1,4 +1,6 @@
1
1
  # lib/familia/verifiable_identifier.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'openssl'
4
6
  require_relative 'secure_identifier'
@@ -33,7 +35,7 @@ module Familia
33
35
  # $ openssl rand -hex 32
34
36
  # > cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d
35
37
  #
36
- # 2. UnsortedSet it as an environment variable in your production environment:
38
+ # 2. Set it as an environment variable in your production environment:
37
39
  # export VERIFIABLE_ID_HMAC_SECRET="cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d"
38
40
  #
39
41
  SECRET_KEY = ENV.fetch('VERIFIABLE_ID_HMAC_SECRET', 'cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d')
@@ -1,6 +1,8 @@
1
1
  # lib/familia/version.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # Version information for the Familia
5
- VERSION = '2.0.0.pre19'.freeze unless defined?(Familia::VERSION)
7
+ VERSION = '2.0.0.pre21'.freeze unless defined?(Familia::VERSION)
6
8
  end
data/lib/familia.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  # lib/familia.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'oj'
4
6
  require 'redis'
5
7
  require 'uri/valkey'
6
8
  require 'connection_pool'
9
+ require 'concurrent-ruby'
7
10
 
8
11
  # OJ configuration is handled internally by Familia::JsonSerializer
9
12
 
@@ -11,6 +14,8 @@ require_relative 'multi_result'
11
14
  require_relative 'familia/refinements'
12
15
  require_relative 'familia/errors'
13
16
  require_relative 'familia/version'
17
+ require_relative 'familia/thread_safety/monitor'
18
+ require_relative 'familia/thread_safety/instrumented_mutex'
14
19
 
15
20
  # Familia - A family warehouse for Valkey/Redis
16
21
  #
@@ -39,9 +44,30 @@ module Familia
39
44
  using Refinements::StylizeWords
40
45
 
41
46
  class << self
42
- attr_accessor :debug
47
+ attr_writer :debug
43
48
  attr_reader :members
44
49
 
50
+ # Thread safety monitoring controls
51
+ def thread_safety_monitor
52
+ ThreadSafety::Monitor.instance
53
+ end
54
+
55
+ def start_monitoring!
56
+ thread_safety_monitor.start!
57
+ end
58
+
59
+ def stop_monitoring!
60
+ thread_safety_monitor.stop!
61
+ end
62
+
63
+ def thread_safety_report
64
+ thread_safety_monitor.report
65
+ end
66
+
67
+ def thread_safety_metrics
68
+ thread_safety_monitor.export_metrics
69
+ end
70
+
45
71
  def included(member)
46
72
  raise Problem, "#{member} should subclass Familia::Horreum"
47
73
  end
@@ -87,7 +113,7 @@ module Familia
87
113
  # @param klass [Class] The class to remove from members
88
114
  # @return [Class, nil] The removed class or nil if not found
89
115
  def unload_member(klass)
90
- Familia.ld "[unload_member] Removing #{klass} from members"
116
+ Familia.debug "[unload_member] Removing #{klass} from members"
91
117
  @members.delete(klass)
92
118
  end
93
119
 
@@ -97,7 +123,7 @@ module Familia
97
123
  # @return [Array<Class>] The removed anonymous classes
98
124
  def clear_anonymous_members
99
125
  anonymous_classes = @members.select { |m| m.name.nil? }
100
- Familia.ld "[clear_anonymous_members] Removing #{anonymous_classes.size} anonymous classes"
126
+ Familia.debug "[clear_anonymous_members] Removing #{anonymous_classes.size} anonymous classes"
101
127
  @members.reject! { |m| m.name.nil? }
102
128
  anonymous_classes
103
129
  end
@@ -128,7 +154,7 @@ module Familia
128
154
  # Familia.member_by_config_name(:nonexistent) # => nil
129
155
  #
130
156
  def member_by_config_name(config_name)
131
- Familia.ld "[member_by_config_name] #{members.map(&:config_name)} #{config_name}"
157
+ Familia.debug "[member_by_config_name] #{members.map(&:config_name)} #{config_name}"
132
158
 
133
159
  members.find { |m| m.config_name.to_s.eql?(config_name.to_s) }
134
160
  end
@@ -149,6 +175,7 @@ module Familia
149
175
  extend Utils
150
176
  end
151
177
 
178
+ require_relative 'familia/instrumentation'
152
179
  require_relative 'familia/base'
153
180
  require_relative 'familia/features'
154
181
  require_relative 'familia/data_type'
@@ -0,0 +1,152 @@
1
+ # lib/middleware/database_command_counter.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'concurrent-ruby'
6
+
7
+ # DatabaseCommandCounter is redis-rb middleware for counting commands.
8
+ #
9
+ # This middleware counts the number of Redis commands executed. It can be
10
+ # useful for performance monitoring and debugging, allowing you to track
11
+ # the volume of Redis operations in your application.
12
+ #
13
+ # Familia uses the redis-rb gem (v4.8.1 to <6.0), which internally uses
14
+ # RedisClient infrastructure. Users work with Redis.new connections - the
15
+ # RedisClient middleware registration is handled automatically by Familia.
16
+ #
17
+ # ## User-Facing API
18
+ #
19
+ # Enable via Familia configuration:
20
+ # Familia.enable_database_counter = true
21
+ #
22
+ # Familia automatically calls RedisClient.register(DatabaseCommandCounter) internally.
23
+ #
24
+ # ## Middleware Chaining
25
+ #
26
+ # This middleware works correctly alongside DatabaseLogger because it uses
27
+ # `super` to properly chain method calls. See {DatabaseLogger} for detailed
28
+ # explanation of middleware chaining mechanics.
29
+ #
30
+ # @example Enable Redis command counting (recommended user-facing API)
31
+ # DatabaseCommandCounter.reset
32
+ # Familia.enable_database_counter = true
33
+ #
34
+ # @example Use with DatabaseLogger
35
+ # Familia.enable_database_logging = true
36
+ # Familia.enable_database_counter = true
37
+ # # Both middlewares registered automatically and execute correctly in sequence
38
+ #
39
+ # @see https://github.com/redis-rb/redis-client?tab=readme-ov-file#instrumentation-and-middlewares
40
+ # @see DatabaseLogger For middleware chain architecture details
41
+ #
42
+ module DatabaseCommandCounter
43
+ @count = Concurrent::AtomicFixnum.new(0)
44
+
45
+ # Commands to skip when counting.
46
+ #
47
+ # We skip SELECT because the frequency depends on connection architecture:
48
+ # - Connection-per-database: Only one SELECT when connection is made
49
+ # - Provider/thread-local: Could theoretically double statement count
50
+ #
51
+ # @return [Set<String>] Commands that won't be counted
52
+ @skip_commands = ::Set.new(['SELECT']).freeze
53
+
54
+ class << self
55
+ # Gets the set of commands to skip counting.
56
+ # @return [Set<String>] The commands that won't be counted
57
+ attr_reader :skip_commands
58
+
59
+ # Gets the current count of Redis commands executed.
60
+ # @return [Integer] The number of Redis commands executed
61
+ def count
62
+ @count.value
63
+ end
64
+
65
+ # Resets the command count to zero.
66
+ # This method is thread-safe.
67
+ # @return [Integer] The reset count (always 0)
68
+ def reset
69
+ @count.value = 0
70
+ end
71
+
72
+ # Increments the command count.
73
+ # This method is thread-safe.
74
+ # @return [Integer] The new count after incrementing
75
+ # @api private
76
+ def increment
77
+ @count.increment
78
+ end
79
+
80
+ # Checks if a command should be skipped.
81
+ # @param command [Array] The Redis command array
82
+ # @return [Boolean] true if command should be skipped
83
+ # @api private
84
+ def skip_command?(command)
85
+ skip_commands.include?(command.first.to_s.upcase)
86
+ end
87
+
88
+ # Counts the number of Redis commands executed within a block.
89
+ #
90
+ # This method captures the command count before and after executing the
91
+ # provided block, returning the difference. This is useful for measuring
92
+ # how many Redis commands are executed by a specific operation.
93
+ #
94
+ # @yield [] The block of code to execute while counting commands
95
+ # @return [Integer] The number of Redis commands executed within the block
96
+ #
97
+ # @example Count commands in a block
98
+ # commands_executed = DatabaseCommandCounter.count_commands do
99
+ # dbclient.set('key1', 'value1')
100
+ # dbclient.get('key1')
101
+ # end
102
+ # # commands_executed will be 2
103
+ def count_commands
104
+ start_count = count # Capture the current command count before execution
105
+ yield # Execute the provided block
106
+ end_count = count # Capture the command count after execution
107
+ end_count - start_count # Return the difference (commands executed in block)
108
+ end
109
+ end
110
+
111
+ # Reference to the module for use in instance methods
112
+ # @api private
113
+ def klass
114
+ DatabaseCommandCounter
115
+ end
116
+
117
+ # Counts the Redis command and delegates its execution.
118
+ #
119
+ # This method is part of the RedisClient middleware chain. It MUST use `super`
120
+ # instead of `yield` to properly chain with other middlewares like DatabaseLogger.
121
+ #
122
+ # @param command [Array] The Redis command and its arguments
123
+ # @param _config [RedisClient::Config, Hash] Connection configuration (unused)
124
+ # @return [Object] The result of the Redis command execution
125
+ def call(command, _config)
126
+ klass.increment unless klass.skip_command?(command)
127
+ super # CRITICAL: Must use super, not yield, to chain middlewares
128
+ end
129
+
130
+ # Counts commands in a pipeline and delegates execution.
131
+ #
132
+ # @param commands [Array<Array>] Array of command arrays
133
+ # @param _config [RedisClient::Config, Hash] Connection configuration (unused)
134
+ # @return [Array] Results from pipelined commands
135
+ def call_pipelined(commands, _config)
136
+ # Count all commands in the pipeline (except skipped ones)
137
+ commands.each do |command|
138
+ klass.increment unless klass.skip_command?(command)
139
+ end
140
+ super # CRITICAL: Must use super, not yield, to chain middlewares
141
+ end
142
+
143
+ # Counts a call_once command and delegates execution.
144
+ #
145
+ # @param command [Array] The Redis command and its arguments
146
+ # @param _config [RedisClient::Config, Hash] Connection configuration (unused)
147
+ # @return [Object] The result of the Redis command execution
148
+ def call_once(command, _config)
149
+ klass.increment unless klass.skip_command?(command)
150
+ super # CRITICAL: Must use super, not yield, to chain middlewares
151
+ end
152
+ end