From eaf93c45feb08b2f11c2514e972d49e89f6bbbec Mon Sep 17 00:00:00 2001
From: Glandos <bugs-github@antipoul.fr>
Date: Fri, 26 Jun 2020 17:24:24 +0200
Subject: [PATCH 1/6] convert test to use new API

---
 test/create-proxy.spec.ts | 35 ++++++++++++++++-------------------
 1 file changed, 16 insertions(+), 19 deletions(-)

diff --git a/test/create-proxy.spec.ts b/test/create-proxy.spec.ts
index ee023fe..f2fa77f 100644
--- a/test/create-proxy.spec.ts
+++ b/test/create-proxy.spec.ts
@@ -2,7 +2,7 @@
 import Vuex, {Store} from 'vuex'
 // @ts-ignore
 import { createLocalVue } from '@vue/test-utils'
-import { Module, VuexModule, getter, mutation, action, getRawActionContext } from '../src'
+import { getter, mutation, action, getRawActionContext, createModule, createProxy, clearProxyCache, createSubModule, extractVuexModule } from '../src'
 
 
 interface Name {
@@ -10,8 +10,7 @@ interface Name {
 	lastname:string
 }
 
-@Module({ namespacedPath: 'user/settings/' })
-class UserSettings extends VuexModule {
+class UserSettings extends createModule({ namespaced: 'user/settings/' }) {
 	@getter cookieConsent = false
 
 	@mutation changeConsent(consent: boolean) {
@@ -19,13 +18,11 @@ class UserSettings extends VuexModule {
 	}
 }
 
-@Module({ namespacedPath: 'user/something/'})
-class Something extends VuexModule {
+class Something extends createModule({ namespaced: 'user/something/' }) {
 	something = 'nothing'
 }
 
-@Module({ namespacedPath: 'books/' })
-class Books extends VuexModule{
+class Books extends createModule({ namespaced: 'books/' }) {
 	books: string[] = []
 
 	@mutation addBook(book: string) {
@@ -33,11 +30,10 @@ class Books extends VuexModule{
 	}
 }
 
-@Module({ namespacedPath: 'user/' })
-class UserStore extends VuexModule {
+class UserStore extends createModule({ namespaced: 'user/' })  {
 
-	settings = UserSettings.CreateSubModule(UserSettings)
-	something = Something.CreateSubModule(Something)
+	settings = createSubModule(UserSettings)
+	something = createSubModule(Something)
 
 	firstname = 'Michael'
 	lastname = 'Olofinjana'
@@ -67,7 +63,7 @@ class UserStore extends VuexModule {
 	}
 
 	@action async addBook(book: string) {
-		const booksProxy = Books.CreateProxy(this.$store, Books)
+		const booksProxy = createProxy(this.$store, Books)
 		booksProxy.addBook(book)
 	}
 
@@ -96,17 +92,18 @@ describe('CreateProxy', () => {
 		localVue.use(Vuex)
 		store = new Store({
 			modules: {
-				user: UserStore.ExtractVuexModule(UserStore)
+				...extractVuexModule(UserStore)
 			}
 		})
+		// console.log('New store with UserStore', store.getters)
 	})
 
 	afterEach(() => {
-		UserStore.ClearProxyCache(UserStore)
+		clearProxyCache(UserStore)
 	})
 
 	it('should proxy getters', () => {
-		const user = UserStore.CreateProxy(store, UserStore);
+		const user = createProxy(store, UserStore);
 
 		expect(user.fullName).toEqual('Michael Olofinjana')
 		expect(user.specialty).toEqual('JavaScript')
@@ -114,7 +111,7 @@ describe('CreateProxy', () => {
 	})
 
 	it('should proxy state', () => {
-		const user = UserStore.CreateProxy(store, UserStore)
+		const user = createProxy(store, UserStore)
 
 		expect(user.firstname).toEqual('Michael')
 		expect(user.lastname).toEqual('Olofinjana')
@@ -122,7 +119,7 @@ describe('CreateProxy', () => {
 
 	it('should proxy actions', async () => {
 
-		const user = UserStore.CreateProxy(store, UserStore)
+		const user = createProxy(store, UserStore)
 
 		await user.doAnotherAsyncStuff('Something')
 
@@ -141,7 +138,7 @@ describe('CreateProxy', () => {
 	})
 
 	it('should proxy mutations', async () => {
-		const user = UserStore.CreateProxy(store, UserStore)
+		const user = createProxy(store, UserStore)
 
 		await user.changeName({ firstname: 'Ola', lastname: 'Nordmann' })
 
@@ -151,4 +148,4 @@ describe('CreateProxy', () => {
 		expect(user.lastname).toEqual('Nordmann')
 	})
 
-})
+})
\ No newline at end of file

From 15d0a1cc98845a9be6ea1511f81cffe194230b2e Mon Sep 17 00:00:00 2001
From: Glandos <bugs-github@antipoul.fr>
Date: Fri, 26 Jun 2020 17:25:13 +0200
Subject: [PATCH 2/6] fix getters to work with unit tests

---
 src/proxy.ts | 62 ++++++++++++++++++++++++++++------------------------
 1 file changed, 34 insertions(+), 28 deletions(-)

diff --git a/src/proxy.ts b/src/proxy.ts
index e9d65be..04f10a4 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -305,18 +305,25 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam
 
     if ( maxDepth === 0 || typeof value !== "object" || (typeof value === 'object' && !fieldIsSubmodule) ) {
 
+      const getter = () => { 
+        // When creating local proxies getters doesn't exist on that context, so we have to account
+        // for that.
+        let getters = namespacedPath ? $store.rootGetters : $store.getters
+        if (getters === undefined) {
+          if ($store.getters === undefined) {
+            namespacedPath = ""
+            getters = $store
+          } else {
+            getters = $store.getters
+          }
+        }
+        return getters[ `${namespacedPath}__${className}_internal_getter__` ]( path )
+      }
+
       if( !strict || fieldIsSubmodule ) {
         
         Object.defineProperty(proxy, field, {
-          get: () => { 
-            // When creating local proxies getters doesn't exist on that context, so we have to account
-            // for that.
-            const getters = cls.prototype.__namespacedPath__ ? $store.rootGetters : $store.getters;
-            if( getters ) {
-              const getterPath = refineNamespacedPath(cls.prototype.__namespacedPath__) + `__${className}_internal_getter__`;
-              return getters[ getterPath ]( path )
-            }else return $store[ `__${className}_internal_getter__` ]( path ) 
-          },
+          get: getter,
           set: payload => { 
             const commit = $store.commit || cls.prototype.__store_cache__.commit;
             if( commit ) commit( refineNamespacedPath( cls.prototype.__namespacedPath__ ) + `__${className}_internal_mutator__`, { field: path, payload }, { root: true });
@@ -333,13 +340,7 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam
       else {
 
         Object.defineProperty(proxy, field, {
-          get: () => { 
-            // When creating local proxies getters doesn't exist on that context, so we have to account
-            // for that.
-            if( $store.getters ) { 
-              return $store.getters[ namespacedPath + `__${className}_internal_getter__` ]( path )
-            }else return $store[ `__${className}_internal_getter__` ]( path ) 
-          },
+          get: getter,
         })
 
       }
@@ -463,15 +464,26 @@ function createGettersAndGetterMutationsProxy({ cls, getters, mutations, proxy,
 
     if( $store === undefined || proxy[ field ] ) continue;
 
+    const getter = () => { 
+      // When creating local proxies getters doesn't exist on that context, so we have to account
+      // for that.
+      let getters = namespacedPath ? $store.rootGetters : $store.getters
+      if (getters === undefined) {
+        if ($store.getters === undefined) {
+          namespacedPath = ""
+          getters = $store
+        } else {
+          getters = $store.getters
+        }
+      }
+      return getters[ `${namespacedPath}${field}` ]
+    }
+
     const fieldHasGetterAndMutation = getterMutations.indexOf( field ) > -1;
     if( fieldHasGetterAndMutation ) {
       
       Object.defineProperty( proxy, field, {
-        get: () => {
-          const storeGetters = namespacedPath ? $store.rootGetters : $store.getters;
-          if( storeGetters ) return storeGetters[ namespacedPath + field ]
-          else return $store[ namespacedPath + field ];
-        },
+        get: getter,
         set: ( payload :any ) => $store.commit( namespacedPath + field, payload, { root: !!namespacedPath } ),
       })
       
@@ -482,13 +494,7 @@ function createGettersAndGetterMutationsProxy({ cls, getters, mutations, proxy,
     if( Object.prototype.hasOwnProperty.call(proxy, field) ) continue;
     
     Object.defineProperty( proxy, field, {
-      get: () => { 
-        const storeGetters = namespacedPath ? $store.rootGetters : $store.getters;
-        if (storeGetters)
-            return storeGetters[ namespacedPath + field ];
-        else
-            return $store[ namespacedPath + field ];
-      }
+      get: getter
     })
 
   }

From 777de282ddc15ec9c7349f20ae6df5fa920ccebb Mon Sep 17 00:00:00 2001
From: Glandos <bugs-github@antipoul.fr>
Date: Mon, 29 Jun 2020 15:50:53 +0200
Subject: [PATCH 3/6] add test for auto setters in strict mode

---
 test/create-proxy.spec.ts | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/test/create-proxy.spec.ts b/test/create-proxy.spec.ts
index f2fa77f..bd1b6a4 100644
--- a/test/create-proxy.spec.ts
+++ b/test/create-proxy.spec.ts
@@ -30,7 +30,7 @@ class Books extends createModule({ namespaced: 'books/' }) {
 	}
 }
 
-class UserStore extends createModule({ namespaced: 'user/' })  {
+class UserStore extends createModule({ namespaced: 'user/', strict: false })  {
 
 	settings = createSubModule(UserSettings)
 	something = createSubModule(Something)
@@ -91,11 +91,11 @@ describe('CreateProxy', () => {
 		localVue = createLocalVue()
 		localVue.use(Vuex)
 		store = new Store({
+			strict: true,
 			modules: {
 				...extractVuexModule(UserStore)
 			}
 		})
-		// console.log('New store with UserStore', store.getters)
 	})
 
 	afterEach(() => {
@@ -148,4 +148,15 @@ describe('CreateProxy', () => {
 		expect(user.lastname).toEqual('Nordmann')
 	})
 
+	it('should proxy non-strict setter in strict mode', () => {
+		const user = createProxy(store, UserStore)
+
+		expect(user.firstname).toEqual('Michael')
+		expect(user.lastname).toEqual('Olofinjana')
+		user.firstname = 'Ola'
+		user.lastname = 'Nordmann'
+		expect(user.firstname).toEqual('Ola')
+		expect(user.lastname).toEqual('Nordmann')
+	})
+
 })
\ No newline at end of file

From 88acfef6199192d36adfbe1bcc315e6d33c8795e Mon Sep 17 00:00:00 2001
From: Glandos <bugs-github@antipoul.fr>
Date: Mon, 29 Jun 2020 15:52:03 +0200
Subject: [PATCH 4/6] clean everything in proxy cache to ensure module and
 proxy is regenerated

this is especially useful for tests
---
 src/proxy.ts | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/proxy.ts b/src/proxy.ts
index 04f10a4..a58de71 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -2,7 +2,17 @@ import { extractVuexModule, getNamespacedPath } from "./module";
 import { VuexModuleConstructor, Map, VuexModule, ProxyWatchers } from "./interfaces";
 import { getClassPath, toCamelCase, refineNamespacedPath } from "./utils";
 
-export function clearProxyCache<T extends typeof VuexModule>( cls :T ) {}
+export function clearProxyCache<T extends typeof VuexModule>( cls :T ) {
+  //@ts-ignore
+  const VuexClass = cls as VuexModuleConstructor
+  delete VuexClass.prototype.__vuex_module_cache__
+  delete VuexClass.prototype.__vuex_proxy_cache__
+  delete VuexClass.prototype.__store_cache__
+  delete VuexClass.prototype.__vuex_local_proxy_cache__
+  for (const submodule of Object.values(VuexClass.prototype.__submodules_cache__)) {
+    clearProxyCache(submodule)
+  }
+}
 
 export function createProxy<T extends typeof VuexModule>( $store :any, cls :T ) :ProxyWatchers & InstanceType<T> {
   //@ts-ignore

From a83e8a29e196b712cb019b3c30551b57f6bb5865 Mon Sep 17 00:00:00 2001
From: Glandos <bugs-github@antipoul.fr>
Date: Mon, 29 Jun 2020 17:18:58 +0200
Subject: [PATCH 5/6] re-enable nested objects automatic setter

This partly reverts #40, and add a test for null object that can be set.
Now nested object can be set without explicit mutation, even in submodule.
---
 src/proxy.ts              |  4 +--
 test/create-proxy.spec.ts | 62 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/src/proxy.ts b/src/proxy.ts
index a58de71..15f76ef 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -287,7 +287,7 @@ function createSubModuleProxy( $store :Map, cls:VuexModuleConstructor, proxy :Ma
 
 }
 
-function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, namespacedPath = "", currentField = "", maxDepth = 1 }: { cls: VuexModuleConstructor, proxy: Map; state: Map; $store: any; namespacedPath?: string; currentField?: string; maxDepth ?:number}) {
+function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, namespacedPath = "", currentField = "", maxDepth = 12 }: { cls: VuexModuleConstructor, proxy: Map, state: Map, $store: any, namespacedPath?: string, currentField?: string, maxDepth ?:number}) {
   /**
    * 1. Go through all fields in the object and check the values of those fields. 
    *  
@@ -313,7 +313,7 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam
     if (currentField.length && !currentField.endsWith(".")) currentField += ".";
     const path = currentField + field;
 
-    if ( maxDepth === 0 || typeof value !== "object" || (typeof value === 'object' && !fieldIsSubmodule) ) {
+    if ( maxDepth === 0 || typeof value !== 'object' || value === null ) {
 
       const getter = () => { 
         // When creating local proxies getters doesn't exist on that context, so we have to account
diff --git a/test/create-proxy.spec.ts b/test/create-proxy.spec.ts
index bd1b6a4..e173bf9 100644
--- a/test/create-proxy.spec.ts
+++ b/test/create-proxy.spec.ts
@@ -20,6 +20,13 @@ class UserSettings extends createModule({ namespaced: 'user/settings/' }) {
 
 class Something extends createModule({ namespaced: 'user/something/' }) {
 	something = 'nothing'
+	nested = {
+		test: "test",
+		deep: {
+			test: "deep test",
+			valid: true
+		}
+	}
 }
 
 class Books extends createModule({ namespaced: 'books/' }) {
@@ -37,6 +44,16 @@ class UserStore extends createModule({ namespaced: 'user/', strict: false })  {
 
 	firstname = 'Michael'
 	lastname = 'Olofinjana'
+	nullField: string | null = null
+	description = {
+		fingers: 10,
+		arms: 2,
+		hungry: true,
+		head: {
+			eyes: 2,
+			hairs: "brown"
+		}
+	}
 	@getter specialty = 'JavaScript' // The @getter decorator automatically exposes a defined state as a getter.
 	@getter occupation = 'Developer'
 
@@ -115,6 +132,7 @@ describe('CreateProxy', () => {
 
 		expect(user.firstname).toEqual('Michael')
 		expect(user.lastname).toEqual('Olofinjana')
+		expect(user.nullField).toEqual(null)
 	})
 
 	it('should proxy actions', async () => {
@@ -153,10 +171,54 @@ describe('CreateProxy', () => {
 
 		expect(user.firstname).toEqual('Michael')
 		expect(user.lastname).toEqual('Olofinjana')
+		expect(user.nullField).toEqual(null)
+
 		user.firstname = 'Ola'
 		user.lastname = 'Nordmann'
+		user.nullField = 'not null'
 		expect(user.firstname).toEqual('Ola')
 		expect(user.lastname).toEqual('Nordmann')
+		expect(user.nullField).toEqual('not null')
+	})
+
+	it('should proxy objects recursively', () => {
+		const user = createProxy(store, UserStore)
+
+		expect(user.description.arms).toEqual(2)
+		expect(user.description.fingers).toEqual(10)
+		expect(user.description.hungry).toEqual(true)
+		expect(user.description.head.eyes).toEqual(2)
+		expect(user.description.head.hairs).toEqual("brown")
+
+		user.description.hungry = false
+		expect(user.description.hungry).toEqual(false)
+
+		user.description.head.hairs = "blond"
+		expect(user.description.head.hairs).toEqual("blond")
+	})
+
+	it('should proxy submodule', () => {
+		const user = createProxy(store, UserStore)
+
+		expect(user.settings.cookieConsent).toEqual(false)
+		expect(user.something.something).toEqual("nothing")
+		expect(user.something.nested.test).toEqual("test")
+		expect(user.something.nested.deep.test).toEqual("deep test")
+		expect(user.something.nested.deep.valid).toEqual(true)
+
+		user.settings.changeConsent(true)
+		expect(user.settings.cookieConsent).toEqual(true)
+
+		user.something.something = "more than nothing"
+		expect(user.something.something).toEqual("more than nothing")
+
+		user.something.nested.test = "nested change"
+		expect(user.something.nested.test).toEqual("nested change")
+
+		user.something.nested.deep.test = "nested deep change"
+		user.something.nested.deep.valid = false
+		expect(user.something.nested.deep.test).toEqual("nested deep change")
+		expect(user.something.nested.deep.valid).toEqual(false)
 	})
 
 })
\ No newline at end of file

From fb6acfd65c26b3c99699f94cc9c207cd5aa51261 Mon Sep 17 00:00:00 2001
From: Glandos <bugs-github@antipoul.fr>
Date: Wed, 1 Jul 2020 17:15:27 +0200
Subject: [PATCH 6/6] include arrays as automatic getters

---
 src/proxy.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/proxy.ts b/src/proxy.ts
index 15f76ef..6fbedc0 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -313,7 +313,7 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam
     if (currentField.length && !currentField.endsWith(".")) currentField += ".";
     const path = currentField + field;
 
-    if ( maxDepth === 0 || typeof value !== 'object' || value === null ) {
+    if ( maxDepth === 0 || typeof value !== 'object' || value === null || Array.isArray(value) ) {
 
       const getter = () => { 
         // When creating local proxies getters doesn't exist on that context, so we have to account