From 9513d1a2eeb2e03bb76bc295aec7bc70fdece3a6 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Sat, 15 May 2021 20:22:55 +0200 Subject: [PATCH] fix(types): forbid non existants keys on store --- src/index.ts | 1 + src/mapHelpers.ts | 38 ++++++++++++------ src/store.ts | 20 +++++----- src/types.ts | 9 +++-- test-dts/customizations.test-d.ts | 4 +- test-dts/mapHelpers.test-d.ts | 2 +- test-dts/plugins.test-d.ts | 2 +- test-dts/store.test-d.ts | 64 ++++++++++++++++++++++++++++++- 8 files changed, 110 insertions(+), 30 deletions(-) diff --git a/src/index.ts b/src/index.ts index f5a9b2c6..14e9d4da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export type { GenericStore, StoreWithGetters, GettersTree, + ActionsTree, _Method, StoreWithActions, StoreWithState, diff --git a/src/mapHelpers.ts b/src/mapHelpers.ts index ee179a0d..c8f8bd35 100644 --- a/src/mapHelpers.ts +++ b/src/mapHelpers.ts @@ -135,7 +135,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 } /** @@ -153,7 +160,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 } @@ -263,6 +270,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 @@ -275,7 +283,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) @@ -291,14 +299,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]] } /** @@ -388,21 +396,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) } @@ -412,8 +424,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 } } @@ -425,8 +437,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 } } @@ -487,10 +499,12 @@ export function mapWritableState< ? keysOrMapper.reduce((reduced, key) => { 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) }, } @@ -499,10 +513,12 @@ export function mapWritableState< : Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => { 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 69fcdb55..b6570283 100644 --- a/src/store.ts +++ b/src/store.ts @@ -343,7 +343,7 @@ export function defineStore< Id extends string, S extends StateTree, G extends GettersTree, - A /* extends Record */ + A /* extends ActionsTree */ >(options: DefineStoreOptions): StoreDefinition { const { id, state, getters, actions } = options @@ -383,9 +383,9 @@ export function defineStore< storeAndDescriptor[0], storeAndDescriptor[1], id, - getters as GettersTree | undefined, - actions as ActionsTree | undefined, - // @ts-expect-error: because of the extend on Actions + getters, + // @ts-expect-error: all good + actions, options ) @@ -395,20 +395,20 @@ export function defineStore< provide(storeAndDescriptor[2], store) } - return store + return store as Store } return ( (hasInstance && inject(storeAndDescriptor[2], null)) || - buildStoreToUse( + (buildStoreToUse( storeAndDescriptor[0], storeAndDescriptor[1], id, - getters as GettersTree | undefined, - actions as ActionsTree | undefined, - // @ts-expect-error: because of the extend on Actions + getters, + // @ts-expect-error: all good + actions, options - ) + ) as Store) ) } diff --git a/src/types.ts b/src/types.ts index b17239c8..ee3a23c9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -198,7 +198,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. @@ -336,9 +337,9 @@ export type Store< // has the actions without the context (this) for typings A = ActionsTree > = StoreWithState & - UnwrapRef & - StoreWithGetters & - StoreWithActions & + (StateTree extends S ? {} : UnwrapRef) & + (GettersTree extends G ? {} : StoreWithGetters) & + (ActionsTree extends A ? {} : StoreWithActions) & PiniaCustomProperties & PiniaCustomStateProperties diff --git a/test-dts/customizations.test-d.ts b/test-dts/customizations.test-d.ts index 30afff13..30930313 100644 --- a/test-dts/customizations.test-d.ts +++ b/test-dts/customizations.test-d.ts @@ -1,4 +1,4 @@ -import { expectType, createPinia, defineStore, mapStores } from '.' +import { expectType, createPinia, defineStore, mapStores } from './' declare module '../dist/src' { export interface MapStoresCustomization { @@ -37,7 +37,6 @@ pinia.use((context) => { const useStore = defineStore({ id: 'main', - state: () => ({}), actions: { one() {}, two() { @@ -73,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 ecd3ccc6..3a2ce2fa 100644 --- a/test-dts/plugins.test-d.ts +++ b/test-dts/plugins.test-d.ts @@ -5,7 +5,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 -- 2.47.3