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
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/external_identifier.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -12,8 +14,8 @@ module Familia
12
14
  base.extend ModelClassMethods
13
15
  base.include ModelInstanceMethods
14
16
 
15
- # Ensure default prefix is set in feature options
16
- base.add_feature_options(:external_identifier, prefix: 'ext')
17
+ # Ensure default format is set in feature options
18
+ base.add_feature_options(:external_identifier, format: 'ext_%{id}')
17
19
 
18
20
  # Add class-level mapping for extid -> id lookups
19
21
  base.class_hashkey :extid_lookup
@@ -35,10 +37,10 @@ module Familia
35
37
  # - Deterministic generation from objid ensures consistency
36
38
  # - Shorter than objid (128-bit vs 256-bit) for external use
37
39
  # - Base-36 encoding for URL-safe identifiers
38
- # - 'ext_' prefix for clear identification as external IDs
40
+ # - Customizable format template (default: 'ext_' prefix)
39
41
  # - Lazy generation preserves values from initialization
40
42
  #
41
- # @example Using external identifier fields
43
+ # @example Using external identifier fields with default format
42
44
  # class User < Familia::Horreum
43
45
  # feature :object_identifier
44
46
  # feature :external_identifier
@@ -51,6 +53,30 @@ module Familia
51
53
  # user2 = User.new(objid: user.objid, email: 'user@example.com')
52
54
  # user2.extid # => "ext_abc123def456ghi789" (identical to user.extid)
53
55
  #
56
+ # @example Using custom format template with hyphen separator
57
+ # class APIKey < Familia::Horreum
58
+ # feature :object_identifier
59
+ # feature :external_identifier, format: 'api-%{id}'
60
+ # end
61
+ # key = APIKey.new
62
+ # key.extid # => "api-abc123def456ghi789"
63
+ #
64
+ # @example Using custom format template with custom prefix
65
+ # class Customer < Familia::Horreum
66
+ # feature :object_identifier
67
+ # feature :external_identifier, format: 'cust_%{id}'
68
+ # end
69
+ # customer = Customer.new
70
+ # customer.extid # => "cust_abc123def456ghi789"
71
+ #
72
+ # @example Using format template without prefix
73
+ # class Resource < Familia::Horreum
74
+ # feature :object_identifier
75
+ # feature :external_identifier, format: 'v2/%{id}'
76
+ # end
77
+ # resource = Resource.new
78
+ # resource.extid # => "v2/abc123def456ghi789"
79
+ #
54
80
  class ExternalIdentifierFieldType < Familia::FieldType
55
81
  # Override getter to provide lazy generation from objid
56
82
  #
@@ -150,6 +176,35 @@ module Familia
150
176
  # extid_lookup.remove_field(extid)
151
177
  nil
152
178
  end
179
+
180
+ # Check if a string matches the extid format for the Horreum class. The specific
181
+ # class is important b/c each one can have its own custom prefix, like `ext_`.
182
+ #
183
+ # The validator accepts a reasonable range of ID lengths (20-32 characters) to
184
+ # accommodate potential changes to the entropy or encoding while maintaining
185
+ # security. The current implementation generates exactly 25 base36 characters
186
+ # from 16 bytes (128 bits), but this flexibility allows for future adjustments
187
+ # without breaking validation.
188
+ #
189
+ # @param guess [String] The string to check
190
+ # @return [Boolean] true if the guess matches the extid format, false otherwise
191
+ def extid?(guess)
192
+ return false if guess.to_s.empty?
193
+
194
+ options = feature_options(:external_identifier)
195
+ format = options[:format] || 'ext_%{id}'
196
+
197
+ # Extract prefix and suffix from format
198
+ return false unless format.include?('%{id}')
199
+ prefix, suffix = format.split('%{id}', 2)
200
+
201
+ # Build regex pattern to match the extid format
202
+ # Accept 20-32 base36 characters to allow for entropy/encoding variations
203
+ # Current generation: 16 bytes -> base36 -> 25 chars (rjust with '0')
204
+ pattern = /\A#{Regexp.escape(prefix)}[0-9a-z]{20,32}#{Regexp.escape(suffix)}\z/i
205
+
206
+ !!(guess =~ pattern)
207
+ end
153
208
  end
154
209
 
155
210
  # Instance methods for external identifier management
@@ -252,11 +307,11 @@ module Familia
252
307
  # 128 bits is approximately 25 characters in base36.
253
308
  external_part = random_bytes.unpack1('H*').to_i(16).to_s(36).rjust(25, '0')
