Skip to content

Commit

Permalink
feat(key): change function to hash keys
Browse files Browse the repository at this point in the history
  • Loading branch information
edumudu committed Sep 8, 2022
1 parent dcc177b commit 3ffabf5
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 102 deletions.
46 changes: 21 additions & 25 deletions lib/composables/global-swr-config/global-swr-config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { provide, reactive, ref, UnwrapRef } from 'vue';
import { provide, ref } from 'vue';
import type { Mock } from 'vitest';

import { defaultConfig, globalConfigKey } from '@/config';
import { AnyFunction, CacheState, Key, SWRConfig } from '@/types';
import { useInjectedSetup, useSetup } from '@/utils/test';
import { MapAdapter } from '@/cache';
import { AnyFunction, SWRConfig } from '@/types';
import {
getDataFromMockedCache,
mockedCache,
setDataToMockedCache,
useInjectedSetup,
useSetup,
} from '@/utils/test';

import { useSWRConfig, configureGlobalSWR } from '.';

Expand All @@ -17,11 +22,11 @@ describe('useSWRConfig', () => {
[{ revalidateIfStale: true, revalidateOnFocus: false }],
])('should get configs from global configuration: "%s"', (objToProvide) => {
const { config } = useInjectedSetup(
() => provide(globalConfigKey, ref(objToProvide)),
() => configureGlobalSWR(objToProvide),
() => useSWRConfig(),
);

expect(config.value).toEqual(objToProvide);
expect(config.value).toContain(objToProvide);
});

