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
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/encrypted_data.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -15,10 +17,10 @@ module Familia
15
17
  # Check for required fields
16
18
  required_fields = %i[algorithm nonce ciphertext auth_tag key_version]
17
19
  result = required_fields.all? { |field| parsed.key?(field) }
18
- Familia.ld "[valid?] result: #{result}, parsed: #{parsed}, required: #{required_fields}"
20
+ Familia.debug "[valid?] result: #{result}, parsed: #{parsed}, required: #{required_fields}"
19
21
  result
20
22
  rescue Familia::SerializerError => e
21
- Familia.ld "[valid?] JSON error: #{e.message}"
23
+ Familia.debug "[valid?] JSON error: #{e.message}"
22
24
  false
23
25
  end
24
26
  end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/manager.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/providers/aes_gcm_provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ⚠️ RUBY MEMORY SAFETY WARNING ⚠️
4
6
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ⚠️ PROTOTYPE IMPLEMENTATION - NOT FOR PRODUCTION USE ⚠️
4
6
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/providers/xchacha20_poly1305_provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ⚠️ RUBY MEMORY SAFETY WARNING ⚠️
4
6
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/registry.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -1,5 +1,7 @@
1
1
  # lib/familia/encryption/request_cache.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  # Request-scoped caching for encryption keys (if needed for performance)
4
6
  # This should ONLY be enabled if performance testing shows it's necessary
5
7
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'base64'
4
6
  require 'oj'
@@ -60,9 +62,14 @@ module Familia
60
62
  # end
61
63
  class << self
62
64
  # Get or create a manager with specific algorithm
65
+ #
66
+ # Thread-safe lazy initialization using Concurrent::Map to ensure
67
+ # only a single Manager instance is created per algorithm even under
68
+ # concurrent encryption/decryption requests.
69
+ #
63
70
  def manager(algorithm: nil)
64
- @managers ||= {}
65
- @managers[algorithm] ||= Manager.new(algorithm: algorithm)
71
+ @managers ||= Concurrent::Map.new
72
+ @managers.fetch_or_store(algorithm) { Manager.new(algorithm: algorithm) }
66
73
  end
67
74
 
68
75
  # Quick encryption with auto-selected best provider
@@ -1,5 +1,7 @@
1
1
  # lib/familia/errors.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  module Familia
4
6
  # Base exception class for all Familia errors
5
7
  class Problem < RuntimeError; end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/autoloader.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # rubocop:disable Style/ClassAndModuleChildren
4
6
  module Familia::Features
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/encrypted_fields/concealed_string.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ConcealedString
4
6
  #
@@ -1,3 +1,7 @@
1
+ # lib/familia/features/encrypted_fields/encrypted_field_type.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # lib/familia/field_types/encrypted_field_type.rb
2
6
 
3
7
  require_relative '../../field_type'
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/encrypted_fields.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative 'encrypted_fields/encrypted_field_type'
4
6
 
@@ -341,8 +343,6 @@ module Familia
341
343
  # Check if this instance has any encrypted fields with values
342
344
  #
343
345
  # @return [Boolean] true if any encrypted fields have values
344
- #
345
- # TODO: Missing test coverage
346
346
  def encrypted_data?
347
347
  self.class.encrypted_fields.any? do |field_name|
348
348
  field_value = instance_variable_get("@#{field_name}")
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/expiration/extensions.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # Add a default update_expiration method for all classes that include
@@ -24,7 +26,7 @@ module Familia
24
26
  # @example MyModel.new.update_expiration(expiration: 3600) # => nothing happens
25
27
  #
26
28
  def update_expiration(expiration: nil)
27
- Familia.ld <<~LOG
29
+ Familia.debug <<~LOG
28
30
  [update_expiration] Expiration feature not enabled for #{self.class}.
29
31
  Key: #{dbkey} Arg: #{expiration} (caller: #{caller(1..1)})
30
32
  LOG
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/expiration.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative 'expiration/extensions'
4
6
 
