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.
- checksums.yaml +4 -4
- data/.github/renovate.json5 +52 -0
- data/.github/workflows/ci.yml +123 -0
- data/.github/workflows/claude-code-review.yml +69 -0
- data/.github/workflows/claude.yml +49 -0
- data/.github/workflows/code-smells.yml +146 -0
- data/.github/workflows/ruby-lint.yml +78 -0
- data/.github/workflows/yardoc.yml +126 -0
- data/.gitignore +55 -0
- data/.pr_agent.toml +63 -0
- data/.pre-commit-config.yaml +89 -0
- data/.prettierignore +8 -0
- data/.prettierrc +38 -0
- data/.reek.yml +98 -0
- data/.rubocop.yml +428 -0
- data/.serena/.gitignore +3 -0
- data/.yardopts +56 -0
- data/CHANGELOG.md +44 -0
- data/CLAUDE.md +1 -2
- data/Gemfile +29 -0
- data/Gemfile.lock +189 -0
- data/README.md +706 -589
- data/Rakefile +46 -0
- data/debug_context.rb +25 -0
- data/demo/rhales-roda-demo/.gitignore +7 -0
- data/demo/rhales-roda-demo/Gemfile +32 -0
- data/demo/rhales-roda-demo/Gemfile.lock +151 -0
- data/demo/rhales-roda-demo/MAIL.md +405 -0
- data/demo/rhales-roda-demo/README.md +376 -0
- data/demo/rhales-roda-demo/RODA-TEMPLATE-ENGINES.md +192 -0
- data/demo/rhales-roda-demo/Rakefile +49 -0
- data/demo/rhales-roda-demo/app.rb +325 -0
- data/demo/rhales-roda-demo/bin/rackup +26 -0
- data/demo/rhales-roda-demo/config.ru +13 -0
- data/demo/rhales-roda-demo/db/migrate/001_create_rodauth_tables.rb +266 -0
- data/demo/rhales-roda-demo/db/migrate/002_create_rodauth_password_tables.rb +79 -0
- data/demo/rhales-roda-demo/db/migrate/003_add_admin_account.rb +68 -0
- data/demo/rhales-roda-demo/templates/change_login.rue +31 -0
- data/demo/rhales-roda-demo/templates/change_password.rue +36 -0
- data/demo/rhales-roda-demo/templates/close_account.rue +31 -0
- data/demo/rhales-roda-demo/templates/create_account.rue +40 -0
- data/demo/rhales-roda-demo/templates/dashboard.rue +150 -0
- data/demo/rhales-roda-demo/templates/home.rue +78 -0
- data/demo/rhales-roda-demo/templates/layouts/main.rue +168 -0
- data/demo/rhales-roda-demo/templates/login.rue +65 -0
- data/demo/rhales-roda-demo/templates/logout.rue +25 -0
- data/demo/rhales-roda-demo/templates/reset_password.rue +26 -0
- data/demo/rhales-roda-demo/templates/verify_account.rue +27 -0
- data/demo/rhales-roda-demo/test_full_output.rb +27 -0
- data/demo/rhales-roda-demo/test_simple.rb +24 -0
- data/docs/.gitignore +9 -0
- data/docs/architecture/data-flow.md +499 -0
- data/examples/dashboard-with-charts.rue +271 -0
- data/examples/form-with-validation.rue +180 -0
- data/examples/simple-page.rue +61 -0
- data/examples/vue.rue +136 -0
- data/generate-json-schemas.ts +158 -0
- data/json_schemer_migration_summary.md +172 -0
- data/lib/rhales/adapters/base_auth.rb +2 -0
- data/lib/rhales/adapters/base_request.rb +2 -0
- data/lib/rhales/adapters/base_session.rb +2 -0
- data/lib/rhales/adapters.rb +7 -0
- data/lib/rhales/configuration.rb +161 -1
- data/lib/rhales/core/context.rb +354 -0
- data/lib/rhales/{rue_document.rb → core/rue_document.rb} +59 -43
- data/lib/rhales/{template_engine.rb → core/template_engine.rb} +80 -33
- data/lib/rhales/core/view.rb +529 -0
- data/lib/rhales/{view_composition.rb → core/view_composition.rb} +81 -9
- data/lib/rhales/core.rb +9 -0
- data/lib/rhales/errors/hydration_collision_error.rb +2 -0
- data/lib/rhales/errors.rb +2 -0
- data/lib/rhales/hydration/earliest_injection_detector.rb +153 -0
- data/lib/rhales/hydration/hydration_data_aggregator.rb +487 -0
- data/lib/rhales/hydration/hydration_endpoint.rb +215 -0
- data/lib/rhales/hydration/hydration_injector.rb +175 -0
- data/lib/rhales/{hydration_registry.rb → hydration/hydration_registry.rb} +2 -0
- data/lib/rhales/hydration/hydrator.rb +102 -0
- data/lib/rhales/hydration/link_based_injection_detector.rb +195 -0
- data/lib/rhales/hydration/mount_point_detector.rb +109 -0
- data/lib/rhales/hydration/safe_injection_validator.rb +103 -0
- data/lib/rhales/hydration.rb +13 -0
- data/lib/rhales/{refinements → integrations/refinements}/require_refinements.rb +7 -13
- data/lib/rhales/{tilt.rb → integrations/tilt.rb} +26 -18
- data/lib/rhales/integrations.rb +6 -0
- data/lib/rhales/middleware/json_responder.rb +191 -0
- data/lib/rhales/middleware/schema_validator.rb +300 -0
- data/lib/rhales/middleware.rb +6 -0
- data/lib/rhales/parsers/handlebars_parser.rb +2 -0
- data/lib/rhales/parsers/rue_format_parser.rb +55 -36
- data/lib/rhales/parsers.rb +9 -0
- data/lib/rhales/{csp.rb → security/csp.rb} +27 -3
- data/lib/rhales/utils/json_serializer.rb +114 -0
- data/lib/rhales/utils/logging_helpers.rb +75 -0
- data/lib/rhales/utils/schema_extractor.rb +132 -0
- data/lib/rhales/utils/schema_generator.rb +194 -0
- data/lib/rhales/utils.rb +40 -0
- data/lib/rhales/version.rb +5 -1
- data/lib/rhales.rb +47 -19
- data/lib/tasks/rhales_schema.rake +197 -0
- data/package.json +10 -0
- data/pnpm-lock.yaml +345 -0
- data/pnpm-workspace.yaml +2 -0
- data/proofs/error_handling.rb +79 -0
- data/proofs/expanded_object_inheritance.rb +82 -0
- data/proofs/partial_context_scoping_fix.rb +168 -0
- data/proofs/ui_context_partial_inheritance.rb +236 -0
- data/rhales.gemspec +14 -6
- data/schema_vs_data_comparison.md +254 -0
- data/test_direct_access.rb +36 -0
- metadata +142 -18
- data/CLAUDE.locale.txt +0 -7
- data/lib/rhales/context.rb +0 -240
- data/lib/rhales/hydration_data_aggregator.rb +0 -220
- data/lib/rhales/hydrator.rb +0 -141
- data/lib/rhales/parsers/handlebars-grammar-review.txt +0 -39
- data/lib/rhales/view.rb +0 -412
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env -S pnpm exec tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate JSON Schemas and TypeScript types from Zod configuration schemas
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pnpm exec tsx scripts/generate-json-schemas.ts # Generate main schema + types
|
|
8
|
+
* pnpm exec tsx scripts/generate-json-schemas.ts --openapi # Include OpenAPI schemas
|
|
9
|
+
* pnpm exec tsx scripts/generate-json-schemas.ts --watch # Watch mode (future)
|
|
10
|
+
*
|
|
11
|
+
* NOTE: The `-S` flag in the hasbang allows `env` to handle multiple arguments.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { z } from 'zod/v4';
|
|
17
|
+
import { configSchema as mutableConfigSchema } from '../schemas/config/mutable';
|
|
18
|
+
import { configSchema as staticConfigSchema } from '../schemas/config/static';
|
|
19
|
+
import { configSchema as runtimeConfigSchema } from '../schemas/config/runtime';
|
|
20
|
+
|
|
21
|
+
const ONETIME_HOME = process.env.ONETIME_HOME || process.cwd();
|
|
22
|
+
|
|
23
|
+
const OUTPUT_MAIN_SCHEMA = join(ONETIME_HOME, 'public/web/dist/schemas/runtime.schema.json');
|
|
24
|
+
const OUTPUT_SCHEMAS_DIR = join(ONETIME_HOME, 'etc/schemas');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate individual JSON schema files
|
|
28
|
+
*/
|
|
29
|
+
function generateIndividualSchemas(): void {
|
|
30
|
+
mkdirSync(OUTPUT_SCHEMAS_DIR, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const schemas = [
|
|
33
|
+
{
|
|
34
|
+
schema: staticConfigSchema,
|
|
35
|
+
name: 'config.schema',
|
|
36
|
+
description: 'Static configuration settings loaded from config.yaml',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
schema: mutableConfigSchema,
|
|
40
|
+
name: 'mutable.schema',
|
|
41
|
+
description: 'Dynamic mutable config loaded from mutable.yaml',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
schema: runtimeConfigSchema,
|
|
45
|
+
name: 'runtime.schema',
|
|
46
|
+
description: 'Combined configuration schema for both static and mutable config',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
schemas.forEach(({ schema, name, description }) => {
|
|
51
|
+
const jsonSchema = z.toJSONSchema(schema, {
|
|
52
|
+
target: 'draft-2020-12',
|
|
53
|
+
unrepresentable: 'any',
|
|
54
|
+
cycles: 'ref',
|
|
55
|
+
reused: 'inline',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const schemaWithMeta = {
|
|
59
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
60
|
+
$id: `https://onetimesecret.com/schemas/${name}.json`,
|
|
61
|
+
title: name
|
|
62
|
+
.split('-')
|
|
63
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
64
|
+
.join(' '),
|
|
65
|
+
description,
|
|
66
|
+
...jsonSchema,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const outputPath = join(OUTPUT_SCHEMAS_DIR, `${name}.json`);
|
|
70
|
+
writeFileSync(outputPath, JSON.stringify(schemaWithMeta, null, 2) + '\n');
|
|
71
|
+
console.log(`Generated: ${outputPath}`);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate main configuration schema as YAML
|
|
77
|
+
*/
|
|
78
|
+
function generateMainSchema(): void {
|
|
79
|
+
const jsonSchema = z.toJSONSchema(staticConfigSchema, {
|
|
80
|
+
target: 'draft-2020-12',
|
|
81
|
+
unrepresentable: 'any',
|
|
82
|
+
cycles: 'ref',
|
|
83
|
+
reused: 'inline',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const schemaWithMeta = {
|
|
87
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
88
|
+
$id: 'https://onetimesecret.com/schemas/config.schema.json',
|
|
89
|
+
title: 'OneTimeSecret Configuration',
|
|
90
|
+
description: 'Configuration schema for OneTimeSecret application',
|
|
91
|
+
...jsonSchema,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
mkdirSync(join(OUTPUT_MAIN_SCHEMA, '..'), { recursive: true });
|
|
95
|
+
writeFileSync(OUTPUT_MAIN_SCHEMA, JSON.stringify(schemaWithMeta, null, 2) + '\n');
|
|
96
|
+
console.log(`Generated: ${OUTPUT_MAIN_SCHEMA}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate OpenAPI schemas (optional)
|
|
101
|
+
*/
|
|
102
|
+
function generateOpenAPISchemas(): void {
|
|
103
|
+
const schemas = [
|
|
104
|
+
{ schema: staticConfigSchema, name: 'static' },
|
|
105
|
+
{ schema: mutableConfigSchema, name: 'mutable' },
|
|
106
|
+
{ schema: runtimeConfigSchema, name: 'runtime' },
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
schemas.forEach(({ schema, name }) => {
|
|
110
|
+
const openAPISchema = z.toJSONSchema(schema, {
|
|
111
|
+
target: 'draft-7',
|
|
112
|
+
unrepresentable: 'any',
|
|
113
|
+
cycles: 'ref',
|
|
114
|
+
reused: 'ref',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const schemaWithMeta = {
|
|
118
|
+
title: name
|
|
119
|
+
.split('-')
|
|
120
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
121
|
+
.join(' '),
|
|
122
|
+
description: `OpenAPI schema for ${name}`,
|
|
123
|
+
...openAPISchema,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const outputPath = join(OUTPUT_SCHEMAS_DIR, `${name}.openapi.json`);
|
|
127
|
+
writeFileSync(outputPath, JSON.stringify(schemaWithMeta, null, 2) + '\n');
|
|
128
|
+
console.log(`Generated: ${outputPath}`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Main execution
|
|
134
|
+
*/
|
|
135
|
+
function main(): void {
|
|
136
|
+
const includeOpenAPI = process.argv.includes('--openapi');
|
|
137
|
+
const watchMode = process.argv.includes('--watch');
|
|
138
|
+
|
|
139
|
+
if (watchMode) {
|
|
140
|
+
console.log('Watch mode not yet implemented');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
generateMainSchema();
|
|
145
|
+
generateIndividualSchemas();
|
|
146
|
+
|
|
147
|
+
if (includeOpenAPI) {
|
|
148
|
+
generateOpenAPISchemas();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('Schema generation complete');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
155
|
+
main();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { generateMainSchema, generateIndividualSchemas, generateOpenAPISchemas };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# JSON Schema Migration Summary: json-schema → json_schemer
|
|
2
|
+
|
|
3
|
+
## Migration Complete ✓
|
|
4
|
+
|
|
5
|
+
Successfully migrated from `json-schema` gem to `json_schemer` for better JSON Schema Draft 2020-12 support.
|
|
6
|
+
|
|
7
|
+
## Changes Made
|
|
8
|
+
|
|
9
|
+
### 1. Dependency Update
|
|
10
|
+
- **File**: `rhales.gemspec`
|
|
11
|
+
- **Change**: Replaced `json-schema ~> 4.0` with `json_schemer ~> 2.3`
|
|
12
|
+
- **Reason**: Better Draft 2020-12 support, modern API, superior performance
|
|
13
|
+
|
|
14
|
+
### 2. Middleware Implementation
|
|
15
|
+
- **File**: `lib/rhales/middleware/schema_validator.rb`
|
|
16
|
+
- **Changes**:
|
|
17
|
+
- Replaced `require 'json-schema'` with `require 'json_schemer'`
|
|
18
|
+
- Updated `load_schema_cached` to create JSONSchemer validator objects
|
|
19
|
+
- Removed `$schema` and `$id` stripping logic (json_schemer handles this natively)
|
|
20
|
+
- Replaced `JSON::Validator.fully_validate` with `schema.validate(data).to_a`
|
|
21
|
+
- Added `format_errors` method for human-readable error messages
|
|
22
|
+
- Maintained backward-compatible error message format
|
|
23
|
+
|
|
24
|
+
### 3. Test Updates
|
|
25
|
+
- **Files**:
|
|
26
|
+
- `spec/rhales/middleware/schema_validator_spec.rb`
|
|
27
|
+
- `spec/rhales/integration/schema_validation_spec.rb`
|
|
28
|
+
- **Changes**:
|
|
29
|
+
- Added `require 'fileutils'` where needed
|
|
30
|
+
- Added `require 'rack'` for Rack::Request
|
|
31
|
+
- All existing tests pass without modification to test logic
|
|
32
|
+
|
|
33
|
+
### 4. Documentation
|
|
34
|
+
- **File**: `CHANGELOG.md`
|
|
35
|
+
- **Added**: Entry documenting the migration and performance improvements
|
|
36
|
+
|
|
37
|
+
## Key Benefits
|
|
38
|
+
|
|
39
|
+
### 1. JSON Schema Draft 2020-12 Support
|
|
40
|
+
- Full support for latest schema standard
|
|
41
|
+
- Native handling of `$schema` and `$id` fields
|
|
42
|
+
- Better specification compliance
|
|
43
|
+
|
|
44
|
+
### 2. Performance Improvement
|
|
45
|
+
- **Before** (json-schema): ~2ms average validation time
|
|
46
|
+
- **After** (json_schemer): ~0.047ms average validation time
|
|
47
|
+
- **Improvement**: ~42x faster (98% reduction)
|
|
48
|
+
|
|
49
|
+
### 3. Better Error Messages
|
|
50
|
+
json_schemer provides structured error objects with:
|
|
51
|
+
- `data_pointer`: JSON Pointer to the invalid data
|
|
52
|
+
- `schema_pointer`: JSON Pointer to the schema rule
|
|
53
|
+
- `type`: Type of validation error
|
|
54
|
+
- `error`: Human-readable message
|
|
55
|
+
- Full error context for debugging
|
|
56
|
+
|
|
57
|
+
### 4. Modern API
|
|
58
|
+
```ruby
|
|
59
|
+
# Old (json-schema)
|
|
60
|
+
errors = JSON::Validator.fully_validate(schema, data, version: :draft4)
|
|
61
|
+
|
|
62
|
+
# New (json_schemer)
|
|
63
|
+
validator = JSONSchemer.schema(schema)
|
|
64
|
+
errors = validator.validate(data).to_a
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Error Message Examples
|
|
68
|
+
|
|
69
|
+
### Type Mismatch
|
|
70
|
+
```
|
|
71
|
+
The property '/authenticated' of type string did not match the following type: boolean
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Missing Required Field
|
|
75
|
+
```
|
|
76
|
+
The property '/' is missing required field(s): id, email
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Enum Validation
|
|
80
|
+
```
|
|
81
|
+
The property '/role' must be one of: admin, user, guest
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Range Validation
|
|
85
|
+
```
|
|
86
|
+
The property '/age' must be >= 0
|
|
87
|
+
The property '/age' must be <= 150
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Test Results
|
|
91
|
+
|
|
92
|
+
### All Tests Pass
|
|
93
|
+
```
|
|
94
|
+
533 examples, 0 failures
|
|
95
|
+
Finished in 0.30839 seconds
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Schema Validation Tests
|
|
99
|
+
```
|
|
100
|
+
26 examples, 0 failures
|
|
101
|
+
- 20 middleware unit tests
|
|
102
|
+
- 6 integration tests
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Performance Test
|
|
106
|
+
```
|
|
107
|
+
Average validation time: 0.0473 ms
|
|
108
|
+
Validated 10,000 times in 0.4726 seconds
|
|
109
|
+
Performance target: < 5ms ✓
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Compatibility
|
|
113
|
+
|
|
114
|
+
### Schema Format
|
|
115
|
+
No changes needed to existing schemas:
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
119
|
+
"$id": "https://rhales.dev/schemas/example.json",
|
|
120
|
+
"type": "object",
|
|
121
|
+
"properties": { ... }
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Middleware Configuration
|
|
126
|
+
No changes needed to middleware setup:
|
|
127
|
+
```ruby
|
|
128
|
+
use Rhales::Middleware::SchemaValidator,
|
|
129
|
+
schemas_dir: './public/schemas',
|
|
130
|
+
fail_on_error: ENV['RACK_ENV'] == 'development'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Error Handling
|
|
134
|
+
Backward-compatible error message format maintained for existing error checks in tests and applications.
|
|
135
|
+
|
|
136
|
+
## Files Modified
|
|
137
|
+
|
|
138
|
+
1. `/Users/d/Projects/opensource/onetime/rhales/rhales.gemspec`
|
|
139
|
+
2. `/Users/d/Projects/opensource/onetime/rhales/lib/rhales/middleware/schema_validator.rb`
|
|
140
|
+
3. `/Users/d/Projects/opensource/onetime/rhales/spec/rhales/middleware/schema_validator_spec.rb`
|
|
141
|
+
4. `/Users/d/Projects/opensource/onetime/rhales/spec/rhales/integration/schema_validation_spec.rb`
|
|
142
|
+
5. `/Users/d/Projects/opensource/onetime/rhales/CHANGELOG.md`
|
|
143
|
+
|
|
144
|
+
## Acceptance Criteria - All Met ✓
|
|
145
|
+
|
|
146
|
+
- [x] `json_schemer` added to gemspec
|
|
147
|
+
- [x] `json-schema` removed from gemspec
|
|
148
|
+
- [x] Middleware updated to use json_schemer
|
|
149
|
+
- [x] Error formatting updated for better messages
|
|
150
|
+
- [x] All middleware tests passing (20/20)
|
|
151
|
+
- [x] All integration tests passing (6/6)
|
|
152
|
+
- [x] Demo schemas validate correctly
|
|
153
|
+
- [x] Full test suite passes (533/533 examples)
|
|
154
|
+
- [x] Performance maintained (< 0.05ms, well under 5ms target)
|
|
155
|
+
- [x] CHANGELOG updated
|
|
156
|
+
- [x] Backward-compatible error messages
|
|
157
|
+
|
|
158
|
+
## Next Steps
|
|
159
|
+
|
|
160
|
+
1. Deploy to staging environment
|
|
161
|
+
2. Monitor validation performance metrics
|
|
162
|
+
3. Consider leveraging json_schemer's additional features:
|
|
163
|
+
- Remote schema references ($ref to URLs)
|
|
164
|
+
- Custom format validators
|
|
165
|
+
- OpenAPI schema support
|
|
166
|
+
- Meta-schema validation
|
|
167
|
+
|
|
168
|
+
## References
|
|
169
|
+
|
|
170
|
+
- json_schemer: https://github.com/davishmcclurg/json_schemer
|
|
171
|
+
- JSON Schema Draft 2020-12: https://json-schema.org/draft/2020-12/schema
|
|
172
|
+
- Migration commit: feature/29-contract branch
|
data/lib/rhales/configuration.rb
CHANGED
|
@@ -1,6 +1,117 @@
|
|
|
1
1
|
# lib/rhales/configuration.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Rhales
|
|
6
|
+
# Hydration-specific configuration settings
|
|
7
|
+
#
|
|
8
|
+
# Controls how hydration scripts are injected into HTML templates.
|
|
9
|
+
# Supports multiple injection strategies for different performance characteristics:
|
|
10
|
+
#
|
|
11
|
+
# ## Traditional Strategies
|
|
12
|
+
# - `:late` (default) - injects before </body> tag (safest, backwards compatible)
|
|
13
|
+
# - `:early` - injects before detected mount points for improved performance
|
|
14
|
+
# - `:earliest` - injects in HTML head section for maximum performance
|
|
15
|
+
#
|
|
16
|
+
# ## Link-Based Strategies (API endpoints)
|
|
17
|
+
# - `:link` - basic link reference to API endpoint
|
|
18
|
+
# - `:prefetch` - browser prefetch for future page loads
|
|
19
|
+
# - `:preload` - high priority preload for current page
|
|
20
|
+
# - `:modulepreload` - ES module preloading
|
|
21
|
+
# - `:lazy` - intersection observer-based lazy loading
|
|
22
|
+
class HydrationConfiguration
|
|
23
|
+
VALID_STRATEGIES = [:late, :early, :earliest, :link, :prefetch, :preload, :modulepreload, :lazy].freeze
|
|
24
|
+
LINK_BASED_STRATEGIES = [:link, :prefetch, :preload, :modulepreload, :lazy].freeze
|
|
25
|
+
DEFAULT_API_CACHE_TTL = 300 # 5 minutes
|
|
26
|
+
|
|
27
|
+
# Injection strategy - one of VALID_STRATEGIES
|
|
28
|
+
attr_accessor :injection_strategy
|
|
29
|
+
|
|
30
|
+
# Array of CSS selectors to detect frontend mount points
|
|
31
|
+
attr_accessor :mount_point_selectors
|
|
32
|
+
|
|
33
|
+
# Whether to fallback to late injection when no mount points detected
|
|
34
|
+
attr_accessor :fallback_to_late
|
|
35
|
+
|
|
36
|
+
# Whether to fallback to late injection when early injection is unsafe
|
|
37
|
+
attr_accessor :fallback_when_unsafe
|
|
38
|
+
|
|
39
|
+
# Disable early injection for specific templates (array of template names)
|
|
40
|
+
attr_accessor :disable_early_for_templates
|
|
41
|
+
|
|
42
|
+
# API endpoint configuration for link-based strategies
|
|
43
|
+
attr_accessor :api_endpoint_enabled, :api_endpoint_path
|
|
44
|
+
|
|
45
|
+
# Link tag configuration
|
|
46
|
+
attr_accessor :link_crossorigin
|
|
47
|
+
|
|
48
|
+
# Module export configuration for :modulepreload strategy
|
|
49
|
+
attr_accessor :module_export_enabled
|
|
50
|
+
|
|
51
|
+
# Lazy loading configuration
|
|
52
|
+
attr_accessor :lazy_mount_selector
|
|
53
|
+
|
|
54
|
+
# Data attribute reflection system
|
|
55
|
+
attr_accessor :reflection_enabled
|
|
56
|
+
|
|
57
|
+
# Caching configuration for API endpoints
|
|
58
|
+
attr_accessor :api_cache_enabled, :api_cache_ttl
|
|
59
|
+
|
|
60
|
+
# CORS configuration for API endpoints
|
|
61
|
+
attr_accessor :cors_enabled, :cors_origin
|
|
62
|
+
|
|
63
|
+
def initialize
|
|
64
|
+
# Traditional strategy settings
|
|
65
|
+
@injection_strategy = :late
|
|
66
|
+
@mount_point_selectors = ['#app', '#root', '[data-rsfc-mount]', '[data-mount]']
|
|
67
|
+
@fallback_to_late = true
|
|
68
|
+
@fallback_when_unsafe = true
|
|
69
|
+
@disable_early_for_templates = []
|
|
70
|
+
|
|
71
|
+
# API endpoint settings
|
|
72
|
+
@api_endpoint_enabled = false
|
|
73
|
+
@api_endpoint_path = '/api/hydration'
|
|
74
|
+
|
|
75
|
+
# Link tag settings
|
|
76
|
+
@link_crossorigin = true
|
|
77
|
+
|
|
78
|
+
# Module export settings
|
|
79
|
+
@module_export_enabled = false
|
|
80
|
+
|
|
81
|
+
# Lazy loading settings
|
|
82
|
+
@lazy_mount_selector = '#app'
|
|
83
|
+
|
|
84
|
+
# Reflection system settings
|
|
85
|
+
@reflection_enabled = true
|
|
86
|
+
|
|
87
|
+
# Caching settings
|
|
88
|
+
@api_cache_enabled = false
|
|
89
|
+
@api_cache_ttl = DEFAULT_API_CACHE_TTL
|
|
90
|
+
|
|
91
|
+
# CORS settings
|
|
92
|
+
@cors_enabled = false
|
|
93
|
+
@cors_origin = '*'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Validate the injection strategy
|
|
97
|
+
def injection_strategy=(strategy)
|
|
98
|
+
unless VALID_STRATEGIES.include?(strategy)
|
|
99
|
+
raise ArgumentError, "Invalid injection strategy: #{strategy}. Valid options: #{VALID_STRATEGIES.join(', ')}"
|
|
100
|
+
end
|
|
101
|
+
@injection_strategy = strategy
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Check if current strategy is link-based
|
|
105
|
+
def link_based_strategy?
|
|
106
|
+
LINK_BASED_STRATEGIES.include?(@injection_strategy)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Check if API endpoints should be enabled
|
|
110
|
+
def api_endpoints_required?
|
|
111
|
+
link_based_strategy? || @api_endpoint_enabled
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
4
115
|
# Configuration management for Rhales library
|
|
5
116
|
#
|
|
6
117
|
# Provides a clean, testable alternative to global configuration access.
|
|
@@ -31,6 +142,21 @@ module Rhales
|
|
|
31
142
|
# Performance settings
|
|
32
143
|
attr_accessor :cache_parsed_templates, :cache_ttl
|
|
33
144
|
|
|
145
|
+
# Hydration settings
|
|
146
|
+
attr_accessor :hydration
|
|
147
|
+
|
|
148
|
+
# Schema validation settings
|
|
149
|
+
attr_accessor :enable_schema_validation, :fail_on_validation_error, :schemas_dir
|
|
150
|
+
|
|
151
|
+
# JSON response settings
|
|
152
|
+
attr_accessor :enable_json_responder, :json_responder_include_metadata
|
|
153
|
+
|
|
154
|
+
# Logging settings
|
|
155
|
+
attr_accessor :allowed_unescaped_variables
|
|
156
|
+
|
|
157
|
+
# Hydration mismatch reporting settings
|
|
158
|
+
attr_accessor :hydration_mismatch_format, :hydration_authority
|
|
159
|
+
|
|
34
160
|
def initialize
|
|
35
161
|
# Set sensible defaults
|
|
36
162
|
@default_locale = 'en'
|
|
@@ -47,6 +173,26 @@ module Rhales
|
|
|
47
173
|
@site_ssl_enabled = false
|
|
48
174
|
@cache_parsed_templates = true
|
|
49
175
|
@cache_ttl = 3600 # 1 hour
|
|
176
|
+
@hydration = HydrationConfiguration.new
|
|
177
|
+
|
|
178
|
+
# Schema validation defaults
|
|
179
|
+
@enable_schema_validation = true
|
|
180
|
+
@fail_on_validation_error = false # Set by environment in middleware
|
|
181
|
+
@schemas_dir = './public/schemas' # Default to implementing project's public directory
|
|
182
|
+
|
|
183
|
+
# JSON responder defaults
|
|
184
|
+
@enable_json_responder = true
|
|
185
|
+
@json_responder_include_metadata = false
|
|
186
|
+
|
|
187
|
+
# Logging defaults
|
|
188
|
+
@allowed_unescaped_variables = []
|
|
189
|
+
|
|
190
|
+
# Hydration mismatch reporting defaults
|
|
191
|
+
@hydration_mismatch_format = :compact # :compact, :multiline, :sidebyside, :json
|
|
192
|
+
@hydration_authority = :schema # :schema or :data
|
|
193
|
+
|
|
194
|
+
# Yield to block for configuration if provided
|
|
195
|
+
yield(self) if block_given?
|
|
50
196
|
end
|
|
51
197
|
|
|
52
198
|
# Build API base URL from site configuration
|
|
@@ -86,7 +232,7 @@ module Rhales
|
|
|
86
232
|
'worker-src' => ["'self'"],
|
|
87
233
|
'manifest-src' => ["'self'"],
|
|
88
234
|
'prefetch-src' => ["'self'"],
|
|
89
|
-
'upgrade-insecure-requests' => []
|
|
235
|
+
'upgrade-insecure-requests' => [],
|
|
90
236
|
}.freeze
|
|
91
237
|
end
|
|
92
238
|
|
|
@@ -140,6 +286,20 @@ module Rhales
|
|
|
140
286
|
yield(configuration) if block_given?
|
|
141
287
|
configuration.validate!
|
|
142
288
|
configuration.freeze!
|
|
289
|
+
|
|
290
|
+
# Debug log the configuration (guard against stale test mocks)
|
|
291
|
+
if Rhales.logger
|
|
292
|
+
require_relative 'utils/logging_helpers'
|
|
293
|
+
extend Rhales::Utils::LoggingHelpers
|
|
294
|
+
|
|
295
|
+
log_with_metadata(Rhales.logger, :debug, 'Rhales configured',
|
|
296
|
+
hydration_mismatch_format: configuration.hydration_mismatch_format,
|
|
297
|
+
hydration_authority: configuration.hydration_authority,
|
|
298
|
+
enable_schema_validation: configuration.enable_schema_validation,
|
|
299
|
+
fail_on_validation_error: configuration.fail_on_validation_error
|
|
300
|
+
)
|
|
301
|
+
end
|
|
302
|
+
|
|
143
303
|
configuration
|
|
144
304
|
end
|
|
145
305
|
|