Skip to content

Commit

Permalink
feat: 'cloneData' option to buildMemoryStorage (#581)
Browse files Browse the repository at this point in the history
* Add 'storeClone' option to buildMemoryStorage function

* fix lint issue

* fix lint issue

* add test for new storeClone option

* fix linter issue

* update comment

* update code according to review

* fix test

* Update src/storage/memory.ts

Add this comment to code coverage skip the structuredClone test

Co-authored-by: Arthur Fiorette <[email protected]>

* Update test/storage/memory.test.ts

Co-authored-by: Arthur Fiorette <[email protected]>

* fix test

* fix indent

* Update src/storage/memory.ts

* Update test/storage/memory.test.ts

* Update test/storage/memory.test.ts

* Update test/storage/memory.test.ts

* Update test/storage/memory.test.ts

* refactor: docs and more

---------

Co-authored-by: Arthur Fiorette <[email protected]>
Co-authored-by: arthurfiorette <[email protected]>
  • Loading branch information
3 people authored Jul 1, 2023
1 parent 7005846 commit 6407cad
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 9 deletions.
5 changes: 3 additions & 2 deletions docs/src/guide/storages.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ A memory storage is the simplest one. It works everywhere and its values are los
page reload or when the process is killed.

If you are directly mutating some response property, you probably will face some reference
issues because the storage will also get mutated. To avoid that, you can use the `clone`
option to clone the response before saving it. _Just like
issues because the storage will also get mutated. To avoid that, you can use the
`clone: true` option to clone the response before saving it or `clone: 'double'` to also
clone both ways, on `set()` and on `get()`. _Just like
[#136](https://github.com/arthurfiorette/axios-cache-interceptor/issues/163) and many
others._

Expand Down
20 changes: 15 additions & 5 deletions src/storage/memory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { buildStorage, canStale, isExpired } from './build';
import type { AxiosStorage, StorageValue } from './types';
import type { AxiosStorage, NotEmptyStorageValue, StorageValue } from './types';

/**
* Modern function to natively deep clone.
Expand Down Expand Up @@ -30,8 +30,10 @@ declare const structuredClone: (<T>(value: T) => T) | undefined;
* delete memoryStorage.data[id];
* ```
*
* @param {boolean} cloneData If the data returned by `find()` should be cloned to avoid
* mutating the original data outside the `set()` method.
* @param {boolean | 'double'} cloneData Use `true` if the data returned by `find()`
* should be cloned to avoid mutating the original data outside the `set()` method. Use
* `'double'` to also clone before saving value in storage using `set()`. Disabled is
* default
* @param {number | false} cleanupInterval The interval in milliseconds to run a
* setInterval job of cleaning old entries. If false, the job will not be created.
* Disabled is default
Expand All @@ -41,7 +43,7 @@ declare const structuredClone: (<T>(value: T) => T) | undefined;
* usage. Disabled is default
*/
export function buildMemoryStorage(
cloneData = false,
cloneData: boolean | 'double' = false,
cleanupInterval: number | false = false,
maxEntries: number | false = false
) {
Expand All @@ -67,7 +69,15 @@ export function buildMemoryStorage(
}
}

storage.data[key] = value;
storage.data[key] =
// Clone the value before storing to prevent future mutations
// from affecting cached data.
cloneData === 'double'
? /* istanbul ignore next 'only available on super recent browsers' */
typeof structuredClone === 'function'
? structuredClone(value)
: (JSON.parse(JSON.stringify(value)) as NotEmptyStorageValue)
: value;
},

remove: (key) => {
Expand Down
2 changes: 0 additions & 2 deletions test/dev.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export {};

describe('tests __ACI_DEV__ usage', () => {
it('expects importing with __ACI_DEV__ true prints a warning', async () => {
expect(__ACI_DEV__).toBeTruthy();
Expand Down
33 changes: 33 additions & 0 deletions test/storage/memory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,39 @@ describe('tests memory storage', () => {
expect(result2.data?.data).toBe('data');
});

// Expects that a when value saved using storage.set() is has his inner properties updated,
// a request to storage.get() should return unmodified value.
//
// https://github.com/arthurfiorette/axios-cache-interceptor/issues/580
it('ensures set() also clones data when cloneData is double', async () => {
const storage = buildMemoryStorage('double');

const data = { ...EMPTY_RESPONSE, data: 'data' };

await storage.set('key', {
state: 'cached',
createdAt: Date.now(),
ttl: 1000 * 60 * 5, // 5 Minutes
data: data
});

data.data = 'another data';

expect(storage.data['key']).not.toBeNull();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(storage.data['key']!.state).toBe('cached');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(storage.data['key']!.data).not.toBeNull();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(storage.data['key']!.data!.data).toBe('data');

const result = (await storage.get('key')) as CachedStorageValue;

expect(result).not.toBeNull();
expect(result.state).toBe('cached');
expect(result.data.data).toBe('data');
});

it('tests cleanup function', async () => {
const storage = buildMemoryStorage(false, 500);

Expand Down

0 comments on commit 6407cad

Please sign in to comment.