Skip to content

Commit

Permalink
fix(specs): partial update operation (#3486)
Browse files Browse the repository at this point in the history
Co-authored-by: shortcuts <vannicattec@gmail.com>
Co-authored-by: Pierre Millot <pierre.millot@algolia.com>
  • Loading branch information
3 people committed Aug 28, 2024
1 parent a278d2f commit 41f849e
Show file tree
Hide file tree
Showing 45 changed files with 460 additions and 665 deletions.
2 changes: 1 addition & 1 deletion clients/algoliasearch-client-javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"build:many": "lerna run build --skip-nx-cache --include-dependencies --scope ${0:-'{@algolia/*,algoliasearch}'}",
"clean": "lerna run clean --include-dependencies",
"release:bump": "lerna version ${0:-patch} --no-changelog --no-git-tag-version --no-push --exact --force-publish --yes",
"release:publish": "tsc --project tsconfig.script.json && node dist/scripts/publish.js",
"release:publish": "tsc --project scripts/tsconfig.json && node scripts/dist/scripts/publish.js",
"test": "lerna run test $*",
"test:size": "bundlesize"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,12 @@ describe('browser local storage cache', () => {
const cache = createBrowserLocalStorageCache({ key: version });
const defaultValue = (): DefaultValue => Promise.resolve({ bar: 1 });

expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{ bar: 1 }
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 1 });
expect(missMock.mock.calls.length).toBe(1);

await cache.set({ key: 'foo' }, { foo: 2 });

expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{ foo: 2 }
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ foo: 2 });
expect(missMock.mock.calls.length).toBe(1);
});

Expand All @@ -51,7 +47,7 @@ describe('browser local storage cache', () => {
expect(
await cache.get({ key: 'foo' }, defaultValue, {
miss: () => Promise.resolve(missMock()),
})
}),
).toMatchObject({ bar: 1 });

expect(missMock.mock.calls.length).toBe(0);
Expand All @@ -65,9 +61,7 @@ describe('browser local storage cache', () => {

const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });

expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{ bar: 2 }
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 });
expect(missMock.mock.calls.length).toBe(1);
});

Expand All @@ -83,7 +77,7 @@ describe('browser local storage cache', () => {
expect(
await cache.get({ key: 'foo' }, defaultValue, {
miss: () => Promise.resolve(missMock()),
})
}),
).toMatchObject({ bar: 2 });

expect(missMock.mock.calls.length).toBe(1);
Expand All @@ -102,7 +96,7 @@ describe('browser local storage cache', () => {
expect(
await cache.get({ key: 'foo' }, defaultValue, {
miss: () => Promise.resolve(missMock()),
})
}),
).toMatchObject({ bar: 2 });

expect(missMock.mock.calls.length).toBe(1);
Expand All @@ -111,8 +105,7 @@ describe('browser local storage cache', () => {
});

it('do throws localstorage exceptions on access', async () => {
const message =
"Failed to read the 'localStorage' property from 'Window': Access is denied for this document.";
const message = "Failed to read the 'localStorage' property from 'Window': Access is denied for this document.";
const cache = createBrowserLocalStorageCache(
new Proxy(
{ key: 'foo' },
Expand All @@ -125,20 +118,16 @@ describe('browser local storage cache', () => {
// Simulates a window.localStorage access.
throw new DOMException(message);
},
}
)
},
),
);
const key = { foo: 'bar' };
const value = 'foo';
const fallback = 'bar';

await expect(cache.delete(key)).rejects.toEqual(new DOMException(message));
await expect(cache.set(key, value)).rejects.toEqual(
new DOMException(message)
);
await expect(
cache.get(key, () => Promise.resolve(fallback))
).rejects.toEqual(new DOMException(message));
await expect(cache.set(key, value)).rejects.toEqual(new DOMException(message));
await expect(cache.get(key, () => Promise.resolve(fallback))).rejects.toEqual(new DOMException(message));
});

