@@ -20,6 +20,7 @@ import {
20
20
Ref ,
21
21
ref ,
22
22
nextTick ,
23
+ triggerRef ,
23
24
} from 'vue'
24
25
import {
25
26
StateTree ,
@@ -75,6 +76,22 @@ interface MarkedAction<Fn extends _Method = _Method> {
75
76
[ ACTION_NAME ] : string
76
77
}
77
78
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
+ */
78
95
function mergeReactiveObjects <
79
96
T extends Record < any , unknown > | Map < unknown , unknown > | Set < unknown > ,
80
97
> ( target : T , patchToApply : _DeepPartial < T > ) : T {
@@ -91,6 +108,7 @@ function mergeReactiveObjects<
91
108
if ( ! patchToApply . hasOwnProperty ( key ) ) continue
92
109
const subPatch = patchToApply [ key ]
93
110
const targetValue = target [ key ]
111
+
94
112
if (
95
113
isPlainObject ( targetValue ) &&
96
114
isPlainObject ( subPatch ) &&
@@ -142,10 +160,47 @@ export function shouldHydrate(obj: any) {
142
160
const { assign } = Object
143
161
144
162
function 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
+ */
145
171
function isComputed ( o : any ) : o is ComputedRef {
146
172
return ! ! ( isRef ( o ) && ( o as any ) . effect )
147
173
}
148
174
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
+ */
149
204
function createOptionsStore <
150
205
Id extends string ,
151
206
S extends StateTree ,
@@ -213,6 +268,30 @@ function createOptionsStore<
213
268
return store as any
214
269
}
215
270
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
+ */
216
295
function createSetupStore <
217
296
Id extends string ,
218
297
SS extends Record < any , unknown > ,
@@ -284,6 +363,10 @@ function createSetupStore<
284
363
// avoid triggering too many listeners
285
364
// https://github.com/vuejs/pinia/issues/1129
286
365
let activeListener : Symbol | undefined
366
+
367
+ // Store reference for shallowRef handling - will be set after setupStore creation
368
+ let setupStoreRef : any = null
369
+
287
370
function $patch ( stateMutation : ( state : UnwrapRef < S > ) => void ) : void
288
371
function $patch ( partialState : _DeepPartial < UnwrapRef < S > > ) : void
289
372
function $patch (
@@ -307,6 +390,28 @@ function createSetupStore<
307
390
}
308
391
} else {
309
392
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
+
310
415
subscriptionMutation = {
311
416
type : MutationType . patchObject ,
312
417
payload : partialStateOrMutator ,
@@ -494,6 +599,9 @@ function createSetupStore<
494
599
pinia . _e . run ( ( ) => ( scope = effectScope ( ) ) . run ( ( ) => setup ( { action } ) ) ! )
495
600
) !
496
601
602
+ // Set setupStore reference for shallowRef handling in $patch
603
+ setupStoreRef = setupStore
604
+
497
605
// overwrite existing actions to support $onAction
498
606
for ( const key in setupStore ) {
499
607
const prop = setupStore [ key ]
0 commit comments