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
@@ -87,7 +87,7 @@ The quantization feature integrates seamlessly with your existing Familia models
87
87
  ```ruby
88
88
  class AnalyticsEvent < Familia::Horreum
89
89
  feature :quantization
90
- default_expiration 300 # 5 minutes (also used as default quantum)
90
+ default_expiration 1.hour # Used as default quantum
91
91
 
92
92
  identifier_field :event_id
93
93
  field :event_id, :event_type, :user_id, :data, :timestamp
@@ -95,7 +95,7 @@ end
95
95
  ```
96
96
 
97
97
  > [!TIP]
98
- > If you set `default_expiration`, it automatically becomes your default quantum for `qstamp` calls without an explicit interval.
98
+ > If you set `default_expiration`, it automatically becomes your default quantum for `qstamp` calls without an explicit interval. If no `default_expiration` is set, the fallback is 10 minutes.
99
99
 
100
100
  ### Your First Quantized Timestamps
101
101
 
@@ -104,9 +104,9 @@ The `qstamp` method is your main tool for creating consistent time buckets:
104
104
  ```ruby
105
105
  event = AnalyticsEvent.new
106
106
 
107
- # Using default quantum (from default_expiration: 300 seconds)
107
+ # Using default quantum (from default_expiration: 1 hour)
108
108
  timestamp = event.qstamp
109
- # => 1687276800 (Unix timestamp rounded to 5-minute boundary)
109
+ # => 1687276800 (Unix timestamp rounded to hour boundary)
110
110
 
111
111
  # Using custom quantum
112
112
  hourly_timestamp = event.qstamp(1.hour)
@@ -132,6 +132,10 @@ daily_key = AnalyticsEvent.qstamp(1.day, pattern: '%Y-%m-%d')
132
132
 
133
133
  weekly_key = AnalyticsEvent.qstamp(1.week, pattern: '%Y-W%U')
134
134
  # => "2023-W24" (Year-Week format)
135
+
136
+ # Array syntax for convenience
137
+ compact_key = AnalyticsEvent.qstamp([1.hour, '%Y%m%d%H'])
138
+ # => "2023061514" (same as above)
135
139
  ```
136
140
 
137
141
  ### Specifying Custom Time
@@ -654,7 +658,7 @@ RSpec.describe AnalyticsEvent do
654
658
  end
655
659
 
656
660
  it "uses default quantum from default_expiration" do
657
- allow(described_class).to receive(:default_expiration).and_return(300)
661
+ allow(described_class).to receive(:default_expiration).and_return(3600)
658
662
 
659
663
  stamp = described_class.qstamp
660
664
 
@@ -740,7 +744,7 @@ class RobustQuantization < Familia::Horreum
740
744
  rescue => e
741
745
  # Fallback to current time with default quantum
742
746
  Rails.logger.warn "Quantization failed: #{e.message}"
743
- qstamp(300) # 5-minute fallback
747
+ qstamp(10.minutes) # 10-minute fallback
744
748
  end
745
749
  end
746
750
  ```
@@ -773,6 +777,116 @@ class QuantizationMonitor
773
777
  end
774
778
  ```
775
779
 
