svelte-on-rails 22.3.0 → 22.4.1

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: a95b04eb2bc4fe9f3b9cf489af8550377bb35bd8297b7c8c9849fae0b4985e89
4
- data.tar.gz: d648e0c9240757feb39c42904165bbd985ee24c19de78e2b1d5068c60c2b9539
3
+ metadata.gz: 61a1a506e531e729e27433251f92fae2e24e73916f9004fafd75efb52b1a073e
4
+ data.tar.gz: d0cad407740e263bfc0f7b1dd58468734b4e4ddc00623257191c847144ddd1d1
5
5
  SHA512:
6
- metadata.gz: f32d6e15c061f5c95abaffdf1e74ac295bd89058a9eb226e8ead4ac34321890509f0dddfe2af0e3e8c5a0e0d0696ac4236eb224c41928ca1ed2cd2f06ac51aca
7
- data.tar.gz: 588239e64c3a43f660c3099710d0b0a41f59fc0636be72d7458756a8b55fd6e773fae911937de0099d5b7d4d30466d2856a62b0405f5e2a6ca2e19fe23e208b7
6
+ metadata.gz: 7d4404822d86e3e1eb612a937e2bf65883067e462779d5c4d469f6f456d87c97ad126d0ea9b2844ba4526880b82d44eeb12b1d40b988bb8c9dab3d9189ab219f
7
+ data.tar.gz: e86f2bb86f30a21165ed9422b7ef23eca36317ce1aedcc0e433792760cd3b7c6b10dc9d9bdd9c4ddc55f726fab5dd1c3f0d97f4817b93c75c14464ce1b8b38c3
@@ -18,6 +18,7 @@ require "svelte_on_rails/lib/to_svelte_values"
18
18
  require "svelte_on_rails/lib/to_svelte_support"
19
19
  require "svelte_on_rails/lib/to_svelte_translations"
20
20
  require "svelte_on_rails/lib/cache_warmer"
21
+ require "svelte_on_rails/lib/boot_context"
21
22
 
22
23
  # installer
23
24
  require 'svelte_on_rails/installer/utils'
@@ -42,16 +42,8 @@ module SvelteOnRails
42
42
 
43
43
  # DEFAULTS
44
44
 
45
- if @configs[:redis_cache_store]
46
- store = @configs[:redis_cache_store]
47
- if store['expires_in'].is_a?(String)
48
- @configs[:redis_cache_store]['expires_in'] = parse_duration(
49
- store['expires_in']
50
- )
51
- end
52
- @redis_namespace = store[:namespace]
53
- end
54
- @redis_namespace ||= "svelte-on-rails:#{Rails.env rescue 'no-env'}"
45
+ rd = @configs[:redis_cache_store] && @configs[:redis_cache_store][:namespace]
46
+ @redis_namespace ||= "#{rd || app_name}:svelte-on-rails:#{Rails.env rescue 'no-env'}"
55
47
  @configs[:skip_ssr_header] ||= 'X-Turbo-Request-ID'
56
48
  if defined? Redis
57
49
  @redis_instance = Redis.new(url: redis_cache_store[:url])
@@ -60,6 +52,7 @@ module SvelteOnRails
60
52
  validate_serialized_configs
61
53
  custom_config_validations
62
54
  validate_npm_packages
55
+ process_configs
63
56
  end
64
57
 
65
58
  def set_config_defaults
@@ -175,11 +168,19 @@ module SvelteOnRails
175
168
  nil
176
169
  end
177
170
 
171
+ def process_configs
172
+ ex = @configs[:cache_expires_in]
173
+ if ex
174
+ @configs[:cache_expires_in] = ex.split('.').first.to_i.send(ex.split('.').last)
175
+ end
176
+ end
177
+
178
178
  private
179
179
 
180
180
  def redis_cache_store_configs
181
181
  if defined?(Rails.application) && Rails.application.config.cache_store.is_a?(Array) && Rails.application.config.cache_store.first == :redis_cache_store
182
- { redis_cache_store: Rails.application.config.cache_store.second.stringify_keys }
182
+ rc = Rails.application.config.cache_store.second.deep_symbolize_keys
183
+ { redis_cache_store: rc.slice(:url, :namespace) }
183
184
  else
184
185
  {}
185
186
  end
@@ -228,7 +229,7 @@ module SvelteOnRails
228
229
  ssr: { options_required: [:auto, true, false] },
229
230
  watch_changes: { options_optional: [true, false] },
230
231
 
