familia 2.0.0.pre19 → 2.0.0.pre22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/.talismanrc +5 -1
  8. data/CHANGELOG.rst +220 -112
  9. data/CLAUDE.md +28 -1
  10. data/Gemfile +1 -1
  11. data/Gemfile.lock +20 -17
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  15. data/docs/guides/encryption.md +486 -0
  16. data/docs/guides/feature-encrypted-fields.md +123 -7
  17. data/docs/guides/feature-expiration.md +161 -117
  18. data/docs/guides/feature-external-identifiers.md +415 -443
  19. data/docs/guides/feature-object-identifiers.md +400 -269
  20. data/docs/guides/feature-quantization.md +120 -6
  21. data/docs/guides/feature-relationships-indexing.md +318 -0
  22. data/docs/guides/feature-relationships-methods.md +146 -604
  23. data/docs/guides/feature-relationships-participation.md +263 -0
  24. data/docs/guides/feature-relationships.md +118 -136
  25. data/docs/guides/feature-system-devs.md +176 -693
  26. data/docs/guides/feature-system.md +119 -6
  27. data/docs/guides/feature-transient-fields.md +81 -0
  28. data/docs/guides/field-system.md +778 -0
  29. data/docs/guides/index.md +32 -15
  30. data/docs/guides/logging.md +187 -0
  31. data/docs/guides/optimized-loading.md +674 -0
  32. data/docs/guides/thread-safety-monitoring.md +61 -0
  33. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  34. data/docs/migrating/v2.0.0-pre22.md +241 -0
  35. data/docs/overview.md +7 -9
  36. data/docs/reference/api-technical.md +267 -320
  37. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  38. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  39. data/examples/autoloader/mega_customer.rb +2 -0
  40. data/examples/datatype_standalone.rb +4 -3
  41. data/examples/encrypted_fields.rb +2 -1
  42. data/examples/json_usage_patterns.rb +2 -0
  43. data/examples/relationships.rb +3 -0
  44. data/examples/safe_dump.rb +2 -1
  45. data/examples/sampling_demo.rb +53 -0
  46. data/examples/single_connection_transaction_confusions.rb +2 -1
  47. data/familia.gemspec +2 -1
  48. data/lib/familia/base.rb +2 -0
  49. data/lib/familia/connection/behavior.rb +2 -0
  50. data/lib/familia/connection/handlers.rb +2 -0
  51. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  52. data/lib/familia/connection/middleware.rb +34 -24
  53. data/lib/familia/connection/operation_core.rb +3 -2
  54. data/lib/familia/connection/operations.rb +2 -0
  55. data/lib/familia/connection/pipelined_core.rb +3 -3
  56. data/lib/familia/connection/transaction_core.rb +69 -2
  57. data/lib/familia/connection.rb +18 -3
  58. data/lib/familia/data_type/class_methods.rb +3 -1
  59. data/lib/familia/data_type/connection.rb +2 -0
  60. data/lib/familia/data_type/database_commands.rb +2 -0
  61. data/lib/familia/data_type/serialization.rb +79 -52
  62. data/lib/familia/data_type/settings.rb +2 -0
  63. data/lib/familia/data_type/types/counter.rb +2 -0
  64. data/lib/familia/data_type/types/hashkey.rb +7 -5
  65. data/lib/familia/data_type/types/listkey.rb +2 -0
  66. data/lib/familia/data_type/types/lock.rb +2 -0
  67. data/lib/familia/data_type/types/sorted_set.rb +7 -10
  68. data/lib/familia/data_type/types/stringkey.rb +24 -0
  69. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  70. data/lib/familia/data_type.rb +2 -0
  71. data/lib/familia/encryption/encrypted_data.rb +4 -2
  72. data/lib/familia/encryption/manager.rb +2 -0
  73. data/lib/familia/encryption/provider.rb +2 -0
  74. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  75. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  76. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  77. data/lib/familia/encryption/registry.rb +2 -0
  78. data/lib/familia/encryption/request_cache.rb +2 -0
  79. data/lib/familia/encryption.rb +9 -2
  80. data/lib/familia/errors.rb +2 -0
  81. data/lib/familia/features/autoloader.rb +2 -0
  82. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  83. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  84. data/lib/familia/features/encrypted_fields.rb +2 -2
  85. data/lib/familia/features/expiration/extensions.rb +3 -1
  86. data/lib/familia/features/expiration.rb +12 -4
  87. data/lib/familia/features/external_identifier.rb +62 -7
  88. data/lib/familia/features/object_identifier.rb +49 -0
  89. data/lib/familia/features/quantization.rb +3 -1
  90. data/lib/familia/features/relationships/README.md +3 -1
  91. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  92. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  93. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
  95. data/lib/familia/features/relationships/indexing.rb +3 -0
  96. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  98. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  100. data/lib/familia/features/relationships/participation.rb +155 -69
  101. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  102. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  103. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  104. data/lib/familia/features/relationships.rb +5 -3
  105. data/lib/familia/features/safe_dump.rb +2 -0
  106. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  107. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  108. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  109. data/lib/familia/features/transient_fields.rb +2 -0
  110. data/lib/familia/features.rb +2 -0
  111. data/lib/familia/field_type.rb +3 -1
  112. data/lib/familia/horreum/connection.rb +17 -1
  113. data/lib/familia/horreum/database_commands.rb +8 -1
  114. data/lib/familia/horreum/definition.rb +16 -6
  115. data/lib/familia/horreum/management.rb +353 -52
  116. data/lib/familia/horreum/persistence.rb +179 -108
  117. data/lib/familia/horreum/related_fields.rb +2 -0
  118. data/lib/familia/horreum/serialization.rb +23 -4
  119. data/lib/familia/horreum/settings.rb +2 -0
  120. data/lib/familia/horreum/utils.rb +2 -0
  121. data/lib/familia/horreum.rb +15 -1
  122. data/lib/familia/identifier_extractor.rb +3 -1
  123. data/lib/familia/instrumentation.rb +156 -0
  124. data/lib/familia/json_serializer.rb +2 -0
  125. data/lib/familia/logging.rb +92 -32
  126. data/lib/familia/refinements/dear_json.rb +2 -0
  127. data/lib/familia/refinements/stylize_words.rb +2 -14
  128. data/lib/familia/refinements/time_literals.rb +2 -0
  129. data/lib/familia/refinements.rb +2 -0
  130. data/lib/familia/secure_identifier.rb +10 -2
  131. data/lib/familia/settings.rb +2 -0
  132. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  133. data/lib/familia/thread_safety/monitor.rb +328 -0
  134. data/lib/familia/utils.rb +13 -0
  135. data/lib/familia/verifiable_identifier.rb +3 -1
  136. data/lib/familia/version.rb +3 -1
  137. data/lib/familia.rb +31 -4
  138. data/lib/middleware/database_command_counter.rb +152 -0
  139. data/lib/middleware/database_logger.rb +295 -170
  140. data/lib/multi_result.rb +61 -31
  141. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  142. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  143. data/try/edge_cases/json_serialization_try.rb +2 -0
  144. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  145. data/try/edge_cases/race_conditions_try.rb +4 -0
  146. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  147. data/try/edge_cases/string_coercion_try.rb +2 -0
  148. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  149. data/try/features/count_any_edge_cases_try.rb +486 -0
  150. data/try/features/count_any_methods_try.rb +197 -0
  151. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  152. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  153. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  154. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  155. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  156. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  158. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  159. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  160. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  161. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  162. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  164. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  165. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  166. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  167. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  168. data/try/features/encryption/config_persistence_try.rb +4 -0
  169. data/try/features/encryption/core_try.rb +4 -0
  170. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  171. data/try/features/encryption/module_loading_try.rb +4 -0
  172. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  173. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  174. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  175. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  176. data/try/features/expiration/expiration_try.rb +4 -0
  177. data/try/features/external_identifier/external_identifier_try.rb +305 -8
  178. data/try/features/feature_dependencies_try.rb +2 -0
  179. data/try/features/feature_improvements_try.rb +2 -0
  180. data/try/features/field_groups_try.rb +2 -0
  181. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  182. data/try/features/object_identifier/object_identifier_try.rb +140 -0
  183. data/try/features/quantization/quantization_try.rb +4 -0
  184. data/try/features/real_feature_integration_try.rb +2 -0
  185. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  186. data/try/features/relationships/indexing_rebuild_try.rb +606 -0
  187. data/try/features/relationships/indexing_try.rb +2 -0
  188. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  189. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  190. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  192. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  193. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  194. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  195. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  196. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  197. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  198. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  199. data/try/features/relationships/relationships_performance_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  201. data/try/features/relationships/relationships_try.rb +6 -4
  202. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  203. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  204. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  205. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  206. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  207. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  209. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  210. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  211. data/try/integration/connection/handler_constraints_try.rb +4 -0
  212. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  213. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  214. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  215. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  216. data/try/integration/connection/pools_try.rb +4 -0
  217. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  218. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  219. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  220. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  221. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  222. data/try/integration/connection/transaction_modes_try.rb +4 -0
  223. data/try/integration/conventional_inheritance_try.rb +4 -0
  224. data/try/integration/create_method_try.rb +4 -0
  225. data/try/integration/cross_component_try.rb +4 -0
  226. data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
  227. data/try/integration/data_types/datatype_transactions_try.rb +17 -7
  228. data/try/integration/database_consistency_try.rb +4 -0
  229. data/try/integration/familia_extended_try.rb +4 -0
  230. data/try/integration/familia_members_methods_try.rb +4 -0
  231. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  232. data/try/integration/models/customer_try.rb +7 -3
  233. data/try/integration/models/datatype_base_try.rb +4 -0
  234. data/try/integration/models/familia_object_try.rb +4 -0
  235. data/try/integration/persistence_operations_try.rb +4 -0
  236. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  237. data/try/integration/save_methods_consistency_try.rb +241 -0
  238. data/try/integration/scenarios_try.rb +4 -0
  239. data/try/integration/secure_identifier_try.rb +4 -0
  240. data/try/integration/transaction_safety_core_try.rb +176 -0
  241. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  242. data/try/integration/verifiable_identifier_try.rb +4 -0
  243. data/try/investigation/pipeline_routing/README.md +228 -0
  244. data/try/performance/benchmarks_try.rb +4 -0
  245. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  246. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  247. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  248. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  249. data/try/support/debugging/debug_aad_process.rb +3 -0
  250. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  251. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  252. data/try/support/debugging/debug_context_aad.rb +3 -0
  253. data/try/support/debugging/debug_context_simple.rb +3 -0
  254. data/try/support/debugging/debug_cross_context.rb +3 -0
  255. data/try/support/debugging/debug_database_load.rb +3 -0
  256. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  257. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  258. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  259. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  260. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  261. data/try/support/debugging/debug_load_path.rb +3 -0
  262. data/try/support/debugging/debug_method_definition.rb +3 -0
  263. data/try/support/debugging/debug_method_resolution.rb +3 -0
  264. data/try/support/debugging/debug_minimal.rb +3 -0
  265. data/try/support/debugging/debug_provider.rb +3 -0
  266. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  267. data/try/support/debugging/debug_string_class.rb +3 -0
  268. data/try/support/debugging/debug_test.rb +3 -0
  269. data/try/support/debugging/debug_test_design.rb +3 -0
  270. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  271. data/try/support/debugging/provider_diagnostics.rb +4 -0
  272. data/try/support/helpers/test_cleanup.rb +4 -0
  273. data/try/support/helpers/test_helpers.rb +5 -0
  274. data/try/support/memory/memory_basic_test.rb +4 -0
  275. data/try/support/memory/memory_detailed_test.rb +4 -0
  276. data/try/support/memory/memory_search_for_string.rb +4 -0
  277. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  278. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  279. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  280. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  282. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  283. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  284. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  285. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  286. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  287. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  290. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  291. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  292. data/try/thread_safety/README.md +496 -0
  293. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  294. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  295. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  296. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  297. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  298. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  299. data/try/thread_safety/field_registration_race_try.rb +222 -0
  300. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  301. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  302. data/try/thread_safety/module_config_race_try.rb +175 -0
  303. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  304. data/try/unit/core/autoloader_try.rb +4 -0
  305. data/try/unit/core/base_enhancements_try.rb +4 -0
  306. data/try/unit/core/connection_try.rb +4 -0
  307. data/try/unit/core/errors_try.rb +4 -0
  308. data/try/unit/core/extensions_try.rb +4 -0
  309. data/try/unit/core/familia_logger_try.rb +2 -0
  310. data/try/unit/core/familia_try.rb +4 -0
  311. data/try/unit/core/middleware_sampling_try.rb +335 -0
  312. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  313. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  314. data/try/unit/core/middleware_try.rb +4 -0
  315. data/try/unit/core/settings_try.rb +4 -0
  316. data/try/unit/core/time_utils_try.rb +4 -0
  317. data/try/unit/core/tools_try.rb +4 -0
  318. data/try/unit/core/utils_try.rb +37 -0
  319. data/try/unit/data_types/boolean_try.rb +39 -22
  320. data/try/unit/data_types/counter_try.rb +4 -0
  321. data/try/unit/data_types/datatype_base_try.rb +4 -0
  322. data/try/unit/data_types/hash_try.rb +6 -2
  323. data/try/unit/data_types/list_try.rb +4 -0
  324. data/try/unit/data_types/lock_try.rb +4 -0
  325. data/try/unit/data_types/serialization_try.rb +386 -0
  326. data/try/unit/data_types/sorted_set_try.rb +4 -0
  327. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  328. data/try/unit/data_types/string_try.rb +4 -0
  329. data/try/unit/data_types/unsortedset_try.rb +4 -0
  330. data/try/unit/familia_resolve_class_try.rb +116 -0
  331. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  332. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  333. data/try/unit/horreum/base_try.rb +4 -0
  334. data/try/unit/horreum/class_methods_try.rb +4 -0
  335. data/try/unit/horreum/commands_try.rb +4 -0
  336. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  337. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
  338. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  339. data/try/unit/horreum/field_categories_try.rb +4 -0
  340. data/try/unit/horreum/field_definition_try.rb +4 -0
  341. data/try/unit/horreum/initialization_try.rb +4 -0
  342. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  343. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  344. data/try/unit/horreum/relations_try.rb +4 -0
  345. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  346. data/try/unit/horreum/serialization_try.rb +4 -0
  347. data/try/unit/horreum/settings_try.rb +4 -0
  348. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  349. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  350. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  351. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  352. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  353. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  354. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  355. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  356. data/try/unit/thread_safety_monitor_try.rb +149 -0
  357. metadata +69 -17
  358. data/.github/workflows/code-quality.yml +0 -138
  359. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  360. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  361. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  362. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -28,7 +28,8 @@ end
