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,501 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Export
5
+ # Terraform/IaC Export
6
+ #
7
+ # Generate Terraform configurations for infrastructure that
8
+ # matches your data schemas. Supports DynamoDB, PostgreSQL RDS,
9
+ # and other AWS services.
10
+ #
11
+ # @example Export to Terraform
12
+ # exporter = Synthra::Export::Terraform.new(registry)
13
+ # tf = exporter.export
14
+ #
15
+ # @example CLI
16
+ # $ synthra export --all -f terraform -o main.tf
17
+ #
18
+ class Terraform
19
+ # Type mapping for DynamoDB
20
+ DYNAMODB_TYPE_MAP = {
21
+ "text" => "S",
22
+ "name" => "S",
23
+ "email" => "S",
24
+ "uuid" => "S",
25
+ "number" => "N",
26
+ "integer" => "N",
27
+ "age" => "N",
28
+ "airport_elevation" => "N",
29
+ "discount" => "N",
30
+ "formula" => "N",
31
+ "row_number" => "N",
32
+ "sequence" => "N",
33
+ "id_sequence" => "N",
34
+ "float" => "N",
35
+ "money" => "N",
36
+ "latitude" => "N",
37
+ "longitude" => "N",
38
+ "airport_latitude" => "N",
39
+ "airport_longitude" => "N",
40
+ "product_price" => "N",
41
+ "tax_rate" => "N",
42
+ "transaction_amount" => "N",
43
+ "boolean" => "S", # DynamoDB stores as string
44
+ "timestamp" => "S",
45
+ "date" => "S",
46
+ "binary" => "B"
47
+ }.freeze
48
+
49
+ # Type mapping for PostgreSQL
50
+ POSTGRES_TYPE_MAP = {
51
+ "text" => "VARCHAR(255)",
52
+ "name" => "VARCHAR(255)",
53
+ "email" => "VARCHAR(255)",
54
+ "paragraph" => "TEXT",
55
+ "uuid" => "UUID",
56
+ "number" => "INTEGER",
57
+ "integer" => "INTEGER",
58
+ "age" => "INTEGER",
59
+ "airport_elevation" => "INTEGER",
60
+ "discount" => "INTEGER",
61
+ "formula" => "INTEGER",
62
+ "row_number" => "INTEGER",
63
+ "sequence" => "INTEGER",
64
+ "id_sequence" => "SERIAL",
65
+ "float" => "DOUBLE PRECISION",
66
+ "money" => "DECIMAL(10,2)",
67
+ "latitude" => "DECIMAL(10,8)",
68
+ "longitude" => "DECIMAL(11,8)",
69
+ "airport_latitude" => "DECIMAL(10,8)",
70
+ "airport_longitude" => "DECIMAL(11,8)",
71
+ "product_price" => "DECIMAL(15,2)",
72
+ "tax_rate" => "DECIMAL(10,4)",
73
+ "transaction_amount" => "DECIMAL(15,2)",
74
+ "boolean" => "BOOLEAN",
75
+ "timestamp" => "TIMESTAMP WITH TIME ZONE",
76
+ "datetime" => "TIMESTAMP WITH TIME ZONE",
77
+ "date" => "DATE",
78
+ "date_between" => "DATE",
79
+ "past_date" => "DATE",
80
+ "future_date" => "DATE",
81
+ "mobile_device_release_date" => "DATE",
82
+ "now" => "DATE",
83
+ "object" => "JSONB",
84
+ "map_by_field" => "JSONB",
85
+ "array" => "TEXT[]"
86
+ }.freeze
87
+
88
+ attr_reader :registry, :options
89
+
90
+ private
91
+
92
+ # Convert CamelCase to snake_case
93
+ def underscore(str)
94
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
95
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
96
+ .downcase
97
+ end
98
+
99
+ # Simple pluralization
100
+ def pluralize(str)
101
+ if str.end_with?('s', 'x', 'z', 'ch', 'sh')
102
+ "#{str}es"
103
+ elsif str.end_with?('y') && !%w[a e i o u].include?(str[-2])
104
+ "#{str[0..-2]}ies"
105
+ else
106
+ "#{str}s"
107
+ end
108
+ end
109
+
110
+ public
111
+
112
+ # Create new exporter
113
+ #
114
+ # @param registry [Registry] schema registry
115
+ # @param options [Hash] export options
116
+ # @option options [Symbol] :provider :aws, :gcp, :azure
117
+ # @option options [Symbol] :database :dynamodb, :rds_postgres, :rds_mysql
118
+ # @option options [String] :prefix resource name prefix
119
+ # @option options [Hash] :tags default tags
120
+ #
121
+ def initialize(registry, **options)
122
+ @registry = registry
123
+ @options = {
124
+ provider: :aws,
125
+ database: :dynamodb,
126
+ prefix: "fake_data",
127
+ region: "us-east-1",
128
+ tags: { "ManagedBy" => "Synthra" }
129
+ }.merge(options)
130
+ end
131
+
132
+ # Export to Terraform HCL
133
+ #
134
+ # @return [String] Terraform configuration
135
+ #
136
+ def export
137
+ sections = []
138
+
139
+ # Provider configuration
140
+ sections << provider_block
141
+
142
+ # Variables
143
+ sections << variables_block
144
+
145
+ # Resources for each schema
146
+ @registry.names.sort.each do |name|
147
+ schema = @registry.schema(name)
148
+ sections << generate_resource(schema)
149
+ end
150
+
151
+ # Outputs
152
+ sections << outputs_block
153
+
154
+ sections.join("\n\n")
155
+ end
156
+
157
+ # Export to specific format
158
+ #
159
+ # @param format [Symbol] :hcl or :json
160
+ # @return [String]
161
+ #
162
+ def export_as(format)
163
+ case format
164
+ when :json
165
+ export_json
166
+ else
167
+ export
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def provider_block
174
+ case @options[:provider]
175
+ when :aws
176
+ <<~HCL
177
+ # Terraform configuration
178
+ terraform {
179
+ required_version = ">= 1.0"
180
+
181
+ required_providers {
182
+ aws = {
183
+ source = "hashicorp/aws"
184
+ version = "~> 5.0"
185
+ }
186
+ }
187
+ }
188
+
189
+ provider "aws" {
190
+ region = var.aws_region
191
+
192
+ default_tags {
193
+ tags = var.default_tags
194
+ }
195
+ }
196
+ HCL
197
+ when :gcp
198
+ <<~HCL
199
+ terraform {
200
+ required_version = ">= 1.0"
201
+
202
+ required_providers {
203
+ google = {
204
+ source = "hashicorp/google"
205
+ version = "~> 5.0"
206
+ }
207
+ }
208
+ }
209
+
210
+ provider "google" {
211
+ project = var.gcp_project
212
+ region = var.gcp_region
213
+ }
214
+ HCL
215
+ else
216
+ ""
217
+ end
218
+ end
219
+
220
+ def variables_block
221
+ <<~HCL
222
+ # Variables
223
+ variable "aws_region" {
224
+ description = "AWS region"
225
+ type = string
226
+ default = "#{@options[:region]}"
227
+ }
228
+
229
+ variable "environment" {
230
+ description = "Environment name"
231
+ type = string
232
+ default = "development"
233
+ }
234
+
235
+ variable "default_tags" {
236
+ description = "Default tags for all resources"
237
+ type = map(string)
238
+ default = {
239
+ #{@options[:tags].map { |k, v| "#{k} = \"#{v}\"" }.join("\n ")}
240
+ }
241
+ }
242
+ HCL
243
+ end
244
+
245
+ def generate_resource(schema)
246
+ case @options[:database]
247
+ when :dynamodb
248
+ generate_dynamodb(schema)
249
+ when :rds_postgres
250
+ generate_rds_postgres(schema)
251
+ when :s3
252
+ generate_s3(schema)
253
+ else
254
+ generate_dynamodb(schema)
255
+ end
256
+ end
257
+
258
+ def generate_dynamodb(schema)
259
+ table_name = "#{@options[:prefix]}_#{underscore(schema.name)}"
260
+ key_field = find_key_field(schema)
261
+ sort_key = find_sort_key(schema)
262
+
263
+ # Find secondary indexes (fields with 'unique' or commonly indexed)
264
+ gsi_fields = schema.fields.select do |f|
265
+ %w[email username slug].include?(f.name) ||
266
+ f.name.end_with?("_id")
267
+ end
268
+
269
+ gsi_blocks = gsi_fields.map do |field|
270
+ <<~GSI
271
+ global_secondary_index {
272
+ name = "#{field.name}-index"
273
+ hash_key = "#{field.name}"
274
+ projection_type = "ALL"
275
+ }
276
+ GSI
277
+ end.join("\n")
278
+
279
+ attributes = [key_field]
280
+ attributes << sort_key if sort_key
281
+ attributes += gsi_fields.map(&:name)
282
+ attributes = attributes.uniq
283
+
284
+ attribute_blocks = attributes.map do |name|
285
+ field = schema.fields.find { |f| f.name == name }
286
+ type = field ? DYNAMODB_TYPE_MAP[field.type_name] || "S" : "S"
287
+ <<~ATTR
288
+ attribute {
289
+ name = "#{name}"
290
+ type = "#{type}"
291
+ }
292
+ ATTR
293
+ end.join("\n")
294
+
295
+ sort_key_line = sort_key ? "\n range_key = \"#{sort_key}\"" : ""
296
+
297
+ <<~HCL
298
+ # DynamoDB table for #{schema.name}
299
+ resource "aws_dynamodb_table" "#{underscore(schema.name)}" {
300
+ name = "#{table_name}"
301
+ billing_mode = "PAY_PER_REQUEST"
302
+ hash_key = "#{key_field}"#{sort_key_line}
303
+
304
+ #{attribute_blocks}
305
+ #{gsi_blocks}
306
+ tags = {
307
+ Name = "#{table_name}"
308
+ Schema = "#{schema.name}"
309
+ }
310
+ }
311
+ HCL
312
+ end
313
+
314
+ def generate_rds_postgres(schema)
315
+ table_name = pluralize(underscore(schema.name))
316
+
317
+ columns = schema.fields.map do |field|
318
+ pg_type = POSTGRES_TYPE_MAP[field.type_name] || "VARCHAR(255)"
319
+ null = field.optional? || field.nullable? ? "" : " NOT NULL"
320
+ default = field.name == "id" ? " DEFAULT gen_random_uuid()" : ""
321
+
322
+ if field.name == "id"
323
+ " id UUID PRIMARY KEY#{default}"
324
+ else
325
+ " #{field.name} #{pg_type}#{null}"
326
+ end
327
+ end
328
+
329
+ # Add timestamps if not present
330
+ unless schema.fields.any? { |f| f.name == "created_at" }
331
+ columns << " created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()"
332
+ end
333
+ unless schema.fields.any? { |f| f.name == "updated_at" }
334
+ columns << " updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()"
335
+ end
336
+
337
+ sql = <<~SQL
338
+ CREATE TABLE IF NOT EXISTS #{table_name} (
339
+ #{columns.join(",\n")}
340
+ );
341
+ SQL
342
+
343
+ # Create index statements
344
+ indexes = schema.fields.select { |f| %w[email username slug].include?(f.name) }
345
+ index_sql = indexes.map do |field|
346
+ "CREATE INDEX IF NOT EXISTS idx_#{table_name}_#{field.name} ON #{table_name}(#{field.name});"
347
+ end.join("\n")
348
+
349
+ <<~HCL
350
+ # PostgreSQL schema for #{schema.name}
351
+ # Run this SQL to create the table:
352
+ #
353
+ # #{sql.gsub("\n", "\n# ")}
354
+ # #{index_sql.gsub("\n", "\n# ")}
355
+
356
+ resource "aws_db_instance" "#{underscore(schema.name)}_db" {
357
+ identifier = "${var.environment}-#{underscore(schema.name)}-db"
358
+
359
+ engine = "postgres"
360
+ engine_version = "15"
361
+ instance_class = "db.t3.micro"
362
+
363
+ allocated_storage = 20
364
+ max_allocated_storage = 100
365
+ storage_type = "gp3"
366
+
367
+ db_name = "#{underscore(schema.name)}_db"
368
+ username = "admin"
369
+ password = var.db_password
370
+
371
+ skip_final_snapshot = var.environment != "production"
372
+
373
+ tags = {
374
+ Name = "${var.environment}-#{underscore(schema.name)}-db"
375
+ Schema = "#{schema.name}"
376
+ }
377
+ }
378
+ HCL
379
+ end
380
+
381
+ def generate_s3(schema)
382
+ bucket_name = "#{@options[:prefix]}-#{underscore(schema.name)}-data"
383
+
384
+ <<~HCL
385
+ # S3 bucket for #{schema.name} data
386
+ resource "aws_s3_bucket" "#{underscore(schema.name)}" {
387
+ bucket = "#{bucket_name}-${var.environment}"
388
+
389
+ tags = {
390
+ Name = "#{bucket_name}"
391
+ Schema = "#{schema.name}"
392
+ }
393
+ }
394
+
395
+ resource "aws_s3_bucket_versioning" "#{underscore(schema.name)}" {
396
+ bucket = aws_s3_bucket.#{underscore(schema.name)}.id
397
+ versioning_configuration {
398
+ status = "Enabled"
399
+ }
400
+ }
401
+
402
+ resource "aws_s3_bucket_server_side_encryption_configuration" "#{underscore(schema.name)}" {
403
+ bucket = aws_s3_bucket.#{underscore(schema.name)}.id
404
+
405
+ rule {
406
+ apply_server_side_encryption_by_default {
407
+ sse_algorithm = "AES256"
408
+ }
409
+ }
410
+ }
411
+ HCL
412
+ end
413
+
414
+ def outputs_block
415
+ outputs = @registry.names.map do |name|
416
+ snake_name = underscore(name)
417
+
418
+ case @options[:database]
419
+ when :dynamodb
420
+ <<~OUTPUT
421
+ output "#{snake_name}_table_name" {
422
+ description = "DynamoDB table name for #{name}"
423
+ value = aws_dynamodb_table.#{snake_name}.name
424
+ }
425
+
426
+ output "#{snake_name}_table_arn" {
427
+ description = "DynamoDB table ARN for #{name}"
428
+ value = aws_dynamodb_table.#{snake_name}.arn
429
+ }
430
+ OUTPUT
431
+ when :rds_postgres
432
+ <<~OUTPUT
433
+ output "#{snake_name}_db_endpoint" {
434
+ description = "RDS endpoint for #{name}"
435
+ value = aws_db_instance.#{snake_name}_db.endpoint
436
+ }
437
+ OUTPUT
438
+ else
439
+ ""
440
+ end
441
+ end
442
+
443
+ outputs.join("\n")
444
+ end
445
+
446
+ def find_key_field(schema)
447
+ %w[id uuid _id].each do |key|
448
+ return key if schema.fields.any? { |f| f.name == key }
449
+ end
450
+ schema.fields.first&.name || "id"
451
+ end
452
+
453
+ def find_sort_key(schema)
454
+ %w[created_at timestamp sort_key].each do |key|
455
+ return key if schema.fields.any? { |f| f.name == key }
456
+ end
457
+ nil
458
+ end
459
+
460
+ def export_json
461
+ # Export as Terraform JSON format
462
+ config = {
463
+ terraform: {
464
+ required_version: ">= 1.0",
465
+ required_providers: {
466
+ aws: {
467
+ source: "hashicorp/aws",
468
+ version: "~> 5.0"
469
+ }
470
+ }
471
+ },
472
+ provider: {
473
+ aws: {
474
+ region: @options[:region]
475
+ }
476
+ },
477
+ resource: {},
478
+ output: {}
479
+ }
480
+
481
+ @registry.names.each do |name|
482
+ schema = @registry.schema(name)
483
+ snake_name = underscore(name)
484
+
485
+ # Add DynamoDB table
486
+ config[:resource][:aws_dynamodb_table] ||= {}
487
+ config[:resource][:aws_dynamodb_table][snake_name] = {
488
+ name: "#{@options[:prefix]}_#{snake_name}",
489
+ billing_mode: "PAY_PER_REQUEST",
490
+ hash_key: find_key_field(schema),
491
+ attribute: schema.fields.select { |f| f.name == find_key_field(schema) }.map do |f|
492
+ { name: f.name, type: DYNAMODB_TYPE_MAP[f.type_name] || "S" }
493
+ end
494
+ }
495
+ end
496
+
497
+ JSON.pretty_generate(config)
498
+ end
499
+ end
500
+ end
501
+ end