]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat: allow strict option feat/strict-mode 493/head
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 12 May 2021 16:44:12 +0000 (18:44 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 12 May 2021 16:44:12 +0000 (18:44 +0200)
Close #58

__tests__/strictMode.spec.ts [new file with mode: 0644]
src/rootStore.ts
src/store.ts
src/types.ts
test-dts/customizations.test-d.ts
test-dts/plugins.test-d.ts

diff --git a/__tests__/strictMode.spec.ts b/__tests__/strictMode.spec.ts
new file mode 100644 (file)
index 0000000..ec3395d
--- /dev/null
@@ -0,0 +1,31 @@
+import { createPinia, defineStore, setActivePinia } from '../src'
+
+describe('Strict mode', () => {
+  const useStore = defineStore({
+    id: 'main',
+    strict: true,
+    state: () => ({
+      a: true,
+      nested: {
+        foo: 'foo',
+        a: { b: 'string' },
+      },
+    }),
+  })
+
+  it('cannot change the state directly', () => {
+    setActivePinia(createPinia())
+    const store = useStore()
+    // @ts-expect-error
+    store.a = false
+    // @ts-expect-error
+    store.nested.foo = 'bar'
+
+    // TODO: should direct $state be allowed?
+    // this could be an escape hatch if we want one
+    store.$state.a = false
+
+    store.$patch({ a: false })
+    store.$patch({ nested: { foo: 'bar' } })
+  })
+})
index e991852bbe7bcee00a8af13d80cfa0d4edd54ff9..c9aef4d946e81f7a5e12461bf73ef6c1bfcc98b2 100644 (file)
@@ -120,7 +120,8 @@ export interface PiniaPluginContext<
   Id extends string = string,
   S extends StateTree = StateTree,
   G extends GettersTree<S> = GettersTree<S>,
-  A /* extends ActionsTree */ = ActionsTree
+  A /* extends ActionsTree */ = ActionsTree,
+  Strict extends boolean = false
 > {
   /**
    * pinia instance.
@@ -140,7 +141,7 @@ export interface PiniaPluginContext<
   /**
    * Current store being extended.
    */
-  options: DefineStoreOptions<Id, S, G, A>
+  options: DefineStoreOptions<Id, S, G, A, Strict>
 }
 
 /**
index f927028fbec8c13d0c36f9130dfabf1a6d4ab6a3..f65ab1b663e00665dbd5be7acf2a4673e8dcca7c 100644 (file)
@@ -276,14 +276,15 @@ function buildStoreToUse<
   Id extends string,
   S extends StateTree,
   G extends GettersTree<S>,
-  A extends ActionsTree
+  A extends ActionsTree,
+  Strict extends boolean
 >(
   partialStore: StoreWithState<Id, S, G, A>,
   descriptor: StateDescriptor<S>,
   $id: Id,
   getters: G = {} as G,
   actions: A = {} as A,
-  options: DefineStoreOptions<Id, S, G, A>
+  options: DefineStoreOptions<Id, S, G, A, Strict>
 ) {
   const pinia = getActivePinia()
 
@@ -337,7 +338,7 @@ function buildStoreToUse<
     } as StoreWithActions<A>[typeof actionName]
   }
 
-  const store: Store<Id, S, G, A> = reactive(
+  const store: Store<Id, S, G, A, Strict> = reactive(
     assign(
       {},
       partialStore,
@@ -346,7 +347,7 @@ function buildStoreToUse<
       computedGetters,
       wrappedActions
     )
-  ) as Store<Id, S, G, A>
+  ) as Store<Id, S, G, A, Strict>
 
   // use this instead of a computed with setter to be able to create it anywhere
   // without linking the computed lifespan to wherever the store is first
@@ -375,11 +376,14 @@ export function defineStore<
   S extends StateTree,
   G extends GettersTree<S>,
   // cannot extends ActionsTree because we loose the typings
-  A /* extends ActionsTree */
->(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A> {
+  A /* extends ActionsTree */,
+  Strict extends boolean
+>(
+  options: DefineStoreOptions<Id, S, G, A, Strict>
+): StoreDefinition<Id, S, G, A, Strict> {
   const { id, state, getters, actions } = options
 
-  function useStore(pinia?: Pinia | null): Store<Id, S, G, A> {
+  function useStore(pinia?: Pinia | null): Store<Id, S, G, A, Strict> {
     const hasInstance = getCurrentInstance()
     // only run provide when pinia hasn't been manually passed
     const shouldProvide = hasInstance && !pinia
@@ -395,7 +399,7 @@ export function defineStore<
       | [
           StoreWithState<Id, S, G, A>,
           StateDescriptor<S>,
-          InjectionKey<Store<Id, S, G, A>>
+          InjectionKey<Store<Id, S, G, A, Strict>>
         ]
       | undefined
     if (!storeAndDescriptor) {
@@ -409,7 +413,8 @@ export function defineStore<
         S,
         G,
         // @ts-expect-error: A without extends
-        A
+        A,
+        Strict
       >(
         storeAndDescriptor[0],
         storeAndDescriptor[1],
@@ -436,7 +441,8 @@ export function defineStore<
         S,
         G,
         // @ts-expect-error: A without extends
-        A
+        A,
+        Strict
       >(
         storeAndDescriptor[0],
         storeAndDescriptor[1],
index 8aaed3616f059b1e58574caee9d0abcddb689cfa..bec604852f667ece55b45514fafbd17857092bf2 100644 (file)
@@ -26,8 +26,15 @@ export function isPlainObject(
   )
 }
 
+/**
+ * @internal
+ */
 export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
-// type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
+
+/**
+ * @internal
+ */
+export type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
 
 /**
  * Possible types for SubscriptionCallback
@@ -142,6 +149,7 @@ export interface StoreWithState<
   S extends StateTree,
   G extends GettersTree<S> = GettersTree<S>,
   A /* extends ActionsTree */ = ActionsTree
+  // Strict extends boolean = false
 > {
   /**
    * Unique identifier of the store
@@ -300,9 +308,10 @@ export type Store<
   S extends StateTree = StateTree,
   G extends GettersTree<S> = GettersTree<S>,
   // has the actions without the context (this) for typings
-  A /* extends ActionsTree */ = ActionsTree
+  A /* extends ActionsTree */ = ActionsTree,
+  Strict extends boolean = false
 > = StoreWithState<Id, S, G, A> &
-  UnwrapRef<S> &
+  (false extends Strict ? UnwrapRef<S> : DeepReadonly<UnwrapRef<S>>) &
   StoreWithGetters<G> &
   StoreWithActions<A> &
   PiniaCustomProperties<Id, S, G, A>
@@ -314,14 +323,15 @@ export interface StoreDefinition<
   Id extends string = string,
   S extends StateTree = StateTree,
   G extends GettersTree<S> = GettersTree<S>,
-  A /* extends ActionsTree */ = ActionsTree
+  A /* extends ActionsTree */ = ActionsTree,
+  Strict extends boolean = false
 > {
   /**
    * Returns a store, creates it if necessary.
    *
    * @param pinia - Pinia instance to retrieve the store
    */
-  (pinia?: Pinia | null | undefined): Store<Id, S, G, A>
+  (pinia?: Pinia | null | undefined): Store<Id, S, G, A, Strict>
 
   /**
    * Id of the store. Used by map helpers.
@@ -342,7 +352,8 @@ export interface PiniaCustomProperties<
   Id extends string = string,
   S extends StateTree = StateTree,
   G extends GettersTree<S> = GettersTree<S>,
-  A /* extends ActionsTree */ = ActionsTree
+  A /* extends ActionsTree */ = ActionsTree,
+  Strict extends boolean = false
 > {}
 
 /**
@@ -370,21 +381,29 @@ export interface DefineStoreOptions<
   Id extends string,
   S extends StateTree,
   G extends GettersTree<S>,
-  A /* extends Record<string, StoreAction> */
+  A /* extends Record<string, StoreAction> */,
+  Strict extends boolean
 > {
   /**
    * Unique string key to identify the store across the application.
    */
   id: Id
+
+  strict?: Strict
+
   /**
    * Function to create a fresh state.
    */
   state?: () => S
+
   /**
    * Optional object of getters.
    */
   getters?: G &
-    ThisType<UnwrapRef<S> & StoreWithGetters<G> & PiniaCustomProperties>
+    ThisType<
+      DeepReadonly<UnwrapRef<S>> & StoreWithGetters<G> & PiniaCustomProperties
+    >
+
   /**
    * Optional object of actions.
    */
index c4aaf6d4322b32fb0e840f79c7e27592e33011a9..34c7432229bbf5a73542afe2f54589faa6abcced 100644 (file)
@@ -7,11 +7,11 @@ declare module '../dist/pinia' {
     suffix: 'Store'
   }
 
-  export interface PiniaCustomProperties<Id, S, G, A> {
+  export interface PiniaCustomProperties<Id, S, G, A, Strict> {
     $actions: Array<keyof A>
   }
 
-  export interface DefineStoreOptions<Id, S, G, A> {
+  export interface DefineStoreOptions<Id, S, G, A, Strict> {
     debounce?: {
       // Record<keyof A, number>
       [k in keyof A]?: number
index efb6d226b6e3b639d9290660e58f3e0fb4d77ec4..eaf966190718be7a76e91bb9cfc175c481faf7c8 100644 (file)
@@ -3,6 +3,7 @@ import {
   expectType,
   createPinia,
   GenericStore,
+  Store,
   Pinia,
   StateTree,
   DefineStoreOptions,
@@ -12,6 +13,7 @@ const pinia = createPinia()
 
 pinia.use(({ store, app, options, pinia }) => {
   expectType<GenericStore>(store)
+  expectType<Store>(store)
   expectType<Pinia>(pinia)
   expectType<App>(app)
   expectType<
@@ -19,7 +21,8 @@ pinia.use(({ store, app, options, pinia }) => {
       string,
       StateTree,
       Record<string, any>,
-      Record<string, any>
+      Record<string, any>,
+      boolean
     >
   >(options)
 })