@algolia/client-common 5.0.0-alpha.92 → 5.0.0-alpha.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client-common.cjs +43 -19
- package/dist/client-common.esm.node.js +43 -19
- package/dist/src/cache/createBrowserLocalStorageCache.d.ts.map +1 -1
- package/dist/src/types/cache.d.ts +14 -0
- package/dist/src/types/cache.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/cache/browser-local-storage-cache.test.ts +61 -9
- package/src/cache/createBrowserLocalStorageCache.ts +55 -6
- package/src/transporter/errors.ts +1 -1
- package/src/types/cache.ts +17 -0
package/dist/client-common.cjs
CHANGED
|
@@ -118,14 +118,35 @@ function createBrowserLocalStorageCache(options) {
|
|
|
118
118
|
function getNamespace() {
|
|
119
119
|
return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
|
|
120
120
|
}
|
|
121
|
+
function setNamespace(namespace) {
|
|
122
|
+
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
123
|
+
}
|
|
124
|
+
function removeOutdatedCacheItems() {
|
|
125
|
+
const timeToLive = options.timeToLive ? options.timeToLive * 1000 : null;
|
|
126
|
+
const namespace = getNamespace();
|
|
127
|
+
const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(Object.entries(namespace).filter(([, cacheItem]) => {
|
|
128
|
+
return cacheItem.timestamp !== undefined;
|
|
129
|
+
}));
|
|
130
|
+
setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
|
|
131
|
+
if (!timeToLive) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const filteredNamespaceWithoutExpiredItems = Object.fromEntries(Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => {
|
|
135
|
+
const currentTimestamp = new Date().getTime();
|
|
136
|
+
const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;
|
|
137
|
+
return !isExpired;
|
|
138
|
+
}));
|
|
139
|
+
setNamespace(filteredNamespaceWithoutExpiredItems);
|
|
140
|
+
}
|
|
121
141
|
return {
|
|
122
142
|
get(key, defaultValue, events = {
|
|
123
143
|
miss: () => Promise.resolve()
|
|
124
144
|
}) {
|
|
125
145
|
return Promise.resolve().then(() => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
146
|
+
removeOutdatedCacheItems();
|
|
147
|
+
return getNamespace()[JSON.stringify(key)];
|
|
148
|
+
}).then(value => {
|
|
149
|
+
return Promise.all([value ? value.value : defaultValue(), value !== undefined]);
|
|
129
150
|
}).then(([value, exists]) => {
|
|
130
151
|
return Promise.all([value, exists || events.miss(value)]);
|
|
131
152
|
}).then(([value]) => value);
|
|
@@ -133,7 +154,10 @@ function createBrowserLocalStorageCache(options) {
|
|
|
133
154
|
set(key, value) {
|
|
134
155
|
return Promise.resolve().then(() => {
|
|
135
156
|
const namespace = getNamespace();
|
|
136
|
-
namespace[JSON.stringify(key)] =
|
|
157
|
+
namespace[JSON.stringify(key)] = {
|
|
158
|
+
timestamp: new Date().getTime(),
|
|
159
|
+
value
|
|
160
|
+
};
|
|
137
161
|
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
138
162
|
return value;
|
|
139
163
|
});
|
|
@@ -263,6 +287,20 @@ function createStatefulHost(host, status = 'up') {
|
|
|
263
287
|
};
|
|
264
288
|
}
|
|
265
289
|
|
|
290
|
+
function _toPrimitive(t, r) {
|
|
291
|
+
if ("object" != typeof t || !t) return t;
|
|
292
|
+
var e = t[Symbol.toPrimitive];
|
|
293
|
+
if (void 0 !== e) {
|
|
294
|
+
var i = e.call(t, r || "default");
|
|
295
|
+
if ("object" != typeof i) return i;
|
|
296
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
297
|
+
}
|
|
298
|
+
return ("string" === r ? String : Number)(t);
|
|
299
|
+
}
|
|
300
|
+
function _toPropertyKey(t) {
|
|
301
|
+
var i = _toPrimitive(t, "string");
|
|
302
|
+
return "symbol" == typeof i ? i : String(i);
|
|
303
|
+
}
|
|
266
304
|
function _defineProperty(obj, key, value) {
|
|
267
305
|
key = _toPropertyKey(key);
|
|
268
306
|
if (key in obj) {
|
|
@@ -277,20 +315,6 @@ function _defineProperty(obj, key, value) {
|
|
|
277
315
|
}
|
|
278
316
|
return obj;
|
|
279
317
|
}
|
|
280
|
-
function _toPrimitive(input, hint) {
|
|
281
|
-
if (typeof input !== "object" || input === null) return input;
|
|
282
|
-
var prim = input[Symbol.toPrimitive];
|
|
283
|
-
if (prim !== undefined) {
|
|
284
|
-
var res = prim.call(input, hint || "default");
|
|
285
|
-
if (typeof res !== "object") return res;
|
|
286
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
287
|
-
}
|
|
288
|
-
return (hint === "string" ? String : Number)(input);
|
|
289
|
-
}
|
|
290
|
-
function _toPropertyKey(arg) {
|
|
291
|
-
var key = _toPrimitive(arg, "string");
|
|
292
|
-
return typeof key === "symbol" ? key : String(key);
|
|
293
|
-
}
|
|
294
318
|
|
|
295
319
|
class AlgoliaError extends Error {
|
|
296
320
|
constructor(message, name) {
|
|
@@ -311,7 +335,7 @@ class ErrorWithStackTrace extends AlgoliaError {
|
|
|
311
335
|
}
|
|
312
336
|
class RetryError extends ErrorWithStackTrace {
|
|
313
337
|
constructor(stackTrace) {
|
|
314
|
-
super('Unreachable hosts - your application id may be incorrect. If the error persists,
|
|
338
|
+
super('Unreachable hosts - your application id may be incorrect. If the error persists, please create a ticket at https://support.algolia.com/ sharing steps we can use to reproduce the issue.', stackTrace, 'RetryError');
|
|
315
339
|
}
|
|
316
340
|
}
|
|
317
341
|
class ApiError extends ErrorWithStackTrace {
|
|
@@ -116,14 +116,35 @@ function createBrowserLocalStorageCache(options) {
|
|
|
116
116
|
function getNamespace() {
|
|
117
117
|
return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
|
|
118
118
|
}
|
|
119
|
+
function setNamespace(namespace) {
|
|
120
|
+
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
121
|
+
}
|
|
122
|
+
function removeOutdatedCacheItems() {
|
|
123
|
+
const timeToLive = options.timeToLive ? options.timeToLive * 1000 : null;
|
|
124
|
+
const namespace = getNamespace();
|
|
125
|
+
const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(Object.entries(namespace).filter(([, cacheItem]) => {
|
|
126
|
+
return cacheItem.timestamp !== undefined;
|
|
127
|
+
}));
|
|
128
|
+
setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
|
|
129
|
+
if (!timeToLive) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const filteredNamespaceWithoutExpiredItems = Object.fromEntries(Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => {
|
|
133
|
+
const currentTimestamp = new Date().getTime();
|
|
134
|
+
const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;
|
|
135
|
+
return !isExpired;
|
|
136
|
+
}));
|
|
137
|
+
setNamespace(filteredNamespaceWithoutExpiredItems);
|
|
138
|
+
}
|
|
119
139
|
return {
|
|
120
140
|
get(key, defaultValue, events = {
|
|
121
141
|
miss: () => Promise.resolve()
|
|
122
142
|
}) {
|
|
123
143
|
return Promise.resolve().then(() => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
144
|
+
removeOutdatedCacheItems();
|
|
145
|
+
return getNamespace()[JSON.stringify(key)];
|
|
146
|
+
}).then(value => {
|
|
147
|
+
return Promise.all([value ? value.value : defaultValue(), value !== undefined]);
|
|
127
148
|
}).then(([value, exists]) => {
|
|
128
149
|
return Promise.all([value, exists || events.miss(value)]);
|
|
129
150
|
}).then(([value]) => value);
|
|
@@ -131,7 +152,10 @@ function createBrowserLocalStorageCache(options) {
|
|
|
131
152
|
set(key, value) {
|
|
132
153
|
return Promise.resolve().then(() => {
|
|
133
154
|
const namespace = getNamespace();
|
|
134
|
-
namespace[JSON.stringify(key)] =
|
|
155
|
+
namespace[JSON.stringify(key)] = {
|
|
156
|
+
timestamp: new Date().getTime(),
|
|
157
|
+
value
|
|
158
|
+
};
|
|
135
159
|
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
136
160
|
return value;
|
|
137
161
|
});
|
|
@@ -261,6 +285,20 @@ function createStatefulHost(host, status = 'up') {
|
|
|
261
285
|
};
|
|
262
286
|
}
|
|
263
287
|
|
|
288
|
+
function _toPrimitive(t, r) {
|
|
289
|
+
if ("object" != typeof t || !t) return t;
|
|
290
|
+
var e = t[Symbol.toPrimitive];
|
|
291
|
+
if (void 0 !== e) {
|
|
292
|
+
var i = e.call(t, r || "default");
|
|
293
|
+
if ("object" != typeof i) return i;
|
|
294
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
295
|
+
}
|
|
296
|
+
return ("string" === r ? String : Number)(t);
|
|
297
|
+
}
|
|
298
|
+
function _toPropertyKey(t) {
|
|
299
|
+
var i = _toPrimitive(t, "string");
|
|
300
|
+
return "symbol" == typeof i ? i : String(i);
|
|
301
|
+
}
|
|
264
302
|
function _defineProperty(obj, key, value) {
|
|
265
303
|
key = _toPropertyKey(key);
|
|
266
304
|
if (key in obj) {
|
|
@@ -275,20 +313,6 @@ function _defineProperty(obj, key, value) {
|
|
|
275
313
|
}
|
|
276
314
|
return obj;
|
|
277
315
|
}
|
|
278
|
-
function _toPrimitive(input, hint) {
|
|
279
|
-
if (typeof input !== "object" || input === null) return input;
|
|
280
|
-
var prim = input[Symbol.toPrimitive];
|
|
281
|
-
if (prim !== undefined) {
|
|
282
|
-
var res = prim.call(input, hint || "default");
|
|
283
|
-
if (typeof res !== "object") return res;
|
|
284
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
285
|
-
}
|
|
286
|
-
return (hint === "string" ? String : Number)(input);
|
|
287
|
-
}
|
|
288
|
-
function _toPropertyKey(arg) {
|
|
289
|
-
var key = _toPrimitive(arg, "string");
|
|
290
|
-
return typeof key === "symbol" ? key : String(key);
|
|
291
|
-
}
|
|
292
316
|
|
|
293
317
|
class AlgoliaError extends Error {
|
|
294
318
|
constructor(message, name) {
|
|
@@ -309,7 +333,7 @@ class ErrorWithStackTrace extends AlgoliaError {
|
|
|
309
333
|
}
|
|
310
334
|
class RetryError extends ErrorWithStackTrace {
|
|
311
335
|
constructor(stackTrace) {
|
|
312
|
-
super('Unreachable hosts - your application id may be incorrect. If the error persists,
|
|
336
|
+
super('Unreachable hosts - your application id may be incorrect. If the error persists, please create a ticket at https://support.algolia.com/ sharing steps we can use to reproduce the issue.', stackTrace, 'RetryError');
|
|
313
337
|
}
|
|
314
338
|
}
|
|
315
339
|
class ApiError extends ErrorWithStackTrace {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createBrowserLocalStorageCache.d.ts","sourceRoot":"","sources":["../../../src/cache/createBrowserLocalStorageCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"createBrowserLocalStorageCache.d.ts","sourceRoot":"","sources":["../../../src/cache/createBrowserLocalStorageCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,0BAA0B,EAC1B,KAAK,EAEN,MAAM,UAAU,CAAC;AAElB,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,0BAA0B,GAClC,KAAK,CAiHP"}
|
|
@@ -33,11 +33,25 @@ export type BrowserLocalStorageOptions = {
|
|
|
33
33
|
* The cache key.
|
|
34
34
|
*/
|
|
35
35
|
key: string;
|
|
36
|
+
/**
|
|
37
|
+
* The time to live for each cached item in seconds.
|
|
38
|
+
*/
|
|
39
|
+
timeToLive?: number;
|
|
36
40
|
/**
|
|
37
41
|
* The native local storage implementation.
|
|
38
42
|
*/
|
|
39
43
|
localStorage?: Storage;
|
|
40
44
|
};
|
|
45
|
+
export type BrowserLocalStorageCacheItem = {
|
|
46
|
+
/**
|
|
47
|
+
* The cache item creation timestamp.
|
|
48
|
+
*/
|
|
49
|
+
timestamp: number;
|
|
50
|
+
/**
|
|
51
|
+
* The cache item value.
|
|
52
|
+
*/
|
|
53
|
+
value: any;
|
|
54
|
+
};
|
|
41
55
|
export type FallbackableCacheOptions = {
|
|
42
56
|
/**
|
|
43
57
|
* List of caches order by priority.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/types/cache.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG;IAClB;;OAEG;IACH,GAAG,EAAE,CAAC,MAAM,EACV,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EACjC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EACnC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,KACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IAErB;;OAEG;IACH,GAAG,EAAE,CAAC,MAAM,EACV,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EACjC,KAAK,EAAE,MAAM,KACV,OAAO,CAAC,MAAM,CAAC,CAAC;IAErB;;OAEG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7D;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,MAAM,IAAI;IAChC;;OAEG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB,CAAC"}
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/types/cache.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG;IAClB;;OAEG;IACH,GAAG,EAAE,CAAC,MAAM,EACV,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EACjC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EACnC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,KACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IAErB;;OAEG;IACH,GAAG,EAAE,CAAC,MAAM,EACV,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EACjC,KAAK,EAAE,MAAM,KACV,OAAO,CAAC,MAAM,CAAC,CAAC;IAErB;;OAEG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7D;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,MAAM,IAAI;IAChC;;OAEG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@algolia/client-common",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.98",
|
|
4
4
|
"description": "Common package for the Algolia JavaScript API client.",
|
|
5
5
|
"repository": "algolia/algoliasearch-client-javascript",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"test": "jest"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@babel/preset-env": "7.23.
|
|
23
|
+
"@babel/preset-env": "7.23.8",
|
|
24
24
|
"@babel/preset-typescript": "7.23.3",
|
|
25
|
-
"@types/jest": "29.5.
|
|
26
|
-
"@types/node": "20.
|
|
25
|
+
"@types/jest": "29.5.11",
|
|
26
|
+
"@types/node": "20.11.1",
|
|
27
27
|
"jest": "29.7.0",
|
|
28
28
|
"jest-environment-jsdom": "29.7.0",
|
|
29
29
|
"ts-jest": "29.1.1",
|
|
30
|
-
"typescript": "5.
|
|
30
|
+
"typescript": "5.3.3"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">= 14.0.0"
|
|
@@ -39,6 +39,24 @@ describe('browser local storage cache', () => {
|
|
|
39
39
|
expect(missMock.mock.calls.length).toBe(1);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it('reads unexpired timeToLive keys', async () => {
|
|
43
|
+
const cache = createBrowserLocalStorageCache({
|
|
44
|
+
key: version,
|
|
45
|
+
timeToLive: 5,
|
|
46
|
+
});
|
|
47
|
+
await cache.set({ key: 'foo' }, { bar: 1 });
|
|
48
|
+
|
|
49
|
+
const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
await cache.get({ key: 'foo' }, defaultValue, {
|
|
53
|
+
miss: () => Promise.resolve(missMock()),
|
|
54
|
+
})
|
|
55
|
+
).toMatchObject({ bar: 1 });
|
|
56
|
+
|
|
57
|
+
expect(missMock.mock.calls.length).toBe(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
42
60
|
it('deletes keys', async () => {
|
|
43
61
|
const cache = createBrowserLocalStorageCache({ key: version });
|
|
44
62
|
|
|
@@ -53,19 +71,43 @@ describe('browser local storage cache', () => {
|
|
|
53
71
|
expect(missMock.mock.calls.length).toBe(1);
|
|
54
72
|
});
|
|
55
73
|
|
|
74
|
+
it('deletes expired keys', async () => {
|
|
75
|
+
const cache = createBrowserLocalStorageCache({
|
|
76
|
+
key: version,
|
|
77
|
+
timeToLive: -1,
|
|
78
|
+
});
|
|
79
|
+
await cache.set({ key: 'foo' }, { bar: 1 });
|
|
80
|
+
|
|
81
|
+
const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });
|
|
82
|
+
|
|
83
|
+
expect(
|
|
84
|
+
await cache.get({ key: 'foo' }, defaultValue, {
|
|
85
|
+
miss: () => Promise.resolve(missMock()),
|
|
86
|
+
})
|
|
87
|
+
).toMatchObject({ bar: 2 });
|
|
88
|
+
|
|
89
|
+
expect(missMock.mock.calls.length).toBe(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
56
92
|
it('can be cleared', async () => {
|
|
57
93
|
const cache = createBrowserLocalStorageCache({ key: version });
|
|
58
|
-
|
|
59
94
|
await cache.set({ key: 'foo' }, { bar: 1 });
|
|
95
|
+
|
|
60
96
|
await cache.clear();
|
|
61
97
|
|
|
62
|
-
const defaultValue = ():
|
|
98
|
+
const defaultValue = (): Promise<void> => Promise.resolve({ bar: 2 });
|
|
63
99
|
|
|
64
|
-
expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
|
|
65
|
-
{ bar: 2 }
|
|
66
|
-
);
|
|
67
|
-
expect(missMock.mock.calls.length).toBe(1);
|
|
68
100
|
expect(localStorage.length).toBe(0);
|
|
101
|
+
|
|
102
|
+
expect(
|
|
103
|
+
await cache.get({ key: 'foo' }, defaultValue, {
|
|
104
|
+
miss: () => Promise.resolve(missMock()),
|
|
105
|
+
})
|
|
106
|
+
).toMatchObject({ bar: 2 });
|
|
107
|
+
|
|
108
|
+
expect(missMock.mock.calls.length).toBe(1);
|
|
109
|
+
|
|
110
|
+
expect(localStorage.getItem(`algolia-client-js-${version}`)).toEqual('{}');
|
|
69
111
|
});
|
|
70
112
|
|
|
71
113
|
it('do throws localstorage exceptions on access', async () => {
|
|
@@ -122,13 +164,23 @@ describe('browser local storage cache', () => {
|
|
|
122
164
|
});
|
|
123
165
|
const key = { foo: 'bar' };
|
|
124
166
|
const value = 'foo';
|
|
125
|
-
|
|
126
167
|
expect(localStorage.getItem(`algolia-client-js-${version}`)).toBeNull();
|
|
127
168
|
|
|
128
169
|
await cache.set(key, value);
|
|
129
170
|
|
|
130
|
-
expect
|
|
131
|
-
|
|
171
|
+
const expectedValue = expect.objectContaining({
|
|
172
|
+
[JSON.stringify(key)]: {
|
|
173
|
+
timestamp: expect.any(Number),
|
|
174
|
+
value,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const localStorageValue = localStorage.getItem(
|
|
179
|
+
`algolia-client-js-${version}`
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(JSON.parse(localStorageValue ? localStorageValue : '{}')).toEqual(
|
|
183
|
+
expectedValue
|
|
132
184
|
);
|
|
133
185
|
});
|
|
134
186
|
});
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BrowserLocalStorageCacheItem,
|
|
3
|
+
BrowserLocalStorageOptions,
|
|
4
|
+
Cache,
|
|
5
|
+
CacheEvents,
|
|
6
|
+
} from '../types';
|
|
2
7
|
|
|
3
8
|
export function createBrowserLocalStorageCache(
|
|
4
9
|
options: BrowserLocalStorageOptions
|
|
@@ -19,20 +24,61 @@ export function createBrowserLocalStorageCache(
|
|
|
19
24
|
return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
function setNamespace(namespace: Record<string, any>): void {
|
|
28
|
+
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function removeOutdatedCacheItems(): void {
|
|
32
|
+
const timeToLive = options.timeToLive ? options.timeToLive * 1000 : null;
|
|
33
|
+
const namespace = getNamespace<BrowserLocalStorageCacheItem>();
|
|
34
|
+
|
|
35
|
+
const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries(
|
|
36
|
+
Object.entries(namespace).filter(([, cacheItem]) => {
|
|
37
|
+
return cacheItem.timestamp !== undefined;
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
setNamespace(filteredNamespaceWithoutOldFormattedCacheItems);
|
|
42
|
+
|
|
43
|
+
if (!timeToLive) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const filteredNamespaceWithoutExpiredItems = Object.fromEntries(
|
|
48
|
+
Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(
|
|
49
|
+
([, cacheItem]) => {
|
|
50
|
+
const currentTimestamp = new Date().getTime();
|
|
51
|
+
const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp;
|
|
52
|
+
|
|
53
|
+
return !isExpired;
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
setNamespace(filteredNamespaceWithoutExpiredItems);
|
|
59
|
+
}
|
|
60
|
+
|
|
22
61
|
return {
|
|
23
62
|
get<TValue>(
|
|
24
63
|
key: Record<string, any> | string,
|
|
25
64
|
defaultValue: () => Promise<TValue>,
|
|
26
65
|
events: CacheEvents<TValue> = {
|
|
27
|
-
miss: ()
|
|
66
|
+
miss: () => Promise.resolve(),
|
|
28
67
|
}
|
|
29
68
|
): Promise<TValue> {
|
|
30
69
|
return Promise.resolve()
|
|
31
70
|
.then(() => {
|
|
32
|
-
|
|
33
|
-
const value = getNamespace<TValue>()[keyAsString];
|
|
71
|
+
removeOutdatedCacheItems();
|
|
34
72
|
|
|
35
|
-
return Promise
|
|
73
|
+
return getNamespace<Promise<BrowserLocalStorageCacheItem>>()[
|
|
74
|
+
JSON.stringify(key)
|
|
75
|
+
];
|
|
76
|
+
})
|
|
77
|
+
.then((value) => {
|
|
78
|
+
return Promise.all([
|
|
79
|
+
value ? value.value : defaultValue(),
|
|
80
|
+
value !== undefined,
|
|
81
|
+
]);
|
|
36
82
|
})
|
|
37
83
|
.then(([value, exists]) => {
|
|
38
84
|
return Promise.all([value, exists || events.miss(value)]);
|
|
@@ -47,7 +93,10 @@ export function createBrowserLocalStorageCache(
|
|
|
47
93
|
return Promise.resolve().then(() => {
|
|
48
94
|
const namespace = getNamespace();
|
|
49
95
|
|
|
50
|
-
namespace[JSON.stringify(key)] =
|
|
96
|
+
namespace[JSON.stringify(key)] = {
|
|
97
|
+
timestamp: new Date().getTime(),
|
|
98
|
+
value,
|
|
99
|
+
};
|
|
51
100
|
|
|
52
101
|
getStorage().setItem(namespaceKey, JSON.stringify(namespace));
|
|
53
102
|
|
|
@@ -25,7 +25,7 @@ export class ErrorWithStackTrace extends AlgoliaError {
|
|
|
25
25
|
export class RetryError extends ErrorWithStackTrace {
|
|
26
26
|
constructor(stackTrace: StackFrame[]) {
|
|
27
27
|
super(
|
|
28
|
-
'Unreachable hosts - your application id may be incorrect. If the error persists,
|
|
28
|
+
'Unreachable hosts - your application id may be incorrect. If the error persists, please create a ticket at https://support.algolia.com/ sharing steps we can use to reproduce the issue.',
|
|
29
29
|
stackTrace,
|
|
30
30
|
'RetryError'
|
|
31
31
|
);
|
package/src/types/cache.ts
CHANGED
|
@@ -47,12 +47,29 @@ export type BrowserLocalStorageOptions = {
|
|
|
47
47
|
*/
|
|
48
48
|
key: string;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The time to live for each cached item in seconds.
|
|
52
|
+
*/
|
|
53
|
+
timeToLive?: number;
|
|
54
|
+
|
|
50
55
|
/**
|
|
51
56
|
* The native local storage implementation.
|
|
52
57
|
*/
|
|
53
58
|
localStorage?: Storage;
|
|
54
59
|
};
|
|
55
60
|
|
|
61
|
+
export type BrowserLocalStorageCacheItem = {
|
|
62
|
+
/**
|
|
63
|
+
* The cache item creation timestamp.
|
|
64
|
+
*/
|
|
65
|
+
timestamp: number;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The cache item value.
|
|
69
|
+
*/
|
|
70
|
+
value: any;
|
|
71
|
+
};
|
|
72
|
+
|
|
56
73
|
export type FallbackableCacheOptions = {
|
|
57
74
|
/**
|
|
58
75
|
* List of caches order by priority.
|