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.
Files changed (302) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +40 -0
  4. data/.yardopts +18 -0
  5. data/ACCESS_DOCS.md +116 -0
  6. data/ADVANCED_FEATURES_SUMMARY.md +245 -0
  7. data/CHANGELOG.md +498 -0
  8. data/CODE_OF_CONDUCT.md +122 -0
  9. data/CONTRIBUTING.md +307 -0
  10. data/DOCUMENTATION.md +204 -0
  11. data/DOCUMENTATION_GUIDE.md +194 -0
  12. data/LICENSE +22 -0
  13. data/README.md +1458 -0
  14. data/Rakefile +128 -0
  15. data/benchmark/README.md +591 -0
  16. data/benchmark/batch_generation.rb +63 -0
  17. data/benchmark/engine_comparison.rb +295 -0
  18. data/benchmark/single_record.rb +108 -0
  19. data/benchmark/streaming.rb +74 -0
  20. data/docs/ACTIVERECORD_INFERENCE.md +524 -0
  21. data/docs/API_SERVER.md +278 -0
  22. data/docs/CONFIG_FILE.md +315 -0
  23. data/docs/DATA_CONTRACTS.md +312 -0
  24. data/docs/ENHANCED_REPL.md +304 -0
  25. data/docs/FACTORY_BOT.md +271 -0
  26. data/docs/GITHUB_ACTION.md +420 -0
  27. data/docs/GRAPHQL_EXPORT.md +303 -0
  28. data/docs/GRAPHQL_FEDERATION.md +651 -0
  29. data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
  30. data/docs/LSP.md +189 -0
  31. data/docs/MIGRATION_GENERATOR.md +547 -0
  32. data/docs/MOCK_SERVER.md +280 -0
  33. data/docs/NATIVE_ENGINE.md +298 -0
  34. data/docs/OPENAPI_EXPORT.md +252 -0
  35. data/docs/PERFORMANCE_MODE.md +228 -0
  36. data/docs/PERSONAS.md +547 -0
  37. data/docs/PROPERTY_TESTING.md +284 -0
  38. data/docs/PROTOBUF_EXPORT.md +276 -0
  39. data/docs/QUALITY_METRICS.md +555 -0
  40. data/docs/QUICK_REFERENCE.md +88 -0
  41. data/docs/RAILS_ENGINE.md +404 -0
  42. data/docs/RAILS_INTEGRATION.md +454 -0
  43. data/docs/README.md +175 -0
  44. data/docs/REPL_QUICK_REFERENCE.md +149 -0
  45. data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
  46. data/docs/SCENARIOS.md +576 -0
  47. data/docs/SECURITY_TESTING.md +318 -0
  48. data/docs/SNAPSHOT_TESTING.md +308 -0
  49. data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
  50. data/docs/TERRAFORM_EXPORT.md +447 -0
  51. data/docs/TIME_TRAVEL.md +507 -0
  52. data/docs/USAGE_GUIDE.md +980 -0
  53. data/docs/WEBHOOK_SIMULATOR.md +579 -0
  54. data/docs/advanced_features.md +183 -0
  55. data/docs/api_reference.md +153 -0
  56. data/docs/behaviors.md +138 -0
  57. data/docs/best_practices.md +191 -0
  58. data/docs/dsl_reference.md +172 -0
  59. data/docs/examples.md +162 -0
  60. data/docs/generation_modes.md +125 -0
  61. data/docs/getting_started.md +92 -0
  62. data/docs/index.html +177 -0
  63. data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
  64. data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
  65. data/docs/troubleshooting.md +215 -0
  66. data/docs/type_reference.md +224 -0
  67. data/examples/hostile_mode_demo.rb +64 -0
  68. data/exe/synthra +6 -0
  69. data/ext/synthra_native/Cargo.lock +972 -0
  70. data/ext/synthra_native/Cargo.toml +46 -0
  71. data/ext/synthra_native/extconf.rb +41 -0
  72. data/ext/synthra_native/src/generator.rs +397 -0
  73. data/ext/synthra_native/src/lib.rs +229 -0
  74. data/ext/synthra_native/src/types.rs +1485 -0
  75. data/lib/generators/synthra/install_generator.rb +86 -0
  76. data/lib/generators/synthra/templates/api_response.dsl +15 -0
  77. data/lib/generators/synthra/templates/fake_data.rake +116 -0
  78. data/lib/generators/synthra/templates/synthra.yml +31 -0
  79. data/lib/generators/synthra/templates/synthra_support.rb +19 -0
  80. data/lib/generators/synthra/templates/user.dsl +11 -0
  81. data/lib/synthra/activerecord_inference.rb +394 -0
  82. data/lib/synthra/api.rb +381 -0
  83. data/lib/synthra/api_server.rb +718 -0
  84. data/lib/synthra/behaviors/applicator.rb +165 -0
  85. data/lib/synthra/behaviors/base.rb +155 -0
  86. data/lib/synthra/behaviors/close_connection.rb +60 -0
  87. data/lib/synthra/behaviors/deprecated.rb +100 -0
  88. data/lib/synthra/behaviors/failure.rb +69 -0
  89. data/lib/synthra/behaviors/latency.rb +103 -0
  90. data/lib/synthra/behaviors/partial_data.rb +83 -0
  91. data/lib/synthra/behaviors/randomize_order.rb +74 -0
  92. data/lib/synthra/behaviors/registry.rb +238 -0
  93. data/lib/synthra/behaviors/simulate_error.rb +79 -0
  94. data/lib/synthra/cli/commands/base.rb +105 -0
  95. data/lib/synthra/cli/commands/diff.rb +227 -0
  96. data/lib/synthra/cli/commands/docs.rb +67 -0
  97. data/lib/synthra/cli/commands/export.rb +150 -0
  98. data/lib/synthra/cli/commands/generate.rb +72 -0
  99. data/lib/synthra/cli/commands/import.rb +80 -0
  100. data/lib/synthra/cli/commands/lint.rb +53 -0
  101. data/lib/synthra/cli/commands/live.rb +57 -0
  102. data/lib/synthra/cli/commands/seed.rb +99 -0
  103. data/lib/synthra/cli/commands/validate.rb +28 -0
  104. data/lib/synthra/cli.rb +2471 -0
  105. data/lib/synthra/config_file.rb +129 -0
  106. data/lib/synthra/configuration.rb +281 -0
  107. data/lib/synthra/contracts_registry.rb +408 -0
  108. data/lib/synthra/database_seeder.rb +268 -0
  109. data/lib/synthra/deterministic_ids.rb +218 -0
  110. data/lib/synthra/documentation_generator.rb +414 -0
  111. data/lib/synthra/engine.rb +251 -0
  112. data/lib/synthra/errors.rb +1169 -0
  113. data/lib/synthra/export/base.rb +85 -0
  114. data/lib/synthra/export/csv.rb +101 -0
  115. data/lib/synthra/export/graphql.rb +266 -0
  116. data/lib/synthra/export/graphql_federation.rb +377 -0
  117. data/lib/synthra/export/graphviz.rb +258 -0
  118. data/lib/synthra/export/javascript.rb +327 -0
  119. data/lib/synthra/export/json_data.rb +61 -0
  120. data/lib/synthra/export/json_schema.rb +290 -0
  121. data/lib/synthra/export/openapi.rb +514 -0
  122. data/lib/synthra/export/protobuf.rb +483 -0
  123. data/lib/synthra/export/python.rb +560 -0
  124. data/lib/synthra/export/sql.rb +381 -0
  125. data/lib/synthra/export/sql_insert.rb +152 -0
  126. data/lib/synthra/export/terraform.rb +501 -0
  127. data/lib/synthra/export/type_mapping.rb +389 -0
  128. data/lib/synthra/export/typescript.rb +300 -0
  129. data/lib/synthra/export/xml_data.rb +104 -0
  130. data/lib/synthra/export/yaml_data.rb +56 -0
  131. data/lib/synthra/export.rb +404 -0
  132. data/lib/synthra/factory_bot_integration.rb +157 -0
  133. data/lib/synthra/field.rb +440 -0
  134. data/lib/synthra/functions/registry.rb +104 -0
  135. data/lib/synthra/generator/context.rb +372 -0
  136. data/lib/synthra/generator/engine.rb +336 -0
  137. data/lib/synthra/generator/faker_adapter.rb +425 -0
  138. data/lib/synthra/generator/modes.rb +444 -0
  139. data/lib/synthra/generator/resolver.rb +256 -0
  140. data/lib/synthra/generator/rng.rb +273 -0
  141. data/lib/synthra/generator/streamer.rb +63 -0
  142. data/lib/synthra/generator/uniqueness.rb +118 -0
  143. data/lib/synthra/initializer.rb +97 -0
  144. data/lib/synthra/limits.rb +279 -0
  145. data/lib/synthra/live_preview.rb +518 -0
  146. data/lib/synthra/loader_config.rb +78 -0
  147. data/lib/synthra/lsp/server.rb +689 -0
  148. data/lib/synthra/migration_generator.rb +301 -0
  149. data/lib/synthra/mixin.rb +174 -0
  150. data/lib/synthra/mock_server.rb +458 -0
  151. data/lib/synthra/native_engine.rb +304 -0
  152. data/lib/synthra/openapi_importer.rb +228 -0
  153. data/lib/synthra/output/json_formatter.rb +40 -0
  154. data/lib/synthra/output/ndjson_formatter.rb +55 -0
  155. data/lib/synthra/parser/ast.rb +1287 -0
  156. data/lib/synthra/parser/lexer.rb +1152 -0
  157. data/lib/synthra/parser/parser.rb +1664 -0
  158. data/lib/synthra/parser/tokens.rb +461 -0
  159. data/lib/synthra/performance_mode.rb +364 -0
  160. data/lib/synthra/personas.rb +397 -0
  161. data/lib/synthra/property_testing.rb +172 -0
  162. data/lib/synthra/quality_metrics.rb +405 -0
  163. data/lib/synthra/rails_test_helper.rb +248 -0
  164. data/lib/synthra/registry.rb +533 -0
  165. data/lib/synthra/relationships.rb +193 -0
  166. data/lib/synthra/repl/enhanced_repl.rb +607 -0
  167. data/lib/synthra/repl/formatter.rb +191 -0
  168. data/lib/synthra/scenarios.rb +423 -0
  169. data/lib/synthra/schema.rb +605 -0
  170. data/lib/synthra/schema_inheritance.rb +104 -0
  171. data/lib/synthra/schema_versioning.rb +212 -0
  172. data/lib/synthra/snapshot_testing.rb +199 -0
  173. data/lib/synthra/time_travel.rb +338 -0
  174. data/lib/synthra/type_definitions.rb +274 -0
  175. data/lib/synthra/types/address_location/addresses.rb +125 -0
  176. data/lib/synthra/types/address_location/airports.rb +200 -0
  177. data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
  178. data/lib/synthra/types/address_location/locations.rb +406 -0
  179. data/lib/synthra/types/base.rb +48 -0
  180. data/lib/synthra/types/commerce_products/commerce.rb +119 -0
  181. data/lib/synthra/types/commerce_products/companies.rb +71 -0
  182. data/lib/synthra/types/commerce_products/construction.rb +126 -0
  183. data/lib/synthra/types/commerce_products/products.rb +205 -0
  184. data/lib/synthra/types/core/collections.rb +513 -0
  185. data/lib/synthra/types/core/defaults.rb +76 -0
  186. data/lib/synthra/types/core/enums.rb +102 -0
  187. data/lib/synthra/types/core/identifiers.rb +445 -0
  188. data/lib/synthra/types/core/primitives.rb +586 -0
  189. data/lib/synthra/types/core/references.rb +466 -0
  190. data/lib/synthra/types/core/sequences.rb +304 -0
  191. data/lib/synthra/types/crypto/crypto.rb +162 -0
  192. data/lib/synthra/types/date_time/dates.rb +387 -0
  193. data/lib/synthra/types/finance_banking/banking.rb +424 -0
  194. data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
  195. data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
  196. data/lib/synthra/types/formula.rb +184 -0
  197. data/lib/synthra/types/health_medical/medical.rb +150 -0
  198. data/lib/synthra/types/hostile_payloads.rb +122 -0
  199. data/lib/synthra/types/json_array.rb +51 -0
  200. data/lib/synthra/types/media_entertainment/media.rb +62 -0
  201. data/lib/synthra/types/naughty_string.rb +30 -0
  202. data/lib/synthra/types/personal_names/chinese.rb +41 -0
  203. data/lib/synthra/types/personal_names/identifiers.rb +187 -0
  204. data/lib/synthra/types/personal_names/names.rb +246 -0
  205. data/lib/synthra/types/personal_names/national_id.rb +89 -0
  206. data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
  207. data/lib/synthra/types/regex.rb +248 -0
  208. data/lib/synthra/types/registry.rb +182 -0
  209. data/lib/synthra/types/repeating_element.rb +50 -0
  210. data/lib/synthra/types/scenario.rb +29 -0
  211. data/lib/synthra/types/technology_internet/apps.rb +67 -0
  212. data/lib/synthra/types/technology_internet/communication.rb +162 -0
  213. data/lib/synthra/types/technology_internet/devices.rb +80 -0
  214. data/lib/synthra/types/technology_internet/formats.rb +139 -0
  215. data/lib/synthra/types/technology_internet/networking.rb +143 -0
  216. data/lib/synthra/types/template.rb +92 -0
  217. data/lib/synthra/types/text_content/business.rb +128 -0
  218. data/lib/synthra/types/text_content/colors.rb +70 -0
  219. data/lib/synthra/types/text_content/misc.rb +237 -0
  220. data/lib/synthra/types/text_content/security.rb +130 -0
  221. data/lib/synthra/types/text_content/text_generation.rb +532 -0
  222. data/lib/synthra/types/travel/travel.rb +150 -0
  223. data/lib/synthra/utils/string_distance.rb +133 -0
  224. data/lib/synthra/validator/dsl_validator.rb +339 -0
  225. data/lib/synthra/validator/path_validator.rb +615 -0
  226. data/lib/synthra/version.rb +35 -0
  227. data/lib/synthra/webhook_simulator.rb +341 -0
  228. data/lib/synthra.rb +259 -0
  229. data/schemas/address.dsl +16 -0
  230. data/schemas/api_response.dsl +8 -0
  231. data/schemas/error_payload.dsl +9 -0
  232. data/schemas/order.dsl +10 -0
  233. data/schemas/order_item.dsl +8 -0
  234. data/schemas/payment.dsl +11 -0
  235. data/schemas/social_post.dsl +13 -0
  236. data/schemas/user.dsl +10 -0
  237. data/scripts/batch_fix_all.rb +96 -0
  238. data/scripts/delete_old_files.rb +32 -0
  239. data/scripts/fix_all_grouped_files.rb +137 -0
  240. data/scripts/fix_all_indentation.rb +48 -0
  241. data/scripts/fix_all_syntax.rb +124 -0
  242. data/scripts/fix_grouped_files.rb +184 -0
  243. data/scripts/fix_syntax_errors.rb +108 -0
  244. data/scripts/group_domain_types.rb +112 -0
  245. data/scripts/group_domain_types_fixed.rb +150 -0
  246. data/scripts/merge_domains_to_one_file.rb +68 -0
  247. data/scripts/move_existing_types.rb +130 -0
  248. data/scripts/split_grouped_types.rb +142 -0
  249. data/tech_docs/README.md +134 -0
  250. data/tech_docs/advanced/streaming.md +405 -0
  251. data/tech_docs/advanced/thread_safety.md +253 -0
  252. data/tech_docs/api/overview.md +485 -0
  253. data/tech_docs/appendices/type_chart.md +193 -0
  254. data/tech_docs/basic_concepts.md +383 -0
  255. data/tech_docs/behaviors/overview.md +346 -0
  256. data/tech_docs/dsl/complex_types.md +790 -0
  257. data/tech_docs/dsl/core_types.md +464 -0
  258. data/tech_docs/dsl/datetime_types.md +325 -0
  259. data/tech_docs/dsl/field_modifiers.md +414 -0
  260. data/tech_docs/dsl/grammar.md +431 -0
  261. data/tech_docs/dsl/schema_definition.md +399 -0
  262. data/tech_docs/export/README.md +276 -0
  263. data/tech_docs/installation.md +273 -0
  264. data/tech_docs/integration/ci_cd.md +707 -0
  265. data/tech_docs/integration/ci_cd_guide.md +579 -0
  266. data/tech_docs/integration/factory_bot.md +485 -0
  267. data/tech_docs/integration/rails.md +630 -0
  268. data/tech_docs/integration/rspec.md +449 -0
  269. data/tech_docs/modes/overview.md +350 -0
  270. data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
  271. data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
  272. data/tech_docs/quick_start.md +256 -0
  273. data/tech_docs/templates/README.md +805 -0
  274. data/tech_docs/tutorials/advanced.md +371 -0
  275. data/tech_docs/tutorials/getting_started.md +189 -0
  276. data/tech_docs/tutorials/intermediate.md +231 -0
  277. data/tech_docs/tutorials/template_gallery.md +569 -0
  278. data/tech_example/01_basic_usage.rb +238 -0
  279. data/tech_example/02_types_demo.rb +336 -0
  280. data/tech_example/04_cli_usage.md +429 -0
  281. data/tech_example/05_database_seeding.rb +283 -0
  282. data/tech_example/07_rspec_integration.rb +359 -0
  283. data/tech_example/10_custom_types.rb +387 -0
  284. data/tech_example/12_behaviors.rb +372 -0
  285. data/tech_example/13_twitter_dm_example.rb +120 -0
  286. data/tech_example/14_exact_json_structure.rb +253 -0
  287. data/tech_example/16_lsp_server.rb +56 -0
  288. data/tech_example/17_property_testing.rb +92 -0
  289. data/tech_example/18_enhanced_repl.rb +120 -0
  290. data/tech_example/19_security_fuzzing.rb +145 -0
  291. data/tech_example/NEW_FEATURES.md +169 -0
  292. data/tech_example/README.md +118 -0
  293. data/tech_example/schemas/api_response.dsl +123 -0
  294. data/tech_example/schemas/ecommerce.dsl +121 -0
  295. data/tech_example/schemas/twitter_dm.dsl +109 -0
  296. data/tech_example/schemas/user.dsl +36 -0
  297. data/vscode-extension/README.md +246 -0
  298. data/vscode-extension/language-configuration.json +31 -0
  299. data/vscode-extension/package.json +55 -0
  300. data/vscode-extension/snippets/fakedatadsl.json +198 -0
  301. data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
  302. metadata +478 -0
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ # High-performance native engine using Rust + fake-rs
5
+ #
6
+ # Provides 100-250x speedup over pure Ruby implementation by using
7
+ # a native Rust extension with the fake-rs library.
8
+ #
9
+ # @example Check availability
10
+ # if Synthra::NativeEngine.available?
11
+ # puts "Native engine is available!"
12
+ # end
13
+ #
14
+ # @example Basic usage
15
+ # schema = Synthra.parse("User:\n id: uuid\n name: name")
16
+ # engine = Synthra::NativeEngine.new(schema)
17
+ # records = engine.generate_many(1000)
18
+ #
19
+ # @example Generate to file
20
+ # engine.generate_to_file(10_000_000, "users.jsonl", format: "jsonl")
21
+ #
22
+ class NativeEngine
23
+ class << self
24
+ # Check if native extension is available
25
+ #
26
+ # @return [Boolean] true if the native extension is loaded and functional
27
+ def available?
28
+ return @available if defined?(@available)
29
+
30
+ @available = begin
31
+ load_native_extension
32
+ defined?(Synthra::Native) &&
33
+ Native.respond_to?(:loaded?) &&
34
+ Native.loaded?
35
+ rescue LoadError, NameError
36
+ false
37
+ end
38
+ end
39
+
40
+ # Get list of types supported by native engine
41
+ #
42
+ # @return [Array<String>] list of type names
43
+ def available_types
44
+ return [] unless available?
45
+
46
+ Native.available_types
47
+ end
48
+
49
+ # Get list of locales supported by native engine
50
+ #
51
+ # @return [Array<String>] list of locale codes
52
+ def available_locales
53
+ return [] unless available?
54
+
55
+ Native.available_locales
56
+ end
57
+
58
+ # Get native extension version
59
+ #
60
+ # @return [String, nil] version string or nil if not available
61
+ def version
62
+ return nil unless available?
63
+
64
+ Native.version
65
+ end
66
+
67
+ # Get performance information
68
+ #
69
+ # @return [String, nil] performance info or nil if not available
70
+ def performance_info
71
+ return nil unless available?
72
+
73
+ Native.performance_info
74
+ end
75
+
76
+ # Get the current number of threads used for parallel generation
77
+ #
78
+ # @return [Integer, nil] thread count or nil if not available
79
+ def thread_count
80
+ return nil unless available?
81
+
82
+ Native.thread_count
83
+ end
84
+
85
+ # Set the number of threads to use for parallel generation
86
+ # Must be called before any generation operations.
87
+ #
88
+ # @param num_threads [Integer] number of threads (0 = use all cores)
89
+ # @return [Integer] actual number of threads configured
90
+ #
91
+ # @example Limit to 4 threads
92
+ # Synthra::NativeEngine.set_threads(4)
93
+ #
94
+ # @note Alternatively, set RAYON_NUM_THREADS env var before loading:
95
+ # RAYON_NUM_THREADS=4 ruby my_script.rb
96
+ def set_threads(num_threads)
97
+ return nil unless available?
98
+
99
+ Native.set_threads(num_threads)
100
+ end
101
+
102
+ private
103
+
104
+ def load_native_extension
105
+ require "synthra/synthra_native"
106
+ rescue LoadError => e
107
+ # Check if it's a missing extension or missing dependencies
108
+ if e.message.include?("cannot load such file")
109
+ raise LoadError, <<~MSG
110
+ Native extension not compiled. To enable native performance:
111
+
112
+ 1. Install Rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
113
+ 2. Install rb_sys: gem install rb_sys
114
+ 3. Compile extension: bundle exec rake compile
115
+
116
+ Original error: #{e.message}
117
+ MSG
118
+ else
119
+ raise
120
+ end
121
+ end
122
+ end
123
+
124
+ # @return [Synthra::Schema] the schema being used
125
+ attr_reader :schema
126
+
127
+ # @return [String] the locale for data generation
128
+ attr_reader :locale
129
+
130
+ # Initialize native engine with a schema
131
+ #
132
+ # @param schema [Synthra::Schema] the schema to generate from
133
+ # @param locale [String] locale for fake data (en, fr_fr, de_de, ja_jp, etc.)
134
+ # @param threads [Integer, nil] number of threads (nil/0 = use all cores)
135
+ # @raise [LoadError] if native extension is not available
136
+ def initialize(schema, locale: "en", threads: nil)
137
+ unless self.class.available?
138
+ raise LoadError, "Native extension not available. " \
139
+ "Install Rust and run `bundle exec rake compile`"
140
+ end
141
+
142
+ @schema = schema
143
+ @locale = locale.to_s
144
+ @threads = threads
145
+ @field_specs = build_field_specs
146
+ end
147
+
148
+ # Generate multiple records
149
+ #
150
+ # @param count [Integer] number of records to generate
151
+ # @param seed [Integer, nil] random seed for reproducibility
152
+ # @param threads [Integer, nil] number of threads (nil/0 = use all cores)
153
+ # @return [Array<Hash>] array of generated records
154
+ #
155
+ # @example Generate with default threads
156
+ # engine.generate_many(1000, seed: 42)
157
+ #
158
+ # @example Generate with limited threads
159
+ # engine.generate_many(1000, seed: 42, threads: 4)
160
+ def generate_many(count, seed: nil, threads: nil)
161
+ Native.generate(@field_specs, count, seed, @locale, threads || @threads)
162
+ end
163
+
164
+ # Generate a single record
165
+ #
166
+ # @param seed [Integer, nil] random seed for reproducibility
167
+ # @return [Hash] generated record
168
+ #
169
+ # @example
170
+ # record = engine.generate(seed: 42)
171
+ def generate(seed: nil)
172
+ Native.generate_one(@field_specs, seed, @locale)
173
+ end
174
+
175
+ # Generate records directly to file (most efficient for large datasets)
176
+ #
177
+ # @param count [Integer] number of records to generate
178
+ # @param path [String] output file path
179
+ # @param seed [Integer, nil] random seed for reproducibility
180
+ # @param format [String] output format: "jsonl", "json", "csv"
181
+ # @param threads [Integer, nil] number of threads (nil/0 = use all cores)
182
+ # @return [Integer] number of records written
183
+ #
184
+ # @example Generate JSONL (one JSON per line)
185
+ # engine.generate_to_file(10_000_000, "users.jsonl", format: "jsonl")
186
+ #
187
+ # @example Generate CSV
188
+ # engine.generate_to_file(1_000_000, "users.csv", format: "csv")
189
+ #
190
+ # @example Generate with limited threads
191
+ # engine.generate_to_file(10_000_000, "users.jsonl", threads: 4)
192
+ def generate_to_file(count, path, seed: nil, format: "jsonl", threads: nil)
193
+ Native.generate_to_file(@field_specs, count, path.to_s, seed, @locale, format.to_s, threads || @threads)
194
+ end
195
+
196
+ # Alias for compatibility with other engines
197
+ alias to_file generate_to_file
198
+
199
+ # Stream records (yields each record)
200
+ #
201
+ # @param count [Integer] number of records to generate
202
+ # @param seed [Integer, nil] random seed for reproducibility
203
+ # @yield [Hash] each generated record
204
+ # @return [Enumerator] if no block given
205
+ #
206
+ # @example
207
+ # engine.stream(1000) do |record|
208
+ # process(record)
209
+ # end
210
+ def stream(count, seed: nil, &block)
211
+ return enum_for(:stream, count, seed: seed) unless block_given?
212
+
213
+ # For now, generate in batches of 10K for memory efficiency
214
+ batch_size = 10_000
215
+ remaining = count
216
+ offset = 0
217
+
218
+ while remaining > 0
219
+ current_batch = [remaining, batch_size].min
220
+ batch_seed = seed ? seed + offset : nil
221
+
222
+ records = Native.generate(@field_specs, current_batch, batch_seed, @locale, @threads)
223
+ records.each(&block)
224
+
225
+ remaining -= current_batch
226
+ offset += current_batch
227
+ end
228
+ end
229
+
230
+ private
231
+
232
+ # Build field specs for native engine
233
+ # Format: [name, type_name, args_json, optional, nullable]
234
+ def build_field_specs
235
+ @schema.fields.map do |field|
236
+ [
237
+ field.name.to_s,
238
+ map_type_name(field.type_name),
239
+ build_args_json(field),
240
+ field.optional?,
241
+ field.nullable?
242
+ ]
243
+ end
244
+ end
245
+
246
+ # Map DSL type names to native engine type names
247
+ def map_type_name(type_name)
248
+ type_str = type_name.to_s
249
+
250
+ # Handle type aliases
251
+ case type_str
252
+ when "full_name" then "name"
253
+ when "phone_number" then "phone"
254
+ when "street_address" then "street"
255
+ when "zip_code" then "postal_code"
256
+ when "int", "integer" then "number"
257
+ when "bool" then "boolean"
258
+ when "timestamp" then "datetime"
259
+ else type_str
260
+ end
261
+ end
262
+
263
+ # Build args JSON for a field
264
+ def build_args_json(field)
265
+ args = field.type_args || {}
266
+
267
+ # Handle enum values
268
+ if field.type_name.to_s == "enum" && args[:values]
269
+ args = args.merge(values: args[:values].map { |v| extract_enum_value(v) })
270
+ end
271
+
272
+ # Handle const value
273
+ if field.type_name.to_s == "const"
274
+ args = { value: args[:value] || args.values.first }
275
+ end
276
+
277
+ # Handle number ranges
278
+ if args[:range]
279
+ range = args[:range]
280
+ args = args.merge(min: range.begin, max: range.end)
281
+ end
282
+
283
+ args.to_json
284
+ end
285
+
286
+ # Extract the string value from an enum value (handles AST nodes and hashes)
287
+ def extract_enum_value(value)
288
+ case value
289
+ when Hash
290
+ value[:value] || value["value"]
291
+ when String, Symbol
292
+ value.to_s
293
+ else
294
+ # Handle AST nodes like EnumValueNode
295
+ if value.respond_to?(:value)
296
+ value.value.to_s
297
+ else
298
+ value.to_s
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
304
+
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "json"
5
+
6
+ module Synthra
7
+ # OpenAPI/Swagger Schema Importer
8
+ #
9
+ # Converts OpenAPI specifications to Synthra schemas.
10
+ #
11
+ # @example CLI usage
12
+ # $ synthra import openapi.yaml --output schemas/
13
+ #
14
+ # @example Ruby usage
15
+ # importer = Synthra::OpenAPIImporter.new("openapi.yaml")
16
+ # schemas = importer.import
17
+ # schemas.each { |name, dsl| File.write("schemas/#{name}.dsl", dsl) }
18
+ #
19
+ class OpenAPIImporter
20
+ # OpenAPI type to Synthra type mapping
21
+ TYPE_MAP = {
22
+ # Strings
23
+ "string" => "text",
24
+ "string:email" => "email",
25
+ "string:uri" => "url",
26
+ "string:url" => "url",
27
+ "string:uuid" => "uuid",
28
+ "string:date" => "date",
29
+ "string:date-time" => "timestamp",
30
+ "string:password" => "password",
31
+ "string:byte" => "text",
32
+ "string:binary" => "text",
33
+ "string:hostname" => "domain",
34
+ "string:ipv4" => "ip",
35
+ "string:ipv6" => "ipv6",
36
+
37
+ # Numbers
38
+ "integer" => "number",
39
+ "integer:int32" => "number",
40
+ "integer:int64" => "number",
41
+ "number" => "float",
42
+ "number:float" => "float",
43
+ "number:double" => "float",
44
+
45
+ # Boolean
46
+ "boolean" => "boolean",
47
+
48
+ # Complex
49
+ "array" => "array",
50
+ "object" => "object"
51
+ }.freeze
52
+
53
+ attr_reader :spec_path, :spec
54
+
55
+ # Create a new importer
56
+ #
57
+ # @param spec_path [String] path to OpenAPI spec file
58
+ #
59
+ def initialize(spec_path)
60
+ @spec_path = spec_path
61
+ @spec = load_spec
62
+ end
63
+
64
+ # Import all schemas from the OpenAPI spec
65
+ #
66
+ # @return [Hash<String, String>] schema name => DSL content
67
+ #
68
+ def import
69
+ schemas = {}
70
+
71
+ components_schemas.each do |name, definition|
72
+ dsl = schema_to_dsl(name, definition)
73
+ schemas[name] = dsl if dsl
74
+ end
75
+
76
+ schemas
77
+ end
78
+
79
+ # Import and write to directory
80
+ #
81
+ # @param output_dir [String] output directory
82
+ # @return [Array<String>] paths to created files
83
+ #
84
+ def import_to_directory(output_dir)
85
+ FileUtils.mkdir_p(output_dir)
86
+ created = []
87
+
88
+ base = File.expand_path(output_dir)
89
+ import.each do |name, dsl|
90
+ # SECURITY (path traversal): a schema name from the spec is attacker-controlled. basename
91
+ # strips any "../" path components and the allowlist drops anything left, so the file can only
92
+ # land inside output_dir; the expand_path assertion is a belt-and-suspenders backstop.
93
+ safe = File.basename(to_snake_case(name.to_s)).gsub(/[^a-z0-9_]+/i, "_")
94
+ # :nocov: defensive backstop — File.basename + the gsub allowlist always leave a non-empty
95
+ # token for any non-empty schema name, so this empty-fallback is unreachable in practice.
96
+ safe = "schema" if safe.empty?
97
+ # :nocov:
98
+ path = File.join(output_dir, "#{safe}.dsl")
99
+ # :nocov: defensive backstop — basename already neutralizes "../" traversal, so this
100
+ # expand_path containment check can never fail for a name that produced a safe basename.
101
+ unless File.expand_path(path).start_with?(base + File::SEPARATOR)
102
+ raise Synthra::Errors::SecurityError, "Refusing to write outside #{output_dir}: #{name.inspect}"
103
+ end
104
+ # :nocov:
105
+
106
+ File.write(path, dsl)
107
+ created << path
108
+ end
109
+
110
+ created
111
+ end
112
+
113
+ private
114
+
115
+ def load_spec
116
+ content = File.read(@spec_path)
117
+
118
+ if @spec_path.end_with?(".json")
119
+ JSON.parse(content)
120
+ else
121
+ YAML.safe_load(content, permitted_classes: [Date, Time])
122
+ end
123
+ end
124
+
125
+ def components_schemas
126
+ @spec.dig("components", "schemas") ||
127
+ @spec.dig("definitions") || # Swagger 2.0
128
+ {}
129
+ end
130
+
131
+ def schema_to_dsl(name, definition)
132
+ return nil unless definition["type"] == "object" || definition["properties"]
133
+
134
+ lines = ["#{name}:"]
135
+ properties = definition["properties"] || {}
136
+ required = definition["required"] || []
137
+
138
+ properties.each do |prop_name, prop_def|
139
+ type = property_to_type(prop_def)
140
+ optional = required.include?(prop_name) ? "" : "?"
141
+ nullable = prop_def["nullable"] ? "?" : ""
142
+
143
+ lines << " #{prop_name}#{optional}: #{type}#{nullable}"
144
+ end
145
+
146
+ lines.join("\n")
147
+ end
148
+
149
+ def property_to_type(prop_def)
150
+ type = prop_def["type"]
151
+ format = prop_def["format"]
152
+
153
+ # Handle references
154
+ if prop_def["$ref"]
155
+ ref = prop_def["$ref"]
156
+ return extract_ref_name(ref)
157
+ end
158
+
159
+ # Handle enums
160
+ if prop_def["enum"]
161
+ values = prop_def["enum"].join(", ")
162
+ return "enum(#{values})"
163
+ end
164
+
165
+ # Handle arrays
166
+ if type == "array"
167
+ items_type = property_to_type(prop_def["items"] || {})
168
+ min_items = prop_def["minItems"] || 0
169
+ max_items = prop_def["maxItems"] || 10
170
+ return "array(#{items_type}, #{min_items}..#{max_items})"
171
+ end
172
+
173
+ # Handle nested objects
174
+ if type == "object"
175
+ if prop_def["properties"]
176
+ # Inline object - use object type
177
+ return "object"
178
+ end
179
+ return "object"
180
+ end
181
+
182
+ # Check for string constraints
183
+ if type == "string"
184
+ # Check for specific patterns
185
+ if (pattern = prop_def["pattern"])
186
+ return "regex(\"#{escape_regex(pattern)}\")"
187
+ end
188
+
189
+ # Check for length constraints
190
+ min_len = prop_def["minLength"]
191
+ max_len = prop_def["maxLength"]
192
+ if min_len || max_len
193
+ range = "#{min_len || 1}..#{max_len || 100}"
194
+ return "text(#{range})"
195
+ end
196
+ end
197
+
198
+ # Check for number constraints
199
+ if %w[integer number].include?(type)
200
+ min_val = prop_def["minimum"]
201
+ max_val = prop_def["maximum"]
202
+ if min_val || max_val
203
+ range = "#{min_val || 0}..#{max_val || 1000}"
204
+ return "number(#{range})"
205
+ end
206
+ end
207
+
208
+ # Look up in type map
209
+ key = format ? "#{type}:#{format}" : type
210
+ TYPE_MAP[key] || TYPE_MAP[type] || "text"
211
+ end
212
+
213
+ def extract_ref_name(ref)
214
+ # Handle #/components/schemas/User or #/definitions/User
215
+ ref.split("/").last
216
+ end
217
+
218
+ def escape_regex(pattern)
219
+ pattern.gsub('"', '\\"')
220
+ end
221
+
222
+ def to_snake_case(str)
223
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
224
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
225
+ .downcase
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Output
5
+
6
+ # JSON formatter for generated data
7
+ class JsonFormatter
8
+
9
+ # Format a single record
10
+ #
11
+ # @param record [Hash] the record
12
+ # @param pretty [Boolean] pretty-print
13
+ # @return [String] JSON string
14
+
15
+ def format(record, pretty: false)
16
+ if pretty
17
+ JSON.pretty_generate(record)
18
+ else
19
+ JSON.generate(record)
20
+ end
21
+ end
22
+
23
+
24
+ # Format multiple records
25
+ #
26
+ # @param records [Array<Hash>] the records
27
+ # @param pretty [Boolean] pretty-print
28
+ # @return [String] JSON string
29
+
30
+ def format_many(records, pretty: false)
31
+ if pretty
32
+ JSON.pretty_generate(records)
33
+ else
34
+ JSON.generate(records)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Output
5
+
6
+ # NDJSON (newline-delimited JSON) formatter
7
+ class NdjsonFormatter
8
+
9
+ # Format a stream to NDJSON
10
+ #
11
+ # @param stream [Enumerator] the record stream
12
+ # @param io [IO] output IO object
13
+ # @return [void]
14
+
15
+ def format_stream(stream, io: $stdout)
16
+ stream.each do |record|
17
+ io.puts JSON.generate(record)
18
+ io.flush if io.respond_to?(:flush)
19
+ end
20
+ end
21
+
22
+
23
+ # Format records to NDJSON string
24
+ #
25
+ # @param records [Array<Hash>] the records
26
+ # @return [String] NDJSON string
27
+
28
+ def format_many(records)
29
+ # Append into one buffer instead of allocating an N-element intermediate array of strings.
30
+ # Identical output to map.join("\n") (no trailing newline). For true O(1) memory, stream
31
+ # to an IO with #format_stream instead of materialising the whole string.
32
+ buffer = +""
33
+ records.each_with_index do |record, i|
34
+ buffer << "\n" if i.positive?
35
+ buffer << JSON.generate(record)
36
+ end
37
+ buffer
38
+ end
39
+
40
+
41
+ # Write to file
42
+ #
43
+ # @param stream [Enumerator] the record stream
44
+ # @param path [String] file path
45
+ # @return [void]
46
+
47
+ def write_to_file(stream, path)
48
+ File.open(path, "w") do |file|
49
+ format_stream(stream, io: file)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+