it('should return default config if not have an provided one', () => {
Expand Down Expand Up @@ -53,7 +58,7 @@ describe('configureGlobalSWR', () => {

it('should merge context config and the passed by argument', () => {
const injectedConfig: Partial<SWRConfig> = Object.freeze({
...globalConfigKey,
...defaultConfig,
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
Expand All @@ -74,7 +79,7 @@ describe('configureGlobalSWR', () => {
});

describe('mutate', () => {
const cacheProvider = reactive(new MapAdapter());
const cacheProvider = mockedCache;
const defaultKey = 'Default key';

const useSWRConfigWrapped = () =>
Expand All @@ -83,36 +88,27 @@ describe('mutate', () => {
() => useSWRConfig(),
);

const setDataToCache = (key: Key, data: UnwrapRef<Partial<CacheState>>) => {
cacheProvider.set(key, {
error: ref(data.error),
data: ref(data.data),
isValidation: ref(data.isValidating || false),
fetchedIn: ref(data.fetchedIn || new Date()),
});
};

beforeEach(() => {
cacheProvider.clear();
setDataToCache(defaultKey, { data: 'cached data' });
setDataToMockedCache(defaultKey, { data: 'cached data' });
});

it('should write in the cache the value resolved from promise passed to mutate', async () => {
const { mutate } = useSWRConfigWrapped();

await mutate(defaultKey, Promise.resolve('resolved value'));

expect(cacheProvider.get(defaultKey)?.data).toEqual('resolved value');
expect(getDataFromMockedCache(defaultKey)?.data).toEqual('resolved value');
});

it('should write in the cache the value returned from function passed to mutate', async () => {
const { mutate } = useSWRConfigWrapped();

await mutate(defaultKey, () => 'sync resolved value');
expect(cacheProvider.get(defaultKey)?.data).toEqual('sync resolved value');
expect(getDataFromMockedCache(defaultKey)?.data).toEqual('sync resolved value');

await mutate(defaultKey, () => Promise.resolve('async resolved value'));
expect(cacheProvider.get(defaultKey)?.data).toEqual('async resolved value');
expect(getDataFromMockedCache(defaultKey)?.data).toEqual('async resolved value');
});

it.each([
Expand All @@ -125,7 +121,7 @@ describe('mutate', () => {
(cachedData) => {
const updateFn = vi.fn();

setDataToCache(defaultKey, { data: cachedData });
setDataToMockedCache(defaultKey, { data: cachedData });

const { mutate } = useInjectedSetup(
() => configureGlobalSWR({ cacheProvider }),
Expand Down Expand Up @@ -178,10 +174,10 @@ describe('mutate', () => {
optimisticData: 'optimistic data',
});

expect(cacheProvider.get(defaultKey)?.data).toEqual('optimistic data');
expect(getDataFromMockedCache(defaultKey)?.data).toEqual('optimistic data');

await promise;
expect(cacheProvider.get(defaultKey)?.data).toEqual('resolved data');
expect(getDataFromMockedCache(defaultKey)?.data).toEqual('resolved data');
});

it('should write rollback data writed in cache whe using `opoptimisticData` and `rollbackOnError`', async () => {
Expand All @@ -193,7 +189,7 @@ describe('mutate', () => {
rollbackOnError: true,
});
} catch (error) {
expect(cacheProvider.get(defaultKey)?.data).toEqual('cached data');
expect(getDataFromMockedCache(defaultKey)?.data).toEqual('cached data');
}

expect.assertions(1);
Expand Down
21 changes: 15 additions & 6 deletions lib/composables/swr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import type {
MaybeRef,
OmitFirstArrayIndex,
SWRComposableConfig,
SWRConfig,
SWRFetcher,
SWRKey,
} from '@/types';
import { serializeKey, subscribeCallback } from '@/utils';
import { isUndefined, serializeKey, subscribeCallback } from '@/utils';
import { mergeConfig } from '@/utils/merge-config';
import { isClient } from '@/config';
import { useSWRConfig } from '@/composables/global-swr-config';
Expand Down Expand Up @@ -40,6 +41,14 @@ const useCachedRef = <T>(initialValue: T, { cache, stateKey, key }: UseCachedRef
});
};

const getFromFallback = createUnrefFn((key: string, fallback: SWRConfig['fallback']) => {
if (!fallback) return undefined;

const findedKey = Object.keys(fallback).find((_key) => serializeKey(_key).key === key);

return findedKey && fallback[findedKey];
});

export const useSWR = <Data = any, Error = any>(
_key: SWRKey,
fetcher: SWRFetcher<Data>,
Expand All @@ -66,8 +75,8 @@ export const useSWR = <Data = any, Error = any>(
onError,
} = mergedConfig;

const { key, args: fetcherArgs } = toRefs(toReactive(computed(() => serializeKey(unref(_key)))));
const fallbackValue = fallbackData === undefined ? fallback?.[key.value] : fallbackData;
const { key, args: fetcherArgs } = toRefs(toReactive(computed(() => serializeKey(_key))));
const fallbackValue = isUndefined(fallbackData) ? getFromFallback(key, fallback) : fallbackData;

const valueInCache = computed(() => cacheProvider.get(key.value));
const hasCachedValue = computed(() => !!valueInCache.value);
Expand All @@ -81,9 +90,9 @@ export const useSWR = <Data = any, Error = any>(

const fetchData = async () => {
const timestampToDedupExpire = (fetchedIn.value?.getTime() || 0) + dedupingInterval;
const hasExpired = timestampToDedupExpire > Date.now();
const hasNotExpired = timestampToDedupExpire > Date.now();

if (hasCachedValue.value && (hasExpired || (isValidating.value && dedupingInterval !== 0)))
if (hasCachedValue.value && (hasNotExpired || (isValidating.value && dedupingInterval !== 0)))
return;

isValidating.value = true;
Expand Down Expand Up @@ -173,6 +182,6 @@ export const useSWR = <Data = any, Error = any>(
error: readonly(error),
isValidating: readonly(isValidating),
mutate: (...params: OmitFirstArrayIndex<Parameters<typeof mutate>>) =>
mutate(key.value, ...params),
mutate(unref(_key), ...params),
};
};
5 changes: 3 additions & 2 deletions lib/composables/swr/swr-cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { nextTick, ref } from 'vue';

import { SWRComposableConfig } from '@/types';
import { mockedCache, useInjectedSetup } from '@/utils/test';
import { serializeKey } from '@/utils';

import { useSWR } from '.';
import { configureGlobalSWR } from '../global-swr-config';
Expand Down Expand Up @@ -30,11 +31,11 @@ describe('useSWR - Cache', () => {
() => useSWR(key, defaultFetcher, defaultOptions),
);

expect(cacheProvider.has(key.value)).toBeTruthy();
expect(cacheProvider.has(serializeKey(key.value).key)).toBeTruthy();

key.value = keyTwo;

await nextTick();
expect(cacheProvider.has(key.value)).toBeTruthy();
expect(cacheProvider.has(serializeKey(key.value).key)).toBeTruthy();
});
});
26 changes: 17 additions & 9 deletions lib/composables/swr/swr-callbacks.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { nextTick, ref, unref } from 'vue';
import { nextTick, ref } from 'vue';

import { SWRComposableConfig } from '@/types';
import { useInjectedSetup, mockedCache } from '@/utils/test';
import { serializeKey } from '@/utils';

import { useSWR } from '.';
import { configureGlobalSWR } from '../global-swr-config';

const cacheProvider = mockedCache;
const defaultKey = 'defaultKey';
const { key: defaultKeySerialized } = serializeKey(defaultKey);

describe('useSWR', () => {
beforeEach(() => {
Expand Down Expand Up @@ -38,9 +40,13 @@ describe('useSWR', () => {

await nextTick();
expect(onSuccess).toHaveBeenCalledOnce();
expect(onSuccess).toHaveBeenCalledWith(fetcherResult, defaultKey, expect.anything());
expect(onSuccess).toHaveBeenCalledWith(fetcherResult, defaultKeySerialized, expect.anything());
expect(globalOnSuccess).toHaveBeenCalledOnce();
expect(globalOnSuccess).toHaveBeenCalledWith(fetcherResult, defaultKey, expect.anything());
expect(globalOnSuccess).toHaveBeenCalledWith(
fetcherResult,
defaultKeySerialized,
expect.anything(),
);
});

it('should call local and global onError if fetcher throws', async () => {
Expand All @@ -55,9 +61,9 @@ describe('useSWR', () => {

await nextTick();
expect(onError).toHaveBeenCalledOnce();
expect(onError).toHaveBeenCalledWith(error, defaultKey, expect.anything());
expect(onError).toHaveBeenCalledWith(error, defaultKeySerialized, expect.anything());
expect(globalOnError).toHaveBeenCalledOnce();
expect(globalOnError).toHaveBeenCalledWith(error, defaultKey, expect.anything());
expect(globalOnError).toHaveBeenCalledWith(error, defaultKeySerialized, expect.anything());
});

it('should call local and global onError with local and global configs merged', async () => {
Expand Down Expand Up @@ -117,6 +123,7 @@ describe('useSWR', () => {
'should call local and global onSuccess with right key "%s" and data',
async (keyStr) => {
const key = keyStr === 'key-ref' ? ref(keyStr) : keyStr;
const { key: serializedKey } = serializeKey(key);
const onSuccess = vi.fn();
const globalOnSuccess = vi.fn();
const resolvedData = 'resolved :)';
Expand All @@ -127,15 +134,16 @@ describe('useSWR', () => {
);

await nextTick();
expect(onSuccess).toHaveBeenCalledWith(resolvedData, unref(key), expect.anything());
expect(globalOnSuccess).toHaveBeenCalledWith(resolvedData, unref(key), expect.anything());
expect(onSuccess).toHaveBeenCalledWith(resolvedData, serializedKey, expect.anything());
expect(globalOnSuccess).toHaveBeenCalledWith(resolvedData, serializedKey, expect.anything());
},
);

it.each(['key-ref', 'key-string'])(
'should call local and global onError with right key "%s" and error',
async (keyStr) => {
const key = keyStr === 'key-ref' ? ref(keyStr) : keyStr;
const { key: serializedKey } = serializeKey(key);
const onError = vi.fn();
const globalOnError = vi.fn();
const error = new Error('fetch failed :(');
Expand All @@ -146,8 +154,8 @@ describe('useSWR', () => {
);

await nextTick();
expect(onError).toHaveBeenCalledWith(error, unref(key), expect.anything());
expect(globalOnError).toHaveBeenCalledWith(error, unref(key), expect.anything());
expect(onError).toHaveBeenCalledWith(error, serializedKey, expect.anything());
expect(globalOnError).toHaveBeenCalledWith(error, serializedKey, expect.anything());
},
);
});
23 changes: 0 additions & 23 deletions lib/utils/hash/hash.spec.ts

This file was deleted.

29 changes: 0 additions & 29 deletions lib/utils/hash/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './check-types';
export * from './hash';
export * from './stable-hash';
export * from './serialize-key';
export * from './merge-config';
export * from './chain-fn';
Expand Down
10 changes: 6 additions & 4 deletions lib/utils/serialize-key/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { createUnrefFn } from '@vueuse/core';

import type { Key, KeyArguments } from '@/types';

import { isFunction } from '../check-types';
import { hash } from '../hash';
import { stableHash } from '../stable-hash';

export const serializeKey = (key: Key) => {
export const serializeKey = createUnrefFn((key: Key) => {
let sanitizedKey: KeyArguments = key;

if (isFunction(sanitizedKey)) {
Expand All @@ -17,7 +19,7 @@ export const serializeKey = (key: Key) => {
const isEmptyArray = Array.isArray(sanitizedKey) && sanitizedKey.length === 0;

return {
key: !isEmptyArray && !!sanitizedKey ? hash(sanitizedKey) : '',
key: !isEmptyArray && !!sanitizedKey ? stableHash(sanitizedKey) : '',
args: sanitizedKey,
};
};
});
Loading

0 comments on commit 3ffabf5

Please sign in to comment.