rhales 0.3.0 → 0.5.3

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.github/renovate.json5 +52 -0
  3. data/.github/workflows/ci.yml +123 -0
  4. data/.github/workflows/claude-code-review.yml +69 -0
  5. data/.github/workflows/claude.yml +49 -0
  6. data/.github/workflows/code-smells.yml +146 -0
  7. data/.github/workflows/ruby-lint.yml +78 -0
  8. data/.github/workflows/yardoc.yml +126 -0
  9. data/.gitignore +55 -0
  10. data/.pr_agent.toml +63 -0
  11. data/.pre-commit-config.yaml +89 -0
  12. data/.prettierignore +8 -0
  13. data/.prettierrc +38 -0
  14. data/.reek.yml +98 -0
  15. data/.rubocop.yml +428 -0
  16. data/.serena/.gitignore +3 -0
  17. data/.yardopts +56 -0
  18. data/CHANGELOG.md +44 -0
  19. data/CLAUDE.md +1 -2
  20. data/Gemfile +29 -0
  21. data/Gemfile.lock +189 -0
  22. data/README.md +706 -589
  23. data/Rakefile +46 -0
  24. data/debug_context.rb +25 -0
  25. data/demo/rhales-roda-demo/.gitignore +7 -0
  26. data/demo/rhales-roda-demo/Gemfile +32 -0
  27. data/demo/rhales-roda-demo/Gemfile.lock +151 -0
  28. data/demo/rhales-roda-demo/MAIL.md +405 -0
  29. data/demo/rhales-roda-demo/README.md +376 -0
  30. data/demo/rhales-roda-demo/RODA-TEMPLATE-ENGINES.md +192 -0
  31. data/demo/rhales-roda-demo/Rakefile +49 -0
  32. data/demo/rhales-roda-demo/app.rb +325 -0
  33. data/demo/rhales-roda-demo/bin/rackup +26 -0
  34. data/demo/rhales-roda-demo/config.ru +13 -0
  35. data/demo/rhales-roda-demo/db/migrate/001_create_rodauth_tables.rb +266 -0
  36. data/demo/rhales-roda-demo/db/migrate/002_create_rodauth_password_tables.rb +79 -0
  37. data/demo/rhales-roda-demo/db/migrate/003_add_admin_account.rb +68 -0
  38. data/demo/rhales-roda-demo/templates/change_login.rue +31 -0
  39. data/demo/rhales-roda-demo/templates/change_password.rue +36 -0
  40. data/demo/rhales-roda-demo/templates/close_account.rue +31 -0
  41. data/demo/rhales-roda-demo/templates/create_account.rue +40 -0
  42. data/demo/rhales-roda-demo/templates/dashboard.rue +150 -0
  43. data/demo/rhales-roda-demo/templates/home.rue +78 -0
  44. data/demo/rhales-roda-demo/templates/layouts/main.rue +168 -0
  45. data/demo/rhales-roda-demo/templates/login.rue +65 -0
  46. data/demo/rhales-roda-demo/templates/logout.rue +25 -0
  47. data/demo/rhales-roda-demo/templates/reset_password.rue +26 -0
  48. data/demo/rhales-roda-demo/templates/verify_account.rue +27 -0
  49. data/demo/rhales-roda-demo/test_full_output.rb +27 -0
  50. data/demo/rhales-roda-demo/test_simple.rb +24 -0
  51. data/docs/.gitignore +9 -0
  52. data/docs/architecture/data-flow.md +499 -0
  53. data/examples/dashboard-with-charts.rue +271 -0
  54. data/examples/form-with-validation.rue +180 -0
  55. data/examples/simple-page.rue +61 -0
  56. data/examples/vue.rue +136 -0
  57. data/generate-json-schemas.ts +158 -0
  58. data/json_schemer_migration_summary.md +172 -0
  59. data/lib/rhales/adapters/base_auth.rb +2 -0
  60. data/lib/rhales/adapters/base_request.rb +2 -0
  61. data/lib/rhales/adapters/base_session.rb +2 -0
  62. data/lib/rhales/adapters.rb +7 -0
  63. data/lib/rhales/configuration.rb +161 -1
  64. data/lib/rhales/core/context.rb +354 -0
  65. data/lib/rhales/{rue_document.rb → core/rue_document.rb} +59 -43
  66. data/lib/rhales/{template_engine.rb → core/template_engine.rb} +80 -33
  67. data/lib/rhales/core/view.rb +529 -0
  68. data/lib/rhales/{view_composition.rb → core/view_composition.rb} +81 -9
  69. data/lib/rhales/core.rb +9 -0
  70. data/lib/rhales/errors/hydration_collision_error.rb +2 -0
  71. data/lib/rhales/errors.rb +2 -0
  72. data/lib/rhales/hydration/earliest_injection_detector.rb +153 -0
  73. data/lib/rhales/hydration/hydration_data_aggregator.rb +487 -0
  74. data/lib/rhales/hydration/hydration_endpoint.rb +215 -0
  75. data/lib/rhales/hydration/hydration_injector.rb +175 -0
  76. data/lib/rhales/{hydration_registry.rb → hydration/hydration_registry.rb} +2 -0
  77. data/lib/rhales/hydration/hydrator.rb +102 -0
  78. data/lib/rhales/hydration/link_based_injection_detector.rb +195 -0
  79. data/lib/rhales/hydration/mount_point_detector.rb +109 -0
  80. data/lib/rhales/hydration/safe_injection_validator.rb +103 -0
  81. data/lib/rhales/hydration.rb +13 -0
  82. data/lib/rhales/{refinements → integrations/refinements}/require_refinements.rb +7 -13
  83. data/lib/rhales/{tilt.rb → integrations/tilt.rb} +26 -18
  84. data/lib/rhales/integrations.rb +6 -0
  85. data/lib/rhales/middleware/json_responder.rb +191 -0
  86. data/lib/rhales/middleware/schema_validator.rb +300 -0
  87. data/lib/rhales/middleware.rb +6 -0
  88. data/lib/rhales/parsers/handlebars_parser.rb +2 -0
  89. data/lib/rhales/parsers/rue_format_parser.rb +55 -36
  90. data/lib/rhales/parsers.rb +9 -0
  91. data/lib/rhales/{csp.rb → security/csp.rb} +27 -3
  92. data/lib/rhales/utils/json_serializer.rb +114 -0
  93. data/lib/rhales/utils/logging_helpers.rb +75 -0
  94. data/lib/rhales/utils/schema_extractor.rb +132 -0
  95. data/lib/rhales/utils/schema_generator.rb +194 -0
  96. data/lib/rhales/utils.rb +40 -0
  97. data/lib/rhales/version.rb +5 -1
  98. data/lib/rhales.rb +47 -19
  99. data/lib/tasks/rhales_schema.rake +197 -0
  100. data/package.json +10 -0
  101. data/pnpm-lock.yaml +345 -0
  102. data/pnpm-workspace.yaml +2 -0
  103. data/proofs/error_handling.rb +79 -0
  104. data/proofs/expanded_object_inheritance.rb +82 -0
  105. data/proofs/partial_context_scoping_fix.rb +168 -0
  106. data/proofs/ui_context_partial_inheritance.rb +236 -0
  107. data/rhales.gemspec +14 -6
  108. data/schema_vs_data_comparison.md +254 -0
  109. data/test_direct_access.rb +36 -0
  110. metadata +142 -18
  111. data/CLAUDE.locale.txt +0 -7
  112. data/lib/rhales/context.rb +0 -240
  113. data/lib/rhales/hydration_data_aggregator.rb +0 -220
  114. data/lib/rhales/hydrator.rb +0 -141
  115. data/lib/rhales/parsers/handlebars-grammar-review.txt +0 -39
  116. data/lib/rhales/view.rb +0 -412