it('do throws localstorage exceptions after access', async () => {
Expand All @@ -153,9 +142,7 @@ describe('browser local storage cache', () => {

await expect(cache.delete(key)).rejects.toEqual(new Error(message));
await expect(cache.set(key, value)).rejects.toEqual(new Error(message));
await expect(
cache.get(key, () => Promise.resolve(fallback))
).rejects.toEqual(new Error(message));
await expect(cache.get(key, () => Promise.resolve(fallback))).rejects.toEqual(new Error(message));
});

it('creates a namespace within local storage', async () => {
Expand All @@ -175,12 +162,8 @@ describe('browser local storage cache', () => {
},
});

const localStorageValue = localStorage.getItem(
`algolia-client-js-${version}`
);
const localStorageValue = localStorage.getItem(`algolia-client-js-${version}`);

expect(JSON.parse(localStorageValue ? localStorageValue : '{}')).toEqual(
expectedValue
);
expect(JSON.parse(localStorageValue ? localStorageValue : '{}')).toEqual(expectedValue);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@ describe('memory cache', () => {
const cache = createMemoryCache();
const defaultValue = (): DefaultValue => Promise.resolve({ bar: 1 });

expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{
bar: 1,
}
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({
bar: 1,
});

await cache.set({ key: 'foo' }, { foo: 2 });

expect(missMock.mock.calls.length).toBe(1);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{ foo: 2 }
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ foo: 2 });
expect(missMock.mock.calls.length).toBe(1);
});

Expand All @@ -54,9 +50,7 @@ describe('memory cache', () => {

const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });

expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{ bar: 2 }
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 });
expect(missMock.mock.calls.length).toBe(1);
});

Expand All @@ -68,9 +62,7 @@ describe('memory cache', () => {

const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });

expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{ bar: 2 }
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 });
expect(missMock.mock.calls.length).toBe(1);
});

Expand All @@ -82,9 +74,7 @@ describe('memory cache', () => {

const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });

expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
{ bar: 2 }
);
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject({ bar: 2 });
expect(missMock.mock.calls.length).toBe(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,19 @@ describe('null cache', () => {

await cache.set({ key: 'key' }, { foo: 10 });

expect(await cache.get({ key: 'key' }, defaultValue, events)).toMatchObject(
{
bar: 12,
}
);
expect(await cache.get({ key: 'key' }, defaultValue, events)).toMatchObject({
bar: 12,
});

expect(missMock.mock.calls.length).toBe(1);
});

it('returns default value', async () => {
const defaultValue = (): DefaultValue => Promise.resolve({ bar: 12 });

expect(await cache.get({ foo: 'foo' }, defaultValue, events)).toMatchObject(
{
bar: 12,
}
);
expect(await cache.get({ foo: 'foo' }, defaultValue, events)).toMatchObject({
bar: 12,
});

expect(missMock.mock.calls.length).toBe(1);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,7 @@ describe('createIterablePromise', () => {
validate: () => false,
});

await expect(promise).rejects.toEqual(
expect.objectContaining({ message: 'nope' })
);
await expect(promise).rejects.toEqual(expect.objectContaining({ message: 'nope' }));
});

it('gets the rejection of the given promise via throw', async () => {
Expand All @@ -178,9 +176,7 @@ describe('createIterablePromise', () => {
validate: () => false,
});

await expect(promise).rejects.toEqual(
expect.objectContaining({ message: 'nope' })
);
await expect(promise).rejects.toEqual(expect.objectContaining({ message: 'nope' }));
});

it('rejects with the given `message` when `validate` hits', async () => {
Expand All @@ -204,7 +200,7 @@ describe('createIterablePromise', () => {
await expect(promise).rejects.toEqual(
expect.objectContaining({
message: 'Error is thrown: 3/3',
})
}),
);
expect(calls).toBe(MAX_RETRIES);
});
Expand All @@ -230,7 +226,7 @@ describe('createIterablePromise', () => {
await expect(promise).rejects.toEqual(
expect.objectContaining({
message: 'Error is thrown: 3/3',
})
}),
);
expect(calls).toBe(MAX_RETRIES);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type {
BrowserLocalStorageCacheItem,
BrowserLocalStorageOptions,
Cache,
CacheEvents,
} from '../types';

export function createBrowserLocalStorageCache(
options: BrowserLocalStorageOptions
): Cache {
import type { BrowserLocalStorageCacheItem, BrowserLocalStorageOptions, Cache, CacheEvents } from '../types';

export function createBrowserLocalStorageCache(options: BrowserLocalStorageOptions): Cache {
let storage: Storage;
// We've changed the namespace to avoid conflicts with v4, as this version is a huge breaking change
const namespaceKey = `algolia-client-js-${options.key}`;
Expand Down Expand Up @@ -35,7 +28,7 @@ export function createBrowserLocalStorageCache(
const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(
Object.entries(namespace).filter(([, cacheItem]) => {
return cacheItem.timestamp !== undefined;
})
}),
);

setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
Expand All @@ -45,14 +38,12 @@ export function createBrowserLocalStorageCache(
}

const filteredNamespaceWithoutExpiredItems = Object.fromEntries(
Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(
([, cacheItem]) => {
const currentTimestamp = new Date().getTime();
const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;

return !isExpired;
}
)
Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => {
const currentTimestamp = new Date().getTime();
const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;

return !isExpired;
}),
);

setNamespace(filteredNamespaceWithoutExpiredItems);
Expand All @@ -64,32 +55,24 @@ export function createBrowserLocalStorageCache(
defaultValue: () => Promise<TValue>,
events: CacheEvents<TValue> = {
miss: () => Promise.resolve(),
}
},
): Promise<TValue> {
return Promise.resolve()
.then(() => {
removeOutdatedCacheItems();

return getNamespace<Promise<BrowserLocalStorageCacheItem>>()[
JSON.stringify(key)
];
return getNamespace<Promise<BrowserLocalStorageCacheItem>>()[JSON.stringify(key)];
})
.then((value) => {
return Promise.all([
value ? value.value : defaultValue(),
value !== undefined,
]);
return Promise.all([value ? value.value : defaultValue(), value !== undefined]);
})
.then(([value, exists]) => {
return Promise.all([value, exists || events.miss(value)]);
})
.then(([value]) => value);
},

set<TValue>(
key: Record<string, any> | string,
value: TValue
): Promise<TValue> {
set<TValue>(key: Record<string, any> | string, value: TValue): Promise<TValue> {
return Promise.resolve().then(() => {
const namespace = getNamespace();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import type { FallbackableCacheOptions, Cache, CacheEvents } from '../types';

import { createNullCache } from './createNullCache';

export function createFallbackableCache(
options: FallbackableCacheOptions
): Cache {
export function createFallbackableCache(options: FallbackableCacheOptions): Cache {
const caches = [...options.caches];
const current = caches.shift();

Expand All @@ -18,21 +16,14 @@ export function createFallbackableCache(
defaultValue: () => Promise<TValue>,
events: CacheEvents<TValue> = {
miss: (): Promise<void> => Promise.resolve(),
}
},
): Promise<TValue> {
return current.get(key, defaultValue, events).catch(() => {
return createFallbackableCache({ caches }).get(
key,
defaultValue,
events
);
return createFallbackableCache({ caches }).get(key, defaultValue, events);
});
},

set<TValue>(
key: Record<string, any> | string,
value: TValue
): Promise<TValue> {
set<TValue>(key: Record<string, any> | string, value: TValue): Promise<TValue> {
return current.set(key, value).catch(() => {
return createFallbackableCache({ caches }).set(key, value);
});
Expand Down
Loading

0 comments on commit 41f849e

Please sign in to comment.