From: Eduardo San Martin Morote Date: Sat, 15 May 2021 18:22:55 +0000 (+0200) Subject: fix(types): forbid non existants keys on store X-Git-Tag: v2.0.0-alpha.17~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e747cba7d90ebd9ef29a379d441744aecc31db80;p=thirdparty%2Fvuejs%2Fpinia.git fix(types): forbid non existants keys on store --- diff --git a/src/devtools/formatting.ts b/src/devtools/formatting.ts index 54bba476..d8196675 100644 --- a/src/devtools/formatting.ts +++ b/src/devtools/formatting.ts @@ -32,6 +32,7 @@ export function formatStoreForInspectorState( editable: false, key: 'getters', value: store._getters.reduce((getters, key) => { + // @ts-expect-error getters[key] = store[key] return getters }, {} as GettersTree), diff --git a/src/index.ts b/src/index.ts index 7c577382..8f1e5702 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ export type { StoreDefinition, StoreWithGetters, GettersTree, + ActionsTree, _Method, StoreWithActions, StoreWithState, diff --git a/src/mapHelpers.ts b/src/mapHelpers.ts index 419f4a9a..00b8c900 100644 --- a/src/mapHelpers.ts +++ b/src/mapHelpers.ts @@ -139,7 +139,14 @@ export function mapStores( * @internal */ export type _MapStateReturn> = { - [key in keyof S | keyof G]: () => Store[key] + // [key in keyof S | keyof G]: () => key extends keyof S + // ? S[key] + // : key extends keyof G + // ? G[key] + // : never + [key in keyof S | keyof G]: () => key extends keyof Store + ? Store[key] + : never } /** @@ -157,7 +164,7 @@ export type _MapStateObjectReturn< > = { [key in keyof T]: () => T[key] extends (store: Store) => infer R ? R - : T[key] extends keyof S | keyof G + : T[key] extends keyof Store ? Store[T[key]] : never } @@ -267,6 +274,7 @@ export function mapState< return Array.isArray(keysOrMapper) ? keysOrMapper.reduce((reduced, key) => { reduced[key] = function (this: ComponentPublicInstance) { + // @ts-expect-error return getCachedStore(this, useStore)[key] } as () => any return reduced @@ -279,7 +287,7 @@ export function mapState< // function return typeof storeKey === 'function' ? (storeKey as (store: Store) => any).call(this, store) - : store[storeKey as keyof S | keyof G] + : store[storeKey as keyof typeof store] } return reduced }, {} as _MapStateObjectReturn) @@ -295,14 +303,14 @@ export const mapGetters = mapState * @internal */ export type _MapActionsReturn = { - [key in keyof A]: Store[key] + [key in keyof A]: A[key] } /** * @internal */ export type _MapActionsObjectReturn> = { - [key in keyof T]: Store[T[key]] + [key in keyof T]: A[T[key]] } /** @@ -392,21 +400,25 @@ export function mapActions< ): _MapActionsReturn | _MapActionsObjectReturn { return Array.isArray(keysOrMapper) ? keysOrMapper.reduce((reduced, key) => { + // @ts-expect-error reduced[key] = function ( this: ComponentPublicInstance, ...args: any[] ) { + // @ts-expect-error return (getCachedStore(this, useStore)[key] as _Method)(...args) - } as Store[keyof A] + } return reduced }, {} as _MapActionsReturn) : Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => { + // @ts-expect-error reduced[key] = function ( this: ComponentPublicInstance, ...args: any[] ) { + // @ts-expect-error return getCachedStore(this, useStore)[keysOrMapper[key]](...args) - } as Store[keyof KeyMapper[]] + } return reduced }, {} as _MapActionsObjectReturn) } @@ -416,8 +428,8 @@ export function mapActions< */ export type _MapWritableStateReturn = { [key in keyof S]: { - get: () => Store[key] - set: (value: Store[key]) => any + get: () => S[key] + set: (value: S[key]) => any } } @@ -429,8 +441,8 @@ export type _MapWritableStateObjectReturn< T extends Record > = { [key in keyof T]: { - get: () => Store[T[key]] - set: (value: Store[T[key]]) => any + get: () => S[T[key]] + set: (value: S[T[key]]) => any } } @@ -492,10 +504,12 @@ export function mapWritableState< // @ts-ignore reduced[key] = { get(this: ComponentPublicInstance) { + // @ts-expect-error return getCachedStore(this, useStore)[key] }, set(this: ComponentPublicInstance, value) { // it's easier to type it here as any + // @ts-expect-error return (getCachedStore(this, useStore)[key] = value as any) }, } @@ -505,10 +519,12 @@ export function mapWritableState< // @ts-ignore reduced[key] = { get(this: ComponentPublicInstance) { + // @ts-expect-error return getCachedStore(this, useStore)[keysOrMapper[key]] }, set(this: ComponentPublicInstance, value) { // it's easier to type it here as any + // @ts-expect-error return (getCachedStore(this, useStore)[keysOrMapper[key]] = value as any) }, diff --git a/src/store.ts b/src/store.ts index 1f459b89..3570b1bc 100644 --- a/src/store.ts +++ b/src/store.ts @@ -425,8 +425,8 @@ export function defineStore< storeAndDescriptor[0], storeAndDescriptor[1], id, - getters as GettersTree | undefined, - actions as A | undefined, + getters, + actions, options ) @@ -437,20 +437,19 @@ export function defineStore< } } else { store = - // null avoids the warning for not found injection key (currentInstance && inject(storeAndDescriptor[2], null)) || buildStoreToUse< Id, S, G, - // @ts-expect-error: A without extends + // @ts-expect-error: cannot extends ActionsTree A >( storeAndDescriptor[0], storeAndDescriptor[1], id, - getters as GettersTree | undefined, - actions as A | undefined, + getters, + actions, options ) } diff --git a/src/types.ts b/src/types.ts index b83fa2c3..4fec98dc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -231,7 +231,8 @@ export interface StoreWithState< /** * State of the Store. Setting it will replace the whole state. */ - $state: UnwrapRef & PiniaCustomStateProperties + $state: (StateTree extends S ? {} : UnwrapRef) & + PiniaCustomStateProperties /** * Private property defining the pinia the store is attached to. @@ -377,12 +378,29 @@ export type Store< // has the actions without the context (this) for typings A /* extends ActionsTree */ = ActionsTree > = StoreWithState & - UnwrapRef & - StoreWithGetters & - StoreWithActions & + (StateTree extends S ? {} : UnwrapRef) & + (GettersTree extends G ? {} : StoreWithGetters) & + (ActionsTree extends A ? {} : StoreWithActions) & PiniaCustomProperties & PiniaCustomStateProperties +/** + * Generic version of Store. Doesn't fail on access with strings + */ +export type GenericStore = StoreWithState< + string, + StateTree, + GettersTree, + ActionsTree +> & + PiniaCustomProperties< + string, + StateTree, + GettersTree, + ActionsTree + > & + PiniaCustomStateProperties + /** * Return type of `defineStore()`. Function that allows instantiating a store. */ @@ -405,12 +423,6 @@ export interface StoreDefinition< $id: Id } -/** - * Generic version of Store. - * @deprecated Use Store instead - */ -export type GenericStore = Store - /** * Properties that are added to every store by `pinia.use()` */ diff --git a/test-dts/customizations.test-d.ts b/test-dts/customizations.test-d.ts index e37c4972..0d4407da 100644 --- a/test-dts/customizations.test-d.ts +++ b/test-dts/customizations.test-d.ts @@ -1,6 +1,5 @@ -import { mapStores } from 'dist/pinia' +import { expectType, createPinia, defineStore, mapStores } from './' import { App } from 'vue' -import { expectType, createPinia, defineStore } from '.' declare module '../dist/pinia' { export interface MapStoresCustomization { @@ -40,7 +39,6 @@ pinia.use((context) => { const useStore = defineStore({ id: 'main', - state: () => ({}), actions: { one() {}, two() { @@ -74,6 +72,7 @@ pinia.use(({ options, store }) => { if (options.debounce) { return Object.keys(options.debounce).reduce((debouncedActions, action) => { debouncedActions[action] = debounce( + // @ts-expect-error: cannot be inferred store[action], options.debounce![action as keyof typeof options['actions']] ) diff --git a/test-dts/mapHelpers.test-d.ts b/test-dts/mapHelpers.test-d.ts index 72dbd803..9fc76609 100644 --- a/test-dts/mapHelpers.test-d.ts +++ b/test-dts/mapHelpers.test-d.ts @@ -5,7 +5,7 @@ import { mapActions, mapState, mapWritableState, -} from '.' +} from './' const useStore = defineStore({ id: 'name', diff --git a/test-dts/plugins.test-d.ts b/test-dts/plugins.test-d.ts index efb6d226..98e4bb19 100644 --- a/test-dts/plugins.test-d.ts +++ b/test-dts/plugins.test-d.ts @@ -6,7 +6,7 @@ import { Pinia, StateTree, DefineStoreOptions, -} from '.' +} from './' const pinia = createPinia() diff --git a/test-dts/store.test-d.ts b/test-dts/store.test-d.ts index e3174984..47bc00ee 100644 --- a/test-dts/store.test-d.ts +++ b/test-dts/store.test-d.ts @@ -33,7 +33,7 @@ const useStore = defineStore({ }, }) -let store = useStore() +const store = useStore() expectType<{ a: 'on' | 'off' }>(store.$state) expectType(store.nested.counter) @@ -56,3 +56,65 @@ store.$patch(() => { // return earlier return }) + +const useNoSAG = defineStore({ + id: 'noSAG', +}) +const useNoAG = defineStore({ + id: 'noAG', + state: () => ({}), +}) +const useNoSG = defineStore({ + id: 'noAG', + actions: {}, +}) +const useNoSA = defineStore({ + id: 'noAG', + getters: {}, +}) +const useNoS = defineStore({ + id: 'noAG', + actions: {}, + getters: {}, +}) +const useNoA = defineStore({ + id: 'noAG', + state: () => ({}), + getters: {}, +}) +const useNoG = defineStore({ + id: 'noAG', + state: () => ({}), + actions: {}, +}) + +const noSAG = useNoSAG() +const noSA = useNoSA() +const noAG = useNoAG() +const noSG = useNoSG() +const noS = useNoS() +const noA = useNoA() +const noG = useNoG() + +// @ts-expect-error +store.notExisting + +// @ts-expect-error +noSAG.notExisting +// @ts-expect-error +noSAG.$state.hey + +// @ts-expect-error +noSA.notExisting +// @ts-expect-error +noSA.notExisting +// @ts-expect-error +noAG.notExisting +// @ts-expect-error +noSG.notExisting +// @ts-expect-error +noS.notExisting +// @ts-expect-error +noA.notExisting +// @ts-expect-error +noG.notExisting