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.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- 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
|