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…
	
	Add table
		Add a link
		
	
		Reference in a new issue