@algolia/client-common 4.14.2 → 5.0.0-alpha.3

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.
Files changed (89) hide show
  1. package/dist/client-common.cjs.js +803 -91
  2. package/dist/client-common.esm.node.js +774 -0
  3. package/dist/index.d.ts +10 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/src/cache/createBrowserLocalStorageCache.d.ts +3 -0
  6. package/dist/src/cache/createBrowserLocalStorageCache.d.ts.map +1 -0
  7. package/dist/src/cache/createFallbackableCache.d.ts +3 -0
  8. package/dist/src/cache/createFallbackableCache.d.ts.map +1 -0
  9. package/dist/src/cache/createMemoryCache.d.ts +3 -0
  10. package/dist/src/cache/createMemoryCache.d.ts.map +1 -0
  11. package/dist/src/cache/createNullCache.d.ts +3 -0
  12. package/dist/src/cache/createNullCache.d.ts.map +1 -0
  13. package/dist/src/cache/index.d.ts +5 -0
  14. package/dist/src/cache/index.d.ts.map +1 -0
  15. package/dist/src/constants.d.ts +7 -0
  16. package/dist/src/constants.d.ts.map +1 -0
  17. package/dist/src/createAlgoliaAgent.d.ts +3 -0
  18. package/dist/src/createAlgoliaAgent.d.ts.map +1 -0
  19. package/dist/src/createAuth.d.ts +6 -0
  20. package/dist/src/createAuth.d.ts.map +1 -0
  21. package/dist/src/createEchoRequester.d.ts +7 -0
  22. package/dist/src/createEchoRequester.d.ts.map +1 -0
  23. package/dist/src/createRetryablePromise.d.ts +14 -0
  24. package/dist/src/createRetryablePromise.d.ts.map +1 -0
  25. package/dist/src/getAlgoliaAgent.d.ts +8 -0
  26. package/dist/src/getAlgoliaAgent.d.ts.map +1 -0
  27. package/dist/src/transporter/createStatefulHost.d.ts +3 -0
  28. package/dist/src/transporter/createStatefulHost.d.ts.map +1 -0
  29. package/dist/src/transporter/createTransporter.d.ts +3 -0
  30. package/dist/src/transporter/createTransporter.d.ts.map +1 -0
  31. package/dist/src/transporter/errors.d.ts +21 -0
  32. package/dist/src/transporter/errors.d.ts.map +1 -0
  33. package/dist/src/transporter/helpers.d.ts +9 -0
  34. package/dist/src/transporter/helpers.d.ts.map +1 -0
  35. package/dist/src/transporter/index.d.ts +7 -0
  36. package/dist/src/transporter/index.d.ts.map +1 -0
  37. package/dist/src/transporter/responses.d.ts +5 -0
  38. package/dist/src/transporter/responses.d.ts.map +1 -0
  39. package/dist/src/transporter/stackTrace.d.ts +4 -0
  40. package/dist/src/transporter/stackTrace.d.ts.map +1 -0
  41. package/dist/src/types/Cache.d.ts +47 -0
  42. package/dist/src/types/Cache.d.ts.map +1 -0
  43. package/dist/src/types/CreateClient.d.ts +12 -0
  44. package/dist/src/types/CreateClient.d.ts.map +1 -0
  45. package/dist/src/types/CreateRetryablePromise.d.ts +19 -0
  46. package/dist/src/types/CreateRetryablePromise.d.ts.map +1 -0
  47. package/dist/src/types/Host.d.ts +33 -0
  48. package/dist/src/types/Host.d.ts.map +1 -0
  49. package/dist/src/types/Requester.d.ts +66 -0
  50. package/dist/src/types/Requester.d.ts.map +1 -0
  51. package/dist/src/types/Transporter.d.ts +128 -0
  52. package/dist/src/types/Transporter.d.ts.map +1 -0
  53. package/dist/src/types/index.d.ts +7 -0
  54. package/dist/src/types/index.d.ts.map +1 -0
  55. package/index.ts +9 -0
  56. package/package.json +24 -15
  57. package/src/__tests__/cache/browser-local-storage-cache.test.ts +134 -0
  58. package/src/__tests__/cache/fallbackable-cache.test.ts +126 -0
  59. package/src/__tests__/cache/memory-cache.test.ts +90 -0
  60. package/src/__tests__/cache/null-cache.test.ts +49 -0
  61. package/src/__tests__/create-retryable-promise.test.ts +86 -0
  62. package/src/cache/createBrowserLocalStorageCache.ts +74 -0
  63. package/src/cache/createFallbackableCache.ts +53 -0
  64. package/src/cache/createMemoryCache.ts +56 -0
  65. package/src/cache/createNullCache.ts +34 -0
  66. package/src/cache/index.ts +4 -0
  67. package/src/constants.ts +7 -0
  68. package/src/createAlgoliaAgent.ts +20 -0
  69. package/src/createAuth.ts +25 -0
  70. package/src/createEchoRequester.ts +59 -0
  71. package/src/createRetryablePromise.ts +52 -0
  72. package/src/getAlgoliaAgent.ts +25 -0
  73. package/src/transporter/createStatefulHost.ts +24 -0
  74. package/src/transporter/createTransporter.ts +347 -0
  75. package/src/transporter/errors.ts +51 -0
  76. package/src/transporter/helpers.ts +119 -0
  77. package/src/transporter/index.ts +6 -0
  78. package/src/transporter/responses.ts +23 -0
  79. package/src/transporter/stackTrace.ts +30 -0
  80. package/src/types/Cache.ts +61 -0
  81. package/src/types/CreateClient.ts +23 -0
  82. package/src/types/CreateRetryablePromise.ts +21 -0
  83. package/src/types/Host.ts +38 -0
  84. package/src/types/Requester.ts +72 -0
  85. package/src/types/Transporter.ts +153 -0
  86. package/src/types/index.ts +6 -0
  87. package/dist/client-common.d.ts +0 -102
  88. package/dist/client-common.esm.js +0 -89
  89. package/index.js +0 -2
package/package.json CHANGED
@@ -1,22 +1,31 @@
1
1
  {
2
2
  "name": "@algolia/client-common",
3
- "version": "4.14.2",
4
- "private": false,
5
- "repository": {
6
- "type": "git",
7
- "url": "git://github.com/algolia/algoliasearch-client-javascript.git"
8
- },
3
+ "version": "5.0.0-alpha.3",
4
+ "description": "Common package for the Algolia JavaScript API client.",
5
+ "repository": "algolia/algoliasearch-client-javascript",
9
6
  "license": "MIT",
10
- "sideEffects": false,
11
- "main": "index.js",
12
- "module": "dist/client-common.esm.js",
13
- "types": "dist/client-common.d.ts",
7
+ "author": "Algolia",
8
+ "main": "dist/client-common.cjs.js",
9
+ "module": "dist/client-common.esm.node.js",
10
+ "types": "dist/index.d.ts",
14
11
  "files": [
15
- "index.js",
16
- "dist"
12
+ "dist",
13
+ "src",
14
+ "index.ts"
17
15
  ],
18
- "dependencies": {
19
- "@algolia/requester-common": "4.14.2",
20
- "@algolia/transporter": "4.14.2"
16
+ "scripts": {
17
+ "clean": "rm -rf dist/",
18
+ "test": "jest"
19
+ },
20
+ "devDependencies": {
21
+ "@types/jest": "28.1.6",
22
+ "@types/node": "16.11.45",
23
+ "jest": "28.1.3",
24
+ "jest-environment-jsdom": "28.1.3",
25
+ "ts-jest": "28.0.7",
26
+ "typescript": "4.7.4"
27
+ },
28
+ "engines": {
29
+ "node": ">= 14.0.0"
21
30
  }
22
31
  }
@@ -0,0 +1,134 @@
1
+ import { createBrowserLocalStorageCache } from '../../cache';
2
+
3
+ const version = 'foobar';
4
+ const notAvailableStorage = new Proxy(window.localStorage, {
5
+ get() {
6
+ return (): void => {
7
+ throw new Error('Component is not available');
8
+ };
9
+ },
10
+ });
11
+
12
+ type DefaultValue = Promise<{ bar: number }>;
13
+
14
+ describe('browser local storage cache', () => {
15
+ const missMock = jest.fn();
16
+ const events = {
17
+ miss: (): Promise<any> => Promise.resolve(missMock()),
18
+ };
19
+
20
+ beforeEach(() => {
21
+ window.localStorage.clear();
22
+ jest.clearAllMocks();
23
+ });
24
+
25
+ it('sets/gets values', async () => {
26
+ const cache = createBrowserLocalStorageCache({ key: version });
27
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 1 });
28
+
29
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
30
+ { bar: 1 }
31
+ );
32
+ expect(missMock.mock.calls.length).toBe(1);
33
+
34
+ await cache.set({ key: 'foo' }, { foo: 2 });
35
+
36
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
37
+ { foo: 2 }
38
+ );
39
+ expect(missMock.mock.calls.length).toBe(1);
40
+ });
41
+
42
+ it('deletes keys', async () => {
43
+ const cache = createBrowserLocalStorageCache({ key: version });
44
+
45
+ await cache.set({ key: 'foo' }, { bar: 1 });
46
+ await cache.delete({ key: 'foo' });
47
+
48
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });
49
+
50
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
51
+ { bar: 2 }
52
+ );
53
+ expect(missMock.mock.calls.length).toBe(1);
54
+ });
55
+
56
+ it('can be cleared', async () => {
57
+ const cache = createBrowserLocalStorageCache({ key: version });
58
+
59
+ await cache.set({ key: 'foo' }, { bar: 1 });
60
+ await cache.clear();
61
+
62
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });
63
+
64
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
65
+ { bar: 2 }
66
+ );
67
+ expect(missMock.mock.calls.length).toBe(1);
68
+ expect(localStorage.length).toBe(0);
69
+ });
70
+
71
+ it('do throws localstorage exceptions on access', async () => {
72
+ const message =
73
+ "Failed to read the 'localStorage' property from 'Window': Access is denied for this document.";
74
+ const cache = createBrowserLocalStorageCache(
75
+ new Proxy(
76
+ { key: 'foo' },
77
+ {
78
+ get(_, key): DOMException | string {
79
+ if (key === 'key') {
80
+ return 'foo';
81
+ }
82
+
83
+ // Simulates a window.localStorage access.
84
+ throw new DOMException(message);
85
+ },
86
+ }
87
+ )
88
+ );
89
+ const key = { foo: 'bar' };
90
+ const value = 'foo';
91
+ const fallback = 'bar';
92
+
93
+ await expect(cache.delete(key)).rejects.toEqual(new DOMException(message));
94
+ await expect(cache.set(key, value)).rejects.toEqual(
95
+ new DOMException(message)
96
+ );
97
+ await expect(
98
+ cache.get(key, () => Promise.resolve(fallback))
99
+ ).rejects.toEqual(new DOMException(message));
100
+ });
101
+
102
+ it('do throws localstorage exceptions after access', async () => {
103
+ const cache = createBrowserLocalStorageCache({
104
+ key: version,
105
+ localStorage: notAvailableStorage,
106
+ });
107
+ const key = { foo: 'bar' };
108
+ const value = 'foo';
109
+ const fallback = 'bar';
110
+ const message = 'Component is not available';
111
+
112
+ await expect(cache.delete(key)).rejects.toEqual(new Error(message));
113
+ await expect(cache.set(key, value)).rejects.toEqual(new Error(message));
114
+ await expect(
115
+ cache.get(key, () => Promise.resolve(fallback))
116
+ ).rejects.toEqual(new Error(message));
117
+ });
118
+
119
+ it('creates a namespace within local storage', async () => {
120
+ const cache = createBrowserLocalStorageCache({
121
+ key: version,
122
+ });
123
+ const key = { foo: 'bar' };
124
+ const value = 'foo';
125
+
126
+ expect(localStorage.getItem(`algolia-client-js-${version}`)).toBeNull();
127
+
128
+ await cache.set(key, value);
129
+
130
+ expect(localStorage.getItem(`algolia-client-js-${version}`)).toBe(
131
+ '{"{\\"foo\\":\\"bar\\"}":"foo"}'
132
+ );
133
+ });
134
+ });
@@ -0,0 +1,126 @@
1
+ import {
2
+ createBrowserLocalStorageCache,
3
+ createFallbackableCache,
4
+ createMemoryCache,
5
+ createNullCache,
6
+ } from '../../cache';
7
+
8
+ const version = 'foobar';
9
+ const notAvailableStorage = new Proxy(window.localStorage, {
10
+ get() {
11
+ return (): void => {
12
+ throw new Error('Component is not available');
13
+ };
14
+ },
15
+ });
16
+
17
+ type DefaultValue = Promise<Record<number, number>>;
18
+
19
+ describe('fallbackable cache', () => {
20
+ const key = { 1: 2 };
21
+ const value = { 3: 4 };
22
+ const defaultValue = (): DefaultValue => Promise.resolve({ 5: 6 });
23
+
24
+ it('always fallback in null cache', async () => {
25
+ const cache = createFallbackableCache({ caches: [] });
26
+
27
+ await cache.set(key, value);
28
+ expect(await cache.get(key, defaultValue)).toEqual({
29
+ 5: 6,
30
+ });
31
+ });
32
+
33
+ describe('order', () => {
34
+ it('use memory cache', async () => {
35
+ const cache = createFallbackableCache({
36
+ caches: [createMemoryCache()],
37
+ });
38
+
39
+ await cache.set(key, value);
40
+
41
+ expect(await cache.get(key, defaultValue)).toEqual({
42
+ 3: 4,
43
+ });
44
+ });
45
+
46
+ it('use null cache first', async () => {
47
+ const cache = createFallbackableCache({
48
+ caches: [createNullCache(), createMemoryCache()],
49
+ });
50
+
51
+ await cache.set(key, value);
52
+
53
+ expect(await cache.get(key, defaultValue)).toEqual({
54
+ 5: 6,
55
+ });
56
+ });
57
+ });
58
+
59
+ describe('fallbacks', () => {
60
+ it('to memory cache', async () => {
61
+ const cache = createFallbackableCache({
62
+ caches: [
63
+ createBrowserLocalStorageCache({
64
+ key: version,
65
+ // @ts-expect-error this will make the cache fail, and normally we fallback on memory cache
66
+ localStorage: {},
67
+ }),
68
+ createMemoryCache(),
69
+ ],
70
+ });
71
+
72
+ await cache.set(key, value);
73
+
74
+ expect(await cache.get(key, defaultValue)).toEqual({
75
+ 3: 4,
76
+ });
77
+ });
78
+
79
+ it('to null cache', async () => {
80
+ const cache = createFallbackableCache({
81
+ caches: [
82
+ createBrowserLocalStorageCache({
83
+ key: version,
84
+ // @ts-expect-error this will make the cache fail, and normally we fallback on memory cache
85
+ localStorage: {},
86
+ }),
87
+ ],
88
+ });
89
+
90
+ await cache.set(key, value);
91
+
92
+ expect(await cache.get(key, defaultValue)).toEqual({
93
+ 5: 6,
94
+ });
95
+ });
96
+
97
+ it('to memory cache', async () => {
98
+ const cache = createFallbackableCache({
99
+ caches: [
100
+ createBrowserLocalStorageCache({
101
+ key: version,
102
+ // @ts-expect-error this will make the cache fail
103
+ localStorage: {},
104
+ }),
105
+ createBrowserLocalStorageCache({
106
+ key: version,
107
+ localStorage: notAvailableStorage, // this will make the cache fail due localStorage not available
108
+ }),
109
+ createMemoryCache(),
110
+ ],
111
+ });
112
+
113
+ await cache.set(key, value);
114
+
115
+ expect(await cache.get(key, defaultValue)).toEqual({
116
+ 3: 4,
117
+ });
118
+
119
+ await cache.clear();
120
+
121
+ expect(await cache.get(key, defaultValue)).toEqual({
122
+ 5: 6,
123
+ });
124
+ });
125
+ });
126
+ });
@@ -0,0 +1,90 @@
1
+ import { createMemoryCache } from '../../cache';
2
+
3
+ type DefaultValue = Promise<{ bar: number }>;
4
+
5
+ describe('memory cache', () => {
6
+ const missMock = jest.fn();
7
+ const events = {
8
+ miss: (): Promise<any> => Promise.resolve(missMock()),
9
+ };
10
+
11
+ beforeEach(() => {
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ it('sets/gets values', async () => {
16
+ const cache = createMemoryCache();
17
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 1 });
18
+
19
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
20
+ {
21
+ bar: 1,
22
+ }
23
+ );
24
+
25
+ await cache.set({ key: 'foo' }, { foo: 2 });
26
+
27
+ expect(missMock.mock.calls.length).toBe(1);
28
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
29
+ { foo: 2 }
30
+ );
31
+ expect(missMock.mock.calls.length).toBe(1);
32
+ });
33
+
34
+ it('getted values do not have references to the value on cache', async () => {
35
+ const cache = createMemoryCache();
36
+ const key = { foo: 'bar' };
37
+ const obj = { 1: { 2: 'bar' } };
38
+ const defaultObj = { 1: { 2: 'too' } };
39
+
40
+ await cache.set(key, obj);
41
+ const gettedValue = await cache.get(key, () => Promise.resolve(defaultObj));
42
+ gettedValue[1][2] = 'foo';
43
+
44
+ expect(await cache.get(key, () => Promise.resolve(defaultObj))).toEqual({
45
+ 1: { 2: 'bar' },
46
+ });
47
+ });
48
+
49
+ it('deletes keys', async () => {
50
+ const cache = createMemoryCache();
51
+
52
+ await cache.set({ key: 'foo' }, { bar: 1 });
53
+ await cache.delete({ key: 'foo' });
54
+
55
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });
56
+
57
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
58
+ { bar: 2 }
59
+ );
60
+ expect(missMock.mock.calls.length).toBe(1);
61
+ });
62
+
63
+ it('can be cleared', async () => {
64
+ const cache = createMemoryCache();
65
+
66
+ await cache.set({ key: 'foo' }, { bar: 1 });
67
+ await cache.clear();
68
+
69
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });
70
+
71
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
72
+ { bar: 2 }
73
+ );
74
+ expect(missMock.mock.calls.length).toBe(1);
75
+ });
76
+
77
+ it('do not force promise based api for clearing cache', async () => {
78
+ const cache = createMemoryCache();
79
+
80
+ cache.set({ key: 'foo' }, { bar: 1 });
81
+ cache.clear();
82
+
83
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 2 });
84
+
85
+ expect(await cache.get({ key: 'foo' }, defaultValue, events)).toMatchObject(
86
+ { bar: 2 }
87
+ );
88
+ expect(missMock.mock.calls.length).toBe(1);
89
+ });
90
+ });
@@ -0,0 +1,49 @@
1
+ import { createNullCache } from '../../cache';
2
+
3
+ type DefaultValue = Promise<{ bar: number }>;
4
+
5
+ describe('null cache', () => {
6
+ const cache = createNullCache();
7
+ const missMock = jest.fn();
8
+ const events = {
9
+ miss: (): Promise<any> => Promise.resolve(missMock()),
10
+ };
11
+
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ it('does not set value', async () => {
17
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 12 });
18
+
19
+ await cache.set({ key: 'key' }, { foo: 10 });
20
+
21
+ expect(await cache.get({ key: 'key' }, defaultValue, events)).toMatchObject(
22
+ {
23
+ bar: 12,
24
+ }
25
+ );
26
+
27
+ expect(missMock.mock.calls.length).toBe(1);
28
+ });
29
+
30
+ it('returns default value', async () => {
31
+ const defaultValue = (): DefaultValue => Promise.resolve({ bar: 12 });
32
+
33
+ expect(await cache.get({ foo: 'foo' }, defaultValue, events)).toMatchObject(
34
+ {
35
+ bar: 12,
36
+ }
37
+ );
38
+
39
+ expect(missMock.mock.calls.length).toBe(1);
40
+ });
41
+
42
+ it('can be deleted', async () => {
43
+ await cache.delete('foo');
44
+ });
45
+
46
+ it('can be cleared', async () => {
47
+ await cache.clear();
48
+ });
49
+ });
@@ -0,0 +1,86 @@
1
+ import { createRetryablePromise } from '../createRetryablePromise';
2
+
3
+ describe('createRetryablePromise', () => {
4
+ it('resolves promise after some retries', async () => {
5
+ let calls = 0;
6
+ const promise = createRetryablePromise({
7
+ func: () => {
8
+ return new Promise((resolve) => {
9
+ calls += 1;
10
+ resolve(`success #${calls}`);
11
+ });
12
+ },
13
+ validate: () => calls >= 3,
14
+ });
15
+
16
+ await expect(promise).resolves.toEqual('success #3');
17
+ expect(calls).toBe(3);
18
+ });
19
+
20
+ it('gets the rejection of the given promise via reject', async () => {
21
+ let calls = 0;
22
+
23
+ const promise = createRetryablePromise({
24
+ func: () => {
25
+ return new Promise((resolve, reject) => {
26
+ calls += 1;
27
+ if (calls <= 3) {
28
+ resolve('okay');
29
+ } else {
30
+ reject(new Error('nope'));
31
+ }
32
+ });
33
+ },
34
+ validate: () => false,
35
+ });
36
+
37
+ await expect(promise).rejects.toEqual(
38
+ expect.objectContaining({ message: 'nope' })
39
+ );
40
+ });
41
+
42
+ it('gets the rejection of the given promise via throw', async () => {
43
+ let calls = 0;
44
+
45
+ const promise = createRetryablePromise({
46
+ func: () => {
47
+ return new Promise((resolve) => {
48
+ calls += 1;
49
+ if (calls <= 3) {
50
+ resolve('okay');
51
+ } else {
52
+ throw new Error('nope');
53
+ }
54
+ });
55
+ },
56
+ validate: () => false,
57
+ });
58
+
59
+ await expect(promise).rejects.toEqual(
60
+ expect.objectContaining({ message: 'nope' })
61
+ );
62
+ });
63
+
64
+ it('gets the rejection when it exceeds the max retries number', async () => {
65
+ const MAX_RETRIES = 3;
66
+ let calls = 0;
67
+
68
+ const promise = createRetryablePromise({
69
+ func: () => {
70
+ return new Promise((resolve) => {
71
+ calls += 1;
72
+ resolve('okay');
73
+ });
74
+ },
75
+ validate: () => false,
76
+ maxRetries: MAX_RETRIES,
77
+ });
78
+
79
+ await expect(promise).rejects.toEqual(
80
+ expect.objectContaining({
81
+ message: 'The maximum number of retries exceeded. (3/3)',
82
+ })
83
+ );
84
+ expect(calls).toBe(MAX_RETRIES);
85
+ });
86
+ });
@@ -0,0 +1,74 @@
1
+ import type { BrowserLocalStorageOptions, Cache, CacheEvents } from '../types';
2
+
3
+ export function createBrowserLocalStorageCache(
4
+ options: BrowserLocalStorageOptions
5
+ ): Cache {
6
+ let storage: Storage;
7
+ // We've changed the namespace to avoid conflicts with v4, as this version is a huge breaking change
8
+ const namespaceKey = `algolia-client-js-${options.key}`;
9
+
10
+ function getStorage(): Storage {
11
+ if (storage === undefined) {
12
+ storage = options.localStorage || window.localStorage;
13
+ }
14
+
15
+ return storage;
16
+ }
17
+
18
+ function getNamespace<TValue>(): Record<string, TValue> {
19
+ return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
20
+ }
21
+
22
+ return {
23
+ get<TValue>(
24
+ key: Record<string, any> | string,
25
+ defaultValue: () => Promise<TValue>,
26
+ events: CacheEvents<TValue> = {
27
+ miss: (): Promise<void> => Promise.resolve(),
28
+ }
29
+ ): Promise<TValue> {
30
+ return Promise.resolve()
31
+ .then(() => {
32
+ const keyAsString = JSON.stringify(key);
33
+ const value = getNamespace<TValue>()[keyAsString];
34
+
35
+ return Promise.all([value || defaultValue(), value !== undefined]);
36
+ })
37
+ .then(([value, exists]) => {
38
+ return Promise.all([value, exists || events.miss(value)]);
39
+ })
40
+ .then(([value]) => value);
41
+ },
42
+
43
+ set<TValue>(
44
+ key: Record<string, any> | string,
45
+ value: TValue
46
+ ): Promise<TValue> {
47
+ return Promise.resolve().then(() => {
48
+ const namespace = getNamespace();
49
+
50
+ namespace[JSON.stringify(key)] = value;
51
+
52
+ getStorage().setItem(namespaceKey, JSON.stringify(namespace));
53
+
54
+ return value;
55
+ });
56
+ },
57
+
58
+ delete(key: Record<string, any> | string): Promise<void> {
59
+ return Promise.resolve().then(() => {
60
+ const namespace = getNamespace();
61
+
62
+ delete namespace[JSON.stringify(key)];
63
+
64
+ getStorage().setItem(namespaceKey, JSON.stringify(namespace));
65
+ });
66
+ },
67
+
68
+ clear(): Promise<void> {
69
+ return Promise.resolve().then(() => {
70
+ getStorage().removeItem(namespaceKey);
71
+ });
72
+ },
73
+ };
74
+ }
@@ -0,0 +1,53 @@
1
+ import type { FallbackableCacheOptions, Cache, CacheEvents } from '../types';
2
+
3
+ import { createNullCache } from './createNullCache';
4
+
5
+ export function createFallbackableCache(
6
+ options: FallbackableCacheOptions
7
+ ): Cache {
8
+ const caches = [...options.caches];
9
+ const current = caches.shift();
10
+
11
+ if (current === undefined) {
12
+ return createNullCache();
13
+ }
14
+
15
+ return {
16
+ get<TValue>(
17
+ key: Record<string, any> | string,
18
+ defaultValue: () => Promise<TValue>,
19
+ events: CacheEvents<TValue> = {
20
+ miss: (): Promise<void> => Promise.resolve(),
21
+ }
22
+ ): Promise<TValue> {
23
+ return current.get(key, defaultValue, events).catch(() => {
24
+ return createFallbackableCache({ caches }).get(
25
+ key,
26
+ defaultValue,
27
+ events
28
+ );
29
+ });
30
+ },
31
+
32
+ set<TValue>(
33
+ key: Record<string, any> | string,
34
+ value: TValue
35
+ ): Promise<TValue> {
36
+ return current.set(key, value).catch(() => {
37
+ return createFallbackableCache({ caches }).set(key, value);
38
+ });
39
+ },
40
+
41
+ delete(key: Record<string, any> | string): Promise<void> {
42
+ return current.delete(key).catch(() => {
43
+ return createFallbackableCache({ caches }).delete(key);
44
+ });
45
+ },
46
+
47
+ clear(): Promise<void> {
48
+ return current.clear().catch(() => {
49
+ return createFallbackableCache({ caches }).clear();
50
+ });
51
+ },
52
+ };
53
+ }