28
28
 
29
29
  **Key Methods:**
30
30
  - `save` - Persist object to Valkey/Redis
31
- - `save_if_not_exists` - Conditional persistence (v2.0.0-pre6)
31
+ - `save_if_not_exists` - Conditional persistence, returns false if exists
32
+ - `save_if_not_exists!` - Conditional persistence, raises RecordExistsError if exists
32
33
  - `load` - Load object from Valkey/Redis
33
34
  - `exists?` - Check if object exists in Valkey/Redis
34
35
  - `destroy` - Remove object from Valkey/Redis
@@ -103,7 +104,7 @@ user = User.new(name: "Alice", email: "alice@example.com", password_hash: "secre
103
104
  user.safe_dump # => {"name" => "Alice", "email" => "alice@example.com"}
104
105
  ```
105
106
 
106
- #### 3. Encrypted Fields Feature (v2.0.0-pre5)
107
+ #### 3. Encrypted Fields Feature
107
108
  Transparent field-level encryption with multiple providers.
108
109
 
109
110
  ```ruby
@@ -146,13 +147,9 @@ vault.secret_key # => "super-secret-123" (decrypted on access)
146
147
  class CriticalData < Familia::Horreum
147
148
  feature :encrypted_fields
148
149
 
149
- # Configure encryption provider preference
150
- set_encryption_provider :xchacha20_poly1305 # Preferred
151
- set_fallback_provider :aes_gcm # Fallback
152
-
153
- encrypted_field :credit_card, aad_fields: [:user_id, :created_at]
154
- encrypted_field :ssn, provider: :xchacha20_poly1305 # Force specific provider
155
- encrypted_field :notes # Uses default provider
150
+ encrypted_field :credit_card
151
+ encrypted_field :ssn
152
+ encrypted_field :notes
156
153
  end
157
154
 
158
155
  # Key versioning and rotation
@@ -163,44 +160,20 @@ Familia.configure do |config|
163
160
  v3: ENV['NEW_KEY'] # New key for rotation
164
161
  }
165
162
  config.current_key_version = :v2
166
-
167
- # Provider configuration
168
- config.encryption_providers = {
169
- xchacha20_poly1305: {
170
- key_size: 32,
171
- nonce_size: 24,
172
- require_gem: 'rbnacl'
173
- },
174
- aes_gcm: {
175
- key_size: 32,
176
- iv_size: 12,
177
- tag_size: 16
178
- }
179
- }
180
- end
181
-
182
- # Request-level key caching for performance
183
- Familia::Encryption.with_request_cache do
184
- 1000.times do |i|
185
- record = CriticalData.new(
186
- credit_card: "4111-1111-1111-#{i.to_s.rjust(4, '0')}",
187
- ssn: "123-45-#{i.to_s.rjust(4, '0')}",
188
- notes: "Customer record #{i}"
189
- )
190
- record.save # Reuses derived keys for performance
191
- end
163
+ config.encryption_personalization = 'MyApp-2024' # Optional (XChaCha20 only)
192
164
  end
193
165
 
194
- # Key rotation procedures
195
- CriticalData.all.each do |record|
196
- # Re-encrypt with current key version
197
- record.re_encrypt_fields!
166
+ # Operations with encrypted fields
167
+ record = CriticalData.new(
168
+ credit_card: "4111-1111-1111-1234",
169
+ ssn: "123-45-6789",
170
+ notes: "Customer notes"
171
+ )
172
+ record.save
198
173
 
199
- # Verify encryption status
200
- status = record.encrypted_fields_status
201
- puts "Record #{record.identifier}: #{status}"
202
- # => {credit_card: {encrypted: true, key_version: :v2, provider: :xchacha20_poly1305}}
203
- end
174
+ # Verify encryption status
175
+ puts "Record #{record.identifier}: #{status}"
176
+ # => {credit_card: {encrypted: true, algorithm: "xchacha20poly1305", cleared: false}}
204
177
  ```
205
178
 
206
179
  **ConcealedString Security Features:**
@@ -226,7 +199,7 @@ user_data = user.to_json
226
199
  # All encrypted fields show as "[CONCEALED]" in JSON
227
200
  ```
228
201
 
229
- #### 4. Transient Fields Feature (v2.0.0-pre5)
202
+ #### 4. Transient Fields Feature
230
203
  Non-persistent fields with memory-safe handling.
231
204
 
232
205
  ```ruby
@@ -252,7 +225,7 @@ form.password.reveal # => "secret123" (explicit access)
252
225
  - `reveal` method for explicit access
253
226
  - Safe for logging and serialization
254
227
 
255
- #### 5. Relationships Feature (v2.0.0-pre7)
228
+ #### 5. Relationships Feature
256
229
  Comprehensive object relationship system with automatic management, clean Ruby-idiomatic syntax, and simplified method generation.
257
230
 
258
231
  ```ruby
@@ -329,12 +302,14 @@ active_domains = Domain.active_domains.members
329
302
  - **Performance**: O(1) hash lookups and efficient sorted set operations
330
303
  - **Flexibility**: Supports class-level and relationship-scoped indexing patterns