@@ -252,13 +254,13 @@ module Familia
252
254
 
253
255
  # Handle cascading expiration to related data structures
254
256
  if self.class.relations?
255
- Familia.ld "[update_expiration] #{self.class} has relations: #{self.class.related_fields.keys}"
257
+ Familia.debug "[update_expiration] #{self.class} has relations: #{self.class.related_fields.keys}"
256
258
  self.class.related_fields.each do |name, definition|
257
259
  # Skip relations that don't have their own expiration settings
258
260
  next if definition.opts[:default_expiration].nil?
259
261
 
260
262
  obj = send(name)
261
- Familia.ld "[update_expiration] Updating expiration for #{name} (#{obj.dbkey}) to #{expiration}"
263
+ Familia.debug "[update_expiration] Updating expiration for #{name} (#{obj.dbkey}) to #{expiration}"
262
264
  obj.update_expiration(expiration: expiration)
263
265
  end
264
266
  end
@@ -280,11 +282,17 @@ module Familia
280
282
  # If zero, simply skip setting an expiry for this key. If we were to set
281
283
  # 0, Valkey/Redis would drop the key immediately.
282
284
  if expiration.zero?
283
- Familia.ld "[update_expiration] No expiration for #{self.class} (#{dbkey})"
285
+ Familia.debug "[update_expiration] No expiration for #{self.class} (#{dbkey})"
284
286
  return true
285
287
  end
286
288
 
287
- Familia.ld "[update_expiration] Expires #{dbkey} in #{expiration} seconds"
289
+ # Structured TTL operation logging
290
+ Familia.debug "TTL updated",
291
+ operation: :expire,
292
+ key: dbkey,
293
+ ttl_seconds: expiration,
294
+ class: self.class.name,
295
+ identifier: (identifier rescue nil)
288
296
 
289
297
  # The Valkey/Redis' EXPIRE command returns 1 if the timeout was set, 0
290
298
  # if key does not exist or the timeout could not be set. Via redis-rb,
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/external_identifier.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -12,8 +14,8 @@ module Familia
12
14
  base.extend ModelClassMethods
13
15
  base.include ModelInstanceMethods
14
16
 
15
- # Ensure default prefix is set in feature options
16
- base.add_feature_options(:external_identifier, prefix: 'ext')
17
+ # Ensure default format is set in feature options
18
+ base.add_feature_options(:external_identifier, format: 'ext_%{id}')
17
19
 
18
20
  # Add class-level mapping for extid -> id lookups
19
21
  base.class_hashkey :extid_lookup
@@ -35,10 +37,10 @@ module Familia
35
37
  # - Deterministic generation from objid ensures consistency
36
38
  # - Shorter than objid (128-bit vs 256-bit) for external use
37
39
  # - Base-36 encoding for URL-safe identifiers
38
- # - 'ext_' prefix for clear identification as external IDs
40
+ # - Customizable format template (default: 'ext_' prefix)
39
41
  # - Lazy generation preserves values from initialization
40
42
  #
41
- # @example Using external identifier fields
43
+ # @example Using external identifier fields with default format
42
44
  # class User < Familia::Horreum
43
45
  # feature :object_identifier
44
46
  # feature :external_identifier
@@ -51,6 +53,30 @@ module Familia
51
53
  # user2 = User.new(objid: user.objid, email: 'user@example.com')
52
54
  # user2.extid # => "ext_abc123def456ghi789" (identical to user.extid)
53
55
  #
