From: Eduardo San Martin Morote Date: Tue, 1 Oct 2024 09:15:33 +0000 (+0200) Subject: fix(types): allow writable getters with storeToRefs X-Git-Tag: pinia@2.2.4~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b464a1f4ff499aff34087cc9cd77ad19cf8646a7;p=thirdparty%2Fvuejs%2Fpinia.git fix(types): allow writable getters with storeToRefs Fix #2767 --- diff --git a/packages/pinia/__tests__/storeToRefs.spec.ts b/packages/pinia/__tests__/storeToRefs.spec.ts index 6da2287b..d7e6a5f2 100644 --- a/packages/pinia/__tests__/storeToRefs.spec.ts +++ b/packages/pinia/__tests__/storeToRefs.spec.ts @@ -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) diff --git a/packages/pinia/src/storeToRefs.ts b/packages/pinia/src/storeToRefs.ts index 63e9bf9c..8d9cbe6e 100644 --- a/packages/pinia/src/storeToRefs.ts +++ b/packages/pinia/src/storeToRefs.ts @@ -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 = { - [K in keyof T]: ToRef extends Ref ? ComputedRef : ToRef +/** + * Internal utility type + */ +type _IfEquals = + (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B + +/** + * Internal utility type + */ +type _IsReadonly = _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 = { + [K in keyof SS]: true extends _IsReadonly + ? ComputedRef + : WritableComputedRef } /** @@ -49,7 +70,7 @@ type _ToStateRefs = */ export type StoreToRefs = _ToStateRefs & ToRefs>> & - ToComputedRefs> + _ToComputedRefs> /** * Creates an object of references with all the state, getters, and plugin-added diff --git a/packages/pinia/src/types.ts b/packages/pinia/src/types.ts index 55db6952..e726415c 100644 --- a/packages/pinia/src/types.ts +++ b/packages/pinia/src/types.ts @@ -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 = _Empty extends T + ? _Empty + : { [key in keyof T]: T[key] } & {} diff --git a/packages/pinia/test-dts/store.test-d.ts b/packages/pinia/test-dts/store.test-d.ts index 0d796d06..3d90a66a 100644 --- a/packages/pinia/test-dts/store.test-d.ts +++ b/packages/pinia/test-dts/store.test-d.ts @@ -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({ 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(writableComputedStore.bananasAmount) // should allow writing to it writableComputedStore.bananasAmount = 0 +// @ts-expect-error: this one is readonly +writableComputedStore.total = 0 expectType(writableComputedStore.bananas) // should allow setting a different type // @ts-expect-error: still not doable writableComputedStore.bananas = 'hello' + +const refs = storeToRefs(writableComputedStore) +expectType(refs.bananas.value) +expectType(refs.bananasAmount.value) +refs.bananasAmount.value = 0 +// @ts-expect-error: this one is readonly +refs.total.value = 0