254
309
 
255
- # Get prefix from feature options, default to "ext"
310
+ # Get format from feature options and interpolate the ID
256
311
  options = self.class.feature_options(:external_identifier)
257
- prefix = options[:prefix] || 'ext'
312
+ format = options[:format] || 'ext_%{id}'
258
313
 
259
- "#{prefix}_#{external_part}"
314
+ format % { id: external_part }
260
315
  end
261
316
 
262
317
  # Full-length alias for extid for clarity when needed
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/object_identifier.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -277,6 +279,53 @@ module Familia
277
279
  # objid_lookup.remove_field(objid)
278
280
  nil
279
281
  end
282
+
283
+ # Check if a string matches the objid format for the Horreum class. The specific
284
+ # class is important b/c each one can have its own type of objid generator.
285
+ #
286
+ # @param guess [String] The string to check
287
+ # @return [Boolean] true if the guess matches the objid format, false otherwise
288
+ def objid?(guess)
289
+ return false if guess.to_s.empty?
290
+
291
+ options = feature_options(:object_identifier)
292
+ generator = options[:generator] || DEFAULT_GENERATOR
293
+
294
+ case generator
295
+ when :uuid_v7, :uuid_v4
296
+ # UUID format: xxxxxxxx-xxxx-Vxxx-xxxx-xxxxxxxxxxxx (36 chars with hyphens)
297
+ # Validate structure and that all characters are valid hex digits
298
+ guess_str = guess.to_s
299
+ return false unless guess_str.length == 36
300
+ return false unless guess_str[8] == '-' && guess_str[13] == '-' && guess_str[18] == '-' && guess_str[23] == '-'
301
+
302
+ # Extract segments and validate each is valid hex
303
+ segments = guess_str.split('-')
304
+ return false unless segments.length == 5
305
+ return false unless segments[0] =~ /\A[0-9a-fA-F]{8}\z/ # 8 hex chars
306
+ return false unless segments[1] =~ /\A[0-9a-fA-F]{4}\z/ # 4 hex chars
307
+ return false unless segments[2] =~ /\A[0-9a-fA-F]{4}\z/ # 4 hex chars (includes version)
308
+ return false unless segments[3] =~ /\A[0-9a-fA-F]{4}\z/ # 4 hex chars
309
+ return false unless segments[4] =~ /\A[0-9a-fA-F]{12}\z/ # 12 hex chars
310
+
311
+ # Validate version character
312
+ version_char = guess_str[14]
313
+ if generator == :uuid_v7
314
+ version_char == '7'
315
+ else # generator == :uuid_v4
316
+ version_char == '4'
317
+ end
318
+ when :hex
319
+ # Hex format: pure hexadecimal without hyphens
320
+ !!(guess =~ /\A[0-9a-fA-F]+\z/)
321
+ when Proc
322
+ # Cannot determine format for custom Proc generators
323
+ Familia.warn "[objid?] Validation not supported for custom Proc generators on #{name}" if Familia.debug?
324
+ false
325
+ else
326
+ false
327
+ end
328
+ end
280
329
  end
281
330
 
282
331
  # Instance methods for object identifier management
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/quantization.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -389,7 +391,7 @@ module Familia
389
391
  # user.quantized_identifier(1.hour) # => "123:1672531200"
390
392
  # user.quantized_identifier(1.hour, pattern: '%Y%m%d%H') # => "123:2023010114"
391
393
  #
392
- def quantized_identifier(quantum, pattern: nil, separator: ':')
394
+ def quantized_identifier(quantum, pattern: nil, separator: Familia.delim)
393
395
  timestamp = qstamp(quantum, pattern: pattern)
394
396
  base_id = respond_to?(:identifier) ? identifier : object_id
395
397
  "#{base_id}#{separator}#{timestamp}"
@@ -18,6 +18,7 @@ participates_in Organization, :members, score: :joined_at, bidirectional: true
18
18
 
19
19
  **unique_index** - Fast unique lookups ("find object by unique field value")
20
20
  ```ruby
21
+ unique_index :email, :email_index # Class-level: User.find_by_email()
21
22
  unique_index :email, :email_index, within: Organization # Scoped: org.find_by_email()
22
23
  ```
23
24
 
@@ -89,7 +90,8 @@ class Customer < Familia::Horreum
89
90
  feature :relationships
90
91
 
91
92
  participates_in Organization, :members # Customer belongs to org
