inertia_rails 3.19.0 → 3.20.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91497e407b470afd9fdc6c938bf97fd1f7e74e8b39ab6e76ca8b8c50b2d92f75
4
- data.tar.gz: d132dd3a36f682357bcbeb63e7d0e825449603b2928d760c5a725e9a35657fc3
3
+ metadata.gz: e56b13d7d02abe1f35709de08b6873cc0d407215a97891502bb4ee7439e0d005
4
+ data.tar.gz: a627501dd94ab5d9ad747c2decce31ab47fe4870e677d2197395680cb4dfaa3e
5
5
  SHA512:
6
- metadata.gz: 385280b11bf6fc2aae7f067bb5c77e1bc641d886aa71aff1e3f25ded9db24256a53b68b0daeb4722fe28bca45f0e7b5daeff308b81d326348f3852b8e54e7f2e
7
- data.tar.gz: 21548a2a8b4e1bf1ecf307d2298c78e24a7fad344f881197d8f421538ca7769704a2301979f45990c3af46fdf1578b49c53a65cfcd169ba5ac6b63150bccce1e
6
+ metadata.gz: 5f9b35e5039a9516daaa7ff6d9c1b702011d3ddd2161fb40922143fde7951522ca3e8268691d2b3490baae217febb046b04d07d677facf39b81dad9f1f5bd076
7
+ data.tar.gz: 3523dadfe1c2877767cf534b4a6c95ef979250bbf3c78a86af31e6220247cb860d020402b359a32c8b5295b1d246aa30d8a5a590dd8fae44de89623b24521880
data/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [3.20.0] - 2026-04-04
8
+
9
+ * Fix partial reload filtering with arrays
10
+ * Docs update for the v3.0 upgrade
11
+ * Add plugin for Puma so that SSR Just Works in production for apps using Puma
12
+
7
13
  ## [3.19.0] - 2026-03-25
8
14
 
9
15
  * Upgrades to support Inertia.js 3.x! All thanks to @skyrukov!
@@ -2,6 +2,8 @@
2
2
 
3
3
  module InertiaRails
4
4
  class Configuration