331
304
 
332
- #### 6. Object Identifier Feature (v2.0.0-pre7)
305
+ #### 6. Object Identifier Feature
333
306
  Automatic generation of unique object identifiers with configurable strategies.
334
307
 
308
+ Default generator is `:uuid_v7` (UUID version 7 with embedded timestamp).
309
+
335
310
  ```ruby
336
311
  class Document < Familia::Horreum
337
- feature :object_identifier, generator: :uuid_v4
312
+ feature :object_identifier # Uses default :uuid_v7
338
313
 
339
314
  field :title, :content, :created_at
340
315
  end
@@ -358,91 +333,69 @@ end
358
333
  ```
359
334
 
360
335
  **Generator Types:**
336
+ - `:uuid_v7` - UUID version 7 with embedded timestamp (default, 36 characters)
361
337
  - `:uuid_v4` - Standard UUID v4 format (36 characters)
362
- - `:hex` - Hexadecimal strings (configurable length, default 12)
363
- - `:custom` - User-defined generator method
338
+ - `:hex` - High-entropy hexadecimal strings (256-bit via SecureIdentifier)
339
+ - Proc/Lambda - Custom generation logic provided as a callable
364
340
 
365
341
  **Technical Implementation:**
366
342
  ```ruby
367
343
  # Auto-generated on object creation
368
344
  doc = Document.create(title: "My Document")
369
- doc.objid # => "f47ac10b-58cc-4372-a567-0e02b2c3d479"
345
+ doc.objid # => "01234567-89ab-7def-8000-123456789abc" # UUID v7 format
370
346
 
371
347
  session = Session.create(user_id: "123")
372
348
  session.objid # => "a1b2c3d4e5f67890"
373
349
 
374
- # Custom identifier validation
350
+ # Custom identifier with proc
375
351
  api_key = ApiKey.create(name: "Production API")
376
352
  api_key.objid # => "ak_Xy9ZaBcD3fG8HjKlMnOpQrStUvWxYz12"
377
353
 
378
- # Collision detection and retry logic
354
+ # Feature options per-class isolation
379
355
  Document.feature_options(:object_identifier)
380
- #=> {generator: :uuid_v4, max_retries: 3, collision_check: true}
356
+ #=> {generator: :uuid_v7}
381
357
  ```
382
358
 
383
- #### 7. External Identifier Feature (v2.0.0-pre7)
384
- Integration patterns for external system identifiers with validation and mapping.
359
+ #### 7. External Identifier Feature
360
+ Derives deterministic external identifiers from object identifiers.
385
361
 
386
362
  ```ruby
387
363
  class ExternalUser < Familia::Horreum
388
364
  feature :external_identifier
389
365
 
390
- identifier_field :internal_id
391
- field :internal_id, :external_id, :name, :sync_status, :last_sync_at
366
+ identifier_field :id
367
+ field :id, :name
392
368
 
393
- # External system validation (custom implementation)
394
- def valid_external_id?
395
- external_id.present? && external_id.match?(/^ext_\d{6,}$/)
396
- end
369
+ # External ID is automatically derived from objid
370
+ # Format: 'ext_' + base36(truncated_hash(objid))
397
371
  end
398
372
 
399
- class LegacyAccount < Familia::Horreum
400
- feature :external_identifier, prefix: "legacy"
373
+ class APIKey < Familia::Horreum
374
+ feature :external_identifier, format: 'api-%{id}'
401
375
 
402
- field :legacy_account_id, :migrated_at, :migration_status
403
-
404
- # Custom validation logic
405
- def valid_external_id?
406
- legacy_account_id.present? &&
407
- legacy_account_id.match?(/^LAC[A-Z]{2}\d{8}$/)
408
- end
409
-
410
- # Bidirectional mapping
411
- def self.find_by_legacy_id(legacy_id)
412
- mapping = external_id_mapping.get(legacy_id)
413
- mapping ? load(mapping) : nil
414
- end
376
+ field :name, :permissions
415
377
  end
416
378
  ```
417
379
 
418
380
  **External ID Management:**
419
381
  ```ruby
420
- # Create with external mapping
421
- user = ExternalUser.new(
422
- internal_id: SecureRandom.uuid,
423
- external_id: "ext_123456",
424
- name: "John Doe"
425
- )
426
- user.save # Automatically creates bidirectional mapping
427
-
428
- # Lookup by external ID (custom implementation)
429
- found_user = nil
430
- ExternalUser.all.each do |user|
431
- if user.external_id == "ext_123456"
432
- found_user = user
433
- break
434
- end
435
- end
436
-
437
- # Sync status tracking (custom implementation)
438
- user.sync_status = "pending"
439
- user.save
440
- user.sync_status = "completed"
382
+ # External ID is deterministically derived from objid
383
+ user = ExternalUser.new(name: "John Doe")
441
384
  user.save
385
+ user.objid # => "01234567-89ab-7def-8000-123456789abc"
386
+ user.extid # => "ext_abc123def456ghi789" # Deterministic from objid
387
+
388
+ # Same objid always produces same extid
389
+ user2 = ExternalUser.new(objid: user.objid, name: "John Doe")
390
+ user2.extid # => "ext_abc123def456ghi789" # Identical
391
+
392
+ # Custom format with APIKey
393
+ key = APIKey.new(name: "Production")
394
+ key.extid # => "api-xyz789abc123"
442
395
  ```
443
396
 
444
- #### 8. Quantization Feature (v2.0.0-pre7)
445
- Advanced time-based data bucketing with configurable strategies and analytics integration.
397
+ #### 8. Quantization Feature
398
+ Time-based data bucketing for analytics and caching.
446
399
 
447
400
  ```ruby
448
401
  class DailyMetric < Familia::Horreum
@@ -473,26 +426,24 @@ metric.counter.increment
473
426
 
474
427
  ---
475
428
 
476
- ## Advanced Feature System Architecture (v2.0.0-pre7)
429
+ ## Advanced Feature System Architecture
477
430
 
478
- ### Feature Autoloader for Complex Projects
479
- Organize features into modular files for large applications.
431
+ ### Feature Autoloader for Project Organization
432
+ Automatically load features from directory structure.
480
433
 
481
434
  ```ruby
482
435
  # app/models/customer.rb - Main model file
483
436
  class Customer < Familia::Horreum
484
- module Features
485
- include Familia::Features::Autoloader
486
- # Automatically loads all .rb files from app/models/customer/features/
487
- end
437
+ include Familia::Features::Autoloader
438
+ # Automatically loads all .rb files from app/models/customer/*.rb
488
439
 
489
440
  # Core model definition
490
441
  identifier_field :custid
