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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Synthra
6
+ module Generators
7
+ # Rails generator for setting up Synthra in a Rails application
8
+ #
9
+ # @example Install Synthra
10
+ # rails generate synthra:install
11
+ #
12
+ class InstallGenerator < Rails::Generators::Base
13
+ source_root File.expand_path("templates", __dir__)
14
+
15
+ desc "Sets up Synthra for your Rails application"
16
+
17
+ def create_config_file
18
+ template "synthra.yml", "config/synthra.yml"
19
+ end
20
+
21
+ def create_schemas_directory
22
+ empty_directory "schemas"
23
+ template "user.dsl", "schemas/user.dsl"
24
+ template "api_response.dsl", "schemas/api_response.dsl"
25
+ end
26
+
27
+ def create_generated_directory
28
+ empty_directory "generated"
29
+ empty_directory "generated/types"
30
+ empty_directory "generated/mocks"
31
+ empty_directory "generated/docs"
32
+
33
+ create_file "generated/.gitkeep"
34
+ create_file "generated/types/.gitkeep"
35
+ create_file "generated/mocks/.gitkeep"
36
+ create_file "generated/docs/.gitkeep"
37
+ end
38
+
39
+ def create_rspec_support
40
+ return unless File.exist?("spec/spec_helper.rb")
41
+
42
+ template "synthra_support.rb", "spec/support/synthra.rb"
43
+ end
44
+
45
+ def create_rake_tasks
46
+ template "fake_data.rake", "lib/tasks/fake_data.rake"
47
+ end
48
+
49
+ def add_gitignore_entries
50
+ append_to_file ".gitignore", <<~GITIGNORE
51
+
52
+ # Synthra generated files
53
+ /generated/types/*
54
+ /generated/mocks/*
55
+ /generated/docs/*
56
+ !/generated/*/.gitkeep
57
+ GITIGNORE
58
+ end
59
+
60
+ def show_readme
61
+ say ""
62
+ say "=" * 60, :green
63
+ say " Synthra installed successfully! 🎉", :green
64
+ say "=" * 60, :green
65
+ say ""
66
+ say "Next steps:", :yellow
67
+ say ""
68
+ say " 1. Edit your schemas in schemas/"
69
+ say " 2. Generate TypeScript types:"
70
+ say " rails fake_data:types", :cyan
71
+ say ""
72
+ say " 3. Generate mock data:"
73
+ say " rails fake_data:mocks", :cyan
74
+ say ""
75
+ say " 4. Start live preview server:"
76
+ say " rails fake_data:live", :cyan
77
+ say ""
78
+ say " 5. Run schema linter:"
79
+ say " rails fake_data:lint", :cyan
80
+ say ""
81
+ say "Documentation: https://github.com/talaatmagdyx/synthra"
82
+ say ""
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,15 @@
1
+ # API Response Schema
2
+ # Use this for simulating API responses with behaviors
3
+
4
+ APIResponse:
5
+ @latency 50..200ms # Simulate network latency
6
+ @failure 2% # 2% chance of failure
7
+
8
+ success: boolean(true:95%)
9
+ data: object
10
+ meta:
11
+ page: number(1..100)
12
+ per_page: const(20)
13
+ total: number(0..10000)
14
+ timestamp: now
15
+ request_id: uuid
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Synthra Rake Tasks
4
+ # Generated by: rails generate synthra:install
5
+
6
+ namespace :fake_data do
7
+ desc "Generate TypeScript types from schemas"
8
+ task types: :environment do
9
+ require "synthra"
10
+
11
+ output_file = "generated/types/api.ts"
12
+
13
+ puts "📝 Generating TypeScript types..."
14
+ system("synthra export --all -d schemas -f typescript -o #{output_file}")
15
+ puts "✅ Generated: #{output_file}"
16
+ end
17
+
18
+ desc "Generate mock JSON data"
19
+ task mocks: :environment do
20
+ require "synthra"
21
+
22
+ registry = Synthra::Registry.new
23
+ registry.load_dir("schemas")
24
+
25
+ puts "📦 Generating mock data..."
26
+
27
+ registry.names.each do |name|
28
+ schema = registry.schema(name)
29
+ output_file = "generated/mocks/#{name.downcase}.json"
30
+
31
+ data = schema.generate_many(50, seed: 42)
32
+ File.write(output_file, JSON.pretty_generate(data))
33
+
34
+ puts " ✅ #{output_file} (50 records)"
35
+ end
36
+
37
+ puts "✅ Mock generation complete!"
38
+ end
39
+
40
+ desc "Generate HTML documentation"
41
+ task docs: :environment do
42
+ require "synthra"
43
+
44
+ puts "📚 Generating documentation..."
45
+ system("synthra docs schemas/ --output generated/docs/")
46
+ puts "✅ Documentation generated: generated/docs/index.html"
47
+ end
48
+
49
+ desc "Start live preview server"
50
+ task live: :environment do
51
+ require "synthra"
52
+
53
+ port = ENV.fetch("PORT", 4567)
54
+ puts "🚀 Starting live preview server on http://localhost:#{port}"
55
+ exec("synthra live schemas/ --port #{port}")
56
+ end
57
+
58
+ desc "Lint schemas for errors"
59
+ task lint: :environment do
60
+ require "synthra"
61
+
62
+ puts "🔍 Linting schemas..."
63
+ system("synthra lint schemas/ --strict")
64
+ end
65
+
66
+ desc "Seed database with fake data"
67
+ task seed: :environment do
68
+ require "synthra"
69
+
70
+ count = ENV.fetch("COUNT", 100).to_i
71
+
72
+ puts "🌱 Seeding database with #{count} records per schema..."
73
+
74
+ Synthra.seed_database do
75
+ # Add your models here:
76
+ # create User, count: count
77
+ # create Product, count: count * 5
78
+ # create Order, count: count * 10
79
+ end
80
+
81
+ puts "✅ Database seeded!"
82
+ end
83
+
84
+ desc "Generate all outputs (types, mocks, docs)"
85
+ task all: [:types, :mocks, :docs] do
86
+ puts ""
87
+ puts "🎉 All outputs generated!"
88
+ puts ""
89
+ puts " TypeScript types: generated/types/api.ts"
90
+ puts " Mock data: generated/mocks/"
91
+ puts " Documentation: generated/docs/index.html"
92
+ end
93
+
94
+ desc "Watch schemas for changes and regenerate"
95
+ task watch: :environment do
96
+ require "synthra"
97
+
98
+ puts "👁 Watching schemas/ for changes..."
99
+ puts " Press Ctrl+C to stop"
100
+
101
+ last_mtime = Dir.glob("schemas/*.dsl").map { |f| File.mtime(f) }.max
102
+
103
+ loop do
104
+ sleep 1
105
+ current_mtime = Dir.glob("schemas/*.dsl").map { |f| File.mtime(f) }.max rescue nil
106
+
107
+ if current_mtime && last_mtime && current_mtime > last_mtime
108
+ puts "♻️ Changes detected, regenerating..."
109
+ Rake::Task["fake_data:all"].execute
110
+ last_mtime = current_mtime
111
+ end
112
+ end
113
+ rescue Interrupt
114
+ puts "\n👋 Stopped watching"
115
+ end
116
+ end
@@ -0,0 +1,31 @@
1
+ # Synthra Configuration
2
+ # https://github.com/talaatmagdyx/synthra
3
+
4
+ # Default generation mode: random, edge, invalid, hostile, mixed
5
+ default_mode: random
6
+
7
+ # Maximum retries for unique field generation
8
+ max_unique_retries: 1000
9
+
10
+ # Resource limits
11
+ limits:
12
+ max_array_size: 100
13
+ max_recursion: 5
14
+ max_text_length: 10000
15
+ max_latency_ms: 5000
16
+
17
+ # Environment-specific overrides
18
+ test:
19
+ default_mode: edge # Use edge cases in tests
20
+ seed: 42 # Deterministic output for CI
21
+
22
+ development:
23
+ default_mode: random
24
+
25
+ staging:
26
+ default_mode: random
27
+ seed: 12345 # Consistent staging data
28
+
29
+ production:
30
+ # Production should NOT use this gem
31
+ # Keep this section empty or commented out
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Synthra RSpec Support
4
+ # Generated by: rails generate synthra:install
5
+
6
+ require "synthra"
7
+ require "synthra/rails_test_helper"
8
+ require "synthra/snapshot_testing"
9
+
10
+ RSpec.configure do |config|
11
+ # Include test helpers
12
+ config.include Synthra::RailsTestHelper
13
+ config.include Synthra::SnapshotTesting
14
+
15
+ # Load schemas once before the test suite
16
+ config.before(:suite) do
17
+ Synthra.configure_test_helper!
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ # User Schema
2
+ # Edit this file to match your User model
3
+
4
+ User:
5
+ id: uuid(unique: true)
6
+ email: email(unique: true)
7
+ name: name
8
+ role: enum(admin, user, guest)
9
+ active: boolean(true:90%)
10
+ created_at: timestamp
11
+ updated_at: timestamp
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ # ActiveRecord Schema Inference
5
+ #
6
+ # Automatically generates DSL schemas from ActiveRecord models by
7
+ # analyzing column types, validations, and associations.
8
+ #
9
+ # @example Infer schema from model
10
+ # schema = Synthra::ActiveRecordInference.infer(User)
11
+ # schema.generate
12
+ #
13
+ # @example Discover all models
14
+ # Synthra::ActiveRecordInference.discover_all!
15
+ #
16
+ # @example Export inferred schema to DSL file
17
+ # Synthra::ActiveRecordInference.export(User, "schemas/user.dsl")
18
+ #
19
+ class ActiveRecordInference
20
+ # Column type to Synthra type mapping
21
+ COLUMN_TYPE_MAP = {
22
+ # Strings
23
+ string: "text",
24
+ text: "paragraph",
25
+ citext: "text",
26
+
27
+ # Numbers
28
+ integer: "number",
29
+ bigint: "number",
30
+ smallint: "number",
31
+ float: "float",
32
+ decimal: "money",
33
+ numeric: "money",
34
+
35
+ # Boolean
36
+ boolean: "boolean",
37
+
38
+ # Date/Time
39
+ date: "date",
40
+ datetime: "timestamp",
41
+ timestamp: "timestamp",
42
+ time: "time",
43
+
44
+ # Binary
45
+ binary: "binary",
46
+ blob: "binary",
47
+
48
+ # Special
49
+ uuid: "uuid",
50
+ json: "object",
51
+ jsonb: "object",
52
+ hstore: "object",
53
+ array: "array(text)"
54
+ }.freeze
55
+
56
+ # Column name patterns for smart type inference
57
+ NAME_PATTERNS = {
58
+ /\Aemail\z/i => "email",
59
+ /email/i => "email",
60
+ /\Aphone\z/i => "phone",
61
+ /phone|mobile|cell/i => "phone",
62
+ /\Aurl\z/i => "url",
63
+ /url|website|link|href/i => "url",
64
+ /\Aname\z/i => "name",
65
+ /first_name/i => "first_name",
66
+ /last_name/i => "last_name",
67
+ /full_name/i => "full_name",
68
+ /username/i => "username",
69
+ /password/i => "password",
70
+ /\Aaddress\z/i => "address",
71
+ /street/i => "street",
72
+ /city/i => "city",
73
+ /state|province/i => "state",
74
+ /country/i => "country",
75
+ /zip|postal/i => "postal_code",
76
+ /latitude|lat\z/i => "latitude",
77
+ /longitude|lng|lon\z/i => "longitude",
78
+ /ip_address|ip\z/i => "ip",
79
+ /user_agent/i => "user_agent",
80
+ /color/i => "hex_color",
81
+ /avatar|image|photo|picture/i => "url",
82
+ /title/i => "sentence",
83
+ /description|bio|about/i => "paragraph",
84
+ /slug/i => "slug",
85
+ /token|secret|api_key/i => "uuid",
86
+ /amount|price|cost|total/i => "money",
87
+ /count|quantity|qty/i => "number(1..100)",
88
+ /age/i => "number(1..120)",
89
+ /ssn/i => "ssn",
90
+ /credit_card|card_number/i => "credit_card",
91
+ /company|organization/i => "company",
92
+ /job_title|position/i => "job_title"
93
+ }.freeze
94
+
95
+ class << self
96
+ # Infer schema from an ActiveRecord model
97
+ #
98
+ # @param model [Class] ActiveRecord model class
99
+ # @param options [Hash] inference options
100
+ # @option options [Boolean] :include_associations include associations
101
+ # @option options [Boolean] :include_validations infer from validations
102
+ # @option options [Array<String>] :skip_columns columns to skip
103
+ # @return [Schema] generated schema
104
+ #
105
+ def infer(model, **options)
106
+ options = {
107
+ include_associations: true,
108
+ include_validations: true,
109
+ skip_columns: %w[id created_at updated_at]
110
+ }.merge(options)
111
+
112
+ builder = SchemaBuilder.new(model.name)
113
+
114
+ # Process columns
115
+ model.columns.each do |column|
116
+ next if options[:skip_columns].include?(column.name)
117
+
118
+ field_type = infer_column_type(column, model, options)
119
+ field_options = infer_field_options(column, model, options)
120
+
121
+ builder.add_field(column.name, field_type, **field_options)
122
+ end
123
+
124
+ # Process associations
125
+ if options[:include_associations]
126
+ model.reflect_on_all_associations.each do |assoc|
127
+ process_association(builder, assoc)
128
+ end
129
+ end
130
+
131
+ # Process validations
132
+ if options[:include_validations]
133
+ process_validations(builder, model)
134
+ end
135
+
136
+ builder.build
137
+ end
138
+
139
+ # Discover and infer schemas for all ActiveRecord models
140
+ #
141
+ # @param options [Hash] inference options
142
+ # @return [Array<Schema>] list of generated schemas
143
+ #
144
+ def discover_all!(**options)
145
+ # Ensure models are loaded
146
+ Rails.application.eager_load! if defined?(Rails) && Rails.env.development?
147
+
148
+ schemas = []
149
+
150
+ ActiveRecord::Base.descendants.each do |model|
151
+ next if model.abstract_class?
152
+ next if model.name.nil?
153
+ next if model.name.start_with?("ActiveRecord::")
154
+ next if model.name.start_with?("HABTM_")
155
+
156
+ begin
157
+ schema = infer(model, **options)
158
+ Synthra.registry.register_schema(model.name, schema)
159
+ schemas << schema
160
+ rescue StandardError => e
161
+ warn "[Synthra] Could not infer #{model.name}: #{e.message}"
162
+ end
163
+ end
164
+
165
+ schemas
166
+ end
167
+
168
+ # Export inferred schema to a DSL file
169
+ #
170
+ # @param model [Class] ActiveRecord model class
171
+ # @param path [String] output file path
172
+ # @param options [Hash] inference options
173
+ # @return [String] generated DSL content
174
+ #
175
+ def export(model, path = nil, **options)
176
+ schema = infer(model, **options)
177
+ dsl_content = schema_to_dsl(schema)
178
+
179
+ if path
180
+ FileUtils.mkdir_p(File.dirname(path))
181
+ File.write(path, dsl_content)
182
+ end
183
+
184
+ dsl_content
185
+ end
186
+
187
+ # Export all discovered models to DSL files
188
+ #
189
+ # @param output_dir [String] output directory
190
+ # @param options [Hash] inference options
191
+ #
192
+ def export_all(output_dir = "db/schemas", **options)
193
+ FileUtils.mkdir_p(output_dir)
194
+
195
+ discover_all!(**options).each do |schema|
196
+ filename = schema.name.underscore + ".dsl"
197
+ path = File.join(output_dir, filename)
198
+ dsl_content = schema_to_dsl(schema)
199
+ File.write(path, dsl_content)
200
+ end
201
+ end
202
+
203
+ private
204
+
205
+ def infer_column_type(column, model, options)
206
+ # First, check name patterns
207
+ NAME_PATTERNS.each do |pattern, type|
208
+ return type if column.name.match?(pattern)
209
+ end
210
+
211
+ # Check for enums
212
+ if model.defined_enums.key?(column.name)
213
+ values = model.defined_enums[column.name].keys
214
+ return "enum(#{values.join(', ')})"
215
+ end
216
+
217
+ # Check validations for format hints
218
+ if options[:include_validations]
219
+ type_from_validations = infer_from_validations(column, model)
220
+ return type_from_validations if type_from_validations
221
+ end
222
+
223
+ # Fall back to column type mapping
224
+ COLUMN_TYPE_MAP[column.type] || "text"
225
+ end
226
+
227
+ def infer_field_options(column, model, _options)
228
+ opts = {}
229
+
230
+ # Check nullability
231
+ opts[:nullable] = column.null
232
+
233
+ # Check default value
234
+ opts[:default] = column.default if column.default
235
+
236
+ # Check for unique constraint
237
+ if model.validators_on(column.name).any? { |v| v.is_a?(ActiveRecord::Validations::UniquenessValidator) }
238
+ opts[:unique] = true
239
+ end
240
+
241
+ opts
242
+ end
243
+
244
+ def process_association(builder, assoc)
245
+ case assoc.macro
246
+ when :belongs_to
247
+ # Add foreign key reference
248
+ fk_column = assoc.foreign_key
249
+ unless builder.has_field?(fk_column)
250
+ ref_type = "Ref(#{assoc.class_name}.id)"
251
+ builder.add_field(fk_column, ref_type, optional: assoc.options[:optional])
252
+ end
253
+
254
+ when :has_one
255
+ # Record the relationship for potential use
256
+ builder.add_relationship(:has_one, assoc.name, assoc.class_name)
257
+
258
+ when :has_many
259
+ # Record the relationship
260
+ builder.add_relationship(:has_many, assoc.name, assoc.class_name)
261
+ end
262
+ end
263
+
264
+ def process_validations(builder, model)
265
+ model.validators.each do |validator|
266
+ validator.attributes.each do |attr|
267
+ case validator
268
+ when ActiveModel::Validations::PresenceValidator
269
+ builder.set_required(attr.to_s)
270
+
271
+ when ActiveModel::Validations::LengthValidator
272
+ if validator.options[:maximum]
273
+ builder.set_max_length(attr.to_s, validator.options[:maximum])
274
+ end
275
+ if validator.options[:minimum]
276
+ builder.set_min_length(attr.to_s, validator.options[:minimum])
277
+ end
278
+
279
+ when ActiveModel::Validations::InclusionValidator
280
+ if validator.options[:in].is_a?(Array)
281
+ values = validator.options[:in]
282
+ builder.update_type(attr.to_s, "enum(#{values.join(', ')})")
283
+ end
284
+
285
+ when ActiveModel::Validations::NumericalityValidator
286
+ opts = validator.options
287
+ if opts[:greater_than_or_equal_to] && opts[:less_than_or_equal_to]
288
+ min = opts[:greater_than_or_equal_to]
289
+ max = opts[:less_than_or_equal_to]
290
+ builder.update_type(attr.to_s, "number(#{min}..#{max})")
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ def infer_from_validations(column, model)
298
+ model.validators_on(column.name).each do |validator|
299
+ case validator
300
+ when ActiveModel::Validations::FormatValidator
301
+ pattern = validator.options[:with]
302
+ return "email" if pattern.to_s.include?("@")
303
+ return "url" if pattern.to_s.include?("http")
304
+
305
+ when ActiveModel::Validations::InclusionValidator
306
+ if validator.options[:in].is_a?(Array)
307
+ values = validator.options[:in]
308
+ return "enum(#{values.join(', ')})"
309
+ end
310
+ end
311
+ end
312
+
313
+ nil
314
+ end
315
+
316
+ def schema_to_dsl(schema)
317
+ lines = ["#{schema.name}:"]
318
+
319
+ schema.fields.each do |field|
320
+ optional_marker = field.optional? ? "?" : ""
321
+ lines << " #{field.name}#{optional_marker}: #{field.type_name}"
322
+ end
323
+
324
+ lines.join("\n")
325
+ end
326
+ end
327
+
328
+ # Internal schema builder
329
+ class SchemaBuilder
330
+ def initialize(name)
331
+ @name = name
332
+ @fields = {}
333
+ @relationships = []
334
+ end
335
+
336
+ def add_field(name, type, **options)
337
+ @fields[name] = {
338
+ type: type,
339
+ options: options
340
+ }
341
+ end
342
+
343
+ def has_field?(name)
344
+ @fields.key?(name)
345
+ end
346
+
347
+ def update_type(name, type)
348
+ @fields[name][:type] = type if @fields[name]
349
+ end
350
+
351
+ def set_required(name)
352
+ @fields[name][:options][:optional] = false if @fields[name]
353
+ end
354
+
355
+ def set_max_length(name, max)
356
+ return unless @fields[name]
357
+
358
+ current_type = @fields[name][:type]
359
+ if current_type == "text"
360
+ @fields[name][:type] = "text(1..#{max})"
361
+ end
362
+ end
363
+
364
+ def set_min_length(name, min)
365
+ return unless @fields[name]
366
+
367
+ current_type = @fields[name][:type]
368
+ if current_type.start_with?("text")
369
+ max = current_type.match(/\.\.(\d+)/)&.[](1) || 255
370
+ @fields[name][:type] = "text(#{min}..#{max})"
371
+ end
372
+ end
373
+
374
+ def add_relationship(type, name, target)
375
+ @relationships << { type: type, name: name, target: target }
376
+ end
377
+
378
+ def build
379
+ # Create DSL string
380
+ dsl_lines = ["#{@name}:"]
381
+
382
+ @fields.each do |name, config|
383
+ optional = config[:options][:optional] ? "?" : ""
384
+ dsl_lines << " #{name}#{optional}: #{config[:type]}"
385
+ end
386
+
387
+ dsl_string = dsl_lines.join("\n")
388
+
389
+ # Parse and return schema
390
+ Synthra::Parser.parse(dsl_string).first
391
+ end
392
+ end
393
+ end
394
+ end