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,5 +1,7 @@
|
|
|
1
1
|
# lib/familia/horreum/utils.rb
|
|
2
2
|
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
3
5
|
module Familia
|
|
4
6
|
# InstanceMethods - Module containing instance-level methods for Familia
|
|
5
7
|
#
|
|
@@ -11,14 +13,6 @@ module Familia
|
|
|
11
13
|
# Provides identifier handling, dbkey generation, and object inspection
|
|
12
14
|
#
|
|
13
15
|
module Utils
|
|
14
|
-
# def uri
|
|
15
|
-
# base_uri = self.class.uri || Familia.uri
|
|
16
|
-
# u = base_uri.dup # make a copy to modify safely
|
|
17
|
-
# u.logical_database = logical_database if logical_database
|
|
18
|
-
# u.key = dbkey
|
|
19
|
-
# u
|
|
20
|
-
# end
|
|
21
|
-
|
|
22
16
|
# +suffix+ is the value to be used at the end of the db key
|
|
23
17
|
# (e.g. `customer:customer_id:scores` would have `scores` as the suffix
|
|
24
18
|
# and `customer_id` would have been the identifier in that case).
|
data/lib/familia/horreum.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/horreum.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative 'horreum/settings'
|
|
4
6
|
require_relative 'horreum/connection'
|
|
@@ -51,7 +53,6 @@ module Familia
|
|
|
51
53
|
include Familia::Base
|
|
52
54
|
include Familia::Horreum::Persistence
|
|
53
55
|
include Familia::Horreum::Serialization
|
|
54
|
-
include Familia::Horreum::Connection
|
|
55
56
|
include Familia::Horreum::DatabaseCommands
|
|
56
57
|
include Familia::Horreum::Settings
|
|
57
58
|
include Familia::Horreum::Utils
|
|
@@ -187,6 +188,7 @@ module Familia
|
|
|
187
188
|
# `Session.new({sessid: "abc123", custid: "user456"})` # legacy hash (robust)
|
|
188
189
|
#
|
|
189
190
|
def initialize(*args, **kwargs)
|
|
191
|
+
start_time = Familia.now_in_μs if Familia.debug?
|
|
190
192
|
Familia.trace :INITIALIZE, nil, "Initializing #{self.class}" if Familia.debug?
|
|
191
193
|
initialize_relatives
|
|
192
194
|
|
|
@@ -226,23 +228,54 @@ module Familia
|
|
|
226
228
|
# Default values are intentionally NOT set here
|
|
227
229
|
end
|
|
228
230
|
|
|
229
|
-
# Implementing classes can define an init method to do any
|
|
230
|
-
#
|
|
231
|
-
#
|
|
231
|
+
# Implementing classes can define an init method to do any additional
|
|
232
|
+
# initialization. Notice that this is called AFTER fields are set from
|
|
233
|
+
# kwargs, so kwargs have been consumed and are no longer available.
|
|
234
|
+
#
|
|
235
|
+
# IMPORTANT: Use ||= in init to apply defaults without overriding:
|
|
236
|
+
# def init
|
|
237
|
+
# @email ||= email # Preserves value already set
|
|
238
|
+
# @status ||= 'pending' # Applies default if nil
|
|
239
|
+
# end
|
|
240
|
+
#
|
|
232
241
|
init
|
|
242
|
+
|
|
243
|
+
# Structured lifecycle logging and instrumentation
|
|
244
|
+
if Familia.debug? && start_time
|
|
245
|
+
duration = Familia.now_in_μs - start_time
|
|
246
|
+
Familia.debug "Horreum initialized",
|
|
247
|
+
class: self.class.name,
|
|
248
|
+
duration: duration,
|
|
249
|
+
identifier: (identifier rescue nil)
|
|
250
|
+
|
|
251
|
+
Familia::Instrumentation.notify_lifecycle(:initialize, self, duration: duration)
|
|
252
|
+
end
|
|
233
253
|
end
|
|
234
254
|
|
|
235
|
-
#
|
|
236
|
-
# This is called AFTER fields are set and relatives are initialized.
|
|
255
|
+
# Initialization method called at the end of initialize
|
|
237
256
|
#
|
|
238
|
-
#
|
|
257
|
+
# Override this method to apply defaults, run validations, or setup
|
|
258
|
+
# callbacks. It's recommended to call super as other modules like
|
|
259
|
+
# features can also override init.
|
|
239
260
|
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
261
|
+
# IMPORTANT: The init method receieves no arguments. By the time this runs,
|
|
262
|
+
# all arguments to initialize have already been consumed and used to set
|
|
263
|
+
# fields. Use the ||= operator to preserve values already set:
|
|
264
|
+
#
|
|
265
|
+
# def init(email: nil, user_id: nil, **kwargs)
|
|
266
|
+
# @email ||= email # Preserves value from new()
|
|
267
|
+
# @user_id ||= user_id # Preserves value from new()
|
|
268
|
+
# @created_at ||= Familia.now # Applies default if not set
|
|
269
|
+
#
|
|
270
|
+
# # Example of additional initialization logic
|
|
271
|
+
# validate_email_format if @email
|
|
272
|
+
# setup_callbacks
|
|
243
273
|
# end
|
|
244
|
-
|
|
245
|
-
|
|
274
|
+
#
|
|
275
|
+
# @return [void]
|
|
276
|
+
#
|
|
277
|
+
def init
|
|
278
|
+
# Default no-op - override in subclasses
|
|
246
279
|
end
|
|
247
280
|
|
|
248
281
|
# Sets up related Database objects for the instance
|
|
@@ -319,7 +352,7 @@ module Familia
|
|
|
319
352
|
# the object with.
|
|
320
353
|
# @return [Array] The list of field names that were updated.
|
|
321
354
|
def naive_refresh(**fields)
|
|
322
|
-
Familia.
|
|
355
|
+
Familia.debug "[naive_refresh] #{self.class} #{dbkey} #{fields.keys}"
|
|
323
356
|
initialize_with_keyword_args_deserialize_value(**fields)
|
|
324
357
|
end
|
|
325
358
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# lib/familia/instrumentation.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require 'concurrent-ruby'
|
|
6
|
+
|
|
7
|
+
module Familia
|
|
8
|
+
# Provides instrumentation hooks for observability into Familia operations.
|
|
9
|
+
#
|
|
10
|
+
# This module allows applications to register callbacks for various events
|
|
11
|
+
# in Familia's lifecycle, enabling audit trails, performance monitoring,
|
|
12
|
+
# and operational observability.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# Familia.on_command do |cmd, duration, context|
|
|
16
|
+
# puts "Redis command: #{cmd} (#{duration}μs)"
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Audit trail for secrets service
|
|
20
|
+
# Familia.on_lifecycle do |event, instance, context|
|
|
21
|
+
# case event
|
|
22
|
+
# when :save
|
|
23
|
+
# AuditLog.create!(
|
|
24
|
+
# event: 'secret_saved',
|
|
25
|
+
# secret_id: instance.identifier,
|
|
26
|
+
# user_id: RequestContext.current_user_id
|
|
27
|
+
# )
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
module Instrumentation
|
|
32
|
+
@hooks = {
|
|
33
|
+
command: Concurrent::Array.new,
|
|
34
|
+
pipeline: Concurrent::Array.new,
|
|
35
|
+
lifecycle: Concurrent::Array.new,
|
|
36
|
+
error: Concurrent::Array.new
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Register a callback for Redis command execution.
|
|
41
|
+
#
|
|
42
|
+
# @yield [cmd, duration, context] Callback block
|
|
43
|
+
# @yieldparam cmd [String] The Redis command name (e.g., "SET", "ZADD")
|
|
44
|
+
# @yieldparam duration [Integer] Command execution duration in microseconds
|
|
45
|
+
# @yieldparam context [Hash] Additional context including:
|
|
46
|
+
# - :full_command [Array] Complete command with arguments
|
|
47
|
+
# - :db [Integer] Database number
|
|
48
|
+
# - :connection_id [String] Connection identifier
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# Familia.on_command do |cmd, duration, ctx|
|
|
52
|
+
# StatsD.timing("familia.command.#{cmd.downcase}", duration / 1000.0)
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
def on_command(&block)
|
|
56
|
+
@hooks[:command] << block
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Register a callback for pipelined Redis operations.
|
|
60
|
+
#
|
|
61
|
+
# @yield [command_count, duration, context] Callback block
|
|
62
|
+
# @yieldparam command_count [Integer] Number of commands in the pipeline
|
|
63
|
+
# @yieldparam duration [Integer] Pipeline execution duration in microseconds
|
|
64
|
+
# @yieldparam context [Hash] Additional context
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# Familia.on_pipeline do |count, duration, ctx|
|
|
68
|
+
# StatsD.timing("familia.pipeline", duration / 1000.0)
|
|
69
|
+
# StatsD.gauge("familia.pipeline.commands", count)
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
def on_pipeline(&block)
|
|
73
|
+
@hooks[:pipeline] << block
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Register a callback for Horreum lifecycle events.
|
|
77
|
+
#
|
|
78
|
+
# @yield [event, instance, context] Callback block
|
|
79
|
+
# @yieldparam event [Symbol] Lifecycle event (:initialize, :save, :destroy)
|
|
80
|
+
# @yieldparam instance [Familia::Horreum] The object instance
|
|
81
|
+
# @yieldparam context [Hash] Additional context including:
|
|
82
|
+
# - :duration [Integer] Operation duration in microseconds (for initialize/save)
|
|
83
|
+
# - :update_expiration [Boolean] Whether TTL was updated (for save)
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
# Familia.on_lifecycle do |event, instance, ctx|
|
|
87
|
+
# case event
|
|
88
|
+
# when :destroy
|
|
89
|
+
# Rails.logger.info("Destroyed #{instance.class}:#{instance.identifier}")
|
|
90
|
+
# end
|
|
91
|
+
# end
|
|
92
|
+
#
|
|
93
|
+
def on_lifecycle(&block)
|
|
94
|
+
@hooks[:lifecycle] << block
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Register a callback for error conditions.
|
|
98
|
+
#
|
|
99
|
+
# @yield [error, context] Callback block
|
|
100
|
+
# @yieldparam error [Exception] The error that occurred
|
|
101
|
+
# @yieldparam context [Hash] Additional context including:
|
|
102
|
+
# - :operation [Symbol] Operation that failed (:serialization, etc.)
|
|
103
|
+
# - :field [Symbol] Field name (for serialization errors)
|
|
104
|
+
# - :object_class [String] Class name of the object
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# Familia.on_error do |error, ctx|
|
|
108
|
+
# Sentry.capture_exception(error, extra: ctx)
|
|
109
|
+
# end
|
|
110
|
+
#
|
|
111
|
+
def on_error(&block)
|
|
112
|
+
@hooks[:error] << block
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Notify all registered command hooks.
|
|
116
|
+
# @api private
|
|
117
|
+
def notify_command(cmd, duration, context = {})
|
|
118
|
+
@hooks[:command].each do |hook|
|
|
119
|
+
hook.call(cmd, duration, context)
|
|
120
|
+
rescue => e
|
|
121
|
+
Familia.error("Instrumentation hook failed", error: e.message, hook_type: :command)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Notify all registered pipeline hooks.
|
|
126
|
+
# @api private
|
|
127
|
+
def notify_pipeline(command_count, duration, context = {})
|
|
128
|
+
@hooks[:pipeline].each do |hook|
|
|
129
|
+
hook.call(command_count, duration, context)
|
|
130
|
+
rescue => e
|
|
131
|
+
Familia.error("Instrumentation hook failed", error: e.message, hook_type: :pipeline)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Notify all registered lifecycle hooks.
|
|
136
|
+
# @api private
|
|
137
|
+
def notify_lifecycle(event, instance, context = {})
|
|
138
|
+
@hooks[:lifecycle].each do |hook|
|
|
139
|
+
hook.call(event, instance, context)
|
|
140
|
+
rescue => e
|
|
141
|
+
Familia.error("Instrumentation hook failed", error: e.message, hook_type: :lifecycle)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Notify all registered error hooks.
|
|
146
|
+
# @api private
|
|
147
|
+
def notify_error(error, context = {})
|
|
148
|
+
@hooks[:error].each do |hook|
|
|
149
|
+
hook.call(error, context)
|
|
150
|
+
rescue => e
|
|
151
|
+
# Don't recurse on hook failures - just silently skip
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
data/lib/familia/logging.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/logging.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'pathname'
|
|
4
6
|
require 'logger'
|
|
@@ -117,7 +119,7 @@ module Familia
|
|
|
117
119
|
'ERROR' => 'E',
|
|
118
120
|
'FATAL' => 'F',
|
|
119
121
|
'UNKNOWN' => 'U',
|
|
120
|
-
'ANY' => 'T'
|
|
122
|
+
'ANY' => 'T', # ANY is Logger's label for severity < 0, treat as TRACE
|
|
121
123
|
}.freeze
|
|
122
124
|
|
|
123
125
|
# Format a log message with severity, timestamp, and context.
|
|
@@ -142,12 +144,9 @@ module Familia
|
|
|
142
144
|
SEVERITY_LETTERS.fetch(severity, severity[0])
|
|
143
145
|
end
|
|
144
146
|
|
|
145
|
-
utc_datetime = datetime.utc.strftime('%
|
|
146
|
-
pid = Process.pid
|
|
147
|
-
thread_id = Thread.current.object_id
|
|
148
|
-
fiber_id = Fiber.current.object_id
|
|
147
|
+
utc_datetime = datetime.utc.strftime('%H:%M:%S.%3N')
|
|
149
148
|
|
|
150
|
-
"#{severity_letter}, #{utc_datetime}
|
|
149
|
+
"#{severity_letter}, #{utc_datetime} #{msg}\n"
|
|
151
150
|
end
|
|
152
151
|
end
|
|
153
152
|
|
|
@@ -176,8 +175,16 @@ module Familia
|
|
|
176
175
|
# Familia.trace :LOAD, redis_client, "user:123", "from cache"
|
|
177
176
|
#
|
|
178
177
|
module Logging
|
|
178
|
+
# Thread-safe mutex initialization when module is extended
|
|
179
|
+
def self.extended(base)
|
|
180
|
+
base.instance_variable_set(:@logger_mutex, Mutex.new)
|
|
181
|
+
end
|
|
182
|
+
|
|
179
183
|
# Get the logger instance, initializing with defaults if not yet set
|
|
180
184
|
#
|
|
185
|
+
# Thread-safe lazy initialization using double-checked locking to ensure
|
|
186
|
+
# only a single logger instance is created even under concurrent logging calls.
|
|
187
|
+
#
|
|
181
188
|
# @return [FamiliaLogger] the logger instance
|
|
182
189
|
#
|
|
183
190
|
# @example Set a custom logger
|
|
@@ -187,10 +194,18 @@ module Familia
|
|
|
187
194
|
# Familia.logger.info "Connection established"
|
|
188
195
|
#
|
|
189
196
|
def logger
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
197
|
+
# Fast path: return existing logger if already initialized
|
|
198
|
+
return @logger if @logger
|
|
199
|
+
|
|
200
|
+
# Slow path: thread-safe initialization
|
|
201
|
+
@logger_mutex.synchronize do
|
|
202
|
+
@logger ||= FamiliaLogger.new($stderr).tap do |log|
|
|
203
|
+
log.progname = name
|
|
204
|
+
log.formatter = LogFormatter.new
|
|
205
|
+
end
|
|
193
206
|
end
|
|
207
|
+
|
|
208
|
+
@logger
|
|
194
209
|
end
|
|
195
210
|
|
|
196
211
|
# Set a custom logger instance.
|
|
@@ -198,6 +213,9 @@ module Familia
|
|
|
198
213
|
# Allows replacing the default FamiliaLogger with any Logger-compatible
|
|
199
214
|
# object. Useful for integrating with application logging frameworks.
|
|
200
215
|
#
|
|
216
|
+
# Automatically synchronizes the logger to DatabaseLogger if it's loaded,
|
|
217
|
+
# ensuring consistent logging across Familia's middleware stack.
|
|
218
|
+
#
|
|
201
219
|
# @param new_logger [Logger] The logger to use
|
|
202
220
|
# @return [Logger] The logger that was set
|
|
203
221
|
#
|
|
@@ -210,20 +228,14 @@ module Familia
|
|
|
210
228
|
# end
|
|
211
229
|
#
|
|
212
230
|
def logger=(new_logger)
|
|
213
|
-
@
|
|
231
|
+
@logger_mutex.synchronize do
|
|
232
|
+
@logger = new_logger
|
|
233
|
+
# Auto-sync to DatabaseLogger if loaded (inside mutex for atomicity)
|
|
234
|
+
DatabaseLogger.logger = new_logger if defined?(DatabaseLogger)
|
|
235
|
+
end
|
|
236
|
+
@logger
|
|
214
237
|
end
|
|
215
238
|
|
|
216
|
-
# Log an informational message.
|
|
217
|
-
#
|
|
218
|
-
# @param msg [String] The message to log
|
|
219
|
-
# @return [true]
|
|
220
|
-
#
|
|
221
|
-
# @example
|
|
222
|
-
# Familia.info "Redis connection established"
|
|
223
|
-
#
|
|
224
|
-
def info(msg)
|
|
225
|
-
logger.info(msg)
|
|
226
|
-
end
|
|
227
239
|
|
|
228
240
|
# Log a warning message.
|
|
229
241
|
#
|
|
@@ -237,34 +249,58 @@ module Familia
|
|
|
237
249
|
logger.warn(msg)
|
|
238
250
|
end
|
|
239
251
|
|
|
240
|
-
# Log a debug message
|
|
252
|
+
# Log a debug message with optional structured context.
|
|
241
253
|
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
254
|
+
# Only outputs when FAMILIA_DEBUG environment variable is enabled.
|
|
255
|
+
# Supports both simple string messages and structured logging with
|
|
256
|
+
# keyword context for operational observability.
|
|
244
257
|
#
|
|
245
|
-
# @param
|
|
258
|
+
# @param message [String, nil] The message to log
|
|
259
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
246
260
|
# @return [true, nil] Returns true if logged, nil if debug disabled
|
|
247
261
|
#
|
|
248
|
-
# @example
|
|
249
|
-
# Familia.
|
|
250
|
-
#
|
|
262
|
+
# @example Simple message
|
|
263
|
+
# Familia.debug "Cache lookup for user:123"
|
|
264
|
+
#
|
|
265
|
+
# @example Structured context
|
|
266
|
+
# Familia.debug "Horreum saved", class: "User", identifier: "user_123", duration: 1234
|
|
267
|
+
# # => "Horreum saved class=User identifier=user_123 duration=1234"
|
|
251
268
|
#
|
|
252
|
-
def
|
|
253
|
-
|
|
269
|
+
def debug(message = nil, **context)
|
|
270
|
+
return unless Familia.debug?
|
|
271
|
+
logger.debug(format_log(message, context))
|
|
254
272
|
end
|
|
255
273
|
|
|
256
|
-
# Log an
|
|
274
|
+
# Log an informational message with optional structured context.
|
|
257
275
|
#
|
|
258
|
-
#
|
|
276
|
+
# @param message [String, nil] The message to log
|
|
277
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
278
|
+
# @return [true]
|
|
259
279
|
#
|
|
260
|
-
# @
|
|
280
|
+
# @example Simple message
|
|
281
|
+
# Familia.info "Connection pool initialized"
|
|
282
|
+
#
|
|
283
|
+
# @example Structured context
|
|
284
|
+
# Familia.info "Pipeline executed", commands: 5, duration: 2340
|
|
285
|
+
#
|
|
286
|
+
def info(message = nil, **context)
|
|
287
|
+
logger.info(format_log(message, context))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Log an error message with optional structured context.
|
|
291
|
+
#
|
|
292
|
+
# @param message [String, nil] The message to log
|
|
293
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
261
294
|
# @return [true]
|
|
262
295
|
#
|
|
263
|
-
# @example
|
|
264
|
-
# Familia.
|
|
296
|
+
# @example Simple message
|
|
297
|
+
# Familia.error "Failed to deserialize value"
|
|
298
|
+
#
|
|
299
|
+
# @example Structured context
|
|
300
|
+
# Familia.error "Serialization failed", field: :email, error: e.message, class: "User"
|
|
265
301
|
#
|
|
266
|
-
def
|
|
267
|
-
logger.error(
|
|
302
|
+
def error(message = nil, **context)
|
|
303
|
+
logger.error(format_log(message, context))
|
|
268
304
|
end
|
|
269
305
|
|
|
270
306
|
# Logs a structured trace message for debugging Familia operations.
|
|
@@ -296,6 +332,27 @@ module Familia
|
|
|
296
332
|
|
|
297
333
|
private
|
|
298
334
|
|
|
335
|
+
# Format a log message with optional structured context.
|
|
336
|
+
#
|
|
337
|
+
# Combines a message string with key-value context into a single
|
|
338
|
+
# log line. Empty context returns the message unchanged.
|
|
339
|
+
#
|
|
340
|
+
# @param message [String, nil] The message to log
|
|
341
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
342
|
+
# @return [String] Formatted log message
|
|
343
|
+
# @api private
|
|
344
|
+
#
|
|
345
|
+
# @example
|
|
346
|
+
# format_log("User saved", id: 123, duration: 1.5)
|
|
347
|
+
# # => "User saved id=123 duration=1.5"
|
|
348
|
+
#
|
|
349
|
+
def format_log(message, context)
|
|
350
|
+
return message if context.empty?
|
|
351
|
+
parts = [message]
|
|
352
|
+
parts << context.map { |k, v| "#{k}=#{v}" }.join(' ')
|
|
353
|
+
parts.compact.join(' ')
|
|
354
|
+
end
|
|
355
|
+
|
|
299
356
|
# Check if trace logging is enabled via FAMILIA_TRACE environment variable.
|
|
300
357
|
#
|
|
301
358
|
# Trace logging is enabled when FAMILIA_TRACE is set to '1', 'true',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/refinements/stylize_words.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Refinements
|
|
@@ -16,20 +18,6 @@ module Familia
|
|
|
16
18
|
.downcase
|
|
17
19
|
end
|
|
18
20
|
|
|
19
|
-
# Convert from plural to singular form using basic English rules
|
|
20
|
-
def singularize
|
|
21
|
-
word = to_s
|
|
22
|
-
if word.end_with?('ies')
|
|
23
|
-
"#{word[0..-4]}y"
|
|
24
|
-
elsif word.end_with?('es') && word.length > 3
|
|
25
|
-
word[0..-3]
|
|
26
|
-
elsif word.end_with?('s') && word.length > 1
|
|
27
|
-
word[0..-2]
|
|
28
|
-
else
|
|
29
|
-
word
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
21
|
# Convert to camelCase
|
|
34
22
|
def camelize
|
|
35
23
|
_ize(:lower)
|
data/lib/familia/refinements.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/secure_identifier.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'securerandom'
|
|
4
6
|
|
|
@@ -118,6 +120,10 @@ module Familia
|
|
|
118
120
|
|
|
119
121
|
# @private
|
|
120
122
|
#
|
|
123
|
+
# Thread-safe lazy initialization using Concurrent::Map to ensure
|
|
124
|
+
# atomic cache population and consistent calculations across all
|
|
125
|
+
# concurrent ID generation requests.
|
|
126
|
+
#
|
|
121
127
|
# @param bits [Integer] The number of bits of entropy.
|
|
122
128
|
# @param base [Integer] The numeric base (2-36).
|
|
123
129
|
# @return [Integer] The minimum string length required.
|
|
@@ -130,8 +136,10 @@ module Familia
|
|
|
130
136
|
}.freeze
|
|
131
137
|
return hex_lengths[bits] if base == 16 && hex_lengths.key?(bits)
|
|
132
138
|
|
|
133
|
-
@min_length_for_bits_cache ||=
|
|
134
|
-
@min_length_for_bits_cache[
|
|
139
|
+
@min_length_for_bits_cache ||= Concurrent::Map.new
|
|
140
|
+
@min_length_for_bits_cache.fetch_or_store([bits, base]) do
|
|
141
|
+
(bits * Math.log(2) / Math.log(base)).ceil
|
|
142
|
+
end
|
|
135
143
|
end
|
|
136
144
|
end
|
|
137
145
|
end
|
data/lib/familia/settings.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/settings.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
# Familia
|
|
4
6
|
#
|
|
@@ -11,7 +13,7 @@ module Familia
|
|
|
11
13
|
@encryption_keys = nil
|
|
12
14
|
@current_key_version = nil
|
|
13
15
|
@encryption_personalization = 'FamilialMatters'.freeze
|
|
14
|
-
@
|
|
16
|
+
@pipelined_mode = :warn
|
|
15
17
|
|
|
16
18
|
# Familia::Settings
|
|
17
19
|
#
|
|
@@ -118,24 +120,24 @@ module Familia
|
|
|
118
120
|
#
|
|
119
121
|
# @example Setting pipeline mode
|
|
120
122
|
# Familia.configure do |config|
|
|
121
|
-
# config.
|
|
123
|
+
# config.pipelined_mode = :permissive
|
|
122
124
|
# end
|
|
123
125
|
#
|
|
124
|
-
def
|
|
126
|
+
def pipelined_mode(val = nil)
|
|
125
127
|
if val
|
|
126
128
|
unless [:strict, :warn, :permissive].include?(val)
|
|
127
129
|
raise ArgumentError, 'Pipeline mode must be :strict, :warn, or :permissive'
|
|
128
130
|
end
|
|
129
|
-
@
|
|
131
|
+
@pipelined_mode = val
|
|
130
132
|
end
|
|
131
|
-
@
|
|
133
|
+
@pipelined_mode || :warn # default to warn mode
|
|
132
134
|
end
|
|
133
135
|
|
|
134
|
-
def
|
|
136
|
+
def pipelined_mode=(val)
|
|
135
137
|
unless [:strict, :warn, :permissive].include?(val)
|
|
136
138
|
raise ArgumentError, 'Pipeline mode must be :strict, :warn, or :permissive'
|
|
137
139
|
end
|
|
138
|
-
@
|
|
140
|
+
@pipelined_mode = val
|
|
139
141
|
end
|
|
140
142
|
|
|
141
143
|
# Configure Familia settings
|