491
442
  field :custid, :name, :email, :created_at
492
443
  end
493
444
 
494
- # app/models/customer/features/notifications.rb
495
- module Customer::Features::Notifications
445
+ # app/models/customer/notifications.rb
446
+ class Customer < Familia::Horreum
496
447
  def send_welcome_email
497
448
  NotificationService.send_template(
498
449
  email: email,
@@ -510,16 +461,9 @@ module Customer::Features::Notifications
510
461
  end
511
462
  end
512
463
 
513
- # app/models/customer/features/analytics.rb
514
- module Customer::Features::Analytics
515
- extend ActiveSupport::Concern
516
-
517
- included do
518
- # Add analytics tracking to core model
519
- feature :relationships
520
- class_participates_in :customer_analytics, score: :created_at
521
- end
522
-
464
+ # app/models/customer/analytics.rb
465
+ class Customer < Familia::Horreum
466
+ # Analytics methods added to Customer
523
467
  def track_activity(activity_type, metadata = {})
524
468
  activity_data = {
525
469
  custid: custid,
@@ -539,56 +483,20 @@ module Customer::Features::Analytics
539
483
  end
540
484
  ```
541
485
 
542
- ### Feature Dependencies and Loading Order
543
- Control feature loading sequence with dependency declarations.
486
+ ### Feature Dependencies
487
+ Features can declare dependencies that are automatically resolved.
544
488
 
545
489
  ```ruby
546
- # lib/features/advanced_encryption.rb
547
- module AdvancedEncryption
548
- extend Familia::Features::Autoloadable
549
-
550
- def self.depends_on
551
- [:encrypted_fields, :safe_dump] # Required features
552
- end
553
-
554
- def self.included(base)
555
- base.extend ClassMethods
556
- end
557
-
558
- module ClassMethods
559
- def encrypt_all_fields!
560
- # Batch encrypt all existing records
561
- all_records.each(&:re_encrypt_fields!)
562
- end
563
-
564
- def encryption_health_check
565
- # Validate encryption across all records
566
- failed_records = []
567
- all_records.each do |record|
568
- unless record.encrypted_fields_status.all? { |_, status| status[:encrypted] }
569
- failed_records << record.identifier
570
- end
571
- end
572
- failed_records
573
- end
574
- end
575
-
576
- def secure_export
577
- # Combine safe_dump with additional security
578
- exported = safe_dump
579
- exported[:export_timestamp] = Familia.now.to_i
580
- exported[:checksum] = Digest::SHA256.hexdigest(exported.to_json)
581
- exported
582
- end
490
+ # External identifier depends on object_identifier
491
+ class User < Familia::Horreum
492
+ feature :external_identifier # Automatically includes :object_identifier
493
+ field :name
583
494
  end
584
495
 
585
- # Usage with automatic dependency resolution
586
- class SecureCustomer < Familia::Horreum
587
- feature :advanced_encryption # Automatically includes dependencies
496
+ # Feature dependency resolution
497
+ Familia::Base.add_feature ExternalIdentifier, :external_identifier, depends_on: [:object_identifier]
588
498
 
589
- field :name, :email
590
- encrypted_field :api_key, :private_notes
591
- safe_dump_field :name, :email
499
+ # When external_identifier is included, object_identifier is automatically loaded first
592
500
  end
593
501
  ```
594
502
 
@@ -597,82 +505,64 @@ Each class maintains independent feature options.
597
505
 
598
506
  ```ruby
599
507
  class PrimaryCache < Familia::Horreum
600
- feature :expiration, cascade_to: [:secondary_cache]
601
- feature :quantization, time_buckets: [1.hour, 6.hours, 1.day]
508
+ feature :expiration
509
+ feature :object_identifier, generator: :uuid_v7
602
510
 
603
511
  field :cache_key, :value, :hit_count
604
512
  default_expiration 24.hours
605
513
  end
606
514
 
607
515
  class SecondaryCache < Familia::Horreum
608
- feature :expiration, cascade_to: [] # No further cascading
609
- feature :quantization, time_buckets: [1.day, 1.week] # Different buckets
516
+ feature :expiration
517
+ feature :object_identifier, generator: :hex # Different generator
610
518
 
611
519
  field :cache_key, :backup_value, :backup_timestamp
612
520
  default_expiration 7.days
613
521
  end
614
522
 
615
523
  # Feature options are completely isolated
616
- PrimaryCache.feature_options(:expiration)
617
- #=> {cascade_to: [:secondary_cache]}
524
+ PrimaryCache.feature_options(:object_identifier)
525
+ #=> {generator: :uuid_v7}
618
526
 
619
- SecondaryCache.feature_options(:expiration)
620
- #=> {cascade_to: []}
621
-
622
- PrimaryCache.feature_options(:quantization)
623
- #=> {time_buckets: [3600, 21600, 86400]}
624
-
625
- SecondaryCache.feature_options(:quantization)
626
- #=> {time_buckets: [86400, 604800]}
527
+ SecondaryCache.feature_options(:object_identifier)
528
+ #=> {generator: :hex}
627
529
  ```
628
530
 
629
- ### Runtime Feature Management
630
- Add, remove, and configure features dynamically.
531
+ ### Runtime Feature Checking
532
+ Check which features are enabled on a class.
631
533
 
632
534
  ```ruby
633
- class DynamicModel < Familia::Horreum
535
+ class SecureModel < Familia::Horreum
536
+ feature :expiration
537
+ feature :encrypted_fields
538
+ feature :safe_dump
539
+
634
540
  field :name, :status
541
+ encrypted_field :api_key
542
+ safe_dump_field :name
543
+ end
635
544
 
636
- def self.enable_feature_set(feature_set)
637
- case feature_set
638
- when :basic
639
- feature :expiration
640
- feature :safe_dump
641
- when :secure
642
- feature :expiration
643
- feature :encrypted_fields
644
- feature :safe_dump
645
- when :analytics
646
- feature :expiration
647
- feature :relationships
648
- feature :quantization
649
- end
650
- end
545
+ # Check enabled features
546
+ SecureModel.features_enabled
547
+ #=> [:expiration, :encrypted_fields, :safe_dump]
651
548
 
652
- def self.feature_enabled?(feature_name)
653
- features_enabled.include?(feature_name.to_sym)
654
- end
549
+ # Check feature options
550
+ SecureModel.feature_options(:encrypted_fields)
551
+ #=> {} # Default options
655
552
 
656
- def self.disable_feature(feature_name)
657
- # Remove feature from enabled list (affects new instances)
658
- features_enabled.delete(feature_name.to_sym)
659
- remove_feature_options(feature_name)
660
- end
553
+ # Each class tracks its own features
554
+ class BasicModel < Familia::Horreum
555
+ feature :expiration
556
+ field :name
661
557
  end
662
558
 
663
- # Runtime configuration
664
- DynamicModel.enable_feature_set(:analytics)
665
- DynamicModel.feature_enabled?(:relationships) # => true
666
-
667
- # Conditional feature usage
668
- if DynamicModel.feature_enabled?(:encrypted_fields)
669
- DynamicModel.encrypted_field :sensitive_data
670
- end
559
+ BasicModel.features_enabled
560
+ #=> [:expiration]
671
561
  ```
672
562
 
673
563
  ---
674
564
 
675
- ## Connection Management (v2.0.0-pre+)
565
+ ## Connection Management
676
566
 
677
567
  ### Connection Provider Pattern
678
568
  Flexible connection pooling with provider-based architecture.
@@ -680,13 +570,14 @@ Flexible connection pooling with provider-based architecture.
680
570
  ```ruby
681
571
  # Basic Valkey/Redis connection
682
572
  Familia.configure do |config|
683
- config.redis_uri = "redis://localhost:6379/0"
573
+ config.uri = "redis://localhost:6379/0"
684
574
  end
685
575
 
686
- # Connection pooling with ConnectionPool gem
576
+ # Custom connection provider with pooling
687
577
  require 'connection_pool'
688
578
 
689
579
  Familia.connection_provider = lambda do |uri|
580
+ # Provider MUST return connection already on correct database
690
581
  parsed = URI.parse(uri)
691
582
  pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
692
583
 
@@ -695,14 +586,11 @@ Familia.connection_provider = lambda do |uri|
695
586
  Redis.new(
696
587
  host: parsed.host,
697
588
  port: parsed.port,
698
- db: parsed.db || 0,
699
- connect_timeout: 1,
700
- read_timeout: 1,
701
- write_timeout: 1
589
+ db: parsed.db || 0
702
590
  )
703
591
  end
704
592
 
705
- @pools[pool_key].with { |conn| yield conn if block_given?; conn }
593
+ @pools[pool_key].with { |conn| conn }
706
594
  end
707
595
  ```
708
596
 
@@ -728,68 +616,129 @@ end
728
616
 
729
617
  ---
730
618
 
731
- ## Advanced Relationship Patterns
619
+ ## Testing and Debugging
732
620
 
733
- ### Permission-Encoded Relationships (v2.0.0-pre7)
734
- Combine timestamps with permission bits for access control.
621
+ ### Database Command Logging
622
+ Monitor all Redis commands with DatabaseLogger middleware.
735
623
 
736
624
  ```ruby
737
- class Document < Familia::Horreum
738
- feature :relationships
739
-
740
- identifier_field :doc_id
741
- field :doc_id, :title, :content
625
+ # Enable command logging (middleware registered automatically)
626
+ Familia.enable_database_logging = true
742
627
 
743
- # Permission constants (bit flags)
744
- READ = 1 # 001
745
- WRITE = 2 # 010
746
- DELETE = 4 # 100
747
- ADMIN = 8 # 1000
628
+ # Capture commands in tests
629
+ commands = DatabaseLogger.capture_commands do
630
+ user = User.create(name: "Test User")
631
+ user.save
748
632
  end
749
633
 
750
- class UserDocumentAccess
751
- # Encode timestamp + permissions into sorted set score
752
- def self.encode_score(timestamp, permissions)
753
- "#{timestamp}.#{permissions}".to_f
754
- end
634
+ puts commands.first.command # => "SET user:123 {...}"
635
+ puts commands.first.μs # => 567 (microseconds)
755
636
 
756
- def self.decode_score(score)
757
- parts = score.to_s.split('.')
758
- timestamp = parts[0].to_i
759
- permissions = parts[1] ? parts[1].to_i : 0
760
- [timestamp, permissions]
761
- end
637
+ # Enable sampling for production
638
+ DatabaseLogger.sample_rate = 0.01 # Log 1% of commands
762
639
 
763
- # Check if user has specific permission
764
- def self.has_permission?(permissions, required)
765
- (permissions & required) != 0
766
- end
640
+ # Structured logging format
641
+ DatabaseLogger.structured_logging = true
642
+ # => "Redis command cmd=SET args=[key, value] duration_ms=0.42 db=0"
643
+ ```
644
+
645
+ ### Debug Mode
646
+ Enable comprehensive debugging output.
647
+
648
+ ```ruby
649
+ # Via environment variable
650
+ ENV['FAMILIA_DEBUG'] = '1'
651
+ ENV['FAMILIA_TRACE'] = '1'
652
+
653
+ # Via configuration
654
+ Familia.configure do |config|
655
+ config.debug = true
767
656
  end
768
657
 
769
- # Usage example
770
- user_id = "user123"
771
- doc_id = "doc456"
772
- timestamp = Familia.now.to_i
658
+ # Check database contents
659
+ Familia.dbclient.keys('user:*')
773
660
 
774
- # Grant read + write permissions
775
- permissions = Document::READ | Document::WRITE # 3
776
- score = UserDocumentAccess.encode_score(timestamp, permissions)
661
+ # Trace specific operations
662
+ Familia.trace :LOAD, redis_client, "user:123", "from cache"
663
+ ```
777
664
 
778
- # Store in sorted set (user_id -> score with permissions)
779
- user_documents = Familia::DataType::SortedSet.new("user:#{user_id}:documents")
780
- user_documents.add(doc_id, score)
665
+ ### Connection Chain Debugging
666
+ Understand connection resolution order.
781
667
 
782
- # Query with permission filtering
783
- docs_with_write = user_documents.select do |doc_id, score|
784
- _, permissions = UserDocumentAccess.decode_score(score)
785
- UserDocumentAccess.has_permission?(permissions, Document::WRITE)
668
+ ```ruby
669
+ # Connection resolution order:
670
+ # 1. Instance @dbclient if set
671
+ # 2. Class logical_database if configured
672
+ # 3. Connection provider if set
673
+ # 4. Global Familia connection
674
+
675
+ class DebugModel < Familia::Horreum
676
+ logical_database 3
677
+ field :name
786
678
  end
679
+
680
+ model = DebugModel.new(name: "test")
681
+ # Uses database 3 from logical_database
682
+
683
+ model.dbclient = custom_connection
684
+ # Now uses custom_connection instead
787
685
  ```
788
686
 
789
- ### Time-Series Relationships with Automatic Management
790
- Track relationships over time with timestamp-based scoring and automatic updates.
687
+ ## Performance Considerations
688
+
689
+ ### Encryption Performance
690
+ - XChaCha20-Poly1305 is ~2x faster than AES-256-GCM
691
+ - Key derivation is NOT cached by default for security
692
+ - Use request-level caching for bulk operations:
791
693
 
792
694
  ```ruby
695
+ Familia::Encryption.with_request_cache do
696
+ # Bulk operations with cached key derivation
697
+ 1000.times do |i|
698
+ User.create(email: "user#{i}@example.com")
699
+ end
700
+ end
701
+ ```
702
+
703
+ ### Connection Pooling
704
+ - Use connection_provider for multi-threaded environments
705
+ - Pool size should match thread count
706
+ - Connections are thread-safe when using provider pattern
707
+
708
+ ### Feature Performance Impact
709
+ - **Encryption**: ~10-20% overhead for field access
710
+ - **Relationships**: O(1) for indexed lookups
711
+ - **Quantization**: Minimal overhead, improves storage efficiency
712
+ - **Safe Dump**: Lazy evaluation, only computed when called
713
+
714
+ ---
715
+
716
+ ## Migration Guide
717
+
718
+ ### From Familia v1.x to v2.0
719
+ - Replace `redis_uri` with `uri` in configuration
720
+ - Update feature syntax from mixins to `feature` declarations
721
+ - Migrate from `global_` prefix to `class_` for class-level methods
722
+ - Update encryption configuration to new provider system
723
+
724
+ ### Connection Provider Pattern
725
+ - Familia v2.0 uses redis-rb gem internally
726
+ - Connection providers must return Redis connections
727
+ - Uses RedisClient middleware architecture internally via redis-rb
728
+
729
+ ---
730
+
731
+ ## Summary
732
+
733
+ Familia v2.0.0-pre series provides a comprehensive ORM for Valkey/Redis with:
734
+ - **Modular Feature System**: Isolated, configurable features per class
735
+ - **Advanced Security**: Field-level encryption with multiple providers
736
+ - **Flexible Relationships**: Automatic management with clean Ruby syntax
737
+ - **Performance Optimized**: Connection pooling, sampling, and caching
738
+ - **Production Ready**: Debug logging, monitoring, and thread safety
739
+
740
+ For additional documentation and examples, see the [Familia GitHub repository](https://github.com/delano/familia).
741
+
793
742
  class ActivityTracker < Familia::Horreum
794
743
  feature :relationships
795
744
 
@@ -799,7 +748,7 @@ class ActivityTracker < Familia::Horreum
799
748
  # Class-level tracking with automatic management
800
749
  class_participates_in :user_activities, score: :created_at
801
750
  class_participates_in :activity_by_type,
802
- score: ->(activity) { "#{activity.activity_type}:#{activity.created_at}".hash }
751
+ score: -> { "#{activity_type}:#{created_at}".hash }
803
752
  end
804
753
 
805
754
  # Create and save activity (automatic tracking)
@@ -814,12 +763,14 @@ activity.save # Automatically added to both tracking collections
814
763
  # Query recent activities (last hour)
815
764
  hour_ago = (Familia.now - 1.hour).to_i
816
765
  recent_activities = ActivityTracker.user_activities.range_by_score(
817
- hour_ago, '+inf', limit: [0, 50]
766
+ hour_ago, '+inf'
818
767
  )
819
768
 
820
769
  # Get activities by type in time range
770
+ login_hash_start = "login:#{hour_ago}".hash
771
+ login_hash_end = "login:#{Familia.now.to_i}".hash
821
772
  login_activities = ActivityTracker.activity_by_type.range_by_score(
822
- "login:#{hour_ago}".hash, "login:#{Familia.now.to_i}".hash
773
+ login_hash_start, login_hash_end
823
774
  )
824
775
  ```
825
776
 
@@ -827,7 +778,7 @@ login_activities = ActivityTracker.activity_by_type.range_by_score(
827
778
 
828
779
  ## Data Type Usage Patterns
829
780
 
830
- ### Advanced Sorted UnsortedSet Operations
781
+ ### Advanced Sorted Set Operations
831
782
  Leverage Valkey/Redis sorted sets for rankings, time series, and scored data.
832
783
 
833
784
  ```ruby
@@ -840,19 +791,20 @@ end
840
791
  leaderboard = Leaderboard.new(game_id: "game1", name: "Daily Challenge")
841
792
 
842
793
  # Add player scores
843
- leaderboard.scores.add("player1", 1500)
844
- leaderboard.scores.add("player2", 2300)
845
- leaderboard.scores.add("player3", 1800)
794
+ leaderboard.scores.add(1500, "player1")
795
+ leaderboard.scores.add(2300, "player2")
796
+ leaderboard.scores.add(1800, "player3")
846
797
 
847
- # Get top 10 players
848
- top_players = leaderboard.scores.range(0, 9, with_scores: true, order: 'DESC')
798
+ # Get top 10 players (highest scores first)
799
+ top_players = leaderboard.scores.revrange(0, 9, withscores: true)
849
800
  # => [["player2", 2300.0], ["player3", 1800.0], ["player1", 1500.0]]
850
801
 
851
- # Get player rank
852
- rank = leaderboard.scores.rank("player1", order: 'DESC') # => 2 (0-indexed)
802
+ # Get player rank (0-indexed, lower scores = lower rank)
803
+ rank = leaderboard.scores.rank("player1") # => 0
804
+ rev_rank = leaderboard.scores.revrank("player1") # => 2 (highest to lowest)
853
805
 
854
806
  # Get score range
855
- mid_tier = leaderboard.scores.range_by_score(1000, 2000, with_scores: true)
807
+ mid_tier = leaderboard.scores.rangebyscore(1000, 2000, withscores: true)
856
808
 
857
809
  # Increment score atomically
858
810
  leaderboard.scores.increment("player1", 100) # Add 100 to existing score
@@ -913,23 +865,23 @@ end
913
865
  prefs = UserPreferences.new(user_id: "user123")
914
866
 
915
867
  # UnsortedSet individual preferences
916
- prefs.settings.set("theme", "dark")
917
- prefs.settings.set("notifications", "true")
918
- prefs.settings.set("timezone", "UTC-5")
868
+ prefs.settings["theme"] = "dark"
869
+ prefs.settings["notifications"] = "true"
870
+ prefs.settings["timezone"] = "UTC-5"
919
871
 
920
872
  # Batch set multiple values
921
- prefs.feature_flags.update({
873
+ prefs.feature_flags.update(
922
874
  "beta_ui" => "true",
923
875
  "new_dashboard" => "false",
924
876
  "advanced_features" => "true"
925
- })
877
+ )
926
878
 
927
879
  # Get preferences
928
- theme = prefs.settings.get("theme") # => "dark"
929
- all_settings = prefs.settings.all # => Hash of all settings
880
+ theme = prefs.settings["theme"] # => "dark"
881
+ all_settings = prefs.settings.to_h # => Hash of all settings
930
882
 
931
883
  # Check feature flags
932
- beta_enabled = prefs.feature_flags.get("beta_ui") == "true"
884
+ beta_enabled = prefs.feature_flags["beta_ui"] == "true"
933
885
  ```
934
886
 
935
887
  ---
@@ -953,7 +905,7 @@ class ResilientService < Familia::Horreum
953
905
  sleep(0.1 * (4 - retries)) # Exponential backoff
954
906
  retry
955
907
  else
956
- Familia.warn "Redis operation failed after retries: #{e.message}"
908
+ Familia.warn "Database operation failed after retries: #{e.message}"
957
909
  nil # Return nil or handle gracefully
958
910
  end
959
911
  end
@@ -1029,12 +981,11 @@ users = []
1029
981
  users << user
1030
982
  end
1031
983
 
1032
- # Use Valkey/Redis pipelining for batch saves
1033
- User.transaction do |redis|
984
+ # Use transactions for batch saves
985
+ User.pipelined do
1034
986
  users.each do |user|
1035
- # All operations batched in transaction
1036
- user.object.set_all(user.to_hash)
1037
- User.email_index.set(user.email, user.identifier)
987
+ # All operations batched in pipeline
988
+ user.save
1038
989
  end
1039
990
  end
1040
991
  ```
@@ -1073,15 +1024,15 @@ Configure connection pools based on application needs.
1073
1024
  # High-throughput application
1074
1025
  Familia.connection_provider = lambda do |uri|
1075
1026
  ConnectionPool.new(size: 25, timeout: 5) do
1076
- Redis.new(uri, connect_timeout: 0.1, read_timeout: 1, write_timeout: 1)
1077
- end.with { |conn| yield conn if block_given?; conn }
1027
+ Redis.new(url: uri)
1028
+ end.with { |conn| conn }
1078
1029
  end
1079
1030
 
1080
1031
  # Memory-constrained environment
1081
1032
  Familia.connection_provider = lambda do |uri|
1082
1033
  ConnectionPool.new(size: 5, timeout: 10) do
1083
- Redis.new(uri, connect_timeout: 2, read_timeout: 5, write_timeout: 5)
1084
- end.with { |conn| yield conn if block_given?; conn }
1034
+ Redis.new(url: uri)
1035
+ end.with { |conn| conn }
1085
1036
  end
1086
1037
  ```
1087
1038
 
@@ -1142,16 +1093,16 @@ class User < Familia::Horreum
1142
1093
 
1143
1094
  # Temporary migration method
1144
1095
  def migrate_api_key!
1145
- if raw_api_key = object.get("api_key") # Read old plaintext
1146
- self.api_key = raw_api_key # Write as encrypted
1147
- object.delete("api_key") # Remove plaintext
1096
+ if raw_api_key = dbclient.hget(dbkey, "api_key") # Read old plaintext
1097
+ self.api_key = raw_api_key # Write as encrypted
1098
+ dbclient.hdel(dbkey, "api_key") # Remove plaintext
1148
1099
  save
1149
1100
  end
1150
1101
  end
1151
1102
  end
1152
1103
 
1153
- # Step 3: Run migration for all users
1154
- User.all.each(&:migrate_api_key!)
1104
+ # Step 3: Run migration for existing users
1105
+ User.instances.each(&:migrate_api_key!)
1155
1106
  ```
1156
1107
 
1157
1108
  ---
@@ -1167,17 +1118,17 @@ require 'familia'
1167
1118
 
1168
1119
  # Use separate Valkey/Redis database for tests
1169
1120
  Familia.configure do |config|
1170
- config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:2525/3')
1121
+ config.uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:2525/3')
1171
1122
  end
1172
1123
 
1173
1124
  module TestHelpers
1174
1125
  def setup_redis
1175
1126
  # Clear test database
1176
- Familia.connection.flushdb
1127
+ Familia.dbclient.flushdb
1177
1128
  end
1178
1129
 
1179
1130
  def teardown_redis
1180
- Familia.connection.flushdb
1131
+ Familia.dbclient.flushdb
1181
1132
  end
1182
1133
 
1183
1134
  def create_test_user(**attrs)
@@ -1208,9 +1159,11 @@ class UserTest < Minitest::Test
1208
1159
  assert user.exists?
1209
1160
  assert_equal "Alice", user.name
1210
1161
 
1211
- # Test automatic indexing
1212
- found_id = User.email_lookup.get(user.email)
1213
- assert_equal user.identifier, found_id
1162
+ # Test automatic indexing (if using unique_index)
1163
+ if User.respond_to?(:find_by_email)
1164
+ found_user = User.find_by_email(user.email)
1165
+ assert_equal user.identifier, found_user.identifier
1166
+ end
1214
1167
  end
1215
1168
 
1216
1169
  def test_relationships_with_clean_syntax
@@ -1285,23 +1238,17 @@ class UserTest < Minitest::Test
1285
1238
  end
1286
1239
 
1287
1240
  def test_external_identifier_mapping
1288
- user = ExternalUser.new(
1289
- internal_id: SecureRandom.uuid,
1290
- external_id: "ext_123456",
1291
- name: "External User"
1292
- )
1241
+ user = ExternalUser.new(name: "External User")
1293
1242
  user.save
1294
1243
 
1295
- # Test bidirectional mapping
1296
- found_by_external = ExternalUser.find_by_external_id("ext_123456")
1297
- assert_equal user.internal_id, found_by_external.internal_id
1298
-
1299
- # Test sync status tracking
1300
- user.mark_sync_pending
1301
- assert_equal "pending", user.sync_status
1244
+ # Test external ID is derived from objid
1245
+ assert_not_nil user.objid
1246
+ assert_not_nil user.extid
1247
+ assert user.extid.start_with?("ext_")
1302
1248
 
1303
- user.mark_sync_completed
1304
- assert_equal "completed", user.sync_status
1249
+ # Test deterministic generation
1250
+ user2 = ExternalUser.new(objid: user.objid, name: "External User")
1251
+ assert_equal user.extid, user2.extid
1305
1252
  end
1306
1253
 
1307
1254
  private
@@ -1330,7 +1277,7 @@ Essential configuration options for Familia v2.0.0-pre.
1330
1277
  ```ruby
1331
1278
  Familia.configure do |config|
1332
1279
  # Basic Valkey/Redis connection
1333
- config.redis_uri = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
1280
+ config.uri = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
1334
1281
 
1335
1282
  # Connection provider for pooling (optional)
1336
1283
  config.connection_provider = MyConnectionProvider