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,327 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Synthra
5
+ module Export
6
+ # Exports schema as JavaScript with JSDoc type annotations
7
+ #
8
+ # @example Export a schema
9
+ # exporter = Javascript.new(schema, registry: registry)
10
+ # javascript = exporter.export
11
+ #
12
+ class Javascript < Base
13
+ # Type mappings from DSL types to JSDoc types
14
+ TYPE_MAPPINGS = {
15
+ # Strings
16
+ "uuid" => "string",
17
+ "ulid" => "string",
18
+ "email" => "string",
19
+ "url" => "string",
20
+ "ip" => "string",
21
+ "ipv6" => "string",
22
+ "date" => "string",
23
+ "past_date" => "string",
24
+ "future_date" => "string",
25
+ "timestamp" => "string",
26
+ "text" => "string",
27
+ "name" => "string",
28
+ "full_name" => "string",
29
+ "first_name" => "string",
30
+ "last_name" => "string",
31
+ "phone" => "string",
32
+ "city" => "string",
33
+ "country" => "string",
34
+ "country_code" => "string",
35
+ "postal_code" => "string",
36
+ "state" => "string",
37
+ "street" => "string",
38
+ "address" => "string",
39
+ "username" => "string",
40
+ "social_handle" => "string",
41
+ "domain" => "string",
42
+ "user_agent" => "string",
43
+ "mac_address" => "string",
44
+ "iban" => "string",
45
+ "credit_card" => "string",
46
+ "currency" => "string",
47
+ "currency_code" => "string",
48
+ "paragraph" => "string",
49
+ "sentence" => "string",
50
+ "word" => "string",
51
+ "message_text" => "string",
52
+ "hashtag_text" => "string",
53
+ "snowflake_id" => "string",
54
+ "numeric_string_id" => "string",
55
+
56
+ # Numbers (integers + floats both map to JS number)
57
+ "number" => "number",
58
+ "integer" => "number",
59
+ "age" => "number",
60
+ "airport_elevation" => "number",
61
+ "discount" => "number",
62
+ "formula" => "number",
63
+ "row_number" => "number",
64
+ "sequence" => "number",
65
+ "id_sequence" => "number",
66
+ "float" => "number",
67
+ "money" => "number",
68
+ "latitude" => "number",
69
+ "longitude" => "number",
70
+ "airport_latitude" => "number",
71
+ "airport_longitude" => "number",
72
+ "product_price" => "number",
73
+ "tax_rate" => "number",
74
+ "transaction_amount" => "number",
75
+
76
+ # Boolean
77
+ "boolean" => "boolean",
78
+
79
+ # Date (string in JSON)
80
+ "date_between" => "string",
81
+ "mobile_device_release_date" => "string",
82
+ "now" => "string",
83
+ "datetime" => "string",
84
+
85
+ # Objects
86
+ "object" => "Object",
87
+ "map_by_field" => "Object",
88
+
89
+ # Arrays
90
+ "array" => "Array",
91
+ "empty_array" => "Array",
92
+ "json_array" => "Array",
93
+ "indices_pair" => "Array.<number>"
94
+ }.freeze
95
+
96
+ def export
97
+ lines = []
98
+
99
+ # Add header comment
100
+ lines << "/**"
101
+ lines << " * Generated from Synthra schema: #{schema.name}"
102
+ lines << " * Version: #{schema.version}" if schema.version
103
+ lines << " * @deprecated #{schema.deprecation_message}" if schema.deprecated?
104
+ lines << " */"
105
+ lines << ""
106
+
107
+ # Export referenced schemas first
108
+ exported = Set.new
109
+ collect_referenced_schemas.each do |ref_schema|
110
+ lines << build_typedef(ref_schema)
111
+ lines << ""
112
+ exported << ref_schema.name
113
+ end
114
+
115
+ # Export main schema
116
+ lines << build_typedef(schema) unless exported.include?(schema.name)
117
+
118
+ # Add factory function
119
+ lines << ""
120
+ lines << build_factory_function(schema)
121
+
122
+ lines.join("\n")
123
+ end
124
+
125
+ # Export all schemas in a registry
126
+ #
127
+ # @param registry [Registry] the registry
128
+ # @param options [Hash] export options
129
+ # @return [String] JavaScript with JSDoc types
130
+ #
131
+ def self.export_all(registry, **options)
132
+ lines = []
133
+ lines << "/**"
134
+ lines << " * Generated from Synthra schemas"
135
+ lines << " * Schemas: #{registry.names.join(', ')}"
136
+ lines << " */"
137
+ lines << ""
138
+
139
+ registry.schemas.each do |name, schema|
140
+ exporter = new(schema, registry: registry, **options)
141
+ lines << exporter.build_typedef(schema)
142
+ lines << ""
143
+ end
144
+
145
+ lines.join("\n")
146
+ end
147
+
148
+ def build_typedef(target_schema)
149
+ lines = []
150
+
151
+ # JSDoc typedef
152
+ lines << "/**"
153
+ lines << " * @typedef {Object} #{target_schema.name}"
154
+ if target_schema.deprecated?
155
+ lines << " * @deprecated #{target_schema.deprecation_message || 'This type is deprecated'}"
156
+ end
157
+
158
+ target_schema.fields.each do |field|
159
+ js_type = field_to_javascript(field)
160
+ optional = field.optional? ? " (optional)" : ""
161
+ nullable = field.nullable? ? "|null" : ""
162
+
163
+ lines << " * @property {#{js_type}#{nullable}} #{field.optional? ? "[#{field.name}]" : field.name}#{optional}"
164
+ end
165
+
166
+ lines << " */"
167
+ lines.join("\n")
168
+ end
169
+
170
+ private
171
+
172
+ def collect_referenced_schemas
173
+ schemas = []
174
+ schema.fields.each do |field|
175
+ collect_field_schemas(field, schemas)
176
+ end
177
+ schemas.uniq(&:name)
178
+ end
179
+
180
+ def collect_field_schemas(field, schemas)
181
+ type_name = field.type_name
182
+
183
+ if schema_reference?(type_name)
184
+ ref_schema = get_schema(type_name)
185
+ if ref_schema && !schemas.any? { |s| s.name == ref_schema.name }
186
+ schemas << ref_schema
187
+ ref_schema.fields.each { |f| collect_field_schemas(f, schemas) }
188
+ end
189
+ end
190
+
191
+ if type_name == "array"
192
+ element = field.type_args[:element]
193
+ if element && schema_reference?(element.to_s)
194
+ ref_schema = get_schema(element.to_s)
195
+ if ref_schema && !schemas.any? { |s| s.name == ref_schema.name }
196
+ schemas << ref_schema
197
+ ref_schema.fields.each { |f| collect_field_schemas(f, schemas) }
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ def field_to_javascript(field)
204
+ type_name = field.type_name
205
+ type_args = field.type_args
206
+
207
+ case type_name
208
+ when "enum"
209
+ build_enum_type(type_args)
210
+ when "const"
211
+ build_const_type(type_args)
212
+ when "array"
213
+ build_array_type(type_args)
214
+ when "one_of"
215
+ build_one_of_type(type_args)
216
+ when "map_by_field"
217
+ "Object.<string, Object>"
218
+ else
219
+ if TYPE_MAPPINGS[type_name]
220
+ TYPE_MAPPINGS[type_name]
221
+ elsif schema_reference?(type_name)
222
+ type_name
223
+ else
224
+ # Most Synthra types generate strings, so an unmapped, non-structural
225
+ # type is a string rather than the any-type wildcard.
226
+ "string"
227
+ end
228
+ end
229
+ end
230
+
231
+ def build_enum_type(args)
232
+ values = args[:values]&.map { |v| v.respond_to?(:value) ? v.value : v } || []
233
+ "(#{values.map { |v| "'#{v}'" }.join('|')})"
234
+ end
235
+
236
+ def build_const_type(args)
237
+ value = args[:value]
238
+ case value
239
+ when String
240
+ "'#{value}'"
241
+ when TrueClass, FalseClass
242
+ value.to_s
243
+ when Numeric
244
+ value.to_s
245
+ else
246
+ "'#{value}'"
247
+ end
248
+ end
249
+
250
+ def build_array_type(args)
251
+ element = args[:element]
252
+ if element
253
+ if schema_reference?(element.to_s)
254
+ "Array.<#{element}>"
255
+ elsif TYPE_MAPPINGS[element.to_s]
256
+ "Array.<#{TYPE_MAPPINGS[element.to_s]}>"
257
+ else
258
+ # Unknown element types are string-producing types (e.g. array(string)).
259
+ "Array.<string>"
260
+ end
261
+ else
262
+ "Array.<*>"
263
+ end
264
+ end
265
+
266
+ def build_one_of_type(args)
267
+ schemas = args[:schemas] || []
268
+ types = schemas.map do |s|
269
+ schema_name = s.is_a?(Hash) ? (s[:schema] || s["schema"]) : s.to_s
270
+ schema_reference?(schema_name) ? schema_name : "Object"
271
+ end
272
+ "(#{types.join('|')})"
273
+ end
274
+
275
+ def build_factory_function(target_schema)
276
+ lines = []
277
+ lines << "/**"
278
+ lines << " * Create a new #{target_schema.name} object"
279
+ lines << " * @param {Partial<#{target_schema.name}>} [overrides] - Optional field overrides"
280
+ lines << " * @returns {#{target_schema.name}}"
281
+ lines << " */"
282
+ lines << "function create#{target_schema.name}(overrides = {}) {"
283
+ lines << " return {"
284
+
285
+ target_schema.fields.each_with_index do |field, i|
286
+ default = field_default_value(field)
287
+ comma = i < target_schema.fields.length - 1 ? "," : ""
288
+ lines << " #{field.name}: overrides.#{field.name} ?? #{default}#{comma}"
289
+ end
290
+
291
+ lines << " };"
292
+ lines << "}"
293
+ lines.join("\n")
294
+ end
295
+
296
+ def field_default_value(field)
297
+ type_name = field.type_name
298
+
299
+ return "null" if field.optional? || field.nullable?
300
+
301
+ case type_name
302
+ when "uuid", "ulid", "text", "name", "email", "url", "phone", "city", "country", "address"
303
+ "''"
304
+ when "number", "integer", "float", "money", "id_sequence"
305
+ "0"
306
+ when "boolean"
307
+ "false"
308
+ when "array"
309
+ "[]"
310
+ when "object", "map_by_field"
311
+ "{}"
312
+ when "enum"
313
+ values = field.type_args[:values] || []
314
+ values.first ? "'#{values.first.respond_to?(:value) ? values.first.value : values.first}'" : "''"
315
+ when "const"
316
+ value = field.type_args[:value]
317
+ value.is_a?(String) ? "'#{value}'" : value.to_s
318
+ when "timestamp", "now", "date", "past_date", "future_date"
319
+ "new Date().toISOString()"
320
+ else
321
+ schema_reference?(type_name) ? "{}" : "null"
322
+ end
323
+ end
324
+ end
325
+ # :nocov:
326
+ end
327
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Synthra
5
+ module Export
6
+ # Exports generated data as JSON
7
+ #
8
+ # @example Export schema data as JSON
9
+ # exporter = JsonData.new(schema, registry: registry, count: 100)
10
+ # json = exporter.export
11
+ #
12
+ class JsonData < Base
13
+ def export
14
+ count = options[:count] || 10
15
+ seed = options[:seed]
16
+ mode = options[:mode] || :random
17
+ pretty = options[:pretty] != false
18
+
19
+ records = if count == 1
20
+ [schema.generate(seed: seed, mode: mode, registry: registry)]
21
+ else
22
+ schema.generate_many(count, seed: seed, mode: mode, registry: registry)
23
+ end
24
+
25
+ if pretty
26
+ JSON.pretty_generate(wrap_output(records))
27
+ else
28
+ JSON.generate(wrap_output(records))
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def wrap_output(records)
35
+ # :nocov:
36
+ if options[:wrap] == false
37
+ records
38
+ # :nocov:
39
+ elsif options[:envelope]
40
+ {
41
+ options[:envelope] => records,
42
+ "meta" => build_meta(records)
43
+ }
44
+ else
45
+ records
46
+ end
47
+ end
48
+
49
+ def build_meta(records)
50
+ {
51
+ "schema" => schema.name,
52
+ "version" => schema.version,
53
+ "count" => records.is_a?(Array) ? records.length : 1,
54
+ "generated_at" => Time.now.iso8601,
55
+ "seed" => options[:seed]
56
+ }.compact
57
+ end
58
+ end
59
+ # :nocov:
60
+ end
61
+ end
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Synthra
5
+ module Export
6
+ # Exports schema as JSON Schema (draft 2020-12)
7
+ #
8
+ # @example Export a schema
9
+ # exporter = JsonSchema.new(schema, registry: registry)
10
+ # json_schema = exporter.export
11
+ #
12
+ class JsonSchema < Base
13
+ # JSON Schema draft version
14
+ SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema"
15
+
16
+ # Type mappings from DSL types to JSON Schema types
17
+ TYPE_MAPPINGS = {
18
+ # Strings with formats
19
+ "uuid" => { "type" => "string", "format" => "uuid" },
20
+ "ulid" => { "type" => "string", "pattern" => "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" },
21
+ "email" => { "type" => "string", "format" => "email" },
22
+ "url" => { "type" => "string", "format" => "uri" },
23
+ "ip" => { "type" => "string", "format" => "ipv4" },
24
+ "ipv6" => { "type" => "string", "format" => "ipv6" },
25
+ "date" => { "type" => "string", "format" => "date" },
26
+ "date_between" => { "type" => "string", "format" => "date" },
27
+ "past_date" => { "type" => "string", "format" => "date" },
28
+ "future_date" => { "type" => "string", "format" => "date" },
29
+ "mobile_device_release_date" => { "type" => "string", "format" => "date" },
30
+ "now" => { "type" => "string", "format" => "date" },
31
+ "timestamp" => { "type" => "string", "format" => "date-time" },
32
+ "datetime" => { "type" => "string", "format" => "date-time" },
33
+
34
+ # Plain strings
35
+ "text" => { "type" => "string" },
36
+ "name" => { "type" => "string" },
37
+ "full_name" => { "type" => "string" },
38
+ "first_name" => { "type" => "string" },
39
+ "last_name" => { "type" => "string" },
40
+ "phone" => { "type" => "string" },
41
+ "city" => { "type" => "string" },
42
+ "country" => { "type" => "string" },
43
+ "country_code" => { "type" => "string", "minLength" => 2, "maxLength" => 3 },
44
+ "postal_code" => { "type" => "string" },
45
+ "state" => { "type" => "string" },
46
+ "street" => { "type" => "string" },
47
+ "address" => { "type" => "string" },
48
+ "username" => { "type" => "string" },
49
+ "social_handle" => { "type" => "string" },
50
+ "domain" => { "type" => "string", "format" => "hostname" },
51
+ "user_agent" => { "type" => "string" },
52
+ "mac_address" => { "type" => "string" },
53
+ "iban" => { "type" => "string" },
54
+ "credit_card" => { "type" => "string" },
55
+ "currency" => { "type" => "string" },
56
+ "currency_code" => { "type" => "string", "minLength" => 3, "maxLength" => 3 },
57
+ "paragraph" => { "type" => "string" },
58
+ "sentence" => { "type" => "string" },
59
+ "word" => { "type" => "string" },
60
+ "message_text" => { "type" => "string" },
61
+ "hashtag_text" => { "type" => "string" },
62
+
63
+ # Numbers (integers)
64
+ "number" => { "type" => "integer" },
65
+ "integer" => { "type" => "integer" },
66
+ "age" => { "type" => "integer", "minimum" => 0, "maximum" => 150 },
67
+ "airport_elevation" => { "type" => "integer" },
68
+ "discount" => { "type" => "integer" },
69
+ "formula" => { "type" => "integer" },
70
+ "row_number" => { "type" => "integer" },
71
+ "sequence" => { "type" => "integer" },
72
+ "id_sequence" => { "type" => "integer" },
73
+
74
+ # Numbers (floats)
75
+ "float" => { "type" => "number" },
76
+ "money" => { "type" => "number" },
77
+ "latitude" => { "type" => "number", "minimum" => -90, "maximum" => 90 },
78
+ "longitude" => { "type" => "number", "minimum" => -180, "maximum" => 180 },
79
+ "airport_latitude" => { "type" => "number", "minimum" => -90, "maximum" => 90 },
80
+ "airport_longitude" => { "type" => "number", "minimum" => -180, "maximum" => 180 },
81
+ "product_price" => { "type" => "number" },
82
+ "tax_rate" => { "type" => "number" },
83
+ "transaction_amount" => { "type" => "number" },
84
+
85
+ # Numeric-looking strings
86
+ "snowflake_id" => { "type" => "string", "pattern" => "^[0-9]+$" },
87
+ "numeric_string_id" => { "type" => "string", "pattern" => "^[0-9]+$" },
88
+
89
+ # Boolean
90
+ "boolean" => { "type" => "boolean" },
91
+
92
+ # Objects
93
+ "object" => { "type" => "object" },
94
+ "map_by_field" => { "type" => "object" },
95
+
96
+ # Arrays
97
+ "array" => { "type" => "array" },
98
+ "empty_array" => { "type" => "array", "maxItems" => 0 },
99
+ "json_array" => { "type" => "array" },
100
+ "indices_pair" => { "type" => "array", "items" => { "type" => "integer" }, "minItems" => 2, "maxItems" => 2 }
101
+ }.freeze
102
+
103
+ def export
104
+ json_schema = build_root_schema
105
+ JSON.pretty_generate(json_schema)
106
+ end
107
+
108
+ private
109
+
110
+ def build_root_schema
111
+ result = {
112
+ "$schema" => SCHEMA_VERSION,
113
+ "$id" => "#{schema.name.downcase}.json",
114
+ "title" => schema.name,
115
+ "description" => build_description,
116
+ "type" => "object",
117
+ "properties" => {},
118
+ "required" => []
119
+ }
120
+
121
+ # Add $defs for referenced schemas
122
+ defs = build_definitions
123
+ result["$defs"] = defs unless defs.empty?
124
+
125
+ # Add properties
126
+ schema.fields.each do |field|
127
+ result["properties"][field.name] = field_to_schema(field)
128
+ result["required"] << field.name unless field.optional?
129
+ end
130
+
131
+ result
132
+ end
133
+
134
+ def build_description
135
+ parts = ["Generated from Synthra schema: #{schema.name}"]
136
+ parts << "Version: #{schema.version}" if schema.version
137
+ parts << "DEPRECATED: #{schema.deprecation_message}" if schema.deprecated?
138
+ parts.join(". ")
139
+ end
140
+
141
+ def build_definitions
142
+ defs = {}
143
+ collect_referenced_schemas.each do |ref_schema|
144
+ defs[ref_schema.name] = build_schema_definition(ref_schema)
145
+ end
146
+ defs
147
+ end
148
+
149
+ def collect_referenced_schemas
150
+ schemas = []
151
+ schema.fields.each do |field|
152
+ collect_field_schemas(field, schemas)
153
+ end
154
+ schemas.uniq(&:name)
155
+ end
156
+
157
+ def collect_field_schemas(field, schemas)
158
+ type_name = field.type_name
159
+
160
+ if schema_reference?(type_name)
161
+ ref_schema = get_schema(type_name)
162
+ schemas << ref_schema if ref_schema && !schemas.any? { |s| s.name == ref_schema.name }
163
+ end
164
+
165
+ if type_name == "array"
166
+ element = field.type_args[:element]
167
+ if element && schema_reference?(element.to_s)
168
+ ref_schema = get_schema(element.to_s)
169
+ schemas << ref_schema if ref_schema && !schemas.any? { |s| s.name == ref_schema.name }
170
+ end
171
+ end
172
+ end
173
+
174
+ def build_schema_definition(ref_schema)
175
+ result = {
176
+ "type" => "object",
177
+ "properties" => {},
178
+ "required" => []
179
+ }
180
+
181
+ ref_schema.fields.each do |field|
182
+ result["properties"][field.name] = field_to_schema(field)
183
+ result["required"] << field.name unless field.optional?
184
+ end
185
+
186
+ result
187
+ end
188
+
189
+ def field_to_schema(field)
190
+ type_name = field.type_name
191
+ type_args = field.type_args
192
+
193
+ prop = case type_name
194
+ when "enum"
195
+ build_enum_schema(type_args)
196
+ when "const"
197
+ build_const_schema(type_args)
198
+ when "array"
199
+ build_array_schema(type_args)
200
+ when "one_of"
201
+ build_one_of_schema(type_args)
202
+ when "map_by_field"
203
+ build_map_by_field_schema(type_args)
204
+ else
205
+ if TYPE_MAPPINGS[type_name]
206
+ TYPE_MAPPINGS[type_name].dup
207
+ elsif schema_reference?(type_name)
208
+ { "$ref" => "#/$defs/#{type_name}" }
209
+ else
210
+ { "type" => "string" }
211
+ end
212
+ end
213
+
214
+ # Add range constraints
215
+ if type_args[:range] && prop["type"] == "integer"
216
+ range = type_args[:range]
217
+ prop["minimum"] = range.min if range.respond_to?(:min)
218
+ prop["maximum"] = range.max if range.respond_to?(:max)
219
+ end
220
+
221
+ # Handle nullable
222
+ if field.nullable?
223
+ prop = { "oneOf" => [prop, { "type" => "null" }] }
224
+ end
225
+
226
+ prop
227
+ end
228
+
229
+ def build_enum_schema(args)
230
+ values = args[:values]&.map { |v| v.respond_to?(:value) ? v.value : v } || []
231
+ { "type" => "string", "enum" => values }
232
+ end
233
+
234
+ def build_const_schema(args)
235
+ { "const" => args[:value] }
236
+ end
237
+
238
+ def build_array_schema(args)
239
+ element = args[:element]
240
+ size = args[:size]
241
+
242
+ prop = { "type" => "array" }
243
+
244
+ if element
245
+ if schema_reference?(element.to_s)
246
+ prop["items"] = { "$ref" => "#/$defs/#{element}" }
247
+ elsif TYPE_MAPPINGS[element.to_s]
248
+ prop["items"] = TYPE_MAPPINGS[element.to_s].dup
249
+ else
250
+ prop["items"] = { "type" => "string" }
251
+ end
252
+ end
253
+
254
+ if size.is_a?(Range)
255
+ prop["minItems"] = size.min
256
+ prop["maxItems"] = size.max
257
+ elsif size.is_a?(Hash)
258
+ prop["minItems"] = size[:min] if size[:min]
259
+ prop["maxItems"] = size[:max] if size[:max]
260
+ elsif size.is_a?(Integer)
261
+ prop["minItems"] = size
262
+ prop["maxItems"] = size
263
+ end
264
+
265
+ prop
266
+ end
267
+
268
+ def build_one_of_schema(args)
269
+ schemas = args[:schemas] || []
270
+ one_of = schemas.map do |s|
271
+ schema_name = s.is_a?(Hash) ? (s[:schema] || s["schema"]) : s.to_s
272
+ if schema_reference?(schema_name)
273
+ { "$ref" => "#/$defs/#{schema_name}" }
274
+ else
275
+ { "type" => "object" }
276
+ end
277
+ end
278
+ { "oneOf" => one_of }
279
+ end
280
+
281
+ def build_map_by_field_schema(args)
282
+ {
283
+ "type" => "object",
284
+ "additionalProperties" => { "type" => "object" }
285
+ }
286
+ end
287
+ end
288
+ # :nocov:
289
+ end
290
+ end