]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
fix(types): allow writable getters with storeToRefs
authorEduardo San Martin Morote <posva13@gmail.com>
Tue, 1 Oct 2024 09:15:33 +0000 (11:15 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Tue, 1 Oct 2024 09:15:33 +0000 (11:15 +0200)
Fix #2767

packages/pinia/__tests__/storeToRefs.spec.ts
packages/pinia/src/storeToRefs.ts
packages/pinia/src/types.ts
packages/pinia/test-dts/store.test-d.ts

index 6da2287b27ab808cdebcce4281d9e52875d3d26c..d7e6a5f2f6ceadb76cfb39c2587d0341d4af8740 100644 (file)
@@ -151,6 +151,25 @@ describe('storeToRefs', () => {
     ).toEqual(objectOfRefs({ n: 0, pluginN: 20 }))
   })
 
+  it('preserve setters in getters', () => {
+    const useStore = defineStore('main', () => {
+      const n = ref(0)
+      const double = computed({
+        get() {
+          return n.value * 2
+        },
+        set(value: string | number) {
+          n.value =
+            (typeof value === 'string' ? parseInt(value) || 0 : value) / 2
+        },
+      })
+      return { n, double }
+    })
+    const refs = storeToRefs(useStore())
+    refs.double.value = 4
+    expect(refs.n.value).toBe(2)
+  })
+
   tds(() => {
     const store1 = defineStore('a', () => {
       const n = ref(0)
index 63e9bf9cd0926b224d8052f636dd07c4def334eb..8d9cbe6ecfc9f475e5f9f5c3bbf9d44692e3b574 100644 (file)
@@ -3,12 +3,12 @@ import {
   isReactive,
   isRef,
   isVue2,
-  Ref,
   toRaw,
   ToRef,
   toRef,
   ToRefs,
   toRefs,
+  WritableComputedRef,
 } from 'vue-demi'
 import { StoreGetters, StoreState } from './store'
 import type {
@@ -21,8 +21,29 @@ import type {
   StoreGeneric,
 } from './types'
 
-type ToComputedRefs<T> = {
-  [K in keyof T]: ToRef<T[K]> extends Ref ? ComputedRef<T[K]> : ToRef<T[K]>
+/**
+ * Internal utility type
+ */
+type _IfEquals<X, Y, A = true, B = false> =
+  (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? A : B
+
+/**
+ * Internal utility type
+ */
+type _IsReadonly<T, K extends keyof T> = _IfEquals<
+  { [P in K]: T[P] },
+  { -readonly [P in K]: T[P] },
+  false, // Property is not readonly if they are the same
+  true // Property is readonly if they differ
+>
+
+/**
+ * Extracts the getters of a store while keeping writable and readonly properties. **Internal type DO NOT USE**.
+ */
+type _ToComputedRefs<SS> = {
+  [K in keyof SS]: true extends _IsReadonly<SS, K>
+    ? ComputedRef<SS[K]>
+    : WritableComputedRef<SS[K]>
 }
 
 /**
@@ -49,7 +70,7 @@ type _ToStateRefs<SS> =
  */
 export type StoreToRefs<SS extends StoreGeneric> = _ToStateRefs<SS> &
   ToRefs<PiniaCustomStateProperties<StoreState<SS>>> &
-  ToComputedRefs<StoreGetters<SS>>
+  _ToComputedRefs<StoreGetters<SS>>
 
 /**
  * Creates an object of references with all the state, getters, and plugin-added
index 55db69521b27804312cbbf41931eff5fca5436f6..e726415c4f2863f59f492fff3419c8c0b1611362 100644 (file)
@@ -727,3 +727,16 @@ export interface DefineStoreOptionsInPlugin<
    */
   actions: A
 }
+
+/**
+ * Utility type. For internal use **only**
+ */
+export interface _Empty {}
+
+/**
+ * Merges type objects for better readability in the code.
+ * Utility type. For internal use **only**
+ */
+export type _Simplify<T> = _Empty extends T
+  ? _Empty
+  : { [key in keyof T]: T[key] } & {}
index 0d796d064edbc17ce194b35e6e8bc2236e607d69..3d90a66abf44940c5b4e833aca6e7281af090252 100644 (file)
@@ -1,4 +1,10 @@
-import { StoreGeneric, acceptHMRUpdate, defineStore, expectType } from './'
+import {
+  StoreGeneric,
+  acceptHMRUpdate,
+  defineStore,
+  expectType,
+  storeToRefs,
+} from './'
 import { computed, ref, UnwrapRef, watch } from 'vue'
 
 const useStore = defineStore({
@@ -285,6 +291,7 @@ useSyncValueToStore(() => 2, genericStore, 'random')
 
 const writableComputedStore = defineStore('computed-writable', () => {
   const fruitsBasket = ref(['banana', 'apple', 'banana', 'orange'])
+  const total = computed(() => fruitsBasket.value.length)
   const bananasAmount = computed<number>({
     get: () => fruitsBasket.value.filter((fruit) => fruit === 'banana').length,
     set: (newAmount) => {
@@ -302,13 +309,22 @@ const writableComputedStore = defineStore('computed-writable', () => {
       )),
   })
   bananas.value = 'hello' // TS ok
-  return { fruitsBasket, bananas, bananasAmount }
+  return { fruitsBasket, bananas, bananasAmount, total }
 })()
 
 expectType<number>(writableComputedStore.bananasAmount)
 // should allow writing to it
 writableComputedStore.bananasAmount = 0
+// @ts-expect-error: this one is readonly
+writableComputedStore.total = 0
 expectType<string[]>(writableComputedStore.bananas)
 // should allow setting a different type
 // @ts-expect-error: still not doable
 writableComputedStore.bananas = 'hello'
+
+const refs = storeToRefs(writableComputedStore)
+expectType<string[]>(refs.bananas.value)
+expectType<number>(refs.bananasAmount.value)
+refs.bananasAmount.value = 0
+// @ts-expect-error: this one is readonly
+refs.total.value = 0