-
Notifications
You must be signed in to change notification settings - Fork 325
cache-control and stale-while-revalidate fixes
#1216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 29 commits
c99eb1d
86207bd
8c51774
527cea5
cbf35b3
a00e30b
dd88b60
ac48e81
c253a05
811bfb5
38a1347
279162f
25af6e7
26f91db
e90d7d5
f0422eb
e7424c2
e2455bd
abe0a3d
b8bf35b
ed18475
d874b5a
1852399
f384018
bcdd74c
011d167
ac6c01a
d6eb12e
1aa2349
eaf923a
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 |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| '@shopify/hydrogen': minor | ||
| --- | ||
|
|
||
| Fixes: | ||
|
|
||
| - Sub queries was not revalidating properly | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -616,10 +616,11 @@ async function hydrate( | |
| ); | ||
|
|
||
| const streamer = rscWriter.renderToPipeableStream(AppRSC); | ||
| const stream = streamer.pipe(response) as Writable; | ||
|
|
||
| response.writeHead(200, 'ok', { | ||
| 'cache-control': componentResponse.cacheControlHeader, | ||
|
Contributor
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. It looks like we're not writing any value for
Author
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. It is being set inside
Author
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. actually nvm - I was wrong
Author
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. Took this fix (that didn't fix) out of this PR |
||
| }); | ||
| const stream = streamer.pipe(response) as Writable; | ||
|
|
||
| stream.on('finish', function () { | ||
| postRequestTasks('rsc', response.statusCode, request, componentResponse); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,18 +3,17 @@ import { | |
| getLoggerWithContext, | ||
| collectQueryCacheControlHeaders, | ||
| collectQueryTimings, | ||
| logCacheApiStatus, | ||
| } from '../../utilities/log'; | ||
| import { | ||
| deleteItemFromCache, | ||
| generateSubRequestCacheControlHeader, | ||
| getItemFromCache, | ||
| isStale, | ||
| setItemInCache, | ||
| } from '../../framework/cache'; | ||
| import {hashKey} from '../../utilities/hash'; | ||
| } from '../../framework/cache-sub-request'; | ||
| import {runDelayedFunction} from '../../framework/runtime'; | ||
| import {useRequestCacheData, useServerRequest} from '../ServerRequestProvider'; | ||
| import {CacheSeconds} from '../../framework/CachingStrategy'; | ||
|
|
||
| export interface HydrogenUseQueryOptions { | ||
| /** The [caching strategy](https://shopify.dev/custom-storefronts/hydrogen/framework/cache#caching-strategies) to help you | ||
|
|
@@ -90,7 +89,6 @@ function cachedQueryFnBuilder<T>( | |
| // to prevent losing the current React cycle. | ||
| const request = useServerRequest(); | ||
| const log = getLoggerWithContext(request); | ||
| const hashedKey = hashKey(key); | ||
|
|
||
| const cacheResponse = await getItemFromCache(key); | ||
|
|
||
|
|
@@ -110,16 +108,20 @@ function cachedQueryFnBuilder<T>( | |
| /** | ||
| * Important: Do this async | ||
| */ | ||
| if (isStale(response, resolvedQueryOptions?.cache)) { | ||
| logCacheApiStatus('STALE', hashedKey); | ||
| const lockKey = `lock-${key}`; | ||
| if (isStale(key, response)) { | ||
| const lockKey = ['lock', ...(typeof key === 'string' ? [key] : key)]; | ||
|
|
||
| runDelayedFunction(async () => { | ||
| logCacheApiStatus('UPDATING', hashedKey); | ||
| const lockExists = await getItemFromCache(lockKey); | ||
| if (lockExists) return; | ||
|
|
||
| await setItemInCache(lockKey, true); | ||
| await setItemInCache( | ||
| lockKey, | ||
| true, | ||
| CacheSeconds({ | ||
| maxAge: 10, | ||
| }) | ||
|
Comment on lines
+121
to
+123
Contributor
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. Why are we setting a maxAge + SWR for the cache lock?
Author
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. Because the key needs to be valid (so that it stays in cache), so that future |
||
| ); | ||
| try { | ||
| const output = await generateNewOutput(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import type {QueryKey, CachingStrategy, AllCacheOptions} from '../types'; | ||
| import {getCache} from './runtime'; | ||
| import {hashKey} from '../utilities/hash'; | ||
| import * as CacheApi from './cache'; | ||
| import {CacheSeconds} from './CachingStrategy'; | ||
|
|
||
| /** | ||
| * Wrapper Cache functions for sub queries | ||
| */ | ||
|
|
||
| /** | ||
| * Cache API is weird. We just need a full URL, so we make one up. | ||
| */ | ||
| function getKeyUrl(key: string) { | ||
| return `https://shopify.dev/?${key}`; | ||
| } | ||
|
|
||
| function getCacheOption(userCacheOptions?: CachingStrategy): AllCacheOptions { | ||
| return userCacheOptions || CacheSeconds(); | ||
| } | ||
|
|
||
| export function generateSubRequestCacheControlHeader( | ||
| userCacheOptions?: CachingStrategy | ||
| ): string { | ||
| return CacheApi.generateDefaultCacheControlHeader( | ||
| getCacheOption(userCacheOptions) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Get an item from the cache. If a match is found, returns a tuple | ||
| * containing the `JSON.parse` version of the response as well | ||
| * as the response itself so it can be checked for staleness. | ||
| */ | ||
| export async function getItemFromCache( | ||
| key: QueryKey | ||
| ): Promise<undefined | [any, Response]> { | ||
| const cache = getCache(); | ||
|
|
||
| if (!cache) { | ||
| return; | ||
| } | ||
|
|
||
| const url = getKeyUrl(hashKey(key)); | ||
| const request = new Request(url); | ||
|
|
||
| const response = await CacheApi.getItemFromCache(request); | ||
|
|
||
| if (!response) { | ||
| return; | ||
| } | ||
|
|
||
| return [await response.json(), response]; | ||
| } | ||
|
|
||
| /** | ||
| * Put an item into the cache. | ||
| */ | ||
| export async function setItemInCache( | ||
| key: QueryKey, | ||
| value: any, | ||
| userCacheOptions?: CachingStrategy | ||
| ) { | ||
| const cache = getCache(); | ||
| if (!cache) { | ||
| return; | ||
| } | ||
|
|
||
| const url = getKeyUrl(hashKey(key)); | ||
| const request = new Request(url); | ||
| const response = new Response(JSON.stringify(value)); | ||
|
|
||
| await CacheApi.setItemInCache( | ||
| request, | ||
| response, | ||
| getCacheOption(userCacheOptions) | ||
| ); | ||
| } | ||
|
|
||
| export async function deleteItemFromCache(key: QueryKey) { | ||
| const cache = getCache(); | ||
| if (!cache) return; | ||
|
|
||
| const url = getKeyUrl(hashKey(key)); | ||
| const request = new Request(url); | ||
|
|
||
| await CacheApi.deleteItemFromCache(request); | ||
| } | ||
|
|
||
| /** | ||
| * Manually check the response to see if it's stale. | ||
| */ | ||
| export function isStale(key: QueryKey, response: Response) { | ||
| return CacheApi.isStale(new Request(getKeyUrl(hashKey(key))), response); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.