familia 2.0.0.pre18 → 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 +205 -88
- data/CLAUDE.md +62 -10
- data/Gemfile +3 -3
- data/Gemfile.lock +27 -62
- data/README.md +39 -0
- 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 +177 -133
- 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-pre19.md +197 -0
- 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 +282 -0
- 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 +254 -0
- data/lib/familia/connection/handlers.rb +97 -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 +3 -1
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
- data/lib/familia/connection/transaction_core.rb +75 -9
- data/lib/familia/connection.rb +21 -5
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +153 -7
- data/lib/familia/data_type/database_commands.rb +9 -4
- data/lib/familia/data_type/serialization.rb +10 -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 +8 -6
- 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 +53 -14
- 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 +11 -11
- data/lib/familia/features/expiration.rb +29 -21
- 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 +177 -47
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
- data/lib/familia/features/relationships/indexing.rb +40 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
- 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 +5 -2
- data/lib/familia/horreum/connection.rb +28 -36
- data/lib/familia/horreum/database_commands.rb +131 -10
- data/lib/familia/horreum/definition.rb +18 -7
- data/lib/familia/horreum/management.rb +233 -57
- data/lib/familia/horreum/persistence.rb +314 -122
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +26 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -8
- data/lib/familia/horreum.rb +46 -13
- 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 +94 -37
- 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 +9 -7
- 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 +325 -129
- 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 +6 -4
- 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 +5 -1
- 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 +30 -4
- 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 +6 -4
- 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 +7 -3
- 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 +5 -1
- data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
- 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 +26 -22
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
- data/try/integration/data_types/datatype_transactions_try.rb +251 -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 +9 -1
- 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 +5 -1
- data/try/integration/persistence_operations_try.rb +166 -10
- 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 +5 -1
- 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 +5 -1
- 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 +36 -16
- data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
- data/try/unit/horreum/base_try.rb +5 -1
- data/try/unit/horreum/class_methods_try.rb +6 -2
- 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 +5 -1
- 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 +8 -4
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +6 -2
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -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 +81 -14
- data/.github/workflows/code-quality.yml +0 -138
- 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
|
@@ -16,7 +16,7 @@ Familia uses a three-tier expiration system:
|
|
|
16
16
|
|
|
17
17
|
### Cascading Expiration
|
|
18
18
|
|
|
19
|
-
When a Horreum object has related fields (DataTypes), the expiration feature automatically cascades TTL updates to
|
|
19
|
+
When a Horreum object has related fields (DataTypes), the expiration feature automatically cascades TTL updates to related objects that have their own `default_expiration` settings defined. This ensures consistent data lifecycle management while respecting per-field expiration policies.
|
|
20
20
|
|
|
21
21
|
## Basic Usage
|
|
22
22
|
|
|
@@ -66,7 +66,7 @@ session.default_expiration # => 900.0
|
|
|
66
66
|
session.update_expiration # Uses instance expiration (15 minutes)
|
|
67
67
|
|
|
68
68
|
# Or specify expiration inline
|
|
69
|
-
session.update_expiration(
|
|
69
|
+
session.update_expiration(expiration: 5.minutes)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
## Advanced Usage
|
|
@@ -103,21 +103,60 @@ class Customer < Familia::Horreum
|
|
|
103
103
|
|
|
104
104
|
identifier_field :customer_id
|
|
105
105
|
field :customer_id, :name, :email
|
|
106
|
-
list :recent_orders # Will
|
|
107
|
-
set :favorite_categories # Will
|
|
108
|
-
hashkey :preferences # Will
|
|
106
|
+
list :recent_orders, default_expiration: 12.hours # Will cascade
|
|
107
|
+
set :favorite_categories # Will NOT cascade (no default_expiration)
|
|
108
|
+
hashkey :preferences, default_expiration: 6.hours # Will cascade
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
customer = Customer.new(customer_id: 'cust_123')
|
|
112
112
|
customer.save
|
|
113
113
|
|
|
114
|
-
# This will set TTL on the main object AND
|
|
115
|
-
customer.update_expiration(
|
|
114
|
+
# This will set TTL on the main object AND related fields with default_expiration
|
|
115
|
+
customer.update_expiration(expiration: 12.hours)
|
|
116
116
|
# Sets expiration on:
|
|
117
|
-
# - customer:cust_123 (main hash)
|
|
118
|
-
# - customer:cust_123:recent_orders (list)
|
|
119
|
-
# - customer:cust_123:
|
|
120
|
-
# - customer:cust_123:
|
|
117
|
+
# - customer:cust_123 (main hash) - 12 hours
|
|
118
|
+
# - customer:cust_123:recent_orders (list) - 12 hours
|
|
119
|
+
# - customer:cust_123:preferences (hashkey) - 12 hours
|
|
120
|
+
# - customer:cust_123:favorite_categories (set) - NO expiration (no default_expiration)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### TTL Status Checking
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
session = UserSession.find('session_123')
|
|
127
|
+
|
|
128
|
+
# Check remaining TTL (returns seconds or special values)
|
|
129
|
+
ttl_seconds = session.ttl
|
|
130
|
+
case ttl_seconds
|
|
131
|
+
when -1
|
|
132
|
+
puts "Session never expires (no TTL set)"
|
|
133
|
+
when -2
|
|
134
|
+
puts "Session key doesn't exist"
|
|
135
|
+
when 0
|
|
136
|
+
puts "Session has expired"
|
|
137
|
+
else
|
|
138
|
+
puts "Session expires in #{ttl_seconds} seconds"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Convenience methods for TTL status
|
|
142
|
+
session.expires? # => true if TTL is set
|
|
143
|
+
session.expired? # => true if TTL <= 0
|
|
144
|
+
session.expired?(5.minutes) # => true if TTL <= 300 seconds
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Expiration Extension and Removal
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
session = UserSession.find('active_session')
|
|
151
|
+
|
|
152
|
+
# Extend current expiration by additional time
|
|
153
|
+
session.extend_expiration(15.minutes) # Adds 15 minutes to current TTL
|
|
154
|
+
|
|
155
|
+
# Remove expiration entirely (persist indefinitely)
|
|
156
|
+
session.persist! # Removes TTL, data won't expire
|
|
157
|
+
|
|
158
|
+
# Zero expiration also persists data (equivalent to persist!)
|
|
159
|
+
session.update_expiration(expiration: 0) # No expiration set
|
|
121
160
|
```
|
|
122
161
|
|
|
123
162
|
### Conditional Expiration
|
|
@@ -137,29 +176,14 @@ class AnalyticsEvent < Familia::Horreum
|
|
|
137
176
|
save
|
|
138
177
|
|
|
139
178
|
if should_expire?
|
|
140
|
-
update_expiration(
|
|
179
|
+
update_expiration(expiration: 1.hour)
|
|
141
180
|
else
|
|
142
|
-
update_expiration(
|
|
181
|
+
update_expiration(expiration: 30.days)
|
|
143
182
|
end
|
|
144
183
|
end
|
|
145
184
|
end
|
|
146
185
|
```
|
|
147
186
|
|
|
148
|
-
### Zero Expiration (Persistent Data)
|
|
149
|
-
|
|
150
|
-
```ruby
|
|
151
|
-
class PermanentRecord < Familia::Horreum
|
|
152
|
-
feature :expiration
|
|
153
|
-
default_expiration 0 # Never expires
|
|
154
|
-
|
|
155
|
-
field :permanent_data
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Zero expiration means data persists indefinitely
|
|
159
|
-
record = PermanentRecord.new
|
|
160
|
-
record.update_expiration # No-op, data won't expire
|
|
161
|
-
```
|
|
162
|
-
|
|
163
187
|
## Integration Patterns
|
|
164
188
|
|
|
165
189
|
### Rails Integration
|
|
@@ -200,7 +224,7 @@ class SessionCleanupJob
|
|
|
200
224
|
# Extend expiration for active sessions
|
|
201
225
|
UserSession.all.each do |session|
|
|
202
226
|
if session.recently_active?
|
|
203
|
-
session.update_expiration(
|
|
227
|
+
session.update_expiration(expiration: 30.minutes)
|
|
204
228
|
end
|
|
205
229
|
end
|
|
206
230
|
end
|
|
@@ -225,7 +249,7 @@ class SessionExpirationMiddleware
|
|
|
225
249
|
session = UserSession.find(session_token)
|
|
226
250
|
|
|
227
251
|
# Extend session TTL on each request
|
|
228
|
-
session&.update_expiration(
|
|
252
|
+
session&.update_expiration(expiration: 30.minutes)
|
|
229
253
|
end
|
|
230
254
|
|
|
231
255
|
@app.call(env)
|
|
@@ -255,6 +279,8 @@ when -1
|
|
|
255
279
|
puts "Session never expires"
|
|
256
280
|
when -2
|
|
257
281
|
puts "Session key doesn't exist"
|
|
282
|
+
when 0
|
|
283
|
+
puts "Session has expired"
|
|
258
284
|
when 0..3600
|
|
259
285
|
puts "Session expires in #{ttl_seconds / 60} minutes"
|
|
260
286
|
else
|
|
@@ -268,21 +294,21 @@ end
|
|
|
268
294
|
class SessionManager
|
|
269
295
|
def self.extend_all_sessions(new_ttl)
|
|
270
296
|
UserSession.all.each do |session|
|
|
271
|
-
session.update_expiration(
|
|
297
|
+
session.update_expiration(expiration: new_ttl)
|
|
272
298
|
end
|
|
273
299
|
end
|
|
274
300
|
|
|
275
301
|
def self.expire_inactive_sessions
|
|
276
302
|
UserSession.all.select(&:inactive?).each do |session|
|
|
277
303
|
# Set very short TTL for inactive sessions
|
|
278
|
-
session.update_expiration(
|
|
304
|
+
session.update_expiration(expiration: 5.minutes)
|
|
279
305
|
end
|
|
280
306
|
end
|
|
281
307
|
|
|
282
308
|
def self.make_sessions_permanent
|
|
283
309
|
# Remove expiration from all sessions
|
|
284
310
|
UserSession.all.each do |session|
|
|
285
|
-
session.persist # Remove TTL entirely
|
|
311
|
+
session.persist! # Remove TTL entirely
|
|
286
312
|
end
|
|
287
313
|
end
|
|
288
314
|
end
|
|
@@ -306,7 +332,7 @@ class DataRetentionService
|
|
|
306
332
|
model_class = data_type.to_s.pascalize.constantize
|
|
307
333
|
|
|
308
334
|
model_class.all.each do |record|
|
|
309
|
-
record.update_expiration(
|
|
335
|
+
record.update_expiration(expiration: ttl)
|
|
310
336
|
end
|
|
311
337
|
end
|
|
312
338
|
end
|
|
@@ -323,7 +349,7 @@ DataRetentionService.apply_retention_policies
|
|
|
323
349
|
```ruby
|
|
324
350
|
# ❌ Inefficient: Multiple round trips
|
|
325
351
|
sessions.each do |session|
|
|
326
|
-
session.update_expiration(
|
|
352
|
+
session.update_expiration(expiration: 1.hour)
|
|
327
353
|
end
|
|
328
354
|
|
|
329
355
|
# ✅ Efficient: Batch operations
|
|
@@ -333,7 +359,9 @@ pipeline = redis.pipelined do |pipe|
|
|
|
333
359
|
pipe.expire(session.dbkey, 3600)
|
|
334
360
|
|
|
335
361
|
# Also expire related fields if needed
|
|
336
|
-
session.class.related_fields.each do |name,
|
|
362
|
+
session.class.related_fields.each do |name, definition|
|
|
363
|
+
next if definition.opts[:default_expiration].nil?
|
|
364
|
+
|
|
337
365
|
related_key = "#{session.dbkey}:#{name}"
|
|
338
366
|
pipe.expire(related_key, 3600)
|
|
339
367
|
end
|
|
@@ -357,7 +385,7 @@ class ResilientSession < Familia::Horreum
|
|
|
357
385
|
return unless exists?
|
|
358
386
|
|
|
359
387
|
begin
|
|
360
|
-
update_expiration(
|
|
388
|
+
update_expiration(expiration: new_ttl)
|
|
361
389
|
rescue => e
|
|
362
390
|
# Log error but don't crash the application
|
|
363
391
|
Familia.logger.warn "Failed to update expiration for #{dbkey}: #{e.message}"
|
|
@@ -367,9 +395,26 @@ class ResilientSession < Familia::Horreum
|
|
|
367
395
|
end
|
|
368
396
|
```
|
|
369
397
|
|
|
370
|
-
##
|
|
398
|
+
## Error Handling and Validation
|
|
399
|
+
|
|
400
|
+
### Expiration Value Validation
|
|
401
|
+
|
|
402
|
+
```ruby
|
|
403
|
+
session = UserSession.new(session_token: 'test')
|
|
371
404
|
|
|
372
|
-
|
|
405
|
+
# Invalid expiration type
|
|
406
|
+
session.update_expiration(expiration: "invalid")
|
|
407
|
+
# => Familia::Problem: Default expiration must be a number (String given for UserSession)
|
|
408
|
+
|
|
409
|
+
# Negative expiration
|
|
410
|
+
session.update_expiration(expiration: -1)
|
|
411
|
+
# => Familia::Problem: Default expiration must be non-negative (-1 given for UserSession)
|
|
412
|
+
|
|
413
|
+
# Valid expiration
|
|
414
|
+
session.update_expiration(expiration: 3600) # => true
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Debug Logging
|
|
373
418
|
|
|
374
419
|
```ruby
|
|
375
420
|
# Enable debug logging to see expiration operations
|
|
@@ -377,22 +422,24 @@ Familia.debug = true
|
|
|
377
422
|
|
|
378
423
|
session = UserSession.new(session_token: 'debug_session')
|
|
379
424
|
session.save
|
|
380
|
-
session.update_expiration(
|
|
381
|
-
#
|
|
382
|
-
#
|
|
425
|
+
session.update_expiration(expiration: 5.minutes)
|
|
426
|
+
# Structured logging output:
|
|
427
|
+
# TTL updated operation=expire key=user_session:debug_session ttl_seconds=300.0 class=UserSession identifier=debug_session
|
|
383
428
|
```
|
|
384
429
|
|
|
430
|
+
## Debugging and Troubleshooting
|
|
431
|
+
|
|
385
432
|
### Common Issues
|
|
386
433
|
|
|
387
434
|
**1. Expiration Not Applied**
|
|
388
435
|
```ruby
|
|
389
436
|
session = UserSession.new
|
|
390
437
|
# ❌ Won't work - object must be saved first
|
|
391
|
-
session.update_expiration(
|
|
438
|
+
session.update_expiration(expiration: 1.hour)
|
|
392
439
|
|
|
393
440
|
# ✅ Correct - save first, then expire
|
|
394
441
|
session.save
|
|
395
|
-
session.update_expiration(
|
|
442
|
+
session.update_expiration(expiration: 1.hour)
|
|
396
443
|
```
|
|
397
444
|
|
|
398
445
|
**2. Related Fields Not Expiring**
|
|
@@ -401,23 +448,16 @@ class Customer < Familia::Horreum
|
|
|
401
448
|
feature :expiration
|
|
402
449
|
|
|
403
450
|
field :name
|
|
404
|
-
list :orders # ❌ Won't cascade
|
|
451
|
+
list :orders # ❌ Won't cascade - no default_expiration defined
|
|
405
452
|
end
|
|
406
453
|
|
|
407
|
-
# ✅ Fix:
|
|
454
|
+
# ✅ Fix: Define default_expiration for fields that should cascade
|
|
408
455
|
class Customer < Familia::Horreum
|
|
409
456
|
feature :expiration
|
|
457
|
+
default_expiration 1.day
|
|
410
458
|
|
|
411
459
|
field :name
|
|
412
|
-
list :orders
|
|
413
|
-
|
|
414
|
-
# Explicitly track relation if needed
|
|
415
|
-
def update_expiration(**opts)
|
|
416
|
-
super(**opts)
|
|
417
|
-
|
|
418
|
-
# Manually cascade to specific fields if needed
|
|
419
|
-
orders.expire(opts[:default_expiration] || default_expiration)
|
|
420
|
-
end
|
|
460
|
+
list :orders, default_expiration: 12.hours # Will cascade
|
|
421
461
|
end
|
|
422
462
|
```
|
|
423
463
|
|
|
@@ -439,6 +479,20 @@ class BaseModel < Familia::Horreum
|
|
|
439
479
|
end
|
|
440
480
|
```
|
|
441
481
|
|
|
482
|
+
**4. No-Op Behavior Without Feature**
|
|
483
|
+
```ruby
|
|
484
|
+
class BasicModel < Familia::Horreum
|
|
485
|
+
# No expiration feature enabled
|
|
486
|
+
field :data
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
basic = BasicModel.new
|
|
490
|
+
basic.update_expiration(expiration: 1.hour) # No-op, returns nil
|
|
491
|
+
basic.ttl # Returns -1
|
|
492
|
+
basic.expires? # Returns false
|
|
493
|
+
basic.expired? # Returns false
|
|
494
|
+
```
|
|
495
|
+
|
|
442
496
|
## Testing TTL Behavior
|
|
443
497
|
|
|
444
498
|
### RSpec Testing
|
|
@@ -459,18 +513,37 @@ RSpec.describe UserSession do
|
|
|
459
513
|
|
|
460
514
|
it "applies TTL to database key" do
|
|
461
515
|
session.save
|
|
462
|
-
session.update_expiration(
|
|
516
|
+
session.update_expiration(expiration: 10.minutes)
|
|
463
517
|
|
|
464
518
|
ttl = session.ttl
|
|
465
519
|
expect(ttl).to be > 500 # Should be close to 600 seconds
|
|
466
520
|
expect(ttl).to be <= 600
|
|
467
521
|
end
|
|
468
522
|
|
|
469
|
-
it "
|
|
523
|
+
it "provides TTL status methods" do
|
|
524
|
+
session.save
|
|
525
|
+
session.update_expiration(expiration: 5.minutes)
|
|
526
|
+
|
|
527
|
+
expect(session.expires?).to be true
|
|
528
|
+
expect(session.expired?).to be false
|
|
529
|
+
expect(session.expired?(10.minutes)).to be true # Expiring within 10 minutes
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
it "supports expiration extension" do
|
|
533
|
+
session.save
|
|
534
|
+
session.update_expiration(expiration: 5.minutes)
|
|
535
|
+
|
|
536
|
+
initial_ttl = session.ttl
|
|
537
|
+
session.extend_expiration(5.minutes)
|
|
538
|
+
|
|
539
|
+
expect(session.ttl).to be > initial_ttl
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
it "cascades expiration to related fields with default_expiration" do
|
|
470
543
|
session.save
|
|
471
|
-
session.activity_log.push('login') # Assume activity_log
|
|
544
|
+
session.activity_log.push('login') # Assume activity_log has default_expiration
|
|
472
545
|
|
|
473
|
-
session.update_expiration(
|
|
546
|
+
session.update_expiration(expiration: 5.minutes)
|
|
474
547
|
|
|
475
548
|
# Both main object and related fields should have TTL
|
|
476
549
|
expect(session.ttl).to be > 250
|
|
@@ -506,92 +579,63 @@ class SessionExpirationTest < ActionDispatch::IntegrationTest
|
|
|
506
579
|
end
|
|
507
580
|
```
|
|
508
581
|
|
|
509
|
-
##
|
|
582
|
+
## API Reference
|
|
510
583
|
|
|
511
|
-
###
|
|
584
|
+
### Instance Methods
|
|
512
585
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
feature :expiration
|
|
586
|
+
#### `update_expiration(expiration: nil)`
|
|
587
|
+
Sets TTL for the object and cascades to related fields with `default_expiration`.
|
|
516
588
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
when 'development'
|
|
520
|
-
default_expiration 8.hours # Convenience for debugging
|
|
521
|
-
when 'test'
|
|
522
|
-
default_expiration 1.minute # Fast cleanup in tests
|
|
523
|
-
when 'production'
|
|
524
|
-
default_expiration 30.minutes # Security-focused
|
|
525
|
-
end
|
|
526
|
-
end
|
|
527
|
-
```
|
|
589
|
+
**Parameters:**
|
|
590
|
+
- `expiration` (Numeric, nil) - TTL in seconds. Uses `default_expiration` if nil.
|
|
528
591
|
|
|
529
|
-
|
|
592
|
+
**Returns:** Boolean indicating success
|
|
530
593
|
|
|
531
|
-
|
|
532
|
-
class TTLHealthCheck
|
|
533
|
-
def self.check_session_health
|
|
534
|
-
expired_count = 0
|
|
535
|
-
total_count = 0
|
|
594
|
+
**Raises:** `Familia::Problem` for invalid expiration values
|
|
536
595
|
|
|
537
|
-
|
|
538
|
-
|
|
596
|
+
#### `ttl`
|
|
597
|
+
Get remaining TTL for this object.
|
|
539
598
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
# Extend TTL for active sessions
|
|
545
|
-
session.update_expiration(default_expiration: 30.minutes) if session.active?
|
|
546
|
-
end
|
|
547
|
-
end
|
|
599
|
+
**Returns:**
|
|
600
|
+
- Integer > 0: Seconds remaining
|
|
601
|
+
- -1: No TTL set (persistent)
|
|
602
|
+
- -2: Key doesn't exist
|
|
548
603
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
expired_sessions: expired_count,
|
|
552
|
-
expiration_rate: expired_count.to_f / total_count
|
|
553
|
-
}
|
|
554
|
-
end
|
|
555
|
-
end
|
|
556
|
-
```
|
|
604
|
+
#### `expires?`
|
|
605
|
+
Check if object has TTL set.
|
|
557
606
|
|
|
558
|
-
|
|
607
|
+
**Returns:** Boolean - true if TTL is set
|
|
559
608
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
def self.get_or_create_session(session_token)
|
|
563
|
-
session = UserSession.find(session_token)
|
|
609
|
+
#### `expired?(threshold = 0)`
|
|
610
|
+
Check if object is expired or expiring soon.
|
|
564
611
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
# Extend TTL on access
|
|
568
|
-
session.update_expiration(default_expiration: 30.minutes)
|
|
569
|
-
session
|
|
570
|
-
else
|
|
571
|
-
# Create new session if old one expired
|
|
572
|
-
create_new_session
|
|
573
|
-
end
|
|
574
|
-
rescue => e
|
|
575
|
-
# Fallback: create new session on any error
|
|
576
|
-
Rails.logger.warn "Session retrieval failed: #{e.message}"
|
|
577
|
-
create_new_session
|
|
578
|
-
end
|
|
579
|
-
end
|
|
580
|
-
```
|
|
612
|
+
**Parameters:**
|
|
613
|
+
- `threshold` (Numeric) - Consider expired if TTL <= threshold
|
|
581
614
|
|
|
582
|
-
|
|
615
|
+
**Returns:** Boolean - true if expired/expiring
|
|
583
616
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
617
|
+
#### `extend_expiration(duration)`
|
|
618
|
+
Extend current TTL by additional time.
|
|
619
|
+
|
|
620
|
+
**Parameters:**
|
|
621
|
+
- `duration` (Numeric) - Additional seconds to add
|
|
622
|
+
|
|
623
|
+
**Returns:** Boolean indicating success
|
|
624
|
+
|
|
625
|
+
#### `persist!`
|
|
626
|
+
Remove TTL, making object persistent.
|
|
627
|
+
|
|
628
|
+
**Returns:** Boolean indicating success
|
|
629
|
+
|
|
630
|
+
### Class Methods
|
|
631
|
+
|
|
632
|
+
#### `default_expiration(num = nil)`
|
|
633
|
+
Get/set class-level default expiration.
|
|
634
|
+
|
|
635
|
+
**Parameters:**
|
|
636
|
+
- `num` (Numeric, nil) - Set expiration if provided
|
|
637
|
+
|
|
638
|
+
**Returns:** Float - current default expiration in seconds
|
|
595
639
|
|
|
596
640
|
---
|
|
597
641
|
|
|
@@ -602,4 +646,4 @@ end
|
|
|
602
646
|
- **[Feature System Guide](feature-system.md)** - Understanding Familia's feature architecture
|
|
603
647
|
- **[Implementation Guide](implementation.md)** - Production deployment and configuration patterns
|
|
604
648
|
|
|
605
|
-
The Expiration feature provides a robust foundation for managing data lifecycle in Familia applications, with flexible configuration options
|
|
649
|
+
The Expiration feature provides a robust foundation for managing data lifecycle in Familia applications, with flexible configuration options, automatic cascading to related objects, and comprehensive TTL monitoring capabilities.
|