92
- unique_index :email, :email_index, within: Organization # Find by unique email
93
+ unique_index :email, :email_index # Class-level: Customer.find_by_email()
94
+ multi_index :status, :status_index, within: Organization # Scoped: org.find_all_by_status()
93
95
  end
94
96
  ```
95
97
 
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/relationships/collection_operations.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Features
@@ -1,3 +1,5 @@
1
+ # lib/familia/features/relationships/indexing/multi_index_generators.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
5
  module Familia
@@ -78,7 +80,7 @@ module Familia
78
80
  # This acts as a factory for field-value-specific DataTypes
79
81
  define_method(:"#{index_name}_for") do |field_value|
80
82
  # Return properly managed DataType instance with parameterized key
81
- index_key = "#{index_name}:#{field_value}"
83
+ index_key = Familia.join(index_name, field_value)
82
84
  Familia::UnsortedSet.new(index_key, parent: self)
83
85
  end
84
86
  end
@@ -97,6 +99,9 @@ module Familia
97
99
  # Resolve scope class using Familia pattern
98
100
  actual_scope_class = Familia.resolve_class(scope_class)
99
101
 
102
+ # Get scope_class_config for method naming (needed for rebuild methods)
103
+ scope_class_config = actual_scope_class.config_name
104
+
100
105
  # Generate instance sampling method (e.g., company.sample_from_department)
101
106
  actual_scope_class.class_eval do
102
107
 
@@ -118,11 +123,135 @@ module Familia
118
123
  index_set.members.map { |id| indexed_class.find_by_identifier(id) }
119
124
  end
120
125
 
121
- # Generate method to rebuild the index for this parent instance
122
- define_method(:"rebuild_#{index_name}") do
123
- # This would need to be implemented based on how you track which
124
- # objects belong to this parent instance
125
- # For now, just a placeholder
126
+ # Generate method to rebuild the multi-value index for this parent instance
127
+ #
128
+ # Multi-indexes create separate sets for each field value, requiring a three-phase approach:
129
+ # 1. Loading: Load all objects once and cache them (discovers field values simultaneously)
130
+ # 2. Clearing: Remove all existing index sets using SCAN
131
+ # 3. Rebuilding: Rebuild index from cached objects (no reload needed)
132
+ #
133
+ # @param batch_size [Integer] Number of identifiers to process per batch
134
+ # @yield [progress] Optional block called with progress updates
135
+ # @yieldparam progress [Hash] Progress information with keys:
136
+ # - :phase [Symbol] Current phase (:loading, :clearing, :rebuilding)
137
+ # - :current [Integer] Current item count
138
+ # - :total [Integer] Total items (when known)
139
+ # - :field_value [String] Current field value being processed
140
+ #
141
+ # @example Basic rebuild
142
+ # company.rebuild_dept_index
143
+ #
144
+ # @example With progress monitoring
145
+ # company.rebuild_dept_index do |progress|
146
+ # puts "#{progress[:phase]}: #{progress[:current]}/#{progress[:total]}"
147
+ # end
148
+ #
149
+ # @example Memory-conscious rebuild for large collections
150
+ # # Process in smaller batches to reduce memory footprint
151
+ # company.rebuild_dept_index(batch_size: 50)
152
+ #
153
+ # @note Memory Considerations:
154
+ # This method caches all objects in memory during rebuild to avoid duplicate
155
+ # database loads. For very large collections (>100k objects), monitor memory usage
156
+ # and consider processing in chunks or using a streaming approach if memory
157
+ # constraints are encountered. The batch_size parameter controls Redis I/O
158
+ # batching but does not affect memory usage since all objects are cached.
159
+ #
160
+ define_method(:"rebuild_#{index_name}") do |batch_size: 100, &progress_block|
161
+ # PHASE 1: Find the collection containing the indexed objects
162
+ # Look for a participation relationship where indexed_class participates in this scope_class
163
+ collection_name = nil
164
+
165
+ # Check if indexed_class has participation to this scope_class
166
+ if indexed_class.respond_to?(:participation_relationships)
167
+ participation = indexed_class.participation_relationships.find do |rel|
168
+ rel.target_class == self.class
169
+ end
170
+ collection_name = participation&.collection_name if participation
171
+ end
172
+
173
+ # Get the collection DataType if we found a participation relationship
174
+ collection = collection_name ? send(collection_name) : nil
175
+
176
+ if collection
177
+ # PHASE 2: Load objects once and cache them for both discovery and rebuilding
178
+ # This avoids duplicate load_multi calls (previous approach loaded twice)
179
+ progress_block&.call(phase: :loading, current: 0, total: collection.size)
180
+
181
+ field_values = Set.new
182
+ cached_objects = []
183
+ processed = 0
184
+
185
+ collection.members.each_slice(batch_size) do |identifiers|
186
+ # Load objects in batches - SINGLE LOAD for both phases
187
+ objects = indexed_class.load_multi(identifiers).compact
188
+ cached_objects.concat(objects)
189
+
190
+ objects.each do |obj|
191
+ value = obj.send(field)
192
+ # Only track non-nil, non-empty field values
193
+ field_values << value.to_s if value && !value.to_s.strip.empty?
194
+ end
195
+
196
+ processed += identifiers.size
197
+ progress_block&.call(phase: :loading, current: processed, total: collection.size)
198
+ end
199
+
200
+ # PHASE 3: Clear all existing field-value-specific index sets
201
+ # Use SCAN to find all existing index keys (including orphaned ones from deleted field values)
202
+ progress_block&.call(phase: :clearing, current: 0, total: field_values.size)
203
+
204
+ # Get the base pattern for this index by creating a sample index set
205
+ # The "*" creates a wildcard pattern like "company:123:dept_index:*" for SCAN
206
+ sample_index = send(:"#{index_name}_for", "*")
207
+ index_pattern = sample_index.dbkey
208
+
209
+ # Find all existing index keys using SCAN
210
+ cleared_count = 0
211
+ dbclient.scan_each(match: index_pattern) do |key|
212
+ dbclient.del(key)
213
+ cleared_count += 1
214
+ progress_block&.call(phase: :clearing, current: cleared_count, total: field_values.size, key: key)
215
+ end
216
+
217
+ # PHASE 4: Rebuild index from cached objects (no reload needed)
218
+ progress_block&.call(phase: :rebuilding, current: 0, total: cached_objects.size)
219
+
220
+ processed = 0
221
+ cached_objects.each_slice(batch_size) do |objects|
222
+ transaction do |_tx|
223
+ objects.each do |obj|
224
+ # Use the generated add_to method to maintain consistency
225
+ # This ensures the same logic is used as during normal operation
226
+ obj.send(:"add_to_#{scope_class_config}_#{index_name}", self)
227
+ end
228
+ end
229
+
230
+ processed += objects.size
231
+ progress_block&.call(phase: :rebuilding, current: processed, total: cached_objects.size)
232
+ end
233
+
234
+ Familia.info "[Rebuild] Multi-index #{index_name} rebuilt: #{field_values.size} field values, #{processed} objects"
235
+
236
+ processed # Return count of processed objects
237
+
238
+ else
239
+ # No participation relationship found - warn and suggest alternative
240
+ Familia.warn <<~WARNING
241
+ [Rebuild] Cannot rebuild multi-index #{index_name}: no participation relationship found
242
+
243
+ Multi-index rebuild requires a participation relationship to find objects.
244
+ Add a participation relationship to #{indexed_class.name}:
245
+
246
+ class #{indexed_class.name} < Familia::Horreum
247
+ participates_in #{self.class.name}, :collection_name, score: :field
248
+ end
249
+
250
+ Then access the collection via: #{self.class.config_name}.collection_name
251
+ WARNING
252
+
253
+ nil
254
+ end
126
255
  end
127
256
  end
128
257
  end
@@ -140,7 +269,7 @@ module Familia
140
269
  scope_class_config = scope_class.config_name
141
270
  indexed_class.class_eval do
142
271
  method_name = :"add_to_#{scope_class_config}_#{index_name}"
143
- Familia.ld("[MultiIndexGenerators] #{name} method #{method_name}")
272
+ Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
144
273
 
145
274
  define_method(method_name) do |scope_instance|
146
275
  return unless scope_instance
@@ -156,7 +285,7 @@ module Familia
156
285
  end
157
286
 
158
287
  method_name = :"remove_from_#{scope_class_config}_#{index_name}"
159
- Familia.ld("[MultiIndexGenerators] #{name} method #{method_name}")
288
+ Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
160
289
 
161
290
  define_method(method_name) do |scope_instance|
162
291
  return unless scope_instance
@@ -172,7 +301,7 @@ module Familia
172
301
  end
173
302
 
174
303
  method_name = :"update_in_#{scope_class_config}_#{index_name}"
175
- Familia.ld("[MultiIndexGenerators] #{name} method #{method_name}")
304
+ Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
176
305
 
177
306
  define_method(method_name) do |scope_instance, old_field_value = nil|
178
307
  return unless scope_instance