@@ -20,6 +20,7 @@ import {
2020 Ref ,
2121 ref ,
2222 nextTick ,
23+ triggerRef ,
2324} from 'vue'
2425import {
2526 StateTree ,
@@ -75,6 +76,22 @@ interface MarkedAction<Fn extends _Method = _Method> {
7576 [ ACTION_NAME ] : string
7677}
7778
79+ /**
80+ * Recursively applies a partial patch onto a reactive container (plain object, Map, or Set) and returns the mutated target.
81+ *
82+ * For Map targets, entries from the patch Map are set into the target. For Set targets, entries from the patch Set are added.
83+ * For plain objects, properties are merged recursively only when:
84+ * - the target property exists,
85+ * - both the target value and the patch value are plain objects,
86+ * - and the patch value is not a Ref or a reactive object.
87+ * In all other cases the patch value overwrites the target property.
88+ *
89+ * The function mutates and returns the provided `target`.
90+ *
91+ * @param target - The reactive container to be patched (plain object, Map, or Set).
92+ * @param patchToApply - A deep partial patch whose entries/properties will be applied to `target`.
93+ * @returns The same `target` instance after applying the patch.
94+ */
7895function mergeReactiveObjects <
7996 T extends Record < any , unknown > | Map < unknown , unknown > | Set < unknown > ,
8097> ( target : T , patchToApply : _DeepPartial < T > ) : T {
@@ -91,6 +108,7 @@ function mergeReactiveObjects<
91108 if ( ! patchToApply . hasOwnProperty ( key ) ) continue
92109 const subPatch = patchToApply [ key ]
93110 const targetValue = target [ key ]
111+
94112 if (
95113 isPlainObject ( targetValue ) &&
96114 isPlainObject ( subPatch ) &&
@@ -142,10 +160,47 @@ export function shouldHydrate(obj: any) {
142160const { assign } = Object
143161
144162function isComputed < T > ( value : ComputedRef < T > | unknown ) : value is ComputedRef < T >
163+ /**
164+ * Type guard that returns true when the provided value is a Vue ComputedRef.
165+ *
166+ * Determines this by checking that the value is a ref-like object and exposes the internal `effect` used by computed refs.
167+ *
168+ * @param o - Value to test
169+ * @returns `true` if `o` is a ComputedRef, otherwise `false`
170+ */
145171function isComputed ( o : any ) : o is ComputedRef {
146172 return ! ! ( isRef ( o ) && ( o as any ) . effect )
147173}
148174
175+ /**
176+ * Type guard that returns true if the given value is a Vue shallowRef.
177+ *
178+ * Detects shallow refs by checking that the value is a Ref and has the internal
179+ * `__v_isShallow` flag set. Useful for distinguishing shallow refs from normal
180+ * refs at runtime.
181+ *
182+ * @param value - Value to test
183+ * @returns `true` when `value` is a shallowRef
184+ */
185+ function isShallowRef ( value : any ) : value is Ref {
186+ return isRef ( value ) && ! ! ( value as any ) . __v_isShallow
187+ }
188+
189+ /**
190+ * Create an options-style store (defineStore with an options object) and register it with Pinia.
191+ *
192+ * Initializes the store's state in pinia.state when needed, exposes state properties as refs
193+ * tied to the shared pinia state, binds actions, and wraps getters as ComputedRefs that run
194+ * with the created store as their context.
195+ *
196+ * In development, warns when a getter name conflicts with an existing state property. When
197+ * `hot` is true, the function uses a temporary local shaping of state suitable for HMR.
198+ *
199+ * @param id - Unique store id
200+ * @param options - Store options containing `state`, `getters`, and `actions`
201+ * @param hot - If true, enable hot-module-replacement specific behavior for state setup
202+ * @returns The created Store instance
203+ */
149204function createOptionsStore <
150205 Id extends string ,
151206 S extends StateTree ,
@@ -213,6 +268,30 @@ function createOptionsStore<
213268 return store as any
214269}
215270
271+ /**
272+ * Create and register a setup-style store instance for the given id.
273+ *
274+ * Instantiates the store by running the provided `setup` function inside a scoped
275+ * reactive context, wires the resulting state/getters/actions into Pinia's
276+ * global state tree, and returns a reactive store object with the standard
277+ * store API (including `$patch`, `$reset` for option stores, `$subscribe`,
278+ * `$onAction`, and `$dispose`). Handles hydration from existing pinia state,
279+ * hot module replacement payloads, devtools integration, plugin extensions,
280+ * and special handling to trigger reactivity for `shallowRef` properties when
281+ * applying object patches.
282+ *
283+ * @param $id - Store unique id.
284+ * @param setup - Setup function that receives `action` helper and returns the store's
285+ * local properties (state refs/objects, getters as computed, and actions).
286+ * @param options - Optional store definition metadata (used for HMR, plugins, and for
287+ * option-store compatibility).
288+ * @param pinia - Pinia app instance (injected/omitted from param docs as a shared service).
289+ * @param hot - If true, create the store in hot-reload mode (preserves hotState and
290+ * avoids overwriting existing state).
291+ * @param isOptionsStore - When true, the store is treated like an options-style store
292+ * (affects `$reset` behavior and getter handling).
293+ * @returns The reactive Store instance corresponding to the created setup store.
294+ */
216295function createSetupStore <
217296 Id extends string ,
218297 SS extends Record < any , unknown > ,
@@ -284,6 +363,10 @@ function createSetupStore<
284363 // avoid triggering too many listeners
285364 // https://github.com/vuejs/pinia/issues/1129
286365 let activeListener : Symbol | undefined
366+
367+ // Store reference for shallowRef handling - will be set after setupStore creation
368+ let setupStoreRef : any = null
369+
287370 function $patch ( stateMutation : ( state : UnwrapRef < S > ) => void ) : void
288371 function $patch ( partialState : _DeepPartial < UnwrapRef < S > > ) : void
289372 function $patch (
@@ -307,6 +390,28 @@ function createSetupStore<
307390 }
308391 } else {
309392 mergeReactiveObjects ( pinia . state . value [ $id ] , partialStateOrMutator )
393+
394+ // Handle shallowRef reactivity: check if any patched properties are shallowRefs
395+ // and trigger their reactivity manually
396+ if ( setupStoreRef ) {
397+ const shallowRefsToTrigger : any [ ] = [ ]
398+ for ( const key in partialStateOrMutator ) {
399+ if ( partialStateOrMutator . hasOwnProperty ( key ) ) {
400+ // Check if the property in the setupStore is a shallowRef
401+ const setupStoreProperty = setupStoreRef [ key ]
402+ if (
403+ isShallowRef ( setupStoreProperty ) &&
404+ isPlainObject ( partialStateOrMutator [ key ] )
405+ ) {
406+ shallowRefsToTrigger . push ( setupStoreProperty )
407+ }
408+ }
409+ }
410+
411+ // Trigger reactivity for all shallowRefs that were patched
412+ shallowRefsToTrigger . forEach ( triggerRef )
413+ }
414+
310415 subscriptionMutation = {
311416 type : MutationType . patchObject ,
312417 payload : partialStateOrMutator ,
@@ -494,6 +599,9 @@ function createSetupStore<
494599 pinia . _e . run ( ( ) => ( scope = effectScope ( ) ) . run ( ( ) => setup ( { action } ) ) ! )
495600 ) !
496601
602+ // Set setupStore reference for shallowRef handling in $patch
603+ setupStoreRef = setupStore
604+
497605 // overwrite existing actions to support $onAction
498606 for ( const key in setupStore ) {
499607 const prop = setupStore [ key ]
0 commit comments