From 5545b9e73f3b4405d73950f08fd435d27704e1eb Mon Sep 17 00:00:00 2001 From: rbshop Date: Fri, 11 Oct 2024 17:00:43 +0100 Subject: [PATCH] Tweaked the types a little to make them docs friendly --- .../src/cache/create-with-cache.doc.ts | 23 +++- .../src/cache/create-with-cache.example.ts | 16 ++- .../src/cache/create-with-cache.test.ts | 120 ++++++++++-------- .../hydrogen/src/cache/create-with-cache.ts | 95 +++++++------- 4 files changed, 144 insertions(+), 110 deletions(-) diff --git a/packages/hydrogen/src/cache/create-with-cache.doc.ts b/packages/hydrogen/src/cache/create-with-cache.doc.ts index f7cb894ee..961be9c1e 100644 --- a/packages/hydrogen/src/cache/create-with-cache.doc.ts +++ b/packages/hydrogen/src/cache/create-with-cache.doc.ts @@ -6,8 +6,27 @@ const data: ReferenceEntityTemplateSchema = { subCategory: 'caching', isVisualComponent: false, related: [], - description: - 'Creates utility functions to store data in cache with stale-while-revalidate support.\n - Use `withCache.fetch` to simply fetch data from a third-party API.\n - Use the more advanced `withCache.run` to execute any asynchronous operation.', + description: `Creates utility functions to store data in cache with stale-while-revalidate support. + - Use \`withCache.fetch\` to simply fetch data from a third-party API. + Fetches data from a URL and caches the result according to the strategy provided. + When the response is not successful (e.g. status code >= 400), the caching is + skipped automatically and the returned \`data\` is \`null\`. + You can also prevent caching by using the \`shouldCacheResponse\` option and returning + \`false\` from the function you pass in. For example, you might want to fetch data from a + third-party GraphQL API but not cache the result if the GraphQL response body contains errors. + - Use the more advanced \`withCache.run\` to execute any asynchronous operation. + Utility function that executes asynchronous operations and caches the + result according to the strategy provided. Use this to do any type + of asynchronous operation where \`withCache.fetch\` is insufficient. + For example, when making multiple calls to a third-party API where the + result of all of them needs to be cached under the same cache key. + Whatever data is returned from the \`fn\` will be cached according + to the strategy provided. + > Note: + > To prevent caching the result you must throw an error. Otherwise, the result will be cached. + > For example, if you call \`fetch\` but the response is not successful (e.g. status code >= 400), + > you should throw an error. Otherwise, the response will be cached. +`, type: 'utility', defaultExample: { description: 'I am the default example', diff --git a/packages/hydrogen/src/cache/create-with-cache.example.ts b/packages/hydrogen/src/cache/create-with-cache.example.ts index f1c234514..1c53cf18f 100644 --- a/packages/hydrogen/src/cache/create-with-cache.example.ts +++ b/packages/hydrogen/src/cache/create-with-cache.example.ts @@ -35,7 +35,7 @@ export default { // Optionally, specify a cache strategy. // Default is CacheShort(). cache: CacheLong(), - // Cache if there are no GralhQL errors: + // Cache if there are no GraphQL errors: shouldCacheResponse: (body) => !body?.errors, // Optionally, add extra information to show // in the Subrequest Profiler utility. @@ -52,11 +52,13 @@ export default { // 2. Or Create a more advanced utility to query multiple APIs under the same cache key: const fetchMultipleCMS = (options: {id: string; handle: string}) => { // Prefix the cache key and make it unique based on arguments. - return withCache.run({ - cacheKey: ['my-cms-composite', options.id, options.handle], - strategy: CacheLong(), - shouldCacheResult: () => true, - actionFn: async (params) => { + return withCache.run( + { + cacheKey: ['my-cms-composite', options.id, options.handle], + strategy: CacheLong(), + shouldCacheResult: () => true, + }, + async (params) => { // Run multiple subrequests in parallel, or any other async operations. const [response1, response2] = await Promise.all([ fetch('https://my-cms-1.com/api', { @@ -97,7 +99,7 @@ export default { extra2: response2.headers.get('X-Extra'), }; }, - }); + ); }; const handleRequest = createRequestHandler({ diff --git a/packages/hydrogen/src/cache/create-with-cache.test.ts b/packages/hydrogen/src/cache/create-with-cache.test.ts index 55d38e550..33fd53fe9 100644 --- a/packages/hydrogen/src/cache/create-with-cache.test.ts +++ b/packages/hydrogen/src/cache/create-with-cache.test.ts @@ -36,12 +36,14 @@ describe('createWithCache', () => { it('skips cache for no-cache policy', async () => { await expect( - withCache.run({ - cacheKey: KEY, - strategy: CacheNone(), - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy: CacheNone(), + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); expect(waitUntil).toHaveBeenCalledTimes(0); @@ -49,12 +51,14 @@ describe('createWithCache', () => { await expect(getItemFromCache(cache, KEY)).resolves.toEqual(undefined); await expect( - withCache.run({ - cacheKey: KEY, - strategy: CacheNone(), - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy: CacheNone(), + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); // No cache, always calls the action function: @@ -71,12 +75,14 @@ describe('createWithCache', () => { }); await expect( - withCache.run({ - cacheKey: KEY, - strategy: CacheShort(), - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy: CacheShort(), + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).rejects.toThrowError('test'); expect(waitUntil).toHaveBeenCalledTimes(0); @@ -87,12 +93,14 @@ describe('createWithCache', () => { it('skips cache when shouldCacheResult returns false', async () => { const strategy = CacheShort({maxAge: 1, staleWhileRevalidate: 9}); await expect( - withCache.run({ - cacheKey: KEY, - strategy, - shouldCacheResult: (v) => v !== VALUE, + withCache.run( + { + cacheKey: KEY, + strategy, + shouldCacheResult: (v) => v !== VALUE, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); expect(waitUntil).toHaveBeenCalledTimes(0); @@ -100,12 +108,14 @@ describe('createWithCache', () => { await expect(getItemFromCache(cache, KEY)).resolves.toEqual(undefined); await expect( - withCache.run({ - cacheKey: KEY, - strategy, - shouldCacheResult: (v) => v !== VALUE, + withCache.run( + { + cacheKey: KEY, + strategy, + shouldCacheResult: (v) => v !== VALUE, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); // Doesn't cache, so always runs the actionFn: @@ -117,12 +127,14 @@ describe('createWithCache', () => { it('stores results in the cache', async () => { const strategy = CacheShort({maxAge: 1, staleWhileRevalidate: 9}); await expect( - withCache.run({ - cacheKey: KEY, - strategy, - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy, + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); expect(waitUntil).toHaveBeenCalledTimes(1); @@ -135,12 +147,14 @@ describe('createWithCache', () => { vi.advanceTimersByTime(999); await expect( - withCache.run({ - cacheKey: KEY, - strategy, - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy, + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); // Cache hit, nothing to update: @@ -154,12 +168,14 @@ describe('createWithCache', () => { it('applies stale-while-revalidate', async () => { const strategy = CacheShort({maxAge: 1, staleWhileRevalidate: 9}); await expect( - withCache.run({ - cacheKey: KEY, - strategy, - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy, + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); expect(waitUntil).toHaveBeenCalledTimes(1); @@ -172,12 +188,14 @@ describe('createWithCache', () => { vi.advanceTimersByTime(3000); await expect( - withCache.run({ - cacheKey: KEY, - strategy, - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy, + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); // Cache stale, call the action function again for SWR: @@ -196,12 +214,14 @@ describe('createWithCache', () => { // Cache is expired, call the action function again: await expect( - withCache.run({ - cacheKey: KEY, - strategy, - shouldCacheResult: () => true, + withCache.run( + { + cacheKey: KEY, + strategy, + shouldCacheResult: () => true, + }, actionFn, - }), + ), ).resolves.toEqual(VALUE); expect(waitUntil).toHaveBeenCalledTimes(3); expect(actionFn).toHaveBeenCalledTimes(3); diff --git a/packages/hydrogen/src/cache/create-with-cache.ts b/packages/hydrogen/src/cache/create-with-cache.ts index cc35cf9be..bb9d50ec2 100644 --- a/packages/hydrogen/src/cache/create-with-cache.ts +++ b/packages/hydrogen/src/cache/create-with-cache.ts @@ -5,9 +5,8 @@ import { CacheActionFunctionParam, CacheKey, runWithCache, - type DebugOptions, } from './run-with-cache'; -import {fetchWithServerCache, type FetchCacheOptions} from './server-fetch'; +import {fetchWithServerCache} from './server-fetch'; import type {WaitUntil} from '../types'; type CreateWithCacheOptions = { @@ -19,46 +18,54 @@ type CreateWithCacheOptions = { request?: CrossRuntimeRequest; }; -type RunOptions = { +type WithCacheRunOptions = { + /** The cache key for this run */ cacheKey: CacheKey; + /** + * Use the `CachingStrategy` to define a custom caching mechanism for your data. + * Or use one of the pre-defined caching strategies: [`CacheNone`](/docs/api/hydrogen/utilities/cachenone), [`CacheShort`](/docs/api/hydrogen/utilities/cacheshort), [`CacheLong`](/docs/api/hydrogen/utilities/cachelong). + */ strategy: CachingStrategy; + /** Useful to avoid accidentally caching bad results */ shouldCacheResult: (value: T) => boolean; - actionFn: ({addDebugData}: CacheActionFunctionParam) => T | Promise; }; -/** - * Creates utility functions to store data in cache with stale-while-revalidate support. - * - Use `withCache.fetch` to simply fetch data from a third-party API. - * - Use the more advanced `withCache.run` to execute any asynchronous operation. - */ -export function createWithCache( +type WithCacheFetchOptions = { + displayName?: string; + /** + * Use the `CachingStrategy` to define a custom caching mechanism for your data. + * Or use one of the pre-defined caching strategies: [`CacheNone`](/docs/api/hydrogen/utilities/cachenone), [`CacheShort`](/docs/api/hydrogen/utilities/cacheshort), [`CacheLong`](/docs/api/hydrogen/utilities/cachelong). + */ + cache?: CachingStrategy; + /** The cache key for this fetch */ + cacheKey?: CacheKey; + /** Useful to avoid e.g. caching a successful response that contains an error in the body */ + shouldCacheResponse: (body: T, response: Response) => boolean; +}; + +export type WithCache = { + run: ( + options: WithCacheRunOptions, + fn: ({addDebugData}: CacheActionFunctionParam) => T | Promise, + ) => Promise; + fetch: ( + url: string, + requestInit: RequestInit, + options: WithCacheFetchOptions, + ) => Promise<{data: T | null; response: Response}>; +}; + +export function createWithCache( cacheOptions: CreateWithCacheOptions, -) { +): WithCache { const {cache, waitUntil, request} = cacheOptions; return { - /** - * Utility function that executes asynchronous operations and caches the - * result according to the strategy provided. Use this to do any type - * of asynchronous operation where `withCache.fetch` is insufficient. - * For example, when making multiple calls to a third-party API where the - * result of all of them needs to be cached under the same cache key. - * Whatever data is returned from the `actionFn` will be cached according - * to the strategy provided. - * Use the `CachingStrategy` to define a custom caching mechanism for your data. - * Or use one of the built-in caching strategies: `CacheNone`, `CacheShort`, `CacheLong`. - * > Note: - * > To prevent caching the result you must throw an error. Otherwise, the result will be cached. - * > For example, if you call `fetch` but the response is not successful (e.g. status code >= 400), - * > you should throw an error. Otherwise, the response will be cached. - */ - run({ - cacheKey, - strategy, - shouldCacheResult, - actionFn, - }: RunOptions) { - return runWithCache(cacheKey, actionFn, { + run: ( + {cacheKey, strategy, shouldCacheResult}: WithCacheRunOptions, + fn: ({addDebugData}: CacheActionFunctionParam) => T | Promise, + ): Promise => { + return runWithCache(cacheKey, fn, { shouldCacheResult, strategy, cacheInstance: cache, @@ -70,24 +77,12 @@ export function createWithCache( }); }, - /** - * Fetches data from a URL and caches the result according to the strategy provided. - * When the response is not successful (e.g. status code >= 400), the caching is - * skipped automatically and the returned `data` is `null`. - * You can also prevent caching by using the `shouldCacheResponse` option and returning - * `false` from the function you pass in. For example, you might want to fetch data from a - * third-party GraphQL API but not cache the result if the GraphQL response body contains errors. - */ - fetch( + fetch: ( url: string, requestInit: RequestInit, - options: Pick & - Pick< - FetchCacheOptions, - 'cache' | 'cacheKey' | 'shouldCacheResponse' - >, - ): Promise<{data: Body | null; response: Response}> { - return fetchWithServerCache(url, requestInit ?? {}, { + options: WithCacheFetchOptions, + ): Promise<{data: T | null; response: Response}> => { + return fetchWithServerCache(url, requestInit ?? {}, { waitUntil, cacheKey: [url, requestInit], cacheInstance: cache, @@ -102,5 +97,3 @@ export function createWithCache( }, }; } - -export type WithCache = ReturnType;