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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/data_type/database_commands.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
class DataType
|
|
@@ -22,11 +24,14 @@ module Familia
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
# Deletes the entire dbkey
|
|
25
|
-
#
|
|
27
|
+
#
|
|
28
|
+
# We return the dbclient.del command's return value instead of a friendly
|
|
29
|
+
# boolean b/c that logic doesn't work inside of a transaction. The return
|
|
30
|
+
# value in that case is a Redis::Future which based on the name indicates
|
|
31
|
+
# that the commend hasn't even run yet.
|
|
26
32
|
def delete!
|
|
27
|
-
Familia.trace :DELETE!, nil, uri if Familia.debug?
|
|
28
|
-
|
|
29
|
-
ret.positive?
|
|
33
|
+
Familia.trace :DELETE!, nil, self.class.uri if Familia.debug?
|
|
34
|
+
dbclient.del dbkey
|
|
30
35
|
end
|
|
31
36
|
alias clear delete!
|
|
32
37
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/data_type/serialization.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
class DataType
|
|
@@ -32,13 +34,13 @@ module Familia
|
|
|
32
34
|
|
|
33
35
|
if opts[:class]
|
|
34
36
|
prepared = Familia.identifier_extractor(opts[:class])
|
|
35
|
-
Familia.
|
|
37
|
+
Familia.debug " from opts[class] <#{opts[:class]}>: #{prepared || '<nil>'}"
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
if prepared.nil?
|
|
39
41
|
# Enforce strict values when no class option is specified
|
|
40
42
|
prepared = Familia.identifier_extractor(val)
|
|
41
|
-
Familia.
|
|
43
|
+
Familia.debug " from <#{val.class}> => <#{prepared.class}>"
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
if Familia.debug?
|
|
@@ -77,7 +79,7 @@ module Familia
|
|
|
77
79
|
# replaced with nil.
|
|
78
80
|
#
|
|
79
81
|
def deserialize_values_with_nil(*values)
|
|
80
|
-
Familia.
|
|
82
|
+
Familia.debug "deserialize_values: (#{@opts}) #{values}"
|
|
81
83
|
return [] if values.empty?
|
|
82
84
|
return values.flatten unless @opts[:class]
|
|
83
85
|
|
|
@@ -89,7 +91,7 @@ module Familia
|
|
|
89
91
|
next if obj.nil?
|
|
90
92
|
|
|
91
93
|
val = @opts[:class].send load_method, obj
|
|
92
|
-
Familia.
|
|
94
|
+
Familia.debug "[#{self.class}#deserialize_values] nil returned for #{@opts[:class]}##{name}" if val.nil?
|
|
93
95
|
|
|
94
96
|
val
|
|
95
97
|
rescue StandardError => e
|
|
@@ -117,7 +119,11 @@ module Familia
|
|
|
117
119
|
# for serialization since everything becomes a string in Valkey.
|
|
118
120
|
#
|
|
119
121
|
def deserialize_value(val)
|
|
122
|
+
# Handle Redis::Future objects during transactions first
|
|
123
|
+
return val if val.is_a?(Redis::Future)
|
|
124
|
+
|
|
120
125
|
return @opts[:default] if val.nil?
|
|
126
|
+
|
|
121
127
|
return val unless @opts[:class]
|
|
122
128
|
|
|
123
129
|
ret = deserialize_values val
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/data_type/types/hashkey.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
class HashKey < DataType
|
|
@@ -22,8 +24,8 @@ module Familia
|
|
|
22
24
|
update_expiration
|
|
23
25
|
ret
|
|
24
26
|
rescue TypeError => e
|
|
25
|
-
Familia.
|
|
26
|
-
Familia.
|
|
27
|
+
Familia.error "[hset]= #{e.message}"
|
|
28
|
+
Familia.debug "[hset]= #{dbkey} #{field}=#{val}"
|
|
27
29
|
echo :hset, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
|
|
28
30
|
klass = val.class
|
|
29
31
|
msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
|
|
@@ -74,8 +76,8 @@ module Familia
|
|
|
74
76
|
update_expiration if ret == 1
|
|
75
77
|
ret
|
|
76
78
|
rescue TypeError => e
|
|
77
|
-
Familia.
|
|
78
|
-
Familia.
|
|
79
|
+
Familia.error "[hsetnx] #{e.message}"
|
|
80
|
+
Familia.debug "[hsetnx] #{dbkey} #{field}=#{val}"
|
|
79
81
|
echo :hsetnx, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
|
|
80
82
|
klass = val.class
|
|
81
83
|
msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
|
|
@@ -152,11 +154,11 @@ module Familia
|
|
|
152
154
|
# puts "Oops! Our hash seems to have vanished into the Database void!"
|
|
153
155
|
# end
|
|
154
156
|
def refresh!
|
|
155
|
-
Familia.trace :REFRESH, nil, uri if Familia.debug?
|
|
157
|
+
Familia.trace :REFRESH, nil, self.class.uri if Familia.debug?
|
|
156
158
|
raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
|
|
157
159
|
|
|
158
160
|
fields = hgetall
|
|
159
|
-
Familia.
|
|
161
|
+
Familia.debug "[refresh!] #{self.class} #{dbkey} #{fields.keys}"
|
|
160
162
|
|
|
161
163
|
# For HashKey, we update by merging the fresh data
|
|
162
164
|
update(fields)
|
data/lib/familia/data_type.rb
CHANGED
|
@@ -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,19 +1,54 @@
|
|
|
1
1
|
# lib/familia/errors.rb
|
|
2
2
|
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
3
5
|
module Familia
|
|
6
|
+
# Base exception class for all Familia errors
|
|
4
7
|
class Problem < RuntimeError; end
|
|
5
|
-
class NoIdentifier < Problem; end
|
|
6
|
-
class NonUniqueKey < Problem; end
|
|
7
8
|
|
|
8
|
-
class
|
|
9
|
-
class
|
|
9
|
+
# Base exception class for Redis/persistence-related errors
|
|
10
|
+
class PersistenceError < Problem; end
|
|
11
|
+
|
|
12
|
+
# Base exception class for Horreum models
|
|
13
|
+
class HorreumError < Problem; end
|
|
14
|
+
|
|
15
|
+
# Raised when an object creation fails (e.g. the identifier
|
|
16
|
+
# is already in use)
|
|
17
|
+
class CreationError < HorreumError; end
|
|
18
|
+
|
|
19
|
+
# Raised when an object lacks a required identifier
|
|
20
|
+
class NoIdentifier < HorreumError; end
|
|
21
|
+
|
|
22
|
+
# Raised when a key is expected to be unique but isn't
|
|
23
|
+
class NonUniqueKey < PersistenceError; end
|
|
24
|
+
|
|
25
|
+
# Raised when watch failed (e.g. key was modified), typically
|
|
26
|
+
# retry
|
|
27
|
+
class OptimisticLockError < PersistenceError; end
|
|
28
|
+
|
|
29
|
+
# Raised when a field type is invalid or unexpected
|
|
30
|
+
class FieldTypeError < HorreumError; end
|
|
31
|
+
|
|
32
|
+
# Raised when autoloading fails
|
|
33
|
+
class AutoloadError < HorreumError; end
|
|
34
|
+
|
|
35
|
+
# Raised when serialization or deserialization fails
|
|
36
|
+
class SerializerError < HorreumError; end
|
|
37
|
+
|
|
38
|
+
# Raised when attempting to start transactions or pipelines on
|
|
39
|
+
# connection types that don't support them
|
|
40
|
+
class OperationModeError < PersistenceError; end
|
|
10
41
|
|
|
11
|
-
|
|
42
|
+
# Raised when attempting to call a major method like save when
|
|
43
|
+
# inside a transaction or pipeline
|
|
44
|
+
class NestedTransactionError < OperationModeError; end
|
|
12
45
|
|
|
13
|
-
# Raised when attempting to
|
|
14
|
-
class
|
|
46
|
+
# Raised when attempting to reference a field that doesn't exist
|
|
47
|
+
class UnknownFieldError < HorreumError; end
|
|
15
48
|
|
|
16
|
-
|
|
49
|
+
# Raised when a value cannot be converted to a distinguishable
|
|
50
|
+
# string representation
|
|
51
|
+
class NotDistinguishableError < HorreumError
|
|
17
52
|
attr_reader :value
|
|
18
53
|
|
|
19
54
|
def initialize(value)
|
|
@@ -26,7 +61,8 @@ module Familia
|
|
|
26
61
|
end
|
|
27
62
|
end
|
|
28
63
|
|
|
29
|
-
|
|
64
|
+
# Raised when no connection is available for a given URI
|
|
65
|
+
class NotConnected < PersistenceError
|
|
30
66
|
attr_reader :uri
|
|
31
67
|
|
|
32
68
|
def initialize(uri)
|
|
@@ -39,13 +75,15 @@ module Familia
|
|
|
39
75
|
end
|
|
40
76
|
end
|
|
41
77
|
|
|
42
|
-
# UnsortedSet Familia.connection_provider or use middleware
|
|
43
|
-
|
|
78
|
+
# UnsortedSet Familia.connection_provider or use middleware
|
|
79
|
+
# to provide connections.
|
|
80
|
+
class NoConnectionAvailable < PersistenceError; end
|
|
44
81
|
|
|
45
82
|
# Raised when a load method fails to find the requested object
|
|
46
|
-
class NotFound <
|
|
83
|
+
class NotFound < PersistenceError; end
|
|
47
84
|
|
|
48
|
-
# Raised when attempting to refresh an object whose key
|
|
85
|
+
# Raised when attempting to refresh an object whose key
|
|
86
|
+
# doesn't exist in the database
|
|
49
87
|
class KeyNotFoundError < NonUniqueKey
|
|
50
88
|
attr_reader :key
|
|
51
89
|
|
|
@@ -59,7 +97,8 @@ module Familia
|
|
|
59
97
|
end
|
|
60
98
|
end
|
|
61
99
|
|
|
62
|
-
# Raised when attempting to create an object that already
|
|
100
|
+
# Raised when attempting to create an object that already
|
|
101
|
+
# exists in the database
|
|
63
102
|
class RecordExistsError < NonUniqueKey
|
|
64
103
|
attr_reader :key
|
|
65
104
|
|
|
@@ -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
|
|
@@ -10,25 +12,23 @@ module Familia
|
|
|
10
12
|
# with the :expiration feature's implementation.
|
|
11
13
|
#
|
|
12
14
|
# This is a no-op implementation that gets overridden by the :expiration
|
|
13
|
-
# feature.
|
|
14
|
-
#
|
|
15
|
+
# feature when it is enabled. This allows for calling this method on any
|
|
16
|
+
# horreum model regardless of the feature status. It accepts an optional
|
|
17
|
+
# expiration parameter to maintain interface compatibility with
|
|
18
|
+
# the overriding implementations.
|
|
15
19
|
#
|
|
16
|
-
# @param
|
|
20
|
+
# @param expiration [Numeric, nil] Time To Live in seconds
|
|
17
21
|
# @return [nil] Always returns nil for the base implementation
|
|
18
22
|
#
|
|
19
23
|
# @note This is a no-op implementation. Classes that need expiration
|
|
20
24
|
# functionality should include the :expiration feature.
|
|
21
25
|
#
|
|
22
|
-
# @example
|
|
23
|
-
# class MyModel < Familia::Horreum
|
|
24
|
-
# feature :expiration
|
|
25
|
-
# default_expiration 1.hour
|
|
26
|
-
# end
|
|
26
|
+
# @example MyModel.new.update_expiration(expiration: 3600) # => nothing happens
|
|
27
27
|
#
|
|
28
|
-
def update_expiration(
|
|
29
|
-
Familia.
|
|
28
|
+
def update_expiration(expiration: nil)
|
|
29
|
+
Familia.debug <<~LOG
|
|
30
30
|
[update_expiration] Expiration feature not enabled for #{self.class}.
|
|
31
|
-
Key: #{dbkey} Arg: #{
|
|
31
|
+
Key: #{dbkey} Arg: #{expiration} (caller: #{caller(1..1)})
|
|
32
32
|
LOG
|
|
33
33
|
nil
|
|
34
34
|
end
|
|
@@ -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
|
|
|
@@ -36,7 +38,7 @@ module Familia
|
|
|
36
38
|
# session.ttl # => 1799
|
|
37
39
|
#
|
|
38
40
|
# # UnsortedSet custom expiration for new objects
|
|
39
|
-
# session.update_expiration(
|
|
41
|
+
# session.update_expiration(expiration: 2.hours)
|
|
40
42
|
#
|
|
41
43
|
# Class-Level Configuration:
|
|
42
44
|
#
|
|
@@ -89,7 +91,7 @@ module Familia
|
|
|
89
91
|
#
|
|
90
92
|
# Setting expiration to 0 (zero) disables TTL, making data persist indefinitely:
|
|
91
93
|
#
|
|
92
|
-
# session.update_expiration(
|
|
94
|
+
# session.update_expiration(expiration: 0) # No expiration
|
|
93
95
|
#
|
|
94
96
|
# TTL Querying:
|
|
95
97
|
#
|
|
@@ -115,7 +117,7 @@ module Familia
|
|
|
115
117
|
# when 'free'
|
|
116
118
|
# update_expiration(1.hour)
|
|
117
119
|
# else
|
|
118
|
-
# update_expiration(
|
|
120
|
+
# update_expiration(expiration)
|
|
119
121
|
# end
|
|
120
122
|
# end
|
|
121
123
|
# end
|
|
@@ -135,10 +137,10 @@ module Familia
|
|
|
135
137
|
#
|
|
136
138
|
# The feature validates expiration values and raises descriptive errors:
|
|
137
139
|
#
|
|
138
|
-
# session.update_expiration(
|
|
140
|
+
# session.update_expiration(expiration: "invalid")
|
|
139
141
|
# # => Familia::Problem: Default expiration must be a number
|
|
140
142
|
#
|
|
141
|
-
# session.update_expiration(
|
|
143
|
+
# session.update_expiration(expiration: -1)
|
|
142
144
|
# # => Familia::Problem: Default expiration must be non-negative
|
|
143
145
|
#
|
|
144
146
|
# Performance Considerations:
|
|
@@ -226,20 +228,20 @@ module Familia
|
|
|
226
228
|
# after which it will be automatically removed. The method also handles
|
|
227
229
|
# cascading expiration to related data structures when applicable.
|
|
228
230
|
#
|
|
229
|
-
# @param
|
|
231
|
+
# @param expiration [Numeric, nil] The Time To Live in seconds. If nil,
|
|
230
232
|
# the default TTL will be used.
|
|
231
233
|
#
|
|
232
234
|
# @return [Boolean] Returns true if the expiration was set successfully,
|
|
233
235
|
# false otherwise.
|
|
234
236
|
#
|
|
235
237
|
# @example Setting an expiration of one day
|
|
236
|
-
# object.update_expiration(
|
|
238
|
+
# object.update_expiration(expiration: 86400)
|
|
237
239
|
#
|
|
238
240
|
# @example Using default expiration
|
|
239
241
|
# object.update_expiration # Uses class default_expiration
|
|
240
242
|
#
|
|
241
243
|
# @example Removing expiration (persist indefinitely)
|
|
242
|
-
# object.update_expiration(
|
|
244
|
+
# object.update_expiration(expiration: 0)
|
|
243
245
|
#
|
|
244
246
|
# @note If default expiration is set to zero, the expiration will be removed,
|
|
245
247
|
# making the data persist indefinitely.
|
|
@@ -247,19 +249,19 @@ module Familia
|
|
|
247
249
|
# @raise [Familia::Problem] Raises an error if the default expiration is not
|
|
248
250
|
# a non-negative number.
|
|
249
251
|
#
|
|
250
|
-
def update_expiration(
|
|
251
|
-
|
|
252
|
+
def update_expiration(expiration: nil)
|
|
253
|
+
expiration ||= default_expiration
|
|
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.
|
|
262
|
-
obj.update_expiration(
|
|
263
|
+
Familia.debug "[update_expiration] Updating expiration for #{name} (#{obj.dbkey}) to #{expiration}"
|
|
264
|
+
obj.update_expiration(expiration: expiration)
|
|
263
265
|
end
|
|
264
266
|
end
|
|
265
267
|
|
|
@@ -267,29 +269,35 @@ module Familia
|
|
|
267
269
|
# It's important to raise exceptions here and not just log warnings. We
|
|
268
270
|
# don't want to silently fail at setting expirations and cause data
|
|
269
271
|
# retention issues (e.g. not removed in a timely fashion).
|
|
270
|
-
unless
|
|
272
|
+
unless expiration.is_a?(Numeric)
|
|
271
273
|
raise Familia::Problem,
|
|
272
|
-
"Default expiration must be a number (#{
|
|
274
|
+
"Default expiration must be a number (#{expiration.class} given for #{self.class})"
|
|
273
275
|
end
|
|
274
276
|
|
|
275
|
-
unless
|
|
277
|
+
unless expiration >= 0
|
|
276
278
|
raise Familia::Problem,
|
|
277
|
-
"Default expiration must be non-negative (#{
|
|
279
|
+
"Default expiration must be non-negative (#{expiration} given for #{self.class})"
|
|
278
280
|
end
|
|
279
281
|
|
|
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
|
-
if
|
|
283
|
-
Familia.
|
|
284
|
+
if expiration.zero?
|
|
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,
|
|
291
299
|
# it's a boolean.
|
|
292
|
-
expire(
|
|
300
|
+
expire(expiration)
|
|
293
301
|
end
|
|
294
302
|
|
|
295
303
|
# Get the remaining time to live for this object's data
|