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,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Synthra
5
+ module Export
6
+ # Base class for all exporters
7
+ #
8
+ # Provides common functionality for exporting schemas to different formats.
9
+ #
10
+ # @abstract Subclass and implement {#export} to create an exporter
11
+ #
12
+ class Base
13
+ # @return [Schema] the schema to export
14
+ attr_reader :schema
15
+
16
+ # @return [Registry] the registry for resolving references
17
+ attr_reader :registry
18
+
19
+ # @return [Hash] export options
20
+ attr_reader :options
21
+
22
+ # Create a new exporter
23
+ #
24
+ # @param schema [Schema] the schema to export
25
+ # @param registry [Registry] the registry for resolving references
26
+ # @param options [Hash] export options
27
+ #
28
+ def initialize(schema, registry: nil, **options)
29
+ @schema = schema
30
+ @registry = registry
31
+ @options = options
32
+ end
33
+
34
+ # Export the schema to the target format
35
+ #
36
+ # @return [String] the exported content
37
+ # @abstract
38
+ #
39
+ def export
40
+ raise NotImplementedError, "Subclasses must implement #export"
41
+ end
42
+
43
+ # Save export to a file
44
+ #
45
+ # @param path [String] file path
46
+ # @return [void]
47
+ #
48
+ def save(path)
49
+ File.write(path, export)
50
+ end
51
+
52
+ protected
53
+
54
+ # Get all fields including from nested schemas
55
+ #
56
+ # @return [Array<Field>] all fields
57
+ #
58
+ def all_fields
59
+ schema.fields
60
+ end
61
+
62
+ # Check if a type is a schema reference
63
+ #
64
+ # @param type_name [String] the type name
65
+ # @return [Boolean]
66
+ #
67
+ def schema_reference?(type_name)
68
+ registry&.schema?(type_name) || false
69
+ end
70
+
71
+ # Get referenced schema
72
+ #
73
+ # @param type_name [String] the type name
74
+ # @return [Schema, nil]
75
+ #
76
+ def get_schema(type_name)
77
+ registry&.schema(type_name)
78
+ rescue KeyError
79
+ nil # schema not found
80
+ end
81
+ end
82
+ # :nocov:
83
+ end
84
+ end
85
+
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ # :nocov:
6
+ module Synthra
7
+ module Export
8
+ # Exports generated data as CSV
9
+ #
10
+ # @example Export schema data as CSV
11
+ # exporter = Csv.new(schema, registry: registry, count: 100)
12
+ # csv = exporter.export
13
+ #
14
+ class Csv < Base
15
+ def export
16
+ count = options[:count] || 10
17
+ seed = options[:seed]
18
+ mode = options[:mode] || :random
19
+
20
+ records = schema.generate_many(count, seed: seed, mode: mode, registry: registry)
21
+
22
+ return "" if records.empty?
23
+
24
+ # Flatten nested objects for CSV
25
+ flat_records = records.map { |r| flatten_record(r) }
26
+
27
+ # Get all possible headers
28
+ headers = flat_records.flat_map(&:keys).uniq.sort
29
+
30
+ CSV.generate do |csv|
31
+ csv << headers
32
+ flat_records.each do |record|
33
+ csv << headers.map { |h| format_value(record[h]) }
34
+ end
35
+ end
36
+ end
37
+
38
+ # Export with headers only (no data)
39
+ #
40
+ # @return [String] CSV header row
41
+ #
42
+ def export_headers
43
+ headers = schema.fields.map(&:name)
44
+ CSV.generate { |csv| csv << headers }
45
+ end
46
+
47
+ private
48
+
49
+ def flatten_record(record, prefix = "")
50
+ result = {}
51
+
52
+ record.each do |key, value|
53
+ full_key = prefix.empty? ? key : "#{prefix}.#{key}"
54
+
55
+ case value
56
+ when Hash
57
+ result.merge!(flatten_record(value, full_key))
58
+ when Array
59
+ if value.first.is_a?(Hash)
60
+ # Array of objects - just count them
61
+ result[full_key] = "[#{value.length} items]"
62
+ else
63
+ result[full_key] = value.join("; ")
64
+ end
65
+ else
66
+ result[full_key] = value
67
+ end
68
+ end
69
+
70
+ result
71
+ end
72
+
73
+ def format_value(value)
74
+ case value
75
+ when nil
76
+ ""
77
+ when true
78
+ "true"
79
+ when false
80
+ "false"
81
+ when Array
82
+ escape_csv_formula(value.join("; "))
83
+ when Hash
84
+ escape_csv_formula(JSON.generate(value))
85
+ when String
86
+ escape_csv_formula(value)
87
+ else
88
+ value.to_s # numerics/dates are safe — never prefix them (would break -5, =NA values stay strings)
89
+ end
90
+ end
91
+
92
+ # SECURITY (CSV injection): a string value beginning with = + - @ tab or CR is treated as a
93
+ # formula by spreadsheet apps that open the export. Prefix it with a single quote so the cell
94
+ # is rendered as literal text. The gem ships a `hostile_payloads` type that emits exactly these.
95
+ def escape_csv_formula(str)
96
+ str.match?(/\A[=+\-@\t\r]/) ? "'#{str}" : str
97
+ end
98
+ end
99
+ # :nocov:
100
+ end
101
+ end
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Export
5
+ # GraphQL schema export
6
+ #
7
+ # Exports Synthra schemas to GraphQL SDL (Schema Definition Language).
8
+ #
9
+ # @example Export single schema
10
+ # exporter = Graphql.new(schema, registry: registry)
11
+ # graphql_schema = exporter.export
12
+ #
13
+ # @example Export all schemas
14
+ # graphql_schema = Graphql.export_all(registry)
15
+ #
16
+ class Graphql < Base
17
+ # Type mapping from Synthra types to GraphQL scalars
18
+ TYPE_MAP = {
19
+ "uuid" => "ID",
20
+ "ulid" => "ID",
21
+ "id_sequence" => "ID",
22
+ "snowflake_id" => "ID",
23
+ "numeric_string_id" => "ID",
24
+ "email" => "String",
25
+ "url" => "String",
26
+ "text" => "String",
27
+ "name" => "String",
28
+ "first_name" => "String",
29
+ "last_name" => "String",
30
+ "full_name" => "String",
31
+ "username" => "String",
32
+ "phone" => "String",
33
+ "address" => "String",
34
+ "city" => "String",
35
+ "state" => "String",
36
+ "country" => "String",
37
+ "country_code" => "String",
38
+ "postal_code" => "String",
39
+ "ip" => "String",
40
+ "ipv6" => "String",
41
+ "domain" => "String",
42
+ "user_agent" => "String",
43
+ "password" => "String",
44
+ "color" => "String",
45
+ "hex_color" => "String",
46
+ "number" => "Int",
47
+ "integer" => "Int",
48
+ "age" => "Int",
49
+ "airport_elevation" => "Int",
50
+ "discount" => "Int",
51
+ "formula" => "Int",
52
+ "row_number" => "Int",
53
+ "sequence" => "Int",
54
+ "float" => "Float",
55
+ "money" => "Float",
56
+ "latitude" => "Float",
57
+ "longitude" => "Float",
58
+ "airport_latitude" => "Float",
59
+ "airport_longitude" => "Float",
60
+ "product_price" => "Float",
61
+ "tax_rate" => "Float",
62
+ "transaction_amount" => "Float",
63
+ "boolean" => "Boolean",
64
+ "date" => "String",
65
+ "date_between" => "String",
66
+ "past_date" => "String",
67
+ "future_date" => "String",
68
+ "mobile_device_release_date" => "String",
69
+ "timestamp" => "String",
70
+ "datetime" => "String",
71
+ "now" => "String",
72
+ "time" => "String"
73
+ }.freeze
74
+
75
+ # Export single schema to GraphQL
76
+ #
77
+ # @return [String] GraphQL type definition
78
+ #
79
+ def export
80
+ lines = []
81
+
82
+ # Add custom scalar definitions
83
+ lines << "# Synthra Generated GraphQL Schema"
84
+ lines << "# Generated at: #{Time.now.iso8601}"
85
+ lines << ""
86
+
87
+ # Build type
88
+ lines << build_type(@schema)
89
+
90
+ lines.join("\n")
91
+ end
92
+
93
+ # Export all schemas to GraphQL
94
+ #
95
+ # @param registry [Registry] schema registry
96
+ # @return [String] complete GraphQL schema
97
+ #
98
+ def self.export_all(registry)
99
+ lines = []
100
+ lines << "# Synthra Generated GraphQL Schema"
101
+ lines << "# Generated at: #{Time.now.iso8601}"
102
+ lines << ""
103
+
104
+ enums = []
105
+ types = []
106
+
107
+ registry.schemas.each_value do |schema|
108
+ exporter = new(schema, registry: registry)
109
+ types << exporter.build_type(schema)
110
+ enums.concat(exporter.collect_enums(schema))
111
+ end
112
+
113
+ # Add enums first
114
+ enums.uniq.each do |enum_def|
115
+ lines << enum_def
116
+ lines << ""
117
+ end
118
+
119
+ # Add types
120
+ types.each do |type_def|
121
+ lines << type_def
122
+ lines << ""
123
+ end
124
+
125
+ # Add Query type
126
+ lines << build_query_type(registry)
127
+
128
+ lines.join("\n")
129
+ end
130
+
131
+ # Build a GraphQL type from a schema
132
+ #
133
+ # @param schema [Schema] the schema to convert
134
+ # @return [String] GraphQL type definition
135
+ #
136
+ def build_type(schema)
137
+ lines = []
138
+
139
+ # Add description if available
140
+ if schema.deprecated?
141
+ lines << "\"\"\"#{schema.deprecation_message || 'This type is deprecated'}\"\"\""
142
+ end
143
+
144
+ lines << "type #{schema.name} {"
145
+
146
+ schema.fields.each do |field|
147
+ field_def = build_field(field)
148
+ lines << " #{field_def}"
149
+ end
150
+
151
+ lines << "}"
152
+
153
+ lines.join("\n")
154
+ end
155
+
156
+ # Collect enum definitions from a schema
157
+ #
158
+ # @param schema [Schema] the schema to scan
159
+ # @return [Array<String>] enum definitions
160
+ #
161
+ def collect_enums(schema)
162
+ enums = []
163
+
164
+ schema.fields.each do |field|
165
+ if field.type_name == "enum"
166
+ enum_def = build_enum(schema.name, field)
167
+ enums << enum_def if enum_def
168
+ end
169
+ end
170
+
171
+ enums
172
+ end
173
+
174
+ private
175
+
176
+ def build_field(field)
177
+ graphql_type = field_to_graphql_type(field)
178
+ nullable = field.optional? || field.nullable?
179
+ required_marker = nullable ? "" : "!"
180
+
181
+ deprecated = ""
182
+ if field.behaviors.any? { |b| b[:name] == :deprecated }
183
+ deprecated = " @deprecated"
184
+ end
185
+
186
+ "#{field.name}: #{graphql_type}#{required_marker}#{deprecated}"
187
+ end
188
+
189
+ def field_to_graphql_type(field)
190
+ type_name = field.type_name
191
+
192
+ # Handle enums
193
+ if type_name == "enum"
194
+ return enum_type_name(field)
195
+ end
196
+
197
+ # Handle const
198
+ if type_name == "const"
199
+ value = field.type_args[:value]
200
+ return case value
201
+ when String then "String"
202
+ when Integer then "Int"
203
+ when Float then "Float"
204
+ when TrueClass, FalseClass then "Boolean"
205
+ else "String"
206
+ end
207
+ end
208
+
209
+ # Handle arrays. The element is either a scalar type (map it; unknown scalars → String,
210
+ # matching the scalar fallback) or a schema reference (a PascalCase schema name — keep it).
211
+ if type_name == "array"
212
+ element_str = field.type_args[:element].to_s
213
+ element_graphql = TYPE_MAP[element_str] || (element_str.match?(/\A[A-Z]/) ? element_str : "String")
214
+ return "[#{element_graphql}!]"
215
+ end
216
+
217
+ # Handle schema references
218
+ if @registry&.schema?(type_name)
219
+ return type_name
220
+ end
221
+
222
+ # Handle known types
223
+ TYPE_MAP[type_name] || "String"
224
+ end
225
+
226
+ def enum_type_name(field)
227
+ # Generate enum type name from field context
228
+ "#{@schema.name}#{field.name.split('_').map(&:capitalize).join}Enum"
229
+ end
230
+
231
+ def build_enum(schema_name, field)
232
+ enum_name = "#{schema_name}#{field.name.split('_').map(&:capitalize).join}Enum"
233
+ values = field.type_args[:values]
234
+
235
+ return nil unless values&.any?
236
+
237
+ lines = ["enum #{enum_name} {"]
238
+
239
+ values.each do |val|
240
+ value_str = val.respond_to?(:value) ? val.value : val.to_s
241
+ # GraphQL enum values must be SCREAMING_SNAKE_CASE
242
+ graphql_value = value_str.to_s.upcase.gsub(/[^A-Z0-9_]/, "_")
243
+ lines << " #{graphql_value}"
244
+ end
245
+
246
+ lines << "}"
247
+ lines.join("\n")
248
+ end
249
+
250
+ def self.build_query_type(registry)
251
+ lines = ["type Query {"]
252
+
253
+ registry.schemas.each do |name, _schema|
254
+ singular = name.downcase
255
+ plural = "#{singular}s"
256
+
257
+ lines << " #{singular}(id: ID!): #{name}"
258
+ lines << " #{plural}(limit: Int = 10, offset: Int = 0): [#{name}!]!"
259
+ end
260
+
261
+ lines << "}"
262
+ lines.join("\n")
263
+ end
264
+ end
265
+ end
266
+ end