Skip to content

Commit c48bc01

Browse files
📝 Add docstrings to fix/shallow-ref-patch-reactivity
Docstrings generation was requested by @doubledare704. * #3041 (comment) The following files were modified: * `packages/pinia/src/store.ts`
1 parent 57bec95 commit c48bc01

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

packages/pinia/src/store.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
Ref,
2121
ref,
2222
nextTick,
23+
triggerRef,
2324
} from 'vue'
2425
import {
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+
*/
7895
function 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) {
142160
const { assign } = Object
143161

144162
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+
*/
145171
function 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+
*/
149204
function 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+
*/
216295
function 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

Comments
 (0)