56
+ # @example Using custom format template with hyphen separator
57
+ # class APIKey < Familia::Horreum
58
+ # feature :object_identifier
59
+ # feature :external_identifier, format: 'api-%{id}'
60
+ # end
61
+ # key = APIKey.new
62
+ # key.extid # => "api-abc123def456ghi789"
63
+ #
64
+ # @example Using custom format template with custom prefix
65
+ # class Customer < Familia::Horreum
66
+ # feature :object_identifier
67
+ # feature :external_identifier, format: 'cust_%{id}'
68
+ # end
69
+ # customer = Customer.new
70
+ # customer.extid # => "cust_abc123def456ghi789"
71
+ #
72
+ # @example Using format template without prefix
73
+ # class Resource < Familia::Horreum
74
+ # feature :object_identifier
75
+ # feature :external_identifier, format: 'v2/%{id}'
76
+ # end
77
+ # resource = Resource.new
78
+ # resource.extid # => "v2/abc123def456ghi789"
79
+ #
54
80
  class ExternalIdentifierFieldType < Familia::FieldType
55
81
  # Override getter to provide lazy generation from objid
56
82
  #
@@ -252,11 +278,11 @@ module Familia
252
278
  # 128 bits is approximately 25 characters in base36.
253
279
  external_part = random_bytes.unpack1('H*').to_i(16).to_s(36).rjust(25, '0')
254
280
 
255
- # Get prefix from feature options, default to "ext"
281
+ # Get format from feature options and interpolate the ID
256
282
  options = self.class.feature_options(:external_identifier)
257
- prefix = options[:prefix] || 'ext'
283
+ format = options[:format] || 'ext_%{id}'
258
284
 
259
- "#{prefix}_#{external_part}"
285
+ format % { id: external_part }
260
286
  end
261
287
 
262
288
  # Full-length alias for extid for clarity when needed
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/object_identifier.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/quantization.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -389,7 +391,7 @@ module Familia
389
391
  # user.quantized_identifier(1.hour) # => "123:1672531200"
390
392
  # user.quantized_identifier(1.hour, pattern: '%Y%m%d%H') # => "123:2023010114"
391
393
  #
392
- def quantized_identifier(quantum, pattern: nil, separator: ':')
394
+ def quantized_identifier(quantum, pattern: nil, separator: Familia.delim)
393
395
  timestamp = qstamp(quantum, pattern: pattern)
394
396
  base_id = respond_to?(:identifier) ? identifier : object_id
395
397
  "#{base_id}#{separator}#{timestamp}"
@@ -18,6 +18,7 @@ participates_in Organization, :members, score: :joined_at, bidirectional: true
18
18
 
19
19
  **unique_index** - Fast unique lookups ("find object by unique field value")
20
20
  ```ruby
21
+ unique_index :email, :email_index # Class-level: User.find_by_email()
21
22
  unique_index :email, :email_index, within: Organization # Scoped: org.find_by_email()
22
23
  ```
23
24
 
@@ -89,7 +90,8 @@ class Customer < Familia::Horreum
89
90
  feature :relationships
90
91
 
91
92
  participates_in Organization, :members # Customer belongs to org
92
- unique_index :email, :email_index, within: Organization # Find by unique email
93
+ unique_index :email, :email_index # Class-level: Customer.find_by_email()
94
+ multi_index :status, :status_index, within: Organization # Scoped: org.find_all_by_status()
93
95
  end
