Skip to content

Commit c4c11ce

Browse files
fix: use a proxy for the caches export (#105)
* feat: use a proxy for the `caches` export * refactor: rename class
1 parent 2f43b50 commit c4c11ce

File tree

4 files changed

+93
-21
lines changed

4 files changed

+93
-21
lines changed

packages/cache/src/fetchwithcache.test.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { Buffer } from 'node:buffer'
22
import { Readable } from 'node:stream'
33
import type { ReadableStream } from 'node:stream/web'
44

5-
import { describe, test, expect, beforeAll, afterAll } from 'vitest'
5+
import { describe, test, expect, beforeEach, afterAll } from 'vitest'
66

77
import { NetlifyCacheStorage } from './bootstrap/cachestorage.js'
8-
import type { FetchWithCache } from './fetchwithcache.js'
8+
import { fetchWithCache } from './fetchwithcache.js'
99
import { getMockFetch } from './test/fetch.js'
1010
import { readAsString, sleep } from './test/util.js'
1111
import { decodeHeaders } from './test/headers.js'
@@ -17,17 +17,11 @@ const token = 'mock-token'
1717

1818
let originalCaches = globalThis.caches
1919

20-
let fetchWithCache: FetchWithCache
21-
22-
beforeAll(async () => {
20+
beforeEach(async () => {
2321
globalThis.caches = new NetlifyCacheStorage({
2422
base64Encode,
2523
getContext: () => ({ host, token, url }),
2624
})
27-
28-
// Using a dynamic import so that `globalThis.caches` is populated by the
29-
// time the polyfill is loaded.
30-
fetchWithCache = (await import('./fetchwithcache.js')).fetchWithCache
3125
})
3226

3327
afterAll(() => {
@@ -183,4 +177,27 @@ describe('`fetchWithCache`', () => {
183177
expect(mockFetch.requests.length).toBe(4)
184178
})
185179
})
180+
181+
test('Uses the exported `caches` proxy', async () => {
182+
const html = '<h1>Hello world</h1>'
183+
const mockFetch = getMockFetch({
184+
responses: {
185+
'https://example.netlify/.netlify/cache/https%3A%2F%2Fnetlify.com%2F': [new Response(html)],
186+
'https://netlify.com/': [new Response(html)],
187+
},
188+
})
189+
const resourceURL = 'https://netlify.com'
190+
const responseWithCache = await fetchWithCache(resourceURL)
191+
expect(await responseWithCache.text()).toBe('<h1>Hello world</h1>')
192+
193+
// @ts-expect-error
194+
delete globalThis.caches
195+
196+
const responseWithProxy = await fetchWithCache(resourceURL)
197+
expect(await responseWithProxy.text()).toBe('<h1>Hello world</h1>')
198+
199+
mockFetch.restore()
200+
201+
expect(mockFetch.requests.length).toBe(2)
202+
})
186203
})

packages/cache/src/fetchwithcache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const isRequestInit = (input: any): input is RequestInit => {
5252
return false
5353
}
5454

55-
export type FetchWithCache = {
55+
type FetchWithCache = {
5656
(request: string | URL | Request, init?: RequestInit): Promise<Response>
5757
(request: string | URL | Request, cacheSettings?: CacheOptions): Promise<Response>
5858
(request: string | URL | Request, init: RequestInit, cacheSettings?: CacheOptions): Promise<Response>

packages/cache/src/main.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('`caches` export', () => {
2828
getContext: () => ({ host, token, url }),
2929
})
3030

31-
const cache2 = await globalThis.caches.open('cache2')
31+
const cache2 = await caches.open('cache2')
3232
const res2 = await cache2.match('https://netlify.com')
3333
expect(res2?.status).toBe(200)
3434
expect(await res2?.text()).toBe(html)

packages/cache/src/polyfill.ts

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,68 @@
1-
import { NetlifyCacheStorage } from './bootstrap/cachestorage.js'
2-
31
/**
42
* Polyfill for local development environments where `globalThis.caches` is not
5-
* available. This is a no-op cache, which will automatically work with the
6-
* real one in production.
3+
* available. It's a no-op cache. In production it will use the real one that
4+
* has been set up in the environment by the bootstrap layer.
75
*/
8-
export const caches =
9-
globalThis.caches ??
10-
new NetlifyCacheStorage({
11-
base64Encode: () => '',
12-
getContext: () => null,
13-
})
6+
class NetlifyCacheStorageProxy implements CacheStorage {
7+
async delete(name: string): Promise<boolean> {
8+
if (globalThis.caches) {
9+
return globalThis.caches.delete(name)
10+
}
11+
12+
return false
13+
}
14+
15+
async has(name: string): Promise<boolean> {
16+
if (globalThis.caches) {
17+
return globalThis.caches.has(name)
18+
}
19+
20+
return false
21+
}
22+
23+
async keys(): Promise<string[]> {
24+
if (globalThis.caches) {
25+
return globalThis.caches.keys()
26+
}
27+
28+
return []
29+
}
30+
31+
async match(request: RequestInfo, options?: MultiCacheQueryOptions): Promise<Response | undefined> {
32+
if (globalThis.caches) {
33+
return globalThis.caches.match(request, options)
34+
}
35+
}
36+
37+
async open(cacheName: string) {
38+
if (globalThis.caches) {
39+
return globalThis.caches.open(cacheName)
40+
}
41+
42+
return new NetlifyNoopCache()
43+
}
44+
}
45+
46+
class NetlifyNoopCache implements Cache {
47+
async add(_: RequestInfo): Promise<void> {}
48+
49+
async addAll(_: RequestInfo[]): Promise<void> {}
50+
51+
async delete(_: RequestInfo): Promise<boolean> {
52+
return true
53+
}
54+
55+
async keys(_?: Request): Promise<Array<Request>> {
56+
return []
57+
}
58+
59+
async match(_: RequestInfo): Promise<undefined> {}
60+
61+
async matchAll(_?: RequestInfo): Promise<readonly Response[]> {
62+
return []
63+
}
64+
65+
async put(_: RequestInfo | URL | string, __: Response) {}
66+
}
67+
68+
export const caches = new NetlifyCacheStorageProxy()

0 commit comments

Comments
 (0)