@@ -48,7 +48,7 @@ def render
4848 else
4949 "#{ @response . headers [ 'Vary' ] } , X-Inertia"
5050 end
51- if @request . headers [ 'X-Inertia' ]
51+ if @request . inertia?
5252 @response . set_header ( 'X-Inertia' , 'true' )
5353 @render_method . call json : page . to_json , status : @response . status , content_type : Mime [ :json ]
5454 else
@@ -128,6 +128,9 @@ def page
128128 @page [ :scrollProps ] = scroll_props if scroll_props . present?
129129 @page . merge! ( resolve_merge_props )
130130
131+ once_props = resolve_once_props
132+ @page [ :onceProps ] = once_props if once_props . present?
133+
131134 @page
132135 end
133136
@@ -173,6 +176,18 @@ def resolve_merge_props
173176 } . delete_if { |_ , v | v . blank? }
174177 end
175178
179+ def resolve_once_props
180+ @props . each_with_object ( { } ) do |( key , prop ) , result |
181+ next unless prop . try ( :once? )
182+ next if excluded_by_once_cache? ( prop , [ key . to_s ] )
183+ next if excluded_by_partial_request? ( [ key . to_s ] )
184+
185+ once_key = ( prop . once_key || key ) . to_s
186+
187+ result [ once_key ] = { prop : key . to_s , expiresAt : prop . expires_at } . compact
188+ end
189+ end
190+
176191 def resolve_match_on_props
177192 all_merge_props . filter_map do |key , prop |
178193 prop . match_on . map! { |ms | "#{ key } .#{ ms } " } if prop . match_on . present?
@@ -251,6 +266,10 @@ def partial_except_keys
251266 @partial_except_keys ||= ( @request . headers [ 'X-Inertia-Partial-Except' ] || '' ) . split ( ',' ) . compact_blank!
252267 end
253268
269+ def except_once_keys
270+ @except_once_keys ||= ( @request . headers [ 'X-Inertia-Except-Once-Props' ] || '' ) . split ( ',' ) . compact_blank!
271+ end
272+
254273 def rendering_partial_component?
255274 @request . headers [ 'X-Inertia-Partial-Component' ] == @component
256275 end
@@ -265,19 +284,30 @@ def resolve_component(component)
265284
266285 def keep_prop? ( prop , path )
267286 return true if prop . is_a? ( AlwaysProp )
268-
269- if rendering_partial_component? && ( partial_keys . present? || partial_except_keys . present? )
270- path_with_prefixes = path_prefixes ( path )
271- return false if excluded_by_only_partial_keys? ( path_with_prefixes )
272- return false if excluded_by_except_partial_keys? ( path_with_prefixes )
273- end
287+ return false if excluded_by_once_cache? ( prop , path )
288+ return false if excluded_by_partial_request? ( path )
274289
275290 # Precedence: Evaluate IgnoreOnFirstLoadProp only after partial keys have been checked
276291 return false if prop . is_a? ( IgnoreOnFirstLoadProp ) && !rendering_partial_component?
277292
278293 true
279294 end
280295
296+ def excluded_by_once_cache? ( prop , path )
297+ return false unless prop . try ( :once? )
298+ return false if prop . try ( :fresh? )
299+
300+ once_key = ( prop . once_key || path . join ( '.' ) ) . to_s
301+ except_once_keys . include? ( once_key )
302+ end
303+
304+ def excluded_by_partial_request? ( path )
305+ return false unless rendering_partial_component? && ( partial_keys . present? || partial_except_keys . present? )
306+
307+ path_with_prefixes = path_prefixes ( path )
308+ excluded_by_only_partial_keys? ( path_with_prefixes ) || excluded_by_except_partial_keys? ( path_with_prefixes )
309+ end
310+
281311 def path_prefixes ( parts )
282312 ( 0 ...parts . length ) . map do |i |
283313 parts [ 0 ..i ] . join ( '.' )
0 commit comments