94
96
  ```
95
97
 
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/relationships/collection_operations.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -1,3 +1,5 @@
1
+ # lib/familia/features/relationships/indexing/multi_index_generators.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
5
  module Familia
@@ -78,7 +80,7 @@ module Familia
78
80
  # This acts as a factory for field-value-specific DataTypes
79
81
  define_method(:"#{index_name}_for") do |field_value|
80
82
  # Return properly managed DataType instance with parameterized key
81
- index_key = "#{index_name}:#{field_value}"
83
+ index_key = Familia.join(index_name, field_value)
82
84
  Familia::UnsortedSet.new(index_key, parent: self)
83
85
  end
84
86
  end
@@ -97,6 +99,9 @@ module Familia
97
99
  # Resolve scope class using Familia pattern
98
100
  actual_scope_class = Familia.resolve_class(scope_class)
99
101
 
102
+ # Get scope_class_config for method naming (needed for rebuild methods)
103
+ scope_class_config = actual_scope_class.config_name
104
+
100
105
  # Generate instance sampling method (e.g., company.sample_from_department)
101
106
  actual_scope_class.class_eval do
102
107
 
@@ -118,11 +123,135 @@ module Familia
118
123
  index_set.members.map { |id| indexed_class.find_by_identifier(id) }
119
124
  end
120
125
 
121
- # Generate method to rebuild the index for this parent instance
122
- define_method(:"rebuild_#{index_name}") do
123
- # This would need to be implemented based on how you track which
124
- # objects belong to this parent instance
125
- # For now, just a placeholder
126
+ # Generate method to rebuild the multi-value index for this parent instance
127
+ #
128
+ # Multi-indexes create separate sets for each field value, requiring a three-phase approach:
129
+ # 1. Loading: Load all objects once and cache them (discovers field values simultaneously)
130
+ # 2. Clearing: Remove all existing index sets using SCAN
131
+ # 3. Rebuilding: Rebuild index from cached objects (no reload needed)
132
+ #
133
+ # @param batch_size [Integer] Number of identifiers to process per batch
134
+ # @yield [progress] Optional block called with progress updates
135
+ # @yieldparam progress [Hash] Progress information with keys:
136
+ # - :phase [Symbol] Current phase (:loading, :clearing, :rebuilding)
137
+ # - :current [Integer] Current item count
138
+ # - :total [Integer] Total items (when known)
139
+ # - :field_value [String] Current field value being processed
140
+ #
141
+ # @example Basic rebuild
142
+ # company.rebuild_dept_index
143
+ #
144
+ # @example With progress monitoring
145
+ # company.rebuild_dept_index do |progress|
146
+ # puts "#{progress[:phase]}: #{progress[:current]}/#{progress[:total]}"
147
+ # end
148
+ #
149
+ # @example Memory-conscious rebuild for large collections
150
+ # # Process in smaller batches to reduce memory footprint
151
+ # company.rebuild_dept_index(batch_size: 50)
152
+ #
153
+ # @note Memory Considerations:
154
+ # This method caches all objects in memory during rebuild to avoid duplicate
155
+ # database loads. For very large collections (>100k objects), monitor memory usage
156
+ # and consider processing in chunks or using a streaming approach if memory
157
+ # constraints are encountered. The batch_size parameter controls Redis I/O
158
+ # batching but does not affect memory usage since all objects are cached.
159
+ #
160
+ define_method(:"rebuild_#{index_name}") do |batch_size: 100, &progress_block|
161
+ # PHASE 1: Find the collection containing the indexed objects
162
+ # Look for a participation relationship where indexed_class participates in this scope_class
163
+ collection_name = nil
164
+
165
+ # Check if indexed_class has participation to this scope_class
166
+ if indexed_class.respond_to?(:participation_relationships)
167
+ participation = indexed_class.participation_relationships.find do |rel|
168
+ rel.target_class == self.class
169
+ end
170
+ collection_name = participation&.collection_name if participation
171
+ end
172
+
173
+ # Get the collection DataType if we found a participation relationship
174
+ collection = collection_name ? send(collection_name) : nil
175
+
176
+ if collection
177
+ # PHASE 2: Load objects once and cache them for both discovery and rebuilding
178
+ # This avoids duplicate load_multi calls (previous approach loaded twice)
179
+ progress_block&.call(phase: :loading, current: 0, total: collection.size)
180
+
181
+ field_values = Set.new
182
+ cached_objects = []
183
+ processed = 0
184
+
185
+ collection.members.each_slice(batch_size) do |identifiers|
186
+ # Load objects in batches - SINGLE LOAD for both phases
187
+ objects = indexed_class.load_multi(identifiers).compact
188
+ cached_objects.concat(objects)
189
+
190
+ objects.each do |obj|
191
+ value = obj.send(field)
192
+ # Only track non-nil, non-empty field values
193
+ field_values << value.to_s if value && !value.to_s.strip.empty?
194
+ end
195
+
196
+ processed += identifiers.size
197
+ progress_block&.call(phase: :loading, current: processed, total: collection.size)
198
+ end
199
+
200
+ # PHASE 3: Clear all existing field-value-specific index sets
201
+ # Use SCAN to find all existing index keys (including orphaned ones from deleted field values)
202
+ progress_block&.call(phase: :clearing, current: 0, total: field_values.size)
203
+
204
+ # Get the base pattern for this index by creating a sample index set
205
+ # The "*" creates a wildcard pattern like "company:123:dept_index:*" for SCAN
206
+ sample_index = send(:"#{index_name}_for", "*")
207
+ index_pattern = sample_index.dbkey
208
+
209
+ # Find all existing index keys using SCAN
210
+ cleared_count = 0
211
+ dbclient.scan_each(match: index_pattern) do |key|
212
+ dbclient.del(key)
213
+ cleared_count += 1
214
+ progress_block&.call(phase: :clearing, current: cleared_count, total: field_values.size, key: key)
215
+ end
216
+
217
+ # PHASE 4: Rebuild index from cached objects (no reload needed)
218
+ progress_block&.call(phase: :rebuilding, current: 0, total: cached_objects.size)
219
+
220
+ processed = 0
221
+ cached_objects.each_slice(batch_size) do |objects|
222
+ transaction do |_tx|
223
+ objects.each do |obj|
224
+ # Use the generated add_to method to maintain consistency
225
+ # This ensures the same logic is used as during normal operation
226
+ obj.send(:"add_to_#{scope_class_config}_#{index_name}", self)
227
+ end
228
+ end
229
+
230
+ processed += objects.size
231
+ progress_block&.call(phase: :rebuilding, current: processed, total: cached_objects.size)
232
+ end
233
+
234
+ Familia.info "[Rebuild] Multi-index #{index_name} rebuilt: #{field_values.size} field values, #{processed} objects"
235
+
236
+ processed # Return count of processed objects
237
+
238
+ else
239
+ # No participation relationship found - warn and suggest alternative
240
+ Familia.warn <<~WARNING
241
+ [Rebuild] Cannot rebuild multi-index #{index_name}: no participation relationship found
242
+
243
+ Multi-index rebuild requires a participation relationship to find objects.
244
+ Add a participation relationship to #{indexed_class.name}:
245
+
246
+ class #{indexed_class.name} < Familia::Horreum
247
+ participates_in #{self.class.name}, :collection_name, score: :field
248
+ end
249
+
250
+ Then access the collection via: #{self.class.config_name}.collection_name
251
+ WARNING
252
+
253
+ nil
254
+ end
126
255
  end
127
256
  end
128
257
  end
@@ -140,7 +269,7 @@ module Familia
140
269
  scope_class_config = scope_class.config_name
141
270
  indexed_class.class_eval do
142
271
  method_name = :"add_to_#{scope_class_config}_#{index_name}"
143
- Familia.ld("[MultiIndexGenerators] #{name} method #{method_name}")
272
+ Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
144
273
 
145
274
  define_method(method_name) do |scope_instance|
146
275
  return unless scope_instance
@@ -156,7 +285,7 @@ module Familia
156
285
  end
157
286
 
158
287
  method_name = :"remove_from_#{scope_class_config}_#{index_name}"
159
- Familia.ld("[MultiIndexGenerators] #{name} method #{method_name}")
288
+ Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
160
289
 
161
290
  define_method(method_name) do |scope_instance|
162
291
  return unless scope_instance
@@ -172,7 +301,7 @@ module Familia
172
301
  end
173
302
 
174
303
  method_name = :"update_in_#{scope_class_config}_#{index_name}"
175
- Familia.ld("[MultiIndexGenerators] #{name} method #{method_name}")
304
+ Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
176
305
 
177
306
  define_method(method_name) do |scope_instance, old_field_value = nil|
178
307
  return unless scope_instance