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.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/CHANGELOG.rst +177 -112
- data/CLAUDE.md +28 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -17
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +161 -117
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +4 -3
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +2 -0
- data/lib/familia/connection/handlers.rb +2 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +2 -0
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +2 -0
- data/lib/familia/connection/transaction_core.rb +68 -0
- data/lib/familia/connection.rb +18 -3
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +2 -0
- data/lib/familia/data_type/database_commands.rb +2 -0
- data/lib/familia/data_type/serialization.rb +6 -4
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +7 -5
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +2 -0
- data/lib/familia/data_type/types/stringkey.rb +2 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +2 -0
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +3 -1
- data/lib/familia/features/expiration.rb +12 -4
- data/lib/familia/features/external_identifier.rb +33 -7
- data/lib/familia/features/object_identifier.rb +2 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
- data/lib/familia/features/relationships/indexing.rb +3 -0
- data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +3 -1
- data/lib/familia/horreum/connection.rb +17 -1
- data/lib/familia/horreum/database_commands.rb +2 -0
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +212 -42
- data/lib/familia/horreum/persistence.rb +176 -108
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +23 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -0
- data/lib/familia/horreum.rb +15 -1
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +92 -32
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +2 -0
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +295 -170
- data/lib/multi_result.rb +2 -0
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +4 -0
- data/try/features/external_identifier/external_identifier_try.rb +171 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +2 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +600 -0
- data/try/features/relationships/indexing_try.rb +2 -0
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +2 -0
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +4 -0
- data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +4 -0
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
- data/try/integration/data_types/datatype_transactions_try.rb +4 -0
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +4 -0
- data/try/integration/models/customer_try.rb +4 -0
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +4 -0
- data/try/integration/persistence_operations_try.rb +4 -0
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +4 -0
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +4 -0
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +4 -0
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
- data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
- data/try/unit/horreum/base_try.rb +4 -0
- data/try/unit/horreum/class_methods_try.rb +4 -0
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +4 -0
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +4 -0
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +4 -0
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +72 -17
- data/.github/workflows/code-quality.yml +0 -138
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
- data/changelog.d/20251011_203905_delano_next.rst +0 -30
- data/changelog.d/20251011_212633_delano_next.rst +0 -13
- data/changelog.d/20251011_221253_delano_next.rst +0 -26
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- data/docs/guides/security-model.md +0 -183
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/encryption/encrypted_data.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Encryption
|
|
@@ -15,10 +17,10 @@ module Familia
|
|
|
15
17
|
# Check for required fields
|
|
16
18
|
required_fields = %i[algorithm nonce ciphertext auth_tag key_version]
|
|
17
19
|
result = required_fields.all? { |field| parsed.key?(field) }
|
|
18
|
-
Familia.
|
|
20
|
+
Familia.debug "[valid?] result: #{result}, parsed: #{parsed}, required: #{required_fields}"
|
|
19
21
|
result
|
|
20
22
|
rescue Familia::SerializerError => e
|
|
21
|
-
Familia.
|
|
23
|
+
Familia.debug "[valid?] JSON error: #{e.message}"
|
|
22
24
|
false
|
|
23
25
|
end
|
|
24
26
|
end
|
data/lib/familia/encryption.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/encryption.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'base64'
|
|
4
6
|
require 'oj'
|
|
@@ -60,9 +62,14 @@ module Familia
|
|
|
60
62
|
# end
|
|
61
63
|
class << self
|
|
62
64
|
# Get or create a manager with specific algorithm
|
|
65
|
+
#
|
|
66
|
+
# Thread-safe lazy initialization using Concurrent::Map to ensure
|
|
67
|
+
# only a single Manager instance is created per algorithm even under
|
|
68
|
+
# concurrent encryption/decryption requests.
|
|
69
|
+
#
|
|
63
70
|
def manager(algorithm: nil)
|
|
64
|
-
@managers ||=
|
|
65
|
-
@managers
|
|
71
|
+
@managers ||= Concurrent::Map.new
|
|
72
|
+
@managers.fetch_or_store(algorithm) { Manager.new(algorithm: algorithm) }
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
# Quick encryption with auto-selected best provider
|
data/lib/familia/errors.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/encrypted_fields.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative 'encrypted_fields/encrypted_field_type'
|
|
4
6
|
|
|
@@ -341,8 +343,6 @@ module Familia
|
|
|
341
343
|
# Check if this instance has any encrypted fields with values
|
|
342
344
|
#
|
|
343
345
|
# @return [Boolean] true if any encrypted fields have values
|
|
344
|
-
#
|
|
345
|
-
# TODO: Missing test coverage
|
|
346
346
|
def encrypted_data?
|
|
347
347
|
self.class.encrypted_fields.any? do |field_name|
|
|
348
348
|
field_value = instance_variable_get("@#{field_name}")
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/expiration/extensions.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
# Add a default update_expiration method for all classes that include
|
|
@@ -24,7 +26,7 @@ module Familia
|
|
|
24
26
|
# @example MyModel.new.update_expiration(expiration: 3600) # => nothing happens
|
|
25
27
|
#
|
|
26
28
|
def update_expiration(expiration: nil)
|
|
27
|
-
Familia.
|
|
29
|
+
Familia.debug <<~LOG
|
|
28
30
|
[update_expiration] Expiration feature not enabled for #{self.class}.
|
|
29
31
|
Key: #{dbkey} Arg: #{expiration} (caller: #{caller(1..1)})
|
|
30
32
|
LOG
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/expiration.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative 'expiration/extensions'
|
|
4
6
|
|
|
@@ -252,13 +254,13 @@ module Familia
|
|
|
252
254
|
|
|
253
255
|
# Handle cascading expiration to related data structures
|
|
254
256
|
if self.class.relations?
|
|
255
|
-
Familia.
|
|
257
|
+
Familia.debug "[update_expiration] #{self.class} has relations: #{self.class.related_fields.keys}"
|
|
256
258
|
self.class.related_fields.each do |name, definition|
|
|
257
259
|
# Skip relations that don't have their own expiration settings
|
|
258
260
|
next if definition.opts[:default_expiration].nil?
|
|
259
261
|
|
|
260
262
|
obj = send(name)
|
|
261
|
-
Familia.
|
|
263
|
+
Familia.debug "[update_expiration] Updating expiration for #{name} (#{obj.dbkey}) to #{expiration}"
|
|
262
264
|
obj.update_expiration(expiration: expiration)
|
|
263
265
|
end
|
|
264
266
|
end
|
|
@@ -280,11 +282,17 @@ module Familia
|
|
|
280
282
|
# If zero, simply skip setting an expiry for this key. If we were to set
|
|
281
283
|
# 0, Valkey/Redis would drop the key immediately.
|
|
282
284
|
if expiration.zero?
|
|
283
|
-
Familia.
|
|
285
|
+
Familia.debug "[update_expiration] No expiration for #{self.class} (#{dbkey})"
|
|
284
286
|
return true
|
|
285
287
|
end
|
|
286
288
|
|
|
287
|
-
|
|
289
|
+
# Structured TTL operation logging
|
|
290
|
+
Familia.debug "TTL updated",
|
|
291
|
+
operation: :expire,
|
|
292
|
+
key: dbkey,
|
|
293
|
+
ttl_seconds: expiration,
|
|
294
|
+
class: self.class.name,
|
|
295
|
+
identifier: (identifier rescue nil)
|
|
288
296
|
|
|
289
297
|
# The Valkey/Redis' EXPIRE command returns 1 if the timeout was set, 0
|
|
290
298
|
# if key does not exist or the timeout could not be set. Via redis-rb,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/external_identifier.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Features
|
|
@@ -12,8 +14,8 @@ module Familia
|
|
|
12
14
|
base.extend ModelClassMethods
|
|
13
15
|
base.include ModelInstanceMethods
|
|
14
16
|
|
|
15
|
-
# Ensure default
|
|
16
|
-
base.add_feature_options(:external_identifier,
|
|
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
|
|
40
|
+
# - Customizable format template (default: 'ext_' prefix)
|
|
39
41
|
# - Lazy generation preserves values from initialization
|
|
40
42
|
#
|
|
41
|
-
# @example Using external identifier fields
|
|
43
|
+
# @example Using external identifier fields with default format
|
|
42
44
|
# class User < Familia::Horreum
|
|
43
45
|
# feature :object_identifier
|
|
44
46
|
# feature :external_identifier
|
|
@@ -51,6 +53,30 @@ module Familia
|
|
|
51
53
|
# user2 = User.new(objid: user.objid, email: 'user@example.com')
|
|
52
54
|
# user2.extid # => "ext_abc123def456ghi789" (identical to user.extid)
|
|
53
55
|
#
|
|
56
|
+
# @example Using custom format template with hyphen separator
|
|
57
|
+
# class APIKey < Familia::Horreum
|
|
58
|
+
# feature :object_identifier
|
|
59
|
+
# feature :external_identifier, format: 'api-%{id}'
|
|
60
|
+
# end
|
|
61
|
+
# key = APIKey.new
|
|
62
|
+
# key.extid # => "api-abc123def456ghi789"
|
|
63
|
+
#
|
|
64
|
+
# @example Using custom format template with custom prefix
|
|
65
|
+
# class Customer < Familia::Horreum
|
|
66
|
+
# feature :object_identifier
|
|
67
|
+
# feature :external_identifier, format: 'cust_%{id}'
|
|
68
|
+
# end
|
|
69
|
+
# customer = Customer.new
|
|
70
|
+
# customer.extid # => "cust_abc123def456ghi789"
|
|
71
|
+
#
|
|
72
|
+
# @example Using format template without prefix
|
|
73
|
+
# class Resource < Familia::Horreum
|
|
74
|
+
# feature :object_identifier
|
|
75
|
+
# feature :external_identifier, format: 'v2/%{id}'
|
|
76
|
+
# end
|
|
77
|
+
# resource = Resource.new
|
|
78
|
+
# resource.extid # => "v2/abc123def456ghi789"
|
|
79
|
+
#
|
|
54
80
|
class ExternalIdentifierFieldType < Familia::FieldType
|
|
55
81
|
# Override getter to provide lazy generation from objid
|
|
56
82
|
#
|
|
@@ -252,11 +278,11 @@ module Familia
|
|
|
252
278
|
# 128 bits is approximately 25 characters in base36.
|
|
253
279
|
external_part = random_bytes.unpack1('H*').to_i(16).to_s(36).rjust(25, '0')
|
|
254
280
|
|
|
255
|
-
# Get
|
|
281
|
+
# Get format from feature options and interpolate the ID
|
|
256
282
|
options = self.class.feature_options(:external_identifier)
|
|
257
|
-
|
|
283
|
+
format = options[:format] || 'ext_%{id}'
|
|
258
284
|
|
|
259
|
-
|
|
285
|
+
format % { id: external_part }
|
|
260
286
|
end
|
|
261
287
|
|
|
262
288
|
# Full-length alias for extid for clarity when needed
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/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
|
|
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,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 =
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|