231
- redis_cache_store: { hash_of_strings_optional: [:url, :namespace, :expires_in] },
232
+ redis_cache_store: { hash_of_strings_optional: [:url, :namespace] },
232
233
  turbo_stream: { hash_of_strings_optional: [:target_html_id, :channel] },
233
234
  action_cable: { hash_of_strings_optional: [:channel] },
234
235
 
@@ -245,6 +246,11 @@ module SvelteOnRails
245
246
  elsif k == :default_components_root
246
247
  available_keys = ['components_subdir'] + @configs[:extra_component_paths].to_a
247
248
  raise "[SOR] Invalid configuration: #{k} must be one of: [«#{available_keys.join('», «')}»]" unless available_keys.include?(v)
249
+ elsif k == :cache_expires_in
250
+ reg = /^[0-9]+\.(minutes|hours|days)$/
251
+ unless v.match(reg)
252
+ raise "#{k} must match #{reg}"
253
+ end
248
254
  elsif validation[:required]
249
255
  if validation[:required] == :string
250
256
  raise "[SOR] Missing configuration: #{k} must contain a string" if !v.present? || !v.is_a?(String)
@@ -262,6 +268,8 @@ module SvelteOnRails
262
268
  raise "[SOR] Invalid configuration: #{k} must be one of #{validation[:options_optional]}" unless validation[:options_optional].include?(v)
263
269
  elsif validation[:hash_of_strings_optional]
264
270
 
271
+ raise "[SOR] #{k} is empty. Either remove this key or provide a hash" unless v.present?
272
+
265
273
  _available_keys = validation[:hash_of_strings_optional]
266
274
  v.each do |_k, _v|
267
275
  raise "[SOR] Invalid configuration: #{k}/#{_k} («#{_v}» / #{_v.class}) must be a string" unless _v.is_a?(String)
@@ -367,5 +375,12 @@ module SvelteOnRails
367
375
  end
368
376
  end
369
377
 
378
+ def app_name
379
+ # Same logic Rails uses internally for e.g. cache_store default namespace
380
+ Rails.application.class&.module_parent_name.to_s[0..30].underscore.presence || 'app'
381
+ rescue StandardError
382
+ 'app'
383
+ end
384
+
370
385
  end
371
386
  end
@@ -0,0 +1,98 @@
1
+ module SvelteOnRails
2
+ module BootContext
3
+ module_function
4
+
5
+ # We deliberately use a BLACKLIST + program-name check, not a whitelist of
6
+ # loaded constants. Reason: `puma` is typically in the app's Gemfile default
7
+ # group, so `defined?(::Puma::Launcher)` is truthy even inside `rails console`
8
+ # or `rails assets:precompile`.
9
+ def serving_http?
10
+ # Explicit overrides for tests / edge cases:
11
+ return true if ENV["SVELTE_ON_RAILS_FORCE_SSR"] == "1"
12
+ return false if ENV["SVELTE_ON_RAILS_DISABLE_SSR"] == "1"
13
+
14
+ return false if rake_task?
15
+ return false if rails_console?
16
+ return false if rails_runner?
17
+ return false if rails_dbconsole?
18
+ return false if rails_generator?
19
+ return false if test_runner?
20
+
21
+ # Positive signal: `rails server` sets this constant during command dispatch.
22
+ return true if defined?(Rails::Server)
23
+
24
+ # Positive signal: launched directly via a server binary (bundle exec puma,
25
+ # rackup, unicorn, falcon, passenger). $PROGRAM_NAME is set by the launcher
26
+ # before it requires the app, so this is reliable.
27
+ return true if server_program_name?
28
+
29
+ # Default: assume this is NOT an HTTP-serving context. Better to be silent
30
+ # than to spawn an unwanted Node process; users can force with the ENV var.
31
+ false
32
+ end
33
+
34
+ def description
35
+ return "rake tasks: #{Rake.application.top_level_tasks.join(', ')}" if rake_task?
36
+ return "rails console" if rails_console?
37
+ return "rails runner" if rails_runner?
38
+ return "rails dbconsole" if rails_dbconsole?
39
+ return "rails generator" if rails_generator?
40
+ return "test runner (#{test_runner_name})" if test_runner?
41
+ return "rails server" if defined?(Rails::Server)
42
+ "program=#{File.basename($PROGRAM_NAME)}"
43
+ end
44
+
45
+ # --- detectors -----------------------------------------------------------
46
+
47
+ def rake_task?
48
+ defined?(Rake) && Rake.respond_to?(:application) && Rake.application.top_level_tasks.any?
49
+ end
50
+
51
+ def rails_console?
52
+ defined?(Rails::Console) ||
53
+ program_basename == "irb" ||
54
+ (defined?(IRB) && $PROGRAM_NAME.to_s.include?("irb"))
55
+ end
56
+
57
+ def rails_runner?
58
+ defined?(Rails::Command::RunnerCommand) &&
59
+ Rails::Command.const_defined?(:RunnerCommand) &&
60
+ current_rails_command?("runner")
61
+ end
62
+
63
+ def rails_dbconsole?
64
+ defined?(Rails::DBConsole) || current_rails_command?("dbconsole")
65
+ end
66
+
67
+ def rails_generator?
68
+ defined?(Rails::Generators) && current_rails_command?("generate", "destroy", "new")
69
+ end
70
+
71
+ def test_runner?
72
+ !test_runner_name.nil?
73
+ end
74
+
75
+ def test_runner_name
76
+ return "rspec" if $PROGRAM_NAME.to_s.include?("rspec")
77
+ return "minitest" if defined?(Minitest) && Minitest.respond_to?(:run) && !defined?(Rails::Server)
78
+ return "cucumber" if $PROGRAM_NAME.to_s.include?("cucumber")
79
+ nil
80
+ end
81
+
82
+ def server_program_name?
83
+ %w[puma rackup unicorn unicorn_rails falcon passenger].include?(program_basename)
84
+ end
85
+
86
+ def program_basename
87
+ File.basename($PROGRAM_NAME.to_s)
88
+ end
89
+
90
+ # `rails <command>` sets ARGV[0] to the command name before Rails::Command
91
+ # dispatches. This is stable across Rails 6/7/8.
92
+ def current_rails_command?(*names)
93
+ argv0 = (defined?(ARGV) && ARGV.first) || nil
94
+ return false unless argv0
95
+ names.include?(argv0.to_s)
96
+ end
97
+ end
98
+ end
@@ -5,7 +5,14 @@ module SvelteOnRails
5
5
  class ViewHelperSupport
