familia 2.0.0.pre18 → 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 (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/CHANGELOG.rst +205 -88
  8. data/CLAUDE.md +62 -10
  9. data/Gemfile +3 -3
  10. data/Gemfile.lock +27 -62
  11. data/README.md +39 -0
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  15. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  16. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  17. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  18. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  19. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  20. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  21. data/docs/guides/encryption.md +486 -0
  22. data/docs/guides/feature-encrypted-fields.md +123 -7
  23. data/docs/guides/feature-expiration.md +177 -133
  24. data/docs/guides/feature-external-identifiers.md +415 -443
  25. data/docs/guides/feature-object-identifiers.md +400 -269
  26. data/docs/guides/feature-quantization.md +120 -6
  27. data/docs/guides/feature-relationships-indexing.md +318 -0
  28. data/docs/guides/feature-relationships-methods.md +146 -604
  29. data/docs/guides/feature-relationships-participation.md +263 -0
  30. data/docs/guides/feature-relationships.md +118 -136
  31. data/docs/guides/feature-system-devs.md +176 -693
  32. data/docs/guides/feature-system.md +119 -6
  33. data/docs/guides/feature-transient-fields.md +81 -0
  34. data/docs/guides/field-system.md +778 -0
  35. data/docs/guides/index.md +32 -15
  36. data/docs/guides/logging.md +187 -0
  37. data/docs/guides/optimized-loading.md +674 -0
  38. data/docs/guides/thread-safety-monitoring.md +61 -0
  39. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  40. data/docs/migrating/v2.0.0-pre19.md +197 -0
  41. data/docs/migrating/v2.0.0-pre22.md +241 -0
  42. data/docs/overview.md +7 -9
  43. data/docs/reference/api-technical.md +267 -320
  44. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  45. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  46. data/examples/autoloader/mega_customer.rb +2 -0
  47. data/examples/datatype_standalone.rb +282 -0
  48. data/examples/encrypted_fields.rb +2 -1
  49. data/examples/json_usage_patterns.rb +2 -0
  50. data/examples/relationships.rb +3 -0
  51. data/examples/safe_dump.rb +2 -1
  52. data/examples/sampling_demo.rb +53 -0
  53. data/examples/single_connection_transaction_confusions.rb +2 -1
  54. data/familia.gemspec +2 -1
  55. data/lib/familia/base.rb +2 -0
  56. data/lib/familia/connection/behavior.rb +254 -0
  57. data/lib/familia/connection/handlers.rb +97 -0
  58. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  59. data/lib/familia/connection/middleware.rb +34 -24
  60. data/lib/familia/connection/operation_core.rb +3 -1
  61. data/lib/familia/connection/operations.rb +2 -0
  62. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
  63. data/lib/familia/connection/transaction_core.rb +75 -9
  64. data/lib/familia/connection.rb +21 -5
  65. data/lib/familia/data_type/class_methods.rb +3 -1
  66. data/lib/familia/data_type/connection.rb +153 -7
  67. data/lib/familia/data_type/database_commands.rb +9 -4
  68. data/lib/familia/data_type/serialization.rb +10 -4
  69. data/lib/familia/data_type/settings.rb +2 -0
  70. data/lib/familia/data_type/types/counter.rb +2 -0
  71. data/lib/familia/data_type/types/hashkey.rb +8 -6
  72. data/lib/familia/data_type/types/listkey.rb +2 -0
  73. data/lib/familia/data_type/types/lock.rb +2 -0
  74. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  75. data/lib/familia/data_type/types/stringkey.rb +2 -0
  76. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  77. data/lib/familia/data_type.rb +2 -0
  78. data/lib/familia/encryption/encrypted_data.rb +4 -2
  79. data/lib/familia/encryption/manager.rb +2 -0
  80. data/lib/familia/encryption/provider.rb +2 -0
  81. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  82. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  83. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  84. data/lib/familia/encryption/registry.rb +2 -0
  85. data/lib/familia/encryption/request_cache.rb +2 -0
  86. data/lib/familia/encryption.rb +9 -2
  87. data/lib/familia/errors.rb +53 -14
  88. data/lib/familia/features/autoloader.rb +2 -0
  89. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  90. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  91. data/lib/familia/features/encrypted_fields.rb +2 -2
  92. data/lib/familia/features/expiration/extensions.rb +11 -11
  93. data/lib/familia/features/expiration.rb +29 -21
  94. data/lib/familia/features/external_identifier.rb +33 -7
  95. data/lib/familia/features/object_identifier.rb +2 -0
  96. data/lib/familia/features/quantization.rb +3 -1
  97. data/lib/familia/features/relationships/README.md +3 -1
  98. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  99. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
  100. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  101. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
  102. data/lib/familia/features/relationships/indexing.rb +40 -42
  103. data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
  104. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  105. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  106. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  107. data/lib/familia/features/relationships/participation.rb +155 -69
  108. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  109. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  110. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  111. data/lib/familia/features/relationships.rb +5 -3
  112. data/lib/familia/features/safe_dump.rb +2 -0
  113. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  114. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  115. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  116. data/lib/familia/features/transient_fields.rb +2 -0
  117. data/lib/familia/features.rb +2 -0
  118. data/lib/familia/field_type.rb +5 -2
  119. data/lib/familia/horreum/connection.rb +28 -36
  120. data/lib/familia/horreum/database_commands.rb +131 -10
  121. data/lib/familia/horreum/definition.rb +18 -7
  122. data/lib/familia/horreum/management.rb +233 -57
  123. data/lib/familia/horreum/persistence.rb +314 -122
  124. data/lib/familia/horreum/related_fields.rb +2 -0
  125. data/lib/familia/horreum/serialization.rb +26 -4
  126. data/lib/familia/horreum/settings.rb +2 -0
  127. data/lib/familia/horreum/utils.rb +2 -8
  128. data/lib/familia/horreum.rb +46 -13
  129. data/lib/familia/identifier_extractor.rb +2 -0
  130. data/lib/familia/instrumentation.rb +156 -0
  131. data/lib/familia/json_serializer.rb +2 -0
  132. data/lib/familia/logging.rb +94 -37
  133. data/lib/familia/refinements/dear_json.rb +2 -0
  134. data/lib/familia/refinements/stylize_words.rb +2 -14
  135. data/lib/familia/refinements/time_literals.rb +2 -0
  136. data/lib/familia/refinements.rb +2 -0
  137. data/lib/familia/secure_identifier.rb +10 -2
  138. data/lib/familia/settings.rb +9 -7
  139. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  140. data/lib/familia/thread_safety/monitor.rb +328 -0
  141. data/lib/familia/utils.rb +13 -0
  142. data/lib/familia/verifiable_identifier.rb +3 -1
  143. data/lib/familia/version.rb +3 -1
  144. data/lib/familia.rb +31 -4
  145. data/lib/middleware/database_command_counter.rb +152 -0
  146. data/lib/middleware/database_logger.rb +325 -129
  147. data/lib/multi_result.rb +2 -0
  148. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  149. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  150. data/try/edge_cases/json_serialization_try.rb +2 -0
  151. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  152. data/try/edge_cases/race_conditions_try.rb +4 -0
  153. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  154. data/try/edge_cases/string_coercion_try.rb +6 -4
  155. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  156. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  157. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  158. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  160. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  161. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  162. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  164. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  165. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  166. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  167. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  168. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  169. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  170. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  171. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  172. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  173. data/try/features/encryption/config_persistence_try.rb +4 -0
  174. data/try/features/encryption/core_try.rb +4 -0
  175. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  176. data/try/features/encryption/module_loading_try.rb +4 -0
  177. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  178. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  179. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  180. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  181. data/try/features/expiration/expiration_try.rb +5 -1
  182. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  183. data/try/features/feature_dependencies_try.rb +2 -0
  184. data/try/features/feature_improvements_try.rb +2 -0
  185. data/try/features/field_groups_try.rb +2 -0
  186. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  187. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  188. data/try/features/quantization/quantization_try.rb +4 -0
  189. data/try/features/real_feature_integration_try.rb +2 -0
  190. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  192. data/try/features/relationships/indexing_try.rb +30 -4
  193. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  194. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  195. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  196. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  197. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  198. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  199. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  200. data/try/features/relationships/relationships_api_changes_try.rb +6 -4
  201. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  204. data/try/features/relationships/relationships_performance_try.rb +4 -0
  205. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  206. data/try/features/relationships/relationships_try.rb +6 -4
  207. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  208. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  209. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  210. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  211. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  212. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  213. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  214. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  215. data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
  216. data/try/integration/connection/handler_constraints_try.rb +4 -0
  217. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  218. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  219. data/try/integration/connection/operation_mode_guards_try.rb +5 -1
  220. data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
  221. data/try/integration/connection/pools_try.rb +4 -0
  222. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  223. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  225. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  226. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  227. data/try/integration/connection/transaction_modes_try.rb +4 -0
  228. data/try/integration/conventional_inheritance_try.rb +4 -0
  229. data/try/integration/create_method_try.rb +26 -22
  230. data/try/integration/cross_component_try.rb +4 -0
  231. data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
  232. data/try/integration/data_types/datatype_transactions_try.rb +251 -0
  233. data/try/integration/database_consistency_try.rb +4 -0
  234. data/try/integration/familia_extended_try.rb +4 -0
  235. data/try/integration/familia_members_methods_try.rb +4 -0
  236. data/try/integration/models/customer_safe_dump_try.rb +9 -1
  237. data/try/integration/models/customer_try.rb +4 -0
  238. data/try/integration/models/datatype_base_try.rb +4 -0
  239. data/try/integration/models/familia_object_try.rb +5 -1
  240. data/try/integration/persistence_operations_try.rb +166 -10
  241. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  242. data/try/integration/save_methods_consistency_try.rb +241 -0
  243. data/try/integration/scenarios_try.rb +4 -0
  244. data/try/integration/secure_identifier_try.rb +4 -0
  245. data/try/integration/transaction_safety_core_try.rb +176 -0
  246. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  247. data/try/integration/verifiable_identifier_try.rb +4 -0
  248. data/try/investigation/pipeline_routing/README.md +228 -0
  249. data/try/performance/benchmarks_try.rb +4 -0
  250. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  251. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  252. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  253. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  254. data/try/support/debugging/debug_aad_process.rb +3 -0
  255. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  256. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  257. data/try/support/debugging/debug_context_aad.rb +3 -0
  258. data/try/support/debugging/debug_context_simple.rb +3 -0
  259. data/try/support/debugging/debug_cross_context.rb +3 -0
  260. data/try/support/debugging/debug_database_load.rb +3 -0
  261. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  262. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  263. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  264. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  265. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  266. data/try/support/debugging/debug_load_path.rb +3 -0
  267. data/try/support/debugging/debug_method_definition.rb +3 -0
  268. data/try/support/debugging/debug_method_resolution.rb +3 -0
  269. data/try/support/debugging/debug_minimal.rb +3 -0
  270. data/try/support/debugging/debug_provider.rb +3 -0
  271. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  272. data/try/support/debugging/debug_string_class.rb +3 -0
  273. data/try/support/debugging/debug_test.rb +3 -0
  274. data/try/support/debugging/debug_test_design.rb +3 -0
  275. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  276. data/try/support/debugging/provider_diagnostics.rb +4 -0
  277. data/try/support/helpers/test_cleanup.rb +4 -0
  278. data/try/support/helpers/test_helpers.rb +5 -0
  279. data/try/support/memory/memory_basic_test.rb +4 -0
  280. data/try/support/memory/memory_detailed_test.rb +4 -0
  281. data/try/support/memory/memory_search_for_string.rb +4 -0
  282. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  285. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  286. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  287. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  288. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  293. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  294. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  295. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  296. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  297. data/try/thread_safety/README.md +496 -0
  298. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  299. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  300. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  301. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  302. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  303. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  304. data/try/thread_safety/field_registration_race_try.rb +222 -0
  305. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  306. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  307. data/try/thread_safety/module_config_race_try.rb +175 -0
  308. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  309. data/try/unit/core/autoloader_try.rb +4 -0
  310. data/try/unit/core/base_enhancements_try.rb +4 -0
  311. data/try/unit/core/connection_try.rb +4 -0
  312. data/try/unit/core/errors_try.rb +4 -0
  313. data/try/unit/core/extensions_try.rb +4 -0
  314. data/try/unit/core/familia_logger_try.rb +2 -0
  315. data/try/unit/core/familia_try.rb +4 -0
  316. data/try/unit/core/middleware_sampling_try.rb +335 -0
  317. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  318. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  319. data/try/unit/core/middleware_try.rb +4 -0
  320. data/try/unit/core/settings_try.rb +4 -0
  321. data/try/unit/core/time_utils_try.rb +4 -0
  322. data/try/unit/core/tools_try.rb +4 -0
  323. data/try/unit/core/utils_try.rb +37 -0
  324. data/try/unit/data_types/boolean_try.rb +5 -1
  325. data/try/unit/data_types/counter_try.rb +4 -0
  326. data/try/unit/data_types/datatype_base_try.rb +4 -0
  327. data/try/unit/data_types/hash_try.rb +4 -0
  328. data/try/unit/data_types/list_try.rb +4 -0
  329. data/try/unit/data_types/lock_try.rb +4 -0
  330. data/try/unit/data_types/sorted_set_try.rb +4 -0
  331. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  332. data/try/unit/data_types/string_try.rb +5 -1
  333. data/try/unit/data_types/unsortedset_try.rb +4 -0
  334. data/try/unit/familia_resolve_class_try.rb +116 -0
  335. data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
  336. data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
  337. data/try/unit/horreum/base_try.rb +5 -1
  338. data/try/unit/horreum/class_methods_try.rb +6 -2
  339. data/try/unit/horreum/commands_try.rb +4 -0
  340. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  341. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  342. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  343. data/try/unit/horreum/field_categories_try.rb +4 -0
  344. data/try/unit/horreum/field_definition_try.rb +4 -0
  345. data/try/unit/horreum/initialization_try.rb +5 -1
  346. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  347. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  348. data/try/unit/horreum/relations_try.rb +8 -4
  349. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  350. data/try/unit/horreum/serialization_try.rb +6 -2
  351. data/try/unit/horreum/settings_try.rb +4 -0
  352. data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
  353. data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
  354. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  355. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  356. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  357. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  358. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  359. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  360. data/try/unit/thread_safety_monitor_try.rb +149 -0
  361. metadata +81 -14
  362. data/.github/workflows/code-quality.yml +0 -138
  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
@@ -0,0 +1,61 @@
1
+ Thread Safety Monitoring Usage Guide
2
+
3
+ ### Development
4
+ ```ruby
5
+ # Enable monitoring during local testing
6
+ Familia.start_monitoring!
7
+ # ... run your application ...
8
+ report = Familia.thread_safety_report
9
+ puts report[:hot_spots] # See which mutexes are contentious
10
+ puts report[:recommendations] # Get actionable insights
11
+ ```
12
+
13
+ ### CI/CD
14
+ ```bash
15
+ # Add to test pipeline for race condition detection
16
+ FAMILIA_THREAD_SAFETY=1 bundle exec rspec
17
+ # Check for any race detections in CI logs
18
+ ```
19
+
20
+ ```ruby
21
+ # In test setup
22
+ if ENV['FAMILIA_THREAD_SAFETY']
23
+ Familia.start_monitoring!
24
+ at_exit do
25
+ report = Familia.thread_safety_report
26
+ if report[:summary][:race_detections] > 0
27
+ puts "❌ Race conditions detected: #{report[:summary][:race_detections]}"
28
+ exit 1
29
+ end
30
+ end
31
+ end
32
+ ```
33
+
34
+ ### Production
35
+ **APM Integration:**
36
+ ```ruby
37
+ # Export metrics to DataDog, NewRelic, etc.
38
+ Thread.new do
39
+ loop do
40
+ metrics = Familia.thread_safety_metrics
41
+ StatsD.gauge('familia.thread_safety.health_score', metrics['familia.thread_safety.health_score'])
42
+ StatsD.count('familia.thread_safety.contentions', metrics['familia.thread_safety.mutex_contentions'])
43
+ sleep 60
44
+ end
45
+ end
46
+ ```
47
+
48
+ **Health Check Endpoint:**
49
+ ```ruby
50
+ # In Rails routes or Sinatra
51
+ get '/health/thread_safety' do
52
+ report = Familia.thread_safety_report
53
+ status = report[:health] >= 80 ? 200 : 503
54
+ json report
55
+ end
56
+ ```
57
+
58
+ **Key Alerts:**
59
+ - `health_score < 70` → Investigate contention
60
+ - `race_detections > 0` → Critical issue
61
+ - `avg_wait_ms > 100` → Performance problem
@@ -9,8 +9,8 @@ The `Familia::Refinements::TimeLiterals` module extends Ruby's built-in classes
9
9
  ```ruby
10
10
  using Familia::Refinements::TimeLiterals
11
11
 
12
- 2.hours #=> 7200 (seconds)
13
- "30m".in_seconds #=> 1800
12
+ 2.hours #=> 7200.0 (seconds)
13
+ "30m".in_seconds #=> 1800.0
14
14
  timestamp.days_old #=> 5.2
15
15
  ```
16
16
 
@@ -45,12 +45,12 @@ using Familia::Refinements::TimeLiterals
45
45
  # Singular and plural forms work identically
46
46
  1.second #=> 1
47
47
  30.seconds #=> 30
48
- 5.minutes #=> 300
49
- 2.hours #=> 7200
50
- 3.days #=> 259200
51
- 1.week #=> 604800
52
- 2.months #=> 5259492
53
- 1.year #=> 31556952
48
+ 5.minutes #=> 300.0
49
+ 2.hours #=> 7200.0
50
+ 3.days #=> 259200.0
51
+ 1.week #=> 604800.0
52
+ 2.months #=> 5259492.0
53
+ 1.year #=> 31556952.0
54
54
  ```
55
55
 
56
56
  ### Converting Time Units Back
@@ -80,9 +80,9 @@ Parse human-readable time strings:
80
80
 
81
81
  | Unit | Abbreviations | Example |
82
82
  |------|---------------|---------|
83
- | Microseconds | `us`, `μs`, `microsecond`, `microseconds` | `"500us"` |
83
+ | Microseconds | `us`, `μs`, `microsecond`, `microseconds` | `"500μs"` |
84
84
  | Milliseconds | `ms`, `millisecond`, `milliseconds` | `"250ms"` |
85
- | Seconds | `s`, `second`, `seconds` | `"30s"` |
85
+ | Seconds | (no unit) | `"30"` |
86
86
  | Minutes | `m`, `minute`, `minutes` | `"15m"` |
87
87
  | Hours | `h`, `hour`, `hours` | `"2h"` |
88
88
  | Days | `d`, `day`, `days` | `"7d"` |
@@ -90,7 +90,7 @@ Parse human-readable time strings:
90
90
  | Months | `mo`, `month`, `months` | `"6mo"` |
91
91
  | Years | `y`, `year`, `years` | `"1y"` |
92
92
 
93
- **Note**: Use `"mo"` for months to avoid confusion with `"m"` (minutes).
93
+ **Note**: Use `"mo"` for months to avoid confusion with `"m"` (minutes). Seconds don't require a unit suffix.
94
94
 
95
95
  ## Age Calculations
96
96
 
@@ -215,7 +215,7 @@ If upgrading from earlier versions:
215
215
  12.months != 1.year # Different values
216
216
 
217
217
  # New behavior (consistent)
218
- 12.months == 1.year # Same value: 31,556,952 seconds
218
+ 12.months == 1.year # Same value: 31,556,952.0 seconds
219
219
  ```
220
220
 
221
221
  Update any code that relied on the old 365-day year constant to expect the new Gregorian year values.
@@ -0,0 +1,197 @@
1
+ # Migrating Guide: v2.0.0-pre19
2
+
3
+ This version introduces significant improvements to Familia's database operations, making them more atomic, reliable, and consistent with Rails conventions. The changes include enhanced error handling, optimistic locking support, and breaking API changes.
4
+
5
+ ## Breaking Changes
6
+
7
+ ### Management.create → create!
8
+
9
+ **What Changed:**
10
+
11
+ The `create` class method has been renamed to `create!` to follow Rails conventions and indicate that it raises exceptions on failure.
12
+
13
+ **Before:**
14
+ ```ruby
15
+ user = User.create(email: "test@example.com")
16
+ ```
17
+
18
+ **After:**
19
+ ```ruby
20
+ user = User.create!(email: "test@example.com")
21
+ ```
22
+
23
+ **Why This Matters:**
24
+
25
+ - Follows Rails naming conventions where `!` methods raise exceptions
26
+ - Makes it clear that `CreationError` will be raised if object already exists
27
+ - Prevents silent failures in object creation
28
+
29
+ ### save_if_not_exists → save_if_not_exists!
30
+
31
+ **What Changed:**
32
+
33
+ The `save_if_not_exists` method has been renamed to `save_if_not_exists!` and now includes optimistic locking with automatic retry logic.
34
+
35
+ **Before:**
36
+ ```ruby
37
+ success = user.save_if_not_exists
38
+ ```
39
+
40
+ **After:**
41
+ ```ruby
42
+ success = user.save_if_not_exists!
43
+ ```
44
+
45
+ **Behavior Changes:**
46
+ - Now uses Redis WATCH/MULTI/EXEC for optimistic locking
47
+ - Automatically retries up to 3 times on `OptimisticLockError`
48
+ - Raises `RecordExistsError` if object already exists
49
+ - More atomic and thread-safe
50
+
51
+ ## Enhanced Error Handling
52
+
53
+ ### New Error Hierarchy
54
+
55
+ **What's New:**
56
+
57
+ A structured error hierarchy provides better error categorization:
58
+
59
+ ```ruby
60
+ Familia::Problem # Base class
61
+ ├── Familia::PersistenceError # Redis/database errors
62
+ │ ├── Familia::NonUniqueKey
63
+ │ ├── Familia::OptimisticLockError
64
+ │ ├── Familia::OperationModeError
65
+ │ ├── Familia::NoConnectionAvailable
66
+ │ ├── Familia::NotFound
67
+ │ └── Familia::NotConnected
68
+ └── Familia::HorreumError # Model-related errors
69
+ ├── Familia::CreationError
70
+ ├── Familia::NoIdentifier
71
+ ├── Familia::FieldTypeError
72
+ ├── Familia::AutoloadError
73
+ ├── Familia::SerializerError
74
+ ├── Familia::UnknownFieldError
75
+ └── Familia::NotDistinguishableError
76
+ ```
77
+
78
+ **Migration:**
79
+
80
+ Update exception handling to use specific error classes:
81
+
82
+ ```ruby
83
+ # Before
84
+ rescue Familia::Problem => e
85
+ # Handle all errors
86
+
87
+ # After - More granular handling
88
+ rescue Familia::CreationError => e
89
+ # Handle creation failures specifically
90
+ rescue Familia::OptimisticLockError => e
91
+ # Handle concurrent modification
92
+ rescue Familia::PersistenceError => e
93
+ # Handle database-related errors
94
+ ```
95
+
96
+ ### New Exception Types
97
+
98
+ - **`CreationError`**: Raised when object creation fails (replaces generic errors)
99
+ - **`OptimisticLockError`**: Raised when WATCH fails due to concurrent modification
100
+ - **`UnknownFieldError`**: Raised when referencing non-existent fields
101
+
102
+ ## Database Operation Improvements
103
+
104
+ ### Atomic Save Operations
105
+
106
+ **What Changed:**
107
+
108
+ The `save` method now uses a single Redis transaction for complete atomicity:
109
+
110
+ ```ruby
111
+ # All operations now happen atomically:
112
+ # 1. Save all fields (HMSET)
113
+ # 2. Set expiration (EXPIRE)
114
+ # 3. Update indexes
115
+ # 4. Add to instances collection
116
+ ```
117
+
118
+ **Benefits:**
119
+ - Eliminates race conditions during save operations
120
+ - Ensures data consistency across related operations
121
+ - Better performance with fewer round trips
122
+
123
+ ### Enhanced Timestamp Precision
124
+
125
+ **What Changed:**
126
+
127
+ Created/updated timestamps now use float values instead of integers for higher precision:
128
+
129
+ **Before:**
130
+ ```ruby
131
+ user.created # => 1697234567 (integer seconds)
132
+ ```
133
+
134
+ **After:**
135
+ ```ruby
136
+ user.created # => 1697234567.123 (float with milliseconds)
137
+ ```
138
+
139
+ **Migration:**
140
+
141
+ No code changes needed. Existing integer timestamps continue to work.
142
+
143
+ ## Redis Command Enhancements
144
+
145
+ ### New Commands Available
146
+
147
+ Added support for optimistic locking commands:
148
+ - `watch(key)` - Watch key for changes
149
+ - `unwatch()` - Remove all watches
150
+ - `discard()` - Discard queued commands
151
+
152
+ ### Improved Command Logging
153
+
154
+ Database command logging now includes:
155
+ - Structured format for better readability
156
+ - Pipelined operation tracking
157
+ - Transaction boundary markers
158
+ - Command timing information
159
+
160
+ ## Terminology Updates
161
+
162
+ **What Changed:**
163
+
164
+ Standardized on "pipelined" terminology throughout (previously mixed "pipeline"/"pipelined").
165
+
166
+ **Files Affected:**
167
+ - Method names now consistently use "pipelined"
168
+ - Documentation updated to match Redis terminology
169
+ - Log messages standardized
170
+
171
+ **Migration:**
172
+
173
+ No code changes needed - this was an internal consistency improvement.
174
+
175
+ ## Recommended Actions
176
+
177
+ 1. **Update method calls:**
178
+ ```ruby
179
+ # Replace all instances
180
+ Model.create(...) → Model.create!(...)
181
+ obj.save_if_not_exists → obj.save_if_not_exists!
182
+ ```
183
+
184
+ 2. **Review error handling:**
185
+ ```ruby
186
+ # Consider more specific error handling
187
+ rescue Familia::CreationError
188
+ rescue Familia::OptimisticLockError
189
+ ```
190
+
191
+ 3. **Test concurrent operations:**
192
+ - The new optimistic locking provides better concurrency handling
193
+ - Verify your application handles `OptimisticLockError` appropriately
194
+
195
+ 4. **Review logging:**
196
+ - Enhanced database command logging may affect log volume
197
+ - Adjust log levels if needed for production environments
@@ -0,0 +1,241 @@
1
+ # Migrating Guide: v2.0.0-pre22
2
+
3
+ This version introduces significant performance optimizations for Redis operations, completes the bidirectional relationships feature, and improves flexibility for external identifiers.
4
+
5
+ ## Major Features
6
+
7
+ ### Bidirectional Relationship Methods
8
+
9
+ **What's New:**
10
+
11
+ The `participates_in` declarations now generate reverse collection methods with the `_instances` suffix, providing symmetric access to relationships from both directions.
12
+
13
+ **Generated Methods:**
14
+ ```ruby
15
+ class User < Familia::Horreum
16
+ participates_in Team, :members
17
+ participates_in Organization, :employees
18
+ end
19
+
20
+ # New reverse collection methods:
21
+ user.team_instances # => [team1, team2]
22
+ user.team_ids # => ["team_123", "team_456"]
23
+ user.team? # => true/false
24
+ user.team_count # => 2
25
+
26
+ user.organization_instances # => [org1]
27
+ user.organization_ids # => ["org_789"]
28
+ ```
29
+
30
+ **Custom Names:**
31
+ ```ruby
32
+ class User < Familia::Horreum
33
+ participates_in Organization, :contractors, as: :clients
34
+ end
35
+
36
+ user.clients_instances # Instead of organization_instances
37
+ user.clients_ids # Instead of organization_ids
38
+ ```
39
+
40
+ **Migration:**
41
+
42
+ No changes required for existing code. The new methods are additive and don't affect existing `participates_in` functionality.
43
+
44
+ ### Pipelined Bulk Loading
45
+
46
+ **What's New:**
47
+
48
+ New `load_multi` methods provide up to 2× performance improvement for bulk object loading by using Redis pipelining.
49
+
50
+ **Before (N×2 commands):**
51
+ ```ruby
52
+ users = ids.map { |id| User.find_by_id(id) }
53
+ # For 14 objects: 28 Redis commands (14 EXISTS + 14 HGETALL)
54
+ ```
55
+
56
+ **After (1 round trip):**
57
+ ```ruby
58
+ users = User.load_multi(ids)
59
+ # For 14 objects: 1 pipelined batch with 14 HGETALL commands
60
+ ```
61
+
62
+ **Additional Methods:**
63
+ ```ruby
64
+ # Load by full dbkeys
65
+ users = User.load_multi_by_keys(['user:123:object', 'user:456:object'])
66
+
67
+ # Filter out nils for missing objects
68
+ existing_only = User.load_multi(ids).compact
69
+ ```
70
+
71
+ ### Optional EXISTS Check Optimization
72
+
73
+ **What's New:**
74
+
75
+ The `find_by_id` and related methods now support skipping the EXISTS check for 50% reduction in Redis commands.
76
+
77
+ ```ruby
78
+ # Default behavior (unchanged, 2 commands)
79
+ user = User.find_by_id(123)
80
+
81
+ # Optimized mode (1 command)
82
+ user = User.find_by_id(123, check_exists: false)
83
+ ```
84
+
85
+ **When to Use:**
86
+ - Performance-critical paths
87
+ - Bulk operations with known-to-exist keys
88
+ - High-throughput APIs
89
+ - Loading from sorted set results
90
+
91
+ ## Enhanced Features
92
+
93
+ ### Flexible External Identifier Format
94
+
95
+ **What's New:**
96
+
97
+ The `external_identifier` feature now supports custom format templates.
98
+
99
+ **Examples:**
100
+ ```ruby
101
+ # Default format (unchanged)
102
+ class User < Familia::Horreum
103
+ feature :external_identifier
104
+ end
105
+ user.extid # => "ext_abc123def456"
106
+
107
+ # Custom prefix
108
+ class Customer < Familia::Horreum
109
+ feature :external_identifier, format: 'cust_%{id}'
110
+ end
111
+ customer.extid # => "cust_abc123def456"
112
+
113
+ # Different separator
114
+ class APIKey < Familia::Horreum
115
+ feature :external_identifier, format: 'api-%{id}'
116
+ end
117
+ key.extid # => "api-abc123def456"
118
+ ```
119
+
120
+ ### Atomic Index Rebuilding
121
+
122
+ **What's New:**
123
+
124
+ Auto-generated rebuild methods for all unique and multi indexes with zero downtime.
125
+
126
+ **Examples:**
127
+
128
+ ```ruby
129
+ # Class-level unique index
130
+ User.rebuild_email_lookup
131
+
132
+ # Instance-scoped unique index
133
+ company.rebuild_badge_index
134
+
135
+ # With progress tracking
136
+ User.rebuild_email_lookup(batch_size: 100) do |progress|
137
+ puts "#{progress[:completed]}/#{progress[:total]}"
138
+ end
139
+ ```
140
+
141
+ When to Use:
142
+ - After data migrations or bulk imports
143
+ - Recovering from index corruption
144
+ - Adding indexes to existing data
145
+
146
+ Migration:
147
+
148
+ Run rebuild methods once after upgrade to ensure index consistency. No code changes required—methods are auto-generated from existing
149
+ unique_index and multi_index declarations.
150
+
151
+
152
+ ## Bug Fixes
153
+
154
+ ### Symbol/String Target Classes in participates_in
155
+
156
+ **What Was Fixed:**
157
+
158
+ Fixed multiple bugs when using Symbol or String class names in `participates_in`:
159
+
160
+ ```ruby
161
+ class Domain < Familia::Horreum
162
+ # All forms now work correctly:
163
+ participates_in Customer, :domains # Class object
164
+ participates_in :Customer, :domains # Symbol (was broken)
165
+ participates_in 'Customer', :domains # String (was broken)
166
+ end
167
+ ```
168
+
169
+ **Errors Fixed:**
170
+ - `NoMethodError: private method 'member_by_config_name'`
171
+ - `NoMethodError: undefined method 'familia_name' for Symbol`
172
+ - `NoMethodError: undefined method 'config_name' for Symbol`
173
+ - Confusing nil errors for unloaded classes
174
+
175
+ **New Behavior:**
176
+
177
+ When a target class can't be resolved, you now get a helpful error:
178
+ ```
179
+ Target class 'Customer' could not be resolved.
180
+ Possible causes:
181
+ 1. The class hasn't been loaded yet (load order issue)
182
+ 2. The class name is misspelled
183
+ 3. The class doesn't inherit from Familia::Horreum
184
+
185
+ Registered Familia classes: ["User", "Team", "Organization"]
186
+ ```
187
+
188
+ ## Performance Recommendations
189
+
190
+ ### Use Bulk Loading for Collections
191
+
192
+ ```ruby
193
+ # ❌ Avoid N+1 queries
194
+ team.members.to_a.map { |id| User.find_by_id(id) }
195
+
196
+ # ✅ Use bulk loading
197
+ User.load_multi(team.members.to_a)
198
+ ```
199
+
200
+ ### Skip EXISTS Checks When Safe
201
+
202
+ ```ruby
203
+ # When loading from sorted sets (keys guaranteed to exist)
204
+ task_ids = project.tasks.range(0, 9)
205
+ tasks = Task.load_multi(task_ids) # Or use check_exists: false
206
+
207
+ # For known-existing keys
208
+ user = User.find_by_id(session[:user_id], check_exists: false)
209
+ ```
210
+
211
+ ### Leverage Reverse Collection Methods
212
+
213
+ ```ruby
214
+ # ❌ Manual parsing of participations
215
+ team_keys = user.participations.members.select { |k| k.start_with?("team:") }
216
+ team_ids = team_keys.map { |k| k.split(':')[1] }
217
+ teams = Team.load_multi(team_ids)
218
+
219
+ # ✅ Use generated methods
220
+ teams = user.team_instances
221
+ ```
222
+
223
+ ## Backwards Compatibility
224
+
225
+ All changes in this version are backwards compatible:
226
+
227
+ - New methods are additive and don't affect existing APIs
228
+ - Default behaviors remain unchanged
229
+ - Symbol/String fixes don't require code changes
230
+
231
+ ## Recommended Actions
232
+
233
+ 1. **Adopt bulk loading** for performance-critical paths
234
+ 2. **Use reverse collection methods** to simplify relationship queries
235
+ 3. **Consider check_exists: false** for guaranteed-existing keys
236
+ 4. **Update external_identifier formats** if custom prefixes are needed
237
+
238
+ ## See Also
239
+
240
+ - [Relationships Guide](../guides/feature-relationships.md)
241
+ - [Performance Optimization Guide](../guides/optimized-loading.md)
data/docs/overview.md CHANGED
@@ -289,13 +289,11 @@ class SecureModel < Familia::Horreum
289
289
  # → password, password= (no fast writer method)
290
290
  # → Values wrapped in RedactedString
291
291
 
292
- # Redacted fields are persisted but return [REDACTED] in logs
293
- redacted_field :security_question
294
- # → security_question, security_question=, security_question!
292
+ # Note: All transient field values are automatically wrapped in RedactedString
293
+ # for security - they never persist to the database
295
294
 
296
- # Object identifier fields auto-generate unique IDs
295
+ # Object identifier fields auto-generate unique IDs when using the feature
297
296
  # → objid, objid= (lazy generation, preserves initialization values)
298
- # → objid_generator_used (provenance tracking)
299
297
  end
300
298
 
301
299
  # Usage examples
@@ -856,7 +854,7 @@ Familia.debug = true # Shows feature loading sequence
856
854
  Familia.debug = true
857
855
 
858
856
  # Check what's in Valkey
859
- Familia.redis.keys('*') # List all keys (use carefully in production)
857
+ Familia.dbclient.keys('*') # List all keys (use carefully in production)
860
858
  ```
861
859
 
862
860
  ## Testing
@@ -880,7 +878,7 @@ Familia.config.current_key_version = :v1
880
878
 
881
879
  # Clear data between tests
882
880
  def clear_redis
883
- Familia.redis.flushdb
881
+ Familia.dbclient.flushdb
884
882
  end
885
883
 
886
884
  # Feature-specific testing patterns
@@ -898,8 +896,8 @@ end
898
896
 
899
897
  def test_relationships_cleanup
900
898
  # Clean up relationship indexes
901
- Familia.redis.keys('*:relationships:*').each do |key|
902
- Familia.redis.del(key)
899
+ Familia.dbclient.keys('*:relationships:*').each do |key|
900
+ Familia.dbclient.del(key)
903
901
  end
904
902
  end
905
903
  ```