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,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Synthra
6
+ # Deterministic ID Assignment
7
+ #
8
+ # Generates stable, reproducible IDs based on context.
9
+ # Useful for testing and seeding where you need consistent IDs.
10
+ #
11
+ # @example DSL syntax
12
+ # User:
13
+ # id: uuid(variant: "user", index: $index)
14
+ #
15
+ # @example Ruby API
16
+ # Synthra::DeterministicIds.uuid(variant: "user", index: 1, seed: 42)
17
+ # # => "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" (always the same)
18
+ #
19
+ module DeterministicIds
20
+ # UUID namespace for Synthra (generated once, fixed)
21
+ NAMESPACE = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
22
+
23
+ class << self
24
+ # Generate a deterministic UUID
25
+ #
26
+ # @param variant [String] variant name (e.g., "user", "order")
27
+ # @param index [Integer] index for sequence
28
+ # @param seed [Integer, nil] random seed
29
+ # @return [String] deterministic UUID
30
+ #
31
+ # @example
32
+ # DeterministicIds.uuid(variant: "user", index: 0)
33
+ # # => "f47ac10b-58cc-4372-a567-0e02b2c3d479"
34
+ #
35
+ def uuid(variant: nil, index: 0, seed: nil)
36
+ input = build_input(variant, index, seed)
37
+ hash = Digest::SHA256.hexdigest(input)
38
+
39
+ # Format as UUID v4
40
+ format_as_uuid(hash)
41
+ end
42
+
43
+ # Generate a deterministic integer ID
44
+ #
45
+ # @param variant [String] variant name
46
+ # @param index [Integer] index
47
+ # @param seed [Integer, nil] random seed
48
+ # @param max [Integer] maximum value
49
+ # @return [Integer] deterministic ID
50
+ #
51
+ def integer(variant: nil, index: 0, seed: nil, max: 2_147_483_647)
52
+ input = build_input(variant, index, seed)
53
+ hash = Digest::MD5.hexdigest(input)
54
+
55
+ # Convert first 8 bytes to integer
56
+ hash[0, 16].to_i(16) % max
57
+ end
58
+
59
+ # Generate a deterministic short ID (like YouTube IDs)
60
+ #
61
+ # @param variant [String] variant name
62
+ # @param index [Integer] index
63
+ # @param seed [Integer, nil] random seed
64
+ # @param length [Integer] ID length
65
+ # @return [String] deterministic short ID
66
+ #
67
+ def short_id(variant: nil, index: 0, seed: nil, length: 11)
68
+ alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
69
+ input = build_input(variant, index, seed)
70
+ hash = Digest::SHA256.hexdigest(input)
71
+
72
+ result = +""
73
+ length.times do |i|
74
+ byte = hash[(i * 2), 2].to_i(16)
75
+ result << alphabet[byte % alphabet.length]
76
+ end
77
+
78
+ result
79
+ end
80
+
81
+ # Generate a deterministic slug
82
+ #
83
+ # @param variant [String] variant name
84
+ # @param index [Integer] index
85
+ # @param seed [Integer, nil] random seed
86
+ # @param words [Integer] number of words
87
+ # @return [String] deterministic slug
88
+ #
89
+ def slug(variant: nil, index: 0, seed: nil, words: 3)
90
+ word_list = %w[
91
+ alpha beta gamma delta epsilon zeta eta theta iota kappa
92
+ lambda mu nu xi omicron pi rho sigma tau upsilon
93
+ able baker charlie delta echo foxtrot golf hotel india juliet
94
+ quick brown fox jumps over lazy dog hello world test
95
+ ]
96
+
97
+ input = build_input(variant, index, seed)
98
+ hash = Digest::SHA256.hexdigest(input)
99
+
100
+ selected = []
101
+ words.times do |i|
102
+ byte = hash[(i * 2), 2].to_i(16)
103
+ selected << word_list[byte % word_list.length]
104
+ end
105
+
106
+ selected.join("-")
107
+ end
108
+
109
+ # Generate deterministic IDs for a batch
110
+ #
111
+ # @param count [Integer] number of IDs
112
+ # @param type [Symbol] ID type (:uuid, :integer, :short_id, :slug)
113
+ # @param variant [String] variant name
114
+ # @param seed [Integer, nil] random seed
115
+ # @return [Array<String|Integer>] array of IDs
116
+ #
117
+ def batch(count, type: :uuid, variant: nil, seed: nil, **options)
118
+ count.times.map do |i|
119
+ send(type, variant: variant, index: i, seed: seed, **options)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def build_input(variant, index, seed)
126
+ parts = [NAMESPACE]
127
+ parts << variant.to_s if variant
128
+ parts << index.to_s
129
+ parts << seed.to_s if seed
130
+ parts.join("-")
131
+ end
132
+
133
+ def format_as_uuid(hash)
134
+ # RFC 4122 v4: the leading "4" sets the version nibble; the 17th nibble must encode variant
135
+ # 10xx (i.e. 8/9/a/b) or strict UUID validators reject it. Mask hash[16] into that range.
136
+ variant = ((hash[16].to_i(16) & 0x3) | 0x8).to_s(16)
137
+ "#{hash[0, 8]}-#{hash[8, 4]}-4#{hash[13, 3]}-#{variant}#{hash[17, 3]}-#{hash[20, 12]}"
138
+ end
139
+ end
140
+ end
141
+
142
+ # Register deterministic ID types
143
+ module Types
144
+ module Core
145
+ # Deterministic UUID type
146
+ class DeterministicUuid < Base
147
+ def generate_random(rng, context, args)
148
+ variant = args[:variant]
149
+ index = args[:index] || context&.shared_context&.dig(:index) || 0
150
+ seed = args[:seed] || context[:seed]
151
+
152
+ DeterministicIds.uuid(variant: variant, index: index, seed: seed)
153
+ end
154
+
155
+ def generate_edge(rng, context, args)
156
+ generate_random(rng, context, args)
157
+ end
158
+
159
+ def generate_invalid(rng, context, args)
160
+ rng.sample(["", "invalid-uuid", "12345", nil])
161
+ end
162
+
163
+ def generate_hostile(rng, context, args)
164
+ "'; DROP TABLE users;--"
165
+ end
166
+ end
167
+
168
+ # Deterministic integer ID type
169
+ class DeterministicId < Base
170
+ def generate_random(rng, context, args)
171
+ variant = args[:variant]
172
+ index = args[:index] || context&.shared_context&.dig(:index) || 0
173
+ seed = args[:seed] || context[:seed]
174
+ max = args[:max] || 2_147_483_647
175
+
176
+ DeterministicIds.integer(variant: variant, index: index, seed: seed, max: max)
177
+ end
178
+
179
+ def generate_edge(rng, context, args)
180
+ # Edge cases: 0, 1, max
181
+ rng.sample([0, 1, args[:max] || 2_147_483_647])
182
+ end
183
+
184
+ def generate_invalid(rng, context, args)
185
+ rng.sample([-1, nil, "not_a_number", 0.5])
186
+ end
187
+
188
+ def generate_hostile(rng, context, args)
189
+ -1
190
+ end
191
+ end
192
+
193
+ # Deterministic short ID type
194
+ class ShortId < Base
195
+ def generate_random(rng, context, args)
196
+ variant = args[:variant]
197
+ index = args[:index] || context&.shared_context&.dig(:index) || 0
198
+ seed = args[:seed] || context[:seed]
199
+ length = args[:length] || 11
200
+
201
+ DeterministicIds.short_id(variant: variant, index: index, seed: seed, length: length)
202
+ end
203
+
204
+ def generate_edge(rng, context, args)
205
+ rng.sample(["", "a", "A" * (args[:length] || 11)])
206
+ end
207
+
208
+ def generate_invalid(rng, context, args)
209
+ rng.sample([nil, 123, "!@#$%"])
210
+ end
211
+
212
+ def generate_hostile(rng, context, args)
213
+ "<script>alert(1)</script>"
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,414 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Synthra
6
+ # Schema Documentation Generator
7
+ #
8
+ # Generates beautiful HTML documentation from schemas.
9
+ #
10
+ # @example CLI usage
11
+ # $ synthra docs schemas/ --output docs/schemas/
12
+ #
13
+ # @example Ruby usage
14
+ # generator = Synthra::DocumentationGenerator.new(registry)
15
+ # generator.generate_html("output/")
16
+ #
17
+ class DocumentationGenerator
18
+ attr_reader :registry, :options
19
+
20
+ def initialize(registry, **options)
21
+ @registry = registry
22
+ @options = {
23
+ title: "Synthra Schema Documentation",
24
+ theme: "dark"
25
+ }.merge(options)
26
+ end
27
+
28
+ # Generate HTML documentation
29
+ #
30
+ # @param output_dir [String] output directory
31
+ # @return [Array<String>] created file paths
32
+ #
33
+ def generate_html(output_dir)
34
+ FileUtils.mkdir_p(output_dir)
35
+
36
+ files = []
37
+
38
+ # Generate index
39
+ files << generate_index(output_dir)
40
+
41
+ # Generate individual schema pages
42
+ @registry.names.each do |name|
43
+ files << generate_schema_page(name, output_dir)
44
+ end
45
+
46
+ # Generate styles
47
+ files << generate_styles(output_dir)
48
+
49
+ files
50
+ end
51
+
52
+ private
53
+
54
+ def generate_index(output_dir)
55
+ path = File.join(output_dir, "index.html")
56
+
57
+ schemas = @registry.names.sort.map do |name|
58
+ schema = @registry.schema(name)
59
+ {
60
+ name: name,
61
+ field_count: schema.fields.count,
62
+ description: extract_description(schema)
63
+ }
64
+ end
65
+
66
+ content = ERB.new(index_template).result(binding)
67
+ File.write(path, content)
68
+
69
+ path
70
+ end
71
+
72
+ def generate_schema_page(name, output_dir)
73
+ path = File.join(output_dir, "#{to_filename(name)}.html")
74
+
75
+ schema = @registry.schema(name)
76
+ sample = schema.generate(seed: 42)
77
+ edge_sample = schema.generate(seed: 42, mode: :edge)
78
+
79
+ fields = schema.fields.map do |field|
80
+ {
81
+ name: field.name,
82
+ type: field.type_name,
83
+ optional: field.optional?,
84
+ nullable: field.respond_to?(:nullable?) ? field.nullable? : false,
85
+ description: field.respond_to?(:description) ? field.description : nil
86
+ }
87
+ end
88
+
89
+ content = ERB.new(schema_template).result(binding)
90
+ File.write(path, content)
91
+
92
+ path
93
+ end
94
+
95
+ def generate_styles(output_dir)
96
+ path = File.join(output_dir, "styles.css")
97
+ File.write(path, css_styles)
98
+ path
99
+ end
100
+
101
+ def extract_description(schema)
102
+ # Try to get description from first field comment or behaviors
103
+ nil
104
+ end
105
+
106
+ def to_filename(name)
107
+ name.gsub(/[^a-zA-Z0-9]/, "_").downcase
108
+ end
109
+
110
+ def index_template
111
+ <<~ERB
112
+ <!DOCTYPE html>
113
+ <html lang="en">
114
+ <head>
115
+ <meta charset="UTF-8">
116
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
117
+ <title><%= @options[:title] %></title>
118
+ <link rel="stylesheet" href="styles.css">
119
+ </head>
120
+ <body>
121
+ <div class="container">
122
+ <header>
123
+ <h1>📚 <%= @options[:title] %></h1>
124
+ <p class="meta">Generated on <%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %></p>
125
+ </header>
126
+
127
+ <main>
128
+ <section class="stats">
129
+ <div class="stat-card">
130
+ <span class="stat-value"><%= schemas.count %></span>
131
+ <span class="stat-label">Schemas</span>
132
+ </div>
133
+ <div class="stat-card">
134
+ <span class="stat-value"><%= schemas.sum { |s| s[:field_count] } %></span>
135
+ <span class="stat-label">Total Fields</span>
136
+ </div>
137
+ </section>
138
+
139
+ <section class="schema-list">
140
+ <h2>Available Schemas</h2>
141
+ <div class="schema-grid">
142
+ <% schemas.each do |schema| %>
143
+ <a href="<%= to_filename(schema[:name]) %>.html" class="schema-card">
144
+ <h3><%= schema[:name] %></h3>
145
+ <p class="field-count"><%= schema[:field_count] %> fields</p>
146
+ </a>
147
+ <% end %>
148
+ </div>
149
+ </section>
150
+ </main>
151
+
152
+ <footer>
153
+ <p>Generated with Synthra</p>
154
+ </footer>
155
+ </div>
156
+ </body>
157
+ </html>
158
+ ERB
159
+ end
160
+
161
+ def schema_template
162
+ <<~ERB
163
+ <!DOCTYPE html>
164
+ <html lang="en">
165
+ <head>
166
+ <meta charset="UTF-8">
167
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
168
+ <title><%= name %> | <%= @options[:title] %></title>
169
+ <link rel="stylesheet" href="styles.css">
170
+ </head>
171
+ <body>
172
+ <div class="container">
173
+ <header>
174
+ <nav>
175
+ <a href="index.html">← Back to Index</a>
176
+ </nav>
177
+ <h1><%= name %></h1>
178
+ </header>
179
+
180
+ <main>
181
+ <section class="field-table">
182
+ <h2>Fields</h2>
183
+ <table>
184
+ <thead>
185
+ <tr>
186
+ <th>Name</th>
187
+ <th>Type</th>
188
+ <th>Required</th>
189
+ <th>Nullable</th>
190
+ </tr>
191
+ </thead>
192
+ <tbody>
193
+ <% fields.each do |field| %>
194
+ <tr>
195
+ <td class="field-name"><%= field[:name] %></td>
196
+ <td class="field-type"><code><%= field[:type] %></code></td>
197
+ <td><%= field[:optional] ? '⚪ Optional' : '🔴 Required' %></td>
198
+ <td><%= field[:nullable] ? '✅ Yes' : '❌ No' %></td>
199
+ </tr>
200
+ <% end %>
201
+ </tbody>
202
+ </table>
203
+ </section>
204
+
205
+ <section class="sample-data">
206
+ <h2>Sample Data (Random Mode)</h2>
207
+ <pre><code><%= JSON.pretty_generate(sample) %></code></pre>
208
+ </section>
209
+
210
+ <section class="sample-data">
211
+ <h2>Sample Data (Edge Mode)</h2>
212
+ <pre><code><%= JSON.pretty_generate(edge_sample) %></code></pre>
213
+ </section>
214
+ </main>
215
+
216
+ <footer>
217
+ <p>Generated with Synthra</p>
218
+ </footer>
219
+ </div>
220
+ </body>
221
+ </html>
222
+ ERB
223
+ end
224
+
225
+ def css_styles
226
+ <<~CSS
227
+ :root {
228
+ --bg: #0d1117;
229
+ --surface: #161b22;
230
+ --primary: #58a6ff;
231
+ --secondary: #bc8cff;
232
+ --success: #3fb950;
233
+ --warning: #d29922;
234
+ --danger: #f85149;
235
+ --text: #c9d1d9;
236
+ --text-muted: #8b949e;
237
+ --border: #30363d;
238
+ }
239
+
240
+ * {
241
+ box-sizing: border-box;
242
+ margin: 0;
243
+ padding: 0;
244
+ }
245
+
246
+ body {
247
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
248
+ background: var(--bg);
249
+ color: var(--text);
250
+ line-height: 1.6;
251
+ }
252
+
253
+ .container {
254
+ max-width: 1200px;
255
+ margin: 0 auto;
256
+ padding: 2rem;
257
+ }
258
+
259
+ header {
260
+ margin-bottom: 2rem;
261
+ padding-bottom: 1rem;
262
+ border-bottom: 1px solid var(--border);
263
+ }
264
+
265
+ header nav {
266
+ margin-bottom: 1rem;
267
+ }
268
+
269
+ header nav a {
270
+ color: var(--primary);
271
+ text-decoration: none;
272
+ }
273
+
274
+ header nav a:hover {
275
+ text-decoration: underline;
276
+ }
277
+
278
+ h1 {
279
+ font-size: 2rem;
280
+ margin-bottom: 0.5rem;
281
+ }
282
+
283
+ h2 {
284
+ font-size: 1.25rem;
285
+ margin-bottom: 1rem;
286
+ color: var(--text-muted);
287
+ }
288
+
289
+ .meta {
290
+ color: var(--text-muted);
291
+ font-size: 0.875rem;
292
+ }
293
+
294
+ .stats {
295
+ display: flex;
296
+ gap: 1rem;
297
+ margin-bottom: 2rem;
298
+ }
299
+
300
+ .stat-card {
301
+ background: var(--surface);
302
+ border: 1px solid var(--border);
303
+ border-radius: 8px;
304
+ padding: 1.5rem;
305
+ display: flex;
306
+ flex-direction: column;
307
+ align-items: center;
308
+ }
309
+
310
+ .stat-value {
311
+ font-size: 2rem;
312
+ font-weight: bold;
313
+ color: var(--primary);
314
+ }
315
+
316
+ .stat-label {
317
+ color: var(--text-muted);
318
+ font-size: 0.875rem;
319
+ }
320
+
321
+ .schema-grid {
322
+ display: grid;
323
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
324
+ gap: 1rem;
325
+ }
326
+
327
+ .schema-card {
328
+ background: var(--surface);
329
+ border: 1px solid var(--border);
330
+ border-radius: 8px;
331
+ padding: 1.5rem;
332
+ text-decoration: none;
333
+ color: inherit;
334
+ transition: border-color 0.2s, transform 0.2s;
335
+ }
336
+
337
+ .schema-card:hover {
338
+ border-color: var(--primary);
339
+ transform: translateY(-2px);
340
+ }
341
+
342
+ .schema-card h3 {
343
+ color: var(--primary);
344
+ margin-bottom: 0.5rem;
345
+ }
346
+
347
+ .field-count {
348
+ color: var(--text-muted);
349
+ font-size: 0.875rem;
350
+ }
351
+
352
+ section {
353
+ margin-bottom: 2rem;
354
+ }
355
+
356
+ table {
357
+ width: 100%;
358
+ border-collapse: collapse;
359
+ background: var(--surface);
360
+ border-radius: 8px;
361
+ overflow: hidden;
362
+ }
363
+
364
+ th, td {
365
+ padding: 1rem;
366
+ text-align: left;
367
+ border-bottom: 1px solid var(--border);
368
+ }
369
+
370
+ th {
371
+ background: var(--bg);
372
+ font-weight: 600;
373
+ color: var(--text-muted);
374
+ text-transform: uppercase;
375
+ font-size: 0.75rem;
376
+ }
377
+
378
+ .field-name {
379
+ font-weight: 600;
380
+ }
381
+
382
+ .field-type code {
383
+ background: var(--bg);
384
+ padding: 0.25rem 0.5rem;
385
+ border-radius: 4px;
386
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
387
+ color: var(--secondary);
388
+ }
389
+
390
+ pre {
391
+ background: var(--surface);
392
+ border: 1px solid var(--border);
393
+ border-radius: 8px;
394
+ padding: 1rem;
395
+ overflow-x: auto;
396
+ }
397
+
398
+ code {
399
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
400
+ font-size: 0.875rem;
401
+ }
402
+
403
+ footer {
404
+ margin-top: 3rem;
405
+ padding-top: 1rem;
406
+ border-top: 1px solid var(--border);
407
+ text-align: center;
408
+ color: var(--text-muted);
409
+ font-size: 0.875rem;
410
+ }
411
+ CSS
412
+ end
413
+ end
414
+ end