synthra 0.1.0
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 +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- metadata +478 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Random Number Generator (RNG)
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Thread-safe seeded random number generator for deterministic fake data
|
|
9
|
+
# generation. When the same seed is used, the RNG produces identical
|
|
10
|
+
# sequences of random numbers, enabling reproducible test data.
|
|
11
|
+
#
|
|
12
|
+
# @example Deterministic generation
|
|
13
|
+
# rng1 = RNG.new(12345)
|
|
14
|
+
# rng2 = RNG.new(12345)
|
|
15
|
+
# rng1.int(1, 100) == rng2.int(1, 100) # => true
|
|
16
|
+
#
|
|
17
|
+
# @example Thread-safe usage
|
|
18
|
+
# rng = RNG.new(42)
|
|
19
|
+
# threads = 10.times.map { Thread.new { rng.int(1, 100) } }
|
|
20
|
+
# threads.each(&:join) # Safe - mutex protects internal state
|
|
21
|
+
#
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
module Synthra
|
|
26
|
+
module Generator
|
|
27
|
+
|
|
28
|
+
# Thread-safe seeded random number generator
|
|
29
|
+
#
|
|
30
|
+
# Provides deterministic random number generation using Ruby's Random
|
|
31
|
+
# class with mutex protection for thread safety. All type generators
|
|
32
|
+
# use this RNG to ensure reproducible output.
|
|
33
|
+
#
|
|
34
|
+
# @example Basic usage
|
|
35
|
+
# rng = RNG.new(12345)
|
|
36
|
+
# rng.int(1, 100) # => 42 (always same with seed 12345)
|
|
37
|
+
# rng.boolean(0.7) # => true/false with 70% true probability
|
|
38
|
+
#
|
|
39
|
+
|
|
40
|
+
class RNG
|
|
41
|
+
|
|
42
|
+
# @!attribute [r] seed
|
|
43
|
+
# The seed value used for this RNG
|
|
44
|
+
# @return [Integer] the seed
|
|
45
|
+
|
|
46
|
+
attr_reader :seed
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Create a new RNG instance
|
|
50
|
+
#
|
|
51
|
+
# @param seed [Integer, nil] seed for determinism (nil for random seed)
|
|
52
|
+
#
|
|
53
|
+
# @example With seed
|
|
54
|
+
# rng = RNG.new(12345) # Deterministic
|
|
55
|
+
#
|
|
56
|
+
# @example Without seed
|
|
57
|
+
# rng = RNG.new # Random seed
|
|
58
|
+
#
|
|
59
|
+
|
|
60
|
+
def initialize(seed = nil)
|
|
61
|
+
@seed = seed || Random.new_seed
|
|
62
|
+
@random = Random.new(@seed)
|
|
63
|
+
@mutex = Mutex.new
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Generate a random number
|
|
68
|
+
#
|
|
69
|
+
# Flexible random number generation supporting multiple argument types.
|
|
70
|
+
#
|
|
71
|
+
# @param max [Integer, Range, nil] max value, range, or nil for 0.0-1.0 float
|
|
72
|
+
# @return [Integer, Float] random number
|
|
73
|
+
#
|
|
74
|
+
# @example No argument (0.0 to 1.0)
|
|
75
|
+
# rng.rand # => 0.7342...
|
|
76
|
+
#
|
|
77
|
+
# @example With max (0 to max-1)
|
|
78
|
+
# rng.rand(100) # => 42
|
|
79
|
+
#
|
|
80
|
+
# @example With range
|
|
81
|
+
# rng.rand(1..10) # => 7
|
|
82
|
+
#
|
|
83
|
+
|
|
84
|
+
def rand(max = nil)
|
|
85
|
+
@mutex.synchronize do
|
|
86
|
+
case max
|
|
87
|
+
when nil
|
|
88
|
+
@random.rand
|
|
89
|
+
when Range
|
|
90
|
+
@random.rand(max)
|
|
91
|
+
else
|
|
92
|
+
@random.rand(max)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Generate a random number in a range (alias)
|
|
99
|
+
#
|
|
100
|
+
# @param range [Range] the range
|
|
101
|
+
# @return [Numeric] random number in range
|
|
102
|
+
#
|
|
103
|
+
|
|
104
|
+
def rand_range(range)
|
|
105
|
+
rand(range)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Pick a random element from an array
|
|
110
|
+
#
|
|
111
|
+
# @param array [Array] the array to sample from
|
|
112
|
+
# @return [Object, nil] random element or nil if empty
|
|
113
|
+
#
|
|
114
|
+
# @example
|
|
115
|
+
# rng.sample(["a", "b", "c"]) # => "b"
|
|
116
|
+
#
|
|
117
|
+
|
|
118
|
+
def sample(array)
|
|
119
|
+
return nil if array.empty?
|
|
120
|
+
|
|
121
|
+
@mutex.synchronize do
|
|
122
|
+
array.sample(random: @random)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Sample multiple elements from an array
|
|
128
|
+
#
|
|
129
|
+
# @param array [Array] the array to sample from
|
|
130
|
+
# @param count [Integer] number of elements to sample
|
|
131
|
+
# @return [Array] sampled elements
|
|
132
|
+
#
|
|
133
|
+
# @example
|
|
134
|
+
# rng.sample_many(["a", "b", "c", "d"], 2) # => ["c", "a"]
|
|
135
|
+
#
|
|
136
|
+
|
|
137
|
+
def sample_many(array, count)
|
|
138
|
+
@mutex.synchronize do
|
|
139
|
+
array.sample(count, random: @random)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Shuffle an array
|
|
145
|
+
#
|
|
146
|
+
# Returns a new shuffled array; does not modify original.
|
|
147
|
+
#
|
|
148
|
+
# @param array [Array] the array to shuffle
|
|
149
|
+
# @return [Array] shuffled copy
|
|
150
|
+
#
|
|
151
|
+
# @example
|
|
152
|
+
# rng.shuffle([1, 2, 3, 4]) # => [3, 1, 4, 2]
|
|
153
|
+
#
|
|
154
|
+
|
|
155
|
+
def shuffle(array)
|
|
156
|
+
@mutex.synchronize do
|
|
157
|
+
array.shuffle(random: @random)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Generate a random boolean
|
|
163
|
+
#
|
|
164
|
+
# @param true_probability [Float] probability of true (0.0 to 1.0)
|
|
165
|
+
# @return [Boolean] random boolean
|
|
166
|
+
#
|
|
167
|
+
# @example 50/50 chance
|
|
168
|
+
# rng.boolean # => true or false
|
|
169
|
+
#
|
|
170
|
+
# @example 70% true
|
|
171
|
+
# rng.boolean(0.7) # => true (70% of the time)
|
|
172
|
+
#
|
|
173
|
+
|
|
174
|
+
def boolean(true_probability = 0.5)
|
|
175
|
+
rand < true_probability
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Select based on weighted probabilities
|
|
180
|
+
#
|
|
181
|
+
# @param options [Hash] mapping of values to weights (any positive numbers)
|
|
182
|
+
# @return [Object, nil] selected value based on weights
|
|
183
|
+
#
|
|
184
|
+
# @example
|
|
185
|
+
# rng.weighted_choice({ "common" => 80, "rare" => 20 })
|
|
186
|
+
# # => "common" (80% chance) or "rare" (20% chance)
|
|
187
|
+
#
|
|
188
|
+
|
|
189
|
+
def weighted_choice(options)
|
|
190
|
+
return nil if options.empty?
|
|
191
|
+
|
|
192
|
+
total = options.values.sum.to_f
|
|
193
|
+
target = rand * total
|
|
194
|
+
cumulative = 0.0
|
|
195
|
+
|
|
196
|
+
options.each do |value, weight|
|
|
197
|
+
cumulative += weight
|
|
198
|
+
return value if target <= cumulative
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Fallback (shouldn't happen but handles float precision)
|
|
203
|
+
options.keys.last
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Generate a random integer in inclusive range
|
|
208
|
+
#
|
|
209
|
+
# @param min [Integer] minimum value (inclusive)
|
|
210
|
+
# @param max [Integer] maximum value (inclusive)
|
|
211
|
+
# @return [Integer] random integer
|
|
212
|
+
#
|
|
213
|
+
# @example
|
|
214
|
+
# rng.int(1, 10) # => 7 (between 1 and 10 inclusive)
|
|
215
|
+
#
|
|
216
|
+
|
|
217
|
+
def int(min, max)
|
|
218
|
+
rand(min..max)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# Generate a random float in range
|
|
223
|
+
#
|
|
224
|
+
# @param min [Float] minimum value
|
|
225
|
+
# @param max [Float] maximum value
|
|
226
|
+
# @return [Float] random float
|
|
227
|
+
#
|
|
228
|
+
# @example
|
|
229
|
+
# rng.float(0.0, 1.0) # => 0.7342...
|
|
230
|
+
#
|
|
231
|
+
|
|
232
|
+
def float(min, max)
|
|
233
|
+
min + rand * (max - min)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# Create a duplicate RNG with the same seed
|
|
238
|
+
#
|
|
239
|
+
# Creates a new RNG that will produce the same sequence.
|
|
240
|
+
#
|
|
241
|
+
# @return [RNG] new RNG with same seed
|
|
242
|
+
#
|
|
243
|
+
# @example
|
|
244
|
+
# rng2 = rng.dup
|
|
245
|
+
# rng2.int(1, 100) == rng.int(1, 100) # Both start fresh
|
|
246
|
+
#
|
|
247
|
+
|
|
248
|
+
def dup
|
|
249
|
+
self.class.new(@seed)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Reset the RNG to initial state
|
|
254
|
+
#
|
|
255
|
+
# Re-initializes the internal random generator to start sequence over.
|
|
256
|
+
#
|
|
257
|
+
# @return [void]
|
|
258
|
+
#
|
|
259
|
+
# @example
|
|
260
|
+
# rng.int(1, 100) # => 42
|
|
261
|
+
# rng.int(1, 100) # => 73
|
|
262
|
+
# rng.reset!
|
|
263
|
+
# rng.int(1, 100) # => 42 (back to start)
|
|
264
|
+
#
|
|
265
|
+
|
|
266
|
+
def reset!
|
|
267
|
+
@mutex.synchronize do
|
|
268
|
+
@random = Random.new(@seed)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Generator
|
|
5
|
+
|
|
6
|
+
# Streaming generator for large batches
|
|
7
|
+
class Streamer
|
|
8
|
+
def initialize(schema, registry: nil, **options)
|
|
9
|
+
@schema = schema
|
|
10
|
+
@registry = registry
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Generate records as a stream
|
|
16
|
+
#
|
|
17
|
+
# Returns an Enumerator that generates records on-demand. The Engine
|
|
18
|
+
# instance is reused across all records in the stream for efficiency.
|
|
19
|
+
#
|
|
20
|
+
# @param count [Integer] number of records
|
|
21
|
+
# @return [Enumerator] lazy enumerator of records
|
|
22
|
+
#
|
|
23
|
+
# @example Stream processing
|
|
24
|
+
# streamer = Streamer.new(schema, seed: 42)
|
|
25
|
+
# streamer.generate_stream(count: 1000).each do |record|
|
|
26
|
+
# process(record) # Records generated one at a time
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
def generate_stream(count:)
|
|
31
|
+
seed = @options[:seed]
|
|
32
|
+
mode = @options[:mode] || :random
|
|
33
|
+
overrides = @options[:overrides] || {}
|
|
34
|
+
|
|
35
|
+
Enumerator.new(count) do |yielder|
|
|
36
|
+
|
|
37
|
+
# Reuse single Engine instance for efficiency
|
|
38
|
+
engine = Engine.new(@schema, registry: @registry)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Initialize RNG once with seed if provided
|
|
42
|
+
# This ensures deterministic streaming - all records use the same RNG sequence
|
|
43
|
+
if seed
|
|
44
|
+
|
|
45
|
+
# Initialize RNG and FakerAdapter without generating a record
|
|
46
|
+
rng = Synthra::Generator::RNG.new(seed)
|
|
47
|
+
engine.instance_variable_set(:@rng, rng)
|
|
48
|
+
engine.instance_variable_set(:@faker_adapter,
|
|
49
|
+
Synthra::Generator::FakerAdapter.new(rng))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
count.times do
|
|
53
|
+
|
|
54
|
+
# Continue RNG sequence without resetting (seed: nil)
|
|
55
|
+
record = engine.generate(seed: nil, mode: mode, overrides: overrides)
|
|
56
|
+
yielder << record
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Generator
|
|
5
|
+
|
|
6
|
+
# Tracks unique values for uniqueness constraints
|
|
7
|
+
class Uniqueness
|
|
8
|
+
def initialize(max_retries: nil, schema_name: nil)
|
|
9
|
+
@max_retries = max_retries || Synthra.configuration.max_unique_retries
|
|
10
|
+
@schema_name = schema_name
|
|
11
|
+
@values = Hash.new { |h, k| h[k] = Set.new }
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Track a generated value
|
|
17
|
+
#
|
|
18
|
+
# @param field [String] field name
|
|
19
|
+
# @param value [Object] generated value
|
|
20
|
+
# @return [void]
|
|
21
|
+
|
|
22
|
+
def track(field, value)
|
|
23
|
+
@mutex.synchronize do
|
|
24
|
+
@values[field.to_s].add(value)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Check if a value is unique for a field
|
|
30
|
+
#
|
|
31
|
+
# @param field [String] field name
|
|
32
|
+
# @param value [Object] value to check
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
|
|
35
|
+
def unique?(field, value)
|
|
36
|
+
@mutex.synchronize do
|
|
37
|
+
!@values[field.to_s].include?(value)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Generate a unique value
|
|
43
|
+
#
|
|
44
|
+
# @param field [String] field name
|
|
45
|
+
# @yield block that generates a value
|
|
46
|
+
# @return [Object] unique value
|
|
47
|
+
# @raise [UniquenessError] if unable to generate unique value
|
|
48
|
+
|
|
49
|
+
def generate_unique(field)
|
|
50
|
+
last_values = []
|
|
51
|
+
@max_retries.times do
|
|
52
|
+
value = yield
|
|
53
|
+
if unique?(field, value)
|
|
54
|
+
track(field, value)
|
|
55
|
+
return value
|
|
56
|
+
end
|
|
57
|
+
last_values << value
|
|
58
|
+
last_values.shift if last_values.length > 10
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
raise UniquenessError.new(
|
|
62
|
+
field: field,
|
|
63
|
+
attempts: @max_retries,
|
|
64
|
+
last_values: last_values,
|
|
65
|
+
schema_name: @schema_name
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Clear all tracked values
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
|
|
74
|
+
def clear
|
|
75
|
+
@mutex.synchronize do
|
|
76
|
+
@values.clear
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Clear values for a specific field
|
|
82
|
+
#
|
|
83
|
+
# @param field [String] field name
|
|
84
|
+
# @return [void]
|
|
85
|
+
|
|
86
|
+
def clear_field(field)
|
|
87
|
+
@mutex.synchronize do
|
|
88
|
+
@values.delete(field.to_s)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Get all tracked values for a field
|
|
94
|
+
#
|
|
95
|
+
# @param field [String] field name
|
|
96
|
+
# @return [Set]
|
|
97
|
+
|
|
98
|
+
def values_for(field)
|
|
99
|
+
@mutex.synchronize do
|
|
100
|
+
@values[field.to_s].dup
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Number of unique values tracked for a field
|
|
106
|
+
#
|
|
107
|
+
# @param field [String] field name
|
|
108
|
+
# @return [Integer]
|
|
109
|
+
|
|
110
|
+
def count_for(field)
|
|
111
|
+
@mutex.synchronize do
|
|
112
|
+
@values[field.to_s].size
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Synthra - Eager Loading and Initialization
|
|
5
|
+
# =============================================================================
|
|
6
|
+
|
|
7
|
+
# NOTE: Do not create a new Zeitwerk loader here - this file is autoloaded
|
|
8
|
+
# by the main gem loader in synthra.rb. Creating another loader would
|
|
9
|
+
# cause a conflict.
|
|
10
|
+
|
|
11
|
+
# Load core utilities
|
|
12
|
+
require_relative "utils/string_distance"
|
|
13
|
+
require_relative "validator/path_validator"
|
|
14
|
+
|
|
15
|
+
# Export system
|
|
16
|
+
require_relative "export"
|
|
17
|
+
require_relative "export/type_mapping"
|
|
18
|
+
|
|
19
|
+
# LSP and REPL
|
|
20
|
+
require_relative "lsp/server"
|
|
21
|
+
require_relative "repl/formatter"
|
|
22
|
+
require_relative "repl/enhanced_repl"
|
|
23
|
+
|
|
24
|
+
# Hostile payloads
|
|
25
|
+
require_relative "types/hostile_payloads"
|
|
26
|
+
|
|
27
|
+
# New features
|
|
28
|
+
require_relative "schema_inheritance"
|
|
29
|
+
require_relative "factory_bot_integration"
|
|
30
|
+
require_relative "snapshot_testing"
|
|
31
|
+
|
|
32
|
+
# Phase 2 features
|
|
33
|
+
require_relative "rails_test_helper"
|
|
34
|
+
require_relative "mixin"
|
|
35
|
+
require_relative "relationships"
|
|
36
|
+
require_relative "schema_versioning"
|
|
37
|
+
require_relative "openapi_importer"
|
|
38
|
+
require_relative "database_seeder"
|
|
39
|
+
require_relative "live_preview"
|
|
40
|
+
require_relative "documentation_generator"
|
|
41
|
+
require_relative "deterministic_ids"
|
|
42
|
+
|
|
43
|
+
# Production-grade features
|
|
44
|
+
require_relative "export/protobuf"
|
|
45
|
+
require_relative "export/openapi"
|
|
46
|
+
require_relative "mock_server"
|
|
47
|
+
require_relative "contracts_registry"
|
|
48
|
+
require_relative "performance_mode"
|
|
49
|
+
require_relative "api_server"
|
|
50
|
+
|
|
51
|
+
# Rails-tier features
|
|
52
|
+
require_relative "time_travel"
|
|
53
|
+
require_relative "scenarios"
|
|
54
|
+
require_relative "personas"
|
|
55
|
+
require_relative "quality_metrics"
|
|
56
|
+
require_relative "webhook_simulator"
|
|
57
|
+
require_relative "migration_generator"
|
|
58
|
+
require_relative "export/graphql_federation"
|
|
59
|
+
require_relative "export/terraform"
|
|
60
|
+
|
|
61
|
+
# Rails Engine (only if Rails is available)
|
|
62
|
+
if defined?(Rails::Engine)
|
|
63
|
+
require_relative "engine"
|
|
64
|
+
require_relative "activerecord_inference"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# CLI command pattern
|
|
68
|
+
require_relative "cli/commands/base"
|
|
69
|
+
require_relative "cli/commands/validate"
|
|
70
|
+
require_relative "cli/commands/lint"
|
|
71
|
+
require_relative "cli/commands/generate"
|
|
72
|
+
require_relative "cli/commands/export"
|
|
73
|
+
require_relative "cli/commands/diff"
|
|
74
|
+
require_relative "cli/commands/import"
|
|
75
|
+
require_relative "cli/commands/docs"
|
|
76
|
+
require_relative "cli/commands/live"
|
|
77
|
+
require_relative "cli/commands/seed"
|
|
78
|
+
|
|
79
|
+
# Load domain folder type files
|
|
80
|
+
IGNORED_DOMAIN_FOLDERS = %w[
|
|
81
|
+
address_location finance_banking technology_internet commerce_products
|
|
82
|
+
media_entertainment personal_names text_content health_medical travel
|
|
83
|
+
date_time crypto core
|
|
84
|
+
].freeze
|
|
85
|
+
|
|
86
|
+
IGNORED_DOMAIN_FOLDERS.each do |folder|
|
|
87
|
+
dir = File.join(__dir__, "types/#{folder}")
|
|
88
|
+
next unless Dir.exist?(dir)
|
|
89
|
+
|
|
90
|
+
Dir.glob(File.join(dir, "*.rb")).sort.each do |file|
|
|
91
|
+
require_relative file.sub(%r{^#{__dir__}/}, "")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Register built-in types and behaviors
|
|
96
|
+
Synthra.register_builtin_types!
|
|
97
|
+
Synthra::Behaviors::Registry.register_builtins!
|