780
+ ## API Reference
781
+
782
+ ### Class Methods
783
+
784
+ #### `qstamp(quantum = nil, pattern: nil, time: nil)`
785
+ Generate a quantized timestamp for the current or specified time.
786
+
787
+ **Parameters:**
788
+ - `quantum` (Numeric, Array, nil) - Time quantum in seconds or `[quantum, pattern]` array
789
+ - `pattern` (String, nil) - Optional strftime format pattern
790
+ - `time` (Time, nil) - Reference time (defaults to current time)
791
+
792
+ **Returns:** Integer timestamp or formatted string
793
+
794
+ **Examples:**
795
+ ```ruby
796
+ User.qstamp(1.hour) # => 1672531200
797
+ User.qstamp(1.hour, pattern: '%Y%m%d%H') # => "2023010114"
798
+ User.qstamp([1.hour, '%Y%m%d%H']) # => "2023010114" (array syntax)
799
+ ```
800
+
801
+ #### `qstamp_range(start_time, end_time, quantum, pattern: nil)`
802
+ Generate multiple quantized timestamps for a time range.
803
+
804
+ **Parameters:**
805
+ - `start_time` (Time) - Start of time range
806
+ - `end_time` (Time) - End of time range
807
+ - `quantum` (Numeric) - Time quantum in seconds
808
+ - `pattern` (String, nil) - Optional strftime format pattern
809
+
810
+ **Returns:** Array of quantized timestamps
811
+
812
+ **Example:**
813
+ ```ruby
814
+ start_time = Time.parse('2023-01-01 00:00:00')
815
+ end_time = Time.parse('2023-01-01 23:59:59')
816
+ User.qstamp_range(start_time, end_time, 1.hour)
817
+ # => [1672531200, 1672534800, 1672538400, ...] (24 hourly buckets)
818
+ ```
819
+
820
+ #### `in_bucket?(timestamp, quantum, bucket_time)`
821
+ Check if a timestamp falls within a quantized bucket.
822
+
823
+ **Parameters:**
824
+ - `timestamp` (Time, Integer) - The timestamp to check
825
+ - `quantum` (Numeric) - The quantum interval
826
+ - `bucket_time` (Time, Integer) - The bucket reference time
827
+
828
+ **Returns:** Boolean
829
+
830
+ **Example:**
831
+ ```ruby
832
+ event_time = Time.parse('2023-01-01 14:37:42')
833
+ bucket_time = Time.parse('2023-01-01 14:00:00')
834
+ User.in_bucket?(event_time, 1.hour, bucket_time) # => true
835
+ ```
836
+
837
+ ### Instance Methods
838
+
839
+ #### `qstamp(quantum = nil, pattern: nil, time: nil)`
840
+ Instance version of qstamp that can use instance-specific default expiration.
841
+
842
+ #### `quantized_identifier(quantum, pattern: nil, separator: ':')`
843
+ Generate a quantized identifier combining instance identifier with timestamp.
844
+
845
+ **Parameters:**
846
+ - `quantum` (Numeric) - Time quantum in seconds
847
+ - `pattern` (String, nil) - Optional strftime format pattern
848
+ - `separator` (String) - Separator between identifier and timestamp
849
+
850
+ **Returns:** String identifier with quantized timestamp
851
+
852
+ **Example:**
853
+ ```ruby
854
+ user = User.new(id: 123)
855
+ user.quantized_identifier(1.hour) # => "123:1672531200"
856
+ user.quantized_identifier(1.hour, pattern: '%Y%m%d%H') # => "123:2023010114"
857
+ ```
858
+
859
+ ### Error Handling
860
+
861
+ The feature validates quantum values:
862
+
863
+ ```ruby
864
+ User.qstamp(0) # => ArgumentError: Quantum must be positive
865
+ User.qstamp(-5) # => ArgumentError: Quantum must be positive
866
+ User.qstamp("invalid") # => ArgumentError: Quantum must be positive
867
+ ```
868
+
869
+ ### Default Quantum Behavior
870
+
871
+ When no quantum is specified:
872
+ 1. Uses class `default_expiration` if available
873
+ 2. Falls back to `10.minutes` if no default expiration set
874
+
875
+ ```ruby
876
+ class TimedModel < Familia::Horreum
877
+ feature :quantization
878
+ default_expiration 1.hour
879
+ end
880
+
881
+ TimedModel.qstamp() # Uses 1.hour as quantum
882
+
883
+ class BasicModel < Familia::Horreum
884
+ feature :quantization
885
+ end
886
+
887
+ BasicModel.qstamp() # Uses 10.minutes fallback (600 seconds)
888
+ ```
889
+
776
890
  ---
777
891
 