6
6
 
7
7
  UNCACHED_PROPS_PLACEHOLDER = "**Uncached Props are available only after hydration**"
8
- RENDER_OPTIONS = { ssr: [true, false, :auto], hydrate: :bool, debug: :bool, cache_key: :string_array, expires_in: :integer, cached: :bool }
8
+ RENDER_OPTIONS = {
9
+ ssr: [:oneof, [true, false, :auto]],
10
+ hydrate: [:oneof, [true, false]],
11
+ debug: [:oneof, [true, false]],
12
+ cache_key: [:typeof, [String, Array]],
13
+ cache_expires_in: [:typeof, [Integer, ActiveSupport::Duration]],
14
+ cached: [:oneof, [true, false]]
15
+ }
9
16
  attr_reader :conf
10
17
 
11
18
  def initialize(
@@ -191,7 +198,7 @@ module SvelteOnRails
191
198
  @conf.debug_log(debug?, component_paths[:name], "rendered and stored to cache (key: «#{cache_key}»)") do
192
199
  r = view_context.instance_eval(&block)
193
200
  unless @skip_caching
194
- @conf.redis_instance.set(cache_key, r)
201
+ @conf.redis_instance.set(cache_key, r, ex: redis_expiration_seconds)
195
202
  end
196
203
  r
197
204
  end
@@ -289,18 +296,17 @@ module SvelteOnRails
289
296
  raise "Unknown options: #{@options.keys - RENDER_OPTIONS.keys}. Valid options are: :#{RENDER_OPTIONS.keys.join(', :')}"
290
297
  end
291
298
  @options.each do |k, v|
292
- _v = RENDER_OPTIONS[k]
293
- if _v == :bool
294
- unless [true, false].include?(v)
295
- raise "[SOR] Invalid option: :#{k} must be true or false (boolean)."
296
- end
297
- elsif _v == :string_array
298
- unless [String, Array].include?(v.class)
299
- raise "[SOR] Invalid option: :#{k} must be a string or Array (list of strings)."
299
+ validation = RENDER_OPTIONS[k]
300
+ _v_type = validation.first
301
+ _v_opts = validation.second
302
+
303
+ if validation.first == :oneof
304
+ unless validation.second.include? v
305
+ raise "[SOR] Invalid option: :#{k} must be one of #{validation.second.join(', ')}"
300
306
  end
301
- elsif _v.is_a?(Array)
302
- unless _v.include?(v)
303
- raise "[SOR] Invalid option: :#{k} must be one of #{_v.join(', ')}"
307
+ elsif validation.first == :typeof
308
+ unless validation.second.include? v.class
309
+ raise "[SOR] Invalid option: :#{k} must be one of #{validation.second.join(', ')}"
304
310
  end
305
311
  else
306
312
  raise "[SOR] Unknown validation for option"
@@ -337,16 +343,18 @@ module SvelteOnRails
337
343
  @cache_key = begin
338
344
  fingerprint = (@conf.watch_changes? ? @conf.fingerprint(component_paths[:path], debug?, component_paths[:name]) : '')
339
345
 
340
- cache_key_raw = @view_dir + @component_name + @options.to_s +
346
+ cache_key_raw = @options.to_s +
341
347
  @cached_props.to_s + fingerprint + @options[:cache_key].to_s +
342
348
  (@cached_props.key?(:_uncached) ? "" : @html_options.to_s)
343
349
 
344
- "#{@conf.redis_namespace}:#{Digest::XXH64.hexdigest(cache_key_raw)}"
350
+ "#{@conf.redis_namespace}:#{@component_name.gsub('/','-')}:#{Digest::XXH64.hexdigest(cache_key_raw)}"
345
351
  end
346
352
  end
347
353
 
348
354
  def redis_expiration_seconds
349
- (conf.redis_cache_store[:expires_in] || @options[:expires_in] || 1.hour).to_i
355
+ # this accepts integer or ActiveSupport::Duration
356
+ ex = @options[:cache_expires_in] || conf.configs[:cache_expires_in] || 3.days
357
+ ex.to_i
350
358
  end
351
359
 
352
360
  end
@@ -13,12 +13,14 @@ module SvelteOnRails
13
13
 
14
14
  # Spawn the Node SSR child process only when we're really about to serve
15
15
  # HTTP. `after_initialize` runs at the end of boot, so Puma/Unicorn/etc.
16
- # are already loaded and the detection in `start_ssr_server?` is reliable.
16
+ # are already loaded and the detection in `serving_http?` is reliable.
17
17
  initializer "svelte-on-rails.initialize_ssr_client" do |app|
18
18
  app.config.after_initialize do
19
19
 
20
- if defined?(Rake) && Rake.respond_to?(:application) && Rake.application.top_level_tasks.any?
21
- SvelteOnRails::Lib::Utils.ssr_server_log("Skipping svelte-on-rails SSR-Server because of top level task: #{Rake.application.top_level_tasks.join(', ')}")
20
+ if !SvelteOnRails::BootContext.serving_http?
21
+ SvelteOnRails::Lib::Utils.ssr_server_log(
22
+ "Skipping svelte-on-rails SSR-Server (#{SvelteOnRails::BootContext.description})"
23
+ )
22
24
  $stdout.flush
23
25
  elsif SvelteOnRails::Configuration.instance.configs[:ssr]
24
26
  SvelteOnRails::SsrClient.instance
@@ -53,6 +55,5 @@ module SvelteOnRails
53
55
  load File.expand_path("../../tasks/svelte_on_rails_tasks.rake", __FILE__)
54
56
  load File.expand_path("../../tasks/cache_tasks.rake", __FILE__)
55
57
  end
56
-
57
58
  end
58
59
  end
@@ -143,15 +143,18 @@ module SvelteOnRails
143
143
  end
144
144
 
145
145
  def calling_view_dir
146
- rails_views_path = Rails.root.join('app', 'views').to_s
147
- raw_path_full = caller_locations.find do |loc|
148
- loc.to_s.include?(rails_views_path)
149
- end.to_s
150
-
151
- if raw_path_full.present?
152
- raw_path = raw_path_full.split(/:[0-9]+:in/).first
153
- File.dirname(raw_path[(rails_views_path.length + 1)..-1])
154
- end.to_s
146
+ rails_views_path = SvelteOnRails::ViewHelpers.rails_views_path
147
+ prefix_len = rails_views_path.length + 1
148
+
149
+ # Walk only a bounded slice of the stack; use loc.path (no string formatting).
150
+ loc = caller_locations(1, 60)&.find { |l| l.path&.start_with?(rails_views_path) }
151
+ return ''.freeze unless loc
152
+
153
+ File.dirname(loc.path[prefix_len..-1])
154
+ end
155
+
156
+ def self.rails_views_path
157
+ @rails_views_path ||= Rails.root.join('app', 'views').to_s
155
158
  end
156
159
 
157
160
  end
@@ -60,10 +60,7 @@ namespace :svelte_on_rails do
60
60
  next
61
61
  end
62
62
 
63
- extra = ENV["PATTERN"].to_s.strip
64
- pattern = extra.empty? ? "#{namespace}:*" : "#{namespace}:*#{extra}*"
65
- limit = ENV["LIMIT"].to_s =~ /\A\d+\z/ ? ENV["LIMIT"].to_i : nil
66
- sort_by = %w[size key].include?(ENV["SORT"]) ? ENV["SORT"] : "size"
63
+ pattern = "#{namespace}:*"
67
64
 
68
65
  puts "Scanning Redis for keys matching «#{pattern}» ..."
69
66
 
@@ -71,7 +68,7 @@ namespace :svelte_on_rails do
71
68
  cursor = "0"
72
69
 
73
70
  loop do
74
- cursor, keys = redis.scan(cursor, match: pattern, count: 500)
71
+ cursor, keys = redis.scan(cursor, match: pattern)
75
72
 
76
73
  if keys.any?
77
74
  # Pipeline MEMORY USAGE / TTL / TYPE for the whole batch.
@@ -102,19 +99,18 @@ namespace :svelte_on_rails do
102
99
  next
103
100
  end
104
101
 
105
- entries.sort_by! { |e| sort_by == "size" ? -e[:bytes] : e[:key] }
106
- shown = limit ? entries.first(limit) : entries
102
+ entries.sort_by! { |e| -e[:bytes] }
107
103
 
108
- key_w = [shown.map { |e| e[:key].length }.max, 3].max
109
- size_w = [shown.map { |e| human_bytes(e[:bytes]).length }.max, 4].max
110
- ttl_w = [shown.map { |e| ttl_str(e[:ttl]).length }.max, 3].max
111
- type_w = [shown.map { |e| e[:type].to_s.length }.max, 4].max
104
+ key_w = [entries.map { |e| e[:key].length }.max, 3].max
105
+ size_w = [entries.map { |e| human_bytes(e[:bytes]).length }.max, 4].max
106
+ ttl_w = [entries.map { |e| ttl_str(e[:ttl]).length }.max, 3].max
107
+ type_w = [entries.map { |e| e[:type].to_s.length }.max, 4].max
112
108
 
113
109
  header = format("%-#{key_w}s %#{size_w}s %#{ttl_w}s %-#{type_w}s", "KEY", "SIZE", "TTL", "TYPE")
114
110
  puts header
115
111
  puts "-" * header.length
116
112
 
117
- shown.each do |e|
113
+ entries.each do |e|
118
114
  puts format(
119
115
  "%-#{key_w}s %#{size_w}s %#{ttl_w}s %-#{type_w}s",
120
116
  e[:key], human_bytes(e[:bytes]), ttl_str(e[:ttl]), e[:type]
@@ -123,8 +119,7 @@ namespace :svelte_on_rails do
123
119
 
124
120
  total_bytes = entries.sum { |e| e[:bytes] }
125
121
  puts "-" * header.length
126
- puts "Total: #{entries.size} key(s), #{human_bytes(total_bytes)} in RAM" \
127
- "#{limit && entries.size > limit ? " (showing top #{limit} by #{sort_by})" : ""}"
122
+ puts "Total: #{entries.size} key(s), #{human_bytes(total_bytes)} in RAM"
128
123
  end
129
124
  end
130
125
  end
@@ -48,7 +48,6 @@ ssr_error_tag: true
48
48
 
49
49
  # redis_cache_store:
50
50
  # url: 'redis://localhost:6379/2'
51
- # expires_in: 90.minutes (=> default / fallback: 1 hour)
52
51
  # namespace: 'my-app-svelte-on-rails'
53
52
 
54
53
 
@@ -56,6 +55,11 @@ use_caching: false
56
55
  # this is the default for the #svelte view-helper.
57
56
  # When set to true, the redis gem is required.
58
57
 
58
+ #cache_expires_in: 4.days
59
+ # ttl for cache keys
60
+ # can be overridden by view-helper options
61
+ # default: 3.days
62
+
59
63
  debug: true
60
64
  # provides deeper logging with timestamps for performance improvements
61
65
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svelte-on-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 22.3.0
4
+ version: 22.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Sedlmair
@@ -84,6 +84,7 @@ files:
84
84
  - lib/svelte_on_rails/installer/npm_sandbox.rb
85
85
  - lib/svelte_on_rails/installer/svelte.rb
86
86
  - lib/svelte_on_rails/installer/utils.rb
87
+ - lib/svelte_on_rails/lib/boot_context.rb
87
88
  - lib/svelte_on_rails/lib/cache_warmer.rb
88
89
  - lib/svelte_on_rails/lib/development_utils.rb
89
90
  - lib/svelte_on_rails/lib/fallback_renderer.rb