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,605 @@
1
+ # frozen_string_literal: true
2
+
3
+ # =============================================================================
4
+
5
+ # Synthra Schema Class
6
+ # =============================================================================
7
+ #
8
+ # This file defines the Schema class which represents a complete schema
9
+ # definition parsed from DSL. A Schema contains fields, behaviors, and
10
+ # provides methods for generating fake data.
11
+ #
12
+ # @example DSL definition
13
+ # User:
14
+ # @seed 12345
15
+ # @latency 50..100ms
16
+ # id: uuid
17
+ # name: name
18
+ # email: email
19
+ # age: number(18..80)
20
+ #
21
+ # @example Using a schema
22
+ # schema = Synthra.load("user.dsl")
23
+ # user = schema.generate
24
+ # users = schema.generate_many(100)
25
+ #
26
+ # =============================================================================
27
+
28
+
29
+ module Synthra
30
+
31
+ # Represents a complete schema definition
32
+ #
33
+ # A Schema is the parsed representation of a DSL schema definition.
34
+ # It contains all the fields, behaviors, and metadata needed to generate
35
+ # fake data records.
36
+ #
37
+ # Schemas are typically created by loading DSL files through the Registry
38
+ # or Synthra.load, but can also be created programmatically.
39
+ #
40
+ # @example Load from file
41
+ # schema = Synthra.load("schemas/user.dsl")
42
+ #
43
+ # @example Generate single record
44
+ # user = schema.generate
45
+ # # => { "id" => "550e8400-...", "name" => "John Doe", ... }
46
+ #
47
+ # @example Generate with options
48
+ # user = schema.generate(
49
+ # seed: 12345, # Deterministic output
50
+ # mode: :edge, # Edge case values
51
+ # overrides: { "name" => "Custom Name" }
52
+ # )
53
+ #
54
+ # @example Generate multiple records
55
+ # users = schema.generate_many(100, seed: 42)
56
+ # # => [{ "id" => "...", ... }, { "id" => "...", ... }, ...]
57
+ #
58
+ # @example With runtime behaviors
59
+ # slow_schema = schema.with_behavior(:latency, 500..1000)
60
+ # user = slow_schema.generate # Will have 500-1000ms delay
61
+ #
62
+
63
+ class Schema
64
+
65
+ # @!attribute [r] name
66
+ # The schema name (from DSL: "User:")
67
+ # @return [String] schema name
68
+
69
+ attr_reader :name
70
+
71
+
72
+ # @!attribute [r] fields
73
+ # All fields defined in this schema
74
+ # @return [Array<Field>] list of Field objects
75
+
76
+ attr_reader :fields
77
+
78
+
79
+ # @!attribute [r] behaviors
80
+ # Schema-level behaviors defined in DSL
81
+ # @return [Array<Hash>] list of behavior hashes ({ name:, value: })
82
+
83
+ attr_reader :behaviors
84
+
85
+
86
+ # @!attribute [r] metadata
87
+ # Additional metadata (seed, etc.)
88
+ # @return [Hash] metadata key-value pairs
89
+
90
+ attr_reader :metadata
91
+
92
+
93
+ # @!attribute [r] version
94
+ # Schema version from @version annotation
95
+ # @return [String, nil] version string or nil if not specified
96
+
97
+ attr_reader :version
98
+
99
+
100
+ # @!attribute [r] deprecated
101
+ # Whether this schema is deprecated
102
+ # @return [Boolean] true if schema has @deprecated annotation
103
+
104
+ attr_reader :deprecated
105
+
106
+
107
+ # @!attribute [r] deprecation_message
108
+ # Deprecation message if schema is deprecated
109
+ # @return [String, nil] deprecation message or nil
110
+
111
+ attr_reader :deprecation_message
112
+
113
+
114
+ # Create a new Schema instance
115
+ #
116
+ # @param name [String] the schema name
117
+ # @param fields [Array<Field>] field definitions
118
+ # @param behaviors [Array<Hash>] schema-level behaviors
119
+ # @param metadata [Hash] additional metadata
120
+ # @param version [String, nil] schema version
121
+ # @param deprecated [Boolean] whether schema is deprecated
122
+ # @param deprecation_message [String, nil] deprecation message
123
+ #
124
+ # @example
125
+ # schema = Schema.new(
126
+ # name: "User",
127
+ # fields: [
128
+ # Field.new(name: "id", type_name: "uuid"),
129
+ # Field.new(name: "name", type_name: "name")
130
+ # ],
131
+ # behaviors: [{ name: :latency, value: 100 }],
132
+ # version: "1.0"
133
+ # )
134
+ #
135
+
136
+ def initialize(name:, fields: [], behaviors: [], metadata: {}, version: nil, deprecated: false, deprecation_message: nil)
137
+ @name = name
138
+ @fields = fields
139
+ @behaviors = behaviors
140
+ @metadata = metadata
141
+ @version = version
142
+ @deprecated = deprecated
143
+ @deprecation_message = deprecation_message
144
+ @runtime_behaviors = []
145
+ end
146
+
147
+
148
+ # Check if schema is deprecated
149
+ #
150
+ # @return [Boolean] true if deprecated
151
+ #
152
+
153
+ def deprecated?
154
+ @deprecated
155
+ end
156
+
157
+
158
+ # Get schema info hash for display
159
+ #
160
+ # @return [Hash] schema information
161
+ #
162
+
163
+ def info
164
+ {
165
+ name: @name,
166
+ version: @version,
167
+ deprecated: @deprecated,
168
+ deprecation_message: @deprecation_message,
169
+ field_count: @fields.length,
170
+ behavior_count: @behaviors.length,
171
+ fields: @fields.map(&:name)
172
+ }
173
+ end
174
+
175
+
176
+ # Get a field by name
177
+ #
178
+ # @param field_name [String, Symbol] the field name to find
179
+ # @return [Field, nil] the field or nil if not found
180
+ #
181
+ # @example
182
+ # email_field = schema.field("email")
183
+ # email_field.type_name # => "email"
184
+ #
185
+
186
+ def field(field_name)
187
+ fields.find { |f| f.name == field_name.to_s }
188
+ end
189
+
190
+
191
+ # Get all required (non-optional) fields
192
+ #
193
+ # @return [Array<Field>] fields that are not marked as optional
194
+ #
195
+ # @example
196
+ # schema.required_fields.each do |field|
197
+ # puts "#{field.name} is required"
198
+ # end
199
+ #
200
+
201
+ def required_fields
202
+ fields.reject(&:optional?)
203
+ end
204
+
205
+
206
+ # Get all optional fields
207
+ #
208
+ # @return [Array<Field>] fields marked with ? suffix
209
+ #
210
+ # @example
211
+ # schema.optional_fields.each do |field|
212
+ # puts "#{field.name} is optional"
213
+ # end
214
+ #
215
+
216
+ def optional_fields
217
+ fields.select(&:optional?)
218
+ end
219
+
220
+
221
+ # Generate a single record
222
+ #
223
+ # Creates one fake data record based on the schema definition.
224
+ # The record is a Hash with field names as keys.
225
+ #
226
+ # @param seed [Integer, nil] random seed for deterministic output
227
+ # @param mode [Symbol, nil] generation mode (:random, :edge, :invalid, :mixed)
228
+ # Defaults to Synthra.configuration.default_mode
229
+ # @param overrides [Hash] field values to use instead of generating
230
+ # @param registry [Registry, nil] registry for resolving cross-schema references
231
+ # @return [Hash] the generated record
232
+ #
233
+ # @example Basic generation
234
+ # user = schema.generate
235
+ # # => { "id" => "550e8400-...", "name" => "John Doe", "email" => "john@..." }
236
+ #
237
+ # @example Deterministic generation
238
+ # user1 = schema.generate(seed: 12345)
239
+ # user2 = schema.generate(seed: 12345)
240
+ # user1 == user2 # => true
241
+ #
242
+ # @example With overrides
243
+ # user = schema.generate(overrides: { "name" => "Test User" })
244
+ # user["name"] # => "Test User"
245
+ #
246
+ # @example Edge case mode
247
+ # user = schema.generate(mode: :edge)
248
+ # # May contain empty strings, nil values, boundary values
249
+ #
250
+
251
+ def generate(seed: nil, mode: nil, overrides: {}, registry: nil, depth: 0, resolver: nil, parent_context: nil, shared_context: nil)
252
+ mode ||= Synthra.configuration.default_mode
253
+ # Inherit shared_context from parent_context if not explicitly provided
254
+ shared_context ||= parent_context&.instance_variable_get(:@shared_context)
255
+ engine = Generator::Engine.new(self, registry: registry, resolver: resolver)
256
+ result = engine.generate(seed: seed, mode: mode, overrides: overrides, depth: depth, parent_context: parent_context, shared_context: shared_context)
257
+ apply_behaviors(result, engine.rng)
258
+ end
259
+
260
+
261
+ # Generate multiple records
262
+ #
263
+ # Creates multiple fake data records in a batch. More efficient
264
+ # than calling generate() multiple times.
265
+ #
266
+ # @param count [Integer] number of records to generate
267
+ # @param seed [Integer, nil] random seed for deterministic output
268
+ # @param mode [Symbol, nil] generation mode
269
+ # @param overrides [Hash] field values to override for all records
270
+ # @param registry [Registry, nil] registry for cross-schema references
271
+ # @return [Array<Hash>] array of generated records
272
+ #
273
+ # @example Generate 100 users
274
+ # users = schema.generate_many(100)
275
+ # users.length # => 100
276
+ #
277
+ # @example Deterministic batch
278
+ # users1 = schema.generate_many(10, seed: 42)
279
+ # users2 = schema.generate_many(10, seed: 42)
280
+ # users1 == users2 # => true
281
+ #
282
+
283
+ def generate_many(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil, engine: nil, locale: "en", threads: nil)
284
+ mode ||= Synthra.configuration.default_mode
285
+
286
+ # Use native engine if requested and available
287
+ if engine == :native
288
+ return generate_many_native(count, seed: seed, locale: locale, threads: threads)
289
+ end
290
+
291
+ # ponytail: fast_mode once selected a Generator::FastEngine that was never implemented (it
292
+ # crashed). The real high-throughput path is the native Rust engine (engine: :native); the
293
+ # standard engine handles everything else.
294
+ gen = Generator::Engine.new(self, registry: registry)
295
+
296
+ gen.generate_many(count, seed: seed, mode: mode, overrides: overrides, shared_context: shared_context)
297
+ end
298
+
299
+
300
+ # Generate multiple records using the Native Rust engine (maximum performance)
301
+ #
302
+ # Uses the Rust native extension with fake-rs for 100-250x faster generation.
303
+ # Requires the native extension to be compiled.
304
+ #
305
+ # @param count [Integer] number of records to generate
306
+ # @param seed [Integer, nil] random seed for deterministic output
307
+ # @param locale [String] locale for data generation (en, fr_fr, de_de, ja_jp, etc.)
308
+ # @param threads [Integer, nil] number of threads (nil/0 = use all cores)
309
+ # @return [Array<Hash>] array of generated records
310
+ # @raise [LoadError] if native extension is not available
311
+ #
312
+ # @example Native engine generation
313
+ # users = schema.generate_many_native(1_000_000, seed: 42)
314
+ #
315
+ # @example With locale
316
+ # users = schema.generate_many_native(100_000, locale: "ja_jp")
317
+ #
318
+ # @example With limited threads
319
+ # users = schema.generate_many_native(100_000, threads: 4)
320
+ #
321
+
322
+ def generate_many_native(count, seed: nil, locale: "en", threads: nil)
323
+ native_engine = NativeEngine.new(self, locale: locale, threads: threads)
324
+ native_engine.generate_many(count, seed: seed)
325
+ end
326
+
327
+
328
+ # Generate records directly to a file using the Native Rust engine
329
+ #
330
+ # This is the most efficient method for generating large amounts of data.
331
+ # Uses the Rust native extension for maximum performance.
332
+ #
333
+ # @param count [Integer] number of records to generate
334
+ # @param path [String] output file path
335
+ # @param seed [Integer, nil] random seed for deterministic output
336
+ # @param locale [String] locale for data generation
337
+ # @param format [String] output format: "jsonl", "json", "csv"
338
+ # @param threads [Integer, nil] number of threads (nil/0 = use all cores)
339
+ # @return [Integer] number of records written
340
+ # @raise [LoadError] if native extension is not available
341
+ #
342
+ # @example Generate 10 million records to JSONL
343
+ # schema.generate_to_file(10_000_000, "users.jsonl", format: "jsonl")
344
+ #
345
+ # @example Generate CSV with seed
346
+ # schema.generate_to_file(1_000_000, "users.csv", seed: 42, format: "csv")
347
+ #
348
+ # @example Generate with limited threads (leaves other cores free)
349
+ # schema.generate_to_file(10_000_000, "users.jsonl", threads: 4)
350
+ #
351
+
352
+ def generate_to_file(count, path, seed: nil, locale: "en", format: "jsonl", threads: nil)
353
+ native_engine = NativeEngine.new(self, locale: locale, threads: threads)
354
+ native_engine.generate_to_file(count, path, seed: seed, format: format)
355
+ end
356
+
357
+
358
+ # Generate multiple records using FastEngine (high performance)
359
+ #
360
+ # Uses the optimized FastEngine for 2-3x faster generation.
361
+ # NOT thread-safe - use only in single-threaded contexts.
362
+ #
363
+ # @param count [Integer] number of records to generate
364
+ # @param seed [Integer, nil] random seed for deterministic output
365
+ # @param mode [Symbol, nil] generation mode
366
+ # @param overrides [Hash] field values to override for all records
367
+ # @param registry [Registry, nil] registry for cross-schema references
368
+ # @return [Array<Hash>] array of generated records
369
+ #
370
+ # @example Fast batch generation
371
+ # users = schema.generate_many_fast(100_000, seed: 42)
372
+ #
373
+
374
+ def generate_many_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil)
375
+ mode ||= Synthra.configuration.default_mode
376
+ # ponytail: kept as an alias for the standard engine — FastEngine was never implemented.
377
+ engine = Generator::Engine.new(self, registry: registry)
378
+ engine.generate_many(count, seed: seed, mode: mode, overrides: overrides)
379
+ end
380
+
381
+
382
+ # Generate multiple records as a lazy stream (Enumerator)
383
+ #
384
+ # Returns an Enumerator that generates records on-demand. This is
385
+ # memory-efficient for large batches as records are not all held
386
+ # in memory at once.
387
+ #
388
+ # @param count [Integer] total number of records to generate
389
+ # @param seed [Integer, nil] random seed for deterministic output
390
+ # @param mode [Symbol, nil] generation mode
391
+ # @param overrides [Hash] field values to override for all records
392
+ # @param registry [Registry, nil] registry for cross-schema references
393
+ # @return [Enumerator] lazy enumerator of records
394
+ #
395
+ # @example Stream processing for large batches
396
+ # schema.generate_many_stream(10000, seed: 42).each do |record|
397
+ # process(record) # Records generated one at a time
398
+ # end
399
+ #
400
+ # @note For small batches (< 1000 records), generate_many is more efficient.
401
+ # Use generate_many_stream for large batches to reduce memory usage.
402
+ #
403
+
404
+ def generate_many_stream(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil)
405
+ mode ||= Synthra.configuration.default_mode
406
+
407
+ # ponytail: fast_mode's FastEngine was never implemented; use the standard engine.
408
+ engine = Generator::Engine.new(self, registry: registry)
409
+
410
+ engine.generate_many_stream(count, seed: seed, mode: mode, overrides: overrides, shared_context: shared_context)
411
+ end
412
+
413
+
414
+ # Generate stream using FastEngine (high performance)
415
+ #
416
+ # Uses the optimized FastEngine for 2-3x faster streaming.
417
+ # NOT thread-safe - use only in single-threaded contexts.
418
+ #
419
+ # @param count [Integer] number of records to generate
420
+ # @param seed [Integer, nil] random seed for deterministic output
421
+ # @param mode [Symbol, nil] generation mode
422
+ # @param overrides [Hash] field values to override for all records
423
+ # @param registry [Registry, nil] registry for cross-schema references
424
+ # @return [Enumerator] lazy enumerator of records
425
+ #
426
+ # @example Fast streaming
427
+ # schema.generate_stream_fast(1_000_000, seed: 42).each do |record|
428
+ # process(record)
429
+ # end
430
+ #
431
+
432
+ def generate_stream_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil)
433
+ mode ||= Synthra.configuration.default_mode
434
+ # ponytail: kept as an alias for the standard engine — FastEngine was never implemented.
435
+ engine = Generator::Engine.new(self, registry: registry)
436
+ engine.generate_many_stream(count, seed: seed, mode: mode, overrides: overrides)
437
+ end
438
+
439
+
440
+ # Generate a record as a JSON string
441
+ #
442
+ # Convenience method that generates a record and serializes it to JSON.
443
+ #
444
+ # @param pretty [Boolean] if true, format with indentation
445
+ # @param options [Hash] additional options passed to generate()
446
+ # @return [String] JSON string
447
+ #
448
+ # @example Minified JSON
449
+ # json = schema.generate_json
450
+ # # => '{"id":"550e8400-...","name":"John Doe"}'
451
+ #
452
+ # @example Pretty-printed JSON
453
+ # json = schema.generate_json(pretty: true)
454
+ # # => '{\n "id": "550e8400-...",\n "name": "John Doe"\n}'
455
+ #
456
+
457
+ def generate_json(pretty: false, **options)
458
+ result = generate(**options)
459
+ if pretty
460
+ JSON.pretty_generate(result)
461
+ else
462
+ JSON.generate(result)
463
+ end
464
+ end
465
+
466
+
467
+ # Generate records as a lazy stream (Enumerator)
468
+ #
469
+ # Returns an Enumerator that generates records on-demand. This is
470
+ # memory-efficient for large batches as records are not all held
471
+ # in memory at once.
472
+ #
473
+ # @param count [Integer] total number of records to generate
474
+ # @param options [Hash] generation options (seed, mode, etc.)
475
+ # @return [Enumerator] lazy enumerator of records
476
+ #
477
+ # @example Stream processing
478
+ # schema.generate_stream(count: 10000).each do |record|
479
+ # process(record) # Records generated one at a time
480
+ # end
481
+ #
482
+ # @example Convert to NDJSON
483
+ # schema.generate_stream(count: 1000).each do |record|
484
+ # puts record.to_json
485
+ # end
486
+ #
487
+
488
+ def generate_stream(count:, **options)
489
+ Generator::Streamer.new(self, **options).generate_stream(count: count)
490
+ end
491
+
492
+
493
+ # Create a new schema with an additional runtime behavior
494
+ #
495
+ # Returns a new Schema instance with the behavior added. The original
496
+ # schema is not modified.
497
+ #
498
+ # @param name [Symbol, String] behavior name (:latency, :failure, etc.)
499
+ # @param value [Object] behavior configuration value
500
+ # @return [Schema] new schema with the behavior added
501
+ #
502
+ # @example Add latency behavior
503
+ # slow_schema = schema.with_behavior(:latency, 500..1000)
504
+ # slow_schema.generate # Will have 500-1000ms delay
505
+ #
506
+ # @example Chain multiple behaviors
507
+ # test_schema = schema
508
+ # .with_behavior(:latency, 100)
509
+ # .with_behavior(:failure, 10)
510
+ #
511
+
512
+ def with_behavior(name, value)
513
+ new_schema = dup
514
+ new_schema.instance_variable_set(
515
+ :@runtime_behaviors,
516
+ @runtime_behaviors + [{ name: name.to_sym, value: value }]
517
+ )
518
+ new_schema
519
+ end
520
+
521
+
522
+ # Get all behaviors (defined in DSL + added at runtime)
523
+ #
524
+ # @return [Array<Hash>] all behavior configurations
525
+ #
526
+ # @example
527
+ # schema.all_behaviors
528
+ # # => [{ name: :latency, value: 100 }, { name: :seed, value: 12345 }]
529
+ #
530
+
531
+ def all_behaviors
532
+ behaviors + @runtime_behaviors
533
+ end
534
+
535
+
536
+ # Check if schema has a specific behavior
537
+ #
538
+ # @param name [Symbol, String] behavior name to check
539
+ # @return [Boolean] true if the behavior is defined
540
+ #
541
+ # @example
542
+ # schema.has_behavior?(:latency) # => true
543
+ # schema.has_behavior?(:failure) # => false
544
+ #
545
+
546
+ def has_behavior?(name)
547
+ all_behaviors.any? { |b| b[:name] == name.to_sym }
548
+ end
549
+
550
+
551
+ # Get a specific behavior configuration
552
+ #
553
+ # @param name [Symbol, String] behavior name
554
+ # @return [Hash, nil] the behavior hash or nil if not found
555
+ #
556
+ # @example
557
+ # latency = schema.behavior(:latency)
558
+ # latency[:value] # => 100..500
559
+ #
560
+
561
+ def behavior(name)
562
+ all_behaviors.find { |b| b[:name] == name.to_sym }
563
+ end
564
+
565
+
566
+ # Get the seed from metadata if specified
567
+ #
568
+ # @return [Integer, nil] the seed value or nil
569
+ #
570
+ # @example
571
+ # # DSL: @seed 12345
572
+ # schema.seed # => 12345
573
+ #
574
+
575
+ def seed
576
+ metadata[:seed]
577
+ end
578
+
579
+
580
+ # String representation
581
+ #
582
+ # @return [String] summary of the schema
583
+ #
584
+
585
+ def to_s
586
+ "#<Schema #{name} fields=#{fields.length} behaviors=#{all_behaviors.length}>"
587
+ end
588
+
589
+ alias inspect to_s
590
+
591
+ private
592
+
593
+
594
+ # Apply schema-level behaviors to generated result
595
+ #
596
+ # @param result [Hash] the generated record
597
+ # @param rng [Generator::RNG] random number generator
598
+ # @return [Hash] the result after applying behaviors
599
+ #
600
+
601
+ def apply_behaviors(result, rng)
602
+ Behaviors::Applicator.new(self, rng).apply_schema_behaviors(result)
603
+ end
604
+ end
605
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ # Schema Inheritance Support
5
+ #
6
+ # Allows schemas to inherit fields from parent schemas using the < operator.
7
+ #
8
+ # @example DSL syntax
9
+ # BaseUser:
10
+ # id: uuid
11
+ # created_at: timestamp
12
+ #
13
+ # AdminUser < BaseUser:
14
+ # role: const("admin")
15
+ # permissions: array(text, 1..5)
16
+ #
17
+ module SchemaInheritance
18
+ # Resolve inheritance for a schema node
19
+ #
20
+ # @param schema_node [AST::SchemaNode] the schema node to process
21
+ # @param registry [Registry] the registry containing parent schemas
22
+ # @return [AST::SchemaNode] schema node with inherited fields
23
+ #
24
+ def self.resolve(schema_node, registry)
25
+ return schema_node unless schema_node.parent_name
26
+
27
+ parent_schema = registry.schema(schema_node.parent_name)
28
+
29
+ # Merge parent fields with child fields (child fields override parent)
30
+ parent_field_names = parent_schema.fields.map(&:name)
31
+ child_field_names = schema_node.fields.map { |f| f.respond_to?(:name) ? f.name : f[:name] }
32
+
33
+ # Get parent fields that aren't overridden
34
+ inherited_fields = parent_schema.fields.reject do |f|
35
+ child_field_names.include?(f.name)
36
+ end
37
+
38
+ # Create new schema node with merged fields
39
+ # Parent fields come first, then child fields
40
+ merged_fields = []
41
+
42
+ # Add inherited fields
43
+ inherited_fields.each do |field|
44
+ merged_fields << convert_field_to_node(field)
45
+ end
46
+
47
+ # Add child's own fields
48
+ schema_node.fields.each do |field|
49
+ merged_fields << field
50
+ end
51
+
52
+ # Create new schema node with merged fields
53
+ Parser::AST::SchemaNode.new(
54
+ name: schema_node.name,
55
+ fields: merged_fields,
56
+ behaviors: schema_node.behaviors,
57
+ parent_name: schema_node.parent_name,
58
+ line: schema_node.line
59
+ )
60
+ end
61
+
62
+ # Convert a Field object to a field node (for inheritance)
63
+ #
64
+ # @param field [Field] the field to convert
65
+ # @return [Parser::AST::FieldNode] the field node
66
+ #
67
+ def self.convert_field_to_node(field)
68
+ Parser::AST::FieldNode.new(
69
+ name: field.name,
70
+ type_node: Parser::AST::TypeNode.new(
71
+ name: field.type_name,
72
+ arguments: field.type_args,
73
+ nullable: field.nullable?,
74
+ line: 0
75
+ ),
76
+ optional: field.optional?,
77
+ condition: nil,
78
+ behaviors: [],
79
+ line: 0
80
+ )
81
+ end
82
+
83
+ # Parse inheritance syntax from schema name
84
+ #
85
+ # @param name [String] the schema name potentially with inheritance
86
+ # @return [Array<String, String>] [schema_name, parent_name or nil]
87
+ #
88
+ # @example
89
+ # parse_inheritance("AdminUser < BaseUser")
90
+ # # => ["AdminUser", "BaseUser"]
91
+ #
92
+ # parse_inheritance("User")
93
+ # # => ["User", nil]
94
+ #
95
+ def self.parse_inheritance(name)
96
+ if name.include?("<")
97
+ parts = name.split("<").map(&:strip)
98
+ [parts[0], parts[1]]
99
+ else
100
+ [name, nil]
101
+ end
102
+ end
103
+ end
104
+ end