Repository: Kong/swrv (ts)

Issue:
IResponse - mutate no argument?
In types.d.ts file, I can see
```
export interface IResponse<Data = any, Error = any> {
    data?: Ref<Data | undefined>;
    error?: Ref<Error | undefined>;
    isValidating: Ref<boolean>;
    mutate: () => Promise<void>;
}
```
mutate: () => Promise<void> has no argument

But when I see source code

```
return {
    ...toRefs(stateRef),
    mutate: revalidate
  } as IResponse<Data, Error>

// Revalidate the cache, mutate data
const revalidate = async (data?: fetcherFn<Data>) => {...}
```

Just like https://swr.vercel.app/docs/mutation#bound-mutate

Pull request description:
feat(mutate) force revalidate on mutate
- mutate should be immune to deduping interval
- fix mutate type

Fixes #87 

Patch:
diff --git a/src/types.ts b/src/types.ts
index 703edfd..a999326 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -20,7 +20,7 @@ export interface IResponse<Data = any, Error = any> {
   data?: Ref<Data | undefined>
   error?: Ref<Error | undefined>
   isValidating: Ref<boolean>
-  mutate: () => Promise<void>
+  mutate: (data?: fetcherFn<Data>, opts?: revalidateOptions) => Promise<void>
 }
 
 export type keyType = string | any[] | null
@@ -30,5 +30,6 @@ export type IKey = keyFunction | keyType
 
 export interface revalidateOptions {
   shouldRetryOnError?: boolean,
-  errorRetryCount?: number
+  errorRetryCount?: number,
+  forceRevalidate?: boolean,
 }
diff --git a/src/use-swrv.ts b/src/use-swrv.ts
index ca6d305..db43bf8 100644
--- a/src/use-swrv.ts
+++ b/src/use-swrv.ts
@@ -226,14 +226,16 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
     }
 
     const fetcher = data || fn
-    if (!fetcher || !isDocumentVisible()) {
+    if (!fetcher || !isDocumentVisible() || (opts?.forceRevalidate !== undefined && !opts?.forceRevalidate)) {
       stateRef.isValidating = false
       return
     }
 
     // Dedupe items that were created in the last interval #76
     if (cacheItem) {
-      const shouldRevalidate = (Date.now() - cacheItem.createdAt) >= config.dedupingInterval
+      const shouldRevalidate = Boolean(
+        ((Date.now() - cacheItem.createdAt) >= config.dedupingInterval) || opts?.forceRevalidate
+      )
 
       if (!shouldRevalidate) {
         stateRef.isValidating = false
@@ -384,10 +386,15 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
     // do nothing
   }
 
-  return {
+  const res: IResponse = {
     ...toRefs(stateRef),
-    mutate: revalidate
-  } as IResponse<Data, Error>
+    mutate: (data, opts: revalidateOptions) => revalidate(data, {
+      ...opts,
+      forceRevalidate: true
+    })
+  }
+
+  return res
 }
 
 function isPromise<T> (p: any): p is Promise<T> {


Test patch:
diff --git a/tests/use-swrv.spec.tsx b/tests/use-swrv.spec.tsx
index 5bae34b..20cf035 100644
--- a/tests/use-swrv.spec.tsx
+++ b/tests/use-swrv.spec.tsx
@@ -699,36 +699,41 @@ describe('useSWRV - mutate', () => {
     timeout(100)
   })
 
-  test.todo('mutate triggers revalidations')
-  // it('mutate triggers revalidations', done => {
-  //   const loadData = key => new Promise(res => setTimeout(() => res(key + Date.now()), 100))
-  //   mutate('mutate-is-revalidated-1', loadData('mutate-is-revalidated-1')).then(async () => {
-  //     const vm = new Vue({
-  //       template: `<div>hello, {{ data }}</div>`,
-  //       setup () {
-  //         setTimeout(() => {
-  //           mutate('mutate-is-revalidated-1', new Promise((resolve) => resolve('hey')))
-  //         }, 50)
-
-  //         return useSWRV('mutate-is-revalidated-1', loadData)
-  //       }
-  //     }).$mount()
-
-  //     tick(vm, 4)
-  //     expect(vm.$el.textContent).toContain('hello, mutate-is-revalidated-1')
-  //     timeout(100)
-  //     tick(vm, 4)
-  //     expect(vm.$el.textContent).toContain('hello, mutate-is-revalidated-1')
-  //     timeout(50)
-  //     tick(vm, 4)
-  //
-  //     // TODO: understand why revalidation isn't working here
-  //     expect(vm.$el.textContent).toBe('hello, hey')
-  //     done()
-  //   })
-
-  //   timeout(100)
-  // })
+  it('mutate triggers revalidations', async done => {
+    let count = 0
+    const loadData = () => new Promise(res => {
+      setTimeout(() => {
+        res(++count)
+      }, 100)
+    })
+    const vm = new Vue(defineComponent({
+      template: `<div>hello, {{data}}</div>`,
+      setup () {
+        const { data, mutate } = useSWRV('mutate-no-arg', loadData)
+
+        setTimeout(() => {
+          // immune to deduping interval
+          mutate()
+        }, 200)
+
+        return {
+          data
+        }
+      }
+    })).$mount()
+
+    expect(vm.$el.textContent).toBe('hello, ')
+
+    timeout(100)
+    await tick(vm, 2)
+    expect(vm.$el.textContent).toBe('hello, 1')
+
+    timeout(200)
+    await tick(vm, 4)
+    expect(vm.$el.textContent).toBe('hello, 2')
+
+    done()
+  })
 })
 
 describe('useSWRV - listeners', () => {