5
+ DEFAULT_SSR_URL = 'http://localhost:13714'
6
+
5
7
  DEFAULTS = {
6
8
  # Whether to combine hashes with the same keys instead of replacing them.
7
9
  deep_merge_shared_data: false,
@@ -24,6 +26,7 @@ module InertiaRails
24
26
 
25
27
  # SSR options.
26
28
  ssr_enabled: false,
29
+ # URL of the SSR server. When nil, defaults to DEFAULT_SSR_URL.
27
30
  ssr_url: nil,
28
31
  ssr_raise_on_error: false,
29
32
  on_ssr_error: nil,
@@ -35,6 +38,9 @@ module InertiaRails
35
38
  # Accepts true, false/nil, or a Hash of Rails.cache.fetch options.
36
39
  # Lambdas are supported (instance_exec'd in controller context).
37
40
  ssr_cache: nil,
41
+ # JavaScript runtime used to run the SSR bundle (e.g. "node", "bun", "deno").
42
+ # When nil, auto-detects from lockfiles or falls back to "node".
43
+ ssr_runtime: nil,
38
44
 
39
45
  # Used to detect version drift between server and client.
40
46
  version: nil,
@@ -88,6 +88,8 @@ module InertiaRails
88
88
  end
89
89
 
90
90
  if prop.is_a?(Array)
91
+ next if !parent_was_resolved && excluded_by_partial_request?(path)
92
+
91
93
  transformed_props[key] = transform_array(prop, path, parent_was_resolved: parent_was_resolved)
92
94
  next
93
95
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InertiaRails
4
+ module SSR
5
+ class << self
6
+ def vite_dev_server_url
7
+ # vite_rails: TCP probe
8
+ if defined?(ViteRuby) && ViteRuby.instance.dev_server_running?
9
+ config = ViteRuby.config
10
+ return "#{config.protocol}://#{config.host_with_port}"
11
+ end
12
+
13
+ # rails_vite + jsbundling: file-based
14
+ path = Rails.root.join('tmp/rails-vite.json')
15
+ JSON.parse(path.read)['url'] if path.exist?
16
+ rescue StandardError
17
+ nil
18
+ end
19
+
20
+ def vite_dev_server_running?
21
+ !vite_dev_server_url.nil?
22
+ end
23
+ end
24
+ end
25
+ end
@@ -29,7 +29,10 @@ module InertiaRails
29
29
  end
30
30
 
31
31
  def request
32
- response = Net::HTTP.post(URI(url), page_json, 'Content-Type' => 'application/json')
32
+ uri = URI.parse(url)
33
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
34
+ http.post(uri.request_uri, page_json, 'Content-Type' => 'application/json')
35
+ end
33
36
 
34
37
  unless response.is_a?(Net::HTTPSuccess)
35
38
  body = begin
@@ -74,14 +77,14 @@ module InertiaRails
74
77
  elsif (dev_url = vite_dev_server_url)
75
78
  "#{dev_url}/__inertia_ssr"
76
79
  else
77
- 'http://localhost:13714/render'
80
+ "#{InertiaRails::Configuration::DEFAULT_SSR_URL}/render"
78
81
  end
79
82
  end
80
83
 
81
84
  def vite_dev_server_url
82
85
  return @vite_dev_server_url if defined?(@vite_dev_server_url)
83
86
 
84
- @vite_dev_server_url = detect_vite_dev_url
87
+ @vite_dev_server_url = InertiaRails::SSR.vite_dev_server_url
85
88
  end
86
89
 
87
90
  def bundle_exists?
@@ -92,19 +95,5 @@ module InertiaRails
92
95
 
93
96
  Array(bundle).any? { |path| File.exist?(path) }
94
97
  end
95
-
96
- def detect_vite_dev_url
97
- # vite_rails: TCP probe, no metadata file
98
- if defined?(ViteRuby) && ViteRuby.instance.dev_server_running?
99
- config = ViteRuby.config
100
- return "#{config.protocol}://#{config.host_with_port}"
101
- end
102
-
103
- # rails_vite + jsbundling: file-based
104
- path = Rails.root.join('tmp/rails-vite.json')
105
- JSON.parse(path.read)['url'] if path.exist?
106
- rescue JSON::ParserError
107
- nil
108
- end
109
98
  end
110
99
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InertiaRails
4
- VERSION = '3.19.0'
4
+ VERSION = '3.20.0'
5
5
  end
data/lib/inertia_rails.rb CHANGED
@@ -27,6 +27,9 @@ require_relative 'inertia_rails/scroll_prop'
27
27
  require_relative 'inertia_rails/prop_evaluator'
28
28
  require_relative 'inertia_rails/props_resolver'
29
29
 
30
+ # ssr
31
+ require_relative 'inertia_rails/ssr'
32
+
30
33
  # rendering
31
34
  require_relative 'inertia_rails/meta_tag'
32
35
  require_relative 'inertia_rails/meta_tag_builder'
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+
5
+ Puma::Plugin.create do
6
+ BOOT_TIMEOUT = 30 # rubocop:disable Lint/ConstantDefinitionInBlock
7
+
8
+ def start(launcher)
9
+ @log_writer = launcher.log_writer
10
+ @running = true
11
+ @boot_signal = Queue.new
12
+
13
+ in_background do
14
+ @boot_signal.pop
15
+ run_ssr_loop
16
+ end
17
+
18
+ events = launcher.events
19
+
20
+ if events.respond_to?(:after_booted)
21
+ events.after_booted { @boot_signal.push(true) }
22
+ events.after_stopped { stop_ssr }
23
+ events.before_restart { stop_ssr }
24
+ else
25
+ events.on_booted { @boot_signal.push(true) }
26
+ events.on_stopped { stop_ssr }
27
+ events.on_restart { stop_ssr }
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def run_ssr_loop
34
+ return unless ssr_enabled?
35
+ return if vite_dev_server_running?
36
+
37
+ bundle = resolve_bundle
38
+ return unless bundle
39
+
40
+ runtime = resolve_runtime
41
+ uri = URI(resolve_base_url)
42
+ consecutive_crashes = 0
43
+ delay = 1
44
+
45
+ while @running
46
+ break if vite_dev_server_running?
47
+
48
+ begin
49
+ @pid = spawn(runtime, bundle)
50
+ rescue Errno::ENOENT
51
+ log "! Inertia SSR: '#{runtime}' not found in PATH"
52
+ break
53
+ end
54
+
55
+ if wait_for_server(uri)
56
+ log "* Inertia SSR: server ready (pid: #{@pid})"
57
+ consecutive_crashes = 0
58
+ delay = 1
59
+ else
60
+ log "! Inertia SSR: server failed to respond within #{BOOT_TIMEOUT}s"
61
+ end
62
+
63
+ _, status = Process.wait2(@pid)
64
+ break unless @running
65
+
66
+ consecutive_crashes += 1
67
+ if consecutive_crashes >= 10
68
+ log '! Inertia SSR: too many crashes, giving up'
69
+ break
70
+ end
71
+
72
+ log "! Inertia SSR: process exited (status: #{status.exitstatus}), restarting in #{delay}s..."
73
+ sleep delay
74
+ delay = [delay * 2, 16].min
75
+ end
76
+ end
77
+
78
+ def stop_ssr
79
+ @running = false
80
+ return unless @pid
81
+
82
+ Process.waitpid(@pid, Process::WNOHANG)
83
+
84
+ # /shutdown correctly handles cluster mode
85
+ begin
86
+ uri = URI(resolve_base_url)
87
+ Net::HTTP.start(uri.host, uri.port, open_timeout: 2, read_timeout: 5) do |http|
88
+ http.post('/shutdown', '')
89
+ end
90
+ Process.wait(@pid)
91
+ rescue StandardError
92
+ Process.kill('TERM', @pid)
93
+ Process.wait(@pid)
94
+ end
95
+ rescue Errno::ESRCH, Errno::ECHILD
96
+ # already exited
97
+ ensure
98
+ @pid = nil
99
+ end
100
+
101
+ def wait_for_server(uri) # rubocop:disable Naming/PredicateMethod
102
+ BOOT_TIMEOUT.times do
103
+ return false unless @running
104
+
105
+ begin
106
+ Net::HTTP.start(uri.host, uri.port, open_timeout: 1, read_timeout: 1) do |http|
107
+ http.get('/health')
108
+ end
109
+ return true
110
+ rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Net::OpenTimeout, Net::ReadTimeout
111
+ sleep 1
112
+ end
113
+ end
114
+ false
115
+ end
116
+
117
+ def ssr_enabled?
118
+ InertiaRails.configuration.ssr_enabled
119
+ end
120
+
121
+ def resolve_runtime
122
+ InertiaRails.configuration.ssr_runtime || detect_runtime
123
+ end
124
+
125
+ def detect_runtime
126
+ root = defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd)
127
+ if File.exist?(root.join('bun.lockb')) || File.exist?(root.join('bun.lock'))
128
+ 'bun'
129
+ elsif File.exist?(root.join('deno.lock'))
130
+ 'deno'
131
+ else
132
+ 'node'
133
+ end
134
+ end
135
+
136
+ def resolve_bundle
137
+ config = InertiaRails.configuration
138
+
139
+ return Array(config.ssr_bundle).find { |path| File.exist?(path) } if config.ssr_bundle
140
+
141
+ if defined?(ViteRuby)
142
+ ssr_dir = ViteRuby.config.ssr_output_dir
143
+ candidates = Dir.glob(File.join(ssr_dir, 'inertia.*')) if ssr_dir
144
+ return candidates.first if candidates&.any?
145
+ end
146
+
147
+ path = 'public/assets-ssr/inertia.js'
148
+ path if File.exist?(path)
149
+ end
150
+
151
+ def resolve_base_url
152
+ url = InertiaRails.configuration.ssr_url
153
+ if url
154
+ url.sub(%r{/(render|__inertia_ssr)\z}, '')
155
+ else
156
+ InertiaRails::Configuration::DEFAULT_SSR_URL
157
+ end
158
+ end
159
+
160
+ def vite_dev_server_running?
161
+ InertiaRails::SSR.vite_dev_server_running?
162
+ end
163
+
164
+ def log(message)
165
+ @log_writer.log(message)
166
+ end
167
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inertia_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.19.0
4
+ version: 3.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Knoles
@@ -9,7 +9,7 @@ authors:
9
9
  - Eugene Granovsky
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-03-25 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -218,9 +218,11 @@ files:
218
218
  - lib/inertia_rails/rspec/deprecated.rb
219
219
  - lib/inertia_rails/scroll_metadata.rb
220
220
  - lib/inertia_rails/scroll_prop.rb
221
+ - lib/inertia_rails/ssr.rb
221
222
  - lib/inertia_rails/ssr_renderer.rb
222
223
  - lib/inertia_rails/testing.rb
223
224
  - lib/inertia_rails/version.rb
225
+ - lib/puma/plugin/inertia_ssr.rb
224
226
  - lib/tasks/inertia_rails.rake
225
227
  homepage: https://github.com/inertiajs/inertia-rails
226
228
  licenses:
@@ -246,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
248
  - !ruby/object:Gem::Version
247
249
  version: '0'
248
250
  requirements: []
249
- rubygems_version: 3.6.2
251
+ rubygems_version: 4.0.6
250
252
  specification_version: 4
251
253
  summary: Inertia.js adapter for Rails
252
254
  test_files: []