@@ -0,0 +1,75 @@
1
+ # lib/rhales/utils/logging_helpers.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ module Rhales
6
+ module Utils
7
+ # Helper methods for consistent logging and timing instrumentation across Rhales components
8
+ module LoggingHelpers
9
+ include Rhales::Utils
10
+
11
+ # Log with timing for an operation
12
+ #
13
+ # @param logger [Logger] The logger instance to use
14
+ # @param level [Symbol] The log level (:debug, :info, :warn, :error)
15
+ # @param message [String] The log message
16
+ # @param metadata [Hash] Additional metadata to include in the log
17
+ # @yield The block to execute and time
18
+ # @return The result of the block
19
+ #
20
+ # Logs the operation with timing information in microseconds.
21
+ def log_timed_operation(logger, level, message, **metadata)
22
+ start_time = now_in_μs
23
+ result = yield
24
+ duration = now_in_μs - start_time
25
+
26
+ log_with_metadata(logger, level, message, metadata.merge(duration: duration))
27
+
28
+ result
29
+ rescue StandardError => ex
30
+ duration = now_in_μs - start_time
31
+ log_with_metadata(logger, :error, "#{message} failed",
32
+ metadata.merge(
33
+ duration: duration,
34
+ error: ex.message,
35
+ error_class: ex.class.name,
36
+ )
37
+ )
38
+ raise
39
+ end
40
+
41
+ # Log a message with structured metadata
42
+ def log_with_metadata(logger, level, message, metadata = {})
43
+ return logger.public_send(level, message) if metadata.empty?
44
+
45
+ metadata_str = metadata.map { |k, v| "#{k}=#{format_value(v)}" }.join(' ')
46
+ logger.public_send(level, "#{message}: #{metadata_str}")
47
+ end
48
+
49
+ private
50
+
51
+ # Format individual log values
52
+ def format_value(value)
53
+ case value
54
+ when String
55
+ value.include?(' ') ? "\"#{value}\"" : value
56
+ when Symbol, Numeric, true, false, nil
57
+ value.to_s
58
+ when Array
59
+ # For arrays longer than 5 items, show count + first/last items
60
+ if value.size > 5
61
+ first_three = value.first(3).join(', ')
62
+ last_two = value.last(2).join(', ')
63
+ "[#{value.size} items: #{first_three} ... #{last_two}]"
64
+ elsif value.empty?
65
+ '[]'
66
+ else
67
+ "[#{value.join(', ')}]"
68
+ end
69
+ else
70
+ value.to_s
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,132 @@
1
+ # lib/rhales/schema_extractor.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'pathname'
6
+ require_relative '../core/rue_document'
7
+
8
+ module Rhales
9
+ # Extracts schema definitions from .rue files
10
+ #
11
+ # This class scans template directories for .rue files containing <schema>
12
+ # sections and extracts the schema code along with metadata (attributes).
13
+ #
14
+ # Usage:
15
+ # extractor = SchemaExtractor.new('./templates')
16
+ # schemas = extractor.extract_all
17
+ # schemas.each do |schema_info|
18
+ # puts "#{schema_info[:template_name]}: #{schema_info[:lang]}"
19
+ # end
20
+ class SchemaExtractor
21
+ class ExtractionError < StandardError; end
22
+
23
+ attr_reader :templates_dir
24
+
25
+ def initialize(templates_dir)
26
+ @templates_dir = File.expand_path(templates_dir)
27
+ validate_directory!
28
+ end
29
+
30
+ # Extract all schemas from .rue files in the templates directory
31
+ #
32
+ # @return [Array<Hash>] Array of schema information hashes
33
+ # @example
34
+ # [
35
+ # {
36
+ # template_name: 'dashboard',
37
+ # template_path: '/path/to/dashboard.rue',
38
+ # schema_code: 'const schema = z.object({...});',
39
+ # lang: 'ts-zod',
40
+ # version: '2',
41
+ # envelope: 'SuccessEnvelope',
42
+ # window: 'appData',
43
+ # merge: 'deep',
44
+ # layout: 'layouts/main',
45
+ # extends: nil
46
+ # }
47
+ # ]
48
+ def extract_all
49
+ rue_files = find_rue_files
50
+ schemas = []
51
+
52
+ rue_files.each do |file_path|
53
+ begin
54
+ schema_info = extract_from_file(file_path)
55
+ schemas << schema_info if schema_info
56
+ rescue => e
57
+ warn "Warning: Failed to extract schema from #{file_path}: #{e.message}"
58
+ end
59
+ end
60
+
61
+ schemas
62
+ end
63
+
64
+ # Extract schema from a single .rue file
65
+ #
66
+ # @param file_path [String] Path to the .rue file
67
+ # @return [Hash, nil] Schema information hash or nil if no schema section
68
+ def extract_from_file(file_path)
69
+ doc = RueDocument.parse_file(file_path)
70
+
71
+ return nil unless doc.section?('schema')
72
+
73
+ template_name = derive_template_name(file_path)
74
+ schema_code = doc.section('schema')
75
+
76
+ {
77
+ template_name: template_name,
78
+ template_path: file_path,
79
+ schema_code: schema_code.strip,
80
+ lang: doc.schema_lang,
81
+ version: doc.schema_version,
82
+ envelope: doc.schema_envelope,
83
+ window: doc.schema_window,
84
+ merge: doc.schema_merge_strategy,
85
+ layout: doc.schema_layout,
86
+ extends: doc.schema_extends
87
+ }
88
+ end
89
+
90
+ # Find all .rue files in the templates directory (recursive)
91
+ #
92
+ # @return [Array<String>] Array of absolute file paths
93
+ def find_rue_files
94
+ pattern = File.join(@templates_dir, '**', '*.rue')
95
+ Dir.glob(pattern).sort
96
+ end
97
+
98
+ # Count how many .rue files have schema sections
99
+ #
100
+ # @return [Hash] Count information
101
+ def schema_stats
102
+ all_files = find_rue_files
103
+ schemas = extract_all
104
+
105
+ {
106
+ total_files: all_files.count,
107
+ files_with_schemas: schemas.count,
108
+ files_without_schemas: all_files.count - schemas.count,
109
+ schemas_by_lang: schemas.group_by { |s| s[:lang] }.transform_values(&:count)
110
+ }
111
+ end
112
+
113
+ private
114
+
115
+ def validate_directory!
116
+ unless File.directory?(@templates_dir)
117
+ raise ExtractionError, "Templates directory does not exist: #{@templates_dir}"
118
+ end
119
+ end
120
+
121
+ # Derive template name from file path
122
+ # Examples:
123
+ # /path/to/templates/dashboard.rue => 'dashboard'
124
+ # /path/to/templates/pages/user/profile.rue => 'pages/user/profile'
125
+ def derive_template_name(file_path)
126
+ templates_pathname = Pathname.new(@templates_dir)
127
+ file_pathname = Pathname.new(file_path)
128
+ relative_path = file_pathname.relative_path_from(templates_pathname)
129
+ relative_path.to_s.sub(/\.rue$/, '')
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,194 @@
1
+ # lib/rhales/schema_generator.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'open3'
6
+ require 'tempfile'
7
+ require 'fileutils'
8
+ require_relative 'schema_extractor'
9
+ require_relative 'json_serializer'
10
+
11
+ module Rhales
12
+ # Generates JSON Schemas from Zod schemas using TypeScript execution
13
+ #
14
+ # This class uses pnpm exec tsx to execute Zod schema code and convert it
15
+ # to JSON Schema format. The generated schemas are saved to disk for use
16
+ # by the validation middleware.
17
+ #
18
+ # Usage:
19
+ # generator = SchemaGenerator.new(
20
+ # templates_dir: './templates',
21
+ # output_dir: './public/schemas'
22
+ # )
23
+ # results = generator.generate_all
24
+ class SchemaGenerator
25
+ class GenerationError < StandardError; end
26
+
27
+ attr_reader :templates_dir, :output_dir
28
+
29
+ # @param templates_dir [String] Directory containing .rue files
30
+ # @param output_dir [String] Directory to save generated JSON schemas
31
+ # Defaults to './public/schemas' (implementing project's public directory)
32
+ def initialize(templates_dir:, output_dir: nil)
33
+ @templates_dir = File.expand_path(templates_dir)
34
+
35
+ # Smart default: place schemas in public/schemas relative to current working directory
36
+ # This ensures schemas are generated in the implementing project, not the gem directory
37
+ @output_dir = if output_dir
38
+ File.expand_path(output_dir)
39
+ else
40
+ # Default to public/schemas in current working directory
41
+ File.expand_path('./public/schemas')
42
+ end
43
+
44
+ validate_setup!
45
+ ensure_output_directory!
46
+ end
47
+
48
+ # Generate JSON Schemas for all templates with <schema> sections
49
+ #
50
+ # @return [Hash] Generation results with stats
51
+ def generate_all
52
+ extractor = SchemaExtractor.new(@templates_dir)
53
+ schemas = extractor.extract_all
54
+
55
+ if schemas.empty?
56
+ return {
57
+ success: true,
58
+ generated: 0,
59
+ failed: 0,
60
+ message: 'No schemas found in templates'
61
+ }
62
+ end
63
+
64
+ results = {
65
+ success: true,
66
+ generated: 0,
67
+ failed: 0,
68
+ errors: []
69
+ }
70
+
71
+ schemas.each do |schema_info|
72
+ begin
73
+ generate_schema(schema_info)
74
+ results[:generated] += 1
75
+ puts "✓ Generated schema for: #{schema_info[:template_name]}"
76
+ rescue => e
77
+ results[:failed] += 1
78
+ results[:success] = false
79
+ error_msg = "Failed to generate schema for #{schema_info[:template_name]}: #{e.message}"
80
+ results[:errors] << error_msg
81
+ warn error_msg
82
+ end
83
+ end
84
+
85
+ results
86
+ end
87
+
88
+ # Generate JSON Schema for a single template
89
+ #
90
+ # @param schema_info [Hash] Schema information from SchemaExtractor
91
+ # @return [Hash] Generated JSON Schema
92
+ def generate_schema(schema_info)
93
+ # Create temp file in project directory so Node.js can resolve modules
94
+ temp_dir = File.join(Dir.pwd, 'tmp')
95
+ FileUtils.mkdir_p(temp_dir) unless Dir.exist?(temp_dir)
96
+
97
+ temp_file = Tempfile.new(['schema', '.mts'], temp_dir)
98
+
99
+ begin
100
+ # Write TypeScript script
101
+ temp_file.write(build_typescript_script(schema_info))
102
+ temp_file.close
103
+
104
+ # Execute with tsx via pnpm
105
+ stdout, stderr, status = Open3.capture3('pnpm', 'exec', 'tsx', temp_file.path)
106
+
107
+ unless status.success?
108
+ raise GenerationError, "TypeScript execution failed: #{stderr}"
109
+ end
110
+
111
+ # Parse JSON Schema from stdout
112
+ json_schema = JSONSerializer.parse(stdout)
113
+
114
+ # Save to disk
115
+ save_schema(schema_info[:template_name], json_schema)
116
+
117
+ json_schema
118
+ ensure
119
+ temp_file.unlink if temp_file
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def build_typescript_script(schema_info)
126
+ # Escape single quotes in template name for TypeScript string
127
+ safe_name = schema_info[:template_name].gsub("'", "\\'")
128
+
129
+ <<~TYPESCRIPT
130
+ // Auto-generated schema generator for #{safe_name}
131
+ import { z } from 'zod/v4';
132
+
133
+ // Schema code from .rue template
134
+ #{schema_info[:schema_code].strip}
135
+
136
+ // Generate JSON Schema
137
+ try {
138
+ const jsonSchema = z.toJSONSchema(schema, {
139
+ target: 'draft-2020-12',
140
+ unrepresentable: 'any',
141
+ cycles: 'ref',
142
+ reused: 'inline',
143
+ });
144
+
145
+ // Add metadata
146
+ const schemaWithMeta = {
147
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
148
+ $id: `https://rhales.dev/schemas/#{safe_name}.json`,
149
+ title: '#{safe_name}',
150
+ description: 'Schema for #{safe_name} template',
151
+ ...jsonSchema,
152
+ };
153
+
154
+ // Output JSON to stdout
155
+ console.log(JSON.stringify(schemaWithMeta, null, 2));
156
+ } catch (error) {
157
+ console.error('Schema generation error:', error.message);
158
+ process.exit(1);
159
+ }
160
+ TYPESCRIPT
161
+ end
162
+
163
+ def save_schema(template_name, json_schema)
164
+ # Create subdirectories if template name contains paths
165
+ schema_file = File.join(@output_dir, "#{template_name}.json")
166
+ schema_dir = File.dirname(schema_file)
167
+ FileUtils.mkdir_p(schema_dir) unless File.directory?(schema_dir)
168
+
169
+ File.write(schema_file, JSONSerializer.dump(json_schema))
170
+ end
171
+
172
+ def validate_setup!
173
+ unless File.directory?(@templates_dir)
174
+ raise GenerationError, "Templates directory does not exist: #{@templates_dir}"
175
+ end
176
+
177
+ # Check pnpm is available
178
+ stdout, stderr, status = Open3.capture3('pnpm', '--version')
179
+ unless status.success?
180
+ raise GenerationError, "pnpm not found. Install pnpm to generate schemas: npm install -g pnpm"
181
+ end
182
+
183
+ # Check tsx is available (will be installed by pnpm if needed)
184
+ stdout, stderr, status = Open3.capture3('pnpm', 'exec', 'tsx', '--version')
185
+ unless status.success?
186
+ raise GenerationError, "tsx not found. Run: pnpm install tsx --save-dev"
187
+ end
188
+ end
189
+
190
+ def ensure_output_directory!
191
+ FileUtils.mkdir_p(@output_dir) unless File.directory?(@output_dir)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,40 @@
1
+ # lib/rhales/utils.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'pathname'
6
+
7
+ module Rhales
8
+ module Utils
9
+ # Utility modules and classes
10
+
11
+ # @return [Time] Current time in UTC
12
+ def now
13
+ Time.now.utc
14
+ end
15
+
16
+ # Returns the current time in microseconds.
17
+ # This is used to measure the duration of Database commands.
18
+ #
19
+ # Alias: now_in_microseconds
20
+ #
21
+ # @return [Integer] The current time in microseconds.
22
+ def now_in_μs
23
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
24
+ end
25
+ alias now_in_microseconds now_in_μs
26
+
27
+ # @param filepath [String, nil] The file path to prettify
28
+ # @return [String, nil] The expanded absolute path, or nil if input is
29
+ def pretty_path(filepath)
30
+ return nil if filepath.nil?
31
+
32
+ Pathname.new(filepath).expand_path.to_s
33
+ end
34
+ end
35
+ end
36
+
37
+ require_relative 'utils/json_serializer'
38
+ require_relative 'utils/schema_generator'
39
+ require_relative 'utils/schema_extractor'
40
+ require_relative 'utils/logging_helpers'
@@ -1,6 +1,10 @@
1
1
  # lib/rhales/version.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Rhales