778
892
  ## See Also
@@ -0,0 +1,318 @@
1
+ # Relationships Indexing Guide
2
+
3
+ Indexing provides O(1) field-to-object lookups using Redis data structures, enabling fast attribute-based queries without relationship semantics.
4
+
5
+ ## Core Concepts
6
+
7
+ Indexing creates fast lookups for finding objects by field values:
8
+ - **O(1) performance** - Hash/Set-based constant-time access
9
+ - **Automatic management** - Class indexes update on save/destroy
10
+ - **Flexible scoping** - Global or parent-scoped uniqueness
11
+ - **Query generation** - Automatic `find_by_*` methods
12
+
13
+ ## Index Types
14
+
15
+ | Type | Scope | Use Case | Structure |
16
+ |------|-------|----------|-----------|
17
+ | `unique_index` | Class | Global unique fields | Redis HashKey |
18
+ | `unique_index` | Instance | Parent-scoped unique | Redis HashKey |
19
+ | `multi_index` | Instance | Non-unique groupings | Redis Set |
20
+
21
+ ## Class-Level Unique Indexing
22
+
23
+ Global unique field lookups with automatic management:
24
+
25
+ ```ruby
26
+ class User < Familia::Horreum
27
+ feature :relationships
28
+ field :email, :username
29
+
30
+ unique_index :email, :email_lookup
31
+ unique_index :username, :username_lookup
32
+ end
33
+
34
+ # Automatic indexing on save
35
+ user = User.create(email: 'alice@example.com')
36
+ User.find_by_email('alice@example.com') # => user (O(1) lookup)
37
+
38
+ # Automatic update on field change
39
+ user.update(email: 'alice.smith@example.com')
40
+ User.find_by_email('alice.smith@example.com') # => user
41
+
42
+ # Automatic cleanup on destroy
43
+ user.destroy
44
+ User.find_by_email('alice.smith@example.com') # => nil
45
+ ```
46
+
47
+ ### Generated Methods
48
+
49
+ | Method | Description |
50
+ |--------|-------------|
51
+ | `User.find_by_email(email)` | O(1) lookup |
52
+ | `User.index_email_for(user)` | Manual index |
53
+ | `User.unindex_email_for(user)` | Remove from index |
54
+ | `User.reindex_email_for(user)` | Update index |
55
+
56
+ ## Instance-Scoped Unique Indexing
57
+
58
+ Unique within parent context, allowing duplicates across parents:
59
+
60
+ ```ruby
61
+ class Employee < Familia::Horreum
62
+ feature :relationships
63
+ field :badge_number
64
+
65
+ unique_index :badge_number, :badge_index, within: Company
66
+ end
67
+
68
+ # Manual indexing required (needs parent context)
69
+ company1 = Company.create(name: 'Acme Corp')
70
+ company2 = Company.create(name: 'Beta Inc')
71
+
72
+ emp1 = Employee.create(badge_number: '12345')
73
+ emp1.add_to_company_badge_index(company1)
74
+
75
+ emp2 = Employee.create(badge_number: '12345') # Same badge OK
76
+ emp2.add_to_company_badge_index(company2)
77
+
78
+ # Scoped lookups
79
+ company1.find_by_badge_number('12345') # => emp1
80
+ company2.find_by_badge_number('12345') # => emp2
81
+ ```
82
+
83
+ ### Generated Methods
84
+
85
+ **On scope class (Company):**
86
+ | Method | Description |
87
+ |--------|-------------|
88
+ | `find_by_badge_number(badge)` | Find within scope |
89
+ | `index_badge_number_for(emp)` | Add to index |
90
+ | `unindex_badge_number_for(emp)` | Remove from index |
91
+
92
+ **On indexed class (Employee):**
93
+ | Method | Description |
94
+ |--------|-------------|
95
+ | `add_to_company_badge_index(company)` | Add to company's index |
96
+ | `remove_from_company_badge_index(company)` | Remove from index |
97
+ | `in_company_badge_index?(company)` | Check if indexed |
98
+
99
+ ## Multi-Value Indexing
100
+
101
+ One-to-many mappings for non-unique field values:
102
+
103
+ ```ruby
104
+ class Employee < Familia::Horreum
105
+ feature :relationships
106
+ field :department
107
+
108
+ multi_index :department, :dept_index, within: Company
109
+ end
110
+
111
+ company = Company.create(name: 'TechCorp')
112
+
113
+ # Multiple employees in same department
114
+ [
115
+ Employee.create(department: 'engineering'),
116
+ Employee.create(department: 'engineering'),
117
+ Employee.create(department: 'sales')
118
+ ].each { |emp| emp.add_to_company_dept_index(company) }
119
+
120
+ # Query all in department
121
+ engineers = company.find_all_by_department('engineering') # => [emp1, emp2]
122
+ sales_team = company.find_all_by_department('sales') # => [emp3]
123
+
124
+ # Random sampling
125
+ sample = company.sample_from_department('engineering', 1) # => [random engineer]
126
+ ```
127
+
128
+ ### Generated Methods
129
+
130
+ **On scope class:**
131
+ | Method | Description |
132
+ |--------|-------------|
133
+ | `find_all_by_department(dept)` | Find all in department |
134
+ | `sample_from_department(dept, count)` | Random sample |
135
+
136
+ ## Advanced Patterns
137
+
138
+ ### Composite Keys
139
+
140
+ ```ruby
141
+ class ApiKey < Familia::Horreum
142
+ field :environment, :key_type
143
+
144
+ unique_index :environment_and_type, :env_type_index, within: Customer
145
+
146
+ private
147
+ def environment_and_type
148
+ "#{environment}:#{key_type}" # e.g., "production:read_write"
149
+ end
150
+ end
151
+
152
+ customer.find_by_environment_and_type("production:read_only")
153
+ ```
154
+
155
+ ### Conditional Indexing
156
+
157
+ ```ruby
158
+ class Document < Familia::Horreum
159
+ field :status, :slug
160
+
161
+ unique_index :slug, :slug_index, within: Project
162
+
163
+ def add_to_project_slug_index(project)
164
+ return unless status == 'published' # Only index published
165
+ super
166
+ end
167
+ end
168
+ ```
169
+
170
+ ### Time Partitioning
171
+
172
+ ```ruby
173
+ class Event < Familia::Horreum
174
+ field :timestamp
175
+
176
+ multi_index :daily_partition, :daily_events, within: User
177
+
178
+ private
179
+ def daily_partition
180
+ Time.at(timestamp).strftime('%Y%m%d') # e.g., "20241215"
181
+ end
182
+ end
183
+
184
+ today = Time.now.strftime('%Y%m%d')
185
+ todays_events = user.find_all_by_daily_partition(today)
186
+ ```
187
+
188
+ ## Key Differences
189
+
190
+ ### Class vs Instance Scoping
191
+
192
+ **Class-level (`unique_index :email, :email_lookup`):**
193
+ - Automatic indexing on save/destroy
194
+ - System-wide uniqueness
195
+ - No parent context needed
196
+ - Examples: emails, usernames, API keys
197
+
198
+ **Instance-scoped (`unique_index :badge, :badge_index, within: Company`):**
199
+ - Manual indexing required
200
+ - Unique within parent only
201
+ - Requires parent context
202
+ - Examples: employee IDs, project names per team
203
+
204
+ ### Unique vs Multi Indexing
205
+
206
+ **Unique index (`unique_index`):**
207
+ - 1:1 field-to-object mapping
208
+ - Returns single object or nil
209
+ - Enforces uniqueness within scope
210
+
211
+ **Multi index (`multi_index`):**
212
+ - 1:many field-to-objects mapping
213
+ - Returns array of objects
214
+ - Allows duplicate values
215
+
216
+ ## Rebuilding Indexes
217
+
218
+ Indexes can be automatically rebuilt from source data using auto-generated rebuild methods:
219
+
220
+ ```ruby
221
+ # Class-level indexes
222
+ User.rebuild_email_lookup # Rebuilds from all User.email values
223
+ User.rebuild_username_lookup # Rebuilds from all User.username values
224
+
225
+ # Instance-scoped indexes
226
+ company.rebuild_badge_index # Rebuilds from all Employee.badge_number values
227
+ ```
228
+
229
+ These methods work because **indexes are derived data** - they're computed from object field values.
230
+
231
+ > **Important:** Participation data (like `@team.members`) cannot be rebuilt automatically because participations represent business decisions, not derived data. See [Why Participations Can't Be Rebuilt](../../lib/familia/features/relationships/participation/rebuild_strategies.md) for the critical distinction between indexes and participations.
232
+
233
+ **When to rebuild indexes:**
234
+ - After data migrations or bulk imports
235
+ - Recovering from index corruption
236
+ - Adding indexes to existing data
237
+
238
+ ## Performance Tips
239
+
240
+ ### Bulk Operations
241
+
242
+ ```ruby
243
+ # Efficient bulk indexing
244
+ employees.each_slice(100) do |batch|
245
+ company.transaction do
246
+ batch.each { |emp| emp.add_to_company_dept_index(company) }
247
+ end
248
+ end
249
+ ```
250
+
251
+ ### Index Monitoring
252
+
253
+ ```ruby
254
+ # Check index sizes
255
+ company.dept_index_engineering.size # Count in engineering
256
+ User.email_lookup.size # Total indexed emails
257
+
258
+ # Index distribution
259
+ %w[engineering sales marketing].map { |dept|
260
+ [dept, company.send("dept_index_#{dept}").size]
261
+ }.to_h
262
+ ```
263
+
264
+ ### Cleanup
265
+
266
+ ```ruby
267
+ # Remove orphaned entries
268
+ company.badge_index.to_h.each do |badge, emp_id|
269
+ unless Employee.exists?(emp_id)
270
+ company.badge_index.delete(badge)
271
+ end
272
+ end
273
+ ```
274
+
275
+ ## Redis Key Patterns
276
+
277
+ | Type | Pattern | Example |
278
+ |------|---------|---------|
279
+ | Class unique | `{class}:{index_name}` | `user:email_lookup` |
280
+ | Instance unique | `{scope}:{id}:{index_name}` | `company:123:badge_index` |
281
+ | Multi-value | `{scope}:{id}:{index_name}:{value}` | `company:123:dept_index:engineering` |
282
+
283
+ ## Troubleshooting
284
+
285
+ ### Common Issues
286
+
287
+ **Query methods not generated:**
288
+ - Check `query: true` (default) or explicitly set
289
+ - Verify `feature :relationships` declared
290
+
291
+ **Index not updating:**
292
+ - Class indexes: automatic on save/destroy
293
+ - Instance indexes: require manual `add_to_*` calls
294
+
295
+ **Duplicate key errors:**
296
+ - Use `multi_index` for non-unique values
297
+ - Consider instance-scoped for contextual uniqueness
298
+
299
+ ### Debugging
300
+
301
+ ```ruby
302
+ # Check configuration
303
+ User.indexing_relationships
304
+ # => [{ field: :email, index_name: :email_lookup, ... }]
305
+
306
+ # Inspect index contents
307
+ User.email_lookup.to_h
308
+ # => {"alice@example.com" => "user_123", ...}
309
+
310
+ # Verify membership
311
+ employee.in_company_badge_index?(company) # => true/false
312
+ ```
313
+
314
+ ## See Also
315
+
316
+ - [**Relationships Overview**](feature-relationships.md) - Core concepts
317
+ - [**Methods Reference**](feature-relationships-methods.md) - Complete API
318
+ - [**Participation Guide**](feature-relationships-participation.md) - Associations