roda-inertia-lite 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e8c72b8c3e0ceb53697e8dd7ddca0d00629c45f818e3ee9f5abd6db37ebc69be
4
+ data.tar.gz: 767440533bd5fe394270edbd47133eec4e0724c8ca94904914ec9619621eae28
5
+ SHA512:
6
+ metadata.gz: 8639f9d4288fa12f78e3d6824311ba60a12ceea90748064521e2e767a245801c0f63e3ee32c3c1924e1dd9c3de9f661c5f9033c276fcf9e90948ef6f3fb642ba
7
+ data.tar.gz: 8da2fac1587eb3e886a7aef4058be8716680313e26cf8ac4134f2a826782db5c8100060cc3a9b00d4c02d475a98c32b65604327b83c15b4470090ffed40f077f
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0]
6
+
7
+ - Fork of https://github.com/holamendi/inertia-roda
8
+ - Removed "render" and "h" plugins dependency
9
+ - Added the string layout optins
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pablo Orellana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # Inertia.js Roda (Lite)
2
+
3
+ A standalone [Roda](https://roda.jeremyevans.net/) adapter for seamlessly integrating [Inertia.js](https://inertiajs.com/) without views or other plugins dependency.
4
+
5
+ *forked from [https://github.com/holamendi/inertia-roda](https://github.com/holamendi/inertia-roda)*
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's `Gemfile`:
11
+
12
+ ```ruby
13
+ gem "roda-inertia-lite"
14
+ ```
15
+
16
+ Then run:
17
+
18
+ ```bash
19
+ bundle install
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### 1. Register the plugin
25
+
26
+ ```ruby
27
+ require "roda"
28
+
29
+ class App < Roda
30
+ plugin :inertia, version: -> { "v1" }
31
+ end
32
+ ```
33
+
34
+ The `version` option is **required**. It can be a string or a callable (e.g., a lambda reading from an environment variable):
35
+
36
+ ```ruby
37
+ # Static version
38
+ plugin :inertia, version: "abc123"
39
+
40
+ # Dynamic version (re-read on every request)
41
+ plugin :inertia, version: { ENV.fetch("ASSET_VERSION", "v1") }
42
+ ```
43
+
44
+ ### 2. Render Inertia responses
45
+
46
+ Use `inertia` in your routes to return page data as JSON (for Inertia requests) or as a full HTML document (for initial page loads):
47
+
48
+ ```ruby
49
+ class App < Roda
50
+ plugin :inertia, version: -> { "v1" }
51
+
52
+ route do |r|
53
+ r.get "users" do
54
+ inertia "Users/Index", props: { users: User.all }
55
+ end
56
+ end
57
+ end
58
+ ```
59
+
60
+ **For Inertia requests** (headers include `X-Inertia: true`):
61
+ - Sets response header `X-Inertia: true`
62
+ - Sets response header `Vary: X-Inertia`
63
+ - Returns a JSON page object:
64
+
65
+ ```json
66
+ {
67
+ "component": "Users/Index",
68
+ "props": { "users": [{ "id": 1, "name": "Alice" }] },
69
+ "url": "/users",
70
+ "version": "v1"
71
+ }
72
+ ```
73
+
74
+ **For non-Inertia requests** (initial page load):
75
+ - Returns a full HTML document with the JSON page data embedded in a `<div id="app" data-page='...'>` tag
76
+ - The HTML structure is built from the configured layout (see [Custom Layout](#custom-layout))
77
+
78
+ ### 3. Handle redirects
79
+
80
+ Use `inertia_redirect` for both Inertia and non-Inertia requests:
81
+
82
+ ```ruby
83
+ class App < Roda
84
+ plugin :inertia, version: -> { "v1" }
85
+
86
+ route do |r|
87
+ r.post "users" do
88
+ user = User.create(r.params["user"])
89
+ inertia_redirect "/users/#{user.id}"
90
+ end
91
+ end
92
+ end
93
+ ```
94
+
95
+ **For Inertia requests:**
96
+ - Internal paths (same host): redirects with **302** (GET) or **303** (non-GET)
97
+ - External URLs (different host): returns **409 Conflict** with an `X-Inertia-Location` header, telling the client to perform a full page visit
98
+
99
+ **For non-Inertia requests:**
100
+ - Always performs a standard redirect (default **302**, or specify a custom status)
101
+
102
+ **Custom status:**
103
+
104
+ ```ruby
105
+ inertia_redirect "/dashboard", status: 301
106
+ ```
107
+
108
+ ### 4. Check if a request is an Inertia request
109
+
110
+ Use `inertia_request?` to conditionally handle Inertia vs. non-Inertia requests:
111
+
112
+ ```ruby
113
+ route do |r|
114
+ r.get "dashboard" do
115
+ if inertia_request?
116
+ inertia "Dashboard", props: { stats: calculate_stats }
117
+ else
118
+ # Handle non-Inertia request differently
119
+ end
120
+ end
121
+ end
122
+ ```
123
+
124
+ ## Configuration
125
+
126
+ ### Custom Layout
127
+
128
+ You can customize the HTML document structure string using the `layout` option:
129
+
130
+ ```ruby
131
+ plugin :inertia,
132
+ version: -> { "v1" },
133
+ layout: {
134
+ head: "<title>My App</title>",
135
+ body: "<footer>&copy; 2024 My App</footer>"
136
+ }
137
+ ```
138
+
139
+ This produces:
140
+
141
+ ```html
142
+ <html><head><meta charset='utf-8' /><meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'><title>My App</title></head><body><div id='app' data-page='{...}'></div><footer>&copy; 2024 My App</footer></body></html>
143
+ ```
144
+
145
+ ## How It Works
146
+
147
+ ### Version Conflicts (409 Responses)
148
+
149
+ When the client's asset version doesn't match the server's version (detected via the `X-Inertia-Version` header), the plugin returns a **409 Conflict** response with an `X-Inertia-Location` header. This tells the Inertia client to perform a full page reload to fetch fresh assets.
150
+
151
+ ```
152
+ Client Request:
153
+ X-Inertia: true
154
+ X-Inertia-Version: v0
155
+
156
+ Server Response:
157
+ Status: 409 Conflict
158
+ X-Inertia-Location: https://example.com/page
159
+ ```
160
+
161
+ ### Request Flow
162
+
163
+ ```
164
+ +------------------+
165
+ | Inertia Request?|
166
+ +--------+---------+
167
+ |
168
+ +--------------+--------------+
169
+ | |
170
+ v v
171
+ +-------+-------+ +--------+--------+
172
+ | X-Inertia: true| | Initial Load |
173
+ +-------+-------+ +--------+--------+
174
+ | |
175
+ v v
176
+ +---------+---------+ +---------+---------+
177
+ | Version matches? | | Return full HTML |
178
+ +---------+---------+ | document with |
179
+ | | embedded JSON |
180
+ +---------+---------+ +-------------------+
181
+ | Yes | No |
182
+ v v
183
+ +---+---+ +---+---+ +------------------+
184
+ | Return| | 409 | | Redirect: |
185
+ | JSON | |Conflict| | 302/303 |
186
+ +---+---+ +---+---+ +------------------+
187
+ External? -> 409
188
+ ```
189
+
190
+ ## License
191
+
192
+ MIT
@@ -0,0 +1,120 @@
1
+ require "oj"
2
+ require "uri"
3
+
4
+ class Roda
5
+ module RodaPlugins
6
+ module Inertia
7
+ HTML_HEAD = "<html><head><meta charset='utf-8' /><meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>".freeze
8
+ HTML_BODY_START = "</head><body><div id='app' data-page='".freeze
9
+ HTML_APP_TAG_END = "'></div>".freeze
10
+ HTML_BODY_END = "</body></html>".freeze
11
+
12
+ HTTP_X_INERTIA = "HTTP_X_INERTIA".freeze
13
+ X_INERTIA_LOCATION = "X-Inertia-Location".freeze
14
+ X_INERTIA = "X-Inertia".freeze
15
+ VARY = "vary".freeze
16
+ STRING_TRUE = "true".freeze
17
+ EMPTY_STRING = "".freeze
18
+ HTTP_X_INERTIA_VERSION = "HTTP_X_INERTIA_VERSION".freeze
19
+ HTTP = "http://".freeze
20
+ HTTPS = "https://".freeze
21
+ JSON_MIME_TYPE = "application/json".freeze
22
+ QUESTION_MARK = "?".freeze
23
+
24
+ def self.configure(app, opts = {})
25
+ app.opts[:inertia_version] = opts[:version]
26
+ head = opts.dig(:layout, :head) || EMPTY_STRING
27
+ body = opts.dig(:layout, :body) || EMPTY_STRING
28
+
29
+ app.opts[:inertia_html_head] ||= '' << HTML_HEAD << head << HTML_BODY_START
30
+ app.opts[:inertia_html_body] ||= '' << HTML_APP_TAG_END << body << HTML_BODY_END
31
+ end
32
+
33
+ module InstanceMethods
34
+ def inertia(component, props: {})
35
+ if request.get? && inertia_request? && version_stale?
36
+ response.status = 409
37
+ response[X_INERTIA_LOCATION] = request.url
38
+ return EMPTY_STRING
39
+ end
40
+
41
+ page_data = {
42
+ component: component,
43
+ props: props,
44
+ url: inertia_request_url,
45
+ version: inertia_version
46
+ }
47
+
48
+ if inertia_request?
49
+ response[X_INERTIA] = STRING_TRUE
50
+ response[VARY] = X_INERTIA
51
+
52
+ page_data
53
+ else
54
+ '' \
55
+ << opts[:inertia_html_head] \
56
+ << Oj.dump(page_data, mode: :compat) \
57
+ << opts[:inertia_html_body] \
58
+ end
59
+ end
60
+
61
+ def inertia_redirect(path, status: nil)
62
+ if inertia_request?
63
+ status ||= request.get? ? 302 : 303
64
+
65
+ if external_url?(path)
66
+ response.status = 409
67
+ response[X_INERTIA_LOCATION] = path
68
+ EMPTY_STRING
69
+ else
70
+ request.redirect(path, status)
71
+ end
72
+ else
73
+ request.redirect(path, status || 302)
74
+ end
75
+ end
76
+
77
+ def inertia_request?
78
+ request.get_header(HTTP_X_INERTIA) == STRING_TRUE
79
+ end
80
+
81
+ private
82
+
83
+ def version_stale?
84
+ client_version = request.get_header(HTTP_X_INERTIA_VERSION)
85
+ server_version = inertia_version
86
+
87
+ server_version && client_version && client_version != server_version.to_s
88
+ end
89
+
90
+ def external_url?(path)
91
+ return false unless path.start_with?(HTTP, HTTPS)
92
+
93
+ uri = URI.parse(path)
94
+ [uri.scheme, uri.host, uri.port] != [request.scheme, request.host, request.port]
95
+ rescue URI::InvalidURIError
96
+ false
97
+ end
98
+
99
+ def inertia_request_url
100
+ query = request.query_string
101
+ return request.path if query.nil? || query.empty?
102
+
103
+ '' << request.path << QUESTION_MARK << query
104
+ end
105
+
106
+ def inertia_version
107
+ version = opts[:inertia_version]
108
+
109
+ if version.respond_to?(:call)
110
+ version.call
111
+ else
112
+ version
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ register_plugin(:inertia, Inertia)
119
+ end
120
+ end
@@ -0,0 +1 @@
1
+ require_relative "roda/plugins/inertia"
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roda-inertia-lite
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Henrique F. Teixeira
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: roda
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: oj
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rack-test
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '13.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: standard
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ description: Inertia.js adapter for Roda, without views or other plugins dependency
97
+ email:
98
+ - hriqueft@gmail.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENSE.txt
105
+ - README.md
106
+ - lib/roda-inertia-lite.rb
107
+ - lib/roda/plugins/inertia.rb
108
+ homepage: https://github.com/henrique-ft/roda-inertia-lite
109
+ licenses:
110
+ - MIT
111
+ metadata:
112
+ homepage_uri: https://github.com/henrique-ft/roda-inertia-lite
113
+ source_code_uri: https://github.com/henrique-ft/roda-inertia-lite
114
+ changelog_uri: https://github.com/henrique-ft/roda-inertia-lite/blob/main/CHANGELOG.md
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '3.0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 4.0.17
130
+ specification_version: 4
131
+ summary: Standalone Inertia.js adapter for Roda
132
+ test_files: []