4
6
  # Version information for the RSFC gem
5
- VERSION = '0.3.0'
7
+ unless defined?(Rhales::VERSION)
8
+ VERSION = '0.5.3'
9
+ end
6
10
  end
data/lib/rhales.rb CHANGED
@@ -1,20 +1,25 @@
1
1
  # lib/rhales.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'logger'
2
6
 
7
+ # Core framework files
3
8
  require_relative 'rhales/version'
4
- require_relative 'rhales/errors'
5
9
  require_relative 'rhales/configuration'
6
- require_relative 'rhales/adapters/base_auth'
7
- require_relative 'rhales/adapters/base_session'
8
- require_relative 'rhales/context'
9
- require_relative 'rhales/rue_document'
10
- require_relative 'rhales/parsers/handlebars_parser'
11
- require_relative 'rhales/parsers/rue_format_parser'
12
- require_relative 'rhales/template_engine'
13
- require_relative 'rhales/hydrator'
14
- require_relative 'rhales/view_composition'
15
- require_relative 'rhales/hydration_data_aggregator'
16
- require_relative 'rhales/refinements/require_refinements'
17
- require_relative 'rhales/view'
10
+ require_relative 'rhales/errors'
11
+
12
+ # Load components in dependency order
13
+ require_relative 'rhales/adapters'
14
+ require_relative 'rhales/parsers'
15
+ require_relative 'rhales/utils'
16
+
17
+ # Security (depends on utils)
18
+ require_relative 'rhales/security/csp'
19
+ require_relative 'rhales/core'
20
+ require_relative 'rhales/hydration'
21
+ require_relative 'rhales/integrations'
22
+ require_relative 'rhales/middleware'
18
23
 
19
24
  # Ruby Single File Components (RSFC)
20
25
  #
@@ -28,19 +33,42 @@ require_relative 'rhales/view'
28
33
  # - Pluggable authentication and session adapters
29
34
  # - Security-first design with XSS protection and CSP support
30
35
  #
36
+ # Modular Loading:
37
+ # require 'rhales' # Loads everything (default)
38
+ # require 'rhales/core' # Core engine only
39
+ # require 'rhales/hydration' # Hydration system only
40
+ # require 'rhales/parsers' # Template parsers only
41
+ # require 'rhales/utils' # Utilities only
42
+ # require 'rhales/all' # Explicit full load
43
+ #
31
44
  # Usage:
32
45
  # Rhales.configure do |config|
33
- # config.default_localhas_role?e = 'en'
46
+ # config.default_locale = 'en'
34
47
  # config.template_paths = ['app/templates']
35
48
  # config.features = { dark_mode: true }
49
+ #
50
+ # # Hydration configuration
51
+ # config.hydration.injection_strategy = :early # :early or :late (default)
52
+ # config.hydration.mount_point_selectors = ['#app', '#root', '[data-mount]']
53
+ # config.hydration.fallback_to_late = true
36
54
  # end
37
55
  #
38
- # view = Rhales::View.new(request, session, user)
56
+ # view = Rhales::View.new(request)
39
57
  # html = view.render('my_component')
40
58
  module Rhales
59
+ extend Utils
60
+
61
+ class << self
62
+ attr_writer :logger
63
+
64
+ def logger
65
+ @logger ||= Logger.new($stdout)
66
+ end
67
+ end
68
+
41
69
  # Convenience method to create a view with props
42
- def self.render(template_name, request: nil, session: nil, user: nil, locale: nil, **props)
43
- view = View.new(request, session, user, locale, props: props)
70
+ def self.render(template_name, request: nil, locale: nil, **props)
71
+ view = View.new(request, locale, props: props)
44
72
  view.render(template_name)
45
73
  end
46
74
 
@@ -51,7 +79,7 @@ module Rhales
51
79
  end
52
80
 
53
81
  # Create context with props (for advanced usage)
54
- def self.create_context(request: nil, session: nil, user: nil, locale: nil, **props)
55
- Context.for_view(request, session, user, locale, **props)
82
+ def self.create_context(request: nil, locale: nil, **props)
83
+ Context.for_view(request, locale, **props)
56
84
  end
57
85
  end