-
Notifications
You must be signed in to change notification settings - Fork 271
feat: add support for conditional requests #119
Changes from 5 commits
d5d2e21
51c0f04
0a18b51
33acdac
c5f11da
4047db2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ a high-level it supports: | |
- supports `GET` and `POST` requests (no `PUT` or `DELETE`) | ||
- timeouts | ||
- query aborts through the `AbortController` API | ||
- conditional `GET` requests using `If-None-Match` and `ETag` headers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd maybe mention There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Conditional request" is the actual name of a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To expand on my comment: we're not simply caching, we're storing the resources and reusing them if and only if the hash hasn't changed. |
||
|
||
#### Example usage | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
/* eslint compat/compat: 'off' */ | ||
import 'whatwg-fetch'; | ||
import { CallApi } from '../types'; | ||
import { CACHE_KEY, NOT_MODIFIED, OK } from '../constants'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry only nit with the status constants is that there's no context if you read them in isolation e.g., There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed this. |
||
|
||
const CACHE_AVAILABLE = 'caches' in self; | ||
|
||
// This function fetches an API response and returns the corresponding json | ||
export default function callApi({ | ||
|
@@ -26,6 +30,38 @@ export default function callApi({ | |
signal, | ||
}; | ||
|
||
if (method === 'GET' && CACHE_AVAILABLE) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kristw and I were discussing whether this should be opt-in or not. currently there's no way to opt-out, though I guess if if depends on the server/backend implementing any thoughts on this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could add a new option to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have merged and added this new issue for turning conditional request on/off. |
||
return caches.open(CACHE_KEY).then(supersetCache => | ||
supersetCache | ||
.match(url) | ||
.then(cachedResponse => { | ||
if (cachedResponse) { | ||
// if we have a cached response, send its ETag in the | ||
// `If-None-Match` header in a conditional request | ||
const etag = cachedResponse.headers.get('Etag') as string; | ||
request.headers = { ...request.headers, 'If-None-Match': etag }; | ||
} | ||
|
||
return fetch(url, request); | ||
}) | ||
.then(response => { | ||
if (response.status === NOT_MODIFIED) { | ||
return supersetCache.match(url).then(cachedResponse => { | ||
if (cachedResponse) { | ||
return cachedResponse.clone(); | ||
} | ||
throw new Error('Received 304 but no content is cached!'); | ||
}); | ||
} else if (response.status === OK && response.headers.get('Etag')) { | ||
supersetCache.delete(url); | ||
supersetCache.put(url, response.clone()); | ||
} | ||
|
||
return response; | ||
}), | ||
); | ||
} | ||
|
||
if ( | ||
(method === 'POST' || method === 'PATCH' || method === 'PUT') && | ||
typeof postPayload === 'object' | ||
|
@@ -44,5 +80,5 @@ export default function callApi({ | |
request.body = formData; | ||
} | ||
|
||
return fetch(url, request); // eslint-disable-line compat/compat | ||
return fetch(url, request); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// HTTP status codes | ||
export const OK = 200; | ||
export const NOT_MODIFIED = 304; | ||
|
||
// Namespace for Cache API | ||
export const CACHE_KEY = '@SUPERSET-UI/CONNECTION'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const caches = {}; | ||
|
||
class Cache { | ||
cache: object; | ||
constructor(key: string) { | ||
caches[key] = caches[key] || {}; | ||
this.cache = caches[key]; | ||
} | ||
match(url: string): Promise<Response | null> { | ||
return new Promise((resolve, reject) => resolve(this.cache[url])); | ||
} | ||
delete(url: string) { | ||
delete this.cache[url]; | ||
} | ||
put(url: string, response: Response) { | ||
this.cache[url] = response; | ||
} | ||
}; | ||
|
||
class Caches { | ||
open(key: string): Promise<Cache> { | ||
return new Promise((resolve, reject) => { | ||
resolve(new Cache(key)); | ||
}); | ||
} | ||
}; | ||
|
||
global.caches = new Caches(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ideally we'd fix
build-config
to resolve.ts
setup files but this is okay for now.