]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
fix(types): forbid non existants keys on store
authorEduardo San Martin Morote <posva13@gmail.com>
Sat, 15 May 2021 18:22:55 +0000 (20:22 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Sat, 15 May 2021 18:22:55 +0000 (20:22 +0200)
src/index.ts
src/mapHelpers.ts
src/store.ts
src/types.ts
test-dts/customizations.test-d.ts
test-dts/mapHelpers.test-d.ts
test-dts/plugins.test-d.ts
test-dts/store.test-d.ts

index f5a9b2c6590e2a2d0a32b9c4bd8e918461aa87ea..14e9d4da46c8f8c92e8f03b52fae5c17e4000d28 100644 (file)
@@ -10,6 +10,7 @@ export type {
   GenericStore,
   StoreWithGetters,
   GettersTree,
+  ActionsTree,
   _Method,
   StoreWithActions,
   StoreWithState,
index ee179a0df1fa0935d63609aff0e0178df6796a8c..c8f8bd358459f09f280478af77ddb3f6a3ef6274 100644 (file)
@@ -135,7 +135,14 @@ export function mapStores<Stores extends any[]>(
  * @internal
  */
 export type _MapStateReturn<S extends StateTree, G extends GettersTree<S>> = {
-  [key in keyof S | keyof G]: () => Store<string, S, G, {}>[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<string, S, G, {}>
+    ? Store<string, S, G, {}>[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<Id, S, G, A>
     ? Store<Id, S, G, A>[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<Id, S, G, A>) => any).call(this, store)
-            : store[storeKey as keyof S | keyof G]
+            : store[storeKey as keyof typeof store]
         }
         return reduced
       }, {} as _MapStateObjectReturn<Id, S, G, A, KeyMapper>)
@@ -291,14 +299,14 @@ export const mapGetters = mapState
  * @internal
  */
 export type _MapActionsReturn<A> = {
-  [key in keyof A]: Store<string, StateTree, {}, A>[key]
+  [key in keyof A]: A[key]
 }
 
 /**
  * @internal
  */
 export type _MapActionsObjectReturn<A, T extends Record<string, keyof A>> = {
-  [key in keyof T]: Store<string, StateTree, {}, A>[T[key]]
+  [key in keyof T]: A[T[key]]
 }
 
 /**
@@ -388,21 +396,25 @@ export function mapActions<
 ): _MapActionsReturn<A> | _MapActionsObjectReturn<A, KeyMapper> {
   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<string, StateTree, {}, A>[keyof A]
+        }
         return reduced
       }, {} as _MapActionsReturn<A>)
     : 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<string, StateTree, {}, A>[keyof KeyMapper[]]
+        }
         return reduced
       }, {} as _MapActionsObjectReturn<A, KeyMapper>)
 }
@@ -412,8 +424,8 @@ export function mapActions<
  */
 export type _MapWritableStateReturn<S extends StateTree> = {
   [key in keyof S]: {
-    get: () => Store<string, S, {}, {}>[key]
-    set: (value: Store<string, S, {}, {}>[key]) => any
+    get: () => S[key]
+    set: (value: S[key]) => any
   }
 }
 
@@ -425,8 +437,8 @@ export type _MapWritableStateObjectReturn<
   T extends Record<string, keyof S>
 > = {
   [key in keyof T]: {
-    get: () => Store<string, S, {}, {}>[T[key]]
-    set: (value: Store<string, S, {}, {}>[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)
           },
index 69fcdb55303c3c2415c066d4876eff1e614da3e4..b6570283fd0ba3d2795e2f64616a4b9f3dd1a506 100644 (file)
@@ -343,7 +343,7 @@ export function defineStore<
   Id extends string,
   S extends StateTree,
   G extends GettersTree<S>,
-  A /* extends Record<string, StoreAction> */
+  A /* extends ActionsTree */
 >(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A> {
   const { id, state, getters, actions } = options
 
@@ -383,9 +383,9 @@ export function defineStore<
         storeAndDescriptor[0],
         storeAndDescriptor[1],
         id,
-        getters as GettersTree<S> | 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<Id, S, G, A>
     }
 
     return (
       (hasInstance && inject(storeAndDescriptor[2], null)) ||
-      buildStoreToUse(
+      (buildStoreToUse(
         storeAndDescriptor[0],
         storeAndDescriptor[1],
         id,
-        getters as GettersTree<S> | undefined,
-        actions as ActionsTree | undefined,
-        // @ts-expect-error: because of the extend on Actions
+        getters,
+        // @ts-expect-error: all good
+        actions,
         options
-      )
+      ) as Store<Id, S, G, A>)
     )
   }
 
index b17239c8acb021cd664e2359a0ca95d9b93d97a8..ee3a23c9744e99b38a56803985c5026c0b729e2c 100644 (file)
@@ -198,7 +198,8 @@ export interface StoreWithState<Id extends string, S extends StateTree> {
   /**
    * State of the Store. Setting it will replace the whole state.
    */
-  $state: UnwrapRef<S> & PiniaCustomStateProperties<S>
+  $state: (StateTree extends S ? {} : UnwrapRef<S>) &
+    PiniaCustomStateProperties<S>
 
   /**
    * 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<Id, S> &
-  UnwrapRef<S> &
-  StoreWithGetters<G> &
-  StoreWithActions<A> &
+  (StateTree extends S ? {} : UnwrapRef<S>) &
+  (GettersTree<S> extends G ? {} : StoreWithGetters<G>) &
+  (ActionsTree extends A ? {} : StoreWithActions<A>) &
   PiniaCustomProperties<Id, S, G, A> &
   PiniaCustomStateProperties<S>
 
index 30afff13c77f395c50302d08d7177b1a48102397..3093031310d5cb7452f42c53d89e6e4032f24187 100644 (file)
@@ -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']]
       )
index 72dbd8039798c4c5fd9661722668bd740a518743..9fc766096c24699208e7065d5202975befdd3992 100644 (file)
@@ -5,7 +5,7 @@ import {
   mapActions,
   mapState,
   mapWritableState,
-} from '.'
+} from './'
 
 const useStore = defineStore({
   id: 'name',
index ecd3ccc627ba010483e13017348b6b082942dfab..3a2ce2fad30eb073e53fcdbf6747bf5fb1248603 100644 (file)
@@ -5,7 +5,7 @@ import {
   Pinia,
   StateTree,
   DefineStoreOptions,
-} from '.'
+} from './'
 
 const pinia = createPinia()
 
index e31749846cefb6bcd011d509ce45fda634e6c4fd..47bc00ee8284166846c30f9894f7c94b9a39787a 100644 (file)
@@ -33,7 +33,7 @@ const useStore = defineStore({
   },
 })
 
-let store = useStore()
+const store = useStore()
 
 expectType<{ a: 'on' | 'off' }>(store.$state)
 expectType<number>(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