add CachedFetch
This commit is contained in:
parent
71bdb21602
commit
97bd6da9ac
1 changed files with 145 additions and 0 deletions
145
src/platform/cache.ts
Normal file
145
src/platform/cache.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
import { addMinutes, formatRFC7231 } from "date-fns";
|
||||
import {
|
||||
createRenderEffect,
|
||||
createResource,
|
||||
untrack,
|
||||
} from "solid-js";
|
||||
|
||||
export function createCacheBucket(name: string) {
|
||||
let bucket: Cache | undefined;
|
||||
|
||||
return async () => {
|
||||
if (bucket) {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
bucket = await self.caches.open(name);
|
||||
|
||||
return bucket;
|
||||
};
|
||||
}
|
||||
|
||||
export type FetchRequest = {
|
||||
url: string;
|
||||
headers?: HeadersInit | Headers;
|
||||
};
|
||||
|
||||
async function searchCache(request: Request) {
|
||||
return await self.caches.match(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link fetch} helper with additional caching support.
|
||||
*/
|
||||
export class CachedFetch<
|
||||
Transformer extends (response: Response) => any,
|
||||
Keyer extends (...args: any[]) => FetchRequest,
|
||||
> {
|
||||
private cacheBucket: () => Promise<Cache>;
|
||||
keyFor: Keyer;
|
||||
private transform: Transformer;
|
||||
|
||||
constructor(
|
||||
cacheBucket: () => Promise<Cache>,
|
||||
keyFor: Keyer,
|
||||
tranformer: Transformer,
|
||||
) {
|
||||
this.cacheBucket = cacheBucket;
|
||||
this.keyFor = keyFor;
|
||||
this.transform = tranformer;
|
||||
}
|
||||
|
||||
private async validateCache(request: Request) {
|
||||
const buk = await this.cacheBucket();
|
||||
const response = await fetch(request);
|
||||
buk.put(request, response.clone());
|
||||
return response;
|
||||
}
|
||||
|
||||
private request(...args: Parameters<Keyer>) {
|
||||
const { url, ...init } = this.keyFor(...args);
|
||||
const request = new Request(url, init);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Race between the cache and the network result,
|
||||
* use the fastest result.
|
||||
*
|
||||
* The cache will be revalidated.
|
||||
*/
|
||||
async fastest(
|
||||
...args: Parameters<Keyer>
|
||||
): Promise<Awaited<ReturnType<Transformer>>> {
|
||||
const request = this.request(...args);
|
||||
const validating = this.validateCache(request);
|
||||
|
||||
const searching = searchCache(request);
|
||||
|
||||
const earlyResult = await Promise.race([validating, searching]);
|
||||
|
||||
if (earlyResult) {
|
||||
return await this.transform(earlyResult);
|
||||
}
|
||||
|
||||
return await this.transform(await validating);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and return the result.
|
||||
*/
|
||||
async validate(
|
||||
...args: Parameters<Keyer>
|
||||
): Promise<Awaited<ReturnType<Transformer>>> {
|
||||
return await this.transform(
|
||||
await this.validateCache(this.request(...args)),
|
||||
);
|
||||
}
|
||||
|
||||
/** Set a response as the cache.
|
||||
* Recommend to set `Expires` or `Cache-Control` to limit its live time.
|
||||
*/
|
||||
async set(key: Parameters<Keyer>, response: Response) {
|
||||
const buk = await this.cacheBucket();
|
||||
await buk.put(this.request(...key), response);
|
||||
}
|
||||
|
||||
/** Set a json object as the cache.
|
||||
* Only available for 5 minutes.
|
||||
*/
|
||||
async setJson(key: Parameters<Keyer>, object: unknown) {
|
||||
const response = new Response(JSON.stringify(object), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Expires: formatRFC7231(addMinutes(new Date(), 5)),
|
||||
"X-Cache-Src": "set",
|
||||
},
|
||||
});
|
||||
|
||||
await this.set(key, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a resource, using the cache at first, and revalidate
|
||||
* later.
|
||||
*/
|
||||
cachedAndRevalidate(args: () => Parameters<Keyer>) {
|
||||
const res = createResource(args, (p) => this.validate(...p));
|
||||
|
||||
const checkCacheIfStillLoading = async () => {
|
||||
const saved = await searchCache(this.request(...args()));
|
||||
if (!saved) {
|
||||
return;
|
||||
}
|
||||
const transformed = await this.transform(saved);
|
||||
if (res[0].loading) {
|
||||
res[1].mutate(transformed);
|
||||
}
|
||||
};
|
||||
|
||||
createRenderEffect(() => void untrack(() => checkCacheIfStillLoading()));
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue